Reorganized client source code

Removed many useless and old classes
Started transition to new CSS
Added URIs for specific REST requests
This commit is contained in:
Pax1601
2023-03-05 21:29:58 +01:00
parent ed2343c704
commit 360c85b563
228 changed files with 1088 additions and 3635 deletions

View File

@@ -26,4 +26,10 @@ declare global {
}
}
export interface ContextMenuOption {
tooltip: string;
src: string;
callback: CallableFunction;
}
export { };

19
client/src/@types/unitdatabase.d.ts vendored Normal file
View File

@@ -0,0 +1,19 @@
interface LoadoutItemBlueprint {
name: string;
quantity: number;
}
interface LoadoutBlueprint {
fuel: number;
items: LoadoutItemBlueprint[];
roles: string[];
code: string;
name: string;
}
interface UnitBlueprint {
name: string;
label: string;
shortLabel: string;
loadouts: LoadoutBlueprint[];
}

View File

@@ -1,38 +0,0 @@
export class Button {
#container: HTMLElement | null;
#srcs: string[];
#callback: CallableFunction;
#img: any;
#state: number = 0;
constructor(ID: string, srcs: string[], callback: CallableFunction) {
this.#container = document.getElementById(ID);
this.#srcs = srcs;
this.#callback = callback;
if (this.#container != null) {
this.#img = document.createElement("img");
this.#img.src = this.#srcs[this.#state];
this.#container.appendChild(this.#img);
this.#container.addEventListener("click", () => this.#onClick());
}
}
setState(state: number) {
if (state < this.#srcs.length) {
this.#state = state;
this.#img.src = this.#srcs[this.#state];
}
}
getState() {
return this.#state;
}
#onClick() {
if (this.#img != null) {
this.setState(this.#state < this.#srcs.length - 1 ? this.#state + 1 : 0);
if (this.#callback)
this.#callback(this.#state);
}
}
}

View File

@@ -1,61 +1,33 @@
import { LatLng } from "leaflet";
import { getActiveCoalition, setActiveCoalition } from "..";
import { ContextMenuOption } from "../@types/dom";
export class ContextMenu {
#container: HTMLElement | null;
#display: string;
constructor(id: string,) {
this.#container = document.getElementById(id);
this.#display = '';
if (this.#container != null) {
this.#container.querySelector("#coalition-switch")?.addEventListener('change', (e) => this.#onSwitch(e))
this.#display = this.#container.style.display;
this.hide();
}
this.#container?.querySelector("#switch")?.addEventListener('change', (e) => this.#onSwitch(e))
this.hide();
}
show(x: number, y: number, title: string, options: any, callback: CallableFunction, showCoalition: boolean) {
/* Hide to remove buttons, if present */
this.hide();
show(x: number, y: number, title: string, options: ContextMenuOption[], showCoalition: boolean) {
this.#container?.classList.toggle("hide", false);
this.#container?.querySelector("#list")?.replaceChildren(...options.map((option: ContextMenuOption) => {
var li = document.createElement("li");
var button = document.createElement("button");
button.textContent = option.tooltip;
li.appendChild(button);
button.addEventListener("click", (e: MouseEvent) => option.callback((e.target as HTMLButtonElement).innerText));
return button;
}));
this.#container?.querySelector("#switch")?.classList.toggle("hide", !showCoalition);
if (this.#container != null && options.length >= 1) {
var titleDiv = this.#container.querySelector("#ol-selection-scroll-top-bar")?.querySelector(".ol-selection-scroll-title");
var titleDiv = this.#container.querySelector("#title");
if (titleDiv)
titleDiv.innerHTML = title;
this.#container.style.display = this.#display;
var scroll = this.#container.querySelector(".ol-selection-scroll");
if (scroll != null)
{
for (let optionID in options) {
var node = document.createElement("div");
node.classList.add("ol-selection-scroll-element");
if (typeof options[optionID] === 'string' || options[optionID] instanceof String){
node.appendChild(document.createTextNode(options[optionID]));
node.addEventListener('click', () => callback(options[optionID]));
}
else {
node.appendChild(document.createTextNode(options[optionID].tooltip));
node.addEventListener('click', () => options[optionID].callback());
}
scroll.appendChild(node);
}
}
/* Hide the coalition switch if required */
var switchContainer = <HTMLElement>this.#container.querySelector("#ol-selection-scroll-top-bar")?.querySelector("#coalition-switch-container");
if (showCoalition == false) {
switchContainer.style.display = "none";
document.documentElement.style.setProperty('--active-coalition-color', getComputedStyle(this.#container).getPropertyValue("--neutral-coalition-color"));
}
else {
switchContainer.style.display = "block";
if (getActiveCoalition() == "blue")
document.documentElement.style.setProperty('--active-coalition-color', getComputedStyle(this.#container).getPropertyValue("--blue-coalition-color"));
else
document.documentElement.style.setProperty('--active-coalition-color', getComputedStyle(this.#container).getPropertyValue("--red-coalition-color"));
}
titleDiv.textContent = title;
if (x - this.#container.offsetWidth / 2 + this.#container.offsetWidth < window.innerWidth)
this.#container.style.left = x - this.#container.offsetWidth / 2 + "px";
@@ -66,34 +38,19 @@ export class ContextMenu {
this.#container.style.top = y - 20 + "px";
else
this.#container.style.top = window.innerHeight - this.#container.offsetHeight + "px";
}
}
hide() {
if (this.#container != null) {
this.#container.style.display = "none";
var buttons = this.#container.querySelectorAll(".ol-selection-scroll-element");
var scroll = this.#container.querySelector(".ol-selection-scroll");
if (scroll != null)
{
for (let child of buttons) {
scroll.removeChild(child);
}
}
}
this.#container?.classList.toggle("hide", true);
}
#onSwitch(e: any) {
if (this.#container != null) {
if (e.currentTarget.checked) {
document.documentElement.style.setProperty('--active-coalition-color', getComputedStyle(this.#container).getPropertyValue("--red-coalition-color"));
if (e.currentTarget.checked)
setActiveCoalition("red");
}
else {
document.documentElement.style.setProperty('--active-coalition-color', getComputedStyle(this.#container).getPropertyValue("--blue-coalition-color"));
else
setActiveCoalition("blue");
}
}
}
}

View File

@@ -1,61 +0,0 @@
export class Dropdown {
#container: HTMLElement | null;
#options: string[];
#open?: boolean;
#content?: HTMLElement;
#callback?: CallableFunction;
constructor(ID: string, options: string[], callback: CallableFunction) {
this.#container = document.getElementById(ID);
this.#options = options;
this.#callback = callback;
this.close()
this.#container?.addEventListener("click", () => {
this.#open ? this.close() : this.open();
})
if (this.#container != null && this.#options.length > 0)
this.#container.innerHTML = this.#options[0];
}
open() {
if (this.#container != null) {
this.#open = true;
this.#container.classList.add("ol-dropdown-open");
this.#container.classList.remove("ol-dropdown-closed");
this.#content = document.createElement("div");
this.#content.classList.add("ol-dropdown-content");
this.#content.style.width = (this.#container.offsetWidth - this.#container.offsetHeight) + "px";
this.#content.style.left = this.#container.offsetLeft + "px";
this.#content.style.top = this.#container.offsetTop + this.#container.offsetHeight + "px";
document.body.appendChild(this.#content);
var height = 2;
for (let optionID in this.#options) {
var node = document.createElement("div");
node.classList.add("ol-dropdown-element");
node.appendChild(document.createTextNode(this.#options[optionID]));
this.#content.appendChild(node);
height += node.offsetHeight + 2;
node.addEventListener('click', () => {
this.close();
if (this.#container != null)
this.#container.innerHTML = this.#options[optionID];
if (this.#callback != null)
this.#callback(this.#options[optionID])
})
}
this.#content.style.height = height + "px";
}
}
close() {
if (this.#container != null) {
this.#open = false;
this.#container?.classList.remove("ol-dropdown-open");
this.#container?.classList.add("ol-dropdown-closed");
if (this.#content != null)
document.body.removeChild(this.#content);
}
}
}

View File

@@ -64,7 +64,7 @@ export class Slider {
{
this.#container.classList.toggle("active", newActive);
if (!newActive && this.#value != null)
this.#value.innerHTML = "Mixed values"
this.#value.innerText = "Mixed values";
}
}

View File

@@ -3,19 +3,14 @@ import { getDataFromDCS } from "./server/server"
import { UnitsManager } from "./units/unitsmanager";
import { UnitInfoPanel } from "./panels/unitinfopanel";
import { ContextMenu } from "./controls/contextmenu";
import { Dropdown } from "./controls/dropdown";
import { ConnectionStatusPanel } from "./panels/connectionstatuspanel";
import { MissionData } from "./missiondata/missiondata";
import { UnitControlPanel } from "./panels/unitcontrolpanel";
import { MouseInfoPanel } from "./panels/mouseinfopanel";
import { Slider } from "./controls/slider";
import { AIC } from "./aic/aic";
import { VisibilityControlPanel } from "./panels/visibilitycontrolpanel";
import { ATC } from "./atc/ATC";
import { FeatureSwitches } from "./FeatureSwitches";
import { LogPanel } from "./panels/logpanel";
import { Button } from "./controls/button";
var map: Map;
var contextMenu: ContextMenu;
@@ -23,24 +18,18 @@ var contextMenu: ContextMenu;
var unitsManager: UnitsManager;
var missionData: MissionData;
var aic: AIC;
var atc: ATC;
var unitInfoPanel: UnitInfoPanel;
var connectionStatusPanel: ConnectionStatusPanel;
var unitControlPanel: UnitControlPanel;
var mouseInfoPanel: MouseInfoPanel;
var visibilityControlPanel: VisibilityControlPanel;
var logPanel: LogPanel;
var mapSourceDropdown: Dropdown;
var aic: AIC;
var aicToggleButton: Button;
var aicHelpButton: Button;
var atc: ATC;
var atcToggleButton: Button;
var connected: boolean;
var activeCoalition: string;
var connected: boolean = false;
var activeCoalition: string = "blue";
var refreshData: boolean = true;
var featureSwitches;
@@ -53,86 +42,57 @@ function setup() {
unitsManager = new UnitsManager();
missionData = new MissionData();
contextMenu = new ContextMenu("selection-scroll");
contextMenu = new ContextMenu("contextmenu");
unitInfoPanel = new UnitInfoPanel("unit-info-panel");
unitControlPanel = new UnitControlPanel("unit-control-panel");
mapSourceDropdown = new Dropdown("map-source-dropdown", map.getLayers(), (option: string) => map.setLayer(option));
connectionStatusPanel = new ConnectionStatusPanel("connection-status-panel");
mouseInfoPanel = new MouseInfoPanel("mouse-info-panel");
visibilityControlPanel = new VisibilityControlPanel("visibility-control-panel");
logPanel = new LogPanel("log-panel");
//logPanel = new LogPanel("log-panel");
missionData = new MissionData();
/* AIC */
let aicFeatureSwitch = featureSwitches.getSwitch( "aic" );
if ( aicFeatureSwitch?.isEnabled() ) {
aic = new AIC();
aicToggleButton = new Button( "toggle-aic-button", ["images/buttons/radar.svg"], () => {
aic.toggleStatus();
});
aicHelpButton = new Button( "aic-help-button", [ "images/buttons/question-mark.svg" ], () => {
aic.toggleHelp();
});
// TODO: add back buttons
}
/* Generic clicks */
document.addEventListener( "click", ( ev ) => {
if ( ev instanceof PointerEvent && ev.target instanceof HTMLElement ) {
if ( ev.target.classList.contains( "olympus-dialog-close" ) ) {
ev.target.closest( "div.olympus-dialog" )?.classList.add( "hide" );
}
}
});
/*** ATC ***/
/* ATC */
let atcFeatureSwitch = featureSwitches.getSwitch( "atc" );
if ( atcFeatureSwitch?.isEnabled() ) {
atc = new ATC();
atcToggleButton = new Button( "atc-toggle-button", [ "images/buttons/atc.svg" ], () => {
atc.toggleStatus();
} );
// TODO: add back buttons
}
mapSourceDropdown = new Dropdown("map-source-dropdown", map.getLayers(), (option: string) => map.setLayer(option));
/* Default values */
activeCoalition = "blue";
connected = false;
/* On the first connection, force request of full data */
requestUpdate();
refreshData = false;
}
function requestUpdate() {
getDataFromDCS(update);
getDataFromDCS(refreshData, update);
/* Main update rate = 250ms is minimum time, equal to server update time. */
setTimeout(() => requestUpdate(), getConnected() ? 250 : 1000);
connectionStatusPanel.update(getConnected());
getConnectionStatusPanel()?.update(getConnected());
}
export function update(data: ServerData) {
unitsManager.update(data);
missionData.update(data);
logPanel.update(data);
getUnitsManager()?.update(data);
getMissionData()?.update(data);
getLogPanel()?.update(data);
}
export function getMap() {
@@ -163,6 +123,14 @@ export function getMouseInfoPanel() {
return mouseInfoPanel;
}
export function getLogPanel() {
return logPanel;
}
export function getConnectionStatusPanel() {
return connectionStatusPanel;
}
export function setActiveCoalition(newActiveCoalition: string) {
activeCoalition = newActiveCoalition;
}

View File

@@ -5,6 +5,7 @@ import { bearing, distance, zeroAppend } from "../other/utils";
import { aircraftDatabase } from "../units/aircraftdatabase";
import { unitTypes } from "../units/unittypes";
import { BoxSelect } from "./boxselect";
import { ContextMenuOption } from "../@types/dom";
L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect);
@@ -126,16 +127,16 @@ export class Map extends L.Map {
return this.#state;
}
/* Selection scroll */
showContextMenu(e: ClickEvent | SpawnEvent, title: string, options: any, callback: CallableFunction, showCoalition: boolean = false) {
/* Context Menu */
showContextMenu(e: ClickEvent | SpawnEvent, title: string, options: ContextMenuOption[], showCoalition: boolean = false) {
var x = e.x;
var y = e.y;
getContextMenu().show(x, y, title, options, callback, showCoalition);
getContextMenu()?.show(x, y, title, options, showCoalition);
document.dispatchEvent(new CustomEvent("mapContextMenu"));
}
hideContextMenu() {
getContextMenu().hide();
getContextMenu()?.hide();
document.dispatchEvent(new CustomEvent("mapContextMenu"));
}
@@ -195,7 +196,7 @@ export class Map extends L.Map {
{ "tooltip": "Smoke", "src": "spawnSmoke.png", "callback": () => this.#smokeSpawnMenu(spawnEvent) },
//{ "tooltip": "Explosion", "src": "spawnExplosion.png", "callback": () => this.#explosionSpawnMenu(e) }
]
this.showContextMenu(spawnEvent, "Action", options, () => {}, false);
this.showContextMenu(spawnEvent, "Action", options, false);
}
}
else if (this.#state === "MOVE_UNIT") {
@@ -240,7 +241,7 @@ export class Map extends L.Map {
{
selectedUnitPosition = new L.LatLng(selectedUnits[0].getFlightData().latitude, selectedUnits[0].getFlightData().longitude);
}
getMouseInfoPanel().update(<L.LatLng>e.latlng, this.#measurePoint, selectedUnitPosition);
getMouseInfoPanel()?.update(<L.LatLng>e.latlng, this.#measurePoint, selectedUnitPosition);
this.#lastMousePosition.x = e.originalEvent.x;
this.#lastMousePosition.y = e.originalEvent.y;
@@ -272,9 +273,9 @@ export class Map extends L.Map {
{ 'coalition': true, 'tooltip': 'Transport', 'src': 'spawnTransport.png', 'callback': () => this.#selectAircraft(e, "transport") },
]
if (e.airbaseName != null)
this.showContextMenu(e, "Spawn at " + e.airbaseName, options, () => {}, true);
this.showContextMenu(e, "Spawn at " + e.airbaseName, options, true);
else
this.showContextMenu(e, "Spawn air unit", options, () => {}, true);
this.showContextMenu(e, "Spawn air unit", options, true);
}
#groundUnitSpawnMenu(e: SpawnEvent) {
@@ -287,7 +288,7 @@ export class Map extends L.Map {
{'coalition': true, 'tooltip': 'Radar', 'src': 'spawnRadar.png', 'callback': () => this.#selectGroundUnit(e, "Radar")},
{'coalition': true, 'tooltip': 'Unarmed', 'src': 'spawnUnarmed.png', 'callback': () => this.#selectGroundUnit(e, "Unarmed")}
]
this.showContextMenu(e, "Spawn ground unit", options, () => {}, true);
this.showContextMenu(e, "Spawn ground unit", options, true);
}
#smokeSpawnMenu(e: SpawnEvent) {
@@ -299,7 +300,7 @@ export class Map extends L.Map {
{'tooltip': 'Green smoke', 'src': 'spawnSmoke.png', 'callback': () => {this.hideContextMenu(); spawnSmoke('green', e.latlng)}, 'tint': 'green'},
{'tooltip': 'Orange smoke', 'src': 'spawnSmoke.png', 'callback': () => {this.hideContextMenu(); spawnSmoke('orange', e.latlng)}, 'tint': 'orange'},
]
this.showContextMenu(e, "Spawn smoke", options, () => {}, false);
this.showContextMenu(e, "Spawn smoke", options, false);
}
#explosionSpawnMenu(e: SpawnEvent) {
@@ -310,12 +311,14 @@ export class Map extends L.Map {
#selectAircraft(e: SpawnEvent, role: string) {
this.hideContextMenu();
var options = aircraftDatabase.getLabelsByRole(role);
this.showContextMenu(e, "Select aircraft", options, (label: string) => {
this.hideContextMenu();
var name = aircraftDatabase.getNameByLabel(label);
if (name != null)
this.#unitSelectPayload(e, name, role);
}, true);
this.showContextMenu(e, "Select aircraft",
options.map((option: string) => {
return {tooltip: option, src: "", callback: (label: string) => {
this.hideContextMenu();
var name = aircraftDatabase.getNameByLabel(label);
if (name != null)
this.#unitSelectPayload(e, name, role);
}}}), true);
}
/* Show weapon selection for air units */
@@ -325,11 +328,13 @@ export class Map extends L.Map {
//options = payloadNames[unitType]
if (options != undefined && options.length > 0) {
options.sort();
this.showContextMenu({x: e.x, y: e.y, latlng: e.latlng}, "Select loadout", options, (loadoutName: string) => {
this.hideContextMenu();
var loadout = aircraftDatabase.getLoadoutsByName(unitType, loadoutName);
spawnAircraft(unitType, e.latlng, getActiveCoalition(), loadout != null? loadout.code: "", e.airbaseName);
}, true);
this.showContextMenu({x: e.x, y: e.y, latlng: e.latlng}, "Select loadout",
options.map((option: string) => {
return {tooltip: option, src: "", callback: (loadoutName: string) => {
this.hideContextMenu();
var loadout = aircraftDatabase.getLoadoutsByName(unitType, loadoutName);
spawnAircraft(unitType, e.latlng, getActiveCoalition(), loadout != null? loadout.code: "", e.airbaseName);
}}}), true);
}
else {
spawnAircraft(unitType, e.latlng, getActiveCoalition());
@@ -342,10 +347,12 @@ export class Map extends L.Map {
this.hideContextMenu();
var options = unitTypes.vehicles[group];
options.sort();
this.showContextMenu(e, "Select ground unit", options, (unitType: string) => {
this.hideContextMenu();
spawnGroundUnit(unitType, e.latlng, getActiveCoalition());
}, true);
this.showContextMenu(e, "Select ground unit",
options.map((option: string) => {
return {tooltip: option, src: "", callback: (unitType: string) => {
this.hideContextMenu();
spawnGroundUnit(unitType, e.latlng, getActiveCoalition());
}}}), true);
}
#drawMeasureLine()

View File

@@ -7,7 +7,7 @@ export interface AirbaseOptions
src: string
}
export class AirbaseMarker extends L.Marker
export class Airbase extends L.Marker
{
#name: string = "";
#coalitionID: number = -1;

View File

@@ -1,7 +1,7 @@
import { Marker, LatLng, Icon } from "leaflet";
import { getMap, getUnitsManager } from "..";
import { SpawnEvent } from "../map/map";
import { AirbaseMarker } from "./airbasemarker";
import { Airbase } from "./airbase";
var bullseyeIcons = [
new Icon({ iconUrl: 'images/bullseye0.png', iconAnchor: [30, 30]}),
@@ -14,7 +14,7 @@ export class MissionData
#bullseyes : any; //TODO declare interface
#bullseyeMarkers: any;
#airbases : any; //TODO declare interface
#airbasesMarkers: {[name: string]: AirbaseMarker};
#airbasesMarkers: {[name: string]: Airbase};
constructor()
{
@@ -59,7 +59,7 @@ export class MissionData
var airbase = this.#airbases[idx]
if (this.#airbasesMarkers[idx] === undefined)
{
this.#airbasesMarkers[idx] = new AirbaseMarker({
this.#airbasesMarkers[idx] = new Airbase({
position: new LatLng(airbase.lat, airbase.lng),
name: airbase.callsign,
src: "images/airbase.png"}).addTo(getMap());
@@ -79,8 +79,10 @@ export class MissionData
options = ["Spawn unit", "Land here"];
else
options = ["Spawn unit"];
getMap().showContextMenu(e.originalEvent, e.sourceTarget.getName(), options, (option: string) => this.#onAirbaseOptionSelection(e, option), false);
getMap().showContextMenu(e.originalEvent, e.sourceTarget.getName(),
options.map((option) => {return {tooltip: option, src: "", callback: (label: string) => {this.#onAirbaseOptionSelection(e, label)}}}, false)
)
}
#onAirbaseOptionSelection(e: any, option: string) {

View File

@@ -1,19 +1,16 @@
export class Panel {
#element: HTMLElement
#display: string;
constructor(ID: string) {
this.#element = <HTMLElement>document.getElementById(ID);
this.#display = '';
this.#display = this.#element.style.display;
}
show() {
this.#element.style.display = this.#display;
this.#element.classList.toggle("hide", false);
}
hide() {
this.#element.style.display = "none";
this.#element.classList.toggle("hide", true);
}
getElement() {

View File

View File

@@ -3,66 +3,42 @@ import { Slider } from "../controls/slider";
import { Aircraft, AirUnit, GroundUnit, Helicopter, NavyUnit, Unit } from "../units/unit";
import { Panel } from "./panel";
interface Button {
id: string,
value: string,
element: null | HTMLElement
}
var ROEs: string[] = ["Free", "Designated free", "Designated", "Return", "Hold"];
var reactionsToThreat: string[] = [ "None", "Passive", "Evade", "Escape", "Abort"];
var minSpeedValues: {[key: string]: number} = {Aircraft: 100, Helicopter: 0, NavyUnit: 0, GroundUnit: 0};
var maxSpeedValues: {[key: string]: number} = {Aircraft: 800, Helicopter: 300, NavyUnit: 60, GroundUnit: 60};
var minAltitudeValues: {[key: string]: number} = {Aircraft: 500, Helicopter: 0, NavyUnit: 0, GroundUnit: 0};
var maxAltitudeValues: {[key: string]: number} = {Aircraft: 50000, Helicopter: 10000, NavyUnit: 60, GroundUnit: 60};
export class UnitControlPanel extends Panel {
#altitudeSlider: Slider;
#airspeedSlider: Slider;
#formationCreationContainer: HTMLElement;
#ROEButtonsContainer: HTMLElement;
#reactionToThreatButtonsContainer: HTMLElement;
#selectedUnitsContainer: HTMLElement;
#ROEButtons: Button[] = [
{id: "#free", value: "Free", element: null},
{id: "#designated-free", value: "Designated free", element: null},
{id: "#designated", value: "Designated", element: null},
{id: "#return", value: "Return", element: null},
{id: "#hold", value: "Hold", element: null}
]
#reactionToThreatButtons: Button[] = [
{id: "#none", value: "None", element: null},
{id: "#passive", value: "Passive", element: null},
{id: "#evade", value: "Evade", element: null},
{id: "#escape", value: "Escape", element: null},
{id: "#abort", value: "Abort", element: null}
]
#optionButtons: {[key: string]: HTMLButtonElement[]} = {}
constructor(ID: string) {
super(ID);
/* Selected units container */
this.#selectedUnitsContainer = <HTMLElement>(this.getElement().querySelector("#selected-units-container"));
/* Unit control sliders */
this.#altitudeSlider = new Slider("altitude-slider", 0, 100, "ft", (value: number) => getUnitsManager().selectedUnitsSetAltitude(value * 0.3048));
this.#airspeedSlider = new Slider("airspeed-slider", 0, 100, "kts", (value: number) => getUnitsManager().selectedUnitsSetSpeed(value / 1.94384));
/* Formation control buttons */
this.#formationCreationContainer = <HTMLElement>(this.getElement().querySelector("#formation-creation-container"));
//var createButton = <HTMLElement>this.#formationCreationContainer.querySelector("#create-formation");
//createButton?.addEventListener("click", () => getUnitsManager().selectedUnitsCreateFormation());
//var undoButton = <HTMLElement>this.#formationCreationContainer.querySelector("#undo-formation");
//undoButton?.addEventListener("click", () => getUnitsManager().selectedUnitsUndoFormation());
/* ROE buttons */
this.#ROEButtonsContainer = <HTMLElement>(this.getElement().querySelector("#roe-buttons-container"));
for (let button of this.#ROEButtons)
{
button.element = <HTMLElement>(this.#ROEButtonsContainer.querySelector(button.id));
button.element?.addEventListener("click", () => getUnitsManager().selectedUnitsSetROE(button.value));
}
/* Reaction to threat buttons */
this.#reactionToThreatButtonsContainer = <HTMLElement>(this.getElement().querySelector("#reaction-to-threat-buttons-container"));
for (let button of this.#reactionToThreatButtons)
{
button.element = <HTMLElement>(this.#reactionToThreatButtonsContainer.querySelector(button.id));
button.element?.addEventListener("click", () => getUnitsManager().selectedUnitsSetReactionToThreat(button.value));
}
/* Option buttons */
this.#optionButtons["ROE"] = ROEs.map((option: string) => {
var button = document.createElement("button");
button.innerText = option;
button.addEventListener("click", () => getUnitsManager().selectedUnitsSetROE(button.value));
return button;
})
this.#optionButtons["reactionToThreat"] = reactionsToThreat.map((option: string) => {
var button = document.createElement("button");
button.innerText = option;
button.addEventListener("click", () => getUnitsManager().selectedUnitsSetROE(button.value));
return button;
})
this.getElement().querySelector("#roe-buttons-container")?.append(...this.#optionButtons["ROE"]);
this.getElement().querySelector("#reaction-to-threat-buttons-container")?.append(...this.#optionButtons["reactionToThreat"]);
this.hide();
}
@@ -70,16 +46,22 @@ export class UnitControlPanel extends Panel {
update(units: Unit[]) {
if (this.getElement() != null)
{
this.#addUnitsButtons(units);
//this.#showFormationButtons(units);
this.#showFlightControlSliders(units);
this.getElement().querySelector("#selected-units-container")?.replaceChildren(...units.map((unit: Unit) =>
{
var button = document.createElement("button");
button.innerText = unit.getData().unitName;
button.addEventListener("click", () => getUnitsManager().selectUnit(unit.ID, true));
return (button);
}));
for (let button of this.#ROEButtons)
button.element?.classList.toggle("white", this.#getROE(units) === button.value);
for (let button of this.#reactionToThreatButtons)
button.element?.classList.toggle("white", this.#getReactionToThreat(units) === button.value);
this.#optionButtons["ROE"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("active", units.every((unit: Unit) => unit.getOptionsData().ROE === button.value))
});
this.#optionButtons["reactionToThreat"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("active", units.every((unit: Unit) => unit.getOptionsData().reactionToThreat === button.value))
});
}
}
@@ -88,263 +70,18 @@ export class UnitControlPanel extends Panel {
this.#airspeedSlider.show();
this.#altitudeSlider.show();
if (this.#checkAllUnitsAircraft(units))
{
this.#airspeedSlider.setMinMax(100, 600);
this.#altitudeSlider.setMinMax(0, 50000);
}
else if (this.#checkAllUnitsHelicopter(units))
{
this.#airspeedSlider.setMinMax(0, 200);
this.#altitudeSlider.setMinMax(0, 10000);
}
else if (this.#checkAllUnitsGroundUnit(units))
{
this.#airspeedSlider.setMinMax(0, 60);
this.#altitudeSlider.hide();
}
else if (this.#checkAllUnitsNavyUnit(units))
{
this.#airspeedSlider.setMinMax(0, 60);
this.#altitudeSlider.hide();
}
else {
this.#airspeedSlider.hide();
this.#altitudeSlider.hide();
}
var unitsType = getUnitsManager().getSelectedUnitsType();
var targetAltitude = getUnitsManager().getSelectedUnitsTargetAltitude();
var targetSpeed = getUnitsManager().getSelectedUnitsTargetSpeed();
var targetSpeed = this.#getTargetAirspeed(units);
if (targetSpeed != null)
{
this.#airspeedSlider.setActive(true);
this.#airspeedSlider.setValue(targetSpeed * 1.94384);
}
else
{
this.#airspeedSlider.setActive(false);
}
if (["GroundUnit", "NavyUnit"].includes(unitsType))
this.#altitudeSlider.hide()
var targetAltitude = this.#getTargetAltitude(units);
if (targetAltitude != null)
{
this.#altitudeSlider.setActive(true);
this.#altitudeSlider.setValue(targetAltitude / 0.3048);
}
else
{
this.#altitudeSlider.setActive(false);
}
}
#addUnitsButtons(units: Unit[])
{
/* Remove any pre-existing unit button */
var elements = this.#selectedUnitsContainer.getElementsByClassName("js-unit-container");
while (elements.length > 0)
this.#selectedUnitsContainer.removeChild(elements[0])
/* Create all the units buttons */
for (let unit of units)
{
this.#addUnitButton(unit, this.#selectedUnitsContainer);
if (unit.getFormationData().isLeader)
for (let wingman of unit.getWingmen())
this.#addUnitButton(wingman, this.#selectedUnitsContainer);
}
}
#addUnitButton(unit: Unit, container: HTMLElement)
{
var el = document.createElement("div");
/* Unit name (actually type, but DCS calls it name for some reason) */
var nameDiv = document.createElement("div");
nameDiv.classList.add("ol-rounded-container-small");
if (unit.getData().name.length >= 7)
nameDiv.innerHTML = `${unit.getData().name.substring(0, 4)} ...`;
else
nameDiv.innerHTML = `${unit.getData().name}`;
/* Unit icon */
var icon = document.createElement("img");
if (unit.getFormationData().isLeader)
icon.src = "images/icons/formation.png"
else if (unit.getFormationData().isWingman)
{
var wingmen = unit.getLeader()?.getWingmen();
if (wingmen && wingmen.lastIndexOf(unit) == wingmen.length - 1)
icon.src = "images/icons/formation-end.svg"
else
icon.src = "images/icons/formation-middle.svg"
}
else
icon.src = "images/icons/singleton.png"
el.innerHTML = unit.getData().unitName;
el.prepend(nameDiv);
/* Show the icon only for air units */
if ((unit instanceof AirUnit))
el.append(icon);
el.classList.add("ol-rounded-container", "js-unit-container");
if (!unit.getSelected())
el.classList.add("not-selected")
/* Set background color */
el.classList.toggle("red", unit.getMissionData().coalition === "red");
icon.classList.toggle("red", unit.getMissionData().coalition === "red");
el.classList.toggle("blue", unit.getMissionData().coalition === "blue");
icon.classList.toggle("blue", unit.getMissionData().coalition === "blue");
el.classList.toggle("neutral", unit.getMissionData().coalition === "neutral");
icon.classList.toggle("neutral", unit.getMissionData().coalition === "neutral");
el.addEventListener("click", () => getUnitsManager().selectUnit(unit.ID));
container.appendChild(el);
}
#showFormationButtons(units: Unit[])
{
var createButton = <HTMLElement>this.#formationCreationContainer.querySelector("#create-formation");
var undoButton = <HTMLElement>this.#formationCreationContainer.querySelector("#undo-formation");
if (createButton && undoButton && this.#checkAllUnitsAir(units))
{
if (!this.#checkUnitsAlreadyInFormation(units))
{
createButton.style.display = '';
undoButton.style.display = 'none';
}
else if (this.#checkUnitsAlreadyInFormation(units) && this.#checkAllUnitsSameFormation(units))
{
createButton.style.display = 'none';
undoButton.style.display = '';
}
else
{
createButton.style.display = 'none';
undoButton.style.display = 'none';
}
}
}
#checkAllUnitsAir(units: Unit[])
{
for (let unit of units)
if (!(unit instanceof AirUnit))
return false
return true
}
#checkAllUnitsAircraft(units: Unit[])
{
for (let unit of units)
if (!(unit instanceof Aircraft))
return false
return true
}
#checkAllUnitsHelicopter(units: Unit[])
{
for (let unit of units)
if (!(unit instanceof Helicopter))
return false
return true
}
#checkAllUnitsGroundUnit(units: Unit[])
{
for (let unit of units)
if (!(unit instanceof GroundUnit))
return false
return true
}
#checkAllUnitsNavyUnit(units: Unit[])
{
for (let unit of units)
if (!(unit instanceof NavyUnit))
return false
return true
}
#checkAllUnitsSameFormation(units: Unit[])
{
var leaderFound = false;
for (let unit of units)
{
if (unit.getFormationData().isLeader)
{
if (leaderFound)
return false
else
leaderFound = true;
}
if (!unit.getFormationData().isLeader)
return false
}
return true
}
#checkUnitsAlreadyInFormation(units: Unit[])
{
for (let unit of units)
if (unit.getFormationData().isLeader)
return true
return false
}
#getTargetAirspeed(units: Unit[])
{
var airspeed = null;
for (let unit of units)
{
if (unit.getTaskData().targetSpeed != airspeed && airspeed != null)
return null
else
airspeed = unit.getTaskData().targetSpeed;
}
return airspeed;
}
#getTargetAltitude(units: Unit[])
{
var altitude = null;
for (let unit of units)
{
if (unit.getTaskData().targetAltitude != altitude && altitude != null)
return null
else
altitude = unit.getTaskData().targetAltitude;
}
return altitude;
}
#getROE(units: Unit[])
{
var ROE = null;
for (let unit of units)
{
if (unit.getOptionsData().ROE !== ROE && ROE != null)
return null
else
ROE = unit.getOptionsData().ROE;
}
return ROE;
}
#getReactionToThreat(units: Unit[])
{
var reactionToThreat = null;
for (let unit of units)
{
if (unit.getOptionsData().reactionToThreat !== reactionToThreat && reactionToThreat != null)
return null
else
reactionToThreat = unit.getOptionsData().reactionToThreat;
}
return reactionToThreat;
this.#airspeedSlider.setMinMax(minSpeedValues[unitsType], maxSpeedValues[unitsType]);
this.#altitudeSlider.setMinMax(minAltitudeValues[unitsType], maxAltitudeValues[unitsType]);
this.#airspeedSlider.setActive(targetSpeed != undefined);
this.#airspeedSlider.setValue(targetSpeed * 1.94384);
this.#altitudeSlider.setActive(targetAltitude != undefined);
this.#altitudeSlider.setValue(targetAltitude / 0.3048);
}
}

View File

@@ -36,16 +36,16 @@ export class UnitInfoPanel extends Panel {
update(unit: Unit) {
if (this.getElement() != null) {
/* Set the unit info */
this.#unitName.innerHTML = unit.getData().unitName;
this.#groupName.innerHTML = unit.getData().groupName;
this.#name.innerHTML = unit.getData().name;
this.#heading.innerHTML = String(Math.floor(rad2deg(unit.getFlightData().heading)) + " °");
this.#altitude.innerHTML = String(Math.floor(unit.getFlightData().altitude / 0.3048) + " ft");
this.#groundSpeed.innerHTML = String(Math.floor(unit.getFlightData().speed * 1.94384) + " kts");
this.#fuel.innerHTML = String(unit.getMissionData().fuel + "%");
this.#latitude.innerHTML = ConvertDDToDMS(unit.getFlightData().latitude, false);
this.#longitude.innerHTML = ConvertDDToDMS(unit.getFlightData().longitude, true);
this.#task.innerHTML = unit.getTaskData().currentTask !== ""? unit.getTaskData().currentTask: "No task";
this.#unitName.innerText = unit.getData().unitName;
this.#groupName.innerText = unit.getData().groupName;
this.#name.innerText = unit.getData().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.#fuel.innerText = String(unit.getMissionData().fuel + "%");
this.#latitude.innerText = ConvertDDToDMS(unit.getFlightData().latitude, false);
this.#longitude.innerText = ConvertDDToDMS(unit.getFlightData().longitude, true);
this.#task.innerText = unit.getTaskData().currentTask !== ""? unit.getTaskData().currentTask: "No task";
/* Set the class of the task container */
this.#task.classList.toggle("red", unit.getMissionData().coalition === "red");
@@ -69,7 +69,7 @@ export class UnitInfoPanel extends Panel {
var amount = ammo.count;
var el = document.createElement("div")
el.classList.add("js-loadout-element", "ol-rectangular-container-dark")
el.innerHTML = amount + "x" + displayName;
el.innerText = amount + "x" + displayName;
this.#loadoutContainer.appendChild(el);
}
}

View File

@@ -1,80 +0,0 @@
import { AirUnit, GroundUnit, NavyUnit, Weapon } from "../units/unit";
export class VisibilityControlPanel {
#element: HTMLElement
constructor(ID: string) {
this.#element = <HTMLElement>document.getElementById(ID);
if (this.#element != null)
{
var airVisibilityCheckbox = this.#element.querySelector("#air-visibility");
var groundVisibilityCheckbox = this.#element.querySelector("#ground-visibility");
var navyVisibilityCheckbox = this.#element.querySelector("#navy-visibility");
var weaponVisibilityCheckbox = this.#element.querySelector("#weapon-visibility");
airVisibilityCheckbox?.addEventListener("change", () => this.#onChange());
groundVisibilityCheckbox?.addEventListener("change", () => this.#onChange());
navyVisibilityCheckbox?.addEventListener("change", () => this.#onChange());
weaponVisibilityCheckbox?.addEventListener("change", () => this.#onChange());
var fullVisibilitySelection = this.#element.querySelector("#full-visibility");
var partialVisibilitySelection = this.#element.querySelector("#partial-visibility");
var minimalVisibilitySelection = this.#element.querySelector("#minimal-visibility");
fullVisibilitySelection?.addEventListener("change", () => this.#onChange());
partialVisibilitySelection?.addEventListener("change", () => this.#onChange());
minimalVisibilitySelection?.addEventListener("change", () => this.#onChange());
var uncontrolledVisibilityCheckbox = this.#element.querySelector("#uncontrolled-visibility");
uncontrolledVisibilityCheckbox?.addEventListener("change", () => this.#onChange());
}
}
#onChange(){
if (this.#element != null)
{
var fullVisibilitySelection = <HTMLInputElement> this.#element.querySelector("#full-visibility");
var partialVisibilitySelection = <HTMLInputElement> this.#element.querySelector("#partial-visibility");
var minimalVisibilitySelection = <HTMLInputElement> this.#element.querySelector("#minimal-visibility");
var activeVisibility = "";
if (fullVisibilitySelection.checked)
activeVisibility = "full";
else if (partialVisibilitySelection.checked)
activeVisibility = "partial";
else if (minimalVisibilitySelection.checked)
activeVisibility = "minimal";
var uncontrolledVisibilityCheckbox = <HTMLInputElement> this.#element.querySelector("#uncontrolled-visibility");
var uncontrolledVisibility = !uncontrolledVisibilityCheckbox.checked;
//var airVisibilityCheckbox = <HTMLInputElement> this.#element.querySelector("#air-visibility");
//if (airVisibilityCheckbox.checked)
// AirUnit.setVisibility({human: "full", AI: activeVisibility, uncontrolled: uncontrolledVisibility? activeVisibility: "hidden", dead: "hidden"});
//else
// AirUnit.setVisibility({human: "hidden", AI: "hidden", uncontrolled: "hidden", dead: "hidden"});
//
//var groundVisibilityCheckbox = <HTMLInputElement> this.#element.querySelector("#ground-visibility");
//if (groundVisibilityCheckbox.checked)
// GroundUnit.setVisibility({human: activeVisibility, AI: activeVisibility, uncontrolled: uncontrolledVisibility? activeVisibility: "hidden", dead: "hidden"});
//else
// GroundUnit.setVisibility({human: "hidden", AI: "hidden", uncontrolled: "hidden", dead: "hidden"});
//
//var navyVisibilityCheckbox = <HTMLInputElement> this.#element.querySelector("#navy-visibility");
//if (navyVisibilityCheckbox.checked)
// NavyUnit.setVisibility({human: activeVisibility, AI: activeVisibility, uncontrolled: uncontrolledVisibility? activeVisibility: "hidden", dead: "hidden"});
//else
// NavyUnit.setVisibility({human: "hidden", AI: "hidden", uncontrolled: "hidden", dead: "hidden"});
//
//var weaponVisibilityCheckbox = <HTMLInputElement> this.#element.querySelector("#weapon-visibility");
//if (weaponVisibilityCheckbox.checked)
// Weapon.setVisibility({human: activeVisibility, AI: activeVisibility, uncontrolled: uncontrolledVisibility? activeVisibility: "hidden", dead: "hidden"});
//else
// Weapon.setVisibility({human: "hidden", AI: "hidden", uncontrolled: "hidden", dead: "hidden"});
}
}
}

View File

@@ -1,11 +1,16 @@
import * as L from 'leaflet'
import { getUnitsManager, setConnected } from '..';
import { ConvertDDToDMS } from '../other/utils';
import { setConnected } from '..';
/* Edit here to change server address */
var RESTaddress = "http://localhost:30000/restdemo";
const RESTaddress = "http://localhost:30000/olympus";
const UNITS_URI = "units";
const FULL_UPDATE_URI = "full";
const PARTIAL_UPDATE_URI = "partial";
const LOGS_URI = "logs";
const AIRBASES_URI = "airbases";
const BULLSEYE_URI = "bullseye";
export function getDataFromDCS(callback: CallableFunction) {
export function getDataFromDCS(refresh: boolean, callback: CallableFunction) {
/* Request the updated unit data from the server */
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET", RESTaddress, true);
@@ -20,7 +25,9 @@ export function getDataFromDCS(callback: CallableFunction) {
console.error("An error occurred during the XMLHttpRequest");
setConnected(false);
};
xmlHttp.send(null);
var request = { "refresh": refresh }
xmlHttp.send(JSON.stringify(request));
}
export function addDestination(ID: number, path: any) {

View File

@@ -49,37 +49,28 @@ export class Unit extends Marker {
this.on('contextmenu', (e) => this.#onContextMenu(e));
var icon = new DivIcon({
html: `<div class="unit"
data-coalition=${this.getMissionData().coalition}
data-pilot=${this.getMissionData().flags.human? "human": "ai"}>
<div class="unit-spotlight">
<div class="unit-selected-border">
<div class="unit-vvi">
<div class="unit-vvi-heading"></div>
</div>
<div class="unit-id">${aircraftDatabase.getShortLabelByName(this.getData().name)}</div>
</div>
</div>
<div class="unit-hotgroup">
<div class="unit-hotgroup-id"></div>
</div>
<div class="unit-fuel">
<div class="unit-fuel-level"></div>
</div>
<div class="unit-ammo">
<div data-ammo-type="fox-1"></div>
<div data-ammo-type="fox-2"></div>
<div data-ammo-type="fox-3"></div>
<div data-ammo-type="other"></div>
</div>
<div class="unit-summary">
<div class="unit-callsign">${this.getData().unitName}</div>
<div class="unit-heading"></div>
<div class="unit-altitude"></div>
</div>
</div>`,
html: ` <svg class="unit" data-coalition="${this.getMissionData().coalition}" xmlns="http://www.w3.org/2000/svg">
<circle class="unit-spotlight" />
<rect class="unit-vvi" style="transform:rotate( calc( var( --unit-marker-air-vvi-rotation-offset ) + 090deg ) ); height:calc( ( var( --unit-marker-air-height ) / 2 ) + 25px );" />
<rect class="unit-hotgroup"></rect>
<text x="74" y="27" class="unit-hotgroup-id">3</text>
<rect class="unit-selected-border" />
<rect class="unit-marker" />
<text x="50%" y="54px" class="unit-short-label">${aircraftDatabase.getShortLabelByName(this.getData().name)}</text>
<rect class="unit-fuel" />
<rect class="unit-fuel-level" />
<circle class="unit-ammo unit-ammo-fox-1" />
<circle class="unit-ammo unit-ammo-fox-2" />
<circle class="unit-ammo unit-ammo-fox-3" />
<circle class="unit-ammo unit-ammo-other" />
<g class="unit-summary">
<text class="unit-callsign" x="1" y="46">${this.getData().unitName}</text>
<text class="unit-heading" x="20" y="60"></text>
<text class="unit-altitude" x="46" y="60"></text>
</g>
</svg>`,
className: 'ol-unit-marker',
iconAnchor: [30, 30]
iconAnchor: [60, 60]
});
this.setIcon(icon);
@@ -88,19 +79,19 @@ export class Unit extends Marker {
this.#targetsPolylines = [];
}
update(response: UnitData) {
setData(data: UnitData) {
var updateMarker = true;
//if (this.#data.flightData.latitude != response.flightData.latitude ||
// this.#data.flightData.longitude != response.flightData.longitude ||
// this.#data.alive != response.alive ||
//if (this.getFlightData().latitude != response.flightData.latitude ||
// this.getFlightData().longitude != response.flightData.longitude ||
// this.getData().alive != response.alive ||
// this.#forceUpdate ||
// !getMap().hasLayer(this.#marker))
// updateMarker = true;
this.#data = response;
this.#data = data;
/* Dead units can't be selected */
this.setSelected(this.getSelected() && this.#data.alive)
this.setSelected(this.getSelected() && this.getData().alive)
if (updateMarker)
this.#updateMarker();
@@ -114,9 +105,33 @@ export class Unit extends Marker {
this.#clearPath();
}
getData() {
return this.#data;
}
getFlightData() {
return this.getData().flightData;
}
getTaskData() {
return this.getData().taskData;
}
getMissionData() {
return this.getData().missionData;
}
getFormationData() {
return this.getData().formationData;
}
getOptionsData() {
return this.getData().optionsData;
}
setSelected(selected: boolean) {
/* Only alive units can be selected. Some units are not selectable (weapons) */
if ((this.#data.alive || !selected) && this.#selectable && this.#selected != selected) {
if ((this.getData().alive || !selected) && this.#selectable && this.#selected != selected) {
this.#selected = selected;
this.getElement()?.querySelector(".unit")?.setAttribute("data-is-selected", String(this.getSelected()));
document.dispatchEvent(new CustomEvent("unitSelection", { detail: this }));
@@ -137,8 +152,8 @@ export class Unit extends Marker {
addDestination(latlng: L.LatLng) {
var path: any = {};
if (this.#data.taskData.activePath != null) {
path = this.#data.taskData.activePath;
if (this.getTaskData().activePath != null) {
path = this.getTaskData().activePath;
path[(Object.keys(path).length + 1).toString()] = latlng;
}
else {
@@ -148,7 +163,7 @@ export class Unit extends Marker {
}
clearDestinations() {
this.#data.taskData.activePath = null;
this.getTaskData().activePath = null;
}
getHidden() {
@@ -156,7 +171,7 @@ export class Unit extends Marker {
}
getLeader() {
return getUnitsManager().getUnitByID(this.#data.formationData.leaderID);
return getUnitsManager().getUnitByID(this.getFormationData().leaderID);
}
getFormation() {
@@ -165,8 +180,8 @@ export class Unit extends Marker {
getWingmen() {
var wingmen: Unit[] = [];
if (this.#data.formationData.wingmenIDs != null) {
for (let ID of this.#data.formationData.wingmenIDs) {
if (this.getFormationData().wingmenIDs != null) {
for (let ID of this.getFormationData().wingmenIDs) {
var unit = getUnitsManager().getUnitByID(ID)
if (unit)
wingmen.push(unit);
@@ -179,30 +194,6 @@ export class Unit extends Marker {
this.#forceUpdate = true;
}
getData() {
return this.#data;
}
getFlightData() {
return this.#data.flightData;
}
getTaskData() {
return this.#data.taskData;
}
getMissionData() {
return this.#data.missionData;
}
getFormationData() {
return this.#data.formationData;
}
getOptionsData() {
return this.#data.optionsData;
}
attackUnit(targetID: number) {
/* Call DCS attackUnit function */
if (this.ID != targetID) {
@@ -241,20 +232,14 @@ export class Unit extends Marker {
setReactionToThreat(this.ID, reactionToThreat);
}
delete() {
deleteUnit(this.ID);
}
/*
setformation(formation)
{
}
*/
setLeader(isLeader: boolean, wingmenIDs: number[] = []) {
setLeader(this.ID, isLeader, wingmenIDs);
}
delete() {
deleteUnit(this.ID);
}
#onClick(e: any) {
this.#timer = setTimeout(() => {
if (!this.#preventClick) {
@@ -279,8 +264,7 @@ export class Unit extends Marker {
'Attack',
'Follow'
]
getMap().showContextMenu(e.originalEvent, "Action: " + this.#data.unitName, options, (action: string) => this.#executeAction(action));
getMap().showContextMenu(e.originalEvent, "Action: " + this.getData().unitName, options.map((option: string) => {return {tooltip: option, src: "", callback: (action: string) => this.#executeAction(action)}}));
}
#executeAction(action: string) {
@@ -300,49 +284,48 @@ export class Unit extends Marker {
getMap().removeLayer(this);
}
else {
this.setLatLng(new LatLng(this.#data.flightData.latitude, this.#data.flightData.longitude));
this.setLatLng(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude));
var element = this.getElement();
if (element != null)
{
element.querySelector(".unit-vvi-heading")?.setAttribute("style",`transform: rotate(${rad2deg(this.getFlightData().heading)}deg); width: ${15 + this.getFlightData().speed / 5}px`);
if (element != null) {
element.querySelector(".unit-vvi")?.setAttribute("style", `transform:rotate( calc( var( --unit-marker-air-vvi-rotation-offset ) + ${rad2deg(this.getFlightData().heading)}deg ) ); height:calc( ( var( --unit-marker-air-height ) / 2 ) + ${this.getFlightData().speed / 5}px );`);
element.querySelector(".unit")?.setAttribute("data-fuel-level", "20");
element.querySelector(".unit")?.setAttribute("data-has-fox-1", "true");
var unitHeadingDiv = element.querySelector(".unit-heading");
if (unitHeadingDiv != null)
unitHeadingDiv.innerHTML = String(Math.floor(rad2deg(this.getFlightData().heading)));
var unitAltitudeDiv = element.querySelector(".unit-altitude");
if (unitAltitudeDiv != null)
unitAltitudeDiv.innerHTML = String(Math.floor(this.getFlightData().altitude / 0.3048 / 1000));
}
var pos = getMap().latLngToLayerPoint(this.getLatLng()).round();
this.setZIndexOffset(Math.floor(this.getFlightData().altitude) - pos.y);
this.setZIndexOffset(Math.floor(this.getFlightData().altitude) - pos.y);
}
this.#forceUpdate = false;
}
#drawPath() {
if (this.#data.taskData.activePath != null) {
if (this.getTaskData().activePath != null) {
var points = [];
points.push(new LatLng(this.#data.flightData.latitude, this.#data.flightData.longitude));
points.push(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude));
/* Add markers if missing */
while (this.#pathMarkers.length < Object.keys(this.#data.taskData.activePath).length) {
while (this.#pathMarkers.length < Object.keys(this.getTaskData().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.#data.taskData.activePath).length) {
while (this.#pathMarkers.length > Object.keys(this.getTaskData().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.#data.taskData.activePath) {
var destination = this.#data.taskData.activePath[WP];
for (let WP in this.getTaskData().activePath) {
var destination = this.getTaskData().activePath[WP];
this.#pathMarkers[parseInt(WP) - 1].setLatLng([destination.lat, destination.lng]);
points.push(new LatLng(destination.lat, destination.lng));
this.#pathPolyline.setLatLngs(points);
@@ -364,7 +347,7 @@ export class Unit extends Marker {
var targetData = this.getMissionData().targets[typeIndex][index];
var target = getUnitsManager().getUnitByID(targetData.object["id_"])
if (target != null) {
var startLatLng = new LatLng(this.#data.flightData.latitude, this.#data.flightData.longitude)
var startLatLng = new LatLng(this.getFlightData().latitude, this.getFlightData().longitude)
var endLatLng = new LatLng(target.getFlightData().latitude, target.getFlightData().longitude)
var color;
@@ -393,7 +376,7 @@ export class Unit extends Marker {
export class AirUnit extends Unit {
getHidden() {
return false;
return false;
}
}

View File

@@ -1,23 +1,3 @@
export interface LoadoutItemBlueprint {
name: string;
quantity: number;
}
export interface LoadoutBlueprint {
fuel: number;
items: LoadoutItemBlueprint[];
roles: string[];
code: string;
name: string;
}
export interface UnitBlueprint {
name: string;
label: string;
shortLabel: string;
loadouts: LoadoutBlueprint[];
}
export class UnitDatabase {
units: {[key: string]: UnitBlueprint} = {};

View File

@@ -1,148 +0,0 @@
import * as L from 'leaflet'
import { getMap } from '..'
import { rad2deg } from '../other/utils'
export interface MarkerOptions {
unitName: string,
name: string,
human: boolean,
coalition: string,
AI: boolean
}
export interface MarkerData {
heading: number,
speed: number,
altitude: number,
alive: boolean
}
export class UnitMarker extends L.Marker {
#options: MarkerOptions;
#data: MarkerData;
#selected: boolean = false
constructor(options: MarkerOptions) {
super(new L.LatLng(0, 0), { riseOnHover: true });
this.#options = options;
this.#data = {heading: 0, speed: 0, altitude: 0, alive: true};
var icon = new L.DivIcon({
html: `<div class="unit"
data-coalition=${this.#options.coalition}
data-pilot=${this.#options.human? "human": "ai"}>
<div class="unit-spotlight">
<div class="unit-selected-border">
<div class="unit-vvi">
<div class="unit-vvi-heading"></div>
</div>
<div class="unit-id">${this.#options.name}</div>
</div>
</div>
<div class="unit-hotgroup">
<div class="unit-hotgroup-id"></div>
</div>
<div class="unit-fuel">
<div class="unit-fuel-level"></div>
</div>
<div class="unit-ammo">
<div data-ammo-type="fox-1"></div>
<div data-ammo-type="fox-2"></div>
<div data-ammo-type="fox-3"></div>
<div data-ammo-type="other"></div>
</div>
<div class="unit-summary">
<div class="unit-callsign">${this.#options.unitName}</div>
<div class="unit-heading"></div>
<div class="unit-altitude"></div>
</div>
</div>`,
className: 'ol-unit-marker',
iconAnchor: [30, 30]
});
this.setIcon(icon);
}
onAdd(map: L.Map): this {
super.onAdd(map);
this.addEventListener('mouseover', function (e: any) { e.target?.setHovered(true); });
this.addEventListener('mouseout', function (e: any) { e.target?.setHovered(false); });
return this
}
draw(data: MarkerData) {
this.#data;
var element = this.getElement();
if (element != null)
{
element.querySelector(".unit-vvi-heading")?.setAttribute("style",`transform: rotate(${rad2deg(data.heading)}deg); width: ${15 + data.speed / 5}px`);
element.querySelector(".unit")?.setAttribute("data-fuel-level", "20");
element.querySelector(".unit")?.setAttribute("data-has-fox-1", "true");
var unitHeadingDiv = element.querySelector(".unit-heading");
if (unitHeadingDiv != null)
unitHeadingDiv.innerHTML = String(Math.floor(rad2deg(data.heading)));
var unitAltitudeDiv = element.querySelector(".unit-altitude");
if (unitAltitudeDiv != null)
unitAltitudeDiv.innerHTML = String(Math.floor(data.altitude / 0.3048 / 1000));
}
var pos = getMap().latLngToLayerPoint(this.getLatLng()).round();
this.setZIndexOffset(Math.floor(data.altitude) - pos.y);
}
setSelected(selected: boolean) {
this.#selected = selected;
this.getElement()?.querySelector(".unit")?.setAttribute("data-is-selected", String(this.getSelected()));
}
getSelected() {
return this.#selected;
}
setHovered(hovered: boolean) {
this.getElement()?.querySelector("#icon")?.classList.toggle("ol-unit-marker-hovered", hovered && this.#data.alive);
}
getData() {
return this.#data;
}
getOptions() {
return this.#options;
}
}
export class AirUnitMarker extends UnitMarker {
}
export class AircraftMarker extends AirUnitMarker {
}
export class HelicopterMarker extends AirUnitMarker {
}
export class GroundUnitMarker extends UnitMarker {
}
export class NavyUnitMarker extends UnitMarker {
}
export class WeaponMarker extends UnitMarker {
}
export class BombMarker extends WeaponMarker {
}
export class MissileMarker extends WeaponMarker {
}

View File

@@ -17,37 +17,10 @@ export class UnitsManager {
document.addEventListener('keydown', (event) => this.#onKeyDown(event));
}
#updateUnitControlPanel() {
/* Update the unit control panel */
if (this.getSelectedUnits().length > 0) {
getUnitControlPanel().show();
getUnitControlPanel().update(this.getSelectedLeaders().concat(this.getSelectedSingletons()));
}
else {
getUnitControlPanel().hide();
}
}
#onKeyDown(event: KeyboardEvent)
{
if (event.key === "Delete")
{
this.selectedUnitsDelete();
}
}
getUnits() {
return this.#units;
}
addUnit(ID: number, data: UnitData) {
/* The name of the unit category is exactly the same as the constructor name */
var constructor = Unit.getConstructor(data.category);
if (constructor != undefined) {
this.#units[ID] = new constructor(ID, data);
}
}
getUnitByID(ID: number) {
if (ID in this.#units)
return this.#units[ID];
@@ -55,21 +28,17 @@ export class UnitsManager {
return null;
}
removeUnit(ID: number) {
}
deselectAllUnits() {
for (let ID in this.#units) {
this.#units[ID].setSelected(false);
addUnit(ID: number, data: UnitData) {
/* The name of the unit category is exactly the same as the constructor name */
var constructor = Unit.getConstructor(data.category);
if (constructor != undefined) {
this.#units[ID] = new constructor(ID, data);
}
}
removeUnit(ID: number) {
selectUnit(ID: number, deselectAllUnits: boolean = true)
{
if (deselectAllUnits)
this.deselectAllUnits();
this.#units[ID]?.setSelected(true);
}
update(data: ServerData) {
@@ -78,16 +47,16 @@ export class UnitsManager {
if (!(ID in this.#units)) {
this.addUnit(parseInt(ID), data.units[ID]);
}
this.#units[parseInt(ID)].update(data.units[ID]);
this.#units[parseInt(ID)].setData(data.units[ID]);
}
/* Update the unit info panel */
if (this.getSelectedUnits().length == 1) {
getUnitInfoPanel().show();
getUnitInfoPanel().update(this.getSelectedUnits()[0]);
getUnitInfoPanel()?.show();
getUnitInfoPanel()?.update(this.getSelectedUnits()[0]);
}
else {
getUnitInfoPanel().hide();
getUnitInfoPanel()?.hide();
}
}
@@ -97,6 +66,13 @@ export class UnitsManager {
}
}
selectUnit(ID: number, deselectAllUnits: boolean = true)
{
if (deselectAllUnits)
this.deselectAllUnits();
this.#units[ID]?.setSelected(true);
}
onUnitSelection() {
if (this.getSelectedUnits().length > 0) {
getMap().setState("MOVE_UNIT");
@@ -135,6 +111,12 @@ export class UnitsManager {
return selectedUnits;
}
deselectAllUnits() {
for (let ID in this.#units) {
this.#units[ID].setSelected(false);
}
}
getSelectedLeaders() {
var leaders: Unit[] = [];
for (let idx in this.getSelectedUnits())
@@ -163,14 +145,34 @@ export class UnitsManager {
return singletons;
}
getSelectedUnitsType () {
return this.getSelectedUnits().map((unit: Unit) => {
return unit.constructor.name
}).reduce((a: any, b: any) => {
return a == b? a: undefined
});
};
getSelectedUnitsTargetSpeed () {
return this.getSelectedUnits().map((unit: Unit) => {
return unit.getTaskData().targetSpeed
}).reduce((a: any, b: any) => {
return a == b? a: undefined
});
};
getSelectedUnitsTargetAltitude () {
return this.getSelectedUnits().map((unit: Unit) => {
return unit.getTaskData().targetAltitude
}).reduce((a: any, b: any) => {
return a == b? a: undefined
});
};
selectedUnitsAddDestination(latlng: L.LatLng) {
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits) {
var commandedUnit = selectedUnits[idx];
//if (selectedUnits[idx].wingman)
//{
// commandedUnit = this.getLeader(selectedUnits[idx].ID);
//}
commandedUnit.addDestination(latlng);
}
}
@@ -179,10 +181,6 @@ export class UnitsManager {
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits) {
var commandedUnit = selectedUnits[idx];
//if (selectedUnits[idx].wingman)
//{
// commandedUnit = this.getLeader(selectedUnits[idx].ID);
//}
commandedUnit.clearDestinations();
}
}
@@ -327,6 +325,15 @@ export class UnitsManager {
setTimeout(() => this.#updateUnitControlPanel(), 300); // TODO find better method, may fail
}
selectedUnitsDelete()
{
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits)
{
selectedUnits[idx].delete();
}
}
copyUnits()
{
this.#copiedUnits = this.getSelectedUnits();
@@ -341,12 +348,22 @@ export class UnitsManager {
}
}
selectedUnitsDelete()
#updateUnitControlPanel() {
/* Update the unit control panel */
if (this.getSelectedUnits().length > 0) {
getUnitControlPanel()?.show();
getUnitControlPanel()?.update(this.getSelectedLeaders().concat(this.getSelectedSingletons()));
}
else {
getUnitControlPanel()?.hide();
}
}
#onKeyDown(event: KeyboardEvent)
{
var selectedUnits = this.getSelectedUnits();
for (let idx in selectedUnits)
if (event.key === "Delete")
{
selectedUnits[idx].delete();
this.selectedUnitsDelete();
}
}
}