diff --git a/client/@types/olympus/index.d.ts b/client/@types/olympus/index.d.ts index 95f77f6b..3cf448d8 100644 --- a/client/@types/olympus/index.d.ts +++ b/client/@types/olympus/index.d.ts @@ -659,7 +659,7 @@ declare module "unit/databases/unitdatabase" { [key: string]: UnitBlueprint; }; getRoles(): string[]; - getTypes(): string[]; + getTypes(unitFilter?: CallableFunction): string[]; getEras(): string[]; getByRange(range: string): UnitBlueprint[]; getByType(type: string): UnitBlueprint[]; @@ -810,6 +810,7 @@ declare module "controls/unitspawnmenu" { #private; protected showRangeCircles: boolean; protected spawnOptions: UnitSpawnOptions; + protected unitTypeFilter: (unit: any) => boolean; constructor(ID: string, unitDatabase: UnitDatabase, orderByRole: boolean); getContainer(): HTMLElement; getVisible(): boolean; @@ -849,6 +850,7 @@ declare module "controls/unitspawnmenu" { } export class GroundUnitSpawnMenu extends UnitSpawnMenu { protected showRangeCircles: boolean; + protected unitTypeFilter: (unit: any) => boolean; /** * * @param ID - the ID of the HTML element which will contain the context menu @@ -856,6 +858,14 @@ declare module "controls/unitspawnmenu" { constructor(ID: string); deployUnits(spawnOptions: UnitSpawnOptions, unitsCount: number): void; } + export class AirDefenceUnitSpawnMenu extends GroundUnitSpawnMenu { + protected unitTypeFilter: (unit: any) => boolean; + /** + * + * @param ID - the ID of the HTML element which will contain the context menu + */ + constructor(ID: string); + } export class NavyUnitSpawnMenu extends UnitSpawnMenu { /** * @@ -1510,6 +1520,7 @@ declare module "map/map" { [key: string]: boolean; }; unitIsProtected(unit: Unit): boolean; + getMapMarkerControls(): MapMarkerControl[]; } } declare module "mission/bullseye" { diff --git a/client/public/stylesheets/markers/units.css b/client/public/stylesheets/markers/units.css index 7f9ae8e9..09f02d65 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,19 @@ 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: " "; + height: 6px; + left: 0; + position: absolute; + top: 0; + translate: -10px -2px; + width: 6px; +} + /*** Dead unit ***/ [data-object|="unit"][data-is-dead] .unit-selected-spotlight, @@ -316,6 +346,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..850b69a3 --- /dev/null +++ b/client/public/themes/olympus/images/icons/health.svg @@ -0,0 +1,4 @@ + + + + diff --git a/client/public/themes/olympus/theme.css b/client/public/themes/olympus/theme.css index 41953812..03a6ef40 100644 --- a/client/public/themes/olympus/theme.css +++ b/client/public/themes/olympus/theme.css @@ -67,6 +67,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/constants/constants.ts b/client/src/constants/constants.ts index 8eb1663f..fe1a07fc 100644 --- a/client/src/constants/constants.ts +++ b/client/src/constants/constants.ts @@ -262,6 +262,7 @@ export enum DataIndexes { operateAs, shotsScatter, shotsIntensity, + health, endOfData = 255 }; diff --git a/client/src/interfaces.ts b/client/src/interfaces.ts index c13419f5..9cd04796 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, @@ -181,6 +182,7 @@ export interface UnitData { operateAs: string; shotsScatter: number; shotsIntensity: number; + health: number; } export interface LoadoutItemBlueprint { diff --git a/client/src/unit/unit.ts b/client/src/unit/unit.ts index 22f6b214..5f529327 100644 --- a/client/src/unit/unit.ts +++ b/client/src/unit/unit.ts @@ -18,6 +18,11 @@ var pathIcon = new Icon({ iconAnchor: [13, 41] }); +/** + * Unit class which controls unit behaviour + * + * Just about everything is a unit - even missiles! + */ export class Unit extends CustomMarker { ID: number; @@ -82,6 +87,7 @@ export class Unit extends CustomMarker { #operateAs: string = "blue"; #shotsScatter: number = 2; #shotsIntensity: number = 2; + #health: number = 100; #selectable: boolean; #selected: boolean = false; @@ -116,8 +122,8 @@ export class Unit extends CustomMarker { getHorizontalVelocity() { return this.#horizontalVelocity }; getVerticalVelocity() { return this.#verticalVelocity }; getHeading() { return this.#heading }; - getIsActiveTanker() { return this.#isActiveTanker }; getIsActiveAWACS() { return this.#isActiveAWACS }; + getIsActiveTanker() { return this.#isActiveTanker }; getOnOff() { return this.#onOff }; getFollowRoads() { return this.#followRoads }; getFuel() { return this.#fuel }; @@ -140,8 +146,9 @@ export class Unit extends CustomMarker { getActivePath() { return this.#activePath }; getIsLeader() { return this.#isLeader }; getOperateAs() { return this.#operateAs }; - getShotsScatter() { return this.#shotsScatter}; - getShotsIntensity() { return this.#shotsIntensity}; + getShotsScatter() { return this.#shotsScatter }; + getShotsIntensity() { return this.#shotsIntensity }; + getHealth() { return this.#health }; static getConstructor(type: string) { if (type === "GroundUnit") return GroundUnit; @@ -191,9 +198,9 @@ export class Unit extends CustomMarker { this.#updateMarker(); /* Circles don't like to be updated when the map is zooming */ - if (!getApp().getMap().isZooming()) + if (!getApp().getMap().isZooming()) this.#drawRanges(); - else + else this.once("zoomend", () => { this.#drawRanges(); }) if (this.getSelected()) @@ -257,6 +264,7 @@ export class Unit extends CustomMarker { case DataIndexes.operateAs: this.#operateAs = enumToCoalition(dataExtractor.extractUInt8()); break; case DataIndexes.shotsScatter: this.#shotsScatter = dataExtractor.extractUInt8(); break; case DataIndexes.shotsIntensity: this.#shotsIntensity = dataExtractor.extractUInt8(); break; + case DataIndexes.health: this.#health = dataExtractor.extractUInt8(); updateMarker = true; break; } } @@ -279,6 +287,10 @@ export class Unit extends CustomMarker { } } + /** Get unit data collated into an object + * + * @returns object populated by unit information which can also be retrieved using getters + */ getData(): UnitData { return { category: this.getCategory(), @@ -324,23 +336,38 @@ export class Unit extends CustomMarker { isLeader: this.#isLeader, operateAs: this.#operateAs, shotsScatter: this.#shotsScatter, - shotsIntensity: this.#shotsIntensity + shotsIntensity: this.#shotsIntensity, + health: this.#health } } + /** + * + * @returns string containing the marker category + */ getMarkerCategory(): string { return getMarkerCategoryByName(this.getName()); } + /** Get a database of information also in this unit's category + * + * @returns UnitDatabase + */ getDatabase(): UnitDatabase | null { return getUnitDatabaseByCategory(this.getMarkerCategory()); } + /** Get the icon options + * Used to configure how the marker appears on the map + * + * @returns ObjectIconOptions + */ getIconOptions(): ObjectIconOptions { // Default values, overloaded by child classes if needed return { showState: false, showVvi: false, + showHealth: true, showHotgroup: false, showUnitIcon: true, showShortLabel: false, @@ -352,21 +379,29 @@ export class Unit extends CustomMarker { } } + /** Set the unit as alive or dead + * + * @param newAlive (boolean) true = alive, false = dead + */ setAlive(newAlive: boolean) { if (newAlive != this.#alive) document.dispatchEvent(new CustomEvent("unitDeath", { detail: this })); this.#alive = newAlive; } + /** Set the unit as user-selected + * + * @param selected (boolean) + */ setSelected(selected: boolean) { /* Only alive units can be selected. Some units are not selectable (weapons) */ if ((this.#alive || !selected) && this.getSelectable() && this.getSelected() != selected && this.belongsToCommandedCoalition()) { this.#selected = selected; /* Circles don't like to be updated when the map is zooming */ - if (!getApp().getMap().isZooming()) + if (!getApp().getMap().isZooming()) this.#drawRanges(); - else + else this.once("zoomend", () => { this.#drawRanges(); }) if (selected) { @@ -396,27 +431,51 @@ export class Unit extends CustomMarker { } } + /** Is this unit selected? + * + * @returns boolean + */ getSelected() { return this.#selected; } + /** Set whether this unit is selectable + * + * @param selectable (boolean) + */ setSelectable(selectable: boolean) { this.#selectable = selectable; } + /** Get whether this unit is selectable + * + * @returns boolean + */ getSelectable() { return this.#selectable; } + /** Set the number of the hotgroup to which the unit belongs + * + * @param hotgroup (number) + */ setHotgroup(hotgroup: number | null) { this.#hotgroup = hotgroup; this.#updateMarker(); } + /** Get the unit's hotgroup number + * + * @returns number + */ getHotgroup() { return this.#hotgroup; } + /** Set the unit as highlighted + * + * @param highlighted (boolean) + */ setHighlighted(highlighted: boolean) { if (this.getSelectable() && this.#highlighted != highlighted) { this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-highlighted", highlighted); @@ -425,18 +484,28 @@ export class Unit extends CustomMarker { } } + /** Get whether the unit is highlighted or not + * + * @returns boolean + */ getHighlighted() { return this.#highlighted; } + /** Get the other members of the group which this unit is in + * + * @returns Unit[] + */ getGroupMembers() { return Object.values(getApp().getUnitsManager().getUnits()).filter((unit: Unit) => { return unit != this && unit.getGroupName() === this.getGroupName(); }); } + /** Returns whether the user is allowed to command this unit, based on coalition + * + * @returns boolean + */ belongsToCommandedCoalition() { - if (getApp().getMissionManager().getCommandModeOptions().commandMode !== GAME_MASTER && getApp().getMissionManager().getCommandedCoalition() !== this.#coalition) - return false; - return true; + return (getApp().getMissionManager().getCommandModeOptions().commandMode !== GAME_MASTER && getApp().getMissionManager().getCommandedCoalition() !== this.#coalition) ? false : true; } getType() { @@ -528,6 +597,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"); @@ -557,9 +636,9 @@ export class Unit extends CustomMarker { this.getElement()?.appendChild(el); /* Circles don't like to be updated when the map is zooming */ - if (!getApp().getMap().isZooming()) + if (!getApp().getMap().isZooming()) this.#drawRanges(); - else + else this.once("zoomend", () => { this.#drawRanges(); }) } @@ -595,9 +674,9 @@ export class Unit extends CustomMarker { if (!this.getHidden()) { /* Circles don't like to be updated when the map is zooming */ - if (!getApp().getMap().isZooming()) + if (!getApp().getMap().isZooming()) this.#drawRanges(); - else + else this.once("zoomend", () => { this.#drawRanges(); }) } else { this.#clearRanges(); @@ -851,7 +930,7 @@ export class Unit extends CustomMarker { } /***********************************************/ - getActions(): { [key: string]: { text: string, tooltip: string, type: string } } { + getActions(): { [key: string]: { text: string, tooltip: string, type: string } } { /* To be implemented by child classes */ // TODO make Unit an abstract class return {}; } @@ -967,14 +1046,14 @@ export class Unit extends CustomMarker { var options: { [key: string]: { text: string, tooltip: string } } = {}; options = { - 'trail': { text: "Trail", tooltip: "Follow unit in trail formation" }, - 'echelon-lh': { text: "Echelon (LH)", tooltip: "Follow unit in echelon left formation" }, - 'echelon-rh': { text: "Echelon (RH)", tooltip: "Follow unit in echelon right formation" }, - 'line-abreast-lh': { text: "Line abreast (LH)", tooltip: "Follow unit in line abreast left formation" }, - 'line-abreast-rh': { text: "Line abreast (RH)", tooltip: "Follow unit in line abreast right formation" }, - 'front': { text: "Front", tooltip: "Fly in front of unit" }, - 'diamond': { text: "Diamond", tooltip: "Follow unit in diamond formation" }, - 'custom': { text: "Custom", tooltip: "Set a custom formation position" }, + 'trail': { text: "Trail", tooltip: "Follow unit in trail formation" }, + 'echelon-lh': { text: "Echelon (LH)", tooltip: "Follow unit in echelon left formation" }, + 'echelon-rh': { text: "Echelon (RH)", tooltip: "Follow unit in echelon right formation" }, + 'line-abreast-lh': { text: "Line abreast (LH)", tooltip: "Follow unit in line abreast left formation" }, + 'line-abreast-rh': { text: "Line abreast (RH)", tooltip: "Follow unit in line abreast right formation" }, + 'front': { text: "Front", tooltip: "Fly in front of unit" }, + 'diamond': { text: "Diamond", tooltip: "Follow unit in diamond formation" }, + 'custom': { text: "Custom", tooltip: "Set a custom formation position" }, } getApp().getMap().getUnitContextMenu().setOptions(options, (option: string) => { @@ -1065,6 +1144,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); @@ -1075,7 +1158,7 @@ export class Unit extends CustomMarker { else if (!this.#controlled) { // Unit is under DCS control (not Olympus) element.querySelector(".unit")?.setAttribute("data-state", "dcs"); } - else if ((this.getCategory() == "Aircraft" || this.getCategory() == "Helicopter") && !this.#hasTask){ + else if ((this.getCategory() == "Aircraft" || this.getCategory() == "Helicopter") && !this.#hasTask) { element.querySelector(".unit")?.setAttribute("data-state", "no-task"); } else { // Unit is under Olympus control @@ -1243,13 +1326,13 @@ export class Unit extends CustomMarker { /* Get the acquisition and engagement ranges of the entire group, not for each unit */ if (this.getIsLeader()) { - var engagementRange = this.getDatabase()?.getByName(this.getName())?.engagementRange?? 0; - var acquisitionRange = this.getDatabase()?.getByName(this.getName())?.acquisitionRange?? 0; + var engagementRange = this.getDatabase()?.getByName(this.getName())?.engagementRange ?? 0; + var acquisitionRange = this.getDatabase()?.getByName(this.getName())?.acquisitionRange ?? 0; this.getGroupMembers().forEach((unit: Unit) => { if (unit.getAlive()) { - let unitEngagementRange = unit.getDatabase()?.getByName(unit.getName())?.engagementRange?? 0; - let unitAcquisitionRange = unit.getDatabase()?.getByName(unit.getName())?.acquisitionRange?? 0; + let unitEngagementRange = unit.getDatabase()?.getByName(unit.getName())?.engagementRange ?? 0; + let unitAcquisitionRange = unit.getDatabase()?.getByName(unit.getName())?.acquisitionRange ?? 0; if (unitEngagementRange > engagementRange) engagementRange = unitEngagementRange; @@ -1260,12 +1343,12 @@ export class Unit extends CustomMarker { }) if (acquisitionRange !== this.#acquisitionCircle.getRadius()) - this.#acquisitionCircle.setRadius(acquisitionRange); + this.#acquisitionCircle.setRadius(acquisitionRange); if (engagementRange !== this.#engagementCircle.getRadius()) this.#engagementCircle.setRadius(engagementRange); - this.#engagementCircle.options.fillOpacity = this.getSelected() && getApp().getMap().getVisibilityOptions()[FILL_SELECTED_RING]? 0.3: 0; + this.#engagementCircle.options.fillOpacity = this.getSelected() && getApp().getMap().getVisibilityOptions()[FILL_SELECTED_RING] ? 0.3 : 0; /* Acquisition circles */ var shortAcquisitionRangeCheck = (acquisitionRange > nmToM(3) || !getApp().getMap().getVisibilityOptions()[HIDE_UNITS_SHORT_RANGE_RINGS]); @@ -1291,7 +1374,7 @@ export class Unit extends CustomMarker { if (getApp().getMap().hasLayer(this.#acquisitionCircle)) this.#acquisitionCircle.removeFrom(getApp().getMap()); } - + /* Engagement circles */ var shortEngagementRangeCheck = (engagementRange > nmToM(3) || !getApp().getMap().getVisibilityOptions()[HIDE_UNITS_SHORT_RANGE_RINGS]); if (getApp().getMap().getVisibilityOptions()[SHOW_UNITS_ENGAGEMENT_RINGS] && shortEngagementRangeCheck && (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, IRST, RWR].includes(value)))) { @@ -1368,6 +1451,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))), @@ -1441,6 +1525,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, @@ -1505,6 +1590,7 @@ export class NavyUnit extends Unit { return { showState: belongsToCommandedCoalition, showVvi: false, + showHealth: true, 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, diff --git a/scripts/OlympusCommand.lua b/scripts/OlympusCommand.lua index 7be6dc31..480e907d 100644 --- a/scripts/OlympusCommand.lua +++ b/scripts/OlympusCommand.lua @@ -937,11 +937,15 @@ function Olympus.setUnitsData(arg, time) table["hasTask"] = controller:hasTask() table["ammo"] = unit:getAmmo() --TODO remove a lot of stuff we don't really need table["fuel"] = unit:getFuel() - table["life"] = unit:getLife() / unit:getLife0() + table["health"] = unit:getLife() / unit:getLife0() * 100 table["contacts"] = contacts units[ID] = table end + else + -- If the unit reference is nil it means the unit no longer exits + units[ID] = {isAlive = false} + Olympus.units[ID] = nil end end else diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj index 7c46020d..65110b14 100644 --- a/src/core/core.vcxproj +++ b/src/core/core.vcxproj @@ -37,7 +37,6 @@ - @@ -55,7 +54,6 @@ - diff --git a/src/core/core.vcxproj.filters b/src/core/core.vcxproj.filters index 56fade36..1878b7a9 100644 --- a/src/core/core.vcxproj.filters +++ b/src/core/core.vcxproj.filters @@ -45,9 +45,6 @@ Header Files - - Header Files - Header Files @@ -95,9 +92,6 @@ Source Files - - Source Files - Source Files diff --git a/src/core/include/datatypes.h b/src/core/include/datatypes.h index 1ab55cb1..8928ab20 100644 --- a/src/core/include/datatypes.h +++ b/src/core/include/datatypes.h @@ -48,6 +48,7 @@ namespace DataIndex { operateAs, shotsScatter, shotsIntensity, + health, lastIndex, endOfData = 255 }; diff --git a/src/core/include/measure.h b/src/core/include/measure.h deleted file mode 100644 index 61ff410d..00000000 --- a/src/core/include/measure.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once -#include "framework.h" - -class Measure -{ -public: - Measure(json::value value, long long time): value(value), time(time) {}; - - void setValue(json::value newValue) { value = newValue; } - void setTime(long long newTime) { time = newTime; } - json::value getValue() { return value; } - long long getTime() { return time; } - -private: - json::value value; - long long time; - -}; - diff --git a/src/core/include/unit.h b/src/core/include/unit.h index c85cbf44..a4ee4744 100644 --- a/src/core/include/unit.h +++ b/src/core/include/unit.h @@ -3,7 +3,6 @@ #include "utils.h" #include "dcstools.h" #include "luatools.h" -#include "measure.h" #include "logger.h" #include "commands.h" #include "datatypes.h" @@ -107,6 +106,7 @@ public: virtual void setOperateAs(unsigned char newValue) { updateValue(operateAs, newValue, DataIndex::operateAs); } virtual void setShotsScatter(unsigned char newValue) { updateValue(shotsScatter, newValue, DataIndex::shotsScatter); } virtual void setShotsIntensity(unsigned char newValue) { updateValue(shotsIntensity, newValue, DataIndex::shotsIntensity); } + virtual void setHealth(unsigned char newValue) { updateValue(health, newValue, DataIndex::health); } /********** Getters **********/ virtual string getCategory() { return category; }; @@ -152,6 +152,7 @@ public: virtual unsigned char getOperateAs() { return operateAs; } virtual unsigned char getShotsScatter() { return shotsScatter; } virtual unsigned char getShotsIntensity() { return shotsIntensity; } + virtual unsigned char getHealth() { return health; } protected: unsigned int ID; @@ -200,6 +201,7 @@ protected: Coords activeDestination = Coords(NULL); unsigned char shotsScatter = 2; unsigned char shotsIntensity = 2; + unsigned char health = 100; /********** Other **********/ unsigned int taskCheckCounter = 0; diff --git a/src/core/include/weapon.h b/src/core/include/weapon.h index a387a111..5ca44170 100644 --- a/src/core/include/weapon.h +++ b/src/core/include/weapon.h @@ -3,7 +3,6 @@ #include "utils.h" #include "dcstools.h" #include "luatools.h" -#include "measure.h" #include "logger.h" #include "commands.h" #include "datatypes.h" diff --git a/src/core/src/measure.cpp b/src/core/src/measure.cpp deleted file mode 100644 index e69de29b..00000000 diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp index 58ef3a8e..6bcf1b10 100644 --- a/src/core/src/unit.cpp +++ b/src/core/src/unit.cpp @@ -137,6 +137,9 @@ void Unit::update(json::value json, double dt) if (json.has_boolean_field(L"hasTask")) setHasTask(json[L"hasTask"].as_bool()); + if (json.has_number_field(L"health")) + setHealth(static_cast(json[L"health"].as_number().to_uint32())); + runAILoop(); } @@ -241,49 +244,50 @@ void Unit::getData(stringstream& ss, unsigned long long time) { if (checkFreshness(datumIndex, time)) { switch (datumIndex) { - case DataIndex::category: appendString(ss, datumIndex, category); break; - case DataIndex::alive: appendNumeric(ss, datumIndex, alive); break; - case DataIndex::human: appendNumeric(ss, datumIndex, human); break; - case DataIndex::controlled: appendNumeric(ss, datumIndex, controlled); break; - case DataIndex::coalition: appendNumeric(ss, datumIndex, coalition); break; - case DataIndex::country: appendNumeric(ss, datumIndex, country); break; - case DataIndex::name: appendString(ss, datumIndex, name); break; - case DataIndex::unitName: appendString(ss, datumIndex, unitName); break; - case DataIndex::groupName: appendString(ss, datumIndex, groupName); break; - case DataIndex::state: appendNumeric(ss, datumIndex, state); break; - case DataIndex::task: appendString(ss, datumIndex, task); break; - case DataIndex::hasTask: appendNumeric(ss, datumIndex, hasTask); break; - case DataIndex::position: appendNumeric(ss, datumIndex, position); break; - case DataIndex::speed: appendNumeric(ss, datumIndex, speed); break; - case DataIndex::horizontalVelocity: appendNumeric(ss, datumIndex, horizontalVelocity); break; - case DataIndex::verticalVelocity: appendNumeric(ss, datumIndex, verticalVelocity); break; - case DataIndex::heading: appendNumeric(ss, datumIndex, heading); break; - case DataIndex::isActiveTanker: appendNumeric(ss, datumIndex, isActiveTanker); break; - case DataIndex::isActiveAWACS: appendNumeric(ss, datumIndex, isActiveAWACS); break; - case DataIndex::onOff: appendNumeric(ss, datumIndex, onOff); break; - case DataIndex::followRoads: appendNumeric(ss, datumIndex, followRoads); break; - case DataIndex::fuel: appendNumeric(ss, datumIndex, fuel); break; - case DataIndex::desiredSpeed: appendNumeric(ss, datumIndex, desiredSpeed); break; - case DataIndex::desiredSpeedType: appendNumeric(ss, datumIndex, desiredSpeedType); break; - case DataIndex::desiredAltitude: appendNumeric(ss, datumIndex, desiredAltitude); break; - case DataIndex::desiredAltitudeType: appendNumeric(ss, datumIndex, desiredAltitudeType); break; - case DataIndex::leaderID: appendNumeric(ss, datumIndex, leaderID); break; - case DataIndex::formationOffset: appendNumeric(ss, datumIndex, formationOffset); break; - case DataIndex::targetID: appendNumeric(ss, datumIndex, targetID); break; - case DataIndex::targetPosition: appendNumeric(ss, datumIndex, targetPosition); break; - case DataIndex::ROE: appendNumeric(ss, datumIndex, ROE); break; - case DataIndex::reactionToThreat: appendNumeric(ss, datumIndex, reactionToThreat); break; - case DataIndex::emissionsCountermeasures: appendNumeric(ss, datumIndex, emissionsCountermeasures); break; - case DataIndex::TACAN: appendNumeric(ss, datumIndex, TACAN); break; - case DataIndex::radio: appendNumeric(ss, datumIndex, radio); break; - case DataIndex::generalSettings: appendNumeric(ss, datumIndex, generalSettings); break; - case DataIndex::ammo: appendVector(ss, datumIndex, ammo); break; - case DataIndex::contacts: appendVector(ss, datumIndex, contacts); break; - case DataIndex::activePath: appendList(ss, datumIndex, activePath); break; - case DataIndex::isLeader: appendNumeric(ss, datumIndex, isLeader); break; - case DataIndex::operateAs: appendNumeric(ss, datumIndex, operateAs); break; - case DataIndex::shotsScatter: appendNumeric(ss, datumIndex, shotsScatter); break; - case DataIndex::shotsIntensity: appendNumeric(ss, datumIndex, shotsIntensity); break; + case DataIndex::category: appendString(ss, datumIndex, category); break; + case DataIndex::alive: appendNumeric(ss, datumIndex, alive); break; + case DataIndex::human: appendNumeric(ss, datumIndex, human); break; + case DataIndex::controlled: appendNumeric(ss, datumIndex, controlled); break; + case DataIndex::coalition: appendNumeric(ss, datumIndex, coalition); break; + case DataIndex::country: appendNumeric(ss, datumIndex, country); break; + case DataIndex::name: appendString(ss, datumIndex, name); break; + case DataIndex::unitName: appendString(ss, datumIndex, unitName); break; + case DataIndex::groupName: appendString(ss, datumIndex, groupName); break; + case DataIndex::state: appendNumeric(ss, datumIndex, state); break; + case DataIndex::task: appendString(ss, datumIndex, task); break; + case DataIndex::hasTask: appendNumeric(ss, datumIndex, hasTask); break; + case DataIndex::position: appendNumeric(ss, datumIndex, position); break; + case DataIndex::speed: appendNumeric(ss, datumIndex, speed); break; + case DataIndex::horizontalVelocity: appendNumeric(ss, datumIndex, horizontalVelocity); break; + case DataIndex::verticalVelocity: appendNumeric(ss, datumIndex, verticalVelocity); break; + case DataIndex::heading: appendNumeric(ss, datumIndex, heading); break; + case DataIndex::isActiveTanker: appendNumeric(ss, datumIndex, isActiveTanker); break; + case DataIndex::isActiveAWACS: appendNumeric(ss, datumIndex, isActiveAWACS); break; + case DataIndex::onOff: appendNumeric(ss, datumIndex, onOff); break; + case DataIndex::followRoads: appendNumeric(ss, datumIndex, followRoads); break; + case DataIndex::fuel: appendNumeric(ss, datumIndex, fuel); break; + case DataIndex::desiredSpeed: appendNumeric(ss, datumIndex, desiredSpeed); break; + case DataIndex::desiredSpeedType: appendNumeric(ss, datumIndex, desiredSpeedType); break; + case DataIndex::desiredAltitude: appendNumeric(ss, datumIndex, desiredAltitude); break; + case DataIndex::desiredAltitudeType: appendNumeric(ss, datumIndex, desiredAltitudeType); break; + case DataIndex::leaderID: appendNumeric(ss, datumIndex, leaderID); break; + case DataIndex::formationOffset: appendNumeric(ss, datumIndex, formationOffset); break; + case DataIndex::targetID: appendNumeric(ss, datumIndex, targetID); break; + case DataIndex::targetPosition: appendNumeric(ss, datumIndex, targetPosition); break; + case DataIndex::ROE: appendNumeric(ss, datumIndex, ROE); break; + case DataIndex::reactionToThreat: appendNumeric(ss, datumIndex, reactionToThreat); break; + case DataIndex::emissionsCountermeasures: appendNumeric(ss, datumIndex, emissionsCountermeasures); break; + case DataIndex::TACAN: appendNumeric(ss, datumIndex, TACAN); break; + case DataIndex::radio: appendNumeric(ss, datumIndex, radio); break; + case DataIndex::generalSettings: appendNumeric(ss, datumIndex, generalSettings); break; + case DataIndex::ammo: appendVector(ss, datumIndex, ammo); break; + case DataIndex::contacts: appendVector(ss, datumIndex, contacts); break; + case DataIndex::activePath: appendList(ss, datumIndex, activePath); break; + case DataIndex::isLeader: appendNumeric(ss, datumIndex, isLeader); break; + case DataIndex::operateAs: appendNumeric(ss, datumIndex, operateAs); break; + case DataIndex::shotsScatter: appendNumeric(ss, datumIndex, shotsScatter); break; + case DataIndex::shotsIntensity: appendNumeric(ss, datumIndex, shotsIntensity); break; + case DataIndex::health: appendNumeric(ss, datumIndex, health); break; } } }