From 493c75b6e781cb7e2252800358565e695a3b375f Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Mon, 15 May 2023 17:37:20 +0200 Subject: [PATCH 1/2] Added hotgroups ui and basic functions User interaction still TBD --- client/public/stylesheets/olympus.css | 33 ++++++++++ client/public/stylesheets/units.css | 93 +++++++++++---------------- client/src/index.ts | 23 ++++++- client/src/panels/hotgrouppanel.ts | 25 +++++++ client/src/units/unit.ts | 35 +++++++--- client/src/units/unitsmanager.ts | 22 ++++++- client/views/hotgrouppanel.ejs | 3 + client/views/index.ejs | 2 +- 8 files changed, 167 insertions(+), 69 deletions(-) create mode 100644 client/src/panels/hotgrouppanel.ts create mode 100644 client/views/hotgrouppanel.ejs diff --git a/client/public/stylesheets/olympus.css b/client/public/stylesheets/olympus.css index d7905e9b..15a5578c 100644 --- a/client/public/stylesheets/olympus.css +++ b/client/public/stylesheets/olympus.css @@ -856,4 +856,37 @@ body[data-hide-navyunit] #unit-visibility-control-navyunit { 50% { opacity: 0; } +} + +#hotgroup-panel { + position: absolute; + bottom: 40px; + left: 50%; + translate: -50%; + z-index: 9999; + display: flex; + column-gap: 10px; +} + +#hotgroup-panel>div { + width: 50px; + height: 50px; + background-color: var(--background-steel); + border-radius: var(--border-radius-sm); + color: white; + display: flex; + align-items: center; + justify-content: center; + font-weight: bold; + border: 0px solid transparent; +} + +#hotgroup-panel>div:hover { + cursor: pointer; + border: 2px solid white; +} + +.hotgroup-selector>.unit-hotgroup { + display: flex; + translate: 0% -300%; } \ No newline at end of file diff --git a/client/public/stylesheets/units.css b/client/public/stylesheets/units.css index 0142c055..0499468d 100644 --- a/client/public/stylesheets/units.css +++ b/client/public/stylesheets/units.css @@ -3,7 +3,7 @@ --unit-centre-x: calc(var(--unit-width) / 2); --unit-centre-y: calc(var(--unit-height) / 2); - --unit-hotgroup-height: 10px; + --unit-hotgroup-height: 15px; --unit-hotgroup-width: var(--unit-hotgroup-height); @@ -23,7 +23,7 @@ width: 100%; } -[data-object|="unit"] .unit-selected-spotlight { +.unit-selected-spotlight { background-color: var(--unit-spotlight-fill); border-radius: 50%; display: none; @@ -32,8 +32,7 @@ z-index: 1; } - -[data-object|="unit"] .unit-vvi { +.unit-vvi { align-self: center; background: var(--secondary-gunmetal-grey); display: flex; @@ -46,30 +45,7 @@ z-index: 3; } - -[data-object|="unit"] .unit-hotgroup { - align-content: center; - background-color: black; - border-radius: var(--border-radius-xs); - display: none; - height: var(--unit-hotgroup-height); - justify-content: center; - position: absolute; - transform: rotate(-45deg); - translate: 0 -275%; - width: var(--unit-hotgroup-width); - z-index: 5; -} - -[data-object|="unit"] .unit-hotgroup-id { - background-color: transparent; - color: white; - font-size: 9px; - font-weight: bolder; - transform: rotate(45deg); -} - -[data-object|="unit"] .unit-marker-border { +.unit-marker-border { border-radius: var(--border-radius-sm); display: none; height: calc(var(--unit-aircraft-height) + (var(--unit-label-border-width) * 2)); @@ -78,6 +54,29 @@ z-index: 2; } +.unit-hotgroup { + align-content: center; + background-color: var(--background-steel); + border-radius: var(--border-radius-xs); + display: none; + height: var(--unit-hotgroup-height); + justify-content: center; + position: absolute; + transform: rotate(-45deg); + translate: 0 -200%; + width: var(--unit-hotgroup-width); + z-index: 5; +} + +.unit-hotgroup-id { + background-color: transparent; + color: white; + font-size: 9px; + font-weight: bolder; + transform: rotate(45deg); + translate: -1px 1px; +} + /****************************** Marker @@ -92,8 +91,6 @@ z-index: 3; } - - /* Air */ [data-object|="unit-aircraft"] .unit-marker { @@ -110,7 +107,6 @@ background-image: var(--unit-aircraft-marker-neutral-selected-url); } - [data-object|="unit-aircraft"][data-coalition="blue"] .unit-marker { background-image: var(--unit-aircraft-marker-blue-url); } @@ -136,9 +132,6 @@ background-image: var(--unit-aircraft-marker-red-selected-url); } - - - /* Ground vehicles (not SAMs) */ [data-object|="unit-groundunit"] .unit-marker { @@ -279,7 +272,6 @@ /* Building */ - [data-object|="unit-building"] .unit-marker { background-image: var(--unit-building-marker-neutral-url); height: var(--unit-building-marker-height); @@ -296,8 +288,6 @@ background-image: var(--unit-building-marker-red-url); } - - /* Weapons */ [data-object|="unit-missile"], @@ -439,8 +429,6 @@ overflow: visible; } - - [data-object|="unit"]:hover .unit-ammo, [data-object|="unit"]:hover .unit-fuel { display: flex; @@ -460,9 +448,6 @@ background-color: var(--secondary-gunmetal-grey); } - - - [data-object|="unit"][data-coalition="blue"][data-is-selected] .unit-short-label { color: var(--secondary-blue-text); } @@ -496,8 +481,6 @@ background-color: var(--secondary-red-outline); } - - @keyframes pulse { 50% { opacity: 0; @@ -508,7 +491,6 @@ animation: pulse 1.5s linear infinite; } - [data-object|="unit"] .unit-state { background-repeat: no-repeat; position: absolute; @@ -569,20 +551,19 @@ } -[data-object|="unit-aircraft"][ data-is-dead] .unit-selected-spotlight, -[data-object|="unit-aircraft"][ data-is-dead] .unit-short-label, -[data-object|="unit-aircraft"][ data-is-dead] .unit-vvi, -[data-object|="unit-aircraft"][ data-is-dead] .unit-hotgroup, -[data-object|="unit-aircraft"][ data-is-dead] .unit-hotgroup-id, -[data-object|="unit-aircraft"][ data-is-dead] .unit-state, -[data-object|="unit-aircraft"][ data-is-dead] .unit-fuel, -[data-object|="unit-aircraft"][ data-is-dead] .unit-ammo, -[data-object|="unit-aircraft"][ data-is-dead]:hover .unit-fuel, -[data-object|="unit-aircraft"][ data-is-dead]:hover .unit-ammo { +[data-object|="unit-aircraft"][data-is-dead] .unit-selected-spotlight, +[data-object|="unit-aircraft"][data-is-dead] .unit-short-label, +[data-object|="unit-aircraft"][data-is-dead] .unit-vvi, +[data-object|="unit-aircraft"][data-is-dead] .unit-hotgroup, +[data-object|="unit-aircraft"][data-is-dead] .unit-hotgroup-id, +[data-object|="unit-aircraft"][data-is-dead] .unit-state, +[data-object|="unit-aircraft"][data-is-dead] .unit-fuel, +[data-object|="unit-aircraft"][data-is-dead] .unit-ammo, +[data-object|="unit-aircraft"][data-is-dead]:hover .unit-fuel, +[data-object|="unit-aircraft"][data-is-dead]:hover .unit-ammo { display: none !important; } - [data-object|="unit-aircraft"][ data-is-dead] .unit-summary>* { display: none; } diff --git a/client/src/index.ts b/client/src/index.ts index b3e7e63e..b4dbee16 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -9,11 +9,12 @@ import { AIC } from "./aic/aic"; import { ATC } from "./atc/atc"; import { FeatureSwitches } from "./featureswitches"; import { LogPanel } from "./panels/logpanel"; -import { getAirbases, getBullseye, getConfig, getFreezed, getMission, getUnits, setAddress, setCredentials, setFreezed, startUpdate, toggleDemoEnabled } from "./server/server"; +import { getConfig, getFreezed, setAddress, setCredentials, setFreezed, startUpdate, toggleDemoEnabled } from "./server/server"; import { UnitDataTable } from "./units/unitdatatable"; import { keyEventWasInInput } from "./other/utils"; import { Popup } from "./popups/popup"; import { Dropdown } from "./controls/dropdown"; +import { HotgroupPanel } from "./panels/hotgrouppanel"; var map: Map; @@ -28,6 +29,7 @@ var connectionStatusPanel: ConnectionStatusPanel; var unitControlPanel: UnitControlPanel; var mouseInfoPanel: MouseInfoPanel; var logPanel: LogPanel; +var hotgroupPanel: HotgroupPanel; var infoPopup: Popup; @@ -50,6 +52,7 @@ function setup() { unitControlPanel = new UnitControlPanel("unit-control-panel"); connectionStatusPanel = new ConnectionStatusPanel("connection-status-panel"); mouseInfoPanel = new MouseInfoPanel("mouse-info-panel"); + hotgroupPanel = new HotgroupPanel("hotgroup-panel"); //logPanel = new LogPanel("log-panel"); /* Popups */ @@ -147,6 +150,20 @@ function setupEvents() { case "ArrowDown": getMap().handleMapPanning(ev); break; + case "Digit1": + case "Digit2": + case "Digit3": + case "Digit4": + case "Digit5": + case "Digit6": + case "Digit7": + case "Digit8": + case "Digit9": + if (ev.ctrlKey) + getUnitsManager().selectedUnitsAddToHotgroup(parseInt(ev.key)); + else + getUnitsManager().selectUnitsByHotgroup(parseInt(ev.key)); + break; } }); @@ -232,6 +249,10 @@ export function getConnectionStatusPanel() { return connectionStatusPanel; } +export function getHotgroupPanel() { + return hotgroupPanel; +} + export function setActiveCoalition(newActiveCoalition: string) { activeCoalition = newActiveCoalition; document.querySelectorAll('[data-active-coalition]').forEach((element: any) => { element.setAttribute("data-active-coalition", activeCoalition) }); diff --git a/client/src/panels/hotgrouppanel.ts b/client/src/panels/hotgrouppanel.ts new file mode 100644 index 00000000..328ef81d --- /dev/null +++ b/client/src/panels/hotgrouppanel.ts @@ -0,0 +1,25 @@ +import { getUnitsManager } from ".."; +import { Panel } from "./panel"; + +export class HotgroupPanel extends Panel { + addHotgroup(hotgroup: number) { + this.removeHotgroup(hotgroup); + const hotgroupHtml = `
+
${hotgroup}
+
+ x${getUnitsManager().getUnitsByHotgroup(hotgroup).length}` + var el = document.createElement("div"); + el.classList.add("hotgroup-selector"); + el.innerHTML = hotgroupHtml; + el.toggleAttribute(`data-hotgroup-${hotgroup}`, true) + this.getElement().appendChild(el); + el.addEventListener("click", () => { + getUnitsManager().selectUnitsByHotgroup(hotgroup); + }); + } + + removeHotgroup(hotgroup: number) { + const el = this.getElement().querySelector(`[data-hotgroup-${hotgroup}]`) as HTMLElement; + if (el) el.remove(); + } +} \ No newline at end of file diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index 82d10681..6d3d1709 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -77,6 +77,8 @@ export class Unit extends Marker { #timer: number = 0; + #hotgroup: number | null = null; + static getConstructor(type: string) { if (type === "GroundUnit") return GroundUnit; if (type === "Aircraft") return Aircraft; @@ -265,6 +267,14 @@ export class Unit extends Marker { return this.#selectable; } + setHotgroup(hotgroup: number | null) { + this.#hotgroup = hotgroup; + } + + getHotgroup() { + return this.#hotgroup; + } + /********************** Visibility *************************/ updateVisibility() { @@ -556,7 +566,6 @@ export class Unit extends Marker { }); /* Turn on ordnance indicators */ - var hasFox1 = element.querySelector(".unit")?.hasAttribute("data-has-fox-1"); var hasFox2 = element.querySelector(".unit")?.hasAttribute("data-has-fox-2"); var hasFox3 = element.querySelector(".unit")?.hasAttribute("data-has-fox-3"); @@ -568,25 +577,30 @@ export class Unit extends Marker { var newHasOtherAmmo = false; Object.values(this.getMissionData().ammo).forEach((ammo: any) => { if (ammo.desc.category == 1 && ammo.desc.missileCategory == 1) { - if (ammo.desc.guidance == 4 || ammo.desc.guidance == 5) { + if (ammo.desc.guidance == 4 || ammo.desc.guidance == 5) newHasFox1 = true; - } - else if (ammo.desc.guidance == 2) { + else if (ammo.desc.guidance == 2) newHasFox2 = true; - } - else if (ammo.desc.guidance == 3) { + else if (ammo.desc.guidance == 3) newHasFox3 = true; - } } - else { + else newHasOtherAmmo = true; - } }); if (hasFox1 != newHasFox1) element.querySelector(".unit")?.toggleAttribute("data-has-fox-1", newHasFox1); if (hasFox2 != newHasFox2) element.querySelector(".unit")?.toggleAttribute("data-has-fox-2", newHasFox2); if (hasFox3 != newHasFox3) element.querySelector(".unit")?.toggleAttribute("data-has-fox-3", newHasFox3); if (hasOtherAmmo != newHasOtherAmmo) element.querySelector(".unit")?.toggleAttribute("data-has-other-ammo", newHasOtherAmmo); + + /* Draw the hotgroup element */ + if (this.#hotgroup) { + element.querySelector(".unit")?.toggleAttribute("data-is-in-hotgroup", this.#hotgroup != null); + const hotgroupEl = element.querySelector(".unit-hotgroup-id") as HTMLElement; + if (hotgroupEl) + hotgroupEl.innerText = String(this.#hotgroup); + } + } /* Set vertical offset for altitude stacking */ @@ -732,6 +746,9 @@ export class GroundUnit extends Unit {
${role?.substring(0, 1)?.toUpperCase() || ""}
+
+
+
` } diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index 62ac5db6..2018b4d4 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -1,5 +1,5 @@ import { LatLng, LatLngBounds } from "leaflet"; -import { getInfoPopup, getMap, getUnitDataTable } from ".."; +import { getHotgroupPanel, getInfoPopup, getMap, getUnitDataTable } from ".."; import { Unit } from "./unit"; import { cloneUnit } from "../server/server"; import { IDLE, MOVE_UNIT } from "../map/map"; @@ -45,6 +45,10 @@ export class UnitsManager { return null; } + getUnitsByHotgroup(hotgroup: number) { + return Object.values(this.#units).filter((unit: Unit) => {return unit.getHotgroup() == hotgroup}); + } + addUnit(ID: number, data: UnitData) { /* The name of the unit category is exactly the same as the constructor name */ var constructor = Unit.getConstructor(data.baseData.category); @@ -112,6 +116,10 @@ export class UnitsManager { } } + selectUnitsByHotgroup(hotgroup: number) { + this.getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setSelected(true)) + } + getSelectedUnitsType() { if (this.getSelectedUnits().length == 0) return undefined; @@ -259,7 +267,7 @@ export class UnitsManager { } selectedUnitsRefuel() { - var selectedUnits = this.getSelectedUnits({excludeHumans: true}); + var selectedUnits = this.getSelectedUnits({excludeHumans: true}); for (let idx in selectedUnits) { selectedUnits[idx].refuel(); } @@ -309,6 +317,16 @@ export class UnitsManager { this.#showActionMessage(selectedUnits, `following unit ${this.getUnitByID(ID)?.getBaseData().unitName}`); } + selectedUnitsAddToHotgroup(hotgroup: number) + { + var selectedUnits = this.getSelectedUnits(); + for (let idx in selectedUnits) { + selectedUnits[idx].setHotgroup(hotgroup); + } + this.#showActionMessage(selectedUnits, `added to hotgroup ${hotgroup}`); + getHotgroupPanel().addHotgroup(hotgroup); + } + /***********************************************/ copyUnits() { this.#copiedUnits = this.getSelectedUnits(); /* Can be applied to humans too */ diff --git a/client/views/hotgrouppanel.ejs b/client/views/hotgrouppanel.ejs new file mode 100644 index 00000000..f8d065cc --- /dev/null +++ b/client/views/hotgrouppanel.ejs @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file diff --git a/client/views/index.ejs b/client/views/index.ejs index ae159a9d..afb19752 100644 --- a/client/views/index.ejs +++ b/client/views/index.ejs @@ -24,7 +24,6 @@
<%- include('aic.ejs') %> <%- include('atc.ejs') %> - <%- include('contextmenus.ejs') %> <%- include('unitcontrolpanel.ejs') %> <%- include('unitinfopanel.ejs') %> @@ -34,6 +33,7 @@ <%- include('dialogs.ejs') %> <%- include('unitdatatable.ejs') %> <%- include('popups.ejs') %> + <%- include('hotgrouppanel.ejs') %>
From 25d411b1be6a100c78155ef06c291971c613eeb6 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Wed, 17 May 2023 11:43:14 +0200 Subject: [PATCH 2/2] Completed hotgroups Single hotgroup per unit --- client/public/stylesheets/units.css | 24 ++-- client/src/@types/dom.d.ts | 3 +- client/src/index.ts | 13 ++- client/src/panels/hotgrouppanel.ts | 25 +++- client/src/server/server.ts | 16 +-- client/src/units/unit.ts | 172 +++++++++++++--------------- client/src/units/unitsmanager.ts | 12 +- client/views/hotgrouppanel.ejs | 2 +- 8 files changed, 146 insertions(+), 121 deletions(-) diff --git a/client/public/stylesheets/units.css b/client/public/stylesheets/units.css index 0499468d..b48389d6 100644 --- a/client/public/stylesheets/units.css +++ b/client/public/stylesheets/units.css @@ -99,7 +99,7 @@ width: var(--unit-aircraft-marker-width); } -[data-object|="unit-aircraft"]:hover .unit-marker { +[data-object|="unit-aircraft"][data-is-highlighted] .unit-marker { background-image: var(--unit-aircraft-marker-neutral-hover-url); } @@ -111,7 +111,7 @@ background-image: var(--unit-aircraft-marker-blue-url); } -[data-object|="unit-aircraft"][data-coalition="blue"]:hover .unit-marker { +[data-object|="unit-aircraft"][data-coalition="blue"][data-is-highlighted] .unit-marker { background-image: var(--unit-aircraft-marker-blue-hover-url); } @@ -124,7 +124,7 @@ background-image: var(--unit-aircraft-marker-red-url); } -[data-object|="unit-aircraft"][data-coalition="red"]:hover .unit-marker { +[data-object|="unit-aircraft"][data-coalition="red"][data-is-highlighted] .unit-marker { background-image: var(--unit-aircraft-marker-red-hover-url); } @@ -140,7 +140,7 @@ width: var(--unit-groundunit-marker-width); } -[data-object|="unit-groundunit"]:hover .unit-marker { +[data-object|="unit-groundunit"][data-is-highlighted] .unit-marker { background-image: var(--unit-groundunit-marker-neutral-hover-url); } @@ -153,7 +153,7 @@ background-image: var(--unit-groundunit-marker-blue-url); } -[data-object|="unit-groundunit"][data-coalition="blue"]:hover .unit-marker { +[data-object|="unit-groundunit"][data-coalition="blue"][data-is-highlighted] .unit-marker { background-image: var(--unit-groundunit-marker-blue-hover-url); } @@ -166,7 +166,7 @@ background-image: var(--unit-groundunit-marker-red-url); } -[data-object|="unit-groundunit"][data-coalition="red"]:hover .unit-marker { +[data-object|="unit-groundunit"][data-coalition="red"][data-is-highlighted] .unit-marker { background-image: var(--unit-groundunit-marker-red-hover-url); } @@ -188,7 +188,7 @@ } -[data-object|="unit-sam"]:hover .unit-marker { +[data-object|="unit-sam"][data-is-highlighted] .unit-marker { background-image: var(--unit-sam-marker-neutral-hover-url); } @@ -201,7 +201,7 @@ background-image: var(--unit-sam-marker-blue-url); } -[data-object|="unit-sam"][data-coalition="blue"]:hover .unit-marker { +[data-object|="unit-sam"][data-coalition="blue"][data-is-highlighted] .unit-marker { background-image: var(--unit-sam-marker-blue-hover-url); } @@ -214,7 +214,7 @@ background-image: var(--unit-sam-marker-red-url); } -[data-object|="unit-sam"][data-coalition="red"]:hover .unit-marker { +[data-object|="unit-sam"][data-coalition="red"][data-is-highlighted] .unit-marker { background-image: var(--unit-sam-marker-red-hover-url); } @@ -236,7 +236,7 @@ } -[data-object|="unit-navyunit"]:hover .unit-marker { +[data-object|="unit-navyunit"][data-is-highlighted] .unit-marker { background-image: var(--unit-navyunit-marker-neutral-hover-url); } @@ -249,7 +249,7 @@ background-image: var(--unit-navyunit-marker-blue-url); } -[data-object|="unit-navyunit"][data-coalition="blue"]:hover .unit-marker { +[data-object|="unit-navyunit"][data-coalition="blue"][data-is-highlighted] .unit-marker { background-image: var(--unit-navyunit-marker-blue-hover-url); } @@ -262,7 +262,7 @@ background-image: var(--unit-navyunit-marker-red-url); } -[data-object|="unit-navyunit"][data-coalition="red"]:hover .unit-marker { +[data-object|="unit-navyunit"][data-coalition="red"][data-is-highlighted] .unit-marker { background-image: var(--unit-navyunit-marker-red-hover-url); } diff --git a/client/src/@types/dom.d.ts b/client/src/@types/dom.d.ts index 094d3444..3feba2b0 100644 --- a/client/src/@types/dom.d.ts +++ b/client/src/@types/dom.d.ts @@ -5,7 +5,8 @@ interface CustomEventMap { "unitsDeselection": CustomEvent, "clearSelection": CustomEvent<>, "unitCreation": CustomEvent, - "unitDeletion": CustomEvent, + "unitDeletion": CustomEvent, + "unitDeath": CustomEvent, "unitUpdated": CustomEvent, "unitMoveCommand": CustomEvent, "unitAttackCommand": CustomEvent, diff --git a/client/src/index.ts b/client/src/index.ts index b4dbee16..86f0057a 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -9,7 +9,7 @@ import { AIC } from "./aic/aic"; import { ATC } from "./atc/atc"; import { FeatureSwitches } from "./featureswitches"; import { LogPanel } from "./panels/logpanel"; -import { getConfig, getFreezed, setAddress, setCredentials, setFreezed, startUpdate, toggleDemoEnabled } from "./server/server"; +import { getConfig, getPaused, setAddress, setCredentials, setPaused, startUpdate, toggleDemoEnabled } from "./server/server"; import { UnitDataTable } from "./units/unitdatatable"; import { keyEventWasInInput } from "./other/utils"; import { Popup } from "./popups/popup"; @@ -138,7 +138,7 @@ function setupEvents() { unitDataTable.toggle(); break case "Space": - setFreezed(!getFreezed()); + setPaused(!getPaused()); break; case "KeyW": case "KeyA": @@ -159,10 +159,13 @@ function setupEvents() { case "Digit7": case "Digit8": case "Digit9": - if (ev.ctrlKey) - getUnitsManager().selectedUnitsAddToHotgroup(parseInt(ev.key)); + // Using the substring because the key will be invalid when pressing the Shift key + if (ev.ctrlKey && ev.shiftKey) + getUnitsManager().selectedUnitsAddToHotgroup(parseInt(ev.code.substring(5))); + else if (ev.ctrlKey && !ev.shiftKey) + getUnitsManager().selectedUnitsSetHotgroup(parseInt(ev.code.substring(5))); else - getUnitsManager().selectUnitsByHotgroup(parseInt(ev.key)); + getUnitsManager().selectUnitsByHotgroup(parseInt(ev.code.substring(5))); break; } }); diff --git a/client/src/panels/hotgrouppanel.ts b/client/src/panels/hotgrouppanel.ts index 328ef81d..8431c2ab 100644 --- a/client/src/panels/hotgrouppanel.ts +++ b/client/src/panels/hotgrouppanel.ts @@ -1,9 +1,23 @@ import { getUnitsManager } from ".."; +import { Unit } from "../units/unit"; import { Panel } from "./panel"; export class HotgroupPanel extends Panel { + constructor(ID: string) { + super(ID); + document.addEventListener("unitDeath", () => this.refreshHotgroups()); + } + + refreshHotgroups() { + for (let hotgroup = 1; hotgroup <= 9; hotgroup++){ + this.removeHotgroup(hotgroup); + if (getUnitsManager().getUnitsByHotgroup(hotgroup).length > 0) + this.addHotgroup(hotgroup); + + } + } + addHotgroup(hotgroup: number) { - this.removeHotgroup(hotgroup); const hotgroupHtml = `
${hotgroup}
@@ -13,9 +27,18 @@ export class HotgroupPanel extends Panel { el.innerHTML = hotgroupHtml; el.toggleAttribute(`data-hotgroup-${hotgroup}`, true) this.getElement().appendChild(el); + el.addEventListener("click", () => { getUnitsManager().selectUnitsByHotgroup(hotgroup); }); + + el.addEventListener("mouseover", () => { + getUnitsManager().getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setHighlighted(true)); + }); + + el.addEventListener("mouseout", () => { + getUnitsManager().getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setHighlighted(false)); + }); } removeHotgroup(hotgroup: number) { diff --git a/client/src/server/server.ts b/client/src/server/server.ts index 92c5b2d2..24ccf144 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -3,7 +3,7 @@ import { getConnectionStatusPanel, getInfoPopup, getMissionData, getUnitDataTabl import { SpawnOptions } from '../controls/mapcontextmenu'; var connected: boolean = false; -var freezed: boolean = false; +var paused: boolean = false; var REST_ADDRESS = "http://localhost:30000/olympus"; var DEMO_ADDRESS = window.location.href + "demo"; @@ -246,7 +246,7 @@ export function startUpdate() { export function requestUpdate() { /* Main update rate = 250ms is minimum time, equal to server update time. */ getUnits((data: UnitsData) => { - if (!getFreezed()) { + if (!getPaused()) { getUnitsManager()?.update(data); checkSessionHash(data.sessionHash); } @@ -259,7 +259,7 @@ export function requestUpdate() { export function requestRefresh() { /* Main refresh rate = 5000ms. */ getUnits((data: UnitsData) => { - if (!getFreezed()) { + if (!getPaused()) { getUnitsManager()?.update(data); getAirbases((data: AirbasesData) => getMissionData()?.update(data)); getBullseye((data: BullseyesData) => getMissionData()?.update(data)); @@ -300,11 +300,11 @@ export function getConnected() { return connected; } -export function setFreezed(newFreezed: boolean) { - freezed = newFreezed; - freezed ? getInfoPopup().setText("Freezed") : getInfoPopup().setText("Unfreezed"); +export function setPaused(newPaused: boolean) { + paused = newPaused; + paused ? getInfoPopup().setText("View paused") : getInfoPopup().setText("View unpaused"); } -export function getFreezed() { - return freezed; +export function getPaused() { + return paused; } \ No newline at end of file diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index 6d3d1709..e8b64881 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -65,8 +65,8 @@ export class Unit extends Marker { #selectable: boolean; #selected: boolean = false; - #hovered: boolean = false; #hidden: boolean = false; + #highlighted: boolean = false; #preventClick: boolean = false; @@ -94,24 +94,24 @@ export class Unit extends Marker { this.ID = ID; this.#selectable = true; - + this.on('click', (e) => this.#onClick(e)); this.on('dblclick', (e) => this.#onDoubleClick(e)); this.on('contextmenu', (e) => this.#onContextMenu(e)); - this.on('mouseover', () => { this.#hovered = true;}) - this.on('mouseout', () => { this.#hovered = false;}) - + this.on('mouseover', () => { this.setHighlighted(true); }) + this.on('mouseout', () => { this.setHighlighted(false); }) + this.#pathPolyline = new Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1 }); this.#pathPolyline.addTo(getMap()); this.#targetsPolylines = []; /* Deselect units if they are hidden */ document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => { - window.setTimeout(() => {this.setSelected(this.getSelected() && !this.getHidden())}, 300); + window.setTimeout(() => { this.setSelected(this.getSelected() && !this.getHidden()) }, 300); }); - + document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => { - window.setTimeout(() => {this.setSelected(this.getSelected() && !this.getHidden())}, 300); + window.setTimeout(() => { this.setSelected(this.getSelected() && !this.getHidden()) }, 300); }); /* Set the unit data */ @@ -128,15 +128,14 @@ export class Unit extends Marker { } getMarkerHTML() { - return `
+ return `
` } - getMarkerCategory() - { + getMarkerCategory() { // Overloaded by child classes return ""; } @@ -146,58 +145,56 @@ export class Unit extends Marker { setData(data: UpdateData) { /* Check if data has changed comparing new values to old values */ const positionChanged = (data.flightData != undefined && data.flightData.latitude != undefined && data.flightData.longitude != undefined && (this.getFlightData().latitude != data.flightData.latitude || this.getFlightData().longitude != data.flightData.longitude)); - const headingChanged = (data.flightData != undefined && data.flightData.heading != undefined && this.getFlightData().heading != data.flightData.heading); - const aliveChanged = (data.baseData != undefined && data.baseData.alive != undefined && this.getBaseData().alive != data.baseData.alive); - var updateMarker = (positionChanged || headingChanged || aliveChanged || !getMap().hasLayer(this)) - - if (data.baseData != undefined) - { + const headingChanged = (data.flightData != undefined && data.flightData.heading != undefined && this.getFlightData().heading != data.flightData.heading); + const aliveChanged = (data.baseData != undefined && data.baseData.alive != undefined && this.getBaseData().alive != data.baseData.alive); + var updateMarker = (positionChanged || headingChanged || aliveChanged || !getMap().hasLayer(this)); + + if (data.baseData != undefined) { for (let key in this.#data.baseData) if (key in data.baseData) //@ts-ignore this.#data.baseData[key] = data.baseData[key]; } - if (data.flightData != undefined) - { + if (data.flightData != undefined) { for (let key in this.#data.flightData) if (key in data.flightData) //@ts-ignore this.#data.flightData[key] = data.flightData[key]; } - if (data.missionData != undefined) - { + if (data.missionData != undefined) { for (let key in this.#data.missionData) if (key in data.missionData) //@ts-ignore this.#data.missionData[key] = data.missionData[key]; } - if (data.formationData != undefined) - { + if (data.formationData != undefined) { for (let key in this.#data.formationData) if (key in data.formationData) //@ts-ignore this.#data.formationData[key] = data.formationData[key]; } - if (data.taskData != undefined) - { + if (data.taskData != undefined) { for (let key in this.#data.taskData) if (key in data.taskData) //@ts-ignore this.#data.taskData[key] = data.taskData[key]; } - if (data.optionsData != undefined) - { + if (data.optionsData != undefined) { for (let key in this.#data.optionsData) if (key in data.optionsData) //@ts-ignore this.#data.optionsData[key] = data.optionsData[key]; } + /* Fire an event when a unit dies */ + if (aliveChanged && this.getBaseData().alive == false) + document.dispatchEvent(new CustomEvent("unitDeath", { detail: this })); + /* Dead units can't be selected */ this.setSelected(this.getSelected() && this.getBaseData().alive && !this.getHidden()) @@ -275,17 +272,24 @@ export class Unit extends Marker { return this.#hotgroup; } - /********************** Visibility *************************/ - updateVisibility() - { - this.setHidden( document.body.getAttribute(`data-hide-${this.getMissionData().coalition}`) != null || - document.body.getAttribute(`data-hide-${this.getMarkerCategory()}`) != null || - !this.getBaseData().alive) + setHighlighted(highlighted: boolean) { + this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-highlighted", highlighted); + this.#highlighted = highlighted; } - setHidden(hidden: boolean) - { - this.#hidden = hidden; + getHighlighted() { + return this.#highlighted; + } + + /********************** Visibility *************************/ + updateVisibility() { + this.setHidden(document.body.getAttribute(`data-hide-${this.getMissionData().coalition}`) != null || + document.body.getAttribute(`data-hide-${this.getMarkerCategory()}`) != null || + !this.getBaseData().alive) + } + + setHidden(hidden: boolean) { + this.#hidden = hidden; /* Add the marker if not present */ if (!getMap().hasLayer(this) && !this.getHidden()) { @@ -295,7 +299,7 @@ export class Unit extends Marker { /* Hide the marker if necessary*/ if (getMap().hasLayer(this) && this.getHidden()) { getMap().removeLayer(this); - } + } } getHidden() { @@ -329,15 +333,15 @@ export class Unit extends Marker { attackUnit(targetID: number) { /* Units can't attack themselves */ if (!this.getMissionData().flags.Human) - if (this.ID != targetID) + if (this.ID != targetID) attackUnit(this.ID, targetID); } - followUnit(targetID: number, offset: {"x": number, "y": number, "z": number}) { + followUnit(targetID: number, offset: { "x": number, "y": number, "z": number }) { /* Units can't follow themselves */ if (!this.getMissionData().flags.Human) - if (this.ID != targetID) - followUnit(this.ID, targetID, offset); + if (this.ID != targetID) + followUnit(this.ID, targetID, offset); } landAt(latlng: LatLng) { @@ -424,26 +428,22 @@ export class Unit extends Marker { } #onContextMenu(e: any) { - var options: {[key: string]: string} = {}; + var options: { [key: string]: string } = {}; - options["Center"] = `
Center map
`; + options["Center"] = `
Center map
`; - if (getUnitsManager().getSelectedUnits().length > 0 && !(getUnitsManager().getSelectedUnits().length == 1 && (getUnitsManager().getSelectedUnits().includes(this)))) - { + if (getUnitsManager().getSelectedUnits().length > 0 && !(getUnitsManager().getSelectedUnits().length == 1 && (getUnitsManager().getSelectedUnits().includes(this)))) { options['Attack'] = `
Attack
`; if (getUnitsManager().getSelectedUnitsType() === "Aircraft") options['Follow'] = `
Follow
`; } - else if ((getUnitsManager().getSelectedUnits().length > 0 && (getUnitsManager().getSelectedUnits().includes(this))) || getUnitsManager().getSelectedUnits().length == 0) - { - if (this.getBaseData().category == "Aircraft") - { + else if ((getUnitsManager().getSelectedUnits().length > 0 && (getUnitsManager().getSelectedUnits().includes(this))) || getUnitsManager().getSelectedUnits().length == 0) { + if (this.getBaseData().category == "Aircraft") { options["Refuel"] = `
Refuel
`; // TODO Add some way of knowing which aircraft can AAR } } - if (Object.keys(options).length > 0) - { + if (Object.keys(options).length > 0) { getMap().showUnitContextMenu(e); getMap().getUnitContextMenu().setOptions(options, (option: string) => { getMap().hideUnitContextMenu(); @@ -464,7 +464,7 @@ export class Unit extends Marker { } #showFollowOptions(e: any) { - var options: {[key: string]: string} = {}; + var options: { [key: string]: string } = {}; options = { 'Trail': `
Trail
`, @@ -484,12 +484,10 @@ export class Unit extends Marker { getMap().showUnitContextMenu(e); } - #applyFollowOptions(action: string) - { - if (action === "Custom") - { + #applyFollowOptions(action: string) { + if (action === "Custom") { document.getElementById("custom-formation-dialog")?.classList.remove("hide"); - getMap().getUnitContextMenu().setCustomFormationCallback((offset: {x: number, y: number, z: number}) => { + getMap().getUnitContextMenu().setCustomFormationCallback((offset: { x: number, y: number, z: number }) => { getUnitsManager().selectedUnitsFollowUnit(this.ID, offset); }) } @@ -502,17 +500,15 @@ export class Unit extends Marker { this.updateVisibility(); /* Draw the minimap marker */ - if (this.getBaseData().alive ) - { - if (this.#miniMapMarker == null) - { - this.#miniMapMarker = new CircleMarker(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude), {radius: 0.5}); + if (this.getBaseData().alive) { + if (this.#miniMapMarker == null) { + this.#miniMapMarker = new CircleMarker(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude), { radius: 0.5 }); if (this.getMissionData().coalition == "neutral") - this.#miniMapMarker.setStyle({color: "#CFD9E8"}); + this.#miniMapMarker.setStyle({ color: "#CFD9E8" }); else if (this.getMissionData().coalition == "red") - this.#miniMapMarker.setStyle({color: "#ff5858"}); - else - this.#miniMapMarker.setStyle({color: "#247be2"}); + this.#miniMapMarker.setStyle({ color: "#ff5858" }); + else + this.#miniMapMarker.setStyle({ color: "#247be2" }); this.#miniMapMarker.addTo(getMap().getMiniMapLayerGroup()); this.#miniMapMarker.bringToBack(); } @@ -554,15 +550,15 @@ export class Unit extends Marker { /* Set altitude and speed */ if (element.querySelector(".unit-altitude")) - ( element.querySelector(".unit-altitude")).innerText = "FL" + String(Math.floor(this.getFlightData().altitude / 0.3048 / 100)); + (element.querySelector(".unit-altitude")).innerText = "FL" + String(Math.floor(this.getFlightData().altitude / 0.3048 / 100)); if (element.querySelector(".unit-speed")) - ( element.querySelector(".unit-speed")).innerHTML = String(Math.floor(this.getFlightData().speed * 1.94384 ) ); - + (element.querySelector(".unit-speed")).innerHTML = String(Math.floor(this.getFlightData().speed * 1.94384)); + /* Rotate elements according to heading */ - element.querySelectorAll( "[data-rotate-to-heading]" ).forEach( el => { - const headingDeg = rad2deg( this.getFlightData().heading ); - let currentStyle = el.getAttribute( "style" ) || ""; - el.setAttribute( "style", currentStyle + `transform:rotate(${headingDeg}deg);` ); + element.querySelectorAll("[data-rotate-to-heading]").forEach(el => { + const headingDeg = rad2deg(this.getFlightData().heading); + let currentStyle = el.getAttribute("style") || ""; + el.setAttribute("style", currentStyle + `transform:rotate(${headingDeg}deg);`); }); /* Turn on ordnance indicators */ @@ -577,14 +573,14 @@ export class Unit extends Marker { var newHasOtherAmmo = false; Object.values(this.getMissionData().ammo).forEach((ammo: any) => { if (ammo.desc.category == 1 && ammo.desc.missileCategory == 1) { - if (ammo.desc.guidance == 4 || ammo.desc.guidance == 5) + if (ammo.desc.guidance == 4 || ammo.desc.guidance == 5) newHasFox1 = true; - else if (ammo.desc.guidance == 2) + else if (ammo.desc.guidance == 2) newHasFox2 = true; - else if (ammo.desc.guidance == 3) + else if (ammo.desc.guidance == 3) newHasFox3 = true; } - else + else newHasOtherAmmo = true; }); @@ -594,18 +590,18 @@ export class Unit extends Marker { if (hasOtherAmmo != newHasOtherAmmo) element.querySelector(".unit")?.toggleAttribute("data-has-other-ammo", newHasOtherAmmo); /* Draw the hotgroup element */ + element.querySelector(".unit")?.toggleAttribute("data-is-in-hotgroup", this.#hotgroup != null); if (this.#hotgroup) { - element.querySelector(".unit")?.toggleAttribute("data-is-in-hotgroup", this.#hotgroup != null); const hotgroupEl = element.querySelector(".unit-hotgroup-id") as HTMLElement; if (hotgroupEl) hotgroupEl.innerText = String(this.#hotgroup); } } - + /* Set vertical offset for altitude stacking */ var pos = getMap().latLngToLayerPoint(this.getLatLng()).round(); - this.setZIndexOffset(1000 + Math.floor(this.getFlightData().altitude) - pos.y + (this.#hovered || this.#selected? 5000: 0)); + this.setZIndexOffset(1000 + Math.floor(this.getFlightData().altitude) - pos.y + (this.#highlighted || this.#selected ? 5000 : 0)); } } @@ -689,8 +685,7 @@ export class Aircraft extends AirUnit { super(ID, data); } - getMarkerHTML() - { + getMarkerHTML() { return `
@@ -718,8 +713,7 @@ export class Aircraft extends AirUnit {
` } - getMarkerCategory() - { + getMarkerCategory() { return "aircraft"; } } @@ -729,8 +723,7 @@ export class Helicopter extends AirUnit { super(ID, data); } - getVisibilityCategory() - { + getVisibilityCategory() { return "helicopter"; } } @@ -742,7 +735,7 @@ export class GroundUnit extends Unit { getMarkerHTML() { var role = groundUnitsDatabase.getByName(this.getBaseData().name)?.loadouts[0].roles[0]; - return `
+ return `
${role?.substring(0, 1)?.toUpperCase() || ""}
@@ -752,8 +745,7 @@ export class GroundUnit extends Unit {
` } - getMarkerCategory() - { + getMarkerCategory() { // TODO this is very messy var role = groundUnitsDatabase.getByName(this.getBaseData().name)?.loadouts[0].roles[0]; var markerCategory = (role === "SAM") ? "sam" : "groundunit"; @@ -765,7 +757,7 @@ export class NavyUnit extends Unit { constructor(ID: number, data: UnitData) { super(ID, data); } - + getMarkerCategory() { return "navyunit"; } diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index 2018b4d4..40adf4dc 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -46,7 +46,7 @@ export class UnitsManager { } getUnitsByHotgroup(hotgroup: number) { - return Object.values(this.#units).filter((unit: Unit) => {return unit.getHotgroup() == hotgroup}); + return Object.values(this.#units).filter((unit: Unit) => {return unit.getBaseData().alive && unit.getHotgroup() == hotgroup}); } addUnit(ID: number, data: UnitData) { @@ -117,6 +117,7 @@ export class UnitsManager { } selectUnitsByHotgroup(hotgroup: number) { + this.deselectAllUnits(); this.getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setSelected(true)) } @@ -222,7 +223,6 @@ export class UnitsManager { for (let idx in selectedUnits) { selectedUnits[idx].setSpeed(speed); } - this.#showActionMessage(selectedUnits, `setting speed to ${speed * 1.94384} kts`); } @@ -317,6 +317,12 @@ export class UnitsManager { this.#showActionMessage(selectedUnits, `following unit ${this.getUnitByID(ID)?.getBaseData().unitName}`); } + selectedUnitsSetHotgroup(hotgroup: number) + { + this.getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setHotgroup(null)); + this.selectedUnitsAddToHotgroup(hotgroup); + } + selectedUnitsAddToHotgroup(hotgroup: number) { var selectedUnits = this.getSelectedUnits(); @@ -324,7 +330,7 @@ export class UnitsManager { selectedUnits[idx].setHotgroup(hotgroup); } this.#showActionMessage(selectedUnits, `added to hotgroup ${hotgroup}`); - getHotgroupPanel().addHotgroup(hotgroup); + getHotgroupPanel().refreshHotgroups(); } /***********************************************/ diff --git a/client/views/hotgrouppanel.ejs b/client/views/hotgrouppanel.ejs index f8d065cc..0838c635 100644 --- a/client/views/hotgrouppanel.ejs +++ b/client/views/hotgrouppanel.ejs @@ -1,3 +1,3 @@
- +
\ No newline at end of file