mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
Merge pull request #290 from Pax1601/main
Merge main commits into airfield JSON addition branch
This commit is contained in:
2
client/src/@types/server.d.ts
vendored
2
client/src/@types/server.d.ts
vendored
@@ -8,7 +8,7 @@ interface AirbasesData {
|
||||
}
|
||||
|
||||
interface BullseyesData {
|
||||
bullseyes: {[key: string]: any},
|
||||
bullseyes: {[key: string]: {latitude: number, longitude: number, coalition: string}},
|
||||
}
|
||||
|
||||
interface LogData {
|
||||
|
||||
12
client/src/@types/unit.d.ts
vendored
12
client/src/@types/unit.d.ts
vendored
@@ -79,4 +79,16 @@ interface GeneralSettings {
|
||||
prohibitAG: boolean;
|
||||
prohibitAfterburner: boolean;
|
||||
prohibitAirWpn: boolean;
|
||||
}
|
||||
|
||||
interface UnitIconOptions {
|
||||
showState: boolean,
|
||||
showVvi: boolean,
|
||||
showHotgroup: boolean,
|
||||
showUnitIcon: boolean,
|
||||
showShortLabel: boolean,
|
||||
showFuel: boolean,
|
||||
showAmmo: boolean,
|
||||
showSummary: boolean,
|
||||
rotateToHeading: boolean
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ToggleableFeature } from "../toggleablefeature";
|
||||
import { ToggleableFeature } from "../features/toggleablefeature";
|
||||
import { AICFormation_Azimuth } from "./aicformation/azimuth";
|
||||
import { AICFormation_Range } from "./aicformation/range";
|
||||
import { AICFormation_Single } from "./aicformation/single";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { getUnitsManager } from "..";
|
||||
import { Panel } from "../panels/panel";
|
||||
import { Unit } from "./unit";
|
||||
import { Unit } from "../units/unit";
|
||||
|
||||
export class UnitDataTable extends Panel {
|
||||
constructor(id: string) {
|
||||
@@ -5,8 +5,7 @@ import { ContextMenu } from "./contextmenu";
|
||||
export class AirbaseContextMenu extends ContextMenu {
|
||||
#airbase: Airbase | null = null;
|
||||
|
||||
constructor(id: string)
|
||||
{
|
||||
constructor(id: string) {
|
||||
super(id);
|
||||
document.addEventListener("contextMenuSpawnAirbase", (e: any) => {
|
||||
this.showSpawnMenu();
|
||||
@@ -19,8 +18,7 @@ export class AirbaseContextMenu extends ContextMenu {
|
||||
})
|
||||
}
|
||||
|
||||
setAirbase(airbase: Airbase)
|
||||
{
|
||||
setAirbase(airbase: Airbase) {
|
||||
this.#airbase = airbase;
|
||||
this.setName(airbase.getName());
|
||||
this.setProperties(airbase.getProperties());
|
||||
@@ -29,24 +27,21 @@ export class AirbaseContextMenu extends ContextMenu {
|
||||
this.enableLandButton(getUnitsManager().getSelectedUnitsType() === "Aircraft" && (getUnitsManager().getSelectedUnitsCoalition() === airbase.getCoalition() || airbase.getCoalition() === "neutral"))
|
||||
}
|
||||
|
||||
setName(airbaseName: string)
|
||||
{
|
||||
setName(airbaseName: string) {
|
||||
var nameDiv = <HTMLElement>this.getContainer()?.querySelector("#airbase-name");
|
||||
if (nameDiv != null)
|
||||
nameDiv.innerText = airbaseName;
|
||||
nameDiv.innerText = airbaseName;
|
||||
}
|
||||
|
||||
setProperties(airbaseProperties: string[])
|
||||
{
|
||||
setProperties(airbaseProperties: string[]) {
|
||||
this.getContainer()?.querySelector("#airbase-properties")?.replaceChildren(...airbaseProperties.map((property: string) => {
|
||||
var div = document.createElement("div");
|
||||
div.innerText = property;
|
||||
return div;
|
||||
}), );
|
||||
}),);
|
||||
}
|
||||
|
||||
setParkings(airbaseParkings: string[])
|
||||
{
|
||||
setParkings(airbaseParkings: string[]) {
|
||||
this.getContainer()?.querySelector("#airbase-parking")?.replaceChildren(...airbaseParkings.map((parking: string) => {
|
||||
var div = document.createElement("div");
|
||||
div.innerText = parking;
|
||||
@@ -54,22 +49,18 @@ export class AirbaseContextMenu extends ContextMenu {
|
||||
}));
|
||||
}
|
||||
|
||||
setCoalition(coalition: string)
|
||||
{
|
||||
setCoalition(coalition: string) {
|
||||
(<HTMLElement>this.getContainer()?.querySelector("#spawn-airbase-aircraft-button")).dataset.activeCoalition = coalition;
|
||||
}
|
||||
|
||||
enableLandButton(enableLandButton: boolean)
|
||||
{
|
||||
enableLandButton(enableLandButton: boolean) {
|
||||
this.getContainer()?.querySelector("#land-here-button")?.classList.toggle("hide", !enableLandButton);
|
||||
}
|
||||
|
||||
showSpawnMenu()
|
||||
{
|
||||
if (this.#airbase != null)
|
||||
{
|
||||
showSpawnMenu() {
|
||||
if (this.#airbase != null) {
|
||||
setActiveCoalition(this.#airbase.getCoalition());
|
||||
getMap().showMapContextMenu({originalEvent: {x: this.getX(), y: this.getY(), latlng: this.getLatLng()}});
|
||||
getMap().showMapContextMenu({ originalEvent: { x: this.getX(), y: this.getY(), latlng: this.getLatLng() } });
|
||||
getMap().getMapContextMenu().hideUpperBar();
|
||||
getMap().getMapContextMenu().showSubMenu("aircraft");
|
||||
getMap().getMapContextMenu().setAirbaseName(this.#airbase.getName());
|
||||
|
||||
@@ -23,28 +23,23 @@ export class ContextMenu {
|
||||
this.#container?.classList.toggle("hide", true);
|
||||
}
|
||||
|
||||
getContainer()
|
||||
{
|
||||
getContainer() {
|
||||
return this.#container;
|
||||
}
|
||||
|
||||
getLatLng()
|
||||
{
|
||||
getLatLng() {
|
||||
return this.#latlng;
|
||||
}
|
||||
|
||||
getX()
|
||||
{
|
||||
getX() {
|
||||
return this.#x;
|
||||
}
|
||||
|
||||
getY()
|
||||
{
|
||||
getY() {
|
||||
return this.#y;
|
||||
}
|
||||
|
||||
clip()
|
||||
{
|
||||
clip() {
|
||||
if (this.#container != null) {
|
||||
if (this.#x + this.#container.offsetWidth < window.innerWidth)
|
||||
this.#container.style.left = this.#x + "px";
|
||||
|
||||
@@ -7,17 +7,16 @@ export class Dropdown {
|
||||
#optionsList: string[] = [];
|
||||
#index: number = 0;
|
||||
|
||||
constructor(ID: string, callback: CallableFunction, options: string[] | null = null)
|
||||
{
|
||||
this.#element = <HTMLElement>document.getElementById(ID);
|
||||
this.#options = <HTMLElement>this.#element.querySelector(".ol-select-options");
|
||||
this.#value = <HTMLElement>this.#element.querySelector(".ol-select-value");
|
||||
constructor(ID: string, callback: CallableFunction, options: string[] | null = null) {
|
||||
this.#element = <HTMLElement>document.getElementById(ID);
|
||||
this.#options = <HTMLElement>this.#element.querySelector(".ol-select-options");
|
||||
this.#value = <HTMLElement>this.#element.querySelector(".ol-select-value");
|
||||
this.#defaultValue = this.#value.innerText;
|
||||
this.#callback = callback;
|
||||
this.#callback = callback;
|
||||
|
||||
if (options != null) {
|
||||
this.setOptions(options);
|
||||
}
|
||||
}
|
||||
|
||||
this.#value.addEventListener("click", (ev) => {
|
||||
this.#element.classList.toggle("is-open");
|
||||
@@ -31,11 +30,10 @@ export class Dropdown {
|
||||
}
|
||||
});
|
||||
|
||||
this.#options.classList.add( "ol-scrollable" );
|
||||
this.#options.classList.add("ol-scrollable");
|
||||
}
|
||||
|
||||
setOptions(optionsList: string[])
|
||||
{
|
||||
setOptions(optionsList: string[]) {
|
||||
this.#optionsList = optionsList;
|
||||
this.#options.replaceChildren(...optionsList.map((option: string, idx: number) => {
|
||||
var div = document.createElement("div");
|
||||
@@ -48,7 +46,9 @@ export class Dropdown {
|
||||
|
||||
button.addEventListener("click", (e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
this.#value.innerHTML = `<div class = "ol-ellipsed"> ${option} </div>`;
|
||||
this.#value = document.createElement("div");
|
||||
this.#value.classList.add("ol-ellipsed");
|
||||
this.#value.innerText = option;
|
||||
this.#close();
|
||||
this.#callback(option, e);
|
||||
this.#index = idx;
|
||||
@@ -57,21 +57,20 @@ export class Dropdown {
|
||||
}));
|
||||
}
|
||||
|
||||
selectText( text:string ) {
|
||||
|
||||
const index = [].slice.call( this.#options.children ).findIndex( ( opt:Element ) => opt.querySelector( "button" )?.innerText === text );
|
||||
if ( index > -1 ) {
|
||||
this.selectValue( index );
|
||||
selectText(text: string) {
|
||||
const index = [].slice.call(this.#options.children).findIndex((opt: Element) => opt.querySelector("button")?.innerText === text);
|
||||
if (index > -1) {
|
||||
this.selectValue(index);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
selectValue(idx: number)
|
||||
{
|
||||
if (idx < this.#optionsList.length)
|
||||
{
|
||||
selectValue(idx: number) {
|
||||
if (idx < this.#optionsList.length) {
|
||||
var option = this.#optionsList[idx];
|
||||
this.#value.innerHTML = `<div class = "ol-ellipsed"> ${option} </div>`;
|
||||
var el = document.createElement("div");
|
||||
el.classList.add("ol-ellipsed");
|
||||
el.innerText = option;
|
||||
this.#value.appendChild(el);
|
||||
this.#index = idx;
|
||||
this.#close();
|
||||
this.#callback(option);
|
||||
@@ -91,8 +90,8 @@ export class Dropdown {
|
||||
}
|
||||
|
||||
setValue(value: string) {
|
||||
var index = this.#optionsList.findIndex((option) => {return option === value});
|
||||
if (index > -1)
|
||||
var index = this.#optionsList.findIndex((option) => { return option === value });
|
||||
if (index > -1)
|
||||
this.selectValue(index);
|
||||
}
|
||||
|
||||
@@ -102,21 +101,21 @@ export class Dropdown {
|
||||
|
||||
#clip() {
|
||||
const options = this.#options;
|
||||
const bounds = options.getBoundingClientRect();
|
||||
this.#element.dataset.position = ( bounds.bottom > window.innerHeight ) ? "top" : "";
|
||||
const bounds = options.getBoundingClientRect();
|
||||
this.#element.dataset.position = (bounds.bottom > window.innerHeight) ? "top" : "";
|
||||
}
|
||||
|
||||
#close() {
|
||||
this.#element.classList.remove( "is-open" );
|
||||
this.#element.classList.remove("is-open");
|
||||
this.#element.dataset.position = "";
|
||||
}
|
||||
|
||||
#open() {
|
||||
this.#element.classList.add( "is-open" );
|
||||
this.#element.classList.add("is-open");
|
||||
}
|
||||
|
||||
#toggle() {
|
||||
if ( this.#element.classList.contains( "is-open" ) ) {
|
||||
if (this.#element.classList.contains("is-open")) {
|
||||
this.#close();
|
||||
} else {
|
||||
this.#open();
|
||||
|
||||
@@ -41,8 +41,7 @@ export class MapContextMenu extends ContextMenu {
|
||||
document.addEventListener("contextMenuDeployAircraft", () => {
|
||||
this.hide();
|
||||
this.#spawnOptions.coalition = getActiveCoalition();
|
||||
if (this.#spawnOptions)
|
||||
{
|
||||
if (this.#spawnOptions) {
|
||||
getMap().addTemporaryMarker(this.#spawnOptions.latlng);
|
||||
spawnAircraft(this.#spawnOptions);
|
||||
}
|
||||
@@ -51,8 +50,7 @@ export class MapContextMenu extends ContextMenu {
|
||||
document.addEventListener("contextMenuDeployGroundUnit", () => {
|
||||
this.hide();
|
||||
this.#spawnOptions.coalition = getActiveCoalition();
|
||||
if (this.#spawnOptions)
|
||||
{
|
||||
if (this.#spawnOptions) {
|
||||
getMap().addTemporaryMarker(this.#spawnOptions.latlng);
|
||||
spawnGroundUnit(this.#spawnOptions);
|
||||
}
|
||||
@@ -189,10 +187,10 @@ export class MapContextMenu extends ContextMenu {
|
||||
this.#spawnOptions.role = role;
|
||||
this.#resetGroundUnitType();
|
||||
|
||||
const types = groundUnitsDatabase.getByRole(role).map((blueprint) => { return blueprint.label } );
|
||||
const types = groundUnitsDatabase.getByRole(role).map((blueprint) => { return blueprint.label });
|
||||
types.sort();
|
||||
|
||||
this.#groundUnitTypeDropdown.setOptions( types );
|
||||
this.#groundUnitTypeDropdown.setOptions(types);
|
||||
this.#groundUnitTypeDropdown.selectValue(0);
|
||||
this.clip();
|
||||
}
|
||||
@@ -205,8 +203,8 @@ export class MapContextMenu extends ContextMenu {
|
||||
|
||||
const roles = groundUnitsDatabase.getRoles();
|
||||
roles.sort();
|
||||
|
||||
this.#groundUnitRoleDropdown.setOptions( roles );
|
||||
|
||||
this.#groundUnitRoleDropdown.setOptions(roles);
|
||||
this.clip();
|
||||
}
|
||||
|
||||
|
||||
@@ -23,8 +23,7 @@ export class Slider {
|
||||
if (this.#container != null) {
|
||||
this.#display = this.#container.style.display;
|
||||
this.#slider = <HTMLInputElement>this.#container.querySelector("input");
|
||||
if (this.#slider != null)
|
||||
{
|
||||
if (this.#slider != null) {
|
||||
this.#slider.addEventListener("input", (e: any) => this.#onInput());
|
||||
this.#slider.addEventListener("mousedown", (e: any) => this.#onStart());
|
||||
this.#slider.addEventListener("mouseup", (e: any) => this.#onFinalize());
|
||||
@@ -33,93 +32,77 @@ export class Slider {
|
||||
}
|
||||
}
|
||||
|
||||
show()
|
||||
{
|
||||
show() {
|
||||
if (this.#container != null)
|
||||
this.#container.style.display = this.#display;
|
||||
}
|
||||
|
||||
hide()
|
||||
{
|
||||
hide() {
|
||||
if (this.#container != null)
|
||||
this.#container.style.display = 'none';
|
||||
}
|
||||
|
||||
setActive(newActive: boolean)
|
||||
{
|
||||
if (this.#container && !this.#dragged)
|
||||
{
|
||||
setActive(newActive: boolean) {
|
||||
if (this.#container && !this.#dragged) {
|
||||
this.#container.classList.toggle("active", newActive);
|
||||
if (!newActive && this.#valueText != null)
|
||||
this.#valueText.innerText = "Mixed values";
|
||||
}
|
||||
}
|
||||
|
||||
setMinMax(newMinValue: number, newMaxValue: number)
|
||||
{
|
||||
setMinMax(newMinValue: number, newMaxValue: number) {
|
||||
this.#minValue = newMinValue;
|
||||
this.#maxValue = newMaxValue;
|
||||
this.#updateMax();
|
||||
}
|
||||
|
||||
setIncrement(newIncrement: number)
|
||||
{
|
||||
setIncrement(newIncrement: number) {
|
||||
this.#increment = newIncrement;
|
||||
this.#updateMax();
|
||||
}
|
||||
|
||||
setValue(newValue: number)
|
||||
{
|
||||
setValue(newValue: number) {
|
||||
// Disable value setting if the user is dragging the element
|
||||
if (!this.#dragged)
|
||||
{
|
||||
if (!this.#dragged) {
|
||||
this.#value = newValue;
|
||||
if (this.#slider != null)
|
||||
this.#slider.value = String((newValue - this.#minValue) / (this.#maxValue - this.#minValue) * parseFloat(this.#slider.max));
|
||||
this.#slider.value = String((newValue - this.#minValue) / (this.#maxValue - this.#minValue) * parseFloat(this.#slider.max));
|
||||
this.#onValue()
|
||||
}
|
||||
}
|
||||
|
||||
getValue()
|
||||
{
|
||||
getValue() {
|
||||
return this.#value;
|
||||
}
|
||||
|
||||
getDragged()
|
||||
{
|
||||
getDragged() {
|
||||
return this.#dragged;
|
||||
}
|
||||
|
||||
#updateMax()
|
||||
{
|
||||
#updateMax() {
|
||||
var oldValue = this.getValue();
|
||||
if (this.#slider != null)
|
||||
this.#slider.max = String((this.#maxValue - this.#minValue) / this.#increment);
|
||||
this.setValue(oldValue);
|
||||
}
|
||||
|
||||
#onValue()
|
||||
{
|
||||
#onValue() {
|
||||
if (this.#valueText != null && this.#slider != null)
|
||||
this.#valueText.innerHTML = this.#minValue + Math.round(parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * (this.#maxValue - this.#minValue)) + this.#unit
|
||||
this.#valueText.innerText = this.#minValue + Math.round(parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * (this.#maxValue - this.#minValue)) + this.#unit
|
||||
this.setActive(true);
|
||||
}
|
||||
|
||||
#onInput()
|
||||
{
|
||||
#onInput() {
|
||||
this.#onValue();
|
||||
}
|
||||
|
||||
#onStart()
|
||||
{
|
||||
#onStart() {
|
||||
this.#dragged = true;
|
||||
}
|
||||
|
||||
#onFinalize()
|
||||
{
|
||||
#onFinalize() {
|
||||
this.#dragged = false;
|
||||
if (this.#slider != null)
|
||||
{
|
||||
if (this.#slider != null) {
|
||||
this.#value = this.#minValue + parseFloat(this.#slider.value) / parseFloat(this.#slider.max) * (this.#maxValue - this.#minValue);
|
||||
this.#callback(this.getValue());
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { getUnitsManager } from "..";
|
||||
import { deg2rad } from "../other/utils";
|
||||
import { ContextMenu } from "./contextmenu";
|
||||
|
||||
@@ -10,21 +9,19 @@ export class UnitContextMenu extends ContextMenu {
|
||||
|
||||
document.addEventListener("applyCustomFormation", () => {
|
||||
var dialog = document.getElementById("custom-formation-dialog");
|
||||
if (dialog)
|
||||
{
|
||||
if (dialog) {
|
||||
dialog.classList.add("hide");
|
||||
var clock = 1;
|
||||
while (clock < 8)
|
||||
{
|
||||
if ((<HTMLInputElement> dialog.querySelector(`#formation-${clock}`)).checked)
|
||||
while (clock < 8) {
|
||||
if ((<HTMLInputElement>dialog.querySelector(`#formation-${clock}`)).checked)
|
||||
break
|
||||
clock++;
|
||||
}
|
||||
var angleDeg = 360 - (clock - 1) * 45;
|
||||
var angleRad = deg2rad(angleDeg);
|
||||
var distance = parseInt((<HTMLInputElement> dialog.querySelector(`#distance`)?.querySelector("input")).value) * 0.3048;
|
||||
var upDown = parseInt((<HTMLInputElement> dialog.querySelector(`#up-down`)?.querySelector("input")).value) * 0.3048;
|
||||
|
||||
var distance = parseInt((<HTMLInputElement>dialog.querySelector(`#distance`)?.querySelector("input")).value) * 0.3048;
|
||||
var upDown = parseInt((<HTMLInputElement>dialog.querySelector(`#up-down`)?.querySelector("input")).value) * 0.3048;
|
||||
|
||||
// X: front-rear, positive front
|
||||
// Y: top-bottom, positive top
|
||||
// Z: left-right, positive right
|
||||
@@ -34,7 +31,7 @@ export class UnitContextMenu extends ContextMenu {
|
||||
var z = distance * Math.sin(angleRad);
|
||||
|
||||
if (this.#customFormationCallback)
|
||||
this.#customFormationCallback({"x": x, "y": y, "z": z})
|
||||
this.#customFormationCallback({ "x": x, "y": y, "z": z })
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -43,13 +40,16 @@ export class UnitContextMenu extends ContextMenu {
|
||||
this.#customFormationCallback = callback;
|
||||
}
|
||||
|
||||
setOptions(options: {[key: string]: string}, callback: CallableFunction)
|
||||
{
|
||||
this.getContainer()?.replaceChildren(...Object.keys(options).map((option: string, idx: number) =>
|
||||
{
|
||||
setOptions(options: { [key: string]: {text: string, tooltip: string }}, callback: CallableFunction) {
|
||||
this.getContainer()?.replaceChildren(...Object.keys(options).map((key: string, idx: number) => {
|
||||
const option = options[key];
|
||||
var button = document.createElement("button");
|
||||
button.innerHTML = options[option];
|
||||
button.addEventListener("click", () => callback(option));
|
||||
var el = document.createElement("div");
|
||||
el.title = option.tooltip;
|
||||
el.innerText = option.text;
|
||||
el.id = key;
|
||||
button.addEventListener("click", () => callback(key));
|
||||
button.appendChild(el);
|
||||
return (button);
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -23,13 +23,13 @@ class FeatureSwitch {
|
||||
userPreference;
|
||||
|
||||
|
||||
constructor( config:FeatureSwitchInterface ) {
|
||||
constructor(config: FeatureSwitchInterface) {
|
||||
|
||||
this.defaultEnabled = config.defaultEnabled;
|
||||
this.label = config.label;
|
||||
this.masterSwitch = config.masterSwitch;
|
||||
this.name = config.name;
|
||||
this.onEnabled = config.onEnabled;
|
||||
this.label = config.label;
|
||||
this.masterSwitch = config.masterSwitch;
|
||||
this.name = config.name;
|
||||
this.onEnabled = config.onEnabled;
|
||||
|
||||
this.userPreference = this.getUserPreference();
|
||||
|
||||
@@ -38,16 +38,16 @@ class FeatureSwitch {
|
||||
|
||||
getUserPreference() {
|
||||
|
||||
let preferences = JSON.parse( localStorage.getItem( "featureSwitches" ) || "{}" );
|
||||
let preferences = JSON.parse(localStorage.getItem("featureSwitches") || "{}");
|
||||
|
||||
return ( preferences.hasOwnProperty( this.name ) ) ? preferences[ this.name ] : this.defaultEnabled;
|
||||
return (preferences.hasOwnProperty(this.name)) ? preferences[this.name] : this.defaultEnabled;
|
||||
|
||||
}
|
||||
|
||||
|
||||
isEnabled() {
|
||||
|
||||
if ( !this.masterSwitch ) {
|
||||
if (!this.masterSwitch) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ class FeatureSwitch {
|
||||
|
||||
export class FeatureSwitches {
|
||||
|
||||
#featureSwitches:FeatureSwitch[] = [
|
||||
#featureSwitches: FeatureSwitch[] = [
|
||||
|
||||
new FeatureSwitch({
|
||||
"defaultEnabled": false,
|
||||
@@ -66,7 +66,7 @@ export class FeatureSwitches {
|
||||
"masterSwitch": true,
|
||||
"name": "aic"
|
||||
}),
|
||||
|
||||
|
||||
new FeatureSwitch({
|
||||
"defaultEnabled": false,
|
||||
"label": "AI Formations",
|
||||
@@ -74,21 +74,21 @@ export class FeatureSwitches {
|
||||
"name": "ai-formations",
|
||||
"removeArtifactsIfDisabled": false
|
||||
}),
|
||||
|
||||
|
||||
new FeatureSwitch({
|
||||
"defaultEnabled": false,
|
||||
"label": "ATC",
|
||||
"masterSwitch": true,
|
||||
"name": "atc"
|
||||
}),
|
||||
|
||||
|
||||
new FeatureSwitch({
|
||||
"defaultEnabled": false,
|
||||
"label": "Force show unit control panel",
|
||||
"masterSwitch": true,
|
||||
"name": "forceShowUnitControlPanel"
|
||||
}),
|
||||
|
||||
|
||||
new FeatureSwitch({
|
||||
"defaultEnabled": true,
|
||||
"label": "Show splash screen",
|
||||
@@ -108,41 +108,41 @@ export class FeatureSwitches {
|
||||
}
|
||||
|
||||
|
||||
getSwitch( switchName:string ) {
|
||||
getSwitch(switchName: string) {
|
||||
|
||||
return this.#featureSwitches.find( featureSwitch => featureSwitch.name === switchName );
|
||||
return this.#featureSwitches.find(featureSwitch => featureSwitch.name === switchName);
|
||||
|
||||
}
|
||||
|
||||
|
||||
#testSwitches() {
|
||||
for ( const featureSwitch of this.#featureSwitches ) {
|
||||
if ( featureSwitch.isEnabled() ) {
|
||||
if ( typeof featureSwitch.onEnabled === "function" ) {
|
||||
for (const featureSwitch of this.#featureSwitches) {
|
||||
if (featureSwitch.isEnabled()) {
|
||||
if (typeof featureSwitch.onEnabled === "function") {
|
||||
featureSwitch.onEnabled();
|
||||
}
|
||||
} else {
|
||||
document.querySelectorAll( "[data-feature-switch='" + featureSwitch.name + "']" ).forEach( el => {
|
||||
if ( featureSwitch.removeArtifactsIfDisabled === false ) {
|
||||
document.querySelectorAll("[data-feature-switch='" + featureSwitch.name + "']").forEach(el => {
|
||||
if (featureSwitch.removeArtifactsIfDisabled === false) {
|
||||
el.remove();
|
||||
} else {
|
||||
el.classList.add( "hide" );
|
||||
el.classList.add("hide");
|
||||
}
|
||||
});
|
||||
}
|
||||
document.body.classList.toggle( "feature-" + featureSwitch.name, featureSwitch.isEnabled() );
|
||||
document.body.classList.toggle("feature-" + featureSwitch.name, featureSwitch.isEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
savePreferences() {
|
||||
|
||||
let preferences:any = {};
|
||||
let preferences: any = {};
|
||||
|
||||
for ( const featureSwitch of this.#featureSwitches ) {
|
||||
preferences[ featureSwitch.name ] = featureSwitch.isEnabled();
|
||||
for (const featureSwitch of this.#featureSwitches) {
|
||||
preferences[featureSwitch.name] = featureSwitch.isEnabled();
|
||||
}
|
||||
|
||||
localStorage.setItem( "featureSwitches", JSON.stringify( preferences ) );
|
||||
localStorage.setItem("featureSwitches", JSON.stringify(preferences));
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
export abstract class ToggleableFeature {
|
||||
|
||||
#status:boolean = false;
|
||||
#status: boolean = false;
|
||||
|
||||
|
||||
constructor( defaultStatus:boolean ) {
|
||||
constructor(defaultStatus: boolean) {
|
||||
|
||||
this.#status = defaultStatus;
|
||||
|
||||
@@ -12,17 +12,17 @@ export abstract class ToggleableFeature {
|
||||
}
|
||||
|
||||
|
||||
getStatus() : boolean {
|
||||
getStatus(): boolean {
|
||||
return this.#status;
|
||||
}
|
||||
|
||||
|
||||
protected onStatusUpdate() {}
|
||||
protected onStatusUpdate() { }
|
||||
|
||||
|
||||
toggleStatus( force?:boolean ) : void {
|
||||
toggleStatus(force?: boolean): void {
|
||||
|
||||
if ( force ) {
|
||||
if (force) {
|
||||
this.#status = force;
|
||||
} else {
|
||||
this.#status = !this.#status;
|
||||
@@ -7,10 +7,10 @@ import { UnitControlPanel } from "./panels/unitcontrolpanel";
|
||||
import { MouseInfoPanel } from "./panels/mouseinfopanel";
|
||||
import { AIC } from "./aic/aic";
|
||||
import { ATC } from "./atc/atc";
|
||||
import { FeatureSwitches } from "./featureswitches";
|
||||
import { FeatureSwitches } from "./features/featureswitches";
|
||||
import { LogPanel } from "./panels/logpanel";
|
||||
import { getConfig, getPaused, setAddress, setCredentials, setPaused, startUpdate, toggleDemoEnabled } from "./server/server";
|
||||
import { UnitDataTable } from "./units/unitdatatable";
|
||||
import { UnitDataTable } from "./atc/unitdatatable";
|
||||
import { keyEventWasInInput } from "./other/utils";
|
||||
import { Popup } from "./popups/popup";
|
||||
import { Dropdown } from "./controls/dropdown";
|
||||
@@ -140,28 +140,14 @@ function setupEvents() {
|
||||
case "Space":
|
||||
setPaused(!getPaused());
|
||||
break;
|
||||
case "KeyW":
|
||||
case "KeyA":
|
||||
case "KeyS":
|
||||
case "KeyD":
|
||||
case "ArrowLeft":
|
||||
case "ArrowRight":
|
||||
case "ArrowUp":
|
||||
case "ArrowDown":
|
||||
case "KeyW": case "KeyA": case "KeyS": case "KeyD":
|
||||
case "ArrowLeft": case "ArrowRight": case "ArrowUp": case "ArrowDown":
|
||||
getMap().handleMapPanning(ev);
|
||||
break;
|
||||
case "Digit1":
|
||||
case "Digit2":
|
||||
case "Digit3":
|
||||
case "Digit4":
|
||||
case "Digit5":
|
||||
case "Digit6":
|
||||
case "Digit7":
|
||||
case "Digit8":
|
||||
case "Digit9":
|
||||
case "Digit1": case "Digit2": case "Digit3": case "Digit4": case "Digit5": case "Digit6": case "Digit7": case "Digit8": case "Digit9":
|
||||
// Using the substring because the key will be invalid when pressing the Shift key
|
||||
if (ev.ctrlKey && ev.shiftKey)
|
||||
getUnitsManager().selectedUnitsAddToHotgroup(parseInt(ev.code.substring(5)));
|
||||
getUnitsManager().selectedUnitsAddToHotgroup(parseInt(ev.code.substring(5)));
|
||||
else if (ev.ctrlKey && !ev.shiftKey)
|
||||
getUnitsManager().selectedUnitsSetHotgroup(parseInt(ev.code.substring(5)));
|
||||
else
|
||||
@@ -176,14 +162,7 @@ function setupEvents() {
|
||||
return;
|
||||
}
|
||||
switch (ev.code) {
|
||||
case "KeyW":
|
||||
case "KeyA":
|
||||
case "KeyS":
|
||||
case "KeyD":
|
||||
case "ArrowLeft":
|
||||
case "ArrowRight":
|
||||
case "ArrowUp":
|
||||
case "ArrowDown":
|
||||
case "KeyW": case "KeyA": case "KeyS": case "KeyD": case "ArrowLeft": case "ArrowRight": case "ArrowUp": case "ArrowDown":
|
||||
getMap().handleMapPanning(ev);
|
||||
break;
|
||||
}
|
||||
@@ -201,8 +180,8 @@ function setupEvents() {
|
||||
|
||||
document.addEventListener("tryConnection", () => {
|
||||
const form = document.querySelector("#splash-content")?.querySelector("#authentication-form");
|
||||
const username = (<HTMLInputElement> (form?.querySelector("#username"))).value;
|
||||
const password = (<HTMLInputElement> (form?.querySelector("#password"))).value;
|
||||
const username = (<HTMLInputElement>(form?.querySelector("#username"))).value;
|
||||
const password = (<HTMLInputElement>(form?.querySelector("#password"))).value;
|
||||
setCredentials(username, btoa("admin" + ":" + password));
|
||||
|
||||
/* Start periodically requesting updates */
|
||||
@@ -214,6 +193,7 @@ function setupEvents() {
|
||||
document.addEventListener("reloadPage", () => {
|
||||
location.reload();
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
export function getMap() {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Map } from 'leaflet';
|
||||
import { Handler} from 'leaflet';
|
||||
import { Handler } from 'leaflet';
|
||||
import { Util } from 'leaflet';
|
||||
import { DomUtil } from 'leaflet';
|
||||
import { DomEvent } from 'leaflet';
|
||||
import { LatLngBounds } from 'leaflet';
|
||||
import { Bounds } from 'leaflet';
|
||||
import { Bounds } from 'leaflet';
|
||||
|
||||
export var BoxSelect = Handler.extend({
|
||||
initialize: function (map: Map) {
|
||||
@@ -82,12 +82,12 @@ export var BoxSelect = Handler.extend({
|
||||
this._point = this._map.mouseEventToContainerPoint(e);
|
||||
|
||||
var bounds = new Bounds(this._point, this._startPoint),
|
||||
size = bounds.getSize();
|
||||
size = bounds.getSize();
|
||||
|
||||
if (bounds.min != undefined)
|
||||
DomUtil.setPosition(this._box, bounds.min);
|
||||
|
||||
this._box.style.width = size.x + 'px';
|
||||
this._box.style.width = size.x + 'px';
|
||||
this._box.style.height = size.y + 'px';
|
||||
},
|
||||
|
||||
@@ -113,7 +113,7 @@ export var BoxSelect = Handler.extend({
|
||||
if ((e.which !== 1) && (e.button !== 0)) { return; }
|
||||
|
||||
this._finish();
|
||||
|
||||
|
||||
if (!this._moved) { return; }
|
||||
// Postpone to next JS tick so internal click event handling
|
||||
// still see it as "moved".
|
||||
@@ -121,8 +121,8 @@ export var BoxSelect = Handler.extend({
|
||||
var bounds = new LatLngBounds(
|
||||
this._map.containerPointToLatLng(this._startPoint),
|
||||
this._map.containerPointToLatLng(this._point));
|
||||
|
||||
this._map.fire('selectionend', {selectionBounds: bounds});
|
||||
|
||||
this._map.fire('selectionend', { selectionBounds: bounds });
|
||||
},
|
||||
|
||||
_onKeyDown: function (e: any) {
|
||||
|
||||
12
client/src/map/clickableminimap.ts
Normal file
12
client/src/map/clickableminimap.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { MiniMap, MiniMapOptions } from "leaflet-control-mini-map";
|
||||
|
||||
export class ClickableMiniMap extends MiniMap {
|
||||
constructor(layer: L.TileLayer | L.LayerGroup, options?: MiniMapOptions) {
|
||||
super(layer, options);
|
||||
}
|
||||
|
||||
getMap() {
|
||||
//@ts-ignore needed to access not exported member. A bit of a hack, required to access click events //TODO: fix me
|
||||
return this._miniMap;
|
||||
}
|
||||
}
|
||||
25
client/src/map/custommarker.ts
Normal file
25
client/src/map/custommarker.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { DivIcon, Map, Marker } from "leaflet";
|
||||
import { MarkerOptions } from "leaflet";
|
||||
import { LatLngExpression } from "leaflet";
|
||||
|
||||
export class CustomMarker extends Marker {
|
||||
constructor(latlng: LatLngExpression, options?: MarkerOptions) {
|
||||
super(latlng, options);
|
||||
}
|
||||
|
||||
onAdd(map: Map): this {
|
||||
this.setIcon(new DivIcon()); // Default empty icon
|
||||
super.onAdd(map);
|
||||
this.createIcon();
|
||||
return this;
|
||||
}
|
||||
|
||||
onRemove(map: Map): this {
|
||||
super.onRemove(map);
|
||||
return this;
|
||||
}
|
||||
|
||||
createIcon() {
|
||||
/* Overloaded by child classes */
|
||||
}
|
||||
}
|
||||
15
client/src/map/destinationpreviewmarker.ts
Normal file
15
client/src/map/destinationpreviewmarker.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { DivIcon } from "leaflet";
|
||||
import { CustomMarker } from "./custommarker";
|
||||
|
||||
export class DestinationPreviewMarker extends CustomMarker {
|
||||
createIcon() {
|
||||
this.setIcon(new DivIcon({
|
||||
iconSize: [52, 52],
|
||||
iconAnchor: [26, 26],
|
||||
className: "leaflet-destination-preview"
|
||||
}));
|
||||
var el = document.createElement("div");
|
||||
el.classList.add("ol-destination-preview-icon");
|
||||
this.getElement()?.appendChild(el);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
import * as L from "leaflet"
|
||||
import { MiniMap, MiniMapOptions } from "leaflet-control-mini-map";
|
||||
|
||||
import { getUnitsManager } from "..";
|
||||
import { BoxSelect } from "./boxselect";
|
||||
import { MapContextMenu, SpawnOptions } from "../controls/mapcontextmenu";
|
||||
@@ -9,31 +7,22 @@ import { AirbaseContextMenu } from "../controls/airbasecontextmenu";
|
||||
import { Dropdown } from "../controls/dropdown";
|
||||
import { Airbase } from "../missionhandler/airbase";
|
||||
import { Unit } from "../units/unit";
|
||||
|
||||
// TODO a bit of a hack, this module is provided as pure javascript only
|
||||
require("../../node_modules/leaflet.nauticscale/dist/leaflet.nauticscale.js")
|
||||
|
||||
export const IDLE = "IDLE";
|
||||
export const MOVE_UNIT = "MOVE_UNIT";
|
||||
import { bearing } from "../other/utils";
|
||||
import { DestinationPreviewMarker } from "./destinationpreviewmarker";
|
||||
import { TemporaryUnitMarker } from "./temporaryunitmarker";
|
||||
import { ClickableMiniMap } from "./clickableminimap";
|
||||
import { SVGInjector } from '@tanem/svg-injector'
|
||||
|
||||
L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect);
|
||||
|
||||
var temporaryIcon = new L.Icon({
|
||||
iconUrl: 'images/icon-temporary.png',
|
||||
iconSize: [52, 52],
|
||||
iconAnchor: [26, 26]
|
||||
});
|
||||
// TODO would be nice to convert to ts
|
||||
require("../../public/javascripts/leaflet.nauticscale.js")
|
||||
|
||||
export class ClickableMiniMap extends MiniMap {
|
||||
constructor(layer: L.TileLayer | L.LayerGroup, options?: MiniMapOptions) {
|
||||
super(layer, options);
|
||||
}
|
||||
|
||||
getMap() {
|
||||
//@ts-ignore needed to access not exported member. A bit of a hack, required to access click events
|
||||
return this._miniMap;
|
||||
}
|
||||
}
|
||||
/* Map constants */
|
||||
export const IDLE = "IDLE";
|
||||
export const MOVE_UNIT = "MOVE_UNIT";
|
||||
export const visibilityControls: string[] = ["human", "dcs", "aircraft", "groundunit-sam", "groundunit-other", "navyunit", "airbase"];
|
||||
export const visibilityControlsTootlips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"];
|
||||
|
||||
export class Map extends L.Map {
|
||||
#state: string;
|
||||
@@ -51,12 +40,17 @@ export class Map extends L.Map {
|
||||
#miniMap: ClickableMiniMap | null = null;
|
||||
#miniMapLayerGroup: L.LayerGroup;
|
||||
#temporaryMarkers: L.Marker[] = [];
|
||||
#destinationPreviewMarkers: L.Marker[] = [];
|
||||
#destinationGroupRotation: number = 0;
|
||||
#computeDestinationRotation: boolean = false;
|
||||
#destinationRotationCenter: L.LatLng | null = null;
|
||||
|
||||
#mapContextMenu: MapContextMenu = new MapContextMenu("map-contextmenu");
|
||||
#unitContextMenu: UnitContextMenu = new UnitContextMenu("unit-contextmenu");
|
||||
#airbaseContextMenu: AirbaseContextMenu = new AirbaseContextMenu("airbase-contextmenu");
|
||||
|
||||
#mapSourceDropdown: Dropdown;
|
||||
#optionButtons: { [key: string]: HTMLButtonElement[] } = {}
|
||||
|
||||
constructor(ID: string) {
|
||||
/* Init the leaflet map */
|
||||
@@ -67,53 +61,18 @@ export class Map extends L.Map {
|
||||
this.setLayer("ArcGIS Satellite");
|
||||
|
||||
/* Minimap */
|
||||
/* Draw the limits of the maps in the minimap*/
|
||||
var latlngs = [[ // NTTR
|
||||
new L.LatLng(39.7982463, -119.985425),
|
||||
new L.LatLng(34.4037128, -119.7806729),
|
||||
new L.LatLng(34.3483316, -112.4529351),
|
||||
new L.LatLng(39.7372411, -112.1130805),
|
||||
new L.LatLng(39.7982463, -119.985425)
|
||||
],
|
||||
[ // Syria
|
||||
new L.LatLng(37.3630556, 29.2686111),
|
||||
new L.LatLng(31.8472222, 29.8975),
|
||||
new L.LatLng(32.1358333, 42.1502778),
|
||||
new L.LatLng(37.7177778, 42.3716667),
|
||||
new L.LatLng(37.3630556, 29.2686111)
|
||||
],
|
||||
[ // Caucasus
|
||||
new L.LatLng(39.6170191, 27.634935),
|
||||
new L.LatLng(38.8735863, 47.1423108),
|
||||
new L.LatLng(47.3907982, 49.3101946),
|
||||
new L.LatLng(48.3955879, 26.7753625),
|
||||
new L.LatLng(39.6170191, 27.634935)
|
||||
],
|
||||
[ // Persian Gulf
|
||||
new L.LatLng(32.9355285, 46.5623682),
|
||||
new L.LatLng(21.729393, 47.572675),
|
||||
new L.LatLng(21.8501348, 63.9734737),
|
||||
new L.LatLng(33.131584, 64.7313594),
|
||||
new L.LatLng(32.9355285, 46.5623682)
|
||||
],
|
||||
[ // Marianas
|
||||
new L.LatLng(22.09, 135.0572222),
|
||||
new L.LatLng(10.5777778, 135.7477778),
|
||||
new L.LatLng(10.7725, 149.3918333),
|
||||
new L.LatLng(22.5127778, 149.5427778),
|
||||
new L.LatLng(22.09, 135.0572222)
|
||||
]
|
||||
];
|
||||
|
||||
var minimapLayer = new L.TileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', { minZoom: 0, maxZoom: 13 });
|
||||
this.#miniMapLayerGroup = new L.LayerGroup([minimapLayer]);
|
||||
var miniMapPolyline = new L.Polyline(latlngs, { color: '#202831' });
|
||||
var miniMapPolyline = new L.Polyline(this.#getMinimapBoundaries(), { color: '#202831' });
|
||||
miniMapPolyline.addTo(this.#miniMapLayerGroup);
|
||||
|
||||
/* Scale */
|
||||
//@ts-ignore TODO more hacking because the module is provided as a pure javascript module only
|
||||
L.control.scalenautic({ position: "topright", maxWidth: 300, nautic: true, metric: true, imperial: false }).addTo(this);
|
||||
|
||||
/* Map source dropdown */
|
||||
this.#mapSourceDropdown = new Dropdown("map-type", (layerName: string) => this.setLayer(layerName), this.getLayers())
|
||||
|
||||
/* Init the state machine */
|
||||
this.#state = IDLE;
|
||||
|
||||
@@ -127,15 +86,21 @@ export class Map extends L.Map {
|
||||
this.on('mousedown', (e: any) => this.#onMouseDown(e));
|
||||
this.on('mouseup', (e: any) => this.#onMouseUp(e));
|
||||
this.on('mousemove', (e: any) => this.#onMouseMove(e));
|
||||
this.on('keydown', (e: any) => this.#updateDestinationPreview(e));
|
||||
this.on('keyup', (e: any) => this.#updateDestinationPreview(e));
|
||||
|
||||
/* Event listeners */
|
||||
document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => {
|
||||
ev.detail._element.classList.toggle("off");
|
||||
document.body.toggleAttribute("data-hide-" + ev.detail.coalition);
|
||||
const el = ev.detail._element;
|
||||
el?.classList.toggle("off");
|
||||
getUnitsManager().setHiddenType(ev.detail.coalition, (el?.currentTarget as HTMLElement)?.classList.contains("off"));
|
||||
Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
|
||||
});
|
||||
|
||||
document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => {
|
||||
document.body.toggleAttribute("data-hide-" + ev.detail.category);
|
||||
const el = ev.detail._element;
|
||||
el?.classList.toggle("off");
|
||||
getUnitsManager().setHiddenType(ev.detail.type, !el?.classList.contains("off"));
|
||||
Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
|
||||
});
|
||||
|
||||
@@ -144,12 +109,17 @@ export class Map extends L.Map {
|
||||
this.#panToUnit(this.#centerUnit);
|
||||
});
|
||||
|
||||
this.#mapSourceDropdown = new Dropdown("map-type", (layerName: string) => this.setLayer(layerName), this.getLayers())
|
||||
|
||||
this.#panInterval = window.setInterval(() => {
|
||||
this.panBy(new L.Point( ((this.#panLeft? -1: 0) + (this.#panRight? 1: 0)) * this.#deafultPanDelta,
|
||||
((this.#panUp? -1: 0) + (this.#panDown? 1: 0)) * this.#deafultPanDelta));
|
||||
/* Pan interval */
|
||||
this.#panInterval = window.setInterval(() => {
|
||||
this.panBy(new L.Point(((this.#panLeft ? -1 : 0) + (this.#panRight ? 1 : 0)) * this.#deafultPanDelta,
|
||||
((this.#panUp ? -1 : 0) + (this.#panDown ? 1 : 0)) * this.#deafultPanDelta));
|
||||
}, 20);
|
||||
|
||||
/* Option buttons */
|
||||
this.#optionButtons["visibility"] = visibilityControls.map((option: string, index: number) => {
|
||||
return this.#createOptionButton(option, `visibility/${option.toLowerCase()}.svg`, visibilityControlsTootlips[index], "toggleUnitVisibility", `{"type": "${option}"}`);
|
||||
});
|
||||
document.querySelector("#unit-visibility-control")?.append(...this.#optionButtons["visibility"]);
|
||||
}
|
||||
|
||||
setLayer(layerName: string) {
|
||||
@@ -205,9 +175,34 @@ export class Map extends L.Map {
|
||||
this.#state = state;
|
||||
if (this.#state === IDLE) {
|
||||
L.DomUtil.removeClass(this.getContainer(), 'crosshair-cursor-enabled');
|
||||
|
||||
/* Remove all the destination preview markers */
|
||||
this.#destinationPreviewMarkers.forEach((marker: L.Marker) => {
|
||||
this.removeLayer(marker);
|
||||
})
|
||||
this.#destinationPreviewMarkers = [];
|
||||
|
||||
this.#destinationGroupRotation = 0;
|
||||
this.#computeDestinationRotation = false;
|
||||
this.#destinationRotationCenter = null;
|
||||
}
|
||||
else if (this.#state === MOVE_UNIT) {
|
||||
L.DomUtil.addClass(this.getContainer(), 'crosshair-cursor-enabled');
|
||||
|
||||
/* Remove all the exising destination preview markers */
|
||||
this.#destinationPreviewMarkers.forEach((marker: L.Marker) => {
|
||||
this.removeLayer(marker);
|
||||
})
|
||||
this.#destinationPreviewMarkers = [];
|
||||
|
||||
if (getUnitsManager().getSelectedUnits({ excludeHumans: true }).length > 1 && getUnitsManager().getSelectedUnits({ excludeHumans: true }).length < 20) {
|
||||
/* Create the unit destination preview markers */
|
||||
this.#destinationPreviewMarkers = getUnitsManager().getSelectedUnits({ excludeHumans: true }).map((unit: Unit) => {
|
||||
var marker = new DestinationPreviewMarker(this.getMouseCoordinates());
|
||||
marker.addTo(this);
|
||||
return marker;
|
||||
})
|
||||
}
|
||||
}
|
||||
document.dispatchEvent(new CustomEvent("mapStateChanged"));
|
||||
}
|
||||
@@ -328,7 +323,6 @@ export class Map extends L.Map {
|
||||
if (this.#miniMap)
|
||||
this.setView(e.latlng);
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
getMiniMapLayerGroup() {
|
||||
@@ -336,7 +330,7 @@ export class Map extends L.Map {
|
||||
}
|
||||
|
||||
handleMapPanning(e: any) {
|
||||
if (e.type === "keyup"){
|
||||
if (e.type === "keyup") {
|
||||
switch (e.code) {
|
||||
case "KeyA":
|
||||
case "ArrowLeft":
|
||||
@@ -356,9 +350,8 @@ export class Map extends L.Map {
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch (e.code)
|
||||
{
|
||||
else {
|
||||
switch (e.code) {
|
||||
case 'KeyA':
|
||||
case 'ArrowLeft':
|
||||
this.#panLeft = true;
|
||||
@@ -380,7 +373,7 @@ export class Map extends L.Map {
|
||||
}
|
||||
|
||||
addTemporaryMarker(latlng: L.LatLng) {
|
||||
var marker = new L.Marker(latlng, {icon: temporaryIcon});
|
||||
var marker = new TemporaryUnitMarker(latlng);
|
||||
marker.addTo(this);
|
||||
this.#temporaryMarkers.push(marker);
|
||||
}
|
||||
@@ -397,8 +390,7 @@ export class Map extends L.Map {
|
||||
i = idx;
|
||||
}
|
||||
});
|
||||
if (closest)
|
||||
{
|
||||
if (closest) {
|
||||
this.removeLayer(closest);
|
||||
delete this.#temporaryMarkers[i];
|
||||
}
|
||||
@@ -433,7 +425,10 @@ export class Map extends L.Map {
|
||||
if (!e.originalEvent.ctrlKey) {
|
||||
getUnitsManager().selectedUnitsClearDestinations();
|
||||
}
|
||||
getUnitsManager().selectedUnitsAddDestination(e.latlng)
|
||||
getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, !e.originalEvent.shiftKey, this.#destinationGroupRotation)
|
||||
this.#destinationGroupRotation = 0;
|
||||
this.#destinationRotationCenter = null;
|
||||
this.#computeDestinationRotation = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,6 +443,16 @@ export class Map extends L.Map {
|
||||
|
||||
#onMouseDown(e: any) {
|
||||
this.hideAllContextMenus();
|
||||
|
||||
if (this.#state == MOVE_UNIT) {
|
||||
this.#destinationGroupRotation = 0;
|
||||
this.#destinationRotationCenter = null;
|
||||
this.#computeDestinationRotation = false;
|
||||
if (e.originalEvent.button == 2) {
|
||||
this.#computeDestinationRotation = true;
|
||||
this.#destinationRotationCenter = this.getMouseCoordinates();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#onMouseUp(e: any) {
|
||||
@@ -456,6 +461,11 @@ export class Map extends L.Map {
|
||||
#onMouseMove(e: any) {
|
||||
this.#lastMousePosition.x = e.originalEvent.x;
|
||||
this.#lastMousePosition.y = e.originalEvent.y;
|
||||
|
||||
if (this.#computeDestinationRotation && this.#destinationRotationCenter != null)
|
||||
this.#destinationGroupRotation = -bearing(this.#destinationRotationCenter.lat, this.#destinationRotationCenter.lng, this.getMouseCoordinates().lat, this.getMouseCoordinates().lng);
|
||||
|
||||
this.#updateDestinationPreview(e);
|
||||
}
|
||||
|
||||
#onZoom(e: any) {
|
||||
@@ -467,4 +477,65 @@ export class Map extends L.Map {
|
||||
var unitPosition = new L.LatLng(unit.getFlightData().latitude, unit.getFlightData().longitude);
|
||||
this.setView(unitPosition, this.getZoom(), { animate: false });
|
||||
}
|
||||
|
||||
#getMinimapBoundaries() {
|
||||
/* Draw the limits of the maps in the minimap*/
|
||||
return [[ // NTTR
|
||||
new L.LatLng(39.7982463, -119.985425),
|
||||
new L.LatLng(34.4037128, -119.7806729),
|
||||
new L.LatLng(34.3483316, -112.4529351),
|
||||
new L.LatLng(39.7372411, -112.1130805),
|
||||
new L.LatLng(39.7982463, -119.985425)
|
||||
],
|
||||
[ // Syria
|
||||
new L.LatLng(37.3630556, 29.2686111),
|
||||
new L.LatLng(31.8472222, 29.8975),
|
||||
new L.LatLng(32.1358333, 42.1502778),
|
||||
new L.LatLng(37.7177778, 42.3716667),
|
||||
new L.LatLng(37.3630556, 29.2686111)
|
||||
],
|
||||
[ // Caucasus
|
||||
new L.LatLng(39.6170191, 27.634935),
|
||||
new L.LatLng(38.8735863, 47.1423108),
|
||||
new L.LatLng(47.3907982, 49.3101946),
|
||||
new L.LatLng(48.3955879, 26.7753625),
|
||||
new L.LatLng(39.6170191, 27.634935)
|
||||
],
|
||||
[ // Persian Gulf
|
||||
new L.LatLng(32.9355285, 46.5623682),
|
||||
new L.LatLng(21.729393, 47.572675),
|
||||
new L.LatLng(21.8501348, 63.9734737),
|
||||
new L.LatLng(33.131584, 64.7313594),
|
||||
new L.LatLng(32.9355285, 46.5623682)
|
||||
],
|
||||
[ // Marianas
|
||||
new L.LatLng(22.09, 135.0572222),
|
||||
new L.LatLng(10.5777778, 135.7477778),
|
||||
new L.LatLng(10.7725, 149.3918333),
|
||||
new L.LatLng(22.5127778, 149.5427778),
|
||||
new L.LatLng(22.09, 135.0572222)
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
#updateDestinationPreview(e: any) {
|
||||
Object.values(getUnitsManager().selectedUnitsComputeGroupDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : this.getMouseCoordinates(), this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => {
|
||||
if (idx < this.#destinationPreviewMarkers.length)
|
||||
this.#destinationPreviewMarkers[idx].setLatLng(!e.originalEvent.shiftKey ? latlng : this.getMouseCoordinates());
|
||||
})
|
||||
}
|
||||
|
||||
#createOptionButton(value: string, url: string, title: string, callback: string, argument: string) {
|
||||
var button = document.createElement("button");
|
||||
const img = document.createElement("img");
|
||||
img.src = `/resources/theme/images/buttons/${url}`;
|
||||
img.onload = () => SVGInjector(img);
|
||||
button.title = title;
|
||||
button.value = value;
|
||||
button.appendChild(img);
|
||||
button.setAttribute("data-on-click", callback);
|
||||
button.setAttribute("data-on-click-params", argument);
|
||||
return button;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
13
client/src/map/temporaryunitmarker.ts
Normal file
13
client/src/map/temporaryunitmarker.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Icon } from "leaflet";
|
||||
import { CustomMarker } from "./custommarker";
|
||||
|
||||
export class TemporaryUnitMarker extends CustomMarker {
|
||||
createIcon() {
|
||||
var icon = new Icon({
|
||||
iconUrl: '/resources/theme/images/markers/temporary-icon.png',
|
||||
iconSize: [52, 52],
|
||||
iconAnchor: [26, 26]
|
||||
});
|
||||
this.setIcon(icon);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,14 @@
|
||||
import * as L from 'leaflet'
|
||||
import { DivIcon } from 'leaflet';
|
||||
import { CustomMarker } from '../map/custommarker';
|
||||
import { SVGInjector } from '@tanem/svg-injector';
|
||||
|
||||
export interface AirbaseOptions
|
||||
{
|
||||
name: string,
|
||||
position: L.LatLng,
|
||||
src: string
|
||||
position: L.LatLng
|
||||
}
|
||||
|
||||
export class Airbase extends L.Marker
|
||||
export class Airbase extends CustomMarker
|
||||
{
|
||||
#name: string = "";
|
||||
#coalition: string = "";
|
||||
@@ -19,21 +20,30 @@ export class Airbase extends L.Marker
|
||||
super(options.position, { riseOnHover: true });
|
||||
|
||||
this.#name = options.name;
|
||||
var icon = new L.DivIcon({
|
||||
html: ` <div class="airbase" data-object="airbase" data-coalition="neutral">
|
||||
<div class="airbase-marker"> </div>
|
||||
</div>`,
|
||||
}
|
||||
|
||||
createIcon() {
|
||||
var icon = new DivIcon({
|
||||
className: 'leaflet-airbase-marker',
|
||||
iconSize: [40, 40],
|
||||
iconAnchor: [20, 20]
|
||||
}); // Set the marker, className must be set to avoid white square
|
||||
this.setIcon(icon);
|
||||
|
||||
var el = document.createElement("div");
|
||||
el.classList.add("airbase-icon");
|
||||
el.setAttribute("data-object", "airbase");
|
||||
var img = document.createElement("img");
|
||||
img.src = "/resources/theme/images/markers/airbase.svg";
|
||||
img.onload = () => SVGInjector(img);
|
||||
el.appendChild(img);
|
||||
this.getElement()?.appendChild(el);
|
||||
}
|
||||
|
||||
setCoalition(coalition: string)
|
||||
{
|
||||
this.#coalition = coalition;
|
||||
(<HTMLElement> this.getElement()?.querySelector(".airbase")).dataset.coalition = this.#coalition;
|
||||
(<HTMLElement> this.getElement()?.querySelector(".airbase-icon")).dataset.coalition = this.#coalition;
|
||||
}
|
||||
|
||||
getCoalition()
|
||||
|
||||
36
client/src/missionhandler/bullseye.ts
Normal file
36
client/src/missionhandler/bullseye.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { DivIcon } from "leaflet";
|
||||
import { CustomMarker } from "../map/custommarker";
|
||||
import { SVGInjector } from "@tanem/svg-injector";
|
||||
|
||||
export class Bullseye extends CustomMarker {
|
||||
#coalition: string = "";
|
||||
|
||||
createIcon() {
|
||||
var icon = new DivIcon({
|
||||
className: 'leaflet-bullseye-marker',
|
||||
iconSize: [40, 40],
|
||||
iconAnchor: [20, 20]
|
||||
}); // Set the marker, className must be set to avoid white square
|
||||
this.setIcon(icon);
|
||||
|
||||
var el = document.createElement("div");
|
||||
el.classList.add("bullseye-icon");
|
||||
el.setAttribute("data-object", "bullseye");
|
||||
var img = document.createElement("img");
|
||||
img.src = "/resources/theme/images/markers/bullseye.svg";
|
||||
img.onload = () => SVGInjector(img);
|
||||
el.appendChild(img);
|
||||
this.getElement()?.appendChild(el);
|
||||
}
|
||||
|
||||
setCoalition(coalition: string)
|
||||
{
|
||||
this.#coalition = coalition;
|
||||
(<HTMLElement> this.getElement()?.querySelector(".bullseye-icon")).dataset.coalition = this.#coalition;
|
||||
}
|
||||
|
||||
getCoalition()
|
||||
{
|
||||
return this.#coalition;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +1,58 @@
|
||||
import { Marker, LatLng, Icon } from "leaflet";
|
||||
import { LatLng } from "leaflet";
|
||||
import { getInfoPopup, getMap } from "..";
|
||||
import { Airbase } from "./airbase";
|
||||
|
||||
var bullseyeIcons = [
|
||||
new Icon({ iconUrl: 'images/bullseye0.png', iconAnchor: [30, 30]}),
|
||||
new Icon({ iconUrl: 'images/bullseye1.png', iconAnchor: [30, 30]}),
|
||||
new Icon({ iconUrl: 'images/bullseye2.png', iconAnchor: [30, 30]})
|
||||
]
|
||||
import { Bullseye } from "./bullseye";
|
||||
|
||||
export class MissionHandler
|
||||
{
|
||||
#bullseyes : any; //TODO declare interface
|
||||
#bullseyeMarkers: any;
|
||||
#airbases : any; //TODO declare interface
|
||||
#airbasesMarkers: {[name: string]: Airbase};
|
||||
#bullseyes : {[name: string]: Bullseye} = {};
|
||||
#airbases : {[name: string]: Airbase} = {};
|
||||
#theatre : string = "";
|
||||
|
||||
constructor()
|
||||
{
|
||||
this.#bullseyes = undefined;
|
||||
this.#bullseyeMarkers = [
|
||||
new Marker([0, 0], {icon: bullseyeIcons[0]}).addTo(getMap()),
|
||||
new Marker([0, 0], {icon: bullseyeIcons[1]}).addTo(getMap()),
|
||||
new Marker([0, 0], {icon: bullseyeIcons[2]}).addTo(getMap())
|
||||
]
|
||||
this.#airbasesMarkers = {};
|
||||
|
||||
}
|
||||
|
||||
update(data: BullseyesData | AirbasesData | any)
|
||||
{
|
||||
if ("bullseyes" in data)
|
||||
{
|
||||
this.#bullseyes = data.bullseyes;
|
||||
this.#drawBullseyes();
|
||||
for (let idx in data.bullseyes)
|
||||
{
|
||||
const bullseye = data.bullseyes[idx];
|
||||
if (!(idx in this.#bullseyes))
|
||||
this.#bullseyes[idx] = new Bullseye([0, 0]).addTo(getMap());
|
||||
|
||||
if (bullseye.latitude && bullseye.longitude && bullseye.coalition)
|
||||
{
|
||||
this.#bullseyes[idx].setLatLng(new LatLng(bullseye.latitude, bullseye.longitude));
|
||||
this.#bullseyes[idx].setCoalition(bullseye.coalition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ("airbases" in data)
|
||||
{
|
||||
this.#airbases = data.airbases;
|
||||
this.#drawAirbases();
|
||||
for (let idx in data.airbases)
|
||||
{
|
||||
var airbase = data.airbases[idx]
|
||||
if (this.#airbases[idx] === undefined)
|
||||
{
|
||||
this.#airbases[idx] = new Airbase({
|
||||
position: new LatLng(airbase.latitude, airbase.longitude),
|
||||
name: airbase.callsign
|
||||
}).addTo(getMap());
|
||||
this.#airbases[idx].on('contextmenu', (e) => this.#onAirbaseClick(e));
|
||||
}
|
||||
if (airbase.latitude && airbase.longitude && airbase.coalition)
|
||||
{
|
||||
this.#airbases[idx].setLatLng(new LatLng(airbase.latitude, airbase.longitude));
|
||||
this.#airbases[idx].setCoalition(airbase.coalition);
|
||||
}
|
||||
//this.#airbases[idx].setProperties(["Runway 1: 31L / 13R", "Runway 2: 31R / 13L", "TCN: 17X", "ILS: ---" ]);
|
||||
//this.#airbases[idx].setParkings(["2x big", "5x small"]);
|
||||
}
|
||||
}
|
||||
|
||||
if ("mission" in data)
|
||||
@@ -58,38 +72,6 @@ export class MissionHandler
|
||||
return this.#bullseyes;
|
||||
}
|
||||
|
||||
#drawBullseyes()
|
||||
{
|
||||
for (let idx in this.#bullseyes)
|
||||
{
|
||||
var bullseye = this.#bullseyes[idx];
|
||||
this.#bullseyeMarkers[idx].setLatLng(new LatLng(bullseye.latitude, bullseye.longitude));
|
||||
}
|
||||
}
|
||||
|
||||
#drawAirbases()
|
||||
{
|
||||
for (let idx in this.#airbases)
|
||||
{
|
||||
var airbase = this.#airbases[idx]
|
||||
if (this.#airbasesMarkers[idx] === undefined)
|
||||
{
|
||||
this.#airbasesMarkers[idx] = new Airbase({
|
||||
position: new LatLng(airbase.latitude, airbase.longitude),
|
||||
name: airbase.callsign,
|
||||
src: "images/airbase.png"}).addTo(getMap());
|
||||
this.#airbasesMarkers[idx].on('contextmenu', (e) => this.#onAirbaseClick(e));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.#airbasesMarkers[idx].setLatLng(new LatLng(airbase.latitude, airbase.longitude));
|
||||
this.#airbasesMarkers[idx].setCoalition(airbase.coalition);
|
||||
//this.#airbasesMarkers[idx].setProperties(["Runway 1: 31L / 13R", "Runway 2: 31R / 13L", "TCN: 17X", "ILS: ---" ]);
|
||||
//this.#airbasesMarkers[idx].setParkings(["2x big", "5x small"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#onAirbaseClick(e: any)
|
||||
{
|
||||
getMap().showAirbaseContextMenu(e, e.sourceTarget);
|
||||
|
||||
@@ -11,48 +11,6 @@ export function bearing(lat1: number, lon1: number, lat2: number, lon2: number)
|
||||
return brng;
|
||||
}
|
||||
|
||||
|
||||
export function ConvertDDToDMS(D: number, lng: boolean) {
|
||||
var dir = D < 0 ? (lng ? "W" : "S") : lng ? "E" : "N";
|
||||
var deg = 0 | (D < 0 ? (D = -D) : D);
|
||||
var min = 0 | (((D += 1e-9) % 1) * 60);
|
||||
var sec = (0 | (((D * 60) % 1) * 6000)) / 100;
|
||||
var dec = Math.round((sec - Math.floor(sec)) * 100);
|
||||
var sec = Math.floor(sec);
|
||||
if (lng)
|
||||
return dir + zeroPad(deg, 3) + "°" + zeroPad(min, 2) + "'" + zeroPad(sec, 2) + "." + zeroPad(dec, 2) + "\"";
|
||||
else
|
||||
return dir + zeroPad(deg, 2) + "°" + zeroPad(min, 2) + "'" + zeroPad(sec, 2) + "." + zeroPad(dec, 2) + "\"";
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
export function distance(lat1: number, lon1: number, lat2: number, lon2: number) {
|
||||
const R = 6371e3; // metres
|
||||
const φ1 = deg2rad(lat1); // φ, λ in radians
|
||||
@@ -68,6 +26,42 @@ export function distance(lat1: number, lon1: number, lat2: number, lon2: number)
|
||||
return d;
|
||||
}
|
||||
|
||||
export function ConvertDDToDMS(D: number, lng: boolean) {
|
||||
var dir = D < 0 ? (lng ? "W" : "S") : lng ? "E" : "N";
|
||||
var deg = 0 | (D < 0 ? (D = -D) : D);
|
||||
var min = 0 | (((D += 1e-9) % 1) * 60);
|
||||
var sec = (0 | (((D * 60) % 1) * 6000)) / 100;
|
||||
var dec = Math.round((sec - Math.floor(sec)) * 100);
|
||||
var sec = Math.floor(sec);
|
||||
if (lng)
|
||||
return dir + zeroPad(deg, 3) + "°" + zeroPad(min, 2) + "'" + zeroPad(sec, 2) + "." + zeroPad(dec, 2) + "\"";
|
||||
else
|
||||
return dir + zeroPad(deg, 2) + "°" + zeroPad(min, 2) + "'" + zeroPad(sec, 2) + "." + zeroPad(dec, 2) + "\"";
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
export function rad2deg(rad: number) {
|
||||
var pi = Math.PI;
|
||||
return rad / (pi / 180);
|
||||
}
|
||||
|
||||
export function generateUUIDv4() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
@@ -76,33 +70,15 @@ export function generateUUIDv4() {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function keyEventWasInInput( event:KeyboardEvent ) {
|
||||
|
||||
const target = event.target;
|
||||
|
||||
return ( target instanceof HTMLElement && ( [ "INPUT", "TEXTAREA" ].includes( target.nodeName ) ) );
|
||||
|
||||
}
|
||||
|
||||
|
||||
export function rad2deg(rad: number) {
|
||||
var pi = Math.PI;
|
||||
return rad / (pi / 180);
|
||||
}
|
||||
|
||||
|
||||
export function reciprocalHeading(heading: number): number {
|
||||
|
||||
if (heading > 180) {
|
||||
return heading - 180;
|
||||
}
|
||||
|
||||
return heading + 180;
|
||||
|
||||
return heading > 180? heading - 180: heading + 180;
|
||||
}
|
||||
|
||||
|
||||
export const zeroAppend = function (num: number, places: number) {
|
||||
var string = String(num);
|
||||
while (string.length < places) {
|
||||
@@ -111,7 +87,6 @@ export const zeroAppend = function (num: number, places: number) {
|
||||
return string;
|
||||
}
|
||||
|
||||
|
||||
export const zeroPad = function (num: number, places: number) {
|
||||
var string = String(num);
|
||||
while (string.length < places) {
|
||||
@@ -120,7 +95,6 @@ export const zeroPad = function (num: number, places: number) {
|
||||
return string;
|
||||
}
|
||||
|
||||
|
||||
export function similarity(s1: string, s2: string) {
|
||||
var longer = s1;
|
||||
var shorter = s2;
|
||||
@@ -160,4 +134,30 @@ export function editDistance(s1: string, s2: string) {
|
||||
costs[s2.length] = lastValue;
|
||||
}
|
||||
return costs[s2.length];
|
||||
}
|
||||
|
||||
export function latLngToMercator(lat: number, lng: number): {x: number, y: number} {
|
||||
var rMajor = 6378137; //Equatorial Radius, WGS84
|
||||
var shift = Math.PI * rMajor;
|
||||
var x = lng * shift / 180;
|
||||
var y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180);
|
||||
y = y * shift / 180;
|
||||
|
||||
return {x: x, y: y};
|
||||
}
|
||||
|
||||
export function mercatorToLatLng(x: number, y: number) {
|
||||
var rMajor = 6378137; //Equatorial Radius, WGS84
|
||||
var shift = Math.PI * rMajor;
|
||||
var lng = x / shift * 180.0;
|
||||
var lat = y / shift * 180.0;
|
||||
lat = 180 / Math.PI * (2 * Math.atan(Math.exp(lat * Math.PI / 180.0)) - Math.PI / 2.0);
|
||||
|
||||
return { lng: lng, lat: lat };
|
||||
}
|
||||
|
||||
export function createDivWithClass(className: string) {
|
||||
var el = document.createElement("div");
|
||||
el.classList.add(className);
|
||||
return el;
|
||||
}
|
||||
@@ -18,14 +18,24 @@ export class HotgroupPanel extends Panel {
|
||||
}
|
||||
|
||||
addHotgroup(hotgroup: number) {
|
||||
const hotgroupHtml = `<div class="unit-hotgroup">
|
||||
<div class="unit-hotgroup-id">${hotgroup}</div>
|
||||
</div>
|
||||
x${getUnitsManager().getUnitsByHotgroup(hotgroup).length}`
|
||||
// Hotgroup number
|
||||
var hotgroupDiv = document.createElement("div");
|
||||
hotgroupDiv.classList.add("unit-hotgroup");
|
||||
var idDiv = document.createElement("div");
|
||||
idDiv.classList.add("unit-hotgroup-id");
|
||||
idDiv.innerText = String(hotgroup);
|
||||
hotgroupDiv.appendChild(idDiv);
|
||||
|
||||
// Hotgroup unit count
|
||||
var countDiv = document.createElement("div");
|
||||
countDiv.innerText = `x${getUnitsManager().getUnitsByHotgroup(hotgroup).length}`;
|
||||
|
||||
var el = document.createElement("div");
|
||||
el.appendChild(hotgroupDiv);
|
||||
el.appendChild(countDiv);
|
||||
el.classList.add("hotgroup-selector");
|
||||
el.innerHTML = hotgroupHtml;
|
||||
el.toggleAttribute(`data-hotgroup-${hotgroup}`, true)
|
||||
|
||||
this.getElement().appendChild(el);
|
||||
|
||||
el.addEventListener("click", () => {
|
||||
|
||||
@@ -37,8 +37,8 @@ export class MouseInfoPanel extends Panel {
|
||||
|
||||
if ( el != null ) {
|
||||
|
||||
var dist = distance(bullseyes[idx].latitude, bullseyes[idx].longitude, mousePosition.lat, mousePosition.lng);
|
||||
var bear = bearing(bullseyes[idx].latitude, bullseyes[idx].longitude, mousePosition.lat, mousePosition.lng);
|
||||
var dist = distance(bullseyes[idx].getLatLng().lat, bullseyes[idx].getLatLng().lng, mousePosition.lat, mousePosition.lng);
|
||||
var bear = bearing(bullseyes[idx].getLatLng().lat, bullseyes[idx].getLatLng().lng, mousePosition.lat, mousePosition.lng);
|
||||
|
||||
let bng = zeroAppend(Math.floor(bear), 3);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { SVGInjector } from "@tanem/svg-injector";
|
||||
import { getUnitsManager } from "..";
|
||||
import { Dropdown } from "../controls/dropdown";
|
||||
import { Slider } from "../controls/slider";
|
||||
@@ -8,12 +9,12 @@ import { UnitDatabase } from "../units/unitdatabase";
|
||||
import { Panel } from "./panel";
|
||||
|
||||
const ROEs: string[] = ["Hold", "Return", "Designated", "Free"];
|
||||
const reactionsToThreat: string[] = ["None", "Passive", "Evade"];
|
||||
const reactionsToThreat: string[] = ["None", "Manoeuvre", "Passive", "Evade"];
|
||||
const emissionsCountermeasures: string[] = ["Silent", "Attack", "Defend", "Free"];
|
||||
|
||||
const ROEDescriptions: string[] = ["Hold (Never fire)", "Return (Only fire if fired upon)", "Designated (Attack the designated target only)", "Free (Attack anyone)"];
|
||||
const reactionsToThreatDescriptions: string[] = ["None (No reaction)", "Passive (Countermeasures only, no manoeuvre)", "Evade (Countermeasures and manoeuvers)"];
|
||||
const emissionsCountermeasuresDescriptions: string[] = ["Silent (Radar off, no countermeasures)", "Attack (Radar only for targeting, countermeasures only if attacked/locked)", "Defend (Radar for searching, jammer if locked, countermeasures inside WEZ)", "Always on (Radar and jammer always on, countermeasures when hostile detected)"];
|
||||
const reactionsToThreatDescriptions: string[] = ["None (No reaction)", "Manoeuvre (no countermeasures)", "Passive (Countermeasures only, no manoeuvre)", "Evade (Countermeasures and manoeuvers)"];
|
||||
const emissionsCountermeasuresDescriptions: string[] = ["Silent (Radar OFF, no ECM)", "Attack (Radar only for targeting, ECM only if locked)", "Defend (Radar for searching, ECM if locked)", "Always on (Radar and ECM always on)"];
|
||||
|
||||
const minSpeedValues: { [key: string]: number } = { Aircraft: 100, Helicopter: 0, NavyUnit: 0, GroundUnit: 0 };
|
||||
const maxSpeedValues: { [key: string]: number } = { Aircraft: 800, Helicopter: 300, NavyUnit: 60, GroundUnit: 60 };
|
||||
@@ -56,15 +57,15 @@ export class UnitControlPanel extends Panel {
|
||||
|
||||
/* Option buttons */
|
||||
this.#optionButtons["ROE"] = ROEs.map((option: string, index: number) => {
|
||||
return this.#createOptionButton(option, ROEDescriptions[index], () => { getUnitsManager().selectedUnitsSetROE(option); });
|
||||
return this.#createOptionButton(option, `roe/${option.toLowerCase()}.svg`, ROEDescriptions[index], () => { getUnitsManager().selectedUnitsSetROE(option); });
|
||||
});
|
||||
|
||||
this.#optionButtons["reactionToThreat"] = reactionsToThreat.map((option: string, index: number) => {
|
||||
return this.#createOptionButton(option, reactionsToThreatDescriptions[index],() => { getUnitsManager().selectedUnitsSetReactionToThreat(option); });
|
||||
return this.#createOptionButton(option, `threat/${option.toLowerCase()}.svg`, reactionsToThreatDescriptions[index],() => { getUnitsManager().selectedUnitsSetReactionToThreat(option); });
|
||||
});
|
||||
|
||||
this.#optionButtons["emissionsCountermeasures"] = emissionsCountermeasures.map((option: string, index: number) => {
|
||||
return this.#createOptionButton(option, emissionsCountermeasuresDescriptions[index],() => { getUnitsManager().selectedUnitsSetEmissionsCountermeasures(option); });
|
||||
return this.#createOptionButton(option, `emissions/${option.toLowerCase()}.svg`, emissionsCountermeasuresDescriptions[index],() => { getUnitsManager().selectedUnitsSetEmissionsCountermeasures(option); });
|
||||
});
|
||||
|
||||
this.getElement().querySelector("#roe-buttons-container")?.append(...this.#optionButtons["ROE"]);
|
||||
@@ -342,10 +343,14 @@ export class UnitControlPanel extends Panel {
|
||||
this.#advancedSettingsDialog.classList.add("hide");
|
||||
}
|
||||
|
||||
#createOptionButton(option: string, title: string, callback: EventListenerOrEventListenerObject) {
|
||||
#createOptionButton(value: string, url: string, title: string, callback: EventListenerOrEventListenerObject) {
|
||||
var button = document.createElement("button");
|
||||
button.value = option;
|
||||
button.title = title;
|
||||
button.value = value;
|
||||
var img = document.createElement("img");
|
||||
img.src = `/resources/theme/images/buttons/${url}`;
|
||||
img.onload = () => SVGInjector(img);
|
||||
button.appendChild(img);
|
||||
button.addEventListener("click", callback);
|
||||
return button;
|
||||
}
|
||||
|
||||
@@ -322,7 +322,7 @@ export class AircraftDatabase extends UnitDatabase {
|
||||
},
|
||||
"H-6J": {
|
||||
"name": "H-6J",
|
||||
"label": "H-6J Badger,
|
||||
"label": "H-6J Badger",
|
||||
"era": ["Mid Cold War, Late Cold War", "Modern"],
|
||||
"shortLabel": "H6",
|
||||
"loadouts": [
|
||||
|
||||
@@ -4,6 +4,24 @@ export class GroundUnitsDatabase extends UnitDatabase {
|
||||
constructor() {
|
||||
super();
|
||||
this.blueprints = {
|
||||
"SA-2 SAM Battery": {
|
||||
"name": "SA-2 SAM Battery",
|
||||
"label": "SA-2 SAM Battery",
|
||||
"shortLabel": "SA-2 SAM Battery",
|
||||
"loadouts": [
|
||||
{
|
||||
"fuel": 1,
|
||||
"items": [
|
||||
],
|
||||
"roles": [
|
||||
"Template"
|
||||
],
|
||||
"code": "",
|
||||
"name": "Default"
|
||||
}
|
||||
],
|
||||
"filename": ""
|
||||
},
|
||||
"2B11 mortar": {
|
||||
"name": "2B11 mortar",
|
||||
"label": "2B11 mortar",
|
||||
|
||||
@@ -4,14 +4,17 @@ import { rad2deg } from '../other/utils';
|
||||
import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, getUnits, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions, followUnit, setEmissionsCountermeasures } from '../server/server';
|
||||
import { aircraftDatabase } from './aircraftdatabase';
|
||||
import { groundUnitsDatabase } from './groundunitsdatabase';
|
||||
import { CustomMarker } from '../map/custommarker';
|
||||
import { SVGInjector } from '@tanem/svg-injector';
|
||||
import { UnitDatabase } from './unitdatabase';
|
||||
|
||||
var pathIcon = new Icon({
|
||||
iconUrl: 'images/marker-icon.png',
|
||||
shadowUrl: 'images/marker-shadow.png',
|
||||
iconUrl: '/resources/theme/images/markers/marker-icon.png',
|
||||
shadowUrl: '/resources/theme/images/markers/marker-shadow.png',
|
||||
iconAnchor: [13, 41]
|
||||
});
|
||||
|
||||
export class Unit extends Marker {
|
||||
export class Unit extends CustomMarker {
|
||||
ID: number;
|
||||
|
||||
#data: UnitData = {
|
||||
@@ -114,22 +117,7 @@ export class Unit extends Marker {
|
||||
/* Set the unit data */
|
||||
this.setData(data);
|
||||
|
||||
/* Set the icon */
|
||||
var icon = new DivIcon({
|
||||
html: this.getMarkerHTML(),
|
||||
className: 'leaflet-unit-marker',
|
||||
iconAnchor: [25, 25],
|
||||
iconSize: [50, 50],
|
||||
});
|
||||
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() {
|
||||
@@ -137,8 +125,77 @@ export class Unit extends Marker {
|
||||
return "";
|
||||
}
|
||||
|
||||
/********************** Unit data *************************/
|
||||
getDatabase(): UnitDatabase | null {
|
||||
// Overloaded by child classes
|
||||
return null;
|
||||
}
|
||||
|
||||
getIconOptions(): UnitIconOptions {
|
||||
// Default values, overloaded by child classes if needed
|
||||
return {
|
||||
showState: false,
|
||||
showVvi: false,
|
||||
showHotgroup: false,
|
||||
showUnitIcon: true,
|
||||
showShortLabel: false,
|
||||
showFuel: false,
|
||||
showAmmo: false,
|
||||
showSummary: false,
|
||||
rotateToHeading: false
|
||||
}
|
||||
}
|
||||
|
||||
setSelected(selected: boolean) {
|
||||
/* Only alive units can be selected. Some units are not selectable (weapons) */
|
||||
if ((this.getBaseData().alive || !selected) && this.getSelectable() && this.getSelected() != selected) {
|
||||
this.#selected = selected;
|
||||
this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-selected", selected);
|
||||
if (selected)
|
||||
document.dispatchEvent(new CustomEvent("unitSelection", { detail: this }));
|
||||
else
|
||||
document.dispatchEvent(new CustomEvent("unitDeselection", { detail: this }));
|
||||
this.getGroupMembers().forEach((unit: Unit) => unit.setSelected(selected));
|
||||
}
|
||||
}
|
||||
|
||||
getSelected() {
|
||||
return this.#selected;
|
||||
}
|
||||
|
||||
setSelectable(selectable: boolean) {
|
||||
this.#selectable = selectable;
|
||||
}
|
||||
|
||||
getSelectable() {
|
||||
return this.#selectable;
|
||||
}
|
||||
|
||||
setHotgroup(hotgroup: number | null) {
|
||||
this.#hotgroup = hotgroup;
|
||||
this.#updateMarker();
|
||||
}
|
||||
|
||||
getHotgroup() {
|
||||
return this.#hotgroup;
|
||||
}
|
||||
|
||||
setHighlighted(highlighted: boolean) {
|
||||
if (this.getSelectable() && this.#highlighted != highlighted) {
|
||||
this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-highlighted", highlighted);
|
||||
this.#highlighted = highlighted;
|
||||
this.getGroupMembers().forEach((unit: Unit) => unit.setHighlighted(highlighted));
|
||||
}
|
||||
}
|
||||
|
||||
getHighlighted() {
|
||||
return this.#highlighted;
|
||||
}
|
||||
|
||||
getGroupMembers() {
|
||||
return Object.values(getUnitsManager().getUnits()).filter((unit: Unit) => {return unit != this && unit.getBaseData().groupName === this.getBaseData().groupName;});
|
||||
}
|
||||
|
||||
/********************** Unit data *************************/
|
||||
setData(data: UpdateData) {
|
||||
/* Check if data has changed comparing new values to old values */
|
||||
const positionChanged = (data.flightData != undefined && data.flightData.latitude != undefined && data.flightData.longitude != undefined && (this.getFlightData().latitude != data.flightData.latitude || this.getFlightData().longitude != data.flightData.longitude));
|
||||
@@ -237,52 +294,119 @@ export class Unit extends Marker {
|
||||
return this.getData().optionsData;
|
||||
}
|
||||
|
||||
setSelected(selected: boolean) {
|
||||
/* Only alive units can be selected. Some units are not selectable (weapons) */
|
||||
if ((this.getBaseData().alive || !selected) && this.getSelectable() && this.getSelected() != selected) {
|
||||
this.#selected = selected;
|
||||
this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-selected");
|
||||
if (selected)
|
||||
document.dispatchEvent(new CustomEvent("unitSelection", { detail: this }));
|
||||
else
|
||||
document.dispatchEvent(new CustomEvent("unitDeselection", { detail: this }));
|
||||
/********************** Icon *************************/
|
||||
createIcon(): void {
|
||||
/* Set the icon */
|
||||
var icon = new DivIcon({
|
||||
className: 'leaflet-unit-icon',
|
||||
iconAnchor: [25, 25],
|
||||
iconSize: [50, 50],
|
||||
});
|
||||
this.setIcon(icon);
|
||||
|
||||
var el = document.createElement("div");
|
||||
el.classList.add("unit");
|
||||
el.setAttribute("data-object", `unit-${this.getMarkerCategory()}`);
|
||||
el.setAttribute("data-coalition", this.getMissionData().coalition);
|
||||
|
||||
// Generate and append elements depending on active options
|
||||
// Velocity vector
|
||||
if (this.getIconOptions().showVvi) {
|
||||
var vvi = document.createElement("div");
|
||||
vvi.classList.add("unit-vvi");
|
||||
vvi.toggleAttribute("data-rotate-to-heading");
|
||||
el.append(vvi);
|
||||
}
|
||||
}
|
||||
|
||||
getSelected() {
|
||||
return this.#selected;
|
||||
}
|
||||
// Hotgroup indicator
|
||||
if (this.getIconOptions().showHotgroup) {
|
||||
var hotgroup = document.createElement("div");
|
||||
hotgroup.classList.add("unit-hotgroup");
|
||||
var hotgroupId = document.createElement("div");
|
||||
hotgroupId.classList.add("unit-hotgroup-id");
|
||||
hotgroup.appendChild(hotgroupId);
|
||||
el.append(hotgroup);
|
||||
}
|
||||
|
||||
setSelectable(selectable: boolean) {
|
||||
this.#selectable = selectable;
|
||||
}
|
||||
// Main icon
|
||||
if (this.getIconOptions().showUnitIcon) {
|
||||
var unitIcon = document.createElement("div");
|
||||
unitIcon.classList.add("unit-icon");
|
||||
var img = document.createElement("img");
|
||||
img.src = `/resources/theme/images/units/${this.getMarkerCategory()}.svg`;
|
||||
img.onload = () => SVGInjector(img);
|
||||
unitIcon.appendChild(img);
|
||||
unitIcon.toggleAttribute("data-rotate-to-heading", this.getIconOptions().rotateToHeading);
|
||||
el.append(unitIcon);
|
||||
}
|
||||
|
||||
getSelectable() {
|
||||
return this.#selectable;
|
||||
}
|
||||
// State icon
|
||||
if (this.getIconOptions().showState){
|
||||
var state = document.createElement("div");
|
||||
state.classList.add("unit-state");
|
||||
el.appendChild(state);
|
||||
}
|
||||
|
||||
setHotgroup(hotgroup: number | null) {
|
||||
this.#hotgroup = hotgroup;
|
||||
}
|
||||
// Short label
|
||||
if (this.getIconOptions().showShortLabel) {
|
||||
var shortLabel = document.createElement("div");
|
||||
shortLabel.classList.add("unit-short-label");
|
||||
shortLabel.innerText = this.getDatabase()?.getByName(this.getBaseData().name)?.shortLabel || "";
|
||||
el.append(shortLabel);
|
||||
}
|
||||
|
||||
getHotgroup() {
|
||||
return this.#hotgroup;
|
||||
}
|
||||
// Fuel indicator
|
||||
if (this.getIconOptions().showFuel) {
|
||||
var fuelIndicator = document.createElement("div");
|
||||
fuelIndicator.classList.add("unit-fuel");
|
||||
var fuelLevel = document.createElement("div");
|
||||
fuelLevel.classList.add("unit-fuel-level");
|
||||
fuelIndicator.appendChild(fuelLevel);
|
||||
el.append(fuelIndicator);
|
||||
}
|
||||
|
||||
setHighlighted(highlighted: boolean) {
|
||||
this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-highlighted", highlighted);
|
||||
this.#highlighted = highlighted;
|
||||
}
|
||||
// Ammo indicator
|
||||
if (this.getIconOptions().showAmmo){
|
||||
var ammoIndicator = document.createElement("div");
|
||||
ammoIndicator.classList.add("unit-ammo");
|
||||
for (let i = 0; i <= 3; i++)
|
||||
ammoIndicator.appendChild(document.createElement("div"));
|
||||
el.append(ammoIndicator);
|
||||
}
|
||||
|
||||
getHighlighted() {
|
||||
return this.#highlighted;
|
||||
// Unit summary
|
||||
if (this.getIconOptions().showSummary) {
|
||||
var summary = document.createElement("div");
|
||||
summary.classList.add("unit-summary");
|
||||
var callsign = document.createElement("div");
|
||||
callsign.classList.add("unit-callsign");
|
||||
callsign.innerText = this.getBaseData().unitName;
|
||||
var altitude = document.createElement("div");
|
||||
altitude.classList.add("unit-altitude");
|
||||
var speed = document.createElement("div");
|
||||
speed.classList.add("unit-speed");
|
||||
summary.appendChild(callsign);
|
||||
summary.appendChild(altitude);
|
||||
summary.appendChild(speed);
|
||||
el.appendChild(summary);
|
||||
}
|
||||
|
||||
this.getElement()?.appendChild(el);
|
||||
}
|
||||
|
||||
/********************** Visibility *************************/
|
||||
updateVisibility() {
|
||||
this.setHidden(document.body.getAttribute(`data-hide-${this.getMissionData().coalition}`) != null ||
|
||||
document.body.getAttribute(`data-hide-${this.getMarkerCategory()}`) != null ||
|
||||
!this.getBaseData().alive)
|
||||
var hidden = false;
|
||||
const hiddenUnits = getUnitsManager().getHiddenTypes();
|
||||
if (this.getMissionData().flags.Human && hiddenUnits.includes("human"))
|
||||
hidden = true;
|
||||
else if (this.getBaseData().AI == false && hiddenUnits.includes("dcs"))
|
||||
hidden = true;
|
||||
else if (hiddenUnits.includes(this.getMarkerCategory()))
|
||||
hidden = true;
|
||||
else if (hiddenUnits.includes(this.getMissionData().coalition))
|
||||
hidden = true;
|
||||
this.setHidden(hidden || !this.getBaseData().alive);
|
||||
}
|
||||
|
||||
setHidden(hidden: boolean) {
|
||||
@@ -430,18 +554,18 @@ export class Unit extends Marker {
|
||||
}
|
||||
|
||||
#onContextMenu(e: any) {
|
||||
var options: { [key: string]: string } = {};
|
||||
var options: {[key: string]: {text: string, tooltip: string}} = {};
|
||||
|
||||
options["Center"] = `<div id="center-map">Center map</div>`;
|
||||
options["center-map"] = {text: "Center map", tooltip: "Center the map on the unit and follow it"};
|
||||
|
||||
if (getUnitsManager().getSelectedUnits().length > 0 && !(getUnitsManager().getSelectedUnits().length == 1 && (getUnitsManager().getSelectedUnits().includes(this)))) {
|
||||
options['Attack'] = `<div id="attack">Attack</div>`;
|
||||
options["attack"] = {text: "Attack", tooltip: "Attack the unit using A/A or A/G weapons"};
|
||||
if (getUnitsManager().getSelectedUnitsType() === "Aircraft")
|
||||
options['Follow'] = `<div id="follow">Follow</div>`;
|
||||
options["follow"] = {text: "Follow", tooltip: "Follow the unit at a user defined distance and position"};;
|
||||
}
|
||||
else if ((getUnitsManager().getSelectedUnits().length > 0 && (getUnitsManager().getSelectedUnits().includes(this))) || getUnitsManager().getSelectedUnits().length == 0) {
|
||||
if (this.getBaseData().category == "Aircraft") {
|
||||
options["Refuel"] = `<div id="refuel">Refuel</div>`; // TODO Add some way of knowing which aircraft can AAR
|
||||
options["refuel"] = {text: "AAR Refuel", tooltip: "Refuel unit at the nearest AAR Tanker. If no tanker is available the unit will RTB."}; // TODO Add some way of knowing which aircraft can AAR
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,28 +579,28 @@ export class Unit extends Marker {
|
||||
}
|
||||
|
||||
#executeAction(e: any, action: string) {
|
||||
if (action === "Center")
|
||||
if (action === "center-map")
|
||||
getMap().centerOnUnit(this.ID);
|
||||
if (action === "Attack")
|
||||
if (action === "attack")
|
||||
getUnitsManager().selectedUnitsAttackUnit(this.ID);
|
||||
else if (action === "Refuel")
|
||||
else if (action === "refuel")
|
||||
getUnitsManager().selectedUnitsRefuel();
|
||||
else if (action === "Follow")
|
||||
else if (action === "follow")
|
||||
this.#showFollowOptions(e);
|
||||
}
|
||||
|
||||
#showFollowOptions(e: any) {
|
||||
var options: { [key: string]: string } = {};
|
||||
var options: {[key: string]: {text: string, tooltip: string}} = {};
|
||||
|
||||
options = {
|
||||
'Trail': `<div id="trail">Trail</div>`,
|
||||
'Echelon (LH)': `<div id="echelon-lh">Echelon (left)</div>`,
|
||||
'Echelon (RH)': `<div id="echelon-rh">Echelon (right)</div>`,
|
||||
'Line abreast (LH)': `<div id="line-abreast">Line abreast (left)</div>`,
|
||||
'Line abreast (RH)': `<div id="line-abreast">Line abreast (right)</div>`,
|
||||
'Front': `<div id="front">In front</div>`,
|
||||
'Diamond': `<div id="diamond">Diamond</div>`,
|
||||
'Custom': `<div id="custom">Custom</div>`
|
||||
'trail': {text: "Trail", tooltip: "Follow unit in trail formation"},
|
||||
'echelon-lh': {text: "Echelon (LH)", tooltip: "Follow unit in echelon left formation"},
|
||||
'echelon-rh': {text: "Echelon (RH)", tooltip: "Follow unit in echelon right formation"},
|
||||
'line-abreast-lh': {text: "Line abreast (LH)", tooltip: "Follow unit in line abreast left formation"},
|
||||
'line-abreast-rh': {text: "Line abreast (RH)", tooltip: "Follow unit in line abreast right formation"},
|
||||
'front': {text: "Front", tooltip: "Fly in front of unit"},
|
||||
'diamond': {text: "Diamond", tooltip: "Follow unit in diamond formation"},
|
||||
'custom': {text: "Custom", tooltip: "Set a custom formation position"},
|
||||
}
|
||||
|
||||
getMap().getUnitContextMenu().setOptions(options, (option: string) => {
|
||||
@@ -487,7 +611,7 @@ export class Unit extends Marker {
|
||||
}
|
||||
|
||||
#applyFollowOptions(action: string) {
|
||||
if (action === "Custom") {
|
||||
if (action === "custom") {
|
||||
document.getElementById("custom-formation-dialog")?.classList.remove("hide");
|
||||
getMap().getUnitContextMenu().setCustomFormationCallback((offset: { x: number, y: number, z: number }) => {
|
||||
getUnitsManager().selectedUnitsFollowUnit(this.ID, offset);
|
||||
@@ -543,18 +667,18 @@ export class Unit extends Marker {
|
||||
element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.getBaseData().alive);
|
||||
|
||||
/* Set current unit state */
|
||||
if (this.getMissionData().flags.Human) // Unit is human
|
||||
if (this.getMissionData().flags.Human) // Unit is human
|
||||
element.querySelector(".unit")?.setAttribute("data-state", "human");
|
||||
else if (!this.getBaseData().AI) // Unit is under DCS control (not Olympus)
|
||||
else if (!this.getBaseData().AI) // Unit is under DCS control (not Olympus)
|
||||
element.querySelector(".unit")?.setAttribute("data-state", "dcs");
|
||||
else // Unit is under Olympus control
|
||||
else // Unit is under Olympus control
|
||||
element.querySelector(".unit")?.setAttribute("data-state", this.getTaskData().currentState.toLowerCase());
|
||||
|
||||
/* Set altitude and speed */
|
||||
if (element.querySelector(".unit-altitude"))
|
||||
(<HTMLElement>element.querySelector(".unit-altitude")).innerText = "FL" + String(Math.floor(this.getFlightData().altitude / 0.3048 / 100));
|
||||
if (element.querySelector(".unit-speed"))
|
||||
(<HTMLElement>element.querySelector(".unit-speed")).innerHTML = String(Math.floor(this.getFlightData().speed * 1.94384));
|
||||
(<HTMLElement>element.querySelector(".unit-speed")).innerText = String(Math.floor(this.getFlightData().speed * 1.94384));
|
||||
|
||||
/* Rotate elements according to heading */
|
||||
element.querySelectorAll("[data-rotate-to-heading]").forEach(el => {
|
||||
@@ -563,7 +687,7 @@ export class Unit extends Marker {
|
||||
el.setAttribute("style", currentStyle + `transform:rotate(${headingDeg}deg);`);
|
||||
});
|
||||
|
||||
/* Turn on ordnance indicators */
|
||||
/* Turn on ammo indicators */
|
||||
var hasFox1 = element.querySelector(".unit")?.hasAttribute("data-has-fox-1");
|
||||
var hasFox2 = element.querySelector(".unit")?.hasAttribute("data-has-fox-2");
|
||||
var hasFox3 = element.querySelector(".unit")?.hasAttribute("data-has-fox-3");
|
||||
@@ -646,27 +770,25 @@ export class Unit extends Marker {
|
||||
}
|
||||
|
||||
#drawTargets() {
|
||||
for (let typeIndex in this.getMissionData().targets) {
|
||||
for (let index in this.getMissionData().targets[typeIndex]) {
|
||||
var targetData = this.getMissionData().targets[typeIndex][index];
|
||||
var target = getUnitsManager().getUnitByID(targetData.object["id_"])
|
||||
if (target != null) {
|
||||
var startLatLng = new LatLng(this.getFlightData().latitude, this.getFlightData().longitude)
|
||||
var endLatLng = new LatLng(target.getFlightData().latitude, target.getFlightData().longitude)
|
||||
for (let index in this.getMissionData().targets) {
|
||||
var targetData = this.getMissionData().targets[index];
|
||||
var target = getUnitsManager().getUnitByID(targetData.object["id_"])
|
||||
if (target != null) {
|
||||
var startLatLng = new LatLng(this.getFlightData().latitude, this.getFlightData().longitude)
|
||||
var endLatLng = new LatLng(target.getFlightData().latitude, target.getFlightData().longitude)
|
||||
|
||||
var color;
|
||||
if (typeIndex === "radar")
|
||||
color = "#FFFF00";
|
||||
else if (typeIndex === "visual")
|
||||
color = "#FF00FF";
|
||||
else if (typeIndex === "rwr")
|
||||
color = "#00FF00";
|
||||
else
|
||||
color = "#FFFFFF";
|
||||
var targetPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 0.4, smoothFactor: 1 });
|
||||
targetPolyline.addTo(getMap());
|
||||
this.#targetsPolylines.push(targetPolyline)
|
||||
}
|
||||
var color;
|
||||
if (targetData.detectionMethod === "RADAR")
|
||||
color = "#FFFF00";
|
||||
else if (targetData.detectionMethod === "VISUAL")
|
||||
color = "#FF00FF";
|
||||
else if (targetData.detectionMethod === "RWR")
|
||||
color = "#00FF00";
|
||||
else
|
||||
color = "#FFFFFF";
|
||||
var targetPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 0.4, smoothFactor: 1 });
|
||||
targetPolyline.addTo(getMap());
|
||||
this.#targetsPolylines.push(targetPolyline)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -679,7 +801,19 @@ export class Unit extends Marker {
|
||||
}
|
||||
|
||||
export class AirUnit extends Unit {
|
||||
|
||||
getIconOptions() {
|
||||
return {
|
||||
showState: true,
|
||||
showVvi: true,
|
||||
showHotgroup: true,
|
||||
showUnitIcon: true,
|
||||
showShortLabel: true,
|
||||
showFuel: true,
|
||||
showAmmo: true,
|
||||
showSummary: true,
|
||||
rotateToHeading: false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class Aircraft extends AirUnit {
|
||||
@@ -687,37 +821,13 @@ export class Aircraft extends AirUnit {
|
||||
super(ID, data);
|
||||
}
|
||||
|
||||
getMarkerHTML() {
|
||||
return `<div class="unit" data-object="unit-aircraft" data-coalition="${this.getMissionData().coalition}">
|
||||
<div class="unit-selected-spotlight"></div>
|
||||
<div class="unit-marker-border"></div>
|
||||
<div class="unit-state"></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";
|
||||
}
|
||||
|
||||
getDatabase(): UnitDatabase | null {
|
||||
return aircraftDatabase;
|
||||
}
|
||||
}
|
||||
|
||||
export class Helicopter extends AirUnit {
|
||||
@@ -725,7 +835,7 @@ export class Helicopter extends AirUnit {
|
||||
super(ID, data);
|
||||
}
|
||||
|
||||
getVisibilityCategory() {
|
||||
getMarkerCategory() {
|
||||
return "helicopter";
|
||||
}
|
||||
}
|
||||
@@ -735,24 +845,30 @@ export class GroundUnit extends Unit {
|
||||
super(ID, data);
|
||||
}
|
||||
|
||||
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 class="unit-hotgroup">
|
||||
<div class="unit-hotgroup-id"></div>
|
||||
</div>
|
||||
</div>`
|
||||
getIconOptions() {
|
||||
return {
|
||||
showState: true,
|
||||
showVvi: false,
|
||||
showHotgroup: true,
|
||||
showUnitIcon: true,
|
||||
showShortLabel: true,
|
||||
showFuel: false,
|
||||
showAmmo: false,
|
||||
showSummary: false,
|
||||
rotateToHeading: false
|
||||
};
|
||||
}
|
||||
|
||||
getMarkerCategory() {
|
||||
// TODO this is very messy
|
||||
var role = groundUnitsDatabase.getByName(this.getBaseData().name)?.loadouts[0].roles[0];
|
||||
var markerCategory = (role === "SAM") ? "sam" : "groundunit";
|
||||
var markerCategory = (role === "SAM") ? "groundunit-sam" : "groundunit-other";
|
||||
return markerCategory;
|
||||
}
|
||||
|
||||
getDatabase(): UnitDatabase | null {
|
||||
return groundUnitsDatabase;
|
||||
}
|
||||
}
|
||||
|
||||
export class NavyUnit extends Unit {
|
||||
@@ -760,6 +876,20 @@ export class NavyUnit extends Unit {
|
||||
super(ID, data);
|
||||
}
|
||||
|
||||
getIconOptions() {
|
||||
return {
|
||||
showState: true,
|
||||
showVvi: false,
|
||||
showHotgroup: true,
|
||||
showUnitIcon: true,
|
||||
showShortLabel: true,
|
||||
showFuel: false,
|
||||
showAmmo: false,
|
||||
showSummary: false,
|
||||
rotateToHeading: false
|
||||
};
|
||||
}
|
||||
|
||||
getMarkerCategory() {
|
||||
return "navyunit";
|
||||
}
|
||||
@@ -771,14 +901,19 @@ export class Weapon extends Unit {
|
||||
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>`
|
||||
getIconOptions() {
|
||||
return {
|
||||
showState: false,
|
||||
showVvi: false,
|
||||
showHotgroup: false,
|
||||
showUnitIcon: true,
|
||||
showShortLabel: false,
|
||||
showFuel: false,
|
||||
showAmmo: false,
|
||||
showSummary: false,
|
||||
rotateToHeading: true
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class Missile extends Weapon {
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
export class UnitDatabase {
|
||||
blueprints: {[key: string]: UnitBlueprint} = {};
|
||||
blueprints: { [key: string]: UnitBlueprint } = {};
|
||||
|
||||
constructor()
|
||||
{
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
/* Returns a list of all possible roles in a database */
|
||||
getRoles()
|
||||
{
|
||||
getRoles() {
|
||||
var roles: string[] = [];
|
||||
for (let unit in this.blueprints)
|
||||
{
|
||||
for (let loadout of this.blueprints[unit].loadouts)
|
||||
{
|
||||
for (let role of loadout.roles)
|
||||
{
|
||||
for (let unit in this.blueprints) {
|
||||
for (let loadout of this.blueprints[unit].loadouts) {
|
||||
for (let role of loadout.roles) {
|
||||
if (role !== "" && !roles.includes(role))
|
||||
roles.push(role);
|
||||
}
|
||||
@@ -25,18 +20,15 @@ export class UnitDatabase {
|
||||
}
|
||||
|
||||
/* Gets a specific blueprint by name */
|
||||
getByName(name: string)
|
||||
{
|
||||
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)
|
||||
{
|
||||
getByLabel(label: string) {
|
||||
for (let unit in this.blueprints) {
|
||||
if (this.blueprints[unit].label === label)
|
||||
return this.blueprints[unit];
|
||||
}
|
||||
@@ -44,15 +36,11 @@ export class UnitDatabase {
|
||||
}
|
||||
|
||||
/* Get all blueprints by role */
|
||||
getByRole(role: string)
|
||||
{
|
||||
getByRole(role: string) {
|
||||
var units = [];
|
||||
for (let unit in this.blueprints)
|
||||
{
|
||||
for (let loadout of this.blueprints[unit].loadouts)
|
||||
{
|
||||
if (loadout.roles.includes(role) || loadout.roles.includes(role.toLowerCase()))
|
||||
{
|
||||
for (let unit in this.blueprints) {
|
||||
for (let loadout of this.blueprints[unit].loadouts) {
|
||||
if (loadout.roles.includes(role) || loadout.roles.includes(role.toLowerCase())) {
|
||||
units.push(this.blueprints[unit])
|
||||
break;
|
||||
}
|
||||
@@ -62,13 +50,10 @@ export class UnitDatabase {
|
||||
}
|
||||
|
||||
/* Get the names of all the loadouts for a specific unit and for a specific role */
|
||||
getLoadoutNamesByRole(name: string, role: string)
|
||||
{
|
||||
getLoadoutNamesByRole(name: string, role: string) {
|
||||
var loadouts = [];
|
||||
for (let loadout of this.blueprints[name].loadouts)
|
||||
{
|
||||
if (loadout.roles.includes(role) || loadout.roles.includes(""))
|
||||
{
|
||||
for (let loadout of this.blueprints[name].loadouts) {
|
||||
if (loadout.roles.includes(role) || loadout.roles.includes("")) {
|
||||
loadouts.push(loadout.name)
|
||||
}
|
||||
}
|
||||
@@ -76,10 +61,8 @@ export class UnitDatabase {
|
||||
}
|
||||
|
||||
/* Get the loadout content from the unit name and loadout name */
|
||||
getLoadoutByName(name: string, loadoutName: string)
|
||||
{
|
||||
for (let loadout of this.blueprints[name].loadouts)
|
||||
{
|
||||
getLoadoutByName(name: string, loadoutName: string) {
|
||||
for (let loadout of this.blueprints[name].loadouts) {
|
||||
if (loadout.name === loadoutName)
|
||||
return loadout;
|
||||
}
|
||||
|
||||
@@ -3,13 +3,14 @@ import { getHotgroupPanel, getInfoPopup, getMap, getUnitDataTable } from "..";
|
||||
import { Unit } from "./unit";
|
||||
import { cloneUnit } from "../server/server";
|
||||
import { IDLE, MOVE_UNIT } from "../map/map";
|
||||
import { keyEventWasInInput } from "../other/utils";
|
||||
import { deg2rad, keyEventWasInInput, latLngToMercator, mercatorToLatLng } from "../other/utils";
|
||||
|
||||
export class UnitsManager {
|
||||
#units: { [ID: number]: Unit };
|
||||
#copiedUnits: Unit[];
|
||||
#selectionEventDisabled: boolean = false;
|
||||
#pasteDisabled: boolean = false;
|
||||
#hiddenTypes: string[] = [];
|
||||
|
||||
constructor() {
|
||||
this.#units = {};
|
||||
@@ -46,7 +47,7 @@ export class UnitsManager {
|
||||
}
|
||||
|
||||
getUnitsByHotgroup(hotgroup: number) {
|
||||
return Object.values(this.#units).filter((unit: Unit) => {return unit.getBaseData().alive && unit.getHotgroup() == hotgroup});
|
||||
return Object.values(this.#units).filter((unit: Unit) => { return unit.getBaseData().alive && unit.getHotgroup() == hotgroup });
|
||||
}
|
||||
|
||||
addUnit(ID: number, data: UnitData) {
|
||||
@@ -62,6 +63,7 @@ export class UnitsManager {
|
||||
}
|
||||
|
||||
update(data: UnitsData) {
|
||||
var updatedUnits: Unit[] = [];
|
||||
Object.keys(data.units)
|
||||
.filter((ID: string) => !(ID in this.#units))
|
||||
.reduce((timeout: number, ID: string) => {
|
||||
@@ -75,7 +77,29 @@ export class UnitsManager {
|
||||
|
||||
Object.keys(data.units)
|
||||
.filter((ID: string) => ID in this.#units)
|
||||
.forEach((ID: string) => this.#units[parseInt(ID)]?.setData(data.units[ID]));
|
||||
.forEach((ID: string) => {
|
||||
updatedUnits.push(this.#units[parseInt(ID)]);
|
||||
this.#units[parseInt(ID)]?.setData(data.units[ID])
|
||||
});
|
||||
|
||||
this.getSelectedUnits().forEach((unit: Unit) => {
|
||||
if (!updatedUnits.includes(unit))
|
||||
unit.setData({})
|
||||
});
|
||||
}
|
||||
|
||||
setHiddenType(key: string, value: boolean) {
|
||||
if (value) {
|
||||
if (this.#hiddenTypes.includes(key))
|
||||
delete this.#hiddenTypes[this.#hiddenTypes.indexOf(key)];
|
||||
}
|
||||
else
|
||||
this.#hiddenTypes.push(key);
|
||||
Object.values(this.getUnits()).forEach((unit: Unit) => unit.updateVisibility());
|
||||
}
|
||||
|
||||
getHiddenTypes() {
|
||||
return this.#hiddenTypes;
|
||||
}
|
||||
|
||||
selectUnit(ID: number, deselectAllUnits: boolean = true) {
|
||||
@@ -96,7 +120,7 @@ export class UnitsManager {
|
||||
}
|
||||
}
|
||||
|
||||
getSelectedUnits(options?: {excludeHumans?: boolean}) {
|
||||
getSelectedUnits(options?: { excludeHumans?: boolean }) {
|
||||
var selectedUnits = [];
|
||||
for (let ID in this.#units) {
|
||||
if (this.#units[ID].getSelected()) {
|
||||
@@ -105,7 +129,7 @@ export class UnitsManager {
|
||||
}
|
||||
if (options) {
|
||||
if (options.excludeHumans)
|
||||
selectedUnits = selectedUnits.filter((unit: Unit) => {return !unit.getMissionData().flags.Human});
|
||||
selectedUnits = selectedUnits.filter((unit: Unit) => { return !unit.getMissionData().flags.Human });
|
||||
}
|
||||
return selectedUnits;
|
||||
}
|
||||
@@ -162,8 +186,16 @@ export class UnitsManager {
|
||||
};
|
||||
|
||||
/*********************** Actions on selected units ************************/
|
||||
selectedUnitsAddDestination(latlng: L.LatLng) {
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
selectedUnitsAddDestination(latlng: L.LatLng, mantainRelativePosition: boolean, rotation: number) {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
|
||||
|
||||
/* Compute the destination for each unit. If mantainRelativePosition is true, compute the destination so to hold the relative distances */
|
||||
var unitDestinations: { [key: number]: LatLng } = {};
|
||||
if (mantainRelativePosition)
|
||||
unitDestinations = this.selectedUnitsComputeGroupDestination(latlng, rotation);
|
||||
else
|
||||
selectedUnits.forEach((unit: Unit) => { unitDestinations[unit.ID] = latlng });
|
||||
|
||||
for (let idx in selectedUnits) {
|
||||
const unit = selectedUnits[idx];
|
||||
/* If a unit is following another unit, and that unit is also selected, send the command to the followed unit */
|
||||
@@ -174,14 +206,17 @@ export class UnitsManager {
|
||||
else
|
||||
unit.addDestination(latlng);
|
||||
}
|
||||
else
|
||||
unit.addDestination(latlng);
|
||||
else {
|
||||
if (unit.ID in unitDestinations)
|
||||
unit.addDestination(unitDestinations[unit.ID]);
|
||||
}
|
||||
|
||||
}
|
||||
this.#showActionMessage(selectedUnits, " new destination added");
|
||||
}
|
||||
|
||||
selectedUnitsClearDestinations() {
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
|
||||
for (let idx in selectedUnits) {
|
||||
const unit = selectedUnits[idx];
|
||||
if (unit.getTaskData().currentState === "Follow") {
|
||||
@@ -197,7 +232,7 @@ export class UnitsManager {
|
||||
}
|
||||
|
||||
selectedUnitsLandAt(latlng: LatLng) {
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].landAt(latlng);
|
||||
}
|
||||
@@ -205,21 +240,21 @@ export class UnitsManager {
|
||||
}
|
||||
|
||||
selectedUnitsChangeSpeed(speedChange: string) {
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].changeSpeed(speedChange);
|
||||
}
|
||||
}
|
||||
|
||||
selectedUnitsChangeAltitude(altitudeChange: string) {
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].changeAltitude(altitudeChange);
|
||||
}
|
||||
}
|
||||
|
||||
selectedUnitsSetSpeed(speed: number) {
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].setSpeed(speed);
|
||||
}
|
||||
@@ -227,7 +262,7 @@ export class UnitsManager {
|
||||
}
|
||||
|
||||
selectedUnitsSetAltitude(altitude: number) {
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].setAltitude(altitude);
|
||||
}
|
||||
@@ -235,7 +270,7 @@ export class UnitsManager {
|
||||
}
|
||||
|
||||
selectedUnitsSetROE(ROE: string) {
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].setROE(ROE);
|
||||
}
|
||||
@@ -243,7 +278,7 @@ export class UnitsManager {
|
||||
}
|
||||
|
||||
selectedUnitsSetReactionToThreat(reactionToThreat: string) {
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].setReactionToThreat(reactionToThreat);
|
||||
}
|
||||
@@ -251,7 +286,7 @@ export class UnitsManager {
|
||||
}
|
||||
|
||||
selectedUnitsSetEmissionsCountermeasures(emissionCountermeasure: string) {
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].setEmissionsCountermeasures(emissionCountermeasure);
|
||||
}
|
||||
@@ -260,7 +295,7 @@ export class UnitsManager {
|
||||
|
||||
|
||||
selectedUnitsAttackUnit(ID: number) {
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].attackUnit(ID);
|
||||
}
|
||||
@@ -276,7 +311,7 @@ export class UnitsManager {
|
||||
}
|
||||
|
||||
selectedUnitsRefuel() {
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].refuel();
|
||||
}
|
||||
@@ -290,15 +325,15 @@ export class UnitsManager {
|
||||
// Y: top-bottom, positive top
|
||||
// Z: left-right, positive right
|
||||
offset = { "x": 0, "y": 0, "z": 0 };
|
||||
if (formation === "Trail") { offset.x = -50; offset.y = -30; offset.z = 0; }
|
||||
else if (formation === "Echelon (LH)") { offset.x = -50; offset.y = -10; offset.z = -50; }
|
||||
else if (formation === "Echelon (RH)") { offset.x = -50; offset.y = -10; offset.z = 50; }
|
||||
else if (formation === "Line abreast (RH)") { offset.x = 0; offset.y = 0; offset.z = 50; }
|
||||
else if (formation === "Line abreast (LH)") { offset.x = 0; offset.y = 0; offset.z = -50; }
|
||||
else if (formation === "Front") { offset.x = 100; offset.y = 0; offset.z = 0; }
|
||||
if (formation === "trail") { offset.x = -50; offset.y = -30; offset.z = 0; }
|
||||
else if (formation === "echelon-lh") { offset.x = -50; offset.y = -10; offset.z = -50; }
|
||||
else if (formation === "echelon-rh") { offset.x = -50; offset.y = -10; offset.z = 50; }
|
||||
else if (formation === "line-abreast-rh") { offset.x = 0; offset.y = 0; offset.z = 50; }
|
||||
else if (formation === "line-abreast-lh") { offset.x = 0; offset.y = 0; offset.z = -50; }
|
||||
else if (formation === "front") { offset.x = 100; offset.y = 0; offset.z = 0; }
|
||||
else offset = undefined;
|
||||
}
|
||||
var selectedUnits = this.getSelectedUnits({excludeHumans: true});
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
|
||||
var count = 1;
|
||||
var xr = 0; var yr = 1; var zr = -1;
|
||||
var layer = 1;
|
||||
@@ -309,7 +344,7 @@ export class UnitsManager {
|
||||
unit.followUnit(ID, { "x": offset.x * count, "y": offset.y * count, "z": offset.z * count });
|
||||
else {
|
||||
/* More complex formations with variable offsets */
|
||||
if (formation === "Diamond") {
|
||||
if (formation === "diamond") {
|
||||
var xl = xr * Math.cos(Math.PI / 4) - yr * Math.sin(Math.PI / 4);
|
||||
var yl = xr * Math.sin(Math.PI / 4) + yr * Math.cos(Math.PI / 4);
|
||||
unit.followUnit(ID, { "x": -yl * 50, "y": zr * 10, "z": xl * 50 });
|
||||
@@ -326,14 +361,12 @@ export class UnitsManager {
|
||||
this.#showActionMessage(selectedUnits, `following unit ${this.getUnitByID(ID)?.getBaseData().unitName}`);
|
||||
}
|
||||
|
||||
selectedUnitsSetHotgroup(hotgroup: number)
|
||||
{
|
||||
selectedUnitsSetHotgroup(hotgroup: number) {
|
||||
this.getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setHotgroup(null));
|
||||
this.selectedUnitsAddToHotgroup(hotgroup);
|
||||
}
|
||||
|
||||
selectedUnitsAddToHotgroup(hotgroup: number)
|
||||
{
|
||||
selectedUnitsAddToHotgroup(hotgroup: number) {
|
||||
var selectedUnits = this.getSelectedUnits();
|
||||
for (let idx in selectedUnits) {
|
||||
selectedUnits[idx].setHotgroup(hotgroup);
|
||||
@@ -342,6 +375,37 @@ export class UnitsManager {
|
||||
getHotgroupPanel().refreshHotgroups();
|
||||
}
|
||||
|
||||
selectedUnitsComputeGroupDestination(latlng: LatLng, rotation: number) {
|
||||
var selectedUnits = this.getSelectedUnits({ excludeHumans: true });
|
||||
/* Compute the center of the group */
|
||||
var center = { x: 0, y: 0 };
|
||||
selectedUnits.forEach((unit: Unit) => {
|
||||
var mercator = latLngToMercator(unit.getFlightData().latitude, unit.getFlightData().longitude);
|
||||
center.x += mercator.x / selectedUnits.length;
|
||||
center.y += mercator.y / selectedUnits.length;
|
||||
});
|
||||
|
||||
/* Compute the distances from the center of the group */
|
||||
var unitDestinations: { [key: number]: LatLng } = {};
|
||||
selectedUnits.forEach((unit: Unit) => {
|
||||
var mercator = latLngToMercator(unit.getFlightData().latitude, unit.getFlightData().longitude);
|
||||
var distancesFromCenter = { dx: mercator.x - center.x, dy: mercator.y - center.y };
|
||||
|
||||
/* Rotate the distance according to the group rotation */
|
||||
var rotatedDistancesFromCenter: { dx: number, dy: number } = { dx: 0, dy: 0 };
|
||||
rotatedDistancesFromCenter.dx = distancesFromCenter.dx * Math.cos(deg2rad(rotation)) - distancesFromCenter.dy * Math.sin(deg2rad(rotation));
|
||||
rotatedDistancesFromCenter.dy = distancesFromCenter.dx * Math.sin(deg2rad(rotation)) + distancesFromCenter.dy * Math.cos(deg2rad(rotation));
|
||||
|
||||
/* Compute the final position of the unit */
|
||||
var destMercator = latLngToMercator(latlng.lat, latlng.lng); // Convert destination point to mercator
|
||||
var unitMercator = { x: destMercator.x + rotatedDistancesFromCenter.dx, y: destMercator.y + rotatedDistancesFromCenter.dy }; // Compute final position of this unit in mercator coordinates
|
||||
var unitLatLng = mercatorToLatLng(unitMercator.x, unitMercator.y);
|
||||
unitDestinations[unit.ID] = new LatLng(unitLatLng.lat, unitLatLng.lng);
|
||||
});
|
||||
|
||||
return unitDestinations;
|
||||
}
|
||||
|
||||
/***********************************************/
|
||||
copyUnits() {
|
||||
this.#copiedUnits = this.getSelectedUnits(); /* Can be applied to humans too */
|
||||
|
||||
Reference in New Issue
Block a user