Added ability to take control of units

And bugfixes
This commit is contained in:
Pax1601
2023-06-13 17:03:04 +02:00
parent 156c730352
commit 4f2debeb4f
20 changed files with 1266 additions and 666 deletions

View File

@@ -3,7 +3,7 @@ interface UpdateData {
}
interface BaseData {
AI: boolean;
controlled: boolean;
name: string;
unitName: string;
groupName: string;
@@ -23,7 +23,7 @@ interface MissionData {
fuel: number;
flags: any;
ammo: any;
targets: any;
contacts: any;
hasTask: boolean;
coalition: string;
}
@@ -36,10 +36,16 @@ interface TaskData {
currentState: string;
currentTask: string;
activePath: any;
targetSpeed: number;
targetAltitude: number;
desiredSpeed: number;
desiredSpeedType: string;
desiredAltitude: number;
desiredAltitudeType: string;
targetLocation: any;
isTanker: boolean;
isAWACS: boolean;
onOff: boolean;
followRoads: boolean;
targetID: number;
}
interface OptionsData {
@@ -51,15 +57,6 @@ interface OptionsData {
generalSettings: GeneralSettings;
}
interface UnitData {
baseData: BaseData;
flightData: FlightData;
missionData: MissionData;
formationData: FormationData;
taskData: TaskData;
optionsData: OptionsData;
}
interface TACAN {
isOn: boolean;
channel: number;
@@ -91,4 +88,13 @@ interface UnitIconOptions {
showAmmo: boolean,
showSummary: boolean,
rotateToHeading: boolean
}
interface UnitData {
baseData: BaseData;
flightData: FlightData;
missionData: MissionData;
formationData: FormationData;
taskData: TaskData;
optionsData: OptionsData;
}

View File

@@ -123,7 +123,7 @@ export abstract class ATCBoard {
return false;
}
if ( baseData.AI === true ) {
if ( baseData.controlled === true ) {
// return false;
}

View File

@@ -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().AI) ? "AI" : "Human"];
const dataset = [unit.getBaseData().unitName, unit.getBaseData().name, unit.getBaseData().category, (unit.getBaseData().controlled) ? "AI" : "Human"];
addRow(el, dataset);
}

View File

@@ -3,34 +3,22 @@ import { getUnitsManager } from "..";
import { Dropdown } from "../controls/dropdown";
import { Slider } from "../controls/slider";
import { aircraftDatabase } from "../units/aircraftdatabase";
import { groundUnitsDatabase } from "../units/groundunitsdatabase";
import { Aircraft, GroundUnit, Unit } from "../units/unit";
import { UnitDatabase } from "../units/unitdatabase";
import { Unit } from "../units/unit";
import { Panel } from "./panel";
const ROEs: string[] = ["Hold", "Return", "Designated", "Free"];
const reactionsToThreat: string[] = ["None", "Manoeuvre", "Passive", "Evade"];
const emissionsCountermeasures: string[] = ["Silent", "Attack", "Defend", "Free"];
const ROEDescriptions: string[] = ["Hold (Never fire)", "Return (Only fire if fired upon)", "Designated (Attack the designated target only)", "Free (Attack anyone)"];
const reactionsToThreatDescriptions: string[] = ["None (No reaction)", "Manoeuvre (no countermeasures)", "Passive (Countermeasures only, no manoeuvre)", "Evade (Countermeasures and manoeuvers)"];
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)"];
const minSpeedValues: { [key: string]: number } = { Aircraft: 100, Helicopter: 0, NavyUnit: 0, GroundUnit: 0 };
const maxSpeedValues: { [key: string]: number } = { Aircraft: 800, Helicopter: 300, NavyUnit: 60, GroundUnit: 60 };
const speedIncrements: { [key: string]: number } = { Aircraft: 25, Helicopter: 10, NavyUnit: 5, GroundUnit: 5 };
const minAltitudeValues: { [key: string]: number } = { Aircraft: 0, Helicopter: 0 };
const maxAltitudeValues: { [key: string]: number } = { Aircraft: 50000, Helicopter: 10000 };
const altitudeIncrements: { [key: string]: number } = { Aircraft: 500, Helicopter: 100 };
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";
export class UnitControlPanel extends Panel {
#altitudeSlider: Slider;
#airspeedSlider: Slider;
#altitudeTypeSwitch: Switch;
#speedSlider: Slider;
#speedTypeSwitch: Switch;
#onOffSwitch: Switch;
#followRoadsSwitch: Switch;
#TACANXYDropdown: Dropdown;
#radioDecimalsDropdown: Dropdown;
#radioCallsignDropdown: Dropdown;
#expectedAltitude: number = -1;
#expectedSpeed: number = -1;
#optionButtons: { [key: string]: HTMLButtonElement[] } = {}
#advancedSettingsDialog: HTMLElement;
@@ -38,22 +26,11 @@ export class UnitControlPanel extends Panel {
super(ID);
/* Unit control sliders */
this.#altitudeSlider = new Slider("altitude-slider", 0, 100, "ft", (value: number) => {
this.#expectedAltitude = value;
getUnitsManager().selectedUnitsSetAltitude(value * 0.3048)
});
this.#altitudeSlider = new Slider("altitude-slider", 0, 100, "ft", (value: number) => { getUnitsManager().selectedUnitsSetAltitude(ftToM(value)); });
this.#altitudeTypeSwitch = new Switch("altitude-type-switch", (value: boolean) => { getUnitsManager().selectedUnitsSetAltitudeType(value? "AGL": "ASL"); });
this.#airspeedSlider = new Slider("airspeed-slider", 0, 100, "kts", (value: number) => {
this.#expectedSpeed = value;
getUnitsManager().selectedUnitsSetSpeed(value / 1.94384)
});
/* Advanced settings dropdowns */
this.#TACANXYDropdown = new Dropdown("TACAN-XY", () => {});
this.#TACANXYDropdown.setOptions(["X", "Y"]);
this.#radioDecimalsDropdown = new Dropdown("radio-decimals", () => {});
this.#radioDecimalsDropdown.setOptions([".000", ".250", ".500", ".750"]);
this.#radioCallsignDropdown = new Dropdown("radio-callsign", () => {});
this.#speedSlider = new Slider("speed-slider", 0, 100, "kts", (value: number) => { getUnitsManager().selectedUnitsSetSpeed(knotsToMs(value)); });
this.#speedTypeSwitch = new Switch("speed-type-switch", (value: boolean) => { getUnitsManager().selectedUnitsSetSpeedType(value? "GS": "CAS"); });
/* Option buttons */
this.#optionButtons["ROE"] = ROEs.map((option: string, index: number) => {
@@ -72,8 +49,27 @@ export class UnitControlPanel extends Panel {
this.getElement().querySelector("#reaction-to-threat-buttons-container")?.append(...this.#optionButtons["reactionToThreat"]);
this.getElement().querySelector("#emissions-countermeasures-buttons-container")?.append(...this.#optionButtons["emissionsCountermeasures"]);
/* On off switch */
this.#onOffSwitch = new Switch("on-off-switch", (value: boolean) => {
getUnitsManager().selectedUnitsSetOnOff(value);
});
/* Follow roads switch */
this.#followRoadsSwitch = new Switch("follow-roads-switch", (value: boolean) => {
getUnitsManager().selectedUnitsSetFollowRoads(value);
});
/* Advanced settings dialog */
this.#advancedSettingsDialog = <HTMLElement> document.querySelector("#advanced-settings-dialog");
/* Advanced settings dropdowns */
this.#TACANXYDropdown = new Dropdown("TACAN-XY", () => {});
this.#TACANXYDropdown.setOptions(["X", "Y"]);
this.#radioDecimalsDropdown = new Dropdown("radio-decimals", () => {});
this.#radioDecimalsDropdown.setOptions([".000", ".250", ".500", ".750"]);
this.#radioCallsignDropdown = new Dropdown("radio-callsign", () => {});
/* Events and timer */
window.setInterval(() => {this.update();}, 25);
document.addEventListener("unitsSelection", (e: CustomEvent<Unit[]>) => { this.show(); this.addButtons();});
@@ -82,35 +78,30 @@ export class UnitControlPanel extends Panel {
document.addEventListener("showAdvancedSettings", () => {
this.#updateAdvancedSettingsDialog(getUnitsManager().getSelectedUnits());
this.#advancedSettingsDialog.classList.remove("hide");
})
});
this.hide();
}
// Do this after panel is hidden (make sure there's a reset)
hide() {
super.hide();
this.#expectedAltitude = -1;
this.#expectedSpeed = -1;
show() {
super.show();
this.#speedTypeSwitch.resetExpectedValue();
this.#altitudeTypeSwitch.resetExpectedValue();
this.#onOffSwitch.resetExpectedValue();
this.#followRoadsSwitch.resetExpectedValue();
this.#altitudeSlider.resetExpectedValue();
this.#speedSlider.resetExpectedValue();
}
addButtons() {
var units = getUnitsManager().getSelectedUnits();
if (units.length < 20) {
this.getElement().querySelector("#selected-units-container")?.replaceChildren(...units.map((unit: Unit, index: number) => {
let database: UnitDatabase | null;
if (unit instanceof Aircraft)
database = aircraftDatabase;
else if (unit instanceof GroundUnit)
database = groundUnitsDatabase;
else
database = null; // TODO add databases for other unit types
var button = document.createElement("button");
var callsign = unit.getBaseData().unitName || "";
var label = unit.getDatabase()?.getByName(unit.getBaseData().name)?.label || unit.getBaseData().name;
button.setAttribute("data-short-label", database?.getByName(unit.getBaseData().name)?.shortLabel || unit.getBaseData().name);
button.setAttribute("data-label", label);
button.setAttribute("data-callsign", callsign);
button.setAttribute("data-coalition", unit.getMissionData().coalition);
@@ -131,11 +122,53 @@ export class UnitControlPanel extends Panel {
update() {
if (this.getVisible()){
var units = getUnitsManager().getSelectedUnits();
this.getElement().querySelector("#advanced-settings-div")?.classList.toggle("hide", units.length != 1);
if (this.getElement() != null && units.length > 0) {
this.#showFlightControlSliders(units);
const element = this.getElement();
const units = getUnitsManager().getSelectedUnits();
const selectedUnitsTypes = getUnitsManager().getSelectedUnitsTypes();
if (element != null && units.length > 0) {
/* Toggle visibility of control elements */
element.toggleAttribute("data-show-categories-tooltip", selectedUnitsTypes.length > 1);
element.toggleAttribute("data-show-speed-slider", selectedUnitsTypes.length == 1);
element.toggleAttribute("data-show-altitude-slider", selectedUnitsTypes.length == 1 && (selectedUnitsTypes.includes("Aircraft") || selectedUnitsTypes.includes("Helicopter")));
element.toggleAttribute("data-show-roe", true);
element.toggleAttribute("data-show-threat", (selectedUnitsTypes.includes("Aircraft") || selectedUnitsTypes.includes("Helicopter")) && !(selectedUnitsTypes.includes("GroundUnit") || selectedUnitsTypes.includes("NavyUnit")));
element.toggleAttribute("data-show-emissions-countermeasures", (selectedUnitsTypes.includes("Aircraft") || selectedUnitsTypes.includes("Helicopter")) && !(selectedUnitsTypes.includes("GroundUnit") || selectedUnitsTypes.includes("NavyUnit")));
element.toggleAttribute("data-show-on-off", (selectedUnitsTypes.includes("GroundUnit") || selectedUnitsTypes.includes("NavyUnit")) && !(selectedUnitsTypes.includes("Aircraft") || selectedUnitsTypes.includes("Helicopter")));
element.toggleAttribute("data-show-follow-roads", (selectedUnitsTypes.length == 1 && selectedUnitsTypes.includes("GroundUnit")));
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});
if (selectedUnitsTypes.length == 1) {
this.#altitudeTypeSwitch.setValue(desiredAltitudeType != undefined? desiredAltitudeType == "AGL": undefined, false);
this.#speedTypeSwitch.setValue(desiredSpeedType != undefined? desiredSpeedType == "GS": undefined, false);
this.#speedSlider.setMinMax(minSpeedValues[selectedUnitsTypes[0]], maxSpeedValues[selectedUnitsTypes[0]]);
this.#altitudeSlider.setMinMax(minAltitudeValues[selectedUnitsTypes[0]], maxAltitudeValues[selectedUnitsTypes[0]]);
this.#speedSlider.setIncrement(speedIncrements[selectedUnitsTypes[0]]);
this.#altitudeSlider.setIncrement(altitudeIncrements[selectedUnitsTypes[0]]);
this.#speedSlider.setActive(desiredSpeed != undefined);
if (desiredSpeed != undefined)
this.#speedSlider.setValue(msToKnots(desiredSpeed), false);
this.#altitudeSlider.setActive(desiredAltitude != undefined);
if (desiredAltitude != undefined)
this.#altitudeSlider.setValue(mToFt(desiredAltitude), false);
}
else {
this.#speedSlider.setActive(false);
this.#altitudeSlider.setActive(false);
}
/* Option buttons */
this.#optionButtons["ROE"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().ROE === button.value))
});
@@ -147,75 +180,13 @@ export class UnitControlPanel extends Panel {
this.#optionButtons["emissionsCountermeasures"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().emissionsCountermeasures === button.value))
});
this.#onOffSwitch.setValue(onOff, false);
this.#followRoadsSwitch.setValue(followRoads, false);
}
}
}
/* Update function will only be allowed to update the sliders once it's matched the expected value for the first time (due to lag of Ajax request) */
#updateCanSetAltitudeSlider(altitude: number) {
if (this.#expectedAltitude < 0 || altitude === this.#expectedAltitude) {
this.#expectedAltitude = -1;
return true;
}
return false;
}
#updateCanSetSpeedSlider(altitude: number) {
if (this.#expectedSpeed < 0 || altitude === this.#expectedSpeed) {
this.#expectedSpeed = -1;
return true;
}
return false;
}
#showFlightControlSliders(units: Unit[]) {
if (getUnitsManager().getSelectedUnitsType() !== undefined)
this.#airspeedSlider.show()
else
this.#airspeedSlider.hide();
if (getUnitsManager().getSelectedUnitsType() === "Aircraft" || getUnitsManager().getSelectedUnitsType() === "Helicopter")
this.#altitudeSlider.show()
else
this.#altitudeSlider.hide();
this.getElement().querySelector(`#categories-tooltip`)?.classList.toggle("hide", getUnitsManager().getSelectedUnitsType() !== undefined);
var unitsType = getUnitsManager().getSelectedUnitsType();
var targetAltitude = getUnitsManager().getSelectedUnitsTargetAltitude();
var targetSpeed = getUnitsManager().getSelectedUnitsTargetSpeed();
if (unitsType != undefined) {
if (["GroundUnit", "NavyUnit"].includes(unitsType))
this.#altitudeSlider.hide()
this.#airspeedSlider.setMinMax(minSpeedValues[unitsType], maxSpeedValues[unitsType]);
this.#altitudeSlider.setMinMax(minAltitudeValues[unitsType], maxAltitudeValues[unitsType]);
this.#airspeedSlider.setIncrement(speedIncrements[unitsType]);
this.#altitudeSlider.setIncrement(altitudeIncrements[unitsType]);
this.#airspeedSlider.setActive(targetSpeed != undefined);
if (targetSpeed != undefined) {
targetSpeed *= 1.94384;
if (this.#updateCanSetSpeedSlider(targetSpeed)) {
this.#airspeedSlider.setValue(targetSpeed);
}
}
this.#altitudeSlider.setActive(targetAltitude != undefined);
if (targetAltitude != undefined) {
targetAltitude /= 0.3048;
if (this.#updateCanSetAltitudeSlider(targetAltitude)) {
this.#altitudeSlider.setValue(targetAltitude);
}
}
}
else {
this.#airspeedSlider.setActive(false);
this.#altitudeSlider.setActive(false);
}
}
#updateAdvancedSettingsDialog(units: Unit[])
{
if (units.length == 1)

View File

@@ -16,7 +16,7 @@ export class UnitInfoPanel extends Panel {
#latitude: HTMLElement;
#longitude: HTMLElement;
#loadoutContainer: HTMLElement;
#silhouette: HTMLElement;
#silhouette: HTMLImageElement;
#unitControl: HTMLElement;
#unitLabel: HTMLElement;
#unitName: HTMLElement;
@@ -24,21 +24,21 @@ export class UnitInfoPanel extends Panel {
constructor(ID: string) {
super(ID);
this.#altitude = <HTMLElement>(this.getElement().querySelector("#altitude"));
this.#currentTask = <HTMLElement>(this.getElement().querySelector("#current-task"));
this.#groundSpeed = <HTMLElement>(this.getElement().querySelector("#ground-speed"));
this.#fuelBar = <HTMLElement>(this.getElement().querySelector("#fuel-bar"));
this.#fuelPercentage = <HTMLElement>(this.getElement().querySelector("#fuel-percentage"));
this.#groupName = <HTMLElement>(this.getElement().querySelector("#group-name"));
this.#heading = <HTMLElement>(this.getElement().querySelector("#heading"));
this.#name = <HTMLElement>(this.getElement().querySelector("#name"));
this.#latitude = <HTMLElement>(this.getElement().querySelector("#latitude"));
this.#loadoutContainer = <HTMLElement>(this.getElement().querySelector("#loadout-container"));
this.#longitude = <HTMLElement>(this.getElement().querySelector("#longitude"));
this.#silhouette = <HTMLElement>(this.getElement().querySelector("#loadout-silhouette"));
this.#unitControl = <HTMLElement>(this.getElement().querySelector("#unit-control"));
this.#unitLabel = <HTMLElement>(this.getElement().querySelector("#unit-label"));
this.#unitName = <HTMLElement>(this.getElement().querySelector("#unit-name"));
this.#altitude = (this.getElement().querySelector("#altitude")) as HTMLElement;
this.#currentTask = (this.getElement().querySelector("#current-task")) as HTMLElement;
this.#groundSpeed = (this.getElement().querySelector("#ground-speed")) as HTMLElement;
this.#fuelBar = (this.getElement().querySelector("#fuel-bar")) as HTMLElement;
this.#fuelPercentage = (this.getElement().querySelector("#fuel-percentage")) as HTMLElement;
this.#groupName = (this.getElement().querySelector("#group-name")) as HTMLElement;
this.#heading = (this.getElement().querySelector("#heading")) as HTMLElement;
this.#name = (this.getElement().querySelector("#name")) as HTMLElement;
this.#latitude = (this.getElement().querySelector("#latitude")) as HTMLElement;
this.#loadoutContainer = (this.getElement().querySelector("#loadout-container")) as HTMLElement;
this.#longitude = (this.getElement().querySelector("#longitude")) as HTMLElement;
this.#silhouette = (this.getElement().querySelector("#loadout-silhouette")) as HTMLImageElement;
this.#unitControl = (this.getElement().querySelector("#unit-control")) as HTMLElement;
this.#unitLabel = (this.getElement().querySelector("#unit-label")) as HTMLElement;
this.#unitName = (this.getElement().querySelector("#unit-name")) as HTMLElement;
document.addEventListener("unitsSelection", (e: CustomEvent<Unit[]>) => this.#onUnitsSelection(e.detail));
document.addEventListener("unitsDeselection", (e: CustomEvent<Unit[]>) => this.#onUnitsDeselection(e.detail));
@@ -47,69 +47,65 @@ export class UnitInfoPanel extends Panel {
this.hide();
}
#onUnitUpdate(unit: Unit) {
if (this.getElement() != null && this.getVisible() && unit.getSelected()) {
const baseData = unit.getBaseData();
/* Set the unit info */
this.#unitLabel.innerText = aircraftDatabase.getByName(baseData.name)?.label || baseData.name;
this.#unitName.innerText = baseData.unitName;
this.#unitControl.innerText = ( ( baseData.AI ) ? "AI" : "Human" ) + " controlled";
// this.#groupName.innerText = baseData.groupName;
//this.#name.innerText = baseData.name;
//this.#heading.innerText = String(Math.floor(rad2deg(unit.getFlightData().heading)) + " °");
//this.#altitude.innerText = String(Math.floor(unit.getFlightData().altitude / 0.3048) + " ft");
//this.#groundSpeed.innerText = String(Math.floor(unit.getFlightData().speed * 1.94384) + " kts");
this.#unitLabel.innerText = aircraftDatabase.getByName(baseData.name)?.label || baseData.name;
this.#unitName.innerText = baseData.unitName;
if (unit.getMissionData().flags.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.#latitude.innerText = ConvertDDToDMS(unit.getFlightData().latitude, false);
//this.#longitude.innerText = ConvertDDToDMS(unit.getFlightData().longitude, true);
this.#currentTask.dataset.currentTask = unit.getTaskData().currentTask !== ""? unit.getTaskData().currentTask: "No task";
this.#currentTask.dataset.currentTask = unit.getTaskData().currentTask !== "" ? unit.getTaskData().currentTask : "No task";
this.#currentTask.dataset.coalition = unit.getMissionData().coalition;
this.#silhouette.setAttribute( "style", `--loadout-background-image:url('/images/units/${aircraftDatabase.getByName( baseData.name )?.filename}');` );;
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 == '');
/* Add the loadout elements */
const items = <HTMLElement>this.#loadoutContainer.querySelector( "#loadout-items" );
if ( items ) {
const ammo = Object.values( unit.getMissionData().ammo );
if ( ammo.length > 0 ) {
const items = <HTMLElement>this.#loadoutContainer.querySelector("#loadout-items");
if (items) {
const ammo = Object.values(unit.getMissionData().ammo);
if (ammo.length > 0) {
items.replaceChildren(...Object.values(unit.getMissionData().ammo).map(
(ammo: any) => {
var el = document.createElement("div");
el.dataset.qty = ammo.count;
el.dataset.qty = ammo.count;
el.dataset.item = ammo.desc.displayName;
return el;
}
));
} else {
items.innerText = "No loadout";
}
}
}
}
#onUnitsSelection(units: Unit[]){
if (units.length == 1)
#onUnitsSelection(units: Unit[]) {
if (units.length == 1) {
this.show();
this.#onUnitUpdate(units[0]);
}
else
this.hide();
}
#onUnitsDeselection(units: Unit[]){
if (units.length == 1)
#onUnitsDeselection(units: Unit[]) {
if (units.length == 1) {
this.show();
this.#onUnitUpdate(units[0]);
}
else
this.hide();
}

View File

@@ -2456,8 +2456,8 @@ export class AircraftDatabase extends UnitDatabase {
],
"filename": "kc-135.png"
},
"KC-135MPRS": {
"name": "KC-135MPRS",
"KC135MPRS": {
"name": "KC135MPRS",
"label": "KC-135 MPRS Stratotanker",
"era": ["Early Cold War", "Mid Cold War", "Late Cold War", "Modern"],
"shortLabel": "135M",
@@ -2476,6 +2476,26 @@ export class AircraftDatabase extends UnitDatabase {
],
"filename": "kc-135.png"
},
"S-3B Tanker": {
"name": "S-3B Tanker",
"label": "S-3B Tanker",
"era": ["Early Cold War", "Mid Cold War", "Late Cold War", "Modern"],
"shortLabel": "S3B",
"loadouts": [
{
"fuel": 1,
"items": [
],
"roles": [
"Tanker"
],
"code": "",
"name": "Default Tanker"
}
],
"filename": "s-3.png"
},
"MiG-15bis": {
"name": "MiG-15bis",
"label": "MiG-15 Fagot",

View File

@@ -1,12 +1,14 @@
import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map } from 'leaflet';
import { getMap, getUnitsManager } from '..';
import { rad2deg } from '../other/utils';
import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, getUnits, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions, followUnit, setEmissionsCountermeasures } from '../server/server';
import { 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 { aircraftDatabase } from './aircraftdatabase';
import { groundUnitsDatabase } from './groundunitsdatabase';
import { CustomMarker } from '../map/custommarker';
import { SVGInjector } from '@tanem/svg-injector';
import { UnitDatabase } from './unitdatabase';
import { BOMBING, CARPET_BOMBING, FIRE_AT_AREA, IDLE, MOVE_UNIT } from '../map/map';
import { TargetMarker } from '../map/targetmarker';
var pathIcon = new Icon({
iconUrl: '/resources/theme/images/markers/marker-icon.png',
@@ -19,7 +21,7 @@ export class Unit extends CustomMarker {
#data: UnitData = {
baseData: {
AI: false,
controlled: false,
name: "",
unitName: "",
groupName: "",
@@ -37,7 +39,7 @@ export class Unit extends CustomMarker {
fuel: 0,
flags: {},
ammo: {},
targets: {},
contacts: {},
hasTask: false,
coalition: "",
},
@@ -48,10 +50,16 @@ export class Unit extends CustomMarker {
currentState: "NONE",
currentTask: "",
activePath: {},
targetSpeed: 0,
targetAltitude: 0,
desiredSpeed: 0,
desiredSpeedType: "GS",
desiredAltitude: 0,
desiredAltitudeType: "AGL",
targetLocation: {},
isTanker: false,
isAWACS: false,
onOff: true,
followRoads: false,
targetID: 0
},
optionsData: {
ROE: "",
@@ -72,8 +80,10 @@ export class Unit extends CustomMarker {
#pathMarkers: Marker[] = [];
#pathPolyline: Polyline;
#targetsPolylines: Polyline[];
#contactsPolylines: Polyline[];
#miniMapMarker: CircleMarker | null = null;
#targetLocationMarker: TargetMarker;
#targetLocationPolyline: Polyline;
#timer: number = 0;
@@ -103,7 +113,10 @@ export class Unit extends CustomMarker {
this.#pathPolyline = new Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1 });
this.#pathPolyline.addTo(getMap());
this.#targetsPolylines = [];
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) => {
@@ -116,8 +129,6 @@ export class Unit extends CustomMarker {
/* Set the unit data */
this.setData(data);
}
getMarkerCategory() {
@@ -150,11 +161,16 @@ export class Unit extends CustomMarker {
if ((this.getBaseData().alive || !selected) && this.getSelectable() && this.getSelected() != selected) {
this.#selected = selected;
this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-selected", selected);
if (selected)
if (selected) {
document.dispatchEvent(new CustomEvent("unitSelection", { detail: this }));
else
this.#updateMarker();
}
else {
document.dispatchEvent(new CustomEvent("unitDeselection", { detail: this }));
this.getGroupMembers().forEach((unit: Unit) => unit.setSelected(selected));
this.#clearDetectedUnits();
this.#clearPath();
this.#clearTarget();
}
}
}
@@ -201,50 +217,23 @@ export class Unit extends CustomMarker {
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);
var updateMarker = (positionChanged || headingChanged || aliveChanged || !getMap().hasLayer(this));
if (data.baseData != undefined) {
for (let key in this.#data.baseData)
if (key in data.baseData)
//@ts-ignore
this.#data.baseData[key] = data.baseData[key];
}
if (data.flightData != undefined) {
for (let key in this.#data.flightData)
if (key in data.flightData)
//@ts-ignore
this.#data.flightData[key] = data.flightData[key];
}
if (data.missionData != undefined) {
for (let key in this.#data.missionData)
if (key in data.missionData)
//@ts-ignore
this.#data.missionData[key] = data.missionData[key];
}
if (data.formationData != undefined) {
for (let key in this.#data.formationData)
if (key in data.formationData)
//@ts-ignore
this.#data.formationData[key] = data.formationData[key];
}
if (data.taskData != undefined) {
for (let key in this.#data.taskData)
if (key in data.taskData)
//@ts-ignore
this.#data.taskData[key] = data.taskData[key];
}
if (data.optionsData != undefined) {
for (let key in this.#data.optionsData)
if (key in data.optionsData)
//@ts-ignore
this.#data.optionsData[key] = data.optionsData[key];
}
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));
/* 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];
}
});
});
/* Fire an event when a unit dies */
if (aliveChanged && this.getBaseData().alive == false)
document.dispatchEvent(new CustomEvent("unitDeath", { detail: this }));
@@ -255,13 +244,17 @@ export class Unit extends CustomMarker {
if (updateMarker)
this.#updateMarker();
this.#clearTargets();
this.#clearDetectedUnits();
if (this.getSelected()) {
this.#drawPath();
this.#drawTargets();
this.#drawDetectedUnits();
this.#drawTarget();
}
else
else {
this.#clearPath();
this.#clearTarget();
}
document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this }));
}
@@ -400,7 +393,7 @@ export class Unit extends CustomMarker {
const hiddenUnits = getUnitsManager().getHiddenTypes();
if (this.getMissionData().flags.Human && hiddenUnits.includes("human"))
hidden = true;
else if (this.getBaseData().AI == false && hiddenUnits.includes("dcs"))
else if (this.getBaseData().controlled == false && hiddenUnits.includes("dcs"))
hidden = true;
else if (hiddenUnits.includes(this.getMarkerCategory()))
hidden = true;
@@ -431,6 +424,15 @@ export class Unit extends CustomMarker {
return getUnitsManager().getUnitByID(this.getFormationData().leaderID);
}
canRole(roles: string | string[]) {
if (typeof(roles) === "string")
roles = [roles];
return this.getDatabase()?.getByName(this.getBaseData().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) {
@@ -485,11 +487,21 @@ export class Unit extends CustomMarker {
setSpeed(this.ID, speed);
}
setSpeedType(speedType: string) {
if (!this.getMissionData().flags.Human)
setSpeedType(this.ID, speedType);
}
setAltitude(altitude: number) {
if (!this.getMissionData().flags.Human)
setAltitude(this.ID, altitude);
}
setAltitudeType(altitudeType: string) {
if (!this.getMissionData().flags.Human)
setAltitudeType(this.ID, altitudeType);
}
setROE(ROE: string) {
if (!this.getMissionData().flags.Human)
setROE(this.ID, ROE);
@@ -510,8 +522,18 @@ export class Unit extends CustomMarker {
setLeader(this.ID, isLeader, wingmenIDs);
}
delete() {
deleteUnit(this.ID);
setOnOff(onOff: boolean) {
if (!this.getMissionData().flags.Human)
setOnOff(this.ID, onOff);
}
setFollowRoads(followRoads: boolean) {
if (!this.getMissionData().flags.Human)
setFollowRoads(this.ID, followRoads);
}
delete(explosion: boolean) {
deleteUnit(this.ID, explosion);
}
refuel() {
@@ -524,6 +546,22 @@ export class Unit extends CustomMarker {
setAdvacedOptions(this.ID, isTanker, isAWACS, TACAN, radio, generalSettings);
}
bombPoint(latlng: LatLng) {
bombPoint(this.ID, latlng);
}
carpetBomb(latlng: LatLng) {
carpetBomb(this.ID, latlng);
}
bombBuilding(latlng: LatLng) {
bombBuilding(this.ID, latlng);
}
fireAtArea(latlng: LatLng) {
fireAtArea(this.ID, latlng);
}
/***********************************************/
onAdd(map: Map): this {
super.onAdd(map);
@@ -534,10 +572,9 @@ 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 (getMap().getState() === IDLE || getMap().getState() === MOVE_UNIT || e.originalEvent.ctrlKey) {
if (!e.originalEvent.ctrlKey)
getUnitsManager().deselectAllUnits();
}
this.setSelected(!this.getSelected());
}
}
@@ -554,20 +591,35 @@ export class Unit extends CustomMarker {
#onContextMenu(e: any) {
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"};
if (getUnitsManager().getSelectedUnits().length > 0 && !(getUnitsManager().getSelectedUnits().length == 1 && (getUnitsManager().getSelectedUnits().includes(this)))) {
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"};
if (getUnitsManager().getSelectedUnitsType() === "Aircraft")
if (getUnitsManager().getSelectedUnitsTypes().length == 1 && getUnitsManager().getSelectedUnitsTypes()[0] === "Aircraft")
options["follow"] = {text: "Follow", tooltip: "Follow the unit at a user defined distance and position"};;
}
else if ((getUnitsManager().getSelectedUnits().length > 0 && (getUnitsManager().getSelectedUnits().includes(this))) || getUnitsManager().getSelectedUnits().length == 0) {
else if ((selectedUnits.length > 0 && (selectedUnits.includes(this))) || selectedUnits.length == 0) {
if (this.getBaseData().category == "Aircraft") {
options["refuel"] = {text: "AAR 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
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.concat([this]).every((unit: Unit) => {return unit.canRole(["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.getBaseData().category == "GroundUnit") || selectedUnitTypes.length === 1 && ["GroundUnit"].includes(selectedUnitTypes[0])) {
if (selectedUnits.concat([this]).every((unit: Unit) => {return unit.canRole(["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) {
getMap().showUnitContextMenu(e);
getMap().getUnitContextMenu().setOptions(options, (option: string) => {
@@ -586,6 +638,12 @@ export class Unit extends CustomMarker {
getUnitsManager().selectedUnitsRefuel();
else if (action === "follow")
this.#showFollowOptions(e);
else if (action === "bomb")
getMap().setState(BOMBING);
else if (action === "carpet-bomb")
getMap().setState(CARPET_BOMBING);
else if (action === "fire-at-area")
getMap().setState(FIRE_AT_AREA);
}
#showFollowOptions(e: any) {
@@ -668,16 +726,18 @@ export class Unit extends CustomMarker {
/* Set current unit state */
if (this.getMissionData().flags.Human) // Unit is human
element.querySelector(".unit")?.setAttribute("data-state", "human");
else if (!this.getBaseData().AI) // Unit is under DCS control (not Olympus)
else if (!this.getBaseData().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)
element.querySelector(".unit")?.setAttribute("data-state", "no-task");
else // Unit is under Olympus control
element.querySelector(".unit")?.setAttribute("data-state", this.getTaskData().currentState.toLowerCase());
/* Set altitude and speed */
if (element.querySelector(".unit-altitude"))
(<HTMLElement>element.querySelector(".unit-altitude")).innerText = "FL" + String(Math.floor(this.getFlightData().altitude / 0.3048 / 100));
(<HTMLElement>element.querySelector(".unit-altitude")).innerText = "FL" + String(Math.floor(mToFt(this.getFlightData().altitude) / 100));
if (element.querySelector(".unit-speed"))
(<HTMLElement>element.querySelector(".unit-speed")).innerText = String(Math.floor(this.getFlightData().speed * 1.94384));
(<HTMLElement>element.querySelector(".unit-speed")).innerText = String(Math.floor(msToKnots(this.getFlightData().speed))) + "GS";
/* Rotate elements according to heading */
element.querySelectorAll("[data-rotate-to-heading]").forEach(el => {
@@ -768,35 +828,74 @@ export class Unit extends CustomMarker {
this.#pathPolyline.setLatLngs([]);
}
#drawTargets() {
for (let index in this.getMissionData().targets) {
var targetData = this.getMissionData().targets[index];
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)
#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)
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 });
targetPolyline.addTo(getMap());
this.#targetsPolylines.push(targetPolyline)
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)
}
}
}
}
#clearTargets() {
for (let index in this.#targetsPolylines) {
getMap().removeLayer(this.#targetsPolylines[index])
#clearDetectedUnits() {
for (let index in this.#contactsPolylines) {
getMap().removeLayer(this.#contactsPolylines[index])
}
}
#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));
}
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
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)])
}
#clearTarget() {
if (getMap().hasLayer(this.#targetLocationMarker))
this.#targetLocationMarker.removeFrom(getMap());
if (getMap().hasLayer(this.#targetLocationPolyline))
this.#targetLocationPolyline.removeFrom(getMap());
}
}
export class AirUnit extends Unit {
@@ -850,7 +949,7 @@ export class GroundUnit extends Unit {
showVvi: false,
showHotgroup: true,
showUnitIcon: true,
showShortLabel: true,
showShortLabel: false,
showFuel: false,
showAmmo: false,
showSummary: false,