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 22f6b214..a8a13198 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;
@@ -279,6 +284,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(),
@@ -328,14 +337,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 {
@@ -352,12 +374,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()) {
@@ -396,27 +426,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 +479,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">
+