Merge branch '111-measurebox-shows-000-when-measuring-north-should-read-360' of https://github.com/Pax1601/DCSOlympus into 111-measurebox-shows-000-when-measuring-north-should-read-360

This commit is contained in:
PeekabooSteam
2023-03-31 20:31:45 +01:00
107 changed files with 3907 additions and 993 deletions

View File

@@ -1,6 +1,6 @@
import { LatLng } from "leaflet";
import { getActiveCoalition, setActiveCoalition } from "..";
import { spawnAircraft, spawnGroundUnit } from "../server/server";
import { spawnAircraft, spawnGroundUnit, spawnSmoke } from "../server/server";
import { aircraftDatabase } from "../units/aircraftdatabase";
import { groundUnitsDatabase } from "../units/groundunitsdatabase";
import { ContextMenu } from "./contextmenu";
@@ -21,7 +21,7 @@ export class MapContextMenu extends ContextMenu {
#aircraftLoadoutDropdown: Dropdown;
#groundUnitRoleDropdown: Dropdown;
#groundUnitTypeDropdown: Dropdown;
#spawnOptions: SpawnOptions = {role: "", type: "", latlng: new LatLng(0, 0), loadout: null, coalition: "blue", airbaseName: null};
#spawnOptions: SpawnOptions = { role: "", type: "", latlng: new LatLng(0, 0), loadout: null, coalition: "blue", airbaseName: null };
constructor(id: string) {
super(id);
@@ -35,33 +35,38 @@ export class MapContextMenu extends ContextMenu {
document.addEventListener("contextMenuShow", (e: any) => {
this.showSubMenu(e.detail.type);
})
});
document.addEventListener("contextMenuDeployAircraft", () => {
this.hide();
this.#spawnOptions.coalition = getActiveCoalition();
if (this.#spawnOptions)
spawnAircraft(this.#spawnOptions);
})
});
document.addEventListener("contextMenuDeployGroundUnit", () => {
this.hide();
this.#spawnOptions.coalition = getActiveCoalition();
if (this.#spawnOptions)
spawnGroundUnit(this.#spawnOptions);
})
});
document.addEventListener("contextMenuDeploySmoke", (e: any) => {
this.hide();
spawnSmoke(e.detail.color, this.getLatLng());
});
this.hide();
}
show(x: number, y: number, latlng: LatLng) {
this.#spawnOptions = {role: "", type: "", latlng: new LatLng(0, 0), loadout: null, coalition: "blue", airbaseName: null};
this.#spawnOptions.airbaseName = null;
super.show(x, y, latlng);
this.#spawnOptions.latlng = latlng;
this.showUpperBar();
}
showSubMenu(type: string){
showSubMenu(type: string) {
this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", type !== "aircraft");
this.getContainer()?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", type === "aircraft");
this.getContainer()?.querySelector("#ground-unit-spawn-menu")?.classList.toggle("hide", type !== "ground-unit");
@@ -84,31 +89,28 @@ export class MapContextMenu extends ContextMenu {
this.getContainer()?.querySelector("#upper-bar")?.classList.toggle("hide", true);
}
setAirbaseName(airbaseName: string)
{
setAirbaseName(airbaseName: string) {
this.#spawnOptions.airbaseName = airbaseName;
}
setLatLng(latlng: LatLng)
{
setLatLng(latlng: LatLng) {
this.#spawnOptions.latlng = latlng;
}
#onSwitch(e: any) {
if (this.getContainer() != null) {
if (e.srcElement.checked)
if (e.srcElement.checked)
setActiveCoalition("red");
else
else
setActiveCoalition("blue");
}
}
/********* Aircraft spawn menu *********/
#setAircraftRole(role: string)
{
#setAircraftRole(role: string) {
this.#spawnOptions.role = role;
this.#resetAircraftRole();
this.#aircraftTypeDropdown.setOptions(aircraftDatabase.getLabelsByRole(role));
this.#resetAircraftType();
this.#aircraftTypeDropdown.setOptions(aircraftDatabase.getByRole(role).map((blueprint) => { return blueprint.label }));
this.#aircraftTypeDropdown.selectValue(0);
this.clip();
}
@@ -122,20 +124,18 @@ export class MapContextMenu extends ContextMenu {
this.clip();
}
#setAircraftType(label: string)
{
#setAircraftType(label: string) {
this.#resetAircraftType();
var type = aircraftDatabase.getNameByLabel(label);
if (type != null)
{
var type = aircraftDatabase.getByLabel(label)?.name || null;
if (type != null) {
this.#spawnOptions.type = type;
this.#aircraftLoadoutDropdown.setOptions(aircraftDatabase.getLoadoutNamesByRole(type, this.#spawnOptions.role));
this.#aircraftLoadoutDropdown.selectValue(0);
var image = (<HTMLImageElement>this.getContainer()?.querySelector("#unit-image"));
image.src = `images/units/${aircraftDatabase.getByLabel(label)?.filename}`;
image.classList.toggle("hide", false);
image.classList.toggle("hide", false);
}
this.clip();
}
@@ -147,15 +147,13 @@ export class MapContextMenu extends ContextMenu {
this.clip();
}
#setAircraftLoadout(loadoutName: string)
{
var loadout = aircraftDatabase.getLoadoutsByName(this.#spawnOptions.type, loadoutName);
if (loadout)
{
#setAircraftLoadout(loadoutName: string) {
var loadout = aircraftDatabase.getLoadoutByName(this.#spawnOptions.type, loadoutName);
if (loadout) {
this.#spawnOptions.loadout = loadout.code;
(<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false;
var items = loadout.items.map((item: any) => {return `${item.quantity}x ${item.name}`;});
items.length == 0? items.push("Empty loadout"): "";
var items = loadout.items.map((item: any) => { return `${item.quantity}x ${item.name}`; });
items.length == 0 ? items.push("Empty loadout") : "";
(<HTMLButtonElement>this.getContainer()?.querySelector("#loadout-list")).replaceChildren(
...items.map((item: any) => {
var div = document.createElement('div');
@@ -168,11 +166,10 @@ export class MapContextMenu extends ContextMenu {
}
/********* Ground unit spawn menu *********/
#setGroundUnitRole(role: string)
{
#setGroundUnitRole(role: string) {
this.#spawnOptions.role = role;
this.#resetGroundUnitRole();
this.#groundUnitTypeDropdown.setOptions(groundUnitsDatabase.getLabelsByRole(role));
this.#resetGroundUnitType();
this.#groundUnitTypeDropdown.setOptions(groundUnitsDatabase.getByRole(role).map((blueprint) => { return blueprint.label }));
this.#groundUnitTypeDropdown.selectValue(0);
this.clip();
}
@@ -186,12 +183,10 @@ export class MapContextMenu extends ContextMenu {
this.clip();
}
#setGroundUnitType(label: string)
{
#setGroundUnitType(label: string) {
this.#resetGroundUnitType();
var type = groundUnitsDatabase.getNameByLabel(label);
if (type != null)
{
var type = groundUnitsDatabase.getByLabel(label)?.name || null;
if (type != null) {
this.#spawnOptions.type = type;
(<HTMLButtonElement>this.getContainer()?.querySelector("#ground-unit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false;
}

View File

@@ -75,7 +75,7 @@ export class Slider {
{
this.#value = newValue;
if (this.#slider != null)
this.#slider.value = String((newValue - this.#minValue) / (this.#maxValue - this.#minValue) * 100);
this.#slider.value = String((newValue - this.#minValue) / (this.#maxValue - this.#minValue) * parseFloat(this.#slider.max));
this.#onValue()
}
}
@@ -120,7 +120,7 @@ export class Slider {
this.#dragged = false;
if (this.#slider != null)
{
this.#value = this.#minValue + parseFloat(this.#slider.value) / 100 * (this.#maxValue - this.#minValue);
this.#value = this.#minValue + parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * (this.#maxValue - this.#minValue);
this.#callback(this.getValue());
}
}

View File

@@ -9,7 +9,7 @@ import { AIC } from "./aic/aic";
import { ATC } from "./atc/atc";
import { FeatureSwitches } from "./featureswitches";
import { LogPanel } from "./panels/logpanel";
import { getAirbases, getBullseye as getBullseyes, getMission, getUnits, toggleDemoEnabled } from "./server/server";
import { getAirbases, getBullseye as getBullseyes, getConfig, getMission, getUnits, setAddress, toggleDemoEnabled } from "./server/server";
import { UnitDataTable } from "./units/unitdatatable";
var map: Map;
@@ -69,16 +69,33 @@ function setup() {
/* Setup event handlers */
setupEvents();
/* On the first connection, force request of full data */
getAirbases((data: AirbasesData) => getMissionData()?.update(data));
getBullseyes((data: BullseyesData) => getMissionData()?.update(data));
getMission((data: any) => {getMissionData()?.update(data)});
getUnits((data: UnitsData) => getUnitsManager()?.update(data), true /* Does a full refresh */);
/* Start periodically requesting updates */
startPeriodicUpdate();
getConfig(readConfig)
}
function readConfig(config: any)
{
if (config && config["server"] != undefined && config["server"]["address"] != undefined && config["server"]["port"] != undefined)
{
const address = config["server"]["address"];
const port = config["server"]["port"];
if ((typeof address === 'string' || address instanceof String) && typeof port == 'number')
{
setAddress(window.location.hostname, <number>port);
}
/* On the first connection, force request of full data */
getAirbases((data: AirbasesData) => getMissionData()?.update(data));
getBullseyes((data: BullseyesData) => getMissionData()?.update(data));
getMission((data: any) => {getMissionData()?.update(data)});
getUnits((data: UnitsData) => getUnitsManager()?.update(data), true /* Does a full refresh */);
/* Start periodically requesting updates */
startPeriodicUpdate();
}
else {
throw new Error('Could not read configuration file!');
}
}
function startPeriodicUpdate() {
requestUpdate();
@@ -124,13 +141,16 @@ function checkSessionHash(newSessionHash: string) {
function setupEvents() {
/* Generic clicks */
document.addEventListener("click", (ev) => {
if (ev instanceof PointerEvent && ev.target instanceof HTMLElement) {
if (ev instanceof MouseEvent && ev.target instanceof HTMLElement) {
const target = ev.target;
if (target.classList.contains("olympus-dialog-close")) {
target.closest("div.olympus-dialog")?.classList.add("hide");
}
const triggerElement = target.closest("[data-on-click]");
if (triggerElement instanceof HTMLElement) {
const eventName: string = triggerElement.dataset.onClick || "";
let params = JSON.parse(triggerElement.dataset.onClickParams || "{}");
@@ -184,15 +204,6 @@ function setupEvents() {
}
//*/
document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => {
ev.detail._element.classList.toggle("off");
document.body.toggleAttribute("data-hide-" + ev.detail.coalition);
});
document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => {
document.body.toggleAttribute("data-hide-" + ev.detail.category);
});
document.addEventListener( "closeDialog", (ev: CustomEventInit) => {
ev.detail._element.closest( ".ol-dialog" ).classList.add( "hide" );
});

View File

@@ -6,6 +6,7 @@ import { UnitContextMenu } from "../controls/unitcontextmenu";
import { AirbaseContextMenu } from "../controls/airbasecontextmenu";
import { Dropdown } from "../controls/dropdown";
import { Airbase } from "../missionhandler/airbase";
import { Unit } from "../units/unit";
export const IDLE = "IDLE";
export const MOVE_UNIT = "MOVE_UNIT";
@@ -45,6 +46,17 @@ export class Map extends L.Map {
this.on('mouseup', (e: any) => this.#onMouseUp(e));
this.on('mousemove', (e: any) => this.#onMouseMove(e));
document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => {
ev.detail._element.classList.toggle("off");
document.body.toggleAttribute("data-hide-" + ev.detail.coalition);
Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
});
document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => {
document.body.toggleAttribute("data-hide-" + ev.detail.category);
Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
});
this.#mapSourceDropdown = new Dropdown("map-type", (layerName: string) => this.setLayer(layerName), this.getLayers())
}
@@ -228,6 +240,7 @@ export class Map extends L.Map {
#onMouseDown(e: any)
{
this.hideAllContextMenus();
if ((e.originalEvent.which == 1) && (e.originalEvent.button == 0))
this.dragging.disable();
}

View File

@@ -26,6 +26,27 @@ export function ConvertDDToDMS(D: number, lng: boolean) {
}
export function dataPointMap( container:HTMLElement, data:any) {
Object.keys( data ).forEach( ( key ) => {
const val = "" + data[ key ]; // Ensure a string
container.querySelectorAll( `[data-point="${key}"]`).forEach( el => {
// We could probably have options here
if ( el instanceof HTMLInputElement ) {
el.value = val;
} else if ( el instanceof HTMLElement ) {
el.innerText = val;
}
});
});
}
export function deg2rad(deg: number) {
var pi = Math.PI;
return deg * (pi / 180);

View File

@@ -1,25 +1,30 @@
import { getUnitsManager } from "..";
import { Slider } from "../controls/slider";
import { dataPointMap } from "../other/utils";
import { aircraftDatabase } from "../units/aircraftdatabase";
import { groundUnitsDatabase } from "../units/groundunitsdatabase";
import { Aircraft, GroundUnit, Unit } from "../units/unit";
import { UnitsManager } from "../units/unitsmanager";
import { UnitDatabase } from "../units/unitdatabase";
import { Panel } from "./panel";
const ROEs: string[] = ["Free", "Designated free", "Designated", "Return", "Hold"];
const reactionsToThreat: string[] = [ "None", "Passive", "Evade", "Escape", "Abort"];
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: 2500, Helicopter: 1000};
// const ROEs: string[] = ["Free", "Designated free", "Designated", "Return", "Hold"]; // Full list
// const reactionsToThreat: string[] = ["None", "Passive", "Evade", "Escape", "Abort"]; // Full list
const ROEs: string[] = [ "Hold", "Return", "Designated", "Free" ];
const reactionsToThreat: string[] = [ "None", "Passive", "Evade" ];
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: 2500, Helicopter: 1000 };
export class UnitControlPanel extends Panel {
#altitudeSlider: Slider;
#airspeedSlider: Slider;
#optionButtons: {[key: string]: HTMLButtonElement[]} = {}
#optionButtons: { [key: string]: HTMLButtonElement[] } = {}
constructor(ID: string) {
super(ID);
@@ -28,51 +33,68 @@ export class UnitControlPanel extends Panel {
this.#airspeedSlider = new Slider("airspeed-slider", 0, 100, "kts", (value: number) => getUnitsManager().selectedUnitsSetSpeed(value / 1.94384));
/* Option buttons */
this.#optionButtons["ROE"] = ROEs.map((option: string, index:number) => {
this.#optionButtons["ROE"] = ROEs.map((option: string, index: number) => {
var button = document.createElement("button");
button.title = option;
button.value = option;
button.addEventListener("click", () => {getUnitsManager().selectedUnitsSetROE(button.title);});
button.addEventListener("click", () => { getUnitsManager().selectedUnitsSetROE(button.title); });
return button;
});
this.#optionButtons["reactionToThreat"] = reactionsToThreat.map((option: string, index:number) => {
this.#optionButtons["reactionToThreat"] = reactionsToThreat.map((option: string, index: number) => {
var button = document.createElement("button");
button.title = option;
button.value = option;
button.addEventListener("click", () => {getUnitsManager().selectedUnitsSetReactionToThreat(button.title);});
button.addEventListener("click", () => { getUnitsManager().selectedUnitsSetReactionToThreat(button.title); });
return button;
});
this.getElement().querySelector("#roe-buttons-container")?.append(...this.#optionButtons["ROE"]);
this.getElement().querySelector("#reaction-to-threat-buttons-container")?.append(...this.#optionButtons["reactionToThreat"]);
document.addEventListener("unitUpdated", (e: CustomEvent<Unit>) => {if (e.detail.getSelected()) this.update()});
document.addEventListener("unitsSelection", (e: CustomEvent<Unit[]>) => {this.show(); this.update()});
document.addEventListener("clearSelection", () => {this.hide()});
document.addEventListener("unitUpdated", (e: CustomEvent<Unit>) => { if (e.detail.getSelected()) this.update() });
document.addEventListener("unitsSelection", (e: CustomEvent<Unit[]>) => { this.show(); this.update() });
document.addEventListener("clearSelection", () => { this.hide() });
this.hide();
}
update() {
var units = getUnitsManager().getSelectedUnits();
if (this.getElement() != null && units.length > 0)
{
if (this.getElement() != null && units.length > 0) {
this.#showFlightControlSliders(units);
this.getElement().querySelector("#selected-units-container")?.replaceChildren(...units.map((unit: Unit) =>
{
var button = document.createElement("button");
button.innerText = unit.getBaseData().unitName;
if (unit instanceof Aircraft)
button.setAttribute( "data-short-label", aircraftDatabase.getShortLabelByName(unit.getBaseData().name));
else if (unit instanceof GroundUnit)
button.setAttribute( "data-short-label", groundUnitsDatabase.getShortLabelByName(unit.getBaseData().name));
else
button.setAttribute( "data-short-label", "");
button.setAttribute( "data-coalition", unit.getMissionData().coalition );
button.classList.add( "pill", "highlight-coalition" )
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
if (index === 0) {
this.getElement().querySelectorAll(`[data-object|="unit"]`).forEach(marker => {
marker.setAttribute("data-coalition", unit.getMissionData().coalition);
dataPointMap( this.getElement(), {
"shortLabel" : database?.getByName(unit.getBaseData().name)?.shortLabel,
"unitName": unit.getBaseData().unitName
});
});
}
var button = document.createElement("button");
var callsign = unit.getBaseData().unitName || "";
button.innerText = unit.getBaseData().unitName;
button.setAttribute("data-short-label", database?.getByName(unit.getBaseData().name)?.shortLabel || "");
button.setAttribute("data-callsign", callsign);
button.setAttribute("data-coalition", unit.getMissionData().coalition);
button.classList.add("pill", "highlight-coalition")
button.addEventListener("click", () => getUnitsManager().selectUnit(unit.ID, true));
return (button);
@@ -80,25 +102,33 @@ export class UnitControlPanel extends Panel {
this.#optionButtons["ROE"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().ROE === button.value))
});
});
this.#optionButtons["reactionToThreat"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().reactionToThreat === button.value))
});
});
}
}
#showFlightControlSliders(units: Unit[])
{
this.#airspeedSlider.show();
this.#altitudeSlider.show();
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 (unitsType != undefined) {
if (["GroundUnit", "NavyUnit"].includes(unitsType))
this.#altitudeSlider.hide()

View File

@@ -1,34 +1,44 @@
import { getUnitsManager } from "..";
import { ConvertDDToDMS, rad2deg } from "../other/utils";
import { aircraftDatabase } from "../units/aircraftdatabase";
import { Unit } from "../units/unit";
import { Panel } from "./panel";
export class UnitInfoPanel extends Panel {
#unitName: HTMLElement;
#groupName: HTMLElement;
#name: HTMLElement;
#heading: HTMLElement;
#altitude: HTMLElement;
#currentTask: HTMLElement;
#fuelBar: HTMLElement;
#fuelPercentage: HTMLElement;
#groundSpeed: HTMLElement;
#fuel: HTMLElement;
#groupName: HTMLElement;
#heading: HTMLElement;
#name: HTMLElement;
#latitude: HTMLElement;
#longitude: HTMLElement;
#currentTask: HTMLElement;
#loadoutContainer: HTMLElement;
#silhouette: HTMLElement;
#unitControl: HTMLElement;
#unitLabel: HTMLElement;
#unitName: HTMLElement;
constructor(ID: string) {
super(ID);
this.#unitName = <HTMLElement>(this.getElement().querySelector("#unit-name"));
this.#groupName= <HTMLElement>(this.getElement().querySelector("#group-name"));
this.#name = <HTMLElement>(this.getElement().querySelector("#name"));
this.#heading = <HTMLElement>(this.getElement().querySelector("#heading"));
this.#altitude = <HTMLElement>(this.getElement().querySelector("#altitude"));
this.#groundSpeed = <HTMLElement>(this.getElement().querySelector("#ground-speed"));
this.#fuel = <HTMLElement>(this.getElement().querySelector("#fuel"));
this.#latitude = <HTMLElement>(this.getElement().querySelector("#latitude"));
this.#longitude = <HTMLElement>(this.getElement().querySelector("#longitude"));
this.#currentTask = <HTMLElement>(this.getElement().querySelector("#current-task"));
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"));
document.addEventListener("unitsSelection", (e: CustomEvent<Unit[]>) => this.#onUnitsSelection(e.detail));
document.addEventListener("unitsDeselection", (e: CustomEvent<Unit[]>) => this.#onUnitsDeselection(e.detail));
@@ -40,29 +50,53 @@ export class UnitInfoPanel extends Panel {
#onUnitUpdate(unit: Unit) {
if (this.getElement() != null && this.getVisible() && unit.getSelected()) {
const baseData = unit.getBaseData();
/* Set the unit info */
this.#unitName.innerText = unit.getBaseData().unitName;
this.#groupName.innerText = unit.getBaseData().groupName;
this.#name.innerText = unit.getBaseData().name;
this.#unitLabel.innerText = aircraftDatabase.getByName(baseData.name)?.label || "";
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.#fuel.innerText = String(unit.getMissionData().fuel + "%");
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.coalition = unit.getMissionData().coalition;
this.#silhouette.setAttribute( "style", `--loadout-background-image:url('/images/units/${aircraftDatabase.getByName( baseData.name )?.filename}');` );;
/* Add the loadout elements */
this.#loadoutContainer.replaceChildren(...Object.values(unit.getMissionData().ammo).map(
(ammo: any) => {
var el = document.createElement("div");
el.classList.add("pill", "loadout-item");
el.dataset.loadoutQty = ammo.count;
el.dataset.loadoutItem = ammo.desc.displayName;
return el;
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.item = ammo.desc.displayName;
return el;
}
));
} else {
items.innerText = "No loadout";
}
))
}
}
}

View File

@@ -2,9 +2,8 @@ import * as L from 'leaflet'
import { setConnected } from '..';
import { SpawnOptions } from '../controls/mapcontextmenu';
/* Edit here to change server address */
const REST_ADDRESS = "http://localhost:30000/olympus";
const DEMO_ADDRESS = "http://localhost:3000/demo";
var REST_ADDRESS = "http://localhost:30000/olympus";
var DEMO_ADDRESS = window.location.href + "demo";
const UNITS_URI = "units";
const LOGS_URI = "logs";
const AIRBASES_URI = "airbases";
@@ -24,9 +23,14 @@ export function GET(callback: CallableFunction, uri: string){
xmlHttp.open("GET", `${demoEnabled? DEMO_ADDRESS: REST_ADDRESS}/${uri}`, true);
xmlHttp.onload = function (e) {
var data = JSON.parse(xmlHttp.responseText);
callback(data);
lastUpdateTime = parseInt(data.time);
setConnected(true);
if (parseInt(data.time) > lastUpdateTime)
{
callback(data);
lastUpdateTime = parseInt(data.time);
if (isNaN(lastUpdateTime))
lastUpdateTime = 0;
setConnected(true);
}
};
xmlHttp.onerror = function () {
console.error("An error occurred during the XMLHttpRequest");
@@ -45,6 +49,24 @@ export function POST(request: object, callback: CallableFunction){
xhr.send(JSON.stringify(request));
}
export function getConfig(callback: CallableFunction) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.open("GET", window.location.href + "config", true);
xmlHttp.onload = function (e) {
var data = JSON.parse(xmlHttp.responseText);
callback(data);
};
xmlHttp.onerror = function () {
console.error("An error occurred during the XMLHttpRequest, could not retrieve configuration file");
};
xmlHttp.send(null);
}
export function setAddress(address: string, port: number) {
REST_ADDRESS = `http://${address}:${port}/olympus`
console.log(`Setting REST address to ${REST_ADDRESS}`)
}
export function getAirbases(callback: CallableFunction) {
GET(callback, AIRBASES_URI);
}

View File

@@ -3,7 +3,7 @@ import { UnitDatabase } from "./unitdatabase"
export class AircraftDatabase extends UnitDatabase {
constructor() {
super();
this.units = {
this.blueprints = {
"A-10C": {
"name": "A-10C",

View File

@@ -3,7 +3,7 @@ import { UnitDatabase } from "./unitdatabase"
export class GroundUnitsDatabase extends UnitDatabase {
constructor() {
super();
this.units = {
this.blueprints = {
"2B11 mortar": {
"name": "2B11 mortar",
"label": "2B11 mortar",

View File

@@ -1,7 +1,7 @@
import { Marker, LatLng, Polyline, Icon, DivIcon } from 'leaflet';
import { getMap, getUnitsManager } from '..';
import { rad2deg } from '../other/utils';
import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, landAt, setAltitude, setReactionToThreat, setROE, setSpeed } from '../server/server';
import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, getUnits, landAt, setAltitude, setReactionToThreat, setROE, setSpeed } from '../server/server';
import { aircraftDatabase } from './aircraftdatabase';
import { groundUnitsDatabase } from './groundunitsdatabase';
@@ -79,7 +79,7 @@ export class Unit extends Marker {
if (type === "NavyUnit") return NavyUnit;
}
constructor(ID: number, data: UpdateData, html: string) {
constructor(ID: number, data: UpdateData) {
super(new LatLng(0, 0), { riseOnHover: true });
this.ID = ID;
@@ -89,19 +89,12 @@ export class Unit extends Marker {
this.on('click', (e) => this.#onClick(e));
this.on('dblclick', (e) => this.#onDoubleClick(e));
this.on('contextmenu', (e) => this.#onContextMenu(e));
var icon = new DivIcon({
html: html,
className: 'leaflet-unit-marker',
iconAnchor: [0, 0]
});
this.setIcon(icon);
this.#pathPolyline = new Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1 });
this.#pathPolyline.addTo(getMap());
this.#targetsPolylines = [];
// Deselect units if they are hidden
/* Deselect units if they are hidden */
document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => {
setTimeout(() => {this.setSelected(this.getSelected() && !this.getHidden())}, 300);
});
@@ -110,7 +103,31 @@ export class Unit extends Marker {
setTimeout(() => {this.setSelected(this.getSelected() && !this.getHidden())}, 300);
});
/* Set the unit data */
this.setData(data);
/* Set the icon */
var icon = new DivIcon({
html: this.getMarkerHTML(),
className: 'leaflet-unit-marker',
iconAnchor: [0, 0]
});
this.setIcon(icon);
}
getMarkerHTML() {
return `<div class="unit" data-object="unit-${this.getMarkerCategory()}" data-coalition="${this.getMissionData().coalition}">
<div class="unit-selected-spotlight"></div>
<div class="unit-marker"></div>
<div class="unit-short-label"></div>
</div>`
}
getMarkerCategory()
{
// Overloaded by child classes
return "";
}
setData(data: UpdateData) {
@@ -118,6 +135,7 @@ export class Unit extends Marker {
var updateMarker = false;
if ((data.flightData.latitude != undefined && data.flightData.longitude != undefined && (this.getFlightData().latitude != data.flightData.latitude || this.getFlightData().longitude != data.flightData.longitude))
|| (data.flightData.heading != undefined && this.getFlightData().heading != data.flightData.heading)
|| (data.baseData.alive != undefined && this.getBaseData().alive != data.baseData.alive)
|| this.#forceUpdate || !getMap().hasLayer(this))
updateMarker = true;
@@ -253,13 +271,30 @@ export class Unit extends Marker {
this.getTaskData().activePath = undefined;
}
updateVisibility()
{
this.setHidden( document.body.getAttribute(`data-hide-${this.getMissionData().coalition}`) != null ||
document.body.getAttribute(`data-hide-${this.getMarkerCategory()}`) != null ||
!this.getBaseData().alive)
}
setHidden(hidden: boolean)
{
this.#hidden = hidden;
this.#hidden = hidden;
/* Add the marker if not present */
if (!getMap().hasLayer(this) && !this.getHidden()) {
this.addTo(getMap());
}
/* Hide the marker if necessary*/
if (getMap().hasLayer(this) && this.getHidden()) {
getMap().removeLayer(this);
}
}
getHidden() {
return (<HTMLElement>this.getElement()?.querySelector(`.unit`))?.offsetParent === null;
return this.#hidden;
}
getLeader() {
@@ -282,10 +317,6 @@ export class Unit extends Marker {
return wingmen;
}
forceUpdate() {
this.#forceUpdate = true;
}
attackUnit(targetID: number) {
/* Call DCS attackUnit function */
if (this.ID != targetID) {
@@ -355,11 +386,14 @@ export class Unit extends Marker {
var options = [
'Attack'
]
getMap().showUnitContextMenu(e);
getMap().getUnitContextMenu().setOptions(options, (option: string) => {
getMap().hideUnitContextMenu();
this.#executeAction(option);
});
if (getUnitsManager().getSelectedUnits().length > 0 && !(getUnitsManager().getSelectedUnits().includes(this)))
{
getMap().showUnitContextMenu(e);
getMap().getUnitContextMenu().setOptions(options, (option: string) => {
getMap().hideUnitContextMenu();
this.#executeAction(option);
});
}
}
#executeAction(action: string) {
@@ -368,21 +402,13 @@ export class Unit extends Marker {
}
#updateMarker() {
/* Add the marker if not present */
if (!getMap().hasLayer(this) && !this.getHidden()) {
this.addTo(getMap());
}
this.updateVisibility();
/* Hide the marker if necessary*/
if (getMap().hasLayer(this) && this.getHidden()) {
getMap().removeLayer(this);
}
else {
if (!this.getHidden()) {
this.setLatLng(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude));
var element = this.getElement();
if (element != null) {
element.querySelector(".unit-vvi")?.setAttribute("style", `height: ${15 + this.getFlightData().speed / 5}px;`);
element.querySelector(".unit")?.setAttribute("data-pilot", this.getMissionData().flags.human? "human": "ai");
@@ -401,9 +427,8 @@ export class Unit extends Marker {
}
const headingDeg = rad2deg( this.getFlightData().heading );
element.querySelectorAll( "[data-rotate-to-heading]" ).forEach( el => {
const headingDeg = rad2deg( this.getFlightData().heading );
let currentStyle = el.getAttribute( "style" ) || "";
el.setAttribute( "style", currentStyle + `transform:rotate(${headingDeg}deg);` );
});
@@ -494,93 +519,120 @@ export class AirUnit extends Unit {
export class Aircraft extends AirUnit {
constructor(ID: number, data: UnitData) {
super(ID, data,
`<div class="unit" data-object="unit-air-aircraft" data-status="" data-coalition="${data.missionData.coalition}">
<div class="unit-selected-spotlight"></div>
<div class="unit-marker-border"></div>
<div class="unit-status"></div>
<div class="unit-vvi" data-rotate-to-heading></div>
<div class="unit-hotgroup">
<div class="unit-hotgroup-id"></div>
</div>
<div class="unit-marker"></div>
<div class="unit-short-label">${aircraftDatabase.getShortLabelByName(data.baseData.name)}</div>
<div class="unit-fuel">
<div class="unit-fuel-level" style="width:100%;"></div>
</div>
<div class="unit-ammo">
<div class="unit-ammo-fox-1"></div>
<div class="unit-ammo-fox-2"></div>
<div class="unit-ammo-fox-3"></div>
<div class="unit-ammo-other"></div>
</div>
<div class="unit-summary">
<div class="unit-callsign">${data.baseData.unitName}</div>
<div class="unit-altitude"></div>
<div class="unit-speed"></div>
</div>
</div>`);
super(ID, data);
}
getMarkerHTML()
{
return `<div class="unit" data-object="unit-aircraft" data-status="" data-coalition="${this.getMissionData().coalition}">
<div class="unit-selected-spotlight"></div>
<div class="unit-marker-border"></div>
<div class="unit-status"></div>
<div class="unit-vvi" data-rotate-to-heading></div>
<div class="unit-hotgroup">
<div class="unit-hotgroup-id"></div>
</div>
<div class="unit-marker"></div>
<div class="unit-short-label">${aircraftDatabase.getByName(this.getBaseData().name)?.shortLabel || ""}</div>
<div class="unit-fuel">
<div class="unit-fuel-level" style="width:100%;"></div>
</div>
<div class="unit-ammo">
<div class="unit-ammo-fox-1"></div>
<div class="unit-ammo-fox-2"></div>
<div class="unit-ammo-fox-3"></div>
<div class="unit-ammo-other"></div>
</div>
<div class="unit-summary">
<div class="unit-callsign">${this.getBaseData().unitName}</div>
<div class="unit-altitude"></div>
<div class="unit-speed"></div>
</div>
</div>`
}
getMarkerCategory()
{
return "aircraft";
}
}
export class Helicopter extends AirUnit {
constructor(ID: number, data: UnitData) {
super(ID, data,
``);
super(ID, data);
}
getVisibilityCategory()
{
return "helicopter";
}
}
export class GroundUnit extends Unit {
constructor(ID: number, data: UnitData) {
// TODO this is very messy
var role = groundUnitsDatabase.getByName(data.baseData.name)?.loadouts[0].roles[0];
if (role == undefined)
role = "U";
var roleType = (role === "SAM") ? "sam" : "mi";
super(ID, data);
}
super(ID, data, `
<div class="unit" data-object="unit-ground-${roleType}" data-coalition="${data.missionData.coalition}">
<div class="unit-selected-spotlight"></div>
<div class="unit-marker"></div>
<div class="unit-short-label">${role?.substring(0, 1).toUpperCase()}</div>
</div>
`);
getMarkerHTML() {
var role = groundUnitsDatabase.getByName(this.getBaseData().name)?.loadouts[0].roles[0];
return `<div class="unit" data-object="unit-${this.getMarkerCategory()}" data-coalition="${this.getMissionData().coalition}">
<div class="unit-selected-spotlight"></div>
<div class="unit-marker"></div>
<div class="unit-short-label">${role?.substring(0, 1)?.toUpperCase() || ""}</div>
</div>`
}
getMarkerCategory()
{
// TODO this is very messy
var role = groundUnitsDatabase.getByName(this.getBaseData().name)?.loadouts[0].roles[0];
var markerCategory = (role === "SAM") ? "sam" : "groundunit";
return markerCategory;
}
}
export class NavyUnit extends Unit {
constructor(ID: number, data: UnitData) {
super(ID, data, `
<div class="unit" data-object="unit-naval" data-coalition="${data.missionData.coalition}">
<div class="unit-selected-spotlight"></div>
<div class="unit-marker"></div>
<div class="unit-short-label">N</div>
</div>
`);
super(ID, data);
}
getMarkerCategory() {
return "navyunit";
}
}
export class Weapon extends Unit {
constructor(ID: number, data: UnitData, html: string) {
super(ID, data, html);
constructor(ID: number, data: UnitData) {
super(ID, data);
this.setSelectable(false);
}
getMarkerHTML(): string {
return `<div class="unit" data-object="unit-${this.getMarkerCategory()}" data-coalition="${this.getMissionData().coalition}">
<div class="unit-selected-spotlight"></div>
<div class="unit-marker" data-rotate-to-heading></div>
<div class="unit-short-label"></div>
</div>`
}
}
export class Missile extends Weapon {
constructor(ID: number, data: UnitData) {
super(ID, data, `
<div class="unit" data-object="unit-weapon-missile" data-coalition="${data.missionData.coalition}">
<div class="unit-selected-spotlight"></div>
<div class="unit-marker" data-rotate-to-heading></div>
<div class="unit-short-label"></div>
</div>
`);
super(ID, data);
}
getMarkerCategory() {
return "missile";
}
}
export class Bomb extends Weapon {
constructor(ID: number, data: UnitData) {
super(ID, data, "");
super(ID, data);
}
getMarkerCategory() {
return "bomb";
}
}

View File

@@ -1,34 +1,18 @@
export class UnitDatabase {
units: {[key: string]: UnitBlueprint} = {};
blueprints: {[key: string]: UnitBlueprint} = {};
constructor()
{
}
getByName(name: string)
{
if (name in this.units)
return this.units[name];
return null;
}
getByLabel(label: string)
{
for (let unit in this.units)
{
if (this.units[unit].label === label)
return this.units[unit];
}
return null;
}
/* Returns a list of all possible roles in a database */
getRoles()
{
var roles: string[] = [];
for (let unit in this.units)
for (let unit in this.blueprints)
{
for (let loadout of this.units[unit].loadouts)
for (let loadout of this.blueprints[unit].loadouts)
{
for (let role of loadout.roles)
{
@@ -40,16 +24,36 @@ export class UnitDatabase {
return roles;
}
getLabelsByRole(role: string)
/* Gets a specific blueprint by name */
getByName(name: string)
{
if (name in this.blueprints)
return this.blueprints[name];
return null;
}
/* Gets a specific blueprint by label */
getByLabel(label: string)
{
for (let unit in this.blueprints)
{
if (this.blueprints[unit].label === label)
return this.blueprints[unit];
}
return null;
}
/* Get all blueprints by role */
getByRole(role: string)
{
var units = [];
for (let unit in this.units)
for (let unit in this.blueprints)
{
for (let loadout of this.units[unit].loadouts)
for (let loadout of this.blueprints[unit].loadouts)
{
if (loadout.roles.includes(role) || loadout.roles.includes(role.toLowerCase()))
{
units.push(this.units[unit].label)
units.push(this.blueprints[unit])
break;
}
}
@@ -57,10 +61,11 @@ export class UnitDatabase {
return units;
}
getLoadoutNamesByRole(unit: string, role: string)
/* Get the names of all the loadouts for a specific unit and for a specific role */
getLoadoutNamesByRole(name: string, role: string)
{
var loadouts = [];
for (let loadout of this.units[unit].loadouts)
for (let loadout of this.blueprints[name].loadouts)
{
if (loadout.roles.includes(role) || loadout.roles.includes(""))
{
@@ -70,37 +75,14 @@ export class UnitDatabase {
return loadouts;
}
getLoadoutsByName(unit: string, loadoutName: string)
/* Get the loadout content from the unit name and loadout name */
getLoadoutByName(name: string, loadoutName: string)
{
for (let loadout of this.units[unit].loadouts)
for (let loadout of this.blueprints[name].loadouts)
{
if (loadout.name === loadoutName)
{
return loadout;
}
}
return null;
}
getNameByLabel(label: string)
{
for (let name in this.units)
{
if (this.units[name].label === label)
{
return name;
}
}
return null;
}
getLabelByName(name: string)
{
return this.units[name] === undefined? name: this.units[name].label;
}
getShortLabelByName(name: string)
{
return this.units[name] === undefined? "U": this.units[name].shortLabel;
}
}

View File

@@ -8,6 +8,7 @@ export class UnitsManager {
#units: { [ID: number]: Unit };
#copiedUnits: Unit[];
#selectionEventDisabled: boolean = false;
#pasteDisabled: boolean = false;
constructor() {
this.#units = {};
@@ -330,10 +331,15 @@ export class UnitsManager {
pasteUnits()
{
for (let idx in this.#copiedUnits)
if (!this.#pasteDisabled)
{
var unit = this.#copiedUnits[idx];
cloneUnit(unit.ID, getMap().getMouseCoordinates());
for (let idx in this.#copiedUnits)
{
var unit = this.#copiedUnits[idx];
cloneUnit(unit.ID, getMap().getMouseCoordinates());
}
this.#pasteDisabled = true;
setTimeout(() => this.#pasteDisabled = false, 250);
}
}