diff --git a/client/@types/olympus/index.d.ts b/client/@types/olympus/index.d.ts index f1215d76..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 { /** * @@ -894,7 +904,7 @@ declare module "contextmenus/mapcontextmenu" { * @param y Y screen coordinate of the top left corner of the context menu * @param latlng Leaflet latlng object of the mouse click */ - show(x: number, y: number, latlng: LatLng): void; + show(x: number, y: number, latlng: LatLng): false | undefined; /** If the user rightclicked on a CoalitionArea, it will be given the ability to edit it. * * @param coalitionArea The CoalitionArea the user can edit @@ -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/plugins/databasemanager/index.js b/client/plugins/databasemanager/index.js index 432c8142..db4029ad 100644 --- a/client/plugins/databasemanager/index.js +++ b/client/plugins/databasemanager/index.js @@ -1043,8 +1043,7 @@ function arrayToString(array) { } exports.arrayToString = arrayToString; function stringToArray(input) { - var _a; - return (_a = input.match(/(\w)+/g)) !== null && _a !== void 0 ? _a : []; + return input.match(/(\w)+/g) || []; } exports.stringToArray = stringToArray; diff --git a/client/src/contextmenus/mapcontextmenu.ts b/client/src/contextmenus/mapcontextmenu.ts index 0f75a596..209a4cfb 100644 --- a/client/src/contextmenus/mapcontextmenu.ts +++ b/client/src/contextmenus/mapcontextmenu.ts @@ -4,7 +4,7 @@ import { ContextMenu } from "./contextmenu"; import { Switch } from "../controls/switch"; import { GAME_MASTER } from "../constants/constants"; import { CoalitionArea } from "../map/coalitionarea/coalitionarea"; -import { AircraftSpawnMenu, GroundUnitSpawnMenu, HelicopterSpawnMenu, NavyUnitSpawnMenu } from "../controls/unitspawnmenu"; +import { AirDefenceUnitSpawnMenu, AircraftSpawnMenu, GroundUnitSpawnMenu, HelicopterSpawnMenu, NavyUnitSpawnMenu } from "../controls/unitspawnmenu"; import { Airbase } from "../mission/airbase"; import { SmokeMarker } from "../map/markers/smokemarker"; @@ -15,6 +15,7 @@ export class MapContextMenu extends ContextMenu { #coalitionSwitch: Switch; #aircraftSpawnMenu: AircraftSpawnMenu; #helicopterSpawnMenu: HelicopterSpawnMenu; + #airDefenceUnitSpawnMenu: AirDefenceUnitSpawnMenu; #groundUnitSpawnMenu: GroundUnitSpawnMenu; #navyUnitSpawnMenu: NavyUnitSpawnMenu; #coalitionArea: CoalitionArea | null = null; @@ -35,6 +36,7 @@ export class MapContextMenu extends ContextMenu { /* Create the spawn menus for the different unit types */ this.#aircraftSpawnMenu = new AircraftSpawnMenu("aircraft-spawn-menu"); this.#helicopterSpawnMenu = new HelicopterSpawnMenu("helicopter-spawn-menu"); + this.#airDefenceUnitSpawnMenu = new AirDefenceUnitSpawnMenu("air-defence-spawn-menu"); this.#groundUnitSpawnMenu = new GroundUnitSpawnMenu("groundunit-spawn-menu"); this.#navyUnitSpawnMenu = new NavyUnitSpawnMenu("navyunit-spawn-menu"); @@ -73,21 +75,25 @@ export class MapContextMenu extends ContextMenu { this.#aircraftSpawnMenu.getContainer().addEventListener("resize", () => this.clip()); this.#helicopterSpawnMenu.getContainer().addEventListener("resize", () => this.clip()); + this.#airDefenceUnitSpawnMenu.getContainer().addEventListener("resize", () => this.clip()); this.#groundUnitSpawnMenu.getContainer().addEventListener("resize", () => this.clip()); this.#navyUnitSpawnMenu.getContainer().addEventListener("resize", () => this.clip()); this.#aircraftSpawnMenu.getContainer().addEventListener("hide", () => this.hide()); this.#helicopterSpawnMenu.getContainer().addEventListener("hide", () => this.hide()); + this.#airDefenceUnitSpawnMenu.getContainer().addEventListener("hide", () => this.hide()); this.#groundUnitSpawnMenu.getContainer().addEventListener("hide", () => this.hide()); this.#navyUnitSpawnMenu.getContainer().addEventListener("hide", () => this.hide()); this.getContainer()?.addEventListener("show", () => this.#aircraftSpawnMenu.showCirclesPreviews()); this.getContainer()?.addEventListener("show", () => this.#helicopterSpawnMenu.showCirclesPreviews()); + this.getContainer()?.addEventListener("show", () => this.#airDefenceUnitSpawnMenu.showCirclesPreviews()); this.getContainer()?.addEventListener("show", () => this.#groundUnitSpawnMenu.showCirclesPreviews()); this.getContainer()?.addEventListener("show", () => this.#navyUnitSpawnMenu.showCirclesPreviews()); this.getContainer()?.addEventListener("hide", () => this.#aircraftSpawnMenu.clearCirclesPreviews()); this.getContainer()?.addEventListener("hide", () => this.#helicopterSpawnMenu.clearCirclesPreviews()); + this.getContainer()?.addEventListener("hide", () => this.#airDefenceUnitSpawnMenu.clearCirclesPreviews()); this.getContainer()?.addEventListener("hide", () => this.#groundUnitSpawnMenu.clearCirclesPreviews()); this.getContainer()?.addEventListener("hide", () => this.#navyUnitSpawnMenu.clearCirclesPreviews()); } @@ -106,11 +112,13 @@ export class MapContextMenu extends ContextMenu { this.#aircraftSpawnMenu.setLatLng(latlng); this.#helicopterSpawnMenu.setLatLng(latlng); + this.#airDefenceUnitSpawnMenu.setLatLng(latlng); this.#groundUnitSpawnMenu.setLatLng(latlng); this.#navyUnitSpawnMenu.setLatLng(latlng); this.#aircraftSpawnMenu.setCountries(); this.#helicopterSpawnMenu.setCountries(); + this.#airDefenceUnitSpawnMenu.setCountries(); this.#groundUnitSpawnMenu.setCountries(); this.#navyUnitSpawnMenu.setCountries(); @@ -146,7 +154,7 @@ export class MapContextMenu extends ContextMenu { #showSubMenu(type: string) { if (type === "more") this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide"); - else if (["aircraft", "helicopter", "groundunit"].includes(type)) + else if (["aircraft", "helicopter", "air-defence", "groundunit"].includes(type)) this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide", true); this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", type !== "aircraft"); @@ -155,6 +163,8 @@ export class MapContextMenu extends ContextMenu { this.getContainer()?.querySelector("#helicopter-spawn-button")?.classList.toggle("is-open", type === "helicopter"); this.getContainer()?.querySelector("#groundunit-spawn-menu")?.classList.toggle("hide", type !== "groundunit"); this.getContainer()?.querySelector("#groundunit-spawn-button")?.classList.toggle("is-open", type === "groundunit"); + this.getContainer()?.querySelector("#air-defence-spawn-menu")?.classList.toggle("hide", type !== "air-defence"); + this.getContainer()?.querySelector("#air-defence-spawn-button")?.classList.toggle("is-open", type === "air-defence"); this.getContainer()?.querySelector("#navyunit-spawn-menu")?.classList.toggle("hide", type !== "navyunit"); this.getContainer()?.querySelector("#navyunit-spawn-button")?.classList.toggle("is-open", type === "navyunit"); this.getContainer()?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", type !== "smoke"); @@ -170,6 +180,9 @@ export class MapContextMenu extends ContextMenu { this.#helicopterSpawnMenu.reset(); this.#helicopterSpawnMenu.setCountries(); this.#helicopterSpawnMenu.clearCirclesPreviews(); + this.#airDefenceUnitSpawnMenu.reset(); + this.#airDefenceUnitSpawnMenu.setCountries(); + this.#airDefenceUnitSpawnMenu.clearCirclesPreviews(); this.#groundUnitSpawnMenu.reset(); this.#groundUnitSpawnMenu.setCountries(); this.#groundUnitSpawnMenu.clearCirclesPreviews(); @@ -203,11 +216,13 @@ export class MapContextMenu extends ContextMenu { this.#aircraftSpawnMenu.reset(); this.#helicopterSpawnMenu.reset(); + this.#airDefenceUnitSpawnMenu.reset(); this.#groundUnitSpawnMenu.reset(); this.#navyUnitSpawnMenu.reset(); this.#aircraftSpawnMenu.clearCirclesPreviews(); this.#helicopterSpawnMenu.clearCirclesPreviews(); + this.#airDefenceUnitSpawnMenu.clearCirclesPreviews(); this.#groundUnitSpawnMenu.clearCirclesPreviews(); this.#navyUnitSpawnMenu.clearCirclesPreviews(); @@ -224,6 +239,7 @@ export class MapContextMenu extends ContextMenu { this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getApp().getActiveCoalition()) }); this.#aircraftSpawnMenu.setCountries(); this.#helicopterSpawnMenu.setCountries(); + this.#airDefenceUnitSpawnMenu.setCountries(); this.#groundUnitSpawnMenu.setCountries(); this.#navyUnitSpawnMenu.setCountries(); } @@ -237,6 +253,7 @@ export class MapContextMenu extends ContextMenu { this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getApp().getActiveCoalition()) }); this.#aircraftSpawnMenu.setCountries(); this.#helicopterSpawnMenu.setCountries(); + this.#airDefenceUnitSpawnMenu.setCountries(); this.#groundUnitSpawnMenu.setCountries(); this.#navyUnitSpawnMenu.setCountries(); } diff --git a/client/src/controls/unitspawnmenu.ts b/client/src/controls/unitspawnmenu.ts index 0ed023d1..b0ee3fd4 100644 --- a/client/src/controls/unitspawnmenu.ts +++ b/client/src/controls/unitspawnmenu.ts @@ -31,6 +31,7 @@ export class UnitSpawnMenu { #unitDatabase: UnitDatabase; #countryCodes: any; #orderByRole: boolean; + protected unitTypeFilter = (unit:any) => { return true; }; /* Controls */ #unitRoleTypeDropdown: Dropdown; @@ -258,7 +259,7 @@ export class UnitSpawnMenu { if (this.#orderByRole) this.#unitRoleTypeDropdown.setOptions(this.#unitDatabase.getRoles()); else - this.#unitRoleTypeDropdown.setOptions(this.#unitDatabase.getTypes()); + this.#unitRoleTypeDropdown.setOptions(this.#unitDatabase.getTypes(this.unitTypeFilter)); this.#unitLoadoutListEl.replaceChildren(); this.#unitLoadoutDropdown.reset(); @@ -585,6 +586,7 @@ export class HelicopterSpawnMenu extends UnitSpawnMenu { export class GroundUnitSpawnMenu extends UnitSpawnMenu { protected showRangeCircles: boolean = true; + protected unitTypeFilter = (unit:any) => {return !(/\bAAA|SAM\b/.test(unit.type) || /\bmanpad|stinger\b/i.test(unit.type))}; /** * @@ -623,6 +625,20 @@ export class GroundUnitSpawnMenu extends UnitSpawnMenu { } } +export class AirDefenceUnitSpawnMenu extends GroundUnitSpawnMenu { + + protected unitTypeFilter = (unit:any) => {return /\bAAA|SAM\b/.test(unit.type) || /\bmanpad|stinger\b/i.test(unit.type)}; + + /** + * + * @param ID - the ID of the HTML element which will contain the context menu + */ + constructor(ID: string){ + super(ID); + this.setMaxUnitCount(4); + } +} + export class NavyUnitSpawnMenu extends UnitSpawnMenu { /** * diff --git a/client/src/unit/databases/unitdatabase.ts b/client/src/unit/databases/unitdatabase.ts index ba7c41cc..94c8acca 100644 --- a/client/src/unit/databases/unitdatabase.ts +++ b/client/src/unit/databases/unitdatabase.ts @@ -94,10 +94,12 @@ export class UnitDatabase { } /* Returns a list of all possible types in a database */ - getTypes() { + getTypes(unitFilter?:CallableFunction) { var filteredBlueprints = this.getBlueprints(); var types: string[] = []; for (let unit in filteredBlueprints) { + if ( typeof unitFilter === "function" && !unitFilter(filteredBlueprints[unit])) + continue; var type = filteredBlueprints[unit].type; if (type && type !== "" && !types.includes(type)) types.push(type); diff --git a/client/src/unit/unit.ts b/client/src/unit/unit.ts index 341e96e9..a6a25fe5 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; @@ -282,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(), @@ -331,14 +340,27 @@ export class Unit extends CustomMarker { } } + /** + * + * @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 { @@ -356,12 +378,20 @@ 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()) { @@ -400,27 +430,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); @@ -429,18 +483,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() { diff --git a/client/views/contextmenus/map.ejs b/client/views/contextmenus/map.ejs index 75533346..ae4de75d 100644 --- a/client/views/contextmenus/map.ejs +++ b/client/views/contextmenus/map.ejs @@ -6,6 +6,8 @@ data-on-click-params='{ "type": "aircraft" }' class="ol-contexmenu-button"> +