diff --git a/client/public/stylesheets/markers/units.css b/client/public/stylesheets/markers/units.css index 7f9ae8e9..0fc7848d 100644 --- a/client/public/stylesheets/markers/units.css +++ b/client/public/stylesheets/markers/units.css @@ -97,6 +97,18 @@ position: absolute; } +/*** Health indicator ***/ +[data-object|="unit"] .unit-health { + background: white; + border: var(--unit-health-border-width) solid var(--secondary-dark-steel); + border-radius: var(--border-radius-sm); + display: none; + height: var(--unit-health-height); + position: absolute; + translate: var(--unit-health-x) var(--unit-health-y); + width: var(--unit-health-width); +} + /*** Fuel indicator ***/ [data-object|="unit"] .unit-fuel { background: white; @@ -109,7 +121,8 @@ width: var(--unit-fuel-width); } -[data-object|="unit"] .unit-fuel-level { +[data-object|="unit"] .unit-fuel-level, +[data-object|="unit"] .unit-health-level { background-color: var(--secondary-light-grey); height: 100%; width: 100%; @@ -178,6 +191,7 @@ /*** Common ***/ [data-object|="unit"]:hover .unit-ammo, +[data-object|="unit"]:hover .unit-health , [data-object|="unit"]:hover .unit-fuel { display: flex; } @@ -188,13 +202,14 @@ } } -[data-object|="unit"][data-has-low-fuel] .unit-fuel { +[data-object|="unit"][data-has-low-fuel] .unit-fuel, [data-object|="unit"][data-has-low-health] .unit-health { animation: pulse 1.5s linear infinite; } [data-object|="unit"][data-is-in-hotgroup] .unit-hotgroup, [data-object|="unit"][data-is-selected] .unit-ammo, [data-object|="unit"][data-is-selected] .unit-fuel, +[data-object|="unit"][data-is-selected] .unit-health, [data-object|="unit"][data-is-selected] .unit-selected-spotlight { display: flex; } @@ -211,6 +226,7 @@ } [data-object|="unit"][data-coalition="blue"] .unit-fuel-level, +[data-object|="unit"][data-coalition="blue"] .unit-health-level, [data-object|="unit"][data-coalition="blue"][data-has-fox-1] .unit-ammo>div:nth-child(1), [data-object|="unit"][data-coalition="blue"][data-has-fox-2] .unit-ammo>div:nth-child(2), [data-object|="unit"][data-coalition="blue"][data-has-fox-3] .unit-ammo>div:nth-child(3), @@ -227,6 +243,7 @@ } [data-object|="unit"][data-coalition="red"] .unit-fuel-level, +[data-object|="unit"][data-coalition="red"] .unit-health-level, [data-object|="unit"][data-coalition="red"][data-has-fox-1] .unit-ammo>div:nth-child(1), [data-object|="unit"][data-coalition="red"][data-has-fox-2] .unit-ammo>div:nth-child(2), [data-object|="unit"][data-coalition="red"][data-has-fox-3] .unit-ammo>div:nth-child(3), @@ -307,6 +324,20 @@ background-image: url("/resources/theme/images/states/awacs.svg"); } +[data-object|="unit"] .unit-health::before { + background-image: url("/resources/theme/images/icons/health.svg"); + background-repeat: no-repeat; + background-size: contain; + content: " "; + filter: invert(100%); + height: 10px; + left: 0; + position: absolute; + top: 0; + translate: -11px -4px; + width: 10px; +} + /*** Dead unit ***/ [data-object|="unit"][data-is-dead] .unit-selected-spotlight, @@ -316,6 +347,7 @@ [data-object|="unit"][data-is-dead] .unit-hotgroup-id, [data-object|="unit"][data-is-dead] .unit-state, [data-object|="unit"][data-is-dead] .unit-fuel, +[data-object|="unit"][data-is-dead] .unit-health, [data-object|="unit"][data-is-dead] .unit-ammo, [data-object|="unit"][data-is-dead]:hover .unit-fuel, [data-object|="unit"][data-is-dead]:hover .unit-ammo { diff --git a/client/public/themes/olympus/images/icons/health.svg b/client/public/themes/olympus/images/icons/health.svg new file mode 100644 index 00000000..cc4a6622 --- /dev/null +++ b/client/public/themes/olympus/images/icons/health.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/themes/olympus/theme.css b/client/public/themes/olympus/theme.css index df1c6244..8cd6f904 100644 --- a/client/public/themes/olympus/theme.css +++ b/client/public/themes/olympus/theme.css @@ -66,6 +66,12 @@ --unit-height: 50px; --unit-width: 50px; + --unit-health-border-width: 2px; + --unit-health-height: 6px; + --unit-health-width: 36px; + --unit-health-x: 0px; + --unit-health-y: 26px; + /*** Air units ***/ --unit-ammo-gap: calc(2px + var(--unit-stroke-width)); --unit-ammo-border-radius: 50%; diff --git a/client/src/interfaces.ts b/client/src/interfaces.ts index dc4931a0..4208e448 100644 --- a/client/src/interfaces.ts +++ b/client/src/interfaces.ts @@ -86,6 +86,7 @@ export interface UnitSpawnTable { export interface ObjectIconOptions { showState: boolean, showVvi: boolean, + showHealth: boolean, showHotgroup: boolean, showUnitIcon: boolean, showShortLabel: boolean, diff --git a/client/src/unit/unit.ts b/client/src/unit/unit.ts index c79c0c18..983d4d27 100644 --- a/client/src/unit/unit.ts +++ b/client/src/unit/unit.ts @@ -40,6 +40,7 @@ export class Unit extends CustomMarker { #onOff: boolean = true; #followRoads: boolean = false; #fuel: number = 0; + #health: number = Math.round(Math.random()*100); #desiredSpeed: number = 0; #desiredSpeedType: string = "CAS"; #desiredAltitude: number = 0; @@ -116,6 +117,7 @@ export class Unit extends CustomMarker { getGroupName() { return this.#groupName }; getHasTask() { return this.#hasTask }; getHeading() { return this.#heading }; + getHealth() { return this.#health }; getHuman() { return this.#human }; getIsActiveAWACS() { return this.#isActiveAWACS }; getIsActiveTanker() { return this.#isActiveTanker }; @@ -228,6 +230,7 @@ export class Unit extends CustomMarker { case DataIndexes.onOff: this.#onOff = dataExtractor.extractBool(); break; case DataIndexes.followRoads: this.#followRoads = dataExtractor.extractBool(); break; case DataIndexes.fuel: this.#fuel = dataExtractor.extractUInt16(); break; + // case DataIndexes.health: this.#health = dataExtractor.extractUInt16(); break; // To be dynamic case DataIndexes.desiredSpeed: this.#desiredSpeed = dataExtractor.extractFloat64(); break; case DataIndexes.desiredSpeedType: this.#desiredSpeedType = dataExtractor.extractBool() ? "GS" : "CAS"; break; case DataIndexes.desiredAltitude: this.#desiredAltitude = dataExtractor.extractFloat64(); break; @@ -327,6 +330,7 @@ export class Unit extends CustomMarker { return { showState: false, showVvi: false, + showHealth: true, showHotgroup: false, showUnitIcon: true, showShortLabel: false, @@ -519,6 +523,16 @@ export class Unit extends CustomMarker { el.append(fuelIndicator); } + // Health indicator + if (iconOptions.showHealth) { + var healthIndicator = document.createElement("div"); + healthIndicator.classList.add("unit-health"); + var healthLevel = document.createElement("div"); + healthLevel.classList.add("unit-health-level"); + healthIndicator.appendChild(healthLevel); + el.append(healthIndicator); + } + // Ammo indicator if (iconOptions.showAmmo) { var ammoIndicator = document.createElement("div"); @@ -1038,6 +1052,10 @@ export class Unit extends CustomMarker { element.querySelector(".unit-fuel-level")?.setAttribute("style", `width: ${this.#fuel}%`); element.querySelector(".unit")?.toggleAttribute("data-has-low-fuel", this.#fuel < 20); + /* Set health data */ + element.querySelector(".unit-health-level")?.setAttribute("style", `width: ${this.#health}%`); + element.querySelector(".unit")?.toggleAttribute("data-has-low-health", this.#health < 20); + /* Set dead/alive flag */ element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.#alive); @@ -1341,6 +1359,7 @@ export class AirUnit extends Unit { return { showState: belongsToCommandedCoalition, showVvi: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), + showHealth: false, showHotgroup: belongsToCommandedCoalition, showUnitIcon: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), showShortLabel: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value))), @@ -1414,6 +1433,7 @@ export class GroundUnit extends Unit { return { showState: belongsToCommandedCoalition, showVvi: false, + showHealth: true, showHotgroup: belongsToCommandedCoalition, showUnitIcon: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), showShortLabel: false, @@ -1478,6 +1498,7 @@ export class NavyUnit extends Unit { return { showState: belongsToCommandedCoalition, showVvi: false, + showHealth: false, showHotgroup: true, showUnitIcon: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), showShortLabel: false, diff --git a/client/src/weapon/weapon.ts b/client/src/weapon/weapon.ts index f673c07e..57496337 100644 --- a/client/src/weapon/weapon.ts +++ b/client/src/weapon/weapon.ts @@ -100,6 +100,7 @@ export class Weapon extends CustomMarker { return { showState: false, showVvi: false, + showHealth: false, showHotgroup: false, showUnitIcon: true, showShortLabel: false, @@ -276,6 +277,7 @@ export class Missile extends Weapon { return { showState: false, showVvi: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))), + showHealth: false, showHotgroup: false, showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), showShortLabel: false, @@ -308,6 +310,7 @@ export class Bomb extends Weapon { return { showState: false, showVvi: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))), + showHealth: false, showHotgroup: false, showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), showShortLabel: false,