More work on units spawn menu and started to add documentation

This commit is contained in:
Pax1601
2023-09-01 16:13:15 +02:00
parent fab7d26191
commit 695adc8acb
255 changed files with 105653 additions and 483 deletions

View File

@@ -3,8 +3,12 @@ import { Panel } from "../panels/panel";
import { Unit } from "../unit/unit";
export class UnitDataTable extends Panel {
constructor(id: string) {
super(id);
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
this.hide();
}

View File

@@ -1,16 +1,23 @@
import { getMap, getMissionHandler, getUnitsManager, setActiveCoalition } from "..";
import { BLUE_COMMANDER, GAME_MASTER, RED_COMMANDER } from "../constants/constants";
import { GAME_MASTER } from "../constants/constants";
import { Airbase } from "../mission/airbase";
import { dataPointMap } from "../other/utils";
import { ContextMenu } from "./contextmenu";
/** This context menu is shown to the user when the airbase marker is right clicked on the map.
* It allows the user to inspect information about the airbase, as well as allowing to spawn units from the airbase itself and land units on it. */
export class AirbaseContextMenu extends ContextMenu {
#airbase: Airbase | null = null;
constructor(id: string) {
super(id);
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
document.addEventListener("contextMenuSpawnAirbase", (e: any) => {
this.showSpawnMenu();
this.#showSpawnMenu();
})
document.addEventListener("contextMenuLandAirbase", (e: any) => {
@@ -20,27 +27,39 @@ export class AirbaseContextMenu extends ContextMenu {
})
}
/** Sets the airbase for which data will be shown in the context menu
*
* @param airbase The airbase for which data will be shown in the context menu. Note: the airbase must be present in the public/databases/airbases/<theatre>.json database.
*/
setAirbase(airbase: Airbase) {
this.#airbase = airbase;
this.setName(this.#airbase.getName());
this.setProperties(this.#airbase.getProperties());
this.setParkings(this.#airbase.getParkings());
this.setCoalition(this.#airbase.getCoalition());
this.enableLandButton(getUnitsManager().getSelectedUnitsTypes().length == 1 && ["Aircraft", "Helicopter"].includes(getUnitsManager().getSelectedUnitsTypes()[0]) && (getUnitsManager().getSelectedUnitsCoalition() === this.#airbase.getCoalition() || this.#airbase.getCoalition() === "neutral"))
this.enableSpawnButton(getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || this.#airbase.getCoalition() == getMissionHandler().getCommandedCoalition());
this.#setName(this.#airbase.getName());
this.#setProperties(this.#airbase.getProperties());
this.#setParkings(this.#airbase.getParkings());
this.#setCoalition(this.#airbase.getCoalition());
this.#showLandButton(getUnitsManager().getSelectedUnitsTypes().length == 1 && ["Aircraft", "Helicopter"].includes(getUnitsManager().getSelectedUnitsTypes()[0]) && (getUnitsManager().getSelectedUnitsCoalition() === this.#airbase.getCoalition() || this.#airbase.getCoalition() === "neutral"))
this.#showSpawnButton(getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER || this.#airbase.getCoalition() == getMissionHandler().getCommandedCoalition());
this.#setAirbaseData();
this.clip();
}
setName(airbaseName: string) {
/**
*
* @param airbaseName The name of the airbase
*/
#setName(airbaseName: string) {
var nameDiv = <HTMLElement>this.getContainer()?.querySelector("#airbase-name");
if (nameDiv != null)
nameDiv.innerText = airbaseName;
}
setProperties(airbaseProperties: string[]) {
/**
*
* @param airbaseProperties The properties of the airbase
*/
#setProperties(airbaseProperties: string[]) {
this.getContainer()?.querySelector("#airbase-properties")?.replaceChildren(...airbaseProperties.map((property: string) => {
var div = document.createElement("div");
div.innerText = property;
@@ -48,7 +67,11 @@ export class AirbaseContextMenu extends ContextMenu {
}),);
}
setParkings(airbaseParkings: string[]) {
/**
*
* @param airbaseParkings List of available parkings at the airbase
*/
#setParkings(airbaseParkings: string[]) {
this.getContainer()?.querySelector("#airbase-parking")?.replaceChildren(...airbaseParkings.map((parking: string) => {
var div = document.createElement("div");
div.innerText = parking;
@@ -56,31 +79,43 @@ export class AirbaseContextMenu extends ContextMenu {
}));
}
setCoalition(coalition: string) {
(<HTMLElement>this.getContainer()?.querySelector("#spawn-airbase-aircraft-button")).dataset.coalition = coalition;
/**
*
* @param coalition Coalition to which the airbase belongs
*/
#setCoalition(coalition: string) {
(this.getContainer()?.querySelector("#spawn-airbase-aircraft-button") as HTMLElement).dataset.coalition = coalition;
}
enableSpawnButton(enableSpawnButton: boolean) {
this.getContainer()?.querySelector("#spawn-airbase-aircraft-button")?.classList.toggle("hide", !enableSpawnButton);
/**
*
* @param showSpawnButton If true, the spawn button will be visibile
*/
#showSpawnButton(showSpawnButton: boolean) {
this.getContainer()?.querySelector("#spawn-airbase-aircraft-button")?.classList.toggle("hide", !showSpawnButton);
}
enableLandButton(enableLandButton: boolean) {
this.getContainer()?.querySelector("#land-here-button")?.classList.toggle("hide", !enableLandButton);
/**
*
* @param showLandButton If true, the land button will be visible
*/
#showLandButton(showLandButton: boolean) {
this.getContainer()?.querySelector("#land-here-button")?.classList.toggle("hide", !showLandButton);
}
showSpawnMenu() {
/** Shows the spawn context menu which allows the user to select a unit to ground spawn at the airbase
*
*/
#showSpawnMenu() {
if (this.#airbase != null) {
setActiveCoalition(this.#airbase.getCoalition());
getMap().showMapContextMenu(this.getX(), this.getY(), this.getLatLng());
getMap().getMapContextMenu().hideUpperBar();
getMap().getMapContextMenu().hideLowerBar();
getMap().getMapContextMenu().hideAltitudeSlider();
getMap().getMapContextMenu().showSubMenu("aircraft");
getMap().getMapContextMenu().setAirbase(this.#airbase);
getMap().getMapContextMenu().setLatLng(this.#airbase.getLatLng());
getMap().showAirbaseSpawnMenu(this.getX(), this.getY(), this.getLatLng(), this.#airbase);
}
}
/** @todo needs commenting
*
*/
#setAirbaseData() {
if (this.#airbase && this.getContainer()) {
dataPointMap(this.getContainer() as HTMLElement, {

View File

@@ -0,0 +1,102 @@
import { LatLng } from "leaflet";
import { getActiveCoalition } from "..";
import { ContextMenu } from "./contextmenu";
import { AircraftSpawnMenu, HelicopterSpawnMenu } from "../controls/unitspawnmenu";
import { Airbase } from "../mission/airbase";
/** This context menu is shown when the user wants to spawn a new aircraft or helicopter from the ground at an airbase.
* It is shown by clicking on the "spawn" button of a AirbaseContextMenu. */
export class AirbaseSpawnContextMenu extends ContextMenu {
#aircraftSpawnMenu: AircraftSpawnMenu;
#helicopterSpawnMenu: HelicopterSpawnMenu;
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
/* Create the spawn menus for the different unit types */
this.#aircraftSpawnMenu = new AircraftSpawnMenu("airbase-aircraft-spawn-menu");
this.#helicopterSpawnMenu = new HelicopterSpawnMenu("airbase-helicopter-spawn-menu");
this.#aircraftSpawnMenu.getAltitudeSlider().hide();
this.#helicopterSpawnMenu.getAltitudeSlider().hide();
/* Event listeners */
document.addEventListener("mapContextMenuShow", (e: any) => {
if (this.getVisibleSubMenu() !== e.detail.type)
this.#showSubMenu(e.detail.type);
else
this.#hideSubMenus();
});
this.#aircraftSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
this.#helicopterSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
this.hide();
}
/** Show the context menu
*
* @param x X screen coordinate of the top left corner of the context menu
* @param y Y screen coordinate of the top left corner of the context menu
*/
show(x: number, y: number) {
super.show(x, y, new LatLng(0, 0));
this.#aircraftSpawnMenu.setAirbase(undefined);
this.#helicopterSpawnMenu.setAirbase(undefined);
this.#aircraftSpawnMenu.setCountries();
this.#helicopterSpawnMenu.setCountries();
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
}
/** Sets the airbase at which the new unit will be spawned
*
* @param airbase The airbase at which the new unit will be spawned. Note: if the airbase has no suitable parking spots, the airplane may be spawned on the runway, or spawning may fail.
*/
setAirbase(airbase: Airbase) {
this.#aircraftSpawnMenu.setAirbase(airbase);
this.#helicopterSpawnMenu.setAirbase(airbase);
}
/** Shows the submenu depending on unit selection
*
* @param type Submenu type, either "aircraft" or "helicopter"
*/
#showSubMenu(type: string) {
this.getContainer()?.querySelector("#airbase-aircraft-spawn-menu")?.classList.toggle("hide", type !== "aircraft");
this.getContainer()?.querySelector("#airbase-aircraft-spawn-button")?.classList.toggle("is-open", type === "aircraft");
this.getContainer()?.querySelector("#airbase-helicopter-spawn-menu")?.classList.toggle("hide", type !== "helicopter");
this.getContainer()?.querySelector("#airbase-helicopter-spawn-button")?.classList.toggle("is-open", type === "helicopter");
(this.getContainer()?.querySelectorAll(".deploy-unit-button"))?.forEach((element: Node) => { (element as HTMLButtonElement).disabled = true; })
this.#aircraftSpawnMenu.reset();
this.#aircraftSpawnMenu.setCountries();
this.#helicopterSpawnMenu.reset();
this.#helicopterSpawnMenu.setCountries();
this.setVisibleSubMenu(type);
this.clip();
}
/** Hide all the open submenus
*
*/
#hideSubMenus() {
this.getContainer()?.querySelector("#airbase-aircraft-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#airbase-aircraft-spawn-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#airbase-helicopter-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#airbase-helicopter-spawn-button")?.classList.toggle("is-open", false);
this.#aircraftSpawnMenu.reset();
this.#helicopterSpawnMenu.reset();
this.setVisibleSubMenu(null);
this.clip();
}
}

View File

@@ -3,12 +3,13 @@ import { getMap, getMissionHandler, getUnitsManager } from "..";
import { GAME_MASTER, IADSTypes } from "../constants/constants";
import { CoalitionArea } from "../map/coalitionarea";
import { ContextMenu } from "./contextmenu";
import { Dropdown } from "./dropdown";
import { Slider } from "./slider";
import { Switch } from "./switch";
import { Dropdown } from "../controls/dropdown";
import { Slider } from "../controls/slider";
import { Switch } from "../controls/switch";
import { groundUnitDatabase } from "../unit/groundunitdatabase";
import { createCheckboxOption, getCheckboxOptions } from "../other/utils";
/** This context menu allows the user to edit or delete a CoalitionArea. Moreover, it allows the user to create a IADS automatically using the CoalitionArea as bounds. */
export class CoalitionAreaContextMenu extends ContextMenu {
#coalitionSwitch: Switch;
#coalitionArea: CoalitionArea | null = null;
@@ -18,16 +19,25 @@ export class CoalitionAreaContextMenu extends ContextMenu {
#iadsErasDropdown: Dropdown;
#iadsRangesDropdown: Dropdown;
constructor(id: string) {
super(id);
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
/* Create the coalition switch */
this.#coalitionSwitch = new Switch("coalition-area-switch", (value: boolean) => this.#onSwitchClick(value));
this.#coalitionSwitch.setValue(false);
/* Create the controls of the IADS creation submenu */
this.#iadsTypesDropdown = new Dropdown("iads-units-type-options", () => { });
this.#iadsErasDropdown = new Dropdown("iads-era-options", () => {});
this.#iadsRangesDropdown = new Dropdown("iads-range-options", () => {});
this.#iadsDensitySlider = new Slider("iads-density-slider", 5, 100, "%", (value: number) => { });
this.#iadsDistributionSlider = new Slider("iads-distribution-slider", 5, 100, "%", (value: number) => { });
/* Set the default parameters of the sliders */
this.#iadsDensitySlider.setIncrement(5);
this.#iadsDensitySlider.setValue(50);
this.#iadsDensitySlider.setActive(true);
@@ -37,9 +47,9 @@ export class CoalitionAreaContextMenu extends ContextMenu {
document.addEventListener("coalitionAreaContextMenuShow", (e: any) => {
if (this.getVisibleSubMenu() !== e.detail.type)
this.showSubMenu(e.detail.type);
this.#showSubMenu(e.detail.type);
else
this.hideSubMenus();
this.#hideSubMenus();
});
document.addEventListener("coalitionAreaBringToBack", (e: any) => {
@@ -62,6 +72,12 @@ export class CoalitionAreaContextMenu extends ContextMenu {
this.hide();
}
/**
*
* @param x X screen coordinate of the top left corner of the context menu
* @param y Y screen coordinate of the top left corner of the context menu
* @param latlng Leaflet latlng object of the mouse click
*/
show(x: number, y: number, latlng: LatLng) {
super.show(x, y, latlng);
@@ -85,26 +101,10 @@ export class CoalitionAreaContextMenu extends ContextMenu {
this.#coalitionSwitch.hide()
}
showSubMenu(type: string) {
this.getContainer()?.querySelector("#iads-menu")?.classList.toggle("hide", type !== "iads");
this.getContainer()?.querySelector("#iads-button")?.classList.toggle("is-open", type === "iads");
this.clip();
this.setVisibleSubMenu(type);
}
hideSubMenus() {
this.getContainer()?.querySelector("#iads-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#iads-button")?.classList.toggle("is-open", false);
this.clip();
this.setVisibleSubMenu(null);
}
getCoalitionArea() {
return this.#coalitionArea;
}
/** Set the CoalitionArea object the user will be able to edit using this menu
*
* @param coalitionArea The CoalitionArea object to edit
*/
setCoalitionArea(coalitionArea: CoalitionArea) {
this.#coalitionArea = coalitionArea;
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => {
@@ -113,6 +113,41 @@ export class CoalitionAreaContextMenu extends ContextMenu {
this.#coalitionSwitch.setValue(this.getCoalitionArea()?.getCoalition() === "red");
}
/** Get the CoalitionArea object the contextmenu is editing
*
* @returns The CoalitionArea the contextmenu is editing
*/
getCoalitionArea() {
return this.#coalitionArea;
}
/** Show a submenu of the contextmenu
*
* @param type The submenu type, currently only "iads"
*/
#showSubMenu(type: string) {
this.getContainer()?.querySelector("#iads-menu")?.classList.toggle("hide", type !== "iads");
this.getContainer()?.querySelector("#iads-button")?.classList.toggle("is-open", type === "iads");
this.clip();
this.setVisibleSubMenu(type);
}
/** Hide all submenus
*
*/
#hideSubMenus() {
this.getContainer()?.querySelector("#iads-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#iads-button")?.classList.toggle("is-open", false);
this.clip();
this.setVisibleSubMenu(null);
}
/** Callback event called when the coalition switch is clicked to change the coalition of the CoalitionArea
*
* @param value Switch position (false: blue, true: red)
*/
#onSwitchClick(value: boolean) {
if (getMissionHandler().getCommandModeOptions().commandMode == GAME_MASTER) {
this.getCoalitionArea()?.setCoalition(value ? "red" : "blue");

View File

@@ -1,52 +1,78 @@
import { LatLng } from "leaflet";
/** Base class for map contextmenus. By default it is empty and requires to be extended. */
export class ContextMenu {
#container: HTMLElement | null;
#latlng: LatLng = new LatLng(0, 0);
#x: number = 0;
#y: number = 0;
#visibleSubMenu: string | null = null;
#hidden: boolean = true;
constructor(id: string) {
this.#container = document.getElementById(id);
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
this.#container = document.getElementById(ID);
this.hide();
}
/** Show the contextmenu on top of the map, usually at the location where the user has clicked on it.
*
* @param x X screen coordinate of the top left corner of the context menu
* @param y Y screen coordinate of the top left corner of the context menu
* @param latlng Leaflet latlng object of the mouse click
*/
show(x: number, y: number, latlng: LatLng) {
this.#latlng = latlng;
this.#container?.classList.toggle("hide", false);
this.#x = x;
this.#y = y;
this.clip();
this.#hidden = false;
}
/** Hide the contextmenu
*
*/
hide() {
this.#container?.classList.toggle("hide", true);
this.#hidden = true;
}
/**
*
* @returns The HTMLElement that contains the contextmenu
*/
getContainer() {
return this.#container;
}
/**
*
* @returns The Leaflet latlng object associated to the click that caused the contextmenu to be shown
*/
getLatLng() {
return this.#latlng;
}
/**
*
* @returns The x coordinate of the top left corner of the menu
*/
getX() {
return this.#x;
}
/**
*
* @returns The y coordinate of the top left corner of the menu
*/
getY() {
return this.#y;
}
getHidden() {
return this.#hidden;
}
/** Clips the contextmenu, meaning it moves it on the screen to make sure it does not overflow the window.
*
*/
clip() {
if (this.#container != null) {
if (this.#x + this.#container.offsetWidth < window.innerWidth)
@@ -61,10 +87,18 @@ export class ContextMenu {
}
}
/** Sets the currently visible submenu
*
* @param menu The name of the currently visibile submenu, or null if no submenu is visible
*/
setVisibleSubMenu(menu: string | null) {
this.#visibleSubMenu = menu;
}
/**
*
* @returns The name of the currently visible submenu
*/
getVisibleSubMenu() {
return this.#visibleSubMenu;
}

View File

@@ -1,46 +1,48 @@
import { LatLng } from "leaflet";
import { getActiveCoalition, getMap, getMissionHandler, getUnitsManager, setActiveCoalition } from "..";
import { getActiveCoalition, getMap, getMissionHandler, setActiveCoalition } from "..";
import { spawnExplosion, spawnSmoke } from "../server/server";
import { aircraftDatabase } from "../unit/aircraftdatabase";
import { groundUnitDatabase } from "../unit/groundunitdatabase";
import { helicopterDatabase } from "../unit/helicopterdatabase";
import { ContextMenu } from "./contextmenu";
import { Dropdown } from "./dropdown";
import { Switch } from "./switch";
import { Slider } from "./slider";
import { ftToM } from "../other/utils";
import { Switch } from "../controls/switch";
import { GAME_MASTER } from "../constants/constants";
import { navyUnitDatabase } from "../unit/navyunitdatabase";
import { CoalitionArea } from "../map/coalitionarea";
import { UnitSpawnMenu } from "./unitspawnmenu";
import { AircraftSpawnMenu, GroundUnitSpawnMenu, HelicopterSpawnMenu, NavyUnitSpawnMenu } from "../controls/unitspawnmenu";
import { Airbase } from "../mission/airbase";
/** The MapContextMenu is the main contextmenu shown to the user whenever it rightclicks on the map. It is the primary interaction method for the user.
* It allows to spawn units, create explosions and smoke, and edit CoalitionAreas.
*/
export class MapContextMenu extends ContextMenu {
#coalitionSwitch: Switch;
#aircraftSpawnMenu: UnitSpawnMenu;
#helicopterSpawnMenu: UnitSpawnMenu;
#groundUnitSpawnMenu: UnitSpawnMenu;
#navyUnitSpawnMenu: UnitSpawnMenu;
#aircraftSpawnMenu: AircraftSpawnMenu;
#helicopterSpawnMenu: HelicopterSpawnMenu;
#groundUnitSpawnMenu: GroundUnitSpawnMenu;
#navyUnitSpawnMenu: NavyUnitSpawnMenu;
#coalitionArea: CoalitionArea | null = null;
constructor(id: string) {
super(id);
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
/* Create the coalition switch */
this.#coalitionSwitch = new Switch("coalition-switch", (value: boolean) => this.#onSwitchClick(value));
this.#coalitionSwitch.setValue(false);
this.#coalitionSwitch.getContainer()?.addEventListener("contextmenu", (e) => this.#onSwitchRightClick(e));
this.#coalitionSwitch.getContainer()?.addEventListener("contextmenu", (e) => this.#onSwitchRightClick());
this.#aircraftSpawnMenu = new UnitSpawnMenu("aircraft-spawn-menu", aircraftDatabase);
this.#helicopterSpawnMenu = new UnitSpawnMenu("helicopter-spawn-menu", helicopterDatabase);
this.#groundUnitSpawnMenu = new UnitSpawnMenu("groundunit-spawn-menu", groundUnitDatabase, {orderByRole: false, maxUnitCount: 20, showLoadout: false, showAltitudeSlider: false});
this.#navyUnitSpawnMenu = new UnitSpawnMenu("navyunit-spawn-menu", navyUnitDatabase, {orderByRole: false, maxUnitCount: 4, showLoadout: false, showAltitudeSlider: false});
/* Create the spawn menus for the different unit types */
this.#aircraftSpawnMenu = new AircraftSpawnMenu("aircraft-spawn-menu");
this.#helicopterSpawnMenu = new HelicopterSpawnMenu("helicopter-spawn-menu");
this.#groundUnitSpawnMenu = new GroundUnitSpawnMenu("groundunit-spawn-menu");
this.#navyUnitSpawnMenu = new NavyUnitSpawnMenu("navyunit-spawn-menu");
/* Event listeners */
document.addEventListener("mapContextMenuShow", (e: any) => {
if (this.getVisibleSubMenu() !== e.detail.type)
this.showSubMenu(e.detail.type);
else
this.hideSubMenus(e.detail.type);
this.#showSubMenu(e.detail.type);
else
this.#hideSubMenus(e.detail.type);
});
document.addEventListener("contextMenuDeploySmoke", (e: any) => {
@@ -52,7 +54,7 @@ export class MapContextMenu extends ContextMenu {
this.hide();
spawnExplosion(e.detail.strength, this.getLatLng());
});
document.addEventListener("editCoalitionArea", (e: any) => {
this.hide();
if (this.#coalitionArea) {
@@ -65,14 +67,6 @@ export class MapContextMenu extends ContextMenu {
//this.#refreshOptions();
});
document.addEventListener("toggleAdvancedOptions", (e: any) => {
if (e.detail.type === "aircraft")
document.querySelector("#aircraft-advanced-options")?.classList.toggle("hide");
else if (e.detail.type === "helicopter")
document.querySelector("#helicopter-advanced-options")?.classList.toggle("hide");
this.clip();
});
this.#aircraftSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
this.#helicopterSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
this.#groundUnitSpawnMenu.getContainer().addEventListener("resize", () => this.clip());
@@ -81,42 +75,59 @@ export class MapContextMenu extends ContextMenu {
this.hide();
}
/** Show the contextmenu on top of the map, usually at the location where the user has clicked on it.
*
* @param x X screen coordinate of the top left corner of the context menu
* @param y Y screen coordinate of the top left corner of the context menu
* @param latlng Leaflet latlng object of the mouse click
*/
show(x: number, y: number, latlng: LatLng) {
super.show(x, y, latlng);
this.showUpperBar();
this.showAltitudeSlider();
this.#aircraftSpawnMenu.setAirbase(undefined);
this.#aircraftSpawnMenu.setLatLng(latlng);
this.#helicopterSpawnMenu.setAirbase(undefined);
this.#helicopterSpawnMenu.setLatLng(latlng);
this.#groundUnitSpawnMenu.setLatLng(latlng);
this.#navyUnitSpawnMenu.setLatLng(latlng);
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
if (getActiveCoalition() == "blue")
this.#coalitionSwitch.setValue(false);
else if (getActiveCoalition() == "red")
this.#coalitionSwitch.setValue(true);
else
this.#coalitionSwitch.setValue(undefined);
if (getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER)
this.#coalitionSwitch.hide()
this.getContainer()?.querySelector("#coalition-area-button")?.classList.toggle("hide", true);
this.#aircraftSpawnMenu.setCountries();
this.#helicopterSpawnMenu.setCountries();
this.#groundUnitSpawnMenu.setCountries();
this.#navyUnitSpawnMenu.setCountries();
/* Only a Game Master can choose the coalition of a new unit */
if (getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) {
this.#coalitionSwitch.hide()
} else {
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
if (getActiveCoalition() == "blue")
this.#coalitionSwitch.setValue(false);
else if (getActiveCoalition() == "red")
this.#coalitionSwitch.setValue(true);
else
this.#coalitionSwitch.setValue(undefined);
}
/* Hide the coalition area button. It will be visible if a coalition area is set */
this.getContainer()?.querySelector("#coalition-area-button")?.classList.toggle("hide", true);
}
showSubMenu(type: string) {
/** If the user rightclicked on a CoalitionArea, it will be given the ability to edit it.
*
* @param coalitionArea The CoalitionArea the user can edit
*/
setCoalitionArea(coalitionArea: CoalitionArea) {
this.#coalitionArea = coalitionArea;
this.getContainer()?.querySelector("#coalition-area-button")?.classList.toggle("hide", false);
}
/** Shows the submenu depending on unit selection
*
* @param type Submenu type, either "aircraft", "helicopter", "groundunit", "navyunit", "smoke", or "explosion"
*/
#showSubMenu(type: string) {
if (type === "more")
this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide");
else if (["aircraft", "groundunit"].includes(type))
else if (["aircraft", "helicopter", "groundunit"].includes(type))
this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", type !== "aircraft");
@@ -132,7 +143,7 @@ export class MapContextMenu extends ContextMenu {
this.getContainer()?.querySelector("#explosion-menu")?.classList.toggle("hide", type !== "explosion");
this.getContainer()?.querySelector("#explosion-spawn-button")?.classList.toggle("is-open", type === "explosion");
(this.getContainer()?.querySelectorAll(".deploy-unit-button"))?.forEach((element: Node) => {(element as HTMLButtonElement).disabled = true;})
(this.getContainer()?.querySelectorAll(".deploy-unit-button"))?.forEach((element: Node) => { (element as HTMLButtonElement).disabled = true; })
this.#aircraftSpawnMenu.reset();
this.#aircraftSpawnMenu.setCountries();
@@ -142,13 +153,18 @@ export class MapContextMenu extends ContextMenu {
this.#groundUnitSpawnMenu.setCountries();
this.#navyUnitSpawnMenu.reset();
this.#navyUnitSpawnMenu.setCountries();
this.setVisibleSubMenu(type);
this.clip();
}
hideSubMenus(type: string) {
this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide", ["aircraft", "groundunit"].includes(type));
/** Hide all the submenus
*
* @param type The type of the currenlt open submenu.
*/
#hideSubMenus(type: string) {
/* Close the lower options bar if the currently open submenu does not required it */
this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide", ["aircraft", "helicopter", "groundunit"].includes(type));
this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#helicopter-spawn-menu")?.classList.toggle("hide", true);
@@ -171,49 +187,12 @@ export class MapContextMenu extends ContextMenu {
this.clip();
}
showUpperBar() {
this.getContainer()?.querySelector(".upper-bar")?.classList.toggle("hide", false);
}
hideUpperBar() {
this.getContainer()?.querySelector(".upper-bar")?.classList.toggle("hide", true);
}
showLowerBar() {
this.getContainer()?.querySelector("#more-options-button-bar")?.classList.toggle("hide", false);
}
hideLowerBar() {
this.getContainer()?.querySelector("#more-optionsbutton-bar")?.classList.toggle("hide", true);
}
showAltitudeSlider() {
this.getContainer()?.querySelector("#aircraft-spawn-altitude-slider")?.classList.toggle("hide", false);
}
hideAltitudeSlider() {
this.getContainer()?.querySelector("#aircraft-spawn-altitude-slider")?.classList.toggle("hide", true);
}
setAirbase(airbase: Airbase) {
this.#aircraftSpawnMenu.setAirbase(airbase);
this.#helicopterSpawnMenu.setAirbase(airbase);
}
setLatLng(latlng: LatLng) {
this.#aircraftSpawnMenu.setLatLng(latlng);
this.#helicopterSpawnMenu.setLatLng(latlng);
this.#groundUnitSpawnMenu.setLatLng(latlng);
this.#navyUnitSpawnMenu.setLatLng(latlng);
}
setCoalitionArea(coalitionArea: CoalitionArea) {
this.#coalitionArea = coalitionArea;
this.getContainer()?.querySelector("#coalition-area-button")?.classList.toggle("hide", false);
}
/** Callback called when the user left clicks on the coalition switch
*
* @param value Switch position (false: "blue", true: "red")
*/
#onSwitchClick(value: boolean) {
value? setActiveCoalition("red"): setActiveCoalition("blue");
value ? setActiveCoalition("red") : setActiveCoalition("blue");
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
this.#aircraftSpawnMenu.setCountries();
this.#helicopterSpawnMenu.setCountries();
@@ -221,7 +200,10 @@ export class MapContextMenu extends ContextMenu {
this.#navyUnitSpawnMenu.setCountries();
}
#onSwitchRightClick(e: any) {
/** Callback called when the user rightclicks on the coalition switch. This will select the "neutral" coalition.
*
*/
#onSwitchRightClick() {
this.#coalitionSwitch.setValue(undefined);
setActiveCoalition("neutral");
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });

View File

@@ -0,0 +1,32 @@
import { deg2rad, ftToM } from "../other/utils";
import { ContextMenu } from "./contextmenu";
/** The UnitContextMenu is shown when the user rightclicks on a unit. It dynamically presents the user with possible actions to perform on the unit. */
export class UnitContextMenu extends ContextMenu {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
}
/** Set the options that will be presented to the user in the contextmenu
*
* @param options Dictionary element containing the text and tooltip of the options shown in the menu
* @param callback Callback that will be called when the user clicks on one of the options
*/
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");
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);
}));
}
}

View File

@@ -2,8 +2,11 @@ export class Control {
#container: HTMLElement | null;
expectedValue: any = null;
constructor(ID: string) {
this.#container = document.getElementById(ID);
constructor(container: string | null, options?: any) {
if (typeof container === "string")
this.#container = document.getElementById(container);
else
this.#container = this.createElement(options);
}
show() {
@@ -31,4 +34,8 @@ export class Control {
checkExpectedValue(value: any) {
return this.expectedValue === null || value === this.expectedValue;
}
createElement(options?: any): HTMLElement | null {
return null;
}
}

View File

@@ -1,5 +1,5 @@
export class Dropdown {
#element: HTMLElement;
#container: HTMLElement;
#options: HTMLElement;
#value: HTMLElement;
#callback: CallableFunction;
@@ -7,14 +7,14 @@ export class Dropdown {
#optionsList: string[] = [];
#index: number = 0;
constructor(element: string | HTMLElement, callback: CallableFunction, options: string[] | null = null) {
if (typeof element === 'string')
this.#element = document.getElementById(element) as HTMLElement;
constructor(ID: string | null, callback: CallableFunction, options: string[] | null = null, defaultText?: string) {
if (ID === null)
this.#container = this.#createElement(defaultText);
else
this.#element = element;
this.#container = document.getElementById(ID) as HTMLElement;
this.#options = this.#element.querySelector(".ol-select-options") as HTMLElement;
this.#value = this.#element.querySelector(".ol-select-value") as HTMLElement;
this.#options = this.#container.querySelector(".ol-select-options") as HTMLElement;
this.#value = this.#container.querySelector(".ol-select-value") as HTMLElement;
this.#defaultValue = this.#value.innerText;
this.#callback = callback;
@@ -25,7 +25,7 @@ export class Dropdown {
this.#value.addEventListener("click", (ev) => { this.#toggle(); });
document.addEventListener("click", (ev) => {
if (!(this.#value.contains(ev.target as Node) || this.#options.contains(ev.target as Node) || this.#element.contains(ev.target as Node))) {
if (!(this.#value.contains(ev.target as Node) || this.#options.contains(ev.target as Node) || this.#container.contains(ev.target as Node))) {
this.close();
}
});
@@ -33,6 +33,10 @@ export class Dropdown {
this.#options.classList.add("ol-scrollable");
}
getContainer() {
return this.#container;
}
setOptions(optionsList: string[], sortAlphabetically: boolean = true) {
this.#optionsList = optionsList.sort();
if (this.#optionsList.length == 0) {
@@ -120,21 +124,44 @@ export class Dropdown {
clip() {
const options = this.#options;
const bounds = options.getBoundingClientRect();
this.#element.dataset.position = (bounds.bottom > window.innerHeight) ? "top" : "";
this.#container.dataset.position = (bounds.bottom > window.innerHeight) ? "top" : "";
}
close() {
this.#element.classList.remove("is-open");
this.#element.dataset.position = "";
this.#container.classList.remove("is-open");
this.#container.dataset.position = "";
}
open() {
this.#element.classList.add("is-open");
this.#container.classList.add("is-open");
this.#options.classList.toggle("scrollbar-visible", this.#options.scrollHeight > this.#options.clientHeight);
this.clip();
}
show() {
this.#container.classList.add("show");
}
hide() {
this.#container.classList.add("hide");
}
#toggle() {
this.#element.classList.contains("is-open")? this.close(): this.open();
this.#container.classList.contains("is-open")? this.close(): this.open();
}
#createElement(defaultText: string | undefined) {
var div = document.createElement("div");
div.classList.add("ol-select");
var value = document.createElement("div");
value.classList.add("ol-select-value");
value.innerText = defaultText? defaultText: "";
var options = document.createElement("div");
options.classList.add("ol-select-options");
div.append(value, options);
return div;
}
}

View File

@@ -13,8 +13,9 @@ export class Slider extends Control {
#dragged: boolean = false;
#value: number = 0;
constructor(ID: string, minValue: number, maxValue: number, unitOfMeasure: string, callback: CallableFunction) {
super(ID);
constructor(ID: string | null, minValue: number, maxValue: number, unitOfMeasure: string, callback: CallableFunction, options?: any) {
super(ID, options);
this.#callback = callback;
this.#unitOfMeasure = unitOfMeasure;
this.#slider = this.getContainer()?.querySelector("input") as HTMLInputElement;
@@ -120,4 +121,31 @@ export class Slider extends Control {
}
}
}
createElement(options?: any): HTMLElement | null {
var containerEl = document.createElement("div");
containerEl.classList.add("ol-slider-container", "flight-control-ol-slider");
var dl = document.createElement("dl");
dl.classList.add("ol-data-grid");
var dt = document.createElement("dt");
dt.innerText = (options !== undefined && options.title !== undefined)? options.title: "";
var dd = document.createElement("dd");
var sliderEl = document.createElement("div");
sliderEl.classList.add("ol-slider-value");
dd.append(sliderEl);
dl.append(dt, dd);
var input = document.createElement("input") as HTMLInputElement;
input.type = "range";
input.min = "0";
input.max = "100";
input.value = "0"
input.classList.add("ol-slider");
containerEl.append(dl, input);
return containerEl;
}
}

View File

@@ -4,6 +4,7 @@ export class Switch extends Control {
#value: boolean | undefined = false;
#callback: CallableFunction | null = null;
// TODO: allow for null ID so that the element is created automatically
constructor(ID: string, callback: CallableFunction, initialValue?: boolean) {
super(ID);
this.getContainer()?.addEventListener('click', (e) => this.#onToggle());

View File

@@ -1,56 +0,0 @@
import { deg2rad, ftToM } from "../other/utils";
import { ContextMenu } from "./contextmenu";
export class UnitContextMenu extends ContextMenu {
#customFormationCallback: CallableFunction | null = null;
constructor(id: string) {
super(id);
document.addEventListener("applyCustomFormation", () => {
var dialog = document.getElementById("custom-formation-dialog");
if (dialog) {
dialog.classList.add("hide");
var clock = 1;
while (clock < 8) {
if ((<HTMLInputElement>dialog.querySelector(`#formation-${clock}`)).checked)
break
clock++;
}
var angleDeg = 360 - (clock - 1) * 45;
var angleRad = deg2rad(angleDeg);
var distance = ftToM(parseInt((<HTMLInputElement>dialog.querySelector(`#distance`)?.querySelector("input")).value));
var upDown = ftToM(parseInt((<HTMLInputElement>dialog.querySelector(`#up-down`)?.querySelector("input")).value));
// X: front-rear, positive front
// Y: top-bottom, positive top
// Z: left-right, positive right
var x = distance * Math.cos(angleRad);
var y = upDown;
var z = distance * Math.sin(angleRad);
if (this.#customFormationCallback)
this.#customFormationCallback({ "x": x, "y": y, "z": z })
}
})
}
setCustomFormationCallback(callback: CallableFunction) {
this.#customFormationCallback = callback;
}
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");
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);
}));
}
}

View File

@@ -6,88 +6,74 @@ import { getActiveCoalition, getMap, getMissionHandler, getUnitsManager } from "
import { GAME_MASTER } from "../constants/constants";
import { UnitSpawnOptions } from "../@types/unitdatabase";
import { Airbase } from "../mission/airbase";
import { ftToM } from "../other/utils";
import { aircraftDatabase } from "../unit/aircraftdatabase";
import { helicopterDatabase } from "../unit/helicopterdatabase";
import { groundUnitDatabase } from "../unit/groundunitdatabase";
import { navyUnitDatabase } from "../unit/navyunitdatabase";
export class UnitSpawnMenu {
#container: HTMLElement;
#unitDatabase: UnitDatabase;
#countryCodes: any;
#orderByRole: boolean;
#spawnOptions: UnitSpawnOptions = {
roleType: "",
name: "",
latlng: new LatLng(0, 0),
coalition: "blue",
count: 1,
country: "",
loadout: undefined,
airbase: undefined,
liveryID: undefined,
altitude: undefined
};
/* Controls */
#unitRoleTypeDropdown: Dropdown;
#unitLabelDropdown: Dropdown;
#unitCountDropdown: Dropdown;
#unitLoadoutDropdown: Dropdown | null = null;
#unitLoadoutDropdown: Dropdown;
#unitCountryDropdown: Dropdown;
#unitLiveryDropdown: Dropdown;
//#unitSpawnAltitudeSlider: Slider;
#unitSpawnAltitudeSlider: Slider;
#unitRoleTypeDropdownEl: HTMLDivElement;
#unitLabelDropdownEl: HTMLDivElement;
#unitLoadoutDropdownEl: HTMLDivElement | null = null;
#unitCountDropdownEl: HTMLDivElement;
#unitCountryDropdownEl: HTMLDivElement;
#unitLiveryDropdownEl: HTMLDivElement;
/* HTML Elements */
#deployUnitButtonEl: HTMLButtonElement;
#unitLoadoutPreviewEl: HTMLDivElement | null = null;
#unitImageEl: HTMLImageElement | null = null;
#unitLoadoutListEl: HTMLDivElement | null = null;
#unitLoadoutPreviewEl: HTMLDivElement;
#unitImageEl: HTMLImageElement;
#unitLoadoutListEl: HTMLDivElement;
#container: HTMLElement;
#options = {orderByRole: true, maxUnitCount: 4, showLoadout: true, showAltitudeSlider: true};
#spawnOptions: UnitSpawnOptions = { roleType: "", name: "", latlng: new LatLng(0, 0), coalition: "blue", count: 1, country: "", loadout: undefined, airbase: undefined, liveryID: undefined, altitude: undefined };
#unitDatabase: UnitDatabase;
#countryCodes: any;
constructor(id: string, unitDatabase: UnitDatabase, options?: {orderByRole: boolean, maxUnitCount: number, showLoadout: boolean, showAltitudeSlider: boolean}) {
this.#container = document.getElementById(id) as HTMLElement;
constructor(ID: string, unitDatabase: UnitDatabase, orderByRole: boolean) {
this.#container = document.getElementById(ID) as HTMLElement;
this.#unitDatabase = unitDatabase;
this.#orderByRole = orderByRole;
if (options !== undefined)
this.#options = options;
/* Create the HTML elements for the dropdowns */
if (this.#options.orderByRole)
this.#unitRoleTypeDropdownEl = this.#addDropdown("Unit role");
else
this.#unitRoleTypeDropdownEl = this.#addDropdown("Unit type");
this.#unitLabelDropdownEl = this.#addDropdown("Unit label");
if (this.#options.showLoadout)
this.#unitLoadoutDropdownEl = this.#addDropdown("Unit loadout");
this.#unitCountDropdownEl = this.#addDropdown("Unit count");
this.#unitCountryDropdownEl = this.#addDropdown("Unit country");
this.#unitLiveryDropdownEl = this.#addDropdown("Unit livery");
/* Create the dropdowns and the altitude slider */
this.#unitRoleTypeDropdown = new Dropdown(null, (roleType: string) => this.#setUnitRoleType(roleType), undefined, "Unit type");
this.#unitLabelDropdown = new Dropdown(null, (label: string) => this.#setUnitLabel(label), undefined, "Unit label");
this.#unitLoadoutDropdown = new Dropdown(null, (loadout: string) => this.#setUnitLoadout(loadout), undefined, "Unit loadout");
this.#unitCountDropdown = new Dropdown(null, (count: string) => this.#setUnitCount(count), undefined, "Unit count");
this.#unitCountryDropdown = new Dropdown(null, () => { /* Custom button implementation */ }, undefined, "Unit country");
this.#unitLiveryDropdown = new Dropdown(null, (livery: string) => this.#setUnitLivery(livery), undefined, "Unit livery");
this.#unitSpawnAltitudeSlider = new Slider(null, 0, 1000, "ft", (value: number) => { this.#spawnOptions.altitude = ftToM(value); }, { title: "Spawn altitude" });
/* The unit label and unit count are in the same "row" for clarity and compactness */
var unitLabelCountContainerEl = document.createElement("div");
unitLabelCountContainerEl.classList.add("unit-label-count-container");
var divider = document.createElement("div");
divider.innerText = "x";
unitLabelCountContainerEl.append(this.#unitLabelDropdownEl, divider, this.#unitCountDropdownEl);
/* Create the dropdowns and the altitude slider */
this.#unitRoleTypeDropdown = new Dropdown(this.#unitRoleTypeDropdownEl, (roleType: string) => this.#setUnitRoleType(roleType));
this.#unitLabelDropdown = new Dropdown(this.#unitLabelDropdownEl, (label: string) => this.#setUnitLabel(label));
if (this.#unitLoadoutDropdownEl !== null)
this.#unitLoadoutDropdown = new Dropdown(this.#unitLoadoutDropdownEl, (loadout: string) => this.#setUnitLoadout(loadout));
this.#unitCountDropdown = new Dropdown(this.#unitCountDropdownEl, (count: string) => this.#setUnitCount(count));
this.#unitCountryDropdown = new Dropdown(this.#unitCountryDropdownEl, () => { /* Custom button implementation */ });
this.#unitLiveryDropdown = new Dropdown(this.#unitLiveryDropdownEl, (livery: string) => this.#setUnitLivery(livery));
//this.#unitSpawnAltitudeSlider = new Slider("unit-spawn-altitude-slider", 0, 50000, "ft", (value: number) => {this.#spawnOptions.altitude = ftToM(value);});
var countOptions: string[] = [];
for (let i = 1; i <= this.#options.maxUnitCount; i++)
countOptions.push(i.toString());
this.#unitCountDropdown.setOptions(countOptions);
this.#unitCountDropdown.selectValue(0);
//this.#unitSpawnAltitudeSlider.setIncrement(500);
//this.#unitSpawnAltitudeSlider.setValue(20000);
//this.#unitSpawnAltitudeSlider.setActive(true);
unitLabelCountContainerEl.append(this.#unitLabelDropdown.getContainer(), divider, this.#unitCountDropdown.getContainer());
/* Create the unit image and loadout elements */
if (this.#options.showLoadout) {
this.#unitLoadoutPreviewEl = document.createElement("div");
this.#unitLoadoutPreviewEl.classList.add("unit-loadout-preview");
this.#unitImageEl = document.createElement("img");
this.#unitImageEl.classList.add("unit-image", "hide");
this.#unitLoadoutListEl = document.createElement("div");
this.#unitLoadoutListEl.classList.add("unit-loadout-list");
this.#unitLoadoutPreviewEl.append(this.#unitImageEl, this.#unitLoadoutListEl);
}
this.#unitLoadoutPreviewEl = document.createElement("div");
this.#unitLoadoutPreviewEl.classList.add("unit-loadout-preview");
this.#unitImageEl = document.createElement("img");
this.#unitImageEl.classList.add("unit-image", "hide");
this.#unitLoadoutListEl = document.createElement("div");
this.#unitLoadoutListEl.classList.add("unit-loadout-list");
this.#unitLoadoutPreviewEl.append(this.#unitImageEl, this.#unitLoadoutListEl);
/* Create the divider and the advanced options collapsible div */
var advancedOptionsDiv = document.createElement("div");
@@ -98,10 +84,9 @@ export class UnitSpawnMenu {
advancedOptionsText.innerText = "Advanced options";
var advancedOptionsHr = document.createElement("hr");
advancedOptionsToggle.append(advancedOptionsText, advancedOptionsHr);
advancedOptionsToggle.addEventListener("click", () => {advancedOptionsDiv.classList.toggle("hide")});
advancedOptionsDiv.append(this.#unitCountryDropdownEl, this.#unitLiveryDropdownEl);
if (this.#unitLoadoutPreviewEl !== null)
advancedOptionsDiv.append(this.#unitLoadoutPreviewEl)
advancedOptionsToggle.addEventListener("click", () => { advancedOptionsDiv.classList.toggle("hide") });
advancedOptionsDiv.append(this.#unitCountryDropdown.getContainer(), this.#unitLiveryDropdown.getContainer(),
this.#unitLoadoutPreviewEl, this.#unitSpawnAltitudeSlider.getContainer() as HTMLElement);
/* Create the unit deploy button */
this.#deployUnitButtonEl = document.createElement("button");
@@ -112,40 +97,38 @@ export class UnitSpawnMenu {
this.#deployUnitButtonEl.addEventListener("click", () => { this.#deployUnits(); });
/* Assemble all components */
this.#container.append(this.#unitRoleTypeDropdownEl, unitLabelCountContainerEl)
if (this.#unitLoadoutDropdownEl !== null)
this.#container.append(this.#unitLoadoutDropdownEl)
this.#container.append(advancedOptionsToggle, advancedOptionsDiv, this.#deployUnitButtonEl);
this.#container.append(this.#unitRoleTypeDropdown.getContainer(), unitLabelCountContainerEl, this.#unitLoadoutDropdown.getContainer(),
advancedOptionsToggle, advancedOptionsDiv, this.#deployUnitButtonEl);
/* Load the country codes from the public folder */
var xhr = new XMLHttpRequest();
xhr.open('GET', 'images/countries/codes.json', true);
xhr.responseType = 'json';
xhr.onload = () => {
var status = xhr.status;
if (status === 200) {
var status = xhr.status;
if (status === 200)
this.#countryCodes = xhr.response;
} else {
console.error(`Error retrieving country codes`)
}
else
console.error(`Error retrieving country codes`)
};
xhr.send();
/* Event listeners */
this.#container.addEventListener("unitRoleTypeChanged", () => {
this.#unitLabelDropdown.reset();
if (this.#unitLoadoutListEl !== null)
this.#unitLoadoutListEl.replaceChildren();
this.#unitLoadoutListEl.replaceChildren();
if (this.#unitLoadoutDropdown !== null)
this.#unitLoadoutDropdown.reset();
if (this.#unitImageEl !== null)
this.#unitImageEl.classList.toggle("hide", true);
this.#unitLiveryDropdown.reset();
if (this.#options.orderByRole)
if (this.#orderByRole)
this.#unitLabelDropdown.setOptions(this.#unitDatabase.getByRole(this.#spawnOptions.roleType).map((blueprint) => { return blueprint.label }));
else
this.#unitLabelDropdown.setOptions(this.#unitDatabase.getByType(this.#spawnOptions.roleType).map((blueprint) => { return blueprint.label }));
this.#container.dispatchEvent(new Event("resize"));
this.#container.dispatchEvent(new Event("resize"));
this.#computeSpawnPoints();
})
@@ -160,9 +143,9 @@ export class UnitSpawnMenu {
}
this.#setUnitLiveryOptions();
this.#container.dispatchEvent(new Event("resize"));
this.#container.dispatchEvent(new Event("resize"));
this.#computeSpawnPoints();
})
})
this.#container.addEventListener("unitLoadoutChanged", () => {
this.#deployUnitButtonEl.disabled = false;
@@ -177,17 +160,17 @@ export class UnitSpawnMenu {
})
);
}
this.#container.dispatchEvent(new Event("resize"));
this.#container.dispatchEvent(new Event("resize"));
})
this.#container.addEventListener("unitCountChanged", () => {
this.#computeSpawnPoints();
})
})
this.#container.addEventListener("unitCountryChanged", () => {
this.#setUnitLiveryOptions();
})
})
this.#container.addEventListener("unitLiveryChanged", () => {
@@ -203,7 +186,7 @@ export class UnitSpawnMenu {
this.#unitRoleTypeDropdown.reset();
this.#unitLabelDropdown.reset();
this.#unitLiveryDropdown.reset();
if (this.#options.orderByRole)
if (this.#orderByRole)
this.#unitRoleTypeDropdown.setOptions(this.#unitDatabase.getRoles());
else
this.#unitRoleTypeDropdown.setOptions(this.#unitDatabase.getTypes());
@@ -216,13 +199,13 @@ export class UnitSpawnMenu {
this.#unitImageEl.classList.toggle("hide", true);
this.setCountries();
this.#container.dispatchEvent(new Event("resize"));
this.#container.dispatchEvent(new Event("resize"));
}
setCountries() {
var coalitions = getMissionHandler().getCoalitions();
var countries = Object.values(coalitions[getActiveCoalition() as keyof typeof coalitions]);
this.#unitCountryDropdown.setOptionsElements(this.#createCountryButtons(this.#unitCountryDropdown, countries, (country: string) => {this.#setUnitCountry(country)}));
this.#unitCountryDropdown.setOptionsElements(this.#createCountryButtons(this.#unitCountryDropdown, countries, (country: string) => { this.#setUnitCountry(country) }));
if (countries.length > 0 && !countries.includes(this.#spawnOptions.country)) {
this.#unitCountryDropdown.forceValue(countries[0]);
@@ -245,6 +228,47 @@ export class UnitSpawnMenu {
this.#spawnOptions.latlng = latlng;
}
setMaxUnitCount(maxUnitCount: number) {
/* Create the unit count options */
var countOptions: string[] = [];
for (let i = 1; i <= maxUnitCount; i++)
countOptions.push(i.toString());
this.#unitCountDropdown.setOptions(countOptions);
this.#unitCountDropdown.selectValue(0);
}
getRoleTypeDrodown() {
return this.#unitRoleTypeDropdown;
}
getLabelDropdown() {
return this.#unitLabelDropdown;
}
getCountDropdown() {
return this.#unitCountDropdown;
}
getLoadoutDropdown() {
return this.#unitLoadoutDropdown;
}
getCountryDropdown() {
return this.#unitCountDropdown;
}
getLiveryDropdown() {
return this.#unitLiveryDropdown;
}
getLoadoutPreview() {
return this.#unitLoadoutPreviewEl;
}
getAltitudeSlider() {
return this.#unitSpawnAltitudeSlider;
}
#setUnitRoleType(roleType: string) {
this.#spawnOptions.roleType = roleType;
this.#container.dispatchEvent(new Event("unitRoleTypeChanged"));
@@ -252,16 +276,16 @@ export class UnitSpawnMenu {
#setUnitLabel(label: string) {
var name = this.#unitDatabase.getByLabel(label)?.name || null;
if (name != null)
if (name != null)
this.#spawnOptions.name = name;
this.#container.dispatchEvent(new Event("unitLabelChanged"));
}
#setUnitLoadout(loadoutName: string) {
var loadout = this.#unitDatabase.getLoadoutByName(this.#spawnOptions.name, loadoutName);
if (loadout)
this.#spawnOptions.loadout = loadout;
this.#container.dispatchEvent(new Event("unitLoadoutChanged"));
if (loadout)
this.#spawnOptions.loadout = loadout;
this.#container.dispatchEvent(new Event("unitLoadoutChanged"));
}
#setUnitCount(count: string) {
@@ -284,28 +308,13 @@ export class UnitSpawnMenu {
this.#container.dispatchEvent(new Event("unitLiveryChanged"));
}
#addDropdown(defaultText: string) {
var div = document.createElement("div");
div.classList.add("ol-select");
var value = document.createElement("div");
value.classList.add("ol-select-value");
value.innerText = defaultText;
var options = document.createElement("div");
options.classList.add("ol-select-options");
div.append(value, options);
return div;
}
#setUnitLiveryOptions() {
if (this.#spawnOptions.name !== "" && this.#spawnOptions.country !== "") {
var liveries = this.#unitDatabase.getLiveryNamesByName(this.#spawnOptions.name);
var countryLiveries: string[] = ["Default"];
liveries.forEach((livery: any) => {
var nationLiveryCodes = this.#countryCodes[this.#spawnOptions.country].liveryCodes;
if (livery.countries.some((country: string) => {return nationLiveryCodes.includes(country)}))
if (livery.countries.some((country: string) => { return nationLiveryCodes.includes(country) }))
countryLiveries.push(livery.name);
});
this.#unitLiveryDropdown.setOptions(countryLiveries);
@@ -317,17 +326,17 @@ export class UnitSpawnMenu {
this.#spawnOptions.coalition = getActiveCoalition();
if (this.#spawnOptions) {
var unitTable = {
unitType: this.#spawnOptions.name,
location: this.#spawnOptions.latlng,
altitude: this.#spawnOptions.altitude,
loadout: this.#spawnOptions.loadout,
unitType: this.#spawnOptions.name,
location: this.#spawnOptions.latlng,
altitude: this.#spawnOptions.altitude,
loadout: this.#spawnOptions.loadout,
liveryID: this.#spawnOptions.liveryID
};
var units = [];
for (let i = 1; i < parseInt(this.#unitCountDropdown.getValue()) + 1; i++) {
units.push(unitTable);
}
if (getUnitsManager().spawnUnits("Unit", units, getActiveCoalition(), false, this.#spawnOptions.airbase? this.#spawnOptions.airbase.getName(): "", this.#spawnOptions.country)) {
if (getUnitsManager().spawnUnits("Unit", units, getActiveCoalition(), false, this.#spawnOptions.airbase ? this.#spawnOptions.airbase.getName() : "", this.#spawnOptions.country)) {
getMap().addTemporaryMarker(this.#spawnOptions.latlng, this.#spawnOptions.name, getActiveCoalition());
getMap().getMapContextMenu().hide();
}
@@ -339,11 +348,11 @@ export class UnitSpawnMenu {
var el = document.createElement("div");
var formattedCountry = "";
if (this.#countryCodes[country] !== undefined && this.#countryCodes[country].displayName !== undefined)
if (this.#countryCodes[country] !== undefined && this.#countryCodes[country].displayName !== undefined)
formattedCountry = this.#countryCodes[country].displayName;
else
formattedCountry = country.charAt(0).toUpperCase() + country.slice(1).toLowerCase();
var button = document.createElement("button");
button.classList.add("country-dropdown-element");
el.appendChild(button);
@@ -371,11 +380,67 @@ export class UnitSpawnMenu {
}
#computeSpawnPoints() {
if (getMissionHandler() && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER){
if (getMissionHandler() && getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER) {
var unitCount = parseInt(this.#unitCountDropdown.getValue());
var unitSpawnPoints = unitCount * this.#unitDatabase.getSpawnPointsByLabel(this.#unitLabelDropdown.getValue());
this.#deployUnitButtonEl.dataset.points = `${unitSpawnPoints}`;
this.#deployUnitButtonEl.disabled = unitSpawnPoints >= getMissionHandler().getAvailableSpawnPoints();
}
}
}
export class AircraftSpawnMenu extends UnitSpawnMenu {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID, aircraftDatabase, true);
this.setMaxUnitCount(4);
this.getAltitudeSlider().setMinMax(0, 50000);
this.getAltitudeSlider().setIncrement(500);
this.getAltitudeSlider().setValue(20000);
}
}
export class HelicopterSpawnMenu extends UnitSpawnMenu {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID, helicopterDatabase, true);
this.setMaxUnitCount(4);
this.getAltitudeSlider().setMinMax(0, 10000);
this.getAltitudeSlider().setIncrement(100);
this.getAltitudeSlider().setValue(5000);
}
}
export class GroundUnitSpawnMenu extends UnitSpawnMenu {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID, groundUnitDatabase, false);
this.setMaxUnitCount(20);
this.getAltitudeSlider().hide();
this.getLoadoutDropdown().hide();
this.getLoadoutPreview().classList.add("hide");
}
}
export class NavyUnitSpawnMenu extends UnitSpawnMenu {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID, navyUnitDatabase, false);
this.setMaxUnitCount(4);
this.getAltitudeSlider().hide();
this.getLoadoutDropdown().hide();
this.getLoadoutPreview().classList.add("hide");
}
}

View File

@@ -231,7 +231,6 @@ export function getWeaponsManager() {
return weaponsManager;
}
export function getMissionHandler() {
return missionHandler;
}

View File

@@ -1,9 +1,9 @@
import * as L from "leaflet"
import { getMissionHandler, getUnitsManager } from "..";
import { BoxSelect } from "./boxselect";
import { MapContextMenu } from "../controls/mapcontextmenu";
import { UnitContextMenu } from "../controls/unitcontextmenu";
import { AirbaseContextMenu } from "../controls/airbasecontextmenu";
import { MapContextMenu } from "../contextmenus/mapcontextmenu";
import { UnitContextMenu } from "../contextmenus/unitcontextmenu";
import { AirbaseContextMenu } from "../contextmenus/airbasecontextmenu";
import { Dropdown } from "../controls/dropdown";
import { Airbase } from "../mission/airbase";
import { Unit } from "../unit/unit";
@@ -15,8 +15,9 @@ import { SVGInjector } from '@tanem/svg-injector'
import { layers as mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, visibilityControls, visibilityControlsTootlips, FIRE_AT_AREA, MOVE_UNIT, CARPET_BOMBING, BOMBING, SHOW_CONTACT_LINES, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, visibilityControlsTypes } from "../constants/constants";
import { TargetMarker } from "./targetmarker";
import { CoalitionArea } from "./coalitionarea";
import { CoalitionAreaContextMenu } from "../controls/coalitionareacontextmenu";
import { CoalitionAreaContextMenu } from "../contextmenus/coalitionareacontextmenu";
import { DrawingCursor } from "./drawingcursor";
import { AirbaseSpawnContextMenu } from "../contextmenus/airbasespawnmenu";
L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect);
@@ -58,6 +59,7 @@ export class Map extends L.Map {
#mapContextMenu: MapContextMenu = new MapContextMenu("map-contextmenu");
#unitContextMenu: UnitContextMenu = new UnitContextMenu("unit-contextmenu");
#airbaseContextMenu: AirbaseContextMenu = new AirbaseContextMenu("airbase-contextmenu");
#airbaseSpawnMenu: AirbaseSpawnContextMenu = new AirbaseSpawnContextMenu("airbase-spawn-contextmenu");
#coalitionAreaContextMenu: CoalitionAreaContextMenu = new CoalitionAreaContextMenu("coalition-area-contextmenu");
#mapSourceDropdown: Dropdown;
@@ -65,7 +67,11 @@ export class Map extends L.Map {
#optionButtons: { [key: string]: HTMLButtonElement[] } = {}
#visibiityOptions: { [key: string]: boolean } = {}
constructor(ID: string) {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
/* Init the leaflet map */
//@ts-ignore Needed because the boxSelect option is non-standard
super(ID, { zoomSnap: 0, zoomDelta: 0.25, preferCanvas: true, doubleClickZoom: false, zoomControl: false, boxZoom: false, boxSelect: true, zoomAnimation: true, maxBoundsViscosity: 1.0, minZoom: 7, keyboard: true, keyboardPanDelta: 0 });
@@ -237,6 +243,7 @@ export class Map extends L.Map {
this.hideMapContextMenu();
this.hideUnitContextMenu();
this.hideAirbaseContextMenu();
this.hideAirbaseSpawnMenu();
this.hideCoalitionAreaContextMenu();
}
@@ -282,6 +289,20 @@ export class Map extends L.Map {
this.#airbaseContextMenu.hide();
}
showAirbaseSpawnMenu(x: number, y: number, latlng: L.LatLng, airbase: Airbase) {
this.hideAllContextMenus();
this.#airbaseSpawnMenu.show(x, y);
this.#airbaseSpawnMenu.setAirbase(airbase);
}
getAirbaseSpawnMenu() {
return this.#airbaseSpawnMenu;
}
hideAirbaseSpawnMenu() {
this.#airbaseSpawnMenu.hide();
}
showCoalitionAreaContextMenu(x: number, y: number, latlng: L.LatLng, coalitionArea: CoalitionArea) {
this.hideAllContextMenus();
this.#coalitionAreaContextMenu.show(x, y, latlng);

View File

@@ -1,7 +1,11 @@
import { Panel } from "./panel";
export class ConnectionStatusPanel extends Panel {
constructor(ID: string) {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
}

View File

@@ -3,7 +3,11 @@ import { Unit } from "../unit/unit";
import { Panel } from "./panel";
export class HotgroupPanel extends Panel {
constructor(ID: string) {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
document.addEventListener("unitDeath", () => this.refreshHotgroups());
}

View File

@@ -7,7 +7,11 @@ export class LogPanel extends Panel {
#scrolledDown: boolean = true;
#logs: {[key: string]: string} = {};
constructor(ID: string) {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
document.addEventListener("toggleLogPanel", () => {

View File

@@ -12,7 +12,11 @@ export class MouseInfoPanel extends Panel {
#measureLine: Polyline = new Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1, interactive: false });
#measureBox: HTMLElement;
constructor(ID: string) {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
this.#measureIcon = new Icon({ iconUrl: 'resources/theme/images/icons/pin.svg', iconAnchor: [16, 32] });
@@ -124,9 +128,9 @@ export class MouseInfoPanel extends Panel {
this.#drawMeasureLine();
}
#drawMeasure(imgId: string | null, textId: string, value: LatLng | null, mousePosition: LatLng) {
var el = this.getElement().querySelector(`#${textId}`) as HTMLElement;
var img = imgId != null ? this.getElement().querySelector(`#${imgId}`) as HTMLElement : null;
#drawMeasure(imgID: string | null, textID: string, value: LatLng | null, mousePosition: LatLng) {
var el = this.getElement().querySelector(`#${textID}`) as HTMLElement;
var img = imgID != null ? this.getElement().querySelector(`#${imgID}`) as HTMLElement : null;
if (value) {
if (el != null) {
el.classList.remove("hide");
@@ -156,9 +160,9 @@ export class MouseInfoPanel extends Panel {
}
}
#drawCoordinates(imgId: string, textId: string, value: string) {
const el = this.getElement().querySelector(`#${textId}`) as HTMLElement;
const img = this.getElement().querySelector(`#${imgId}`) as HTMLElement;
#drawCoordinates(imgID: string, textID: string, value: string) {
const el = this.getElement().querySelector(`#${textID}`) as HTMLElement;
const img = this.getElement().querySelector(`#${imgID}`) as HTMLElement;
if (img && el) {
el.dataset.value = value.substring(1);
img.dataset.label = value[0];

View File

@@ -2,7 +2,11 @@ export class Panel {
#element: HTMLElement
#visible: boolean = true;
constructor(ID: string) {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
this.#element = <HTMLElement>document.getElementById(ID);
}

View File

@@ -1,7 +1,11 @@
import { Panel } from "./panel";
export class ServerStatusPanel extends Panel {
constructor(ID: string) {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
}

View File

@@ -25,7 +25,11 @@ export class UnitControlPanel extends Panel {
#units: Unit[] = [];
#selectedUnitsTypes: string[] = [];
constructor(ID: string) {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
/* Unit control sliders */

View File

@@ -22,7 +22,11 @@ export class UnitInfoPanel extends Panel {
#unitLabel: HTMLElement;
#unitName: HTMLElement;
constructor(ID: string) {
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
*/
constructor(ID: string){
super(ID);
this.#altitude = (this.getElement().querySelector("#altitude")) as HTMLElement;

View File

@@ -1,6 +1,6 @@
import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map, Point } from 'leaflet';
import { getMap, getMissionHandler, getUnitsManager, getWeaponsManager } from '..';
import { enumToCoalition, enumToEmissioNCountermeasure, getMarkerCategoryByName, enumToROE, enumToReactionToThreat, enumToState, getUnitDatabaseByCategory, mToFt, msToKnots, rad2deg, bearing, deg2rad } from '../other/utils';
import { enumToCoalition, enumToEmissioNCountermeasure, getMarkerCategoryByName, enumToROE, enumToReactionToThreat, enumToState, getUnitDatabaseByCategory, mToFt, msToKnots, rad2deg, bearing, deg2rad, ftToM } from '../other/utils';
import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions, followUnit, setEmissionsCountermeasures, setSpeedType, setAltitudeType, setOnOff, setFollowRoads, bombPoint, carpetBomb, bombBuilding, fireAtArea } from '../server/server';
import { CustomMarker } from '../map/custommarker';
import { SVGInjector } from '@tanem/svg-injector';
@@ -818,9 +818,31 @@ export class Unit extends CustomMarker {
#applyFollowOptions(action: string) {
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);
})
document.addEventListener("applyCustomFormation", () => {
var dialog = document.getElementById("custom-formation-dialog");
if (dialog) {
dialog.classList.add("hide");
var clock = 1;
while (clock < 8) {
if ((<HTMLInputElement>dialog.querySelector(`#formation-${clock}`)).checked)
break
clock++;
}
var angleDeg = 360 - (clock - 1) * 45;
var angleRad = deg2rad(angleDeg);
var distance = ftToM(parseInt((<HTMLInputElement>dialog.querySelector(`#distance`)?.querySelector("input")).value));
var upDown = ftToM(parseInt((<HTMLInputElement>dialog.querySelector(`#up-down`)?.querySelector("input")).value));
// X: front-rear, positive front
// Y: top-bottom, positive top
// Z: left-right, positive right
var x = distance * Math.cos(angleRad);
var y = upDown;
var z = distance * Math.sin(angleRad);
getUnitsManager().selectedUnitsFollowUnit(this.ID, { "x": x, "y": y, "z": z });
}
});
}
else {
getUnitsManager().selectedUnitsFollowUnit(this.ID, undefined, action);