DCSOlympus/client/src/units/unitsmanager.ts
2023-05-24 23:13:47 +01:00

380 lines
15 KiB
TypeScript

import { LatLng, LatLngBounds } from "leaflet";
import { getInfoPopup, getMap, getUnitDataTable } from "..";
import { Unit } from "./unit";
import { cloneUnit } from "../server/server";
import { IDLE, MOVE_UNIT } from "../map/map";
import { keyEventWasInInput } from "../other/utils";
export class UnitsManager {
#units: { [ID: number]: Unit };
#copiedUnits: Unit[];
#selectionEventDisabled: boolean = false;
#pasteDisabled: boolean = false;
constructor() {
this.#units = {};
this.#copiedUnits = [];
document.addEventListener('copy', () => this.copyUnits());
document.addEventListener('paste', () => this.pasteUnits());
document.addEventListener('unitSelection', (e: CustomEvent) => this.#onUnitSelection(e.detail));
document.addEventListener('unitDeselection', (e: CustomEvent) => this.#onUnitDeselection(e.detail));
document.addEventListener('keyup', (event) => this.#onKeyUp(event));
document.addEventListener('deleteSelectedUnits', () => this.selectedUnitsDelete())
}
getSelectableAircraft() {
const units = this.getUnits();
return Object.keys(units).reduce((acc: { [key: number]: Unit }, unitId: any) => {
const baseData = units[unitId].getBaseData();
if (baseData.category === "Aircraft" && baseData.alive === true) {
acc[unitId] = units[unitId];
}
return acc;
}, {});
}
getUnits() {
return this.#units;
}
getUnitByID(ID: number) {
if (ID in this.#units)
return this.#units[ID];
else
return null;
}
addUnit(ID: number, data: UnitData) {
/* The name of the unit category is exactly the same as the constructor name */
var constructor = Unit.getConstructor(data.baseData.category);
if (constructor != undefined) {
this.#units[ID] = new constructor(ID, data);
}
}
removeUnit(ID: number) {
}
update(data: UnitsData) {
Object.keys(data.units)
.filter((ID: string) => !(ID in this.#units))
.reduce((timeout: number, ID: string) => {
window.setTimeout(() => {
if (!(ID in this.#units))
this.addUnit(parseInt(ID), data.units[ID]);
this.#units[parseInt(ID)]?.setData(data.units[ID]);
}, timeout);
return timeout + 10;
}, 10);
Object.keys(data.units)
.filter((ID: string) => ID in this.#units)
.forEach((ID: string) => this.#units[parseInt(ID)]?.setData(data.units[ID]));
}
selectUnit(ID: number, deselectAllUnits: boolean = true) {
if (deselectAllUnits)
this.getSelectedUnits().filter((unit: Unit) => unit.ID !== ID).forEach((unit: Unit) => unit.setSelected(false));
this.#units[ID]?.setSelected(true);
}
selectFromBounds(bounds: LatLngBounds) {
this.deselectAllUnits();
for (let ID in this.#units) {
if (this.#units[ID].getHidden() == false) {
var latlng = new LatLng(this.#units[ID].getFlightData().latitude, this.#units[ID].getFlightData().longitude);
if (bounds.contains(latlng)) {
this.#units[ID].setSelected(true);
}
}
}
}
getSelectedUnits(options?: {excludeHumans?: boolean}) {
var selectedUnits = [];
for (let ID in this.#units) {
if (this.#units[ID].getSelected()) {
selectedUnits.push(this.#units[ID]);
}
}
if (options) {
if (options.excludeHumans)
selectedUnits = selectedUnits.filter((unit: Unit) => {return !unit.getMissionData().flags.Human});
}
return selectedUnits;
}
deselectAllUnits() {
for (let ID in this.#units) {
this.#units[ID].setSelected(false);
}
}
getSelectedUnitsType() {
if (this.getSelectedUnits().length == 0)
return undefined;
return this.getSelectedUnits().map((unit: Unit) => {
return unit.constructor.name
})?.reduce((a: any, b: any) => {
return a == b ? a : undefined
});
};
getSelectedUnitsTargetSpeed() {
if (this.getSelectedUnits().length == 0)
return undefined;
return this.getSelectedUnits().map((unit: Unit) => {
return unit.getTaskData().targetSpeed
})?.reduce((a: any, b: any) => {
return a == b ? a : undefined
});
};
getSelectedUnitsTargetAltitude() {
if (this.getSelectedUnits().length == 0)
return undefined;
return this.getSelectedUnits().map((unit: Unit) => {
return unit.getTaskData().targetAltitude
})?.reduce((a: any, b: any) => {
return a == b ? a : undefined
});
};
getSelectedUnitsCoalition() {
if (this.getSelectedUnits().length == 0)
return undefined;
return this.getSelectedUnits().map((unit: Unit) => {
return unit.getMissionData().coalition
})?.reduce((a: any, b: any) => {
return a == b ? a : undefined
});
};
/*********************** Actions on selected units ************************/
selectedUnitsAddDestination(latlng: L.LatLng) {
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
for (let idx in selectedUnits) {
const unit = selectedUnits[idx];
/* If a unit is following another unit, and that unit is also selected, send the command to the followed unit */
if (unit.getTaskData().currentState === "Follow") {
const leader = this.getUnitByID(unit.getFormationData().leaderID)
if (leader && leader.getSelected())
leader.addDestination(latlng);
else
unit.addDestination(latlng);
}
else
unit.addDestination(latlng);
}
this.#showActionMessage(selectedUnits, " new destination added");
}
selectedUnitsClearDestinations() {
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
for (let idx in selectedUnits) {
const unit = selectedUnits[idx];
if (unit.getTaskData().currentState === "Follow") {
const leader = this.getUnitByID(unit.getFormationData().leaderID)
if (leader && leader.getSelected())
leader.clearDestinations();
else
unit.clearDestinations();
}
else
unit.clearDestinations();
}
}
selectedUnitsLandAt(latlng: LatLng) {
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
for (let idx in selectedUnits) {
selectedUnits[idx].landAt(latlng);
}
this.#showActionMessage(selectedUnits, " landing");
}
selectedUnitsChangeSpeed(speedChange: string) {
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
for (let idx in selectedUnits) {
selectedUnits[idx].changeSpeed(speedChange);
}
}
selectedUnitsChangeAltitude(altitudeChange: string) {
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
for (let idx in selectedUnits) {
selectedUnits[idx].changeAltitude(altitudeChange);
}
}
selectedUnitsSetSpeed(speed: number) {
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
for (let idx in selectedUnits) {
selectedUnits[idx].setSpeed(speed);
}
this.#showActionMessage(selectedUnits, `setting speed to ${speed * 1.94384} kts`);
}
selectedUnitsSetAltitude(altitude: number) {
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
for (let idx in selectedUnits) {
selectedUnits[idx].setAltitude(altitude);
}
this.#showActionMessage(selectedUnits, `setting altitude to ${altitude / 0.3048} ft`);
}
selectedUnitsSetROE(ROE: string) {
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
for (let idx in selectedUnits) {
selectedUnits[idx].setROE(ROE);
}
this.#showActionMessage(selectedUnits, `ROE set to ${ROE}`);
}
selectedUnitsSetReactionToThreat(reactionToThreat: string) {
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
for (let idx in selectedUnits) {
selectedUnits[idx].setReactionToThreat(reactionToThreat);
}
this.#showActionMessage(selectedUnits, `reaction to threat set to ${reactionToThreat}`);
}
selectedUnitsAttackUnit(ID: number) {
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
for (let idx in selectedUnits) {
selectedUnits[idx].attackUnit(ID);
}
this.#showActionMessage(selectedUnits, `attacking unit ${this.getUnitByID(ID)?.getBaseData().unitName}`);
}
selectedUnitsDelete() {
var selectedUnits = this.getSelectedUnits(); /* Can be applied to humans too */
for (let idx in selectedUnits) {
selectedUnits[idx].delete();
}
this.#showActionMessage(selectedUnits, `deleted`);
}
selectedUnitsRefuel() {
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
for (let idx in selectedUnits) {
selectedUnits[idx].refuel();
}
this.#showActionMessage(selectedUnits, `sent to nearest tanker`);
}
selectedUnitsFollowUnit(ID: number, offset?: { "x": number, "y": number, "z": number }, formation?: string) {
if (offset == undefined) {
/* Simple formations with fixed offsets */
// X: front-rear, positive front
// Y: top-bottom, positive top
// Z: left-right, positive right
offset = { "x": 0, "y": 0, "z": 0 };
if (formation === "Trail") { offset.x = -50; offset.y = -30; offset.z = 0; }
else if (formation === "Echelon (LH)") { offset.x = -50; offset.y = -10; offset.z = -50; }
else if (formation === "Echelon (RH)") { offset.x = -50; offset.y = -10; offset.z = 50; }
else if (formation === "Line abreast (RH)") { offset.x = 0; offset.y = 0; offset.z = 50; }
else if (formation === "Line abreast (LH)") { offset.x = 0; offset.y = 0; offset.z = -50; }
else if (formation === "Front") { offset.x = 100; offset.y = 0; offset.z = 0; }
else offset = undefined;
}
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
var count = 1;
var xr = 0; var yr = 1; var zr = -1;
var layer = 1;
for (let idx in selectedUnits) {
var unit = selectedUnits[idx];
if (offset != undefined)
/* Offset is set, apply it */
unit.followUnit(ID, { "x": offset.x * count, "y": offset.y * count, "z": offset.z * count });
else {
/* More complex formations with variable offsets */
if (formation === "Diamond") {
var xl = xr * Math.cos(Math.PI / 4) - yr * Math.sin(Math.PI / 4);
var yl = xr * Math.sin(Math.PI / 4) + yr * Math.cos(Math.PI / 4);
unit.followUnit(ID, { "x": -yl * 50, "y": zr * 10, "z": xl * 50 });
if (yr == 0) { layer++; xr = 0; yr = layer; zr = -layer; }
else {
if (xr < layer) { xr++; zr--; }
else { yr--; zr++; }
}
}
}
count++;
}
this.#showActionMessage(selectedUnits, `following unit ${this.getUnitByID(ID)?.getBaseData().unitName}`);
}
/***********************************************/
copyUnits() {
this.#copiedUnits = this.getSelectedUnits(); /* Can be applied to humans too */
this.#showActionMessage(this.#copiedUnits, `copied`);
}
pasteUnits() {
if (!this.#pasteDisabled) {
for (let idx in this.#copiedUnits) {
var unit = this.#copiedUnits[idx];
getMap().addTemporaryMarker(getMap().getMouseCoordinates());
cloneUnit(unit.ID, getMap().getMouseCoordinates());
this.#showActionMessage(this.#copiedUnits, `pasted`);
}
this.#pasteDisabled = true;
window.setTimeout(() => this.#pasteDisabled = false, 250);
}
}
/***********************************************/
#onKeyUp(event: KeyboardEvent) {
if (!keyEventWasInInput(event) && event.key === "Delete" ) {
const selectedUnits = this.getSelectedUnits();
const selectionContainsAHuman = selectedUnits.some( ( unit:Unit ) => {
return unit.getMissionData().flags.Human === true;
});
if ( !selectionContainsAHuman || confirm( "Your selection includes a human player. Deleting humans causes their vehicle to crash.\n\nAre you sure you want to do this?" ) ) {
this.selectedUnitsDelete();
}
}
}
#onUnitSelection(unit: Unit) {
if (this.getSelectedUnits().length > 0) {
getMap().setState(MOVE_UNIT);
/* Disable the firing of the selection event for a certain amount of time. This avoids firing many events if many units are selected */
if (!this.#selectionEventDisabled) {
window.setTimeout(() => {
document.dispatchEvent(new CustomEvent("unitsSelection", { detail: this.getSelectedUnits() }));
this.#selectionEventDisabled = false;
}, 100);
this.#selectionEventDisabled = true;
}
}
else {
getMap().setState(IDLE);
document.dispatchEvent(new CustomEvent("clearSelection"));
}
}
#onUnitDeselection(unit: Unit) {
if (this.getSelectedUnits().length == 0) {
getMap().setState(IDLE);
document.dispatchEvent(new CustomEvent("clearSelection"));
}
else
document.dispatchEvent(new CustomEvent("unitsDeselection", { detail: this.getSelectedUnits() }));
}
#showActionMessage(units: Unit[], message: string) {
if (units.length == 1)
getInfoPopup().setText(`${units[0].getBaseData().unitName} ${message}`);
else if (units.length > 1)
getInfoPopup().setText(`${units[0].getBaseData().unitName} and ${units.length - 1} other units ${message}`);
}
}