From d508263f9e487fa9a03af6770460e8e130dded07 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Mon, 5 Jun 2023 16:21:49 +0200 Subject: [PATCH 1/2] Minor fixes and mouse measuring restyle --- client/package-lock.json | 4 +- client/package.json | 2 +- client/public/stylesheets/layout/layout.css | 8 + client/public/stylesheets/olympus.css | 17 +- .../public/stylesheets/panels/mouseinfo.css | 62 +++-- .../themes/olympus/images/icons/pin.png | Bin 1393 -> 0 bytes .../themes/olympus/images/icons/pin.svg | 46 ++++ .../themes/olympus/images/icons/plane.svg | 3 + client/src/panels/mouseinfopanel.ts | 235 +++++++----------- client/views/panels/mouseinfo.ejs | 22 +- client/views/panels/navbar.ejs | 4 +- installer/olympus.iss | 2 +- olympus.json | 2 +- scripts/OlympusCommand.lua | 2 +- scripts/OlympusHook.lua | 2 +- 15 files changed, 213 insertions(+), 198 deletions(-) delete mode 100644 client/public/themes/olympus/images/icons/pin.png create mode 100644 client/public/themes/olympus/images/icons/pin.svg create mode 100644 client/public/themes/olympus/images/icons/plane.svg 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/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/icons/pin.png b/client/public/themes/olympus/images/icons/pin.png deleted file mode 100644 index c6222cd29b40e039065e19a5256b3b9617abd6df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1393 zcmV-%1&;cOP)EX>4Tx04R}tkv&MmP!xqvQ>7vmhjtKg$WRM{1yK=4sbUcq3h_DdxIq^re&o9B@*C%p!vfC?8`<UyLAHipMfi_C-6bKR}rbTlA00V7FL_t(o!|j;QZxm%1$3O4R zP8YXZ7Pf6#YLiWRGmQ`tG$ zJPW)I?6Y&XfiHkL;LcWqB!II?csm`5>^U5Zk&4C8A}oa=-2U`Jh z2)Oj7>!y$H-c8bVF`*$4@I?p!Un%Z-9@7sW@|`ii1MdJcn+fs^aQVYTqW6VFf*=eD zfIy@M5s0h}$6>Zq;xo@H051W*Yz9aI`00a0V&ui{ZnPD44j!cM$Poti?0k=Nt@9*8G6h5%`_;KDieVW_Xuk-b}bKJ^i@yq3=P-%O97x=tX zDgaLdUb`L{9gRklDWz&cFP}QaN}<5iv13$j+#sMa9P7RE6wvE9$$~MXzz_c>6lnpH zPJx3JN{x@x)7QsWlam$#0%;Vk9W7883NaA}NOzJWZp6M&ix3+aKnDTK`FvxgK+O}n zX z)QgW3aW(+5BJf8(2;LfY99*T=Dxy9y*FhAuX3qC9@ILTuD-madi{5M*O1~O&H08RH zE?Wt~KUGZ_@)t1c`^LaTtJdD`e&iM4lcZAV=aNa)1MX8A7fp>v# zwsKe34ZH^21fpxkvO%Me*LqkKv z<8c61S63h9Tw5O(;R&%FkR6a6knI8yK-=C?-$!(-o!DlBXy9Tlm%|u?F@{_&2XN6| z+x9;RS2CH5WHK26u5_?ubZmpb$94?zf3Uv+rZ!t=0C|Rj00000NkvXXu0mjfy{mSH 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/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/views/panels/mouseinfo.ejs b/client/views/panels/mouseinfo.ejs index 5127e4a4..c4434cc6 100644 --- a/client/views/panels/mouseinfo.ejs +++ b/client/views/panels/mouseinfo.ejs @@ -1,24 +1,28 @@
-
+
-
+
+ +
-
+
+ +
-
+
-
-
-
-
+
+
+
+
-
+
diff --git a/client/views/panels/navbar.ejs b/client/views/panels/navbar.ejs index 3ae2453a..922fe414 100644 --- a/client/views/panels/navbar.ejs +++ b/client/views/panels/navbar.ejs @@ -6,7 +6,7 @@

DCS Olympus

-
version v0.2.1
+
version v0.3.0
Discord @@ -15,7 +15,7 @@ Github
diff --git a/installer/olympus.iss b/installer/olympus.iss index dfc937f3..55664142 100644 --- a/installer/olympus.iss +++ b/installer/olympus.iss @@ -1,5 +1,5 @@ #define nwjsFolder "C:\Users\dpass\Documents\nwjs\" -#define version "v0.2.1-alpha" +#define version "v0.3.0-alpha" [Setup] AppName=DCS Olympus diff --git a/olympus.json b/olympus.json index 45d703ff..3618c236 100644 --- a/olympus.json +++ b/olympus.json @@ -1,6 +1,6 @@ { "server": { - "address": "136.243.170.132", + "address": "localhost", "port": 30000 }, "authentication": { diff --git a/scripts/OlympusCommand.lua b/scripts/OlympusCommand.lua index bd61f8d2..d137728c 100644 --- a/scripts/OlympusCommand.lua +++ b/scripts/OlympusCommand.lua @@ -1,4 +1,4 @@ -local version = "v0.2.1-alpha" +local version = "v0.3.0-alpha" local debug = true diff --git a/scripts/OlympusHook.lua b/scripts/OlympusHook.lua index 7215d3cd..b87c8926 100644 --- a/scripts/OlympusHook.lua +++ b/scripts/OlympusHook.lua @@ -1,4 +1,4 @@ -local version = 'v0.2.1-alpha' +local version = 'v0.3.0-alpha' Olympus = {} Olympus.OlympusDLL = nil From 6c52f4de5923cf97716bd9d3589798fe59ae122c Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Mon, 5 Jun 2023 17:30:37 +0200 Subject: [PATCH 2/2] Added front end for area attacks --- .../public/stylesheets/other/contextmenus.css | 8 ++- .../images/buttons/spawn/explosion.svg | 42 ++++++++++++++ client/src/controls/mapcontextmenu.ts | 15 +++-- client/src/map/map.ts | 57 +++++++++++++++---- client/src/server/server.ts | 37 ++++++++++-- client/src/units/unit.ts | 20 ++++++- client/src/units/unitsmanager.ts | 38 ++++++++++++- client/views/other/contextmenus.ejs | 8 +++ 8 files changed, 200 insertions(+), 25 deletions(-) create mode 100644 client/public/themes/olympus/images/buttons/spawn/explosion.svg 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/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/src/controls/mapcontextmenu.ts b/client/src/controls/mapcontextmenu.ts index 38cbba2c..863238a2 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"; @@ -38,6 +38,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 = 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)); @@ -68,9 +71,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(); } @@ -89,6 +94,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/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 2e8bf910..5393c1ad 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 { 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 540ea7ef..82512468 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -2,7 +2,7 @@ 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 { IDLE, UNIT_SELECTED } from "../map/map"; import { deg2rad, keyEventWasInInput, latLngToMercator, mercatorToLatLng } from "../other/utils"; export class UnitsManager { @@ -305,7 +305,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) { @@ -437,6 +437,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 */ @@ -459,7 +491,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"> +
@@ -81,6 +83,12 @@
+
+ + + + +