diff --git a/client/package-lock.json b/client/package-lock.json
index e6e672a7..e627bd43 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "DCSOlympus",
- "version": "v0.2.1-alpha",
+ "version": "v0.3.0-alpha",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "DCSOlympus",
- "version": "v0.2.1-alpha",
+ "version": "v0.3.0-alpha",
"dependencies": {
"@types/geojson": "^7946.0.10",
"@types/leaflet": "^1.9.0",
diff --git a/client/package.json b/client/package.json
index f0db50cb..9f2fec90 100644
--- a/client/package.json
+++ b/client/package.json
@@ -2,7 +2,7 @@
"name": "DCSOlympus",
"node-main": "./bin/www",
"main": "http://localhost:3000",
- "version": "v0.2.1-alpha",
+ "version": "v0.3.0-alpha",
"private": true,
"scripts": {
"copy": "copy.bat",
diff --git a/client/public/stylesheets/layout/layout.css b/client/public/stylesheets/layout/layout.css
index 31cb44fc..cf209ea9 100644
--- a/client/public/stylesheets/layout/layout.css
+++ b/client/public/stylesheets/layout/layout.css
@@ -14,6 +14,10 @@
z-index: 1000;
}
+#app-icon>.ol-select-options {
+ width: fit-content;
+}
+
#toolbar-summary {
background-image: url("/images/icon-round.png");
background-position: 20px 22px;
@@ -25,6 +29,10 @@
text-indent: 60px;
}
+#toolbar-summary {
+ white-space: nowrap;
+}
+
#connection-status-panel {
bottom: 20px;
font-size: 12px;
diff --git a/client/public/stylesheets/olympus.css b/client/public/stylesheets/olympus.css
index b0fbae9e..7fe17ff9 100644
--- a/client/public/stylesheets/olympus.css
+++ b/client/public/stylesheets/olympus.css
@@ -574,9 +574,9 @@ nav.ol-panel> :last-child {
.ol-measure-box {
background-color: var(--background-steel);
border-radius: 999px;
- color: var(--primary-neutral);
+ color: var(--background-offwhite);
font-size: 12px;
- font-weight: var(--font-weight-bolder);
+ font-weight: bolder;
height: fit-content;
padding-bottom: 0.2em;
padding-left: 0.5em;
@@ -586,6 +586,7 @@ nav.ol-panel> :last-child {
text-align: center;
width: fit-content;
z-index: 2000;
+ pointer-events: none;
}
.ol-sortable .handle {
@@ -910,18 +911,6 @@ dl.ol-data-grid dd {
margin-left: auto;
}
-.br-info::after {
- content: attr(data-bearing) '\00B0 / ' attr(data-distance) attr(data-distance-units);
-}
-
-.br-info[data-message]::after {
- content: attr(data-message);
-}
-
-.coordinates::after {
- content: attr(data-dd) "\00b0 " attr(data-mm) "'" attr(data-ss) "." attr(data-sss) '"' attr(data-label);
-}
-
.ol-button-box {
column-gap: 6px;
display: flex;
diff --git a/client/public/stylesheets/other/contextmenus.css b/client/public/stylesheets/other/contextmenus.css
index e621d13d..57fc1341 100644
--- a/client/public/stylesheets/other/contextmenus.css
+++ b/client/public/stylesheets/other/contextmenus.css
@@ -4,7 +4,7 @@
height: fit-content;
position: absolute;
row-gap: 5px;
- width: 230px;
+ width: 280px;
z-index: 9999;
}
@@ -109,6 +109,11 @@
background-size: 48px;
}
+#explosion-spawn-button {
+ background-image: url("/resources/theme/images/buttons/spawn/explosion.svg");
+ background-size: 48px;
+}
+
.unit-spawn-button {
border: none;
border-radius: 0px;
@@ -208,6 +213,7 @@
text-align: center;
}
+#explosion-menu>button,
#smoke-spawn-menu>button {
align-items: center;
column-gap: 10px;
diff --git a/client/public/stylesheets/panels/mouseinfo.css b/client/public/stylesheets/panels/mouseinfo.css
index 1c3d5e2e..e32f4540 100644
--- a/client/public/stylesheets/panels/mouseinfo.css
+++ b/client/public/stylesheets/panels/mouseinfo.css
@@ -6,11 +6,11 @@
#mouse-info-panel dl {
margin-bottom: 4px;
- row-gap: 8px;
+ row-gap: 5px;
}
#mouse-info-panel dt {
- height: 20px;
+ height: fit-content;
width: 30%;
}
@@ -30,20 +30,22 @@
width: 16px;
}
-#mouse-info-panel dt#ref-unit-position::after {
- background-image: url("/resources/theme/images/icons/ruler.svg");
- background-position: 50% 50%;
- background-repeat: no-repeat;
- background-size: 16px 16px;
- content: "";
+#mouse-info-panel #measuring-tool dt {
+ height: 24px;
+ width: 24px;
+ background-color: var(--background-offwhite);
+ border-radius: var(--border-radius-sm);
}
-#mouse-info-panel dt#ref-measure-position::after {
- background-image: url("/resources/theme/images/icons/pin.png");
- background-position: 50% 50%;
- background-repeat: no-repeat;
- background-size: 16px 16px;
- content: "";
+#mouse-info-panel #measuring-tool svg {
+ padding: 3px;
+ height: 100%;
+ width: 100%;
+}
+
+#mouse-info-panel #measuring-tool dt svg>* {
+ fill: black;
+ stroke: black;
}
#mouse-info-panel dt[data-label]::after {
@@ -72,4 +74,36 @@
#mouse-info-panel dd {
width: 70%;
+}
+
+.br-info::after {
+ content: attr(data-bearing) '\00B0 / ' attr(data-distance) " " attr(data-distance-units);
+ font-weight: bold;
+ font-size: 13px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--background-offwhite);
+}
+
+.br-info[data-coalition="blue"]::after {
+ color: var(--primary-blue)
+}
+
+.br-info[data-coalition="red"]::after {
+ color: var(--primary-red)
+}
+
+.br-info[data-message]::after {
+ content: attr(data-message);
+}
+
+.coordinates::after {
+ content: attr(data-dd) "\00b0 " attr(data-mm) "'" attr(data-ss) "." attr(data-sss) '"' attr(data-label);
+ font-weight: bold;
+ font-size: 13px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ color: var(--background-offwhite);
}
\ No newline at end of file
diff --git a/client/public/themes/olympus/images/buttons/spawn/explosion.svg b/client/public/themes/olympus/images/buttons/spawn/explosion.svg
new file mode 100644
index 00000000..192784b0
--- /dev/null
+++ b/client/public/themes/olympus/images/buttons/spawn/explosion.svg
@@ -0,0 +1,42 @@
+
+
diff --git a/client/public/themes/olympus/images/icons/pin.png b/client/public/themes/olympus/images/icons/pin.png
deleted file mode 100644
index c6222cd2..00000000
Binary files a/client/public/themes/olympus/images/icons/pin.png and /dev/null differ
diff --git a/client/public/themes/olympus/images/icons/pin.svg b/client/public/themes/olympus/images/icons/pin.svg
new file mode 100644
index 00000000..e42653e9
--- /dev/null
+++ b/client/public/themes/olympus/images/icons/pin.svg
@@ -0,0 +1,46 @@
+
+
diff --git a/client/public/themes/olympus/images/icons/plane.svg b/client/public/themes/olympus/images/icons/plane.svg
new file mode 100644
index 00000000..b672646e
--- /dev/null
+++ b/client/public/themes/olympus/images/icons/plane.svg
@@ -0,0 +1,3 @@
+
diff --git a/client/src/controls/mapcontextmenu.ts b/client/src/controls/mapcontextmenu.ts
index 60b415a7..51027f01 100644
--- a/client/src/controls/mapcontextmenu.ts
+++ b/client/src/controls/mapcontextmenu.ts
@@ -1,6 +1,6 @@
import { LatLng } from "leaflet";
import { getActiveCoalition, getMap, setActiveCoalition } from "..";
-import { spawnAircraft, spawnGroundUnit, spawnSmoke } from "../server/server";
+import { spawnAircraft, spawnExplosion, spawnGroundUnit, spawnSmoke } from "../server/server";
import { aircraftDatabase } from "../units/aircraftdatabase";
import { groundUnitsDatabase } from "../units/groundunitsdatabase";
import { ContextMenu } from "./contextmenu";
@@ -39,6 +39,9 @@ export class MapContextMenu extends ContextMenu {
this.#aircraftTypeDropdown = new Dropdown("aircraft-type-options", (type: string) => this.#setAircraftType(type));
this.#aircraftLoadoutDropdown = new Dropdown("loadout-options", (loadout: string) => this.#setAircraftLoadout(loadout));
this.#aircrafSpawnAltitudeSlider = new Slider("aircraft-spawn-altitude-slider", 0, 50000, "ft", (value: number) => {this.#spawnOptions.altitude = ftToM(value);});
+ this.#aircrafSpawnAltitudeSlider.setIncrement(500);
+ this.#aircrafSpawnAltitudeSlider.setValue(20000);
+ this.#aircrafSpawnAltitudeSlider.setActive(true);
this.#groundUnitRoleDropdown = new Dropdown("ground-unit-role-options", (role: string) => this.#setGroundUnitRole(role));
this.#groundUnitTypeDropdown = new Dropdown("ground-unit-type-options", (type: string) => this.#setGroundUnitType(type));
@@ -69,9 +72,11 @@ export class MapContextMenu extends ContextMenu {
spawnSmoke(e.detail.color, this.getLatLng());
});
- this.#aircrafSpawnAltitudeSlider.setIncrement(500);
- this.#aircrafSpawnAltitudeSlider.setValue(20000);
- this.#aircrafSpawnAltitudeSlider.setActive(true);
+ document.addEventListener("contextMenuExplosion", (e: any) => {
+ this.hide();
+ spawnExplosion(e.detail.strength, this.getLatLng());
+ });
+
this.hide();
}
@@ -90,6 +95,8 @@ export class MapContextMenu extends ContextMenu {
this.getContainer()?.querySelector("#ground-unit-spawn-button")?.classList.toggle("is-open", type === "ground-unit");
this.getContainer()?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", type !== "smoke");
this.getContainer()?.querySelector("#smoke-spawn-button")?.classList.toggle("is-open", type === "smoke");
+ this.getContainer()?.querySelector("#explosion-menu")?.classList.toggle("hide", type !== "explosion");
+ this.getContainer()?.querySelector("#explosion-spawn-button")?.classList.toggle("is-open", type === "explosion");
this.#resetAircraftRole();
this.#resetAircraftType();
diff --git a/client/src/map/map.ts b/client/src/map/map.ts
index 6a75186b..eaf0951d 100644
--- a/client/src/map/map.ts
+++ b/client/src/map/map.ts
@@ -21,7 +21,7 @@ require("../../public/javascripts/leaflet.nauticscale.js")
/* Map constants */
export const IDLE = "IDLE";
-export const MOVE_UNIT = "MOVE_UNIT";
+export const UNIT_SELECTED = "MOVE_UNIT";
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"];
@@ -159,7 +159,7 @@ export class Map extends L.Map {
this.#computeDestinationRotation = false;
this.#destinationRotationCenter = null;
}
- else if (this.#state === MOVE_UNIT) {
+ else if (this.#state === UNIT_SELECTED) {
/* Remove all the exising destination preview markers */
this.#destinationPreviewMarkers.forEach((marker: L.Marker) => {
this.removeLayer(marker);
@@ -363,7 +363,7 @@ export class Map extends L.Map {
if (this.#state === IDLE) {
}
- else if (this.#state === MOVE_UNIT) {
+ else if (this.#state === UNIT_SELECTED) {
this.setState(IDLE);
getUnitsManager().deselectAllUnits();
}
@@ -381,17 +381,52 @@ export class Map extends L.Map {
this.showMapContextMenu(e);
}
}
- else if (this.#state === MOVE_UNIT) {
- if (!e.originalEvent.ctrlKey) {
- getUnitsManager().selectedUnitsClearDestinations();
+ else if (this.#state === UNIT_SELECTED) {
+ if (e.originalEvent.shiftKey) {
+ var options: {[key: string]: {text: string, tooltip: string}} = {};
+ var selectedUnitTypes = getUnitsManager().getSelectedUnitsTypes();
+
+ if (selectedUnitTypes.length === 1 && ["Aircraft"].includes(selectedUnitTypes[0]))
+ {
+ options["bomb"] = {text: "Bomb here", tooltip: "Precision bombing of this specific point"};
+ options["carpet-bomb"] = {text: "Carpet bomb", tooltip: "Carpet bombing around this point"};
+ options["building-bomb"] = {text: "Bomb building", tooltip: "Precision bombing of the building closest to this point"};
+ }
+
+ if (selectedUnitTypes.length === 1 && ["GroundUnit"].includes(selectedUnitTypes[0]))
+ options["fire-at-area"] = {text: "Fire at area", tooltip: "Fire at this point"};
+
+ if (Object.keys(options).length > 0) {
+ this.showUnitContextMenu(e);
+ this.getUnitContextMenu().setOptions(options, (option: string) => {
+ this.hideUnitContextMenu();
+ this.#executeAction(e, option);
+ });
+ }
+
+ } else {
+ if (!e.originalEvent.ctrlKey) {
+ getUnitsManager().selectedUnitsClearDestinations();
+ }
+ getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, e.originalEvent.shiftKey, this.#destinationGroupRotation)
+ this.#destinationGroupRotation = 0;
+ this.#destinationRotationCenter = null;
+ this.#computeDestinationRotation = false;
}
- getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, e.originalEvent.shiftKey, this.#destinationGroupRotation)
- this.#destinationGroupRotation = 0;
- this.#destinationRotationCenter = null;
- this.#computeDestinationRotation = false;
}
}
+ #executeAction(e: any, action: string) {
+ if (action === "bomb")
+ getUnitsManager().selectedUnitsBombPoint(this.getMouseCoordinates());
+ else if (action === "carpet-bomb")
+ getUnitsManager().selectedUnitsCarpetBomb(this.getMouseCoordinates());
+ else if (action === "building-bomb")
+ getUnitsManager().selectedUnitsBombBuilding(this.getMouseCoordinates());
+ else if (action === "fire-at-area")
+ getUnitsManager().selectedUnitsFireAtArea(this.getMouseCoordinates());
+ }
+
#onSelectionEnd(e: any) {
clearTimeout(this.#leftClickTimer);
this.#preventLeftClick = true;
@@ -404,7 +439,7 @@ export class Map extends L.Map {
#onMouseDown(e: any) {
this.hideAllContextMenus();
- if (this.#state == MOVE_UNIT) {
+ if (this.#state == UNIT_SELECTED) {
this.#destinationGroupRotation = 0;
this.#destinationRotationCenter = null;
this.#computeDestinationRotation = false;
diff --git a/client/src/panels/mouseinfopanel.ts b/client/src/panels/mouseinfopanel.ts
index bdd38fb3..7301e130 100644
--- a/client/src/panels/mouseinfopanel.ts
+++ b/client/src/panels/mouseinfopanel.ts
@@ -14,8 +14,8 @@ export class MouseInfoPanel extends Panel {
constructor(ID: string) {
super(ID);
- this.#measureIcon = new Icon({ iconUrl: 'images/pin.png', iconAnchor: [16, 32]});
- this.#measureMarker = new Marker([0, 0], {icon: this.#measureIcon, interactive: false});
+ this.#measureIcon = new Icon({ iconUrl: 'resources/theme/images/icons/pin.svg', iconAnchor: [16, 32] });
+ this.#measureMarker = new Marker([0, 0], { icon: this.#measureIcon, interactive: false });
this.#measureBox = document.createElement("div");
this.#measureBox.classList.add("ol-measure-box", "hide");
@@ -25,109 +25,36 @@ export class MouseInfoPanel extends Panel {
getMap()?.on('zoom', (e: any) => this.#onZoom(e));
getMap()?.on('mousemove', (e: any) => this.#onMouseMove(e));
- document.addEventListener('unitsSelection', (e: CustomEvent) => this.#onUnitsSelection(e.detail));
- document.addEventListener('clearSelection', () => this.#onClearSelection());
+ document.addEventListener('unitsSelection', (e: CustomEvent) => this.#update());
+ document.addEventListener('clearSelection', () => this.#update());
}
- #update(mousePosition: LatLng, measurePosition: LatLng | null, unitPosition: LatLng | null) {
+ #update() {
+ const mousePosition = getMap().getMouseCoordinates();
+
+ var selectedUnitPosition = null;
+ var selectedUnits = getUnitsManager().getSelectedUnits();
+ if (selectedUnits && selectedUnits.length == 1)
+ selectedUnitPosition = new LatLng(selectedUnits[0].getFlightData().latitude, selectedUnits[0].getFlightData().longitude);
+
+ /* Draw measures from selected unit, from pin location, and from bullseyes */
+ this.#drawMeasure("ref-measure-position", "measure-position", this.#measurePoint, mousePosition);
+ this.#drawMeasure("ref-unit-position", "unit-position", selectedUnitPosition, mousePosition);
+
+ this.getElement().querySelector(`#measuring-tool`)?.classList.toggle("hide", this.#measurePoint === null && selectedUnitPosition === null);
+
var bullseyes = getMissionData().getBullseyes();
for (let idx in bullseyes)
- {
- var el = this.getElement().querySelector(`#bullseye-${idx}`);
+ this.#drawMeasure(null, `bullseye-${idx}`, bullseyes[idx].getLatLng(), mousePosition);
- if ( el != null ) {
-
- var dist = distance(bullseyes[idx].getLatLng().lat, bullseyes[idx].getLatLng().lng, mousePosition.lat, mousePosition.lng);
- var bear = bearing(bullseyes[idx].getLatLng().lat, bullseyes[idx].getLatLng().lng, mousePosition.lat, mousePosition.lng);
-
- let bng = zeroAppend(Math.floor(bear), 3);
-
- if ( bng === "000" ) {
- bng = "360";
- }
-
- el.dataset.bearing = bng;
- el.dataset.distance = zeroAppend(Math.floor(dist*0.000539957), 3);
- el.dataset.distanceUnits = "NM";
- }
-
- }
-
- if (measurePosition) {
- var el = this.getElement().querySelector(`#measure-position`);
-
- if (el != null) {
- var bear = bearing(measurePosition.lat, measurePosition.lng, mousePosition.lat, mousePosition.lng);
- var dist = distance(measurePosition.lat, measurePosition.lng, mousePosition.lat, mousePosition.lng);
-
- let bng = zeroAppend(Math.floor(bear), 3);
-
- if ( bng === "000" ) {
- bng = "360";
- }
-
- el.dataset.bearing = bng;
- el.dataset.distance = zeroAppend(Math.floor(dist*0.000539957), 3);
- el.dataset.distanceUnits = "NM";
-
- }
- }
-
-
- if (unitPosition) {
- var el = this.getElement().querySelector(`#unit-position`);
- if (el != null) {
- var dist = distance(unitPosition.lat, unitPosition.lng, mousePosition.lat, mousePosition.lng);
- var bear = bearing(unitPosition.lat, unitPosition.lng, mousePosition.lat, mousePosition.lng);
-
- el.dataset.bearing = zeroAppend(Math.floor(bear), 3);
- el.dataset.distance = zeroAppend(Math.floor(dist*0.000539957), 3);
- el.dataset.distanceUnits = "NM";
- }
- }
-
- const refMouseLat = document.getElementById( "ref-mouse-position-latitude" );
- const mouseLat = document.getElementById( "mouse-position-latitude" );
-
- if ( refMouseLat && mouseLat ) {
-
- let matches = String( mousePosition.lat ).match( /^\-?(\d+)\.(\d{2})(\d{2})(\d{2})/ );
-
- if ( matches && matches.length ) {
- mouseLat.dataset.dd = matches[1];
- mouseLat.dataset.mm = matches[2];
- mouseLat.dataset.ss = matches[3];
- mouseLat.dataset.sss = matches[4];
- }
-
- refMouseLat.dataset.label = ( mousePosition.lat < 0 ) ? "S" : "N";
-
- }
-
- const refMouseLng = document.getElementById( "ref-mouse-position-longitude" );
- const mouseLng = document.getElementById( "mouse-position-longitude" );
-
- if ( refMouseLng && mouseLng ) {
-
- let matches = String( mousePosition.lng ).match( /^\-?(\d+)\.(\d{2})(\d{2})(\d{2})/ );
-
- if ( matches && matches.length ) {
- mouseLng.dataset.dd = matches[1];
- mouseLng.dataset.mm = matches[2];
- mouseLng.dataset.ss = matches[3];
- mouseLng.dataset.sss = matches[4];
- }
-
- refMouseLng.dataset.label = ( mousePosition.lng < 0 ) ? "W" : "E";
- }
+ /* Draw coordinates */
+ this.#drawCoordinates("ref-mouse-position-latitude", "mouse-position-latitude", mousePosition.lat, ["N", "S"]);
+ this.#drawCoordinates("ref-mouse-position-longitude", "mouse-position-longitude", mousePosition.lng, ["E", "W"]);
}
- #onMapClick(e: any)
- {
- if (e.originalEvent.ctrlKey)
- {
- if (!this.#measurePoint)
- {
+ #onMapClick(e: any) {
+ if (e.originalEvent.ctrlKey) {
+ if (!this.#measurePoint) {
this.#measureBox.classList.toggle("hide", false);
this.#measurePoint = e.latlng;
this.#measureMarker.setLatLng(e.latlng);
@@ -135,8 +62,7 @@ export class MouseInfoPanel extends Panel {
if (!getMap().hasLayer(this.#measureLine))
this.#measureLine.addTo(getMap());
}
- else
- {
+ else {
this.#measureBox.classList.toggle("hide", true);
this.#measurePoint = null;
if (getMap().hasLayer(this.#measureMarker))
@@ -147,13 +73,13 @@ export class MouseInfoPanel extends Panel {
getMap().removeLayer(this.#measureLine);
}
}
+
+ this.#update();
}
- #drawMeasureLine()
- {
+ #drawMeasureLine() {
var mouseLatLng = getMap().containerPointToLatLng(getMap().getMousePosition());
- if (this.#measurePoint != null)
- {
+ if (this.#measurePoint != null) {
var points = [this.#measurePoint, mouseLatLng];
this.#measureLine.setLatLngs(points);
var dist = distance(this.#measurePoint.lat, this.#measurePoint.lng, mouseLatLng.lat, mouseLatLng.lng);
@@ -163,74 +89,79 @@ export class MouseInfoPanel extends Panel {
var dy = (getMap().getMousePosition().y - startXY.y);
var angle = Math.atan2(dy, dx);
- if (angle > Math.PI / 2)
+ if (angle > Math.PI / 2)
angle = angle - Math.PI;
- if (angle < -Math.PI / 2)
+ if (angle < -Math.PI / 2)
angle = angle + Math.PI;
let bng = zeroAppend(Math.floor(bear), 3);
- const reciprocal = zeroAppend( reciprocalHeading( parseInt( bng ) ), 3 );
- if ( bng === "000" ) {
+ if (bng === "000")
bng = "360";
- }
- let data = [ `${bng}°`, `${Math.floor(dist*0.000539957)}NM`, `${reciprocal}°` ];
+ let data = [`${bng}°`, `${Math.floor(dist * 0.000539957)} NM`];
- if ( bear < 180 ) {
- data = data.reverse();
- }
-
- this.#measureBox.innerText = data.join( " | " );
+ this.#measureBox.innerText = data.join(" / ");
this.#measureBox.style.left = (getMap().getMousePosition().x + startXY.x) / 2 - this.#measureBox.offsetWidth / 2 + "px";
this.#measureBox.style.top = (getMap().getMousePosition().y + startXY.y) / 2 - this.#measureBox.offsetHeight / 2 + "px";
this.#measureBox.style.rotate = angle + "rad";
}
}
- #onMouseMove(e: any)
- {
- var selectedUnitPosition = null;
- var selectedUnits = getUnitsManager().getSelectedUnits();
- if (selectedUnits && selectedUnits.length == 1)
- selectedUnitPosition = new LatLng(selectedUnits[0].getFlightData().latitude, selectedUnits[0].getFlightData().longitude);
-
- this.#update(e.latlng, this.#measurePoint, selectedUnitPosition);
- this.#drawMeasureLine();
- }
-
- #onZoom(e: any)
- {
- this.#drawMeasureLine();
- }
-
- #onUnitsSelection(units: Unit[])
- {
- const pos = this.getElement().querySelector(`#unit-position`);
+ #onMouseMove(e: any) {
- if ( units.length > 1 ) {
- pos?.setAttribute( "data-message", "(multiple units)" );
- } else {
- pos?.removeAttribute( "data-message" );
+ this.#update();
+ this.#drawMeasureLine();
+ }
+
+ #onZoom(e: any) {
+ this.#drawMeasureLine();
+ }
+
+ #drawMeasure(imgId: string | null, textId: string, value: LatLng | null, mousePosition: LatLng) {
+ var el = this.getElement().querySelector(`#${textId}`) as HTMLElement;
+ var img = imgId != null ? this.getElement().querySelector(`#${imgId}`) as HTMLElement : null;
+ if (value) {
+ if (el != null) {
+ el.classList.remove("hide");
+
+ var bear = bearing(value.lat, value.lng, mousePosition.lat, mousePosition.lng);
+ var dist = distance(value.lat, value.lng, mousePosition.lat, mousePosition.lng);
+
+ let bng = zeroAppend(Math.floor(bear), 3);
+
+ if (bng === "000")
+ bng = "360";
+
+ el.dataset.bearing = bng;
+ el.dataset.distance = zeroAppend(Math.floor(dist * 0.000539957), 3);
+ el.dataset.distanceUnits = "NM";
+ }
+ if (img != null)
+ img.classList.remove("hide");
+ }
+ else {
+ if (el != null)
+ el.classList.add("hide");
+ if (img != null)
+ img.classList.add("hide");
}
-
}
-
- #onClearSelection()
- {
- this.#measureBox.classList.toggle("hide", true);
-
- const pos = this.getElement().querySelector(`#unit-position`);
-
- if ( pos instanceof HTMLElement ) {
- pos?.removeAttribute( "data-message" );
-
- pos.dataset.bearing = "---";
- pos.dataset.distance = "---";
- pos.dataset.distanceUnits = "NM";
+ #drawCoordinates(imgId: string, textId: string, value: number, prefixes: string[]) {
+ const el = this.getElement().querySelector(`#${textId}`) as HTMLElement;
+ const img = this.getElement().querySelector(`#${imgId}`) as HTMLElement;
+ if (img && el) {
+ let matches = String(value).match(/^\-?(\d+)\.(\d{2})(\d{2})(\d{2})/);
+ if (matches && matches.length) {
+ el.dataset.dd = matches[1];
+ el.dataset.mm = matches[2];
+ el.dataset.ss = matches[3];
+ el.dataset.sss = matches[4];
+ }
+ img.dataset.label = (value < 0) ? prefixes[1] : prefixes[0];
}
}
}
diff --git a/client/src/server/server.ts b/client/src/server/server.ts
index 7ee189b1..f4a0000f 100644
--- a/client/src/server/server.ts
+++ b/client/src/server/server.ts
@@ -1,4 +1,4 @@
-import * as L from 'leaflet'
+import { LatLng } from 'leaflet';
import { getConnectionStatusPanel, getInfoPopup, getMissionData, getUnitDataTable, getUnitsManager, setConnectionStatus } from '..';
import { SpawnOptions } from '../controls/mapcontextmenu';
@@ -121,12 +121,18 @@ export function addDestination(ID: number, path: any) {
POST(data, () => { });
}
-export function spawnSmoke(color: string, latlng: L.LatLng) {
+export function spawnSmoke(color: string, latlng: LatLng) {
var command = { "color": color, "location": latlng };
var data = { "smoke": command }
POST(data, () => { });
}
+export function spawnExplosion(strength: number, latlng: LatLng) {
+ var command = { "strength": strength, "location": latlng };
+ var data = { "explosion": command }
+ POST(data, () => { });
+}
+
export function spawnGroundUnit(spawnOptions: SpawnOptions) {
var command = { "type": spawnOptions.type, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition };
var data = { "spawnGround": command }
@@ -155,7 +161,7 @@ export function followUnit(ID: number, targetID: number, offset: { "x": number,
POST(data, () => { });
}
-export function cloneUnit(ID: number, latlng: L.LatLng) {
+export function cloneUnit(ID: number, latlng: LatLng) {
var command = { "ID": ID, "location": latlng };
var data = { "cloneUnit": command }
POST(data, () => { });
@@ -167,7 +173,7 @@ export function deleteUnit(ID: number, explosion: boolean) {
POST(data, () => { });
}
-export function landAt(ID: number, latlng: L.LatLng) {
+export function landAt(ID: number, latlng: LatLng) {
var command = { "ID": ID, "location": latlng };
var data = { "landAt": command }
POST(data, () => { });
@@ -251,6 +257,29 @@ export function refuel(ID: number) {
POST(data, () => { });
}
+export function bombPoint(ID: number, latlng: LatLng) {
+ var command = { "ID": ID, "location": latlng }
+ var data = { "bombPoint": command }
+ POST(data, () => { });
+}
+
+export function carpetBomb(ID: number, latlng: LatLng) {
+ var command = { "ID": ID, "location": latlng }
+ var data = { "carpetBomb": command }
+ POST(data, () => { });
+}
+
+export function bombBuilding(ID: number, latlng: LatLng) {
+ var command = { "ID": ID, "location": latlng }
+ var data = { "bombBuilding": command }
+ POST(data, () => { });
+}
+
+export function fireAtArea(ID: number, latlng: LatLng) {
+ var command = { "ID": ID, "location": latlng }
+ var data = { "fireAtArea": command }
+ POST(data, () => { });
+}
export function setAdvacedOptions(ID: number, isTanker: boolean, isAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings) {
var command = {
"ID": ID,
diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts
index cfa26640..fb4ab994 100644
--- a/client/src/units/unit.ts
+++ b/client/src/units/unit.ts
@@ -1,7 +1,7 @@
import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map } from 'leaflet';
import { getMap, getUnitsManager } from '..';
import { 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 } from '../server/server';
+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';
@@ -517,6 +517,22 @@ export class Unit extends CustomMarker {
setAdvacedOptions(this.ID, isTanker, isAWACS, TACAN, radio, generalSettings);
}
+ bombPoint(latlng: LatLng) {
+ bombPoint(this.ID, latlng);
+ }
+
+ carpetBomb(latlng: LatLng) {
+ carpetBomb(this.ID, latlng);
+ }
+
+ bombBuilding(latlng: LatLng) {
+ bombBuilding(this.ID, latlng);
+ }
+
+ fireAtArea(latlng: LatLng) {
+ fireAtArea(this.ID, latlng);
+ }
+
/***********************************************/
onAdd(map: Map): this {
super.onAdd(map);
@@ -557,7 +573,7 @@ export class Unit extends CustomMarker {
}
else if ((getUnitsManager().getSelectedUnits().length > 0 && (getUnitsManager().getSelectedUnits().includes(this))) || getUnitsManager().getSelectedUnits().length == 0) {
if (this.getBaseData().category == "Aircraft") {
- options["refuel"] = {text: "AAR Refuel", tooltip: "Refuel unit at the nearest AAR Tanker. If no tanker is available the unit will RTB."}; // TODO Add some way of knowing which aircraft can AAR
+ options["refuel"] = {text: "Air to air refuel", tooltip: "Refuel unit at the nearest AAR Tanker. If no tanker is available the unit will RTB."}; // TODO Add some way of knowing which aircraft can AAR
}
}
diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts
index 3dc4c985..05f839ee 100644
--- a/client/src/units/unitsmanager.ts
+++ b/client/src/units/unitsmanager.ts
@@ -2,8 +2,8 @@ import { LatLng, LatLngBounds } from "leaflet";
import { getHotgroupPanel, getInfoPopup, getMap, getUnitDataTable } from "..";
import { Unit } from "./unit";
import { cloneUnit } from "../server/server";
-import { IDLE, MOVE_UNIT } from "../map/map";
import { deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots } from "../other/utils";
+import { IDLE, UNIT_SELECTED } from "../map/map";
export class UnitsManager {
#units: { [ID: number]: Unit };
@@ -306,7 +306,7 @@ export class UnitsManager {
for (let idx in selectedUnits) {
selectedUnits[idx].setOnOff(onOff);
}
- this.#showActionMessage(selectedUnits, `unit acitve set to ${onOff}`);
+ this.#showActionMessage(selectedUnits, `unit active set to ${onOff}`);
}
selectedUnitsSetFollowRoads(followRoads: boolean) {
@@ -438,6 +438,38 @@ export class UnitsManager {
return unitDestinations;
}
+ selectedUnitsBombPoint(mouseCoordinates: LatLng) {
+ var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
+ for (let idx in selectedUnits) {
+ selectedUnits[idx].bombPoint(mouseCoordinates);
+ }
+ this.#showActionMessage(selectedUnits, `unit bombing point`);
+ }
+
+ selectedUnitsCarpetBomb(mouseCoordinates: LatLng) {
+ var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
+ for (let idx in selectedUnits) {
+ selectedUnits[idx].carpetBomb(mouseCoordinates);
+ }
+ this.#showActionMessage(selectedUnits, `unit bombing point`);
+ }
+
+ selectedUnitsBombBuilding(mouseCoordinates: LatLng) {
+ var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
+ for (let idx in selectedUnits) {
+ selectedUnits[idx].bombBuilding(mouseCoordinates);
+ }
+ this.#showActionMessage(selectedUnits, `unit bombing point`);
+ }
+
+ selectedUnitsFireAtArea(mouseCoordinates: LatLng) {
+ var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
+ for (let idx in selectedUnits) {
+ selectedUnits[idx].fireAtArea(mouseCoordinates);
+ }
+ this.#showActionMessage(selectedUnits, `unit bombing point`);
+ }
+
/***********************************************/
copyUnits() {
this.#copiedUnits = this.getSelectedUnits(); /* Can be applied to humans too */
@@ -466,7 +498,7 @@ export class UnitsManager {
#onUnitSelection(unit: Unit) {
if (this.getSelectedUnits().length > 0) {
- getMap().setState(MOVE_UNIT);
+ getMap().setState(UNIT_SELECTED);
/* Disable the firing of the selection event for a certain amount of time. This avoids firing many events if many units are selected */
if (!this.#selectionEventDisabled) {
window.setTimeout(() => {
diff --git a/client/views/other/contextmenus.ejs b/client/views/other/contextmenus.ejs
index 422177f4..77a6006b 100644
--- a/client/views/other/contextmenus.ejs
+++ b/client/views/other/contextmenus.ejs
@@ -8,6 +8,8 @@
data-on-click-params='{ "type": "ground-unit" }' class="unit-spawn-button">
+