Created group handlers

This commit is contained in:
Pax1601 2023-11-15 15:38:16 +01:00
parent 25e2c50438
commit 8d7a49a31f
4 changed files with 172 additions and 75 deletions

View File

@ -206,7 +206,8 @@
"canTargetPoint": false,
"canRearm": false,
"tags": "",
"markerFile": "groundunit-sam-launcher"
"markerFile": "groundunit-sam-launcher",
"unitWhenGrouped": "SA-3 SAM Battery"
},
"AAV7": {
"name": "AAV7",
@ -1545,7 +1546,8 @@
"abilities": "",
"canTargetPoint": false,
"canRearm": false,
"markerFile": "groundunit-sam-launcher"
"markerFile": "groundunit-sam-launcher",
"unitWhenGrouped": "Hawk SAM Battery"
},
"Hawk pcp": {
"name": "Hawk pcp",
@ -2518,7 +2520,8 @@
"abilities": "",
"canTargetPoint": false,
"canRearm": false,
"markerFile": "groundunit-sam-launcher"
"markerFile": "groundunit-sam-launcher",
"unitWhenGrouped": "SA-6 SAM Battery"
},
"LAV-25": {
"name": "LAV-25",
@ -3788,7 +3791,8 @@
"abilities": "",
"canTargetPoint": false,
"canRearm": false,
"markerFile": "groundunit-sam-launcher"
"markerFile": "groundunit-sam-launcher",
"unitWhenGrouped": "SA-8 SAM Battery"
},
"Paratrooper AKS-74": {
"name": "Paratrooper AKS-74",
@ -4028,17 +4032,18 @@
"abilities": "",
"canTargetPoint": false,
"canRearm": false,
"markerFile": "groundunit-sam-launcher"
"markerFile": "groundunit-sam-launcher",
"unitWhenGrouped": "Patriot site"
},
"Patriot site": {
"name": "Patriot site",
"coalition": "blue",
"era": "Late Cold War",
"label": "Patriot site",
"shortLabel": "Patriot site",
"label": "Patriot SAM Battery",
"shortLabel": "PA",
"range": "Long",
"filename": "",
"type": "SAM Site Parts",
"type": "SAM Site",
"enabled": true,
"liveries": {
"grc_summer": {
@ -4399,7 +4404,8 @@
"abilities": "",
"canTargetPoint": false,
"canRearm": false,
"markerFile": "groundunit-sam-launcher"
"markerFile": "groundunit-sam-launcher",
"unitWhenGrouped": "SA-5 SAM Battery"
},
"S-300PS 40B6M tr": {
"name": "S-300PS 40B6M tr",
@ -4559,7 +4565,8 @@
"canTargetPoint": false,
"canRearm": false,
"tags": "5P85C",
"markerFile": "groundunit-sam-launcher"
"markerFile": "groundunit-sam-launcher",
"unitWhenGrouped": "SA-10 SAM Battery"
},
"S-300PS 5P85D ln": {
"name": "S-300PS 5P85D ln",
@ -4600,7 +4607,8 @@
"canTargetPoint": false,
"canRearm": false,
"tags": "5P85D",
"markerFile": "groundunit-sam-launcher"
"markerFile": "groundunit-sam-launcher",
"unitWhenGrouped": "SA-10 SAM Battery"
},
"S-300PS 64H6E sr": {
"name": "S-300PS 64H6E sr",
@ -4754,7 +4762,8 @@
"abilities": "",
"canTargetPoint": false,
"canRearm": false,
"markerFile": "groundunit-sam-launcher"
"markerFile": "groundunit-sam-launcher",
"unitWhenGrouped": "SA-11 SAM Battery"
},
"SA-11 Buk SR 9S18M1": {
"name": "SA-11 Buk SR 9S18M1",

45
client/src/unit/group.ts Normal file
View File

@ -0,0 +1,45 @@
import { Unit } from "./unit";
export class Group {
#members: Unit[] = [];
#name: string;
constructor(name: string) {
this.#name = name;
document.addEventListener("unitDeath", (e: any) => {
if (this.#members.includes(e.detail))
this.getLeader()?.onGroupChanged(e.detail);
});
}
getName() {
return this.#name;
}
addMember(member: Unit) {
if (!this.#members.includes(member)) {
this.#members.push(member);
member.setGroup(this);
this.getLeader()?.onGroupChanged(member);
}
}
removeMember(member: Unit) {
if (this.#members.includes(member)) {
delete this.#members[this.#members.indexOf(member)];
member.setGroup(null);
this.getLeader()?.onGroupChanged(member);
}
}
getMembers() {
return this.#members;
}
getLeader() {
return this.#members.find((unit: Unit) => { return (unit.getIsLeader() && unit.getAlive())})
}
}

View File

@ -12,6 +12,7 @@ import { navyUnitDatabase } from './databases/navyunitdatabase';
import { Weapon } from '../weapon/weapon';
import { Ammo, Contact, GeneralSettings, LoadoutBlueprint, ObjectIconOptions, Offset, Radio, TACAN, UnitData } from '../interfaces';
import { RangeCircle } from "../map/rangecircle";
import { Group } from './group';
var pathIcon = new Icon({
iconUrl: '/resources/theme/images/markers/marker-icon.png',
@ -90,7 +91,7 @@ export abstract class Unit extends CustomMarker {
#health: number = 100;
/* Other members used to draw the unit, mostly ancillary stuff like targets, ranges and so on */
#selectable: boolean;
#group: Group | null = null;
#selected: boolean = false;
#hidden: boolean = false;
#highlighted: boolean = false;
@ -163,7 +164,6 @@ export abstract class Unit extends CustomMarker {
super(new LatLng(0, 0), { riseOnHover: true, keyboard: false });
this.ID = ID;
this.#selectable = true;
this.#pathPolyline = new Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1 });
this.#pathPolyline.addTo(getApp().getMap());
@ -238,9 +238,10 @@ export abstract class Unit extends CustomMarker {
* @param dataExtractor The DataExtractor object pointing to the binary buffer which contains the raw data coming from the backend
*/
setData(dataExtractor: DataExtractor) {
/* This variable controls if the marker must be updated. This is not always true since not all variables have an effect on the marker*/
/* This variable controls if the marker must be updated. This is not always true since not all variables have an effect on the marker */
var updateMarker = !getApp().getMap().hasLayer(this);
var oldIsLeader = this.#isLeader;
var datumIndex = 0;
while (datumIndex != DataIndexes.endOfData) {
datumIndex = dataExtractor.extractUInt8();
@ -284,7 +285,7 @@ export abstract class Unit extends CustomMarker {
case DataIndexes.ammo: this.#ammo = dataExtractor.extractAmmo(); break;
case DataIndexes.contacts: this.#contacts = dataExtractor.extractContacts(); document.dispatchEvent(new CustomEvent("contactsUpdated", { detail: this })); break;
case DataIndexes.activePath: this.#activePath = dataExtractor.extractActivePath(); break;
case DataIndexes.isLeader: this.#isLeader = dataExtractor.extractBool(); updateMarker = true; break;
case DataIndexes.isLeader: this.#isLeader = dataExtractor.extractBool(); break;
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;
@ -299,6 +300,17 @@ export abstract class Unit extends CustomMarker {
if (updateMarker)
this.#updateMarker();
/* Redraw the marker if isLeader has changed. TODO I don't love this approach, observables may be more elegant */
if (oldIsLeader !== this.#isLeader) {
this.#redrawMarker();
/* Reapply selection */
if (this.getSelected()) {
this.setSelected(false);
this.setSelected(true);
}
}
/* If the unit is selected or if the view is centered on this unit, sent the update signal so that other elements like the UnitControlPanel can be updated. */
if (this.getSelected() || getApp().getMap().getCenterUnit() === this)
document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this }));
@ -477,7 +489,19 @@ export abstract class Unit extends CustomMarker {
* @returns Unit[]
*/
getGroupMembers() {
return Object.values(getApp().getUnitsManager().getUnits()).filter((unit: Unit) => { return unit != this && unit.getGroupName() === this.getGroupName(); });
if (this.#group !== null)
return this.#group.getMembers().filter((unit: Unit) => { return unit != this; })
return [];
}
/** Return the leader of the group
*
* @returns Unit The leader of the group
*/
getGroupLeader() {
if (this.#group !== null)
return this.#group.getLeader();
return null;
}
/** Returns whether the user is allowed to command this unit, based on coalition
@ -500,6 +524,14 @@ export abstract class Unit extends CustomMarker {
return this.getDatabase()?.getByName(this.#name);
}
getGroup() {
return this.#group;
}
setGroup(group: Group | null) {
this.#group = group;
}
drawLines() {
/* Leaflet does not like it when you change coordinates when the map is zooming */
if (!getApp().getMap().isZooming()) {
@ -509,7 +541,7 @@ export abstract class Unit extends CustomMarker {
}
}
checkRedraw() {
checkZoomRedraw() {
return false;
}
@ -956,6 +988,10 @@ export abstract class Unit extends CustomMarker {
return this;
}
onGroupChanged(member: Unit) {
this.#redrawMarker();
}
/***********************************************/
#onClick(e: any) {
/* Exit if we were waiting for a doubleclick */
@ -1218,11 +1254,6 @@ export abstract class Unit extends CustomMarker {
if (hotgroupEl)
hotgroupEl.innerText = String(this.#hotgroup);
}
/* If the unit is a leader of a SAM Site, show the group as a SAM with the appropriate short label */
if (this.#isLeader) {
}
}
/* Set vertical offset for altitude stacking */
@ -1234,6 +1265,9 @@ export abstract class Unit extends CustomMarker {
#redrawMarker() {
this.removeFrom(getApp().getMap());
this.#updateMarker();
/* Activate the selection effects on the marker */
this.getElement()?.querySelector(`.unit`)?.toggleAttribute("data-is-selected", this.getSelected());
}
#drawPath() {
@ -1449,15 +1483,8 @@ export abstract class Unit extends CustomMarker {
}
#onZoom(e: any) {
if (this.checkRedraw()) {
if (this.checkZoomRedraw())
this.#redrawMarker();
/* If the marker has been redrawn, reapply selection */
if (this.getSelected()) {
this.setSelected(false);
this.setSelected(true);
}
}
this.#updateMarker();
}
}
@ -1606,16 +1633,16 @@ export class GroundUnit extends Unit {
return unit
return prev;
}, null);
unitWhenGrouped == member !== null ? member?.getDatabaseEntry()?.unitWhenGrouped : unitWhenGrouped;
unitWhenGrouped = (member !== null ? member?.getDatabaseEntry()?.unitWhenGrouped : unitWhenGrouped);
}
if (unitWhenGrouped !== null)
if (unitWhenGrouped)
return this.getDatabase()?.getByName(unitWhenGrouped);
else
return this.getDatabase()?.getByName(this.getName());
}
/* When we zoom past the grouping limit, grouping is enabled and the unit is a leader, we redraw the unit to apply any possible grouped marker */
checkRedraw(): boolean {
checkZoomRedraw(): boolean {
return (this.getIsLeader() && getApp().getMap().getVisibilityOptions()[HIDE_GROUP_MEMBERS] &&
(getApp().getMap().getZoom() >= GROUPING_ZOOM_TRANSITION && getApp().getMap().getPreviousZoom() < GROUPING_ZOOM_TRANSITION ||
getApp().getMap().getZoom() < GROUPING_ZOOM_TRANSITION && getApp().getMap().getPreviousZoom() >= GROUPING_ZOOM_TRANSITION))

View File

@ -15,6 +15,7 @@ import { Popup } from "../popups/popup";
import { HotgroupPanel } from "../panels/hotgrouppanel";
import { Contact, UnitData, UnitSpawnTable } from "../interfaces";
import { Dialog } from "../dialog/dialog";
import { Group } from "./group";
/** The UnitsManager handles the creation, update, and control of units. Data is strictly updated by the server ONLY. This means that any interaction from the user will always and only
* result in a command to the server, executed by means of a REST PUT request. Any subsequent change in data will be reflected only when the new data is sent back by the server. This strategy allows
@ -25,9 +26,9 @@ export class UnitsManager {
#deselectionEventDisabled: boolean = false;
#requestDetectionUpdate: boolean = false;
#selectionEventDisabled: boolean = false;
#slowDeleteDialog!:Dialog;
#slowDeleteDialog!: Dialog;
#units: { [ID: number]: Unit };
#groups: { [groupName: string]: Group } = {};
constructor() {
this.#copiedUnits = [];
@ -46,10 +47,9 @@ export class UnitsManager {
document.addEventListener('selectedUnitsChangeSpeed', (e: any) => { this.selectedUnitsChangeSpeed(e.detail.type) });
document.addEventListener('unitDeselection', (e: CustomEvent) => this.#onUnitDeselection(e.detail));
document.addEventListener('unitSelection', (e: CustomEvent) => this.#onUnitSelection(e.detail));
document.addEventListener("toggleMarkerProtection", (ev: CustomEventInit) => { this.#showNumberOfSelectedProtectedUnits() });
document.addEventListener("toggleMarkerProtection", (ev:CustomEventInit) => { this.#showNumberOfSelectedProtectedUnits() });
this.#slowDeleteDialog = new Dialog( "slow-delete-dialog" );
this.#slowDeleteDialog = new Dialog("slow-delete-dialog");
}
/**
@ -130,6 +130,22 @@ export class UnitsManager {
this.#units[ID]?.setData(dataExtractor);
}
/* Update the unit groups */
for (let ID in this.#units) {
const unit = this.#units[ID];
const groupName = unit.getGroupName();
if (groupName !== "") {
/* If the group does not yet exist, create it */
if (!(groupName in this.#groups))
this.#groups[groupName] = new Group(groupName);
/* If the unit was not assigned to a group yet, assign it */
if (unit.getGroup() === null)
this.#groups[groupName].addMember(unit);
}
}
/* If we are not in Game Master mode, visibility of units by the user is determined by the detections of the units themselves. This is performed here.
This operation is computationally expensive, therefore it is only performed when #requestDetectionUpdate is true. This happens whenever a change in the detectionUpdates is detected
*/
@ -209,9 +225,9 @@ export class UnitsManager {
*
* @param hotgroup The hotgroup number
*/
selectUnitsByHotgroup(hotgroup: number, deselectAllUnits: boolean = true ) {
selectUnitsByHotgroup(hotgroup: number, deselectAllUnits: boolean = true) {
if ( deselectAllUnits ) {
if (deselectAllUnits) {
this.deselectAllUnits();
}
@ -223,8 +239,8 @@ export class UnitsManager {
* @param options Selection options
* @returns Array of selected units
*/
getSelectedUnits(options?: { excludeHumans?: boolean, excludeProtected?:boolean, onlyOnePerGroup?: boolean, showProtectionReminder?:boolean }) {
let selectedUnits:Unit[] = [];
getSelectedUnits(options?: { excludeHumans?: boolean, excludeProtected?: boolean, onlyOnePerGroup?: boolean, showProtectionReminder?: boolean }) {
let selectedUnits: Unit[] = [];
let numProtectedUnits = 0;
for (const [ID, unit] of Object.entries(this.#units)) {
if (unit.getSelected()) {
@ -533,7 +549,7 @@ export class UnitsManager {
* @param operateAsBool If true, units will operate as blue
*/
selectedUnitsSetOperateAs(operateAsBool: boolean) {
var operateAs = operateAsBool? "blue": "red";
var operateAs = operateAsBool ? "blue" : "red";
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
for (let idx in selectedUnits) {
selectedUnits[idx].setOperateAs(operateAs);
@ -585,7 +601,7 @@ export class UnitsManager {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true });
if ( selectedUnits.length === 0)
if (selectedUnits.length === 0)
return;
var count = 1;
@ -672,7 +688,7 @@ export class UnitsManager {
});
this.#showActionMessage(selectedUnits, `unit simulating fire fight`);
}
/** Instruct units to enter into scenic AAA mode. Units will shoot in the air without aiming
*
*/
@ -761,7 +777,7 @@ export class UnitsManager {
var unit = selectedUnits[idx];
units.push({ ID: unit.ID, location: unit.getPosition() });
}
getApp().getServerManager().cloneUnits(units, true, 0 /* No spawn points, we delete the original units */);
getApp().getServerManager().cloneUnits(units, true, 0 /* No spawn points, we delete the original units */);
} else {
(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Groups can only be created from units of the same category`);
}
@ -795,7 +811,7 @@ export class UnitsManager {
* @returns
*/
selectedUnitsDelete(explosion: boolean = false, explosionType: string = "") {
var selectedUnits = this.getSelectedUnits({excludeProtected:true}); /* Can be applied to humans too */
var selectedUnits = this.getSelectedUnits({ excludeProtected: true }); /* Can be applied to humans too */
const selectionContainsAHuman = selectedUnits.some((unit: Unit) => {
return unit.getHuman() === true;
});
@ -812,7 +828,7 @@ export class UnitsManager {
}
if (selectedUnits.length >= DELETE_SLOW_THRESHOLD)
this.#showSlowDeleteDialog(selectedUnits).then((action:any) => {
this.#showSlowDeleteDialog(selectedUnits).then((action: any) => {
if (action === "delete-slow")
doDelete(explosion, explosionType, false);
else if (action === "delete-immediate")
@ -868,7 +884,7 @@ export class UnitsManager {
this.#copiedUnits = JSON.parse(JSON.stringify(this.getSelectedUnits().map((unit: Unit) => { return unit.getData() }))); /* Can be applied to humans too */
(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`${this.#copiedUnits.length} units copied`);
}
/*********************** Unit manipulation functions ************************/
/** Paste the copied units
*
@ -889,7 +905,7 @@ export class UnitsManager {
if (unitSpawnPoints !== undefined)
spawnPoints += unitSpawnPoints;
})
if (spawnPoints > getApp().getMissionManager().getAvailableSpawnPoints()) {
(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Not enough spawn points available!");
return false;
@ -924,7 +940,7 @@ export class UnitsManager {
markers.push(getApp().getMap().addTemporaryMarker(position, unit.name, unit.coalition));
units.push({ ID: unit.ID, location: position });
});
getApp().getServerManager().cloneUnits(units, false, spawnPoints, (res: any) => {
if (res.commandHash !== undefined) {
markers.forEach((marker: TemporaryUnitMarker) => {
@ -972,7 +988,7 @@ export class UnitsManager {
if (Math.random() < IADSDensities[type]) {
/* Get a random blueprint depending on the selected parameters and spawn the unit */
const unitBlueprint = randomUnitBlueprint(groundUnitDatabase, { type: type, eras: activeEras, ranges: activeRanges });
if (unitBlueprint)
if (unitBlueprint)
this.spawnUnits("GroundUnit", [{ unitType: unitBlueprint.name, location: latlng, liveryID: "" }], coalitionArea.getCoalition(), true);
}
}
@ -1044,24 +1060,24 @@ export class UnitsManager {
* @param callback CallableFunction called when the command is received by the server
* @returns True if the spawn command was successfully sent
*/
spawnUnits(category: string, units: UnitSpawnTable[], coalition: string = "blue", immediate: boolean = true, airbase: string = "", country: string = "", callback: CallableFunction = () => {}) {
spawnUnits(category: string, units: UnitSpawnTable[], coalition: string = "blue", immediate: boolean = true, airbase: string = "", country: string = "", callback: CallableFunction = () => { }) {
var spawnPoints = 0;
var spawnFunction = () => {};
var spawnFunction = () => { };
var spawnsRestricted = getApp().getMissionManager().getCommandModeOptions().restrictSpawns && getApp().getMissionManager().getRemainingSetupTime() < 0 && getApp().getMissionManager().getCommandModeOptions().commandMode !== GAME_MASTER;
if (category === "Aircraft") {
if (airbase == "" && spawnsRestricted) {
(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Aircrafts can be air spawned during the SETUP phase only");
return false;
}
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => {return points + aircraftDatabase.getSpawnPointsByName(unit.unitType)}, 0);
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => { return points + aircraftDatabase.getSpawnPointsByName(unit.unitType) }, 0);
spawnFunction = () => getApp().getServerManager().spawnAircrafts(units, coalition, airbase, country, immediate, spawnPoints, callback);
} else if (category === "Helicopter") {
if (airbase == "" && spawnsRestricted) {
(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Helicopters can be air spawned during the SETUP phase only");
return false;
}
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => {return points + helicopterDatabase.getSpawnPointsByName(unit.unitType)}, 0);
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => { return points + helicopterDatabase.getSpawnPointsByName(unit.unitType) }, 0);
spawnFunction = () => getApp().getServerManager().spawnHelicopters(units, coalition, airbase, country, immediate, spawnPoints, callback);
} else if (category === "GroundUnit") {
@ -1069,7 +1085,7 @@ export class UnitsManager {
(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Ground units can be spawned during the SETUP phase only");
return false;
}
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => {return points + groundUnitDatabase.getSpawnPointsByName(unit.unitType)}, 0);
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => { return points + groundUnitDatabase.getSpawnPointsByName(unit.unitType) }, 0);
spawnFunction = () => getApp().getServerManager().spawnGroundUnits(units, coalition, country, immediate, spawnPoints, callback);
} else if (category === "NavyUnit") {
@ -1077,7 +1093,7 @@ export class UnitsManager {
(getApp().getPopupsManager().get("infoPopup") as Popup).setText("Navy units can be spawned during the SETUP phase only");
return false;
}
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => {return points + navyUnitDatabase.getSpawnPointsByName(unit.unitType)}, 0);
spawnPoints = units.reduce((points: number, unit: UnitSpawnTable) => { return points + navyUnitDatabase.getSpawnPointsByName(unit.unitType) }, 0);
spawnFunction = () => getApp().getServerManager().spawnNavyUnits(units, coalition, country, immediate, spawnPoints, callback);
}
@ -1143,30 +1159,30 @@ export class UnitsManager {
else if (units.length > 1)
(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`${units[0].getUnitName()} and ${units.length - 1} other units ${message}`);
}
#showSlowDeleteDialog(selectedUnits:Unit[]) {
let button:HTMLButtonElement | null = null;
const deletionTime = Math.round( selectedUnits.length * DELETE_CYCLE_TIME ).toString();
#showSlowDeleteDialog(selectedUnits: Unit[]) {
let button: HTMLButtonElement | null = null;
const deletionTime = Math.round(selectedUnits.length * DELETE_CYCLE_TIME).toString();
const dialog = this.#slowDeleteDialog;
const element = dialog.getElement();
const listener = (ev:MouseEvent) => {
const listener = (ev: MouseEvent) => {
if (ev.target instanceof HTMLButtonElement && ev.target.matches("[data-action]"))
button = ev.target;
}
element.querySelectorAll(".deletion-count").forEach( el => el.innerHTML = selectedUnits.length.toString() );
element.querySelectorAll(".deletion-time").forEach( el => el.innerHTML = deletionTime );
element.querySelectorAll(".deletion-count").forEach(el => el.innerHTML = selectedUnits.length.toString());
element.querySelectorAll(".deletion-time").forEach(el => el.innerHTML = deletionTime);
dialog.show();
return new Promise((resolve) => {
element.addEventListener("click", listener);
const interval = setInterval(() => {
if (button instanceof HTMLButtonElement ) {
if (button instanceof HTMLButtonElement) {
clearInterval(interval);
dialog.hide();
element.removeEventListener("click", listener);
resolve( button.getAttribute("data-action") );
resolve(button.getAttribute("data-action"));
}
}, 250);
});
@ -1174,18 +1190,18 @@ export class UnitsManager {
#showNumberOfSelectedProtectedUnits() {
const map = getApp().getMap();
const selectedUnits = this.getSelectedUnits();
const numSelectedUnits = selectedUnits.length;
const numProtectedUnits = selectedUnits.filter((unit:Unit) => map.unitIsProtected(unit) ).length;
const selectedUnits = this.getSelectedUnits();
const numSelectedUnits = selectedUnits.length;
const numProtectedUnits = selectedUnits.filter((unit: Unit) => map.unitIsProtected(unit)).length;
if (numProtectedUnits === 1 && numSelectedUnits === numProtectedUnits)
(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Notice: unit is protected`);
if (numProtectedUnits > 1)
(getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Notice: selection contains ${numProtectedUnits} protected units.`);
}
#unitIsProtected(unit:Unit) {
#unitIsProtected(unit: Unit) {
return getApp().getMap().unitIsProtected(unit)
}
}