Splitted weapons and units managers

This commit is contained in:
Pax1601
2023-07-27 16:27:59 +02:00
parent 875f3ebe68
commit 0150ae9df1
38 changed files with 5616 additions and 4883 deletions

323
client/src/weapon/weapon.ts Normal file
View File

@@ -0,0 +1,323 @@
import { LatLng, DivIcon, Map } from 'leaflet';
import { getMap, getMissionHandler, getUnitsManager } from '..';
import { enumToCoalition, mToFt, msToKnots, rad2deg } from '../other/utils';
import { CustomMarker } from '../map/custommarker';
import { SVGInjector } from '@tanem/svg-injector';
import { DLINK, DataIndexes, GAME_MASTER, IRST, OPTIC, RADAR, VISUAL } from '../constants/constants';
import { ObjectIconOptions } from '../@types/unit';
import { DataExtractor } from '../server/dataextractor';
export class Weapon extends CustomMarker {
ID: number;
#alive: boolean = false;
#coalition: string = "neutral";
#name: string = "";
#position: LatLng = new LatLng(0, 0, 0);
#speed: number = 0;
#heading: number = 0;
#hidden: boolean = false;
#detectionMethods: number[] = [];
getAlive() {return this.#alive};
getCoalition() {return this.#coalition};
getName() {return this.#name};
getPosition() {return this.#position};
getSpeed() {return this.#speed};
getHeading() {return this.#heading};
static getConstructor(type: string) {
if (type === "Missile") return Missile;
if (type === "Bomb") return Bomb;
}
constructor(ID: number) {
super(new LatLng(0, 0), { riseOnHover: true, keyboard: false });
this.ID = ID;
/* Deselect units if they are hidden */
document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => {
window.setTimeout(() => { !this.getHidden() }, 300);
});
document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => {
window.setTimeout(() => { !this.getHidden() }, 300);
});
document.addEventListener("mapVisibilityOptionsChanged", (ev: CustomEventInit) => {
this.#updateMarker();
});
}
getCategory() {
// Overloaded by child classes
return "";
}
/********************** Unit data *************************/
setData(dataExtractor: DataExtractor) {
var updateMarker = !getMap().hasLayer(this);
var datumIndex = 0;
while (datumIndex != DataIndexes.endOfData) {
datumIndex = dataExtractor.extractUInt8();
switch (datumIndex) {
case DataIndexes.category: dataExtractor.extractString(); break;
case DataIndexes.alive: this.setAlive(dataExtractor.extractBool()); updateMarker = true; break;
case DataIndexes.coalition: this.#coalition = enumToCoalition(dataExtractor.extractUInt8()); break;
case DataIndexes.name: this.#name = dataExtractor.extractString(); break;
case DataIndexes.position: this.#position = dataExtractor.extractLatLng(); updateMarker = true; break;
case DataIndexes.speed: this.#speed = dataExtractor.extractFloat64(); updateMarker = true; break;
case DataIndexes.heading: this.#heading = dataExtractor.extractFloat64(); updateMarker = true; break;
}
}
if (updateMarker)
this.#updateMarker();
}
getData() {
return {
category: this.getCategory(),
ID: this.ID,
alive: this.#alive,
coalition: this.#coalition,
name: this.#name,
position: this.#position,
speed: this.#speed,
heading: this.#heading
}
}
getMarkerCategory(): string {
return "";
}
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
}
}
setAlive(newAlive: boolean) {
this.#alive = newAlive;
}
belongsToCommandedCoalition() {
if (getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER && getMissionHandler().getCommandedCoalition() !== this.#coalition)
return false;
return true;
}
getType() {
return "";
}
/********************** Icon *************************/
createIcon(): void {
/* Set the icon */
var icon = new DivIcon({
className: 'leaflet-unit-icon',
iconAnchor: [25, 25],
iconSize: [50, 50],
});
this.setIcon(icon);
var el = document.createElement("div");
el.classList.add("unit");
el.setAttribute("data-object", `unit-${this.getMarkerCategory()}`);
el.setAttribute("data-coalition", this.#coalition);
// Generate and append elements depending on active options
// Velocity vector
if (this.getIconOptions().showVvi) {
var vvi = document.createElement("div");
vvi.classList.add("unit-vvi");
vvi.toggleAttribute("data-rotate-to-heading");
el.append(vvi);
}
// Main icon
if (this.getIconOptions().showUnitIcon) {
var unitIcon = document.createElement("div");
unitIcon.classList.add("unit-icon");
var img = document.createElement("img");
img.src = `/resources/theme/images/units/${this.getMarkerCategory()}.svg`;
img.onload = () => SVGInjector(img);
unitIcon.appendChild(img);
unitIcon.toggleAttribute("data-rotate-to-heading", this.getIconOptions().rotateToHeading);
el.append(unitIcon);
}
this.getElement()?.appendChild(el);
}
/********************** Visibility *************************/
updateVisibility() {
const hiddenUnits = getUnitsManager().getHiddenTypes();
var hidden = (hiddenUnits.includes(this.getMarkerCategory())) ||
(hiddenUnits.includes(this.#coalition)) ||
(!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0);
this.setHidden(hidden || !this.#alive);
}
setHidden(hidden: boolean) {
this.#hidden = hidden;
/* Add the marker if not present */
if (!getMap().hasLayer(this) && !this.getHidden()) {
if (getMap().isZooming())
this.once("zoomend", () => {this.addTo(getMap())})
else
this.addTo(getMap());
}
/* Hide the marker if necessary*/
if (getMap().hasLayer(this) && this.getHidden()) {
getMap().removeLayer(this);
}
}
getHidden() {
return this.#hidden;
}
setDetectionMethods(newDetectionMethods: number[]) {
if (!this.belongsToCommandedCoalition()) {
/* Check if the detection methods of this unit have changed */
if (this.#detectionMethods.length !== newDetectionMethods.length || this.getDetectionMethods().some(value => !newDetectionMethods.includes(value))) {
/* Force a redraw of the unit to reflect the new status of the detection methods */
this.setHidden(true);
this.#detectionMethods = newDetectionMethods;
this.updateVisibility();
}
}
}
getDetectionMethods() {
return this.#detectionMethods;
}
/***********************************************/
onAdd(map: Map): this {
super.onAdd(map);
/* If this is the first time adding this unit to the map, remove the temporary marker */
getMap().removeTemporaryMarker(new LatLng(this.#position.lat, this.#position.lng));
return this;
}
#updateMarker() {
this.updateVisibility();
/* Draw the marker */
if (!this.getHidden()) {
if (this.getLatLng().lat !== this.#position.lat || this.getLatLng().lng !== this.#position.lng) {
this.setLatLng(new LatLng(this.#position.lat, this.#position.lng));
}
var element = this.getElement();
if (element != null) {
/* Draw the velocity vector */
element.querySelector(".unit-vvi")?.setAttribute("style", `height: ${15 + this.#speed / 5}px;`);
/* Set dead/alive flag */
element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.#alive);
/* Set altitude and speed */
if (element.querySelector(".unit-altitude"))
(<HTMLElement>element.querySelector(".unit-altitude")).innerText = "FL" + String(Math.floor(mToFt(this.#position.alt as number) / 100));
if (element.querySelector(".unit-speed"))
(<HTMLElement>element.querySelector(".unit-speed")).innerText = String(Math.floor(msToKnots(this.#speed))) + "GS";
/* Rotate elements according to heading */
element.querySelectorAll("[data-rotate-to-heading]").forEach(el => {
const headingDeg = rad2deg(this.#heading);
let currentStyle = el.getAttribute("style") || "";
el.setAttribute("style", currentStyle + `transform:rotate(${headingDeg}deg);`);
});
}
/* Set vertical offset for altitude stacking */
var pos = getMap().latLngToLayerPoint(this.getLatLng()).round();
this.setZIndexOffset(1000 + Math.floor(this.#position.alt as number) - pos.y);
}
}
}
export class Missile extends Weapon {
constructor(ID: number) {
super(ID);
}
getCategory() {
return "Missile";
}
getMarkerCategory() {
if (this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC))
return "missile";
else
return "aircraft";
}
getIconOptions() {
return {
showState: false,
showVvi: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))),
showHotgroup: false,
showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
showShortLabel: false,
showFuel: false,
showAmmo: false,
showSummary: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))),
showCallsign: false,
rotateToHeading: this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC)
};
}
}
export class Bomb extends Weapon {
constructor(ID: number) {
super(ID);
}
getCategory() {
return "Bomb";
}
getMarkerCategory() {
if (this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC))
return "bomb";
else
return "aircraft";
}
getIconOptions() {
return {
showState: false,
showVvi: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))),
showHotgroup: false,
showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))),
showShortLabel: false,
showFuel: false,
showAmmo: false,
showSummary: (!this.belongsToCommandedCoalition() && !this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value)) && this.getDetectionMethods().some(value => [RADAR, IRST, DLINK].includes(value))),
showCallsign: false,
rotateToHeading: this.belongsToCommandedCoalition() || this.getDetectionMethods().includes(VISUAL) || this.getDetectionMethods().includes(OPTIC)
};
}
}

View File

@@ -0,0 +1,84 @@
import { getMissionHandler, getUnitsManager } from "..";
import { Weapon } from "./weapon";
import { DataIndexes, GAME_MASTER } from "../constants/constants";
import { DataExtractor } from "../server/dataextractor";
import { Contact } from "../@types/unit";
export class WeaponsManager {
#weapons: { [ID: number]: Weapon };
#requestDetectionUpdate: boolean = false;
constructor() {
this.#weapons = {};
document.addEventListener("commandModeOptionsChanged", () => {Object.values(this.#weapons).forEach((weapon: Weapon) => weapon.updateVisibility())});
document.addEventListener('contactsUpdated', (e: CustomEvent) => {this.#requestDetectionUpdate = true});
}
getWeapons() {
return this.#weapons;
}
getWeaponByID(ID: number) {
if (ID in this.#weapons)
return this.#weapons[ID];
else
return null;
}
addWeapon(ID: number, category: string) {
if (category){
/* The name of the weapon category is exactly the same as the constructor name */
var constructor = Weapon.getConstructor(category);
if (constructor != undefined) {
this.#weapons[ID] = new constructor(ID);
}
}
}
update(buffer: ArrayBuffer) {
var dataExtractor = new DataExtractor(buffer);
var updateTime = Number(dataExtractor.extractUInt64());
var requestRefresh = false;
while (dataExtractor.getSeekPosition() < buffer.byteLength) {
const ID = dataExtractor.extractUInt32();
if (!(ID in this.#weapons)) {
const datumIndex = dataExtractor.extractUInt8();
if (datumIndex == DataIndexes.category) {
const category = dataExtractor.extractString();
this.addWeapon(ID, category);
}
else {
requestRefresh = true;
}
}
this.#weapons[ID]?.setData(dataExtractor);
}
if (this.#requestDetectionUpdate && getMissionHandler().getCommandModeOptions().commandMode != GAME_MASTER) {
for (let ID in this.#weapons) {
var weapon = this.#weapons[ID];
if (!weapon.belongsToCommandedCoalition())
weapon.setDetectionMethods(this.getWeaponDetectedMethods(weapon));
}
this.#requestDetectionUpdate = false;
}
return updateTime;
}
getWeaponDetectedMethods(weapon: Weapon) {
var detectionMethods: number[] = [];
var units = getUnitsManager().getUnits();
for (let idx in units) {
if (units[idx].getAlive() && units[idx].getIsLeader() && units[idx].getCoalition() !== "neutral" && units[idx].getCoalition() != weapon.getCoalition())
{
units[idx].getContacts().forEach((contact: Contact) => {
if (contact.ID == weapon.ID && !detectionMethods.includes(contact.detectionMethod))
detectionMethods.push(contact.detectionMethod);
});
}
}
return detectionMethods;
}
}