diff --git a/client/public/stylesheets/markers/units.css b/client/public/stylesheets/markers/units.css
index 65ec1d4b..62ca82b6 100644
--- a/client/public/stylesheets/markers/units.css
+++ b/client/public/stylesheets/markers/units.css
@@ -311,4 +311,8 @@
[data-object|="unit-aircraft"][data-is-dead] .unit-summary .unit-callsign {
display: block;
+}
+
+.ol-temporary-marker {
+ opacity: 0.5;
}
\ No newline at end of file
diff --git a/client/public/stylesheets/other/contextmenus.css b/client/public/stylesheets/other/contextmenus.css
index 9e5754a4..99e8b35c 100644
--- a/client/public/stylesheets/other/contextmenus.css
+++ b/client/public/stylesheets/other/contextmenus.css
@@ -350,7 +350,7 @@
height: fit-content;
position: absolute;
row-gap: 5px;
- width: 225px;
+ width: 250px;
z-index: 9999;
}
@@ -370,6 +370,11 @@
background-size: 48px;
}
+#coalitionarea-back-button {
+ background-image: url("/resources/theme/images/buttons/other/back.svg");
+ background-size: 48px;
+}
+
#coalitionarea-delete-button {
background-image: url("/resources/theme/images/buttons/other/delete.svg");
background-size: 48px;
diff --git a/client/public/themes/olympus/images/buttons/other/back.svg b/client/public/themes/olympus/images/buttons/other/back.svg
new file mode 100644
index 00000000..52c98f94
--- /dev/null
+++ b/client/public/themes/olympus/images/buttons/other/back.svg
@@ -0,0 +1,41 @@
+
+
diff --git a/client/public/themes/olympus/images/buttons/tools/pen-solid.svg b/client/public/themes/olympus/images/buttons/tools/pen-solid.svg
new file mode 100644
index 00000000..a690992f
--- /dev/null
+++ b/client/public/themes/olympus/images/buttons/tools/pen-solid.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/client/src/constants/constants.ts b/client/src/constants/constants.ts
index 0f91ed25..a230e17d 100644
--- a/client/src/constants/constants.ts
+++ b/client/src/constants/constants.ts
@@ -99,4 +99,15 @@ export const layers = {
maxZoom: 20,
attribution: 'CyclOSM | Map data: © OpenStreetMap contributors'
}
-}
\ No newline at end of file
+}
+
+/* Map constants */
+export const IDLE = "Idle";
+export const MOVE_UNIT = "Move unit";
+export const BOMBING = "Bombing";
+export const CARPET_BOMBING = "Carpet bombing";
+export const FIRE_AT_AREA = "Fire at area";
+export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area";
+export const COALITIONAREA_INTERACT = "Interact with Coalition Areas"
+export const visibilityControls: string[] = ["human", "dcs", "aircraft", "groundunit-sam", "groundunit-other", "navyunit", "airbase"];
+export const visibilityControlsTootlips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"];
diff --git a/client/src/controls/mapcontextmenu.ts b/client/src/controls/mapcontextmenu.ts
index d90e96f2..c0cdcdf9 100644
--- a/client/src/controls/mapcontextmenu.ts
+++ b/client/src/controls/mapcontextmenu.ts
@@ -11,7 +11,7 @@ import { ftToM } from "../other/utils";
export interface SpawnOptions {
role: string;
- type: string;
+ name: string;
latlng: LatLng;
coalition: string;
loadout?: string | null;
@@ -28,7 +28,7 @@ export class MapContextMenu extends ContextMenu {
#aircrafSpawnAltitudeSlider: Slider;
#groundUnitRoleDropdown: Dropdown;
#groundUnitTypeDropdown: Dropdown;
- #spawnOptions: SpawnOptions = { role: "", type: "", latlng: new LatLng(0, 0), loadout: null, coalition: "blue", airbaseName: null, altitude: ftToM(20000) };
+ #spawnOptions: SpawnOptions = { role: "", name: "", latlng: new LatLng(0, 0), loadout: null, coalition: "blue", airbaseName: null, altitude: ftToM(20000) };
constructor(id: string) {
super(id);
@@ -57,7 +57,7 @@ export class MapContextMenu extends ContextMenu {
this.hide();
this.#spawnOptions.coalition = getActiveCoalition();
if (this.#spawnOptions) {
- getMap().addTemporaryMarker(this.#spawnOptions.latlng);
+ getMap().addTemporaryMarker(this.#spawnOptions);
spawnAircraft(this.#spawnOptions);
}
});
@@ -66,7 +66,7 @@ export class MapContextMenu extends ContextMenu {
this.hide();
this.#spawnOptions.coalition = getActiveCoalition();
if (this.#spawnOptions) {
- getMap().addTemporaryMarker(this.#spawnOptions.latlng);
+ getMap().addTemporaryMarker(this.#spawnOptions);
spawnGroundUnit(this.#spawnOptions);
}
});
@@ -179,7 +179,7 @@ export class MapContextMenu extends ContextMenu {
this.#resetAircraftType();
var type = aircraftDatabase.getByLabel(label)?.name || null;
if (type != null) {
- this.#spawnOptions.type = type;
+ this.#spawnOptions.name = type;
this.#aircraftLoadoutDropdown.setOptions(aircraftDatabase.getLoadoutNamesByRole(type, this.#spawnOptions.role));
this.#aircraftLoadoutDropdown.selectValue(0);
var image = (this.getContainer()?.querySelector("#unit-image"));
@@ -198,7 +198,7 @@ export class MapContextMenu extends ContextMenu {
}
#setAircraftLoadout(loadoutName: string) {
- var loadout = aircraftDatabase.getLoadoutByName(this.#spawnOptions.type, loadoutName);
+ var loadout = aircraftDatabase.getLoadoutByName(this.#spawnOptions.name, loadoutName);
if (loadout) {
this.#spawnOptions.loadout = loadout.code;
(this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false;
@@ -241,7 +241,7 @@ export class MapContextMenu extends ContextMenu {
this.#resetGroundUnitType();
var type = groundUnitsDatabase.getByLabel(label)?.name || null;
if (type != null) {
- this.#spawnOptions.type = type;
+ this.#spawnOptions.name = type;
(this.getContainer()?.querySelector("#ground-unit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false;
}
this.clip();
diff --git a/client/src/map/boxselect.ts b/client/src/map/boxselect.ts
index 2ea92fd8..a37f4ace 100644
--- a/client/src/map/boxselect.ts
+++ b/client/src/map/boxselect.ts
@@ -46,7 +46,7 @@ export var BoxSelect = Handler.extend({
_onMouseDown: function (e: any) {
if ((e.which == 1 && e.button == 0 && e.shiftKey)) {
-
+ this._map.fire('selectionstart');
// Clear the deferred resetState if it hasn't executed yet, otherwise it
// will interrupt the interaction and orphan a box element in the container.
this._clearDeferredResetState();
diff --git a/client/src/map/coalitionarea.ts b/client/src/map/coalitionarea.ts
index b5e61be0..2f44d9f5 100644
--- a/client/src/map/coalitionarea.ts
+++ b/client/src/map/coalitionarea.ts
@@ -1,4 +1,4 @@
-import { LatLng, LatLngExpression, Map, Point, Polygon, PolylineOptions } from "leaflet";
+import { DomUtil, LatLng, LatLngExpression, Map, Point, Polygon, PolylineOptions } from "leaflet";
import { getMap } from "..";
import { CoalitionAreaHandle } from "./coalitionareahandle";
import { CoalitionAreaMiddleHandle } from "./coalitionareamiddlehandle";
@@ -19,6 +19,7 @@ export class CoalitionArea extends Polygon {
super(latlngs, options);
this.#setColors();
this.#registerCallbacks();
+
}
setCoalition(coalition: string) {
@@ -61,6 +62,17 @@ export class CoalitionArea extends Polygon {
return this.#editing;
}
+ setInteractive(interactive: boolean) {
+ this.setOpacity(interactive? 1: 0.5);
+ this.options.interactive = interactive;
+
+ if (interactive) {
+ DomUtil.addClass(this.getElement() as HTMLElement, 'leaflet-interactive');
+ } else {
+ DomUtil.removeClass(this.getElement() as HTMLElement, 'leaflet-interactive');
+ }
+ }
+
addTemporaryLatLng(latlng: LatLng) {
this.#activeIndex++;
var latlngs = this.getLatLngs()[0] as LatLng[];
@@ -69,13 +81,17 @@ export class CoalitionArea extends Polygon {
this.#setHandles();
}
- moveTemporaryLatLng(latlng: LatLng) {
+ moveActiveVertex(latlng: LatLng) {
var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs[this.#activeIndex] = latlng;
this.setLatLngs(latlngs);
this.#setHandles();
}
+ setOpacity(opacity: number) {
+ this.setStyle({opacity: opacity, fillOpacity: opacity * 0.25});
+ }
+
#setColors() {
const coalitionColor = this.getCoalition() === "blue" ? "#247be2" : "#ff5858";
this.setStyle({ color: this.getSelected() ? "white" : coalitionColor, fillColor: coalitionColor });
@@ -138,8 +154,11 @@ export class CoalitionArea extends Polygon {
});
this.on("contextmenu", (e: any) => {
- if (this.getSelected() && !this.getEditing())
+ if (!this.getEditing()) {
+ getMap().deselectAllCoalitionAreas();
+ this.setSelected(true);
getMap().showCoalitionAreaContextMenu(e, this);
+ }
else
this.setEditing(false);
});
diff --git a/client/src/map/map.ts b/client/src/map/map.ts
index e165f2db..864f4783 100644
--- a/client/src/map/map.ts
+++ b/client/src/map/map.ts
@@ -12,7 +12,7 @@ import { DestinationPreviewMarker } from "./destinationpreviewmarker";
import { TemporaryUnitMarker } from "./temporaryunitmarker";
import { ClickableMiniMap } from "./clickableminimap";
import { SVGInjector } from '@tanem/svg-injector'
-import { layers as mapLayers, mapBounds, minimapBoundaries } from "../constants/constants";
+import { layers as mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, visibilityControls, visibilityControlsTootlips, FIRE_AT_AREA, MOVE_UNIT, CARPET_BOMBING, BOMBING, COALITIONAREA_INTERACT } from "../constants/constants";
import { TargetMarker } from "./targetmarker";
import { CoalitionArea } from "./coalitionarea";
import { CoalitionAreaContextMenu } from "../controls/coalitionareacontextmenu";
@@ -24,16 +24,6 @@ L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect);
require("../../public/javascripts/leaflet.nauticscale.js")
require("../../public/javascripts/L.Path.Drag.js")
-/* Map constants */
-export const IDLE = "Idle";
-export const MOVE_UNIT = "Move unit";
-export const BOMBING = "Bombing";
-export const CARPET_BOMBING = "Carpet bombing";
-export const FIRE_AT_AREA = "Fire at area";
-export const DRAW_COALITIONAREA_POLYGON = "Draw Coalition Area";
-export const visibilityControls: string[] = ["human", "dcs", "aircraft", "groundunit-sam", "groundunit-other", "navyunit", "airbase"];
-export const visibilityControlsTootlips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"];
-
export class Map extends L.Map {
#ID: string;
#state: string;
@@ -51,16 +41,17 @@ export class Map extends L.Map {
#miniMap: ClickableMiniMap | null = null;
#miniMapLayerGroup: L.LayerGroup;
#temporaryMarkers: TemporaryUnitMarker[] = [];
-
+ #selecting: boolean = false;
+
#destinationGroupRotation: number = 0;
#computeDestinationRotation: boolean = false;
#destinationRotationCenter: L.LatLng | null = null;
#coalitionAreas: CoalitionArea[] = [];
- #targetCursor: TargetMarker = new TargetMarker(new L.LatLng(0, 0), {interactive: false});
+ #targetCursor: TargetMarker = new TargetMarker(new L.LatLng(0, 0), { interactive: false });
#destinationPreviewCursors: DestinationPreviewMarker[] = [];
#drawingCursor: DrawingCursor = new DrawingCursor();
-
+
#mapContextMenu: MapContextMenu = new MapContextMenu("map-contextmenu");
#unitContextMenu: UnitContextMenu = new UnitContextMenu("unit-contextmenu");
#airbaseContextMenu: AirbaseContextMenu = new AirbaseContextMenu("airbase-contextmenu");
@@ -71,8 +62,7 @@ export class Map extends L.Map {
constructor(ID: string) {
/* Init the leaflet map */
-
- //@ts-ignore
+ //@ts-ignore Needed because the boxSelect option is non-standard
super(ID, { doubleClickZoom: false, zoomControl: false, boxZoom: false, boxSelect: true, zoomAnimation: true, maxBoundsViscosity: 1.0, minZoom: 7, keyboard: true, keyboardPanDelta: 0 });
this.setView([37.23, -115.8], 10);
@@ -102,6 +92,7 @@ export class Map extends L.Map {
this.on("zoomstart", (e: any) => this.#onZoom(e));
this.on("drag", (e: any) => this.centerOnUnit(null));
this.on("contextmenu", (e: any) => this.#onContextMenu(e));
+ this.on('selectionstart', (e: any) => this.#onSelectionStart(e));
this.on('selectionend', (e: any) => this.#onSelectionEnd(e));
this.on('mousedown', (e: any) => this.#onMouseDown(e));
this.on('mouseup', (e: any) => this.#onMouseUp(e));
@@ -113,7 +104,7 @@ export class Map extends L.Map {
document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => {
const el = ev.detail._element;
el?.classList.toggle("off");
- getUnitsManager().setHiddenType(ev.detail.coalition, (el?.currentTarget as HTMLElement)?.classList.contains("off"));
+ getUnitsManager().setHiddenType(ev.detail.coalition, !el?.classList.contains("off"));
Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
});
@@ -124,16 +115,28 @@ export class Map extends L.Map {
Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
});
+ document.addEventListener("toggleCoalitionAreaInteraction", (ev: CustomEventInit) => {
+ const el = ev.detail._element;
+ /* Add listener to set the button to off if the state changes */
+ document.addEventListener("mapStateChanged", () => el?.classList.toggle("off", !(this.getState() === COALITIONAREA_INTERACT)));
+ if (this.getState() !== COALITIONAREA_INTERACT)
+ this.setState(COALITIONAREA_INTERACT);
+ else
+ this.setState(IDLE);
+ });
+
document.addEventListener("toggleCoalitionAreaDraw", (ev: CustomEventInit) => {
const el = ev.detail._element;
- document.addEventListener("mapStateChanged", () => el?.classList.toggle("off", !(this.getState() === DRAW_COALITIONAREA_POLYGON)));
+ /* Add listener to set the button to off if the state changes */
+ document.addEventListener("mapStateChanged", () => el?.classList.toggle("off", !(this.getState() === COALITIONAREA_DRAW_POLYGON)));
+ this.deselectAllCoalitionAreas();
if (ev.detail?.type == "polygon") {
- if (this.getState() !== DRAW_COALITIONAREA_POLYGON)
- this.setState(DRAW_COALITIONAREA_POLYGON);
- else
+ if (this.getState() !== COALITIONAREA_DRAW_POLYGON)
+ this.setState(COALITIONAREA_DRAW_POLYGON);
+ else
this.setState(IDLE);
}
- })
+ });
document.addEventListener("unitUpdated", (ev: CustomEvent) => {
if (this.#centerUnit != null && ev.detail == this.#centerUnit)
@@ -143,7 +146,7 @@ export class Map extends L.Map {
/* Pan interval */
this.#panInterval = window.setInterval(() => {
if (this.#panLeft || this.#panDown || this.#panRight || this.#panLeft)
- this.panBy(new L.Point(((this.#panLeft? -1 : 0) + (this.#panRight ? 1 : 0)) * this.#deafultPanDelta,
+ this.panBy(new L.Point(((this.#panLeft ? -1 : 0) + (this.#panRight ? 1 : 0)) * this.#deafultPanDelta,
((this.#panUp ? -1 : 0) + (this.#panDown ? 1 : 0)) * this.#deafultPanDelta));
}, 20);
@@ -155,10 +158,10 @@ export class Map extends L.Map {
}
setLayer(layerName: string) {
- if (this.#layer != null)
+ if (this.#layer != null)
this.removeLayer(this.#layer)
-
- if (layerName in mapLayers){
+
+ if (layerName in mapLayers) {
const layerData = mapLayers[layerName as keyof typeof mapLayers];
var options: L.TileLayerOptions = {
attribution: layerData.attribution,
@@ -178,17 +181,25 @@ export class Map extends L.Map {
/* State machine */
setState(state: string) {
this.#state = state;
- this.#showCursor();
- if (this.#state === IDLE) {
+ this.#updateCursor();
+
+ /* Operations to perform if you are NOT in a state */
+ if (this.#state !== COALITIONAREA_INTERACT) {
+ this.#coalitionAreas.forEach((coalitionArea: CoalitionArea) => {
+ coalitionArea.setInteractive(false);
+ });
+ }
+ if (this.#state !== COALITIONAREA_DRAW_POLYGON) {
this.#deselectCoalitionAreas();
}
- else if (this.#state === MOVE_UNIT) {
- this.#deselectCoalitionAreas();
+
+ /* Operations to perform if you ARE in a state */
+ if (this.#state === COALITIONAREA_INTERACT) {
+ this.#coalitionAreas.forEach((coalitionArea: CoalitionArea) => {
+ coalitionArea.setInteractive(true);
+ });
}
- else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) {
- this.#deselectCoalitionAreas();
- }
- else if (this.#state === DRAW_COALITIONAREA_POLYGON) {
+ else if (this.#state === COALITIONAREA_DRAW_POLYGON) {
this.#coalitionAreas.push(new CoalitionArea([]));
this.#coalitionAreas[this.#coalitionAreas.length - 1].addTo(this);
}
@@ -377,13 +388,14 @@ export class Map extends L.Map {
}
}
- addTemporaryMarker(latlng: L.LatLng) {
- var marker = new TemporaryUnitMarker(latlng);
+ addTemporaryMarker(spawnOptions: SpawnOptions) {
+ var marker = new TemporaryUnitMarker(spawnOptions);
marker.addTo(this);
this.#temporaryMarkers.push(marker);
}
removeTemporaryMarker(latlng: L.LatLng) {
+ // TODO something more refined than this
var d: number | null = null;
var closest: L.Marker | null = null;
var i: number = 0;
@@ -402,7 +414,7 @@ export class Map extends L.Map {
}
getSelectedCoalitionArea() {
- return this.#coalitionAreas.find((area: CoalitionArea) => {return area.getSelected()});
+ return this.#coalitionAreas.find((area: CoalitionArea) => { return area.getSelected() });
}
/* Event handlers */
@@ -412,7 +424,7 @@ export class Map extends L.Map {
if (this.#state === IDLE) {
this.deselectAllCoalitionAreas();
}
- else if (this.#state === DRAW_COALITIONAREA_POLYGON) {
+ else if (this.#state === COALITIONAREA_DRAW_POLYGON) {
if (this.getSelectedCoalitionArea()?.getEditing()) {
this.getSelectedCoalitionArea()?.addTemporaryLatLng(e.latlng);
}
@@ -443,41 +455,48 @@ export class Map extends L.Map {
getUnitsManager().selectedUnitsClearDestinations();
}
getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, e.originalEvent.shiftKey, this.#destinationGroupRotation)
- this.#destinationGroupRotation = 0;
+ this.#destinationGroupRotation = 0;
this.#destinationRotationCenter = null;
this.#computeDestinationRotation = false;
}
else if (this.#state === BOMBING) {
- getUnitsManager().getSelectedUnits().length > 0? this.setState(MOVE_UNIT): this.setState(IDLE);
- getUnitsManager().selectedUnitsBombPoint(this.getMouseCoordinates());
+ getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
+ getUnitsManager().selectedUnitsBombPoint(this.getMouseCoordinates());
}
else if (this.#state === CARPET_BOMBING) {
- getUnitsManager().getSelectedUnits().length > 0? this.setState(MOVE_UNIT): this.setState(IDLE);
- getUnitsManager().selectedUnitsCarpetBomb(this.getMouseCoordinates());
+ getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
+ getUnitsManager().selectedUnitsCarpetBomb(this.getMouseCoordinates());
}
else if (this.#state === FIRE_AT_AREA) {
- getUnitsManager().getSelectedUnits().length > 0? this.setState(MOVE_UNIT): this.setState(IDLE);
- getUnitsManager().selectedUnitsFireAtArea(this.getMouseCoordinates());
+ getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
+ getUnitsManager().selectedUnitsFireAtArea(this.getMouseCoordinates());
}
else {
this.setState(IDLE);
}
}
+ #onSelectionStart(e: any) {
+ this.#selecting = true;
+ this.#updateCursor();
+ }
+
#onSelectionEnd(e: any) {
+ this.#selecting = false;
clearTimeout(this.#leftClickTimer);
this.#preventLeftClick = true;
this.#leftClickTimer = window.setTimeout(() => {
this.#preventLeftClick = false;
}, 200);
getUnitsManager().selectFromBounds(e.selectionBounds);
+ this.#updateCursor();
}
#onMouseDown(e: any) {
this.hideAllContextMenus();
if (this.#state == MOVE_UNIT) {
- this.#destinationGroupRotation = 0;
+ this.#destinationGroupRotation = 0;
this.#destinationRotationCenter = null;
this.#computeDestinationRotation = false;
if (e.originalEvent.button == 2) {
@@ -494,32 +513,32 @@ export class Map extends L.Map {
this.#lastMousePosition.x = e.originalEvent.x;
this.#lastMousePosition.y = e.originalEvent.y;
- this.#showCursor(e);
+ this.#updateCursor(e);
- if (this.#state === MOVE_UNIT){
+ if (this.#state === MOVE_UNIT) {
+ /* Update the position of the destination cursors depeding on mouse rotation */
if (this.#computeDestinationRotation && this.#destinationRotationCenter != null)
this.#destinationGroupRotation = -bearing(this.#destinationRotationCenter.lat, this.#destinationRotationCenter.lng, this.getMouseCoordinates().lat, this.getMouseCoordinates().lng);
- this.#updateDestinationPreview(e);
+ this.#updateDestinationCursors(e);
}
else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) {
this.#targetCursor.setLatLng(this.getMouseCoordinates());
}
- else if (this.#state === DRAW_COALITIONAREA_POLYGON) {
- if (this.getSelectedCoalitionArea()?.getEditing() && !e.originalEvent.ctrlKey){
- this.#drawingCursor.setLatLng(e.latlng);
- this.getSelectedCoalitionArea()?.moveTemporaryLatLng(e.latlng);
- }
+ else if (this.#state === COALITIONAREA_DRAW_POLYGON) {
+ this.#drawingCursor.setLatLng(e.latlng);
+ /* Update the polygon being drawn with the current position of the mouse cursor */
+ this.getSelectedCoalitionArea()?.moveActiveVertex(e.latlng);
}
}
#onKeyDown(e: any) {
- this.#updateDestinationPreview(e);
- this.#showCursor(e);
+ this.#updateDestinationCursors(e);
+ this.#updateCursor(e);
}
#onKeyUp(e: any) {
- this.#updateDestinationPreview(e);
- this.#showCursor(e);
+ this.#updateDestinationCursors(e);
+ this.#updateCursor(e);
}
#onZoom(e: any) {
@@ -537,13 +556,6 @@ export class Map extends L.Map {
return minimapBoundaries;
}
- #updateDestinationPreview(e: any) {
- Object.values(getUnitsManager().selectedUnitsComputeGroupDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : this.getMouseCoordinates(), this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => {
- if (idx < this.#destinationPreviewCursors.length)
- this.#destinationPreviewCursors[idx].setLatLng(e.originalEvent.shiftKey ? latlng : this.getMouseCoordinates());
- })
- }
-
#createOptionButton(value: string, url: string, title: string, callback: string, argument: string) {
var button = document.createElement("button");
const img = document.createElement("img");
@@ -557,32 +569,58 @@ export class Map extends L.Map {
return button;
}
- #showDestinationCursors() {
- this.#hideDestinationCursors();
+ #deselectCoalitionAreas() {
+ this.getSelectedCoalitionArea()?.setSelected(false);
+ }
- if (getUnitsManager().getSelectedUnits({ excludeHumans: true }).length > 0) {
- /* Create the unit destination preview markers */
- this.#destinationPreviewCursors = getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).map((unit: Unit) => {
- var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), {interactive: false});
- marker.addTo(this);
- return marker;
- })
+ /* Cursors */
+ #showDefaultCursor() {
+ document.getElementById(this.#ID)?.classList.remove("hidden-cursor");
+ }
+
+ #hideDefaultCursor() {
+ document.getElementById(this.#ID)?.classList.add("hidden-cursor");
+ }
+
+ #showDestinationCursors() {
+ /* Don't create the cursors if there already are the correct number of them available */
+ if (getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).length != this.#destinationPreviewCursors.length) {
+ /* Reset the cursors to start from a clean condition */
+ this.#hideDestinationCursors();
+
+ if (getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).length > 0) {
+ /* Create the cursors. If a group is selected only one cursor is shown for it, because you can't control single units in a group */
+ this.#destinationPreviewCursors = getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).map((unit: Unit) => {
+ var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), { interactive: false });
+ marker.addTo(this);
+ return marker;
+ })
+ }
}
}
+ #updateDestinationCursors(e: any) {
+ const groupLatLng = this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : this.getMouseCoordinates();
+ Object.values(getUnitsManager().selectedUnitsComputeGroupDestination(groupLatLng, this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => {
+ if (idx < this.#destinationPreviewCursors.length)
+ this.#destinationPreviewCursors[idx].setLatLng(e.originalEvent.shiftKey ? latlng : this.getMouseCoordinates());
+ })
+ }
+
#hideDestinationCursors() {
- /* Remove all the destination preview markers */
+ /* Remove all the destination cursors */
this.#destinationPreviewCursors.forEach((marker: L.Marker) => {
this.removeLayer(marker);
})
this.#destinationPreviewCursors = [];
+ /* Reset the variables used to compute the rotation of the group cursors */
this.#destinationGroupRotation = 0;
this.#computeDestinationRotation = false;
this.#destinationRotationCenter = null;
}
- #showTargetCursor(){
+ #showTargetCursor() {
this.#hideTargetCursor();
this.#targetCursor.addTo(this);
}
@@ -592,18 +630,6 @@ export class Map extends L.Map {
this.removeLayer(this.#targetCursor);
}
- #deselectCoalitionAreas() {
- this.getSelectedCoalitionArea()?.setSelected(false);
- }
-
- #showDefaultCursor() {
- document.getElementById(this.#ID)?.classList.remove("hidden-cursor");
- }
-
- #hideDefaultCursor() {
- document.getElementById(this.#ID)?.classList.add("hidden-cursor");
- }
-
#showDrawingCursor() {
this.#hideDefaultCursor();
if (!this.hasLayer(this.#drawingCursor))
@@ -611,38 +637,33 @@ export class Map extends L.Map {
}
#hideDrawingCursor() {
+ this.#drawingCursor.setLatLng(new L.LatLng(0, 0));
if (this.hasLayer(this.#drawingCursor))
this.#drawingCursor.removeFrom(this);
}
- #showCursor(e?: any) {
- if (e?.originalEvent.ctrlKey) {
- this.#hideDefaultCursor();
+ #updateCursor(e?: any) {
+ /* If the ctrl key is being pressed or we are performing an area selection, show the default cursor */
+ if (e?.originalEvent.ctrlKey || this.#selecting) {
+ /* Hide all non default cursors */
this.#hideDestinationCursors();
this.#hideTargetCursor();
this.#hideDrawingCursor();
+
this.#showDefaultCursor();
} else {
- if (this.#state !== IDLE) this.#hideDefaultCursor();
+ /* Hide all the unnecessary cursors depending on the active state */
+ if (this.#state !== IDLE && this.#state !== COALITIONAREA_INTERACT) this.#hideDefaultCursor();
if (this.#state !== MOVE_UNIT) this.#hideDestinationCursors();
if (![BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) this.#hideTargetCursor();
- if (this.#state !== DRAW_COALITIONAREA_POLYGON) this.#hideDrawingCursor();
+ if (this.#state !== COALITIONAREA_DRAW_POLYGON) this.#hideDrawingCursor();
- if (this.#state === IDLE) this.#showDefaultCursor();
+ /* Show the active cursor depending on the active state */
+ if (this.#state === IDLE || this.#state === COALITIONAREA_INTERACT) this.#showDefaultCursor();
else if (this.#state === MOVE_UNIT) this.#showDestinationCursors();
else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) this.#showTargetCursor();
- else if (this.#state === DRAW_COALITIONAREA_POLYGON) {
- if (this.getSelectedCoalitionArea()?.getEditing())
- {
- this.#hideDefaultCursor();
- this.#showDrawingCursor();
- }
- else {
- this.#hideDrawingCursor();
- this.#showDefaultCursor();
- }
- }
+ else if (this.#state === COALITIONAREA_DRAW_POLYGON) this.#showDrawingCursor();
}
}
-}
+}
diff --git a/client/src/map/temporaryunitmarker.ts b/client/src/map/temporaryunitmarker.ts
index 904ee83d..7c266e0e 100644
--- a/client/src/map/temporaryunitmarker.ts
+++ b/client/src/map/temporaryunitmarker.ts
@@ -1,13 +1,52 @@
-import { Icon } from "leaflet";
import { CustomMarker } from "./custommarker";
+import { SpawnOptions } from "../controls/mapcontextmenu";
+import { DivIcon } from "leaflet";
+import { SVGInjector } from "@tanem/svg-injector";
+import { getMarkerCategoryByName, getUnitDatabaseByCategory } from "../other/utils";
export class TemporaryUnitMarker extends CustomMarker {
+ #spawnOptions: SpawnOptions;
+
+ constructor(spawnOptions: SpawnOptions) {
+ super(spawnOptions.latlng, {interactive: false});
+ this.#spawnOptions = spawnOptions;
+ }
+
createIcon() {
- var icon = new Icon({
- iconUrl: '/resources/theme/images/markers/temporary-icon.png',
- iconSize: [52, 52],
- iconAnchor: [26, 26]
+ const category = getMarkerCategoryByName(this.#spawnOptions.name);
+
+ /* Set the icon */
+ var icon = new DivIcon({
+ className: 'leaflet-unit-icon',
+ iconAnchor: [25, 25],
+ iconSize: [50, 50],
});
this.setIcon(icon);
+
+ var el = document.createElement("div");
+ el.classList.add("unit");
+ el.setAttribute("data-object", `unit-${category}`);
+ el.setAttribute("data-coalition", this.#spawnOptions.coalition);
+
+ // Main icon
+ var unitIcon = document.createElement("div");
+ unitIcon.classList.add("unit-icon");
+ var img = document.createElement("img");
+ img.src = `/resources/theme/images/units/${category}.svg`;
+ img.onload = () => SVGInjector(img);
+ unitIcon.appendChild(img);
+ unitIcon.toggleAttribute("data-rotate-to-heading", false);
+ el.append(unitIcon);
+
+ // Short label
+ if (category == "aircraft" || category == "helicopter") {
+ var shortLabel = document.createElement("div");
+ shortLabel.classList.add("unit-short-label");
+ shortLabel.innerText = getUnitDatabaseByCategory(category)?.getByName(this.#spawnOptions.name)?.shortLabel || "";
+ el.append(shortLabel);
+ }
+
+ this.getElement()?.appendChild(el);
+ this.getElement()?.classList.add("ol-temporary-marker");
}
}
\ No newline at end of file
diff --git a/client/src/other/utils.ts b/client/src/other/utils.ts
index 5b9419fe..a38e0f26 100644
--- a/client/src/other/utils.ts
+++ b/client/src/other/utils.ts
@@ -1,6 +1,9 @@
import { LatLng, Point, Polygon } from "leaflet";
import * as turf from "@turf/turf";
import { UnitDatabase } from "../units/unitdatabase";
+import { aircraftDatabase } from "../units/aircraftdatabase";
+import { helicopterDatabase } from "../units/helicopterdatabase";
+import { groundUnitsDatabase } from "../units/groundunitsdatabase";
export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) {
const φ1 = deg2rad(lat1); // φ, λ in radians
@@ -219,4 +222,29 @@ export function randomUnitBlueprintByRole(unitDatabse: UnitDatabase, role: strin
const unitBlueprints = unitDatabse.getByRole(role);
var index = Math.floor(Math.random() * unitBlueprints.length);
return unitBlueprints[index];
+}
+
+export function getMarkerCategoryByName(name: string) {
+ if (aircraftDatabase.getByName(name) != null)
+ return "aircraft";
+ else if (helicopterDatabase.getByName(name) != null)
+ return "helicopter";
+ else if (groundUnitsDatabase.getByName(name) != null){
+ // TODO this is very messy
+ var role = groundUnitsDatabase.getByName(name)?.loadouts[0].roles[0];
+ return (role?.includes("SAM")) ? "groundunit-sam" : "groundunit-other";
+ }
+ else
+ return ""; // TODO add other unit types
+}
+
+export function getUnitDatabaseByCategory(category: string) {
+ if (category == "aircraft")
+ return aircraftDatabase;
+ else if (category == "helicopter")
+ return helicopterDatabase;
+ else if (category.includes("groundunit"))
+ return groundUnitsDatabase;
+ else
+ return null;
}
\ No newline at end of file
diff --git a/client/src/server/server.ts b/client/src/server/server.ts
index 646b78f1..fd71dba2 100644
--- a/client/src/server/server.ts
+++ b/client/src/server/server.ts
@@ -134,13 +134,13 @@ export function spawnExplosion(intensity: number, latlng: LatLng) {
}
export function spawnGroundUnit(spawnOptions: SpawnOptions) {
- var command = { "type": spawnOptions.type, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition, "immediate": spawnOptions.immediate? true: false };
+ var command = { "type": spawnOptions.name, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition, "immediate": spawnOptions.immediate? true: false };
var data = { "spawnGround": command }
POST(data, () => { });
}
export function spawnAircraft(spawnOptions: SpawnOptions) {
- var command = { "type": spawnOptions.type, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition, "altitude": spawnOptions.altitude, "payloadName": spawnOptions.loadout != null ? spawnOptions.loadout : "", "airbaseName": spawnOptions.airbaseName != null ? spawnOptions.airbaseName : "", "immediate": spawnOptions.immediate? true: false };
+ var command = { "type": spawnOptions.name, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition, "altitude": spawnOptions.altitude, "payloadName": spawnOptions.loadout != null ? spawnOptions.loadout : "", "airbaseName": spawnOptions.airbaseName != null ? spawnOptions.airbaseName : "", "immediate": spawnOptions.immediate? true: false };
var data = { "spawnAir": command }
POST(data, () => { });
}
diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts
index 54e9d37b..200c19a6 100644
--- a/client/src/units/unit.ts
+++ b/client/src/units/unit.ts
@@ -1,14 +1,12 @@
import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map } from 'leaflet';
import { getMap, getUnitsManager } from '..';
-import { mToFt, msToKnots, rad2deg } from '../other/utils';
+import { getMarkerCategoryByName, getUnitDatabaseByCategory, mToFt, msToKnots, rad2deg } from '../other/utils';
import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, getUnits, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions, followUnit, setEmissionsCountermeasures, setSpeedType, setAltitudeType, setOnOff, setFollowRoads, bombPoint, carpetBomb, bombBuilding, fireAtArea } from '../server/server';
-import { aircraftDatabase } from './aircraftdatabase';
-import { groundUnitsDatabase } from './groundunitsdatabase';
import { CustomMarker } from '../map/custommarker';
import { SVGInjector } from '@tanem/svg-injector';
import { UnitDatabase } from './unitdatabase';
-import { BOMBING, CARPET_BOMBING, FIRE_AT_AREA, IDLE, MOVE_UNIT } from '../map/map';
import { TargetMarker } from '../map/targetmarker';
+import { BOMBING, CARPET_BOMBING, FIRE_AT_AREA, IDLE, MOVE_UNIT } from '../constants/constants';
var pathIcon = new Icon({
iconUrl: '/resources/theme/images/markers/marker-icon.png',
@@ -133,12 +131,12 @@ export class Unit extends CustomMarker {
getMarkerCategory() {
// Overloaded by child classes
+ // TODO convert to use getMarkerCategoryByName
return "";
}
getDatabase(): UnitDatabase | null {
- // Overloaded by child classes
- return null;
+ return getUnitDatabaseByCategory(this.getMarkerCategory());
}
getIconOptions(): UnitIconOptions {
@@ -344,7 +342,7 @@ export class Unit extends CustomMarker {
if (this.getIconOptions().showShortLabel) {
var shortLabel = document.createElement("div");
shortLabel.classList.add("unit-short-label");
- shortLabel.innerText = this.getDatabase()?.getByName(this.getBaseData().name)?.shortLabel || "";
+ shortLabel.innerText = getUnitDatabaseByCategory(this.getMarkerCategory())?.getByName(this.getBaseData().name)?.shortLabel || "";
el.append(shortLabel);
}
@@ -424,7 +422,7 @@ export class Unit extends CustomMarker {
return getUnitsManager().getUnitByID(this.getFormationData().leaderID);
}
- canRole(roles: string | string[]) {
+ canFulfillRole(roles: string | string[]) {
if (typeof(roles) === "string")
roles = [roles];
@@ -565,7 +563,9 @@ export class Unit extends CustomMarker {
/***********************************************/
onAdd(map: Map): this {
super.onAdd(map);
- getMap().removeTemporaryMarker(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude));
+ /* If this is the first time adding this unit to the map, remove the temporary marker */
+ if (getUnitsManager().getUnitByID(this.ID) == null)
+ getMap().removeTemporaryMarker(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude));
return this;
}
@@ -609,14 +609,14 @@ export class Unit extends CustomMarker {
if ((selectedUnits.length === 0 && this.getBaseData().category == "Aircraft") || (selectedUnitTypes.length === 1 && ["Aircraft"].includes(selectedUnitTypes[0])))
{
- if (selectedUnits.concat([this]).every((unit: Unit) => {return unit.canRole(["CAS", "Strike"])})) {
+ if (selectedUnits.concat([this]).every((unit: Unit) => {return unit.canFulfillRole(["CAS", "Strike"])})) {
options["bomb"] = {text: "Precision bombing", tooltip: "Precision bombing of a specific point"};
options["carpet-bomb"] = {text: "Carpet bombing", tooltip: "Carpet bombing close to a point"};
}
}
if ((selectedUnits.length === 0 && this.getBaseData().category == "GroundUnit") || selectedUnitTypes.length === 1 && ["GroundUnit"].includes(selectedUnitTypes[0])) {
- if (selectedUnits.concat([this]).every((unit: Unit) => {return unit.canRole(["Gun Artillery", "Rocket Artillery", "Infantry", "IFV", "Tank"])}))
+ if (selectedUnits.concat([this]).every((unit: Unit) => {return unit.canFulfillRole(["Gun Artillery", "Rocket Artillery", "Infantry", "IFV", "Tank"])}))
options["fire-at-area"] = {text: "Fire at area", tooltip: "Fire at a large area"};
}
@@ -915,21 +915,17 @@ export class AirUnit extends Unit {
}
export class Aircraft extends AirUnit {
- constructor(ID: number, data: UnitData) {
+ constructor(ID: number, data: UpdateData) {
super(ID, data);
}
getMarkerCategory() {
return "aircraft";
}
-
- getDatabase(): UnitDatabase | null {
- return aircraftDatabase;
- }
}
export class Helicopter extends AirUnit {
- constructor(ID: number, data: UnitData) {
+ constructor(ID: number, data: UpdateData) {
super(ID, data);
}
@@ -939,7 +935,7 @@ export class Helicopter extends AirUnit {
}
export class GroundUnit extends Unit {
- constructor(ID: number, data: UnitData) {
+ constructor(ID: number, data: UpdateData) {
super(ID, data);
}
@@ -958,19 +954,12 @@ export class GroundUnit extends Unit {
}
getMarkerCategory() {
- // TODO this is very messy
- var role = groundUnitsDatabase.getByName(this.getBaseData().name)?.loadouts[0].roles[0];
- var markerCategory = (role === "SAM") ? "groundunit-sam" : "groundunit-other";
- return markerCategory;
- }
-
- getDatabase(): UnitDatabase | null {
- return groundUnitsDatabase;
+ return getMarkerCategoryByName(this.getBaseData().name);
}
}
export class NavyUnit extends Unit {
- constructor(ID: number, data: UnitData) {
+ constructor(ID: number, data: UpdateData) {
super(ID, data);
}
@@ -994,7 +983,7 @@ export class NavyUnit extends Unit {
}
export class Weapon extends Unit {
- constructor(ID: number, data: UnitData) {
+ constructor(ID: number, data: UpdateData) {
super(ID, data);
this.setSelectable(false);
}
@@ -1015,7 +1004,7 @@ export class Weapon extends Unit {
}
export class Missile extends Weapon {
- constructor(ID: number, data: UnitData) {
+ constructor(ID: number, data: UpdateData) {
super(ID, data);
}
@@ -1025,7 +1014,7 @@ export class Missile extends Weapon {
}
export class Bomb extends Weapon {
- constructor(ID: number, data: UnitData) {
+ constructor(ID: number, data: UpdateData) {
super(ID, data);
}
diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts
index f30e25d9..b45cf120 100644
--- a/client/src/units/unitsmanager.ts
+++ b/client/src/units/unitsmanager.ts
@@ -3,10 +3,10 @@ import { getHotgroupPanel, getInfoPopup, getMap, getMissionHandler, getUnitDataT
import { Unit } from "./unit";
import { cloneUnit, spawnGroundUnit } from "../server/server";
import { deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots, polygonArea, randomPointInPoly, randomUnitBlueprintByRole } from "../other/utils";
-import { IDLE, MOVE_UNIT } from "../map/map";
import { CoalitionArea } from "../map/coalitionarea";
import { Airbase } from "../missionhandler/airbase";
import { groundUnitsDatabase } from "./groundunitsdatabase";
+import { IDLE, MOVE_UNIT } from "../constants/constants";
export class UnitsManager {
#units: { [ID: number]: Unit };
@@ -493,7 +493,7 @@ export class UnitsManager {
if (!this.#pasteDisabled) {
for (let idx in this.#copiedUnits) {
var unit = this.#copiedUnits[idx];
- getMap().addTemporaryMarker(getMap().getMouseCoordinates());
+ //getMap().addTemporaryMarker(getMap().getMouseCoordinates());
cloneUnit(unit.ID, getMap().getMouseCoordinates());
this.#showActionMessage(this.#copiedUnits, `pasted`);
}
@@ -520,14 +520,9 @@ export class UnitsManager {
if (Math.random() < probability){
const role = activeRoles[Math.floor(Math.random() * activeRoles.length)];
const unitBlueprint = randomUnitBlueprintByRole(groundUnitsDatabase, role);
- spawnGroundUnit({
- role: role,
- latlng: latlng,
- type: unitBlueprint.name,
- coalition: coalitionArea.getCoalition(),
- immediate: true
- });
- getMap().addTemporaryMarker(latlng);
+ const spawnOptions = {role: role, latlng: latlng, name: unitBlueprint.name, coalition: coalitionArea.getCoalition(), immediate: true};
+ spawnGroundUnit(spawnOptions);
+ getMap().addTemporaryMarker(spawnOptions);
}
}
}
diff --git a/client/views/other/contextmenus.ejs b/client/views/other/contextmenus.ejs
index f1f1dc7c..eed3f719 100644
--- a/client/views/other/contextmenus.ejs
+++ b/client/views/other/contextmenus.ejs
@@ -116,6 +116,8 @@
data-on-click-params='{ "type": "iads" }' class="ol-contexmenu-button">
+
diff --git a/client/views/panels/navbar.ejs b/client/views/panels/navbar.ejs
index a2ca6b2e..5fa3e35f 100644
--- a/client/views/panels/navbar.ejs
+++ b/client/views/panels/navbar.ejs
@@ -61,6 +61,9 @@