mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
More work on client conversion to binary data
This commit is contained in:
141
client/src/@types/unit.d.ts
vendored
141
client/src/@types/unit.d.ts
vendored
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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 ) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
219
client/src/units/dataextractor.ts
Normal file
219
client/src/units/dataextractor.ts
Normal 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];
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
@@ -59,7 +59,7 @@ namespace Options {
|
||||
namespace DataTypes {
|
||||
struct Ammo {
|
||||
unsigned short quantity = 0;
|
||||
string name;
|
||||
char name[32];
|
||||
unsigned char guidance = 0;
|
||||
unsigned char category = 0;
|
||||
unsigned char missileCategory = 0;
|
||||
@@ -88,6 +88,8 @@ namespace DataTypes {
|
||||
Options::TACAN TACAN;
|
||||
Options::Radio Radio;
|
||||
unsigned short pathLength;
|
||||
unsigned short ammoLength;
|
||||
unsigned short contactsLength;
|
||||
unsigned char nameLength;
|
||||
unsigned char unitNameLength;
|
||||
unsigned char groupNameLength;
|
||||
|
||||
@@ -180,8 +180,13 @@ unsigned int Unit::getUpdateData(char* &data)
|
||||
/* Reserve data for:
|
||||
1) DataPacket;
|
||||
2) Active path;
|
||||
3) Ammo vector;
|
||||
4) Contacts vector;
|
||||
*/
|
||||
data = (char*)malloc(sizeof(DataTypes::DataPacket) + activePath.size() * sizeof(Coords));
|
||||
data = (char*)malloc(sizeof(DataTypes::DataPacket) +
|
||||
activePath.size() * sizeof(Coords) +
|
||||
ammo.size() * sizeof(Coords) +
|
||||
contacts.size() * sizeof(Coords));
|
||||
unsigned int offset = 0;
|
||||
|
||||
/* Prepare the data packet and copy it to memory */
|
||||
@@ -221,6 +226,8 @@ unsigned int Unit::getUpdateData(char* &data)
|
||||
TACAN,
|
||||
radio,
|
||||
activePath.size(),
|
||||
ammo.size(),
|
||||
contacts.size(),
|
||||
name.size(),
|
||||
unitName.size(),
|
||||
groupName.size(),
|
||||
@@ -231,14 +238,21 @@ unsigned int Unit::getUpdateData(char* &data)
|
||||
memcpy(data + offset, &dataPacket, sizeof(dataPacket));
|
||||
offset += sizeof(dataPacket);
|
||||
|
||||
/* Prepare the path memory and copy it to memory */
|
||||
/* Prepare the path vector and copy it to memory */
|
||||
std::vector<Coords> path;
|
||||
for (const Coords& c : activePath)
|
||||
path.push_back(c);
|
||||
|
||||
memcpy(data + offset, &path, activePath.size() * sizeof(Coords));
|
||||
offset += activePath.size() * sizeof(Coords);
|
||||
|
||||
/* Copy the ammo vector to memory */
|
||||
memcpy(data + offset, &ammo, ammo.size() * sizeof(DataTypes::Ammo));
|
||||
offset += ammo.size() * sizeof(DataTypes::Ammo);
|
||||
|
||||
/* Copy the contacts vector to memory */
|
||||
memcpy(data + offset, &contacts, contacts.size() * sizeof(DataTypes::Contact));
|
||||
offset += contacts.size() * sizeof(DataTypes::Contact);
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user