Merge and fix

This commit is contained in:
PeekabooSteam
2023-11-16 11:28:32 +00:00
89 changed files with 18208 additions and 16222 deletions

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

@@ -5,12 +5,14 @@ import { CustomMarker } from '../map/markers/custommarker';
import { SVGInjector } from '@tanem/svg-injector';
import { UnitDatabase } from './databases/unitdatabase';
import { TargetMarker } from '../map/markers/targetmarker';
import { DLINK, DataIndexes, GAME_MASTER, HIDE_GROUP_MEMBERS, IDLE, IRST, MOVE_UNIT, OPTIC, RADAR, ROEs, RWR, SHOW_UNIT_CONTACTS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, VISUAL, emissionsCountermeasures, reactionsToThreat, states, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, GROUND_UNIT_AIR_DEFENCE_REGEX } from '../constants/constants';
import { DLINK, DataIndexes, GAME_MASTER, HIDE_GROUP_MEMBERS, IDLE, IRST, MOVE_UNIT, OPTIC, RADAR, ROEs, RWR, SHOW_UNIT_CONTACTS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, VISUAL, emissionsCountermeasures, reactionsToThreat, states, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, GROUPING_ZOOM_TRANSITION, GROUND_UNIT_AIR_DEFENCE_REGEX } from '../constants/constants';
import { DataExtractor } from '../server/dataextractor';
import { groundUnitDatabase } from './databases/groundunitdatabase';
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',
@@ -20,12 +22,11 @@ var pathIcon = new Icon({
/**
* Unit class which controls unit behaviour
*
* Just about everything is a unit - even missiles!
*/
export class Unit extends CustomMarker {
export abstract class Unit extends CustomMarker {
ID: number;
/* Data controlled directly by the backend. No setters are provided to avoid misalignments */
#alive: boolean = false;
#human: boolean = false;
#controlled: boolean = false;
@@ -87,8 +88,10 @@ export class Unit extends CustomMarker {
#operateAs: string = "blue";
#shotsScatter: number = 2;
#shotsIntensity: number = 2;
#health: number = 100;
#selectable: boolean;
/* Other members used to draw the unit, mostly ancillary stuff like targets, ranges and so on */
#group: Group | null = null;
#selected: boolean = false;
#hidden: boolean = false;
#highlighted: boolean = false;
@@ -96,8 +99,8 @@ export class Unit extends CustomMarker {
#pathMarkers: Marker[] = [];
#pathPolyline: Polyline;
#contactsPolylines: Polyline[] = [];
#engagementCircle: Circle;
#acquisitionCircle: Circle;
#engagementCircle: RangeCircle;
#acquisitionCircle: RangeCircle;
#miniMapMarker: CircleMarker | null = null;
#targetPositionMarker: TargetMarker;
#targetPositionPolyline: Polyline;
@@ -105,6 +108,7 @@ export class Unit extends CustomMarker {
#hotgroup: number | null = null;
#detectionMethods: number[] = [];
/* Getters for backend driven data */
getAlive() { return this.#alive };
getHuman() { return this.#human };
getControlled() { return this.#controlled };
@@ -121,8 +125,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 };
@@ -145,8 +149,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;
@@ -159,15 +164,15 @@ export 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());
this.#targetPositionMarker = new TargetMarker(new LatLng(0, 0));
this.#targetPositionPolyline = new Polyline([], { color: '#FF0000', weight: 3, opacity: 0.5, smoothFactor: 1 });
this.#engagementCircle = new Circle(this.getPosition(), { radius: 0, weight: 4, opacity: 1, fillOpacity: 0, dashArray: "4 8", interactive: false, bubblingMouseEvents: false });
this.#acquisitionCircle = new Circle(this.getPosition(), { radius: 0, weight: 2, opacity: 1, fillOpacity: 0, dashArray: "8 12", interactive: false, bubblingMouseEvents: false });
this.#engagementCircle = new RangeCircle(this.getPosition(), { radius: 0, weight: 4, opacity: 1, fillOpacity: 0, dashArray: "4 8", interactive: false, bubblingMouseEvents: false });
this.#acquisitionCircle = new RangeCircle(this.getPosition(), { radius: 0, weight: 2, opacity: 1, fillOpacity: 0, dashArray: "8 12", interactive: false, bubblingMouseEvents: false });
/* Leaflet events listeners */
this.on('click', (e) => this.#onClick(e));
this.on('dblclick', (e) => this.#onDoubleClick(e));
this.on('contextmenu', (e) => this.#onContextMenu(e));
@@ -181,7 +186,7 @@ export class Unit extends CustomMarker {
this.setHighlighted(false);
document.dispatchEvent(new CustomEvent("unitMouseout", { detail: this }));
});
getApp().getMap().on("zoomend", () => { this.#onZoom(); })
getApp().getMap().on("zoomend", (e: any) => { this.#onZoom(e); })
/* Deselect units if they are hidden */
document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => {
@@ -192,13 +197,14 @@ export class Unit extends CustomMarker {
window.setTimeout(() => { this.setSelected(this.getSelected() && !this.getHidden()) }, 300);
});
/* Update the marker when the visibility options change */
document.addEventListener("mapVisibilityOptionsChanged", (ev: CustomEventInit) => {
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())
@@ -206,10 +212,25 @@ export class Unit extends CustomMarker {
});
}
getCategory() {
// Overloaded by child classes
return "";
}
/********************** Abstract methods *************************/
/** Get the unit category string
*
* @returns string The unit category
*/
abstract getCategory(): string;
/** Get the icon options
* Used to configure how the marker appears on the map
*
* @returns ObjectIconOptions
*/
abstract getIconOptions(): ObjectIconOptions;
/** Get the actions that this unit can perform
*
* @returns Object containing the available actions
*/
abstract getActions(): {[key: string]: { text: string, tooltip: string, type: string}};
/** Get the category but for display use - for the user. (i.e. has spaces in it)
*
@@ -220,9 +241,15 @@ export class Unit extends CustomMarker {
}
/********************** Unit data *************************/
/** This function is called by the units manager to update all the data coming from the backend. It reads the binary raw data using a DataExtractor
*
* @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 */
var updateMarker = !getApp().getMap().hasLayer(this);
var oldIsLeader = this.#isLeader;
var datumIndex = 0;
while (datumIndex != DataIndexes.endOfData) {
datumIndex = dataExtractor.extractUInt8();
@@ -231,7 +258,7 @@ export class Unit extends CustomMarker {
case DataIndexes.alive: this.setAlive(dataExtractor.extractBool()); updateMarker = true; break;
case DataIndexes.human: this.#human = dataExtractor.extractBool(); break;
case DataIndexes.controlled: this.#controlled = dataExtractor.extractBool(); updateMarker = true; break;
case DataIndexes.coalition: this.#coalition = enumToCoalition(dataExtractor.extractUInt8()); updateMarker = true; this.#clearRanges(); break;
case DataIndexes.coalition: let newCoalition = enumToCoalition(dataExtractor.extractUInt8()); updateMarker = true; if (newCoalition != this.#coalition) this.#clearRanges(); this.#coalition = newCoalition; break; // If the coalition has changed, redraw the range circles to update the colour
case DataIndexes.country: this.#country = dataExtractor.extractUInt8(); break;
case DataIndexes.name: this.#name = dataExtractor.extractString(); break;
case DataIndexes.unitName: this.#unitName = dataExtractor.extractString(); break;
@@ -266,32 +293,37 @@ export 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;
case DataIndexes.health: this.#health = dataExtractor.extractUInt8(); updateMarker = true; break;
}
}
/* Dead units can't be selected */
/* Dead and hidden units can't be selected */
this.setSelected(this.getSelected() && this.#alive && !this.getHidden())
/* Update the marker if required */
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 }));
}
drawLines() {
/* Leaflet does not like it when you change coordinates when the map is zooming */
if (!getApp().getMap().isZooming()) {
this.#drawPath();
this.#drawContacts();
this.#drawTarget();
}
}
/** Get unit data collated into an object
*
* @returns object populated by unit information which can also be retrieved using getters
@@ -342,7 +374,8 @@ export class Unit extends CustomMarker {
isLeader: this.#isLeader,
operateAs: this.#operateAs,
shotsScatter: this.#shotsScatter,
shotsIntensity: this.#shotsIntensity
shotsIntensity: this.#shotsIntensity,
health: this.#health
}
}
@@ -362,27 +395,6 @@ export class Unit extends CustomMarker {
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,
showHotgroup: false,
showUnitIcon: true,
showShortLabel: false,
showFuel: false,
showAmmo: false,
showSummary: true,
showCallsign: true,
rotateToHeading: false
}
}
/** Set the unit as alive or dead
*
* @param newAlive (boolean) true = alive, false = dead
@@ -398,16 +410,11 @@ export class Unit extends CustomMarker {
* @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()) {
/* Only alive units can be selected that belong to the commanded coalition can be selected */
if ((this.#alive || !selected) && this.belongsToCommandedCoalition() && this.getSelected() != selected) {
this.#selected = selected;
/* Circles don't like to be updated when the map is zooming */
if (!getApp().getMap().isZooming())
this.#drawRanges();
else
this.once("zoomend", () => { this.#drawRanges(); })
/* If selected, update the marker to show the selected effects, else clear all the drawings that are only shown for selected units. */
if (selected) {
this.#updateMarker();
}
@@ -417,21 +424,27 @@ export class Unit extends CustomMarker {
this.#clearTarget();
}
this.getElement()?.querySelector(`.unit`)?.toggleAttribute("data-is-selected", selected);
if (this.getCategory() === "GroundUnit" && getApp().getMap().getZoom() < 13) {
if (this.#isLeader)
/* When the group leader is selected, if grouping is active, all the other group members are also selected */
if (this.getCategory() === "GroundUnit" && getApp().getMap().getZoom() < GROUPING_ZOOM_TRANSITION) {
if (this.#isLeader) {
/* Redraw the marker in case the leader unit was replaced by a group marker, like for SAM Sites */
this.#redrawMarker();
this.getGroupMembers().forEach((unit: Unit) => unit.setSelected(selected));
else
}
else {
this.#updateMarker();
}
}
// Trigger events after all (de-)selecting has been done
/* Activate the selection effects on the marker */
this.getElement()?.querySelector(`.unit`)?.toggleAttribute("data-is-selected", selected);
/* Trigger events after all (de-)selecting has been done */
if (selected) {
document.dispatchEvent(new CustomEvent("unitSelection", { detail: this }));
} else {
document.dispatchEvent(new CustomEvent("unitDeselection", { detail: this }));
}
}
}
@@ -443,22 +456,6 @@ export class Unit extends CustomMarker {
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)
@@ -481,9 +478,9 @@ export class Unit extends CustomMarker {
* @param highlighted (boolean)
*/
setHighlighted(highlighted: boolean) {
if (this.getSelectable() && this.#highlighted != highlighted) {
this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-highlighted", highlighted);
if (this.#highlighted != highlighted) {
this.#highlighted = highlighted;
this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-highlighted", highlighted);
this.getGroupMembers().forEach((unit: Unit) => unit.setHighlighted(highlighted));
}
}
@@ -501,7 +498,19 @@ export 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
@@ -520,6 +529,31 @@ export class Unit extends CustomMarker {
return this.getDatabase()?.getSpawnPointsByName(this.getName());
}
getDatabaseEntry() {
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()) {
this.#drawPath();
this.#drawContacts();
this.#drawTarget();
}
}
checkZoomRedraw() {
return false;
}
/********************** Icon *************************/
createIcon(): void {
/* Set the icon */
@@ -530,6 +564,7 @@ export class Unit extends CustomMarker {
});
this.setIcon(icon);
/* Create the base element */
var el = document.createElement("div");
el.classList.add("unit");
el.setAttribute("data-object", `unit-${this.getMarkerCategory()}`);
@@ -537,8 +572,8 @@ export class Unit extends CustomMarker {
var iconOptions = this.getIconOptions();
// Generate and append elements depending on active options
// Velocity vector
/* Generate and append elements depending on active options */
/* Velocity vector */
if (iconOptions.showVvi) {
var vvi = document.createElement("div");
vvi.classList.add("unit-vvi");
@@ -546,7 +581,7 @@ export class Unit extends CustomMarker {
el.append(vvi);
}
// Hotgroup indicator
/* Hotgroup indicator */
if (iconOptions.showHotgroup) {
var hotgroup = document.createElement("div");
hotgroup.classList.add("unit-hotgroup");
@@ -556,42 +591,42 @@ export class Unit extends CustomMarker {
el.append(hotgroup);
}
// Main icon
/* Main icon */
if (iconOptions.showUnitIcon) {
var unitIcon = document.createElement("div");
unitIcon.classList.add("unit-icon");
var img = document.createElement("img");
var imgSrc;
/* If a unit does not belong to the commanded coalition or it is not visually detected, show it with the generic aircraft square */
var marker;
if (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)))
imgSrc = this.getMarkerCategory();
marker = this.getDatabaseEntry()?.markerFile ?? this.getMarkerCategory();
else
imgSrc = "aircraft";
img.src = `/resources/theme/images/units/${imgSrc}.svg`;
marker = "aircraft";
img.src = `/resources/theme/images/units/${marker}.svg`;
img.onload = () => SVGInjector(img);
unitIcon.appendChild(img);
unitIcon.toggleAttribute("data-rotate-to-heading", iconOptions.rotateToHeading);
el.append(unitIcon);
}
// State icon
/* State icon */
if (iconOptions.showState) {
var state = document.createElement("div");
state.classList.add("unit-state");
el.appendChild(state);
}
// Short label
/* Short label */
if (iconOptions.showShortLabel) {
var shortLabel = document.createElement("div");
shortLabel.classList.add("unit-short-label");
shortLabel.innerText = getUnitDatabaseByCategory(this.getMarkerCategory())?.getByName(this.#name)?.shortLabel || "";
shortLabel.innerText = this.getDatabaseEntry()?.shortLabel || "";
el.append(shortLabel);
}
// Fuel indicator
/* Fuel indicator */
if (iconOptions.showFuel) {
var fuelIndicator = document.createElement("div");
fuelIndicator.classList.add("unit-fuel");
@@ -601,7 +636,17 @@ export class Unit extends CustomMarker {
el.append(fuelIndicator);
}
// Ammo indicator
/* 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");
ammoIndicator.classList.add("unit-ammo");
@@ -610,7 +655,7 @@ export class Unit extends CustomMarker {
el.append(ammoIndicator);
}
// Unit summary
/* Unit summary */
if (iconOptions.showSummary) {
var summary = document.createElement("div");
summary.classList.add("unit-summary");
@@ -628,25 +673,29 @@ 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())
this.#drawRanges();
else
this.once("zoomend", () => { this.#drawRanges(); })
}
/********************** Visibility *************************/
updateVisibility() {
const hiddenUnits = getApp().getMap().getHiddenTypes();
var hidden = ((this.#human && hiddenUnits.includes("human")) ||
(this.#controlled == false && hiddenUnits.includes("dcs")) ||
(hiddenUnits.includes(this.getMarkerCategory())) ||
(hiddenUnits.includes(this.#coalition)) ||
const hiddenTypes = getApp().getMap().getHiddenTypes();
var hidden = (
/* Hide the unit if it is a human and humans are hidden */
(this.#human && hiddenTypes.includes("human")) ||
/* Hide the unit if it is DCS controlled and DCS controlled units are hidden */
(this.#controlled == false && hiddenTypes.includes("dcs")) ||
/* Hide the unit if this specific category is hidden */
(hiddenTypes.includes(this.getMarkerCategory())) ||
/* Hide the unit if this coalition is hidden */
(hiddenTypes.includes(this.#coalition)) ||
/* Hide the unit if it does not belong to the commanded coalition and it is not detected by a method that can pinpoint its location (RWR does not count) */
(!this.belongsToCommandedCoalition() && (this.#detectionMethods.length == 0 || (this.#detectionMethods.length == 1 && this.#detectionMethods[0] === RWR))) ||
(getApp().getMap().getVisibilityOptions()[HIDE_GROUP_MEMBERS] && !this.#isLeader && this.getCategory() == "GroundUnit" && getApp().getMap().getZoom() < 13 && (this.belongsToCommandedCoalition() || (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0)))) &&
!(this.getSelected());
/* Hide the unit if grouping is activated, the unit is not the group leader, it is not selected, and the zoom is higher than the grouping threshold */
(getApp().getMap().getVisibilityOptions()[HIDE_GROUP_MEMBERS] && !this.#isLeader && this.getCategory() == "GroundUnit" && getApp().getMap().getZoom() < GROUPING_ZOOM_TRANSITION &&
(this.belongsToCommandedCoalition() || (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0)))) &&
!(this.getSelected()
);
/* Force dead units to be hidden */
this.setHidden(hidden || !this.#alive);
}
@@ -666,11 +715,12 @@ export class Unit extends CustomMarker {
getApp().getMap().removeLayer(this);
}
/* Draw the range circles if the unit is not hidden */
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();
@@ -705,7 +755,7 @@ export class Unit extends CustomMarker {
if (typeof (roles) === "string")
roles = [roles];
var loadouts = this.getDatabase()?.getByName(this.#name)?.loadouts;
var loadouts = this.getDatabaseEntry()?.loadouts;
if (loadouts) {
return loadouts.some((loadout: LoadoutBlueprint) => {
return (roles as string[]).some((role: string) => { return loadout.roles.includes(role) });
@@ -719,11 +769,11 @@ export class Unit extends CustomMarker {
}
canTargetPoint() {
return this.getDatabase()?.getByName(this.#name)?.canTargetPoint === true;
return this.getDatabaseEntry()?.canTargetPoint === true;
}
canRearm() {
return this.getDatabase()?.getByName(this.#name)?.canRearm === true;
return this.getDatabaseEntry()?.canRearm === true;
}
canLandAtPoint() {
@@ -731,11 +781,11 @@ export class Unit extends CustomMarker {
}
canAAA() {
return this.getDatabase()?.getByName(this.#name)?.canAAA === true;
return this.getDatabaseEntry()?.canAAA === true;
}
indirectFire() {
return this.getDatabase()?.getByName(this.#name)?.indirectFire === true;
return this.getDatabaseEntry()?.indirectFire === true;
}
isTanker() {
@@ -845,8 +895,8 @@ export class Unit extends CustomMarker {
getApp().getServerManager().setOperateAs(this.ID, coalitionToEnum(operateAs));
}
delete(explosion: boolean, immediate: boolean) {
getApp().getServerManager().deleteUnit(this.ID, explosion, immediate);
delete(explosion: boolean, explosionType: string, immediate: boolean) {
getApp().getServerManager().deleteUnit(this.ID, explosion, explosionType, immediate);
}
refuel() {
@@ -924,11 +974,6 @@ export class Unit extends CustomMarker {
}
/***********************************************/
getActions(): { [key: string]: { text: string, tooltip: string, type: string } } {
/* To be implemented by child classes */ // TODO make Unit an abstract class
return {};
}
executeAction(e: any, action: string) {
if (action === "center-map")
getApp().getMap().centerOnUnit(this.ID);
@@ -952,20 +997,21 @@ export class Unit extends CustomMarker {
return this;
}
onGroupChanged(member: Unit) {
this.#redrawMarker();
}
/***********************************************/
#onClick(e: any) {
// Exit if we were waiting for a doubleclick
/* Exit if we were waiting for a doubleclick */
if (this.#waitingForDoubleClick) {
return;
}
// We'll wait for a doubleclick
/* We'll wait for a doubleclick */
this.#waitingForDoubleClick = true;
this.#doubleClickTimer = window.setTimeout(() => {
// Still waiting so no doubleclick; do the click action
/* Still waiting so no doubleclick; do the click action */
if (this.#waitingForDoubleClick) {
if (getApp().getMap().getState() === IDLE || getApp().getMap().getState() === MOVE_UNIT || e.originalEvent.ctrlKey) {
if (!e.originalEvent.ctrlKey)
@@ -975,17 +1021,17 @@ export class Unit extends CustomMarker {
}
}
// No longer waiting for a doubleclick
/* No longer waiting for a doubleclick */
this.#waitingForDoubleClick = false;
}, 200);
}
#onDoubleClick(e: any) {
// Let single clicks work again
/* Let single clicks work again */
this.#waitingForDoubleClick = false;
clearTimeout(this.#doubleClickTimer);
// Select all matching units in the viewport
/* Select all matching units in the viewport */
const unitsManager = getApp().getUnitsManager();
Object.values(unitsManager.getUnits()).forEach((unit: Unit) => {
if (unit.getAlive() === true && unit.getName() === this.getName() && unit.isInViewport())
@@ -1040,14 +1086,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) => {
@@ -1138,6 +1184,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);
@@ -1148,7 +1198,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
@@ -1221,6 +1271,14 @@ export 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() {
if (this.#activePath != undefined && getApp().getMap().getVisibilityOptions()[SHOW_UNIT_PATHS]) {
var points = [];
@@ -1316,13 +1374,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;
@@ -1332,13 +1390,14 @@ export class Unit extends CustomMarker {
}
})
if (acquisitionRange !== this.#acquisitionCircle.getRadius())
this.#acquisitionCircle.setRadius(acquisitionRange);
if (acquisitionRange !== this.#acquisitionCircle.getRadius()) {
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]);
@@ -1358,13 +1417,14 @@ export class Unit extends CustomMarker {
break;
}
}
this.#acquisitionCircle.setLatLng(this.getPosition());
if (this.getPosition() != this.#acquisitionCircle.getLatLng())
this.#acquisitionCircle.setLatLng(this.getPosition());
}
else {
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)))) {
@@ -1382,7 +1442,8 @@ export class Unit extends CustomMarker {
break;
}
}
this.#engagementCircle.setLatLng(this.getPosition());
if (this.getPosition() != this.#engagementCircle.getLatLng())
this.#engagementCircle.setLatLng(this.getPosition());
}
else {
if (getApp().getMap().hasLayer(this.#engagementCircle))
@@ -1430,17 +1491,20 @@ export class Unit extends CustomMarker {
this.#targetPositionPolyline.removeFrom(getApp().getMap());
}
#onZoom() {
#onZoom(e: any) {
if (this.checkZoomRedraw())
this.#redrawMarker();
this.#updateMarker();
}
}
export class AirUnit extends Unit {
export abstract class AirUnit extends Unit {
getIconOptions() {
var belongsToCommandedCoalition = this.belongsToCommandedCoalition();
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))),
@@ -1514,9 +1578,10 @@ 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,
showShortLabel: this.getDatabaseEntry()?.type === "SAM Site",
showFuel: false,
showAmmo: false,
showSummary: false,
@@ -1566,6 +1631,31 @@ export class GroundUnit extends Unit {
var blueprint = groundUnitDatabase.getByName(this.getName());
return blueprint?.type ? blueprint.type : "";
}
/* When a unit is a leader of a group, the map is zoomed out and grouping when zoomed out is enabled, check if the unit should be shown as a specific group. This is used to show a SAM battery instead of the group leader */
getDatabaseEntry() {
let unitWhenGrouped = null;
if (!this.getSelected() && this.getIsLeader() && getApp().getMap().getVisibilityOptions()[HIDE_GROUP_MEMBERS] && getApp().getMap().getZoom() < GROUPING_ZOOM_TRANSITION) {
unitWhenGrouped = this.getDatabase()?.getByName(this.getName())?.unitWhenGrouped ?? null;
let member = this.getGroupMembers().reduce((prev: Unit | null, unit: Unit, index: number) => {
if (unit.getDatabaseEntry()?.unitWhenGrouped != undefined)
return unit
return prev;
}, null);
unitWhenGrouped = (member !== null ? member?.getDatabaseEntry()?.unitWhenGrouped : unitWhenGrouped);
}
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 */
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))
}
}
export class NavyUnit extends Unit {
@@ -1578,6 +1668,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,

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";
import { UnitDataFileExport } from "./unitdatafileexport";
import { UnitDataFileImport } from "./unitdatafileimport";
@@ -27,8 +28,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 } = {};
#unitDataExport!:UnitDataFileExport;
#unitDataImport!:UnitDataFileImport;
@@ -40,7 +42,7 @@ export class UnitsManager {
document.addEventListener('contactsUpdated', (e: CustomEvent) => { this.#requestDetectionUpdate = true });
document.addEventListener('copy', () => this.selectedUnitsCopy());
document.addEventListener('deleteSelectedUnits', () => this.selectedUnitsDelete());
document.addEventListener('explodeSelectedUnits', () => this.selectedUnitsDelete(true));
document.addEventListener('explodeSelectedUnits', (e: any) => this.selectedUnitsDelete(true, e.detail.type));
document.addEventListener('exportToFile', () => this.exportToFile());
document.addEventListener('importFromFile', () => this.importFromFile());
document.addEventListener('keyup', (event) => this.#onKeyUp(event));
@@ -49,10 +51,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");
}
/**
@@ -133,6 +134,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
*/
@@ -212,9 +229,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();
}
@@ -226,8 +243,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()) {
@@ -536,7 +553,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);
@@ -588,7 +605,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;
@@ -675,7 +692,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
*
*/
@@ -764,7 +781,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`);
}
@@ -797,8 +814,8 @@ export class UnitsManager {
* @param explosion If true, the unit will be deleted using an explosion
* @returns
*/
selectedUnitsDelete(explosion: boolean = false) {
var selectedUnits = this.getSelectedUnits({excludeProtected:true}); /* Can be applied to humans too */
selectedUnitsDelete(explosion: boolean = false, explosionType: string = "") {
var selectedUnits = this.getSelectedUnits({ excludeProtected: true }); /* Can be applied to humans too */
const selectionContainsAHuman = selectedUnits.some((unit: Unit) => {
return unit.getHuman() === true;
});
@@ -807,22 +824,22 @@ export class UnitsManager {
return;
}
const doDelete = (explosion = false, immediate = false) => {
const doDelete = (explosion = false, explosionType = "", immediate = false) => {
for (let idx in selectedUnits) {
selectedUnits[idx].delete(explosion, immediate);
selectedUnits[idx].delete(explosion, explosionType, immediate);
}
this.#showActionMessage(selectedUnits, `deleted`);
}
if (selectedUnits.length >= DELETE_SLOW_THRESHOLD)
this.#showSlowDeleteDialog(selectedUnits).then((action:any) => {
this.#showSlowDeleteDialog(selectedUnits).then((action: any) => {
if (action === "delete-slow")
doDelete(explosion, false);
doDelete(explosion, explosionType, false);
else if (action === "delete-immediate")
doDelete(explosion, true);
doDelete(explosion, explosionType, true);
})
else
doDelete(explosion);
doDelete(explosion, explosionType);
}
@@ -871,7 +888,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
*
@@ -892,7 +909,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;
@@ -927,7 +944,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) => {
@@ -975,7 +992,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);
}
}
@@ -1013,24 +1030,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") {
@@ -1038,7 +1055,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") {
@@ -1046,7 +1063,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);
}
@@ -1112,30 +1129,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);
});
@@ -1143,18 +1160,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)
}
}