diff --git a/client/public/stylesheets/olympus.css b/client/public/stylesheets/olympus.css index 6e96c593..3570cffb 100644 --- a/client/public/stylesheets/olympus.css +++ b/client/public/stylesheets/olympus.css @@ -7,7 +7,7 @@ @import url("unitdatatable.css"); @import url("unitcontrolpanel.css"); @import url("unitinfopanel.css"); - +@import url("popup.css"); * { @@ -441,8 +441,6 @@ nav.ol-panel> :last-child { font-weight: var(--font-weight-bolder); } - - .hide { display: none !important; } diff --git a/client/public/stylesheets/popup.css b/client/public/stylesheets/popup.css new file mode 100644 index 00000000..fb514460 --- /dev/null +++ b/client/public/stylesheets/popup.css @@ -0,0 +1,23 @@ +#info-popup { + position: absolute; + width: fit-content; + height: fit-content; + top: 100px; + left: 50%; + translate: -50% 0%; + z-index: 9999; +} + +.ol-popup > div { + padding-left: 15px; + padding-right: 15px; +} + +.visible { + opacity: 1; +} + +.invisible { + opacity: 0; + transition: opacity 2s linear; +} \ No newline at end of file diff --git a/client/src/controls/unitcontextmenu.ts b/client/src/controls/unitcontextmenu.ts index ea27bcd0..aaa1b205 100644 --- a/client/src/controls/unitcontextmenu.ts +++ b/client/src/controls/unitcontextmenu.ts @@ -1,7 +1,9 @@ +import { getUnitsManager } from ".."; +import { deg2rad } from "../other/utils"; import { ContextMenu } from "./contextmenu"; export class UnitContextMenu extends ContextMenu { - #callback: CallableFunction | null = null; + #customFormationCallback: CallableFunction | null = null; constructor(id: string) { super(id); @@ -19,17 +21,28 @@ export class UnitContextMenu extends ContextMenu { clock++; } var angleDeg = 360 - (clock - 1) * 45; - var distance = parseInt(( dialog.querySelector(`#distance`)?.querySelector("input")).value); - var upDown = parseInt(( dialog.querySelector(`#up-down`)?.querySelector("input")).value); - var asd= 1; + var angleRad = deg2rad(angleDeg); + var distance = parseInt(( dialog.querySelector(`#distance`)?.querySelector("input")).value) * 0.3048; + var upDown = parseInt(( dialog.querySelector(`#up-down`)?.querySelector("input")).value) * 0.3048; - } + // X: front-rear, positive front + // Y: top-bottom, positive top + // Z: left-right, positive right - if (this.#callback) - this.#callback() + var x = distance * Math.cos(angleRad); + var y = upDown; + var z = distance * Math.sin(angleRad); + + if (this.#customFormationCallback) + this.#customFormationCallback({"x": x, "y": y, "z": z}) + } }) } + setCustomFormationCallback(callback: CallableFunction) { + this.#customFormationCallback = callback; + } + setOptions(options: {[key: string]: string}, callback: CallableFunction) { this.getContainer()?.replaceChildren(...Object.keys(options).map((option: string, idx: number) => diff --git a/client/src/index.ts b/client/src/index.ts index f95e4d5a..dda20f45 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -12,6 +12,7 @@ import { LogPanel } from "./panels/logpanel"; import { getAirbases, getBullseye as getBullseyes, getConfig, getMission, getUnits, setAddress, toggleDemoEnabled } from "./server/server"; import { UnitDataTable } from "./units/unitdatatable"; import { keyEventWasInInput } from "./other/utils"; +import { Popup } from "./popups/popup"; var map: Map; @@ -27,6 +28,8 @@ var unitControlPanel: UnitControlPanel; var mouseInfoPanel: MouseInfoPanel; var logPanel: LogPanel; +var infoPopup: Popup; + var connected: boolean = false; var paused: boolean = false; var activeCoalition: string = "blue"; @@ -52,6 +55,9 @@ function setup() { mouseInfoPanel = new MouseInfoPanel("mouse-info-panel"); //logPanel = new LogPanel("log-panel"); + /* Popups */ + infoPopup = new Popup("info-popup"); + unitDataTable = new UnitDataTable("unit-data-table"); /* AIC */ @@ -269,7 +275,9 @@ export function getActiveCoalition() { } export function setConnected(newConnected: boolean) { - connected = newConnected + if (connected != newConnected) + newConnected? getInfoPopup().setText("Connected to DCS Olympus server"): getInfoPopup().setText("Disconnected from DCS Olympus server"); + connected = newConnected; } export function getConnected() { @@ -278,10 +286,15 @@ export function getConnected() { export function setPaused(newPaused: boolean) { paused = newPaused; + paused? getInfoPopup().setText("Paused"): getInfoPopup().setText("Unpaused"); } export function getPaused() { return paused; } +export function getInfoPopup() { + return infoPopup; +} + window.onload = setup; \ No newline at end of file diff --git a/client/src/popups/popup.ts b/client/src/popups/popup.ts new file mode 100644 index 00000000..29fad207 --- /dev/null +++ b/client/src/popups/popup.ts @@ -0,0 +1,26 @@ +import { Panel } from "../panels/panel"; + +export class Popup extends Panel { + #fadeTime: number = 2000; // Milliseconds + #hideTimer: number | undefined = undefined; + #visibilityTimer: number | undefined = undefined; + + setFadeTime(fadeTime: number) { + this.#fadeTime = fadeTime; + } + + setText(text: string) { + ( this.getElement().querySelector("div")).innerText = text; + this.show(); + this.getElement().classList.remove("invisible"); + this.getElement().classList.add("visible"); + + clearTimeout(this.#visibilityTimer); + clearTimeout(this.#hideTimer); + this.#visibilityTimer = setTimeout(() => { + this.getElement().classList.remove("visible"); + this.getElement().classList.add("invisible"); + this.#hideTimer = setTimeout(() => this.hide(), 2000); + }, this.#fadeTime); + } +} \ No newline at end of file diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index f45390c6..4bd59997 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -476,6 +476,9 @@ export class Unit extends Marker { if (action === "Custom") { document.getElementById("custom-formation-dialog")?.classList.remove("hide"); + getMap().getUnitContextMenu().setCustomFormationCallback((offset: {x: number, y: number, z: number}) => { + getUnitsManager().selectedUnitsFollowUnit(this.ID, offset); + }) } else { // X: front-rear, positive front diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index bab60807..becb74c5 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -1,5 +1,5 @@ import { LatLng, LatLngBounds } from "leaflet"; -import { getMap, getUnitDataTable } from ".."; +import { getInfoPopup, getMap, getUnitDataTable } from ".."; import { Unit } from "./unit"; import { cloneUnit } from "../server/server"; import { IDLE, MOVE_UNIT } from "../map/map"; @@ -176,6 +176,7 @@ export class UnitsManager { var commandedUnit = selectedUnits[idx]; commandedUnit.addDestination(latlng); } + this.#showActionMessage(selectedUnits, " new destination added"); } selectedUnitsClearDestinations() { @@ -193,6 +194,7 @@ export class UnitsManager { { selectedUnits[idx].landAt(latlng); } + this.#showActionMessage(selectedUnits, " landing"); } selectedUnitsChangeSpeed(speedChange: string) @@ -220,6 +222,8 @@ export class UnitsManager { { selectedUnits[idx].setSpeed(speed); } + + this.#showActionMessage(selectedUnits, `setting speed to ${speed * 1.94384} kts`); } selectedUnitsSetAltitude(altitude: number) @@ -229,6 +233,7 @@ export class UnitsManager { { selectedUnits[idx].setAltitude(altitude); } + this.#showActionMessage(selectedUnits, `setting altitude to ${altitude / 0.3048} ft`); } selectedUnitsSetROE(ROE: string) @@ -238,6 +243,7 @@ export class UnitsManager { { selectedUnits[idx].setROE(ROE); } + this.#showActionMessage(selectedUnits, `ROE set to ${ROE}`); } selectedUnitsSetReactionToThreat(reactionToThreat: string) @@ -247,73 +253,15 @@ export class UnitsManager { { selectedUnits[idx].setReactionToThreat(reactionToThreat); } + this.#showActionMessage(selectedUnits, `reaction to threat set to ${reactionToThreat}`); } selectedUnitsAttackUnit(ID: number) { var selectedUnits = this.getSelectedUnits(); for (let idx in selectedUnits) { - /* If a unit is a wingman, send the command to its leader */ - var commandedUnit = selectedUnits[idx]; - //if (selectedUnits[idx].wingman) - //{ - // commandedUnit = this.getLeader(selectedUnits[idx].ID); - //} - commandedUnit.attackUnit(ID); - } - } - - selectedUnitsCreateFormation(ID: number | null = null) - { - var selectedUnits = this.getSelectedUnits(); - if (selectedUnits.length >= 2) - { - if (ID == null) - ID = selectedUnits[0].ID - - var wingmenIDs = []; - for (let idx in selectedUnits) - { - if (selectedUnits[idx].getFormationData().isWingman) - { - //console.log(selectedUnits[idx].unitName + " is already in a formation."); - return; - } - else if (selectedUnits[idx].getFormationData().isLeader) - { - //console.log(selectedUnits[idx].unitName + " is already in a formation."); - return; - } - else - { - /* TODO - if (selectedUnits[idx].category !== this.getUnitByID(ID).category) - { - showMessage("All units must be of the same category to create a formation."); - } - */ - if (selectedUnits[idx].ID != ID) - { - wingmenIDs.push(selectedUnits[idx].ID); - } - } - } - if (wingmenIDs.length > 0) - { - this.getUnitByID(ID)?.setLeader(true, wingmenIDs); - } - else - { - //console.log("At least 2 units must be selected to create a formation."); - } - } - } - - selectedUnitsUndoFormation() - { - for (let leader of this.getSelectedLeaders()) - { - leader.setLeader(false); + selectedUnits[idx].attackUnit(ID); } + this.#showActionMessage(selectedUnits, `attacking unit ${this.getUnitByID(ID)?.getBaseData().unitName}`); } selectedUnitsDelete() @@ -323,6 +271,7 @@ export class UnitsManager { { selectedUnits[idx].delete(); } + this.#showActionMessage(selectedUnits, `deleted`); } selectedUnitsRefuel() @@ -332,6 +281,7 @@ export class UnitsManager { { selectedUnits[idx].refuel(); } + this.#showActionMessage(selectedUnits, `sent to nearest tanker`); } selectedUnitsFollowUnit(ID: number, offset: {"x": number, "y": number, "z": number}) { @@ -342,11 +292,13 @@ export class UnitsManager { commandedUnit.followUnit(ID, {"x": offset.x * count, "y": offset.y * count, "z": offset.z * count} ); count++; } + this.#showActionMessage(selectedUnits, `following unit ${this.getUnitByID(ID)?.getBaseData().unitName}`); } copyUnits() { this.#copiedUnits = this.getSelectedUnits(); + this.#showActionMessage(this.#copiedUnits, `copied`); } pasteUnits() @@ -357,6 +309,7 @@ export class UnitsManager { { var unit = this.#copiedUnits[idx]; cloneUnit(unit.ID, getMap().getMouseCoordinates()); + this.#showActionMessage(this.#copiedUnits, `pasted`); } this.#pasteDisabled = true; setTimeout(() => this.#pasteDisabled = false, 250); @@ -398,4 +351,11 @@ export class UnitsManager { else document.dispatchEvent(new CustomEvent("unitsDeselection", {detail: this.getSelectedUnits()})); } + + #showActionMessage(units: Unit[], message: string) { + if (units.length == 1) + getInfoPopup().setText(`${units[0].getBaseData().unitName} ${message}`); + else + getInfoPopup().setText(`${units[0].getBaseData().unitName} and ${units.length - 1} other units ${message}`); + } } \ No newline at end of file diff --git a/client/views/dialogs.ejs b/client/views/dialogs.ejs index 9c641408..1f2981d1 100644 --- a/client/views/dialogs.ejs +++ b/client/views/dialogs.ejs @@ -17,8 +17,6 @@ - -
@@ -143,7 +141,7 @@
-
+
@@ -168,7 +166,7 @@
- +
diff --git a/client/views/index.ejs b/client/views/index.ejs index 79e15902..f23920bf 100644 --- a/client/views/index.ejs +++ b/client/views/index.ejs @@ -33,6 +33,7 @@ <%- include('connectionstatuspanel.ejs') %> <%- include('dialogs.ejs') %> <%- include('unitdatatable.ejs') %> + <%- include('popups.ejs') %> <% /* %> <%- include('log.ejs') %> diff --git a/client/views/popups.ejs b/client/views/popups.ejs new file mode 100644 index 00000000..80043521 --- /dev/null +++ b/client/views/popups.ejs @@ -0,0 +1,5 @@ +
+
+ +
+
\ No newline at end of file