Converted data transfer to binary key-value method

This commit is contained in:
Pax1601 2023-06-26 18:53:04 +02:00
parent 1989219579
commit 4d9dd364b6
22 changed files with 858 additions and 885 deletions

View File

@ -46,42 +46,8 @@ interface Contact {
detectionMethod: number
}
interface UnitData {
ID: number,
alive: boolean,
human: boolean,
controlled: boolean,
hasTask: boolean,
desiredAltitudeType: string,
desiredSpeedType: string,
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
interface Offset {
x: number,
y: number,
z: number
}

View File

@ -119,7 +119,7 @@ export abstract class ATCBoard {
const unitCanBeAdded = () => {
if ( baseData.category !== "Aircraft" ) {
if ( unit.getCategory() !== "Aircraft" ) {
return false;
}

View File

@ -48,7 +48,7 @@ export class UnitDataTable extends Panel {
for (const unit of unitsArray) {
const dataset = [unit.getData().unitName, unit.getData().name, unit.getData().category, (unit.getData().controlled) ? "AI" : "Human"];
const dataset = [unit.getData().unitName, unit.getData().name, unit.getCategory(), (unit.getData().controlled) ? "AI" : "Human"];
addRow(el, dataset);
}

View File

@ -131,4 +131,46 @@ export const COALITIONAREA_INTERACT = "Interact with Coalition Areas"
export const visibilityControls: string[] = ["human", "dcs", "aircraft", "groundunit-sam", "groundunit-other", "navyunit", "airbase"];
export const visibilityControlsTootlips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"];
export const IADSRoles: {[key: string]: number}= {"AAA": 0.8, "MANPADS": 0.3, "SAM Sites": 0.1, "Radar": 0.05};
export const IADSRoles: {[key: string]: number}= {"AAA": 0.8, "MANPADS": 0.3, "SAM Sites": 0.1, "Radar": 0.05};
export enum DataIndexes {
startOfData = 0,
category,
alive,
human,
controlled,
coalition,
country,
name,
unitName,
groupName,
state,
task,
hasTask,
position,
speed,
heading,
isTanker,
isAWACS,
onOff,
followRoads,
fuel,
desiredSpeed,
desiredSpeedType,
desiredAltitude,
desiredAltitudeType,
leaderID,
formationOffset,
targetID,
targetPosition,
ROE,
reactionToThreat,
emissionsCountermeasures,
TACAN,
radio,
generalSettings,
ammo,
contacts,
activePath,
endOfData = 255
};

View File

@ -5,6 +5,7 @@ import { aircraftDatabase } from "../units/aircraftdatabase";
import { helicopterDatabase } from "../units/helicopterdatabase";
import { groundUnitsDatabase } from "../units/groundunitsdatabase";
import { Buffer } from "buffer";
import { ROEs, emissionsCountermeasures, reactionsToThreat, states } from "../constants/constants";
export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) {
const φ1 = deg2rad(lat1); // φ, λ in radians
@ -253,3 +254,40 @@ export function getUnitDatabaseByCategory(category: string) {
export function base64ToBytes(base64: string) {
return Buffer.from(base64, 'base64').buffer;
}
export function enumToState(state: number) {
if (state < states.length)
return states[state];
else
return states[0];
}
export function enumToROE(ROE: number) {
if (ROE < ROEs.length)
return ROEs[ROE];
else
return ROEs[0];
}
export function enumToReactionToThreat(reactionToThreat: number) {
if (reactionToThreat < reactionsToThreat.length)
return reactionsToThreat[reactionToThreat];
else
return reactionsToThreat[0];
}
export function enumToEmissioNCountermeasure(emissionCountermeasure: number) {
if (emissionCountermeasure < emissionsCountermeasures.length)
return emissionsCountermeasures[emissionCountermeasure];
else
return emissionsCountermeasures[0];
}
export function enumToCoalition(coalitionID: number) {
switch (coalitionID){
case 0: return "neutral";
case 1: return "red";
case 2: return "blue";
}
return "";
}

View File

@ -1,9 +1,8 @@
import { LatLng } from "leaflet";
import { UnitData } from "../@types/unit";
import { ROEs, emissionsCountermeasures, reactionsToThreat, states } from "../constants/constants";
import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN } from "../@types/unit";
export class DataExtractor {
#offset = 0;
#seekPosition = 0;
#dataview: DataView;
#decoder: TextDecoder;
#buffer: ArrayBuffer;
@ -14,181 +13,59 @@ export class DataExtractor {
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)? "AGL": "ASL",
desiredSpeedType: this.extractFromBitmask(bitmask, 17)? "GS": "CAS",
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(),
leaderID: this.extractUInt32(),
targetID: this.extractUInt32(),
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()),
coalition: this.#getCoalition(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: [],
task: "",
name: "",
unitName: "",
groupName: "",
category: "",
}
const pathLength = this.extractUInt16();
const ammoLength = this.extractUInt16();
const contactsLength = this.extractUInt16();
const taskLength = this.extractUInt8();
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 (taskLength > 0) {
unitData.task = this.extractString(taskLength);
}
const nameLength = this.extractUInt16();
const unitNameLength = this.extractUInt16();
const groupNameLength = this.extractUInt16();
const categoryLength = this.extractUInt16();
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);
}
return {data: unitData, offset: this.#offset};
getSeekPosition() {
return this.#seekPosition;
}
extractBool() {
const value = this.#dataview.getUint8(this.#offset);
this.#offset += 1;
const value = this.#dataview.getUint8(this.#seekPosition);
this.#seekPosition += 1;
return value > 0;
}
extractUInt8() {
const value = this.#dataview.getUint8(this.#offset);
this.#offset += 1;
const value = this.#dataview.getUint8(this.#seekPosition);
this.#seekPosition += 1;
return value;
}
extractUInt16() {
const value = this.#dataview.getUint16(this.#offset, true);
this.#offset += 2;
const value = this.#dataview.getUint16(this.#seekPosition, true);
this.#seekPosition += 2;
return value;
}
extractUInt32() {
const value = this.#dataview.getUint32(this.#offset, true);
this.#offset += 4;
const value = this.#dataview.getUint32(this.#seekPosition, true);
this.#seekPosition += 4;
return value;
}
extractUInt64() {
const value = this.#dataview.getBigUint64(this.#offset, true);
this.#offset += 8;
const value = this.#dataview.getBigUint64(this.#seekPosition, true);
this.#seekPosition += 8;
return value;
}
extractFloat64() {
const value = this.#dataview.getFloat64(this.#offset, true);
this.#offset += 8;
const value = this.#dataview.getFloat64(this.#seekPosition, true);
this.#seekPosition += 8;
return value;
}
extractLatLng() {
return new LatLng(this.extractFloat64(), this.extractFloat64(), this.extractFloat64())
}
extractFromBitmask(bitmask: number, position: number) {
return ((bitmask >> position) & 1) > 0;
}
extractString(length: number) {
const value = this.#decoder.decode(this.#buffer.slice(this.#offset, this.#offset +length));
this.#offset += length;
extractString(length?: number) {
if (length === undefined)
length = this.extractUInt16()
const value = this.#decoder.decode(this.#buffer.slice(this.#seekPosition, this.#seekPosition + length));
this.#seekPosition += length;
return value;
}
@ -196,44 +73,78 @@ export class DataExtractor {
return this.extractString(1);
}
getOffset() {
return this.#offset;
extractTACAN() {
const value: TACAN = {
isOn: this.extractBool(),
channel: this.extractUInt8(),
XY: this.extractChar(),
callsign: this.extractString(4)
}
return value;
}
#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];
}
#getCoalition(coalitionID: number) {
switch (coalitionID){
case 0: return "neutral";
case 1: return "red";
case 2: return "blue";
extractRadio() {
const value: Radio = {
frequency: this.extractUInt32(),
callsign: this.extractUInt8(),
callsignNumber: this.extractUInt8()
}
return "";
return value;
}
extractGeneralSettings() {
const value: GeneralSettings = {
prohibitJettison: this.extractBool(),
prohibitAA: this.extractBool(),
prohibitAG: this.extractBool(),
prohibitAfterburner: this.extractBool(),
prohibitAirWpn: this.extractBool(),
}
return value;
}
extractAmmo() {
const value: Ammo[] = [];
const size = this.extractUInt16();
for (let idx = 0; idx < size; idx++) {
value.push({
quantity: this.extractUInt16(),
name: this.extractString(32),
guidance: this.extractUInt8(),
category: this.extractUInt8(),
missileCategory: this.extractUInt8()
});
}
return value;
}
extractContacts(){
const value: Contact[] = [];
const size = this.extractUInt16();
for (let idx = 0; idx < size; idx++) {
value.push({
ID: this.extractUInt32(),
detectionMethod: this.extractUInt8()
});
}
return value;
}
extractActivePath() {
const value: LatLng[] = [];
const size = this.extractUInt16();
for (let idx = 0; idx < size; idx++) {
value.push(this.extractLatLng());
}
return value;
}
extractOffset() {
const value: Offset = {
x: this.extractFloat64(),
y: this.extractFloat64(),
z: this.extractFloat64(),
}
return value;
}
}

View File

@ -1,13 +1,14 @@
import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map } from 'leaflet';
import { getMap, getUnitsManager } from '..';
import { getMarkerCategoryByName, getUnitDatabaseByCategory, mToFt, msToKnots, rad2deg } from '../other/utils';
import { enumToCoalition, enumToEmissioNCountermeasure, getMarkerCategoryByName, enumToROE, enumToReactionToThreat, enumToState, getUnitDatabaseByCategory, mToFt, msToKnots, rad2deg } from '../other/utils';
import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, getUnits, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions, followUnit, setEmissionsCountermeasures, setSpeedType, setAltitudeType, setOnOff, setFollowRoads, bombPoint, carpetBomb, bombBuilding, fireAtArea } from '../server/server';
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, ROEs, emissionsCountermeasures, reactionsToThreat, states } from '../constants/constants';
import { GeneralSettings, Radio, TACAN, UnitData, UnitIconOptions } from '../@types/unit';
import { BOMBING, CARPET_BOMBING, DataIndexes, FIRE_AT_AREA, IDLE, MOVE_UNIT, ROEs, emissionsCountermeasures, reactionsToThreat, states } from '../constants/constants';
import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN, UnitIconOptions } from '../@types/unit';
import { DataExtractor } from './dataextractor';
var pathIcon = new Icon({
iconUrl: '/resources/theme/images/markers/marker-icon.png',
@ -18,77 +19,74 @@ var pathIcon = new Icon({
export class Unit extends CustomMarker {
ID: number;
#data: UnitData = {
ID: 0,
alive: false,
human: false,
controlled: false,
hasTask: false,
desiredAltitudeType: "AGL",
desiredSpeedType: "GS",
isTanker: false,
isAWACS: false,
onOff: false,
followRoads: false,
EPLRS: false,
generalSettings: {
prohibitAA: false,
prohibitAfterburner: false,
prohibitAG: false,
prohibitAirWpn: false,
prohibitJettison: false
},
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
},
radio: {
frequency: 0,
callsign: 0,
callsignNumber: 0
},
activePath: [],
ammo: [],
contacts: [],
name: "",
unitName: "",
groupName: "",
category: "",
coalition: "",
task: ""
#alive: boolean = false;
#human: boolean = false;
#controlled: boolean = false;
#coalition: string = "";
#country: number = 0;
#name: string = "";
#unitName: string = "";
#groupName: string = "";
#state: string = states[0];
#task: string = ""
#hasTask: boolean = false;
#position: LatLng = new LatLng(0, 0);
#speed: number = 0;
#heading: number = 0;
#isTanker: boolean = false;
#isAWACS: boolean = false;
#onOff: boolean = false;
#followRoads: boolean = false;
#fuel: number = 0;
#desiredSpeed: number = 0;
#desiredSpeedType: string = "GS";
#desiredAltitude: number = 0;
#desiredAltitudeType: string = "AGL";
#leaderID: number = 0;
#formationOffset: Offset = {
x: 0,
y: 0,
z: 0
};
#targetID: number = 0;
#targetPosition: LatLng = new LatLng(0, 0);
#ROE: string = ROEs[0];
#reactionToThreat: string = reactionsToThreat[0];
#emissionsCountermeasures: string = emissionsCountermeasures[0];
#TACAN: TACAN = {
isOn: false,
XY: 'X',
callsign: '',
channel: 0
};
#radio: Radio = {
frequency: 0,
callsign: 0,
callsignNumber: 0
};
#generalSettings: GeneralSettings = {
prohibitAA: false,
prohibitAfterburner: false,
prohibitAG: false,
prohibitAirWpn: false,
prohibitJettison: false
};
#ammo: Ammo[] = [];
#contacts: Contact[] = [];
#activePath: LatLng[] = [];
#selectable: boolean;
#selected: boolean = false;
#hidden: boolean = false;
#highlighted: boolean = false;
#preventClick: boolean = false;
#pathMarkers: Marker[] = [];
#pathPolyline: Polyline;
#contactsPolylines: Polyline[];
#miniMapMarker: CircleMarker | null = null;
#targetPositionMarker: TargetMarker;
#targetPositionPolyline: Polyline;
#timer: number = 0;
#hotgroup: number | null = null;
static getConstructor(type: string) {
@ -100,7 +98,7 @@ export class Unit extends CustomMarker {
if (type === "NavyUnit") return NavyUnit;
}
constructor(ID: number, data: UnitData) {
constructor(ID: number) {
super(new LatLng(0, 0), { riseOnHover: true, keyboard: false });
this.ID = ID;
@ -126,9 +124,122 @@ export class Unit extends CustomMarker {
document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => {
window.setTimeout(() => { this.setSelected(this.getSelected() && !this.getHidden()) }, 300);
});
}
/* Set the unit data */
this.setData(data);
getCategory() {
// Overloaded by child classes
return "";
}
/********************** Unit data *************************/
setData(dataExtractor: DataExtractor) {
var updateMarker = !getMap().hasLayer(this);
var datumIndex = dataExtractor.extractUInt8();
if (datumIndex == DataIndexes.startOfData) {
while (datumIndex != DataIndexes.endOfData) {
datumIndex = dataExtractor.extractUInt8();
switch (datumIndex) {
case DataIndexes.alive: this.setAlive(dataExtractor.extractBool()); updateMarker = true; break;
case DataIndexes.human: this.#human = dataExtractor.extractBool(); break;
case DataIndexes.controlled: this.#controlled = dataExtractor.extractBool(); updateMarker = true; break;
case DataIndexes.coalition: this.#coalition = enumToCoalition(dataExtractor.extractUInt8()); break;
case DataIndexes.country: this.#country = dataExtractor.extractUInt8(); break;
case DataIndexes.name: this.#name = dataExtractor.extractString(); break;
case DataIndexes.unitName: this.#unitName = dataExtractor.extractString(); break;
case DataIndexes.groupName: this.#groupName = dataExtractor.extractString(); break;
case DataIndexes.state: this.#state = enumToState(dataExtractor.extractUInt8()); updateMarker = true; break;
case DataIndexes.task: this.#task = dataExtractor.extractString(); break;
case DataIndexes.hasTask: this.#hasTask = dataExtractor.extractBool(); break;
case DataIndexes.position: this.#position = dataExtractor.extractLatLng(); updateMarker = true; break;
case DataIndexes.speed: this.#speed = dataExtractor.extractFloat64(); updateMarker = true; break;
case DataIndexes.heading: this.#heading = dataExtractor.extractFloat64(); updateMarker = true; break;
case DataIndexes.isTanker: this.#isTanker = dataExtractor.extractBool(); break;
case DataIndexes.isAWACS: this.#isAWACS = dataExtractor.extractBool(); break;
case DataIndexes.onOff: this.#onOff = dataExtractor.extractBool(); break;
case DataIndexes.followRoads: this.#followRoads = dataExtractor.extractBool(); break;
case DataIndexes.fuel: this.#fuel = dataExtractor.extractUInt16(); break;
case DataIndexes.desiredSpeed: this.#desiredSpeed = dataExtractor.extractFloat64(); break;
case DataIndexes.desiredSpeedType: this.#desiredSpeedType = dataExtractor.extractBool() ? "GS" : "CAS"; break;
case DataIndexes.desiredAltitude: this.#desiredAltitude = dataExtractor.extractFloat64(); break;
case DataIndexes.desiredAltitudeType: this.#desiredAltitudeType = dataExtractor.extractFloat64() ? "AGL" : "ASL"; break;
case DataIndexes.leaderID: this.#leaderID = dataExtractor.extractUInt32(); break;
case DataIndexes.formationOffset: dataExtractor.extractOffset(); break;
case DataIndexes.targetID: this.#targetID = dataExtractor.extractUInt32(); break;
case DataIndexes.targetPosition: this.#targetPosition = dataExtractor.extractLatLng(); break;
case DataIndexes.ROE: this.#ROE = enumToROE(dataExtractor.extractUInt8()); break;
case DataIndexes.reactionToThreat: this.#reactionToThreat = enumToReactionToThreat(dataExtractor.extractUInt8()); break;
case DataIndexes.emissionsCountermeasures: this.#emissionsCountermeasures = enumToEmissioNCountermeasure(dataExtractor.extractUInt8()); break;
case DataIndexes.TACAN: this.#TACAN = dataExtractor.extractTACAN(); break;
case DataIndexes.radio: this.#radio = dataExtractor.extractRadio(); break;
case DataIndexes.generalSettings: this.#generalSettings = dataExtractor.extractGeneralSettings(); break;
case DataIndexes.ammo: this.#ammo = dataExtractor.extractAmmo(); break;
case DataIndexes.contacts: this.#contacts = dataExtractor.extractContacts(); break;
case DataIndexes.activePath: this.#activePath = dataExtractor.extractActivePath(); break;
}
}
}
/* Dead units can't be selected */
this.setSelected(this.getSelected() && this.#alive && !this.getHidden())
if (updateMarker)
this.#updateMarker();
// TODO dont delete the polylines of the detected units
this.#clearDetectedUnits();
if (this.getSelected()) {
this.#drawPath();
this.#drawDetectedUnits();
this.#drawTarget();
}
else {
this.#clearPath();
this.#clearTarget();
}
document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this }));
}
getData() {
return {
alive: this.#alive,
human: this.#human,
controlled: this.#controlled,
coalition: this.#coalition,
country: this.#country,
name: this.#name,
unitName: this.#unitName,
groupName: this.#groupName,
state: this.#state,
task: this.#task,
hasTask: this.#hasTask,
position: this.#position,
speed: this.#speed,
heading: this.#heading,
isTanker: this.#isTanker,
isAWACS: this.#isAWACS,
onOff: this.#onOff,
followRoads: this.#followRoads,
fuel: this.#fuel,
desiredSpeed: this.#desiredSpeed,
desiredSpeedType: this.#desiredSpeedType,
desiredAltitude: this.#desiredAltitude,
desiredAltitudeType: this.#desiredAltitudeType,
leaderID: this.#leaderID,
formationOffset: this.#formationOffset,
targetID: this.#targetID,
targetPosition: this.#targetPosition,
ROE: this.#ROE,
reactionToThreat: this.#reactionToThreat,
emissionsCountermeasures: this.#emissionsCountermeasures,
TACAN: this.#TACAN,
radio: this.#radio,
generalSettings: this.#generalSettings,
ammo: this.#ammo,
contacts: this.#contacts,
activePath: this.#activePath
}
}
getMarkerCategory() {
@ -151,14 +262,20 @@ export class Unit extends CustomMarker {
showShortLabel: false,
showFuel: false,
showAmmo: false,
showSummary: false,
showSummary: false,
rotateToHeading: false
}
}
setAlive(newAlive: boolean) {
if (newAlive != this.#alive)
document.dispatchEvent(new CustomEvent("unitDeath", { detail: this }));
this.#alive = newAlive;
}
setSelected(selected: boolean) {
/* Only alive units can be selected. Some units are not selectable (weapons) */
if ((this.getData().alive || !selected) && this.getSelectable() && this.getSelected() != selected) {
if ((this.#alive || !selected) && this.getSelectable() && this.getSelected() != selected) {
this.#selected = selected;
this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-selected", selected);
if (selected) {
@ -208,49 +325,7 @@ export class Unit extends CustomMarker {
}
getGroupMembers() {
return Object.values(getUnitsManager().getUnits()).filter((unit: Unit) => {return unit != this && unit.getData().groupName === this.getData().groupName;});
}
/********************** Unit data *************************/
setData(data: UnitData) {
/* Check if data has changed comparing new values to old values */
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);
/* Assign the data */
this.#data = data;
/* Fire an event when a unit dies */
if (aliveChanged && this.getData().alive == false)
document.dispatchEvent(new CustomEvent("unitDeath", { detail: this }));
/* Dead units can't be selected */
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();
this.#drawDetectedUnits();
this.#drawTarget();
}
else {
this.#clearPath();
this.#clearTarget();
}
document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this }));
}
getData() {
return this.#data;
return Object.values(getUnitsManager().getUnits()).filter((unit: Unit) => { return unit != this && unit.#groupName === this.#groupName; });
}
/********************** Icon *************************/
@ -266,7 +341,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.getData().coalition);
el.setAttribute("data-coalition", this.#coalition);
// Generate and append elements depending on active options
// Velocity vector
@ -300,7 +375,7 @@ export class Unit extends CustomMarker {
}
// State icon
if (this.getIconOptions().showState){
if (this.getIconOptions().showState) {
var state = document.createElement("div");
state.classList.add("unit-state");
el.appendChild(state);
@ -310,7 +385,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.getData().name)?.shortLabel || "";
shortLabel.innerText = getUnitDatabaseByCategory(this.getMarkerCategory())?.getByName(this.#name)?.shortLabel || "";
el.append(shortLabel);
}
@ -325,7 +400,7 @@ export class Unit extends CustomMarker {
}
// Ammo indicator
if (this.getIconOptions().showAmmo){
if (this.getIconOptions().showAmmo) {
var ammoIndicator = document.createElement("div");
ammoIndicator.classList.add("unit-ammo");
for (let i = 0; i <= 3; i++)
@ -339,7 +414,7 @@ export class Unit extends CustomMarker {
summary.classList.add("unit-summary");
var callsign = document.createElement("div");
callsign.classList.add("unit-callsign");
callsign.innerText = this.getData().unitName;
callsign.innerText = this.#unitName;
var altitude = document.createElement("div");
altitude.classList.add("unit-altitude");
var speed = document.createElement("div");
@ -357,15 +432,15 @@ export class Unit extends CustomMarker {
updateVisibility() {
var hidden = false;
const hiddenUnits = getUnitsManager().getHiddenTypes();
if (this.getData().human && hiddenUnits.includes("human"))
if (this.#human && hiddenUnits.includes("human"))
hidden = true;
else if (this.getData().controlled == false && hiddenUnits.includes("dcs"))
else if (this.#controlled == false && hiddenUnits.includes("dcs"))
hidden = true;
else if (hiddenUnits.includes(this.getMarkerCategory()))
hidden = true;
else if (hiddenUnits.includes(this.getData().coalition))
else if (hiddenUnits.includes(this.#coalition))
hidden = true;
this.setHidden(hidden || !this.getData().alive);
this.setHidden(hidden || !this.#alive);
}
setHidden(hidden: boolean) {
@ -387,24 +462,24 @@ export class Unit extends CustomMarker {
}
getLeader() {
return getUnitsManager().getUnitByID(this.getData().leaderID);
return getUnitsManager().getUnitByID(this.#leaderID);
}
canFulfillRole(roles: string | string[]) {
if (typeof(roles) === "string")
if (typeof (roles) === "string")
roles = [roles];
return this.getDatabase()?.getByName(this.getData().name)?.loadouts.some((loadout: LoadoutBlueprint) => {
return (roles as string[]).some((role: string) => {return loadout.roles.includes(role)});
return this.getDatabase()?.getByName(this.#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.getData().human) {
if (!this.#human) {
var path: any = {};
if (this.getData().activePath.length > 0) {
path = this.getData().activePath;
if (this.#activePath.length > 0) {
path = this.#activePath;
path[(Object.keys(path).length).toString()] = latlng;
}
else {
@ -415,86 +490,86 @@ export class Unit extends CustomMarker {
}
clearDestinations() {
if (!this.getData().human)
this.getData().activePath = [];
if (!this.#human)
this.#activePath = [];
}
attackUnit(targetID: number) {
/* Units can't attack themselves */
if (!this.getData().human)
if (!this.#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.getData().human)
if (!this.#human)
if (this.ID != targetID)
followUnit(this.ID, targetID, offset);
}
landAt(latlng: LatLng) {
if (!this.getData().human)
if (!this.#human)
landAt(this.ID, latlng);
}
changeSpeed(speedChange: string) {
if (!this.getData().human)
if (!this.#human)
changeSpeed(this.ID, speedChange);
}
changeAltitude(altitudeChange: string) {
if (!this.getData().human)
if (!this.#human)
changeAltitude(this.ID, altitudeChange);
}
setSpeed(speed: number) {
if (!this.getData().human)
if (!this.#human)
setSpeed(this.ID, speed);
}
setSpeedType(speedType: string) {
if (!this.getData().human)
if (!this.#human)
setSpeedType(this.ID, speedType);
}
setAltitude(altitude: number) {
if (!this.getData().human)
if (!this.#human)
setAltitude(this.ID, altitude);
}
setAltitudeType(altitudeType: string) {
if (!this.getData().human)
if (!this.#human)
setAltitudeType(this.ID, altitudeType);
}
setROE(ROE: string) {
if (!this.getData().human)
if (!this.#human)
setROE(this.ID, ROE);
}
setReactionToThreat(reactionToThreat: string) {
if (!this.getData().human)
if (!this.#human)
setReactionToThreat(this.ID, reactionToThreat);
}
setEmissionsCountermeasures(emissionCountermeasure: string) {
if (!this.getData().human)
if (!this.#human)
setEmissionsCountermeasures(this.ID, emissionCountermeasure);
}
setLeader(isLeader: boolean, wingmenIDs: number[] = []) {
if (!this.getData().human)
if (!this.#human)
setLeader(this.ID, isLeader, wingmenIDs);
}
setOnOff(onOff: boolean) {
if (!this.getData().human)
if (!this.#human)
setOnOff(this.ID, onOff);
}
setFollowRoads(followRoads: boolean) {
if (!this.getData().human)
if (!this.#human)
setFollowRoads(this.ID, followRoads);
}
@ -503,12 +578,12 @@ export class Unit extends CustomMarker {
}
refuel() {
if (!this.getData().human)
if (!this.#human)
refuel(this.ID);
}
setAdvancedOptions(isTanker: boolean, isAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings) {
if (!this.getData().human)
if (!this.#human)
setAdvacedOptions(this.ID, isTanker, isAWACS, TACAN, radio, generalSettings);
}
@ -533,7 +608,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.getData().position.lat, this.getData().position.lng));
getMap().removeTemporaryMarker(new LatLng(this.#position.lat, this.#position.lng));
return this;
}
@ -541,7 +616,7 @@ export class Unit extends CustomMarker {
#onClick(e: any) {
if (!this.#preventClick) {
if (getMap().getState() === IDLE || getMap().getState() === MOVE_UNIT || e.originalEvent.ctrlKey) {
if (!e.originalEvent.ctrlKey)
if (!e.originalEvent.ctrlKey)
getUnitsManager().deselectAllUnits();
this.setSelected(!this.getSelected());
}
@ -558,34 +633,33 @@ export class Unit extends CustomMarker {
}
#onContextMenu(e: any) {
var options: {[key: string]: {text: string, tooltip: string}} = {};
var options: { [key: string]: { text: string, tooltip: string } } = {};
const selectedUnits = getUnitsManager().getSelectedUnits();
const selectedUnitTypes = getUnitsManager().getSelectedUnitsTypes();
options["center-map"] = {text: "Center map", tooltip: "Center the map on the unit and follow it"};
options["center-map"] = { text: "Center map", tooltip: "Center the map on the unit and follow it" };
if (selectedUnits.length > 0 && !(selectedUnits.length == 1 && (selectedUnits.includes(this)))) {
options["attack"] = {text: "Attack", tooltip: "Attack the unit using A/A or A/G weapons"};
options["attack"] = { text: "Attack", tooltip: "Attack the unit using A/A or A/G weapons" };
if (getUnitsManager().getSelectedUnitsTypes().length == 1 && getUnitsManager().getSelectedUnitsTypes()[0] === "Aircraft")
options["follow"] = {text: "Follow", tooltip: "Follow the unit at a user defined distance and position"};;
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.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 (this.getCategory() == "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.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"};
options["carpet-bomb"] = {text: "Carpet bombing", tooltip: "Carpet bombing close to a point"};
if ((selectedUnits.length === 0 && this.getCategory() == "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" };
options["carpet-bomb"] = { text: "Carpet bombing", tooltip: "Carpet bombing close to a point" };
}
}
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"};
if ((selectedUnits.length === 0 && this.getCategory() == "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" };
}
if (Object.keys(options).length > 0) {
@ -615,17 +689,17 @@ export class Unit extends CustomMarker {
}
#showFollowOptions(e: any) {
var options: {[key: string]: {text: string, tooltip: string}} = {};
var options: { [key: string]: { text: string, tooltip: string } } = {};
options = {
'trail': {text: "Trail", tooltip: "Follow unit in trail formation"},
'echelon-lh': {text: "Echelon (LH)", tooltip: "Follow unit in echelon left formation"},
'echelon-rh': {text: "Echelon (RH)", tooltip: "Follow unit in echelon right formation"},
'line-abreast-lh': {text: "Line abreast (LH)", tooltip: "Follow unit in line abreast left formation"},
'line-abreast-rh': {text: "Line abreast (RH)", tooltip: "Follow unit in line abreast right formation"},
'front': {text: "Front", tooltip: "Fly in front of unit"},
'diamond': {text: "Diamond", tooltip: "Follow unit in diamond formation"},
'custom': {text: "Custom", tooltip: "Set a custom formation position"},
'trail': { text: "Trail", tooltip: "Follow unit in trail formation" },
'echelon-lh': { text: "Echelon (LH)", tooltip: "Follow unit in echelon left formation" },
'echelon-rh': { text: "Echelon (RH)", tooltip: "Follow unit in echelon right formation" },
'line-abreast-lh': { text: "Line abreast (LH)", tooltip: "Follow unit in line abreast left formation" },
'line-abreast-rh': { text: "Line abreast (RH)", tooltip: "Follow unit in line abreast right formation" },
'front': { text: "Front", tooltip: "Fly in front of unit" },
'diamond': { text: "Diamond", tooltip: "Follow unit in diamond formation" },
'custom': { text: "Custom", tooltip: "Set a custom formation position" },
}
getMap().getUnitContextMenu().setOptions(options, (option: string) => {
@ -651,12 +725,12 @@ export class Unit extends CustomMarker {
this.updateVisibility();
/* Draw the minimap marker */
if (this.getData().alive) {
if (this.#alive) {
if (this.#miniMapMarker == null) {
this.#miniMapMarker = new CircleMarker(new LatLng(this.getData().position.lat, this.getData().position.lng), { radius: 0.5 });
if (this.getData().coalition == "neutral")
this.#miniMapMarker = new CircleMarker(new LatLng(this.#position.lat, this.#position.lng), { radius: 0.5 });
if (this.#coalition == "neutral")
this.#miniMapMarker.setStyle({ color: "#CFD9E8" });
else if (this.getData().coalition == "red")
else if (this.#coalition == "red")
this.#miniMapMarker.setStyle({ color: "#ff5858" });
else
this.#miniMapMarker.setStyle({ color: "#247be2" });
@ -664,7 +738,7 @@ export class Unit extends CustomMarker {
this.#miniMapMarker.bringToBack();
}
else {
this.#miniMapMarker.setLatLng(new LatLng(this.getData().position.lat, this.getData().position.lng));
this.#miniMapMarker.setLatLng(new LatLng(this.#position.lat, this.#position.lng));
this.#miniMapMarker.bringToBack();
}
}
@ -677,39 +751,39 @@ export class Unit extends CustomMarker {
/* Draw the marker */
if (!this.getHidden()) {
this.setLatLng(new LatLng(this.getData().position.lat, this.getData().position.lng));
this.setLatLng(new LatLng(this.#position.lat, this.#position.lng));
var element = this.getElement();
if (element != null) {
/* Draw the velocity vector */
element.querySelector(".unit-vvi")?.setAttribute("style", `height: ${15 + this.getData().speed / 5}px;`);
element.querySelector(".unit-vvi")?.setAttribute("style", `height: ${15 + this.#speed / 5}px;`);
/* Set fuel data */
element.querySelector(".unit-fuel-level")?.setAttribute("style", `width: ${this.getData().fuel}%`);
element.querySelector(".unit")?.toggleAttribute("data-has-low-fuel", this.getData().fuel < 20);
element.querySelector(".unit-fuel-level")?.setAttribute("style", `width: ${this.#fuel}%`);
element.querySelector(".unit")?.toggleAttribute("data-has-low-fuel", this.#fuel < 20);
/* Set dead/alive flag */
element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.getData().alive);
element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.#alive);
/* Set current unit state */
if (this.getData().human) // Unit is human
if (this.#human) // Unit is human
element.querySelector(".unit")?.setAttribute("data-state", "human");
else if (!this.getData().controlled) // Unit is under DCS control (not Olympus)
else if (!this.#controlled) // Unit is under DCS control (not Olympus)
element.querySelector(".unit")?.setAttribute("data-state", "dcs");
else if ((this.getData().category == "Aircraft" || this.getData().category == "Helicopter") && !this.getData().hasTask)
else if ((this.getCategory() == "Aircraft" || this.getCategory() == "Helicopter") && !this.#hasTask)
element.querySelector(".unit")?.setAttribute("data-state", "no-task");
else // Unit is under Olympus control
element.querySelector(".unit")?.setAttribute("data-state", this.getData().state.toLowerCase());
element.querySelector(".unit")?.setAttribute("data-state", this.#state.toLowerCase());
/* Set altitude and speed */
if (element.querySelector(".unit-altitude"))
(<HTMLElement>element.querySelector(".unit-altitude")).innerText = "FL" + String(Math.floor(mToFt(this.getData().position.alt as number) / 100));
(<HTMLElement>element.querySelector(".unit-altitude")).innerText = "FL" + String(Math.floor(mToFt(this.#position.alt as number) / 100));
if (element.querySelector(".unit-speed"))
(<HTMLElement>element.querySelector(".unit-speed")).innerText = String(Math.floor(msToKnots(this.getData().speed))) + "GS";
(<HTMLElement>element.querySelector(".unit-speed")).innerText = String(Math.floor(msToKnots(this.#speed))) + "GS";
/* Rotate elements according to heading */
element.querySelectorAll("[data-rotate-to-heading]").forEach(el => {
const headingDeg = rad2deg(this.getData().heading);
const headingDeg = rad2deg(this.#heading);
let currentStyle = el.getAttribute("style") || "";
el.setAttribute("style", currentStyle + `transform:rotate(${headingDeg}deg);`);
});
@ -724,7 +798,7 @@ export class Unit extends CustomMarker {
var newHasFox2 = false;
var newHasFox3 = false;
var newHasOtherAmmo = false;
Object.values(this.getData().ammo).forEach((ammo: any) => {
Object.values(this.#ammo).forEach((ammo: any) => {
if (ammo.desc.category == 1 && ammo.desc.missileCategory == 1) {
if (ammo.desc.guidance == 4 || ammo.desc.guidance == 5)
newHasFox1 = true;
@ -754,30 +828,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.getData().position.alt as number) - pos.y + (this.#highlighted || this.#selected ? 5000 : 0));
this.setZIndexOffset(1000 + Math.floor(this.#position.alt as number) - pos.y + (this.#highlighted || this.#selected ? 5000 : 0));
}
}
#drawPath() {
if (this.getData().activePath != undefined) {
if (this.#activePath != undefined) {
var points = [];
points.push(new LatLng(this.getData().position.lat, this.getData().position.lng));
points.push(new LatLng(this.#position.lat, this.#position.lng));
/* Add markers if missing */
while (this.#pathMarkers.length < Object.keys(this.getData().activePath).length) {
while (this.#pathMarkers.length < Object.keys(this.#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.getData().activePath).length) {
while (this.#pathMarkers.length > Object.keys(this.#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.getData().activePath) {
var destination = this.getData().activePath[WP];
for (let WP in this.#activePath) {
var destination = this.#activePath[WP];
this.#pathMarkers[parseInt(WP)].setLatLng([destination.lat, destination.lng]);
points.push(new LatLng(destination.lat, destination.lng));
this.#pathPolyline.setLatLngs(points);
@ -797,12 +871,12 @@ export class Unit extends CustomMarker {
}
#drawDetectedUnits() {
for (let index in this.getData().contacts) {
var targetData = this.getData().contacts[index];
for (let index in this.#contacts) {
var targetData = this.#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 startLatLng = new LatLng(this.#position.lat, this.#position.lng)
var endLatLng = new LatLng(target.#position.lat, target.#position.lng)
var color;
if (targetData.detectionMethod === 1)
@ -827,31 +901,31 @@ export class Unit extends CustomMarker {
}
#drawTarget() {
if (this.getData().targetPosition.lat != 0 && this.getData().targetPosition.lng != 0) {
this.#drawtargetPosition(this.getData().targetPosition);
if (this.#targetPosition.lat != 0 && this.#targetPosition.lng != 0) {
this.#drawtargetPosition(this.#targetPosition);
}
else if (this.getData().targetID != 0 && getUnitsManager().getUnitByID(this.getData().targetID)) {
const position = getUnitsManager().getUnitByID(this.getData().targetID)?.getData().position;
else if (this.#targetID != 0 && getUnitsManager().getUnitByID(this.#targetID)) {
const position = getUnitsManager().getUnitByID(this.#targetID)?.getData().position;
if (position)
this.#drawtargetPosition(position);
}
else
else
this.#clearTarget();
}
#drawtargetPosition(targetPosition: LatLng) {
if (!getMap().hasLayer(this.#targetPositionMarker))
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)])
}
this.#targetPositionPolyline.setLatLngs([new LatLng(this.#position.lat, this.#position.lng), new LatLng(targetPosition.lat, targetPosition.lng)])
}
#clearTarget() {
if (getMap().hasLayer(this.#targetPositionMarker))
this.#targetPositionMarker.removeFrom(getMap());
if (getMap().hasLayer(this.#targetPositionPolyline))
this.#targetPositionPolyline.removeFrom(getMap());
}
@ -874,8 +948,12 @@ export class AirUnit extends Unit {
}
export class Aircraft extends AirUnit {
constructor(ID: number, data: UnitData) {
super(ID, data);
constructor(ID: number) {
super(ID);
}
getCategory() {
return "Aircraft";
}
getMarkerCategory() {
@ -884,8 +962,12 @@ export class Aircraft extends AirUnit {
}
export class Helicopter extends AirUnit {
constructor(ID: number, data: UnitData) {
super(ID, data);
constructor(ID: number) {
super(ID);
}
getCategory() {
return "Helicopter";
}
getMarkerCategory() {
@ -894,8 +976,8 @@ export class Helicopter extends AirUnit {
}
export class GroundUnit extends Unit {
constructor(ID: number, data: UnitData) {
super(ID, data);
constructor(ID: number) {
super(ID);
}
getIconOptions() {
@ -912,14 +994,18 @@ export class GroundUnit extends Unit {
};
}
getCategory() {
return "GroundUnit";
}
getMarkerCategory() {
return getMarkerCategoryByName(this.getData().name);
}
}
export class NavyUnit extends Unit {
constructor(ID: number, data: UnitData) {
super(ID, data);
constructor(ID: number) {
super(ID);
}
getIconOptions() {
@ -936,14 +1022,18 @@ export class NavyUnit extends Unit {
};
}
getCategory() {
return "NavyUnit";
}
getMarkerCategory() {
return "navyunit";
}
}
export class Weapon extends Unit {
constructor(ID: number, data: UnitData) {
super(ID, data);
constructor(ID: number) {
super(ID);
this.setSelectable(false);
}
@ -963,8 +1053,12 @@ export class Weapon extends Unit {
}
export class Missile extends Weapon {
constructor(ID: number, data: UnitData) {
super(ID, data);
constructor(ID: number) {
super(ID);
}
getCategory() {
return "Missile";
}
getMarkerCategory() {
@ -973,8 +1067,12 @@ export class Missile extends Weapon {
}
export class Bomb extends Weapon {
constructor(ID: number, data: UnitData) {
super(ID, data);
constructor(ID: number) {
super(ID);
}
getCategory() {
return "Bomb";
}
getMarkerCategory() {

View File

@ -6,9 +6,8 @@ import { deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng,
import { CoalitionArea } from "../map/coalitionarea";
import { Airbase } from "../missionhandler/airbase";
import { groundUnitsDatabase } from "./groundunitsdatabase";
import { IADSRoles, IDLE, MOVE_UNIT } from "../constants/constants";
import { DataIndexes, IADSRoles, IDLE, MOVE_UNIT } from "../constants/constants";
import { DataExtractor } from "./dataextractor";
import { UnitData } from "../@types/unit";
export class UnitsManager {
#units: { [ID: number]: Unit };
@ -33,8 +32,7 @@ export class UnitsManager {
getSelectableAircraft() {
const units = this.getUnits();
return Object.keys(units).reduce((acc: { [key: number]: Unit }, unitId: any) => {
const baseData = units[unitId].getData();
if (baseData.category === "Aircraft" && baseData.alive === true) {
if (units[unitId].getCategory() === "Aircraft" && units[unitId].getData().alive === true) {
acc[unitId] = units[unitId];
}
return acc;
@ -56,12 +54,12 @@ export class UnitsManager {
return Object.values(this.#units).filter((unit: Unit) => { return unit.getData().alive && unit.getHotgroup() == hotgroup });
}
addUnit(ID: number, data: UnitData) {
if (data.category){
addUnit(ID: number, category: string) {
if (category){
/* The name of the unit category is exactly the same as the constructor name */
var constructor = Unit.getConstructor(data.category);
var constructor = Unit.getConstructor(category);
if (constructor != undefined) {
this.#units[ID] = new constructor(ID, data);
this.#units[ID] = new constructor(ID);
}
}
}
@ -71,42 +69,24 @@ export class UnitsManager {
}
update(buffer: ArrayBuffer) {
var updatedUnits: Unit[] = [];
var dataExtractor = new DataExtractor(buffer);
var data: {[key: string]: UnitData} = {};
var updateTime = Number(dataExtractor.extractUInt64());
var offset = dataExtractor.getOffset();
while (offset < buffer.byteLength) {
const result = dataExtractor.extractData(offset);
data[result.data.ID] = result.data;
offset = result.offset;
while (dataExtractor.getSeekPosition() < buffer.byteLength) {
const ID = dataExtractor.extractUInt32();
if (!(ID in this.#units)) {
const datumIndex = dataExtractor.extractUInt8();
if (datumIndex == DataIndexes.category) {
const category = dataExtractor.extractString();
this.addUnit(ID, category);
}
else {
// TODO request a refresh since we must have missed some packets
}
}
this.#units[ID]?.setData(dataExtractor);
}
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[ID]);
this.#units[parseInt(ID)]?.setData(data[ID]);
}, timeout);
return timeout + 10;
}, 10);
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[ID]);
});
// TODO why did we do this?
//this.getSelectedUnits().forEach((unit: Unit) => {
// if (!updatedUnits.includes(unit))
// unit.setData(null);
//});
setLastUpdateTime(updateTime);
}

View File

@ -6,8 +6,6 @@ class Aircraft : public AirUnit
public:
Aircraft(json::value json, unsigned int ID);
virtual string getCategory() { return "Aircraft"; };
virtual void changeSpeed(string change);
virtual void changeAltitude(string change);
};

View File

@ -14,7 +14,6 @@ public:
virtual void setState(unsigned char newState);
virtual string getCategory() = 0;
virtual void changeSpeed(string change) = 0;
virtual void changeAltitude(string change) = 0;

View File

@ -7,7 +7,6 @@ class GroundUnit : public Unit
{
public:
GroundUnit(json::value json, unsigned int ID);
virtual string getCategory() { return "GroundUnit"; };
virtual void setState(unsigned char newState);

View File

@ -6,8 +6,6 @@ class Helicopter : public AirUnit
public:
Helicopter(json::value json, unsigned int ID);
virtual string getCategory() { return "Helicopter"; };
virtual void changeSpeed(string change);
virtual void changeAltitude(string change);
};

View File

@ -7,7 +7,6 @@ public:
NavyUnit(json::value json, unsigned int ID);
virtual void AIloop();
virtual string getCategory() { return "NavyUnit"; };
virtual void changeSpeed(string change);
};

View File

@ -12,6 +12,50 @@ using namespace std::chrono;
#define TASK_CHECK_INIT_VALUE 10
namespace DataIndex {
enum DataIndexes {
startOfData = 0,
category,
alive,
human,
controlled,
coalition,
country,
name,
unitName,
groupName,
state,
task,
hasTask,
position,
speed,
heading,
isTanker,
isAWACS,
onOff,
followRoads,
fuel,
desiredSpeed,
desiredSpeedType,
desiredAltitude,
desiredAltitudeType,
leaderID,
formationOffset,
targetID,
targetPosition,
ROE,
reactionToThreat,
emissionsCountermeasures,
TACAN,
radio,
generalSettings,
ammo,
contacts,
activePath,
endOfData = 255
};
}
namespace State
{
enum States
@ -33,7 +77,7 @@ namespace State
};
#pragma pack(push, 1)
namespace Options {
namespace DataTypes {
struct TACAN
{
bool isOn = false;
@ -57,9 +101,7 @@ namespace Options {
bool prohibitAfterburner = false;
bool prohibitAirWpn = false;
};
}
namespace DataTypes {
struct Ammo {
unsigned short quantity = 0;
char name[32];
@ -72,31 +114,6 @@ namespace DataTypes {
unsigned int ID = 0;
unsigned char detectionMethod = 0;
};
struct DataPacket {
unsigned int ID;
unsigned int bitmask;
Coords position;
double speed;
double heading;
unsigned short fuel;
double desiredSpeed;
double desiredAltitude;
unsigned int leaderID;
unsigned int targetID;
Coords targetPosition;
unsigned char coalition;
unsigned char state;
unsigned char ROE;
unsigned char reactionToThreat;
unsigned char emissionsCountermeasures;
Options::TACAN TACAN;
Options::Radio Radio;
unsigned short pathLength;
unsigned short ammoLength;
unsigned short contactsLength;
unsigned char taskLength;
};
}
#pragma pack(pop)
@ -106,209 +123,213 @@ public:
Unit(json::value json, unsigned int ID);
~Unit();
/********** Public methods **********/
/********** Methods **********/
void initialize(json::value json);
void setDefaults(bool force = false);
unsigned int getID() { return ID; }
void runAILoop();
void updateExportData(json::value json, double dt = 0);
void updateMissionData(json::value json);
unsigned int getDataPacket(char* &data);
void getData(stringstream &ss, unsigned long long time, bool refresh);
virtual string getCategory() { return "No category"; };
/********** Base data **********/
void setControlled(bool newValue) { updateValue(controlled, newValue); }
void setName(string newValue) { updateValue(name, newValue); }
void setUnitName(string newValue) { updateValue(unitName, newValue); }
void setGroupName(string newValue) { updateValue(groupName, newValue); }
void setAlive(bool newValue) { updateValue(alive, newValue); }
void setCountry(unsigned int newValue) { updateValue(country, newValue); }
void setHuman(bool newValue) { updateValue(human, newValue); }
bool getControlled() { return controlled; }
string getName() { return name; }
string getUnitName() { return unitName; }
string getGroupName() { return groupName; }
bool getAlive() { return alive; }
unsigned int getCountry() { return country; }
bool getHuman() { return human; }
/********** Flight data **********/
void setPosition(Coords newValue) { updateValue(position, newValue); }
void setHeading(double newValue) { updateValue(heading, newValue); }
void setSpeed(double newValue) { updateValue(speed, newValue); }
Coords getPosition() { return position; }
double getHeading() { return heading; }
double getSpeed() { return speed; }
/********** Mission data **********/
void setFuel(unsigned short newValue) { updateValue(fuel, newValue); }
void setAmmo(vector<DataTypes::Ammo> newAmmo) { ammo = newAmmo; }
void setContacts(vector<DataTypes::Contact> newContacts) { contacts = newContacts; }
void setHasTask(bool newValue) { updateValue(hasTask, newValue); }
void setCoalition(unsigned char newValue) { updateValue(coalition, newValue);}
double getFuel() { return fuel; }
vector<DataTypes::Ammo> getAmmo() { return ammo; }
vector<DataTypes::Contact> getTargets() { return contacts; }
bool getHasTask() { return hasTask; }
unsigned int getCoalition() { return coalition; }
/********** Formation data **********/
void setLeaderID(unsigned int newValue) { updateValue(leaderID, newValue); }
void setFormationOffset(Offset formationOffset);
unsigned int getLeaderID() { return leaderID; }
Offset getFormationoffset() { return formationOffset; }
/********** Task data **********/
void setTask(string newValue) { updateValue(task, newValue); }
void setDesiredSpeed(double newValue);
void setDesiredAltitude(double newValue);
void setDesiredSpeedType(string newValue);
void setDesiredAltitudeType(string newValue);
void setActiveDestination(Coords newValue) { updateValue(activeDestination, newValue); }
void setActivePath(list<Coords> newValue);
void setTargetID(unsigned int newValue) { updateValue(targetID, newValue); }
void setTargetPosition(Coords newValue) { updateValue(targetPosition, newValue); }
void setIsTanker(bool newValue);
void setIsAWACS(bool newValue);
virtual void setOnOff(bool newValue) { updateValue(onOff, newValue); };
virtual void setFollowRoads(bool newValue) { updateValue(followRoads, newValue); };
string getTask() { return task; }
virtual double getDesiredSpeed() { return desiredSpeed; };
virtual double getDesiredAltitude() { return desiredAltitude; };
virtual bool getDesiredSpeedType() { return desiredSpeedType; };
virtual bool getDesiredAltitudeType() { return desiredAltitudeType; };
unsigned int getDataPacket(char*& data);
unsigned int getID() { return ID; }
void getData(stringstream& ss, unsigned long long time, bool refresh);
Coords getActiveDestination() { return activeDestination; }
list<Coords> getActivePath() { return activePath; }
unsigned int getTargetID() { return targetID; }
Coords getTargetPosition() { return targetPosition; }
bool getIsTanker() { return isTanker; }
bool getIsAWACS() { return isAWACS; }
bool getOnOff() { return onOff; };
bool getFollowRoads() { return followRoads; };
/********** Options data **********/
void setROE(unsigned char newValue, bool force = false);
void setReactionToThreat(unsigned char newValue, bool force = false);
void setEmissionsCountermeasures(unsigned char newValue, bool force = false);
void setTACAN(Options::TACAN newValue, bool force = false);
void setRadio(Options::Radio newValue, bool force = false);
void setGeneralSettings(Options::GeneralSettings newValue, bool force = false);
void setEPLRS(bool newValue, bool force = false);
unsigned char getROE() { return ROE; }
unsigned char getReactionToThreat() { return reactionToThreat; }
unsigned char getEmissionsCountermeasures() { return emissionsCountermeasures; };
Options::TACAN getTACAN() { return TACAN; }
Options::Radio getRadio() { return radio; }
Options::GeneralSettings getGeneralSettings() { return generalSettings; }
bool getEPLRS() { return EPLRS; }
/********** Control functions **********/
void landAt(Coords loc);
virtual void changeSpeed(string change) {};
virtual void changeAltitude(string change) {};
bool setActiveDestination();
void resetActiveDestination();
virtual void setState(unsigned char newState) { state = newState; };
void resetTask();
void landAt(Coords loc);
bool updateActivePath(bool looping);
void clearActivePath();
void pushActivePathFront(Coords newActivePathFront);
void pushActivePathBack(Coords newActivePathBack);
void popActivePathFront();
void triggerUpdate() { lastUpdateTime = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count(); }
unsigned long long getLastUpdateTime() { return lastUpdateTime; };
void goToDestination(string enrouteTask = "nil");
bool isDestinationReached(double threshold);
protected:
unsigned int ID;
map<string, Measure*> measures;
unsigned int taskCheckCounter = 0;
/********** Base data **********/
bool controlled = false;
string name = "undefined";
string unitName = "undefined";
string groupName = "undefined";
bool alive = true;
bool human = false;
unsigned int country = NULL;
/********** Flight data **********/
Coords position = Coords(NULL);
double speed = NULL;
double heading = NULL;
/********** Mission data **********/
unsigned short fuel = 0;
double initialFuel = 0; // Used internally to detect refueling completed
vector<DataTypes::Ammo> ammo;
vector<DataTypes::Contact> contacts;
bool hasTask = false;
unsigned char coalition;
/********** Formation data **********/
unsigned int leaderID = NULL;
Offset formationOffset = Offset(NULL);
/********** Task data **********/
string task = "";
double desiredSpeed = 0;
double desiredAltitude = 0;
bool desiredSpeedType = 1;
bool desiredAltitudeType = 1;
list<Coords> activePath;
Coords activeDestination = Coords(NULL);
unsigned int targetID = NULL;
Coords targetPosition = Coords(NULL);
bool isTanker = false;
bool isAWACS = false;
bool onOff = true;
bool followRoads = false;
/********** Options data **********/
unsigned char ROE = ROE::OPEN_FIRE_WEAPON_FREE;
unsigned char reactionToThreat = ReactionToThreat::EVADE_FIRE;
unsigned char emissionsCountermeasures = EmissionCountermeasure::DEFEND;
Options::TACAN TACAN;
Options::Radio radio;
Options::GeneralSettings generalSettings;
bool EPLRS = false;
/********** Data packet **********/
DataTypes::DataPacket dataPacket;
/********** State machine **********/
unsigned char state = State::NONE;
/********** Other **********/
unsigned long long lastUpdateTime = 0;
Coords oldPosition = Coords(0); // Used to approximate speed
/********** Functions **********/
string getTargetName();
string getLeaderName();
bool isTargetAlive();
bool isLeaderAlive();
virtual void AIloop() = 0;
bool isDestinationReached(double threshold);
bool setActiveDestination();
bool updateActivePath(bool looping);
void goToDestination(string enrouteTask = "nil");
void resetTask();
bool checkTaskFailed();
void resetTaskFailedCounter();
void triggerUpdate(unsigned char datumIndex);
/********** Setters **********/
virtual void setCategory(string newValue) { updateValue(category, newValue, DataIndex::category); }
virtual void setAlive(bool newValue) { updateValue(alive, newValue, DataIndex::alive); }
virtual void setHuman(bool newValue) { updateValue(human, newValue, DataIndex::human); }
virtual void setControlled(bool newValue) { updateValue(controlled, newValue, DataIndex::controlled); }
virtual void setCoalition(unsigned char newValue) { updateValue(coalition, newValue, DataIndex::coalition); }
virtual void setCountry(unsigned int newValue) { updateValue(country, newValue, DataIndex::country); }
virtual void setName(string newValue) { updateValue(name, newValue, DataIndex::name); }
virtual void setUnitName(string newValue) { updateValue(unitName, newValue, DataIndex::unitName); }
virtual void setGroupName(string newValue) { updateValue(groupName, newValue, DataIndex::groupName); }
virtual void setState(unsigned char newValue) { updateValue(state, newValue, DataIndex::state); };
virtual void setTask(string newValue) { updateValue(task, newValue, DataIndex::task); }
virtual void setHasTask(bool newValue) { updateValue(hasTask, newValue, DataIndex::hasTask); }
virtual void setPosition(Coords newValue) { updateValue(position, newValue, DataIndex::position); }
virtual void setSpeed(double newValue) { updateValue(speed, newValue, DataIndex::speed); }
virtual void setHeading(double newValue) { updateValue(heading, newValue, DataIndex::heading); }
virtual void setIsTanker(bool newValue);
virtual void setIsAWACS(bool newValue);
virtual void setOnOff(bool newValue) { updateValue(onOff, newValue, DataIndex::onOff); };
virtual void setFollowRoads(bool newValue) { updateValue(followRoads, newValue, DataIndex::followRoads); };
virtual void setFuel(unsigned short newValue) { updateValue(fuel, newValue, DataIndex::fuel); }
virtual void setDesiredSpeed(double newValue);
virtual void setDesiredSpeedType(string newValue);
virtual void setDesiredAltitude(double newValue);
virtual void setDesiredAltitudeType(string newValue);
virtual void setLeaderID(unsigned int newValue) { updateValue(leaderID, newValue, DataIndex::leaderID); }
virtual void setFormationOffset(Offset formationOffset);
virtual void setTargetID(unsigned int newValue) { updateValue(targetID, newValue, DataIndex::targetID); }
virtual void setTargetPosition(Coords newValue) { updateValue(targetPosition, newValue, DataIndex::targetPosition); }
virtual void setROE(unsigned char newValue, bool force = false);
virtual void setReactionToThreat(unsigned char newValue, bool force = false);
virtual void setEmissionsCountermeasures(unsigned char newValue, bool force = false);
virtual void setTACAN(DataTypes::TACAN newValue, bool force = false);
virtual void setRadio(DataTypes::Radio newValue, bool force = false);
virtual void setGeneralSettings(DataTypes::GeneralSettings newValue, bool force = false);
virtual void setAmmo(vector<DataTypes::Ammo> newAmmo) { ammo = newAmmo; }
virtual void setContacts(vector<DataTypes::Contact> newContacts) { contacts = newContacts; }
virtual void setActivePath(list<Coords> newValue);
/********** Getters **********/
virtual string getCategory() { return category; };
virtual bool getAlive() { return alive; }
virtual bool getHuman() { return human; }
virtual bool getControlled() { return controlled; }
virtual unsigned int getCoalition() { return coalition; }
virtual unsigned int getCountry() { return country; }
virtual string getName() { return name; }
virtual string getUnitName() { return unitName; }
virtual string getGroupName() { return groupName; }
virtual unsigned char getState() { return state; }
virtual string getTask() { return task; }
virtual bool getHasTask() { return hasTask; }
virtual Coords getPosition() { return position; }
virtual double getSpeed() { return speed; }
virtual double getHeading() { return heading; }
virtual bool getIsTanker() { return isTanker; }
virtual bool getIsAWACS() { return isAWACS; }
virtual bool getOnOff() { return onOff; };
virtual bool getFollowRoads() { return followRoads; };
virtual double getFuel() { return fuel; }
virtual double getDesiredSpeed() { return desiredSpeed; };
virtual bool getDesiredSpeedType() { return desiredSpeedType; };
virtual double getDesiredAltitude() { return desiredAltitude; };
virtual bool getDesiredAltitudeType() { return desiredAltitudeType; };
virtual unsigned int getLeaderID() { return leaderID; }
virtual Offset getFormationoffset() { return formationOffset; }
virtual unsigned int getTargetID() { return targetID; }
virtual Coords getTargetPosition() { return targetPosition; }
virtual unsigned char getROE() { return ROE; }
virtual unsigned char getReactionToThreat() { return reactionToThreat; }
virtual unsigned char getEmissionsCountermeasures() { return emissionsCountermeasures; };
virtual DataTypes::TACAN getTACAN() { return TACAN; }
virtual DataTypes::Radio getRadio() { return radio; }
virtual DataTypes::GeneralSettings getGeneralSettings() { return generalSettings; }
virtual vector<DataTypes::Ammo> getAmmo() { return ammo; }
virtual vector<DataTypes::Contact> getTargets() { return contacts; }
virtual list<Coords> getActivePath() { return activePath; }
protected:
unsigned int ID;
string category;
bool alive = true;
bool human = false;
bool controlled = false;
unsigned char coalition;
unsigned int country = NULL;
string name = "undefined";
string unitName = "undefined";
string groupName = "undefined";
unsigned char state = State::NONE;
string task = "";
bool hasTask = false;
Coords position = Coords(NULL);
double speed = NULL;
double heading = NULL;
bool isTanker = false;
bool isAWACS = false;
bool onOff = true;
bool followRoads = false;
unsigned short fuel = 0;
double desiredSpeed = 0;
bool desiredSpeedType = 1;
double desiredAltitude = 0;
bool desiredAltitudeType = 1;
unsigned int leaderID = NULL;
Offset formationOffset = Offset(NULL);
unsigned int targetID = NULL;
Coords targetPosition = Coords(NULL);
unsigned char ROE = ROE::OPEN_FIRE_WEAPON_FREE;
unsigned char reactionToThreat = ReactionToThreat::EVADE_FIRE;
unsigned char emissionsCountermeasures = EmissionCountermeasure::DEFEND;
DataTypes::TACAN TACAN;
DataTypes::Radio radio;
DataTypes::GeneralSettings generalSettings;
vector<DataTypes::Ammo> ammo;
vector<DataTypes::Contact> contacts;
list<Coords> activePath;
/********** Other **********/
unsigned int taskCheckCounter = 0;
Coords activeDestination = Coords(NULL);
double initialFuel = 0;
Coords oldPosition = Coords(0);
map<unsigned char, unsigned long long> updateTimeMap;
/********** Private methods **********/
virtual void AIloop() = 0;
/********** Template methods **********/
template <typename T>
void updateValue(T& value, T& newValue)
void updateValue(T& value, T& newValue, unsigned char datumIndex)
{
if (newValue != value)
{
triggerUpdate();
triggerUpdate(datumIndex);
*(&value) = newValue;
}
}
template <typename T>
void appendNumeric(stringstream& ss, const unsigned char& datumIndex, T& datumValue) {
ss.write((const char*)&datumIndex, sizeof(unsigned char));
ss.write((const char*)&datumValue, sizeof(T));
}
void appendString(stringstream& ss, const unsigned char& datumIndex, string& datumValue) {
const unsigned short size = datumValue.size();
ss.write((const char*)&datumIndex, sizeof(unsigned char));
ss.write((const char*)&size, sizeof(unsigned short));
ss << datumValue;
}
template <typename T>
void appendVector(stringstream& ss, const unsigned char& datumIndex, vector<T>& datumValue) {
const unsigned short size = datumValue.size();
ss.write((const char*)&datumIndex, sizeof(unsigned char));
ss.write((const char*)&size, sizeof(unsigned short));
ss.write((const char*)&datumValue, size * sizeof(T));
}
template <typename T>
void appendList(stringstream& ss, const unsigned char& datumIndex, list<T>& datumValue) {
const unsigned short size = datumValue.size();
ss.write((const char*)&datumIndex, sizeof(unsigned char));
ss.write((const char*)&size, sizeof(unsigned short));
for (auto el: datumValue)
ss.write((const char*)&el, sizeof(T));
}
};

View File

@ -5,9 +5,6 @@ class Weapon : public Unit
{
public:
Weapon(json::value json, unsigned int ID);
virtual string getCategory() = 0;
protected:
/* Weapons are not controllable and have no AIloop */
virtual void AIloop() {};
@ -17,14 +14,10 @@ class Missile : public Weapon
{
public:
Missile(json::value json, unsigned int ID);
virtual string getCategory() { return "Missile"; };
};
class Bomb : public Weapon
{
public:
Bomb(json::value json, unsigned int ID);
virtual string getCategory() { return "Bomb"; };
};

View File

@ -17,10 +17,10 @@ Aircraft::Aircraft(json::value json, unsigned int ID) : AirUnit(json, ID)
{
log("New Aircraft created with ID: " + to_string(ID));
double desiredSpeed = knotsToMs(300);
double desiredAltitude = ftToM(20000);
setDesiredSpeed(desiredSpeed);
setDesiredAltitude(desiredAltitude);
setCategory("Aircraft");
setDesiredSpeed(knotsToMs(300));
setDesiredAltitude(ftToM(20000));
};
void Aircraft::changeSpeed(string change)

View File

@ -17,8 +17,8 @@ GroundUnit::GroundUnit(json::value json, unsigned int ID) : Unit(json, ID)
{
log("New Ground Unit created with ID: " + to_string(ID));
double desiredSpeed = 10;
setDesiredSpeed(desiredSpeed);
setCategory("GroundUnit");
setDesiredSpeed(10);
};
void GroundUnit::setState(unsigned char newState)

View File

@ -17,10 +17,9 @@ Helicopter::Helicopter(json::value json, unsigned int ID) : AirUnit(json, ID)
{
log("New Helicopter created with ID: " + to_string(ID));
double desiredSpeed = knotsToMs(100);
double desiredAltitude = ftToM(5000);
setDesiredSpeed(desiredSpeed);
setDesiredAltitude(desiredAltitude);
setCategory("Helicopter");
setDesiredSpeed(knotsToMs(100));
setDesiredAltitude(ftToM(5000));
};
void Helicopter::changeSpeed(string change)

View File

@ -17,8 +17,8 @@ NavyUnit::NavyUnit(json::value json, unsigned int ID) : Unit(json, ID)
{
log("New Navy Unit created with ID: " + to_string(ID));
double desiredSpeed = 10;
setDesiredSpeed(desiredSpeed);
setCategory("NavyUnit");
setDesiredSpeed(10);
};
void NavyUnit::AIloop()

View File

@ -288,7 +288,7 @@ void Scheduler::handleRequest(string key, json::value value)
unit->setIsAWACS(value[L"isAWACS"].as_bool());
/* TACAN Options */
Options::TACAN TACAN;
DataTypes::TACAN TACAN;
TACAN.isOn = value[L"TACAN"][L"isOn"].as_bool();
TACAN.channel = static_cast<unsigned char>(value[L"TACAN"][L"channel"].as_number().to_uint32());
TACAN.XY = to_string(value[L"TACAN"][L"XY"]).at(0);

View File

@ -16,20 +16,20 @@ extern Scheduler* scheduler;
extern UnitsManager* unitsManager;
// TODO: Make dedicated file
bool operator==(const Options::TACAN& lhs, const Options::TACAN& rhs)
bool operator==(const DataTypes::TACAN& lhs, const DataTypes::TACAN& rhs)
{
return lhs.isOn == rhs.isOn && lhs.channel == rhs.channel && lhs.XY == rhs.XY && lhs.callsign == rhs.callsign;
}
bool operator==(const Options::Radio& lhs, const Options::Radio& rhs)
bool operator==(const DataTypes::Radio& lhs, const DataTypes::Radio& rhs)
{
return lhs.frequency == rhs.frequency && lhs.callsign == rhs.callsign && lhs.callsignNumber == rhs.callsignNumber;
}
bool operator==(const Options::GeneralSettings& lhs, const Options::GeneralSettings& rhs)
bool operator==(const DataTypes::GeneralSettings& lhs, const DataTypes::GeneralSettings& rhs)
{
return lhs.prohibitAA == rhs.prohibitAA && lhs.prohibitAfterburner == rhs.prohibitAfterburner && lhs.prohibitAG == rhs.prohibitAG &&
lhs.prohibitAirWpn == rhs.prohibitAirWpn && lhs.prohibitJettison == rhs.prohibitJettison;
lhs.prohibitAirWpn == rhs.prohibitAirWpn && lhs.prohibitJettison == rhs.prohibitJettison;
}
Unit::Unit(json::value json, unsigned int ID) :
@ -87,7 +87,6 @@ void Unit::setDefaults(bool force)
strcpy_s(TACAN.callsign, 4, "TKR");
setTACAN(TACAN, force);
setRadio(radio, force);
setEPLRS(EPLRS, force);
setGeneralSettings(generalSettings, force);
}
@ -101,7 +100,7 @@ void Unit::runAILoop() {
const bool isUnitAlive = getAlive();
const bool isUnitLeaderOfAGroupWithOtherUnits = unitsManager->isUnitInGroup(this) && unitsManager->isUnitGroupLeader(this);
if (!(isUnitAlive || isUnitLeaderOfAGroupWithOtherUnits)) return;
if (checkTaskFailed() && state != State::IDLE && State::LAND)
setState(State::IDLE);
@ -113,14 +112,14 @@ void Unit::updateExportData(json::value json, double dt)
Coords newPosition = Coords(NULL);
double newHeading = 0;
double newSpeed = 0;
if (json.has_object_field(L"LatLongAlt"))
{
setPosition({
json[L"LatLongAlt"][L"Lat"].as_number().to_double(),
json[L"LatLongAlt"][L"Long"].as_number().to_double(),
json[L"LatLongAlt"][L"Alt"].as_number().to_double()
});
});
}
if (json.has_number_field(L"Heading"))
setHeading(json[L"Heading"].as_number().to_double());
@ -133,7 +132,7 @@ void Unit::updateExportData(json::value json, double dt)
if (dt > 0)
setSpeed(getSpeed() * 0.95 + (dist / dt) * 0.05);
}
oldPosition = position;
}
@ -180,120 +179,67 @@ void Unit::updateMissionData(json::value json)
setHasTask(json[L"hasTask"].as_bool());
}
unsigned int Unit::getDataPacket(char* &data)
void Unit::getData(stringstream& ss, unsigned long long time, bool refresh)
{
/* Reserve data for:
1) DataPacket;
2) Active path;
3) Ammo vector;
4) Contacts vector;
*/
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 */
unsigned int bitmask = 0;
bitmask |= alive << 0;
bitmask |= human << 1;
bitmask |= controlled << 2;
bitmask |= hasTask << 3;
bitmask |= desiredAltitudeType << 16;
bitmask |= desiredSpeedType << 17;
bitmask |= isTanker << 18;
bitmask |= isAWACS << 19;
bitmask |= onOff << 20;
bitmask |= followRoads << 21;
bitmask |= EPLRS << 22;
bitmask |= generalSettings.prohibitAA << 23;
bitmask |= generalSettings.prohibitAfterburner << 24;
bitmask |= generalSettings.prohibitAG << 25;
bitmask |= generalSettings.prohibitAirWpn << 26;
bitmask |= generalSettings.prohibitJettison << 27;
/* If the unit is in a group, get the update data from the group leader and only replace the position: speed and heading */
//if (unitsManager->isUnitInGroup(this) && !unitsManager->isUnitGroupLeader(this)) {
// DataTypes::DataPacket* p = (DataTypes::DataPacket*)data;
// p->position = position;
// p->speed = speed;
// p->heading = heading;
//}
DataTypes::DataPacket dataPacket{
ID,
bitmask,
position,
speed,
heading,
fuel,
desiredSpeed,
desiredAltitude,
leaderID,
targetID,
targetPosition,
state,
ROE,
reactionToThreat,
emissionsCountermeasures,
coalition,
TACAN,
radio,
activePath.size(),
ammo.size(),
contacts.size(),
task.size()
};
const unsigned char startOfData = DataIndex::startOfData;
const unsigned char endOfData = DataIndex::endOfData;
memcpy(data + offset, &dataPacket, sizeof(dataPacket));
offset += sizeof(dataPacket);
/* Prepare the path vector and copy it to memory */
for (const Coords& c : activePath) {
memcpy(data + offset, &c, sizeof(Coords));
offset += sizeof(Coords);
ss.write((const char*)&ID, sizeof(ID));
ss.write((const char*)&startOfData, sizeof(startOfData));
for (auto d : updateTimeMap) {
if (d.second > time) {
switch (d.first) {
case DataIndex::category: appendString(ss, d.first, category); break;
case DataIndex::alive: appendNumeric(ss, d.first, alive); break;
case DataIndex::human: appendNumeric(ss, d.first, human); break;
case DataIndex::controlled: appendNumeric(ss, d.first, controlled); break;
case DataIndex::coalition: appendNumeric(ss, d.first, coalition); break;
case DataIndex::country: appendNumeric(ss, d.first, country); break;
case DataIndex::name: appendString(ss, d.first, name); break;
case DataIndex::unitName: appendString(ss, d.first, unitName); break;
case DataIndex::groupName: appendString(ss, d.first, groupName); break;
case DataIndex::state: appendNumeric(ss, d.first, state); break;
case DataIndex::task: appendString(ss, d.first, task); break;
case DataIndex::hasTask: appendNumeric(ss, d.first, hasTask); break;
case DataIndex::position: appendNumeric(ss, d.first, position); break;
case DataIndex::speed: appendNumeric(ss, d.first, speed); break;
case DataIndex::heading: appendNumeric(ss, d.first, heading); break;
case DataIndex::isTanker: appendNumeric(ss, d.first, isTanker); break;
case DataIndex::isAWACS: appendNumeric(ss, d.first, isAWACS); break;
case DataIndex::onOff: appendNumeric(ss, d.first, onOff); break;
case DataIndex::followRoads: appendNumeric(ss, d.first, followRoads); break;
case DataIndex::fuel: appendNumeric(ss, d.first, fuel); break;
case DataIndex::desiredSpeed: appendNumeric(ss, d.first, desiredSpeed); break;
case DataIndex::desiredSpeedType: appendNumeric(ss, d.first, desiredSpeedType); break;
case DataIndex::desiredAltitude: appendNumeric(ss, d.first, desiredAltitude); break;
case DataIndex::desiredAltitudeType: appendNumeric(ss, d.first, desiredAltitudeType); break;
case DataIndex::leaderID: appendNumeric(ss, d.first, leaderID); break;
case DataIndex::formationOffset: appendNumeric(ss, d.first, formationOffset); break;
case DataIndex::targetID: appendNumeric(ss, d.first, targetID); break;
case DataIndex::targetPosition: appendNumeric(ss, d.first, targetPosition); break;
case DataIndex::ROE: appendNumeric(ss, d.first, ROE); break;
case DataIndex::reactionToThreat: appendNumeric(ss, d.first, reactionToThreat); break;
case DataIndex::emissionsCountermeasures: appendNumeric(ss, d.first, emissionsCountermeasures); break;
case DataIndex::TACAN: appendNumeric(ss, d.first, TACAN); break;
case DataIndex::radio: appendNumeric(ss, d.first, radio); break;
case DataIndex::generalSettings: appendNumeric(ss, d.first, generalSettings); break;
case DataIndex::ammo: appendVector(ss, d.first, ammo); break;
case DataIndex::contacts: appendVector(ss, d.first, contacts); break;
case DataIndex::activePath: appendList(ss, d.first, activePath); break;
}
}
}
/* 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;
}
void Unit::getData(stringstream &ss, unsigned long long time, bool refresh)
{
if (time > lastUpdateTime && !refresh) return;
char* data;
unsigned int size = getDataPacket(data);
/* Prepare the data packet and copy it to memory */
/* If the unit is in a group, get the update data from the group leader and only replace the position, speed and heading */
if (unitsManager->isUnitInGroup(this) && !unitsManager->isUnitGroupLeader(this)) {
DataTypes::DataPacket* p = (DataTypes::DataPacket*)data;
p->position = position;
p->speed = speed;
p->heading = heading;
}
ss.write(data, size);
ss << task;
unsigned short nameLength = name.length();
unsigned short unitNameLength = unitName.length();
unsigned short groupNameLength = groupName.length();
unsigned short categoryLength = getCategory().length();
ss.write((char*)&nameLength, sizeof(nameLength));
ss.write((char*)&unitNameLength, sizeof(unitNameLength));
ss.write((char*)&groupNameLength, sizeof(groupNameLength));
ss.write((char*)&categoryLength, sizeof(categoryLength));
ss << name;
ss << unitName;
ss << groupName;
ss << getCategory();
delete data;
ss.write((const char*)&endOfData, sizeof(endOfData));
}
void Unit::setActivePath(list<Coords> newPath)
@ -393,17 +339,17 @@ void Unit::setFormationOffset(Offset newFormationOffset)
formationOffset = newFormationOffset;
resetTask();
triggerUpdate();
triggerUpdate(DataIndex::formationOffset);
}
void Unit::setROE(unsigned char newROE, bool force)
void Unit::setROE(unsigned char newROE, bool force)
{
if (ROE != newROE || force) {
ROE = newROE;
Command* command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::ROE, static_cast<unsigned int>(ROE)));
scheduler->appendCommand(command);
triggerUpdate();
triggerUpdate(DataIndex::ROE);
}
}
@ -415,7 +361,7 @@ void Unit::setReactionToThreat(unsigned char newReactionToThreat, bool force)
Command* command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::REACTION_ON_THREAT, static_cast<unsigned int>(reactionToThreat)));
scheduler->appendCommand(command);
triggerUpdate();
triggerUpdate(DataIndex::reactionToThreat);
}
}
@ -465,39 +411,38 @@ void Unit::setEmissionsCountermeasures(unsigned char newEmissionsCountermeasures
command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::ECM_USING, ECMEnum));
scheduler->appendCommand(command);
triggerUpdate();
triggerUpdate(DataIndex::emissionsCountermeasures);
}
}
void Unit::landAt(Coords loc)
void Unit::landAt(Coords loc)
{
clearActivePath();
pushActivePathBack(loc);
setState(State::LAND);
}
void Unit::setIsTanker(bool newIsTanker)
{
void Unit::setIsTanker(bool newIsTanker)
{
if (isTanker != newIsTanker) {
isTanker = newIsTanker;
resetTask();
triggerUpdate();
triggerUpdate(DataIndex::isTanker);
}
}
void Unit::setIsAWACS(bool newIsAWACS)
{
{
if (isAWACS != newIsAWACS) {
isAWACS = newIsAWACS;
resetTask();
setEPLRS(isAWACS);
triggerUpdate();
triggerUpdate(DataIndex::isAWACS);
}
}
void Unit::setTACAN(Options::TACAN newTACAN, bool force)
void Unit::setTACAN(DataTypes::TACAN newTACAN, bool force)
{
if (TACAN != newTACAN || force)
{
@ -528,11 +473,11 @@ void Unit::setTACAN(Options::TACAN newTACAN, bool force)
scheduler->appendCommand(command);
}
triggerUpdate();
triggerUpdate(DataIndex::TACAN);
}
}
void Unit::setRadio(Options::Radio newRadio, bool force)
void Unit::setRadio(DataTypes::Radio newRadio, bool force)
{
if (radio != newRadio || force)
{
@ -564,30 +509,11 @@ void Unit::setRadio(Options::Radio newRadio, bool force)
command = dynamic_cast<Command*>(new SetCommand(groupName, commandSS.str()));
scheduler->appendCommand(command);
triggerUpdate();
triggerUpdate(DataIndex::radio);
}
}
void Unit::setEPLRS(bool newEPLRS, bool force)
{
//addMeasure("EPLRS", json::value(newEPLRS));
//
//if (EPLRS != newEPLRS || force) {
// EPLRS = newEPLRS;
//
// std::ostringstream commandSS;
// commandSS << "{"
// << "id = 'EPLRS',"
// << "params = {"
// << "value = " << (EPLRS ? "true" : "false") << ", "
// << "}"
// << "}";
// Command* command = dynamic_cast<Command*>(new SetCommand(ID, commandSS.str()));
// scheduler->appendCommand(command);
//}
}
void Unit::setGeneralSettings(Options::GeneralSettings newGeneralSettings, bool force)
void Unit::setGeneralSettings(DataTypes::GeneralSettings newGeneralSettings, bool force)
{
if (generalSettings != newGeneralSettings)
{
@ -605,22 +531,22 @@ void Unit::setGeneralSettings(Options::GeneralSettings newGeneralSettings, bool
command = dynamic_cast<Command*>(new SetOption(groupName, SetCommandType::ENGAGE_AIR_WEAPONS, !generalSettings.prohibitAirWpn));
scheduler->appendCommand(command);
triggerUpdate();
triggerUpdate(DataIndex::generalSettings);
}
}
void Unit::setDesiredSpeed(double newDesiredSpeed)
void Unit::setDesiredSpeed(double newDesiredSpeed)
{
desiredSpeed = newDesiredSpeed;
desiredSpeed = newDesiredSpeed;
if (state == State::IDLE)
resetTask();
else
goToDestination(); /* Send the command to reach the destination */
triggerUpdate();
triggerUpdate(DataIndex::desiredSpeed);
}
void Unit::setDesiredAltitude(double newDesiredAltitude)
void Unit::setDesiredAltitude(double newDesiredAltitude)
{
desiredAltitude = newDesiredAltitude;
if (state == State::IDLE)
@ -628,10 +554,10 @@ void Unit::setDesiredAltitude(double newDesiredAltitude)
else
goToDestination(); /* Send the command to reach the destination */
triggerUpdate();
triggerUpdate(DataIndex::desiredAltitude);
}
void Unit::setDesiredSpeedType(string newDesiredSpeedType)
void Unit::setDesiredSpeedType(string newDesiredSpeedType)
{
desiredSpeedType = newDesiredSpeedType.compare("GS") == 0;
if (state == State::IDLE)
@ -639,10 +565,10 @@ void Unit::setDesiredSpeedType(string newDesiredSpeedType)
else
goToDestination(); /* Send the command to reach the destination */
triggerUpdate();
triggerUpdate(DataIndex::desiredSpeedType);
}
void Unit::setDesiredAltitudeType(string newDesiredAltitudeType)
void Unit::setDesiredAltitudeType(string newDesiredAltitudeType)
{
desiredAltitudeType = newDesiredAltitudeType.compare("AGL") == 0;
if (state == State::IDLE)
@ -650,14 +576,14 @@ void Unit::setDesiredAltitudeType(string newDesiredAltitudeType)
else
goToDestination(); /* Send the command to reach the destination */
triggerUpdate();
triggerUpdate(DataIndex::desiredAltitudeType);
}
void Unit::goToDestination(string enrouteTask)
{
if (activeDestination != NULL)
{
Command* command = dynamic_cast<Command*>(new Move(groupName, activeDestination, getDesiredSpeed(), getDesiredSpeedType()? "GS": "CAS", getDesiredAltitude(), getDesiredAltitudeType()? "AGL" : "ASL", enrouteTask, getCategory()));
Command* command = dynamic_cast<Command*>(new Move(groupName, activeDestination, getDesiredSpeed(), getDesiredSpeedType() ? "GS" : "CAS", getDesiredAltitude(), getDesiredAltitudeType() ? "AGL" : "ASL", enrouteTask, getCategory()));
scheduler->appendCommand(command);
setHasTask(true);
}
@ -668,7 +594,7 @@ bool Unit::isDestinationReached(double threshold)
if (activeDestination != NULL)
{
/* Check if any unit in the group has reached the point */
for (auto const& p: unitsManager->getGroupMembers(groupName))
for (auto const& p : unitsManager->getGroupMembers(groupName))
{
double dist = 0;
Geodesic::WGS84().Inverse(p->getPosition().lat, p->getPosition().lng, activeDestination.lat, activeDestination.lng, dist);
@ -694,7 +620,7 @@ bool Unit::setActiveDestination()
activeDestination = activePath.front();
log(unitName + " active destination set to queue front");
triggerUpdate();
triggerUpdate(DataIndex::activePath);
return true;
}
else
@ -702,7 +628,7 @@ bool Unit::setActiveDestination()
activeDestination = Coords(0);
log(unitName + " active destination set to NULL");
triggerUpdate();
triggerUpdate(DataIndex::activePath);
return false;
}
}
@ -723,9 +649,9 @@ bool Unit::updateActivePath(bool looping)
}
}
bool Unit::checkTaskFailed()
bool Unit::checkTaskFailed()
{
if (getHasTask())
if (getHasTask())
return false;
else {
if (taskCheckCounter > 0)
@ -737,3 +663,7 @@ bool Unit::checkTaskFailed()
void Unit::resetTaskFailedCounter() {
taskCheckCounter = TASK_CHECK_INIT_VALUE;
}
void Unit::triggerUpdate(unsigned char datumIndex) {
updateTimeMap[datumIndex] = duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
}

View File

@ -22,10 +22,12 @@ Weapon::Weapon(json::value json, unsigned int ID) : Unit(json, ID)
Missile::Missile(json::value json, unsigned int ID) : Weapon(json, ID)
{
log("New Missile created with ID: " + to_string(ID));
setCategory("Missile");
};
/* Bomb */
Bomb::Bomb(json::value json, unsigned int ID) : Weapon(json, ID)
{
log("New Bomb created with ID: " + to_string(ID));
setCategory("Bomb");
};