More work on client conversion to binary data

This commit is contained in:
Pax1601
2023-06-23 17:32:38 +02:00
parent 916752301a
commit dd2e858db4
15 changed files with 597 additions and 432 deletions

View File

@@ -1,60 +1,23 @@
interface UpdateData {
[key: string]: any
import { LatLng } from "leaflet"
interface UnitIconOptions {
showState: boolean,
showVvi: boolean,
showHotgroup: boolean,
showUnitIcon: boolean,
showShortLabel: boolean,
showFuel: boolean,
showAmmo: boolean,
showSummary: boolean,
rotateToHeading: boolean
}
interface BaseData {
controlled: boolean;
name: string;
unitName: string;
groupName: string;
alive: boolean;
category: string;
}
interface FlightData {
latitude: number;
longitude: number;
altitude: number;
heading: number;
speed: number;
}
interface MissionData {
fuel: number;
flags: any;
ammo: any;
contacts: any;
hasTask: boolean;
coalition: string;
}
interface FormationData {
leaderID: number;
}
interface TaskData {
currentState: string;
currentTask: string;
activePath: any;
desiredSpeed: number;
desiredSpeedType: string;
desiredAltitude: number;
desiredAltitudeType: string;
targetLocation: any;
isTanker: boolean;
isAWACS: boolean;
onOff: boolean;
followRoads: boolean;
targetID: number;
}
interface OptionsData {
ROE: string;
reactionToThreat: string;
emissionsCountermeasures: string;
TACAN: TACAN;
radio: Radio;
generalSettings: GeneralSettings;
interface GeneralSettings {
prohibitJettison: boolean;
prohibitAA: boolean;
prohibitAG: boolean;
prohibitAfterburner: boolean;
prohibitAirWpn: boolean;
}
interface TACAN {
@@ -70,31 +33,55 @@ interface Radio {
callsignNumber: number;
}
interface GeneralSettings {
prohibitJettison: boolean;
prohibitAA: boolean;
prohibitAG: boolean;
prohibitAfterburner: boolean;
prohibitAirWpn: boolean;
interface Ammo {
quantity: number,
name: string,
guidance: number,
category: number,
missileCategory: number
}
interface UnitIconOptions {
showState: boolean,
showVvi: boolean,
showHotgroup: boolean,
showUnitIcon: boolean,
showShortLabel: boolean,
showFuel: boolean,
showAmmo: boolean,
showSummary: boolean,
rotateToHeading: boolean
interface Contact {
ID: number,
detectionMethod: number
}
interface UnitData {
baseData: BaseData;
flightData: FlightData;
missionData: MissionData;
formationData: FormationData;
taskData: TaskData;
optionsData: OptionsData;
ID: number,
alive: boolean,
human: boolean,
controlled: boolean,
hasTask: boolean,
desiredAltitudeType: boolean,
desiredSpeedType: boolean,
isTanker: boolean,
isAWACS: boolean,
onOff: boolean,
followRoads: boolean,
EPLRS: boolean,
generalSettings: GeneralSettings
position: LatLng,
speed: number,
heading: number,
fuel: number,
desiredSpeed: number,
desiredAltitude: number,
targetID: number,
leaderID: number,
targetPosition: LatLng,
state: string,
ROE: string,
reactionToThreat: string,
emissionsCountermeasures: string,
TACAN: TACAN,
radio: Radio,
activePath: LatLng[],
ammo: Ammo[],
contacts: Contact[],
name: string,
unitName: string,
groupName: string,
category: string,
coalition: string,
task: string
}

View File

@@ -115,7 +115,7 @@ export abstract class ATCBoard {
addFlight( unit:Unit ) {
const baseData = unit.getBaseData();
const baseData = unit.getData();
const unitCanBeAdded = () => {
@@ -345,7 +345,7 @@ export abstract class ATCBoard {
const results = Object.keys( units ).reduce( ( acc:Unit[], unitId:any ) => {
const unit = units[ unitId ];
const baseData = unit.getBaseData();
const baseData = unit.getData();
if ( !unitIdsBeingMonitored.includes( parseInt( unitId ) ) && baseData.unitName.toLowerCase().indexOf( searchString ) > -1 ) {
acc.push( unit );
@@ -359,7 +359,7 @@ export abstract class ATCBoard {
results.forEach( unit => {
const baseData = unit.getBaseData();
const baseData = unit.getData();
const a = document.createElement( "a" );
a.innerText = baseData.unitName;

View File

@@ -34,13 +34,13 @@ export class ATCBoardTower extends ATCBoard {
return;
}
const flightData:FlightData = {
const flightData = {
latitude: -1,
longitude: -1,
altitude: -1,
heading: -1,
speed: -1,
...( selectableUnits.hasOwnProperty( flight.unitId ) ? selectableUnits[flight.unitId].getFlightData() : {} )
...( selectableUnits.hasOwnProperty( flight.unitId ) ? selectableUnits[flight.unitId].getData() : {} )
};
if ( !strip ) {

View File

@@ -12,8 +12,8 @@ export class UnitDataTable extends Panel {
var units = getUnitsManager().getUnits();
const unitsArray = Object.values(units).sort((a: Unit, b: Unit) => {
const aVal = a.getBaseData().unitName?.toLowerCase();
const bVal = b.getBaseData().unitName?.toLowerCase();
const aVal = a.getData().unitName?.toLowerCase();
const bVal = b.getData().unitName?.toLowerCase();
if (aVal > bVal) {
return 1;
@@ -48,7 +48,7 @@ export class UnitDataTable extends Panel {
for (const unit of unitsArray) {
const dataset = [unit.getBaseData().unitName, unit.getBaseData().name, unit.getBaseData().category, (unit.getBaseData().controlled) ? "AI" : "Human"];
const dataset = [unit.getData().unitName, unit.getData().name, unit.getData().category, (unit.getData().controlled) ? "AI" : "Human"];
addRow(el, dataset);
}

View File

@@ -1,12 +1,31 @@
import { LatLng, LatLngBounds, TileLayer, tileLayer } from "leaflet";
export const ROEs: string[] = ["Hold", "Return", "Designated", "Free"];
export const reactionsToThreat: string[] = ["None", "Manoeuvre", "Passive", "Evade"];
export const emissionsCountermeasures: string[] = ["Silent", "Attack", "Defend", "Free"];
export const states: string[] = ["none", "idle", "reach-destination", "attack", "follow", "land", "refuel", "AWACS", "tanker", "bomb-point", "carpet-bomb", "bomb-building", "fire-at-area"];
export const ROEs: string[] = ["free", "designated", "return", "hold"];
export const reactionsToThreat: string[] = ["none", "manoeuvre", "passive", "evade"];
export const emissionsCountermeasures: string[] = ["silent", "attack", "defend", "free"];
export const ROEDescriptions: string[] = ["Hold (Never fire)", "Return (Only fire if fired upon)", "Designated (Attack the designated target only)", "Free (Attack anyone)"];
export const reactionsToThreatDescriptions: string[] = ["None (No reaction)", "Manoeuvre (no countermeasures)", "Passive (Countermeasures only, no manoeuvre)", "Evade (Countermeasures and manoeuvers)"];
export const emissionsCountermeasuresDescriptions: string[] = ["Silent (Radar OFF, no ECM)", "Attack (Radar only for targeting, ECM only if locked)", "Defend (Radar for searching, ECM if locked)", "Always on (Radar and ECM always on)"];
export const ROEDescriptions: string[] = [
"Free (Attack anyone)",
"Designated (Attack the designated target only)",
"",
"Return (Only fire if fired upon)",
"Hold (Never fire)"
];
export const reactionsToThreatDescriptions: string[] = [
"None (No reaction)",
"Manoeuvre (no countermeasures)",
"Passive (Countermeasures only, no manoeuvre)",
"Evade (Countermeasures and manoeuvers)"
];
export const emissionsCountermeasuresDescriptions: string[] = [
"Silent (Radar OFF, no ECM)",
"Attack (Radar only for targeting, ECM only if locked)",
"Defend (Radar for searching, ECM if locked)",
"Always on (Radar and ECM always on)"
];
export const minSpeedValues: { [key: string]: number } = { Aircraft: 100, Helicopter: 0, NavyUnit: 0, GroundUnit: 0 };
export const maxSpeedValues: { [key: string]: number } = { Aircraft: 800, Helicopter: 300, NavyUnit: 60, GroundUnit: 60 };

View File

@@ -547,7 +547,7 @@ export class Map extends L.Map {
}
#panToUnit(unit: Unit) {
var unitPosition = new L.LatLng(unit.getFlightData().latitude, unit.getFlightData().longitude);
var unitPosition = new L.LatLng(unit.getData().position.lat, unit.getData().position.lng);
this.setView(unitPosition, this.getZoom(), { animate: false });
}

View File

@@ -36,7 +36,7 @@ export class MouseInfoPanel extends Panel {
var selectedUnitPosition = null;
var selectedUnits = getUnitsManager().getSelectedUnits();
if (selectedUnits && selectedUnits.length == 1)
selectedUnitPosition = new LatLng(selectedUnits[0].getFlightData().latitude, selectedUnits[0].getFlightData().longitude);
selectedUnitPosition = new LatLng(selectedUnits[0].getData().position.lat, selectedUnits[0].getData().position.lng);
/* Draw measures from selected unit, from pin location, and from bullseyes */
this.#drawMeasure("ref-measure-position", "measure-position", this.#measurePoint, mousePosition);

View File

@@ -8,6 +8,7 @@ import { Panel } from "./panel";
import { Switch } from "../controls/switch";
import { ROEDescriptions, ROEs, altitudeIncrements, emissionsCountermeasures, emissionsCountermeasuresDescriptions, maxAltitudeValues, maxSpeedValues, minAltitudeValues, minSpeedValues, reactionsToThreat, reactionsToThreatDescriptions, speedIncrements } from "../constants/constants";
import { ftToM, knotsToMs, mToFt, msToKnots } from "../other/utils";
import { GeneralSettings, Radio, TACAN } from "../@types/unit";
export class UnitControlPanel extends Panel {
#altitudeSlider: Slider;
@@ -98,13 +99,13 @@ export class UnitControlPanel extends Panel {
if (units.length < 20) {
this.getElement().querySelector("#selected-units-container")?.replaceChildren(...units.map((unit: Unit, index: number) => {
var button = document.createElement("button");
var callsign = unit.getBaseData().unitName || "";
var label = unit.getDatabase()?.getByName(unit.getBaseData().name)?.label || unit.getBaseData().name;
var callsign = unit.getData().unitName || "";
var label = unit.getDatabase()?.getByName(unit.getData().name)?.label || unit.getData().name;
button.setAttribute("data-label", label);
button.setAttribute("data-callsign", callsign);
button.setAttribute("data-coalition", unit.getMissionData().coalition);
button.setAttribute("data-coalition", unit.getData().coalition);
button.classList.add("pill", "highlight-coalition")
button.addEventListener("click", () => {
@@ -139,12 +140,12 @@ export class UnitControlPanel extends Panel {
element.toggleAttribute("data-show-advanced-settings-button", units.length == 1);
/* Flight controls */
var desiredAltitude: number | undefined = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().desiredAltitude});
var desiredAltitudeType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().desiredAltitudeType});
var desiredSpeed = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().desiredSpeed});
var desiredSpeedType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().desiredSpeedType});
var onOff = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().onOff});
var followRoads = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().followRoads});
var desiredAltitude: number | undefined = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getData().desiredAltitude});
var desiredAltitudeType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getData().desiredAltitudeType});
var desiredSpeed = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getData().desiredSpeed});
var desiredSpeedType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getData().desiredSpeedType});
var onOff = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getData().onOff});
var followRoads = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getData().followRoads});
if (selectedUnitsTypes.length == 1) {
this.#altitudeTypeSwitch.setValue(desiredAltitudeType != undefined? desiredAltitudeType == "AGL": undefined, false);
@@ -170,15 +171,15 @@ export class UnitControlPanel extends Panel {
/* Option buttons */
this.#optionButtons["ROE"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().ROE === button.value))
button.classList.toggle("selected", units.every((unit: Unit) => unit.getData().ROE === button.value))
});
this.#optionButtons["reactionToThreat"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().reactionToThreat === button.value))
button.classList.toggle("selected", units.every((unit: Unit) => unit.getData().reactionToThreat === button.value))
});
this.#optionButtons["emissionsCountermeasures"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().emissionsCountermeasures === button.value))
button.classList.toggle("selected", units.every((unit: Unit) => unit.getData().emissionsCountermeasures === button.value))
});
this.#onOffSwitch.setValue(onOff, false);
@@ -207,11 +208,11 @@ export class UnitControlPanel extends Panel {
const radioCallsignNumberInput = this.#advancedSettingsDialog.querySelector("#radio-callsign-number")?.querySelector("input") as HTMLInputElement;
const unit = units[0];
const roles = aircraftDatabase.getByName(unit.getBaseData().name)?.loadouts.map((loadout) => {return loadout.roles})
const roles = aircraftDatabase.getByName(unit.getData().name)?.loadouts.map((loadout) => {return loadout.roles})
const tanker = roles != undefined && Array.prototype.concat.apply([], roles)?.includes("Tanker");
const AWACS = roles != undefined && Array.prototype.concat.apply([], roles)?.includes("AWACS");
const radioMHz = Math.floor(unit.getOptionsData().radio.frequency / 1000000);
const radioDecimals = (unit.getOptionsData().radio.frequency / 1000000 - radioMHz) * 1000;
const radioMHz = Math.floor(unit.getData().radio.frequency / 1000000);
const radioDecimals = (unit.getData().radio.frequency / 1000000 - radioMHz) * 1000;
/* Activate the correct options depending on unit type */
this.#advancedSettingsDialog.toggleAttribute("data-show-settings", !tanker && !AWACS);
@@ -223,28 +224,28 @@ export class UnitControlPanel extends Panel {
/* Set common properties */
// Name
unitNameEl.innerText = unit.getBaseData().unitName;
unitNameEl.innerText = unit.getData().unitName;
// General settings
prohibitJettisonCheckbox.checked = unit.getOptionsData().generalSettings.prohibitJettison;
prohibitAfterburnerCheckbox.checked = unit.getOptionsData().generalSettings.prohibitAfterburner;
prohibitAACheckbox.checked = unit.getOptionsData().generalSettings.prohibitAA;
prohibitAGCheckbox.checked = unit.getOptionsData().generalSettings.prohibitAG;
prohibitAirWpnCheckbox.checked = unit.getOptionsData().generalSettings.prohibitAirWpn;
prohibitJettisonCheckbox.checked = unit.getData().generalSettings.prohibitJettison;
prohibitAfterburnerCheckbox.checked = unit.getData().generalSettings.prohibitAfterburner;
prohibitAACheckbox.checked = unit.getData().generalSettings.prohibitAA;
prohibitAGCheckbox.checked = unit.getData().generalSettings.prohibitAG;
prohibitAirWpnCheckbox.checked = unit.getData().generalSettings.prohibitAirWpn;
// Tasking
tankerCheckbox.checked = unit.getTaskData().isTanker;
AWACSCheckbox.checked = unit.getTaskData().isAWACS;
tankerCheckbox.checked = unit.getData().isTanker;
AWACSCheckbox.checked = unit.getData().isAWACS;
// TACAN
TACANCheckbox.checked = unit.getOptionsData().TACAN.isOn;
TACANChannelInput.value = String(unit.getOptionsData().TACAN.channel);
TACANCallsignInput.value = String(unit.getOptionsData().TACAN.callsign);
this.#TACANXYDropdown.setValue(unit.getOptionsData().TACAN.XY);
TACANCheckbox.checked = unit.getData().TACAN.isOn;
TACANChannelInput.value = String(unit.getData().TACAN.channel);
TACANCallsignInput.value = String(unit.getData().TACAN.callsign);
this.#TACANXYDropdown.setValue(unit.getData().TACAN.XY);
// Radio
radioMhzInput.value = String(radioMHz);
radioCallsignNumberInput.value = String(unit.getOptionsData().radio.callsignNumber);
radioCallsignNumberInput.value = String(unit.getData().radio.callsignNumber);
this.#radioDecimalsDropdown.setValue("." + radioDecimals);
if (tanker) /* Set tanker specific options */
@@ -255,7 +256,7 @@ export class UnitControlPanel extends Panel {
this.#radioCallsignDropdown.setOptions(["Enfield", "Springfield", "Uzi", "Colt", "Dodge", "Ford", "Chevy", "Pontiac"]);
// This must be done after setting the options
if (!this.#radioCallsignDropdown.selectValue(unit.getOptionsData().radio.callsign - 1)) // Ensure the selected value is in the acceptable range
if (!this.#radioCallsignDropdown.selectValue(unit.getData().radio.callsign - 1)) // Ensure the selected value is in the acceptable range
this.#radioCallsignDropdown.selectValue(0);
}
}

View File

@@ -51,21 +51,21 @@ export class UnitInfoPanel extends Panel {
#onUnitUpdate(unit: Unit) {
if (this.getElement() != null && this.getVisible() && unit.getSelected()) {
const baseData = unit.getBaseData();
const baseData = unit.getData();
/* Set the unit info */
this.#unitLabel.innerText = aircraftDatabase.getByName(baseData.name)?.label || baseData.name;
this.#unitName.innerText = baseData.unitName;
if (unit.getMissionData().flags.Human)
if (unit.getData().human)
this.#unitControl.innerText = "Human";
else if (baseData.controlled)
this.#unitControl.innerText = "Olympus controlled";
else
this.#unitControl.innerText = "DCS Controlled";
this.#fuelBar.style.width = String(unit.getMissionData().fuel + "%");
this.#fuelPercentage.dataset.percentage = "" + unit.getMissionData().fuel;
this.#currentTask.dataset.currentTask = unit.getTaskData().currentTask !== "" ? unit.getTaskData().currentTask : "No task";
this.#currentTask.dataset.coalition = unit.getMissionData().coalition;
this.#fuelBar.style.width = String(unit.getData().fuel + "%");
this.#fuelPercentage.dataset.percentage = "" + unit.getData().fuel;
this.#currentTask.dataset.currentTask = unit.getData().task !== "" ? unit.getData().task : "No task";
this.#currentTask.dataset.coalition = unit.getData().coalition;
this.#silhouette.src = `/images/units/${unit.getDatabase()?.getByName(baseData.name)?.filename}`;
this.#silhouette.classList.toggle("hide", unit.getDatabase()?.getByName(baseData.name)?.filename == undefined || unit.getDatabase()?.getByName(baseData.name)?.filename == '');
@@ -74,9 +74,9 @@ export class UnitInfoPanel extends Panel {
const items = <HTMLElement>this.#loadoutContainer.querySelector("#loadout-items");
if (items) {
const ammo = Object.values(unit.getMissionData().ammo);
const ammo = Object.values(unit.getData().ammo);
if (ammo.length > 0) {
items.replaceChildren(...Object.values(unit.getMissionData().ammo).map(
items.replaceChildren(...Object.values(unit.getData().ammo).map(
(ammo: any) => {
var el = document.createElement("div");
el.dataset.qty = ammo.count;

View File

@@ -1,6 +1,7 @@
import { LatLng } from 'leaflet';
import { getConnectionStatusPanel, getInfoPopup, getMissionData, getUnitDataTable, getUnitsManager, setConnectionStatus } from '..';
import { SpawnOptions } from '../controls/mapcontextmenu';
import { GeneralSettings, Radio, TACAN } from '../@types/unit';
var connected: boolean = false;
var paused: boolean = false;

View File

@@ -0,0 +1,219 @@
import { LatLng } from "leaflet";
import { UnitData } from "../@types/unit";
import { ROEs, emissionsCountermeasures, reactionsToThreat, states } from "../constants/constants";
export class DataExtractor {
#offset = 0;
#dataview: DataView;
#decoder: TextDecoder;
#buffer: ArrayBuffer;
constructor(buffer: ArrayBuffer) {
this.#buffer = buffer;
this.#dataview = new DataView(this.#buffer);
this.#decoder = new TextDecoder("utf-8");
}
extractData(offset: number) {
this.#offset = offset;
const ID = this.#extractUInt32();
const bitmask = this.#extractUInt32();
const unitData: UnitData = {
ID: ID,
alive: this.#extractFromBitmask(bitmask, 0),
human: this.#extractFromBitmask(bitmask, 1),
controlled: this.#extractFromBitmask(bitmask, 2),
hasTask: this.#extractFromBitmask(bitmask, 3),
desiredAltitudeType: this.#extractFromBitmask(bitmask, 16),
desiredSpeedType: this.#extractFromBitmask(bitmask, 17),
isTanker: this.#extractFromBitmask(bitmask, 18),
isAWACS: this.#extractFromBitmask(bitmask, 19),
onOff: this.#extractFromBitmask(bitmask, 20),
followRoads: this.#extractFromBitmask(bitmask, 21),
EPLRS: this.#extractFromBitmask(bitmask, 22),
generalSettings: {
prohibitAA: this.#extractFromBitmask(bitmask, 23),
prohibitAfterburner: this.#extractFromBitmask(bitmask, 24),
prohibitAG: this.#extractFromBitmask(bitmask, 25),
prohibitAirWpn: this.#extractFromBitmask(bitmask, 26),
prohibitJettison: this.#extractFromBitmask(bitmask, 27),
},
position: new LatLng(
this.#extractFloat64(),
this.#extractFloat64(),
this.#extractFloat64()
),
speed: this.#extractFloat64(),
heading: this.#extractFloat64(),
fuel: this.#extractUInt16(),
desiredSpeed: this.#extractFloat64(),
desiredAltitude: this.#extractFloat64(),
targetID: this.#extractUInt32(),
leaderID: 0,
targetPosition: new LatLng(
this.#extractFloat64(),
this.#extractFloat64(),
this.#extractFloat64()
),
state: this.#getState(this.#extractUint8()),
ROE: this.#getROE(this.#extractUint8()),
reactionToThreat: this.#getReactionToThreat(this.#extractUint8()),
emissionsCountermeasures: this.#getEmissionCountermeasure(this.#extractUint8()),
TACAN: {
isOn: this.#extractBool(),
channel: this.#extractUint8(),
XY: this.#extractChar(),
callsign: this.#extractString(4)
},
radio: {
frequency: this.#extractUInt32(),
callsign: this.#extractUint8(),
callsignNumber: this.#extractUint8()
},
activePath: [],
ammo: [],
contacts: [],
name: "",
unitName: "",
groupName: "",
category: "",
coalition: "",
task: ""
}
const pathLength = this.#extractUInt16();
const ammoLength = this.#extractUInt16();
const contactsLength = this.#extractUInt16();
const nameLength = this.#extractUInt16();
const unitNameLength = this.#extractUInt16();
const groupNameLength = this.#extractUInt16();
const categoryLength = this.#extractUInt16();
const coalitionLength = this.#extractUInt16();
if (pathLength > 0) {
unitData.activePath = [];
for (let idx = 0; idx < pathLength; idx++) {
unitData.activePath.push(new LatLng(this.#extractFloat64(), this.#extractFloat64(), this.#extractFloat64()));
}
}
if (ammoLength > 0) {
unitData.ammo = [];
for (let idx = 0; idx < pathLength; idx++) {
unitData.ammo.push({
quantity: this.#extractUInt16(),
name: this.#extractString(32),
guidance: this.#extractUint8(),
category: this.#extractUint8(),
missileCategory: this.#extractUint8()
});
}
}
if (contactsLength > 0) {
unitData.contacts = [];
for (let idx = 0; idx < pathLength; idx++) {
unitData.contacts.push({
ID: this.#extractUInt32(),
detectionMethod: this.#extractUint8()
});
}
}
if (nameLength > 0) {
unitData.name = this.#extractString(nameLength);
}
if (unitNameLength > 0) {
unitData.unitName = this.#extractString(unitNameLength);
}
if (groupNameLength > 0) {
unitData.groupName = this.#extractString(groupNameLength);
}
if (categoryLength > 0) {
unitData.category = this.#extractString(categoryLength);
}
if (coalitionLength > 0) {
unitData.coalition = this.#extractString(coalitionLength);
}
return {data: unitData, offset: this.#offset};
}
#extractBool() {
const value = this.#dataview.getUint8(this.#offset);
this.#offset += 1;
return value > 0;
}
#extractUint8() {
const value = this.#dataview.getUint8(this.#offset);
this.#offset += 1;
return value;
}
#extractUInt16() {
const value = this.#dataview.getUint16(this.#offset);
this.#offset += 2;
return value;
}
#extractUInt32() {
const value = this.#dataview.getUint32(this.#offset);
this.#offset += 4;
return value;
}
#extractFloat64() {
const value = this.#dataview.getFloat64(this.#offset);
this.#offset += 8;
return value;
}
#extractFromBitmask(bitmask: number, position: number) {
return (bitmask >> position & 1) > 0;
}
#extractString(length: number) {
const value = this.#decoder.decode(this.#buffer.slice(this.#offset, length));
this.#offset += length;
return value;
}
#extractChar() {
return this.#extractString(1);
}
#getState(state: number) {
if (state < states.length)
return states[state];
else
return states[0];
}
#getROE(ROE: number) {
if (ROE < ROEs.length)
return ROEs[ROE];
else
return ROEs[0];
}
#getReactionToThreat(reactionToThreat: number) {
if (reactionToThreat < reactionsToThreat.length)
return reactionsToThreat[reactionToThreat];
else
return reactionsToThreat[0];
}
#getEmissionCountermeasure(emissionCountermeasure: number) {
if (emissionCountermeasure < emissionsCountermeasures.length)
return emissionsCountermeasures[emissionCountermeasure];
else
return emissionsCountermeasures[0];
}
}

View File

@@ -6,7 +6,8 @@ import { CustomMarker } from '../map/custommarker';
import { SVGInjector } from '@tanem/svg-injector';
import { UnitDatabase } from './unitdatabase';
import { TargetMarker } from '../map/targetmarker';
import { BOMBING, CARPET_BOMBING, FIRE_AT_AREA, IDLE, MOVE_UNIT } from '../constants/constants';
import { BOMBING, CARPET_BOMBING, FIRE_AT_AREA, IDLE, MOVE_UNIT, ROEs, emissionsCountermeasures, reactionsToThreat, states } from '../constants/constants';
import { GeneralSettings, Radio, TACAN, UnitData, UnitIconOptions } from '../@types/unit';
var pathIcon = new Icon({
iconUrl: '/resources/theme/images/markers/marker-icon.png',
@@ -18,55 +19,58 @@ export class Unit extends CustomMarker {
ID: number;
#data: UnitData = {
baseData: {
controlled: false,
name: "",
unitName: "",
groupName: "",
alive: true,
category: "",
ID: 0,
alive: false,
human: false,
controlled: false,
hasTask: false,
desiredAltitudeType: false,
desiredSpeedType: false,
isTanker: false,
isAWACS: false,
onOff: false,
followRoads: false,
EPLRS: false,
generalSettings: {
prohibitAA: false,
prohibitAfterburner: false,
prohibitAG: false,
prohibitAirWpn: false,
prohibitJettison: false
},
flightData: {
latitude: 0,
longitude: 0,
altitude: 0,
heading: 0,
speed: 0,
position: new LatLng(0, 0),
speed: 0,
heading: 0,
fuel: 0,
desiredSpeed: 0,
desiredAltitude: 0,
targetID: 0,
leaderID: 0,
targetPosition: new LatLng(0, 0),
state: states[0],
ROE: ROEs[0],
reactionToThreat: reactionsToThreat[0],
emissionsCountermeasures: emissionsCountermeasures[0],
TACAN: {
isOn: false,
XY: 'X',
callsign: '',
channel: 0
},
missionData: {
fuel: 0,
flags: {},
ammo: {},
contacts: {},
hasTask: false,
coalition: "",
radio: {
frequency: 0,
callsign: 0,
callsignNumber: 0
},
formationData: {
leaderID: 0
},
taskData: {
currentState: "NONE",
currentTask: "",
activePath: {},
desiredSpeed: 0,
desiredSpeedType: "GS",
desiredAltitude: 0,
desiredAltitudeType: "AGL",
targetLocation: {},
isTanker: false,
isAWACS: false,
onOff: true,
followRoads: false,
targetID: 0
},
optionsData: {
ROE: "",
reactionToThreat: "",
emissionsCountermeasures: "",
TACAN: { isOn: false, channel: 0, XY: "X", callsign: "" },
radio: { frequency: 0, callsign: 1, callsignNumber: 1},
generalSettings: { prohibitJettison: false, prohibitAA: false, prohibitAG: false, prohibitAfterburner: false, prohibitAirWpn: false}
}
activePath: [],
ammo: [],
contacts: [],
name: "",
unitName: "",
groupName: "",
category: "",
coalition: "",
task: ""
};
#selectable: boolean;
@@ -80,8 +84,8 @@ export class Unit extends CustomMarker {
#pathPolyline: Polyline;
#contactsPolylines: Polyline[];
#miniMapMarker: CircleMarker | null = null;
#targetLocationMarker: TargetMarker;
#targetLocationPolyline: Polyline;
#targetPositionMarker: TargetMarker;
#targetPositionPolyline: Polyline;
#timer: number = 0;
@@ -96,26 +100,24 @@ export class Unit extends CustomMarker {
if (type === "NavyUnit") return NavyUnit;
}
constructor(ID: number, data: UpdateData) {
constructor(ID: number, data: UnitData) {
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(getMap());
this.#contactsPolylines = [];
this.#targetPositionMarker = new TargetMarker(new LatLng(0, 0));
this.#targetPositionPolyline = new Polyline([], { color: '#FF0000', weight: 3, opacity: 0.5, smoothFactor: 1 });
this.on('click', (e) => this.#onClick(e));
this.on('dblclick', (e) => this.#onDoubleClick(e));
this.on('contextmenu', (e) => this.#onContextMenu(e));
this.on('mouseover', () => { this.setHighlighted(true); })
this.on('mouseout', () => { this.setHighlighted(false); })
this.#pathPolyline = new Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1 });
this.#pathPolyline.addTo(getMap());
this.#contactsPolylines = [];
this.#targetLocationMarker = new TargetMarker(new LatLng(0, 0));
this.#targetLocationPolyline = new Polyline([], { color: '#FF0000', weight: 3, opacity: 0.5, smoothFactor: 1 });
/* Deselect units if they are hidden */
document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => {
window.setTimeout(() => { this.setSelected(this.getSelected() && !this.getHidden()) }, 300);
@@ -156,7 +158,7 @@ export class Unit extends CustomMarker {
setSelected(selected: boolean) {
/* Only alive units can be selected. Some units are not selectable (weapons) */
if ((this.getBaseData().alive || !selected) && this.getSelectable() && this.getSelected() != selected) {
if ((this.getData().alive || !selected) && this.getSelectable() && this.getSelected() != selected) {
this.#selected = selected;
this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-selected", selected);
if (selected) {
@@ -206,42 +208,34 @@ export class Unit extends CustomMarker {
}
getGroupMembers() {
return Object.values(getUnitsManager().getUnits()).filter((unit: Unit) => {return unit != this && unit.getBaseData().groupName === this.getBaseData().groupName;});
return Object.values(getUnitsManager().getUnits()).filter((unit: Unit) => {return unit != this && unit.getData().groupName === this.getData().groupName;});
}
/********************** Unit data *************************/
setData(data: UpdateData) {
setData(data: UnitData) {
/* Check if data has changed comparing new values to old values */
const positionChanged = (data.flightData != undefined && data.flightData.latitude != undefined && data.flightData.longitude != undefined && (this.getFlightData().latitude != data.flightData.latitude || this.getFlightData().longitude != data.flightData.longitude));
const headingChanged = (data.flightData != undefined && data.flightData.heading != undefined && this.getFlightData().heading != data.flightData.heading);
const aliveChanged = (data.baseData != undefined && data.baseData.alive != undefined && this.getBaseData().alive != data.baseData.alive);
const stateChanged = (data.taskData != undefined && data.taskData.currentState != undefined && this.getTaskData().currentState != data.taskData.currentState);
const controlledChanged = (data.baseData != undefined && data.baseData.controlled != undefined && this.getBaseData().controlled != data.baseData.controlled);
var updateMarker = (positionChanged || headingChanged || aliveChanged || stateChanged || controlledChanged || !getMap().hasLayer(this));
const positionChanged = this.getData().position.lat != data.position.lat || this.getData().position.lng != data.position.lng;
const headingChanged = this.getData().heading != data.heading;
const aliveChanged = this.getData().alive != data.alive;
const stateChanged = this.getData().state != data.state;
const controlledChanged = this.getData().controlled != data.controlled;
var updateMarker = positionChanged || headingChanged || aliveChanged || stateChanged || controlledChanged || !getMap().hasLayer(this);
/* Load the data from the received json */
Object.keys(this.#data).forEach((key1: string) => {
Object.keys(this.#data[key1 as keyof(UnitData)]).forEach((key2: string) => {
if (key1 in data && key2 in data[key1]) {
var value1 = this.#data[key1 as keyof(UnitData)];
var value2 = value1[key2 as keyof typeof value1];
if (typeof data[key1][key2] === typeof value2 || typeof value2 === "undefined")
//@ts-ignore
this.#data[key1 as keyof(UnitData)][key2 as keyof typeof struct] = data[key1][key2];
}
});
});
/* Assign the data */
/* TODO Allow for partial updates */
this.#data = data;
/* Fire an event when a unit dies */
if (aliveChanged && this.getBaseData().alive == false)
if (aliveChanged && this.getData().alive == false)
document.dispatchEvent(new CustomEvent("unitDeath", { detail: this }));
/* Dead units can't be selected */
this.setSelected(this.getSelected() && this.getBaseData().alive && !this.getHidden())
this.setSelected(this.getSelected() && this.getData().alive && !this.getHidden())
if (updateMarker)
this.#updateMarker();
// TODO dont delete the polylines of the detected units
this.#clearDetectedUnits();
if (this.getSelected()) {
this.#drawPath();
@@ -253,7 +247,6 @@ export class Unit extends CustomMarker {
this.#clearTarget();
}
document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this }));
}
@@ -261,30 +254,6 @@ export class Unit extends CustomMarker {
return this.#data;
}
getBaseData() {
return this.getData().baseData;
}
getFlightData() {
return this.getData().flightData;
}
getTaskData() {
return this.getData().taskData;
}
getMissionData() {
return this.getData().missionData;
}
getFormationData() {
return this.getData().formationData;
}
getOptionsData() {
return this.getData().optionsData;
}
/********************** Icon *************************/
createIcon(): void {
/* Set the icon */
@@ -298,7 +267,7 @@ export class Unit extends CustomMarker {
var el = document.createElement("div");
el.classList.add("unit");
el.setAttribute("data-object", `unit-${this.getMarkerCategory()}`);
el.setAttribute("data-coalition", this.getMissionData().coalition);
el.setAttribute("data-coalition", this.getData().coalition);
// Generate and append elements depending on active options
// Velocity vector
@@ -342,7 +311,7 @@ export class Unit extends CustomMarker {
if (this.getIconOptions().showShortLabel) {
var shortLabel = document.createElement("div");
shortLabel.classList.add("unit-short-label");
shortLabel.innerText = getUnitDatabaseByCategory(this.getMarkerCategory())?.getByName(this.getBaseData().name)?.shortLabel || "";
shortLabel.innerText = getUnitDatabaseByCategory(this.getMarkerCategory())?.getByName(this.getData().name)?.shortLabel || "";
el.append(shortLabel);
}
@@ -371,7 +340,7 @@ export class Unit extends CustomMarker {
summary.classList.add("unit-summary");
var callsign = document.createElement("div");
callsign.classList.add("unit-callsign");
callsign.innerText = this.getBaseData().unitName;
callsign.innerText = this.getData().unitName;
var altitude = document.createElement("div");
altitude.classList.add("unit-altitude");
var speed = document.createElement("div");
@@ -389,15 +358,15 @@ export class Unit extends CustomMarker {
updateVisibility() {
var hidden = false;
const hiddenUnits = getUnitsManager().getHiddenTypes();
if (this.getMissionData().flags.Human && hiddenUnits.includes("human"))
if (this.getData().human && hiddenUnits.includes("human"))
hidden = true;
else if (this.getBaseData().controlled == false && hiddenUnits.includes("dcs"))
else if (this.getData().controlled == false && hiddenUnits.includes("dcs"))
hidden = true;
else if (hiddenUnits.includes(this.getMarkerCategory()))
hidden = true;
else if (hiddenUnits.includes(this.getMissionData().coalition))
else if (hiddenUnits.includes(this.getData().coalition))
hidden = true;
this.setHidden(hidden || !this.getBaseData().alive);
this.setHidden(hidden || !this.getData().alive);
}
setHidden(hidden: boolean) {
@@ -419,24 +388,24 @@ export class Unit extends CustomMarker {
}
getLeader() {
return getUnitsManager().getUnitByID(this.getFormationData().leaderID);
return getUnitsManager().getUnitByID(this.getData().leaderID);
}
canFulfillRole(roles: string | string[]) {
if (typeof(roles) === "string")
roles = [roles];
return this.getDatabase()?.getByName(this.getBaseData().name)?.loadouts.some((loadout: LoadoutBlueprint) => {
return this.getDatabase()?.getByName(this.getData().name)?.loadouts.some((loadout: LoadoutBlueprint) => {
return (roles as string[]).some((role: string) => {return loadout.roles.includes(role)});
});
}
/********************** Unit commands *************************/
addDestination(latlng: L.LatLng) {
if (!this.getMissionData().flags.Human) {
if (!this.getData().human) {
var path: any = {};
if (this.getTaskData().activePath != undefined) {
path = this.getTaskData().activePath;
if (this.getData().activePath.length > 0) {
path = this.getData().activePath;
path[(Object.keys(path).length + 1).toString()] = latlng;
}
else {
@@ -447,86 +416,86 @@ export class Unit extends CustomMarker {
}
clearDestinations() {
if (!this.getMissionData().flags.Human)
this.getTaskData().activePath = undefined;
if (!this.getData().human)
this.getData().activePath = [];
}
attackUnit(targetID: number) {
/* Units can't attack themselves */
if (!this.getMissionData().flags.Human)
if (!this.getData().human)
if (this.ID != targetID)
attackUnit(this.ID, targetID);
}
followUnit(targetID: number, offset: { "x": number, "y": number, "z": number }) {
/* Units can't follow themselves */
if (!this.getMissionData().flags.Human)
if (!this.getData().human)
if (this.ID != targetID)
followUnit(this.ID, targetID, offset);
}
landAt(latlng: LatLng) {
if (!this.getMissionData().flags.Human)
if (!this.getData().human)
landAt(this.ID, latlng);
}
changeSpeed(speedChange: string) {
if (!this.getMissionData().flags.Human)
if (!this.getData().human)
changeSpeed(this.ID, speedChange);
}
changeAltitude(altitudeChange: string) {
if (!this.getMissionData().flags.Human)
if (!this.getData().human)
changeAltitude(this.ID, altitudeChange);
}
setSpeed(speed: number) {
if (!this.getMissionData().flags.Human)
if (!this.getData().human)
setSpeed(this.ID, speed);
}
setSpeedType(speedType: string) {
if (!this.getMissionData().flags.Human)
if (!this.getData().human)
setSpeedType(this.ID, speedType);
}
setAltitude(altitude: number) {
if (!this.getMissionData().flags.Human)
if (!this.getData().human)
setAltitude(this.ID, altitude);
}
setAltitudeType(altitudeType: string) {
if (!this.getMissionData().flags.Human)
if (!this.getData().human)
setAltitudeType(this.ID, altitudeType);
}
setROE(ROE: string) {
if (!this.getMissionData().flags.Human)
if (!this.getData().human)
setROE(this.ID, ROE);
}
setReactionToThreat(reactionToThreat: string) {
if (!this.getMissionData().flags.Human)
if (!this.getData().human)
setReactionToThreat(this.ID, reactionToThreat);
}
setEmissionsCountermeasures(emissionCountermeasure: string) {
if (!this.getMissionData().flags.Human)
if (!this.getData().human)
setEmissionsCountermeasures(this.ID, emissionCountermeasure);
}
setLeader(isLeader: boolean, wingmenIDs: number[] = []) {
if (!this.getMissionData().flags.Human)
if (!this.getData().human)
setLeader(this.ID, isLeader, wingmenIDs);
}
setOnOff(onOff: boolean) {
if (!this.getMissionData().flags.Human)
if (!this.getData().human)
setOnOff(this.ID, onOff);
}
setFollowRoads(followRoads: boolean) {
if (!this.getMissionData().flags.Human)
if (!this.getData().human)
setFollowRoads(this.ID, followRoads);
}
@@ -535,12 +504,12 @@ export class Unit extends CustomMarker {
}
refuel() {
if (!this.getMissionData().flags.Human)
if (!this.getData().human)
refuel(this.ID);
}
setAdvancedOptions(isTanker: boolean, isAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings) {
if (!this.getMissionData().flags.Human)
if (!this.getData().human)
setAdvacedOptions(this.ID, isTanker, isAWACS, TACAN, radio, generalSettings);
}
@@ -565,7 +534,7 @@ export class Unit extends CustomMarker {
super.onAdd(map);
/* If this is the first time adding this unit to the map, remove the temporary marker */
if (getUnitsManager().getUnitByID(this.ID) == null)
getMap().removeTemporaryMarker(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude));
getMap().removeTemporaryMarker(new LatLng(this.getData().position.lat, this.getData().position.lng));
return this;
}
@@ -602,12 +571,12 @@ export class Unit extends CustomMarker {
options["follow"] = {text: "Follow", tooltip: "Follow the unit at a user defined distance and position"};;
}
else if ((selectedUnits.length > 0 && (selectedUnits.includes(this))) || selectedUnits.length == 0) {
if (this.getBaseData().category == "Aircraft") {
if (this.getData().category == "Aircraft") {
options["refuel"] = {text: "Air to air refuel", tooltip: "Refuel unit at the nearest AAR Tanker. If no tanker is available the unit will RTB."}; // TODO Add some way of knowing which aircraft can AAR
}
}
if ((selectedUnits.length === 0 && this.getBaseData().category == "Aircraft") || (selectedUnitTypes.length === 1 && ["Aircraft"].includes(selectedUnitTypes[0])))
if ((selectedUnits.length === 0 && this.getData().category == "Aircraft") || (selectedUnitTypes.length === 1 && ["Aircraft"].includes(selectedUnitTypes[0])))
{
if (selectedUnits.concat([this]).every((unit: Unit) => {return unit.canFulfillRole(["CAS", "Strike"])})) {
options["bomb"] = {text: "Precision bombing", tooltip: "Precision bombing of a specific point"};
@@ -615,7 +584,7 @@ export class Unit extends CustomMarker {
}
}
if ((selectedUnits.length === 0 && this.getBaseData().category == "GroundUnit") || selectedUnitTypes.length === 1 && ["GroundUnit"].includes(selectedUnitTypes[0])) {
if ((selectedUnits.length === 0 && this.getData().category == "GroundUnit") || selectedUnitTypes.length === 1 && ["GroundUnit"].includes(selectedUnitTypes[0])) {
if (selectedUnits.concat([this]).every((unit: Unit) => {return unit.canFulfillRole(["Gun Artillery", "Rocket Artillery", "Infantry", "IFV", "Tank"])}))
options["fire-at-area"] = {text: "Fire at area", tooltip: "Fire at a large area"};
}
@@ -683,12 +652,12 @@ export class Unit extends CustomMarker {
this.updateVisibility();
/* Draw the minimap marker */
if (this.getBaseData().alive) {
if (this.getData().alive) {
if (this.#miniMapMarker == null) {
this.#miniMapMarker = new CircleMarker(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude), { radius: 0.5 });
if (this.getMissionData().coalition == "neutral")
this.#miniMapMarker = new CircleMarker(new LatLng(this.getData().position.lat, this.getData().position.lng), { radius: 0.5 });
if (this.getData().coalition == "neutral")
this.#miniMapMarker.setStyle({ color: "#CFD9E8" });
else if (this.getMissionData().coalition == "red")
else if (this.getData().coalition == "red")
this.#miniMapMarker.setStyle({ color: "#ff5858" });
else
this.#miniMapMarker.setStyle({ color: "#247be2" });
@@ -696,7 +665,7 @@ export class Unit extends CustomMarker {
this.#miniMapMarker.bringToBack();
}
else {
this.#miniMapMarker.setLatLng(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude));
this.#miniMapMarker.setLatLng(new LatLng(this.getData().position.lat, this.getData().position.lng));
this.#miniMapMarker.bringToBack();
}
}
@@ -709,39 +678,39 @@ export class Unit extends CustomMarker {
/* Draw the marker */
if (!this.getHidden()) {
this.setLatLng(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude));
this.setLatLng(new LatLng(this.getData().position.lat, this.getData().position.lng));
var element = this.getElement();
if (element != null) {
/* Draw the velocity vector */
element.querySelector(".unit-vvi")?.setAttribute("style", `height: ${15 + this.getFlightData().speed / 5}px;`);
element.querySelector(".unit-vvi")?.setAttribute("style", `height: ${15 + this.getData().speed / 5}px;`);
/* Set fuel data */
element.querySelector(".unit-fuel-level")?.setAttribute("style", `width: ${this.getMissionData().fuel}%`);
element.querySelector(".unit")?.toggleAttribute("data-has-low-fuel", this.getMissionData().fuel < 20);
element.querySelector(".unit-fuel-level")?.setAttribute("style", `width: ${this.getData().fuel}%`);
element.querySelector(".unit")?.toggleAttribute("data-has-low-fuel", this.getData().fuel < 20);
/* Set dead/alive flag */
element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.getBaseData().alive);
element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.getData().alive);
/* Set current unit state */
if (this.getMissionData().flags.Human) // Unit is human
if (this.getData().human) // Unit is human
element.querySelector(".unit")?.setAttribute("data-state", "human");
else if (!this.getBaseData().controlled) // Unit is under DCS control (not Olympus)
else if (!this.getData().controlled) // Unit is under DCS control (not Olympus)
element.querySelector(".unit")?.setAttribute("data-state", "dcs");
else if ((this.getBaseData().category == "Aircraft" || this.getBaseData().category == "Helicopter") && !this.getMissionData().hasTask)
else if ((this.getData().category == "Aircraft" || this.getData().category == "Helicopter") && !this.getData().hasTask)
element.querySelector(".unit")?.setAttribute("data-state", "no-task");
else // Unit is under Olympus control
element.querySelector(".unit")?.setAttribute("data-state", this.getTaskData().currentState.toLowerCase());
else // Unit is under Olympus control
element.querySelector(".unit")?.setAttribute("data-state", this.getData().state.toLowerCase());
/* Set altitude and speed */
if (element.querySelector(".unit-altitude"))
(<HTMLElement>element.querySelector(".unit-altitude")).innerText = "FL" + String(Math.floor(mToFt(this.getFlightData().altitude) / 100));
(<HTMLElement>element.querySelector(".unit-altitude")).innerText = "FL" + String(Math.floor(mToFt(this.getData().position.alt as number) / 100));
if (element.querySelector(".unit-speed"))
(<HTMLElement>element.querySelector(".unit-speed")).innerText = String(Math.floor(msToKnots(this.getFlightData().speed))) + "GS";
(<HTMLElement>element.querySelector(".unit-speed")).innerText = String(Math.floor(msToKnots(this.getData().speed))) + "GS";
/* Rotate elements according to heading */
element.querySelectorAll("[data-rotate-to-heading]").forEach(el => {
const headingDeg = rad2deg(this.getFlightData().heading);
const headingDeg = rad2deg(this.getData().heading);
let currentStyle = el.getAttribute("style") || "";
el.setAttribute("style", currentStyle + `transform:rotate(${headingDeg}deg);`);
});
@@ -756,7 +725,7 @@ export class Unit extends CustomMarker {
var newHasFox2 = false;
var newHasFox3 = false;
var newHasOtherAmmo = false;
Object.values(this.getMissionData().ammo).forEach((ammo: any) => {
Object.values(this.getData().ammo).forEach((ammo: any) => {
if (ammo.desc.category == 1 && ammo.desc.missileCategory == 1) {
if (ammo.desc.guidance == 4 || ammo.desc.guidance == 5)
newHasFox1 = true;
@@ -786,30 +755,30 @@ export class Unit extends CustomMarker {
/* Set vertical offset for altitude stacking */
var pos = getMap().latLngToLayerPoint(this.getLatLng()).round();
this.setZIndexOffset(1000 + Math.floor(this.getFlightData().altitude) - pos.y + (this.#highlighted || this.#selected ? 5000 : 0));
this.setZIndexOffset(1000 + Math.floor(this.getData().position.alt as number) - pos.y + (this.#highlighted || this.#selected ? 5000 : 0));
}
}
#drawPath() {
if (this.getTaskData().activePath != undefined) {
if (this.getData().activePath != undefined) {
var points = [];
points.push(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude));
points.push(new LatLng(this.getData().position.lat, this.getData().position.lng));
/* Add markers if missing */
while (this.#pathMarkers.length < Object.keys(this.getTaskData().activePath).length) {
while (this.#pathMarkers.length < Object.keys(this.getData().activePath).length) {
var marker = new Marker([0, 0], { icon: pathIcon }).addTo(getMap());
this.#pathMarkers.push(marker);
}
/* Remove markers if too many */
while (this.#pathMarkers.length > Object.keys(this.getTaskData().activePath).length) {
while (this.#pathMarkers.length > Object.keys(this.getData().activePath).length) {
getMap().removeLayer(this.#pathMarkers[this.#pathMarkers.length - 1]);
this.#pathMarkers.splice(this.#pathMarkers.length - 1, 1)
}
/* Update the position of the existing markers (to avoid creating markers uselessly) */
for (let WP in this.getTaskData().activePath) {
var destination = this.getTaskData().activePath[WP];
for (let WP in this.getData().activePath) {
var destination = this.getData().activePath[WP];
this.#pathMarkers[parseInt(WP) - 1].setLatLng([destination.lat, destination.lng]);
points.push(new LatLng(destination.lat, destination.lng));
this.#pathPolyline.setLatLngs(points);
@@ -829,27 +798,25 @@ export class Unit extends CustomMarker {
}
#drawDetectedUnits() {
for (let index in this.getMissionData().contacts) {
var targetData = this.getMissionData().contacts[index];
if (targetData.object != undefined){
var target = getUnitsManager().getUnitByID(targetData.object["id_"])
if (target != null) {
var startLatLng = new LatLng(this.getFlightData().latitude, this.getFlightData().longitude)
var endLatLng = new LatLng(target.getFlightData().latitude, target.getFlightData().longitude)
for (let index in this.getData().contacts) {
var targetData = this.getData().contacts[index];
var target = getUnitsManager().getUnitByID(targetData.ID)
if (target != null) {
var startLatLng = new LatLng(this.getData().position.lat, this.getData().position.lng)
var endLatLng = new LatLng(target.getData().position.lat, target.getData().position.lng)
var color;
if (targetData.detectionMethod === "RADAR")
color = "#FFFF00";
else if (targetData.detectionMethod === "VISUAL")
color = "#FF00FF";
else if (targetData.detectionMethod === "RWR")
color = "#00FF00";
else
color = "#FFFFFF";
var targetPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 0.4, smoothFactor: 1, dashArray: "4, 8" });
targetPolyline.addTo(getMap());
this.#contactsPolylines.push(targetPolyline)
}
var color;
if (targetData.detectionMethod === 1)
color = "#FF00FF";
else if (targetData.detectionMethod === 4)
color = "#FFFF00";
else if (targetData.detectionMethod === 16)
color = "#00FF00";
else
color = "#FFFFFF";
var targetPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 0.4, smoothFactor: 1, dashArray: "4, 8" });
targetPolyline.addTo(getMap());
this.#contactsPolylines.push(targetPolyline)
}
}
}
@@ -861,40 +828,33 @@ export class Unit extends CustomMarker {
}
#drawTarget() {
const targetLocation = this.getTaskData().targetLocation;
if (targetLocation.latitude && targetLocation.longitude && targetLocation.latitude != 0 && targetLocation.longitude != 0) {
const lat = targetLocation.latitude;
const lng = targetLocation.longitude;
if (lat && lng)
this.#drawTargetLocation(new LatLng(lat, lng));
if (this.getData().targetPosition.lat != 0 && this.getData().targetPosition.lng != 0) {
this.#drawtargetPosition(this.getData().targetPosition);
}
else if (this.getTaskData().targetID != 0 && getUnitsManager().getUnitByID(this.getTaskData().targetID)) {
const flightData = getUnitsManager().getUnitByID(this.getTaskData().targetID)?.getFlightData();
const lat = flightData?.latitude;
const lng = flightData?.longitude;
if (lat && lng)
this.#drawTargetLocation(new LatLng(lat, lng));
else if (this.getData().targetID != 0 && getUnitsManager().getUnitByID(this.getData().targetID)) {
const position = getUnitsManager().getUnitByID(this.getData().targetID)?.getData().position;
if (position)
this.#drawtargetPosition(position);
}
else
this.#clearTarget();
}
#drawTargetLocation(targetLocation: LatLng) {
if (!getMap().hasLayer(this.#targetLocationMarker))
this.#targetLocationMarker.addTo(getMap());
if (!getMap().hasLayer(this.#targetLocationPolyline))
this.#targetLocationPolyline.addTo(getMap());
this.#targetLocationMarker.setLatLng(new LatLng(targetLocation.lat, targetLocation.lng));
this.#targetLocationPolyline.setLatLngs([new LatLng(this.getFlightData().latitude, this.getFlightData().longitude), new LatLng(targetLocation.lat, targetLocation.lng)])
#drawtargetPosition(targetPosition: LatLng) {
if (!getMap().hasLayer(this.#targetPositionMarker))
this.#targetPositionMarker.addTo(getMap());
if (!getMap().hasLayer(this.#targetPositionPolyline))
this.#targetPositionPolyline.addTo(getMap());
this.#targetPositionMarker.setLatLng(new LatLng(targetPosition.lat, targetPosition.lng));
this.#targetPositionPolyline.setLatLngs([new LatLng(this.getData().position.lat, this.getData().position.lng), new LatLng(targetPosition.lat, targetPosition.lng)])
}
#clearTarget() {
if (getMap().hasLayer(this.#targetLocationMarker))
this.#targetLocationMarker.removeFrom(getMap());
if (getMap().hasLayer(this.#targetPositionMarker))
this.#targetPositionMarker.removeFrom(getMap());
if (getMap().hasLayer(this.#targetLocationPolyline))
this.#targetLocationPolyline.removeFrom(getMap());
if (getMap().hasLayer(this.#targetPositionPolyline))
this.#targetPositionPolyline.removeFrom(getMap());
}
}
@@ -915,7 +875,7 @@ export class AirUnit extends Unit {
}
export class Aircraft extends AirUnit {
constructor(ID: number, data: UpdateData) {
constructor(ID: number, data: UnitData) {
super(ID, data);
}
@@ -925,7 +885,7 @@ export class Aircraft extends AirUnit {
}
export class Helicopter extends AirUnit {
constructor(ID: number, data: UpdateData) {
constructor(ID: number, data: UnitData) {
super(ID, data);
}
@@ -935,7 +895,7 @@ export class Helicopter extends AirUnit {
}
export class GroundUnit extends Unit {
constructor(ID: number, data: UpdateData) {
constructor(ID: number, data: UnitData) {
super(ID, data);
}
@@ -954,12 +914,12 @@ export class GroundUnit extends Unit {
}
getMarkerCategory() {
return getMarkerCategoryByName(this.getBaseData().name);
return getMarkerCategoryByName(this.getData().name);
}
}
export class NavyUnit extends Unit {
constructor(ID: number, data: UpdateData) {
constructor(ID: number, data: UnitData) {
super(ID, data);
}
@@ -983,7 +943,7 @@ export class NavyUnit extends Unit {
}
export class Weapon extends Unit {
constructor(ID: number, data: UpdateData) {
constructor(ID: number, data: UnitData) {
super(ID, data);
this.setSelectable(false);
}
@@ -1004,7 +964,7 @@ export class Weapon extends Unit {
}
export class Missile extends Weapon {
constructor(ID: number, data: UpdateData) {
constructor(ID: number, data: UnitData) {
super(ID, data);
}
@@ -1014,7 +974,7 @@ export class Missile extends Weapon {
}
export class Bomb extends Weapon {
constructor(ID: number, data: UpdateData) {
constructor(ID: number, data: UnitData) {
super(ID, data);
}

View File

@@ -7,6 +7,8 @@ import { CoalitionArea } from "../map/coalitionarea";
import { Airbase } from "../missionhandler/airbase";
import { groundUnitsDatabase } from "./groundunitsdatabase";
import { IADSRoles, IDLE, MOVE_UNIT } from "../constants/constants";
import { DataExtractor } from "./dataextractor";
import { UnitData } from "../@types/unit";
export class UnitsManager {
#units: { [ID: number]: Unit };
@@ -31,7 +33,7 @@ export class UnitsManager {
getSelectableAircraft() {
const units = this.getUnits();
return Object.keys(units).reduce((acc: { [key: number]: Unit }, unitId: any) => {
const baseData = units[unitId].getBaseData();
const baseData = units[unitId].getData();
if (baseData.category === "Aircraft" && baseData.alive === true) {
acc[unitId] = units[unitId];
}
@@ -51,13 +53,13 @@ export class UnitsManager {
}
getUnitsByHotgroup(hotgroup: number) {
return Object.values(this.#units).filter((unit: Unit) => { return unit.getBaseData().alive && unit.getHotgroup() == hotgroup });
return Object.values(this.#units).filter((unit: Unit) => { return unit.getData().alive && unit.getHotgroup() == hotgroup });
}
addUnit(ID: number, data: UnitData) {
if (data.baseData && data.baseData.category){
/* The name of the unit category is exactly the same as the constructor name */
var constructor = Unit.getConstructor(data.baseData.category);
if (data.category){
/* The name of the unit category is exactly the same as the constructor name */
var constructor = Unit.getConstructor(data.category);
if (constructor != undefined) {
this.#units[ID] = new constructor(ID, data);
}
@@ -68,82 +70,42 @@ export class UnitsManager {
}
update(data: string) {
update(encodedData: string) {
var updatedUnits: Unit[] = [];
var buffer = base64ToBytes(data);
/*Coords position;
double speed;
double heading;
unsigned short fuel;
double desiredSpeed;
double desiredAltitude;
unsigned int targetID;
Coords targetPosition;
unsigned char state;
unsigned char ROE;
unsigned char reactionToThreat;
unsigned char emissionsCountermeasures;
Options::TACAN TACAN;
Options::Radio Radio;
unsigned short pathLength;
unsigned char nameLength;
unsigned char unitNameLength;
unsigned char groupNameLength;
unsigned char categoryLength;
unsigned char coalitionLength;*/
var buffer = base64ToBytes(encodedData);
var dataExtractor = new DataExtractor(buffer);
var data: {[key: string]: UnitData} = {};
var offset = 0;
var dataview = new DataView(buffer);
const ID = dataview.getUint32(offset, true); offset += 4;
const bitmask = dataview.getUint32(offset , true); offset += 4;
const alive = bitmask & (1 << 0);
const human = bitmask >> 1 & 1;
const controlled = bitmask >> 2 & 1;
const hasTask = bitmask >> 3 & 1;
const desiredAltitudeType = bitmask >> 16 & 1;
const desiredSpeedType = bitmask >> 17 & 1;
const isTanker = bitmask >> 18 & 1;
const isAWACS = bitmask >> 19 & 1;
const onOff = bitmask >> 20 & 1;
const followRoads = bitmask >> 21 & 1;
const EPLRS = bitmask >> 22 & 1;
const prohibitAA = bitmask >> 23 & 1;
const prohibitAfterburner = bitmask >> 24 & 1;
const prohibitAG = bitmask >> 25 & 1;
const prohibitAirWpn = bitmask >> 26 & 1;
const prohibitJettison = bitmask >> 27 & 1;
while (offset < buffer.byteLength) {
const result = dataExtractor.extractData(offset);
data[result.data.ID] = result.data;
offset = result.offset;
}
const latitude = dataview.getFloat64(offset , true); offset += 8;
const longitude = dataview.getFloat64(offset , true); offset += 8;
const altitude = dataview.getFloat64(offset , true); offset += 8;
const speed = dataview.getFloat64(offset , true); offset += 8;
const heading = dataview.getFloat64(offset , true); offset += 8;
var foo = 12;
/*Object.keys(data.units)
Object.keys(data)
.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]);
this.addUnit(parseInt(ID), data[ID]);
this.#units[parseInt(ID)]?.setData(data[ID]);
}, timeout);
return timeout + 10;
}, 10);
Object.keys(data.units)
Object.keys(data)
.filter((ID: string) => ID in this.#units)
.forEach((ID: string) => {
updatedUnits.push(this.#units[parseInt(ID)]);
this.#units[parseInt(ID)]?.setData(data.units[ID])
this.#units[parseInt(ID)]?.setData(data[ID]);
});
this.getSelectedUnits().forEach((unit: Unit) => {
if (!updatedUnits.includes(unit))
unit.setData({})
});*/
// TODO why did we do this?
//this.getSelectedUnits().forEach((unit: Unit) => {
// if (!updatedUnits.includes(unit))
// unit.setData(null);
//});
}
setHiddenType(key: string, value: boolean) {
@@ -170,7 +132,7 @@ export class UnitsManager {
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);
var latlng = new LatLng(this.#units[ID].getData().position.lat, this.#units[ID].getData().position.lng);
if (bounds.contains(latlng)) {
this.#units[ID].setSelected(true);
}
@@ -187,11 +149,11 @@ export class UnitsManager {
}
if (options) {
if (options.excludeHumans)
selectedUnits = selectedUnits.filter((unit: Unit) => { return !unit.getMissionData().flags.Human });
selectedUnits = selectedUnits.filter((unit: Unit) => { return !unit.getData().human });
if (options.onlyOnePerGroup) {
var temp: Unit[] = [];
for (let unit of selectedUnits) {
if (!temp.some((otherUnit: Unit) => unit.getBaseData().groupName == otherUnit.getBaseData().groupName))
if (!temp.some((otherUnit: Unit) => unit.getData().groupName == otherUnit.getData().groupName))
temp.push(unit);
}
selectedUnits = temp;
@@ -236,7 +198,7 @@ export class UnitsManager {
if (this.getSelectedUnits().length == 0)
return undefined;
return this.getSelectedUnits().map((unit: Unit) => {
return unit.getMissionData().coalition
return unit.getData().coalition
})?.reduce((a: any, b: any) => {
return a == b ? a : undefined
});
@@ -256,8 +218,8 @@ export class UnitsManager {
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 (unit.getData().state === "Follow") {
const leader = this.getUnitByID(unit.getData().leaderID)
if (leader && leader.getSelected())
leader.addDestination(latlng);
else
@@ -276,8 +238,8 @@ export class UnitsManager {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true });
for (let idx in selectedUnits) {
const unit = selectedUnits[idx];
if (unit.getTaskData().currentState === "Follow") {
const leader = this.getUnitByID(unit.getFormationData().leaderID)
if (unit.getData().state === "Follow") {
const leader = this.getUnitByID(unit.getData().leaderID)
if (leader && leader.getSelected())
leader.clearDestinations();
else
@@ -388,13 +350,13 @@ export class UnitsManager {
for (let idx in selectedUnits) {
selectedUnits[idx].attackUnit(ID);
}
this.#showActionMessage(selectedUnits, `attacking unit ${this.getUnitByID(ID)?.getBaseData().unitName}`);
this.#showActionMessage(selectedUnits, `attacking unit ${this.getUnitByID(ID)?.getData().unitName}`);
}
selectedUnitsDelete(explosion: boolean = false) {
var selectedUnits = this.getSelectedUnits(); /* Can be applied to humans too */
const selectionContainsAHuman = selectedUnits.some( ( unit:Unit ) => {
return unit.getMissionData().flags.Human === true;
return unit.getData().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?" ) ) {
@@ -455,7 +417,7 @@ export class UnitsManager {
}
count++;
}
this.#showActionMessage(selectedUnits, `following unit ${this.getUnitByID(ID)?.getBaseData().unitName}`);
this.#showActionMessage(selectedUnits, `following unit ${this.getUnitByID(ID)?.getData().unitName}`);
}
selectedUnitsSetHotgroup(hotgroup: number) {
@@ -477,7 +439,7 @@ export class UnitsManager {
/* Compute the center of the group */
var center = { x: 0, y: 0 };
selectedUnits.forEach((unit: Unit) => {
var mercator = latLngToMercator(unit.getFlightData().latitude, unit.getFlightData().longitude);
var mercator = latLngToMercator(unit.getData().position.lat, unit.getData().position.lng);
center.x += mercator.x / selectedUnits.length;
center.y += mercator.y / selectedUnits.length;
});
@@ -485,7 +447,7 @@ export class UnitsManager {
/* Compute the distances from the center of the group */
var unitDestinations: { [key: number]: LatLng } = {};
selectedUnits.forEach((unit: Unit) => {
var mercator = latLngToMercator(unit.getFlightData().latitude, unit.getFlightData().longitude);
var mercator = latLngToMercator(unit.getData().position.lat, unit.getData().position.lat);
var distancesFromCenter = { dx: mercator.x - center.x, dy: mercator.y - center.y };
/* Rotate the distance according to the group rotation */
@@ -615,8 +577,8 @@ export class UnitsManager {
#showActionMessage(units: Unit[], message: string) {
if (units.length == 1)
getInfoPopup().setText(`${units[0].getBaseData().unitName} ${message}`);
getInfoPopup().setText(`${units[0].getData().unitName} ${message}`);
else if (units.length > 1)
getInfoPopup().setText(`${units[0].getBaseData().unitName} and ${units.length - 1} other units ${message}`);
getInfoPopup().setText(`${units[0].getData().unitName} and ${units.length - 1} other units ${message}`);
}
}