Merge pull request #318 from Pax1601/312-add-ability-to-draw-areas-and-fill-with-iads

312 add ability to draw areas and fill with iads
This commit is contained in:
Pax1601
2023-07-06 13:00:14 +02:00
committed by GitHub
90 changed files with 7125 additions and 2673 deletions

View File

@@ -1,5 +1,5 @@
interface UnitsData {
units: {[key: string]: UnitData},
units: string,
sessionHash: string
}

View File

@@ -1,60 +1,23 @@
interface UpdateData {
[key: string]: any
import { LatLng } from "leaflet"
interface UnitIconOptions {
showState: boolean,
showVvi: boolean,
showHotgroup: boolean,
showUnitIcon: boolean,
showShortLabel: boolean,
showFuel: boolean,
showAmmo: boolean,
showSummary: boolean,
rotateToHeading: boolean
}
interface BaseData {
controlled: boolean;
name: string;
unitName: string;
groupName: string;
alive: boolean;
category: string;
}
interface FlightData {
latitude: number;
longitude: number;
altitude: number;
heading: number;
speed: number;
}
interface MissionData {
fuel: number;
flags: any;
ammo: any;
contacts: any;
hasTask: boolean;
coalition: string;
}
interface FormationData {
leaderID: number;
}
interface TaskData {
currentState: string;
currentTask: string;
activePath: any;
desiredSpeed: number;
desiredSpeedType: string;
desiredAltitude: number;
desiredAltitudeType: string;
targetLocation: any;
isTanker: boolean;
isAWACS: boolean;
onOff: boolean;
followRoads: boolean;
targetID: number;
}
interface OptionsData {
ROE: string;
reactionToThreat: string;
emissionsCountermeasures: string;
TACAN: TACAN;
radio: Radio;
generalSettings: GeneralSettings;
interface GeneralSettings {
prohibitJettison: boolean;
prohibitAA: boolean;
prohibitAG: boolean;
prohibitAfterburner: boolean;
prohibitAirWpn: boolean;
}
interface TACAN {
@@ -70,31 +33,21 @@ interface Radio {
callsignNumber: number;
}
interface GeneralSettings {
prohibitJettison: boolean;
prohibitAA: boolean;
prohibitAG: boolean;
prohibitAfterburner: boolean;
prohibitAirWpn: boolean;
interface Ammo {
quantity: number,
name: string,
guidance: number,
category: number,
missileCategory: number
}
interface UnitIconOptions {
showState: boolean,
showVvi: boolean,
showHotgroup: boolean,
showUnitIcon: boolean,
showShortLabel: boolean,
showFuel: boolean,
showAmmo: boolean,
showSummary: boolean,
rotateToHeading: boolean
interface Contact {
ID: number,
detectionMethod: number
}
interface UnitData {
baseData: BaseData;
flightData: FlightData;
missionData: MissionData;
formationData: FormationData;
taskData: TaskData;
optionsData: OptionsData;
interface Offset {
x: number,
y: number,
z: number
}

View File

@@ -115,11 +115,11 @@ export abstract class ATCBoard {
addFlight( unit:Unit ) {
const baseData = unit.getBaseData();
const baseData = unit.getData();
const unitCanBeAdded = () => {
if ( baseData.category !== "Aircraft" ) {
if ( unit.getCategory() !== "Aircraft" ) {
return false;
}
@@ -345,7 +345,7 @@ export abstract class ATCBoard {
const results = Object.keys( units ).reduce( ( acc:Unit[], unitId:any ) => {
const unit = units[ unitId ];
const baseData = unit.getBaseData();
const baseData = unit.getData();
if ( !unitIdsBeingMonitored.includes( parseInt( unitId ) ) && baseData.unitName.toLowerCase().indexOf( searchString ) > -1 ) {
acc.push( unit );
@@ -359,7 +359,7 @@ export abstract class ATCBoard {
results.forEach( unit => {
const baseData = unit.getBaseData();
const baseData = unit.getData();
const a = document.createElement( "a" );
a.innerText = baseData.unitName;

View File

@@ -34,13 +34,13 @@ export class ATCBoardTower extends ATCBoard {
return;
}
const flightData:FlightData = {
const flightData = {
latitude: -1,
longitude: -1,
altitude: -1,
heading: -1,
speed: -1,
...( selectableUnits.hasOwnProperty( flight.unitId ) ? selectableUnits[flight.unitId].getFlightData() : {} )
...( selectableUnits.hasOwnProperty( flight.unitId ) ? selectableUnits[flight.unitId].getData() : {} )
};
if ( !strip ) {

View File

@@ -12,8 +12,8 @@ export class UnitDataTable extends Panel {
var units = getUnitsManager().getUnits();
const unitsArray = Object.values(units).sort((a: Unit, b: Unit) => {
const aVal = a.getBaseData().unitName?.toLowerCase();
const bVal = b.getBaseData().unitName?.toLowerCase();
const aVal = a.getUnitName()?.toLowerCase();
const bVal = b.getUnitName()?.toLowerCase();
if (aVal > bVal) {
return 1;
@@ -48,7 +48,7 @@ export class UnitDataTable extends Panel {
for (const unit of unitsArray) {
const dataset = [unit.getBaseData().unitName, unit.getBaseData().name, unit.getBaseData().category, (unit.getBaseData().controlled) ? "AI" : "Human"];
const dataset = [unit.getUnitName(), unit.getName(), unit.getCategory(), (unit.getControlled()) ? "AI" : "Human"];
addRow(el, dataset);
}

View File

@@ -1,12 +1,31 @@
import { LatLng, LatLngBounds, TileLayer, tileLayer } from "leaflet";
export const ROEs: string[] = ["Hold", "Return", "Designated", "Free"];
export const reactionsToThreat: string[] = ["None", "Manoeuvre", "Passive", "Evade"];
export const emissionsCountermeasures: string[] = ["Silent", "Attack", "Defend", "Free"];
export const states: string[] = ["none", "idle", "reach-destination", "attack", "follow", "land", "refuel", "AWACS", "tanker", "bomb-point", "carpet-bomb", "bomb-building", "fire-at-area"];
export const ROEs: string[] = ["free", "designated", "return", "hold"];
export const reactionsToThreat: string[] = ["none", "manoeuvre", "passive", "evade"];
export const emissionsCountermeasures: string[] = ["silent", "attack", "defend", "free"];
export const ROEDescriptions: string[] = ["Hold (Never fire)", "Return (Only fire if fired upon)", "Designated (Attack the designated target only)", "Free (Attack anyone)"];
export const reactionsToThreatDescriptions: string[] = ["None (No reaction)", "Manoeuvre (no countermeasures)", "Passive (Countermeasures only, no manoeuvre)", "Evade (Countermeasures and manoeuvers)"];
export 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)"];
export const ROEDescriptions: string[] = [
"Free (Attack anyone)",
"Designated (Attack the designated target only)",
"",
"Return (Only fire if fired upon)",
"Hold (Never fire)"
];
export const reactionsToThreatDescriptions: string[] = [
"None (No reaction)",
"Manoeuvre (no countermeasures)",
"Passive (Countermeasures only, no manoeuvre)",
"Evade (Countermeasures and manoeuvers)"
];
export 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)"
];
export const minSpeedValues: { [key: string]: number } = { Aircraft: 100, Helicopter: 0, NavyUnit: 0, GroundUnit: 0 };
export const maxSpeedValues: { [key: string]: number } = { Aircraft: 800, Helicopter: 300, NavyUnit: 60, GroundUnit: 60 };
@@ -99,4 +118,59 @@ export const layers = {
maxZoom: 20,
attribution: '<a href="https://github.com/cyclosm/cyclosm-cartocss-style/releases" title="CyclOSM - Open Bicycle render">CyclOSM</a> | Map data: &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}
}
}
/* Map constants */
export const IDLE = "Idle";
export const MOVE_UNIT = "Move unit";
export const BOMBING = "Bombing";
export const CARPET_BOMBING = "Carpet bombing";
export const FIRE_AT_AREA = "Fire at area";
export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area";
export const COALITIONAREA_INTERACT = "Interact with Coalition Areas"
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 const IADSRoles: {[key: string]: number}= {"AAA": 0.8, "MANPADS": 0.3, "SAM Sites": 0.1, "Radar": 0.05};
export enum DataIndexes {
startOfData = 0,
category,
alive,
human,
controlled,
coalition,
country,
name,
unitName,
groupName,
state,
task,
hasTask,
position,
speed,
heading,
isTanker,
isAWACS,
onOff,
followRoads,
fuel,
desiredSpeed,
desiredSpeedType,
desiredAltitude,
desiredAltitudeType,
leaderID,
formationOffset,
targetID,
targetPosition,
ROE,
reactionToThreat,
emissionsCountermeasures,
TACAN,
radio,
generalSettings,
ammo,
contacts,
activePath,
endOfData = 255
};

View File

@@ -0,0 +1,112 @@
import { getMap, getUnitsManager } from "..";
import { IADSRoles } from "../constants/constants";
import { CoalitionArea } from "../map/coalitionarea";
import { ContextMenu } from "./contextmenu";
import { Dropdown } from "./dropdown";
import { Slider } from "./slider";
import { Switch } from "./switch";
export class CoalitionAreaContextMenu extends ContextMenu {
#coalitionSwitch: Switch;
#coalitionArea: CoalitionArea | null = null;
#iadsDensitySlider: Slider;
#iadsRoleDropdown: Dropdown;
//#iadsPeriodDropdown: Dropdown;
constructor(id: string) {
super(id);
this.#coalitionSwitch = new Switch("coalition-area-switch", (value: boolean) => this.#onSwitchClick(value));
this.#coalitionSwitch.setValue(false);
this.#iadsRoleDropdown = new Dropdown("iads-units-role-options", () => { });
//this.#iadsPeriodDropdown = new Dropdown("iads-period-options", () => {});
this.#iadsDensitySlider = new Slider("iads-density-slider", 5, 100, "%", (value: number) => { });
this.#iadsDensitySlider.setIncrement(5);
this.#iadsDensitySlider.setValue(50);
this.#iadsDensitySlider.setActive(true);
document.addEventListener("coalitionAreaContextMenuShow", (e: any) => {
if (this.getVisibleSubMenu() !== e.detail.type)
this.showSubMenu(e.detail.type);
else
this.hideSubMenus();
});
document.addEventListener("coalitionAreaDelete", (e: any) => {
if (this.#coalitionArea)
getMap().deleteCoalitionArea(this.#coalitionArea);
getMap().hideCoalitionAreaContextMenu();
});
document.addEventListener("contextMenuCreateIads", (e: any) => {
const values: { [key: string]: boolean } = {};
const element = this.#iadsRoleDropdown.getOptionElements();
for (let idx = 0; idx < element.length; idx++) {
const option = element.item(idx) as HTMLElement;
const key = option.querySelector("span")?.innerText;
const value = option.querySelector("input")?.checked;
if (key !== undefined && value !== undefined)
values[key] = value;
}
const area = this.getCoalitionArea();
if (area)
getUnitsManager().createIADS(area, values, this.#iadsDensitySlider.getValue());
})
/* Create the checkboxes to select the unit roles */
this.#iadsRoleDropdown.setOptionsElements(Object.keys(IADSRoles).map((unitRole: string) => {
var div = document.createElement("div");
div.classList.add("ol-checkbox");
var label = document.createElement("label");
label.title = `Add ${unitRole}s to the IADS`;
var input = document.createElement("input");
input.type = "checkbox";
input.checked = true;
var span = document.createElement("span");
span.innerText = unitRole;
label.appendChild(input);
label.appendChild(span);
div.appendChild(label);
return div as HTMLElement;
}));
this.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;
}
setCoalitionArea(coalitionArea: CoalitionArea) {
this.#coalitionArea = coalitionArea;
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => {
element.setAttribute("data-coalition", this.getCoalitionArea()?.getCoalition())
});
this.#coalitionSwitch.setValue(this.getCoalitionArea()?.getCoalition() === "red");
}
#onSwitchClick(value: boolean) {
this.getCoalitionArea()?.setCoalition(value ? "red" : "blue");
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => {
element.setAttribute("data-coalition", this.getCoalitionArea()?.getCoalition())
});
}
}

View File

@@ -5,6 +5,7 @@ export class ContextMenu {
#latlng: LatLng = new LatLng(0, 0);
#x: number = 0;
#y: number = 0;
#visibleSubMenu: string | null = null;
constructor(id: string) {
this.#container = document.getElementById(id);
@@ -52,4 +53,12 @@ export class ContextMenu {
this.#container.style.top = window.innerHeight - this.#container.offsetHeight - 10 + "px";
}
}
setVisibleSubMenu(menu: string | null) {
this.#visibleSubMenu = menu;
}
getVisibleSubMenu() {
return this.#visibleSubMenu;
}
}

View File

@@ -50,6 +50,15 @@ export class Dropdown {
}));
}
setOptionsElements(optionsElements: HTMLElement[]) {
this.#optionsList = [];
this.#options.replaceChildren(...optionsElements);
}
getOptionElements() {
return this.#options.children;
}
selectText(text: string) {
const index = [].slice.call(this.#options.children).findIndex((opt: Element) => opt.querySelector("button")?.innerText === text);
if (index > -1) {

View File

@@ -11,12 +11,13 @@ import { ftToM } from "../other/utils";
export interface SpawnOptions {
role: string;
type: string;
name: string;
latlng: LatLng;
coalition: string;
loadout: string | null;
airbaseName: string | null;
altitude: number | null;
loadout?: string | null;
airbaseName?: string | null;
altitude?: number | null;
immediate?: boolean;
}
export class MapContextMenu extends ContextMenu {
@@ -27,12 +28,12 @@ export class MapContextMenu extends ContextMenu {
#aircrafSpawnAltitudeSlider: Slider;
#groundUnitRoleDropdown: Dropdown;
#groundUnitTypeDropdown: Dropdown;
#spawnOptions: SpawnOptions = { role: "", type: "", latlng: new LatLng(0, 0), loadout: null, coalition: "blue", airbaseName: null, altitude: ftToM(20000) };
#spawnOptions: SpawnOptions = { role: "", name: "", latlng: new LatLng(0, 0), loadout: null, coalition: "blue", airbaseName: null, altitude: ftToM(20000) };
constructor(id: string) {
super(id);
this.#coalitionSwitch = new Switch("coalition-switch", this.#onSwitchClick);
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.#aircraftRoleDropdown = new Dropdown("aircraft-role-options", (role: string) => this.#setAircraftRole(role));
@@ -45,15 +46,18 @@ export class MapContextMenu extends ContextMenu {
this.#groundUnitRoleDropdown = new Dropdown("ground-unit-role-options", (role: string) => this.#setGroundUnitRole(role));
this.#groundUnitTypeDropdown = new Dropdown("ground-unit-type-options", (type: string) => this.#setGroundUnitType(type));
document.addEventListener("contextMenuShow", (e: any) => {
this.showSubMenu(e.detail.type);
document.addEventListener("mapContextMenuShow", (e: any) => {
if (this.getVisibleSubMenu() !== e.detail.type)
this.showSubMenu(e.detail.type);
else
this.hideSubMenus();
});
document.addEventListener("contextMenuDeployAircraft", () => {
this.hide();
this.#spawnOptions.coalition = getActiveCoalition();
if (this.#spawnOptions) {
getMap().addTemporaryMarker(this.#spawnOptions.latlng);
getMap().addTemporaryMarker(this.#spawnOptions);
spawnAircraft(this.#spawnOptions);
}
});
@@ -62,7 +66,7 @@ export class MapContextMenu extends ContextMenu {
this.hide();
this.#spawnOptions.coalition = getActiveCoalition();
if (this.#spawnOptions) {
getMap().addTemporaryMarker(this.#spawnOptions.latlng);
getMap().addTemporaryMarker(this.#spawnOptions);
spawnGroundUnit(this.#spawnOptions);
}
});
@@ -92,7 +96,7 @@ export class MapContextMenu extends ContextMenu {
this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", type !== "aircraft");
this.getContainer()?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", type === "aircraft");
this.getContainer()?.querySelector("#ground-unit-spawn-menu")?.classList.toggle("hide", type !== "ground-unit");
this.getContainer()?.querySelector("#ground-unit-spawn-button")?.classList.toggle("is-open", type === "ground-unit");
this.getContainer()?.querySelector("#ground-ol-contexmenu-button")?.classList.toggle("is-open", type === "ground-unit");
this.getContainer()?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", type !== "smoke");
this.getContainer()?.querySelector("#smoke-spawn-button")?.classList.toggle("is-open", type === "smoke");
this.getContainer()?.querySelector("#explosion-menu")?.classList.toggle("hide", type !== "explosion");
@@ -103,8 +107,30 @@ export class MapContextMenu extends ContextMenu {
this.#resetGroundUnitRole();
this.#resetGroundUnitType();
this.clip();
this.setVisibleSubMenu(type);
}
hideSubMenus() {
this.getContainer()?.querySelector("#aircraft-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#aircraft-spawn-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#ground-unit-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#ground-ol-contexmenu-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#smoke-spawn-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#smoke-spawn-button")?.classList.toggle("is-open", false);
this.getContainer()?.querySelector("#explosion-menu")?.classList.toggle("hide", true);
this.getContainer()?.querySelector("#explosion-spawn-button")?.classList.toggle("is-open", false);
this.#resetAircraftRole();
this.#resetAircraftType();
this.#resetGroundUnitRole();
this.#resetGroundUnitType();
this.clip();
this.setVisibleSubMenu(null);
}
showUpperBar() {
this.getContainer()?.querySelector("#upper-bar")?.classList.toggle("hide", false);
}
@@ -123,6 +149,7 @@ export class MapContextMenu extends ContextMenu {
#onSwitchClick(value: boolean) {
value? setActiveCoalition("red"): setActiveCoalition("blue");
this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { element.setAttribute("data-coalition", getActiveCoalition()) });
}
#onSwitchRightClick(e: any) {
@@ -152,7 +179,7 @@ export class MapContextMenu extends ContextMenu {
this.#resetAircraftType();
var type = aircraftDatabase.getByLabel(label)?.name || null;
if (type != null) {
this.#spawnOptions.type = type;
this.#spawnOptions.name = type;
this.#aircraftLoadoutDropdown.setOptions(aircraftDatabase.getLoadoutNamesByRole(type, this.#spawnOptions.role));
this.#aircraftLoadoutDropdown.selectValue(0);
var image = (<HTMLImageElement>this.getContainer()?.querySelector("#unit-image"));
@@ -171,7 +198,7 @@ export class MapContextMenu extends ContextMenu {
}
#setAircraftLoadout(loadoutName: string) {
var loadout = aircraftDatabase.getLoadoutByName(this.#spawnOptions.type, loadoutName);
var loadout = aircraftDatabase.getLoadoutByName(this.#spawnOptions.name, loadoutName);
if (loadout) {
this.#spawnOptions.loadout = loadout.code;
(<HTMLButtonElement>this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false;
@@ -214,7 +241,7 @@ export class MapContextMenu extends ContextMenu {
this.#resetGroundUnitType();
var type = groundUnitsDatabase.getByLabel(label)?.name || null;
if (type != null) {
this.#spawnOptions.type = type;
this.#spawnOptions.name = type;
(<HTMLButtonElement>this.getContainer()?.querySelector("#ground-unit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false;
}
this.clip();

View File

@@ -223,6 +223,10 @@ export function getUnitsManager() {
return unitsManager;
}
export function getMissionHandler() {
return missionHandler;
}
export function getUnitInfoPanel() {
return unitInfoPanel;
}
@@ -249,7 +253,6 @@ export function getHotgroupPanel() {
export function setActiveCoalition(newActiveCoalition: string) {
activeCoalition = newActiveCoalition;
document.querySelectorAll('[data-active-coalition]').forEach((element: any) => { element.setAttribute("data-active-coalition", activeCoalition) });
}
export function getActiveCoalition() {

View File

@@ -46,7 +46,7 @@ export var BoxSelect = Handler.extend({
_onMouseDown: function (e: any) {
if ((e.which == 1 && e.button == 0 && e.shiftKey)) {
this._map.fire('selectionstart');
// Clear the deferred resetState if it hasn't executed yet, otherwise it
// will interrupt the interaction and orphan a box element in the container.
this._clearDeferredResetState();

View File

@@ -0,0 +1,171 @@
import { DomUtil, LatLng, LatLngExpression, Map, Point, Polygon, PolylineOptions } from "leaflet";
import { getMap } from "..";
import { CoalitionAreaHandle } from "./coalitionareahandle";
import { CoalitionAreaMiddleHandle } from "./coalitionareamiddlehandle";
export class CoalitionArea extends Polygon {
#coalition: string = "blue";
#selected: boolean = true;
#editing: boolean = true;
#handles: CoalitionAreaHandle[] = [];
#middleHandles: CoalitionAreaMiddleHandle[] = [];
#activeIndex: number = 0;
constructor(latlngs: LatLngExpression[] | LatLngExpression[][] | LatLngExpression[][][], options?: PolylineOptions) {
if (options === undefined)
options = {};
options.bubblingMouseEvents = false;
super(latlngs, options);
this.#setColors();
this.#registerCallbacks();
}
setCoalition(coalition: string) {
this.#coalition = coalition;
this.#setColors();
}
getCoalition() {
return this.#coalition;
}
setSelected(selected: boolean) {
this.#selected = selected;
this.#setColors();
this.#setHandles();
if (!this.getSelected() && this.getEditing()) {
/* Remove the vertex we were working on */
var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs.splice(this.#activeIndex, 1);
this.setLatLngs(latlngs);
this.setEditing(false);
}
}
getSelected() {
return this.#selected;
}
setEditing(editing: boolean) {
this.#editing = editing;
this.#setHandles();
var latlngs = this.getLatLngs()[0] as LatLng[];
/* Remove areas with less than 2 vertexes */
if (latlngs.length <= 2)
getMap().deleteCoalitionArea(this);
}
getEditing() {
return this.#editing;
}
setInteractive(interactive: boolean) {
this.setOpacity(interactive? 1: 0.5);
this.options.interactive = interactive;
if (interactive)
DomUtil.addClass(this.getElement() as HTMLElement, 'leaflet-interactive');
else
DomUtil.removeClass(this.getElement() as HTMLElement, 'leaflet-interactive');
}
addTemporaryLatLng(latlng: LatLng) {
this.#activeIndex++;
var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs.splice(this.#activeIndex, 0, latlng);
this.setLatLngs(latlngs);
this.#setHandles();
}
moveActiveVertex(latlng: LatLng) {
var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs[this.#activeIndex] = latlng;
this.setLatLngs(latlngs);
this.#setHandles();
}
setOpacity(opacity: number) {
this.setStyle({opacity: opacity, fillOpacity: opacity * 0.25});
}
#setColors() {
const coalitionColor = this.getCoalition() === "blue" ? "#247be2" : "#ff5858";
this.setStyle({ color: this.getSelected() ? "white" : coalitionColor, fillColor: coalitionColor });
}
#setHandles() {
this.#handles.forEach((handle: CoalitionAreaHandle) => handle.removeFrom(getMap()));
this.#handles = [];
if (this.getSelected()) {
var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs.forEach((latlng: LatLng, idx: number) => {
/* Add the polygon vertex handle (for moving the vertex) */
const handle = new CoalitionAreaHandle(latlng);
handle.addTo(getMap());
handle.on("drag", (e: any) => {
var latlngs = this.getLatLngs()[0] as LatLng[];
latlngs[idx] = e.target.getLatLng();
this.setLatLngs(latlngs);
this.#setMiddleHandles();
});
this.#handles.push(handle);
});
}
this.#setMiddleHandles();
}
#setMiddleHandles() {
this.#middleHandles.forEach((handle: CoalitionAreaMiddleHandle) => handle.removeFrom(getMap()));
this.#middleHandles = [];
var latlngs = this.getLatLngs()[0] as LatLng[];
if (this.getSelected() && latlngs.length >= 2) {
var lastLatLng: LatLng | null = null;
latlngs.concat([latlngs[0]]).forEach((latlng: LatLng, idx: number) => {
/* Add the polygon middle point handle (for adding new vertexes) */
if (lastLatLng != null) {
const handle1Point = getMap().latLngToLayerPoint(latlng);
const handle2Point = getMap().latLngToLayerPoint(lastLatLng);
const middlePoint = new Point((handle1Point.x + handle2Point.x) / 2, (handle1Point.y + handle2Point.y) / 2);
const middleLatLng = getMap().layerPointToLatLng(middlePoint);
const middleHandle = new CoalitionAreaMiddleHandle(middleLatLng);
middleHandle.addTo(getMap());
middleHandle.on("click", (e: any) => {
this.#activeIndex = idx - 1;
this.addTemporaryLatLng(middleLatLng);
});
this.#middleHandles.push(middleHandle);
}
lastLatLng = latlng;
});
}
}
#registerCallbacks() {
this.on("click", (e: any) => {
getMap().deselectAllCoalitionAreas();
if (!this.getSelected()) {
this.setSelected(true);
}
});
this.on("contextmenu", (e: any) => {
if (!this.getEditing()) {
getMap().deselectAllCoalitionAreas();
this.setSelected(true);
getMap().showCoalitionAreaContextMenu(e, this);
}
else
this.setEditing(false);
});
}
onRemove(map: Map): this {
super.onRemove(map);
this.#handles.concat(this.#middleHandles).forEach((handle: CoalitionAreaHandle | CoalitionAreaMiddleHandle) => handle.removeFrom(getMap()));
return this;
}
}

View File

@@ -0,0 +1,19 @@
import { DivIcon, LatLng } from "leaflet";
import { CustomMarker } from "./custommarker";
export class CoalitionAreaHandle extends CustomMarker {
constructor(latlng: LatLng) {
super(latlng, {interactive: true, draggable: true});
}
createIcon() {
this.setIcon(new DivIcon({
iconSize: [24, 24],
iconAnchor: [12, 12],
className: "leaflet-coalitionarea-handle-marker",
}));
var el = document.createElement("div");
el.classList.add("ol-coalitionarea-handle-icon");
this.getElement()?.appendChild(el);
}
}

View File

@@ -0,0 +1,19 @@
import { DivIcon, LatLng } from "leaflet";
import { CustomMarker } from "./custommarker";
export class CoalitionAreaMiddleHandle extends CustomMarker {
constructor(latlng: LatLng) {
super(latlng, {interactive: true, draggable: false});
}
createIcon() {
this.setIcon(new DivIcon({
iconSize: [16, 16],
iconAnchor: [8, 8],
className: "leaflet-coalitionarea-middle-handle-marker",
}));
var el = document.createElement("div");
el.classList.add("ol-coalitionarea-middle-handle-icon");
this.getElement()?.appendChild(el);
}
}

View File

@@ -1,7 +1,12 @@
import { DivIcon } from "leaflet";
import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet";
import { CustomMarker } from "./custommarker";
export class DestinationPreviewMarker extends CustomMarker {
constructor(latlng: LatLngExpression, options?: MarkerOptions) {
super(latlng, options);
this.setZIndexOffset(9999);
}
createIcon() {
this.setIcon(new DivIcon({
iconSize: [52, 52],

View File

@@ -0,0 +1,20 @@
import { DivIcon, LatLng } from "leaflet";
import { CustomMarker } from "./custommarker";
export class DrawingCursor extends CustomMarker {
constructor() {
super(new LatLng(0, 0), {interactive: false})
this.setZIndexOffset(9999);
}
createIcon() {
this.setIcon(new DivIcon({
iconSize: [24, 24],
iconAnchor: [0, 24],
className: "leaflet-draw-marker",
}));
var el = document.createElement("div");
el.classList.add("ol-draw-icon");
this.getElement()?.appendChild(el);
}
}

View File

@@ -12,22 +12,17 @@ import { DestinationPreviewMarker } from "./destinationpreviewmarker";
import { TemporaryUnitMarker } from "./temporaryunitmarker";
import { ClickableMiniMap } from "./clickableminimap";
import { SVGInjector } from '@tanem/svg-injector'
import { layers as mapLayers, mapBounds, minimapBoundaries } from "../constants/constants";
import { layers as mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, visibilityControls, visibilityControlsTootlips, FIRE_AT_AREA, MOVE_UNIT, CARPET_BOMBING, BOMBING, COALITIONAREA_INTERACT } from "../constants/constants";
import { TargetMarker } from "./targetmarker";
import { CoalitionArea } from "./coalitionarea";
import { CoalitionAreaContextMenu } from "../controls/coalitionareacontextmenu";
import { DrawingCursor } from "./drawingcursor";
L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect);
// TODO would be nice to convert to ts
require("../../public/javascripts/leaflet.nauticscale.js")
/* Map constants */
export const IDLE = "Idle";
export const MOVE_UNIT = "Move unit";
export const BOMBING = "Bombing";
export const CARPET_BOMBING = "Carpet bombing";
export const FIRE_AT_AREA = "Fire at area";
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"];
require("../../public/javascripts/L.Path.Drag.js")
export class Map extends L.Map {
#ID: string;
@@ -42,27 +37,34 @@ export class Map extends L.Map {
#panUp: boolean = false;
#panDown: boolean = false;
#lastMousePosition: L.Point = new L.Point(0, 0);
#shiftKey: boolean = false;
#ctrlKey: boolean = false;
#centerUnit: Unit | null = null;
#miniMap: ClickableMiniMap | null = null;
#miniMapLayerGroup: L.LayerGroup;
#temporaryMarkers: TemporaryUnitMarker[] = [];
#destinationPreviewMarkers: DestinationPreviewMarker[] = [];
#targetMarker: TargetMarker;
#selecting: boolean = false;
#destinationGroupRotation: number = 0;
#computeDestinationRotation: boolean = false;
#destinationRotationCenter: L.LatLng | null = null;
#coalitionAreas: CoalitionArea[] = [];
#targetCursor: TargetMarker = new TargetMarker(new L.LatLng(0, 0), { interactive: false });
#destinationPreviewCursors: DestinationPreviewMarker[] = [];
#drawingCursor: DrawingCursor = new DrawingCursor();
#mapContextMenu: MapContextMenu = new MapContextMenu("map-contextmenu");
#unitContextMenu: UnitContextMenu = new UnitContextMenu("unit-contextmenu");
#airbaseContextMenu: AirbaseContextMenu = new AirbaseContextMenu("airbase-contextmenu");
#coalitionAreaContextMenu: CoalitionAreaContextMenu = new CoalitionAreaContextMenu("coalition-area-contextmenu");
#mapSourceDropdown: Dropdown;
#optionButtons: { [key: string]: HTMLButtonElement[] } = {}
constructor(ID: string) {
/* Init the leaflet map */
//@ts-ignore
//@ts-ignore Needed because the boxSelect option is non-standard
super(ID, { doubleClickZoom: false, zoomControl: false, boxZoom: false, boxSelect: true, zoomAnimation: true, maxBoundsViscosity: 1.0, minZoom: 7, keyboard: true, keyboardPanDelta: 0 });
this.setView([37.23, -115.8], 10);
@@ -92,18 +94,19 @@ export class Map extends L.Map {
this.on("zoomstart", (e: any) => this.#onZoom(e));
this.on("drag", (e: any) => this.centerOnUnit(null));
this.on("contextmenu", (e: any) => this.#onContextMenu(e));
this.on('selectionstart', (e: any) => this.#onSelectionStart(e));
this.on('selectionend', (e: any) => this.#onSelectionEnd(e));
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));
this.on('keydown', (e: any) => this.#onKeyDown(e));
this.on('keyup', (e: any) => this.#onKeyUp(e));
/* Event listeners */
document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => {
const el = ev.detail._element;
el?.classList.toggle("off");
getUnitsManager().setHiddenType(ev.detail.coalition, (el?.currentTarget as HTMLElement)?.classList.contains("off"));
getUnitsManager().setHiddenType(ev.detail.coalition, !el?.classList.contains("off"));
Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
});
@@ -114,6 +117,29 @@ export class Map extends L.Map {
Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility());
});
document.addEventListener("toggleCoalitionAreaInteraction", (ev: CustomEventInit) => {
const el = ev.detail._element;
/* Add listener to set the button to off if the state changes */
document.addEventListener("mapStateChanged", () => el?.classList.toggle("off", !(this.getState() === COALITIONAREA_INTERACT)));
if (this.getState() !== COALITIONAREA_INTERACT)
this.setState(COALITIONAREA_INTERACT);
else
this.setState(IDLE);
});
document.addEventListener("toggleCoalitionAreaDraw", (ev: CustomEventInit) => {
const el = ev.detail._element;
/* Add listener to set the button to off if the state changes */
document.addEventListener("mapStateChanged", () => el?.classList.toggle("off", !(this.getState() === COALITIONAREA_DRAW_POLYGON)));
this.deselectAllCoalitionAreas();
if (ev.detail?.type == "polygon") {
if (this.getState() !== COALITIONAREA_DRAW_POLYGON)
this.setState(COALITIONAREA_DRAW_POLYGON);
else
this.setState(IDLE);
}
});
document.addEventListener("unitUpdated", (ev: CustomEvent) => {
if (this.#centerUnit != null && ev.detail == this.#centerUnit)
this.#panToUnit(this.#centerUnit);
@@ -122,7 +148,7 @@ export class Map extends L.Map {
/* Pan interval */
this.#panInterval = window.setInterval(() => {
if (this.#panLeft || this.#panDown || this.#panRight || this.#panLeft)
this.panBy(new L.Point(((this.#panLeft? -1 : 0) + (this.#panRight ? 1 : 0)) * this.#deafultPanDelta,
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);
@@ -131,16 +157,13 @@ export class Map extends L.Map {
return this.#createOptionButton(option, `visibility/${option.toLowerCase()}.svg`, visibilityControlsTootlips[index], "toggleUnitVisibility", `{"type": "${option}"}`);
});
document.querySelector("#unit-visibility-control")?.append(...this.#optionButtons["visibility"]);
/* Markers */
this.#targetMarker = new TargetMarker(new L.LatLng(0, 0), {interactive: false});
}
setLayer(layerName: string) {
if (this.#layer != null)
if (this.#layer != null)
this.removeLayer(this.#layer)
if (layerName in mapLayers){
if (layerName in mapLayers) {
const layerData = mapLayers[layerName as keyof typeof mapLayers];
var options: L.TileLayerOptions = {
attribution: layerData.attribution,
@@ -160,21 +183,27 @@ export class Map extends L.Map {
/* State machine */
setState(state: string) {
this.#state = state;
if (this.#state === IDLE) {
this.#resetDestinationMarkers();
this.#resetTargetMarker();
this.#showCursor();
this.#updateCursor();
/* Operations to perform if you are NOT in a state */
if (this.#state !== COALITIONAREA_INTERACT) {
this.#coalitionAreas.forEach((coalitionArea: CoalitionArea) => {
coalitionArea.setInteractive(false);
});
}
else if (this.#state === MOVE_UNIT) {
this.#resetTargetMarker();
this.#createDestinationMarkers();
if (this.#destinationPreviewMarkers.length > 0)
this.#hideCursor();
if (this.#state !== COALITIONAREA_DRAW_POLYGON) {
this.#deselectCoalitionAreas();
}
else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) {
this.#resetDestinationMarkers();
this.#createTargetMarker();
this.#hideCursor();
/* Operations to perform if you ARE in a state */
if (this.#state === COALITIONAREA_INTERACT) {
this.#coalitionAreas.forEach((coalitionArea: CoalitionArea) => {
coalitionArea.setInteractive(true);
});
}
else if (this.#state === COALITIONAREA_DRAW_POLYGON) {
this.#coalitionAreas.push(new CoalitionArea([]));
this.#coalitionAreas[this.#coalitionAreas.length - 1].addTo(this);
}
document.dispatchEvent(new CustomEvent("mapStateChanged"));
}
@@ -183,11 +212,23 @@ export class Map extends L.Map {
return this.#state;
}
deselectAllCoalitionAreas() {
this.#coalitionAreas.forEach((coalitionArea: CoalitionArea) => coalitionArea.setSelected(false));
}
deleteCoalitionArea(coalitionArea: CoalitionArea) {
if (this.#coalitionAreas.includes(coalitionArea))
this.#coalitionAreas.splice(this.#coalitionAreas.indexOf(coalitionArea), 1);
if (this.hasLayer(coalitionArea))
this.removeLayer(coalitionArea);
}
/* Context Menus */
hideAllContextMenus() {
this.hideMapContextMenu();
this.hideUnitContextMenu();
this.hideAirbaseContextMenu();
this.hideCoalitionAreaContextMenu();
}
showMapContextMenu(e: any) {
@@ -238,6 +279,22 @@ export class Map extends L.Map {
this.#airbaseContextMenu.hide();
}
showCoalitionAreaContextMenu(e: any, coalitionArea: CoalitionArea) {
this.hideAllContextMenus();
var x = e.originalEvent.x;
var y = e.originalEvent.y;
this.#coalitionAreaContextMenu.show(x, y, e.latlng);
this.#coalitionAreaContextMenu.setCoalitionArea(coalitionArea);
}
getCoalitionAreaContextMenu() {
return this.#coalitionAreaContextMenu;
}
hideCoalitionAreaContextMenu() {
this.#coalitionAreaContextMenu.hide();
}
/* Mouse coordinates */
getMousePosition() {
return this.#lastMousePosition;
@@ -333,36 +390,49 @@ export class Map extends L.Map {
}
}
addTemporaryMarker(latlng: L.LatLng) {
var marker = new TemporaryUnitMarker(latlng);
addTemporaryMarker(spawnOptions: SpawnOptions) {
var marker = new TemporaryUnitMarker(spawnOptions);
marker.addTo(this);
this.#temporaryMarkers.push(marker);
}
removeTemporaryMarker(latlng: L.LatLng) {
var d: number | null = null;
// TODO something more refined than this
var dist: number | null = null;
var closest: L.Marker | null = null;
var i: number = 0;
this.#temporaryMarkers.forEach((marker: L.Marker, idx: number) => {
var t = latlng.distanceTo(marker.getLatLng());
if (d == null || t < d) {
d = t;
if (dist == null || t < dist) {
dist = t;
closest = marker;
i = idx;
}
});
if (closest) {
if (closest && dist != null && dist < 100) {
this.removeLayer(closest);
delete this.#temporaryMarkers[i];
this.#temporaryMarkers.splice(i, 1);
}
}
getSelectedCoalitionArea() {
return this.#coalitionAreas.find((area: CoalitionArea) => { return area.getSelected() });
}
/* Event handlers */
#onClick(e: any) {
if (!this.#preventLeftClick) {
this.hideAllContextMenus();
if (this.#state === IDLE) {
this.deselectAllCoalitionAreas();
}
else if (this.#state === COALITIONAREA_DRAW_POLYGON) {
if (this.getSelectedCoalitionArea()?.getEditing()) {
this.getSelectedCoalitionArea()?.addTemporaryLatLng(e.latlng);
}
else {
this.deselectAllCoalitionAreas();
}
}
else {
this.setState(IDLE);
@@ -372,7 +442,7 @@ export class Map extends L.Map {
}
#onDoubleClick(e: any) {
this.deselectAllCoalitionAreas();
}
#onContextMenu(e: any) {
@@ -386,50 +456,49 @@ export class Map extends L.Map {
if (!e.originalEvent.ctrlKey) {
getUnitsManager().selectedUnitsClearDestinations();
}
getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, e.originalEvent.shiftKey, this.#destinationGroupRotation)
this.#destinationGroupRotation = 0;
getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, this.#shiftKey, this.#destinationGroupRotation)
this.#destinationGroupRotation = 0;
this.#destinationRotationCenter = null;
this.#computeDestinationRotation = false;
}
else if (this.#state === BOMBING) {
getUnitsManager().getSelectedUnits().length > 0? this.setState(MOVE_UNIT): this.setState(IDLE);
getUnitsManager().selectedUnitsBombPoint(this.getMouseCoordinates());
getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
getUnitsManager().selectedUnitsBombPoint(this.getMouseCoordinates());
}
else if (this.#state === CARPET_BOMBING) {
getUnitsManager().getSelectedUnits().length > 0? this.setState(MOVE_UNIT): this.setState(IDLE);
getUnitsManager().selectedUnitsCarpetBomb(this.getMouseCoordinates());
getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
getUnitsManager().selectedUnitsCarpetBomb(this.getMouseCoordinates());
}
else if (this.#state === FIRE_AT_AREA) {
getUnitsManager().getSelectedUnits().length > 0? this.setState(MOVE_UNIT): this.setState(IDLE);
getUnitsManager().selectedUnitsFireAtArea(this.getMouseCoordinates());
getUnitsManager().getSelectedUnits().length > 0 ? this.setState(MOVE_UNIT) : this.setState(IDLE);
getUnitsManager().selectedUnitsFireAtArea(this.getMouseCoordinates());
}
else {
this.setState(IDLE);
}
}
#executeAction(e: any, action: string) {
if (action === "bomb")
getUnitsManager().selectedUnitsBombPoint(this.getMouseCoordinates());
else if (action === "carpet-bomb")
getUnitsManager().selectedUnitsCarpetBomb(this.getMouseCoordinates());
else if (action === "building-bomb")
getUnitsManager().selectedUnitsBombBuilding(this.getMouseCoordinates());
else if (action === "fire-at-area")
getUnitsManager().selectedUnitsFireAtArea(this.getMouseCoordinates());
#onSelectionStart(e: any) {
this.#selecting = true;
this.#updateCursor();
}
#onSelectionEnd(e: any) {
this.#selecting = false;
clearTimeout(this.#leftClickTimer);
this.#preventLeftClick = true;
this.#leftClickTimer = window.setTimeout(() => {
this.#preventLeftClick = false;
}, 200);
getUnitsManager().selectFromBounds(e.selectionBounds);
this.#updateCursor();
}
#onMouseDown(e: any) {
this.hideAllContextMenus();
if (this.#state == MOVE_UNIT) {
this.#destinationGroupRotation = 0;
this.#destinationGroupRotation = 0;
this.#destinationRotationCenter = null;
this.#computeDestinationRotation = false;
if (e.originalEvent.button == 2) {
@@ -446,14 +515,36 @@ export class Map extends L.Map {
this.#lastMousePosition.x = e.originalEvent.x;
this.#lastMousePosition.y = e.originalEvent.y;
if (this.#state === MOVE_UNIT){
this.#updateCursor();
if (this.#state === MOVE_UNIT) {
/* Update the position of the destination cursors depeding on mouse rotation */
if (this.#computeDestinationRotation && this.#destinationRotationCenter != null)
this.#destinationGroupRotation = -bearing(this.#destinationRotationCenter.lat, this.#destinationRotationCenter.lng, this.getMouseCoordinates().lat, this.getMouseCoordinates().lng);
this.#updateDestinationPreview(e);
this.#updateDestinationCursors();
}
else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) {
this.#targetMarker.setLatLng(this.getMouseCoordinates());
this.#targetCursor.setLatLng(this.getMouseCoordinates());
}
else if (this.#state === COALITIONAREA_DRAW_POLYGON) {
this.#drawingCursor.setLatLng(e.latlng);
/* Update the polygon being drawn with the current position of the mouse cursor */
this.getSelectedCoalitionArea()?.moveActiveVertex(e.latlng);
}
}
#onKeyDown(e: any) {
this.#shiftKey = e.originalEvent.shiftKey;
this.#ctrlKey = e.originalEvent.ctrlKey;
this.#updateCursor();
this.#updateDestinationCursors();
}
#onKeyUp(e: any) {
this.#shiftKey = e.originalEvent.shiftKey;
this.#ctrlKey = e.originalEvent.ctrlKey;
this.#updateCursor();
this.#updateDestinationCursors();
}
#onZoom(e: any) {
@@ -462,7 +553,7 @@ export class Map extends L.Map {
}
#panToUnit(unit: Unit) {
var unitPosition = new L.LatLng(unit.getFlightData().latitude, unit.getFlightData().longitude);
var unitPosition = new L.LatLng(unit.getPosition().lat, unit.getPosition().lng);
this.setView(unitPosition, this.getZoom(), { animate: false });
}
@@ -471,13 +562,6 @@ export class Map extends L.Map {
return minimapBoundaries;
}
#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");
@@ -491,47 +575,115 @@ export class Map extends L.Map {
return button;
}
#createDestinationMarkers() {
this.#resetDestinationMarkers();
#deselectCoalitionAreas() {
this.getSelectedCoalitionArea()?.setSelected(false);
}
if (getUnitsManager().getSelectedUnits({ excludeHumans: true }).length > 0) {
/* Create the unit destination preview markers */
this.#destinationPreviewMarkers = getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).map((unit: Unit) => {
var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), {interactive: false});
/* Cursors */
#showDefaultCursor() {
document.getElementById(this.#ID)?.classList.remove("hidden-cursor");
}
#hideDefaultCursor() {
document.getElementById(this.#ID)?.classList.add("hidden-cursor");
}
#showDestinationCursors() {
const singleCursor = !this.#shiftKey;
const selectedUnitsCount = getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).length;
if (selectedUnitsCount > 0) {
if (singleCursor && this.#destinationPreviewCursors.length != 1) {
this.#hideDestinationCursors();
var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), { interactive: false });
marker.addTo(this);
return marker;
})
this.#destinationPreviewCursors = [marker];
}
else if (!singleCursor) {
while (this.#destinationPreviewCursors.length > selectedUnitsCount) {
this.removeLayer(this.#destinationPreviewCursors[0]);
this.#destinationPreviewCursors.splice(0, 1);
}
while (this.#destinationPreviewCursors.length < selectedUnitsCount) {
var cursor = new DestinationPreviewMarker(this.getMouseCoordinates(), { interactive: false });
cursor.addTo(this);
this.#destinationPreviewCursors.push(cursor);
}
this.#updateDestinationCursors();
}
}
}
#resetDestinationMarkers() {
/* Remove all the destination preview markers */
this.#destinationPreviewMarkers.forEach((marker: L.Marker) => {
#updateDestinationCursors() {
const groupLatLng = this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : this.getMouseCoordinates();
if (this.#destinationPreviewCursors.length == 1)
this.#destinationPreviewCursors[0].setLatLng(this.getMouseCoordinates());
else {
Object.values(getUnitsManager().selectedUnitsComputeGroupDestination(groupLatLng, this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => {
if (idx < this.#destinationPreviewCursors.length)
this.#destinationPreviewCursors[idx].setLatLng(this.#shiftKey? latlng : this.getMouseCoordinates());
})
};
}
#hideDestinationCursors() {
/* Remove all the destination cursors */
this.#destinationPreviewCursors.forEach((marker: L.Marker) => {
this.removeLayer(marker);
})
this.#destinationPreviewMarkers = [];
this.#destinationPreviewCursors = [];
/* Reset the variables used to compute the rotation of the group cursors */
this.#destinationGroupRotation = 0;
this.#computeDestinationRotation = false;
this.#destinationRotationCenter = null;
}
#createTargetMarker(){
this.#resetTargetMarker();
this.#targetMarker.addTo(this);
#showTargetCursor() {
this.#hideTargetCursor();
this.#targetCursor.addTo(this);
}
#resetTargetMarker() {
this.#targetMarker.setLatLng(new L.LatLng(0, 0));
this.removeLayer(this.#targetMarker);
#hideTargetCursor() {
this.#targetCursor.setLatLng(new L.LatLng(0, 0));
this.removeLayer(this.#targetCursor);
}
#showCursor() {
document.getElementById(this.#ID)?.classList.remove("hidden-cursor");
#showDrawingCursor() {
this.#hideDefaultCursor();
if (!this.hasLayer(this.#drawingCursor))
this.#drawingCursor.addTo(this);
}
#hideCursor() {
document.getElementById(this.#ID)?.classList.add("hidden-cursor");
#hideDrawingCursor() {
this.#drawingCursor.setLatLng(new L.LatLng(0, 0));
if (this.hasLayer(this.#drawingCursor))
this.#drawingCursor.removeFrom(this);
}
}
#updateCursor() {
/* If the ctrl key is being pressed or we are performing an area selection, show the default cursor */
if (this.#ctrlKey || this.#selecting) {
/* Hide all non default cursors */
this.#hideDestinationCursors();
this.#hideTargetCursor();
this.#hideDrawingCursor();
this.#showDefaultCursor();
} else {
/* Hide all the unnecessary cursors depending on the active state */
if (this.#state !== IDLE && this.#state !== COALITIONAREA_INTERACT) this.#hideDefaultCursor();
if (this.#state !== MOVE_UNIT) this.#hideDestinationCursors();
if (![BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) this.#hideTargetCursor();
if (this.#state !== COALITIONAREA_DRAW_POLYGON) this.#hideDrawingCursor();
/* Show the active cursor depending on the active state */
if (this.#state === IDLE || this.#state === COALITIONAREA_INTERACT) this.#showDefaultCursor();
else if (this.#state === MOVE_UNIT) this.#showDestinationCursors();
else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) this.#showTargetCursor();
else if (this.#state === COALITIONAREA_DRAW_POLYGON) this.#showDrawingCursor();
}
}
}

View File

@@ -1,8 +1,11 @@
import { DivIcon } from "leaflet";
import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet";
import { CustomMarker } from "./custommarker";
export class TargetMarker extends CustomMarker {
#interactive: boolean = false;
constructor(latlng: LatLngExpression, options?: MarkerOptions) {
super(latlng, options);
this.setZIndexOffset(9999);
}
createIcon() {
this.setIcon(new DivIcon({
@@ -12,7 +15,6 @@ export class TargetMarker extends CustomMarker {
}));
var el = document.createElement("div");
el.classList.add("ol-target-icon");
el.classList.toggle("ol-target-icon-interactive", this.#interactive)
this.getElement()?.appendChild(el);
}
}

View File

@@ -1,13 +1,52 @@
import { Icon } from "leaflet";
import { CustomMarker } from "./custommarker";
import { SpawnOptions } from "../controls/mapcontextmenu";
import { DivIcon } from "leaflet";
import { SVGInjector } from "@tanem/svg-injector";
import { getMarkerCategoryByName, getUnitDatabaseByCategory } from "../other/utils";
export class TemporaryUnitMarker extends CustomMarker {
#spawnOptions: SpawnOptions;
constructor(spawnOptions: SpawnOptions) {
super(spawnOptions.latlng, {interactive: false});
this.#spawnOptions = spawnOptions;
}
createIcon() {
var icon = new Icon({
iconUrl: '/resources/theme/images/markers/temporary-icon.png',
iconSize: [52, 52],
iconAnchor: [26, 26]
const category = getMarkerCategoryByName(this.#spawnOptions.name);
/* 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-${category}`);
el.setAttribute("data-coalition", this.#spawnOptions.coalition);
// Main icon
var unitIcon = document.createElement("div");
unitIcon.classList.add("unit-icon");
var img = document.createElement("img");
img.src = `/resources/theme/images/units/${category}.svg`;
img.onload = () => SVGInjector(img);
unitIcon.appendChild(img);
unitIcon.toggleAttribute("data-rotate-to-heading", false);
el.append(unitIcon);
// Short label
if (category == "aircraft" || category == "helicopter") {
var shortLabel = document.createElement("div");
shortLabel.classList.add("unit-short-label");
shortLabel.innerText = getUnitDatabaseByCategory(category)?.getByName(this.#spawnOptions.name)?.shortLabel || "";
el.append(shortLabel);
}
this.getElement()?.appendChild(el);
this.getElement()?.classList.add("ol-temporary-marker");
}
}

View File

@@ -3,50 +3,41 @@ import { getInfoPopup, getMap } from "..";
import { Airbase } from "./airbase";
import { Bullseye } from "./bullseye";
export class MissionHandler
{
#bullseyes : {[name: string]: Bullseye} = {};
#airbases : {[name: string]: Airbase} = {};
#theatre : string = "";
export class MissionHandler {
#bullseyes: { [name: string]: Bullseye } = {};
#airbases: { [name: string]: Airbase } = {};
#theatre: string = "";
#airbaseData : { [name: string]: object } = {};
#airbaseData: { [name: string]: object } = {};
// Time
#date : any;
#elapsedTime : any;
#startTime : any;
#time : any;
#date: any;
#elapsedTime: any;
#startTime: any;
#time: any;
#updateTime : any;
#updateTime: any;
constructor()
{
constructor() {
}
update(data: BullseyesData | AirbasesData | any)
{
if ("bullseyes" in data)
{
for (let idx in data.bullseyes)
{
update(data: BullseyesData | AirbasesData | any) {
if ("bullseyes" in data) {
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));
if (bullseye.latitude && bullseye.longitude && bullseye.coalition) {
this.#bullseyes[idx].setLatLng(new LatLng(bullseye.latitude, bullseye.longitude));
this.#bullseyes[idx].setCoalition(bullseye.coalition);
}
}
}
if ("mission" in data)
{
if (data.mission != null && data.mission.theatre != this.#theatre)
{
if ("mission" in data) {
if (data.mission != null && data.mission.theatre != this.#theatre) {
this.#theatre = data.mission.theatre;
getMap().setTheatre(this.#theatre);
@@ -54,31 +45,18 @@ export class MissionHandler
}
}
if ("airbases" in data)
{
/*
console.log( Object.values( data.airbases ).sort( ( a:any, b:any ) => {
const aVal = a.callsign.toLowerCase();
const bVal = b.callsign.toLowerCase();
return aVal > bVal ? 1 : -1;
}) );
//*/
for (let idx in data.airbases)
{
if ("airbases" in data) {
for (let idx in data.airbases) {
var airbase = data.airbases[idx]
if (this.#airbases[idx] === undefined && airbase.callsign != '')
{
if (this.#airbases[idx] === undefined && airbase.callsign != '') {
this.#airbases[idx] = new Airbase({
position: new LatLng(airbase.latitude, airbase.longitude),
position: new LatLng(airbase.latitude, airbase.longitude),
name: airbase.callsign
}).addTo(getMap());
this.#airbases[idx].on('contextmenu', (e) => this.#onAirbaseClick(e));
}
if (this.#airbases[idx] != undefined && airbase.latitude && airbase.longitude && airbase.coalition)
{
if (this.#airbases[idx] != undefined && airbase.latitude && airbase.longitude && airbase.coalition) {
this.#airbases[idx].setLatLng(new LatLng(airbase.latitude, airbase.longitude));
this.#airbases[idx].setCoalition(airbase.coalition);
}
@@ -87,83 +65,68 @@ export class MissionHandler
}
}
if ("mission" in data && data.mission != null)
{
if (data.mission != null && data.mission.theatre != this.#theatre)
{
if ("mission" in data && data.mission != null) {
if (data.mission != null && data.mission.theatre != this.#theatre) {
this.#theatre = data.mission.theatre;
getMap().setTheatre(this.#theatre);
getInfoPopup().setText("Map set to " + this.#theatre);
}
if ( "date" in data.mission ) {
if ("date" in data.mission)
this.#date = data.mission.date;
}
if ( "elapsedTime" in data.mission ) {
if ("elapsedTime" in data.mission)
this.#elapsedTime = data.mission.elapsedTime;
}
if ( "startTime" in data.mission ) {
if ("startTime" in data.mission)
this.#startTime = data.mission.startTime;
}
if ( "time" in data.mission ) {
if ("time" in data.mission)
this.#time = data.mission.time;
}
}
if ( "time" in data ) {
if ("time" in data)
this.#updateTime = data.time;
}
}
getBullseyes()
{
getBullseyes() {
return this.#bullseyes;
}
getAirbases() {
return this.#airbases;
}
getDate() {
return this.#date;
}
getNowDate() {
const date = this.getDate();
const time = this.getTime();
if ( !date ) {
if (!date) {
return new Date();
}
let year = date.Year;
let year = date.Year;
let month = date.Month - 1;
if ( month < 0 ) {
if (month < 0) {
month = 11;
year--;
}
return new Date( year, month, date.Day, time.h, time.m, time.s );
return new Date(year, month, date.Day, time.h, time.m, time.s);
}
getTime() {
return this.#time;
}
getUpdateTime() {
return this.#updateTime;
}
#onAirbaseClick(e: any)
{
#onAirbaseClick(e: any) {
getMap().showAirbaseContextMenu(e, e.sourceTarget);
}
}

View File

@@ -1,3 +1,12 @@
import { LatLng, Point, Polygon } from "leaflet";
import * as turf from "@turf/turf";
import { UnitDatabase } from "../units/unitdatabase";
import { aircraftDatabase } from "../units/aircraftdatabase";
import { helicopterDatabase } from "../units/helicopterdatabase";
import { groundUnitsDatabase } from "../units/groundunitsdatabase";
import { Buffer } from "buffer";
import { ROEs, emissionsCountermeasures, reactionsToThreat, states } from "../constants/constants";
export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) {
const φ1 = deg2rad(lat1); // φ, λ in radians
const φ2 = deg2rad(lat2);
@@ -184,4 +193,101 @@ export function mToNm(m: number) {
export function nmToFt(nm: number) {
return nm * 6076.12;
}
export function randomPointInPoly(polygon: Polygon): LatLng {
var bounds = polygon.getBounds();
var x_min = bounds.getEast();
var x_max = bounds.getWest();
var y_min = bounds.getSouth();
var y_max = bounds.getNorth();
var lat = y_min + (Math.random() * (y_max - y_min));
var lng = x_min + (Math.random() * (x_max - x_min));
var poly = polygon.toGeoJSON();
var inside = turf.inside(turf.point([lng, lat]), poly);
if (inside) {
return new LatLng(lat, lng);
} else {
return randomPointInPoly(polygon);
}
}
export function polygonArea(polygon: Polygon) {
var poly = polygon.toGeoJSON();
return turf.area(poly);
}
export function randomUnitBlueprintByRole(unitDatabse: UnitDatabase, role: string) {
const unitBlueprints = unitDatabse.getByRole(role);
var index = Math.floor(Math.random() * unitBlueprints.length);
return unitBlueprints[index];
}
export function getMarkerCategoryByName(name: string) {
if (aircraftDatabase.getByName(name) != null)
return "aircraft";
else if (helicopterDatabase.getByName(name) != null)
return "helicopter";
else if (groundUnitsDatabase.getByName(name) != null){
// TODO this is very messy
var role = groundUnitsDatabase.getByName(name)?.loadouts[0].roles[0];
return (role?.includes("SAM")) ? "groundunit-sam" : "groundunit-other";
}
else
return ""; // TODO add other unit types
}
export function getUnitDatabaseByCategory(category: string) {
if (category == "aircraft")
return aircraftDatabase;
else if (category == "helicopter")
return helicopterDatabase;
else if (category.includes("groundunit"))
return groundUnitsDatabase;
else
return null;
}
export function base64ToBytes(base64: string) {
return Buffer.from(base64, 'base64').buffer;
}
export function enumToState(state: number) {
if (state < states.length)
return states[state];
else
return states[0];
}
export function enumToROE(ROE: number) {
if (ROE < ROEs.length)
return ROEs[ROE];
else
return ROEs[0];
}
export function enumToReactionToThreat(reactionToThreat: number) {
if (reactionToThreat < reactionsToThreat.length)
return reactionsToThreat[reactionToThreat];
else
return reactionsToThreat[0];
}
export function enumToEmissioNCountermeasure(emissionCountermeasure: number) {
if (emissionCountermeasure < emissionsCountermeasures.length)
return emissionsCountermeasures[emissionCountermeasure];
else
return emissionsCountermeasures[0];
}
export function enumToCoalition(coalitionID: number) {
switch (coalitionID){
case 0: return "neutral";
case 1: return "red";
case 2: return "blue";
}
return "";
}

View File

@@ -36,7 +36,7 @@ export class MouseInfoPanel extends Panel {
var selectedUnitPosition = null;
var selectedUnits = getUnitsManager().getSelectedUnits();
if (selectedUnits && selectedUnits.length == 1)
selectedUnitPosition = new LatLng(selectedUnits[0].getFlightData().latitude, selectedUnits[0].getFlightData().longitude);
selectedUnitPosition = new LatLng(selectedUnits[0].getPosition().lat, selectedUnits[0].getPosition().lng);
/* Draw measures from selected unit, from pin location, and from bullseyes */
this.#drawMeasure("ref-measure-position", "measure-position", this.#measurePoint, mousePosition);

View File

@@ -8,6 +8,7 @@ import { Panel } from "./panel";
import { Switch } from "../controls/switch";
import { ROEDescriptions, ROEs, altitudeIncrements, emissionsCountermeasures, emissionsCountermeasuresDescriptions, maxAltitudeValues, maxSpeedValues, minAltitudeValues, minSpeedValues, reactionsToThreat, reactionsToThreatDescriptions, speedIncrements } from "../constants/constants";
import { ftToM, knotsToMs, mToFt, msToKnots } from "../other/utils";
import { GeneralSettings, Radio, TACAN } from "../@types/unit";
export class UnitControlPanel extends Panel {
#altitudeSlider: Slider;
@@ -33,7 +34,8 @@ export class UnitControlPanel extends Panel {
this.#speedTypeSwitch = new Switch("speed-type-switch", (value: boolean) => { getUnitsManager().selectedUnitsSetSpeedType(value? "GS": "CAS"); });
/* Option buttons */
this.#optionButtons["ROE"] = ROEs.map((option: string, index: number) => {
// Reversing the ROEs so that the least "aggressive" option is always on the left
this.#optionButtons["ROE"] = ROEs.slice(0).reverse().map((option: string, index: number) => {
return this.#createOptionButton(option, `roe/${option.toLowerCase()}.svg`, ROEDescriptions[index], () => { getUnitsManager().selectedUnitsSetROE(option); });
});
@@ -98,13 +100,13 @@ export class UnitControlPanel extends Panel {
if (units.length < 20) {
this.getElement().querySelector("#selected-units-container")?.replaceChildren(...units.map((unit: Unit, index: number) => {
var button = document.createElement("button");
var callsign = unit.getBaseData().unitName || "";
var label = unit.getDatabase()?.getByName(unit.getBaseData().name)?.label || unit.getBaseData().name;
var callsign = unit.getUnitName() || "";
var label = unit.getDatabase()?.getByName(unit.getName())?.label || unit.getName();
button.setAttribute("data-label", label);
button.setAttribute("data-callsign", callsign);
button.setAttribute("data-coalition", unit.getMissionData().coalition);
button.setAttribute("data-coalition", unit.getCoalition());
button.classList.add("pill", "highlight-coalition")
button.addEventListener("click", () => {
@@ -139,12 +141,12 @@ export class UnitControlPanel extends Panel {
element.toggleAttribute("data-show-advanced-settings-button", units.length == 1);
/* Flight controls */
var desiredAltitude: number | undefined = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().desiredAltitude});
var desiredAltitudeType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().desiredAltitudeType});
var desiredSpeed = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().desiredSpeed});
var desiredSpeedType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().desiredSpeedType});
var onOff = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().onOff});
var followRoads = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getTaskData().followRoads});
var desiredAltitude: number | undefined = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitude()});
var desiredAltitudeType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredAltitudeType()});
var desiredSpeed = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeed()});
var desiredSpeedType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getDesiredSpeedType()});
var onOff = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getOnOff()});
var followRoads = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getFollowRoads()});
if (selectedUnitsTypes.length == 1) {
this.#altitudeTypeSwitch.setValue(desiredAltitudeType != undefined? desiredAltitudeType == "AGL": undefined, false);
@@ -170,15 +172,15 @@ export class UnitControlPanel extends Panel {
/* Option buttons */
this.#optionButtons["ROE"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().ROE === button.value))
button.classList.toggle("selected", units.every((unit: Unit) => unit.getROE() === button.value))
});
this.#optionButtons["reactionToThreat"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().reactionToThreat === button.value))
button.classList.toggle("selected", units.every((unit: Unit) => unit.getReactionToThreat() === button.value))
});
this.#optionButtons["emissionsCountermeasures"].forEach((button: HTMLButtonElement) => {
button.classList.toggle("selected", units.every((unit: Unit) => unit.getOptionsData().emissionsCountermeasures === button.value))
button.classList.toggle("selected", units.every((unit: Unit) => unit.getEmissionsCountermeasures() === button.value))
});
this.#onOffSwitch.setValue(onOff, false);
@@ -207,11 +209,11 @@ export class UnitControlPanel extends Panel {
const radioCallsignNumberInput = this.#advancedSettingsDialog.querySelector("#radio-callsign-number")?.querySelector("input") as HTMLInputElement;
const unit = units[0];
const roles = aircraftDatabase.getByName(unit.getBaseData().name)?.loadouts.map((loadout) => {return loadout.roles})
const roles = aircraftDatabase.getByName(unit.getName())?.loadouts.map((loadout) => {return loadout.roles})
const tanker = roles != undefined && Array.prototype.concat.apply([], roles)?.includes("Tanker");
const AWACS = roles != undefined && Array.prototype.concat.apply([], roles)?.includes("AWACS");
const radioMHz = Math.floor(unit.getOptionsData().radio.frequency / 1000000);
const radioDecimals = (unit.getOptionsData().radio.frequency / 1000000 - radioMHz) * 1000;
const radioMHz = Math.floor(unit.getRadio().frequency / 1000000);
const radioDecimals = (unit.getRadio().frequency / 1000000 - radioMHz) * 1000;
/* Activate the correct options depending on unit type */
this.#advancedSettingsDialog.toggleAttribute("data-show-settings", !tanker && !AWACS);
@@ -223,28 +225,28 @@ export class UnitControlPanel extends Panel {
/* Set common properties */
// Name
unitNameEl.innerText = unit.getBaseData().unitName;
unitNameEl.innerText = unit.getUnitName();
// General settings
prohibitJettisonCheckbox.checked = unit.getOptionsData().generalSettings.prohibitJettison;
prohibitAfterburnerCheckbox.checked = unit.getOptionsData().generalSettings.prohibitAfterburner;
prohibitAACheckbox.checked = unit.getOptionsData().generalSettings.prohibitAA;
prohibitAGCheckbox.checked = unit.getOptionsData().generalSettings.prohibitAG;
prohibitAirWpnCheckbox.checked = unit.getOptionsData().generalSettings.prohibitAirWpn;
prohibitJettisonCheckbox.checked = unit.getGeneralSettings().prohibitJettison;
prohibitAfterburnerCheckbox.checked = unit.getGeneralSettings().prohibitAfterburner;
prohibitAACheckbox.checked = unit.getGeneralSettings().prohibitAA;
prohibitAGCheckbox.checked = unit.getGeneralSettings().prohibitAG;
prohibitAirWpnCheckbox.checked = unit.getGeneralSettings().prohibitAirWpn;
// Tasking
tankerCheckbox.checked = unit.getTaskData().isTanker;
AWACSCheckbox.checked = unit.getTaskData().isAWACS;
tankerCheckbox.checked = unit.getIsTanker();
AWACSCheckbox.checked = unit.getIsAWACS();
// TACAN
TACANCheckbox.checked = unit.getOptionsData().TACAN.isOn;
TACANChannelInput.value = String(unit.getOptionsData().TACAN.channel);
TACANCallsignInput.value = String(unit.getOptionsData().TACAN.callsign);
this.#TACANXYDropdown.setValue(unit.getOptionsData().TACAN.XY);
TACANCheckbox.checked = unit.getTACAN().isOn;
TACANChannelInput.value = String(unit.getTACAN().channel);
TACANCallsignInput.value = String(unit.getTACAN().callsign);
this.#TACANXYDropdown.setValue(unit.getTACAN().XY);
// Radio
radioMhzInput.value = String(radioMHz);
radioCallsignNumberInput.value = String(unit.getOptionsData().radio.callsignNumber);
radioCallsignNumberInput.value = String(unit.getRadio().callsignNumber);
this.#radioDecimalsDropdown.setValue("." + radioDecimals);
if (tanker) /* Set tanker specific options */
@@ -255,7 +257,7 @@ export class UnitControlPanel extends Panel {
this.#radioCallsignDropdown.setOptions(["Enfield", "Springfield", "Uzi", "Colt", "Dodge", "Ford", "Chevy", "Pontiac"]);
// This must be done after setting the options
if (!this.#radioCallsignDropdown.selectValue(unit.getOptionsData().radio.callsign - 1)) // Ensure the selected value is in the acceptable range
if (!this.#radioCallsignDropdown.selectValue(unit.getRadio().callsign - 1)) // Ensure the selected value is in the acceptable range
this.#radioCallsignDropdown.selectValue(0);
}
}

View File

@@ -1,4 +1,5 @@
import { getUnitsManager } from "..";
import { Ammo } from "../@types/unit";
import { ConvertDDToDMS, rad2deg } from "../other/utils";
import { aircraftDatabase } from "../units/aircraftdatabase";
import { Unit } from "../units/unit";
@@ -51,21 +52,21 @@ export class UnitInfoPanel extends Panel {
#onUnitUpdate(unit: Unit) {
if (this.getElement() != null && this.getVisible() && unit.getSelected()) {
const baseData = unit.getBaseData();
const baseData = unit.getData();
/* Set the unit info */
this.#unitLabel.innerText = aircraftDatabase.getByName(baseData.name)?.label || baseData.name;
this.#unitName.innerText = baseData.unitName;
if (unit.getMissionData().flags.Human)
if (unit.getHuman())
this.#unitControl.innerText = "Human";
else if (baseData.controlled)
this.#unitControl.innerText = "Olympus controlled";
else
this.#unitControl.innerText = "DCS Controlled";
this.#fuelBar.style.width = String(unit.getMissionData().fuel + "%");
this.#fuelPercentage.dataset.percentage = "" + unit.getMissionData().fuel;
this.#currentTask.dataset.currentTask = unit.getTaskData().currentTask !== "" ? unit.getTaskData().currentTask : "No task";
this.#currentTask.dataset.coalition = unit.getMissionData().coalition;
this.#fuelBar.style.width = String(unit.getFuel() + "%");
this.#fuelPercentage.dataset.percentage = "" + unit.getFuel();
this.#currentTask.dataset.currentTask = unit.getTask() !== "" ? unit.getTask() : "No task";
this.#currentTask.dataset.coalition = unit.getCoalition();
this.#silhouette.src = `/images/units/${unit.getDatabase()?.getByName(baseData.name)?.filename}`;
this.#silhouette.classList.toggle("hide", unit.getDatabase()?.getByName(baseData.name)?.filename == undefined || unit.getDatabase()?.getByName(baseData.name)?.filename == '');
@@ -74,13 +75,13 @@ export class UnitInfoPanel extends Panel {
const items = <HTMLElement>this.#loadoutContainer.querySelector("#loadout-items");
if (items) {
const ammo = Object.values(unit.getMissionData().ammo);
const ammo = Object.values(unit.getAmmo());
if (ammo.length > 0) {
items.replaceChildren(...Object.values(unit.getMissionData().ammo).map(
(ammo: any) => {
items.replaceChildren(...Object.values(unit.getAmmo()).map(
(ammo: Ammo) => {
var el = document.createElement("div");
el.dataset.qty = ammo.count;
el.dataset.item = ammo.desc.displayName;
el.dataset.qty = `${ammo.quantity}`;
el.dataset.item = ammo.name;
return el;
}
));

View File

@@ -1,6 +1,8 @@
import { LatLng } from 'leaflet';
import { getConnectionStatusPanel, getInfoPopup, getMissionData, getUnitDataTable, getUnitsManager, setConnectionStatus } from '..';
import { SpawnOptions } from '../controls/mapcontextmenu';
import { GeneralSettings, Radio, TACAN } from '../@types/unit';
import { ROEs, emissionsCountermeasures, reactionsToThreat } from '../constants/constants';
var connected: boolean = false;
var paused: boolean = false;
@@ -37,19 +39,21 @@ export function GET(callback: CallableFunction, uri: string, options?: { time?:
if (options?.time != undefined)
optionsString = `time=${options.time}`;
xmlHttp.open("GET", `${demoEnabled ? DEMO_ADDRESS : REST_ADDRESS}/${uri}${optionsString ? `?${optionsString}` : ''}`, true);
if (credentials)
xmlHttp.setRequestHeader("Authorization", "Basic " + credentials);
if (uri === UNITS_URI)
xmlHttp.responseType = "arraybuffer";
xmlHttp.onload = function (e) {
if (xmlHttp.status == 200) {
var data = JSON.parse(xmlHttp.responseText);
if (uri !== UNITS_URI || (options?.time == 0) || parseInt(data.time) > lastUpdateTime) {
setConnected(true);
if (xmlHttp.responseType == 'arraybuffer')
callback(xmlHttp.response);
else {
var data = JSON.parse(xmlHttp.responseText);
callback(data);
lastUpdateTime = parseInt(data.time);
if (isNaN(lastUpdateTime))
lastUpdateTime = 0;
setConnected(true);
}
} else if (xmlHttp.status == 401) {
console.error("Incorrect username/password");
@@ -95,6 +99,10 @@ export function setAddress(address: string, port: number) {
console.log(`Setting REST address to ${REST_ADDRESS}`)
}
export function setLastUpdateTime(newLastUpdateTime: number) {
lastUpdateTime = newLastUpdateTime;
}
export function getAirbases(callback: CallableFunction) {
GET(callback, AIRBASES_URI);
}
@@ -134,13 +142,13 @@ export function spawnExplosion(intensity: number, latlng: LatLng) {
}
export function spawnGroundUnit(spawnOptions: SpawnOptions) {
var command = { "type": spawnOptions.type, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition };
var command = { "type": spawnOptions.name, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition, "immediate": spawnOptions.immediate? true: false };
var data = { "spawnGround": command }
POST(data, () => { });
}
export function spawnAircraft(spawnOptions: SpawnOptions) {
var command = { "type": spawnOptions.type, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition, "altitude": spawnOptions.altitude, "payloadName": spawnOptions.loadout != null ? spawnOptions.loadout : "", "airbaseName": spawnOptions.airbaseName != null ? spawnOptions.airbaseName : "" };
var command = { "type": spawnOptions.name, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition, "altitude": spawnOptions.altitude, "payloadName": spawnOptions.loadout != null ? spawnOptions.loadout : "", "airbaseName": spawnOptions.airbaseName != null ? spawnOptions.airbaseName : "", "immediate": spawnOptions.immediate? true: false };
var data = { "spawnAir": command }
POST(data, () => { });
}
@@ -222,19 +230,19 @@ export function createFormation(ID: number, isLeader: boolean, wingmenIDs: numbe
}
export function setROE(ID: number, ROE: string) {
var command = { "ID": ID, "ROE": ROE }
var command = { "ID": ID, "ROE": ROEs.indexOf(ROE) }
var data = { "setROE": command }
POST(data, () => { });
}
export function setReactionToThreat(ID: number, reactionToThreat: string) {
var command = { "ID": ID, "reactionToThreat": reactionToThreat }
var command = { "ID": ID, "reactionToThreat": reactionsToThreat.indexOf(reactionToThreat) }
var data = { "setReactionToThreat": command }
POST(data, () => { });
}
export function setEmissionsCountermeasures(ID: number, emissionCountermeasure: string) {
var command = { "ID": ID, "emissionsCountermeasures": emissionCountermeasure }
var command = { "ID": ID, "emissionsCountermeasures": emissionsCountermeasures.indexOf(emissionCountermeasure) }
var data = { "setEmissionsCountermeasures": command }
POST(data, () => { });
}
@@ -299,8 +307,11 @@ export function startUpdate() {
/* On the first connection, force request of full data */
getAirbases((data: AirbasesData) => getMissionData()?.update(data));
getBullseye((data: BullseyesData) => getMissionData()?.update(data));
getMission((data: any) => { getMissionData()?.update(data) });
getUnits((data: UnitsData) => getUnitsManager()?.update(data), true /* Does a full refresh */);
getMission((data: any) => {
getMissionData()?.update(data);
checkSessionHash(data.sessionHash);
});
getUnits((buffer: ArrayBuffer) => getUnitsManager()?.update(buffer), true /* Does a full refresh */);
requestUpdate();
requestRefresh();
@@ -308,10 +319,9 @@ export function startUpdate() {
export function requestUpdate() {
/* Main update rate = 250ms is minimum time, equal to server update time. */
getUnits((data: UnitsData) => {
getUnits((buffer: ArrayBuffer) => {
if (!getPaused()) {
getUnitsManager()?.update(data);
checkSessionHash(data.sessionHash);
getUnitsManager()?.update(buffer);
}
}, false);
window.setTimeout(() => requestUpdate(), getConnected() ? 250 : 1000);
@@ -321,22 +331,19 @@ export function requestUpdate() {
export function requestRefresh() {
/* Main refresh rate = 5000ms. */
getUnits((data: UnitsData) => {
if (!getPaused()) {
getUnitsManager()?.update(data);
getAirbases((data: AirbasesData) => getMissionData()?.update(data));
getBullseye((data: BullseyesData) => getMissionData()?.update(data));
getMission((data: any) => {
getMissionData()?.update(data)
});
// Update the list of existing units
getUnitDataTable()?.update();
if (!getPaused()) {
getAirbases((data: AirbasesData) => getMissionData()?.update(data));
getBullseye((data: BullseyesData) => getMissionData()?.update(data));
getMission((data: any) => {
checkSessionHash(data.sessionHash);
}
}, true);
window.setTimeout(() => requestRefresh(), 5000);
getMissionData()?.update(data)
});
// Update the list of existing units
getUnitDataTable()?.update();
}
window.setTimeout(() => requestRefresh(), 1000);
}
export function checkSessionHash(newSessionHash: string) {

View File

@@ -0,0 +1,150 @@
import { LatLng } from "leaflet";
import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN } from "../@types/unit";
export class DataExtractor {
#seekPosition = 0;
#dataview: DataView;
#decoder: TextDecoder;
#buffer: ArrayBuffer;
constructor(buffer: ArrayBuffer) {
this.#buffer = buffer;
this.#dataview = new DataView(this.#buffer);
this.#decoder = new TextDecoder("utf-8");
}
getSeekPosition() {
return this.#seekPosition;
}
extractBool() {
const value = this.#dataview.getUint8(this.#seekPosition);
this.#seekPosition += 1;
return value > 0;
}
extractUInt8() {
const value = this.#dataview.getUint8(this.#seekPosition);
this.#seekPosition += 1;
return value;
}
extractUInt16() {
const value = this.#dataview.getUint16(this.#seekPosition, true);
this.#seekPosition += 2;
return value;
}
extractUInt32() {
const value = this.#dataview.getUint32(this.#seekPosition, true);
this.#seekPosition += 4;
return value;
}
extractUInt64() {
const value = this.#dataview.getBigUint64(this.#seekPosition, true);
this.#seekPosition += 8;
return value;
}
extractFloat64() {
const value = this.#dataview.getFloat64(this.#seekPosition, true);
this.#seekPosition += 8;
return value;
}
extractLatLng() {
return new LatLng(this.extractFloat64(), this.extractFloat64(), this.extractFloat64())
}
extractFromBitmask(bitmask: number, position: number) {
return ((bitmask >> position) & 1) > 0;
}
extractString(length?: number) {
if (length === undefined)
length = this.extractUInt16()
const value = this.#decoder.decode(this.#buffer.slice(this.#seekPosition, this.#seekPosition + length));
this.#seekPosition += length;
return value;
}
extractChar() {
return this.extractString(1);
}
extractTACAN() {
const value: TACAN = {
isOn: this.extractBool(),
channel: this.extractUInt8(),
XY: this.extractChar(),
callsign: this.extractString(4)
}
return value;
}
extractRadio() {
const value: Radio = {
frequency: this.extractUInt32(),
callsign: this.extractUInt8(),
callsignNumber: this.extractUInt8()
}
return value;
}
extractGeneralSettings() {
const value: GeneralSettings = {
prohibitJettison: this.extractBool(),
prohibitAA: this.extractBool(),
prohibitAG: this.extractBool(),
prohibitAfterburner: this.extractBool(),
prohibitAirWpn: this.extractBool(),
}
return value;
}
extractAmmo() {
const value: Ammo[] = [];
const size = this.extractUInt16();
for (let idx = 0; idx < size; idx++) {
value.push({
quantity: this.extractUInt16(),
name: this.extractString(33),
guidance: this.extractUInt8(),
category: this.extractUInt8(),
missileCategory: this.extractUInt8()
});
}
return value;
}
extractContacts(){
const value: Contact[] = [];
const size = this.extractUInt16();
for (let idx = 0; idx < size; idx++) {
value.push({
ID: this.extractUInt32(),
detectionMethod: this.extractUInt8()
});
}
return value;
}
extractActivePath() {
const value: LatLng[] = [];
const size = this.extractUInt16();
for (let idx = 0; idx < size; idx++) {
value.push(this.extractLatLng());
}
return value;
}
extractOffset() {
const value: Offset = {
x: this.extractFloat64(),
y: this.extractFloat64(),
z: this.extractFloat64(),
}
return value;
}
}

View File

@@ -16,7 +16,7 @@ export class GroundUnitsDatabase extends UnitDatabase {
"items": [
],
"roles": [
"Template"
"SAM Sites"
],
"code": "",
"name": "Default"
@@ -35,7 +35,7 @@ export class GroundUnitsDatabase extends UnitDatabase {
"fuel": 1,
"items": [],
"roles": [
"Template"
"SAM Sites"
],
"code": "",
"name": "Default"
@@ -54,7 +54,7 @@ export class GroundUnitsDatabase extends UnitDatabase {
"fuel": 1,
"items": [],
"roles": [
"Template"
"SAM Sites"
],
"code": "",
"name": "Default"
@@ -73,7 +73,7 @@ export class GroundUnitsDatabase extends UnitDatabase {
"fuel": 1,
"items": [],
"roles": [
"Template"
"SAM Sites"
],
"code": "",
"name": "Default"
@@ -92,7 +92,7 @@ export class GroundUnitsDatabase extends UnitDatabase {
"fuel": 1,
"items": [],
"roles": [
"Template"
"SAM Sites"
],
"code": "",
"name": "Default"
@@ -111,7 +111,7 @@ export class GroundUnitsDatabase extends UnitDatabase {
"fuel": 1,
"items": [],
"roles": [
"Template"
"SAM Sites"
],
"code": "",
"name": "Default"
@@ -130,7 +130,7 @@ export class GroundUnitsDatabase extends UnitDatabase {
"fuel": 1,
"items": [],
"roles": [
"Template"
"SAM Sites"
],
"code": "",
"name": "Default"
@@ -1630,7 +1630,7 @@ export class GroundUnitsDatabase extends UnitDatabase {
}
],
"roles": [
"SAM"
"MANPADS"
],
"code": "",
"name": "Default"
@@ -1655,7 +1655,7 @@ export class GroundUnitsDatabase extends UnitDatabase {
}
],
"roles": [
"SAM"
"MANPADS"
],
"code": "",
"name": "Default"
@@ -1680,7 +1680,7 @@ export class GroundUnitsDatabase extends UnitDatabase {
}
],
"roles": [
"SAM"
"MANPADS"
],
"code": "",
"name": "Default"
@@ -1989,7 +1989,7 @@ export class GroundUnitsDatabase extends UnitDatabase {
}
],
"roles": [
"SAM"
"MANPADS"
],
"code": "",
"name": "Default"
@@ -2039,7 +2039,7 @@ export class GroundUnitsDatabase extends UnitDatabase {
}
],
"roles": [
"SAM"
"MANPADS"
],
"code": "",
"name": "Default"

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,13 @@
import { LatLng, LatLngBounds } from "leaflet";
import { getHotgroupPanel, getInfoPopup, getMap, getUnitDataTable } from "..";
import { getHotgroupPanel, getInfoPopup, getMap, getMissionHandler } from "..";
import { Unit } from "./unit";
import { cloneUnit } from "../server/server";
import { deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots } from "../other/utils";
import { IDLE, MOVE_UNIT } from "../map/map";
import { cloneUnit, setLastUpdateTime, spawnGroundUnit } from "../server/server";
import { deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots, polygonArea, randomPointInPoly, randomUnitBlueprintByRole } from "../other/utils";
import { CoalitionArea } from "../map/coalitionarea";
import { Airbase } from "../missionhandler/airbase";
import { groundUnitsDatabase } from "./groundunitsdatabase";
import { DataIndexes, IADSRoles, IDLE, MOVE_UNIT } from "../constants/constants";
import { DataExtractor } from "./dataextractor";
export class UnitsManager {
#units: { [ID: number]: Unit };
@@ -28,8 +32,7 @@ export class UnitsManager {
getSelectableAircraft() {
const units = this.getUnits();
return Object.keys(units).reduce((acc: { [key: number]: Unit }, unitId: any) => {
const baseData = units[unitId].getBaseData();
if (baseData.category === "Aircraft" && baseData.alive === true) {
if (units[unitId].getCategory() === "Aircraft" && units[unitId].getAlive() === true) {
acc[unitId] = units[unitId];
}
return acc;
@@ -48,15 +51,15 @@ 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.getAlive() && unit.getHotgroup() == hotgroup });
}
addUnit(ID: number, data: UnitData) {
if (data.baseData && data.baseData.category){
/* The name of the unit category is exactly the same as the constructor name */
var constructor = Unit.getConstructor(data.baseData.category);
addUnit(ID: number, category: string) {
if (category){
/* The name of the unit category is exactly the same as the constructor name */
var constructor = Unit.getConstructor(category);
if (constructor != undefined) {
this.#units[ID] = new constructor(ID, data);
this.#units[ID] = new constructor(ID);
}
}
}
@@ -65,30 +68,26 @@ 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) => {
window.setTimeout(() => {
if (!(ID in this.#units))
this.addUnit(parseInt(ID), data.units[ID]);
this.#units[parseInt(ID)]?.setData(data.units[ID]);
}, timeout);
return timeout + 10;
}, 10);
update(buffer: ArrayBuffer) {
var dataExtractor = new DataExtractor(buffer);
var updateTime = Number(dataExtractor.extractUInt64());
Object.keys(data.units)
.filter((ID: string) => ID in this.#units)
.forEach((ID: string) => {
updatedUnits.push(this.#units[parseInt(ID)]);
this.#units[parseInt(ID)]?.setData(data.units[ID])
});
while (dataExtractor.getSeekPosition() < buffer.byteLength) {
const ID = dataExtractor.extractUInt32();
if (!(ID in this.#units)) {
const datumIndex = dataExtractor.extractUInt8();
if (datumIndex == DataIndexes.category) {
const category = dataExtractor.extractString();
this.addUnit(ID, category);
}
else {
// TODO request a refresh since we must have missed some packets
}
}
this.#units[ID]?.setData(dataExtractor);
}
this.getSelectedUnits().forEach((unit: Unit) => {
if (!updatedUnits.includes(unit))
unit.setData({})
});
setLastUpdateTime(updateTime);
}
setHiddenType(key: string, value: boolean) {
@@ -115,7 +114,7 @@ export class UnitsManager {
this.deselectAllUnits();
for (let ID in this.#units) {
if (this.#units[ID].getHidden() == false) {
var latlng = new LatLng(this.#units[ID].getFlightData().latitude, this.#units[ID].getFlightData().longitude);
var latlng = new LatLng(this.#units[ID].getPosition().lat, this.#units[ID].getPosition().lng);
if (bounds.contains(latlng)) {
this.#units[ID].setSelected(true);
}
@@ -132,11 +131,11 @@ 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.getHuman() });
if (options.onlyOnePerGroup) {
var temp: Unit[] = [];
for (let unit of selectedUnits) {
if (!temp.some((otherUnit: Unit) => unit.getBaseData().groupName == otherUnit.getBaseData().groupName))
if (!temp.some((otherUnit: Unit) => unit.getGroupName() == otherUnit.getGroupName()))
temp.push(unit);
}
selectedUnits = temp;
@@ -181,7 +180,7 @@ export class UnitsManager {
if (this.getSelectedUnits().length == 0)
return undefined;
return this.getSelectedUnits().map((unit: Unit) => {
return unit.getMissionData().coalition
return unit.getCoalition()
})?.reduce((a: any, b: any) => {
return a == b ? a : undefined
});
@@ -201,8 +200,8 @@ export class UnitsManager {
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 */
if (unit.getTaskData().currentState === "Follow") {
const leader = this.getUnitByID(unit.getFormationData().leaderID)
if (unit.getState() === "Follow") {
const leader = this.getUnitByID(unit.getLeaderID())
if (leader && leader.getSelected())
leader.addDestination(latlng);
else
@@ -221,8 +220,8 @@ export class UnitsManager {
var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true });
for (let idx in selectedUnits) {
const unit = selectedUnits[idx];
if (unit.getTaskData().currentState === "Follow") {
const leader = this.getUnitByID(unit.getFormationData().leaderID)
if (unit.getState() === "Follow") {
const leader = this.getUnitByID(unit.getLeaderID())
if (leader && leader.getSelected())
leader.clearDestinations();
else
@@ -333,13 +332,13 @@ export class UnitsManager {
for (let idx in selectedUnits) {
selectedUnits[idx].attackUnit(ID);
}
this.#showActionMessage(selectedUnits, `attacking unit ${this.getUnitByID(ID)?.getBaseData().unitName}`);
this.#showActionMessage(selectedUnits, `attacking unit ${this.getUnitByID(ID)?.getUnitName()}`);
}
selectedUnitsDelete(explosion: boolean = false) {
var selectedUnits = this.getSelectedUnits(); /* Can be applied to humans too */
const selectionContainsAHuman = selectedUnits.some( ( unit:Unit ) => {
return unit.getMissionData().flags.Human === true;
return unit.getHuman() === true;
});
if (selectionContainsAHuman && !confirm( "Your selection includes a human player. Deleting humans causes their vehicle to crash.\n\nAre you sure you want to do this?" ) ) {
@@ -400,7 +399,7 @@ export class UnitsManager {
}
count++;
}
this.#showActionMessage(selectedUnits, `following unit ${this.getUnitByID(ID)?.getBaseData().unitName}`);
this.#showActionMessage(selectedUnits, `following unit ${this.getUnitByID(ID)?.getUnitName()}`);
}
selectedUnitsSetHotgroup(hotgroup: number) {
@@ -422,7 +421,7 @@ export class UnitsManager {
/* 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);
var mercator = latLngToMercator(unit.getPosition().lat, unit.getPosition().lng);
center.x += mercator.x / selectedUnits.length;
center.y += mercator.y / selectedUnits.length;
});
@@ -430,7 +429,7 @@ export class UnitsManager {
/* 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 mercator = latLngToMercator(unit.getPosition().lat, unit.getPosition().lng);
var distancesFromCenter = { dx: mercator.x - center.x, dy: mercator.y - center.y };
/* Rotate the distance according to the group rotation */
@@ -490,7 +489,7 @@ export class UnitsManager {
if (!this.#pasteDisabled) {
for (let idx in this.#copiedUnits) {
var unit = this.#copiedUnits[idx];
getMap().addTemporaryMarker(getMap().getMouseCoordinates());
//getMap().addTemporaryMarker(getMap().getMouseCoordinates());
cloneUnit(unit.ID, getMap().getMouseCoordinates());
this.#showActionMessage(this.#copiedUnits, `pasted`);
}
@@ -499,6 +498,31 @@ export class UnitsManager {
}
}
createIADS(coalitionArea: CoalitionArea, options: {[key: string]: boolean}, density: number) {
const activeRoles = Object.keys(options).filter((key: string) => { return options[key]; });
const airbases = getMissionHandler().getAirbases();
const pointsNumber = polygonArea(coalitionArea) / 1e7 * density / 100;
for (let i = 0; i < pointsNumber; i++) {
const latlng = randomPointInPoly(coalitionArea);
var minDistance: number = Infinity;
var maxDistance: number = 0;
Object.values(airbases).forEach((airbase: Airbase) => {
var distance = airbase.getLatLng().distanceTo(latlng);
if (distance < minDistance) minDistance = distance;
if (distance > maxDistance) maxDistance = distance;
});
const role = activeRoles[Math.floor(Math.random() * activeRoles.length)];
const probability = Math.pow(1 - minDistance / 50e3, 5) * IADSRoles[role];
if (Math.random() < probability){
const unitBlueprint = randomUnitBlueprintByRole(groundUnitsDatabase, role);
const spawnOptions = {role: role, latlng: latlng, name: unitBlueprint.name, coalition: coalitionArea.getCoalition(), immediate: true};
spawnGroundUnit(spawnOptions);
getMap().addTemporaryMarker(spawnOptions);
}
}
}
/***********************************************/
#onKeyUp(event: KeyboardEvent) {
if (!keyEventWasInInput(event) && event.key === "Delete" ) {
@@ -535,8 +559,8 @@ export class UnitsManager {
#showActionMessage(units: Unit[], message: string) {
if (units.length == 1)
getInfoPopup().setText(`${units[0].getBaseData().unitName} ${message}`);
getInfoPopup().setText(`${units[0].getUnitName()} ${message}`);
else if (units.length > 1)
getInfoPopup().setText(`${units[0].getBaseData().unitName} and ${units.length - 1} other units ${message}`);
getInfoPopup().setText(`${units[0].getUnitName()} and ${units.length - 1} other units ${message}`);
}
}