From ad3b1cb1671faceb247199cb4ce3810a362ea348 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Fri, 16 Jun 2023 17:32:09 +0200 Subject: [PATCH 01/15] Added basic ability to draw poligons --- client/copy.bat | 1 + client/package-lock.json | 11 ++ client/package.json | 1 + client/public/javascripts/L.Path.Drag.js | 6 + client/public/stylesheets/olympus.css | 86 +++++++++++- .../public/stylesheets/other/contextmenus.css | 130 ++++++++---------- .../src/controls/coalitionareacontextmenu.ts | 71 ++++++++++ client/src/controls/dropdown.ts | 5 + client/src/controls/mapcontextmenu.ts | 4 +- client/src/map/coalitionarea.ts | 20 +++ client/src/map/map.ts | 45 +++++- client/src/units/groundunitsdatabase.ts | 14 +- client/views/other/contextmenus.ejs | 74 +++++++--- client/views/panels/navbar.ejs | 20 +-- 14 files changed, 375 insertions(+), 113 deletions(-) create mode 100644 client/public/javascripts/L.Path.Drag.js create mode 100644 client/src/controls/coalitionareacontextmenu.ts create mode 100644 client/src/map/coalitionarea.ts diff --git a/client/copy.bat b/client/copy.bat index f163f70e..cacd14c4 100644 --- a/client/copy.bat +++ b/client/copy.bat @@ -1,2 +1,3 @@ copy .\\node_modules\\leaflet\\dist\\leaflet.css .\\public\\stylesheets\\leaflet\\leaflet.css copy .\\node_modules\\leaflet.nauticscale\\dist\\leaflet.nauticscale.js .\\public\\javascripts\\leaflet.nauticscale.js +copy .\\node_modules\\leaflet-path-drag\\dist\\L.Path.Drag.js .\\public\\javascripts\\L.Path.Drag.js \ No newline at end of file diff --git a/client/package-lock.json b/client/package-lock.json index 2fc981a0..4907e9c1 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -19,6 +19,7 @@ "formatcoords": "^1.1.3", "leaflet": "^1.9.3", "leaflet-control-mini-map": "^0.4.0", + "leaflet-path-drag": "*", "leaflet.nauticscale": "^1.1.0", "morgan": "~1.9.1", "save": "^2.9.0" @@ -4060,6 +4061,11 @@ "leaflet": ">=1.7.0" } }, + "node_modules/leaflet-path-drag": { + "version": "1.8.0-beta.3", + "resolved": "https://registry.npmjs.org/leaflet-path-drag/-/leaflet-path-drag-1.8.0-beta.3.tgz", + "integrity": "sha512-kpZ6sPOKlR+m+VChIzZZ7XFH4C+VGTrAxgnM4UN5iYl7lJ00iDOxS+r717bDf/xFFHB6n2jpUp9vvzjjteMpeQ==" + }, "node_modules/leaflet.nauticscale": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/leaflet.nauticscale/-/leaflet.nauticscale-1.1.0.tgz", @@ -8764,6 +8770,11 @@ "integrity": "sha512-qnetYIYFqlrAbX7MaGsAkBsuA2fg3Z4xDRlEXJN1wSrnhNsIhQUzXt7Z3vapdEzx4r7VUqWTaqHYd7eY5C6Gfw==", "requires": {} }, + "leaflet-path-drag": { + "version": "1.8.0-beta.3", + "resolved": "https://registry.npmjs.org/leaflet-path-drag/-/leaflet-path-drag-1.8.0-beta.3.tgz", + "integrity": "sha512-kpZ6sPOKlR+m+VChIzZZ7XFH4C+VGTrAxgnM4UN5iYl7lJ00iDOxS+r717bDf/xFFHB6n2jpUp9vvzjjteMpeQ==" + }, "leaflet.nauticscale": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/leaflet.nauticscale/-/leaflet.nauticscale-1.1.0.tgz", diff --git a/client/package.json b/client/package.json index 5030d648..9385bdc3 100644 --- a/client/package.json +++ b/client/package.json @@ -21,6 +21,7 @@ "formatcoords": "^1.1.3", "leaflet": "^1.9.3", "leaflet-control-mini-map": "^0.4.0", + "leaflet-path-drag": "*", "leaflet.nauticscale": "^1.1.0", "morgan": "~1.9.1", "save": "^2.9.0" diff --git a/client/public/javascripts/L.Path.Drag.js b/client/public/javascripts/L.Path.Drag.js new file mode 100644 index 00000000..b6dec124 --- /dev/null +++ b/client/public/javascripts/L.Path.Drag.js @@ -0,0 +1,6 @@ +/** + * Leaflet vector features drag functionality + * @author Alexander Milevski + * @preserve + */ +L.Path.include({_transform:function(t){if(this._renderer){if(t){this._renderer.transformPath(this,t)}else{this._renderer._resetTransformPath(this);this._update()}}return this},_onMouseClick:function(t){if(this.dragging&&this.dragging.moved()||this._map.dragging&&this._map.dragging.moved()){return}this._fireMouseEvent(t)}});var END={mousedown:"mouseup",touchstart:"touchend",pointerdown:"touchend",MSPointerDown:"touchend"};var MOVE={mousedown:"mousemove",touchstart:"touchmove",pointerdown:"touchmove",MSPointerDown:"touchmove"};function distance(t,a){var i=t.x-a.x,n=t.y-a.y;return Math.sqrt(i*i+n*n)}L.Handler.PathDrag=L.Handler.extend({statics:{DRAGGING_CLS:"leaflet-path-draggable"},initialize:function(t){this._path=t;this._matrix=null;this._startPoint=null;this._dragStartPoint=null;this._mapDraggingWasEnabled=false;this._path._dragMoved=false},addHooks:function(){this._path.on("mousedown",this._onDragStart,this);this._path.options.className=this._path.options.className?this._path.options.className+" "+L.Handler.PathDrag.DRAGGING_CLS:L.Handler.PathDrag.DRAGGING_CLS;if(this._path._path){L.DomUtil.addClass(this._path._path,L.Handler.PathDrag.DRAGGING_CLS)}},removeHooks:function(){this._path.off("mousedown",this._onDragStart,this);this._path.options.className=this._path.options.className.replace(new RegExp("\\s+"+L.Handler.PathDrag.DRAGGING_CLS),"");if(this._path._path){L.DomUtil.removeClass(this._path._path,L.Handler.PathDrag.DRAGGING_CLS)}},moved:function(){return this._path._dragMoved},_onDragStart:function(t){var a=t.originalEvent._simulated?"touchstart":t.originalEvent.type;this._mapDraggingWasEnabled=false;this._startPoint=t.containerPoint.clone();this._dragStartPoint=t.containerPoint.clone();this._matrix=[1,0,0,1,0,0];L.DomEvent.stop(t.originalEvent);L.DomUtil.addClass(this._path._renderer._container,"leaflet-interactive");L.DomEvent.on(document,MOVE[a],this._onDrag,this).on(document,END[a],this._onDragEnd,this);if(this._path._map.dragging.enabled()){this._path._map.dragging.disable();this._mapDraggingWasEnabled=true}this._path._dragMoved=false;if(this._path._popup){this._path._popup.close()}this._replaceCoordGetters(t)},_onDrag:function(t){L.DomEvent.stop(t);var a=t.touches&&t.touches.length>=1?t.touches[0]:t;var i=this._path._map.mouseEventToContainerPoint(a);if(t.type==="touchmove"&&!this._path._dragMoved){var n=this._dragStartPoint.distanceTo(i);if(n<=this._path._map.options.tapTolerance){return}}var e=i.x;var r=i.y;var s=e-this._startPoint.x;var o=r-this._startPoint.y;if(s||o){if(!this._path._dragMoved){this._path._dragMoved=true;this._path.options.interactive=false;this._path._map.dragging._draggable._moved=true;this._path.fire("dragstart",t);this._path.bringToFront()}this._matrix[4]+=s;this._matrix[5]+=o;this._startPoint.x=e;this._startPoint.y=r;this._path.fire("predrag",t);this._path._transform(this._matrix);this._path.fire("drag",t)}},_onDragEnd:function(t){var a=this._path._map.mouseEventToContainerPoint(t);var i=this.moved();if(i){this._transformPoints(this._matrix);this._path._updatePath();this._path._project();this._path._transform(null);L.DomEvent.stop(t)}L.DomEvent.off(document,"mousemove touchmove",this._onDrag,this);L.DomEvent.off(document,"mouseup touchend",this._onDragEnd,this);this._restoreCoordGetters();if(i){this._path.fire("dragend",{distance:distance(this._dragStartPoint,a)});var n=this._path._containsPoint;this._path._containsPoint=L.Util.falseFn;L.Util.requestAnimFrame(function(){this._path._dragMoved=false;this._path.options.interactive=true;this._path._containsPoint=n},this)}if(this._mapDraggingWasEnabled){this._path._map.dragging.enable()}},_transformPoints:function(t,a){var i=this._path;var n,e,r;var s=L.point(t[4],t[5]);var o=i._map.options.crs;var h=o.transformation;var _=o.scale(i._map.getZoom());var g=o.projection;var d=h.untransform(s,_).subtract(h.untransform(L.point(0,0),_));var p=!a;i._bounds=new L.LatLngBounds;if(i._point){a=g.unproject(g.project(i._latlng)._add(d));if(p){i._latlng=a;i._point._add(s)}}else if(i._rings||i._parts){var l=i._rings||i._parts;var f=i._latlngs;a=a||f;if(!L.Util.isArray(f[0])){f=[f];a=[a]}for(n=0,e=l.length;n :last-child { .ol-panel .ol-group-button-toggle { align-items: center; - column-gap: 15px; display: flex; flex-wrap: nowrap; white-space: nowrap; @@ -421,7 +420,7 @@ nav.ol-panel> :last-child { border: 0; display: flex; justify-items: left; - text-indent: 5px; + text-indent: 2px; } .ol-panel .ol-group-button-toggle button::before { @@ -1134,4 +1133,87 @@ input[type=number]::-webkit-outer-spin-button { .ol-switch[data-value="undefined"]>.ol-switch-fill::after { transform: translateX(calc((var(--width) - var(--height)) * 0.5)); +} + +.ol-contexmenu-panel { + padding: 20px; +} + +.ol-coalition-switch[data-value="false"]>.ol-switch-fill { + background-color: var(--primary-blue); +} + +.ol-coalition-switch[data-value="true"]>.ol-switch-fill { + background-color: var(--primary-red); +} + +.ol-coalition-switch[data-value="undefined"]>.ol-switch-fill { + background-color: var(--primary-neutral); +} + +.ol-context-menu>div:nth-child(2) { + align-items: center; + display: flex; + flex-direction: row; + justify-content: space-between; + padding-right: 0px; +} + +.ol-context-menu>ul { + max-height: 200px; + overflow-x: hidden; + overflow-y: auto; +} + +.ol-context-menu .ol-panel { + border-radius: var(--border-radius-sm); + width: 100%; +} + +.ol-context-menu ul { + margin: 0px; +} + +.ol-context-menu>div:nth-child(n+3) { + align-items: center; + display: flex; + flex-direction: column; + justify-content: space-between; + row-gap: 5px; +} + +.ol-context-menu .ol-select-container { + align-self: stretch; + flex: 0 0 auto; + width: 100%; +} + + +.ol-contexmenu-button { + border: none; + border-radius: 0px; + height: 48px; + margin-bottom: -10px; + margin-top: -10px; + width: 48px; +} + +.ol-contexmenu-button:last-of-type { + border-bottom-right-radius: var(--border-radius-sm); + border-top-right-radius: var(--border-radius-sm); +} + +[data-active-coalition="blue"].ol-contexmenu-button:hover, +[data-active-coalition="blue"].ol-contexmenu-button.is-open { + background-color: var(--primary-blue) +} + +[data-active-coalition="red"].ol-contexmenu-button:hover, +[data-active-coalition="red"].ol-contexmenu-button.is-open { + background-color: var(--primary-red) +} + +[data-active-coalition="neutral"].ol-contexmenu-button:hover, +[data-active-coalition="neutral"].ol-contexmenu-button.is-open { + background-color: var(--primary-neutral) } \ No newline at end of file diff --git a/client/public/stylesheets/other/contextmenus.css b/client/public/stylesheets/other/contextmenus.css index 9f50a4d4..f6ecc8a4 100644 --- a/client/public/stylesheets/other/contextmenus.css +++ b/client/public/stylesheets/other/contextmenus.css @@ -35,61 +35,13 @@ width: 50px; } -#coalition-switch[data-value="false"]>.ol-switch-fill { - background-color: var(--primary-blue); -} - -#coalition-switch[data-value="true"]>.ol-switch-fill { - background-color: var(--primary-red); -} - -#coalition-switch[data-value="undefined"]>.ol-switch-fill { - background-color: var(--primary-neutral); -} - -#map-contextmenu>div:nth-child(2) { - align-items: center; - display: flex; - flex-direction: row; - justify-content: space-between; - padding-right: 0px; -} - -#map-contextmenu>ul { - max-height: 200px; - overflow-x: hidden; - overflow-y: auto; -} - -#map-contextmenu .ol-panel { - border-radius: var(--border-radius-sm); - width: 100%; -} - -#map-contextmenu ul { - margin: 0px; -} - -#map-contextmenu>div:nth-child(n+3) { - align-items: center; - display: flex; - flex-direction: column; - justify-content: space-between; - row-gap: 5px; -} - -#map-contextmenu .ol-select-container { - align-self: stretch; - flex: 0 0 auto; - width: 100%; -} - #aircraft-spawn-menu .ol-select.is-open .ol-select-options { max-height: 300px; } #aircraft-spawn-menu>button, -#ground-unit-spawn-menu>button { +#ground-unit-spawn-menu>button, +#iads-menu>button { text-align: center; width: 100%; } @@ -99,7 +51,7 @@ background-size: 48px; } -#ground-unit-spawn-button { +#ground-ol-contexmenu-button { background-image: url("/resources/theme/images/buttons/spawn/ground.svg"); background-size: 48px; } @@ -114,41 +66,24 @@ background-size: 48px; } -.unit-spawn-button { - border: none; - border-radius: 0px; - height: 48px; - margin-bottom: -10px; - margin-top: -10px; - width: 48px; -} - -.unit-spawn-button:last-of-type { - border-bottom-right-radius: var(--border-radius-sm); - border-top-right-radius: var(--border-radius-sm); -} - -[data-active-coalition="blue"].unit-spawn-button:hover, -[data-active-coalition="blue"].unit-spawn-button.is-open, [data-active-coalition="blue"]#active-coalition-label, [data-active-coalition="blue"].deploy-unit-button, -[data-active-coalition="blue"]#spawn-airbase-aircraft-button { +[data-active-coalition="blue"]#spawn-airbase-aircraft-button, +[data-active-coalition="blue"].create-iads-button { background-color: var(--primary-blue) } -[data-active-coalition="red"].unit-spawn-button:hover, -[data-active-coalition="red"].unit-spawn-button.is-open, [data-active-coalition="red"]#active-coalition-label, [data-active-coalition="red"].deploy-unit-button, -[data-active-coalition="red"]#spawn-airbase-aircraft-button { +[data-active-coalition="red"]#spawn-airbase-aircraft-button, +[data-active-coalition="red"].create-iads-button { background-color: var(--primary-red) } -[data-active-coalition="neutral"].unit-spawn-button:hover, -[data-active-coalition="neutral"].unit-spawn-button.is-open, [data-active-coalition="neutral"]#active-coalition-label, [data-active-coalition="neutral"].deploy-unit-button, -[data-active-coalition="neutral"]#spawn-airbase-aircraft-button { +[data-active-coalition="neutral"]#spawn-airbase-aircraft-button, +[data-active-coalition="neutral"].create-iads-button { background-color: var(--primary-neutral) } @@ -407,3 +342,50 @@ width: 180px; z-index: 9999; } + +/* Coalition area context menu */ +#coalition-area-contextmenu { + display: flex; + flex-direction: column; + height: fit-content; + position: absolute; + row-gap: 5px; + width: 225px; + z-index: 9999; +} + +#coalition-area-switch { + margin-right: 10px; + height: 25px; + width: 50px; +} + +#iads-button { + background-image: url("/resources/theme/images/buttons/spawn/aircraft.svg"); + background-size: 48px; +} + +#coalition-area-contextmenu .ol-checkbox { + align-self: flex-start; +} + +#iads-menu .ol-select-options>* { + padding-top: 8px; + padding-bottom: 8px; +} + +#iads-menu .ol-select-options>*:first-child { + padding-top: 15px; +} + +#iads-menu .ol-select-options>*:last-child { + padding-bottom: 15px; +} + +#iads-menu .ol-select { + width: 100%; +} + +#iads-menu { + row-gap: 10px; +} diff --git a/client/src/controls/coalitionareacontextmenu.ts b/client/src/controls/coalitionareacontextmenu.ts new file mode 100644 index 00000000..b4985950 --- /dev/null +++ b/client/src/controls/coalitionareacontextmenu.ts @@ -0,0 +1,71 @@ +import { CoalitionArea } from "../map/coalitionarea"; +import { groundUnitsDatabase } from "../units/groundunitsdatabase"; +import { ContextMenu } from "./contextmenu"; +import { Dropdown } from "./dropdown"; +import { Slider } from "./slider"; +import { Switch } from "./switch"; + +const unitRole = ["AAA", "MANPADS", "Short range SAM", "Long range SAM", "Radar"]; + +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) => { + this.showSubMenu(e.detail.type); + }); + + /* Create the checkboxes to select the unit roles */ + this.#iadsRoleDropdown.setOptionsElements(unitRole.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"; + 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(); + } + + getCoalitionArea() { + return this.#coalitionArea; + } + + setCoalitionArea(coalitionArea: CoalitionArea) { + this.#coalitionArea = coalitionArea; + } + + #onSwitchClick(value: boolean) { + this.getCoalitionArea()?.setCoalition(value? "red": "blue"); + } +} \ No newline at end of file diff --git a/client/src/controls/dropdown.ts b/client/src/controls/dropdown.ts index 2877d80d..66c2a09e 100644 --- a/client/src/controls/dropdown.ts +++ b/client/src/controls/dropdown.ts @@ -50,6 +50,11 @@ export class Dropdown { })); } + setOptionsElements(optionsElements: HTMLElement[]) { + this.#optionsList = []; + this.#options.replaceChildren(...optionsElements); + } + selectText(text: string) { const index = [].slice.call(this.#options.children).findIndex((opt: Element) => opt.querySelector("button")?.innerText === text); if (index > -1) { diff --git a/client/src/controls/mapcontextmenu.ts b/client/src/controls/mapcontextmenu.ts index 51027f01..84f20333 100644 --- a/client/src/controls/mapcontextmenu.ts +++ b/client/src/controls/mapcontextmenu.ts @@ -45,7 +45,7 @@ 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) => { + document.addEventListener("mapContextMenuShow", (e: any) => { this.showSubMenu(e.detail.type); }); @@ -92,7 +92,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"); diff --git a/client/src/map/coalitionarea.ts b/client/src/map/coalitionarea.ts new file mode 100644 index 00000000..f3ffee35 --- /dev/null +++ b/client/src/map/coalitionarea.ts @@ -0,0 +1,20 @@ +import { LatLngExpression, Polygon, PolylineOptions } from "leaflet"; +import { getMap } from ".."; + +export class CoalitionArea extends Polygon { + constructor(latlngs: LatLngExpression[] | LatLngExpression[][] | LatLngExpression[][][], options?: PolylineOptions) { + if (options === undefined) + options = {}; + + options.bubblingMouseEvents = false; + super(latlngs, options); + + this.on("contextmenu", (e: any) => { + getMap().showCoalitionAreaContextMenu(e, this); + }) + } + + setCoalition(coalition: string) { + this.setStyle({color: coalition, fillColor: coalition}); + } +} \ No newline at end of file diff --git a/client/src/map/map.ts b/client/src/map/map.ts index 04b85d28..d19d5327 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -14,11 +14,14 @@ import { ClickableMiniMap } from "./clickableminimap"; import { SVGInjector } from '@tanem/svg-injector' import { layers as mapLayers, mapBounds, minimapBoundaries } from "../constants/constants"; import { TargetMarker } from "./targetmarker"; +import { CoalitionArea } from "./coalitionarea"; +import { CoalitionAreaContextMenu } from "../controls/coalitionareacontextmenu"; L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect); // TODO would be nice to convert to ts require("../../public/javascripts/leaflet.nauticscale.js") +require("../../public/javascripts/L.Path.Drag.js") /* Map constants */ export const IDLE = "Idle"; @@ -26,6 +29,7 @@ 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 DRAW_POLYGON = "Draw polygon"; 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"]; @@ -51,10 +55,12 @@ export class Map extends L.Map { #destinationGroupRotation: number = 0; #computeDestinationRotation: boolean = false; #destinationRotationCenter: L.LatLng | null = null; - + #polygons: CoalitionArea[] = []; + #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[] } = {} @@ -114,6 +120,12 @@ export class Map extends L.Map { Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility()); }); + document.addEventListener("toggleMapDraw", (ev: CustomEventInit) => { + if (ev.detail?.type == "polygon") { + this.setState(DRAW_POLYGON); + } + }) + document.addEventListener("unitUpdated", (ev: CustomEvent) => { if (this.#centerUnit != null && ev.detail == this.#centerUnit) this.#panToUnit(this.#centerUnit); @@ -176,6 +188,14 @@ export class Map extends L.Map { this.#createTargetMarker(); this.#hideCursor(); } + else if (this.#state === DRAW_POLYGON) { + this.#resetDestinationMarkers(); + this.#resetTargetMarker(); + this.#showCursor(); + //@ts-ignore draggable option added by plugin + this.#polygons.push(new CoalitionArea([])); + this.#polygons[this.#polygons.length - 1].addTo(this); + } document.dispatchEvent(new CustomEvent("mapStateChanged")); } @@ -188,6 +208,7 @@ export class Map extends L.Map { this.hideMapContextMenu(); this.hideUnitContextMenu(); this.hideAirbaseContextMenu(); + this.hideCoalitionAreaContextMenu(); } showMapContextMenu(e: any) { @@ -238,6 +259,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; @@ -363,6 +400,9 @@ export class Map extends L.Map { this.hideAllContextMenus(); if (this.#state === IDLE) { + } + else if (this.#state === DRAW_POLYGON) { + this.#polygons[this.#polygons.length - 1].addLatLng(e.latlng); } else { this.setState(IDLE); @@ -403,6 +443,9 @@ export class Map extends L.Map { getUnitsManager().getSelectedUnits().length > 0? this.setState(MOVE_UNIT): this.setState(IDLE); getUnitsManager().selectedUnitsFireAtArea(this.getMouseCoordinates()); } + else { + this.setState(IDLE); + } } #executeAction(e: any, action: string) { diff --git a/client/src/units/groundunitsdatabase.ts b/client/src/units/groundunitsdatabase.ts index b3ed6b62..e5f838a9 100644 --- a/client/src/units/groundunitsdatabase.ts +++ b/client/src/units/groundunitsdatabase.ts @@ -14,7 +14,7 @@ export class GroundUnitsDatabase extends UnitDatabase { "items": [ ], "roles": [ - "Template" + "SAM Site" ], "code": "", "name": "Default" @@ -31,7 +31,7 @@ export class GroundUnitsDatabase extends UnitDatabase { "fuel": 1, "items": [], "roles": [ - "Template" + "SAM Site" ], "code": "", "name": "Default" @@ -48,7 +48,7 @@ export class GroundUnitsDatabase extends UnitDatabase { "fuel": 1, "items": [], "roles": [ - "Template" + "SAM Site" ], "code": "", "name": "Default" @@ -65,7 +65,7 @@ export class GroundUnitsDatabase extends UnitDatabase { "fuel": 1, "items": [], "roles": [ - "Template" + "SAM Site" ], "code": "", "name": "Default" @@ -82,7 +82,7 @@ export class GroundUnitsDatabase extends UnitDatabase { "fuel": 1, "items": [], "roles": [ - "Template" + "SAM Site" ], "code": "", "name": "Default" @@ -99,7 +99,7 @@ export class GroundUnitsDatabase extends UnitDatabase { "fuel": 1, "items": [], "roles": [ - "Template" + "SAM Site" ], "code": "", "name": "Default" @@ -116,7 +116,7 @@ export class GroundUnitsDatabase extends UnitDatabase { "fuel": 1, "items": [], "roles": [ - "Template" + "SAM Site" ], "code": "", "name": "Default" diff --git a/client/views/other/contextmenus.ejs b/client/views/other/contextmenus.ejs index 0940f076..09da39cd 100644 --- a/client/views/other/contextmenus.ejs +++ b/client/views/other/contextmenus.ejs @@ -1,17 +1,17 @@ -
+
-
- - - - +
+ + + +
-
+
Aircraft role
@@ -56,7 +56,7 @@
-
+
Ground unit role
@@ -76,14 +76,14 @@
-
+
-
+
@@ -96,11 +96,9 @@
-

-
- +

Parking available:

@@ -108,5 +106,45 @@ - +
+ +
+
+
+
+ +
+
+
+
Unit types
+
+ +
+
+ +
+
+
Units period
+
+ +
+
+
+ +
+ +
+ +
+
+
IADS density
+
+ +
+ +
\ No newline at end of file diff --git a/client/views/panels/navbar.ejs b/client/views/panels/navbar.ejs index 922fe414..411f4dc1 100644 --- a/client/views/panels/navbar.ejs +++ b/client/views/panels/navbar.ejs @@ -30,32 +30,28 @@
- +
-
+ data-on-click-params='{ "coalition": "blue" }'>BLUEFOR
+ data-on-click-params='{ "coalition": "red" }'>REDFOR
+ data-on-click-params='{ "coalition": "neutral" }'>NEUTRAL
-
-
-
ATC
+
@@ -63,4 +59,10 @@ data-on-click-params='{"selector": "#strip-board-tower"}'>TWR
+ +
+
+ +
+
\ No newline at end of file From f9f02c3eb071fcc621484befcce07fa94c2c9b34 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Sun, 18 Jun 2023 19:55:01 +0200 Subject: [PATCH 02/15] Added more functions to edit Coalition Areas --- client/package-lock.json | 3362 ++++++++++++++++- client/package.json | 1 + .../src/controls/coalitionareacontextmenu.ts | 23 +- client/src/controls/dropdown.ts | 4 + client/src/controls/mapcontextmenu.ts | 7 +- client/src/index.ts | 4 + client/src/map/coalitionarea.ts | 95 +- client/src/map/coalitionareahandle.ts | 19 + client/src/map/map.ts | 41 +- client/src/missionhandler/missionhandler.ts | 120 +- client/src/other/utils.ts | 35 + client/src/server/server.ts | 4 +- client/src/units/groundunitsdatabase.ts | 24 +- client/src/units/unitsmanager.ts | 38 +- client/views/other/contextmenus.ejs | 8 +- scripts/OlympusCommand.lua | 2 +- src/core/include/commands.h | 26 +- src/core/src/scheduler.cpp | 6 +- 18 files changed, 3660 insertions(+), 159 deletions(-) create mode 100644 client/src/map/coalitionareahandle.ts diff --git a/client/package-lock.json b/client/package-lock.json index 4907e9c1..f773cef7 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -8,6 +8,7 @@ "name": "DCSOlympus", "version": "v0.3.0-alpha", "dependencies": { + "@turf/turf": "^6.5.0", "@types/formatcoords": "^1.1.0", "@types/geojson": "^7946.0.10", "@types/leaflet": "^1.9.0", @@ -1838,6 +1839,1570 @@ "tslib": "^2.5.0" } }, + "node_modules/@turf/along": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/along/-/along-6.5.0.tgz", + "integrity": "sha512-LLyWQ0AARqJCmMcIEAXF4GEu8usmd4Kbz3qk1Oy5HoRNpZX47+i5exQtmIWKdqJ1MMhW26fCTXgpsEs5zgJ5gw==", + "dependencies": { + "@turf/bearing": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/angle": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/angle/-/angle-6.5.0.tgz", + "integrity": "sha512-4pXMbWhFofJJAOvTMCns6N4C8CMd5Ih4O2jSAG9b3dDHakj3O4yN1+Zbm+NUei+eVEZ9gFeVp9svE3aMDenIkw==", + "dependencies": { + "@turf/bearing": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/rhumb-bearing": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/area": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/area/-/area-6.5.0.tgz", + "integrity": "sha512-xCZdiuojokLbQ+29qR6qoMD89hv+JAgWjLrwSEWL+3JV8IXKeNFl6XkEJz9HGkVpnXvQKJoRz4/liT+8ZZ5Jyg==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bbox": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bbox/-/bbox-6.5.0.tgz", + "integrity": "sha512-RBbLaao5hXTYyyg577iuMtDB8ehxMlUqHEJiMs8jT1GHkFhr6sYre3lmLsPeYEi/ZKj5TP5tt7fkzNdJ4GIVyw==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bbox-clip": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bbox-clip/-/bbox-clip-6.5.0.tgz", + "integrity": "sha512-F6PaIRF8WMp8EmgU/Ke5B1Y6/pia14UAYB5TiBC668w5rVVjy5L8rTm/m2lEkkDMHlzoP9vNY4pxpNthE7rLcQ==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bbox-polygon": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bbox-polygon/-/bbox-polygon-6.5.0.tgz", + "integrity": "sha512-+/r0NyL1lOG3zKZmmf6L8ommU07HliP4dgYToMoTxqzsWzyLjaj/OzgQ8rBmv703WJX+aS6yCmLuIhYqyufyuw==", + "dependencies": { + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bearing": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bearing/-/bearing-6.5.0.tgz", + "integrity": "sha512-dxINYhIEMzgDOztyMZc20I7ssYVNEpSv04VbMo5YPQsqa80KO3TFvbuCahMsCAW5z8Tncc8dwBlEFrmRjJG33A==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/bezier-spline": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bezier-spline/-/bezier-spline-6.5.0.tgz", + "integrity": "sha512-vokPaurTd4PF96rRgGVm6zYYC5r1u98ZsG+wZEv9y3kJTuJRX/O3xIY2QnTGTdbVmAJN1ouOsD0RoZYaVoXORQ==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-clockwise": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-clockwise/-/boolean-clockwise-6.5.0.tgz", + "integrity": "sha512-45+C7LC5RMbRWrxh3Z0Eihsc8db1VGBO5d9BLTOAwU4jR6SgsunTfRWR16X7JUwIDYlCVEmnjcXJNi/kIU3VIw==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-contains": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-contains/-/boolean-contains-6.5.0.tgz", + "integrity": "sha512-4m8cJpbw+YQcKVGi8y0cHhBUnYT+QRfx6wzM4GI1IdtYH3p4oh/DOBJKrepQyiDzFDaNIjxuWXBh0ai1zVwOQQ==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/boolean-point-on-line": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-crosses": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-crosses/-/boolean-crosses-6.5.0.tgz", + "integrity": "sha512-gvshbTPhAHporTlQwBJqyfW+2yV8q/mOTxG6PzRVl6ARsqNoqYQWkd4MLug7OmAqVyBzLK3201uAeBjxbGw0Ng==", + "dependencies": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/polygon-to-line": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-disjoint": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-disjoint/-/boolean-disjoint-6.5.0.tgz", + "integrity": "sha512-rZ2ozlrRLIAGo2bjQ/ZUu4oZ/+ZjGvLkN5CKXSKBcu6xFO6k2bgqeM8a1836tAW+Pqp/ZFsTA5fZHsJZvP2D5g==", + "dependencies": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/polygon-to-line": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-equal": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-equal/-/boolean-equal-6.5.0.tgz", + "integrity": "sha512-cY0M3yoLC26mhAnjv1gyYNQjn7wxIXmL2hBmI/qs8g5uKuC2hRWi13ydufE3k4x0aNRjFGlg41fjoYLwaVF+9Q==", + "dependencies": { + "@turf/clean-coords": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "geojson-equality": "0.1.6" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-intersects": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-intersects/-/boolean-intersects-6.5.0.tgz", + "integrity": "sha512-nIxkizjRdjKCYFQMnml6cjPsDOBCThrt+nkqtSEcxkKMhAQj5OO7o2CecioNTaX8EayqwMGVKcsz27oP4mKPTw==", + "dependencies": { + "@turf/boolean-disjoint": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-overlap": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-overlap/-/boolean-overlap-6.5.0.tgz", + "integrity": "sha512-8btMIdnbXVWUa1M7D4shyaSGxLRw6NjMcqKBcsTXcZdnaixl22k7ar7BvIzkaRYN3SFECk9VGXfLncNS3ckQUw==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/line-overlap": "^6.5.0", + "@turf/meta": "^6.5.0", + "geojson-equality": "0.1.6" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-parallel": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-parallel/-/boolean-parallel-6.5.0.tgz", + "integrity": "sha512-aSHJsr1nq9e5TthZGZ9CZYeXklJyRgR5kCLm5X4urz7+MotMOp/LsGOsvKvK9NeUl9+8OUmfMn8EFTT8LkcvIQ==", + "dependencies": { + "@turf/clean-coords": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/line-segment": "^6.5.0", + "@turf/rhumb-bearing": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-point-in-polygon": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-point-in-polygon/-/boolean-point-in-polygon-6.5.0.tgz", + "integrity": "sha512-DtSuVFB26SI+hj0SjrvXowGTUCHlgevPAIsukssW6BG5MlNSBQAo70wpICBNJL6RjukXg8d2eXaAWuD/CqL00A==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-point-on-line": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-point-on-line/-/boolean-point-on-line-6.5.0.tgz", + "integrity": "sha512-A1BbuQ0LceLHvq7F/P7w3QvfpmZqbmViIUPHdNLvZimFNLo4e6IQunmzbe+8aSStH9QRZm3VOflyvNeXvvpZEQ==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/boolean-within": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-within/-/boolean-within-6.5.0.tgz", + "integrity": "sha512-YQB3oU18Inx35C/LU930D36RAVe7LDXk1kWsQ8mLmuqYn9YdPsDQTMTkLJMhoQ8EbN7QTdy333xRQ4MYgToteQ==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/boolean-point-on-line": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/buffer": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/buffer/-/buffer-6.5.0.tgz", + "integrity": "sha512-qeX4N6+PPWbKqp1AVkBVWFerGjMYMUyencwfnkCesoznU6qvfugFHNAngNqIBVnJjZ5n8IFyOf+akcxnrt9sNg==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/center": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/projection": "^6.5.0", + "d3-geo": "1.7.1", + "turf-jsts": "*" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/center": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/center/-/center-6.5.0.tgz", + "integrity": "sha512-T8KtMTfSATWcAX088rEDKjyvQCBkUsLnK/Txb6/8WUXIeOZyHu42G7MkdkHRoHtwieLdduDdmPLFyTdG5/e7ZQ==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/center-mean": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/center-mean/-/center-mean-6.5.0.tgz", + "integrity": "sha512-AAX6f4bVn12pTVrMUiB9KrnV94BgeBKpyg3YpfnEbBpkN/znfVhL8dG8IxMAxAoSZ61Zt9WLY34HfENveuOZ7Q==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/center-median": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/center-median/-/center-median-6.5.0.tgz", + "integrity": "sha512-dT8Ndu5CiZkPrj15PBvslpuf01ky41DEYEPxS01LOxp5HOUHXp1oJxsPxvc+i/wK4BwccPNzU1vzJ0S4emd1KQ==", + "dependencies": { + "@turf/center-mean": "^6.5.0", + "@turf/centroid": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/center-of-mass": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/center-of-mass/-/center-of-mass-6.5.0.tgz", + "integrity": "sha512-EWrriU6LraOfPN7m1jZi+1NLTKNkuIsGLZc2+Y8zbGruvUW+QV7K0nhf7iZWutlxHXTBqEXHbKue/o79IumAsQ==", + "dependencies": { + "@turf/centroid": "^6.5.0", + "@turf/convex": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/centroid": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/centroid/-/centroid-6.5.0.tgz", + "integrity": "sha512-MwE1oq5E3isewPprEClbfU5pXljIK/GUOMbn22UM3IFPDJX0KeoyLNwghszkdmFp/qMGL/M13MMWvU+GNLXP/A==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/circle": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/circle/-/circle-6.5.0.tgz", + "integrity": "sha512-oU1+Kq9DgRnoSbWFHKnnUdTmtcRUMmHoV9DjTXu9vOLNV5OWtAAh1VZ+mzsioGGzoDNT/V5igbFOkMfBQc0B6A==", + "dependencies": { + "@turf/destination": "^6.5.0", + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clean-coords": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/clean-coords/-/clean-coords-6.5.0.tgz", + "integrity": "sha512-EMX7gyZz0WTH/ET7xV8MyrExywfm9qUi0/MY89yNffzGIEHuFfqwhcCqZ8O00rZIPZHUTxpmsxQSTfzJJA1CPw==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clone": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/clone/-/clone-6.5.0.tgz", + "integrity": "sha512-mzVtTFj/QycXOn6ig+annKrM6ZlimreKYz6f/GSERytOpgzodbQyOgkfwru100O1KQhhjSudKK4DsQ0oyi9cTw==", + "dependencies": { + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clusters": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/clusters/-/clusters-6.5.0.tgz", + "integrity": "sha512-Y6gfnTJzQ1hdLfCsyd5zApNbfLIxYEpmDibHUqR5z03Lpe02pa78JtgrgUNt1seeO/aJ4TG1NLN8V5gOrHk04g==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clusters-dbscan": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/clusters-dbscan/-/clusters-dbscan-6.5.0.tgz", + "integrity": "sha512-SxZEE4kADU9DqLRiT53QZBBhu8EP9skviSyl+FGj08Y01xfICM/RR9ACUdM0aEQimhpu+ZpRVcUK+2jtiCGrYQ==", + "dependencies": { + "@turf/clone": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0", + "density-clustering": "1.3.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/clusters-kmeans": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/clusters-kmeans/-/clusters-kmeans-6.5.0.tgz", + "integrity": "sha512-DwacD5+YO8kwDPKaXwT9DV46tMBVNsbi1IzdajZu1JDSWoN7yc7N9Qt88oi+p30583O0UPVkAK+A10WAQv4mUw==", + "dependencies": { + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "skmeans": "0.9.7" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/collect": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/collect/-/collect-6.5.0.tgz", + "integrity": "sha512-4dN/T6LNnRg099m97BJeOcTA5fSI8cu87Ydgfibewd2KQwBexO69AnjEFqfPX3Wj+Zvisj1uAVIZbPmSSrZkjg==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0", + "rbush": "2.x" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/combine": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/combine/-/combine-6.5.0.tgz", + "integrity": "sha512-Q8EIC4OtAcHiJB3C4R+FpB4LANiT90t17uOd851qkM2/o6m39bfN5Mv0PWqMZIHWrrosZqRqoY9dJnzz/rJxYQ==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/concave": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/concave/-/concave-6.5.0.tgz", + "integrity": "sha512-I/sUmUC8TC5h/E2vPwxVht+nRt+TnXIPRoztDFvS8/Y0+cBDple9inLSo9nnPXMXidrBlGXZ9vQx/BjZUJgsRQ==", + "dependencies": { + "@turf/clone": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/tin": "^6.5.0", + "topojson-client": "3.x", + "topojson-server": "3.x" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/convex": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/convex/-/convex-6.5.0.tgz", + "integrity": "sha512-x7ZwC5z7PJB0SBwNh7JCeCNx7Iu+QSrH7fYgK0RhhNop13TqUlvHMirMLRgf2db1DqUetrAO2qHJeIuasquUWg==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0", + "concaveman": "*" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/destination": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/destination/-/destination-6.5.0.tgz", + "integrity": "sha512-4cnWQlNC8d1tItOz9B4pmJdWpXqS0vEvv65bI/Pj/genJnsL7evI0/Xw42RvEGROS481MPiU80xzvwxEvhQiMQ==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/difference": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/difference/-/difference-6.5.0.tgz", + "integrity": "sha512-l8iR5uJqvI+5Fs6leNbhPY5t/a3vipUF/3AeVLpwPQcgmedNXyheYuy07PcMGH5Jdpi5gItOiTqwiU/bUH4b3A==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "polygon-clipping": "^0.15.3" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/dissolve": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/dissolve/-/dissolve-6.5.0.tgz", + "integrity": "sha512-WBVbpm9zLTp0Bl9CE35NomTaOL1c4TQCtEoO43YaAhNEWJOOIhZMFJyr8mbvYruKl817KinT3x7aYjjCMjTAsQ==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "polygon-clipping": "^0.15.3" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/distance": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/distance/-/distance-6.5.0.tgz", + "integrity": "sha512-xzykSLfoURec5qvQJcfifw/1mJa+5UwByZZ5TZ8iaqjGYN0vomhV9aiSLeYdUGtYRESZ+DYC/OzY+4RclZYgMg==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/distance-weight": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/distance-weight/-/distance-weight-6.5.0.tgz", + "integrity": "sha512-a8qBKkgVNvPKBfZfEJZnC3DV7dfIsC3UIdpRci/iap/wZLH41EmS90nM+BokAJflUHYy8PqE44wySGWHN1FXrQ==", + "dependencies": { + "@turf/centroid": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/ellipse": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/ellipse/-/ellipse-6.5.0.tgz", + "integrity": "sha512-kuXtwFviw/JqnyJXF1mrR/cb496zDTSbGKtSiolWMNImYzGGkbsAsFTjwJYgD7+4FixHjp0uQPzo70KDf3AIBw==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/rhumb-destination": "^6.5.0", + "@turf/transform-rotate": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/envelope": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/envelope/-/envelope-6.5.0.tgz", + "integrity": "sha512-9Z+FnBWvOGOU4X+fMZxYFs1HjFlkKqsddLuMknRaqcJd6t+NIv5DWvPtDL8ATD2GEExYDiFLwMdckfr1yqJgHA==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/bbox-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/explode": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/explode/-/explode-6.5.0.tgz", + "integrity": "sha512-6cSvMrnHm2qAsace6pw9cDmK2buAlw8+tjeJVXMfMyY+w7ZUi1rprWMsY92J7s2Dar63Bv09n56/1V7+tcj52Q==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/flatten": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/flatten/-/flatten-6.5.0.tgz", + "integrity": "sha512-IBZVwoNLVNT6U/bcUUllubgElzpMsNoCw8tLqBw6dfYg9ObGmpEjf9BIYLr7a2Yn5ZR4l7YIj2T7kD5uJjZADQ==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/flip": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/flip/-/flip-6.5.0.tgz", + "integrity": "sha512-oyikJFNjt2LmIXQqgOGLvt70RgE2lyzPMloYWM7OR5oIFGRiBvqVD2hA6MNw6JewIm30fWZ8DQJw1NHXJTJPbg==", + "dependencies": { + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/great-circle": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/great-circle/-/great-circle-6.5.0.tgz", + "integrity": "sha512-7ovyi3HaKOXdFyN7yy1yOMa8IyOvV46RC1QOQTT+RYUN8ke10eyqExwBpL9RFUPvlpoTzoYbM/+lWPogQlFncg==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/helpers": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-6.5.0.tgz", + "integrity": "sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw==", + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/hex-grid": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/hex-grid/-/hex-grid-6.5.0.tgz", + "integrity": "sha512-Ln3tc2tgZT8etDOldgc6e741Smg1CsMKAz1/Mlel+MEL5Ynv2mhx3m0q4J9IB1F3a4MNjDeVvm8drAaf9SF33g==", + "dependencies": { + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/intersect": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/interpolate": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/interpolate/-/interpolate-6.5.0.tgz", + "integrity": "sha512-LSH5fMeiGyuDZ4WrDJNgh81d2DnNDUVJtuFryJFup8PV8jbs46lQGfI3r1DJ2p1IlEJIz3pmAZYeTfMMoeeohw==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/centroid": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/hex-grid": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/point-grid": "^6.5.0", + "@turf/square-grid": "^6.5.0", + "@turf/triangle-grid": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/intersect": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/intersect/-/intersect-6.5.0.tgz", + "integrity": "sha512-2legGJeKrfFkzntcd4GouPugoqPUjexPZnOvfez+3SfIMrHvulw8qV8u7pfVyn2Yqs53yoVCEjS5sEpvQ5YRQg==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "polygon-clipping": "^0.15.3" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/invariant": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-6.5.0.tgz", + "integrity": "sha512-Wv8PRNCtPD31UVbdJE/KVAWKe7l6US+lJItRR/HOEW3eh+U/JwRCSUl/KZ7bmjM/C+zLNoreM2TU6OoLACs4eg==", + "dependencies": { + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/isobands": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/isobands/-/isobands-6.5.0.tgz", + "integrity": "sha512-4h6sjBPhRwMVuFaVBv70YB7eGz+iw0bhPRnp+8JBdX1UPJSXhoi/ZF2rACemRUr0HkdVB/a1r9gC32vn5IAEkw==", + "dependencies": { + "@turf/area": "^6.5.0", + "@turf/bbox": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/explode": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "object-assign": "*" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/isolines": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/isolines/-/isolines-6.5.0.tgz", + "integrity": "sha512-6ElhiLCopxWlv4tPoxiCzASWt/jMRvmp6mRYrpzOm3EUl75OhHKa/Pu6Y9nWtCMmVC/RcWtiiweUocbPLZLm0A==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "object-assign": "*" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/kinks": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/kinks/-/kinks-6.5.0.tgz", + "integrity": "sha512-ViCngdPt1eEL7hYUHR2eHR662GvCgTc35ZJFaNR6kRtr6D8plLaDju0FILeFFWSc+o8e3fwxZEJKmFj9IzPiIQ==", + "dependencies": { + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/length": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/length/-/length-6.5.0.tgz", + "integrity": "sha512-5pL5/pnw52fck3oRsHDcSGrj9HibvtlrZ0QNy2OcW8qBFDNgZ4jtl6U7eATVoyWPKBHszW3dWETW+iLV7UARig==", + "dependencies": { + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-arc": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-arc/-/line-arc-6.5.0.tgz", + "integrity": "sha512-I6c+V6mIyEwbtg9P9zSFF89T7QPe1DPTG3MJJ6Cm1MrAY0MdejwQKOpsvNl8LDU2ekHOlz2kHpPVR7VJsoMllA==", + "dependencies": { + "@turf/circle": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-chunk": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-chunk/-/line-chunk-6.5.0.tgz", + "integrity": "sha512-i1FGE6YJaaYa+IJesTfyRRQZP31QouS+wh/pa6O3CC0q4T7LtHigyBSYjrbjSLfn2EVPYGlPCMFEqNWCOkC6zg==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/length": "^6.5.0", + "@turf/line-slice-along": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-intersect": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-intersect/-/line-intersect-6.5.0.tgz", + "integrity": "sha512-CS6R1tZvVQD390G9Ea4pmpM6mJGPWoL82jD46y0q1KSor9s6HupMIo1kY4Ny+AEYQl9jd21V3Scz20eldpbTVA==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-segment": "^6.5.0", + "@turf/meta": "^6.5.0", + "geojson-rbush": "3.x" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-offset": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-offset/-/line-offset-6.5.0.tgz", + "integrity": "sha512-CEXZbKgyz8r72qRvPchK0dxqsq8IQBdH275FE6o4MrBkzMcoZsfSjghtXzKaz9vvro+HfIXal0sTk2mqV1lQTw==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-overlap": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-overlap/-/line-overlap-6.5.0.tgz", + "integrity": "sha512-xHOaWLd0hkaC/1OLcStCpfq55lPHpPNadZySDXYiYjEz5HXr1oKmtMYpn0wGizsLwrOixRdEp+j7bL8dPt4ojQ==", + "dependencies": { + "@turf/boolean-point-on-line": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-segment": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/nearest-point-on-line": "^6.5.0", + "deep-equal": "1.x", + "geojson-rbush": "3.x" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-segment": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-segment/-/line-segment-6.5.0.tgz", + "integrity": "sha512-jI625Ho4jSuJESNq66Mmi290ZJ5pPZiQZruPVpmHkUw257Pew0alMmb6YrqYNnLUuiVVONxAAKXUVeeUGtycfw==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-slice": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-slice/-/line-slice-6.5.0.tgz", + "integrity": "sha512-vDqJxve9tBHhOaVVFXqVjF5qDzGtKWviyjbyi2QnSnxyFAmLlLnBfMX8TLQCAf2GxHibB95RO5FBE6I2KVPRuw==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/nearest-point-on-line": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-slice-along": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-slice-along/-/line-slice-along-6.5.0.tgz", + "integrity": "sha512-KHJRU6KpHrAj+BTgTNqby6VCTnDzG6a1sJx/I3hNvqMBLvWVA2IrkR9L9DtsQsVY63IBwVdQDqiwCuZLDQh4Ng==", + "dependencies": { + "@turf/bearing": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-split": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-split/-/line-split-6.5.0.tgz", + "integrity": "sha512-/rwUMVr9OI2ccJjw7/6eTN53URtGThNSD5I0GgxyFXMtxWiloRJ9MTff8jBbtPWrRka/Sh2GkwucVRAEakx9Sw==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/line-segment": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/nearest-point-on-line": "^6.5.0", + "@turf/square": "^6.5.0", + "@turf/truncate": "^6.5.0", + "geojson-rbush": "3.x" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/line-to-polygon": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-to-polygon/-/line-to-polygon-6.5.0.tgz", + "integrity": "sha512-qYBuRCJJL8Gx27OwCD1TMijM/9XjRgXH/m/TyuND4OXedBpIWlK5VbTIO2gJ8OCfznBBddpjiObLBrkuxTpN4Q==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/mask": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/mask/-/mask-6.5.0.tgz", + "integrity": "sha512-RQha4aU8LpBrmrkH8CPaaoAfk0Egj5OuXtv6HuCQnHeGNOQt3TQVibTA3Sh4iduq4EPxnZfDjgsOeKtrCA19lg==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "polygon-clipping": "^0.15.3" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/meta": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-6.5.0.tgz", + "integrity": "sha512-RrArvtsV0vdsCBegoBtOalgdSOfkBrTJ07VkpiCnq/491W67hnMWmDu7e6Ztw0C3WldRYTXkg3SumfdzZxLBHA==", + "dependencies": { + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/midpoint": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/midpoint/-/midpoint-6.5.0.tgz", + "integrity": "sha512-MyTzV44IwmVI6ec9fB2OgZ53JGNlgOpaYl9ArKoF49rXpL84F9rNATndbe0+MQIhdkw8IlzA6xVP4lZzfMNVCw==", + "dependencies": { + "@turf/bearing": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/moran-index": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/moran-index/-/moran-index-6.5.0.tgz", + "integrity": "sha512-ItsnhrU2XYtTtTudrM8so4afBCYWNaB0Mfy28NZwLjB5jWuAsvyV+YW+J88+neK/ougKMTawkmjQqodNJaBeLQ==", + "dependencies": { + "@turf/distance-weight": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/nearest-point": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/nearest-point/-/nearest-point-6.5.0.tgz", + "integrity": "sha512-fguV09QxilZv/p94s8SMsXILIAMiaXI5PATq9d7YWijLxWUj6Q/r43kxyoi78Zmwwh1Zfqz9w+bCYUAxZ5+euA==", + "dependencies": { + "@turf/clone": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/nearest-point-on-line": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/nearest-point-on-line/-/nearest-point-on-line-6.5.0.tgz", + "integrity": "sha512-WthrvddddvmymnC+Vf7BrkHGbDOUu6Z3/6bFYUGv1kxw8tiZ6n83/VG6kHz4poHOfS0RaNflzXSkmCi64fLBlg==", + "dependencies": { + "@turf/bearing": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/nearest-point-to-line": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/nearest-point-to-line/-/nearest-point-to-line-6.5.0.tgz", + "integrity": "sha512-PXV7cN0BVzUZdjj6oeb/ESnzXSfWmEMrsfZSDRgqyZ9ytdiIj/eRsnOXLR13LkTdXVOJYDBuf7xt1mLhM4p6+Q==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/point-to-line-distance": "^6.5.0", + "object-assign": "*" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/planepoint": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/planepoint/-/planepoint-6.5.0.tgz", + "integrity": "sha512-R3AahA6DUvtFbka1kcJHqZ7DMHmPXDEQpbU5WaglNn7NaCQg9HB0XM0ZfqWcd5u92YXV+Gg8QhC8x5XojfcM4Q==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/point-grid": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/point-grid/-/point-grid-6.5.0.tgz", + "integrity": "sha512-Iq38lFokNNtQJnOj/RBKmyt6dlof0yhaHEDELaWHuECm1lIZLY3ZbVMwbs+nXkwTAHjKfS/OtMheUBkw+ee49w==", + "dependencies": { + "@turf/boolean-within": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/point-on-feature": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/point-on-feature/-/point-on-feature-6.5.0.tgz", + "integrity": "sha512-bDpuIlvugJhfcF/0awAQ+QI6Om1Y1FFYE8Y/YdxGRongivix850dTeXCo0mDylFdWFPGDo7Mmh9Vo4VxNwW/TA==", + "dependencies": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/center": "^6.5.0", + "@turf/explode": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/nearest-point": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/point-to-line-distance": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/point-to-line-distance/-/point-to-line-distance-6.5.0.tgz", + "integrity": "sha512-opHVQ4vjUhNBly1bob6RWy+F+hsZDH9SA0UW36pIRzfpu27qipU18xup0XXEePfY6+wvhF6yL/WgCO2IbrLqEA==", + "dependencies": { + "@turf/bearing": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/projection": "^6.5.0", + "@turf/rhumb-bearing": "^6.5.0", + "@turf/rhumb-distance": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/points-within-polygon": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/points-within-polygon/-/points-within-polygon-6.5.0.tgz", + "integrity": "sha512-YyuheKqjliDsBDt3Ho73QVZk1VXX1+zIA2gwWvuz8bR1HXOkcuwk/1J76HuFMOQI3WK78wyAi+xbkx268PkQzQ==", + "dependencies": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/polygon-smooth": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/polygon-smooth/-/polygon-smooth-6.5.0.tgz", + "integrity": "sha512-LO/X/5hfh/Rk4EfkDBpLlVwt3i6IXdtQccDT9rMjXEP32tRgy0VMFmdkNaXoGlSSKf/1mGqLl4y4wHd86DqKbg==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/polygon-tangents": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/polygon-tangents/-/polygon-tangents-6.5.0.tgz", + "integrity": "sha512-sB4/IUqJMYRQH9jVBwqS/XDitkEfbyqRy+EH/cMRJURTg78eHunvJ708x5r6umXsbiUyQU4eqgPzEylWEQiunw==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/boolean-within": "^6.5.0", + "@turf/explode": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/nearest-point": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/polygon-to-line": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/polygon-to-line/-/polygon-to-line-6.5.0.tgz", + "integrity": "sha512-5p4n/ij97EIttAq+ewSnKt0ruvuM+LIDzuczSzuHTpq4oS7Oq8yqg5TQ4nzMVuK41r/tALCk7nAoBuw3Su4Gcw==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/polygonize": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/polygonize/-/polygonize-6.5.0.tgz", + "integrity": "sha512-a/3GzHRaCyzg7tVYHo43QUChCspa99oK4yPqooVIwTC61npFzdrmnywMv0S+WZjHZwK37BrFJGFrZGf6ocmY5w==", + "dependencies": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/envelope": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/projection": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/projection/-/projection-6.5.0.tgz", + "integrity": "sha512-/Pgh9mDvQWWu8HRxqpM+tKz8OzgauV+DiOcr3FCjD6ubDnrrmMJlsf6fFJmggw93mtVPrZRL6yyi9aYCQBOIvg==", + "dependencies": { + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/random": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/random/-/random-6.5.0.tgz", + "integrity": "sha512-8Q25gQ/XbA7HJAe+eXp4UhcXM9aOOJFaxZ02+XSNwMvY8gtWSCBLVqRcW4OhqilgZ8PeuQDWgBxeo+BIqqFWFQ==", + "dependencies": { + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rectangle-grid": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/rectangle-grid/-/rectangle-grid-6.5.0.tgz", + "integrity": "sha512-yQZ/1vbW68O2KsSB3OZYK+72aWz/Adnf7m2CMKcC+aq6TwjxZjAvlbCOsNUnMAuldRUVN1ph6RXMG4e9KEvKvg==", + "dependencies": { + "@turf/boolean-intersects": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rewind": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/rewind/-/rewind-6.5.0.tgz", + "integrity": "sha512-IoUAMcHWotBWYwSYuYypw/LlqZmO+wcBpn8ysrBNbazkFNkLf3btSDZMkKJO/bvOzl55imr/Xj4fi3DdsLsbzQ==", + "dependencies": { + "@turf/boolean-clockwise": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rhumb-bearing": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/rhumb-bearing/-/rhumb-bearing-6.5.0.tgz", + "integrity": "sha512-jMyqiMRK4hzREjQmnLXmkJ+VTNTx1ii8vuqRwJPcTlKbNWfjDz/5JqJlb5NaFDcdMpftWovkW5GevfnuzHnOYA==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rhumb-destination": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/rhumb-destination/-/rhumb-destination-6.5.0.tgz", + "integrity": "sha512-RHNP1Oy+7xTTdRrTt375jOZeHceFbjwohPHlr9Hf68VdHHPMAWgAKqiX2YgSWDcvECVmiGaBKWus1Df+N7eE4Q==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/rhumb-distance": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/rhumb-distance/-/rhumb-distance-6.5.0.tgz", + "integrity": "sha512-oKp8KFE8E4huC2Z1a1KNcFwjVOqa99isxNOwfo4g3SUABQ6NezjKDDrnvC4yI5YZ3/huDjULLBvhed45xdCrzg==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/sample": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/sample/-/sample-6.5.0.tgz", + "integrity": "sha512-kSdCwY7el15xQjnXYW520heKUrHwRvnzx8ka4eYxX9NFeOxaFITLW2G7UtXb6LJK8mmPXI8Aexv23F2ERqzGFg==", + "dependencies": { + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/sector": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/sector/-/sector-6.5.0.tgz", + "integrity": "sha512-cYUOkgCTWqa23SOJBqxoFAc/yGCUsPRdn/ovbRTn1zNTm/Spmk6hVB84LCKOgHqvSF25i0d2kWqpZDzLDdAPbw==", + "dependencies": { + "@turf/circle": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-arc": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/shortest-path": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/shortest-path/-/shortest-path-6.5.0.tgz", + "integrity": "sha512-4de5+G7+P4hgSoPwn+SO9QSi9HY5NEV/xRJ+cmoFVRwv2CDsuOPDheHKeuIAhKyeKDvPvPt04XYWbac4insJMg==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/bbox-polygon": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/clean-coords": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/transform-scale": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/simplify": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/simplify/-/simplify-6.5.0.tgz", + "integrity": "sha512-USas3QqffPHUY184dwQdP8qsvcVH/PWBYdXY5am7YTBACaQOMAlf6AKJs9FT8jiO6fQpxfgxuEtwmox+pBtlOg==", + "dependencies": { + "@turf/clean-coords": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/square": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/square/-/square-6.5.0.tgz", + "integrity": "sha512-BM2UyWDmiuHCadVhHXKIx5CQQbNCpOxB6S/aCNOCLbhCeypKX5Q0Aosc5YcmCJgkwO5BERCC6Ee7NMbNB2vHmQ==", + "dependencies": { + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/square-grid": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/square-grid/-/square-grid-6.5.0.tgz", + "integrity": "sha512-mlR0ayUdA+L4c9h7p4k3pX6gPWHNGuZkt2c5II1TJRmhLkW2557d6b/Vjfd1z9OVaajb1HinIs1FMSAPXuuUrA==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/rectangle-grid": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/standard-deviational-ellipse": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/standard-deviational-ellipse/-/standard-deviational-ellipse-6.5.0.tgz", + "integrity": "sha512-02CAlz8POvGPFK2BKK8uHGUk/LXb0MK459JVjKxLC2yJYieOBTqEbjP0qaWhiBhGzIxSMaqe8WxZ0KvqdnstHA==", + "dependencies": { + "@turf/center-mean": "^6.5.0", + "@turf/ellipse": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/points-within-polygon": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/tag": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/tag/-/tag-6.5.0.tgz", + "integrity": "sha512-XwlBvrOV38CQsrNfrxvBaAPBQgXMljeU0DV8ExOyGM7/hvuGHJw3y8kKnQ4lmEQcmcrycjDQhP7JqoRv8vFssg==", + "dependencies": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/tesselate": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/tesselate/-/tesselate-6.5.0.tgz", + "integrity": "sha512-M1HXuyZFCfEIIKkglh/r5L9H3c5QTEsnMBoZOFQiRnGPGmJWcaBissGb7mTFX2+DKE7FNWXh4TDnZlaLABB0dQ==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "earcut": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/tin": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/tin/-/tin-6.5.0.tgz", + "integrity": "sha512-YLYikRzKisfwj7+F+Tmyy/LE3d2H7D4kajajIfc9mlik2+esG7IolsX/+oUz1biguDYsG0DUA8kVYXDkobukfg==", + "dependencies": { + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/transform-rotate": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/transform-rotate/-/transform-rotate-6.5.0.tgz", + "integrity": "sha512-A2Ip1v4246ZmpssxpcL0hhiVBEf4L8lGnSPWTgSv5bWBEoya2fa/0SnFX9xJgP40rMP+ZzRaCN37vLHbv1Guag==", + "dependencies": { + "@turf/centroid": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/rhumb-bearing": "^6.5.0", + "@turf/rhumb-destination": "^6.5.0", + "@turf/rhumb-distance": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/transform-scale": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/transform-scale/-/transform-scale-6.5.0.tgz", + "integrity": "sha512-VsATGXC9rYM8qTjbQJ/P7BswKWXHdnSJ35JlV4OsZyHBMxJQHftvmZJsFbOqVtQnIQIzf2OAly6rfzVV9QLr7g==", + "dependencies": { + "@turf/bbox": "^6.5.0", + "@turf/center": "^6.5.0", + "@turf/centroid": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/rhumb-bearing": "^6.5.0", + "@turf/rhumb-destination": "^6.5.0", + "@turf/rhumb-distance": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/transform-translate": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/transform-translate/-/transform-translate-6.5.0.tgz", + "integrity": "sha512-NABLw5VdtJt/9vSstChp93pc6oel4qXEos56RBMsPlYB8hzNTEKYtC146XJvyF4twJeeYS8RVe1u7KhoFwEM5w==", + "dependencies": { + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/rhumb-destination": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/triangle-grid": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/triangle-grid/-/triangle-grid-6.5.0.tgz", + "integrity": "sha512-2jToUSAS1R1htq4TyLQYPTIsoy6wg3e3BQXjm2rANzw4wPQCXGOxrur1Fy9RtzwqwljlC7DF4tg0OnWr8RjmfA==", + "dependencies": { + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/intersect": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/truncate": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/truncate/-/truncate-6.5.0.tgz", + "integrity": "sha512-pFxg71pLk+eJj134Z9yUoRhIi8vqnnKvCYwdT4x/DQl/19RVdq1tV3yqOT3gcTQNfniteylL5qV1uTBDV5sgrg==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/turf": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/turf/-/turf-6.5.0.tgz", + "integrity": "sha512-ipMCPnhu59bh92MNt8+pr1VZQhHVuTMHklciQURo54heoxRzt1neNYZOBR6jdL+hNsbDGAECMuIpAutX+a3Y+w==", + "dependencies": { + "@turf/along": "^6.5.0", + "@turf/angle": "^6.5.0", + "@turf/area": "^6.5.0", + "@turf/bbox": "^6.5.0", + "@turf/bbox-clip": "^6.5.0", + "@turf/bbox-polygon": "^6.5.0", + "@turf/bearing": "^6.5.0", + "@turf/bezier-spline": "^6.5.0", + "@turf/boolean-clockwise": "^6.5.0", + "@turf/boolean-contains": "^6.5.0", + "@turf/boolean-crosses": "^6.5.0", + "@turf/boolean-disjoint": "^6.5.0", + "@turf/boolean-equal": "^6.5.0", + "@turf/boolean-intersects": "^6.5.0", + "@turf/boolean-overlap": "^6.5.0", + "@turf/boolean-parallel": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/boolean-point-on-line": "^6.5.0", + "@turf/boolean-within": "^6.5.0", + "@turf/buffer": "^6.5.0", + "@turf/center": "^6.5.0", + "@turf/center-mean": "^6.5.0", + "@turf/center-median": "^6.5.0", + "@turf/center-of-mass": "^6.5.0", + "@turf/centroid": "^6.5.0", + "@turf/circle": "^6.5.0", + "@turf/clean-coords": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/clusters": "^6.5.0", + "@turf/clusters-dbscan": "^6.5.0", + "@turf/clusters-kmeans": "^6.5.0", + "@turf/collect": "^6.5.0", + "@turf/combine": "^6.5.0", + "@turf/concave": "^6.5.0", + "@turf/convex": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/difference": "^6.5.0", + "@turf/dissolve": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/distance-weight": "^6.5.0", + "@turf/ellipse": "^6.5.0", + "@turf/envelope": "^6.5.0", + "@turf/explode": "^6.5.0", + "@turf/flatten": "^6.5.0", + "@turf/flip": "^6.5.0", + "@turf/great-circle": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/hex-grid": "^6.5.0", + "@turf/interpolate": "^6.5.0", + "@turf/intersect": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/isobands": "^6.5.0", + "@turf/isolines": "^6.5.0", + "@turf/kinks": "^6.5.0", + "@turf/length": "^6.5.0", + "@turf/line-arc": "^6.5.0", + "@turf/line-chunk": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/line-offset": "^6.5.0", + "@turf/line-overlap": "^6.5.0", + "@turf/line-segment": "^6.5.0", + "@turf/line-slice": "^6.5.0", + "@turf/line-slice-along": "^6.5.0", + "@turf/line-split": "^6.5.0", + "@turf/line-to-polygon": "^6.5.0", + "@turf/mask": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/midpoint": "^6.5.0", + "@turf/moran-index": "^6.5.0", + "@turf/nearest-point": "^6.5.0", + "@turf/nearest-point-on-line": "^6.5.0", + "@turf/nearest-point-to-line": "^6.5.0", + "@turf/planepoint": "^6.5.0", + "@turf/point-grid": "^6.5.0", + "@turf/point-on-feature": "^6.5.0", + "@turf/point-to-line-distance": "^6.5.0", + "@turf/points-within-polygon": "^6.5.0", + "@turf/polygon-smooth": "^6.5.0", + "@turf/polygon-tangents": "^6.5.0", + "@turf/polygon-to-line": "^6.5.0", + "@turf/polygonize": "^6.5.0", + "@turf/projection": "^6.5.0", + "@turf/random": "^6.5.0", + "@turf/rewind": "^6.5.0", + "@turf/rhumb-bearing": "^6.5.0", + "@turf/rhumb-destination": "^6.5.0", + "@turf/rhumb-distance": "^6.5.0", + "@turf/sample": "^6.5.0", + "@turf/sector": "^6.5.0", + "@turf/shortest-path": "^6.5.0", + "@turf/simplify": "^6.5.0", + "@turf/square": "^6.5.0", + "@turf/square-grid": "^6.5.0", + "@turf/standard-deviational-ellipse": "^6.5.0", + "@turf/tag": "^6.5.0", + "@turf/tesselate": "^6.5.0", + "@turf/tin": "^6.5.0", + "@turf/transform-rotate": "^6.5.0", + "@turf/transform-scale": "^6.5.0", + "@turf/transform-translate": "^6.5.0", + "@turf/triangle-grid": "^6.5.0", + "@turf/truncate": "^6.5.0", + "@turf/union": "^6.5.0", + "@turf/unkink-polygon": "^6.5.0", + "@turf/voronoi": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/union": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/union/-/union-6.5.0.tgz", + "integrity": "sha512-igYWCwP/f0RFHIlC2c0SKDuM/ObBaqSljI3IdV/x71805QbIvY/BYGcJdyNcgEA6cylIGl/0VSlIbpJHZ9ldhw==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "polygon-clipping": "^0.15.3" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/unkink-polygon": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/unkink-polygon/-/unkink-polygon-6.5.0.tgz", + "integrity": "sha512-8QswkzC0UqKmN1DT6HpA9upfa1HdAA5n6bbuzHy8NJOX8oVizVAqfEPY0wqqTgboDjmBR4yyImsdPGUl3gZ8JQ==", + "dependencies": { + "@turf/area": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0", + "rbush": "^2.0.1" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/voronoi": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/voronoi/-/voronoi-6.5.0.tgz", + "integrity": "sha512-C/xUsywYX+7h1UyNqnydHXiun4UPjK88VDghtoRypR9cLlb7qozkiLRphQxxsCM0KxyxpVPHBVQXdAL3+Yurow==", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "d3-voronoi": "1.1.2" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, "node_modules/@types/formatcoords": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@types/formatcoords/-/formatcoords-1.1.0.tgz", @@ -2635,7 +4200,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -2758,6 +4322,11 @@ "source-map": "~0.5.3" } }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2778,6 +4347,30 @@ "typedarray": "^0.0.6" } }, + "node_modules/concaveman": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/concaveman/-/concaveman-1.2.1.tgz", + "integrity": "sha512-PwZYKaM/ckQSa8peP5JpVr7IMJ4Nn/MHIaWUjP4be+KoZ7Botgs8seAZGpmaOM+UZXawcdYRao/px9ycrCihHw==", + "dependencies": { + "point-in-polygon": "^1.1.0", + "rbush": "^3.0.1", + "robust-predicates": "^2.0.4", + "tinyqueue": "^2.0.3" + } + }, + "node_modules/concaveman/node_modules/quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" + }, + "node_modules/concaveman/node_modules/rbush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", + "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==", + "dependencies": { + "quickselect": "^2.0.0" + } + }, "node_modules/concurrently": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.6.0.tgz", @@ -2977,6 +4570,24 @@ "node": "*" } }, + "node_modules/d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" + }, + "node_modules/d3-geo": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.7.1.tgz", + "integrity": "sha512-O4AempWAr+P5qbk2bC2FuN/sDW4z+dN2wDf9QV3bxQt4M5HfOEeXLgJ/UKQW0+o1Dj8BE+L5kiDbdWUMjsmQpw==", + "dependencies": { + "d3-array": "1" + } + }, + "node_modules/d3-voronoi": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.2.tgz", + "integrity": "sha512-RhGS1u2vavcO7ay7ZNAPo4xeDh/VYeGof3x5ZLJBQgYhLegxr3s5IykvWmJ94FTU6mcbtp4sloqZ54mP6R4Utw==" + }, "node_modules/dash-ast": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", @@ -3004,6 +4615,37 @@ "ms": "2.0.0" } }, + "node_modules/deep-equal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "dependencies": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/defined": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz", @@ -3013,6 +4655,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/density-clustering": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/density-clustering/-/density-clustering-1.3.0.tgz", + "integrity": "sha512-icpmBubVTwLnsaor9qH/4tG5+7+f61VcqMN3V3pm9sxxSCt2Jcs0zWOgwZW9ARJYaKD3FumIgHiMOcIMRRAzFQ==" + }, "node_modules/depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -3109,6 +4756,11 @@ "readable-stream": "^2.0.2" } }, + "node_modules/earcut": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", + "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==" + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -3452,8 +5104,15 @@ "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/gensync": { "version": "1.0.0-beta.2", @@ -3464,6 +5123,44 @@ "node": ">=6.9.0" } }, + "node_modules/geojson-equality": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/geojson-equality/-/geojson-equality-0.1.6.tgz", + "integrity": "sha512-TqG8YbqizP3EfwP5Uw4aLu6pKkg6JQK9uq/XZ1lXQntvTHD1BBKJWhNpJ2M0ax6TuWMP3oyx6Oq7FCIfznrgpQ==", + "dependencies": { + "deep-equal": "^1.0.0" + } + }, + "node_modules/geojson-rbush": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/geojson-rbush/-/geojson-rbush-3.2.0.tgz", + "integrity": "sha512-oVltQTXolxvsz1sZnutlSuLDEcQAKYC/uXt9zDzJJ6bu0W+baTI8LZBaTup5afzibEH4N3jlq2p+a152wlBJ7w==", + "dependencies": { + "@turf/bbox": "*", + "@turf/helpers": "6.x", + "@turf/meta": "6.x", + "@types/geojson": "7946.0.8", + "rbush": "^3.0.1" + } + }, + "node_modules/geojson-rbush/node_modules/@types/geojson": { + "version": "7946.0.8", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz", + "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==" + }, + "node_modules/geojson-rbush/node_modules/quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" + }, + "node_modules/geojson-rbush/node_modules/rbush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", + "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==", + "dependencies": { + "quickselect": "^2.0.0" + } + }, "node_modules/get-assigned-identifiers": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", @@ -3483,7 +5180,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", - "dev": true, "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -3550,7 +5246,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "dependencies": { "function-bind": "^1.1.1" }, @@ -3587,11 +5282,21 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -3603,7 +5308,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, "dependencies": { "has-symbols": "^1.0.2" }, @@ -3821,7 +5525,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -3881,6 +5584,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3935,6 +5652,21 @@ "node": ">=0.12.0" } }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-typed-array": { "version": "1.1.10", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", @@ -4405,11 +6137,33 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", @@ -4578,6 +6332,19 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/point-in-polygon": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/point-in-polygon/-/point-in-polygon-1.1.0.tgz", + "integrity": "sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw==" + }, + "node_modules/polygon-clipping": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/polygon-clipping/-/polygon-clipping-0.15.3.tgz", + "integrity": "sha512-ho0Xx5DLkgxRx/+n4O74XyJ67DcyN3Tu9bGYKsnTukGAW6ssnuak6Mwcyb1wHy9MZc9xsUWqIoiazkZB5weECg==", + "dependencies": { + "splaytree": "^3.1.0" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -4664,6 +6431,11 @@ "node": ">=0.4.x" } }, + "node_modules/quickselect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-1.1.1.tgz", + "integrity": "sha512-qN0Gqdw4c4KGPsBOQafj6yj/PA6c/L63f6CaZ/DCF/xF4Esu3jVmKLUDYxghFx8Kb/O7y9tI7x2RjTSXwdK1iQ==" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -4705,6 +6477,14 @@ "node": ">= 0.8" } }, + "node_modules/rbush": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-2.0.2.tgz", + "integrity": "sha512-XBOuALcTm+O/H8G90b6pzu6nX6v2zCKiFG4BJho8a+bY6AER6t8uQUZdi5bomQc0AprCWhEGa7ncAbbRap0bRA==", + "dependencies": { + "quickselect": "^1.0.1" + } + }, "node_modules/read-only-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", @@ -4783,6 +6563,22 @@ "@babel/runtime": "^7.8.4" } }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/regexpu-core": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", @@ -4857,6 +6653,11 @@ "inherits": "^2.0.1" } }, + "node_modules/robust-predicates": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-2.0.4.tgz", + "integrity": "sha512-l4NwboJM74Ilm4VKfbAtFeGq7aEjWL+5kVFcmgFA2MrdnQWx9iE/tUGvxY5HyMI7o/WpSIUFLbC5fbeaHgSCYg==" + }, "node_modules/rxjs": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", @@ -5010,6 +6811,11 @@ "semver": "bin/semver.js" } }, + "node_modules/skmeans": { + "version": "0.9.7", + "resolved": "https://registry.npmjs.org/skmeans/-/skmeans-0.9.7.tgz", + "integrity": "sha512-hNj1/oZ7ygsfmPZ7ZfN5MUBRoGg1gtpnImuJBgLO0ljQ67DtJuiQaiYdS4lUA6s0KCwnPhGivtC/WRwIZLkHyg==" + }, "node_modules/sortablejs": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz", @@ -5031,6 +6837,11 @@ "integrity": "sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==", "dev": true }, + "node_modules/splaytree": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/splaytree/-/splaytree-3.1.2.tgz", + "integrity": "sha512-4OM2BJgC5UzrhVnnJA4BkHKGtjXNzzUfpQjCO8I05xYPsfS/VuQDwjCGGMi8rYQilHEV4j8NBqTFbls/PZEE7A==" + }, "node_modules/split": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", @@ -5285,6 +7096,11 @@ "node": ">=0.6.0" } }, + "node_modules/tinyqueue": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", + "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==" + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -5306,6 +7122,30 @@ "node": ">=8.0" } }, + "node_modules/topojson-client": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", + "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==", + "dependencies": { + "commander": "2" + }, + "bin": { + "topo2geo": "bin/topo2geo", + "topomerge": "bin/topomerge", + "topoquantize": "bin/topoquantize" + } + }, + "node_modules/topojson-server": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/topojson-server/-/topojson-server-3.0.1.tgz", + "integrity": "sha512-/VS9j/ffKr2XAOjlZ9CgyyeLmgJ9dMwq6Y0YEON8O7p/tGGk+dCWnrE03zEdu7i4L7YsFZLEPZPzCvcB7lEEXw==", + "dependencies": { + "commander": "2" + }, + "bin": { + "geo2topo": "bin/geo2topo" + } + }, "node_modules/touch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", @@ -5372,6 +7212,11 @@ "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", "dev": true }, + "node_modules/turf-jsts": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/turf-jsts/-/turf-jsts-1.2.3.tgz", + "integrity": "sha512-Ja03QIJlPuHt4IQ2FfGex4F4JAr8m3jpaHbFbQrgwr7s7L6U8ocrHiF3J1+wf9jzhGKxvDeaCAnGDot8OjGFyA==" + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -6982,6 +8827,1249 @@ "tslib": "^2.5.0" } }, + "@turf/along": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/along/-/along-6.5.0.tgz", + "integrity": "sha512-LLyWQ0AARqJCmMcIEAXF4GEu8usmd4Kbz3qk1Oy5HoRNpZX47+i5exQtmIWKdqJ1MMhW26fCTXgpsEs5zgJ5gw==", + "requires": { + "@turf/bearing": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/angle": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/angle/-/angle-6.5.0.tgz", + "integrity": "sha512-4pXMbWhFofJJAOvTMCns6N4C8CMd5Ih4O2jSAG9b3dDHakj3O4yN1+Zbm+NUei+eVEZ9gFeVp9svE3aMDenIkw==", + "requires": { + "@turf/bearing": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/rhumb-bearing": "^6.5.0" + } + }, + "@turf/area": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/area/-/area-6.5.0.tgz", + "integrity": "sha512-xCZdiuojokLbQ+29qR6qoMD89hv+JAgWjLrwSEWL+3JV8IXKeNFl6XkEJz9HGkVpnXvQKJoRz4/liT+8ZZ5Jyg==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/bbox": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bbox/-/bbox-6.5.0.tgz", + "integrity": "sha512-RBbLaao5hXTYyyg577iuMtDB8ehxMlUqHEJiMs8jT1GHkFhr6sYre3lmLsPeYEi/ZKj5TP5tt7fkzNdJ4GIVyw==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/bbox-clip": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bbox-clip/-/bbox-clip-6.5.0.tgz", + "integrity": "sha512-F6PaIRF8WMp8EmgU/Ke5B1Y6/pia14UAYB5TiBC668w5rVVjy5L8rTm/m2lEkkDMHlzoP9vNY4pxpNthE7rLcQ==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/bbox-polygon": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bbox-polygon/-/bbox-polygon-6.5.0.tgz", + "integrity": "sha512-+/r0NyL1lOG3zKZmmf6L8ommU07HliP4dgYToMoTxqzsWzyLjaj/OzgQ8rBmv703WJX+aS6yCmLuIhYqyufyuw==", + "requires": { + "@turf/helpers": "^6.5.0" + } + }, + "@turf/bearing": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bearing/-/bearing-6.5.0.tgz", + "integrity": "sha512-dxINYhIEMzgDOztyMZc20I7ssYVNEpSv04VbMo5YPQsqa80KO3TFvbuCahMsCAW5z8Tncc8dwBlEFrmRjJG33A==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/bezier-spline": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/bezier-spline/-/bezier-spline-6.5.0.tgz", + "integrity": "sha512-vokPaurTd4PF96rRgGVm6zYYC5r1u98ZsG+wZEv9y3kJTuJRX/O3xIY2QnTGTdbVmAJN1ouOsD0RoZYaVoXORQ==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/boolean-clockwise": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-clockwise/-/boolean-clockwise-6.5.0.tgz", + "integrity": "sha512-45+C7LC5RMbRWrxh3Z0Eihsc8db1VGBO5d9BLTOAwU4jR6SgsunTfRWR16X7JUwIDYlCVEmnjcXJNi/kIU3VIw==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/boolean-contains": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-contains/-/boolean-contains-6.5.0.tgz", + "integrity": "sha512-4m8cJpbw+YQcKVGi8y0cHhBUnYT+QRfx6wzM4GI1IdtYH3p4oh/DOBJKrepQyiDzFDaNIjxuWXBh0ai1zVwOQQ==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/boolean-point-on-line": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/boolean-crosses": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-crosses/-/boolean-crosses-6.5.0.tgz", + "integrity": "sha512-gvshbTPhAHporTlQwBJqyfW+2yV8q/mOTxG6PzRVl6ARsqNoqYQWkd4MLug7OmAqVyBzLK3201uAeBjxbGw0Ng==", + "requires": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/polygon-to-line": "^6.5.0" + } + }, + "@turf/boolean-disjoint": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-disjoint/-/boolean-disjoint-6.5.0.tgz", + "integrity": "sha512-rZ2ozlrRLIAGo2bjQ/ZUu4oZ/+ZjGvLkN5CKXSKBcu6xFO6k2bgqeM8a1836tAW+Pqp/ZFsTA5fZHsJZvP2D5g==", + "requires": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/polygon-to-line": "^6.5.0" + } + }, + "@turf/boolean-equal": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-equal/-/boolean-equal-6.5.0.tgz", + "integrity": "sha512-cY0M3yoLC26mhAnjv1gyYNQjn7wxIXmL2hBmI/qs8g5uKuC2hRWi13ydufE3k4x0aNRjFGlg41fjoYLwaVF+9Q==", + "requires": { + "@turf/clean-coords": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "geojson-equality": "0.1.6" + } + }, + "@turf/boolean-intersects": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-intersects/-/boolean-intersects-6.5.0.tgz", + "integrity": "sha512-nIxkizjRdjKCYFQMnml6cjPsDOBCThrt+nkqtSEcxkKMhAQj5OO7o2CecioNTaX8EayqwMGVKcsz27oP4mKPTw==", + "requires": { + "@turf/boolean-disjoint": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/boolean-overlap": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-overlap/-/boolean-overlap-6.5.0.tgz", + "integrity": "sha512-8btMIdnbXVWUa1M7D4shyaSGxLRw6NjMcqKBcsTXcZdnaixl22k7ar7BvIzkaRYN3SFECk9VGXfLncNS3ckQUw==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/line-overlap": "^6.5.0", + "@turf/meta": "^6.5.0", + "geojson-equality": "0.1.6" + } + }, + "@turf/boolean-parallel": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-parallel/-/boolean-parallel-6.5.0.tgz", + "integrity": "sha512-aSHJsr1nq9e5TthZGZ9CZYeXklJyRgR5kCLm5X4urz7+MotMOp/LsGOsvKvK9NeUl9+8OUmfMn8EFTT8LkcvIQ==", + "requires": { + "@turf/clean-coords": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/line-segment": "^6.5.0", + "@turf/rhumb-bearing": "^6.5.0" + } + }, + "@turf/boolean-point-in-polygon": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-point-in-polygon/-/boolean-point-in-polygon-6.5.0.tgz", + "integrity": "sha512-DtSuVFB26SI+hj0SjrvXowGTUCHlgevPAIsukssW6BG5MlNSBQAo70wpICBNJL6RjukXg8d2eXaAWuD/CqL00A==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/boolean-point-on-line": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-point-on-line/-/boolean-point-on-line-6.5.0.tgz", + "integrity": "sha512-A1BbuQ0LceLHvq7F/P7w3QvfpmZqbmViIUPHdNLvZimFNLo4e6IQunmzbe+8aSStH9QRZm3VOflyvNeXvvpZEQ==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/boolean-within": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/boolean-within/-/boolean-within-6.5.0.tgz", + "integrity": "sha512-YQB3oU18Inx35C/LU930D36RAVe7LDXk1kWsQ8mLmuqYn9YdPsDQTMTkLJMhoQ8EbN7QTdy333xRQ4MYgToteQ==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/boolean-point-on-line": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/buffer": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/buffer/-/buffer-6.5.0.tgz", + "integrity": "sha512-qeX4N6+PPWbKqp1AVkBVWFerGjMYMUyencwfnkCesoznU6qvfugFHNAngNqIBVnJjZ5n8IFyOf+akcxnrt9sNg==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/center": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/projection": "^6.5.0", + "d3-geo": "1.7.1", + "turf-jsts": "*" + } + }, + "@turf/center": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/center/-/center-6.5.0.tgz", + "integrity": "sha512-T8KtMTfSATWcAX088rEDKjyvQCBkUsLnK/Txb6/8WUXIeOZyHu42G7MkdkHRoHtwieLdduDdmPLFyTdG5/e7ZQ==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/helpers": "^6.5.0" + } + }, + "@turf/center-mean": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/center-mean/-/center-mean-6.5.0.tgz", + "integrity": "sha512-AAX6f4bVn12pTVrMUiB9KrnV94BgeBKpyg3YpfnEbBpkN/znfVhL8dG8IxMAxAoSZ61Zt9WLY34HfENveuOZ7Q==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/center-median": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/center-median/-/center-median-6.5.0.tgz", + "integrity": "sha512-dT8Ndu5CiZkPrj15PBvslpuf01ky41DEYEPxS01LOxp5HOUHXp1oJxsPxvc+i/wK4BwccPNzU1vzJ0S4emd1KQ==", + "requires": { + "@turf/center-mean": "^6.5.0", + "@turf/centroid": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/center-of-mass": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/center-of-mass/-/center-of-mass-6.5.0.tgz", + "integrity": "sha512-EWrriU6LraOfPN7m1jZi+1NLTKNkuIsGLZc2+Y8zbGruvUW+QV7K0nhf7iZWutlxHXTBqEXHbKue/o79IumAsQ==", + "requires": { + "@turf/centroid": "^6.5.0", + "@turf/convex": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/centroid": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/centroid/-/centroid-6.5.0.tgz", + "integrity": "sha512-MwE1oq5E3isewPprEClbfU5pXljIK/GUOMbn22UM3IFPDJX0KeoyLNwghszkdmFp/qMGL/M13MMWvU+GNLXP/A==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/circle": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/circle/-/circle-6.5.0.tgz", + "integrity": "sha512-oU1+Kq9DgRnoSbWFHKnnUdTmtcRUMmHoV9DjTXu9vOLNV5OWtAAh1VZ+mzsioGGzoDNT/V5igbFOkMfBQc0B6A==", + "requires": { + "@turf/destination": "^6.5.0", + "@turf/helpers": "^6.5.0" + } + }, + "@turf/clean-coords": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/clean-coords/-/clean-coords-6.5.0.tgz", + "integrity": "sha512-EMX7gyZz0WTH/ET7xV8MyrExywfm9qUi0/MY89yNffzGIEHuFfqwhcCqZ8O00rZIPZHUTxpmsxQSTfzJJA1CPw==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/clone": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/clone/-/clone-6.5.0.tgz", + "integrity": "sha512-mzVtTFj/QycXOn6ig+annKrM6ZlimreKYz6f/GSERytOpgzodbQyOgkfwru100O1KQhhjSudKK4DsQ0oyi9cTw==", + "requires": { + "@turf/helpers": "^6.5.0" + } + }, + "@turf/clusters": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/clusters/-/clusters-6.5.0.tgz", + "integrity": "sha512-Y6gfnTJzQ1hdLfCsyd5zApNbfLIxYEpmDibHUqR5z03Lpe02pa78JtgrgUNt1seeO/aJ4TG1NLN8V5gOrHk04g==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/clusters-dbscan": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/clusters-dbscan/-/clusters-dbscan-6.5.0.tgz", + "integrity": "sha512-SxZEE4kADU9DqLRiT53QZBBhu8EP9skviSyl+FGj08Y01xfICM/RR9ACUdM0aEQimhpu+ZpRVcUK+2jtiCGrYQ==", + "requires": { + "@turf/clone": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0", + "density-clustering": "1.3.0" + } + }, + "@turf/clusters-kmeans": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/clusters-kmeans/-/clusters-kmeans-6.5.0.tgz", + "integrity": "sha512-DwacD5+YO8kwDPKaXwT9DV46tMBVNsbi1IzdajZu1JDSWoN7yc7N9Qt88oi+p30583O0UPVkAK+A10WAQv4mUw==", + "requires": { + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "skmeans": "0.9.7" + } + }, + "@turf/collect": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/collect/-/collect-6.5.0.tgz", + "integrity": "sha512-4dN/T6LNnRg099m97BJeOcTA5fSI8cu87Ydgfibewd2KQwBexO69AnjEFqfPX3Wj+Zvisj1uAVIZbPmSSrZkjg==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0", + "rbush": "2.x" + } + }, + "@turf/combine": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/combine/-/combine-6.5.0.tgz", + "integrity": "sha512-Q8EIC4OtAcHiJB3C4R+FpB4LANiT90t17uOd851qkM2/o6m39bfN5Mv0PWqMZIHWrrosZqRqoY9dJnzz/rJxYQ==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/concave": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/concave/-/concave-6.5.0.tgz", + "integrity": "sha512-I/sUmUC8TC5h/E2vPwxVht+nRt+TnXIPRoztDFvS8/Y0+cBDple9inLSo9nnPXMXidrBlGXZ9vQx/BjZUJgsRQ==", + "requires": { + "@turf/clone": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/tin": "^6.5.0", + "topojson-client": "3.x", + "topojson-server": "3.x" + } + }, + "@turf/convex": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/convex/-/convex-6.5.0.tgz", + "integrity": "sha512-x7ZwC5z7PJB0SBwNh7JCeCNx7Iu+QSrH7fYgK0RhhNop13TqUlvHMirMLRgf2db1DqUetrAO2qHJeIuasquUWg==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0", + "concaveman": "*" + } + }, + "@turf/destination": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/destination/-/destination-6.5.0.tgz", + "integrity": "sha512-4cnWQlNC8d1tItOz9B4pmJdWpXqS0vEvv65bI/Pj/genJnsL7evI0/Xw42RvEGROS481MPiU80xzvwxEvhQiMQ==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/difference": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/difference/-/difference-6.5.0.tgz", + "integrity": "sha512-l8iR5uJqvI+5Fs6leNbhPY5t/a3vipUF/3AeVLpwPQcgmedNXyheYuy07PcMGH5Jdpi5gItOiTqwiU/bUH4b3A==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "polygon-clipping": "^0.15.3" + } + }, + "@turf/dissolve": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/dissolve/-/dissolve-6.5.0.tgz", + "integrity": "sha512-WBVbpm9zLTp0Bl9CE35NomTaOL1c4TQCtEoO43YaAhNEWJOOIhZMFJyr8mbvYruKl817KinT3x7aYjjCMjTAsQ==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "polygon-clipping": "^0.15.3" + } + }, + "@turf/distance": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/distance/-/distance-6.5.0.tgz", + "integrity": "sha512-xzykSLfoURec5qvQJcfifw/1mJa+5UwByZZ5TZ8iaqjGYN0vomhV9aiSLeYdUGtYRESZ+DYC/OzY+4RclZYgMg==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/distance-weight": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/distance-weight/-/distance-weight-6.5.0.tgz", + "integrity": "sha512-a8qBKkgVNvPKBfZfEJZnC3DV7dfIsC3UIdpRci/iap/wZLH41EmS90nM+BokAJflUHYy8PqE44wySGWHN1FXrQ==", + "requires": { + "@turf/centroid": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/ellipse": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/ellipse/-/ellipse-6.5.0.tgz", + "integrity": "sha512-kuXtwFviw/JqnyJXF1mrR/cb496zDTSbGKtSiolWMNImYzGGkbsAsFTjwJYgD7+4FixHjp0uQPzo70KDf3AIBw==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/rhumb-destination": "^6.5.0", + "@turf/transform-rotate": "^6.5.0" + } + }, + "@turf/envelope": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/envelope/-/envelope-6.5.0.tgz", + "integrity": "sha512-9Z+FnBWvOGOU4X+fMZxYFs1HjFlkKqsddLuMknRaqcJd6t+NIv5DWvPtDL8ATD2GEExYDiFLwMdckfr1yqJgHA==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/bbox-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0" + } + }, + "@turf/explode": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/explode/-/explode-6.5.0.tgz", + "integrity": "sha512-6cSvMrnHm2qAsace6pw9cDmK2buAlw8+tjeJVXMfMyY+w7ZUi1rprWMsY92J7s2Dar63Bv09n56/1V7+tcj52Q==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/flatten": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/flatten/-/flatten-6.5.0.tgz", + "integrity": "sha512-IBZVwoNLVNT6U/bcUUllubgElzpMsNoCw8tLqBw6dfYg9ObGmpEjf9BIYLr7a2Yn5ZR4l7YIj2T7kD5uJjZADQ==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/flip": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/flip/-/flip-6.5.0.tgz", + "integrity": "sha512-oyikJFNjt2LmIXQqgOGLvt70RgE2lyzPMloYWM7OR5oIFGRiBvqVD2hA6MNw6JewIm30fWZ8DQJw1NHXJTJPbg==", + "requires": { + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/great-circle": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/great-circle/-/great-circle-6.5.0.tgz", + "integrity": "sha512-7ovyi3HaKOXdFyN7yy1yOMa8IyOvV46RC1QOQTT+RYUN8ke10eyqExwBpL9RFUPvlpoTzoYbM/+lWPogQlFncg==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/helpers": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/helpers/-/helpers-6.5.0.tgz", + "integrity": "sha512-VbI1dV5bLFzohYYdgqwikdMVpe7pJ9X3E+dlr425wa2/sMJqYDhTO++ec38/pcPvPE6oD9WEEeU3Xu3gza+VPw==" + }, + "@turf/hex-grid": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/hex-grid/-/hex-grid-6.5.0.tgz", + "integrity": "sha512-Ln3tc2tgZT8etDOldgc6e741Smg1CsMKAz1/Mlel+MEL5Ynv2mhx3m0q4J9IB1F3a4MNjDeVvm8drAaf9SF33g==", + "requires": { + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/intersect": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/interpolate": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/interpolate/-/interpolate-6.5.0.tgz", + "integrity": "sha512-LSH5fMeiGyuDZ4WrDJNgh81d2DnNDUVJtuFryJFup8PV8jbs46lQGfI3r1DJ2p1IlEJIz3pmAZYeTfMMoeeohw==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/centroid": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/hex-grid": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/point-grid": "^6.5.0", + "@turf/square-grid": "^6.5.0", + "@turf/triangle-grid": "^6.5.0" + } + }, + "@turf/intersect": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/intersect/-/intersect-6.5.0.tgz", + "integrity": "sha512-2legGJeKrfFkzntcd4GouPugoqPUjexPZnOvfez+3SfIMrHvulw8qV8u7pfVyn2Yqs53yoVCEjS5sEpvQ5YRQg==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "polygon-clipping": "^0.15.3" + } + }, + "@turf/invariant": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/invariant/-/invariant-6.5.0.tgz", + "integrity": "sha512-Wv8PRNCtPD31UVbdJE/KVAWKe7l6US+lJItRR/HOEW3eh+U/JwRCSUl/KZ7bmjM/C+zLNoreM2TU6OoLACs4eg==", + "requires": { + "@turf/helpers": "^6.5.0" + } + }, + "@turf/isobands": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/isobands/-/isobands-6.5.0.tgz", + "integrity": "sha512-4h6sjBPhRwMVuFaVBv70YB7eGz+iw0bhPRnp+8JBdX1UPJSXhoi/ZF2rACemRUr0HkdVB/a1r9gC32vn5IAEkw==", + "requires": { + "@turf/area": "^6.5.0", + "@turf/bbox": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/explode": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "object-assign": "*" + } + }, + "@turf/isolines": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/isolines/-/isolines-6.5.0.tgz", + "integrity": "sha512-6ElhiLCopxWlv4tPoxiCzASWt/jMRvmp6mRYrpzOm3EUl75OhHKa/Pu6Y9nWtCMmVC/RcWtiiweUocbPLZLm0A==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "object-assign": "*" + } + }, + "@turf/kinks": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/kinks/-/kinks-6.5.0.tgz", + "integrity": "sha512-ViCngdPt1eEL7hYUHR2eHR662GvCgTc35ZJFaNR6kRtr6D8plLaDju0FILeFFWSc+o8e3fwxZEJKmFj9IzPiIQ==", + "requires": { + "@turf/helpers": "^6.5.0" + } + }, + "@turf/length": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/length/-/length-6.5.0.tgz", + "integrity": "sha512-5pL5/pnw52fck3oRsHDcSGrj9HibvtlrZ0QNy2OcW8qBFDNgZ4jtl6U7eATVoyWPKBHszW3dWETW+iLV7UARig==", + "requires": { + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/line-arc": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-arc/-/line-arc-6.5.0.tgz", + "integrity": "sha512-I6c+V6mIyEwbtg9P9zSFF89T7QPe1DPTG3MJJ6Cm1MrAY0MdejwQKOpsvNl8LDU2ekHOlz2kHpPVR7VJsoMllA==", + "requires": { + "@turf/circle": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/helpers": "^6.5.0" + } + }, + "@turf/line-chunk": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-chunk/-/line-chunk-6.5.0.tgz", + "integrity": "sha512-i1FGE6YJaaYa+IJesTfyRRQZP31QouS+wh/pa6O3CC0q4T7LtHigyBSYjrbjSLfn2EVPYGlPCMFEqNWCOkC6zg==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/length": "^6.5.0", + "@turf/line-slice-along": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/line-intersect": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-intersect/-/line-intersect-6.5.0.tgz", + "integrity": "sha512-CS6R1tZvVQD390G9Ea4pmpM6mJGPWoL82jD46y0q1KSor9s6HupMIo1kY4Ny+AEYQl9jd21V3Scz20eldpbTVA==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-segment": "^6.5.0", + "@turf/meta": "^6.5.0", + "geojson-rbush": "3.x" + } + }, + "@turf/line-offset": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-offset/-/line-offset-6.5.0.tgz", + "integrity": "sha512-CEXZbKgyz8r72qRvPchK0dxqsq8IQBdH275FE6o4MrBkzMcoZsfSjghtXzKaz9vvro+HfIXal0sTk2mqV1lQTw==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/line-overlap": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-overlap/-/line-overlap-6.5.0.tgz", + "integrity": "sha512-xHOaWLd0hkaC/1OLcStCpfq55lPHpPNadZySDXYiYjEz5HXr1oKmtMYpn0wGizsLwrOixRdEp+j7bL8dPt4ojQ==", + "requires": { + "@turf/boolean-point-on-line": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-segment": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/nearest-point-on-line": "^6.5.0", + "deep-equal": "1.x", + "geojson-rbush": "3.x" + } + }, + "@turf/line-segment": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-segment/-/line-segment-6.5.0.tgz", + "integrity": "sha512-jI625Ho4jSuJESNq66Mmi290ZJ5pPZiQZruPVpmHkUw257Pew0alMmb6YrqYNnLUuiVVONxAAKXUVeeUGtycfw==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/line-slice": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-slice/-/line-slice-6.5.0.tgz", + "integrity": "sha512-vDqJxve9tBHhOaVVFXqVjF5qDzGtKWviyjbyi2QnSnxyFAmLlLnBfMX8TLQCAf2GxHibB95RO5FBE6I2KVPRuw==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/nearest-point-on-line": "^6.5.0" + } + }, + "@turf/line-slice-along": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-slice-along/-/line-slice-along-6.5.0.tgz", + "integrity": "sha512-KHJRU6KpHrAj+BTgTNqby6VCTnDzG6a1sJx/I3hNvqMBLvWVA2IrkR9L9DtsQsVY63IBwVdQDqiwCuZLDQh4Ng==", + "requires": { + "@turf/bearing": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0" + } + }, + "@turf/line-split": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-split/-/line-split-6.5.0.tgz", + "integrity": "sha512-/rwUMVr9OI2ccJjw7/6eTN53URtGThNSD5I0GgxyFXMtxWiloRJ9MTff8jBbtPWrRka/Sh2GkwucVRAEakx9Sw==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/line-segment": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/nearest-point-on-line": "^6.5.0", + "@turf/square": "^6.5.0", + "@turf/truncate": "^6.5.0", + "geojson-rbush": "3.x" + } + }, + "@turf/line-to-polygon": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/line-to-polygon/-/line-to-polygon-6.5.0.tgz", + "integrity": "sha512-qYBuRCJJL8Gx27OwCD1TMijM/9XjRgXH/m/TyuND4OXedBpIWlK5VbTIO2gJ8OCfznBBddpjiObLBrkuxTpN4Q==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/mask": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/mask/-/mask-6.5.0.tgz", + "integrity": "sha512-RQha4aU8LpBrmrkH8CPaaoAfk0Egj5OuXtv6HuCQnHeGNOQt3TQVibTA3Sh4iduq4EPxnZfDjgsOeKtrCA19lg==", + "requires": { + "@turf/helpers": "^6.5.0", + "polygon-clipping": "^0.15.3" + } + }, + "@turf/meta": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/meta/-/meta-6.5.0.tgz", + "integrity": "sha512-RrArvtsV0vdsCBegoBtOalgdSOfkBrTJ07VkpiCnq/491W67hnMWmDu7e6Ztw0C3WldRYTXkg3SumfdzZxLBHA==", + "requires": { + "@turf/helpers": "^6.5.0" + } + }, + "@turf/midpoint": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/midpoint/-/midpoint-6.5.0.tgz", + "integrity": "sha512-MyTzV44IwmVI6ec9fB2OgZ53JGNlgOpaYl9ArKoF49rXpL84F9rNATndbe0+MQIhdkw8IlzA6xVP4lZzfMNVCw==", + "requires": { + "@turf/bearing": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0" + } + }, + "@turf/moran-index": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/moran-index/-/moran-index-6.5.0.tgz", + "integrity": "sha512-ItsnhrU2XYtTtTudrM8so4afBCYWNaB0Mfy28NZwLjB5jWuAsvyV+YW+J88+neK/ougKMTawkmjQqodNJaBeLQ==", + "requires": { + "@turf/distance-weight": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/nearest-point": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/nearest-point/-/nearest-point-6.5.0.tgz", + "integrity": "sha512-fguV09QxilZv/p94s8SMsXILIAMiaXI5PATq9d7YWijLxWUj6Q/r43kxyoi78Zmwwh1Zfqz9w+bCYUAxZ5+euA==", + "requires": { + "@turf/clone": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/nearest-point-on-line": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/nearest-point-on-line/-/nearest-point-on-line-6.5.0.tgz", + "integrity": "sha512-WthrvddddvmymnC+Vf7BrkHGbDOUu6Z3/6bFYUGv1kxw8tiZ6n83/VG6kHz4poHOfS0RaNflzXSkmCi64fLBlg==", + "requires": { + "@turf/bearing": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/nearest-point-to-line": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/nearest-point-to-line/-/nearest-point-to-line-6.5.0.tgz", + "integrity": "sha512-PXV7cN0BVzUZdjj6oeb/ESnzXSfWmEMrsfZSDRgqyZ9ytdiIj/eRsnOXLR13LkTdXVOJYDBuf7xt1mLhM4p6+Q==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/point-to-line-distance": "^6.5.0", + "object-assign": "*" + } + }, + "@turf/planepoint": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/planepoint/-/planepoint-6.5.0.tgz", + "integrity": "sha512-R3AahA6DUvtFbka1kcJHqZ7DMHmPXDEQpbU5WaglNn7NaCQg9HB0XM0ZfqWcd5u92YXV+Gg8QhC8x5XojfcM4Q==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/point-grid": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/point-grid/-/point-grid-6.5.0.tgz", + "integrity": "sha512-Iq38lFokNNtQJnOj/RBKmyt6dlof0yhaHEDELaWHuECm1lIZLY3ZbVMwbs+nXkwTAHjKfS/OtMheUBkw+ee49w==", + "requires": { + "@turf/boolean-within": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/point-on-feature": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/point-on-feature/-/point-on-feature-6.5.0.tgz", + "integrity": "sha512-bDpuIlvugJhfcF/0awAQ+QI6Om1Y1FFYE8Y/YdxGRongivix850dTeXCo0mDylFdWFPGDo7Mmh9Vo4VxNwW/TA==", + "requires": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/center": "^6.5.0", + "@turf/explode": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/nearest-point": "^6.5.0" + } + }, + "@turf/point-to-line-distance": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/point-to-line-distance/-/point-to-line-distance-6.5.0.tgz", + "integrity": "sha512-opHVQ4vjUhNBly1bob6RWy+F+hsZDH9SA0UW36pIRzfpu27qipU18xup0XXEePfY6+wvhF6yL/WgCO2IbrLqEA==", + "requires": { + "@turf/bearing": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/projection": "^6.5.0", + "@turf/rhumb-bearing": "^6.5.0", + "@turf/rhumb-distance": "^6.5.0" + } + }, + "@turf/points-within-polygon": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/points-within-polygon/-/points-within-polygon-6.5.0.tgz", + "integrity": "sha512-YyuheKqjliDsBDt3Ho73QVZk1VXX1+zIA2gwWvuz8bR1HXOkcuwk/1J76HuFMOQI3WK78wyAi+xbkx268PkQzQ==", + "requires": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/polygon-smooth": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/polygon-smooth/-/polygon-smooth-6.5.0.tgz", + "integrity": "sha512-LO/X/5hfh/Rk4EfkDBpLlVwt3i6IXdtQccDT9rMjXEP32tRgy0VMFmdkNaXoGlSSKf/1mGqLl4y4wHd86DqKbg==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/polygon-tangents": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/polygon-tangents/-/polygon-tangents-6.5.0.tgz", + "integrity": "sha512-sB4/IUqJMYRQH9jVBwqS/XDitkEfbyqRy+EH/cMRJURTg78eHunvJ708x5r6umXsbiUyQU4eqgPzEylWEQiunw==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/boolean-within": "^6.5.0", + "@turf/explode": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/nearest-point": "^6.5.0" + } + }, + "@turf/polygon-to-line": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/polygon-to-line/-/polygon-to-line-6.5.0.tgz", + "integrity": "sha512-5p4n/ij97EIttAq+ewSnKt0ruvuM+LIDzuczSzuHTpq4oS7Oq8yqg5TQ4nzMVuK41r/tALCk7nAoBuw3Su4Gcw==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/polygonize": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/polygonize/-/polygonize-6.5.0.tgz", + "integrity": "sha512-a/3GzHRaCyzg7tVYHo43QUChCspa99oK4yPqooVIwTC61npFzdrmnywMv0S+WZjHZwK37BrFJGFrZGf6ocmY5w==", + "requires": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/envelope": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/projection": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/projection/-/projection-6.5.0.tgz", + "integrity": "sha512-/Pgh9mDvQWWu8HRxqpM+tKz8OzgauV+DiOcr3FCjD6ubDnrrmMJlsf6fFJmggw93mtVPrZRL6yyi9aYCQBOIvg==", + "requires": { + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/random": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/random/-/random-6.5.0.tgz", + "integrity": "sha512-8Q25gQ/XbA7HJAe+eXp4UhcXM9aOOJFaxZ02+XSNwMvY8gtWSCBLVqRcW4OhqilgZ8PeuQDWgBxeo+BIqqFWFQ==", + "requires": { + "@turf/helpers": "^6.5.0" + } + }, + "@turf/rectangle-grid": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/rectangle-grid/-/rectangle-grid-6.5.0.tgz", + "integrity": "sha512-yQZ/1vbW68O2KsSB3OZYK+72aWz/Adnf7m2CMKcC+aq6TwjxZjAvlbCOsNUnMAuldRUVN1ph6RXMG4e9KEvKvg==", + "requires": { + "@turf/boolean-intersects": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0" + } + }, + "@turf/rewind": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/rewind/-/rewind-6.5.0.tgz", + "integrity": "sha512-IoUAMcHWotBWYwSYuYypw/LlqZmO+wcBpn8ysrBNbazkFNkLf3btSDZMkKJO/bvOzl55imr/Xj4fi3DdsLsbzQ==", + "requires": { + "@turf/boolean-clockwise": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/rhumb-bearing": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/rhumb-bearing/-/rhumb-bearing-6.5.0.tgz", + "integrity": "sha512-jMyqiMRK4hzREjQmnLXmkJ+VTNTx1ii8vuqRwJPcTlKbNWfjDz/5JqJlb5NaFDcdMpftWovkW5GevfnuzHnOYA==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/rhumb-destination": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/rhumb-destination/-/rhumb-destination-6.5.0.tgz", + "integrity": "sha512-RHNP1Oy+7xTTdRrTt375jOZeHceFbjwohPHlr9Hf68VdHHPMAWgAKqiX2YgSWDcvECVmiGaBKWus1Df+N7eE4Q==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/rhumb-distance": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/rhumb-distance/-/rhumb-distance-6.5.0.tgz", + "integrity": "sha512-oKp8KFE8E4huC2Z1a1KNcFwjVOqa99isxNOwfo4g3SUABQ6NezjKDDrnvC4yI5YZ3/huDjULLBvhed45xdCrzg==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + } + }, + "@turf/sample": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/sample/-/sample-6.5.0.tgz", + "integrity": "sha512-kSdCwY7el15xQjnXYW520heKUrHwRvnzx8ka4eYxX9NFeOxaFITLW2G7UtXb6LJK8mmPXI8Aexv23F2ERqzGFg==", + "requires": { + "@turf/helpers": "^6.5.0" + } + }, + "@turf/sector": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/sector/-/sector-6.5.0.tgz", + "integrity": "sha512-cYUOkgCTWqa23SOJBqxoFAc/yGCUsPRdn/ovbRTn1zNTm/Spmk6hVB84LCKOgHqvSF25i0d2kWqpZDzLDdAPbw==", + "requires": { + "@turf/circle": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/line-arc": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/shortest-path": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/shortest-path/-/shortest-path-6.5.0.tgz", + "integrity": "sha512-4de5+G7+P4hgSoPwn+SO9QSi9HY5NEV/xRJ+cmoFVRwv2CDsuOPDheHKeuIAhKyeKDvPvPt04XYWbac4insJMg==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/bbox-polygon": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/clean-coords": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/transform-scale": "^6.5.0" + } + }, + "@turf/simplify": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/simplify/-/simplify-6.5.0.tgz", + "integrity": "sha512-USas3QqffPHUY184dwQdP8qsvcVH/PWBYdXY5am7YTBACaQOMAlf6AKJs9FT8jiO6fQpxfgxuEtwmox+pBtlOg==", + "requires": { + "@turf/clean-coords": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/square": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/square/-/square-6.5.0.tgz", + "integrity": "sha512-BM2UyWDmiuHCadVhHXKIx5CQQbNCpOxB6S/aCNOCLbhCeypKX5Q0Aosc5YcmCJgkwO5BERCC6Ee7NMbNB2vHmQ==", + "requires": { + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0" + } + }, + "@turf/square-grid": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/square-grid/-/square-grid-6.5.0.tgz", + "integrity": "sha512-mlR0ayUdA+L4c9h7p4k3pX6gPWHNGuZkt2c5II1TJRmhLkW2557d6b/Vjfd1z9OVaajb1HinIs1FMSAPXuuUrA==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/rectangle-grid": "^6.5.0" + } + }, + "@turf/standard-deviational-ellipse": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/standard-deviational-ellipse/-/standard-deviational-ellipse-6.5.0.tgz", + "integrity": "sha512-02CAlz8POvGPFK2BKK8uHGUk/LXb0MK459JVjKxLC2yJYieOBTqEbjP0qaWhiBhGzIxSMaqe8WxZ0KvqdnstHA==", + "requires": { + "@turf/center-mean": "^6.5.0", + "@turf/ellipse": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/points-within-polygon": "^6.5.0" + } + }, + "@turf/tag": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/tag/-/tag-6.5.0.tgz", + "integrity": "sha512-XwlBvrOV38CQsrNfrxvBaAPBQgXMljeU0DV8ExOyGM7/hvuGHJw3y8kKnQ4lmEQcmcrycjDQhP7JqoRv8vFssg==", + "requires": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/tesselate": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/tesselate/-/tesselate-6.5.0.tgz", + "integrity": "sha512-M1HXuyZFCfEIIKkglh/r5L9H3c5QTEsnMBoZOFQiRnGPGmJWcaBissGb7mTFX2+DKE7FNWXh4TDnZlaLABB0dQ==", + "requires": { + "@turf/helpers": "^6.5.0", + "earcut": "^2.0.0" + } + }, + "@turf/tin": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/tin/-/tin-6.5.0.tgz", + "integrity": "sha512-YLYikRzKisfwj7+F+Tmyy/LE3d2H7D4kajajIfc9mlik2+esG7IolsX/+oUz1biguDYsG0DUA8kVYXDkobukfg==", + "requires": { + "@turf/helpers": "^6.5.0" + } + }, + "@turf/transform-rotate": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/transform-rotate/-/transform-rotate-6.5.0.tgz", + "integrity": "sha512-A2Ip1v4246ZmpssxpcL0hhiVBEf4L8lGnSPWTgSv5bWBEoya2fa/0SnFX9xJgP40rMP+ZzRaCN37vLHbv1Guag==", + "requires": { + "@turf/centroid": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/rhumb-bearing": "^6.5.0", + "@turf/rhumb-destination": "^6.5.0", + "@turf/rhumb-distance": "^6.5.0" + } + }, + "@turf/transform-scale": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/transform-scale/-/transform-scale-6.5.0.tgz", + "integrity": "sha512-VsATGXC9rYM8qTjbQJ/P7BswKWXHdnSJ35JlV4OsZyHBMxJQHftvmZJsFbOqVtQnIQIzf2OAly6rfzVV9QLr7g==", + "requires": { + "@turf/bbox": "^6.5.0", + "@turf/center": "^6.5.0", + "@turf/centroid": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/rhumb-bearing": "^6.5.0", + "@turf/rhumb-destination": "^6.5.0", + "@turf/rhumb-distance": "^6.5.0" + } + }, + "@turf/transform-translate": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/transform-translate/-/transform-translate-6.5.0.tgz", + "integrity": "sha512-NABLw5VdtJt/9vSstChp93pc6oel4qXEos56RBMsPlYB8hzNTEKYtC146XJvyF4twJeeYS8RVe1u7KhoFwEM5w==", + "requires": { + "@turf/clone": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/rhumb-destination": "^6.5.0" + } + }, + "@turf/triangle-grid": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/triangle-grid/-/triangle-grid-6.5.0.tgz", + "integrity": "sha512-2jToUSAS1R1htq4TyLQYPTIsoy6wg3e3BQXjm2rANzw4wPQCXGOxrur1Fy9RtzwqwljlC7DF4tg0OnWr8RjmfA==", + "requires": { + "@turf/distance": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/intersect": "^6.5.0" + } + }, + "@turf/truncate": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/truncate/-/truncate-6.5.0.tgz", + "integrity": "sha512-pFxg71pLk+eJj134Z9yUoRhIi8vqnnKvCYwdT4x/DQl/19RVdq1tV3yqOT3gcTQNfniteylL5qV1uTBDV5sgrg==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0" + } + }, + "@turf/turf": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/turf/-/turf-6.5.0.tgz", + "integrity": "sha512-ipMCPnhu59bh92MNt8+pr1VZQhHVuTMHklciQURo54heoxRzt1neNYZOBR6jdL+hNsbDGAECMuIpAutX+a3Y+w==", + "requires": { + "@turf/along": "^6.5.0", + "@turf/angle": "^6.5.0", + "@turf/area": "^6.5.0", + "@turf/bbox": "^6.5.0", + "@turf/bbox-clip": "^6.5.0", + "@turf/bbox-polygon": "^6.5.0", + "@turf/bearing": "^6.5.0", + "@turf/bezier-spline": "^6.5.0", + "@turf/boolean-clockwise": "^6.5.0", + "@turf/boolean-contains": "^6.5.0", + "@turf/boolean-crosses": "^6.5.0", + "@turf/boolean-disjoint": "^6.5.0", + "@turf/boolean-equal": "^6.5.0", + "@turf/boolean-intersects": "^6.5.0", + "@turf/boolean-overlap": "^6.5.0", + "@turf/boolean-parallel": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/boolean-point-on-line": "^6.5.0", + "@turf/boolean-within": "^6.5.0", + "@turf/buffer": "^6.5.0", + "@turf/center": "^6.5.0", + "@turf/center-mean": "^6.5.0", + "@turf/center-median": "^6.5.0", + "@turf/center-of-mass": "^6.5.0", + "@turf/centroid": "^6.5.0", + "@turf/circle": "^6.5.0", + "@turf/clean-coords": "^6.5.0", + "@turf/clone": "^6.5.0", + "@turf/clusters": "^6.5.0", + "@turf/clusters-dbscan": "^6.5.0", + "@turf/clusters-kmeans": "^6.5.0", + "@turf/collect": "^6.5.0", + "@turf/combine": "^6.5.0", + "@turf/concave": "^6.5.0", + "@turf/convex": "^6.5.0", + "@turf/destination": "^6.5.0", + "@turf/difference": "^6.5.0", + "@turf/dissolve": "^6.5.0", + "@turf/distance": "^6.5.0", + "@turf/distance-weight": "^6.5.0", + "@turf/ellipse": "^6.5.0", + "@turf/envelope": "^6.5.0", + "@turf/explode": "^6.5.0", + "@turf/flatten": "^6.5.0", + "@turf/flip": "^6.5.0", + "@turf/great-circle": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/hex-grid": "^6.5.0", + "@turf/interpolate": "^6.5.0", + "@turf/intersect": "^6.5.0", + "@turf/invariant": "^6.5.0", + "@turf/isobands": "^6.5.0", + "@turf/isolines": "^6.5.0", + "@turf/kinks": "^6.5.0", + "@turf/length": "^6.5.0", + "@turf/line-arc": "^6.5.0", + "@turf/line-chunk": "^6.5.0", + "@turf/line-intersect": "^6.5.0", + "@turf/line-offset": "^6.5.0", + "@turf/line-overlap": "^6.5.0", + "@turf/line-segment": "^6.5.0", + "@turf/line-slice": "^6.5.0", + "@turf/line-slice-along": "^6.5.0", + "@turf/line-split": "^6.5.0", + "@turf/line-to-polygon": "^6.5.0", + "@turf/mask": "^6.5.0", + "@turf/meta": "^6.5.0", + "@turf/midpoint": "^6.5.0", + "@turf/moran-index": "^6.5.0", + "@turf/nearest-point": "^6.5.0", + "@turf/nearest-point-on-line": "^6.5.0", + "@turf/nearest-point-to-line": "^6.5.0", + "@turf/planepoint": "^6.5.0", + "@turf/point-grid": "^6.5.0", + "@turf/point-on-feature": "^6.5.0", + "@turf/point-to-line-distance": "^6.5.0", + "@turf/points-within-polygon": "^6.5.0", + "@turf/polygon-smooth": "^6.5.0", + "@turf/polygon-tangents": "^6.5.0", + "@turf/polygon-to-line": "^6.5.0", + "@turf/polygonize": "^6.5.0", + "@turf/projection": "^6.5.0", + "@turf/random": "^6.5.0", + "@turf/rewind": "^6.5.0", + "@turf/rhumb-bearing": "^6.5.0", + "@turf/rhumb-destination": "^6.5.0", + "@turf/rhumb-distance": "^6.5.0", + "@turf/sample": "^6.5.0", + "@turf/sector": "^6.5.0", + "@turf/shortest-path": "^6.5.0", + "@turf/simplify": "^6.5.0", + "@turf/square": "^6.5.0", + "@turf/square-grid": "^6.5.0", + "@turf/standard-deviational-ellipse": "^6.5.0", + "@turf/tag": "^6.5.0", + "@turf/tesselate": "^6.5.0", + "@turf/tin": "^6.5.0", + "@turf/transform-rotate": "^6.5.0", + "@turf/transform-scale": "^6.5.0", + "@turf/transform-translate": "^6.5.0", + "@turf/triangle-grid": "^6.5.0", + "@turf/truncate": "^6.5.0", + "@turf/union": "^6.5.0", + "@turf/unkink-polygon": "^6.5.0", + "@turf/voronoi": "^6.5.0" + } + }, + "@turf/union": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/union/-/union-6.5.0.tgz", + "integrity": "sha512-igYWCwP/f0RFHIlC2c0SKDuM/ObBaqSljI3IdV/x71805QbIvY/BYGcJdyNcgEA6cylIGl/0VSlIbpJHZ9ldhw==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "polygon-clipping": "^0.15.3" + } + }, + "@turf/unkink-polygon": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/unkink-polygon/-/unkink-polygon-6.5.0.tgz", + "integrity": "sha512-8QswkzC0UqKmN1DT6HpA9upfa1HdAA5n6bbuzHy8NJOX8oVizVAqfEPY0wqqTgboDjmBR4yyImsdPGUl3gZ8JQ==", + "requires": { + "@turf/area": "^6.5.0", + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0", + "@turf/meta": "^6.5.0", + "rbush": "^2.0.1" + } + }, + "@turf/voronoi": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@turf/voronoi/-/voronoi-6.5.0.tgz", + "integrity": "sha512-C/xUsywYX+7h1UyNqnydHXiun4UPjK88VDghtoRypR9cLlb7qozkiLRphQxxsCM0KxyxpVPHBVQXdAL3+Yurow==", + "requires": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0", + "d3-voronoi": "1.1.2" + } + }, "@types/formatcoords": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@types/formatcoords/-/formatcoords-1.1.0.tgz", @@ -7654,7 +10742,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, "requires": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -7737,6 +10824,11 @@ "source-map": "~0.5.3" } }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -7754,6 +10846,32 @@ "typedarray": "^0.0.6" } }, + "concaveman": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/concaveman/-/concaveman-1.2.1.tgz", + "integrity": "sha512-PwZYKaM/ckQSa8peP5JpVr7IMJ4Nn/MHIaWUjP4be+KoZ7Botgs8seAZGpmaOM+UZXawcdYRao/px9ycrCihHw==", + "requires": { + "point-in-polygon": "^1.1.0", + "rbush": "^3.0.1", + "robust-predicates": "^2.0.4", + "tinyqueue": "^2.0.3" + }, + "dependencies": { + "quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" + }, + "rbush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", + "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==", + "requires": { + "quickselect": "^2.0.0" + } + } + } + }, "concurrently": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.6.0.tgz", @@ -7920,6 +11038,24 @@ "randomfill": "^1.0.3" } }, + "d3-array": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", + "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" + }, + "d3-geo": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.7.1.tgz", + "integrity": "sha512-O4AempWAr+P5qbk2bC2FuN/sDW4z+dN2wDf9QV3bxQt4M5HfOEeXLgJ/UKQW0+o1Dj8BE+L5kiDbdWUMjsmQpw==", + "requires": { + "d3-array": "1" + } + }, + "d3-voronoi": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.2.tgz", + "integrity": "sha512-RhGS1u2vavcO7ay7ZNAPo4xeDh/VYeGof3x5ZLJBQgYhLegxr3s5IykvWmJ94FTU6mcbtp4sloqZ54mP6R4Utw==" + }, "dash-ast": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", @@ -7940,12 +11076,39 @@ "ms": "2.0.0" } }, + "deep-equal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "requires": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + } + }, + "define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, "defined": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz", "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==", "dev": true }, + "density-clustering": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/density-clustering/-/density-clustering-1.3.0.tgz", + "integrity": "sha512-icpmBubVTwLnsaor9qH/4tG5+7+f61VcqMN3V3pm9sxxSCt2Jcs0zWOgwZW9ARJYaKD3FumIgHiMOcIMRRAzFQ==" + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -8028,6 +11191,11 @@ "readable-stream": "^2.0.2" } }, + "earcut": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz", + "integrity": "sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==" + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -8325,8 +11493,12 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" }, "gensync": { "version": "1.0.0-beta.2", @@ -8334,6 +11506,46 @@ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true }, + "geojson-equality": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/geojson-equality/-/geojson-equality-0.1.6.tgz", + "integrity": "sha512-TqG8YbqizP3EfwP5Uw4aLu6pKkg6JQK9uq/XZ1lXQntvTHD1BBKJWhNpJ2M0ax6TuWMP3oyx6Oq7FCIfznrgpQ==", + "requires": { + "deep-equal": "^1.0.0" + } + }, + "geojson-rbush": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/geojson-rbush/-/geojson-rbush-3.2.0.tgz", + "integrity": "sha512-oVltQTXolxvsz1sZnutlSuLDEcQAKYC/uXt9zDzJJ6bu0W+baTI8LZBaTup5afzibEH4N3jlq2p+a152wlBJ7w==", + "requires": { + "@turf/bbox": "*", + "@turf/helpers": "6.x", + "@turf/meta": "6.x", + "@types/geojson": "7946.0.8", + "rbush": "^3.0.1" + }, + "dependencies": { + "@types/geojson": { + "version": "7946.0.8", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.8.tgz", + "integrity": "sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==" + }, + "quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" + }, + "rbush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", + "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==", + "requires": { + "quickselect": "^2.0.0" + } + } + } + }, "get-assigned-identifiers": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", @@ -8350,7 +11562,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", - "dev": true, "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -8399,7 +11610,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -8426,17 +11636,23 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "requires": { + "get-intrinsic": "^1.1.1" + } + }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, "has-tostringtag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, "requires": { "has-symbols": "^1.0.2" } @@ -8601,7 +11817,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, "requires": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -8643,6 +11858,14 @@ "has": "^1.0.3" } }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -8679,6 +11902,15 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, "is-typed-array": { "version": "1.1.10", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", @@ -9050,8 +12282,21 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, + "object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, "on-finished": { "version": "2.3.0", @@ -9194,6 +12439,19 @@ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, + "point-in-polygon": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/point-in-polygon/-/point-in-polygon-1.1.0.tgz", + "integrity": "sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw==" + }, + "polygon-clipping": { + "version": "0.15.3", + "resolved": "https://registry.npmjs.org/polygon-clipping/-/polygon-clipping-0.15.3.tgz", + "integrity": "sha512-ho0Xx5DLkgxRx/+n4O74XyJ67DcyN3Tu9bGYKsnTukGAW6ssnuak6Mwcyb1wHy9MZc9xsUWqIoiazkZB5weECg==", + "requires": { + "splaytree": "^3.1.0" + } + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -9266,6 +12524,11 @@ "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==", "dev": true }, + "quickselect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-1.1.1.tgz", + "integrity": "sha512-qN0Gqdw4c4KGPsBOQafj6yj/PA6c/L63f6CaZ/DCF/xF4Esu3jVmKLUDYxghFx8Kb/O7y9tI7x2RjTSXwdK1iQ==" + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -9301,6 +12564,14 @@ "unpipe": "1.0.0" } }, + "rbush": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-2.0.2.tgz", + "integrity": "sha512-XBOuALcTm+O/H8G90b6pzu6nX6v2zCKiFG4BJho8a+bY6AER6t8uQUZdi5bomQc0AprCWhEGa7ncAbbRap0bRA==", + "requires": { + "quickselect": "^1.0.1" + } + }, "read-only-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", @@ -9375,6 +12646,16 @@ "@babel/runtime": "^7.8.4" } }, + "regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + } + }, "regexpu-core": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", @@ -9433,6 +12714,11 @@ "inherits": "^2.0.1" } }, + "robust-predicates": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-2.0.4.tgz", + "integrity": "sha512-l4NwboJM74Ilm4VKfbAtFeGq7aEjWL+5kVFcmgFA2MrdnQWx9iE/tUGvxY5HyMI7o/WpSIUFLbC5fbeaHgSCYg==" + }, "rxjs": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.0.tgz", @@ -9553,6 +12839,11 @@ } } }, + "skmeans": { + "version": "0.9.7", + "resolved": "https://registry.npmjs.org/skmeans/-/skmeans-0.9.7.tgz", + "integrity": "sha512-hNj1/oZ7ygsfmPZ7ZfN5MUBRoGg1gtpnImuJBgLO0ljQ67DtJuiQaiYdS4lUA6s0KCwnPhGivtC/WRwIZLkHyg==" + }, "sortablejs": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.15.0.tgz", @@ -9571,6 +12862,11 @@ "integrity": "sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==", "dev": true }, + "splaytree": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/splaytree/-/splaytree-3.1.2.tgz", + "integrity": "sha512-4OM2BJgC5UzrhVnnJA4BkHKGtjXNzzUfpQjCO8I05xYPsfS/VuQDwjCGGMi8rYQilHEV4j8NBqTFbls/PZEE7A==" + }, "split": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", @@ -9781,6 +13077,11 @@ "process": "~0.11.0" } }, + "tinyqueue": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tinyqueue/-/tinyqueue-2.0.3.tgz", + "integrity": "sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==" + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -9796,6 +13097,22 @@ "is-number": "^7.0.0" } }, + "topojson-client": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", + "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==", + "requires": { + "commander": "2" + } + }, + "topojson-server": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/topojson-server/-/topojson-server-3.0.1.tgz", + "integrity": "sha512-/VS9j/ffKr2XAOjlZ9CgyyeLmgJ9dMwq6Y0YEON8O7p/tGGk+dCWnrE03zEdu7i4L7YsFZLEPZPzCvcB7lEEXw==", + "requires": { + "commander": "2" + } + }, "touch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", @@ -9849,6 +13166,11 @@ "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==", "dev": true }, + "turf-jsts": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/turf-jsts/-/turf-jsts-1.2.3.tgz", + "integrity": "sha512-Ja03QIJlPuHt4IQ2FfGex4F4JAr8m3jpaHbFbQrgwr7s7L6U8ocrHiF3J1+wf9jzhGKxvDeaCAnGDot8OjGFyA==" + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", diff --git a/client/package.json b/client/package.json index 9385bdc3..9c451789 100644 --- a/client/package.json +++ b/client/package.json @@ -10,6 +10,7 @@ "watch": "watchify .\\src\\index.ts --debug -o .\\public\\javascripts\\bundle.js -t [ babelify --global true --presets [ @babel/preset-env ] --extensions '.js'] -p [ tsify --noImplicitAny ]" }, "dependencies": { + "@turf/turf": "^6.5.0", "@types/formatcoords": "^1.1.0", "@types/geojson": "^7946.0.10", "@types/leaflet": "^1.9.0", diff --git a/client/src/controls/coalitionareacontextmenu.ts b/client/src/controls/coalitionareacontextmenu.ts index b4985950..e60e9382 100644 --- a/client/src/controls/coalitionareacontextmenu.ts +++ b/client/src/controls/coalitionareacontextmenu.ts @@ -1,11 +1,11 @@ +import { getUnitsManager } from ".."; import { CoalitionArea } from "../map/coalitionarea"; -import { groundUnitsDatabase } from "../units/groundunitsdatabase"; import { ContextMenu } from "./contextmenu"; import { Dropdown } from "./dropdown"; import { Slider } from "./slider"; import { Switch } from "./switch"; -const unitRole = ["AAA", "MANPADS", "Short range SAM", "Long range SAM", "Radar"]; +const unitRole = ["AAA", "MANPADS", "SAM Sites", "Radar"]; export class CoalitionAreaContextMenu extends ContextMenu { #coalitionSwitch: Switch; @@ -30,6 +30,22 @@ export class CoalitionAreaContextMenu extends ContextMenu { this.showSubMenu(e.detail.type); }); + 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(unitRole.map((unitRole: string) => { var div = document.createElement("div"); @@ -38,6 +54,7 @@ export class CoalitionAreaContextMenu extends ContextMenu { 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); @@ -46,8 +63,6 @@ export class CoalitionAreaContextMenu extends ContextMenu { return div as HTMLElement; })); - - this.hide(); } diff --git a/client/src/controls/dropdown.ts b/client/src/controls/dropdown.ts index 66c2a09e..b8f490ea 100644 --- a/client/src/controls/dropdown.ts +++ b/client/src/controls/dropdown.ts @@ -55,6 +55,10 @@ export class Dropdown { 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) { diff --git a/client/src/controls/mapcontextmenu.ts b/client/src/controls/mapcontextmenu.ts index 84f20333..db63ca60 100644 --- a/client/src/controls/mapcontextmenu.ts +++ b/client/src/controls/mapcontextmenu.ts @@ -14,9 +14,10 @@ export interface SpawnOptions { type: 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 { diff --git a/client/src/index.ts b/client/src/index.ts index e4760d01..c514bbef 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -223,6 +223,10 @@ export function getUnitsManager() { return unitsManager; } +export function getMissionHandler() { + return missionHandler; +} + export function getUnitInfoPanel() { return unitInfoPanel; } diff --git a/client/src/map/coalitionarea.ts b/client/src/map/coalitionarea.ts index f3ffee35..7f3f2873 100644 --- a/client/src/map/coalitionarea.ts +++ b/client/src/map/coalitionarea.ts @@ -1,20 +1,105 @@ -import { LatLngExpression, Polygon, PolylineOptions } from "leaflet"; +import { LatLng, LatLngExpression, Polygon, PolylineOptions } from "leaflet"; import { getMap } from ".."; +import { DRAW_POLYGON } from "./map"; +import { CoalitionAreaHandle } from "./coalitionareahandle"; export class CoalitionArea extends Polygon { + #coalition: string = "blue"; + #selected: boolean = true; + #editing: boolean = true; + #handles: CoalitionAreaHandle[] = []; + constructor(latlngs: LatLngExpression[] | LatLngExpression[][] | LatLngExpression[][][], options?: PolylineOptions) { if (options === undefined) options = {}; - + options.bubblingMouseEvents = false; super(latlngs, options); + this.on("click", (e: any) => { + if (!this.getSelected()) { + this.setSelected(true); + getMap().setState(DRAW_POLYGON); + } + else if (this.getEditing()) { + this.addLatLng(e.latlng); + this.addTemporaryLatLng(e.latlng); + } + }); + this.on("contextmenu", (e: any) => { - getMap().showCoalitionAreaContextMenu(e, this); - }) + if (!this.#editing) + getMap().showCoalitionAreaContextMenu(e, this); + else + this.setEditing(false); + }); + + this.#setColors(); } setCoalition(coalition: string) { - this.setStyle({color: coalition, fillColor: coalition}); + this.#coalition = coalition; + this.#setColors(); + } + + getCoalition() { + return this.#coalition; + } + + setSelected(selected: boolean) { + this.#selected = selected; + this.#setColors(); + this.#setHandles(); + if (!selected) + this.setEditing(false); + } + + getSelected() { + return this.#selected; + } + + setEditing(editing: boolean) { + this.#editing = editing; + this.#setHandles(); + } + + getEditing() { + return this.#editing; + } + + addTemporaryLatLng(latlng: LatLng) { + this.addLatLng(latlng); + } + + moveTemporaryLatLng(latlng: LatLng) { + var latlngs = this.getLatLngs()[0] as LatLng[]; + latlngs[latlngs.length - 1] = latlng; + this.setLatLngs(latlngs); + } + + addLatLng(latlng: LatLngExpression | LatLngExpression[], latlngs?: LatLng[] | undefined): this { + super.addLatLng(latlng, latlngs) + this.#setHandles(); + return this; + } + + #setColors() { + this.setStyle({color: this.getSelected()? "white": this.#coalition, fillColor: this.#coalition}); + } + + #setHandles() { + this.#handles.forEach((handle: CoalitionAreaHandle) => handle.removeFrom(getMap())); + this.#handles = []; + var latlngs = this.getLatLngs()[0] as LatLng[]; + latlngs.forEach((latlng: LatLng, idx: number) => { + const handle = new CoalitionAreaHandle(latlng); + handle.addTo(getMap()); + handle.on("dragend", (e: any) => { + var latlngs = this.getLatLngs()[0] as LatLng[]; + latlngs[idx] = e.latlng; + this.setLatLngs(latlngs); + }); + this.#handles.push(handle); + }); } } \ No newline at end of file diff --git a/client/src/map/coalitionareahandle.ts b/client/src/map/coalitionareahandle.ts new file mode 100644 index 00000000..2e761275 --- /dev/null +++ b/client/src/map/coalitionareahandle.ts @@ -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: [52, 52], + iconAnchor: [26, 26], + className: "leaflet-target-marker", + })); + var el = document.createElement("div"); + el.classList.add("ol-target-icon"); + this.getElement()?.appendChild(el); + } +} \ No newline at end of file diff --git a/client/src/map/map.ts b/client/src/map/map.ts index d19d5327..47294f19 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -55,7 +55,7 @@ export class Map extends L.Map { #destinationGroupRotation: number = 0; #computeDestinationRotation: boolean = false; #destinationRotationCenter: L.LatLng | null = null; - #polygons: CoalitionArea[] = []; + #coalitionAreas: CoalitionArea[] = []; #mapContextMenu: MapContextMenu = new MapContextMenu("map-contextmenu"); #unitContextMenu: UnitContextMenu = new UnitContextMenu("unit-contextmenu"); @@ -175,16 +175,19 @@ export class Map extends L.Map { if (this.#state === IDLE) { this.#resetDestinationMarkers(); this.#resetTargetMarker(); + this.#deselectCoalitionAreas(); this.#showCursor(); } else if (this.#state === MOVE_UNIT) { this.#resetTargetMarker(); + this.#deselectCoalitionAreas(); this.#createDestinationMarkers(); if (this.#destinationPreviewMarkers.length > 0) this.#hideCursor(); } else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) { this.#resetDestinationMarkers(); + this.#deselectCoalitionAreas(); this.#createTargetMarker(); this.#hideCursor(); } @@ -193,8 +196,8 @@ export class Map extends L.Map { this.#resetTargetMarker(); this.#showCursor(); //@ts-ignore draggable option added by plugin - this.#polygons.push(new CoalitionArea([])); - this.#polygons[this.#polygons.length - 1].addTo(this); + this.#coalitionAreas.push(new CoalitionArea([])); + this.#coalitionAreas[this.#coalitionAreas.length - 1].addTo(this); } document.dispatchEvent(new CustomEvent("mapStateChanged")); } @@ -394,6 +397,10 @@ export class Map extends L.Map { } } + getSelectedCoalitionArea() { + return this.#coalitionAreas.find((area: CoalitionArea) => {return area.getSelected()}); + } + /* Event handlers */ #onClick(e: any) { if (!this.#preventLeftClick) { @@ -402,7 +409,14 @@ export class Map extends L.Map { } else if (this.#state === DRAW_POLYGON) { - this.#polygons[this.#polygons.length - 1].addLatLng(e.latlng); + /* This gets only called to create the first point of the area. All other points are added by the area itself */ + if (this.getSelectedCoalitionArea()?.getEditing()) { + this.getSelectedCoalitionArea()?.addLatLng(e.latlng); + this.getSelectedCoalitionArea()?.addTemporaryLatLng(e.latlng); + } + else { + this.getSelectedCoalitionArea()?.setSelected(false); + } } else { this.setState(IDLE); @@ -448,17 +462,6 @@ export class Map extends L.Map { } } - #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()); - } - #onSelectionEnd(e: any) { clearTimeout(this.#leftClickTimer); this.#preventLeftClick = true; @@ -497,6 +500,10 @@ export class Map extends L.Map { else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) { this.#targetMarker.setLatLng(this.getMouseCoordinates()); } + else if (this.#state === DRAW_POLYGON) { + if (this.getSelectedCoalitionArea()?.getEditing()) + this.getSelectedCoalitionArea()?.moveTemporaryLatLng(e.latlng); + } } #onZoom(e: any) { @@ -569,6 +576,10 @@ export class Map extends L.Map { this.removeLayer(this.#targetMarker); } + #deselectCoalitionAreas() { + this.getSelectedCoalitionArea()?.setSelected(false); + } + #showCursor() { document.getElementById(this.#ID)?.classList.remove("hidden-cursor"); } diff --git a/client/src/missionhandler/missionhandler.ts b/client/src/missionhandler/missionhandler.ts index 867e08bf..50864e1c 100644 --- a/client/src/missionhandler/missionhandler.ts +++ b/client/src/missionhandler/missionhandler.ts @@ -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); @@ -55,30 +46,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 +66,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); } } \ No newline at end of file diff --git a/client/src/other/utils.ts b/client/src/other/utils.ts index d6ef4fd9..5b9419fe 100644 --- a/client/src/other/utils.ts +++ b/client/src/other/utils.ts @@ -1,3 +1,7 @@ +import { LatLng, Point, Polygon } from "leaflet"; +import * as turf from "@turf/turf"; +import { UnitDatabase } from "../units/unitdatabase"; + export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) { const φ1 = deg2rad(lat1); // φ, λ in radians const φ2 = deg2rad(lat2); @@ -184,4 +188,35 @@ 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]; } \ No newline at end of file diff --git a/client/src/server/server.ts b/client/src/server/server.ts index 01317c18..646b78f1 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -134,13 +134,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.type, "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.type, "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, () => { }); } diff --git a/client/src/units/groundunitsdatabase.ts b/client/src/units/groundunitsdatabase.ts index e5f838a9..8cc1edce 100644 --- a/client/src/units/groundunitsdatabase.ts +++ b/client/src/units/groundunitsdatabase.ts @@ -14,7 +14,7 @@ export class GroundUnitsDatabase extends UnitDatabase { "items": [ ], "roles": [ - "SAM Site" + "SAM Sites" ], "code": "", "name": "Default" @@ -31,7 +31,7 @@ export class GroundUnitsDatabase extends UnitDatabase { "fuel": 1, "items": [], "roles": [ - "SAM Site" + "SAM Sites" ], "code": "", "name": "Default" @@ -48,7 +48,7 @@ export class GroundUnitsDatabase extends UnitDatabase { "fuel": 1, "items": [], "roles": [ - "SAM Site" + "SAM Sites" ], "code": "", "name": "Default" @@ -65,7 +65,7 @@ export class GroundUnitsDatabase extends UnitDatabase { "fuel": 1, "items": [], "roles": [ - "SAM Site" + "SAM Sites" ], "code": "", "name": "Default" @@ -82,7 +82,7 @@ export class GroundUnitsDatabase extends UnitDatabase { "fuel": 1, "items": [], "roles": [ - "SAM Site" + "SAM Sites" ], "code": "", "name": "Default" @@ -99,7 +99,7 @@ export class GroundUnitsDatabase extends UnitDatabase { "fuel": 1, "items": [], "roles": [ - "SAM Site" + "SAM Sites" ], "code": "", "name": "Default" @@ -116,7 +116,7 @@ export class GroundUnitsDatabase extends UnitDatabase { "fuel": 1, "items": [], "roles": [ - "SAM Site" + "SAM Sites" ], "code": "", "name": "Default" @@ -1544,7 +1544,7 @@ export class GroundUnitsDatabase extends UnitDatabase { } ], "roles": [ - "SAM" + "MANPADS" ], "code": "", "name": "Default" @@ -1590,7 +1590,7 @@ export class GroundUnitsDatabase extends UnitDatabase { } ], "roles": [ - "SAM" + "MANPADS" ], "code": "", "name": "Default" @@ -1613,7 +1613,7 @@ export class GroundUnitsDatabase extends UnitDatabase { } ], "roles": [ - "SAM" + "MANPADS" ], "code": "", "name": "Default" @@ -1927,7 +1927,7 @@ export class GroundUnitsDatabase extends UnitDatabase { } ], "roles": [ - "SAM" + "MANPADS" ], "code": "", "name": "Default" @@ -1973,7 +1973,7 @@ export class GroundUnitsDatabase extends UnitDatabase { } ], "roles": [ - "SAM" + "MANPADS" ], "code": "", "name": "Default" diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index 299c2e0a..3d7af00d 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -1,9 +1,12 @@ import { LatLng, LatLngBounds } from "leaflet"; -import { getHotgroupPanel, getInfoPopup, getMap, getUnitDataTable } from ".."; +import { getHotgroupPanel, getInfoPopup, getMap, getMissionHandler, getUnitDataTable } from ".."; import { Unit } from "./unit"; -import { cloneUnit } from "../server/server"; -import { deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots } from "../other/utils"; +import { cloneUnit, spawnGroundUnit } from "../server/server"; +import { deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots, polygonArea, randomPointInPoly, randomUnitBlueprintByRole } from "../other/utils"; import { IDLE, MOVE_UNIT } from "../map/map"; +import { CoalitionArea } from "../map/coalitionarea"; +import { Airbase } from "../missionhandler/airbase"; +import { groundUnitsDatabase } from "./groundunitsdatabase"; export class UnitsManager { #units: { [ID: number]: Unit }; @@ -499,6 +502,35 @@ 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 probability = Math.pow(1 - minDistance / 50e3, 5); + if (Math.random() < probability){ + const role = activeRoles[Math.floor(Math.random() * activeRoles.length)]; + const unitBlueprint = randomUnitBlueprintByRole(groundUnitsDatabase, role); + spawnGroundUnit({ + role: role, + latlng: latlng, + type: unitBlueprint.name, + coalition: coalitionArea.getCoalition(), + immediate: true + }) + } + } + } + /***********************************************/ #onKeyUp(event: KeyboardEvent) { if (!keyEventWasInInput(event) && event.key === "Delete" ) { diff --git a/client/views/other/contextmenus.ejs b/client/views/other/contextmenus.ejs index 09da39cd..6f1ba42f 100644 --- a/client/views/other/contextmenus.ejs +++ b/client/views/other/contextmenus.ejs @@ -112,7 +112,7 @@
-
@@ -123,11 +123,12 @@
+ +
@@ -145,6 +147,6 @@
- +
\ No newline at end of file diff --git a/scripts/OlympusCommand.lua b/scripts/OlympusCommand.lua index 81ccd4ff..34d74cb5 100644 --- a/scripts/OlympusCommand.lua +++ b/scripts/OlympusCommand.lua @@ -1,6 +1,6 @@ local version = "v0.3.0-alpha" -local debug = false +local debug = true Olympus.unitCounter = 1 Olympus.payloadRegistry = {} diff --git a/src/core/include/commands.h b/src/core/include/commands.h index d4e0bab3..b3668f32 100644 --- a/src/core/include/commands.h +++ b/src/core/include/commands.h @@ -145,37 +145,40 @@ private: class SpawnGroundUnit : public Command { public: - SpawnGroundUnit(wstring coalition, wstring unitType, Coords location) : + SpawnGroundUnit(wstring coalition, wstring unitType, Coords location, bool immediate) : coalition(coalition), unitType(unitType), - location(location) + location(location), + immediate(immediate) { priority = CommandPriority::LOW; }; virtual wstring getString(lua_State* L); - virtual int getLoad() { return 100; } + virtual int getLoad() { return 100 * !immediate; } private: const wstring coalition; const wstring unitType; const Coords location; + const bool immediate; }; /* Spawn air unit command */ class SpawnAircraft : public Command { public: - SpawnAircraft(wstring coalition, wstring unitType, Coords location, wstring payloadName, wstring airbaseName) : + SpawnAircraft(wstring coalition, wstring unitType, Coords location, wstring payloadName, wstring airbaseName, bool immediate) : coalition(coalition), unitType(unitType), location(location), payloadName(payloadName), - airbaseName(airbaseName) + airbaseName(airbaseName), + immediate(immediate) { priority = CommandPriority::LOW; }; virtual wstring getString(lua_State* L); - virtual int getLoad() { return 100; } + virtual int getLoad() { return 100 * !immediate; } private: const wstring coalition; @@ -183,6 +186,7 @@ private: const Coords location; const wstring payloadName; const wstring airbaseName; + const bool immediate; }; /* Clone unit command */ @@ -232,7 +236,7 @@ public: priority = CommandPriority::MEDIUM; }; virtual wstring getString(lua_State* L); - virtual int getLoad() { return 10; } + virtual int getLoad() { return 2; } private: const wstring groupName; @@ -249,7 +253,7 @@ public: priority = CommandPriority::HIGH; }; virtual wstring getString(lua_State* L); - virtual int getLoad() { return 10; } + virtual int getLoad() { return 2; } private: const wstring groupName; @@ -266,7 +270,7 @@ public: priority = CommandPriority::HIGH; }; virtual wstring getString(lua_State* L); - virtual int getLoad() { return 10; } + virtual int getLoad() { return 2; } private: const wstring groupName; @@ -297,7 +301,7 @@ public: priority = CommandPriority::HIGH; }; virtual wstring getString(lua_State* L); - virtual int getLoad() { return 10; } + virtual int getLoad() { return 2; } private: const wstring groupName; @@ -318,7 +322,7 @@ public: priority = CommandPriority::HIGH; }; virtual wstring getString(lua_State* L); - virtual int getLoad() { return 10; } + virtual int getLoad() { return 2; } private: const wstring groupName; diff --git a/src/core/src/scheduler.cpp b/src/core/src/scheduler.cpp index e7a7de4e..2b55058e 100644 --- a/src/core/src/scheduler.cpp +++ b/src/core/src/scheduler.cpp @@ -92,16 +92,18 @@ void Scheduler::handleRequest(wstring key, json::value value) } else if (key.compare(L"spawnGround") == 0) { + bool immediate = value[L"immediate"].as_bool(); wstring coalition = value[L"coalition"].as_string(); wstring type = value[L"type"].as_string(); double lat = value[L"location"][L"lat"].as_double(); double lng = value[L"location"][L"lng"].as_double(); log(L"Spawning " + coalition + L" ground unit of type " + type + L" at (" + to_wstring(lat) + L", " + to_wstring(lng) + L")"); Coords loc; loc.lat = lat; loc.lng = lng; - command = dynamic_cast(new SpawnGroundUnit(coalition, type, loc)); + command = dynamic_cast(new SpawnGroundUnit(coalition, type, loc, immediate)); } else if (key.compare(L"spawnAir") == 0) { + bool immediate = value[L"immediate"].as_bool(); wstring coalition = value[L"coalition"].as_string(); wstring type = value[L"type"].as_string(); double lat = value[L"location"][L"lat"].as_double(); @@ -111,7 +113,7 @@ void Scheduler::handleRequest(wstring key, json::value value) wstring payloadName = value[L"payloadName"].as_string(); wstring airbaseName = value[L"airbaseName"].as_string(); log(L"Spawning " + coalition + L" air unit of type " + type + L" with payload " + payloadName + L" at (" + to_wstring(lat) + L", " + to_wstring(lng) + L" " + airbaseName + L")"); - command = dynamic_cast(new SpawnAircraft(coalition, type, loc, payloadName, airbaseName)); + command = dynamic_cast(new SpawnAircraft(coalition, type, loc, payloadName, airbaseName, immediate)); } else if (key.compare(L"attackUnit") == 0) { From 635c487c2b73ac07c95172406dd3a9f790c47984 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Mon, 19 Jun 2023 17:14:53 +0200 Subject: [PATCH 03/15] Added area editing functions --- client/public/stylesheets/olympus.css | 65 +++++-- .../public/stylesheets/other/contextmenus.css | 46 +++-- .../olympus/images/buttons/other/delete.svg | 40 +++++ .../olympus/images/buttons/spawn/sam.svg | 91 ++++++++++ .../buttons/tools/draw-polygon-solid.svg | 1 + .../olympus/images/buttons/tools/ground.svg | 44 +++++ .../olympus/images/buttons/tools/tower.svg | 44 +++++ .../themes/olympus/images/markers/draw.svg | 38 +++++ .../src/controls/coalitionareacontextmenu.ts | 43 ++++- client/src/controls/contextmenu.ts | 9 + client/src/controls/mapcontextmenu.ts | 32 +++- client/src/index.ts | 1 - client/src/map/coalitionarea.ts | 132 ++++++++++----- client/src/map/coalitionareahandle.ts | 8 +- client/src/map/coalitionareamiddlehandle.ts | 19 +++ client/src/map/destinationpreviewmarker.ts | 7 +- client/src/map/drawingmarker.ts | 20 +++ client/src/map/map.ts | 159 ++++++++++++------ client/src/map/targetmarker.ts | 8 +- client/src/missionhandler/missionhandler.ts | 1 - client/src/units/unitsmanager.ts | 3 +- client/views/other/contextmenus.ejs | 26 +-- client/views/panels/navbar.ejs | 15 +- client/views/uikit/uikit.ejs | 2 +- 24 files changed, 685 insertions(+), 169 deletions(-) create mode 100644 client/public/themes/olympus/images/buttons/other/delete.svg create mode 100644 client/public/themes/olympus/images/buttons/spawn/sam.svg create mode 100644 client/public/themes/olympus/images/buttons/tools/draw-polygon-solid.svg create mode 100644 client/public/themes/olympus/images/buttons/tools/ground.svg create mode 100644 client/public/themes/olympus/images/buttons/tools/tower.svg create mode 100644 client/public/themes/olympus/images/markers/draw.svg create mode 100644 client/src/map/coalitionareamiddlehandle.ts create mode 100644 client/src/map/drawingmarker.ts diff --git a/client/public/stylesheets/olympus.css b/client/public/stylesheets/olympus.css index 4e65fd69..480d5ab7 100644 --- a/client/public/stylesheets/olympus.css +++ b/client/public/stylesheets/olympus.css @@ -38,6 +38,11 @@ body { cursor: none !important; } +.hidden-cursor * { + cursor: none !important; + pointer-events: none !important; +} + a { text-decoration: none; } @@ -623,39 +628,39 @@ nav.ol-panel> :last-child { width: 28px; } -#unit-visibility-control { +.ol-navbar-buttons-group { align-items: center; } -#unit-visibility-control button { +.ol-navbar-buttons-group button { border: none; height: 32px; padding: 0px; width: 32px; } -#unit-visibility-control button svg { +.ol-navbar-buttons-group button svg { height: 16px; pointer-events: none; width: 16px; } -#unit-visibility-control button { +.ol-navbar-buttons-group button { background-color: white; border: 1px solid transparent; } -#unit-visibility-control button.off { +.ol-navbar-buttons-group button.off { background-color: transparent; border: 1px solid white; } -#unit-visibility-control button.off svg * { +.ol-navbar-buttons-group button.off svg * { fill: white !important; stroke: white !important; } -#unit-visibility-control button svg * { +.ol-navbar-buttons-group button svg * { fill: var(--background-steel) !important; stroke: var(--background-steel) !important; } @@ -666,10 +671,9 @@ nav.ol-panel> :last-child { flex-direction: column; } -#atc-navbar-control button { - background: #ffffff20; - border-radius: var(--border-radius-sm); - padding: 4px; +#atc-navbar-control button svg { + height: 24px; + width: 24px; } #roe-buttons-container button, @@ -876,6 +880,33 @@ nav.ol-panel> :last-child { z-index: 9999; } +.ol-draw-icon { + background-image: url("/resources/theme/images/markers/draw.svg"); + height: 24px; + pointer-events: none; + width: 24px; + z-index: 9999; +} + +.ol-coalitionarea-handle-icon, +.ol-coalitionarea-middle-handle-icon { + pointer-events: none; + z-index: 9999; + border-radius: 999px; +} + +.ol-coalitionarea-handle-icon { + background-color: #FFFFFFEE; + width: 24px; + height: 24px; +} + +.ol-coalitionarea-middle-handle-icon { + background-color: #FFFFFFAA; + width: 16px; + height: 16px; +} + dl.ol-data-grid { align-items: center; display: flex; @@ -1203,17 +1234,17 @@ input[type=number]::-webkit-outer-spin-button { border-top-right-radius: var(--border-radius-sm); } -[data-active-coalition="blue"].ol-contexmenu-button:hover, -[data-active-coalition="blue"].ol-contexmenu-button.is-open { +[data-coalition="blue"].ol-contexmenu-button:hover, +[data-coalition="blue"].ol-contexmenu-button.is-open { background-color: var(--primary-blue) } -[data-active-coalition="red"].ol-contexmenu-button:hover, -[data-active-coalition="red"].ol-contexmenu-button.is-open { +[data-coalition="red"].ol-contexmenu-button:hover, +[data-coalition="red"].ol-contexmenu-button.is-open { background-color: var(--primary-red) } -[data-active-coalition="neutral"].ol-contexmenu-button:hover, -[data-active-coalition="neutral"].ol-contexmenu-button.is-open { +[data-coalition="neutral"].ol-contexmenu-button:hover, +[data-coalition="neutral"].ol-contexmenu-button.is-open { background-color: var(--primary-neutral) } \ No newline at end of file diff --git a/client/public/stylesheets/other/contextmenus.css b/client/public/stylesheets/other/contextmenus.css index f6ecc8a4..9e5754a4 100644 --- a/client/public/stylesheets/other/contextmenus.css +++ b/client/public/stylesheets/other/contextmenus.css @@ -66,54 +66,54 @@ background-size: 48px; } -[data-active-coalition="blue"]#active-coalition-label, -[data-active-coalition="blue"].deploy-unit-button, -[data-active-coalition="blue"]#spawn-airbase-aircraft-button, -[data-active-coalition="blue"].create-iads-button { +[data-coalition="blue"]#active-coalition-label, +[data-coalition="blue"].deploy-unit-button, +[data-coalition="blue"]#spawn-airbase-aircraft-button, +[data-coalition="blue"].create-iads-button { background-color: var(--primary-blue) } -[data-active-coalition="red"]#active-coalition-label, -[data-active-coalition="red"].deploy-unit-button, -[data-active-coalition="red"]#spawn-airbase-aircraft-button, -[data-active-coalition="red"].create-iads-button { +[data-coalition="red"]#active-coalition-label, +[data-coalition="red"].deploy-unit-button, +[data-coalition="red"]#spawn-airbase-aircraft-button, +[data-coalition="red"].create-iads-button { background-color: var(--primary-red) } -[data-active-coalition="neutral"]#active-coalition-label, -[data-active-coalition="neutral"].deploy-unit-button, -[data-active-coalition="neutral"]#spawn-airbase-aircraft-button, -[data-active-coalition="neutral"].create-iads-button { +[data-coalition="neutral"]#active-coalition-label, +[data-coalition="neutral"].deploy-unit-button, +[data-coalition="neutral"]#spawn-airbase-aircraft-button, +[data-coalition="neutral"].create-iads-button { background-color: var(--primary-neutral) } -[data-active-coalition="blue"].deploy-unit-button:disabled { +[data-coalition="blue"].deploy-unit-button:disabled { background-color: transparent; border: 1px solid var(--primary-blue); cursor: default; } -[data-active-coalition="red"].deploy-unit-button:disabled { +[data-coalition="red"].deploy-unit-button:disabled { background-color: transparent; border: 1px solid var(--primary-red); cursor: default; } -[data-active-coalition="neutral"].deploy-unit-button:disabled { +[data-coalition="neutral"].deploy-unit-button:disabled { background-color: transparent; border: 1px solid var(--primary-neutral); cursor: default; } -[data-active-coalition="blue"]#active-coalition-label::after { +[data-coalition="blue"]#active-coalition-label::after { content: "Create blue unit"; } -[data-active-coalition="red"]#active-coalition-label::after { +[data-coalition="red"]#active-coalition-label::after { content: "Create red unit"; } -[data-active-coalition="neutral"]#active-coalition-label::after { +[data-coalition="neutral"]#active-coalition-label::after { content: "Create neutral unit"; } @@ -361,10 +361,20 @@ } #iads-button { + background-image: url("/resources/theme/images/buttons/spawn/sam.svg"); + background-size: 48px; +} + +#cap-button { background-image: url("/resources/theme/images/buttons/spawn/aircraft.svg"); background-size: 48px; } +#coalitionarea-delete-button { + background-image: url("/resources/theme/images/buttons/other/delete.svg"); + background-size: 48px; +} + #coalition-area-contextmenu .ol-checkbox { align-self: flex-start; } diff --git a/client/public/themes/olympus/images/buttons/other/delete.svg b/client/public/themes/olympus/images/buttons/other/delete.svg new file mode 100644 index 00000000..c290353d --- /dev/null +++ b/client/public/themes/olympus/images/buttons/other/delete.svg @@ -0,0 +1,40 @@ + + + + + + + diff --git a/client/public/themes/olympus/images/buttons/spawn/sam.svg b/client/public/themes/olympus/images/buttons/spawn/sam.svg new file mode 100644 index 00000000..0109de6b --- /dev/null +++ b/client/public/themes/olympus/images/buttons/spawn/sam.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + diff --git a/client/public/themes/olympus/images/buttons/tools/draw-polygon-solid.svg b/client/public/themes/olympus/images/buttons/tools/draw-polygon-solid.svg new file mode 100644 index 00000000..f755fed5 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/tools/draw-polygon-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/themes/olympus/images/buttons/tools/ground.svg b/client/public/themes/olympus/images/buttons/tools/ground.svg new file mode 100644 index 00000000..34ab621c --- /dev/null +++ b/client/public/themes/olympus/images/buttons/tools/ground.svg @@ -0,0 +1,44 @@ + + + + + + + + diff --git a/client/public/themes/olympus/images/buttons/tools/tower.svg b/client/public/themes/olympus/images/buttons/tools/tower.svg new file mode 100644 index 00000000..a5134f93 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/tools/tower.svg @@ -0,0 +1,44 @@ + + + + + + + + diff --git a/client/public/themes/olympus/images/markers/draw.svg b/client/public/themes/olympus/images/markers/draw.svg new file mode 100644 index 00000000..7e1d8f98 --- /dev/null +++ b/client/public/themes/olympus/images/markers/draw.svg @@ -0,0 +1,38 @@ + + + + + + + diff --git a/client/src/controls/coalitionareacontextmenu.ts b/client/src/controls/coalitionareacontextmenu.ts index e60e9382..03b67d90 100644 --- a/client/src/controls/coalitionareacontextmenu.ts +++ b/client/src/controls/coalitionareacontextmenu.ts @@ -1,4 +1,4 @@ -import { getUnitsManager } from ".."; +import { getMap, getUnitsManager } from ".."; import { CoalitionArea } from "../map/coalitionarea"; import { ContextMenu } from "./contextmenu"; import { Dropdown } from "./dropdown"; @@ -9,29 +9,39 @@ const unitRole = ["AAA", "MANPADS", "SAM Sites", "Radar"]; export class CoalitionAreaContextMenu extends ContextMenu { #coalitionSwitch: Switch; - #coalitionArea: CoalitionArea|null = null; + #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.#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 = 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) => { - this.showSubMenu(e.detail.type); + 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 values: { [key: string]: boolean } = {}; const element = this.#iadsRoleDropdown.getOptionElements(); for (let idx = 0; idx < element.length; idx++) { const option = element.item(idx) as HTMLElement; @@ -70,6 +80,16 @@ export class CoalitionAreaContextMenu extends ContextMenu { 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() { @@ -78,9 +98,16 @@ export class CoalitionAreaContextMenu extends ContextMenu { 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.getCoalitionArea()?.setCoalition(value ? "red" : "blue"); + this.getContainer()?.querySelectorAll('[data-coalition]').forEach((element: any) => { + element.setAttribute("data-coalition", this.getCoalitionArea()?.getCoalition()) + }); } } \ No newline at end of file diff --git a/client/src/controls/contextmenu.ts b/client/src/controls/contextmenu.ts index 58cf371f..8e3261bb 100644 --- a/client/src/controls/contextmenu.ts +++ b/client/src/controls/contextmenu.ts @@ -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; + } } \ No newline at end of file diff --git a/client/src/controls/mapcontextmenu.ts b/client/src/controls/mapcontextmenu.ts index db63ca60..d90e96f2 100644 --- a/client/src/controls/mapcontextmenu.ts +++ b/client/src/controls/mapcontextmenu.ts @@ -29,11 +29,11 @@ export class MapContextMenu extends ContextMenu { #groundUnitRoleDropdown: Dropdown; #groundUnitTypeDropdown: Dropdown; #spawnOptions: SpawnOptions = { role: "", type: "", 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)); @@ -47,7 +47,10 @@ export class MapContextMenu extends ContextMenu { this.#groundUnitTypeDropdown = new Dropdown("ground-unit-type-options", (type: string) => this.#setGroundUnitType(type)); document.addEventListener("mapContextMenuShow", (e: any) => { - this.showSubMenu(e.detail.type); + if (this.getVisibleSubMenu() !== e.detail.type) + this.showSubMenu(e.detail.type); + else + this.hideSubMenus(); }); document.addEventListener("contextMenuDeployAircraft", () => { @@ -104,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); } @@ -124,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) { diff --git a/client/src/index.ts b/client/src/index.ts index c514bbef..933551cb 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -253,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() { diff --git a/client/src/map/coalitionarea.ts b/client/src/map/coalitionarea.ts index 7f3f2873..b5e61be0 100644 --- a/client/src/map/coalitionarea.ts +++ b/client/src/map/coalitionarea.ts @@ -1,40 +1,24 @@ -import { LatLng, LatLngExpression, Polygon, PolylineOptions } from "leaflet"; +import { LatLng, LatLngExpression, Map, Point, Polygon, PolylineOptions } from "leaflet"; import { getMap } from ".."; -import { DRAW_POLYGON } from "./map"; 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) + if (options === undefined) options = {}; - + options.bubblingMouseEvents = false; super(latlngs, options); - - this.on("click", (e: any) => { - if (!this.getSelected()) { - this.setSelected(true); - getMap().setState(DRAW_POLYGON); - } - else if (this.getEditing()) { - this.addLatLng(e.latlng); - this.addTemporaryLatLng(e.latlng); - } - }); - - this.on("contextmenu", (e: any) => { - if (!this.#editing) - getMap().showCoalitionAreaContextMenu(e, this); - else - this.setEditing(false); - }); - this.#setColors(); + this.#registerCallbacks(); } setCoalition(coalition: string) { @@ -50,10 +34,15 @@ export class CoalitionArea extends Polygon { this.#selected = selected; this.#setColors(); this.#setHandles(); - if (!selected) + 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; } @@ -61,6 +50,11 @@ export class CoalitionArea extends Polygon { 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() { @@ -68,38 +62,92 @@ export class CoalitionArea extends Polygon { } addTemporaryLatLng(latlng: LatLng) { - this.addLatLng(latlng); + this.#activeIndex++; + var latlngs = this.getLatLngs()[0] as LatLng[]; + latlngs.splice(this.#activeIndex, 0, latlng); + this.setLatLngs(latlngs); + this.#setHandles(); } moveTemporaryLatLng(latlng: LatLng) { var latlngs = this.getLatLngs()[0] as LatLng[]; - latlngs[latlngs.length - 1] = latlng; + latlngs[this.#activeIndex] = latlng; this.setLatLngs(latlngs); - } - - addLatLng(latlng: LatLngExpression | LatLngExpression[], latlngs?: LatLng[] | undefined): this { - super.addLatLng(latlng, latlngs) this.#setHandles(); - return this; } #setColors() { - this.setStyle({color: this.getSelected()? "white": this.#coalition, fillColor: this.#coalition}); + 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 = []; - var latlngs = this.getLatLngs()[0] as LatLng[]; - latlngs.forEach((latlng: LatLng, idx: number) => { - const handle = new CoalitionAreaHandle(latlng); - handle.addTo(getMap()); - handle.on("dragend", (e: any) => { - var latlngs = this.getLatLngs()[0] as LatLng[]; - latlngs[idx] = e.latlng; - this.setLatLngs(latlngs); + 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.#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.getSelected() && !this.getEditing()) + 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; + } } \ No newline at end of file diff --git a/client/src/map/coalitionareahandle.ts b/client/src/map/coalitionareahandle.ts index 2e761275..6dceaaa8 100644 --- a/client/src/map/coalitionareahandle.ts +++ b/client/src/map/coalitionareahandle.ts @@ -8,12 +8,12 @@ export class CoalitionAreaHandle extends CustomMarker { createIcon() { this.setIcon(new DivIcon({ - iconSize: [52, 52], - iconAnchor: [26, 26], - className: "leaflet-target-marker", + iconSize: [24, 24], + iconAnchor: [12, 12], + className: "leaflet-coalitionarea-handle-marker", })); var el = document.createElement("div"); - el.classList.add("ol-target-icon"); + el.classList.add("ol-coalitionarea-handle-icon"); this.getElement()?.appendChild(el); } } \ No newline at end of file diff --git a/client/src/map/coalitionareamiddlehandle.ts b/client/src/map/coalitionareamiddlehandle.ts new file mode 100644 index 00000000..b268ec9b --- /dev/null +++ b/client/src/map/coalitionareamiddlehandle.ts @@ -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); + } +} \ No newline at end of file diff --git a/client/src/map/destinationpreviewmarker.ts b/client/src/map/destinationpreviewmarker.ts index aa76f395..28f21650 100644 --- a/client/src/map/destinationpreviewmarker.ts +++ b/client/src/map/destinationpreviewmarker.ts @@ -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], diff --git a/client/src/map/drawingmarker.ts b/client/src/map/drawingmarker.ts new file mode 100644 index 00000000..2758dc1a --- /dev/null +++ b/client/src/map/drawingmarker.ts @@ -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); + } +} \ No newline at end of file diff --git a/client/src/map/map.ts b/client/src/map/map.ts index 47294f19..e165f2db 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -16,6 +16,7 @@ import { layers as mapLayers, mapBounds, minimapBoundaries } from "../constants/ import { TargetMarker } from "./targetmarker"; import { CoalitionArea } from "./coalitionarea"; import { CoalitionAreaContextMenu } from "../controls/coalitionareacontextmenu"; +import { DrawingCursor } from "./drawingmarker"; L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect); @@ -29,7 +30,7 @@ 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 DRAW_POLYGON = "Draw polygon"; +export const DRAW_COALITIONAREA_POLYGON = "Draw Coalition 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"]; @@ -50,12 +51,15 @@ export class Map extends L.Map { #miniMap: ClickableMiniMap | null = null; #miniMapLayerGroup: L.LayerGroup; #temporaryMarkers: TemporaryUnitMarker[] = []; - #destinationPreviewMarkers: DestinationPreviewMarker[] = []; - #targetMarker: TargetMarker; + #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"); @@ -102,8 +106,8 @@ export class Map extends L.Map { this.on('mousedown', (e: any) => this.#onMouseDown(e)); this.on('mouseup', (e: any) => this.#onMouseUp(e)); this.on('mousemove', (e: any) => this.#onMouseMove(e)); - this.on('keydown', (e: any) => this.#updateDestinationPreview(e)); - this.on('keyup', (e: any) => this.#updateDestinationPreview(e)); + this.on('keydown', (e: any) => this.#onKeyDown(e)); + this.on('keyup', (e: any) => this.#onKeyUp(e)); /* Event listeners */ document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => { @@ -120,9 +124,14 @@ export class Map extends L.Map { Object.values(getUnitsManager().getUnits()).forEach((unit: Unit) => unit.updateVisibility()); }); - document.addEventListener("toggleMapDraw", (ev: CustomEventInit) => { + document.addEventListener("toggleCoalitionAreaDraw", (ev: CustomEventInit) => { + const el = ev.detail._element; + document.addEventListener("mapStateChanged", () => el?.classList.toggle("off", !(this.getState() === DRAW_COALITIONAREA_POLYGON))); if (ev.detail?.type == "polygon") { - this.setState(DRAW_POLYGON); + if (this.getState() !== DRAW_COALITIONAREA_POLYGON) + this.setState(DRAW_COALITIONAREA_POLYGON); + else + this.setState(IDLE); } }) @@ -143,9 +152,6 @@ 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) { @@ -172,30 +178,17 @@ export class Map extends L.Map { /* State machine */ setState(state: string) { this.#state = state; + this.#showCursor(); if (this.#state === IDLE) { - this.#resetDestinationMarkers(); - this.#resetTargetMarker(); this.#deselectCoalitionAreas(); - this.#showCursor(); } else if (this.#state === MOVE_UNIT) { - this.#resetTargetMarker(); this.#deselectCoalitionAreas(); - this.#createDestinationMarkers(); - if (this.#destinationPreviewMarkers.length > 0) - this.#hideCursor(); } else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) { - this.#resetDestinationMarkers(); this.#deselectCoalitionAreas(); - this.#createTargetMarker(); - this.#hideCursor(); } - else if (this.#state === DRAW_POLYGON) { - this.#resetDestinationMarkers(); - this.#resetTargetMarker(); - this.#showCursor(); - //@ts-ignore draggable option added by plugin + else if (this.#state === DRAW_COALITIONAREA_POLYGON) { this.#coalitionAreas.push(new CoalitionArea([])); this.#coalitionAreas[this.#coalitionAreas.length - 1].addTo(this); } @@ -206,6 +199,17 @@ 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(); @@ -393,7 +397,7 @@ export class Map extends L.Map { }); if (closest) { this.removeLayer(closest); - delete this.#temporaryMarkers[i]; + this.#temporaryMarkers.splice(i, 1); } } @@ -406,16 +410,14 @@ export class Map extends L.Map { if (!this.#preventLeftClick) { this.hideAllContextMenus(); if (this.#state === IDLE) { - + this.deselectAllCoalitionAreas(); } - else if (this.#state === DRAW_POLYGON) { - /* This gets only called to create the first point of the area. All other points are added by the area itself */ + else if (this.#state === DRAW_COALITIONAREA_POLYGON) { if (this.getSelectedCoalitionArea()?.getEditing()) { - this.getSelectedCoalitionArea()?.addLatLng(e.latlng); this.getSelectedCoalitionArea()?.addTemporaryLatLng(e.latlng); } else { - this.getSelectedCoalitionArea()?.setSelected(false); + this.deselectAllCoalitionAreas(); } } else { @@ -426,7 +428,7 @@ export class Map extends L.Map { } #onDoubleClick(e: any) { - + this.deselectAllCoalitionAreas(); } #onContextMenu(e: any) { @@ -492,20 +494,34 @@ export class Map extends L.Map { this.#lastMousePosition.x = e.originalEvent.x; this.#lastMousePosition.y = e.originalEvent.y; + this.#showCursor(e); + if (this.#state === MOVE_UNIT){ if (this.#computeDestinationRotation && this.#destinationRotationCenter != null) this.#destinationGroupRotation = -bearing(this.#destinationRotationCenter.lat, this.#destinationRotationCenter.lng, this.getMouseCoordinates().lat, this.getMouseCoordinates().lng); this.#updateDestinationPreview(e); } 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 === DRAW_POLYGON) { - if (this.getSelectedCoalitionArea()?.getEditing()) + else if (this.#state === DRAW_COALITIONAREA_POLYGON) { + if (this.getSelectedCoalitionArea()?.getEditing() && !e.originalEvent.ctrlKey){ + this.#drawingCursor.setLatLng(e.latlng); this.getSelectedCoalitionArea()?.moveTemporaryLatLng(e.latlng); + } } } + #onKeyDown(e: any) { + this.#updateDestinationPreview(e); + this.#showCursor(e); + } + + #onKeyUp(e: any) { + this.#updateDestinationPreview(e); + this.#showCursor(e); + } + #onZoom(e: any) { if (this.#centerUnit != null) this.#panToUnit(this.#centerUnit); @@ -523,8 +539,8 @@ export class Map extends L.Map { #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()); + if (idx < this.#destinationPreviewCursors.length) + this.#destinationPreviewCursors[idx].setLatLng(e.originalEvent.shiftKey ? latlng : this.getMouseCoordinates()); }) } @@ -541,12 +557,12 @@ export class Map extends L.Map { return button; } - #createDestinationMarkers() { - this.#resetDestinationMarkers(); + #showDestinationCursors() { + this.#hideDestinationCursors(); if (getUnitsManager().getSelectedUnits({ excludeHumans: true }).length > 0) { /* Create the unit destination preview markers */ - this.#destinationPreviewMarkers = getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).map((unit: Unit) => { + this.#destinationPreviewCursors = getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).map((unit: Unit) => { var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), {interactive: false}); marker.addTo(this); return marker; @@ -554,38 +570,79 @@ export class Map extends L.Map { } } - #resetDestinationMarkers() { + #hideDestinationCursors() { /* Remove all the destination preview markers */ - this.#destinationPreviewMarkers.forEach((marker: L.Marker) => { + this.#destinationPreviewCursors.forEach((marker: L.Marker) => { this.removeLayer(marker); }) - this.#destinationPreviewMarkers = []; + this.#destinationPreviewCursors = []; 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); } #deselectCoalitionAreas() { this.getSelectedCoalitionArea()?.setSelected(false); } - #showCursor() { + #showDefaultCursor() { document.getElementById(this.#ID)?.classList.remove("hidden-cursor"); } - #hideCursor() { + #hideDefaultCursor() { document.getElementById(this.#ID)?.classList.add("hidden-cursor"); } + + #showDrawingCursor() { + this.#hideDefaultCursor(); + if (!this.hasLayer(this.#drawingCursor)) + this.#drawingCursor.addTo(this); + } + + #hideDrawingCursor() { + if (this.hasLayer(this.#drawingCursor)) + this.#drawingCursor.removeFrom(this); + } + + #showCursor(e?: any) { + if (e?.originalEvent.ctrlKey) { + this.#hideDefaultCursor(); + this.#hideDestinationCursors(); + this.#hideTargetCursor(); + this.#hideDrawingCursor(); + this.#showDefaultCursor(); + } else { + if (this.#state !== IDLE) this.#hideDefaultCursor(); + if (this.#state !== MOVE_UNIT) this.#hideDestinationCursors(); + if (![BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) this.#hideTargetCursor(); + if (this.#state !== DRAW_COALITIONAREA_POLYGON) this.#hideDrawingCursor(); + + if (this.#state === IDLE) 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 === DRAW_COALITIONAREA_POLYGON) { + if (this.getSelectedCoalitionArea()?.getEditing()) + { + this.#hideDefaultCursor(); + this.#showDrawingCursor(); + } + else { + this.#hideDrawingCursor(); + this.#showDefaultCursor(); + } + } + } + } } diff --git a/client/src/map/targetmarker.ts b/client/src/map/targetmarker.ts index 30232dc1..9b781f1c 100644 --- a/client/src/map/targetmarker.ts +++ b/client/src/map/targetmarker.ts @@ -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); } } diff --git a/client/src/missionhandler/missionhandler.ts b/client/src/missionhandler/missionhandler.ts index 50864e1c..0c81884b 100644 --- a/client/src/missionhandler/missionhandler.ts +++ b/client/src/missionhandler/missionhandler.ts @@ -45,7 +45,6 @@ export class MissionHandler { } } - if ("airbases" in data) { for (let idx in data.airbases) { var airbase = data.airbases[idx] diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index 3d7af00d..f30e25d9 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -526,7 +526,8 @@ export class UnitsManager { type: unitBlueprint.name, coalition: coalitionArea.getCoalition(), immediate: true - }) + }); + getMap().addTemporaryMarker(latlng); } } } diff --git a/client/views/other/contextmenus.ejs b/client/views/other/contextmenus.ejs index 6f1ba42f..f1f1dc7c 100644 --- a/client/views/other/contextmenus.ejs +++ b/client/views/other/contextmenus.ejs @@ -1,14 +1,14 @@
-
+
- - - -
@@ -54,7 +54,7 @@
- +
@@ -74,7 +74,7 @@
- +
@@ -104,16 +104,20 @@

Parking available:

- +
-
+
- + +
@@ -147,6 +151,6 @@
- +
\ No newline at end of file diff --git a/client/views/panels/navbar.ejs b/client/views/panels/navbar.ejs index 411f4dc1..a2ca6b2e 100644 --- a/client/views/panels/navbar.ejs +++ b/client/views/panels/navbar.ejs @@ -29,7 +29,7 @@
-
+
@@ -50,19 +50,20 @@
-
- +
+ data-on-click-params='{"selector": "#strip-board-ground"}' class="off"> + data-on-click-params='{"selector": "#strip-board-tower"}' class="off">
-
+
- +
\ No newline at end of file diff --git a/client/views/uikit/uikit.ejs b/client/views/uikit/uikit.ejs index e4c0a2f4..224cc7b3 100644 --- a/client/views/uikit/uikit.ejs +++ b/client/views/uikit/uikit.ejs @@ -1047,7 +1047,7 @@
Open air
5
- +
From 61d6d9f16c04bd1407c158f13bb83e0fc6e22f67 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Tue, 20 Jun 2023 17:36:21 +0200 Subject: [PATCH 04/15] Added better temporary markers and more map functions --- client/public/stylesheets/markers/units.css | 4 + .../public/stylesheets/other/contextmenus.css | 7 +- .../olympus/images/buttons/other/back.svg | 41 +++ .../images/buttons/tools/pen-solid.svg | 1 + client/src/constants/constants.ts | 13 +- client/src/controls/mapcontextmenu.ts | 14 +- client/src/map/boxselect.ts | 2 +- client/src/map/coalitionarea.ts | 25 +- client/src/map/map.ts | 233 ++++++++++-------- client/src/map/temporaryunitmarker.ts | 49 +++- client/src/other/utils.ts | 28 +++ client/src/server/server.ts | 4 +- client/src/units/unit.ts | 49 ++-- client/src/units/unitsmanager.ts | 15 +- client/views/other/contextmenus.ejs | 2 + client/views/panels/navbar.ejs | 3 + 16 files changed, 324 insertions(+), 166 deletions(-) create mode 100644 client/public/themes/olympus/images/buttons/other/back.svg create mode 100644 client/public/themes/olympus/images/buttons/tools/pen-solid.svg diff --git a/client/public/stylesheets/markers/units.css b/client/public/stylesheets/markers/units.css index 65ec1d4b..62ca82b6 100644 --- a/client/public/stylesheets/markers/units.css +++ b/client/public/stylesheets/markers/units.css @@ -311,4 +311,8 @@ [data-object|="unit-aircraft"][data-is-dead] .unit-summary .unit-callsign { display: block; +} + +.ol-temporary-marker { + opacity: 0.5; } \ No newline at end of file diff --git a/client/public/stylesheets/other/contextmenus.css b/client/public/stylesheets/other/contextmenus.css index 9e5754a4..99e8b35c 100644 --- a/client/public/stylesheets/other/contextmenus.css +++ b/client/public/stylesheets/other/contextmenus.css @@ -350,7 +350,7 @@ height: fit-content; position: absolute; row-gap: 5px; - width: 225px; + width: 250px; z-index: 9999; } @@ -370,6 +370,11 @@ background-size: 48px; } +#coalitionarea-back-button { + background-image: url("/resources/theme/images/buttons/other/back.svg"); + background-size: 48px; +} + #coalitionarea-delete-button { background-image: url("/resources/theme/images/buttons/other/delete.svg"); background-size: 48px; diff --git a/client/public/themes/olympus/images/buttons/other/back.svg b/client/public/themes/olympus/images/buttons/other/back.svg new file mode 100644 index 00000000..52c98f94 --- /dev/null +++ b/client/public/themes/olympus/images/buttons/other/back.svg @@ -0,0 +1,41 @@ + + + + + + + diff --git a/client/public/themes/olympus/images/buttons/tools/pen-solid.svg b/client/public/themes/olympus/images/buttons/tools/pen-solid.svg new file mode 100644 index 00000000..a690992f --- /dev/null +++ b/client/public/themes/olympus/images/buttons/tools/pen-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/constants/constants.ts b/client/src/constants/constants.ts index 0f91ed25..a230e17d 100644 --- a/client/src/constants/constants.ts +++ b/client/src/constants/constants.ts @@ -99,4 +99,15 @@ export const layers = { maxZoom: 20, attribution: 'CyclOSM | Map data: © OpenStreetMap contributors' } -} \ No newline at end of file +} + +/* 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"]; diff --git a/client/src/controls/mapcontextmenu.ts b/client/src/controls/mapcontextmenu.ts index d90e96f2..c0cdcdf9 100644 --- a/client/src/controls/mapcontextmenu.ts +++ b/client/src/controls/mapcontextmenu.ts @@ -11,7 +11,7 @@ import { ftToM } from "../other/utils"; export interface SpawnOptions { role: string; - type: string; + name: string; latlng: LatLng; coalition: string; loadout?: string | null; @@ -28,7 +28,7 @@ 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); @@ -57,7 +57,7 @@ export class MapContextMenu extends ContextMenu { this.hide(); this.#spawnOptions.coalition = getActiveCoalition(); if (this.#spawnOptions) { - getMap().addTemporaryMarker(this.#spawnOptions.latlng); + getMap().addTemporaryMarker(this.#spawnOptions); spawnAircraft(this.#spawnOptions); } }); @@ -66,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); } }); @@ -179,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 = (this.getContainer()?.querySelector("#unit-image")); @@ -198,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; (this.getContainer()?.querySelector("#aircraft-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false; @@ -241,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; (this.getContainer()?.querySelector("#ground-unit-spawn-menu")?.querySelector(".deploy-unit-button")).disabled = false; } this.clip(); diff --git a/client/src/map/boxselect.ts b/client/src/map/boxselect.ts index 2ea92fd8..a37f4ace 100644 --- a/client/src/map/boxselect.ts +++ b/client/src/map/boxselect.ts @@ -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(); diff --git a/client/src/map/coalitionarea.ts b/client/src/map/coalitionarea.ts index b5e61be0..2f44d9f5 100644 --- a/client/src/map/coalitionarea.ts +++ b/client/src/map/coalitionarea.ts @@ -1,4 +1,4 @@ -import { LatLng, LatLngExpression, Map, Point, Polygon, PolylineOptions } from "leaflet"; +import { DomUtil, LatLng, LatLngExpression, Map, Point, Polygon, PolylineOptions } from "leaflet"; import { getMap } from ".."; import { CoalitionAreaHandle } from "./coalitionareahandle"; import { CoalitionAreaMiddleHandle } from "./coalitionareamiddlehandle"; @@ -19,6 +19,7 @@ export class CoalitionArea extends Polygon { super(latlngs, options); this.#setColors(); this.#registerCallbacks(); + } setCoalition(coalition: string) { @@ -61,6 +62,17 @@ export class CoalitionArea extends Polygon { 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[]; @@ -69,13 +81,17 @@ export class CoalitionArea extends Polygon { this.#setHandles(); } - moveTemporaryLatLng(latlng: LatLng) { + 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 }); @@ -138,8 +154,11 @@ export class CoalitionArea extends Polygon { }); this.on("contextmenu", (e: any) => { - if (this.getSelected() && !this.getEditing()) + if (!this.getEditing()) { + getMap().deselectAllCoalitionAreas(); + this.setSelected(true); getMap().showCoalitionAreaContextMenu(e, this); + } else this.setEditing(false); }); diff --git a/client/src/map/map.ts b/client/src/map/map.ts index e165f2db..864f4783 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -12,7 +12,7 @@ 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"; @@ -24,16 +24,6 @@ L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect); require("../../public/javascripts/leaflet.nauticscale.js") require("../../public/javascripts/L.Path.Drag.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 DRAW_COALITIONAREA_POLYGON = "Draw Coalition 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"]; - export class Map extends L.Map { #ID: string; #state: string; @@ -51,16 +41,17 @@ export class Map extends L.Map { #miniMap: ClickableMiniMap | null = null; #miniMapLayerGroup: L.LayerGroup; #temporaryMarkers: TemporaryUnitMarker[] = []; - + #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}); + #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"); @@ -71,8 +62,7 @@ export class Map extends L.Map { 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); @@ -102,6 +92,7 @@ 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)); @@ -113,7 +104,7 @@ export class Map extends L.Map { 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()); }); @@ -124,16 +115,28 @@ 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; - document.addEventListener("mapStateChanged", () => el?.classList.toggle("off", !(this.getState() === DRAW_COALITIONAREA_POLYGON))); + /* 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() !== DRAW_COALITIONAREA_POLYGON) - this.setState(DRAW_COALITIONAREA_POLYGON); - else + 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) @@ -143,7 +146,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); @@ -155,10 +158,10 @@ export class Map extends L.Map { } 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, @@ -178,17 +181,25 @@ export class Map extends L.Map { /* State machine */ setState(state: string) { this.#state = state; - this.#showCursor(); - if (this.#state === IDLE) { + 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); + }); + } + if (this.#state !== COALITIONAREA_DRAW_POLYGON) { this.#deselectCoalitionAreas(); } - else if (this.#state === MOVE_UNIT) { - this.#deselectCoalitionAreas(); + + /* Operations to perform if you ARE in a state */ + if (this.#state === COALITIONAREA_INTERACT) { + this.#coalitionAreas.forEach((coalitionArea: CoalitionArea) => { + coalitionArea.setInteractive(true); + }); } - else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) { - this.#deselectCoalitionAreas(); - } - else if (this.#state === DRAW_COALITIONAREA_POLYGON) { + else if (this.#state === COALITIONAREA_DRAW_POLYGON) { this.#coalitionAreas.push(new CoalitionArea([])); this.#coalitionAreas[this.#coalitionAreas.length - 1].addTo(this); } @@ -377,13 +388,14 @@ 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) { + // TODO something more refined than this var d: number | null = null; var closest: L.Marker | null = null; var i: number = 0; @@ -402,7 +414,7 @@ export class Map extends L.Map { } getSelectedCoalitionArea() { - return this.#coalitionAreas.find((area: CoalitionArea) => {return area.getSelected()}); + return this.#coalitionAreas.find((area: CoalitionArea) => { return area.getSelected() }); } /* Event handlers */ @@ -412,7 +424,7 @@ export class Map extends L.Map { if (this.#state === IDLE) { this.deselectAllCoalitionAreas(); } - else if (this.#state === DRAW_COALITIONAREA_POLYGON) { + else if (this.#state === COALITIONAREA_DRAW_POLYGON) { if (this.getSelectedCoalitionArea()?.getEditing()) { this.getSelectedCoalitionArea()?.addTemporaryLatLng(e.latlng); } @@ -443,41 +455,48 @@ export class Map extends L.Map { getUnitsManager().selectedUnitsClearDestinations(); } getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, e.originalEvent.shiftKey, this.#destinationGroupRotation) - this.#destinationGroupRotation = 0; + 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); } } + #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) { @@ -494,32 +513,32 @@ export class Map extends L.Map { this.#lastMousePosition.x = e.originalEvent.x; this.#lastMousePosition.y = e.originalEvent.y; - this.#showCursor(e); + this.#updateCursor(e); - if (this.#state === MOVE_UNIT){ + 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(e); } else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) { this.#targetCursor.setLatLng(this.getMouseCoordinates()); } - else if (this.#state === DRAW_COALITIONAREA_POLYGON) { - if (this.getSelectedCoalitionArea()?.getEditing() && !e.originalEvent.ctrlKey){ - this.#drawingCursor.setLatLng(e.latlng); - this.getSelectedCoalitionArea()?.moveTemporaryLatLng(e.latlng); - } + 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.#updateDestinationPreview(e); - this.#showCursor(e); + this.#updateDestinationCursors(e); + this.#updateCursor(e); } #onKeyUp(e: any) { - this.#updateDestinationPreview(e); - this.#showCursor(e); + this.#updateDestinationCursors(e); + this.#updateCursor(e); } #onZoom(e: any) { @@ -537,13 +556,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.#destinationPreviewCursors.length) - this.#destinationPreviewCursors[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"); @@ -557,32 +569,58 @@ export class Map extends L.Map { return button; } - #showDestinationCursors() { - this.#hideDestinationCursors(); + #deselectCoalitionAreas() { + this.getSelectedCoalitionArea()?.setSelected(false); + } - if (getUnitsManager().getSelectedUnits({ excludeHumans: true }).length > 0) { - /* Create the unit destination preview markers */ - this.#destinationPreviewCursors = getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).map((unit: Unit) => { - var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), {interactive: false}); - marker.addTo(this); - return marker; - }) + /* Cursors */ + #showDefaultCursor() { + document.getElementById(this.#ID)?.classList.remove("hidden-cursor"); + } + + #hideDefaultCursor() { + document.getElementById(this.#ID)?.classList.add("hidden-cursor"); + } + + #showDestinationCursors() { + /* Don't create the cursors if there already are the correct number of them available */ + if (getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).length != this.#destinationPreviewCursors.length) { + /* Reset the cursors to start from a clean condition */ + this.#hideDestinationCursors(); + + if (getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).length > 0) { + /* Create the cursors. If a group is selected only one cursor is shown for it, because you can't control single units in a group */ + this.#destinationPreviewCursors = getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).map((unit: Unit) => { + var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), { interactive: false }); + marker.addTo(this); + return marker; + }) + } } } + #updateDestinationCursors(e: any) { + const groupLatLng = this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : this.getMouseCoordinates(); + Object.values(getUnitsManager().selectedUnitsComputeGroupDestination(groupLatLng, this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => { + if (idx < this.#destinationPreviewCursors.length) + this.#destinationPreviewCursors[idx].setLatLng(e.originalEvent.shiftKey ? latlng : this.getMouseCoordinates()); + }) + } + #hideDestinationCursors() { - /* Remove all the destination preview markers */ + /* Remove all the destination cursors */ this.#destinationPreviewCursors.forEach((marker: L.Marker) => { this.removeLayer(marker); }) this.#destinationPreviewCursors = []; + /* Reset the variables used to compute the rotation of the group cursors */ this.#destinationGroupRotation = 0; this.#computeDestinationRotation = false; this.#destinationRotationCenter = null; } - #showTargetCursor(){ + #showTargetCursor() { this.#hideTargetCursor(); this.#targetCursor.addTo(this); } @@ -592,18 +630,6 @@ export class Map extends L.Map { this.removeLayer(this.#targetCursor); } - #deselectCoalitionAreas() { - this.getSelectedCoalitionArea()?.setSelected(false); - } - - #showDefaultCursor() { - document.getElementById(this.#ID)?.classList.remove("hidden-cursor"); - } - - #hideDefaultCursor() { - document.getElementById(this.#ID)?.classList.add("hidden-cursor"); - } - #showDrawingCursor() { this.#hideDefaultCursor(); if (!this.hasLayer(this.#drawingCursor)) @@ -611,38 +637,33 @@ export class Map extends L.Map { } #hideDrawingCursor() { + this.#drawingCursor.setLatLng(new L.LatLng(0, 0)); if (this.hasLayer(this.#drawingCursor)) this.#drawingCursor.removeFrom(this); } - #showCursor(e?: any) { - if (e?.originalEvent.ctrlKey) { - this.#hideDefaultCursor(); + #updateCursor(e?: any) { + /* If the ctrl key is being pressed or we are performing an area selection, show the default cursor */ + if (e?.originalEvent.ctrlKey || this.#selecting) { + /* Hide all non default cursors */ this.#hideDestinationCursors(); this.#hideTargetCursor(); this.#hideDrawingCursor(); + this.#showDefaultCursor(); } else { - if (this.#state !== IDLE) this.#hideDefaultCursor(); + /* 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 !== DRAW_COALITIONAREA_POLYGON) this.#hideDrawingCursor(); + if (this.#state !== COALITIONAREA_DRAW_POLYGON) this.#hideDrawingCursor(); - if (this.#state === IDLE) this.#showDefaultCursor(); + /* 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 === DRAW_COALITIONAREA_POLYGON) { - if (this.getSelectedCoalitionArea()?.getEditing()) - { - this.#hideDefaultCursor(); - this.#showDrawingCursor(); - } - else { - this.#hideDrawingCursor(); - this.#showDefaultCursor(); - } - } + else if (this.#state === COALITIONAREA_DRAW_POLYGON) this.#showDrawingCursor(); } } -} +} diff --git a/client/src/map/temporaryunitmarker.ts b/client/src/map/temporaryunitmarker.ts index 904ee83d..7c266e0e 100644 --- a/client/src/map/temporaryunitmarker.ts +++ b/client/src/map/temporaryunitmarker.ts @@ -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"); } } \ No newline at end of file diff --git a/client/src/other/utils.ts b/client/src/other/utils.ts index 5b9419fe..a38e0f26 100644 --- a/client/src/other/utils.ts +++ b/client/src/other/utils.ts @@ -1,6 +1,9 @@ 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"; export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) { const φ1 = deg2rad(lat1); // φ, λ in radians @@ -219,4 +222,29 @@ export function randomUnitBlueprintByRole(unitDatabse: UnitDatabase, role: strin 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; } \ No newline at end of file diff --git a/client/src/server/server.ts b/client/src/server/server.ts index 646b78f1..fd71dba2 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -134,13 +134,13 @@ export function spawnExplosion(intensity: number, latlng: LatLng) { } export function spawnGroundUnit(spawnOptions: SpawnOptions) { - var command = { "type": spawnOptions.type, "location": spawnOptions.latlng, "coalition": spawnOptions.coalition, "immediate": spawnOptions.immediate? true: false }; + 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 : "", "immediate": spawnOptions.immediate? true: false }; + 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, () => { }); } diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index 54e9d37b..200c19a6 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -1,14 +1,12 @@ import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map } from 'leaflet'; import { getMap, getUnitsManager } from '..'; -import { mToFt, msToKnots, rad2deg } from '../other/utils'; +import { getMarkerCategoryByName, getUnitDatabaseByCategory, mToFt, msToKnots, rad2deg } from '../other/utils'; import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, getUnits, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions, followUnit, setEmissionsCountermeasures, setSpeedType, setAltitudeType, setOnOff, setFollowRoads, bombPoint, carpetBomb, bombBuilding, fireAtArea } from '../server/server'; -import { aircraftDatabase } from './aircraftdatabase'; -import { groundUnitsDatabase } from './groundunitsdatabase'; import { CustomMarker } from '../map/custommarker'; import { SVGInjector } from '@tanem/svg-injector'; import { UnitDatabase } from './unitdatabase'; -import { BOMBING, CARPET_BOMBING, FIRE_AT_AREA, IDLE, MOVE_UNIT } from '../map/map'; import { TargetMarker } from '../map/targetmarker'; +import { BOMBING, CARPET_BOMBING, FIRE_AT_AREA, IDLE, MOVE_UNIT } from '../constants/constants'; var pathIcon = new Icon({ iconUrl: '/resources/theme/images/markers/marker-icon.png', @@ -133,12 +131,12 @@ export class Unit extends CustomMarker { getMarkerCategory() { // Overloaded by child classes + // TODO convert to use getMarkerCategoryByName return ""; } getDatabase(): UnitDatabase | null { - // Overloaded by child classes - return null; + return getUnitDatabaseByCategory(this.getMarkerCategory()); } getIconOptions(): UnitIconOptions { @@ -344,7 +342,7 @@ export class Unit extends CustomMarker { if (this.getIconOptions().showShortLabel) { var shortLabel = document.createElement("div"); shortLabel.classList.add("unit-short-label"); - shortLabel.innerText = this.getDatabase()?.getByName(this.getBaseData().name)?.shortLabel || ""; + shortLabel.innerText = getUnitDatabaseByCategory(this.getMarkerCategory())?.getByName(this.getBaseData().name)?.shortLabel || ""; el.append(shortLabel); } @@ -424,7 +422,7 @@ export class Unit extends CustomMarker { return getUnitsManager().getUnitByID(this.getFormationData().leaderID); } - canRole(roles: string | string[]) { + canFulfillRole(roles: string | string[]) { if (typeof(roles) === "string") roles = [roles]; @@ -565,7 +563,9 @@ export class Unit extends CustomMarker { /***********************************************/ onAdd(map: Map): this { super.onAdd(map); - getMap().removeTemporaryMarker(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude)); + /* If this is the first time adding this unit to the map, remove the temporary marker */ + if (getUnitsManager().getUnitByID(this.ID) == null) + getMap().removeTemporaryMarker(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude)); return this; } @@ -609,14 +609,14 @@ export class Unit extends CustomMarker { if ((selectedUnits.length === 0 && this.getBaseData().category == "Aircraft") || (selectedUnitTypes.length === 1 && ["Aircraft"].includes(selectedUnitTypes[0]))) { - if (selectedUnits.concat([this]).every((unit: Unit) => {return unit.canRole(["CAS", "Strike"])})) { + if (selectedUnits.concat([this]).every((unit: Unit) => {return unit.canFulfillRole(["CAS", "Strike"])})) { options["bomb"] = {text: "Precision bombing", tooltip: "Precision bombing of a specific point"}; options["carpet-bomb"] = {text: "Carpet bombing", tooltip: "Carpet bombing close to a point"}; } } if ((selectedUnits.length === 0 && this.getBaseData().category == "GroundUnit") || selectedUnitTypes.length === 1 && ["GroundUnit"].includes(selectedUnitTypes[0])) { - if (selectedUnits.concat([this]).every((unit: Unit) => {return unit.canRole(["Gun Artillery", "Rocket Artillery", "Infantry", "IFV", "Tank"])})) + if (selectedUnits.concat([this]).every((unit: Unit) => {return unit.canFulfillRole(["Gun Artillery", "Rocket Artillery", "Infantry", "IFV", "Tank"])})) options["fire-at-area"] = {text: "Fire at area", tooltip: "Fire at a large area"}; } @@ -915,21 +915,17 @@ export class AirUnit extends Unit { } export class Aircraft extends AirUnit { - constructor(ID: number, data: UnitData) { + constructor(ID: number, data: UpdateData) { super(ID, data); } getMarkerCategory() { return "aircraft"; } - - getDatabase(): UnitDatabase | null { - return aircraftDatabase; - } } export class Helicopter extends AirUnit { - constructor(ID: number, data: UnitData) { + constructor(ID: number, data: UpdateData) { super(ID, data); } @@ -939,7 +935,7 @@ export class Helicopter extends AirUnit { } export class GroundUnit extends Unit { - constructor(ID: number, data: UnitData) { + constructor(ID: number, data: UpdateData) { super(ID, data); } @@ -958,19 +954,12 @@ export class GroundUnit extends Unit { } getMarkerCategory() { - // TODO this is very messy - var role = groundUnitsDatabase.getByName(this.getBaseData().name)?.loadouts[0].roles[0]; - var markerCategory = (role === "SAM") ? "groundunit-sam" : "groundunit-other"; - return markerCategory; - } - - getDatabase(): UnitDatabase | null { - return groundUnitsDatabase; + return getMarkerCategoryByName(this.getBaseData().name); } } export class NavyUnit extends Unit { - constructor(ID: number, data: UnitData) { + constructor(ID: number, data: UpdateData) { super(ID, data); } @@ -994,7 +983,7 @@ export class NavyUnit extends Unit { } export class Weapon extends Unit { - constructor(ID: number, data: UnitData) { + constructor(ID: number, data: UpdateData) { super(ID, data); this.setSelectable(false); } @@ -1015,7 +1004,7 @@ export class Weapon extends Unit { } export class Missile extends Weapon { - constructor(ID: number, data: UnitData) { + constructor(ID: number, data: UpdateData) { super(ID, data); } @@ -1025,7 +1014,7 @@ export class Missile extends Weapon { } export class Bomb extends Weapon { - constructor(ID: number, data: UnitData) { + constructor(ID: number, data: UpdateData) { super(ID, data); } diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index f30e25d9..b45cf120 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -3,10 +3,10 @@ import { getHotgroupPanel, getInfoPopup, getMap, getMissionHandler, getUnitDataT import { Unit } from "./unit"; import { cloneUnit, spawnGroundUnit } from "../server/server"; import { deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots, polygonArea, randomPointInPoly, randomUnitBlueprintByRole } from "../other/utils"; -import { IDLE, MOVE_UNIT } from "../map/map"; import { CoalitionArea } from "../map/coalitionarea"; import { Airbase } from "../missionhandler/airbase"; import { groundUnitsDatabase } from "./groundunitsdatabase"; +import { IDLE, MOVE_UNIT } from "../constants/constants"; export class UnitsManager { #units: { [ID: number]: Unit }; @@ -493,7 +493,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`); } @@ -520,14 +520,9 @@ export class UnitsManager { if (Math.random() < probability){ const role = activeRoles[Math.floor(Math.random() * activeRoles.length)]; const unitBlueprint = randomUnitBlueprintByRole(groundUnitsDatabase, role); - spawnGroundUnit({ - role: role, - latlng: latlng, - type: unitBlueprint.name, - coalition: coalitionArea.getCoalition(), - immediate: true - }); - getMap().addTemporaryMarker(latlng); + const spawnOptions = {role: role, latlng: latlng, name: unitBlueprint.name, coalition: coalitionArea.getCoalition(), immediate: true}; + spawnGroundUnit(spawnOptions); + getMap().addTemporaryMarker(spawnOptions); } } } diff --git a/client/views/other/contextmenus.ejs b/client/views/other/contextmenus.ejs index f1f1dc7c..eed3f719 100644 --- a/client/views/other/contextmenus.ejs +++ b/client/views/other/contextmenus.ejs @@ -116,6 +116,8 @@ data-on-click-params='{ "type": "iads" }' class="ol-contexmenu-button"> +
diff --git a/client/views/panels/navbar.ejs b/client/views/panels/navbar.ejs index a2ca6b2e..5fa3e35f 100644 --- a/client/views/panels/navbar.ejs +++ b/client/views/panels/navbar.ejs @@ -61,6 +61,9 @@
+ From 9d0e2239e49d502a94bfb4fe8f18d010c1234299 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Wed, 21 Jun 2023 20:05:41 +0200 Subject: [PATCH 05/15] Added new method for handling data --- client/src/constants/constants.ts | 2 + .../src/controls/coalitionareacontextmenu.ts | 5 +-- client/src/map/coalitionarea.ts | 7 ++-- client/src/map/map.ts | 41 ++++++++++++------- client/src/server/server.ts | 27 ++++++------ client/src/units/unitsmanager.ts | 6 +-- scripts/OlympusCommand.lua | 4 +- src/core/include/commands.h | 6 +-- src/core/include/server.h | 4 ++ src/core/include/unit.h | 2 +- src/core/include/unitsmanager.h | 6 ++- src/core/src/core.cpp | 41 ++++++++++++++++--- src/core/src/scheduler.cpp | 2 +- src/core/src/server.cpp | 8 +++- src/core/src/unit.cpp | 32 +++++++-------- src/core/src/unitsmanager.cpp | 37 ++++++++--------- 16 files changed, 137 insertions(+), 93 deletions(-) diff --git a/client/src/constants/constants.ts b/client/src/constants/constants.ts index a230e17d..4fabffcf 100644 --- a/client/src/constants/constants.ts +++ b/client/src/constants/constants.ts @@ -111,3 +111,5 @@ 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}; \ No newline at end of file diff --git a/client/src/controls/coalitionareacontextmenu.ts b/client/src/controls/coalitionareacontextmenu.ts index 03b67d90..dcb9bc23 100644 --- a/client/src/controls/coalitionareacontextmenu.ts +++ b/client/src/controls/coalitionareacontextmenu.ts @@ -1,12 +1,11 @@ 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"; -const unitRole = ["AAA", "MANPADS", "SAM Sites", "Radar"]; - export class CoalitionAreaContextMenu extends ContextMenu { #coalitionSwitch: Switch; #coalitionArea: CoalitionArea | null = null; @@ -57,7 +56,7 @@ export class CoalitionAreaContextMenu extends ContextMenu { }) /* Create the checkboxes to select the unit roles */ - this.#iadsRoleDropdown.setOptionsElements(unitRole.map((unitRole: string) => { + this.#iadsRoleDropdown.setOptionsElements(Object.keys(IADSRoles).map((unitRole: string) => { var div = document.createElement("div"); div.classList.add("ol-checkbox"); var label = document.createElement("label"); diff --git a/client/src/map/coalitionarea.ts b/client/src/map/coalitionarea.ts index 2f44d9f5..2e2ab0fe 100644 --- a/client/src/map/coalitionarea.ts +++ b/client/src/map/coalitionarea.ts @@ -66,11 +66,10 @@ export class CoalitionArea extends Polygon { this.setOpacity(interactive? 1: 0.5); this.options.interactive = interactive; - if (interactive) { + if (interactive) DomUtil.addClass(this.getElement() as HTMLElement, 'leaflet-interactive'); - } else { - DomUtil.removeClass(this.getElement() as HTMLElement, 'leaflet-interactive'); - } + else + DomUtil.removeClass(this.getElement() as HTMLElement, 'leaflet-interactive'); } addTemporaryLatLng(latlng: LatLng) { diff --git a/client/src/map/map.ts b/client/src/map/map.ts index 864f4783..fc4e0cc7 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -396,18 +396,18 @@ export class Map extends L.Map { removeTemporaryMarker(latlng: L.LatLng) { // TODO something more refined than this - var d: number | null = null; + 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); this.#temporaryMarkers.splice(i, 1); } @@ -582,29 +582,40 @@ export class Map extends L.Map { document.getElementById(this.#ID)?.classList.add("hidden-cursor"); } - #showDestinationCursors() { + #showDestinationCursors(singleCursor: boolean) { /* Don't create the cursors if there already are the correct number of them available */ - if (getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).length != this.#destinationPreviewCursors.length) { + if (singleCursor || getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).length != this.#destinationPreviewCursors.length) { /* Reset the cursors to start from a clean condition */ this.#hideDestinationCursors(); if (getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).length > 0) { - /* Create the cursors. If a group is selected only one cursor is shown for it, because you can't control single units in a group */ - this.#destinationPreviewCursors = getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).map((unit: Unit) => { + if (singleCursor) { var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), { interactive: false }); marker.addTo(this); - return marker; - }) + this.#destinationPreviewCursors = [marker]; + } + else { + /* Create the cursors. If a group is selected only one cursor is shown for it, because you can't control single units in a group */ + this.#destinationPreviewCursors = getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).map((unit: Unit) => { + var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), { interactive: false }); + marker.addTo(this); + return marker; + }); + } } } } #updateDestinationCursors(e: any) { const groupLatLng = this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : this.getMouseCoordinates(); - Object.values(getUnitsManager().selectedUnitsComputeGroupDestination(groupLatLng, this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => { - if (idx < this.#destinationPreviewCursors.length) - this.#destinationPreviewCursors[idx].setLatLng(e.originalEvent.shiftKey ? latlng : 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(e.originalEvent.shiftKey ? latlng : this.getMouseCoordinates()); + }) + }; } #hideDestinationCursors() { @@ -660,7 +671,7 @@ export class Map extends L.Map { /* 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 (this.#state === MOVE_UNIT) this.#showDestinationCursors(!e.originalEvent.shiftKey); else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) this.#showTargetCursor(); else if (this.#state === COALITIONAREA_DRAW_POLYGON) this.#showDrawingCursor(); } diff --git a/client/src/server/server.ts b/client/src/server/server.ts index fd71dba2..f3ea1afd 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -321,22 +321,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) { diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index b45cf120..35a7afcb 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -6,7 +6,7 @@ import { deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, import { CoalitionArea } from "../map/coalitionarea"; import { Airbase } from "../missionhandler/airbase"; import { groundUnitsDatabase } from "./groundunitsdatabase"; -import { IDLE, MOVE_UNIT } from "../constants/constants"; +import { IADSRoles, IDLE, MOVE_UNIT } from "../constants/constants"; export class UnitsManager { #units: { [ID: number]: Unit }; @@ -516,9 +516,9 @@ export class UnitsManager { if (distance > maxDistance) maxDistance = distance; }); - const probability = Math.pow(1 - minDistance / 50e3, 5); + const role = activeRoles[Math.floor(Math.random() * activeRoles.length)]; + const probability = Math.pow(1 - minDistance / 50e3, 5) * IADSRoles[role]; if (Math.random() < probability){ - const role = activeRoles[Math.floor(Math.random() * activeRoles.length)]; const unitBlueprint = randomUnitBlueprintByRole(groundUnitsDatabase, role); const spawnOptions = {role: role, latlng: latlng, name: unitBlueprint.name, coalition: coalitionArea.getCoalition(), immediate: true}; spawnGroundUnit(spawnOptions); diff --git a/scripts/OlympusCommand.lua b/scripts/OlympusCommand.lua index 34d74cb5..50772ef6 100644 --- a/scripts/OlympusCommand.lua +++ b/scripts/OlympusCommand.lua @@ -1,6 +1,6 @@ local version = "v0.3.0-alpha" -local debug = true +local debug = false Olympus.unitCounter = 1 Olympus.payloadRegistry = {} @@ -343,7 +343,7 @@ function Olympus.spawnGroundUnit(coalition, unitType, lat, lng) units = unitTable, country = countryID, category = 'vehicle', - name = "Olympus-" .. Olympus.unitCounter, + name = "Ground-" .. Olympus.unitCounter, } mist.dynAdd(vars) Olympus.unitCounter = Olympus.unitCounter + 1 diff --git a/src/core/include/commands.h b/src/core/include/commands.h index b3668f32..1cd8aae4 100644 --- a/src/core/include/commands.h +++ b/src/core/include/commands.h @@ -5,7 +5,7 @@ #include "logger.h" namespace CommandPriority { - enum CommandPriorities { LOW, MEDIUM, HIGH }; + enum CommandPriorities { LOW, MEDIUM, HIGH, IMMEDIATE }; }; namespace SetCommandType { @@ -151,7 +151,7 @@ public: location(location), immediate(immediate) { - priority = CommandPriority::LOW; + priority = immediate? CommandPriority::IMMEDIATE: CommandPriority::LOW; }; virtual wstring getString(lua_State* L); virtual int getLoad() { return 100 * !immediate; } @@ -175,7 +175,7 @@ public: airbaseName(airbaseName), immediate(immediate) { - priority = CommandPriority::LOW; + priority = immediate ? CommandPriority::IMMEDIATE : CommandPriority::LOW; }; virtual wstring getString(lua_State* L); virtual int getLoad() { return 100 * !immediate; } diff --git a/src/core/include/server.h b/src/core/include/server.h index ec08d264..cb735442 100644 --- a/src/core/include/server.h +++ b/src/core/include/server.h @@ -15,6 +15,8 @@ public: void start(lua_State* L); void stop(lua_State* L); + json::value& getUpdateJson() { return updateJson; } + json::value& getRefreshJson() { return refreshJson; } private: std::thread* serverThread; @@ -27,6 +29,8 @@ private: void task(); atomic runListener; + json::value updateJson; + json::value refreshJson; wstring password = L""; }; diff --git a/src/core/include/unit.h b/src/core/include/unit.h index 817beb29..196e89b1 100644 --- a/src/core/include/unit.h +++ b/src/core/include/unit.h @@ -65,7 +65,7 @@ public: void setDefaults(bool force = false); int getID() { return ID; } void runAILoop(); - void updateExportData(json::value json); + void updateExportData(json::value json, double dt = 0); void updateMissionData(json::value json); json::value getData(long long time, bool getAll = false); virtual wstring getCategory() { return L"No category"; }; diff --git a/src/core/include/unitsmanager.h b/src/core/include/unitsmanager.h index 871c9e74..0a0e138b 100644 --- a/src/core/include/unitsmanager.h +++ b/src/core/include/unitsmanager.h @@ -10,16 +10,18 @@ public: UnitsManager(lua_State* L); ~UnitsManager(); + map& getUnits() { return units; }; Unit* getUnit(int ID); bool isUnitInGroup(Unit* unit); bool isUnitGroupLeader(Unit* unit); Unit* getGroupLeader(int ID); Unit* getGroupLeader(Unit* unit); vector getGroupMembers(wstring groupName); - void updateExportData(lua_State* L); + void updateExportData(lua_State* L, double dt = 0); void updateMissionData(json::value missionData); void runAILoop(); - void getData(json::value& answer, long long time); + void getUnitData(json::value& answer, long long time); + void appendUnitData(int ID, json::value& answer, long long time); void deleteUnit(int ID, bool explosion); void acquireControl(int ID); diff --git a/src/core/src/core.cpp b/src/core/src/core.cpp index 38e490f2..aab6d0ac 100644 --- a/src/core/src/core.cpp +++ b/src/core/src/core.cpp @@ -6,6 +6,8 @@ #include "scheduler.h" #include "scriptLoader.h" #include "luatools.h" +#include +using namespace std::chrono; auto before = std::chrono::system_clock::now(); UnitsManager* unitsManager = nullptr; @@ -17,6 +19,10 @@ json::value mission; mutex mutexLock; bool initialized = false; string sessionHash; +int lastUpdateIndex = 0; +int frameCounter = 0; +double frameRate = 30; +long long lastUpdateTime = 0; /* Called when DCS simulation stops. All singleton instances are deleted. */ extern "C" DllExport int coreDeinit(lua_State* L) @@ -61,21 +67,44 @@ extern "C" DllExport int coreFrame(lua_State* L) /* Lock for thread safety */ lock_guard guard(mutexLock); + frameCounter++; + const std::chrono::duration duration = std::chrono::system_clock::now() - before; - /* TODO make intervals editable */ - if (duration.count() > UPDATE_TIME_INTERVAL) + if (unitsManager != nullptr) { + // TODO put in a function + vector IDs; + for (auto iter = unitsManager->getUnits().begin(); iter != unitsManager->getUnits().end(); ++iter) + IDs.push_back(iter->first); + + int updateChunk = 20; + int finalUpdateIndex = lastUpdateIndex + updateChunk; + + /* Get all the new data (with some margin) */ + while (lastUpdateIndex < unitsManager->getUnits().size() && lastUpdateIndex <= finalUpdateIndex) + unitsManager->appendUnitData(IDs[lastUpdateIndex++], server->getUpdateJson(), lastUpdateTime - 1000); + } + + if (duration.count() > UPDATE_TIME_INTERVAL && lastUpdateIndex == unitsManager->getUnits().size()) { + milliseconds ms = duration_cast(system_clock::now().time_since_epoch()); + lastUpdateTime = ms.count(); + frameRate = frameCounter / duration.count(); + frameCounter = 0; + if (unitsManager != nullptr) - { - unitsManager->updateExportData(L); - unitsManager->runAILoop(); - } + unitsManager->updateExportData(L, duration.count()); before = std::chrono::system_clock::now(); + + /* Restart the update counter */ + lastUpdateIndex = 0; } if (scheduler != nullptr) scheduler->execute(L); + + if (duration.count() > UPDATE_TIME_INTERVAL && unitsManager != nullptr) + unitsManager->runAILoop(); return(0); } diff --git a/src/core/src/scheduler.cpp b/src/core/src/scheduler.cpp index 2b55058e..76d3ffcd 100644 --- a/src/core/src/scheduler.cpp +++ b/src/core/src/scheduler.cpp @@ -32,7 +32,7 @@ void Scheduler::execute(lua_State* L) return; } - int priority = CommandPriority::HIGH; + int priority = CommandPriority::IMMEDIATE; while (priority >= CommandPriority::LOW) { for (auto command : commands) diff --git a/src/core/src/server.cpp b/src/core/src/server.cpp index a2b01978..621f0e43 100644 --- a/src/core/src/server.cpp +++ b/src/core/src/server.cpp @@ -36,7 +36,8 @@ Server::Server(lua_State* L): serverThread(nullptr), runListener(true) { - + refreshJson = json::value::object(); + updateJson = json::value::object(); } void Server::start(lua_State* L) @@ -93,7 +94,10 @@ void Server::handle_get(http_request request) time = 0; } } - unitsManager->getData(answer, time); + if (time == 0) + unitsManager->getUnitData(answer, 0); + else + answer[L"units"] = updateJson; } else if (path[0] == LOGS_URI) { diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp index 4b18efc7..cf7ba257 100644 --- a/src/core/src/unit.cpp +++ b/src/core/src/unit.cpp @@ -92,31 +92,31 @@ void Unit::addMeasure(wstring key, json::value value) } void Unit::runAILoop() { - /* If the unit is alive and it is not a human, run the AI Loop that performs the requested commands and instructions (moving, attacking, etc) */ - const bool isUnitControlledByOlympus = getControlled(); - const bool isUnitAlive = getAlive(); - const bool isUnitLeader = unitsManager->isUnitGroupLeader(this); - const bool isUnitLeaderOfAGroupWithOtherUnits = unitsManager->isUnitInGroup(this) && unitsManager->isUnitGroupLeader(this); - const bool isUnitHuman = getFlags()[L"Human"].as_bool(); + /* If the unit is alive, controlled and it is not a human, run the AI Loop that performs the requested commands and instructions (moving, attacking, etc) */ + if (!getControlled()) return; + if (!unitsManager->isUnitGroupLeader(this)) return; + if (getFlags()[L"Human"].as_bool()) return; // Keep running the AI loop even if the unit is dead if it is the leader of a group which has other members in it - if (isUnitControlledByOlympus && (isUnitAlive || isUnitLeaderOfAGroupWithOtherUnits) && isUnitLeader && !isUnitHuman) - { - if (checkTaskFailed() && state != State::IDLE && State::LAND) - setState(State::IDLE); + const bool isUnitAlive = getAlive(); + const bool isUnitLeaderOfAGroupWithOtherUnits = unitsManager->isUnitInGroup(this) && unitsManager->isUnitGroupLeader(this); + if (!(isUnitAlive || isUnitLeaderOfAGroupWithOtherUnits)) return; + + if (checkTaskFailed() && state != State::IDLE && State::LAND) + setState(State::IDLE); - AIloop(); - } + AIloop(); } -void Unit::updateExportData(json::value json) +void Unit::updateExportData(json::value json, double dt) { /* Compute speed (loGetWorldObjects does not provide speed, we compute it for better performance instead of relying on many lua calls) */ if (oldPosition != NULL) { double dist = 0; Geodesic::WGS84().Inverse(latitude, longitude, oldPosition.lat, oldPosition.lng, dist); - setSpeed(getSpeed() * 0.95 + (dist / UPDATE_TIME_INTERVAL) * 0.05); + if (dt > 0) + setSpeed(getSpeed() * 0.95 + (dist / dt) * 0.05); } oldPosition = Coords(latitude, longitude, altitude); @@ -160,7 +160,7 @@ void Unit::updateMissionData(json::value json) setHasTask(json[L"hasTask"].as_bool()); } -json::value Unit::getData(long long time, bool sendAll) +json::value Unit::getData(long long time, bool getAll) { auto json = json::value::object(); @@ -178,7 +178,7 @@ json::value Unit::getData(long long time, bool sendAll) if (json[L"baseData"].size() == 0) json.erase(L"baseData"); - if (alive || sendAll) { + if (alive || getAll) { /********** Flight data **********/ json[L"flightData"] = json::value::object(); for (auto key : { L"latitude", L"longitude", L"altitude", L"speed", L"heading" }) diff --git a/src/core/src/unitsmanager.cpp b/src/core/src/unitsmanager.cpp index 2c683858..4d9ed681 100644 --- a/src/core/src/unitsmanager.cpp +++ b/src/core/src/unitsmanager.cpp @@ -59,18 +59,11 @@ Unit* UnitsManager::getGroupLeader(Unit* unit) if (unit != nullptr) { wstring groupName = unit->getGroupName(); - /* Get the unit IDs in order */ - std::vector keys; - for (auto const& p : units) - keys.push_back(p.first); - sort(keys.begin(), keys.end()); - /* Find the first unit that has the same groupName */ - for (auto const& tempID : keys) + for (auto const& p : units) { - Unit* tempUnit = getUnit(tempID); - if (tempUnit != nullptr && tempUnit->getGroupName().compare(groupName) == 0) - return tempUnit; + if (p.second->getGroupName().compare(groupName) == 0) + return p.second; } } return nullptr; @@ -93,7 +86,7 @@ Unit* UnitsManager::getGroupLeader(int ID) return getGroupLeader(unit); } -void UnitsManager::updateExportData(lua_State* L) +void UnitsManager::updateExportData(lua_State* L, double dt) { map unitJSONs = getAllUnits(L); @@ -132,15 +125,13 @@ void UnitsManager::updateExportData(lua_State* L) else { /* Update the unit if present*/ if (units.count(ID) != 0) - units[ID]->updateExportData(p.second); + units[ID]->updateExportData(p.second, dt); } } /* Set the units that are not present in the JSON as dead (probably have been destroyed) */ for (auto const& unit : units) - { unit.second->setAlive(unitJSONs.find(unit.first) != unitJSONs.end()); - } } void UnitsManager::updateMissionData(json::value missionData) @@ -150,32 +141,38 @@ void UnitsManager::updateMissionData(json::value missionData) { int ID = p.first; if (missionData.has_field(to_wstring(ID))) - { p.second->updateMissionData(missionData[to_wstring(ID)]); - } } } void UnitsManager::runAILoop() { /* Run the AI Loop on all units */ for (auto const& unit : units) - { unit.second->runAILoop(); - } } -void UnitsManager::getData(json::value& answer, long long time) +void UnitsManager::getUnitData(json::value& answer, long long time) { auto unitsJson = json::value::object(); for (auto const& p : units) { auto unitJson = p.second->getData(time); if (unitJson.size() > 0) - unitsJson[to_wstring(p.first)] = p.second->getData(time); + unitsJson[to_wstring(p.first)] = unitJson; } answer[L"units"] = unitsJson; } +void UnitsManager::appendUnitData(int ID, json::value& answer, long long time) +{ + Unit* unit = getUnit(ID); + if (unit != nullptr) { + auto unitJson = unit->getData(time); + if (unitJson.size() > 0) + answer[to_wstring(ID)] = unitJson; + } +} + void UnitsManager::deleteUnit(int ID, bool explosion) { if (getUnit(ID) != nullptr) From 1d62b4c115f464910713efe24276b1264106dacf Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Thu, 22 Jun 2023 17:28:40 +0200 Subject: [PATCH 06/15] Transition to json to binary data transfers --- src/core/include/aircraft.h | 8 +- src/core/include/airunit.h | 12 +- src/core/include/commands.h | 139 ++++---- src/core/include/groundunit.h | 8 +- src/core/include/helicopter.h | 8 +- src/core/include/navyunit.h | 6 +- src/core/include/scheduler.h | 4 +- src/core/include/server.h | 6 +- src/core/include/unit.h | 214 ++++++------ src/core/include/unitsmanager.h | 17 +- src/core/include/weapon.h | 12 +- src/core/src/aircraft.cpp | 17 +- src/core/src/airunit.cpp | 73 ++-- src/core/src/commands.cpp | 52 +-- src/core/src/core.cpp | 48 +-- src/core/src/groundunit.cpp | 34 +- src/core/src/helicopter.cpp | 17 +- src/core/src/navyunit.cpp | 11 +- src/core/src/scheduler.cpp | 219 ++++++------ src/core/src/scriptloader.cpp | 2 + src/core/src/server.cpp | 54 ++- src/core/src/unit.cpp | 460 ++++++++++++-------------- src/core/src/unitsmanager.cpp | 42 +-- src/core/src/weapon.cpp | 8 +- src/dcstools/include/dcstools.h | 6 +- src/dcstools/src/dcstools.cpp | 15 +- src/logger/include/interface.h | 2 +- src/logger/include/logger.h | 2 +- src/logger/src/interface.cpp | 2 +- src/logger/src/logger.cpp | 4 +- src/shared/include/defines.h | 14 +- src/shared/include/framework.h | 1 + src/utils/include/utils.h | 9 +- src/utils/src/utils.cpp | 22 +- third-party/base64/include/base64.hpp | 141 ++++---- 35 files changed, 827 insertions(+), 862 deletions(-) diff --git a/src/core/include/aircraft.h b/src/core/include/aircraft.h index 7fd3f615..87c48a5f 100644 --- a/src/core/include/aircraft.h +++ b/src/core/include/aircraft.h @@ -4,10 +4,10 @@ class Aircraft : public AirUnit { public: - Aircraft(json::value json, int ID); + Aircraft(json::value json, unsigned int ID); - virtual wstring getCategory() { return L"Aircraft"; }; + virtual string getCategory() { return "Aircraft"; }; - virtual void changeSpeed(wstring change); - virtual void changeAltitude(wstring change); + virtual void changeSpeed(string change); + virtual void changeAltitude(string change); }; \ No newline at end of file diff --git a/src/core/include/airunit.h b/src/core/include/airunit.h index cabd7b9e..7c72c88d 100644 --- a/src/core/include/airunit.h +++ b/src/core/include/airunit.h @@ -5,18 +5,18 @@ #include "luatools.h" #include "Unit.h" -#define AIR_DEST_DIST_THR 2000 +#define AIR_DEST_DIST_THR 2000 // Meters class AirUnit : public Unit { public: - AirUnit(json::value json, int ID); + AirUnit(json::value json, unsigned int ID); - virtual void setState(int newState); + virtual void setState(unsigned char newState); - virtual wstring getCategory() = 0; - virtual void changeSpeed(wstring change) = 0; - virtual void changeAltitude(wstring change) = 0; + virtual string getCategory() = 0; + virtual void changeSpeed(string change) = 0; + virtual void changeAltitude(string change) = 0; protected: virtual void AIloop(); diff --git a/src/core/include/commands.h b/src/core/include/commands.h index 1cd8aae4..091eac61 100644 --- a/src/core/include/commands.h +++ b/src/core/include/commands.h @@ -54,6 +54,15 @@ namespace ReactionToThreat { }; } +namespace EmissionCountermeasure { + enum ReactionsToThreat { + SILENT = 0, + ATTACK = 1, + DEFEND = 2, + FREE = 3 + }; +} + namespace RadarUse { enum RadarUses { NEVER = 0, @@ -85,19 +94,19 @@ namespace ECMUse { class Command { public: - int getPriority() { return priority; } - virtual wstring getString(lua_State* L) = 0; - virtual int getLoad() = 0; + unsigned int getPriority() { return priority; } + virtual string getString(lua_State* L) = 0; + virtual unsigned int getLoad() = 0; protected: - int priority = CommandPriority::LOW; + unsigned int priority = CommandPriority::LOW; }; /* Simple low priority move command (from user click) */ class Move : public Command { public: - Move(wstring groupName, Coords destination, double speed, wstring speedType, double altitude, wstring altitudeType, wstring taskOptions, wstring category): + Move(string groupName, Coords destination, double speed, string speedType, double altitude, string altitudeType, string taskOptions, string category): groupName(groupName), destination(destination), speed(speed), @@ -109,35 +118,35 @@ public: { priority = CommandPriority::HIGH; }; - virtual wstring getString(lua_State* L); - virtual int getLoad() { return 5; } + virtual string getString(lua_State* L); + virtual unsigned int getLoad() { return 5; } private: - const wstring groupName; + const string groupName; const Coords destination; const double speed; - const wstring speedType; + const string speedType; const double altitude; - const wstring altitudeType; - const wstring taskOptions; - const wstring category; + const string altitudeType; + const string taskOptions; + const string category; }; /* Smoke command */ class Smoke : public Command { public: - Smoke(wstring color, Coords location) : + Smoke(string color, Coords location) : color(color), location(location) { priority = CommandPriority::LOW; }; - virtual wstring getString(lua_State* L); - virtual int getLoad() { return 5; } + virtual string getString(lua_State* L); + virtual unsigned int getLoad() { return 5; } private: - const wstring color; + const string color; const Coords location; }; @@ -145,7 +154,7 @@ private: class SpawnGroundUnit : public Command { public: - SpawnGroundUnit(wstring coalition, wstring unitType, Coords location, bool immediate) : + SpawnGroundUnit(string coalition, string unitType, Coords location, bool immediate) : coalition(coalition), unitType(unitType), location(location), @@ -153,12 +162,12 @@ public: { priority = immediate? CommandPriority::IMMEDIATE: CommandPriority::LOW; }; - virtual wstring getString(lua_State* L); - virtual int getLoad() { return 100 * !immediate; } + virtual string getString(lua_State* L); + virtual unsigned int getLoad() { return 100 * !immediate; } private: - const wstring coalition; - const wstring unitType; + const string coalition; + const string unitType; const Coords location; const bool immediate; }; @@ -167,7 +176,7 @@ private: class SpawnAircraft : public Command { public: - SpawnAircraft(wstring coalition, wstring unitType, Coords location, wstring payloadName, wstring airbaseName, bool immediate) : + SpawnAircraft(string coalition, string unitType, Coords location, string payloadName, string airbaseName, bool immediate) : coalition(coalition), unitType(unitType), location(location), @@ -177,15 +186,15 @@ public: { priority = immediate ? CommandPriority::IMMEDIATE : CommandPriority::LOW; }; - virtual wstring getString(lua_State* L); - virtual int getLoad() { return 100 * !immediate; } + virtual string getString(lua_State* L); + virtual unsigned int getLoad() { return 100 * !immediate; } private: - const wstring coalition; - const wstring unitType; + const string coalition; + const string unitType; const Coords location; - const wstring payloadName; - const wstring airbaseName; + const string payloadName; + const string airbaseName; const bool immediate; }; @@ -193,17 +202,17 @@ private: class Clone : public Command { public: - Clone(int ID, Coords location) : + Clone(unsigned int ID, Coords location) : ID(ID), location(location) { priority = CommandPriority::LOW; }; - virtual wstring getString(lua_State* L); - virtual int getLoad() { return 100; } + virtual string getString(lua_State* L); + virtual unsigned int getLoad() { return 100; } private: - const int ID; + const unsigned int ID; const Coords location; }; @@ -211,17 +220,17 @@ private: class Delete : public Command { public: - Delete(int ID, bool explosion) : + Delete(unsigned int ID, bool explosion) : ID(ID), explosion(explosion) { priority = CommandPriority::HIGH; }; - virtual wstring getString(lua_State* L); - virtual int getLoad() { return 20; } + virtual string getString(lua_State* L); + virtual unsigned int getLoad() { return 20; } private: - const int ID; + const unsigned int ID; const bool explosion; }; @@ -229,59 +238,59 @@ private: class SetTask : public Command { public: - SetTask(wstring groupName, wstring task) : + SetTask(string groupName, string task) : groupName(groupName), task(task) { priority = CommandPriority::MEDIUM; }; - virtual wstring getString(lua_State* L); - virtual int getLoad() { return 2; } + virtual string getString(lua_State* L); + virtual unsigned int getLoad() { return 2; } private: - const wstring groupName; - const wstring task; + const string groupName; + const string task; }; /* Reset task command */ class ResetTask : public Command { public: - ResetTask(wstring groupName) : + ResetTask(string groupName) : groupName(groupName) { priority = CommandPriority::HIGH; }; - virtual wstring getString(lua_State* L); - virtual int getLoad() { return 2; } + virtual string getString(lua_State* L); + virtual unsigned int getLoad() { return 2; } private: - const wstring groupName; + const string groupName; }; /* Set command */ class SetCommand : public Command { public: - SetCommand(wstring groupName, wstring command) : + SetCommand(string groupName, string command) : groupName(groupName), command(command) { priority = CommandPriority::HIGH; }; - virtual wstring getString(lua_State* L); - virtual int getLoad() { return 2; } + virtual string getString(lua_State* L); + virtual unsigned int getLoad() { return 2; } private: - const wstring groupName; - const wstring command; + const string groupName; + const string command; }; /* Set option command */ class SetOption : public Command { public: - SetOption(wstring groupName, int optionID, int optionValue) : + SetOption(string groupName, unsigned int optionID, unsigned int optionValue) : groupName(groupName), optionID(optionID), optionValue(optionValue), @@ -291,7 +300,7 @@ public: priority = CommandPriority::HIGH; }; - SetOption(wstring groupName, int optionID, bool optionBool) : + SetOption(string groupName, unsigned int optionID, bool optionBool) : groupName(groupName), optionID(optionID), optionValue(0), @@ -300,13 +309,13 @@ public: { priority = CommandPriority::HIGH; }; - virtual wstring getString(lua_State* L); - virtual int getLoad() { return 2; } + virtual string getString(lua_State* L); + virtual unsigned int getLoad() { return 2; } private: - const wstring groupName; - const int optionID; - const int optionValue; + const string groupName; + const unsigned int optionID; + const unsigned int optionValue; const bool optionBool; const bool isBoolean; }; @@ -315,17 +324,17 @@ private: class SetOnOff : public Command { public: - SetOnOff(wstring groupName, bool onOff) : + SetOnOff(string groupName, bool onOff) : groupName(groupName), onOff(onOff) { priority = CommandPriority::HIGH; }; - virtual wstring getString(lua_State* L); - virtual int getLoad() { return 2; } + virtual string getString(lua_State* L); + virtual unsigned int getLoad() { return 2; } private: - const wstring groupName; + const string groupName; const bool onOff; }; @@ -333,16 +342,16 @@ private: class Explosion : public Command { public: - Explosion(int intensity, Coords location) : + Explosion(unsigned int intensity, Coords location) : location(location), intensity(intensity) { priority = CommandPriority::MEDIUM; }; - virtual wstring getString(lua_State* L); - virtual int getLoad() { return 10; } + virtual string getString(lua_State* L); + virtual unsigned int getLoad() { return 10; } private: const Coords location; - const int intensity; + const unsigned int intensity; }; diff --git a/src/core/include/groundunit.h b/src/core/include/groundunit.h index 6b5930b0..fcb8da22 100644 --- a/src/core/include/groundunit.h +++ b/src/core/include/groundunit.h @@ -6,12 +6,12 @@ class GroundUnit : public Unit { public: - GroundUnit(json::value json, int ID); - virtual wstring getCategory() { return L"GroundUnit"; }; + GroundUnit(json::value json, unsigned int ID); + virtual string getCategory() { return "GroundUnit"; }; - virtual void setState(int newState); + virtual void setState(unsigned char newState); - virtual void changeSpeed(wstring change); + virtual void changeSpeed(string change); virtual void setOnOff(bool newOnOff); virtual void setFollowRoads(bool newFollowRoads); diff --git a/src/core/include/helicopter.h b/src/core/include/helicopter.h index 097990b3..94f3d09b 100644 --- a/src/core/include/helicopter.h +++ b/src/core/include/helicopter.h @@ -4,10 +4,10 @@ class Helicopter : public AirUnit { public: - Helicopter(json::value json, int ID); + Helicopter(json::value json, unsigned int ID); - virtual wstring getCategory() { return L"Helicopter"; }; + virtual string getCategory() { return "Helicopter"; }; - virtual void changeSpeed(wstring change); - virtual void changeAltitude(wstring change); + virtual void changeSpeed(string change); + virtual void changeAltitude(string change); }; \ No newline at end of file diff --git a/src/core/include/navyunit.h b/src/core/include/navyunit.h index a27b5a31..d1c6e31e 100644 --- a/src/core/include/navyunit.h +++ b/src/core/include/navyunit.h @@ -4,10 +4,10 @@ class NavyUnit : public Unit { public: - NavyUnit(json::value json, int ID); + NavyUnit(json::value json, unsigned int ID); virtual void AIloop(); - virtual wstring getCategory() { return L"NavyUnit"; }; - virtual void changeSpeed(wstring change); + virtual string getCategory() { return "NavyUnit"; }; + virtual void changeSpeed(string change); }; \ No newline at end of file diff --git a/src/core/include/scheduler.h b/src/core/include/scheduler.h index d5ebfcf6..0b931e1c 100644 --- a/src/core/include/scheduler.h +++ b/src/core/include/scheduler.h @@ -11,10 +11,10 @@ public: void appendCommand(Command* command); void execute(lua_State* L); - void handleRequest(wstring key, json::value value); + void handleRequest(string key, json::value value); private: list commands; - int load; + unsigned int load; }; diff --git a/src/core/include/server.h b/src/core/include/server.h index cb735442..6880f7b4 100644 --- a/src/core/include/server.h +++ b/src/core/include/server.h @@ -15,8 +15,6 @@ public: void start(lua_State* L); void stop(lua_State* L); - json::value& getUpdateJson() { return updateJson; } - json::value& getRefreshJson() { return refreshJson; } private: std::thread* serverThread; @@ -29,9 +27,7 @@ private: void task(); atomic runListener; - json::value updateJson; - json::value refreshJson; - wstring password = L""; + string password = ""; }; diff --git a/src/core/include/unit.h b/src/core/include/unit.h index 196e89b1..2fc60171 100644 --- a/src/core/include/unit.h +++ b/src/core/include/unit.h @@ -5,6 +5,7 @@ #include "luatools.h" #include "measure.h" #include "logger.h" +#include "commands.h" #define TASK_CHECK_INIT_VALUE 10 @@ -32,16 +33,16 @@ namespace Options { struct TACAN { bool isOn = false; - int channel = 40; - wstring XY = L"X"; - wstring callsign = L"TKR"; + unsigned char channel = 40; + char XY = 'X'; + char callsign[4]; }; struct Radio { - int frequency = 124000000; // MHz - int callsign = 1; - int callsignNumber = 1; + unsigned int frequency = 124000000; // MHz + unsigned char callsign = 1; + unsigned char callsignNumber = 1; }; struct GeneralSettings @@ -54,116 +55,145 @@ namespace Options { }; } +namespace DataTypes { + struct Ammo { + unsigned short quantity = 0; + string name; + unsigned char guidance = 0; + unsigned char category = 0; + unsigned char missileCategory = 0; + }; + + struct Contact { + unsigned int ID = 0; + unsigned char detectionMethod = 0; + }; + + struct DataPacket { + unsigned int ID; + unsigned int bitmask; + Coords position; + double speed; + double heading; + unsigned short fuel; + double desiredSpeed; + double desiredAltitude; + unsigned int targetID; + Coords targetPosition; + unsigned char state; + unsigned char ROE; + unsigned char reactionToThreat; + unsigned char emissionsCountermeasures; + Options::TACAN TACAN; + Options::Radio Radio; + }; +} + class Unit { public: - Unit(json::value json, int ID); + Unit(json::value json, unsigned int ID); ~Unit(); /********** Public methods **********/ void initialize(json::value json); void setDefaults(bool force = false); - int getID() { return ID; } + unsigned int getID() { return ID; } void runAILoop(); void updateExportData(json::value json, double dt = 0); void updateMissionData(json::value json); - json::value getData(long long time, bool getAll = false); - virtual wstring getCategory() { return L"No category"; }; + DataTypes::DataPacket getDataPacket(); + string getData(bool refresh); + virtual string getCategory() { return "No category"; }; /********** Base data **********/ - void setControlled(bool newControlled) { controlled = newControlled; addMeasure(L"controlled", json::value(newControlled)); } - void setName(wstring newName) { name = newName; addMeasure(L"name", json::value(newName));} - void setUnitName(wstring newUnitName) { unitName = newUnitName; addMeasure(L"unitName", json::value(newUnitName));} - void setGroupName(wstring newGroupName) { groupName = newGroupName; addMeasure(L"groupName", json::value(newGroupName));} - void setAlive(bool newAlive) { alive = newAlive; addMeasure(L"alive", json::value(newAlive));} - void setType(json::value newType) { type = newType; addMeasure(L"type", newType);} - void setCountry(int newCountry) { country = newCountry; addMeasure(L"country", json::value(newCountry));} + void setControlled(bool newControlled) { controlled = newControlled; } + void setName(string newName) { name = newName; } + void setUnitName(string newUnitName) { unitName = newUnitName; } + void setGroupName(string newGroupName) { groupName = newGroupName; } + void setAlive(bool newAlive) { alive = newAlive; } + void setCountry(unsigned int newCountry) { country = newCountry; } + void setHuman(bool newHuman) { human = newHuman; } bool getControlled() { return controlled; } - wstring getName() { return name; } - wstring getUnitName() { return unitName; } - wstring getGroupName() { return groupName; } + string getName() { return name; } + string getUnitName() { return unitName; } + string getGroupName() { return groupName; } bool getAlive() { return alive; } - json::value getType() { return type; } - int getCountry() { return country; } + unsigned int getCountry() { return country; } + bool getHuman() { return human; } /********** Flight data **********/ - void setLatitude(double newLatitude) {latitude = newLatitude; addMeasure(L"latitude", json::value(newLatitude));} - void setLongitude(double newLongitude) {longitude = newLongitude; addMeasure(L"longitude", json::value(newLongitude));} - void setAltitude(double newAltitude) {altitude = newAltitude; addMeasure(L"altitude", json::value(newAltitude));} - void setHeading(double newHeading) {heading = newHeading; addMeasure(L"heading", json::value(newHeading));} - void setSpeed(double newSpeed) {speed = newSpeed; addMeasure(L"speed", json::value(newSpeed));} + void setPosition(Coords newPosition) { position = newPosition; } + void setHeading(double newHeading) {heading = newHeading; } + void setSpeed(double newSpeed) {speed = newSpeed; } - double getLatitude() { return latitude; } - double getLongitude() { return longitude; } - double getAltitude() { return altitude; } + Coords getPosition() { return position; } double getHeading() { return heading; } double getSpeed() { return speed; } /********** Mission data **********/ - void setFuel(double newFuel) { fuel = newFuel; addMeasure(L"fuel", json::value(newFuel));} - void setAmmo(json::value newAmmo) { ammo = newAmmo; addMeasure(L"ammo", json::value(newAmmo));} - void setContacts(json::value newContacts) {contacts = newContacts; addMeasure(L"contacts", json::value(newContacts));} + void setFuel(short newFuel) { fuel = newFuel; } + void setAmmo(vector newAmmo) { ammo = newAmmo; } + void setContacts(vector newContacts) {contacts = newContacts; } void setHasTask(bool newHasTask); - void setCoalitionID(int newCoalitionID); - void setFlags(json::value newFlags) { flags = newFlags; addMeasure(L"flags", json::value(newFlags));} + void setCoalitionID(unsigned int newCoalitionID); double getFuel() { return fuel; } - json::value getAmmo() { return ammo; } - json::value getTargets() { return contacts; } + vector getAmmo() { return ammo; } + vector getTargets() { return contacts; } bool getHasTask() { return hasTask; } - wstring getCoalition() { return coalition; } - int getCoalitionID(); - json::value getFlags() { return flags; } + string getCoalition() { return coalition; } + unsigned int getCoalitionID(); /********** Formation data **********/ - void setLeaderID(int newLeaderID) { leaderID = newLeaderID; addMeasure(L"leaderID", json::value(newLeaderID)); } + void setLeaderID(unsigned int newLeaderID) { leaderID = newLeaderID; } void setFormationOffset(Offset formationOffset); - int getLeaderID() { return leaderID; } + unsigned int getLeaderID() { return leaderID; } Offset getFormationoffset() { return formationOffset; } /********** Task data **********/ - void setCurrentTask(wstring newCurrentTask) { currentTask = newCurrentTask; addMeasure(L"currentTask", json::value(newCurrentTask)); } + void setCurrentTask(string newCurrentTask) { currentTask = newCurrentTask; } void setDesiredSpeed(double newDesiredSpeed); void setDesiredAltitude(double newDesiredAltitude); - void setDesiredSpeedType(wstring newDesiredSpeedType); - void setDesiredAltitudeType(wstring newDesiredAltitudeType); - void setActiveDestination(Coords newActiveDestination) { activeDestination = newActiveDestination; addMeasure(L"activeDestination", json::value("")); } // TODO fix + void setDesiredSpeedType(string newDesiredSpeedType); + void setDesiredAltitudeType(string newDesiredAltitudeType); + void setActiveDestination(Coords newActiveDestination) { activeDestination = newActiveDestination; } void setActivePath(list newActivePath); - void setTargetID(int newTargetID) { targetID = newTargetID; addMeasure(L"targetID", json::value(newTargetID));} - void setTargetLocation(Coords newTargetLocation); + void setTargetID(unsigned int newTargetID) { targetID = newTargetID; } + void setTargetPosition(Coords newTargetPosition); void setIsTanker(bool newIsTanker); void setIsAWACS(bool newIsAWACS); - virtual void setOnOff(bool newOnOff) { onOff = newOnOff; addMeasure(L"onOff", json::value(newOnOff));}; - virtual void setFollowRoads(bool newFollowRoads) { followRoads = newFollowRoads; addMeasure(L"followRoads", json::value(newFollowRoads)); }; + virtual void setOnOff(bool newOnOff) { onOff = newOnOff; }; + virtual void setFollowRoads(bool newFollowRoads) { followRoads = newFollowRoads; }; - wstring getCurrentTask() { return currentTask; } + string getCurrentTask() { return currentTask; } virtual double getDesiredSpeed() { return desiredSpeed; }; virtual double getDesiredAltitude() { return desiredAltitude; }; - virtual wstring getDesiredSpeedType() { return desiredSpeedType; }; - virtual wstring getDesiredAltitudeType() { return desiredAltitudeType; }; + virtual bool getDesiredSpeedType() { return desiredSpeedType; }; + virtual bool getDesiredAltitudeType() { return desiredAltitudeType; }; Coords getActiveDestination() { return activeDestination; } list getActivePath() { return activePath; } - int getTargetID() { return targetID; } - Coords getTargetLocation() { return targetLocation; } + unsigned int getTargetID() { return targetID; } + Coords getTargetPosition() { return targetPosition; } bool getIsTanker() { return isTanker; } bool getIsAWACS() { return isAWACS; } bool getOnOff() { return onOff; }; bool getFollowRoads() { return followRoads; }; /********** Options data **********/ - void setROE(wstring newROE, bool force = false); - void setReactionToThreat(wstring newReactionToThreat, bool force = false); - void setEmissionsCountermeasures(wstring newEmissionsCountermeasures, bool force = false); + void setROE(unsigned char newROE, bool force = false); + void setReactionToThreat(unsigned char newReactionToThreat, bool force = false); + void setEmissionsCountermeasures(unsigned char newEmissionsCountermeasures, bool force = false); void setTACAN(Options::TACAN newTACAN, bool force = false); void setRadio(Options::Radio newradio, bool force = false); void setGeneralSettings(Options::GeneralSettings newGeneralSettings, bool force = false); void setEPLRS(bool newEPLRS, bool force = false); - wstring getROE() { return ROE; } - wstring getReactionToThreat() { return reactionToThreat; } - wstring getEmissionsCountermeasures() { return emissionsCountermeasures; }; + unsigned char getROE() { return ROE; } + unsigned char getReactionToThreat() { return reactionToThreat; } + unsigned char getEmissionsCountermeasures() { return emissionsCountermeasures; }; Options::TACAN getTACAN() { return TACAN; } Options::Radio getRadio() { return radio; } Options::GeneralSettings getGeneralSettings() { return generalSettings; } @@ -171,10 +201,10 @@ public: /********** Control functions **********/ void landAt(Coords loc); - virtual void changeSpeed(wstring change) {}; - virtual void changeAltitude(wstring change) {}; + virtual void changeSpeed(string change) {}; + virtual void changeAltitude(string change) {}; void resetActiveDestination(); - virtual void setState(int newState) { state = newState; }; + virtual void setState(unsigned char newState) { state = newState; }; void resetTask(); void clearActivePath(); void pushActivePathFront(Coords newActivePathFront); @@ -182,81 +212,77 @@ public: void popActivePathFront(); protected: - int ID; + unsigned int ID; - map measures; - int taskCheckCounter = 0; + map measures; + unsigned int taskCheckCounter = 0; /********** Base data **********/ bool controlled = false; - wstring name = L"undefined"; - wstring unitName = L"undefined"; - wstring groupName = L"undefined"; + string name = "undefined"; + string unitName = "undefined"; + string groupName = "undefined"; bool alive = true; - json::value type = json::value::null(); - int country = NULL; + bool human = false; + unsigned int country = NULL; /********** Flight data **********/ - double latitude = NULL; - double longitude = NULL; - double altitude = NULL; + Coords position = Coords(NULL); double speed = NULL; double heading = NULL; /********** Mission data **********/ - double fuel = 0; + unsigned short fuel = 0; double initialFuel = 0; // Used internally to detect refueling completed - json::value ammo = json::value::null(); - json::value contacts = json::value::null(); + vector ammo; + vector contacts; bool hasTask = false; - wstring coalition = L""; - json::value flags = json::value::null(); + string coalition = ""; /********** Formation data **********/ - int leaderID = NULL; + unsigned int leaderID = NULL; Offset formationOffset = Offset(NULL); /********** Task data **********/ - wstring currentTask = L""; + string currentTask = ""; double desiredSpeed = 0; double desiredAltitude = 0; - wstring desiredSpeedType = L"GS"; - wstring desiredAltitudeType = L"AGL"; + bool desiredSpeedType = 0; + bool desiredAltitudeType = 0; list activePath; Coords activeDestination = Coords(NULL); - int targetID = NULL; - Coords targetLocation = Coords(NULL); + unsigned int targetID = NULL; + Coords targetPosition = Coords(NULL); bool isTanker = false; bool isAWACS = false; bool onOff = true; bool followRoads = false; /********** Options data **********/ - wstring ROE = L"Designated"; - wstring reactionToThreat = L"Evade"; - wstring emissionsCountermeasures = L"Defend"; + unsigned char ROE = ROE::OPEN_FIRE_WEAPON_FREE; + unsigned char reactionToThreat = ReactionToThreat::EVADE_FIRE; + unsigned char emissionsCountermeasures = EmissionCountermeasure::DEFEND; Options::TACAN TACAN; Options::Radio radio; Options::GeneralSettings generalSettings; bool EPLRS = false; /********** State machine **********/ - int state = State::NONE; + unsigned char state = State::NONE; /********** Other **********/ Coords oldPosition = Coords(0); // Used to approximate speed /********** Functions **********/ - wstring getTargetName(); - wstring getLeaderName(); + string getTargetName(); + string getLeaderName(); bool isTargetAlive(); bool isLeaderAlive(); virtual void AIloop() = 0; - void addMeasure(wstring key, json::value value); bool isDestinationReached(double threshold); bool setActiveDestination(); bool updateActivePath(bool looping); - void goToDestination(wstring enrouteTask = L"nil"); + void goToDestination(string enrouteTask = "nil"); bool checkTaskFailed(); void resetTaskFailedCounter(); }; diff --git a/src/core/include/unitsmanager.h b/src/core/include/unitsmanager.h index 0a0e138b..b2233183 100644 --- a/src/core/include/unitsmanager.h +++ b/src/core/include/unitsmanager.h @@ -10,23 +10,22 @@ public: UnitsManager(lua_State* L); ~UnitsManager(); - map& getUnits() { return units; }; - Unit* getUnit(int ID); + map& getUnits() { return units; }; + Unit* getUnit(unsigned int ID); bool isUnitInGroup(Unit* unit); bool isUnitGroupLeader(Unit* unit); - Unit* getGroupLeader(int ID); + Unit* getGroupLeader(unsigned int ID); Unit* getGroupLeader(Unit* unit); - vector getGroupMembers(wstring groupName); + vector getGroupMembers(string groupName); void updateExportData(lua_State* L, double dt = 0); void updateMissionData(json::value missionData); void runAILoop(); - void getUnitData(json::value& answer, long long time); - void appendUnitData(int ID, json::value& answer, long long time); - void deleteUnit(int ID, bool explosion); - void acquireControl(int ID); + string getUnitData(bool refresh); + void deleteUnit(unsigned int ID, bool explosion); + void acquireControl(unsigned int ID); private: - map units; + map units; json::value missionDB; }; diff --git a/src/core/include/weapon.h b/src/core/include/weapon.h index a1097bcb..1597ffec 100644 --- a/src/core/include/weapon.h +++ b/src/core/include/weapon.h @@ -4,9 +4,9 @@ class Weapon : public Unit { public: - Weapon(json::value json, int ID); + Weapon(json::value json, unsigned int ID); - virtual wstring getCategory() = 0; + virtual string getCategory() = 0; protected: /* Weapons are not controllable and have no AIloop */ @@ -16,15 +16,15 @@ protected: class Missile : public Weapon { public: - Missile(json::value json, int ID); + Missile(json::value json, unsigned int ID); - virtual wstring getCategory() { return L"Missile"; }; + virtual string getCategory() { return "Missile"; }; }; class Bomb : public Weapon { public: - Bomb(json::value json, int ID); + Bomb(json::value json, unsigned int ID); - virtual wstring getCategory() { return L"Bomb"; }; + virtual string getCategory() { return "Bomb"; }; }; \ No newline at end of file diff --git a/src/core/src/aircraft.cpp b/src/core/src/aircraft.cpp index 1284f903..c1cca92e 100644 --- a/src/core/src/aircraft.cpp +++ b/src/core/src/aircraft.cpp @@ -13,10 +13,9 @@ extern Scheduler* scheduler; extern UnitsManager* unitsManager; /* Aircraft */ -Aircraft::Aircraft(json::value json, int ID) : AirUnit(json, ID) +Aircraft::Aircraft(json::value json, unsigned int ID) : AirUnit(json, ID) { log("New Aircraft created with ID: " + to_string(ID)); - addMeasure(L"category", json::value(getCategory())); double desiredSpeed = knotsToMs(300); double desiredAltitude = ftToM(20000); @@ -24,13 +23,13 @@ Aircraft::Aircraft(json::value json, int ID) : AirUnit(json, ID) setDesiredAltitude(desiredAltitude); }; -void Aircraft::changeSpeed(wstring change) +void Aircraft::changeSpeed(string change) { - if (change.compare(L"stop") == 0) + if (change.compare("stop") == 0) setState(State::IDLE); - else if (change.compare(L"slow") == 0) + else if (change.compare("slow") == 0) setDesiredSpeed(getDesiredSpeed() - knotsToMs(25)); - else if (change.compare(L"fast") == 0) + else if (change.compare("fast") == 0) setDesiredSpeed(getDesiredSpeed() + knotsToMs(25)); if (getDesiredSpeed() < knotsToMs(50)) @@ -42,16 +41,16 @@ void Aircraft::changeSpeed(wstring change) goToDestination(); /* Send the command to reach the destination */ } -void Aircraft::changeAltitude(wstring change) +void Aircraft::changeAltitude(string change) { - if (change.compare(L"descend") == 0) + if (change.compare("descend") == 0) { if (getDesiredAltitude() > 5000) setDesiredAltitude(getDesiredAltitude() - ftToM(2500)); else if (getDesiredAltitude() > 0) setDesiredAltitude(getDesiredAltitude() - ftToM(500)); } - else if (change.compare(L"climb") == 0) + else if (change.compare("climb") == 0) { if (getDesiredAltitude() > 5000) setDesiredAltitude(getDesiredAltitude() + ftToM(2500)); diff --git a/src/core/src/airunit.cpp b/src/core/src/airunit.cpp index 06c0808a..375a996d 100644 --- a/src/core/src/airunit.cpp +++ b/src/core/src/airunit.cpp @@ -13,12 +13,12 @@ extern Scheduler* scheduler; extern UnitsManager* unitsManager; /* Air unit */ -AirUnit::AirUnit(json::value json, int ID) : Unit(json, ID) +AirUnit::AirUnit(json::value json, unsigned int ID) : Unit(json, ID) { }; -void AirUnit::setState(int newState) +void AirUnit::setState(unsigned char newState) { /************ Perform any action required when LEAVING a state ************/ if (newState != state) { @@ -46,7 +46,7 @@ void AirUnit::setState(int newState) case State::BOMB_POINT: case State::CARPET_BOMB: case State::BOMB_BUILDING: { - setTargetLocation(Coords(NULL)); + setTargetPosition(Coords(NULL)); break; } default: @@ -59,12 +59,10 @@ void AirUnit::setState(int newState) case State::IDLE: { clearActivePath(); resetActiveDestination(); - addMeasure(L"currentState", json::value(L"Idle")); break; } case State::REACH_DESTINATION: { resetActiveDestination(); - addMeasure(L"currentState", json::value(L"Reach destination")); break; } case State::ATTACK: { @@ -74,42 +72,35 @@ void AirUnit::setState(int newState) clearActivePath(); pushActivePathFront(targetPosition); resetActiveDestination(); - addMeasure(L"currentState", json::value(L"Attack")); } break; } case State::FOLLOW: { clearActivePath(); resetActiveDestination(); - addMeasure(L"currentState", json::value(L"Follow")); break; } case State::LAND: { resetActiveDestination(); - addMeasure(L"currentState", json::value(L"Land")); break; } case State::REFUEL: { initialFuel = fuel; clearActivePath(); resetActiveDestination(); - addMeasure(L"currentState", json::value(L"Refuel")); break; } case State::BOMB_POINT: { - addMeasure(L"currentState", json::value(L"Bombing point")); clearActivePath(); resetActiveDestination(); break; } case State::CARPET_BOMB: { - addMeasure(L"currentState", json::value(L"Carpet bombing")); clearActivePath(); resetActiveDestination(); break; } case State::BOMB_BUILDING: { - addMeasure(L"currentState", json::value(L"Bombing building")); clearActivePath(); resetActiveDestination(); break; @@ -120,7 +111,7 @@ void AirUnit::setState(int newState) resetTask(); - log(unitName + L" setting state from " + to_wstring(state) + L" to " + to_wstring(newState)); + log(unitName + " setting state from " + to_string(state) + " to " + to_string(newState)); state = newState; } @@ -129,11 +120,11 @@ void AirUnit::AIloop() /* State machine */ switch (state) { case State::IDLE: { - currentTask = L"Idle"; + currentTask = "Idle"; if (!getHasTask()) { - std::wostringstream taskSS; + std::ostringstream taskSS; if (isTanker) { taskSS << "{ [1] = { id = 'Tanker' }, [2] = { id = 'Orbit', pattern = 'Race-Track', altitude = " << desiredAltitude << ", speed = " << desiredSpeed << ", altitudeType = '" << desiredAltitudeType << "' } }"; } @@ -150,23 +141,23 @@ void AirUnit::AIloop() break; } case State::REACH_DESTINATION: { - wstring enrouteTask = L""; + string enrouteTask = ""; bool looping = false; if (isTanker) { - enrouteTask = L"{ id = 'Tanker' }"; - currentTask = L"Tanker"; + enrouteTask = "{ id = 'Tanker' }"; + currentTask = "Tanker"; } else if (isAWACS) { - enrouteTask = L"{ id = 'AWACS' }"; - currentTask = L"AWACS"; + enrouteTask = "{ id = 'AWACS' }"; + currentTask = "AWACS"; } else { - enrouteTask = L"nil"; - currentTask = L"Reaching destination"; + enrouteTask = "nil"; + currentTask = "Reaching destination"; } if (activeDestination == NULL || !getHasTask()) @@ -187,8 +178,8 @@ void AirUnit::AIloop() break; } case State::LAND: { - wstring enrouteTask = L"{ id = 'Land' }"; - currentTask = L"Landing"; + string enrouteTask = "{ id = 'Land' }"; + currentTask = "Landing"; if (activeDestination == NULL) { @@ -206,13 +197,13 @@ void AirUnit::AIloop() /* Attack state is an "enroute" task, meaning the unit will keep trying to attack even if a new destination is set. This is useful to manoeuvre the unit so that it can detect and engage the target. */ - std::wostringstream enrouteTaskSS; + std::ostringstream enrouteTaskSS; enrouteTaskSS << "{" << "id = 'EngageUnit'" << "," << "targetID = " << targetID << "," << "}"; - wstring enrouteTask = enrouteTaskSS.str(); - currentTask = L"Attacking " + getTargetName(); + string enrouteTask = enrouteTaskSS.str(); + currentTask = "Attacking " + getTargetName(); if (!getHasTask()) { @@ -232,13 +223,13 @@ void AirUnit::AIloop() break; } - currentTask = L"Following " + getTargetName(); + currentTask = "Following " + getTargetName(); Unit* leader = unitsManager->getUnit(leaderID); if (!getHasTask()) { if (leader != nullptr && leader->getAlive() && formationOffset != NULL) { - std::wostringstream taskSS; + std::ostringstream taskSS; taskSS << "{" << "id = 'FollowUnit'" << ", " << "leaderID = " << leader->getID() << "," @@ -256,11 +247,11 @@ void AirUnit::AIloop() break; } case State::REFUEL: { - currentTask = L"Refueling"; + currentTask = "Refueling"; if (!getHasTask()) { if (fuel <= initialFuel) { - std::wostringstream taskSS; + std::ostringstream taskSS; taskSS << "{" << "id = 'Refuel'" << "}"; @@ -274,22 +265,22 @@ void AirUnit::AIloop() } } case State::BOMB_POINT: { - currentTask = L"Bombing point"; + currentTask = "Bombing point"; if (!getHasTask()) { - std::wostringstream taskSS; - taskSS << "{id = 'Bombing', lat = " << targetLocation.lat << ", lng = " << targetLocation.lng << "}"; + std::ostringstream taskSS; + taskSS << "{id = 'Bombing', lat = " << targetPosition.lat << ", lng = " << targetPosition.lng << "}"; Command* command = dynamic_cast(new SetTask(groupName, taskSS.str())); scheduler->appendCommand(command); setHasTask(true); } } case State::CARPET_BOMB: { - currentTask = L"Carpet bombing"; + currentTask = "Carpet bombing"; if (!getHasTask()) { - std::wostringstream taskSS; - taskSS << "{id = 'CarpetBombing', lat = " << targetLocation.lat << ", lng = " << targetLocation.lng << "}"; + std::ostringstream taskSS; + taskSS << "{id = 'CarpetBombing', lat = " << targetPosition.lat << ", lng = " << targetPosition.lng << "}"; Command* command = dynamic_cast(new SetTask(groupName, taskSS.str())); scheduler->appendCommand(command); setHasTask(true); @@ -297,11 +288,11 @@ void AirUnit::AIloop() break; } case State::BOMB_BUILDING: { - currentTask = L"Bombing building"; + currentTask = "Bombing building"; if (!getHasTask()) { - std::wostringstream taskSS; - taskSS << "{id = 'AttackMapObject', lat = " << targetLocation.lat << ", lng = " << targetLocation.lng << "}"; + std::ostringstream taskSS; + taskSS << "{id = 'AttackMapObject', lat = " << targetPosition.lat << ", lng = " << targetPosition.lng << "}"; Command* command = dynamic_cast(new SetTask(groupName, taskSS.str())); scheduler->appendCommand(command); setHasTask(true); @@ -311,6 +302,4 @@ void AirUnit::AIloop() default: break; } - - addMeasure(L"currentTask", json::value(currentTask)); } \ No newline at end of file diff --git a/src/core/src/commands.cpp b/src/core/src/commands.cpp index 87f33218..fbd6da60 100644 --- a/src/core/src/commands.cpp +++ b/src/core/src/commands.cpp @@ -7,10 +7,10 @@ extern UnitsManager* unitsManager; /* Move command */ -wstring Move::getString(lua_State* L) +string Move::getString(lua_State* L) { - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.move, " << "\"" << groupName << "\"" << ", " @@ -26,9 +26,9 @@ wstring Move::getString(lua_State* L) } /* Smoke command */ -wstring Smoke::getString(lua_State* L) +string Smoke::getString(lua_State* L) { - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.smoke, " << "\"" << color << "\"" << ", " @@ -38,9 +38,9 @@ wstring Smoke::getString(lua_State* L) } /* Spawn ground command */ -wstring SpawnGroundUnit::getString(lua_State* L) +string SpawnGroundUnit::getString(lua_State* L) { - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.spawnGroundUnit, " << "\"" << coalition << "\"" << ", " @@ -51,16 +51,16 @@ wstring SpawnGroundUnit::getString(lua_State* L) } /* Spawn air command */ -wstring SpawnAircraft::getString(lua_State* L) +string SpawnAircraft::getString(lua_State* L) { - std::wostringstream optionsSS; + std::ostringstream optionsSS; optionsSS.precision(10); optionsSS << "{" << "payloadName = \"" << payloadName << "\", " << "airbaseName = \"" << airbaseName << "\", " << "}"; - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.spawnAircraft, " << "\"" << coalition << "\"" << ", " @@ -73,12 +73,12 @@ wstring SpawnAircraft::getString(lua_State* L) } /* Clone unit command */ -wstring Clone::getString(lua_State* L) +string Clone::getString(lua_State* L) { Unit* unit = unitsManager->getUnit(ID); if (unit != nullptr) { - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.clone, " << ID << ", " @@ -89,14 +89,14 @@ wstring Clone::getString(lua_State* L) } else { - return L""; + return ""; } } /* Delete unit command */ -wstring Delete::getString(lua_State* L) +string Delete::getString(lua_State* L) { - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.delete, " << ID << ", " @@ -105,9 +105,9 @@ wstring Delete::getString(lua_State* L) } /* Set task command */ -wstring SetTask::getString(lua_State* L) +string SetTask::getString(lua_State* L) { - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.setTask, " << "\"" << groupName << "\"" << ", " @@ -117,9 +117,9 @@ wstring SetTask::getString(lua_State* L) } /* Reset task command */ -wstring ResetTask::getString(lua_State* L) +string ResetTask::getString(lua_State* L) { - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.resetTask, " << "\"" << groupName << "\""; @@ -128,9 +128,9 @@ wstring ResetTask::getString(lua_State* L) } /* Set command command */ -wstring SetCommand::getString(lua_State* L) +string SetCommand::getString(lua_State* L) { - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.setCommand, " << "\"" << groupName << "\"" << ", " @@ -140,9 +140,9 @@ wstring SetCommand::getString(lua_State* L) } /* Set option command */ -wstring SetOption::getString(lua_State* L) +string SetOption::getString(lua_State* L) { - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS.precision(10); if (!isBoolean) { @@ -160,9 +160,9 @@ wstring SetOption::getString(lua_State* L) } /* Set onOff command */ -wstring SetOnOff::getString(lua_State* L) +string SetOnOff::getString(lua_State* L) { - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.setOnOff, " @@ -173,9 +173,9 @@ wstring SetOnOff::getString(lua_State* L) } /* Explosion command */ -wstring Explosion::getString(lua_State* L) +string Explosion::getString(lua_State* L) { - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.explosion, " << intensity << ", " diff --git a/src/core/src/core.cpp b/src/core/src/core.cpp index aab6d0ac..7a17691b 100644 --- a/src/core/src/core.cpp +++ b/src/core/src/core.cpp @@ -10,19 +10,24 @@ using namespace std::chrono; auto before = std::chrono::system_clock::now(); + +/* Singleton objects */ UnitsManager* unitsManager = nullptr; Server* server = nullptr; Scheduler* scheduler = nullptr; + +/* Data jsons */ json::value airbases; json::value bullseyes; json::value mission; + mutex mutexLock; -bool initialized = false; string sessionHash; -int lastUpdateIndex = 0; -int frameCounter = 0; + +bool initialized = false; + +unsigned int frameCounter = 0; double frameRate = 30; -long long lastUpdateTime = 0; /* Called when DCS simulation stops. All singleton instances are deleted. */ extern "C" DllExport int coreDeinit(lua_State* L) @@ -64,48 +69,29 @@ extern "C" DllExport int coreFrame(lua_State* L) if (!initialized) return (0); - /* Lock for thread safety */ - lock_guard guard(mutexLock); - frameCounter++; + /* Slow down the update rate if the frameRate is very low since it means DCS is struggling to keep up */ const std::chrono::duration duration = std::chrono::system_clock::now() - before; - - if (unitsManager != nullptr) { - // TODO put in a function - vector IDs; - for (auto iter = unitsManager->getUnits().begin(); iter != unitsManager->getUnits().end(); ++iter) - IDs.push_back(iter->first); - - int updateChunk = 20; - int finalUpdateIndex = lastUpdateIndex + updateChunk; - - /* Get all the new data (with some margin) */ - while (lastUpdateIndex < unitsManager->getUnits().size() && lastUpdateIndex <= finalUpdateIndex) - unitsManager->appendUnitData(IDs[lastUpdateIndex++], server->getUpdateJson(), lastUpdateTime - 1000); - } - - if (duration.count() > UPDATE_TIME_INTERVAL && lastUpdateIndex == unitsManager->getUnits().size()) + if (duration.count() > UPDATE_TIME_INTERVAL * (60.0 / frameRate)) { + /* Lock for thread safety */ + lock_guard guard(mutexLock); + milliseconds ms = duration_cast(system_clock::now().time_since_epoch()); - lastUpdateTime = ms.count(); frameRate = frameCounter / duration.count(); frameCounter = 0; - if (unitsManager != nullptr) + if (unitsManager != nullptr) { unitsManager->updateExportData(L, duration.count()); + unitsManager->runAILoop(); + } before = std::chrono::system_clock::now(); - - /* Restart the update counter */ - lastUpdateIndex = 0; } if (scheduler != nullptr) scheduler->execute(L); - if (duration.count() > UPDATE_TIME_INTERVAL && unitsManager != nullptr) - unitsManager->runAILoop(); - return(0); } diff --git a/src/core/src/groundunit.cpp b/src/core/src/groundunit.cpp index 58b8fd50..f28e8a18 100644 --- a/src/core/src/groundunit.cpp +++ b/src/core/src/groundunit.cpp @@ -13,16 +13,15 @@ extern Scheduler* scheduler; extern UnitsManager* unitsManager; /* Ground unit */ -GroundUnit::GroundUnit(json::value json, int ID) : Unit(json, ID) +GroundUnit::GroundUnit(json::value json, unsigned int ID) : Unit(json, ID) { log("New Ground Unit created with ID: " + to_string(ID)); - addMeasure(L"category", json::value(getCategory())); double desiredSpeed = 10; setDesiredSpeed(desiredSpeed); }; -void GroundUnit::setState(int newState) +void GroundUnit::setState(unsigned char newState) { /************ Perform any action required when LEAVING a state ************/ if (newState != state) { @@ -34,7 +33,7 @@ void GroundUnit::setState(int newState) break; } case State::FIRE_AT_AREA: { - setTargetLocation(Coords(NULL)); + setTargetPosition(Coords(NULL)); break; } default: @@ -47,16 +46,13 @@ void GroundUnit::setState(int newState) case State::IDLE: { clearActivePath(); resetActiveDestination(); - addMeasure(L"currentState", json::value(L"Idle")); break; } case State::REACH_DESTINATION: { resetActiveDestination(); - addMeasure(L"currentState", json::value(L"Reach destination")); break; } case State::FIRE_AT_AREA: { - addMeasure(L"currentState", json::value(L"Firing at area")); clearActivePath(); resetActiveDestination(); break; @@ -67,7 +63,7 @@ void GroundUnit::setState(int newState) resetTask(); - log(unitName + L" setting state from " + to_wstring(state) + L" to " + to_wstring(newState)); + log(unitName + " setting state from " + to_string(state) + " to " + to_string(newState)); state = newState; } @@ -75,16 +71,16 @@ void GroundUnit::AIloop() { switch (state) { case State::IDLE: { - currentTask = L"Idle"; + currentTask = "Idle"; if (getHasTask()) resetTask(); break; } case State::REACH_DESTINATION: { - wstring enrouteTask = L""; + string enrouteTask = ""; bool looping = false; - std::wostringstream taskSS; + std::ostringstream taskSS; taskSS << "{ id = 'FollowRoads', value = " << (getFollowRoads() ? "true" : "false") << " }"; enrouteTask = taskSS.str(); @@ -106,11 +102,11 @@ void GroundUnit::AIloop() break; } case State::FIRE_AT_AREA: { - currentTask = L"Firing at area"; + currentTask = "Firing at area"; if (!getHasTask()) { - std::wostringstream taskSS; - taskSS << "{id = 'FireAtPoint', lat = " << targetLocation.lat << ", lng = " << targetLocation.lng << ", radius = 1000}"; + std::ostringstream taskSS; + taskSS << "{id = 'FireAtPoint', lat = " << targetPosition.lat << ", lng = " << targetPosition.lng << ", radius = 1000}"; Command* command = dynamic_cast(new SetTask(groupName, taskSS.str())); scheduler->appendCommand(command); setHasTask(true); @@ -119,17 +115,15 @@ void GroundUnit::AIloop() default: break; } - - addMeasure(L"currentTask", json::value(currentTask)); } -void GroundUnit::changeSpeed(wstring change) +void GroundUnit::changeSpeed(string change) { - if (change.compare(L"stop") == 0) + if (change.compare("stop") == 0) setState(State::IDLE); - else if (change.compare(L"slow") == 0) + else if (change.compare("slow") == 0) setDesiredSpeed(getDesiredSpeed() - knotsToMs(5)); - else if (change.compare(L"fast") == 0) + else if (change.compare("fast") == 0) setDesiredSpeed(getDesiredSpeed() + knotsToMs(5)); if (getDesiredSpeed() < 0) diff --git a/src/core/src/helicopter.cpp b/src/core/src/helicopter.cpp index d728f4c5..5215a484 100644 --- a/src/core/src/helicopter.cpp +++ b/src/core/src/helicopter.cpp @@ -13,10 +13,9 @@ extern Scheduler* scheduler; extern UnitsManager* unitsManager; /* Helicopter */ -Helicopter::Helicopter(json::value json, int ID) : AirUnit(json, ID) +Helicopter::Helicopter(json::value json, unsigned int ID) : AirUnit(json, ID) { log("New Helicopter created with ID: " + to_string(ID)); - addMeasure(L"category", json::value(getCategory())); double desiredSpeed = knotsToMs(100); double desiredAltitude = ftToM(5000); @@ -24,16 +23,16 @@ Helicopter::Helicopter(json::value json, int ID) : AirUnit(json, ID) setDesiredAltitude(desiredAltitude); }; -void Helicopter::changeSpeed(wstring change) +void Helicopter::changeSpeed(string change) { - if (change.compare(L"stop") == 0) + if (change.compare("stop") == 0) { /* Air units can't hold a position, so we can only set them to hold. At the moment, this will erase any other command. TODO: helicopters should be able to hover in place */ clearActivePath(); } - else if (change.compare(L"slow") == 0) + else if (change.compare("slow") == 0) desiredSpeed -= knotsToMs(10); - else if (change.compare(L"fast") == 0) + else if (change.compare("fast") == 0) desiredSpeed += knotsToMs(10); if (desiredSpeed < 0) desiredSpeed = 0; @@ -41,16 +40,16 @@ void Helicopter::changeSpeed(wstring change) goToDestination(); /* Send the command to reach the destination */ } -void Helicopter::changeAltitude(wstring change) +void Helicopter::changeAltitude(string change) { - if (change.compare(L"descend") == 0) + if (change.compare("descend") == 0) { if (desiredAltitude > 100) desiredAltitude -= ftToM(100); else if (desiredAltitude > 0) desiredAltitude -= ftToM(10); } - else if (change.compare(L"climb") == 0) + else if (change.compare("climb") == 0) { if (desiredAltitude > 100) desiredAltitude += ftToM(100); diff --git a/src/core/src/navyunit.cpp b/src/core/src/navyunit.cpp index dc96f7a1..b3746803 100644 --- a/src/core/src/navyunit.cpp +++ b/src/core/src/navyunit.cpp @@ -13,10 +13,9 @@ extern Scheduler* scheduler; extern UnitsManager* unitsManager; /* Navy Unit */ -NavyUnit::NavyUnit(json::value json, int ID) : Unit(json, ID) +NavyUnit::NavyUnit(json::value json, unsigned int ID) : Unit(json, ID) { log("New Navy Unit created with ID: " + to_string(ID)); - addMeasure(L"category", json::value(getCategory())); double desiredSpeed = 10; setDesiredSpeed(desiredSpeed); @@ -27,17 +26,17 @@ void NavyUnit::AIloop() /* TODO */ } -void NavyUnit::changeSpeed(wstring change) +void NavyUnit::changeSpeed(string change) { - if (change.compare(L"stop") == 0) + if (change.compare("stop") == 0) { } - else if (change.compare(L"slow") == 0) + else if (change.compare("slow") == 0) { } - else if (change.compare(L"fast") == 0) + else if (change.compare("fast") == 0) { } diff --git a/src/core/src/scheduler.cpp b/src/core/src/scheduler.cpp index 76d3ffcd..e070414a 100644 --- a/src/core/src/scheduler.cpp +++ b/src/core/src/scheduler.cpp @@ -32,16 +32,16 @@ void Scheduler::execute(lua_State* L) return; } - int priority = CommandPriority::IMMEDIATE; + unsigned int priority = CommandPriority::IMMEDIATE; while (priority >= CommandPriority::LOW) { for (auto command : commands) { if (command->getPriority() == priority) { - wstring commandString = L"Olympus.protectedCall(" + command->getString(L) + L")"; - if (dostring_in(L, "server", to_string(commandString))) - log(L"Error executing command " + commandString); + string commandString = "Olympus.protectedCall(" + command->getString(L) + ")"; + if (dostring_in(L, "server", (commandString))) + log("Error executing command " + commandString); load = command->getLoad(); commands.remove(command); return; @@ -51,81 +51,81 @@ void Scheduler::execute(lua_State* L) } } -void Scheduler::handleRequest(wstring key, json::value value) +void Scheduler::handleRequest(string key, json::value value) { Command* command = nullptr; - log(L"Received request with ID: " + key); - if (key.compare(L"setPath") == 0) + log("Received request with ID: " + key); + if (key.compare("setPath") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) { - wstring unitName = unit->getUnitName(); + string unitName = unit->getUnitName(); json::value path = value[L"path"]; list newPath; - for (int i = 1; i <= path.as_object().size(); i++) + for (unsigned int i = 1; i <= path.as_object().size(); i++) { - wstring WP = to_wstring(i); - double lat = path[WP][L"lat"].as_double(); - double lng = path[WP][L"lng"].as_double(); - log(unitName + L" set path destination " + WP + L" (" + to_wstring(lat) + L", " + to_wstring(lng) + L")"); + string WP = to_string(i); + double lat = path[to_wstring(i)][L"lat"].as_double(); + double lng = path[to_wstring(i)][L"lng"].as_double(); + log(unitName + " set path destination " + WP + " (" + to_string(lat) + ", " + to_string(lng) + ")"); Coords dest; dest.lat = lat; dest.lng = lng; newPath.push_back(dest); } unit->setActivePath(newPath); unit->setState(State::REACH_DESTINATION); - log(unitName + L" new path set successfully"); + log(unitName + " new path set successfully"); } } - else if (key.compare(L"smoke") == 0) + else if (key.compare("smoke") == 0) { - wstring color = value[L"color"].as_string(); + string color = to_string(value[L"color"]); double lat = value[L"location"][L"lat"].as_double(); double lng = value[L"location"][L"lng"].as_double(); - log(L"Adding " + color + L" smoke at (" + to_wstring(lat) + L", " + to_wstring(lng) + L")"); + log("Adding " + color + " smoke at (" + to_string(lat) + ", " + to_string(lng) + ")"); Coords loc; loc.lat = lat; loc.lng = lng; command = dynamic_cast(new Smoke(color, loc)); } - else if (key.compare(L"spawnGround") == 0) + else if (key.compare("spawnGround") == 0) { bool immediate = value[L"immediate"].as_bool(); - wstring coalition = value[L"coalition"].as_string(); - wstring type = value[L"type"].as_string(); + string coalition = to_string(value[L"coalition"]); + string type = to_string(value[L"type"]); double lat = value[L"location"][L"lat"].as_double(); double lng = value[L"location"][L"lng"].as_double(); - log(L"Spawning " + coalition + L" ground unit of type " + type + L" at (" + to_wstring(lat) + L", " + to_wstring(lng) + L")"); + log("Spawning " + coalition + " ground unit of type " + type + " at (" + to_string(lat) + ", " + to_string(lng) + ")"); Coords loc; loc.lat = lat; loc.lng = lng; command = dynamic_cast(new SpawnGroundUnit(coalition, type, loc, immediate)); } - else if (key.compare(L"spawnAir") == 0) + else if (key.compare("spawnAir") == 0) { bool immediate = value[L"immediate"].as_bool(); - wstring coalition = value[L"coalition"].as_string(); - wstring type = value[L"type"].as_string(); + string coalition = to_string(value[L"coalition"]); + string type = to_string(value[L"type"]); double lat = value[L"location"][L"lat"].as_double(); double lng = value[L"location"][L"lng"].as_double(); double altitude = value[L"altitude"].as_double(); Coords loc; loc.lat = lat; loc.lng = lng; loc.alt = altitude; - wstring payloadName = value[L"payloadName"].as_string(); - wstring airbaseName = value[L"airbaseName"].as_string(); - log(L"Spawning " + coalition + L" air unit of type " + type + L" with payload " + payloadName + L" at (" + to_wstring(lat) + L", " + to_wstring(lng) + L" " + airbaseName + L")"); + string payloadName = to_string(value[L"payloadName"]); + string airbaseName = to_string(value[L"airbaseName"]); + log("Spawning " + coalition + " air unit of type " + type + " with payload " + payloadName + " at (" + to_string(lat) + ", " + to_string(lng) + " " + airbaseName + ")"); command = dynamic_cast(new SpawnAircraft(coalition, type, loc, payloadName, airbaseName, immediate)); } - else if (key.compare(L"attackUnit") == 0) + else if (key.compare("attackUnit") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); - int targetID = value[L"targetID"].as_integer(); + unsigned int targetID = value[L"targetID"].as_integer(); Unit* unit = unitsManager->getGroupLeader(ID); Unit* target = unitsManager->getUnit(targetID); - wstring unitName; - wstring targetName; + string unitName; + string targetName; if (unit != nullptr) unitName = unit->getUnitName(); @@ -137,24 +137,24 @@ void Scheduler::handleRequest(wstring key, json::value value) else return; - log(L"Unit " + unitName + L" attacking unit " + targetName); + log("Unit " + unitName + " attacking unit " + targetName); unit->setTargetID(targetID); unit->setState(State::ATTACK); } - else if (key.compare(L"followUnit") == 0) + else if (key.compare("followUnit") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); - int leaderID = value[L"targetID"].as_integer(); - int offsetX = value[L"offsetX"].as_integer(); - int offsetY = value[L"offsetY"].as_integer(); - int offsetZ = value[L"offsetZ"].as_integer(); + unsigned int leaderID = value[L"targetID"].as_integer(); + unsigned int offsetX = value[L"offsetX"].as_integer(); + unsigned int offsetY = value[L"offsetY"].as_integer(); + unsigned int offsetZ = value[L"offsetZ"].as_integer(); Unit* unit = unitsManager->getGroupLeader(ID); Unit* leader = unitsManager->getUnit(leaderID); - wstring unitName; - wstring leaderName; + string unitName; + string leaderName; if (unit != nullptr) unitName = unit->getUnitName(); @@ -166,95 +166,95 @@ void Scheduler::handleRequest(wstring key, json::value value) else return; - log(L"Unit " + unitName + L" following unit " + leaderName); + log("Unit " + unitName + " following unit " + leaderName); unit->setFormationOffset(Offset(offsetX, offsetY, offsetZ)); unit->setLeaderID(leaderID); unit->setState(State::FOLLOW); } - else if (key.compare(L"changeSpeed") == 0) + else if (key.compare("changeSpeed") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) - unit->changeSpeed(value[L"change"].as_string()); + unit->changeSpeed(to_string(value[L"change"])); } - else if (key.compare(L"changeAltitude") == 0) + else if (key.compare("changeAltitude") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) - unit->changeAltitude(value[L"change"].as_string()); + unit->changeAltitude(to_string(value[L"change"])); } - else if (key.compare(L"setSpeed") == 0) + else if (key.compare("setSpeed") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) unit->setDesiredSpeed(value[L"speed"].as_double()); } - else if (key.compare(L"setSpeedType") == 0) + else if (key.compare("setSpeedType") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) - unit->setDesiredSpeedType(value[L"speedType"].as_string()); + unit->setDesiredSpeedType(to_string(value[L"speedType"])); } - else if (key.compare(L"setAltitude") == 0) + else if (key.compare("setAltitude") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) unit->setDesiredAltitude(value[L"altitude"].as_double()); } - else if (key.compare(L"setAltitudeType") == 0) + else if (key.compare("setAltitudeType") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) - unit->setDesiredAltitudeType(value[L"altitudeType"].as_string()); + unit->setDesiredAltitudeType(to_string(value[L"altitudeType"])); } - else if (key.compare(L"cloneUnit") == 0) + else if (key.compare("cloneUnit") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); double lat = value[L"location"][L"lat"].as_double(); double lng = value[L"location"][L"lng"].as_double(); Coords loc; loc.lat = lat; loc.lng = lng; command = dynamic_cast(new Clone(ID, loc)); - log(L"Cloning unit " + to_wstring(ID)); + log("Cloning unit " + to_string(ID)); } - else if (key.compare(L"setROE") == 0) + else if (key.compare("setROE") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); - wstring ROE = value[L"ROE"].as_string(); + unsigned char ROE = value[L"ROE"].as_number().is_uint32(); unit->setROE(ROE); } - else if (key.compare(L"setReactionToThreat") == 0) + else if (key.compare("setReactionToThreat") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); - wstring reactionToThreat = value[L"reactionToThreat"].as_string(); + unsigned char reactionToThreat = value[L"reactionToThreat"].as_number().is_uint32(); unit->setReactionToThreat(reactionToThreat); } - else if (key.compare(L"setEmissionsCountermeasures") == 0) + else if (key.compare("setEmissionsCountermeasures") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); - wstring emissionsCountermeasures = value[L"emissionsCountermeasures"].as_string(); + unsigned char emissionsCountermeasures = value[L"emissionsCountermeasures"].as_number().is_uint32(); unit->setEmissionsCountermeasures(emissionsCountermeasures); } - else if (key.compare(L"landAt") == 0) + else if (key.compare("landAt") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); double lat = value[L"location"][L"lat"].as_double(); @@ -262,22 +262,22 @@ void Scheduler::handleRequest(wstring key, json::value value) Coords loc; loc.lat = lat; loc.lng = lng; unit->landAt(loc); } - else if (key.compare(L"deleteUnit") == 0) + else if (key.compare("deleteUnit") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); bool explosion = value[L"explosion"].as_bool(); unitsManager->deleteUnit(ID, explosion); } - else if (key.compare(L"refuel") == 0) + else if (key.compare("refuel") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); unit->setState(State::REFUEL); } - else if (key.compare(L"setAdvancedOptions") == 0) + else if (key.compare("setAdvancedOptions") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) @@ -287,18 +287,21 @@ void Scheduler::handleRequest(wstring key, json::value value) unit->setIsAWACS(value[L"isAWACS"].as_bool()); /* TACAN Options */ - auto TACAN = value[L"TACAN"]; - unit->setTACAN({ TACAN[L"isOn"].as_bool(), - TACAN[L"channel"].as_number().to_int32(), - TACAN[L"XY"].as_string(), - TACAN[L"callsign"].as_string() - }); + Options::TACAN TACAN; + TACAN.isOn = value[L"TACAN"][L"isOn"].as_bool(); + TACAN.channel = static_cast(value[L"TACAN"][L"channel"].as_number().to_uint32()); + TACAN.XY = to_string(value[L"TACAN"][L"XY"]).at(0); + string callsign = to_string(value[L"TACAN"][L"callsign"]); + if (callsign.length() > 3) + callsign = callsign.substr(0, 3); + strcpy_s(TACAN.callsign, 4, callsign.c_str()); + unit->setTACAN(TACAN); /* Radio Options */ auto radio = value[L"radio"]; - unit->setRadio({ radio[L"frequency"].as_number().to_int32(), - radio[L"callsign"].as_number().to_int32(), - radio[L"callsignNumber"].as_number().to_int32() + unit->setRadio({ radio[L"frequency"].as_number().to_uint32(), + static_cast(radio[L"callsign"].as_number().to_uint32()), + static_cast(radio[L"callsignNumber"].as_number().to_uint32()) }); /* General Settings */ @@ -313,78 +316,78 @@ void Scheduler::handleRequest(wstring key, json::value value) unit->resetActiveDestination(); } } - else if (key.compare(L"setFollowRoads") == 0) + else if (key.compare("setFollowRoads") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); bool followRoads = value[L"followRoads"].as_bool(); Unit* unit = unitsManager->getGroupLeader(ID); unit->setFollowRoads(followRoads); } - else if (key.compare(L"setOnOff") == 0) + else if (key.compare("setOnOff") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); bool onOff = value[L"onOff"].as_bool(); Unit* unit = unitsManager->getGroupLeader(ID); unit->setOnOff(onOff); } - else if (key.compare(L"explosion") == 0) + else if (key.compare("explosion") == 0) { - int intensity = value[L"intensity"].as_integer(); + unsigned int intensity = value[L"intensity"].as_integer(); double lat = value[L"location"][L"lat"].as_double(); double lng = value[L"location"][L"lng"].as_double(); - log(L"Adding " + to_wstring(intensity) + L" explosion at (" + to_wstring(lat) + L", " + to_wstring(lng) + L")"); + log("Adding " + to_string(intensity) + " explosion at (" + to_string(lat) + ", " + to_string(lng) + ")"); Coords loc; loc.lat = lat; loc.lng = lng; command = dynamic_cast(new Explosion(intensity, loc)); } - else if (key.compare(L"bombPoint") == 0) + else if (key.compare("bombPoint") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); double lat = value[L"location"][L"lat"].as_double(); double lng = value[L"location"][L"lng"].as_double(); Coords loc; loc.lat = lat; loc.lng = lng; Unit* unit = unitsManager->getGroupLeader(ID); unit->setState(State::BOMB_POINT); - unit->setTargetLocation(loc); + unit->setTargetPosition(loc); } - else if (key.compare(L"carpetBomb") == 0) + else if (key.compare("carpetBomb") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); double lat = value[L"location"][L"lat"].as_double(); double lng = value[L"location"][L"lng"].as_double(); Coords loc; loc.lat = lat; loc.lng = lng; Unit* unit = unitsManager->getGroupLeader(ID); unit->setState(State::CARPET_BOMB); - unit->setTargetLocation(loc); + unit->setTargetPosition(loc); } - else if (key.compare(L"bombBuilding") == 0) + else if (key.compare("bombBuilding") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); double lat = value[L"location"][L"lat"].as_double(); double lng = value[L"location"][L"lng"].as_double(); Coords loc; loc.lat = lat; loc.lng = lng; Unit* unit = unitsManager->getGroupLeader(ID); unit->setState(State::BOMB_BUILDING); - unit->setTargetLocation(loc); + unit->setTargetPosition(loc); } - else if (key.compare(L"fireAtArea") == 0) + else if (key.compare("fireAtArea") == 0) { - int ID = value[L"ID"].as_integer(); + unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); double lat = value[L"location"][L"lat"].as_double(); double lng = value[L"location"][L"lng"].as_double(); Coords loc; loc.lat = lat; loc.lng = lng; Unit* unit = unitsManager->getGroupLeader(ID); unit->setState(State::FIRE_AT_AREA); - unit->setTargetLocation(loc); + unit->setTargetPosition(loc); } else { - log(L"Unknown command: " + key); + log("Unknown command: " + key); } if (command != nullptr) diff --git a/src/core/src/scriptloader.cpp b/src/core/src/scriptloader.cpp index 0509463e..12ed68c8 100644 --- a/src/core/src/scriptloader.cpp +++ b/src/core/src/scriptloader.cpp @@ -13,10 +13,12 @@ bool executeLuaScript(lua_State* L, string path) if (dostring_in(L, "server", str.c_str()) != 0) { log("Error registering " + path); + return false; } else { log(path + " registered successfully"); + return true; } } diff --git a/src/core/src/server.cpp b/src/core/src/server.cpp index 621f0e43..e8c71e5d 100644 --- a/src/core/src/server.cpp +++ b/src/core/src/server.cpp @@ -36,8 +36,7 @@ Server::Server(lua_State* L): serverThread(nullptr), runListener(true) { - refreshJson = json::value::object(); - updateJson = json::value::object(); + } void Server::start(lua_State* L) @@ -71,8 +70,8 @@ void Server::handle_get(http_request request) lock_guard guard(mutexLock); http_response response(status_codes::OK); - string authorization = to_base64("admin:" + to_string(password)); - if (password == L"" || (request.headers().has(L"Authorization") && request.headers().find(L"Authorization")->second == L"Basic " + to_wstring(authorization))) + string authorization = to_base64("admin:" + password); + if (password == "" || (request.headers().has(L"Authorization") && request.headers().find(L"Authorization")->second == L"Basic " + to_wstring(authorization))) { std::exception_ptr eptr; try { @@ -81,7 +80,8 @@ void Server::handle_get(http_request request) if (path.size() > 0) { - if (path[0] == UNITS_URI) + string URI = to_string(path[0]); + if (URI.compare(UNITS_URI) == 0) { map query = request.relative_uri().split_query(request.relative_uri().query()); long long time = 0; @@ -94,22 +94,21 @@ void Server::handle_get(http_request request) time = 0; } } - if (time == 0) - unitsManager->getUnitData(answer, 0); - else - answer[L"units"] = updateJson; + + // TODO would be nice to optimize this + answer[L"units"] = json::value(to_wstring(unitsManager->getUnitData(time == 0))); } - else if (path[0] == LOGS_URI) + else if (URI.compare(LOGS_URI) == 0) { auto logs = json::value::object(); getLogsJSON(logs, 100); // By reference, for thread safety. Get the last 100 log entries answer[L"logs"] = logs; } - else if (path[0] == AIRBASES_URI) + else if (URI.compare(AIRBASES_URI) == 0) answer[L"airbases"] = airbases; - else if (path[0] == BULLSEYE_URI) + else if (URI.compare(BULLSEYE_URI) == 0) answer[L"bullseyes"] = bullseyes; - else if (path[0] == MISSION_URI) + else if (URI.compare(MISSION_URI) == 0) answer[L"mission"] = mission; milliseconds ms = duration_cast(system_clock::now().time_since_epoch()); @@ -139,8 +138,8 @@ void Server::handle_get(http_request request) void Server::handle_request(http_request request, function action) { http_response response(status_codes::OK); - string authorization = to_base64("admin:" + to_string(password)); - if (password == L"" || (request.headers().has(L"Authorization") && request.headers().find(L"Authorization")->second == L"Basic " + to_wstring(authorization))) + string authorization = to_base64("admin:" + password); + if (password == "" || (request.headers().has(L"Authorization") && request.headers().find(L"Authorization")->second.compare(L"Basic " + to_wstring(authorization)))) { auto answer = json::value::object(); request.extract_json().then([&answer, &action](pplx::task task) @@ -148,11 +147,8 @@ void Server::handle_request(http_request request, functionhandleRequest(key, value); + scheduler->handleRequest(to_string(key), value); } catch (...) { eptr = std::current_exception(); // capture @@ -200,8 +196,8 @@ void Server::handle_put(http_request request) void Server::task() { - wstring address = wstring(REST_ADDRESS); - wstring modLocation; + string address = REST_ADDRESS; + string modLocation; char* buf = nullptr; size_t sz = 0; if (_dupenv_s(&buf, &sz, "DCSOLYMPUS_PATH") == 0 && buf != nullptr) @@ -210,31 +206,31 @@ void Server::task() std::stringstream ss; ss << ifstream.rdbuf(); std::error_code errorCode; - json::value config = json::value::parse(to_wstring(ss.str()), errorCode); + json::value config = json::value::parse(ss.str(), errorCode); if (config.is_object() && config.has_object_field(L"server") && config[L"server"].has_string_field(L"address") && config[L"server"].has_number_field(L"port")) { - address = L"http://" + config[L"server"][L"address"].as_string() + L":" + to_wstring(config[L"server"][L"port"].as_number().to_int32()); - log(L"Starting server on " + address); + address = "http://" + to_string(config[L"server"][L"address"]) + ":" + to_string(config[L"server"][L"port"].as_number().to_int32()); + log("Starting server on " + address); } else - log(L"Error reading configuration file. Starting server on " + address); + log("Error reading configuration file. Starting server on " + address); if (config.is_object() && config.has_object_field(L"authentication") && config[L"authentication"].has_string_field(L"password")) { - password = config[L"authentication"][L"password"].as_string(); + password = to_string(config[L"authentication"][L"password"]); } else - log(L"Error reading configuration file. No password set."); + log("Error reading configuration file. No password set."); free(buf); } else { - log(L"DCSOLYMPUS_PATH environment variable is missing, starting server on " + address); + log("DCSOLYMPUS_PATH environment variable is missing, starting server on " + address); } - http_listener listener(address + L"/" + wstring(REST_URI)); + http_listener listener(to_wstring(address + "/" + REST_URI)); std::function handle_options = std::bind(&Server::handle_options, this, std::placeholders::_1); std::function handle_get = std::bind(&Server::handle_get, this, std::placeholders::_1); diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp index cf7ba257..1fdda23a 100644 --- a/src/core/src/unit.cpp +++ b/src/core/src/unit.cpp @@ -6,6 +6,9 @@ #include "defines.h" #include "unitsmanager.h" +#include "base64.hpp" +using namespace base64; + #include using namespace std::chrono; @@ -32,7 +35,7 @@ bool operator==(const Options::GeneralSettings& lhs, const Options::GeneralSetti lhs.prohibitAirWpn == rhs.prohibitAirWpn && lhs.prohibitJettison == rhs.prohibitJettison; } -Unit::Unit(json::value json, int ID) : +Unit::Unit(json::value json, unsigned int ID) : ID(ID) { log("Creating unit with ID: " + to_string(ID)); @@ -55,18 +58,19 @@ void Unit::setDefaults(bool force) const bool isUnitAlive = getAlive(); const bool isUnitLeader = unitsManager->isUnitGroupLeader(this); const bool isUnitLeaderOfAGroupWithOtherUnits = unitsManager->isUnitInGroup(this) && unitsManager->isUnitGroupLeader(this); - const bool isUnitHuman = getFlags()[L"Human"].as_bool(); - if (isUnitControlledByOlympus && (isUnitAlive || isUnitLeaderOfAGroupWithOtherUnits) && isUnitLeader && !isUnitHuman) { + + if (isUnitControlledByOlympus && (isUnitAlive || isUnitLeaderOfAGroupWithOtherUnits) && isUnitLeader && !human) { /* Set the default IDLE state */ setState(State::IDLE); /* Set desired altitude to be equal to current altitude so the unit does not climb/descend after spawn */ - setDesiredAltitude(altitude); + setDesiredAltitude(position.alt); /* Set the default options (these are all defaults so will only affect the export data, no DCS command will be sent) */ - setROE(L"Designated", force); - setReactionToThreat(L"Evade", force); - setEmissionsCountermeasures(L"Defend", force); + setROE(ROE::OPEN_FIRE_WEAPON_FREE, force); + setReactionToThreat(ReactionToThreat::EVADE_FIRE, force); + setEmissionsCountermeasures(EmissionCountermeasure::DEFEND, force); + strcpy_s(TACAN.callsign, 4, "TKR"); setTACAN(TACAN, force); setRadio(radio, force); setEPLRS(EPLRS, force); @@ -76,28 +80,13 @@ void Unit::setDefaults(bool force) } } -void Unit::addMeasure(wstring key, json::value value) -{ - milliseconds ms = duration_cast(system_clock::now().time_since_epoch()); - if (measures.find(key) == measures.end()) - measures[key] = new Measure(value, ms.count()); - else - { - if (measures[key]->getValue() != value) - { - measures[key]->setValue(value); - measures[key]->setTime(ms.count()); - } - } -} - void Unit::runAILoop() { /* If the unit is alive, controlled and it is not a human, run the AI Loop that performs the requested commands and instructions (moving, attacking, etc) */ if (!getControlled()) return; if (!unitsManager->isUnitGroupLeader(this)) return; - if (getFlags()[L"Human"].as_bool()) return; + if (human) return; - // Keep running the AI loop even if the unit is dead if it is the leader of a group which has other members in it + /* Keep running the AI loop even if the unit is dead if it is the leader of a group which has other members in it */ const bool isUnitAlive = getAlive(); const bool isUnitLeaderOfAGroupWithOtherUnits = unitsManager->isUnitInGroup(this) && unitsManager->isUnitGroupLeader(this); if (!(isUnitAlive || isUnitLeaderOfAGroupWithOtherUnits)) return; @@ -114,146 +103,182 @@ void Unit::updateExportData(json::value json, double dt) if (oldPosition != NULL) { double dist = 0; - Geodesic::WGS84().Inverse(latitude, longitude, oldPosition.lat, oldPosition.lng, dist); + Geodesic::WGS84().Inverse(getPosition().lat, getPosition().lng, oldPosition.lat, oldPosition.lng, dist); if (dt > 0) setSpeed(getSpeed() * 0.95 + (dist / dt) * 0.05); } - oldPosition = Coords(latitude, longitude, altitude); + oldPosition = position; if (json.has_string_field(L"Name")) - setName(json[L"Name"].as_string()); + setName(to_string(json[L"Name"])); if (json.has_string_field(L"UnitName")) - setUnitName(json[L"UnitName"].as_string()); + setUnitName(to_string(json[L"UnitName"])); if (json.has_string_field(L"GroupName")) - setGroupName(json[L"GroupName"].as_string()); - if (json.has_object_field(L"Type")) - setType(json[L"Type"]); + setGroupName(to_string(json[L"GroupName"])); if (json.has_number_field(L"Country")) setCountry(json[L"Country"].as_number().to_int32()); if (json.has_number_field(L"CoalitionID")) setCoalitionID(json[L"CoalitionID"].as_number().to_int32()); if (json.has_object_field(L"LatLongAlt")) { - setLatitude(json[L"LatLongAlt"][L"Lat"].as_number().to_double()); - setLongitude(json[L"LatLongAlt"][L"Long"].as_number().to_double()); - setAltitude(json[L"LatLongAlt"][L"Alt"].as_number().to_double()); + Coords position = { + json[L"LatLongAlt"][L"Lat"].as_number().to_double(), + json[L"LatLongAlt"][L"Long"].as_number().to_double(), + json[L"LatLongAlt"][L"Alt"].as_number().to_double() + }; + setPosition(position); } if (json.has_number_field(L"Heading")) setHeading(json[L"Heading"].as_number().to_double()); if (json.has_object_field(L"Flags")) - setFlags(json[L"Flags"]); + setHuman(json[L"Flags"][L"Human"].as_bool()); /* All units which contain the name "Olympus" are automatically under AI control */ - if (getUnitName().find(L"Olympus") != wstring::npos) + if (getUnitName().find("Olympus") != string::npos) setControlled(true); } void Unit::updateMissionData(json::value json) { if (json.has_number_field(L"fuel")) - setFuel(int(json[L"fuel"].as_number().to_double() * 100)); - if (json.has_object_field(L"ammo")) - setAmmo(json[L"ammo"]); - if (json.has_object_field(L"contacts")) - setContacts(json[L"contacts"]); + setFuel(short(json[L"fuel"].as_number().to_double() * 100)); + + if (json.has_object_field(L"ammo")) { + vector ammo; + for (auto const& el : json[L"ammo"].as_object()) { + DataTypes::Ammo ammoItem; + auto ammoJson = el.second; + ammoItem.quantity = ammoJson[L"count"].as_number().to_uint32(); + ammoItem.name = to_string(ammoJson[L"desc"][L"displayName"]); + ammoItem.guidance = ammoJson[L"desc"][L"guidance"].as_number().to_uint32(); + ammoItem.category = ammoJson[L"desc"][L"category"].as_number().to_uint32(); + ammoItem.missileCategory = ammoJson[L"desc"][L"missileCategory"].as_number().to_uint32(); + ammo.push_back(ammoItem); + } + setAmmo(ammo); + } + + if (json.has_object_field(L"contacts")) { + vector contacts; + for (auto const& el : json[L"ammo"].as_object()) { + DataTypes::Contact contactItem; + auto contactJson = el.second; + contactItem.ID = contactJson[L"object"][L"id_"].as_number().to_uint32(); + + string detectionMethod = to_string(contactJson[L"detectionMethod"]); + if (detectionMethod.compare("VISUAL")) contactItem.detectionMethod = 1; + else if (detectionMethod.compare("OPTIC")) contactItem.detectionMethod = 2; + else if (detectionMethod.compare("RADAR")) contactItem.detectionMethod = 4; + else if (detectionMethod.compare("IRST")) contactItem.detectionMethod = 8; + else if (detectionMethod.compare("RWR")) contactItem.detectionMethod = 16; + else if (detectionMethod.compare("DLINK")) contactItem.detectionMethod = 32; + contacts.push_back(contactItem); + } + setContacts(contacts); + } + if (json.has_boolean_field(L"hasTask")) setHasTask(json[L"hasTask"].as_bool()); } -json::value Unit::getData(long long time, bool getAll) +DataTypes::DataPacket Unit::getDataPacket() { - auto json = json::value::object(); + unsigned int bitmask = 0; + bitmask |= alive << 0; + bitmask |= human << 1; + bitmask |= controlled << 2; + bitmask |= hasTask << 3; + bitmask |= desiredAltitudeType << 16; + bitmask |= desiredSpeedType << 17; + bitmask |= isTanker << 18; + bitmask |= isAWACS << 19; + bitmask |= onOff << 19; + bitmask |= followRoads << 19; + bitmask |= EPLRS << 20; + bitmask |= generalSettings.prohibitAA << 21; + bitmask |= generalSettings.prohibitAfterburner << 22; + bitmask |= generalSettings.prohibitAG << 23; + bitmask |= generalSettings.prohibitAirWpn << 24; + bitmask |= generalSettings.prohibitJettison << 25; - /* If the unit is in a group, task & option data is given by the group leader */ - if (unitsManager->isUnitInGroup(this) && !unitsManager->isUnitGroupLeader(this)) - json = unitsManager->getGroupLeader(this)->getData(time, true); + DataTypes::DataPacket datapacket{ + ID, + bitmask, + position, + speed, + heading, + fuel, + desiredSpeed, + desiredAltitude, + targetID, + targetPosition, + state, + ROE, + reactionToThreat, + emissionsCountermeasures, + TACAN, + radio + }; + + return datapacket; +} + +string Unit::getData(bool refresh) +{ + /* Prepare the data in a stringstream */ + stringstream ss; + + /* Reserve data for: + 1) DataPacket; + 2) Length of active path; + 3) Active path; + */ + char* data = (char*)malloc(sizeof(DataTypes::DataPacket) + sizeof(unsigned short) + activePath.size() * sizeof(Coords)); + unsigned int offset = 0; + + /* Prepare the data packet and copy it to memory */ + DataTypes::DataPacket dataPacket; + /* If the unit is in a group, get the datapacket from the group leader and only replace the position, speed and heading */ + if (unitsManager->isUnitInGroup(this) && !unitsManager->isUnitGroupLeader(this)) { + dataPacket = unitsManager->getGroupLeader(this)->getDataPacket(); + dataPacket.position = position; + dataPacket.speed = speed; + dataPacket.heading = heading; + } + else + dataPacket = getDataPacket(); - /********** Base data **********/ - json[L"baseData"] = json::value::object(); - for (auto key : { L"controlled", L"name", L"unitName", L"groupName", L"alive", L"category"}) - { - if (measures.find(key) != measures.end() && measures[key]->getTime() > time) - json[L"baseData"][key] = measures[key]->getValue(); - } - if (json[L"baseData"].size() == 0) - json.erase(L"baseData"); + memcpy(data + offset, &dataPacket, sizeof(dataPacket)); + offset += sizeof(dataPacket); - if (alive || getAll) { - /********** Flight data **********/ - json[L"flightData"] = json::value::object(); - for (auto key : { L"latitude", L"longitude", L"altitude", L"speed", L"heading" }) - { - if (measures.find(key) != measures.end() && measures[key]->getTime() > time) - json[L"flightData"][key] = measures[key]->getValue(); - } - if (json[L"flightData"].size() == 0) - json.erase(L"flightData"); + /* Prepare the path memory and copy it to memory */ + std::vector path; + for (const Coords& c : activePath) + path.push_back(c); + unsigned short pathLength = activePath.size(); - /********** Mission data **********/ - json[L"missionData"] = json::value::object(); - for (auto key : { L"fuel", L"ammo", L"contacts", L"hasTask", L"coalition", L"flags" }) - { - if (measures.find(key) != measures.end() && measures[key]->getTime() > time) - json[L"missionData"][key] = measures[key]->getValue(); - } - if (json[L"missionData"].size() == 0) - json.erase(L"missionData"); + memcpy(data + offset, &pathLength, sizeof(unsigned short)); + offset += sizeof(unsigned short); + memcpy(data + offset, &path, activePath.size() * sizeof(Coords)); + offset += sizeof(unsigned short); - /********** Formation data **********/ - json[L"formationData"] = json::value::object(); - for (auto key : { L"leaderID" }) - { - if (measures.find(key) != measures.end() && measures[key]->getTime() > time) - json[L"formationData"][key] = measures[key]->getValue(); - } - if (json[L"formationData"].size() == 0) - json.erase(L"formationData"); + ss << to_base64(data, offset); - /* If the unit is in a group, task & option data is given by the group leader */ - if (unitsManager->isUnitGroupLeader(this)) { - /********** Task data **********/ - json[L"taskData"] = json::value::object(); - for (auto key : { L"currentState", L"currentTask", L"desiredSpeed", L"desiredAltitude", L"desiredSpeedType", L"desiredAltitudeType", L"activePath", L"isTanker", L"isAWACS", L"onOff", L"followRoads", L"targetID", L"targetLocation" }) - { - if (measures.find(key) != measures.end() && measures[key]->getTime() > time) - json[L"taskData"][key] = measures[key]->getValue(); - } - if (json[L"taskData"].size() == 0) - json.erase(L"taskData"); - - /********** Options data **********/ - json[L"optionsData"] = json::value::object(); - for (auto key : { L"ROE", L"reactionToThreat", L"emissionsCountermeasures", L"TACAN", L"radio", L"generalSettings" }) - { - if (measures.find(key) != measures.end() && measures[key]->getTime() > time) - json[L"optionsData"][key] = measures[key]->getValue(); - } - if (json[L"optionsData"].size() == 0) - json.erase(L"optionsData"); - } + if (refresh) { + ss << name; + ss << unitName; + ss << groupName; + ss << getCategory(); + ss << coalition; } - return json; + return ss.str(); } void Unit::setActivePath(list newPath) { activePath = newPath; resetActiveDestination(); - - auto path = json::value::object(); - if (activePath.size() > 0) { - int count = 1; - for (auto& destination : activePath) - { - auto json = json::value::object(); - json[L"lat"] = destination.lat; - json[L"lng"] = destination.lng; - json[L"alt"] = destination.alt; - path[to_wstring(count++)] = json; - } - } - addMeasure(L"activePath", path); } void Unit::clearActivePath() @@ -283,28 +308,27 @@ void Unit::popActivePathFront() setActivePath(path); } -void Unit::setCoalitionID(int newCoalitionID) +void Unit::setCoalitionID(unsigned int newCoalitionID) { if (newCoalitionID == 0) - coalition = L"neutral"; + coalition = "neutral"; else if (newCoalitionID == 1) - coalition = L"red"; + coalition = "red"; else - coalition = L"blue"; - addMeasure(L"coalition", json::value(coalition)); + coalition = "blue"; } -int Unit::getCoalitionID() +unsigned int Unit::getCoalitionID() { - if (coalition == L"neutral") + if (coalition == "neutral") return 0; - else if (coalition == L"red") + else if (coalition == "red") return 1; else return 2; } -wstring Unit::getTargetName() +string Unit::getTargetName() { if (isTargetAlive()) { @@ -312,7 +336,7 @@ wstring Unit::getTargetName() if (target != nullptr) return target->getUnitName(); } - return L""; + return ""; } bool Unit::isTargetAlive() @@ -327,7 +351,7 @@ bool Unit::isTargetAlive() return false; } -wstring Unit::getLeaderName() +string Unit::getLeaderName() { if (isLeaderAlive()) { @@ -335,7 +359,7 @@ wstring Unit::getLeaderName() if (leader != nullptr) return leader->getUnitName(); } - return L""; + return ""; } bool Unit::isLeaderAlive() @@ -369,84 +393,52 @@ void Unit::setFormationOffset(Offset newFormationOffset) resetTask(); } -void Unit::setROE(wstring newROE, bool force) { - addMeasure(L"ROE", json::value(newROE)); - +void Unit::setROE(unsigned char newROE, bool force) +{ if (ROE != newROE || force) { ROE = newROE; - - int ROEEnum; - if (ROE.compare(L"Free") == 0) - ROEEnum = ROE::WEAPON_FREE; - else if (ROE.compare(L"Designated free") == 0) - ROEEnum = ROE::OPEN_FIRE_WEAPON_FREE; - else if (ROE.compare(L"Designated") == 0) - ROEEnum = ROE::OPEN_FIRE; - else if (ROE.compare(L"Return") == 0) - ROEEnum = ROE::RETURN_FIRE; - else if (ROE.compare(L"Hold") == 0) - ROEEnum = ROE::WEAPON_HOLD; - else - return; - - Command* command = dynamic_cast(new SetOption(groupName, SetCommandType::ROE, ROEEnum)); + Command* command = dynamic_cast(new SetOption(groupName, SetCommandType::ROE, ROE)); scheduler->appendCommand(command); } } -void Unit::setReactionToThreat(wstring newReactionToThreat, bool force) { - addMeasure(L"reactionToThreat", json::value(newReactionToThreat)); - +void Unit::setReactionToThreat(unsigned char newReactionToThreat, bool force) +{ if (reactionToThreat != newReactionToThreat || force) { reactionToThreat = newReactionToThreat; - int reactionToThreatEnum; - if (reactionToThreat.compare(L"None") == 0) - reactionToThreatEnum = ReactionToThreat::NO_REACTION; - else if (reactionToThreat.compare(L"Passive") == 0) - reactionToThreatEnum = ReactionToThreat::PASSIVE_DEFENCE; - else if (reactionToThreat.compare(L"Evade") == 0) - reactionToThreatEnum = ReactionToThreat::EVADE_FIRE; - else if (reactionToThreat.compare(L"Escape") == 0) - reactionToThreatEnum = ReactionToThreat::BYPASS_AND_ESCAPE; - else if (reactionToThreat.compare(L"Abort") == 0) - reactionToThreatEnum = ReactionToThreat::ALLOW_ABORT_MISSION; - else - return; - - Command* command = dynamic_cast(new SetOption(groupName, SetCommandType::REACTION_ON_THREAT, reactionToThreatEnum)); + Command* command = dynamic_cast(new SetOption(groupName, SetCommandType::REACTION_ON_THREAT, reactionToThreat)); scheduler->appendCommand(command); } } -void Unit::setEmissionsCountermeasures(wstring newEmissionsCountermeasures, bool force) { - addMeasure(L"emissionsCountermeasures", json::value(newEmissionsCountermeasures)); - +void Unit::setEmissionsCountermeasures(unsigned char newEmissionsCountermeasures, bool force) +{ if (emissionsCountermeasures != newEmissionsCountermeasures || force) { emissionsCountermeasures = newEmissionsCountermeasures; - int radarEnum; - int flareEnum; - int ECMEnum; - if (emissionsCountermeasures.compare(L"Silent") == 0) + unsigned int radarEnum; + unsigned int flareEnum; + unsigned int ECMEnum; + if (emissionsCountermeasures == EmissionCountermeasure::SILENT) { radarEnum = RadarUse::NEVER; flareEnum = FlareUse::NEVER; ECMEnum = ECMUse::NEVER_USE; } - else if (emissionsCountermeasures.compare(L"Attack") == 0) + else if (emissionsCountermeasures == EmissionCountermeasure::ATTACK) { radarEnum = RadarUse::FOR_ATTACK_ONLY; flareEnum = FlareUse::AGAINST_FIRED_MISSILE; ECMEnum = ECMUse::USE_IF_ONLY_LOCK_BY_RADAR; } - else if (emissionsCountermeasures.compare(L"Defend") == 0) + else if (emissionsCountermeasures == EmissionCountermeasure::DEFEND) { radarEnum = RadarUse::FOR_SEARCH_IF_REQUIRED; flareEnum = FlareUse::WHEN_FLYING_IN_SAM_WEZ; ECMEnum = ECMUse::USE_IF_DETECTED_LOCK_BY_RADAR; } - else if (emissionsCountermeasures.compare(L"Free") == 0) + else if (emissionsCountermeasures == EmissionCountermeasure::FREE) { radarEnum = RadarUse::FOR_CONTINUOUS_SEARCH; flareEnum = FlareUse::WHEN_FLYING_NEAR_ENEMIES; @@ -468,42 +460,37 @@ void Unit::setEmissionsCountermeasures(wstring newEmissionsCountermeasures, bool } } -void Unit::landAt(Coords loc) { +void Unit::landAt(Coords loc) +{ clearActivePath(); pushActivePathBack(loc); setState(State::LAND); } -void Unit::setIsTanker(bool newIsTanker) { +void Unit::setIsTanker(bool newIsTanker) +{ isTanker = newIsTanker; resetTask(); - addMeasure(L"isTanker", json::value(newIsTanker)); } -void Unit::setIsAWACS(bool newIsAWACS) { +void Unit::setIsAWACS(bool newIsAWACS) +{ isAWACS = newIsAWACS; resetTask(); - addMeasure(L"isAWACS", json::value(newIsAWACS)); setEPLRS(isAWACS); } -void Unit::setTACAN(Options::TACAN newTACAN, bool force) { - auto json = json::value(); - json[L"isOn"] = json::value(newTACAN.isOn); - json[L"channel"] = json::value(newTACAN.channel); - json[L"XY"] = json::value(newTACAN.XY); - json[L"callsign"] = json::value(newTACAN.callsign); - addMeasure(L"TACAN", json); - +void Unit::setTACAN(Options::TACAN newTACAN, bool force) +{ if (TACAN != newTACAN || force) { TACAN = newTACAN; if (TACAN.isOn) { - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS << "{" << "id = 'ActivateBeacon'," << "params = {" - << "type = " << ((TACAN.XY.compare(L"X") == 0) ? 4 : 5) << "," + << "type = " << ((TACAN.XY == 'X' == 0) ? 4 : 5) << "," << "system = 3," << "name = \"Olympus_TACAN\"," << "callsign = \"" << TACAN.callsign << "\", " @@ -514,7 +501,7 @@ void Unit::setTACAN(Options::TACAN newTACAN, bool force) { scheduler->appendCommand(command); } else { - std::wostringstream commandSS; + std::ostringstream commandSS; commandSS << "{" << "id = 'DeactivateBeacon'," << "params = {" @@ -526,19 +513,13 @@ void Unit::setTACAN(Options::TACAN newTACAN, bool force) { } } -void Unit::setRadio(Options::Radio newRadio, bool force) { - - auto json = json::value(); - json[L"frequency"] = json::value(newRadio.frequency); - json[L"callsign"] = json::value(newRadio.callsign); - json[L"callsignNumber"] = json::value(newRadio.callsignNumber); - addMeasure(L"radio", json); - +void Unit::setRadio(Options::Radio newRadio, bool force) +{ if (radio != newRadio || force) { radio = newRadio; - std::wostringstream commandSS; + std::ostringstream commandSS; Command* command; commandSS << "{" @@ -552,7 +533,7 @@ void Unit::setRadio(Options::Radio newRadio, bool force) { scheduler->appendCommand(command); // Clear the stringstream - commandSS.str(wstring()); + commandSS.str(string("")); commandSS << "{" << "id = 'SetCallsign'," @@ -568,12 +549,12 @@ void Unit::setRadio(Options::Radio newRadio, bool force) { void Unit::setEPLRS(bool newEPLRS, bool force) { - //addMeasure(L"EPLRS", json::value(newEPLRS)); + //addMeasure("EPLRS", json::value(newEPLRS)); // //if (EPLRS != newEPLRS || force) { // EPLRS = newEPLRS; // - // std::wostringstream commandSS; + // std::ostringstream commandSS; // commandSS << "{" // << "id = 'EPLRS'," // << "params = {" @@ -585,16 +566,8 @@ void Unit::setEPLRS(bool newEPLRS, bool force) //} } -void Unit::setGeneralSettings(Options::GeneralSettings newGeneralSettings, bool force) { - - auto json = json::value(); - json[L"prohibitJettison"] = json::value(newGeneralSettings.prohibitJettison); - json[L"prohibitAA"] = json::value(newGeneralSettings.prohibitAA); - json[L"prohibitAG"] = json::value(newGeneralSettings.prohibitAG); - json[L"prohibitAfterburner"] = json::value(newGeneralSettings.prohibitAfterburner); - json[L"prohibitAirWpn"] = json::value(newGeneralSettings.prohibitAirWpn); - addMeasure(L"generalSettings", json); - +void Unit::setGeneralSettings(Options::GeneralSettings newGeneralSettings, bool force) +{ if (generalSettings != newGeneralSettings) { generalSettings = newGeneralSettings; @@ -613,47 +586,47 @@ void Unit::setGeneralSettings(Options::GeneralSettings newGeneralSettings, bool } } -void Unit::setDesiredSpeed(double newDesiredSpeed) { +void Unit::setDesiredSpeed(double newDesiredSpeed) +{ desiredSpeed = newDesiredSpeed; - addMeasure(L"desiredSpeed", json::value(newDesiredSpeed)); if (state == State::IDLE) resetTask(); else goToDestination(); /* Send the command to reach the destination */ } -void Unit::setDesiredAltitude(double newDesiredAltitude) { +void Unit::setDesiredAltitude(double newDesiredAltitude) +{ desiredAltitude = newDesiredAltitude; - addMeasure(L"desiredAltitude", json::value(newDesiredAltitude)); if (state == State::IDLE) resetTask(); else goToDestination(); /* Send the command to reach the destination */ } -void Unit::setDesiredSpeedType(wstring newDesiredSpeedType) { - desiredSpeedType = newDesiredSpeedType; - addMeasure(L"desiredSpeedType", json::value(newDesiredSpeedType)); +void Unit::setDesiredSpeedType(string newDesiredSpeedType) +{ + desiredSpeedType = newDesiredSpeedType.compare("GS") == 0; if (state == State::IDLE) resetTask(); else goToDestination(); /* Send the command to reach the destination */ } -void Unit::setDesiredAltitudeType(wstring newDesiredAltitudeType) { - desiredAltitudeType = newDesiredAltitudeType; - addMeasure(L"desiredAltitudeType", json::value(newDesiredAltitudeType)); +void Unit::setDesiredAltitudeType(string newDesiredAltitudeType) +{ + desiredAltitudeType = newDesiredAltitudeType.compare("AGL") == 0; if (state == State::IDLE) resetTask(); else goToDestination(); /* Send the command to reach the destination */ } -void Unit::goToDestination(wstring enrouteTask) +void Unit::goToDestination(string enrouteTask) { if (activeDestination != NULL) { - Command* command = dynamic_cast(new Move(groupName, activeDestination, getDesiredSpeed(), getDesiredSpeedType(), getDesiredAltitude(), getDesiredAltitudeType(), enrouteTask, getCategory())); + Command* command = dynamic_cast(new Move(groupName, activeDestination, getDesiredSpeed(), getDesiredSpeedType()? "GS": "CAS", getDesiredAltitude(), getDesiredAltitudeType()? "AGL" : "ASL", enrouteTask, getCategory())); scheduler->appendCommand(command); setHasTask(true); } @@ -667,16 +640,17 @@ bool Unit::isDestinationReached(double threshold) for (auto const& p: unitsManager->getGroupMembers(groupName)) { double dist = 0; - Geodesic::WGS84().Inverse(p->getLatitude(), p->getLongitude(), activeDestination.lat, activeDestination.lng, dist); + Geodesic::WGS84().Inverse(p->getPosition().lat, p->getPosition().lng, activeDestination.lat, activeDestination.lng, dist); if (dist < threshold) { - log(unitName + L" destination reached"); + log(unitName + " destination reached"); return true; } else { return false; } - } + } + return false; } else return true; @@ -687,13 +661,13 @@ bool Unit::setActiveDestination() if (activePath.size() > 0) { activeDestination = activePath.front(); - log(unitName + L" active destination set to queue front"); + log(unitName + " active destination set to queue front"); return true; } else { activeDestination = Coords(0); - log(unitName + L" active destination set to NULL"); + log(unitName + " active destination set to NULL"); return false; } } @@ -706,7 +680,7 @@ bool Unit::updateActivePath(bool looping) if (looping) pushActivePathBack(activePath.front()); popActivePathFront(); - log(unitName + L" active path front popped"); + log(unitName + " active path front popped"); return true; } else { @@ -714,15 +688,13 @@ bool Unit::updateActivePath(bool looping) } } -void Unit::setTargetLocation(Coords newTargetLocation) { - targetLocation = newTargetLocation; - auto json = json::value(); - json[L"latitude"] = json::value(newTargetLocation.lat); - json[L"longitude"] = json::value(newTargetLocation.lng); - addMeasure(L"targetLocation", json::value(json)); +void Unit::setTargetPosition(Coords newTargetPosition) +{ + targetPosition = newTargetPosition; } -bool Unit::checkTaskFailed() { +bool Unit::checkTaskFailed() +{ if (getHasTask()) return false; else { @@ -736,7 +708,7 @@ void Unit::resetTaskFailedCounter() { taskCheckCounter = TASK_CHECK_INIT_VALUE; } -void Unit::setHasTask(bool newHasTask) { +void Unit::setHasTask(bool newHasTask) +{ hasTask = newHasTask; - addMeasure(L"hasTask", json::value(newHasTask)); } \ No newline at end of file diff --git a/src/core/src/unitsmanager.cpp b/src/core/src/unitsmanager.cpp index 4d9ed681..1c6b33aa 100644 --- a/src/core/src/unitsmanager.cpp +++ b/src/core/src/unitsmanager.cpp @@ -22,7 +22,7 @@ UnitsManager::~UnitsManager() } -Unit* UnitsManager::getUnit(int ID) +Unit* UnitsManager::getUnit(unsigned int ID) { if (units.find(ID) == units.end()) { return nullptr; @@ -35,7 +35,7 @@ Unit* UnitsManager::getUnit(int ID) bool UnitsManager::isUnitInGroup(Unit* unit) { if (unit != nullptr) { - wstring groupName = unit->getGroupName(); + string groupName = unit->getGroupName(); for (auto const& p : units) { if (p.second->getGroupName().compare(groupName) == 0 && p.second != unit) @@ -57,7 +57,7 @@ bool UnitsManager::isUnitGroupLeader(Unit* unit) Unit* UnitsManager::getGroupLeader(Unit* unit) { if (unit != nullptr) { - wstring groupName = unit->getGroupName(); + string groupName = unit->getGroupName(); /* Find the first unit that has the same groupName */ for (auto const& p : units) @@ -69,7 +69,7 @@ Unit* UnitsManager::getGroupLeader(Unit* unit) return nullptr; } -vector UnitsManager::getGroupMembers(wstring groupName) +vector UnitsManager::getGroupMembers(string groupName) { vector members; for (auto const& p : units) @@ -80,7 +80,7 @@ vector UnitsManager::getGroupMembers(wstring groupName) return members; } -Unit* UnitsManager::getGroupLeader(int ID) +Unit* UnitsManager::getGroupLeader(unsigned int ID) { Unit* unit = getUnit(ID); return getGroupLeader(unit); @@ -88,12 +88,12 @@ Unit* UnitsManager::getGroupLeader(int ID) void UnitsManager::updateExportData(lua_State* L, double dt) { - map unitJSONs = getAllUnits(L); + map unitJSONs = getAllUnits(L); /* Update all units, create them if needed TODO: move code to get constructor in dedicated function */ for (auto const& p : unitJSONs) { - int ID = p.first; + unsigned int ID = p.first; if (units.count(ID) == 0) { json::value type = static_cast(p.second)[L"Type"]; @@ -139,7 +139,7 @@ void UnitsManager::updateMissionData(json::value missionData) /* Update all units */ for (auto const& p : units) { - int ID = p.first; + unsigned int ID = p.first; if (missionData.has_field(to_wstring(ID))) p.second->updateMissionData(missionData[to_wstring(ID)]); } @@ -151,29 +151,15 @@ void UnitsManager::runAILoop() { unit.second->runAILoop(); } -void UnitsManager::getUnitData(json::value& answer, long long time) +string UnitsManager::getUnitData(bool refresh) { - auto unitsJson = json::value::object(); + stringstream ss; for (auto const& p : units) - { - auto unitJson = p.second->getData(time); - if (unitJson.size() > 0) - unitsJson[to_wstring(p.first)] = unitJson; - } - answer[L"units"] = unitsJson; + ss << p.second->getData(refresh); + return ss.str(); } -void UnitsManager::appendUnitData(int ID, json::value& answer, long long time) -{ - Unit* unit = getUnit(ID); - if (unit != nullptr) { - auto unitJson = unit->getData(time); - if (unitJson.size() > 0) - answer[to_wstring(ID)] = unitJson; - } -} - -void UnitsManager::deleteUnit(int ID, bool explosion) +void UnitsManager::deleteUnit(unsigned int ID, bool explosion) { if (getUnit(ID) != nullptr) { @@ -182,7 +168,7 @@ void UnitsManager::deleteUnit(int ID, bool explosion) } } -void UnitsManager::acquireControl(int ID) { +void UnitsManager::acquireControl(unsigned int ID) { Unit* unit = getUnit(ID); if (unit != nullptr) { for (auto const& groupMember : getGroupMembers(unit->getGroupName())) { diff --git a/src/core/src/weapon.cpp b/src/core/src/weapon.cpp index 7705b710..3308c1e8 100644 --- a/src/core/src/weapon.cpp +++ b/src/core/src/weapon.cpp @@ -13,21 +13,19 @@ extern Scheduler* scheduler; extern UnitsManager* unitsManager; /* Weapon */ -Weapon::Weapon(json::value json, int ID) : Unit(json, ID) +Weapon::Weapon(json::value json, unsigned int ID) : Unit(json, ID) { }; /* Missile */ -Missile::Missile(json::value json, int ID) : Weapon(json, ID) +Missile::Missile(json::value json, unsigned int ID) : Weapon(json, ID) { log("New Missile created with ID: " + to_string(ID)); - addMeasure(L"category", json::value(getCategory())); }; /* Bomb */ -Bomb::Bomb(json::value json, int ID) : Weapon(json, ID) +Bomb::Bomb(json::value json, unsigned int ID) : Weapon(json, ID) { log("New Bomb created with ID: " + to_string(ID)); - addMeasure(L"category", json::value(getCategory())); }; \ No newline at end of file diff --git a/src/dcstools/include/dcstools.h b/src/dcstools/include/dcstools.h index db8ff519..5547f686 100644 --- a/src/dcstools/include/dcstools.h +++ b/src/dcstools/include/dcstools.h @@ -5,8 +5,8 @@ void DllExport LogInfo(lua_State* L, string message); void DllExport LogWarning(lua_State* L, string message); void DllExport LogError(lua_State* L, string message); -void DllExport Log(lua_State* L, string message, int level); +void DllExport Log(lua_State* L, string message, unsigned int level); int DllExport dostring_in(lua_State* L, string target, string command); -map DllExport getAllUnits(lua_State* L); -int DllExport TACANChannelToFrequency(int channel, wstring XY); +map DllExport getAllUnits(lua_State* L); +unsigned int DllExport TACANChannelToFrequency(unsigned int channel, char XY); diff --git a/src/dcstools/src/dcstools.cpp b/src/dcstools/src/dcstools.cpp index 64184a98..515d9e7e 100644 --- a/src/dcstools/src/dcstools.cpp +++ b/src/dcstools/src/dcstools.cpp @@ -42,7 +42,7 @@ void LogError(lua_State* L, string message) Log(L, message, errorLevel); } -void Log(lua_State* L, string message, int level) +void Log(lua_State* L, string message, unsigned int level) { STACK_INIT; @@ -56,10 +56,10 @@ void Log(lua_State* L, string message, int level) STACK_CLEAN; } -map getAllUnits(lua_State* L) +map getAllUnits(lua_State* L) { - int res = 0; - map units; + unsigned int res = 0; + map units; STACK_INIT; @@ -83,7 +83,8 @@ map getAllUnits(lua_State* L) lua_pushnil(L); while (lua_next(L, 2) != 0) { - int ID = lua_tonumber(L, -2); + unsigned int ID = lua_tonumber(L, -2); + // TODO more efficient method can be used, converting all the lua data to a json object may be overkill units[ID] = luaTableToJSON(L, -1); STACK_POP(1) } @@ -103,8 +104,8 @@ int dostring_in(lua_State* L, string target, string command) return lua_pcall(L, 2, 0, 0); } -int TACANChannelToFrequency(int channel, wstring XY) +unsigned int TACANChannelToFrequency(unsigned int channel, char XY) { - int basef = (XY == L"X" && channel > 63) || (XY == L"Y" && channel < 64) ? 1087: 961; + unsigned int basef = (XY == 'X' && channel > 63) || (XY == 'Y' && channel < 64) ? 1087 : 961; return (basef + channel) * 1000000; } \ No newline at end of file diff --git a/src/logger/include/interface.h b/src/logger/include/interface.h index f5dbe800..bc65f27d 100644 --- a/src/logger/include/interface.h +++ b/src/logger/include/interface.h @@ -3,4 +3,4 @@ void DllExport log(const std::string& sMessage); void DllExport log(const std::wstring& sMessage); -void DllExport getLogsJSON(json::value& json, int logsNumber = NULL); +void DllExport getLogsJSON(json::value& json, unsigned int logsNumber = NULL); diff --git a/src/logger/include/logger.h b/src/logger/include/logger.h index 20e17d71..6de74ec8 100644 --- a/src/logger/include/logger.h +++ b/src/logger/include/logger.h @@ -7,7 +7,7 @@ class Logger public: void log(const string& sMessage); void log(const wstring& sMessage); - void toJSON(json::value& json, int logsNumber = NULL); + void toJSON(json::value& json, unsigned int logsNumber = NULL); static Logger* GetLogger(); diff --git a/src/logger/src/interface.cpp b/src/logger/src/interface.cpp index 77c743b5..dd1200e0 100644 --- a/src/logger/src/interface.cpp +++ b/src/logger/src/interface.cpp @@ -14,7 +14,7 @@ void log(const wstring& message) LOGGER->log(message); } -void getLogsJSON(json::value& json, int logsNumber) +void getLogsJSON(json::value& json, unsigned int logsNumber) { LOGGER->toJSON(json, logsNumber); } \ No newline at end of file diff --git a/src/logger/src/logger.cpp b/src/logger/src/logger.cpp index 4c9810d3..cb3174ed 100644 --- a/src/logger/src/logger.cpp +++ b/src/logger/src/logger.cpp @@ -32,10 +32,10 @@ void Logger::Close() m_Logfile.close(); } -void Logger::toJSON(json::value& json, int logsNumber) +void Logger::toJSON(json::value& json, unsigned int logsNumber) { lock_guard guard(mutexLock); - int i = 0; + unsigned int i = 0; for (auto itr = m_logs.end(); itr != m_logs.begin(); --itr) { json[to_wstring(m_logs.size() - 1 - i)] = json::value::string(to_wstring(*itr)); diff --git a/src/shared/include/defines.h b/src/shared/include/defines.h index 9289e51d..de0a36ba 100644 --- a/src/shared/include/defines.h +++ b/src/shared/include/defines.h @@ -2,12 +2,12 @@ #define VERSION "v0.2.1" #define LOG_NAME "Olympus_log.txt" -#define REST_ADDRESS L"http://localhost:30000" -#define REST_URI L"olympus" -#define UNITS_URI L"units" -#define LOGS_URI L"logs" -#define AIRBASES_URI L"airbases" -#define BULLSEYE_URI L"bullseyes" -#define MISSION_URI L"mission" +#define REST_ADDRESS "http://localhost:30000" +#define REST_URI "olympus" +#define UNITS_URI "units" +#define LOGS_URI "logs" +#define AIRBASES_URI "airbases" +#define BULLSEYE_URI "bullseyes" +#define MISSION_URI "mission" #define UPDATE_TIME_INTERVAL 0.25 \ No newline at end of file diff --git a/src/shared/include/framework.h b/src/shared/include/framework.h index c1b6de88..60157cb4 100644 --- a/src/shared/include/framework.h +++ b/src/shared/include/framework.h @@ -20,6 +20,7 @@ #include #include #include +#include #include using namespace std; diff --git a/src/utils/include/utils.h b/src/utils/include/utils.h index 4534a8de..3ab1a636 100644 --- a/src/utils/include/utils.h +++ b/src/utils/include/utils.h @@ -16,18 +16,19 @@ struct Offset { // Get current date/time, format is YYYY-MM-DD.HH:mm:ss const DllExport std::string CurrentDateTime(); std::wstring DllExport to_wstring(const std::string& str); +std::string DllExport to_string(json::value& value); std::string DllExport to_string(const std::wstring& wstr); std::string DllExport random_string(size_t length); bool DllExport operator== (const Coords& a, const Coords& b); bool DllExport operator!= (const Coords& a, const Coords& b); -bool DllExport operator== (const Coords& a, const int& b); -bool DllExport operator!= (const Coords& a, const int& b); +bool DllExport operator== (const Coords& a, const double& b); +bool DllExport operator!= (const Coords& a, const double& b); bool DllExport operator== (const Offset& a, const Offset& b); bool DllExport operator!= (const Offset& a, const Offset& b); -bool DllExport operator== (const Offset& a, const int& b); -bool DllExport operator!= (const Offset& a, const int& b); +bool DllExport operator== (const Offset& a, const double& b); +bool DllExport operator!= (const Offset& a, const double& b); double DllExport knotsToMs(const double knots); double DllExport msToKnots(const double ms); diff --git a/src/utils/src/utils.cpp b/src/utils/src/utils.cpp index 9d91fbe7..62299d2d 100644 --- a/src/utils/src/utils.cpp +++ b/src/utils/src/utils.cpp @@ -14,12 +14,16 @@ const std::string CurrentDateTime() std::wstring to_wstring(const std::string& str) { - int size_needed = MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), NULL, 0); + unsigned int size_needed = MultiByteToWideChar(CP_UTF8, 0, &str[0], (unsigned int)str.size(), NULL, 0); std::wstring wstrTo(size_needed, 0); - MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), &wstrTo[0], size_needed); + MultiByteToWideChar(CP_UTF8, 0, &str[0], (unsigned int)str.size(), &wstrTo[0], size_needed); return wstrTo; } +std::string to_string(json::value value) { + return to_string(value.as_string()); +} + std::string to_string(const std::wstring& wstr) { if (wstr.empty()) @@ -27,14 +31,14 @@ std::string to_string(const std::wstring& wstr) return ""; } - const auto size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr.at(0), (int)wstr.size(), nullptr, 0, nullptr, nullptr); + const auto size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr.at(0), (unsigned int)wstr.size(), nullptr, 0, nullptr, nullptr); if (size_needed <= 0) { throw std::runtime_error("WideCharToMultiByte() failed: " + std::to_string(size_needed)); } std::string result(size_needed, 0); - WideCharToMultiByte(CP_UTF8, 0, &wstr.at(0), (int)wstr.size(), &result.at(0), size_needed, nullptr, nullptr); + WideCharToMultiByte(CP_UTF8, 0, &wstr.at(0), (unsigned int)wstr.size(), &result.at(0), size_needed, nullptr, nullptr); return result; } @@ -56,13 +60,13 @@ std::string random_string(size_t length) bool operator== (const Coords& a, const Coords& b) { return a.lat == b.lat && a.lng == b.lng && a.alt == b.alt; } bool operator!= (const Coords& a, const Coords& b) { return !(a == b); } -bool operator== (const Coords& a, const int& b) { return a.lat == b && a.lng == b && a.alt == b; } -bool operator!= (const Coords& a, const int& b) { return !(a == b); } +bool operator== (const Coords& a, const double& b) { return a.lat == b && a.lng == b && a.alt == b; } +bool operator!= (const Coords& a, const double& b) { return !(a == b); } bool operator== (const Offset& a, const Offset& b) { return a.x == b.x && a.y == b.y && a.z == b.z; } bool operator!= (const Offset& a, const Offset& b) { return !(a == b); } -bool operator== (const Offset& a, const int& b) { return a.x == b && a.y == b && a.z == b; } -bool operator!= (const Offset& a, const int& b) { return !(a == b); } +bool operator== (const Offset& a, const double& b) { return a.x == b && a.y == b && a.z == b; } +bool operator!= (const Offset& a, const double& b) { return !(a == b); } double knotsToMs(const double knots) { @@ -79,4 +83,4 @@ double ftToM(const double ft) { double mToFt(const double m) { return m / 0.3048; -} \ No newline at end of file +} diff --git a/third-party/base64/include/base64.hpp b/third-party/base64/include/base64.hpp index adcf1c04..5590e7a8 100644 --- a/third-party/base64/include/base64.hpp +++ b/third-party/base64/include/base64.hpp @@ -6,76 +6,81 @@ namespace base64 { -inline std::string get_base64_chars() { - static std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; - return base64_chars; -} + inline std::string get_base64_chars() { + static std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + return base64_chars; + } + inline std::string to_base64(std::string const& data) { + return to_base64(data.c_str(), data.length()); + } -inline std::string to_base64(std::string const &data) { - int counter = 0; - uint32_t bit_stream = 0; - const std::string base64_chars = get_base64_chars(); - std::string encoded; - int offset = 0; - for (unsigned char c : data) { - auto num_val = static_cast(c); - offset = 16 - counter % 3 * 8; - bit_stream += num_val << offset; - if (offset == 16) { - encoded += base64_chars.at(bit_stream >> 18 & 0x3f); - } - if (offset == 8) { - encoded += base64_chars.at(bit_stream >> 12 & 0x3f); - } - if (offset == 0 && counter != 3) { - encoded += base64_chars.at(bit_stream >> 6 & 0x3f); - encoded += base64_chars.at(bit_stream & 0x3f); - bit_stream = 0; - } - counter++; - } - if (offset == 16) { - encoded += base64_chars.at(bit_stream >> 12 & 0x3f); - encoded += "=="; - } - if (offset == 8) { - encoded += base64_chars.at(bit_stream >> 6 & 0x3f); - encoded += '='; - } - return encoded; -} - -inline std::string from_base64(std::string const &data) { - int counter = 0; - uint32_t bit_stream = 0; - std::string decoded; - int offset = 0; - const std::string base64_chars = get_base64_chars(); - for (unsigned char c : data) { - auto num_val = base64_chars.find(c); - if (num_val != std::string::npos) { - offset = 18 - counter % 4 * 6; - bit_stream += num_val << offset; - if (offset == 12) { - decoded += static_cast(bit_stream >> 16 & 0xff); - } - if (offset == 6) { - decoded += static_cast(bit_stream >> 8 & 0xff); - } - if (offset == 0 && counter != 4) { - decoded += static_cast(bit_stream & 0xff); - bit_stream = 0; - } - } else if (c != '=') { - return std::string(); - } - counter++; - } - return decoded; -} + inline std::string to_base64(const char* data, size_t size) { + int counter = 0; + uint32_t bit_stream = 0; + const std::string base64_chars = get_base64_chars(); + std::string encoded; + encoded.reserve(ceil(4.0 / 3.0 * size)); + int offset = 0; + for (unsigned int idx = 0; idx < size; idx++) { + unsigned char c = data[idx]; + auto num_val = static_cast(c); + offset = 16 - counter % 3 * 8; + bit_stream += num_val << offset; + if (offset == 16) { + encoded += base64_chars.at(bit_stream >> 18 & 0x3f); + } + if (offset == 8) { + encoded += base64_chars.at(bit_stream >> 12 & 0x3f); + } + if (offset == 0 && counter != 3) { + encoded += base64_chars.at(bit_stream >> 6 & 0x3f); + encoded += base64_chars.at(bit_stream & 0x3f); + bit_stream = 0; + } + counter++; + } + if (offset == 16) { + encoded += base64_chars.at(bit_stream >> 12 & 0x3f); + encoded += "=="; + } + if (offset == 8) { + encoded += base64_chars.at(bit_stream >> 6 & 0x3f); + encoded += '='; + } + return encoded; + } + inline std::string from_base64(std::string const& data) { + int counter = 0; + uint32_t bit_stream = 0; + std::string decoded; + int offset = 0; + const std::string base64_chars = get_base64_chars(); + for (unsigned char c : data) { + auto num_val = base64_chars.find(c); + if (num_val != std::string::npos) { + offset = 18 - counter % 4 * 6; + bit_stream += num_val << offset; + if (offset == 12) { + decoded += static_cast(bit_stream >> 16 & 0xff); + } + if (offset == 6) { + decoded += static_cast(bit_stream >> 8 & 0xff); + } + if (offset == 0 && counter != 4) { + decoded += static_cast(bit_stream & 0xff); + bit_stream = 0; + } + } + else if (c != '=') { + return std::string(); + } + counter++; + } + return decoded; + } } #endif // BASE_64_HPP \ No newline at end of file From 916752301a010371782d807ead90491b70e353a5 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Thu, 22 Jun 2023 22:17:00 +0200 Subject: [PATCH 07/15] Started transition to binary data on client side --- client/src/@types/server.d.ts | 2 +- client/src/other/utils.ts | 7 +- client/src/server/server.ts | 4 +- client/src/units/unitsmanager.ts | 62 +++++++- src/core/include/unit.h | 12 +- src/core/src/airunit.cpp | 2 +- src/core/src/core.cpp | 7 +- src/core/src/server.cpp | 1 + src/core/src/unit.cpp | 213 +++++++++++++------------- src/core/src/unitsmanager.cpp | 7 +- src/utils/src/utils.cpp | 2 +- third-party/base64/include/base64.hpp | 9 +- 12 files changed, 196 insertions(+), 132 deletions(-) diff --git a/client/src/@types/server.d.ts b/client/src/@types/server.d.ts index e1a9c3e9..23920339 100644 --- a/client/src/@types/server.d.ts +++ b/client/src/@types/server.d.ts @@ -1,5 +1,5 @@ interface UnitsData { - units: {[key: string]: UnitData}, + units: string, sessionHash: string } diff --git a/client/src/other/utils.ts b/client/src/other/utils.ts index a38e0f26..6043539d 100644 --- a/client/src/other/utils.ts +++ b/client/src/other/utils.ts @@ -4,6 +4,7 @@ import { UnitDatabase } from "../units/unitdatabase"; import { aircraftDatabase } from "../units/aircraftdatabase"; import { helicopterDatabase } from "../units/helicopterdatabase"; import { groundUnitsDatabase } from "../units/groundunitsdatabase"; +import { Buffer } from "buffer"; export function bearing(lat1: number, lon1: number, lat2: number, lon2: number) { const φ1 = deg2rad(lat1); // φ, λ in radians @@ -247,4 +248,8 @@ export function getUnitDatabaseByCategory(category: string) { return groundUnitsDatabase; else return null; -} \ No newline at end of file +} + +export function base64ToBytes(base64: string) { + return Buffer.from(base64, 'base64').buffer; +} diff --git a/client/src/server/server.ts b/client/src/server/server.ts index f3ea1afd..f6249184 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -300,7 +300,7 @@ export function startUpdate() { 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 */); + getUnits((data: UnitsData) => getUnitsManager()?.update(data.units), true /* Does a full refresh */); requestUpdate(); requestRefresh(); @@ -310,7 +310,7 @@ export function requestUpdate() { /* Main update rate = 250ms is minimum time, equal to server update time. */ getUnits((data: UnitsData) => { if (!getPaused()) { - getUnitsManager()?.update(data); + getUnitsManager()?.update(data.units); checkSessionHash(data.sessionHash); } }, false); diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index 35a7afcb..2bb85f3c 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -1,8 +1,8 @@ import { LatLng, LatLngBounds } from "leaflet"; -import { getHotgroupPanel, getInfoPopup, getMap, getMissionHandler, getUnitDataTable } from ".."; +import { getHotgroupPanel, getInfoPopup, getMap, getMissionHandler } from ".."; import { Unit } from "./unit"; import { cloneUnit, spawnGroundUnit } from "../server/server"; -import { deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots, polygonArea, randomPointInPoly, randomUnitBlueprintByRole } from "../other/utils"; +import { base64ToBytes, 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"; @@ -68,9 +68,61 @@ export class UnitsManager { } - update(data: UnitsData) { + update(data: string) { var updatedUnits: Unit[] = []; - Object.keys(data.units) + var buffer = base64ToBytes(data); + + /*Coords position; + double speed; + double heading; + unsigned short fuel; + double desiredSpeed; + double desiredAltitude; + unsigned int targetID; + Coords targetPosition; + unsigned char state; + unsigned char ROE; + unsigned char reactionToThreat; + unsigned char emissionsCountermeasures; + Options::TACAN TACAN; + Options::Radio Radio; + unsigned short pathLength; + unsigned char nameLength; + unsigned char unitNameLength; + unsigned char groupNameLength; + unsigned char categoryLength; + unsigned char coalitionLength;*/ + + var offset = 0; + var dataview = new DataView(buffer); + const ID = dataview.getUint32(offset, true); offset += 4; + const bitmask = dataview.getUint32(offset , true); offset += 4; + const alive = bitmask & (1 << 0); + const human = bitmask >> 1 & 1; + const controlled = bitmask >> 2 & 1; + const hasTask = bitmask >> 3 & 1; + const desiredAltitudeType = bitmask >> 16 & 1; + const desiredSpeedType = bitmask >> 17 & 1; + const isTanker = bitmask >> 18 & 1; + const isAWACS = bitmask >> 19 & 1; + const onOff = bitmask >> 20 & 1; + const followRoads = bitmask >> 21 & 1; + const EPLRS = bitmask >> 22 & 1; + const prohibitAA = bitmask >> 23 & 1; + const prohibitAfterburner = bitmask >> 24 & 1; + const prohibitAG = bitmask >> 25 & 1; + const prohibitAirWpn = bitmask >> 26 & 1; + const prohibitJettison = bitmask >> 27 & 1; + + const latitude = dataview.getFloat64(offset , true); offset += 8; + const longitude = dataview.getFloat64(offset , true); offset += 8; + const altitude = dataview.getFloat64(offset , true); offset += 8; + const speed = dataview.getFloat64(offset , true); offset += 8; + const heading = dataview.getFloat64(offset , true); offset += 8; + + + var foo = 12; + /*Object.keys(data.units) .filter((ID: string) => !(ID in this.#units)) .reduce((timeout: number, ID: string) => { window.setTimeout(() => { @@ -91,7 +143,7 @@ export class UnitsManager { this.getSelectedUnits().forEach((unit: Unit) => { if (!updatedUnits.includes(unit)) unit.setData({}) - }); + });*/ } setHiddenType(key: string, value: boolean) { diff --git a/src/core/include/unit.h b/src/core/include/unit.h index 2fc60171..f8f5241d 100644 --- a/src/core/include/unit.h +++ b/src/core/include/unit.h @@ -29,6 +29,7 @@ namespace State }; }; +#pragma pack(push, 1) namespace Options { struct TACAN { @@ -86,8 +87,15 @@ namespace DataTypes { unsigned char emissionsCountermeasures; Options::TACAN TACAN; Options::Radio Radio; + unsigned short pathLength; + unsigned char nameLength; + unsigned char unitNameLength; + unsigned char groupNameLength; + unsigned char categoryLength; + unsigned char coalitionLength; }; } +#pragma pack(pop) class Unit { @@ -102,8 +110,8 @@ public: void runAILoop(); void updateExportData(json::value json, double dt = 0); void updateMissionData(json::value json); - DataTypes::DataPacket getDataPacket(); - string getData(bool refresh); + unsigned int getUpdateData(char* &data); + void getData(stringstream &ss, bool refresh); virtual string getCategory() { return "No category"; }; /********** Base data **********/ diff --git a/src/core/src/airunit.cpp b/src/core/src/airunit.cpp index 375a996d..fe0b9977 100644 --- a/src/core/src/airunit.cpp +++ b/src/core/src/airunit.cpp @@ -68,7 +68,7 @@ void AirUnit::setState(unsigned char newState) case State::ATTACK: { if (isTargetAlive()) { Unit* target = unitsManager->getUnit(targetID); - Coords targetPosition = Coords(target->getLatitude(), target->getLongitude(), 0); + Coords targetPosition = Coords(target->getPosition().lat, target->getPosition().lng, 0); clearActivePath(); pushActivePathFront(targetPosition); resetActiveDestination(); diff --git a/src/core/src/core.cpp b/src/core/src/core.cpp index 7a17691b..4f75b5ec 100644 --- a/src/core/src/core.cpp +++ b/src/core/src/core.cpp @@ -79,18 +79,19 @@ extern "C" DllExport int coreFrame(lua_State* L) lock_guard guard(mutexLock); milliseconds ms = duration_cast(system_clock::now().time_since_epoch()); - frameRate = frameCounter / duration.count(); + if (duration.count() > 0) + frameRate = frameCounter / duration.count(); frameCounter = 0; if (unitsManager != nullptr) { unitsManager->updateExportData(L, duration.count()); - unitsManager->runAILoop(); + //unitsManager->runAILoop(); } before = std::chrono::system_clock::now(); } if (scheduler != nullptr) - scheduler->execute(L); + //scheduler->execute(L); return(0); } diff --git a/src/core/src/server.cpp b/src/core/src/server.cpp index e8c71e5d..086882d7 100644 --- a/src/core/src/server.cpp +++ b/src/core/src/server.cpp @@ -71,6 +71,7 @@ void Server::handle_get(http_request request) http_response response(status_codes::OK); string authorization = to_base64("admin:" + password); + log(authorization); if (password == "" || (request.headers().has(L"Authorization") && request.headers().find(L"Authorization")->second == L"Basic " + to_wstring(authorization))) { std::exception_ptr eptr; diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp index 1fdda23a..bc99edbd 100644 --- a/src/core/src/unit.cpp +++ b/src/core/src/unit.cpp @@ -6,9 +6,6 @@ #include "defines.h" #include "unitsmanager.h" -#include "base64.hpp" -using namespace base64; - #include using namespace std::chrono; @@ -54,30 +51,26 @@ void Unit::initialize(json::value json) void Unit::setDefaults(bool force) { - const bool isUnitControlledByOlympus = getControlled(); - const bool isUnitAlive = getAlive(); - const bool isUnitLeader = unitsManager->isUnitGroupLeader(this); - const bool isUnitLeaderOfAGroupWithOtherUnits = unitsManager->isUnitInGroup(this) && unitsManager->isUnitGroupLeader(this); + if (!getControlled()) return; + if (!unitsManager->isUnitGroupLeader(this)) return; + if (!(getAlive() || unitsManager->isUnitInGroup(this) && unitsManager->isUnitGroupLeader(this))) return; + if (getHuman()) return; - if (isUnitControlledByOlympus && (isUnitAlive || isUnitLeaderOfAGroupWithOtherUnits) && isUnitLeader && !human) { - /* Set the default IDLE state */ - setState(State::IDLE); + /* Set the default IDLE state */ + setState(State::IDLE); - /* Set desired altitude to be equal to current altitude so the unit does not climb/descend after spawn */ - setDesiredAltitude(position.alt); + /* Set desired altitude to be equal to current altitude so the unit does not climb/descend after spawn */ + setDesiredAltitude(position.alt); - /* Set the default options (these are all defaults so will only affect the export data, no DCS command will be sent) */ - setROE(ROE::OPEN_FIRE_WEAPON_FREE, force); - setReactionToThreat(ReactionToThreat::EVADE_FIRE, force); - setEmissionsCountermeasures(EmissionCountermeasure::DEFEND, force); - strcpy_s(TACAN.callsign, 4, "TKR"); - setTACAN(TACAN, force); - setRadio(radio, force); - setEPLRS(EPLRS, force); - setGeneralSettings(generalSettings, force); - setOnOff(onOff); - setFollowRoads(followRoads); - } + /* Set the default options */ + setROE(ROE::OPEN_FIRE_WEAPON_FREE, force); + setReactionToThreat(ReactionToThreat::EVADE_FIRE, force); + setEmissionsCountermeasures(EmissionCountermeasure::DEFEND, force); + strcpy_s(TACAN.callsign, 4, "TKR"); + setTACAN(TACAN, force); + setRadio(radio, force); + setEPLRS(EPLRS, force); + setGeneralSettings(generalSettings, force); } void Unit::runAILoop() { @@ -107,8 +100,7 @@ void Unit::updateExportData(json::value json, double dt) if (dt > 0) setSpeed(getSpeed() * 0.95 + (dist / dt) * 0.05); } - oldPosition = position; - + if (json.has_string_field(L"Name")) setName(to_string(json[L"Name"])); if (json.has_string_field(L"UnitName")) @@ -136,53 +128,63 @@ void Unit::updateExportData(json::value json, double dt) /* All units which contain the name "Olympus" are automatically under AI control */ if (getUnitName().find("Olympus") != string::npos) setControlled(true); + + oldPosition = position; } void Unit::updateMissionData(json::value json) { - if (json.has_number_field(L"fuel")) - setFuel(short(json[L"fuel"].as_number().to_double() * 100)); - - if (json.has_object_field(L"ammo")) { - vector ammo; - for (auto const& el : json[L"ammo"].as_object()) { - DataTypes::Ammo ammoItem; - auto ammoJson = el.second; - ammoItem.quantity = ammoJson[L"count"].as_number().to_uint32(); - ammoItem.name = to_string(ammoJson[L"desc"][L"displayName"]); - ammoItem.guidance = ammoJson[L"desc"][L"guidance"].as_number().to_uint32(); - ammoItem.category = ammoJson[L"desc"][L"category"].as_number().to_uint32(); - ammoItem.missileCategory = ammoJson[L"desc"][L"missileCategory"].as_number().to_uint32(); - ammo.push_back(ammoItem); - } - setAmmo(ammo); - } - - if (json.has_object_field(L"contacts")) { - vector contacts; - for (auto const& el : json[L"ammo"].as_object()) { - DataTypes::Contact contactItem; - auto contactJson = el.second; - contactItem.ID = contactJson[L"object"][L"id_"].as_number().to_uint32(); - - string detectionMethod = to_string(contactJson[L"detectionMethod"]); - if (detectionMethod.compare("VISUAL")) contactItem.detectionMethod = 1; - else if (detectionMethod.compare("OPTIC")) contactItem.detectionMethod = 2; - else if (detectionMethod.compare("RADAR")) contactItem.detectionMethod = 4; - else if (detectionMethod.compare("IRST")) contactItem.detectionMethod = 8; - else if (detectionMethod.compare("RWR")) contactItem.detectionMethod = 16; - else if (detectionMethod.compare("DLINK")) contactItem.detectionMethod = 32; - contacts.push_back(contactItem); - } - setContacts(contacts); - } + //if (json.has_number_field(L"fuel")) + // setFuel(short(json[L"fuel"].as_number().to_double() * 100)); + // + //if (json.has_object_field(L"ammo")) { + // vector ammo; + // for (auto const& el : json[L"ammo"].as_object()) { + // DataTypes::Ammo ammoItem; + // auto ammoJson = el.second; + // ammoItem.quantity = ammoJson[L"count"].as_number().to_uint32(); + // ammoItem.name = to_string(ammoJson[L"desc"][L"displayName"]); + // ammoItem.guidance = ammoJson[L"desc"][L"guidance"].as_number().to_uint32(); + // ammoItem.category = ammoJson[L"desc"][L"category"].as_number().to_uint32(); + // ammoItem.missileCategory = ammoJson[L"desc"][L"missileCategory"].as_number().to_uint32(); + // ammo.push_back(ammoItem); + // } + // setAmmo(ammo); + //} + // + //if (json.has_object_field(L"contacts")) { + // vector contacts; + // for (auto const& el : json[L"ammo"].as_object()) { + // DataTypes::Contact contactItem; + // auto contactJson = el.second; + // contactItem.ID = contactJson[L"object"][L"id_"].as_number().to_uint32(); + // + // string detectionMethod = to_string(contactJson[L"detectionMethod"]); + // if (detectionMethod.compare("VISUAL")) contactItem.detectionMethod = 1; + // else if (detectionMethod.compare("OPTIC")) contactItem.detectionMethod = 2; + // else if (detectionMethod.compare("RADAR")) contactItem.detectionMethod = 4; + // else if (detectionMethod.compare("IRST")) contactItem.detectionMethod = 8; + // else if (detectionMethod.compare("RWR")) contactItem.detectionMethod = 16; + // else if (detectionMethod.compare("DLINK")) contactItem.detectionMethod = 32; + // contacts.push_back(contactItem); + // } + // setContacts(contacts); + //} if (json.has_boolean_field(L"hasTask")) setHasTask(json[L"hasTask"].as_bool()); } -DataTypes::DataPacket Unit::getDataPacket() +unsigned int Unit::getUpdateData(char* &data) { + /* Reserve data for: + 1) DataPacket; + 2) Active path; + */ + data = (char*)malloc(sizeof(DataTypes::DataPacket) + activePath.size() * sizeof(Coords)); + unsigned int offset = 0; + + /* Prepare the data packet and copy it to memory */ unsigned int bitmask = 0; bitmask |= alive << 0; bitmask |= human << 1; @@ -192,16 +194,16 @@ DataTypes::DataPacket Unit::getDataPacket() bitmask |= desiredSpeedType << 17; bitmask |= isTanker << 18; bitmask |= isAWACS << 19; - bitmask |= onOff << 19; - bitmask |= followRoads << 19; - bitmask |= EPLRS << 20; - bitmask |= generalSettings.prohibitAA << 21; - bitmask |= generalSettings.prohibitAfterburner << 22; - bitmask |= generalSettings.prohibitAG << 23; - bitmask |= generalSettings.prohibitAirWpn << 24; - bitmask |= generalSettings.prohibitJettison << 25; + bitmask |= onOff << 20; + bitmask |= followRoads << 21; + bitmask |= EPLRS << 22; + bitmask |= generalSettings.prohibitAA << 23; + bitmask |= generalSettings.prohibitAfterburner << 24; + bitmask |= generalSettings.prohibitAG << 25; + bitmask |= generalSettings.prohibitAirWpn << 26; + bitmask |= generalSettings.prohibitJettison << 27; - DataTypes::DataPacket datapacket{ + DataTypes::DataPacket dataPacket{ ID, bitmask, position, @@ -217,37 +219,15 @@ DataTypes::DataPacket Unit::getDataPacket() reactionToThreat, emissionsCountermeasures, TACAN, - radio + radio, + activePath.size(), + name.size(), + unitName.size(), + groupName.size(), + getCategory().size(), + coalition.size() }; - return datapacket; -} - -string Unit::getData(bool refresh) -{ - /* Prepare the data in a stringstream */ - stringstream ss; - - /* Reserve data for: - 1) DataPacket; - 2) Length of active path; - 3) Active path; - */ - char* data = (char*)malloc(sizeof(DataTypes::DataPacket) + sizeof(unsigned short) + activePath.size() * sizeof(Coords)); - unsigned int offset = 0; - - /* Prepare the data packet and copy it to memory */ - DataTypes::DataPacket dataPacket; - /* If the unit is in a group, get the datapacket from the group leader and only replace the position, speed and heading */ - if (unitsManager->isUnitInGroup(this) && !unitsManager->isUnitGroupLeader(this)) { - dataPacket = unitsManager->getGroupLeader(this)->getDataPacket(); - dataPacket.position = position; - dataPacket.speed = speed; - dataPacket.heading = heading; - } - else - dataPacket = getDataPacket(); - memcpy(data + offset, &dataPacket, sizeof(dataPacket)); offset += sizeof(dataPacket); @@ -255,14 +235,29 @@ string Unit::getData(bool refresh) std::vector path; for (const Coords& c : activePath) path.push_back(c); - unsigned short pathLength = activePath.size(); - memcpy(data + offset, &pathLength, sizeof(unsigned short)); - offset += sizeof(unsigned short); memcpy(data + offset, &path, activePath.size() * sizeof(Coords)); - offset += sizeof(unsigned short); + offset += activePath.size() * sizeof(Coords); - ss << to_base64(data, offset); + return offset; +} + +void Unit::getData(stringstream &ss, bool refresh) +{ + char* data; + unsigned int size = getUpdateData(data); + + /* Prepare the data packet and copy it to memory */ + /* If the unit is in a group, get the update data from the group leader and only replace the position, speed and heading */ + if (unitsManager->isUnitInGroup(this) && !unitsManager->isUnitGroupLeader(this)) { + DataTypes::DataPacket* p = (DataTypes::DataPacket*)data; + p->position = position; + p->speed = speed; + p->heading = heading; + } + + ss.write(data, size); + delete data; if (refresh) { ss << name; @@ -271,8 +266,6 @@ string Unit::getData(bool refresh) ss << getCategory(); ss << coalition; } - - return ss.str(); } void Unit::setActivePath(list newPath) @@ -397,7 +390,7 @@ void Unit::setROE(unsigned char newROE, bool force) { if (ROE != newROE || force) { ROE = newROE; - Command* command = dynamic_cast(new SetOption(groupName, SetCommandType::ROE, ROE)); + Command* command = dynamic_cast(new SetOption(groupName, SetCommandType::ROE, static_cast(ROE))); scheduler->appendCommand(command); } } @@ -407,7 +400,7 @@ void Unit::setReactionToThreat(unsigned char newReactionToThreat, bool force) if (reactionToThreat != newReactionToThreat || force) { reactionToThreat = newReactionToThreat; - Command* command = dynamic_cast(new SetOption(groupName, SetCommandType::REACTION_ON_THREAT, reactionToThreat)); + Command* command = dynamic_cast(new SetOption(groupName, SetCommandType::REACTION_ON_THREAT, static_cast(reactionToThreat))); scheduler->appendCommand(command); } } diff --git a/src/core/src/unitsmanager.cpp b/src/core/src/unitsmanager.cpp index 1c6b33aa..66a976b5 100644 --- a/src/core/src/unitsmanager.cpp +++ b/src/core/src/unitsmanager.cpp @@ -10,6 +10,9 @@ #include "commands.h" #include "scheduler.h" +#include "base64.hpp" +using namespace base64; + extern Scheduler* scheduler; UnitsManager::UnitsManager(lua_State* L) @@ -155,8 +158,8 @@ string UnitsManager::getUnitData(bool refresh) { stringstream ss; for (auto const& p : units) - ss << p.second->getData(refresh); - return ss.str(); + p.second->getData(ss, refresh); + return to_base64(ss.str()); } void UnitsManager::deleteUnit(unsigned int ID, bool explosion) diff --git a/src/utils/src/utils.cpp b/src/utils/src/utils.cpp index 62299d2d..f6b0a556 100644 --- a/src/utils/src/utils.cpp +++ b/src/utils/src/utils.cpp @@ -20,7 +20,7 @@ std::wstring to_wstring(const std::string& str) return wstrTo; } -std::string to_string(json::value value) { +std::string to_string(json::value& value) { return to_string(value.as_string()); } diff --git a/third-party/base64/include/base64.hpp b/third-party/base64/include/base64.hpp index 5590e7a8..9e3d1565 100644 --- a/third-party/base64/include/base64.hpp +++ b/third-party/base64/include/base64.hpp @@ -12,10 +12,7 @@ namespace base64 { "0123456789+/"; return base64_chars; } - inline std::string to_base64(std::string const& data) { - return to_base64(data.c_str(), data.length()); - } - + inline std::string to_base64(const char* data, size_t size) { int counter = 0; uint32_t bit_stream = 0; @@ -52,6 +49,10 @@ namespace base64 { return encoded; } + inline std::string to_base64(std::string const& data) { + return to_base64(data.c_str(), data.length()); + } + inline std::string from_base64(std::string const& data) { int counter = 0; uint32_t bit_stream = 0; From dd2e858db4f13d7893a47767de56aef7501e6799 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Fri, 23 Jun 2023 17:32:38 +0200 Subject: [PATCH 08/15] More work on client conversion to binary data --- client/src/@types/unit.d.ts | 141 +++++---- client/src/atc/atcboard.ts | 6 +- client/src/atc/board/tower.ts | 4 +- client/src/atc/unitdatatable.ts | 6 +- client/src/constants/constants.ts | 31 +- client/src/map/map.ts | 2 +- client/src/panels/mouseinfopanel.ts | 2 +- client/src/panels/unitcontrolpanel.ts | 59 ++-- client/src/panels/unitinfopanel.ts | 16 +- client/src/server/server.ts | 1 + client/src/units/dataextractor.ts | 219 ++++++++++++++ client/src/units/unit.ts | 398 ++++++++++++-------------- client/src/units/unitsmanager.ts | 120 +++----- src/core/include/unit.h | 4 +- src/core/src/unit.cpp | 20 +- 15 files changed, 597 insertions(+), 432 deletions(-) create mode 100644 client/src/units/dataextractor.ts diff --git a/client/src/@types/unit.d.ts b/client/src/@types/unit.d.ts index 0a7847b9..0c7d400a 100644 --- a/client/src/@types/unit.d.ts +++ b/client/src/@types/unit.d.ts @@ -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,55 @@ 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; + ID: number, + alive: boolean, + human: boolean, + controlled: boolean, + hasTask: boolean, + desiredAltitudeType: boolean, + desiredSpeedType: boolean, + isTanker: boolean, + isAWACS: boolean, + onOff: boolean, + followRoads: boolean, + EPLRS: boolean, + generalSettings: GeneralSettings + position: LatLng, + speed: number, + heading: number, + fuel: number, + desiredSpeed: number, + desiredAltitude: number, + targetID: number, + leaderID: number, + targetPosition: LatLng, + state: string, + ROE: string, + reactionToThreat: string, + emissionsCountermeasures: string, + TACAN: TACAN, + radio: Radio, + activePath: LatLng[], + ammo: Ammo[], + contacts: Contact[], + name: string, + unitName: string, + groupName: string, + category: string, + coalition: string, + task: string } \ No newline at end of file diff --git a/client/src/atc/atcboard.ts b/client/src/atc/atcboard.ts index 2edcbc9e..a65f55bd 100644 --- a/client/src/atc/atcboard.ts +++ b/client/src/atc/atcboard.ts @@ -115,7 +115,7 @@ export abstract class ATCBoard { addFlight( unit:Unit ) { - const baseData = unit.getBaseData(); + const baseData = unit.getData(); const unitCanBeAdded = () => { @@ -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; diff --git a/client/src/atc/board/tower.ts b/client/src/atc/board/tower.ts index c1698966..8800dfc2 100644 --- a/client/src/atc/board/tower.ts +++ b/client/src/atc/board/tower.ts @@ -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 ) { diff --git a/client/src/atc/unitdatatable.ts b/client/src/atc/unitdatatable.ts index 1ef21f2c..f54a4553 100644 --- a/client/src/atc/unitdatatable.ts +++ b/client/src/atc/unitdatatable.ts @@ -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.getData().unitName?.toLowerCase(); + const bVal = b.getData().unitName?.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.getData().unitName, unit.getData().name, unit.getData().category, (unit.getData().controlled) ? "AI" : "Human"]; addRow(el, dataset); } diff --git a/client/src/constants/constants.ts b/client/src/constants/constants.ts index 4fabffcf..1cff2f7d 100644 --- a/client/src/constants/constants.ts +++ b/client/src/constants/constants.ts @@ -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 }; diff --git a/client/src/map/map.ts b/client/src/map/map.ts index fc4e0cc7..30c0cc0c 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -547,7 +547,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.getData().position.lat, unit.getData().position.lng); this.setView(unitPosition, this.getZoom(), { animate: false }); } diff --git a/client/src/panels/mouseinfopanel.ts b/client/src/panels/mouseinfopanel.ts index b5e7e8eb..dfb48d92 100644 --- a/client/src/panels/mouseinfopanel.ts +++ b/client/src/panels/mouseinfopanel.ts @@ -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].getData().position.lat, selectedUnits[0].getData().position.lng); /* Draw measures from selected unit, from pin location, and from bullseyes */ this.#drawMeasure("ref-measure-position", "measure-position", this.#measurePoint, mousePosition); diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index c5d6f489..df2b1b0a 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -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; @@ -98,13 +99,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.getData().unitName || ""; + var label = unit.getDatabase()?.getByName(unit.getData().name)?.label || unit.getData().name; button.setAttribute("data-label", label); button.setAttribute("data-callsign", callsign); - button.setAttribute("data-coalition", unit.getMissionData().coalition); + button.setAttribute("data-coalition", unit.getData().coalition); button.classList.add("pill", "highlight-coalition") button.addEventListener("click", () => { @@ -139,12 +140,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.getData().desiredAltitude}); + var desiredAltitudeType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getData().desiredAltitudeType}); + var desiredSpeed = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getData().desiredSpeed}); + var desiredSpeedType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getData().desiredSpeedType}); + var onOff = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getData().onOff}); + var followRoads = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getData().followRoads}); if (selectedUnitsTypes.length == 1) { this.#altitudeTypeSwitch.setValue(desiredAltitudeType != undefined? desiredAltitudeType == "AGL": undefined, false); @@ -170,15 +171,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.getData().ROE === 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.getData().reactionToThreat === 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.getData().emissionsCountermeasures === button.value)) }); this.#onOffSwitch.setValue(onOff, false); @@ -207,11 +208,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.getData().name)?.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.getData().radio.frequency / 1000000); + const radioDecimals = (unit.getData().radio.frequency / 1000000 - radioMHz) * 1000; /* Activate the correct options depending on unit type */ this.#advancedSettingsDialog.toggleAttribute("data-show-settings", !tanker && !AWACS); @@ -223,28 +224,28 @@ export class UnitControlPanel extends Panel { /* Set common properties */ // Name - unitNameEl.innerText = unit.getBaseData().unitName; + unitNameEl.innerText = unit.getData().unitName; // 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.getData().generalSettings.prohibitJettison; + prohibitAfterburnerCheckbox.checked = unit.getData().generalSettings.prohibitAfterburner; + prohibitAACheckbox.checked = unit.getData().generalSettings.prohibitAA; + prohibitAGCheckbox.checked = unit.getData().generalSettings.prohibitAG; + prohibitAirWpnCheckbox.checked = unit.getData().generalSettings.prohibitAirWpn; // Tasking - tankerCheckbox.checked = unit.getTaskData().isTanker; - AWACSCheckbox.checked = unit.getTaskData().isAWACS; + tankerCheckbox.checked = unit.getData().isTanker; + AWACSCheckbox.checked = unit.getData().isAWACS; // 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.getData().TACAN.isOn; + TACANChannelInput.value = String(unit.getData().TACAN.channel); + TACANCallsignInput.value = String(unit.getData().TACAN.callsign); + this.#TACANXYDropdown.setValue(unit.getData().TACAN.XY); // Radio radioMhzInput.value = String(radioMHz); - radioCallsignNumberInput.value = String(unit.getOptionsData().radio.callsignNumber); + radioCallsignNumberInput.value = String(unit.getData().radio.callsignNumber); this.#radioDecimalsDropdown.setValue("." + radioDecimals); if (tanker) /* Set tanker specific options */ @@ -255,7 +256,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.getData().radio.callsign - 1)) // Ensure the selected value is in the acceptable range this.#radioCallsignDropdown.selectValue(0); } } diff --git a/client/src/panels/unitinfopanel.ts b/client/src/panels/unitinfopanel.ts index 91e18920..8ca140b0 100644 --- a/client/src/panels/unitinfopanel.ts +++ b/client/src/panels/unitinfopanel.ts @@ -51,21 +51,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.getData().human) 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.getData().fuel + "%"); + this.#fuelPercentage.dataset.percentage = "" + unit.getData().fuel; + this.#currentTask.dataset.currentTask = unit.getData().task !== "" ? unit.getData().task : "No task"; + this.#currentTask.dataset.coalition = unit.getData().coalition; 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,9 +74,9 @@ export class UnitInfoPanel extends Panel { const items = this.#loadoutContainer.querySelector("#loadout-items"); if (items) { - const ammo = Object.values(unit.getMissionData().ammo); + const ammo = Object.values(unit.getData().ammo); if (ammo.length > 0) { - items.replaceChildren(...Object.values(unit.getMissionData().ammo).map( + items.replaceChildren(...Object.values(unit.getData().ammo).map( (ammo: any) => { var el = document.createElement("div"); el.dataset.qty = ammo.count; diff --git a/client/src/server/server.ts b/client/src/server/server.ts index f6249184..ca392e5f 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -1,6 +1,7 @@ import { LatLng } from 'leaflet'; import { getConnectionStatusPanel, getInfoPopup, getMissionData, getUnitDataTable, getUnitsManager, setConnectionStatus } from '..'; import { SpawnOptions } from '../controls/mapcontextmenu'; +import { GeneralSettings, Radio, TACAN } from '../@types/unit'; var connected: boolean = false; var paused: boolean = false; diff --git a/client/src/units/dataextractor.ts b/client/src/units/dataextractor.ts new file mode 100644 index 00000000..8b45a504 --- /dev/null +++ b/client/src/units/dataextractor.ts @@ -0,0 +1,219 @@ +import { LatLng } from "leaflet"; +import { UnitData } from "../@types/unit"; +import { ROEs, emissionsCountermeasures, reactionsToThreat, states } from "../constants/constants"; + +export class DataExtractor { + #offset = 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"); + } + + extractData(offset: number) { + this.#offset = offset; + + const ID = this.#extractUInt32(); + const bitmask = this.#extractUInt32(); + + const unitData: UnitData = { + ID: ID, + alive: this.#extractFromBitmask(bitmask, 0), + human: this.#extractFromBitmask(bitmask, 1), + controlled: this.#extractFromBitmask(bitmask, 2), + hasTask: this.#extractFromBitmask(bitmask, 3), + desiredAltitudeType: this.#extractFromBitmask(bitmask, 16), + desiredSpeedType: this.#extractFromBitmask(bitmask, 17), + isTanker: this.#extractFromBitmask(bitmask, 18), + isAWACS: this.#extractFromBitmask(bitmask, 19), + onOff: this.#extractFromBitmask(bitmask, 20), + followRoads: this.#extractFromBitmask(bitmask, 21), + EPLRS: this.#extractFromBitmask(bitmask, 22), + generalSettings: { + prohibitAA: this.#extractFromBitmask(bitmask, 23), + prohibitAfterburner: this.#extractFromBitmask(bitmask, 24), + prohibitAG: this.#extractFromBitmask(bitmask, 25), + prohibitAirWpn: this.#extractFromBitmask(bitmask, 26), + prohibitJettison: this.#extractFromBitmask(bitmask, 27), + }, + position: new LatLng( + this.#extractFloat64(), + this.#extractFloat64(), + this.#extractFloat64() + ), + speed: this.#extractFloat64(), + heading: this.#extractFloat64(), + fuel: this.#extractUInt16(), + desiredSpeed: this.#extractFloat64(), + desiredAltitude: this.#extractFloat64(), + targetID: this.#extractUInt32(), + leaderID: 0, + targetPosition: new LatLng( + this.#extractFloat64(), + this.#extractFloat64(), + this.#extractFloat64() + ), + state: this.#getState(this.#extractUint8()), + ROE: this.#getROE(this.#extractUint8()), + reactionToThreat: this.#getReactionToThreat(this.#extractUint8()), + emissionsCountermeasures: this.#getEmissionCountermeasure(this.#extractUint8()), + TACAN: { + isOn: this.#extractBool(), + channel: this.#extractUint8(), + XY: this.#extractChar(), + callsign: this.#extractString(4) + }, + radio: { + frequency: this.#extractUInt32(), + callsign: this.#extractUint8(), + callsignNumber: this.#extractUint8() + }, + activePath: [], + ammo: [], + contacts: [], + name: "", + unitName: "", + groupName: "", + category: "", + coalition: "", + task: "" + } + + const pathLength = this.#extractUInt16(); + const ammoLength = this.#extractUInt16(); + const contactsLength = this.#extractUInt16(); + const nameLength = this.#extractUInt16(); + const unitNameLength = this.#extractUInt16(); + const groupNameLength = this.#extractUInt16(); + const categoryLength = this.#extractUInt16(); + const coalitionLength = this.#extractUInt16(); + + if (pathLength > 0) { + unitData.activePath = []; + for (let idx = 0; idx < pathLength; idx++) { + unitData.activePath.push(new LatLng(this.#extractFloat64(), this.#extractFloat64(), this.#extractFloat64())); + } + } + + if (ammoLength > 0) { + unitData.ammo = []; + for (let idx = 0; idx < pathLength; idx++) { + unitData.ammo.push({ + quantity: this.#extractUInt16(), + name: this.#extractString(32), + guidance: this.#extractUint8(), + category: this.#extractUint8(), + missileCategory: this.#extractUint8() + }); + } + } + + if (contactsLength > 0) { + unitData.contacts = []; + for (let idx = 0; idx < pathLength; idx++) { + unitData.contacts.push({ + ID: this.#extractUInt32(), + detectionMethod: this.#extractUint8() + }); + } + } + + if (nameLength > 0) { + unitData.name = this.#extractString(nameLength); + } + + if (unitNameLength > 0) { + unitData.unitName = this.#extractString(unitNameLength); + } + + if (groupNameLength > 0) { + unitData.groupName = this.#extractString(groupNameLength); + } + + if (categoryLength > 0) { + unitData.category = this.#extractString(categoryLength); + } + + if (coalitionLength > 0) { + unitData.coalition = this.#extractString(coalitionLength); + } + + return {data: unitData, offset: this.#offset}; + } + + #extractBool() { + const value = this.#dataview.getUint8(this.#offset); + this.#offset += 1; + return value > 0; + } + + #extractUint8() { + const value = this.#dataview.getUint8(this.#offset); + this.#offset += 1; + return value; + } + + #extractUInt16() { + const value = this.#dataview.getUint16(this.#offset); + this.#offset += 2; + return value; + } + + #extractUInt32() { + const value = this.#dataview.getUint32(this.#offset); + this.#offset += 4; + return value; + } + + #extractFloat64() { + const value = this.#dataview.getFloat64(this.#offset); + this.#offset += 8; + return value; + } + + #extractFromBitmask(bitmask: number, position: number) { + return (bitmask >> position & 1) > 0; + } + + #extractString(length: number) { + const value = this.#decoder.decode(this.#buffer.slice(this.#offset, length)); + this.#offset += length; + return value; + } + + #extractChar() { + return this.#extractString(1); + } + + #getState(state: number) { + if (state < states.length) + return states[state]; + else + return states[0]; + } + + #getROE(ROE: number) { + if (ROE < ROEs.length) + return ROEs[ROE]; + else + return ROEs[0]; + } + + #getReactionToThreat(reactionToThreat: number) { + if (reactionToThreat < reactionsToThreat.length) + return reactionsToThreat[reactionToThreat]; + else + return reactionsToThreat[0]; + } + + #getEmissionCountermeasure(emissionCountermeasure: number) { + if (emissionCountermeasure < emissionsCountermeasures.length) + return emissionsCountermeasures[emissionCountermeasure]; + else + return emissionsCountermeasures[0]; + } +} \ No newline at end of file diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index 200c19a6..4d2416ce 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -6,7 +6,8 @@ import { CustomMarker } from '../map/custommarker'; import { SVGInjector } from '@tanem/svg-injector'; import { UnitDatabase } from './unitdatabase'; import { TargetMarker } from '../map/targetmarker'; -import { BOMBING, CARPET_BOMBING, FIRE_AT_AREA, IDLE, MOVE_UNIT } from '../constants/constants'; +import { BOMBING, CARPET_BOMBING, FIRE_AT_AREA, IDLE, MOVE_UNIT, ROEs, emissionsCountermeasures, reactionsToThreat, states } from '../constants/constants'; +import { GeneralSettings, Radio, TACAN, UnitData, UnitIconOptions } from '../@types/unit'; var pathIcon = new Icon({ iconUrl: '/resources/theme/images/markers/marker-icon.png', @@ -18,55 +19,58 @@ export class Unit extends CustomMarker { ID: number; #data: UnitData = { - baseData: { - controlled: false, - name: "", - unitName: "", - groupName: "", - alive: true, - category: "", + ID: 0, + alive: false, + human: false, + controlled: false, + hasTask: false, + desiredAltitudeType: false, + desiredSpeedType: false, + isTanker: false, + isAWACS: false, + onOff: false, + followRoads: false, + EPLRS: false, + generalSettings: { + prohibitAA: false, + prohibitAfterburner: false, + prohibitAG: false, + prohibitAirWpn: false, + prohibitJettison: false }, - flightData: { - latitude: 0, - longitude: 0, - altitude: 0, - heading: 0, - speed: 0, + position: new LatLng(0, 0), + speed: 0, + heading: 0, + fuel: 0, + desiredSpeed: 0, + desiredAltitude: 0, + targetID: 0, + leaderID: 0, + targetPosition: new LatLng(0, 0), + state: states[0], + ROE: ROEs[0], + reactionToThreat: reactionsToThreat[0], + emissionsCountermeasures: emissionsCountermeasures[0], + TACAN: { + isOn: false, + XY: 'X', + callsign: '', + channel: 0 }, - missionData: { - fuel: 0, - flags: {}, - ammo: {}, - contacts: {}, - hasTask: false, - coalition: "", + radio: { + frequency: 0, + callsign: 0, + callsignNumber: 0 }, - formationData: { - leaderID: 0 - }, - taskData: { - currentState: "NONE", - currentTask: "", - activePath: {}, - desiredSpeed: 0, - desiredSpeedType: "GS", - desiredAltitude: 0, - desiredAltitudeType: "AGL", - targetLocation: {}, - isTanker: false, - isAWACS: false, - onOff: true, - followRoads: false, - targetID: 0 - }, - optionsData: { - ROE: "", - reactionToThreat: "", - emissionsCountermeasures: "", - TACAN: { isOn: false, channel: 0, XY: "X", callsign: "" }, - radio: { frequency: 0, callsign: 1, callsignNumber: 1}, - generalSettings: { prohibitJettison: false, prohibitAA: false, prohibitAG: false, prohibitAfterburner: false, prohibitAirWpn: false} - } + activePath: [], + ammo: [], + contacts: [], + name: "", + unitName: "", + groupName: "", + category: "", + coalition: "", + task: "" }; #selectable: boolean; @@ -80,8 +84,8 @@ export class Unit extends CustomMarker { #pathPolyline: Polyline; #contactsPolylines: Polyline[]; #miniMapMarker: CircleMarker | null = null; - #targetLocationMarker: TargetMarker; - #targetLocationPolyline: Polyline; + #targetPositionMarker: TargetMarker; + #targetPositionPolyline: Polyline; #timer: number = 0; @@ -96,26 +100,24 @@ export class Unit extends CustomMarker { if (type === "NavyUnit") return NavyUnit; } - constructor(ID: number, data: UpdateData) { + constructor(ID: number, data: UnitData) { super(new LatLng(0, 0), { riseOnHover: true, keyboard: false }); this.ID = ID; - this.#selectable = true; + this.#pathPolyline = new Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1 }); + this.#pathPolyline.addTo(getMap()); + this.#contactsPolylines = []; + this.#targetPositionMarker = new TargetMarker(new LatLng(0, 0)); + this.#targetPositionPolyline = new Polyline([], { color: '#FF0000', weight: 3, opacity: 0.5, smoothFactor: 1 }); + this.on('click', (e) => this.#onClick(e)); this.on('dblclick', (e) => this.#onDoubleClick(e)); this.on('contextmenu', (e) => this.#onContextMenu(e)); this.on('mouseover', () => { this.setHighlighted(true); }) this.on('mouseout', () => { this.setHighlighted(false); }) - this.#pathPolyline = new Polyline([], { color: '#2d3e50', weight: 3, opacity: 0.5, smoothFactor: 1 }); - this.#pathPolyline.addTo(getMap()); - this.#contactsPolylines = []; - - this.#targetLocationMarker = new TargetMarker(new LatLng(0, 0)); - this.#targetLocationPolyline = new Polyline([], { color: '#FF0000', weight: 3, opacity: 0.5, smoothFactor: 1 }); - /* Deselect units if they are hidden */ document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => { window.setTimeout(() => { this.setSelected(this.getSelected() && !this.getHidden()) }, 300); @@ -156,7 +158,7 @@ export class Unit extends CustomMarker { setSelected(selected: boolean) { /* Only alive units can be selected. Some units are not selectable (weapons) */ - if ((this.getBaseData().alive || !selected) && this.getSelectable() && this.getSelected() != selected) { + if ((this.getData().alive || !selected) && this.getSelectable() && this.getSelected() != selected) { this.#selected = selected; this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-selected", selected); if (selected) { @@ -206,42 +208,34 @@ export class Unit extends CustomMarker { } getGroupMembers() { - return Object.values(getUnitsManager().getUnits()).filter((unit: Unit) => {return unit != this && unit.getBaseData().groupName === this.getBaseData().groupName;}); + return Object.values(getUnitsManager().getUnits()).filter((unit: Unit) => {return unit != this && unit.getData().groupName === this.getData().groupName;}); } /********************** Unit data *************************/ - setData(data: UpdateData) { + setData(data: UnitData) { /* Check if data has changed comparing new values to old values */ - const positionChanged = (data.flightData != undefined && data.flightData.latitude != undefined && data.flightData.longitude != undefined && (this.getFlightData().latitude != data.flightData.latitude || this.getFlightData().longitude != data.flightData.longitude)); - const headingChanged = (data.flightData != undefined && data.flightData.heading != undefined && this.getFlightData().heading != data.flightData.heading); - const aliveChanged = (data.baseData != undefined && data.baseData.alive != undefined && this.getBaseData().alive != data.baseData.alive); - const stateChanged = (data.taskData != undefined && data.taskData.currentState != undefined && this.getTaskData().currentState != data.taskData.currentState); - const controlledChanged = (data.baseData != undefined && data.baseData.controlled != undefined && this.getBaseData().controlled != data.baseData.controlled); - var updateMarker = (positionChanged || headingChanged || aliveChanged || stateChanged || controlledChanged || !getMap().hasLayer(this)); + const positionChanged = this.getData().position.lat != data.position.lat || this.getData().position.lng != data.position.lng; + const headingChanged = this.getData().heading != data.heading; + const aliveChanged = this.getData().alive != data.alive; + const stateChanged = this.getData().state != data.state; + const controlledChanged = this.getData().controlled != data.controlled; + var updateMarker = positionChanged || headingChanged || aliveChanged || stateChanged || controlledChanged || !getMap().hasLayer(this); - /* Load the data from the received json */ - Object.keys(this.#data).forEach((key1: string) => { - Object.keys(this.#data[key1 as keyof(UnitData)]).forEach((key2: string) => { - if (key1 in data && key2 in data[key1]) { - var value1 = this.#data[key1 as keyof(UnitData)]; - var value2 = value1[key2 as keyof typeof value1]; - if (typeof data[key1][key2] === typeof value2 || typeof value2 === "undefined") - //@ts-ignore - this.#data[key1 as keyof(UnitData)][key2 as keyof typeof struct] = data[key1][key2]; - } - }); - }); + /* Assign the data */ + /* TODO Allow for partial updates */ + this.#data = data; /* Fire an event when a unit dies */ - if (aliveChanged && this.getBaseData().alive == false) + if (aliveChanged && this.getData().alive == false) document.dispatchEvent(new CustomEvent("unitDeath", { detail: this })); /* Dead units can't be selected */ - this.setSelected(this.getSelected() && this.getBaseData().alive && !this.getHidden()) + this.setSelected(this.getSelected() && this.getData().alive && !this.getHidden()) if (updateMarker) this.#updateMarker(); + // TODO dont delete the polylines of the detected units this.#clearDetectedUnits(); if (this.getSelected()) { this.#drawPath(); @@ -253,7 +247,6 @@ export class Unit extends CustomMarker { this.#clearTarget(); } - document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this })); } @@ -261,30 +254,6 @@ export class Unit extends CustomMarker { return this.#data; } - getBaseData() { - return this.getData().baseData; - } - - getFlightData() { - return this.getData().flightData; - } - - getTaskData() { - return this.getData().taskData; - } - - getMissionData() { - return this.getData().missionData; - } - - getFormationData() { - return this.getData().formationData; - } - - getOptionsData() { - return this.getData().optionsData; - } - /********************** Icon *************************/ createIcon(): void { /* Set the icon */ @@ -298,7 +267,7 @@ export class Unit extends CustomMarker { var el = document.createElement("div"); el.classList.add("unit"); el.setAttribute("data-object", `unit-${this.getMarkerCategory()}`); - el.setAttribute("data-coalition", this.getMissionData().coalition); + el.setAttribute("data-coalition", this.getData().coalition); // Generate and append elements depending on active options // Velocity vector @@ -342,7 +311,7 @@ export class Unit extends CustomMarker { if (this.getIconOptions().showShortLabel) { var shortLabel = document.createElement("div"); shortLabel.classList.add("unit-short-label"); - shortLabel.innerText = getUnitDatabaseByCategory(this.getMarkerCategory())?.getByName(this.getBaseData().name)?.shortLabel || ""; + shortLabel.innerText = getUnitDatabaseByCategory(this.getMarkerCategory())?.getByName(this.getData().name)?.shortLabel || ""; el.append(shortLabel); } @@ -371,7 +340,7 @@ export class Unit extends CustomMarker { summary.classList.add("unit-summary"); var callsign = document.createElement("div"); callsign.classList.add("unit-callsign"); - callsign.innerText = this.getBaseData().unitName; + callsign.innerText = this.getData().unitName; var altitude = document.createElement("div"); altitude.classList.add("unit-altitude"); var speed = document.createElement("div"); @@ -389,15 +358,15 @@ export class Unit extends CustomMarker { updateVisibility() { var hidden = false; const hiddenUnits = getUnitsManager().getHiddenTypes(); - if (this.getMissionData().flags.Human && hiddenUnits.includes("human")) + if (this.getData().human && hiddenUnits.includes("human")) hidden = true; - else if (this.getBaseData().controlled == false && hiddenUnits.includes("dcs")) + else if (this.getData().controlled == false && hiddenUnits.includes("dcs")) hidden = true; else if (hiddenUnits.includes(this.getMarkerCategory())) hidden = true; - else if (hiddenUnits.includes(this.getMissionData().coalition)) + else if (hiddenUnits.includes(this.getData().coalition)) hidden = true; - this.setHidden(hidden || !this.getBaseData().alive); + this.setHidden(hidden || !this.getData().alive); } setHidden(hidden: boolean) { @@ -419,24 +388,24 @@ export class Unit extends CustomMarker { } getLeader() { - return getUnitsManager().getUnitByID(this.getFormationData().leaderID); + return getUnitsManager().getUnitByID(this.getData().leaderID); } canFulfillRole(roles: string | string[]) { if (typeof(roles) === "string") roles = [roles]; - return this.getDatabase()?.getByName(this.getBaseData().name)?.loadouts.some((loadout: LoadoutBlueprint) => { + return this.getDatabase()?.getByName(this.getData().name)?.loadouts.some((loadout: LoadoutBlueprint) => { return (roles as string[]).some((role: string) => {return loadout.roles.includes(role)}); }); } /********************** Unit commands *************************/ addDestination(latlng: L.LatLng) { - if (!this.getMissionData().flags.Human) { + if (!this.getData().human) { var path: any = {}; - if (this.getTaskData().activePath != undefined) { - path = this.getTaskData().activePath; + if (this.getData().activePath.length > 0) { + path = this.getData().activePath; path[(Object.keys(path).length + 1).toString()] = latlng; } else { @@ -447,86 +416,86 @@ export class Unit extends CustomMarker { } clearDestinations() { - if (!this.getMissionData().flags.Human) - this.getTaskData().activePath = undefined; + if (!this.getData().human) + this.getData().activePath = []; } attackUnit(targetID: number) { /* Units can't attack themselves */ - if (!this.getMissionData().flags.Human) + if (!this.getData().human) if (this.ID != targetID) attackUnit(this.ID, targetID); } followUnit(targetID: number, offset: { "x": number, "y": number, "z": number }) { /* Units can't follow themselves */ - if (!this.getMissionData().flags.Human) + if (!this.getData().human) if (this.ID != targetID) followUnit(this.ID, targetID, offset); } landAt(latlng: LatLng) { - if (!this.getMissionData().flags.Human) + if (!this.getData().human) landAt(this.ID, latlng); } changeSpeed(speedChange: string) { - if (!this.getMissionData().flags.Human) + if (!this.getData().human) changeSpeed(this.ID, speedChange); } changeAltitude(altitudeChange: string) { - if (!this.getMissionData().flags.Human) + if (!this.getData().human) changeAltitude(this.ID, altitudeChange); } setSpeed(speed: number) { - if (!this.getMissionData().flags.Human) + if (!this.getData().human) setSpeed(this.ID, speed); } setSpeedType(speedType: string) { - if (!this.getMissionData().flags.Human) + if (!this.getData().human) setSpeedType(this.ID, speedType); } setAltitude(altitude: number) { - if (!this.getMissionData().flags.Human) + if (!this.getData().human) setAltitude(this.ID, altitude); } setAltitudeType(altitudeType: string) { - if (!this.getMissionData().flags.Human) + if (!this.getData().human) setAltitudeType(this.ID, altitudeType); } setROE(ROE: string) { - if (!this.getMissionData().flags.Human) + if (!this.getData().human) setROE(this.ID, ROE); } setReactionToThreat(reactionToThreat: string) { - if (!this.getMissionData().flags.Human) + if (!this.getData().human) setReactionToThreat(this.ID, reactionToThreat); } setEmissionsCountermeasures(emissionCountermeasure: string) { - if (!this.getMissionData().flags.Human) + if (!this.getData().human) setEmissionsCountermeasures(this.ID, emissionCountermeasure); } setLeader(isLeader: boolean, wingmenIDs: number[] = []) { - if (!this.getMissionData().flags.Human) + if (!this.getData().human) setLeader(this.ID, isLeader, wingmenIDs); } setOnOff(onOff: boolean) { - if (!this.getMissionData().flags.Human) + if (!this.getData().human) setOnOff(this.ID, onOff); } setFollowRoads(followRoads: boolean) { - if (!this.getMissionData().flags.Human) + if (!this.getData().human) setFollowRoads(this.ID, followRoads); } @@ -535,12 +504,12 @@ export class Unit extends CustomMarker { } refuel() { - if (!this.getMissionData().flags.Human) + if (!this.getData().human) refuel(this.ID); } setAdvancedOptions(isTanker: boolean, isAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings) { - if (!this.getMissionData().flags.Human) + if (!this.getData().human) setAdvacedOptions(this.ID, isTanker, isAWACS, TACAN, radio, generalSettings); } @@ -565,7 +534,7 @@ export class Unit extends CustomMarker { super.onAdd(map); /* If this is the first time adding this unit to the map, remove the temporary marker */ if (getUnitsManager().getUnitByID(this.ID) == null) - getMap().removeTemporaryMarker(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude)); + getMap().removeTemporaryMarker(new LatLng(this.getData().position.lat, this.getData().position.lng)); return this; } @@ -602,12 +571,12 @@ export class Unit extends CustomMarker { options["follow"] = {text: "Follow", tooltip: "Follow the unit at a user defined distance and position"};; } else if ((selectedUnits.length > 0 && (selectedUnits.includes(this))) || selectedUnits.length == 0) { - if (this.getBaseData().category == "Aircraft") { + if (this.getData().category == "Aircraft") { options["refuel"] = {text: "Air to air refuel", tooltip: "Refuel unit at the nearest AAR Tanker. If no tanker is available the unit will RTB."}; // TODO Add some way of knowing which aircraft can AAR } } - if ((selectedUnits.length === 0 && this.getBaseData().category == "Aircraft") || (selectedUnitTypes.length === 1 && ["Aircraft"].includes(selectedUnitTypes[0]))) + if ((selectedUnits.length === 0 && this.getData().category == "Aircraft") || (selectedUnitTypes.length === 1 && ["Aircraft"].includes(selectedUnitTypes[0]))) { if (selectedUnits.concat([this]).every((unit: Unit) => {return unit.canFulfillRole(["CAS", "Strike"])})) { options["bomb"] = {text: "Precision bombing", tooltip: "Precision bombing of a specific point"}; @@ -615,7 +584,7 @@ export class Unit extends CustomMarker { } } - if ((selectedUnits.length === 0 && this.getBaseData().category == "GroundUnit") || selectedUnitTypes.length === 1 && ["GroundUnit"].includes(selectedUnitTypes[0])) { + if ((selectedUnits.length === 0 && this.getData().category == "GroundUnit") || selectedUnitTypes.length === 1 && ["GroundUnit"].includes(selectedUnitTypes[0])) { if (selectedUnits.concat([this]).every((unit: Unit) => {return unit.canFulfillRole(["Gun Artillery", "Rocket Artillery", "Infantry", "IFV", "Tank"])})) options["fire-at-area"] = {text: "Fire at area", tooltip: "Fire at a large area"}; } @@ -683,12 +652,12 @@ export class Unit extends CustomMarker { this.updateVisibility(); /* Draw the minimap marker */ - if (this.getBaseData().alive) { + if (this.getData().alive) { if (this.#miniMapMarker == null) { - this.#miniMapMarker = new CircleMarker(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude), { radius: 0.5 }); - if (this.getMissionData().coalition == "neutral") + this.#miniMapMarker = new CircleMarker(new LatLng(this.getData().position.lat, this.getData().position.lng), { radius: 0.5 }); + if (this.getData().coalition == "neutral") this.#miniMapMarker.setStyle({ color: "#CFD9E8" }); - else if (this.getMissionData().coalition == "red") + else if (this.getData().coalition == "red") this.#miniMapMarker.setStyle({ color: "#ff5858" }); else this.#miniMapMarker.setStyle({ color: "#247be2" }); @@ -696,7 +665,7 @@ export class Unit extends CustomMarker { this.#miniMapMarker.bringToBack(); } else { - this.#miniMapMarker.setLatLng(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude)); + this.#miniMapMarker.setLatLng(new LatLng(this.getData().position.lat, this.getData().position.lng)); this.#miniMapMarker.bringToBack(); } } @@ -709,39 +678,39 @@ export class Unit extends CustomMarker { /* Draw the marker */ if (!this.getHidden()) { - this.setLatLng(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude)); + this.setLatLng(new LatLng(this.getData().position.lat, this.getData().position.lng)); var element = this.getElement(); if (element != null) { /* Draw the velocity vector */ - element.querySelector(".unit-vvi")?.setAttribute("style", `height: ${15 + this.getFlightData().speed / 5}px;`); + element.querySelector(".unit-vvi")?.setAttribute("style", `height: ${15 + this.getData().speed / 5}px;`); /* Set fuel data */ - element.querySelector(".unit-fuel-level")?.setAttribute("style", `width: ${this.getMissionData().fuel}%`); - element.querySelector(".unit")?.toggleAttribute("data-has-low-fuel", this.getMissionData().fuel < 20); + element.querySelector(".unit-fuel-level")?.setAttribute("style", `width: ${this.getData().fuel}%`); + element.querySelector(".unit")?.toggleAttribute("data-has-low-fuel", this.getData().fuel < 20); /* Set dead/alive flag */ - element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.getBaseData().alive); + element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.getData().alive); /* Set current unit state */ - if (this.getMissionData().flags.Human) // Unit is human + if (this.getData().human) // Unit is human element.querySelector(".unit")?.setAttribute("data-state", "human"); - else if (!this.getBaseData().controlled) // Unit is under DCS control (not Olympus) + else if (!this.getData().controlled) // Unit is under DCS control (not Olympus) element.querySelector(".unit")?.setAttribute("data-state", "dcs"); - else if ((this.getBaseData().category == "Aircraft" || this.getBaseData().category == "Helicopter") && !this.getMissionData().hasTask) + else if ((this.getData().category == "Aircraft" || this.getData().category == "Helicopter") && !this.getData().hasTask) element.querySelector(".unit")?.setAttribute("data-state", "no-task"); - else // Unit is under Olympus control - element.querySelector(".unit")?.setAttribute("data-state", this.getTaskData().currentState.toLowerCase()); + else // Unit is under Olympus control + element.querySelector(".unit")?.setAttribute("data-state", this.getData().state.toLowerCase()); /* Set altitude and speed */ if (element.querySelector(".unit-altitude")) - (element.querySelector(".unit-altitude")).innerText = "FL" + String(Math.floor(mToFt(this.getFlightData().altitude) / 100)); + (element.querySelector(".unit-altitude")).innerText = "FL" + String(Math.floor(mToFt(this.getData().position.alt as number) / 100)); if (element.querySelector(".unit-speed")) - (element.querySelector(".unit-speed")).innerText = String(Math.floor(msToKnots(this.getFlightData().speed))) + "GS"; + (element.querySelector(".unit-speed")).innerText = String(Math.floor(msToKnots(this.getData().speed))) + "GS"; /* Rotate elements according to heading */ element.querySelectorAll("[data-rotate-to-heading]").forEach(el => { - const headingDeg = rad2deg(this.getFlightData().heading); + const headingDeg = rad2deg(this.getData().heading); let currentStyle = el.getAttribute("style") || ""; el.setAttribute("style", currentStyle + `transform:rotate(${headingDeg}deg);`); }); @@ -756,7 +725,7 @@ export class Unit extends CustomMarker { var newHasFox2 = false; var newHasFox3 = false; var newHasOtherAmmo = false; - Object.values(this.getMissionData().ammo).forEach((ammo: any) => { + Object.values(this.getData().ammo).forEach((ammo: any) => { if (ammo.desc.category == 1 && ammo.desc.missileCategory == 1) { if (ammo.desc.guidance == 4 || ammo.desc.guidance == 5) newHasFox1 = true; @@ -786,30 +755,30 @@ export class Unit extends CustomMarker { /* Set vertical offset for altitude stacking */ var pos = getMap().latLngToLayerPoint(this.getLatLng()).round(); - this.setZIndexOffset(1000 + Math.floor(this.getFlightData().altitude) - pos.y + (this.#highlighted || this.#selected ? 5000 : 0)); + this.setZIndexOffset(1000 + Math.floor(this.getData().position.alt as number) - pos.y + (this.#highlighted || this.#selected ? 5000 : 0)); } } #drawPath() { - if (this.getTaskData().activePath != undefined) { + if (this.getData().activePath != undefined) { var points = []; - points.push(new LatLng(this.getFlightData().latitude, this.getFlightData().longitude)); + points.push(new LatLng(this.getData().position.lat, this.getData().position.lng)); /* Add markers if missing */ - while (this.#pathMarkers.length < Object.keys(this.getTaskData().activePath).length) { + while (this.#pathMarkers.length < Object.keys(this.getData().activePath).length) { var marker = new Marker([0, 0], { icon: pathIcon }).addTo(getMap()); this.#pathMarkers.push(marker); } /* Remove markers if too many */ - while (this.#pathMarkers.length > Object.keys(this.getTaskData().activePath).length) { + while (this.#pathMarkers.length > Object.keys(this.getData().activePath).length) { getMap().removeLayer(this.#pathMarkers[this.#pathMarkers.length - 1]); this.#pathMarkers.splice(this.#pathMarkers.length - 1, 1) } /* Update the position of the existing markers (to avoid creating markers uselessly) */ - for (let WP in this.getTaskData().activePath) { - var destination = this.getTaskData().activePath[WP]; + for (let WP in this.getData().activePath) { + var destination = this.getData().activePath[WP]; this.#pathMarkers[parseInt(WP) - 1].setLatLng([destination.lat, destination.lng]); points.push(new LatLng(destination.lat, destination.lng)); this.#pathPolyline.setLatLngs(points); @@ -829,27 +798,25 @@ export class Unit extends CustomMarker { } #drawDetectedUnits() { - for (let index in this.getMissionData().contacts) { - var targetData = this.getMissionData().contacts[index]; - if (targetData.object != undefined){ - var target = getUnitsManager().getUnitByID(targetData.object["id_"]) - if (target != null) { - var startLatLng = new LatLng(this.getFlightData().latitude, this.getFlightData().longitude) - var endLatLng = new LatLng(target.getFlightData().latitude, target.getFlightData().longitude) + for (let index in this.getData().contacts) { + var targetData = this.getData().contacts[index]; + var target = getUnitsManager().getUnitByID(targetData.ID) + if (target != null) { + var startLatLng = new LatLng(this.getData().position.lat, this.getData().position.lng) + var endLatLng = new LatLng(target.getData().position.lat, target.getData().position.lng) - var color; - if (targetData.detectionMethod === "RADAR") - color = "#FFFF00"; - else if (targetData.detectionMethod === "VISUAL") - color = "#FF00FF"; - else if (targetData.detectionMethod === "RWR") - color = "#00FF00"; - else - color = "#FFFFFF"; - var targetPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 0.4, smoothFactor: 1, dashArray: "4, 8" }); - targetPolyline.addTo(getMap()); - this.#contactsPolylines.push(targetPolyline) - } + var color; + if (targetData.detectionMethod === 1) + color = "#FF00FF"; + else if (targetData.detectionMethod === 4) + color = "#FFFF00"; + else if (targetData.detectionMethod === 16) + color = "#00FF00"; + else + color = "#FFFFFF"; + var targetPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 0.4, smoothFactor: 1, dashArray: "4, 8" }); + targetPolyline.addTo(getMap()); + this.#contactsPolylines.push(targetPolyline) } } } @@ -861,40 +828,33 @@ export class Unit extends CustomMarker { } #drawTarget() { - const targetLocation = this.getTaskData().targetLocation; - - if (targetLocation.latitude && targetLocation.longitude && targetLocation.latitude != 0 && targetLocation.longitude != 0) { - const lat = targetLocation.latitude; - const lng = targetLocation.longitude; - if (lat && lng) - this.#drawTargetLocation(new LatLng(lat, lng)); + if (this.getData().targetPosition.lat != 0 && this.getData().targetPosition.lng != 0) { + this.#drawtargetPosition(this.getData().targetPosition); } - else if (this.getTaskData().targetID != 0 && getUnitsManager().getUnitByID(this.getTaskData().targetID)) { - const flightData = getUnitsManager().getUnitByID(this.getTaskData().targetID)?.getFlightData(); - const lat = flightData?.latitude; - const lng = flightData?.longitude; - if (lat && lng) - this.#drawTargetLocation(new LatLng(lat, lng)); + else if (this.getData().targetID != 0 && getUnitsManager().getUnitByID(this.getData().targetID)) { + const position = getUnitsManager().getUnitByID(this.getData().targetID)?.getData().position; + if (position) + this.#drawtargetPosition(position); } else this.#clearTarget(); } - #drawTargetLocation(targetLocation: LatLng) { - if (!getMap().hasLayer(this.#targetLocationMarker)) - this.#targetLocationMarker.addTo(getMap()); - if (!getMap().hasLayer(this.#targetLocationPolyline)) - this.#targetLocationPolyline.addTo(getMap()); - this.#targetLocationMarker.setLatLng(new LatLng(targetLocation.lat, targetLocation.lng)); - this.#targetLocationPolyline.setLatLngs([new LatLng(this.getFlightData().latitude, this.getFlightData().longitude), new LatLng(targetLocation.lat, targetLocation.lng)]) + #drawtargetPosition(targetPosition: LatLng) { + if (!getMap().hasLayer(this.#targetPositionMarker)) + this.#targetPositionMarker.addTo(getMap()); + if (!getMap().hasLayer(this.#targetPositionPolyline)) + this.#targetPositionPolyline.addTo(getMap()); + this.#targetPositionMarker.setLatLng(new LatLng(targetPosition.lat, targetPosition.lng)); + this.#targetPositionPolyline.setLatLngs([new LatLng(this.getData().position.lat, this.getData().position.lng), new LatLng(targetPosition.lat, targetPosition.lng)]) } #clearTarget() { - if (getMap().hasLayer(this.#targetLocationMarker)) - this.#targetLocationMarker.removeFrom(getMap()); + if (getMap().hasLayer(this.#targetPositionMarker)) + this.#targetPositionMarker.removeFrom(getMap()); - if (getMap().hasLayer(this.#targetLocationPolyline)) - this.#targetLocationPolyline.removeFrom(getMap()); + if (getMap().hasLayer(this.#targetPositionPolyline)) + this.#targetPositionPolyline.removeFrom(getMap()); } } @@ -915,7 +875,7 @@ export class AirUnit extends Unit { } export class Aircraft extends AirUnit { - constructor(ID: number, data: UpdateData) { + constructor(ID: number, data: UnitData) { super(ID, data); } @@ -925,7 +885,7 @@ export class Aircraft extends AirUnit { } export class Helicopter extends AirUnit { - constructor(ID: number, data: UpdateData) { + constructor(ID: number, data: UnitData) { super(ID, data); } @@ -935,7 +895,7 @@ export class Helicopter extends AirUnit { } export class GroundUnit extends Unit { - constructor(ID: number, data: UpdateData) { + constructor(ID: number, data: UnitData) { super(ID, data); } @@ -954,12 +914,12 @@ export class GroundUnit extends Unit { } getMarkerCategory() { - return getMarkerCategoryByName(this.getBaseData().name); + return getMarkerCategoryByName(this.getData().name); } } export class NavyUnit extends Unit { - constructor(ID: number, data: UpdateData) { + constructor(ID: number, data: UnitData) { super(ID, data); } @@ -983,7 +943,7 @@ export class NavyUnit extends Unit { } export class Weapon extends Unit { - constructor(ID: number, data: UpdateData) { + constructor(ID: number, data: UnitData) { super(ID, data); this.setSelectable(false); } @@ -1004,7 +964,7 @@ export class Weapon extends Unit { } export class Missile extends Weapon { - constructor(ID: number, data: UpdateData) { + constructor(ID: number, data: UnitData) { super(ID, data); } @@ -1014,7 +974,7 @@ export class Missile extends Weapon { } export class Bomb extends Weapon { - constructor(ID: number, data: UpdateData) { + constructor(ID: number, data: UnitData) { super(ID, data); } diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index 2bb85f3c..2f56b189 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -7,6 +7,8 @@ import { CoalitionArea } from "../map/coalitionarea"; import { Airbase } from "../missionhandler/airbase"; import { groundUnitsDatabase } from "./groundunitsdatabase"; import { IADSRoles, IDLE, MOVE_UNIT } from "../constants/constants"; +import { DataExtractor } from "./dataextractor"; +import { UnitData } from "../@types/unit"; export class UnitsManager { #units: { [ID: number]: Unit }; @@ -31,7 +33,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(); + const baseData = units[unitId].getData(); if (baseData.category === "Aircraft" && baseData.alive === true) { acc[unitId] = units[unitId]; } @@ -51,13 +53,13 @@ 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.getData().alive && 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); + if (data.category){ + /* The name of the unit category is exactly the same as the constructor name */ + var constructor = Unit.getConstructor(data.category); if (constructor != undefined) { this.#units[ID] = new constructor(ID, data); } @@ -68,82 +70,42 @@ export class UnitsManager { } - update(data: string) { + update(encodedData: string) { var updatedUnits: Unit[] = []; - var buffer = base64ToBytes(data); - - /*Coords position; - double speed; - double heading; - unsigned short fuel; - double desiredSpeed; - double desiredAltitude; - unsigned int targetID; - Coords targetPosition; - unsigned char state; - unsigned char ROE; - unsigned char reactionToThreat; - unsigned char emissionsCountermeasures; - Options::TACAN TACAN; - Options::Radio Radio; - unsigned short pathLength; - unsigned char nameLength; - unsigned char unitNameLength; - unsigned char groupNameLength; - unsigned char categoryLength; - unsigned char coalitionLength;*/ + var buffer = base64ToBytes(encodedData); + var dataExtractor = new DataExtractor(buffer); + var data: {[key: string]: UnitData} = {}; var offset = 0; - var dataview = new DataView(buffer); - const ID = dataview.getUint32(offset, true); offset += 4; - const bitmask = dataview.getUint32(offset , true); offset += 4; - const alive = bitmask & (1 << 0); - const human = bitmask >> 1 & 1; - const controlled = bitmask >> 2 & 1; - const hasTask = bitmask >> 3 & 1; - const desiredAltitudeType = bitmask >> 16 & 1; - const desiredSpeedType = bitmask >> 17 & 1; - const isTanker = bitmask >> 18 & 1; - const isAWACS = bitmask >> 19 & 1; - const onOff = bitmask >> 20 & 1; - const followRoads = bitmask >> 21 & 1; - const EPLRS = bitmask >> 22 & 1; - const prohibitAA = bitmask >> 23 & 1; - const prohibitAfterburner = bitmask >> 24 & 1; - const prohibitAG = bitmask >> 25 & 1; - const prohibitAirWpn = bitmask >> 26 & 1; - const prohibitJettison = bitmask >> 27 & 1; + while (offset < buffer.byteLength) { + const result = dataExtractor.extractData(offset); + data[result.data.ID] = result.data; + offset = result.offset; + } - const latitude = dataview.getFloat64(offset , true); offset += 8; - const longitude = dataview.getFloat64(offset , true); offset += 8; - const altitude = dataview.getFloat64(offset , true); offset += 8; - const speed = dataview.getFloat64(offset , true); offset += 8; - const heading = dataview.getFloat64(offset , true); offset += 8; - - - var foo = 12; - /*Object.keys(data.units) + Object.keys(data) .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]); + this.addUnit(parseInt(ID), data[ID]); + this.#units[parseInt(ID)]?.setData(data[ID]); }, timeout); return timeout + 10; }, 10); - Object.keys(data.units) + Object.keys(data) .filter((ID: string) => ID in this.#units) .forEach((ID: string) => { updatedUnits.push(this.#units[parseInt(ID)]); - this.#units[parseInt(ID)]?.setData(data.units[ID]) + this.#units[parseInt(ID)]?.setData(data[ID]); }); - this.getSelectedUnits().forEach((unit: Unit) => { - if (!updatedUnits.includes(unit)) - unit.setData({}) - });*/ + // TODO why did we do this? + //this.getSelectedUnits().forEach((unit: Unit) => { + // if (!updatedUnits.includes(unit)) + // unit.setData(null); + //}); } setHiddenType(key: string, value: boolean) { @@ -170,7 +132,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].getData().position.lat, this.#units[ID].getData().position.lng); if (bounds.contains(latlng)) { this.#units[ID].setSelected(true); } @@ -187,11 +149,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.getData().human }); 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.getData().groupName == otherUnit.getData().groupName)) temp.push(unit); } selectedUnits = temp; @@ -236,7 +198,7 @@ export class UnitsManager { if (this.getSelectedUnits().length == 0) return undefined; return this.getSelectedUnits().map((unit: Unit) => { - return unit.getMissionData().coalition + return unit.getData().coalition })?.reduce((a: any, b: any) => { return a == b ? a : undefined }); @@ -256,8 +218,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.getData().state === "Follow") { + const leader = this.getUnitByID(unit.getData().leaderID) if (leader && leader.getSelected()) leader.addDestination(latlng); else @@ -276,8 +238,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.getData().state === "Follow") { + const leader = this.getUnitByID(unit.getData().leaderID) if (leader && leader.getSelected()) leader.clearDestinations(); else @@ -388,13 +350,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)?.getData().unitName}`); } 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.getData().human === 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?" ) ) { @@ -455,7 +417,7 @@ export class UnitsManager { } count++; } - this.#showActionMessage(selectedUnits, `following unit ${this.getUnitByID(ID)?.getBaseData().unitName}`); + this.#showActionMessage(selectedUnits, `following unit ${this.getUnitByID(ID)?.getData().unitName}`); } selectedUnitsSetHotgroup(hotgroup: number) { @@ -477,7 +439,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.getData().position.lat, unit.getData().position.lng); center.x += mercator.x / selectedUnits.length; center.y += mercator.y / selectedUnits.length; }); @@ -485,7 +447,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.getData().position.lat, unit.getData().position.lat); var distancesFromCenter = { dx: mercator.x - center.x, dy: mercator.y - center.y }; /* Rotate the distance according to the group rotation */ @@ -615,8 +577,8 @@ export class UnitsManager { #showActionMessage(units: Unit[], message: string) { if (units.length == 1) - getInfoPopup().setText(`${units[0].getBaseData().unitName} ${message}`); + getInfoPopup().setText(`${units[0].getData().unitName} ${message}`); else if (units.length > 1) - getInfoPopup().setText(`${units[0].getBaseData().unitName} and ${units.length - 1} other units ${message}`); + getInfoPopup().setText(`${units[0].getData().unitName} and ${units.length - 1} other units ${message}`); } } \ No newline at end of file diff --git a/src/core/include/unit.h b/src/core/include/unit.h index f8f5241d..bdf55afc 100644 --- a/src/core/include/unit.h +++ b/src/core/include/unit.h @@ -59,7 +59,7 @@ namespace Options { namespace DataTypes { struct Ammo { unsigned short quantity = 0; - string name; + char name[32]; unsigned char guidance = 0; unsigned char category = 0; unsigned char missileCategory = 0; @@ -88,6 +88,8 @@ namespace DataTypes { Options::TACAN TACAN; Options::Radio Radio; unsigned short pathLength; + unsigned short ammoLength; + unsigned short contactsLength; unsigned char nameLength; unsigned char unitNameLength; unsigned char groupNameLength; diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp index bc99edbd..b5580505 100644 --- a/src/core/src/unit.cpp +++ b/src/core/src/unit.cpp @@ -180,8 +180,13 @@ unsigned int Unit::getUpdateData(char* &data) /* Reserve data for: 1) DataPacket; 2) Active path; + 3) Ammo vector; + 4) Contacts vector; */ - data = (char*)malloc(sizeof(DataTypes::DataPacket) + activePath.size() * sizeof(Coords)); + data = (char*)malloc(sizeof(DataTypes::DataPacket) + + activePath.size() * sizeof(Coords) + + ammo.size() * sizeof(Coords) + + contacts.size() * sizeof(Coords)); unsigned int offset = 0; /* Prepare the data packet and copy it to memory */ @@ -221,6 +226,8 @@ unsigned int Unit::getUpdateData(char* &data) TACAN, radio, activePath.size(), + ammo.size(), + contacts.size(), name.size(), unitName.size(), groupName.size(), @@ -231,14 +238,21 @@ unsigned int Unit::getUpdateData(char* &data) memcpy(data + offset, &dataPacket, sizeof(dataPacket)); offset += sizeof(dataPacket); - /* Prepare the path memory and copy it to memory */ + /* Prepare the path vector and copy it to memory */ std::vector path; for (const Coords& c : activePath) path.push_back(c); - memcpy(data + offset, &path, activePath.size() * sizeof(Coords)); offset += activePath.size() * sizeof(Coords); + /* Copy the ammo vector to memory */ + memcpy(data + offset, &ammo, ammo.size() * sizeof(DataTypes::Ammo)); + offset += ammo.size() * sizeof(DataTypes::Ammo); + + /* Copy the contacts vector to memory */ + memcpy(data + offset, &contacts, contacts.size() * sizeof(DataTypes::Contact)); + offset += contacts.size() * sizeof(DataTypes::Contact); + return offset; } From 1989219579d391a98450e7a92a472aeae5f639ac Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Sun, 25 Jun 2023 19:04:12 +0200 Subject: [PATCH 09/15] Fixed update error and added update trigger --- client/src/@types/unit.d.ts | 4 +- client/src/map/map.ts | 2 +- client/src/server/server.ts | 39 +++--- client/src/units/dataextractor.ts | 184 ++++++++++++++++------------- client/src/units/unit.ts | 11 +- client/src/units/unitsmanager.ts | 12 +- scripts/OlympusCommand.lua | 2 +- src/.vscode/settings.json | 8 -- src/core/include/unit.h | 114 ++++++++++-------- src/core/include/unitsmanager.h | 2 +- src/core/src/airunit.cpp | 22 ++-- src/core/src/core.cpp | 5 +- src/core/src/groundunit.cpp | 4 +- src/core/src/scheduler.cpp | 44 +++---- src/core/src/server.cpp | 53 +++++---- src/core/src/unit.cpp | 190 ++++++++++++++++-------------- src/core/src/unitsmanager.cpp | 5 +- src/utils/include/utils.h | 2 + 18 files changed, 385 insertions(+), 318 deletions(-) delete mode 100644 src/.vscode/settings.json diff --git a/client/src/@types/unit.d.ts b/client/src/@types/unit.d.ts index 0c7d400a..703b8852 100644 --- a/client/src/@types/unit.d.ts +++ b/client/src/@types/unit.d.ts @@ -52,8 +52,8 @@ interface UnitData { human: boolean, controlled: boolean, hasTask: boolean, - desiredAltitudeType: boolean, - desiredSpeedType: boolean, + desiredAltitudeType: string, + desiredSpeedType: string, isTanker: boolean, isAWACS: boolean, onOff: boolean, diff --git a/client/src/map/map.ts b/client/src/map/map.ts index 30c0cc0c..c81c2e82 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -671,7 +671,7 @@ export class Map extends L.Map { /* 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(!e.originalEvent.shiftKey); + else if (this.#state === MOVE_UNIT) this.#showDestinationCursors(e && e.originaleEvent && !e.originalEvent.shiftKey); else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) this.#showTargetCursor(); else if (this.#state === COALITIONAREA_DRAW_POLYGON) this.#showDrawingCursor(); } diff --git a/client/src/server/server.ts b/client/src/server/server.ts index ca392e5f..533053fe 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -2,6 +2,7 @@ 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; @@ -38,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"); @@ -96,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); } @@ -223,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, () => { }); } @@ -300,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.units), 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(); @@ -309,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.units); - checkSessionHash(data.sessionHash); + getUnitsManager()?.update(buffer); } }, false); window.setTimeout(() => requestUpdate(), getConnected() ? 250 : 1000); diff --git a/client/src/units/dataextractor.ts b/client/src/units/dataextractor.ts index 8b45a504..d3265a9a 100644 --- a/client/src/units/dataextractor.ts +++ b/client/src/units/dataextractor.ts @@ -17,85 +17,81 @@ export class DataExtractor { extractData(offset: number) { this.#offset = offset; - const ID = this.#extractUInt32(); - const bitmask = this.#extractUInt32(); + const ID = this.extractUInt32(); + const bitmask = this.extractUInt32(); const unitData: UnitData = { ID: ID, - alive: this.#extractFromBitmask(bitmask, 0), - human: this.#extractFromBitmask(bitmask, 1), - controlled: this.#extractFromBitmask(bitmask, 2), - hasTask: this.#extractFromBitmask(bitmask, 3), - desiredAltitudeType: this.#extractFromBitmask(bitmask, 16), - desiredSpeedType: this.#extractFromBitmask(bitmask, 17), - isTanker: this.#extractFromBitmask(bitmask, 18), - isAWACS: this.#extractFromBitmask(bitmask, 19), - onOff: this.#extractFromBitmask(bitmask, 20), - followRoads: this.#extractFromBitmask(bitmask, 21), - EPLRS: this.#extractFromBitmask(bitmask, 22), + alive: this.extractFromBitmask(bitmask, 0), + human: this.extractFromBitmask(bitmask, 1), + controlled: this.extractFromBitmask(bitmask, 2), + hasTask: this.extractFromBitmask(bitmask, 3), + desiredAltitudeType: this.extractFromBitmask(bitmask, 16)? "AGL": "ASL", + desiredSpeedType: this.extractFromBitmask(bitmask, 17)? "GS": "CAS", + isTanker: this.extractFromBitmask(bitmask, 18), + isAWACS: this.extractFromBitmask(bitmask, 19), + onOff: this.extractFromBitmask(bitmask, 20), + followRoads: this.extractFromBitmask(bitmask, 21), + EPLRS: this.extractFromBitmask(bitmask, 22), generalSettings: { - prohibitAA: this.#extractFromBitmask(bitmask, 23), - prohibitAfterburner: this.#extractFromBitmask(bitmask, 24), - prohibitAG: this.#extractFromBitmask(bitmask, 25), - prohibitAirWpn: this.#extractFromBitmask(bitmask, 26), - prohibitJettison: this.#extractFromBitmask(bitmask, 27), + prohibitAA: this.extractFromBitmask(bitmask, 23), + prohibitAfterburner: this.extractFromBitmask(bitmask, 24), + prohibitAG: this.extractFromBitmask(bitmask, 25), + prohibitAirWpn: this.extractFromBitmask(bitmask, 26), + prohibitJettison: this.extractFromBitmask(bitmask, 27), }, position: new LatLng( - this.#extractFloat64(), - this.#extractFloat64(), - this.#extractFloat64() + this.extractFloat64(), + this.extractFloat64(), + this.extractFloat64() ), - speed: this.#extractFloat64(), - heading: this.#extractFloat64(), - fuel: this.#extractUInt16(), - desiredSpeed: this.#extractFloat64(), - desiredAltitude: this.#extractFloat64(), - targetID: this.#extractUInt32(), - leaderID: 0, + speed: this.extractFloat64(), + heading: this.extractFloat64(), + fuel: this.extractUInt16(), + desiredSpeed: this.extractFloat64(), + desiredAltitude: this.extractFloat64(), + leaderID: this.extractUInt32(), + targetID: this.extractUInt32(), targetPosition: new LatLng( - this.#extractFloat64(), - this.#extractFloat64(), - this.#extractFloat64() + this.extractFloat64(), + this.extractFloat64(), + this.extractFloat64() ), - state: this.#getState(this.#extractUint8()), - ROE: this.#getROE(this.#extractUint8()), - reactionToThreat: this.#getReactionToThreat(this.#extractUint8()), - emissionsCountermeasures: this.#getEmissionCountermeasure(this.#extractUint8()), + state: this.#getState(this.extractUInt8()), + ROE: this.#getROE(this.extractUInt8()), + reactionToThreat: this.#getReactionToThreat(this.extractUInt8()), + emissionsCountermeasures: this.#getEmissionCountermeasure(this.extractUInt8()), + coalition: this.#getCoalition(this.extractUInt8()), TACAN: { - isOn: this.#extractBool(), - channel: this.#extractUint8(), - XY: this.#extractChar(), - callsign: this.#extractString(4) + isOn: this.extractBool(), + channel: this.extractUInt8(), + XY: this.extractChar(), + callsign: this.extractString(4) }, radio: { - frequency: this.#extractUInt32(), - callsign: this.#extractUint8(), - callsignNumber: this.#extractUint8() + frequency: this.extractUInt32(), + callsign: this.extractUInt8(), + callsignNumber: this.extractUInt8() }, activePath: [], ammo: [], contacts: [], + task: "", name: "", unitName: "", groupName: "", category: "", - coalition: "", - task: "" } - const pathLength = this.#extractUInt16(); - const ammoLength = this.#extractUInt16(); - const contactsLength = this.#extractUInt16(); - const nameLength = this.#extractUInt16(); - const unitNameLength = this.#extractUInt16(); - const groupNameLength = this.#extractUInt16(); - const categoryLength = this.#extractUInt16(); - const coalitionLength = this.#extractUInt16(); + const pathLength = this.extractUInt16(); + const ammoLength = this.extractUInt16(); + const contactsLength = this.extractUInt16(); + const taskLength = this.extractUInt8(); if (pathLength > 0) { unitData.activePath = []; for (let idx = 0; idx < pathLength; idx++) { - unitData.activePath.push(new LatLng(this.#extractFloat64(), this.#extractFloat64(), this.#extractFloat64())); + unitData.activePath.push(new LatLng(this.extractFloat64(), this.extractFloat64(), this.extractFloat64())); } } @@ -103,11 +99,11 @@ export class DataExtractor { unitData.ammo = []; for (let idx = 0; idx < pathLength; idx++) { unitData.ammo.push({ - quantity: this.#extractUInt16(), - name: this.#extractString(32), - guidance: this.#extractUint8(), - category: this.#extractUint8(), - missileCategory: this.#extractUint8() + quantity: this.extractUInt16(), + name: this.extractString(32), + guidance: this.extractUInt8(), + category: this.extractUInt8(), + missileCategory: this.extractUInt8() }); } } @@ -116,77 +112,92 @@ export class DataExtractor { unitData.contacts = []; for (let idx = 0; idx < pathLength; idx++) { unitData.contacts.push({ - ID: this.#extractUInt32(), - detectionMethod: this.#extractUint8() + ID: this.extractUInt32(), + detectionMethod: this.extractUInt8() }); } } + + if (taskLength > 0) { + unitData.task = this.extractString(taskLength); + } + const nameLength = this.extractUInt16(); + const unitNameLength = this.extractUInt16(); + const groupNameLength = this.extractUInt16(); + const categoryLength = this.extractUInt16(); + if (nameLength > 0) { - unitData.name = this.#extractString(nameLength); + unitData.name = this.extractString(nameLength); } if (unitNameLength > 0) { - unitData.unitName = this.#extractString(unitNameLength); + unitData.unitName = this.extractString(unitNameLength); } if (groupNameLength > 0) { - unitData.groupName = this.#extractString(groupNameLength); + unitData.groupName = this.extractString(groupNameLength); } if (categoryLength > 0) { - unitData.category = this.#extractString(categoryLength); - } - - if (coalitionLength > 0) { - unitData.coalition = this.#extractString(coalitionLength); + unitData.category = this.extractString(categoryLength); } return {data: unitData, offset: this.#offset}; } - #extractBool() { + extractBool() { const value = this.#dataview.getUint8(this.#offset); this.#offset += 1; return value > 0; } - #extractUint8() { + extractUInt8() { const value = this.#dataview.getUint8(this.#offset); this.#offset += 1; return value; } - #extractUInt16() { - const value = this.#dataview.getUint16(this.#offset); + extractUInt16() { + const value = this.#dataview.getUint16(this.#offset, true); this.#offset += 2; return value; } - #extractUInt32() { - const value = this.#dataview.getUint32(this.#offset); + extractUInt32() { + const value = this.#dataview.getUint32(this.#offset, true); this.#offset += 4; return value; } - #extractFloat64() { - const value = this.#dataview.getFloat64(this.#offset); + extractUInt64() { + const value = this.#dataview.getBigUint64(this.#offset, true); this.#offset += 8; return value; } - #extractFromBitmask(bitmask: number, position: number) { - return (bitmask >> position & 1) > 0; + extractFloat64() { + const value = this.#dataview.getFloat64(this.#offset, true); + this.#offset += 8; + return value; } - #extractString(length: number) { - const value = this.#decoder.decode(this.#buffer.slice(this.#offset, length)); + extractFromBitmask(bitmask: number, position: number) { + return ((bitmask >> position) & 1) > 0; + } + + extractString(length: number) { + const value = this.#decoder.decode(this.#buffer.slice(this.#offset, this.#offset +length)); this.#offset += length; return value; } - #extractChar() { - return this.#extractString(1); + extractChar() { + return this.extractString(1); + } + + getOffset() { + return this.#offset; } #getState(state: number) { @@ -216,4 +227,13 @@ export class DataExtractor { else return emissionsCountermeasures[0]; } + + #getCoalition(coalitionID: number) { + switch (coalitionID){ + case 0: return "neutral"; + case 1: return "red"; + case 2: return "blue"; + } + return ""; + } } \ No newline at end of file diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index 4d2416ce..c71d0d3f 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -24,8 +24,8 @@ export class Unit extends CustomMarker { human: false, controlled: false, hasTask: false, - desiredAltitudeType: false, - desiredSpeedType: false, + desiredAltitudeType: "AGL", + desiredSpeedType: "GS", isTanker: false, isAWACS: false, onOff: false, @@ -222,7 +222,6 @@ export class Unit extends CustomMarker { var updateMarker = positionChanged || headingChanged || aliveChanged || stateChanged || controlledChanged || !getMap().hasLayer(this); /* Assign the data */ - /* TODO Allow for partial updates */ this.#data = data; /* Fire an event when a unit dies */ @@ -406,10 +405,10 @@ export class Unit extends CustomMarker { var path: any = {}; if (this.getData().activePath.length > 0) { path = this.getData().activePath; - path[(Object.keys(path).length + 1).toString()] = latlng; + path[(Object.keys(path).length).toString()] = latlng; } else { - path = { "1": latlng }; + path = [latlng]; } addDestination(this.ID, path); } @@ -779,7 +778,7 @@ export class Unit extends CustomMarker { /* Update the position of the existing markers (to avoid creating markers uselessly) */ for (let WP in this.getData().activePath) { var destination = this.getData().activePath[WP]; - this.#pathMarkers[parseInt(WP) - 1].setLatLng([destination.lat, destination.lng]); + this.#pathMarkers[parseInt(WP)].setLatLng([destination.lat, destination.lng]); points.push(new LatLng(destination.lat, destination.lng)); this.#pathPolyline.setLatLngs(points); } diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index 2f56b189..c9124df6 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -1,8 +1,8 @@ import { LatLng, LatLngBounds } from "leaflet"; import { getHotgroupPanel, getInfoPopup, getMap, getMissionHandler } from ".."; import { Unit } from "./unit"; -import { cloneUnit, spawnGroundUnit } from "../server/server"; -import { base64ToBytes, deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots, polygonArea, randomPointInPoly, randomUnitBlueprintByRole } from "../other/utils"; +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"; @@ -70,13 +70,13 @@ export class UnitsManager { } - update(encodedData: string) { + update(buffer: ArrayBuffer) { var updatedUnits: Unit[] = []; - var buffer = base64ToBytes(encodedData); var dataExtractor = new DataExtractor(buffer); var data: {[key: string]: UnitData} = {}; - var offset = 0; + var updateTime = Number(dataExtractor.extractUInt64()); + var offset = dataExtractor.getOffset(); while (offset < buffer.byteLength) { const result = dataExtractor.extractData(offset); data[result.data.ID] = result.data; @@ -106,6 +106,8 @@ export class UnitsManager { // if (!updatedUnits.includes(unit)) // unit.setData(null); //}); + + setLastUpdateTime(updateTime); } setHiddenType(key: string, value: boolean) { diff --git a/scripts/OlympusCommand.lua b/scripts/OlympusCommand.lua index 50772ef6..7f086d6e 100644 --- a/scripts/OlympusCommand.lua +++ b/scripts/OlympusCommand.lua @@ -1,6 +1,6 @@ local version = "v0.3.0-alpha" -local debug = false +local debug = FALSE Olympus.unitCounter = 1 Olympus.payloadRegistry = {} diff --git a/src/.vscode/settings.json b/src/.vscode/settings.json deleted file mode 100644 index e8ff2462..00000000 --- a/src/.vscode/settings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "files.associations": { - "*.ejs": "html", - "xstring": "cpp", - "vector": "cpp", - "list": "cpp" - } -} \ No newline at end of file diff --git a/src/core/include/unit.h b/src/core/include/unit.h index bdf55afc..2022b0d4 100644 --- a/src/core/include/unit.h +++ b/src/core/include/unit.h @@ -7,6 +7,9 @@ #include "logger.h" #include "commands.h" +#include +using namespace std::chrono; + #define TASK_CHECK_INIT_VALUE 10 namespace State @@ -79,8 +82,10 @@ namespace DataTypes { unsigned short fuel; double desiredSpeed; double desiredAltitude; + unsigned int leaderID; unsigned int targetID; Coords targetPosition; + unsigned char coalition; unsigned char state; unsigned char ROE; unsigned char reactionToThreat; @@ -90,11 +95,7 @@ namespace DataTypes { unsigned short pathLength; unsigned short ammoLength; unsigned short contactsLength; - unsigned char nameLength; - unsigned char unitNameLength; - unsigned char groupNameLength; - unsigned char categoryLength; - unsigned char coalitionLength; + unsigned char taskLength; }; } #pragma pack(pop) @@ -112,18 +113,18 @@ public: void runAILoop(); void updateExportData(json::value json, double dt = 0); void updateMissionData(json::value json); - unsigned int getUpdateData(char* &data); - void getData(stringstream &ss, bool refresh); + unsigned int getDataPacket(char* &data); + void getData(stringstream &ss, unsigned long long time, bool refresh); virtual string getCategory() { return "No category"; }; /********** Base data **********/ - void setControlled(bool newControlled) { controlled = newControlled; } - void setName(string newName) { name = newName; } - void setUnitName(string newUnitName) { unitName = newUnitName; } - void setGroupName(string newGroupName) { groupName = newGroupName; } - void setAlive(bool newAlive) { alive = newAlive; } - void setCountry(unsigned int newCountry) { country = newCountry; } - void setHuman(bool newHuman) { human = newHuman; } + void setControlled(bool newValue) { updateValue(controlled, newValue); } + void setName(string newValue) { updateValue(name, newValue); } + void setUnitName(string newValue) { updateValue(unitName, newValue); } + void setGroupName(string newValue) { updateValue(groupName, newValue); } + void setAlive(bool newValue) { updateValue(alive, newValue); } + void setCountry(unsigned int newValue) { updateValue(country, newValue); } + void setHuman(bool newValue) { updateValue(human, newValue); } bool getControlled() { return controlled; } string getName() { return name; } @@ -134,51 +135,50 @@ public: bool getHuman() { return human; } /********** Flight data **********/ - void setPosition(Coords newPosition) { position = newPosition; } - void setHeading(double newHeading) {heading = newHeading; } - void setSpeed(double newSpeed) {speed = newSpeed; } + void setPosition(Coords newValue) { updateValue(position, newValue); } + void setHeading(double newValue) { updateValue(heading, newValue); } + void setSpeed(double newValue) { updateValue(speed, newValue); } Coords getPosition() { return position; } double getHeading() { return heading; } double getSpeed() { return speed; } /********** Mission data **********/ - void setFuel(short newFuel) { fuel = newFuel; } + void setFuel(unsigned short newValue) { updateValue(fuel, newValue); } void setAmmo(vector newAmmo) { ammo = newAmmo; } - void setContacts(vector newContacts) {contacts = newContacts; } - void setHasTask(bool newHasTask); - void setCoalitionID(unsigned int newCoalitionID); + void setContacts(vector newContacts) { contacts = newContacts; } + void setHasTask(bool newValue) { updateValue(hasTask, newValue); } + void setCoalition(unsigned char newValue) { updateValue(coalition, newValue);} double getFuel() { return fuel; } vector getAmmo() { return ammo; } vector getTargets() { return contacts; } bool getHasTask() { return hasTask; } - string getCoalition() { return coalition; } - unsigned int getCoalitionID(); + unsigned int getCoalition() { return coalition; } /********** Formation data **********/ - void setLeaderID(unsigned int newLeaderID) { leaderID = newLeaderID; } + void setLeaderID(unsigned int newValue) { updateValue(leaderID, newValue); } void setFormationOffset(Offset formationOffset); unsigned int getLeaderID() { return leaderID; } Offset getFormationoffset() { return formationOffset; } /********** Task data **********/ - void setCurrentTask(string newCurrentTask) { currentTask = newCurrentTask; } - void setDesiredSpeed(double newDesiredSpeed); - void setDesiredAltitude(double newDesiredAltitude); - void setDesiredSpeedType(string newDesiredSpeedType); - void setDesiredAltitudeType(string newDesiredAltitudeType); - void setActiveDestination(Coords newActiveDestination) { activeDestination = newActiveDestination; } - void setActivePath(list newActivePath); - void setTargetID(unsigned int newTargetID) { targetID = newTargetID; } - void setTargetPosition(Coords newTargetPosition); - void setIsTanker(bool newIsTanker); - void setIsAWACS(bool newIsAWACS); - virtual void setOnOff(bool newOnOff) { onOff = newOnOff; }; - virtual void setFollowRoads(bool newFollowRoads) { followRoads = newFollowRoads; }; + void setTask(string newValue) { updateValue(task, newValue); } + void setDesiredSpeed(double newValue); + void setDesiredAltitude(double newValue); + void setDesiredSpeedType(string newValue); + void setDesiredAltitudeType(string newValue); + void setActiveDestination(Coords newValue) { updateValue(activeDestination, newValue); } + void setActivePath(list newValue); + void setTargetID(unsigned int newValue) { updateValue(targetID, newValue); } + void setTargetPosition(Coords newValue) { updateValue(targetPosition, newValue); } + void setIsTanker(bool newValue); + void setIsAWACS(bool newValue); + virtual void setOnOff(bool newValue) { updateValue(onOff, newValue); }; + virtual void setFollowRoads(bool newValue) { updateValue(followRoads, newValue); }; - string getCurrentTask() { return currentTask; } + string getTask() { return task; } virtual double getDesiredSpeed() { return desiredSpeed; }; virtual double getDesiredAltitude() { return desiredAltitude; }; virtual bool getDesiredSpeedType() { return desiredSpeedType; }; @@ -193,13 +193,13 @@ public: bool getFollowRoads() { return followRoads; }; /********** Options data **********/ - void setROE(unsigned char newROE, bool force = false); - void setReactionToThreat(unsigned char newReactionToThreat, bool force = false); - void setEmissionsCountermeasures(unsigned char newEmissionsCountermeasures, bool force = false); - void setTACAN(Options::TACAN newTACAN, bool force = false); - void setRadio(Options::Radio newradio, bool force = false); - void setGeneralSettings(Options::GeneralSettings newGeneralSettings, bool force = false); - void setEPLRS(bool newEPLRS, bool force = false); + void setROE(unsigned char newValue, bool force = false); + void setReactionToThreat(unsigned char newValue, bool force = false); + void setEmissionsCountermeasures(unsigned char newValue, bool force = false); + void setTACAN(Options::TACAN newValue, bool force = false); + void setRadio(Options::Radio newValue, bool force = false); + void setGeneralSettings(Options::GeneralSettings newValue, bool force = false); + void setEPLRS(bool newValue, bool force = false); unsigned char getROE() { return ROE; } unsigned char getReactionToThreat() { return reactionToThreat; } @@ -220,6 +220,8 @@ public: void pushActivePathFront(Coords newActivePathFront); void pushActivePathBack(Coords newActivePathBack); void popActivePathFront(); + void triggerUpdate() { lastUpdateTime = duration_cast(system_clock::now().time_since_epoch()).count(); } + unsigned long long getLastUpdateTime() { return lastUpdateTime; }; protected: unsigned int ID; @@ -247,18 +249,18 @@ protected: vector ammo; vector contacts; bool hasTask = false; - string coalition = ""; + unsigned char coalition; /********** Formation data **********/ unsigned int leaderID = NULL; Offset formationOffset = Offset(NULL); /********** Task data **********/ - string currentTask = ""; + string task = ""; double desiredSpeed = 0; double desiredAltitude = 0; - bool desiredSpeedType = 0; - bool desiredAltitudeType = 0; + bool desiredSpeedType = 1; + bool desiredAltitudeType = 1; list activePath; Coords activeDestination = Coords(NULL); unsigned int targetID = NULL; @@ -277,10 +279,14 @@ protected: Options::GeneralSettings generalSettings; bool EPLRS = false; + /********** Data packet **********/ + DataTypes::DataPacket dataPacket; + /********** State machine **********/ unsigned char state = State::NONE; /********** Other **********/ + unsigned long long lastUpdateTime = 0; Coords oldPosition = Coords(0); // Used to approximate speed /********** Functions **********/ @@ -295,4 +301,14 @@ protected: void goToDestination(string enrouteTask = "nil"); bool checkTaskFailed(); void resetTaskFailedCounter(); + + template + void updateValue(T& value, T& newValue) + { + if (newValue != value) + { + triggerUpdate(); + *(&value) = newValue; + } + } }; diff --git a/src/core/include/unitsmanager.h b/src/core/include/unitsmanager.h index b2233183..081545a9 100644 --- a/src/core/include/unitsmanager.h +++ b/src/core/include/unitsmanager.h @@ -20,7 +20,7 @@ public: void updateExportData(lua_State* L, double dt = 0); void updateMissionData(json::value missionData); void runAILoop(); - string getUnitData(bool refresh); + string getUnitData(stringstream &ss, unsigned long long time, bool refresh); void deleteUnit(unsigned int ID, bool explosion); void acquireControl(unsigned int ID); diff --git a/src/core/src/airunit.cpp b/src/core/src/airunit.cpp index fe0b9977..85eb71fe 100644 --- a/src/core/src/airunit.cpp +++ b/src/core/src/airunit.cpp @@ -120,7 +120,7 @@ void AirUnit::AIloop() /* State machine */ switch (state) { case State::IDLE: { - currentTask = "Idle"; + setTask("Idle"); if (!getHasTask()) { @@ -147,17 +147,17 @@ void AirUnit::AIloop() if (isTanker) { enrouteTask = "{ id = 'Tanker' }"; - currentTask = "Tanker"; + setTask("Tanker"); } else if (isAWACS) { enrouteTask = "{ id = 'AWACS' }"; - currentTask = "AWACS"; + setTask("AWACS"); } else { enrouteTask = "nil"; - currentTask = "Reaching destination"; + setTask("Reaching destination"); } if (activeDestination == NULL || !getHasTask()) @@ -179,7 +179,7 @@ void AirUnit::AIloop() } case State::LAND: { string enrouteTask = "{ id = 'Land' }"; - currentTask = "Landing"; + setTask("Landing"); if (activeDestination == NULL) { @@ -203,7 +203,7 @@ void AirUnit::AIloop() << "targetID = " << targetID << "," << "}"; string enrouteTask = enrouteTaskSS.str(); - currentTask = "Attacking " + getTargetName(); + setTask("Attacking " + getTargetName()); if (!getHasTask()) { @@ -223,7 +223,7 @@ void AirUnit::AIloop() break; } - currentTask = "Following " + getTargetName(); + setTask("Following " + getTargetName()); Unit* leader = unitsManager->getUnit(leaderID); if (!getHasTask()) { @@ -247,7 +247,7 @@ void AirUnit::AIloop() break; } case State::REFUEL: { - currentTask = "Refueling"; + setTask("Refueling"); if (!getHasTask()) { if (fuel <= initialFuel) { @@ -265,7 +265,7 @@ void AirUnit::AIloop() } } case State::BOMB_POINT: { - currentTask = "Bombing point"; + setTask("Bombing point"); if (!getHasTask()) { std::ostringstream taskSS; @@ -276,7 +276,7 @@ void AirUnit::AIloop() } } case State::CARPET_BOMB: { - currentTask = "Carpet bombing"; + setTask("Carpet bombing"); if (!getHasTask()) { std::ostringstream taskSS; @@ -288,7 +288,7 @@ void AirUnit::AIloop() break; } case State::BOMB_BUILDING: { - currentTask = "Bombing building"; + setTask("Bombing building"); if (!getHasTask()) { std::ostringstream taskSS; diff --git a/src/core/src/core.cpp b/src/core/src/core.cpp index 4f75b5ec..9dcc5a4b 100644 --- a/src/core/src/core.cpp +++ b/src/core/src/core.cpp @@ -85,13 +85,14 @@ extern "C" DllExport int coreFrame(lua_State* L) if (unitsManager != nullptr) { unitsManager->updateExportData(L, duration.count()); - //unitsManager->runAILoop(); + unitsManager->runAILoop(); + } before = std::chrono::system_clock::now(); } if (scheduler != nullptr) - //scheduler->execute(L); + scheduler->execute(L); return(0); } diff --git a/src/core/src/groundunit.cpp b/src/core/src/groundunit.cpp index f28e8a18..dc61510c 100644 --- a/src/core/src/groundunit.cpp +++ b/src/core/src/groundunit.cpp @@ -71,7 +71,7 @@ void GroundUnit::AIloop() { switch (state) { case State::IDLE: { - currentTask = "Idle"; + setTask("Idle"); if (getHasTask()) resetTask(); break; @@ -102,7 +102,7 @@ void GroundUnit::AIloop() break; } case State::FIRE_AT_AREA: { - currentTask = "Firing at area"; + setTask("Firing at area"); if (!getHasTask()) { std::ostringstream taskSS; diff --git a/src/core/src/scheduler.cpp b/src/core/src/scheduler.cpp index e070414a..98c855ff 100644 --- a/src/core/src/scheduler.cpp +++ b/src/core/src/scheduler.cpp @@ -7,7 +7,7 @@ extern UnitsManager* unitsManager; -Scheduler::Scheduler(lua_State* L): +Scheduler::Scheduler(lua_State* L) : load(0) { LogInfo(L, "Scheduler constructor called successfully"); @@ -25,16 +25,15 @@ void Scheduler::appendCommand(Command* command) void Scheduler::execute(lua_State* L) { - /* Decrease the active computation load. New commands can be sent only if the load has reached 0. + /* Decrease the active computation load. New commands can be sent only if the load has reached 0. This is needed to avoid server lag. */ if (load > 0) { load--; return; } - unsigned int priority = CommandPriority::IMMEDIATE; - while (priority >= CommandPriority::LOW) - { + int priority = CommandPriority::IMMEDIATE; + while (priority >= CommandPriority::LOW) { for (auto command : commands) { if (command->getPriority() == priority) @@ -42,13 +41,15 @@ void Scheduler::execute(lua_State* L) string commandString = "Olympus.protectedCall(" + command->getString(L) + ")"; if (dostring_in(L, "server", (commandString))) log("Error executing command " + commandString); + else + log("Command '" + commandString + "' executed correctly, current load " + to_string(load)); load = command->getLoad(); commands.remove(command); return; } } priority--; - } + }; } void Scheduler::handleRequest(string key, json::value value) @@ -66,11 +67,11 @@ void Scheduler::handleRequest(string key, json::value value) string unitName = unit->getUnitName(); json::value path = value[L"path"]; list newPath; - for (unsigned int i = 1; i <= path.as_object().size(); i++) + for (unsigned int i = 0; i < path.as_array().size(); i++) { string WP = to_string(i); - double lat = path[to_wstring(i)][L"lat"].as_double(); - double lng = path[to_wstring(i)][L"lng"].as_double(); + double lat = path[i][L"lat"].as_double(); + double lng = path[i][L"lng"].as_double(); log(unitName + " set path destination " + WP + " (" + to_string(lat) + ", " + to_string(lng) + ")"); Coords dest; dest.lat = lat; dest.lng = lng; newPath.push_back(dest); @@ -126,7 +127,7 @@ void Scheduler::handleRequest(string key, json::value value) string unitName; string targetName; - + if (unit != nullptr) unitName = unit->getUnitName(); else @@ -197,11 +198,11 @@ void Scheduler::handleRequest(string key, json::value value) } else if (key.compare("setSpeedType") == 0) { - unsigned int ID = value[L"ID"].as_integer(); - unitsManager->acquireControl(ID); - Unit* unit = unitsManager->getGroupLeader(ID); - if (unit != nullptr) - unit->setDesiredSpeedType(to_string(value[L"speedType"])); + unsigned int ID = value[L"ID"].as_integer(); + unitsManager->acquireControl(ID); + Unit* unit = unitsManager->getGroupLeader(ID); + if (unit != nullptr) + unit->setDesiredSpeedType(to_string(value[L"speedType"])); } else if (key.compare("setAltitude") == 0) { @@ -233,7 +234,7 @@ void Scheduler::handleRequest(string key, json::value value) unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); - unsigned char ROE = value[L"ROE"].as_number().is_uint32(); + unsigned char ROE = value[L"ROE"].as_number().to_uint32(); unit->setROE(ROE); } else if (key.compare("setReactionToThreat") == 0) @@ -241,7 +242,7 @@ void Scheduler::handleRequest(string key, json::value value) unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); - unsigned char reactionToThreat = value[L"reactionToThreat"].as_number().is_uint32(); + unsigned char reactionToThreat = value[L"reactionToThreat"].as_number().to_uint32(); unit->setReactionToThreat(reactionToThreat); } else if (key.compare("setEmissionsCountermeasures") == 0) @@ -249,7 +250,7 @@ void Scheduler::handleRequest(string key, json::value value) unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); - unsigned char emissionsCountermeasures = value[L"emissionsCountermeasures"].as_number().is_uint32(); + unsigned char emissionsCountermeasures = value[L"emissionsCountermeasures"].as_number().to_uint32(); unit->setEmissionsCountermeasures(emissionsCountermeasures); } else if (key.compare("landAt") == 0) @@ -317,7 +318,7 @@ void Scheduler::handleRequest(string key, json::value value) } } else if (key.compare("setFollowRoads") == 0) - { + { unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); bool followRoads = value[L"followRoads"].as_bool(); @@ -325,7 +326,7 @@ void Scheduler::handleRequest(string key, json::value value) unit->setFollowRoads(followRoads); } else if (key.compare("setOnOff") == 0) - { + { unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); bool onOff = value[L"onOff"].as_bool(); @@ -389,10 +390,11 @@ void Scheduler::handleRequest(string key, json::value value) { log("Unknown command: " + key); } - + if (command != nullptr) { appendCommand(command); + log("New command appended correctly to stack. Current server load: " + to_string(load)); } } diff --git a/src/core/src/server.cpp b/src/core/src/server.cpp index 086882d7..68398d1d 100644 --- a/src/core/src/server.cpp +++ b/src/core/src/server.cpp @@ -69,10 +69,11 @@ void Server::handle_get(http_request request) /* Lock for thread safety */ lock_guard guard(mutexLock); + milliseconds ms = duration_cast(system_clock::now().time_since_epoch()); + http_response response(status_codes::OK); string authorization = to_base64("admin:" + password); - log(authorization); - if (password == "" || (request.headers().has(L"Authorization") && request.headers().find(L"Authorization")->second == L"Basic " + to_wstring(authorization))) + if (password.length() == 0 || (request.headers().has(L"Authorization") && request.headers().find(L"Authorization")->second.compare(L"Basic " + to_wstring(authorization)) == 0)) { std::exception_ptr eptr; try { @@ -96,28 +97,34 @@ void Server::handle_get(http_request request) } } - // TODO would be nice to optimize this - answer[L"units"] = json::value(to_wstring(unitsManager->getUnitData(time == 0))); - } - else if (URI.compare(LOGS_URI) == 0) - { - auto logs = json::value::object(); - getLogsJSON(logs, 100); // By reference, for thread safety. Get the last 100 log entries - answer[L"logs"] = logs; - } - else if (URI.compare(AIRBASES_URI) == 0) - answer[L"airbases"] = airbases; - else if (URI.compare(BULLSEYE_URI) == 0) - answer[L"bullseyes"] = bullseyes; - else if (URI.compare(MISSION_URI) == 0) - answer[L"mission"] = mission; + bool refresh = (time == 0); + unsigned long long updateTime = ms.count(); - milliseconds ms = duration_cast(system_clock::now().time_since_epoch()); - answer[L"time"] = json::value::string(to_wstring(ms.count())); - answer[L"sessionHash"] = json::value::string(to_wstring(sessionHash)); + stringstream ss; + ss.write((char*)&updateTime, sizeof(updateTime)); + unitsManager->getUnitData(ss, time, refresh); + response.set_body(concurrency::streams::bytestream::open_istream(ss.str())); + } + else { + if (URI.compare(LOGS_URI) == 0) + { + auto logs = json::value::object(); + getLogsJSON(logs, 100); // By reference, for thread safety. Get the last 100 log entries + answer[L"logs"] = logs; + } + else if (URI.compare(AIRBASES_URI) == 0) + answer[L"airbases"] = airbases; + else if (URI.compare(BULLSEYE_URI) == 0) + answer[L"bullseyes"] = bullseyes; + else if (URI.compare(MISSION_URI) == 0) + answer[L"mission"] = mission; + + + answer[L"time"] = json::value::string(to_wstring(ms.count())); + answer[L"sessionHash"] = json::value::string(to_wstring(sessionHash)); + response.set_body(answer); + } } - - response.set_body(answer); } catch (...) { eptr = std::current_exception(); // capture @@ -140,7 +147,7 @@ void Server::handle_request(http_request request, functionsecond.compare(L"Basic " + to_wstring(authorization)))) + if (password.length() == 0 || (request.headers().has(L"Authorization") && request.headers().find(L"Authorization")->second.compare(L"Basic " + to_wstring(authorization)) == 0)) { auto answer = json::value::object(); request.extract_json().then([&answer, &action](pplx::task task) diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp index b5580505..3d2650b2 100644 --- a/src/core/src/unit.cpp +++ b/src/core/src/unit.cpp @@ -45,6 +45,24 @@ Unit::~Unit() void Unit::initialize(json::value json) { + if (json.has_string_field(L"Name")) + setName(to_string(json[L"Name"])); + if (json.has_string_field(L"UnitName")) + setUnitName(to_string(json[L"UnitName"])); + if (json.has_string_field(L"GroupName")) + setGroupName(to_string(json[L"GroupName"])); + if (json.has_number_field(L"Country")) + setCountry(json[L"Country"].as_number().to_int32()); + if (json.has_number_field(L"CoalitionID")) + setCoalition(json[L"CoalitionID"].as_number().to_int32()); + + if (json.has_object_field(L"Flags")) + setHuman(json[L"Flags"][L"Human"].as_bool()); + + /* All units which contain the name "Olympus" are automatically under AI control */ + if (getUnitName().find("Olympus") != string::npos) + setControlled(true); + updateExportData(json); setDefaults(); } @@ -92,6 +110,21 @@ void Unit::runAILoop() { void Unit::updateExportData(json::value json, double dt) { + Coords newPosition = Coords(NULL); + double newHeading = 0; + double newSpeed = 0; + + if (json.has_object_field(L"LatLongAlt")) + { + setPosition({ + json[L"LatLongAlt"][L"Lat"].as_number().to_double(), + json[L"LatLongAlt"][L"Long"].as_number().to_double(), + json[L"LatLongAlt"][L"Alt"].as_number().to_double() + }); + } + if (json.has_number_field(L"Heading")) + setHeading(json[L"Heading"].as_number().to_double()); + /* Compute speed (loGetWorldObjects does not provide speed, we compute it for better performance instead of relying on many lua calls) */ if (oldPosition != NULL) { @@ -100,35 +133,7 @@ void Unit::updateExportData(json::value json, double dt) if (dt > 0) setSpeed(getSpeed() * 0.95 + (dist / dt) * 0.05); } - - if (json.has_string_field(L"Name")) - setName(to_string(json[L"Name"])); - if (json.has_string_field(L"UnitName")) - setUnitName(to_string(json[L"UnitName"])); - if (json.has_string_field(L"GroupName")) - setGroupName(to_string(json[L"GroupName"])); - if (json.has_number_field(L"Country")) - setCountry(json[L"Country"].as_number().to_int32()); - if (json.has_number_field(L"CoalitionID")) - setCoalitionID(json[L"CoalitionID"].as_number().to_int32()); - if (json.has_object_field(L"LatLongAlt")) - { - Coords position = { - json[L"LatLongAlt"][L"Lat"].as_number().to_double(), - json[L"LatLongAlt"][L"Long"].as_number().to_double(), - json[L"LatLongAlt"][L"Alt"].as_number().to_double() - }; - setPosition(position); - } - if (json.has_number_field(L"Heading")) - setHeading(json[L"Heading"].as_number().to_double()); - if (json.has_object_field(L"Flags")) - setHuman(json[L"Flags"][L"Human"].as_bool()); - - /* All units which contain the name "Olympus" are automatically under AI control */ - if (getUnitName().find("Olympus") != string::npos) - setControlled(true); - + oldPosition = position; } @@ -175,7 +180,7 @@ void Unit::updateMissionData(json::value json) setHasTask(json[L"hasTask"].as_bool()); } -unsigned int Unit::getUpdateData(char* &data) +unsigned int Unit::getDataPacket(char* &data) { /* Reserve data for: 1) DataPacket; @@ -217,34 +222,31 @@ unsigned int Unit::getUpdateData(char* &data) fuel, desiredSpeed, desiredAltitude, + leaderID, targetID, targetPosition, state, ROE, reactionToThreat, emissionsCountermeasures, + coalition, TACAN, radio, activePath.size(), ammo.size(), contacts.size(), - name.size(), - unitName.size(), - groupName.size(), - getCategory().size(), - coalition.size() + task.size() }; memcpy(data + offset, &dataPacket, sizeof(dataPacket)); offset += sizeof(dataPacket); /* Prepare the path vector and copy it to memory */ - std::vector path; - for (const Coords& c : activePath) - path.push_back(c); - memcpy(data + offset, &path, activePath.size() * sizeof(Coords)); - offset += activePath.size() * sizeof(Coords); - + for (const Coords& c : activePath) { + memcpy(data + offset, &c, sizeof(Coords)); + offset += sizeof(Coords); + } + /* Copy the ammo vector to memory */ memcpy(data + offset, &ammo, ammo.size() * sizeof(DataTypes::Ammo)); offset += ammo.size() * sizeof(DataTypes::Ammo); @@ -256,10 +258,12 @@ unsigned int Unit::getUpdateData(char* &data) return offset; } -void Unit::getData(stringstream &ss, bool refresh) +void Unit::getData(stringstream &ss, unsigned long long time, bool refresh) { + if (time > lastUpdateTime && !refresh) return; + char* data; - unsigned int size = getUpdateData(data); + unsigned int size = getDataPacket(data); /* Prepare the data packet and copy it to memory */ /* If the unit is in a group, get the update data from the group leader and only replace the position, speed and heading */ @@ -271,15 +275,25 @@ void Unit::getData(stringstream &ss, bool refresh) } ss.write(data, size); - delete data; + ss << task; - if (refresh) { - ss << name; - ss << unitName; - ss << groupName; - ss << getCategory(); - ss << coalition; - } + unsigned short nameLength = name.length(); + unsigned short unitNameLength = unitName.length(); + unsigned short groupNameLength = groupName.length(); + unsigned short categoryLength = getCategory().length(); + + ss.write((char*)&nameLength, sizeof(nameLength)); + ss.write((char*)&unitNameLength, sizeof(unitNameLength)); + ss.write((char*)&groupNameLength, sizeof(groupNameLength)); + ss.write((char*)&categoryLength, sizeof(categoryLength)); + + ss << name; + ss << unitName; + ss << groupName; + ss << getCategory(); + + + delete data; } void Unit::setActivePath(list newPath) @@ -315,26 +329,6 @@ void Unit::popActivePathFront() setActivePath(path); } -void Unit::setCoalitionID(unsigned int newCoalitionID) -{ - if (newCoalitionID == 0) - coalition = "neutral"; - else if (newCoalitionID == 1) - coalition = "red"; - else - coalition = "blue"; -} - -unsigned int Unit::getCoalitionID() -{ - if (coalition == "neutral") - return 0; - else if (coalition == "red") - return 1; - else - return 2; -} - string Unit::getTargetName() { if (isTargetAlive()) @@ -398,6 +392,8 @@ void Unit::setFormationOffset(Offset newFormationOffset) { formationOffset = newFormationOffset; resetTask(); + + triggerUpdate(); } void Unit::setROE(unsigned char newROE, bool force) @@ -406,6 +402,8 @@ void Unit::setROE(unsigned char newROE, bool force) ROE = newROE; Command* command = dynamic_cast(new SetOption(groupName, SetCommandType::ROE, static_cast(ROE))); scheduler->appendCommand(command); + + triggerUpdate(); } } @@ -416,6 +414,8 @@ void Unit::setReactionToThreat(unsigned char newReactionToThreat, bool force) Command* command = dynamic_cast(new SetOption(groupName, SetCommandType::REACTION_ON_THREAT, static_cast(reactionToThreat))); scheduler->appendCommand(command); + + triggerUpdate(); } } @@ -464,6 +464,8 @@ void Unit::setEmissionsCountermeasures(unsigned char newEmissionsCountermeasures command = dynamic_cast(new SetOption(groupName, SetCommandType::ECM_USING, ECMEnum)); scheduler->appendCommand(command); + + triggerUpdate(); } } @@ -476,15 +478,23 @@ void Unit::landAt(Coords loc) void Unit::setIsTanker(bool newIsTanker) { - isTanker = newIsTanker; - resetTask(); + if (isTanker != newIsTanker) { + isTanker = newIsTanker; + resetTask(); + + triggerUpdate(); + } } void Unit::setIsAWACS(bool newIsAWACS) { - isAWACS = newIsAWACS; - resetTask(); - setEPLRS(isAWACS); + if (isAWACS != newIsAWACS) { + isAWACS = newIsAWACS; + resetTask(); + setEPLRS(isAWACS); + + triggerUpdate(); + } } void Unit::setTACAN(Options::TACAN newTACAN, bool force) @@ -517,6 +527,8 @@ void Unit::setTACAN(Options::TACAN newTACAN, bool force) Command* command = dynamic_cast(new SetCommand(groupName, commandSS.str())); scheduler->appendCommand(command); } + + triggerUpdate(); } } @@ -551,6 +563,8 @@ void Unit::setRadio(Options::Radio newRadio, bool force) << "}"; command = dynamic_cast(new SetCommand(groupName, commandSS.str())); scheduler->appendCommand(command); + + triggerUpdate(); } } @@ -590,6 +604,8 @@ void Unit::setGeneralSettings(Options::GeneralSettings newGeneralSettings, bool scheduler->appendCommand(command); command = dynamic_cast(new SetOption(groupName, SetCommandType::ENGAGE_AIR_WEAPONS, !generalSettings.prohibitAirWpn)); scheduler->appendCommand(command); + + triggerUpdate(); } } @@ -600,6 +616,8 @@ void Unit::setDesiredSpeed(double newDesiredSpeed) resetTask(); else goToDestination(); /* Send the command to reach the destination */ + + triggerUpdate(); } void Unit::setDesiredAltitude(double newDesiredAltitude) @@ -609,6 +627,8 @@ void Unit::setDesiredAltitude(double newDesiredAltitude) resetTask(); else goToDestination(); /* Send the command to reach the destination */ + + triggerUpdate(); } void Unit::setDesiredSpeedType(string newDesiredSpeedType) @@ -618,6 +638,8 @@ void Unit::setDesiredSpeedType(string newDesiredSpeedType) resetTask(); else goToDestination(); /* Send the command to reach the destination */ + + triggerUpdate(); } void Unit::setDesiredAltitudeType(string newDesiredAltitudeType) @@ -627,6 +649,8 @@ void Unit::setDesiredAltitudeType(string newDesiredAltitudeType) resetTask(); else goToDestination(); /* Send the command to reach the destination */ + + triggerUpdate(); } void Unit::goToDestination(string enrouteTask) @@ -669,12 +693,16 @@ bool Unit::setActiveDestination() { activeDestination = activePath.front(); log(unitName + " active destination set to queue front"); + + triggerUpdate(); return true; } else { activeDestination = Coords(0); log(unitName + " active destination set to NULL"); + + triggerUpdate(); return false; } } @@ -695,11 +723,6 @@ bool Unit::updateActivePath(bool looping) } } -void Unit::setTargetPosition(Coords newTargetPosition) -{ - targetPosition = newTargetPosition; -} - bool Unit::checkTaskFailed() { if (getHasTask()) @@ -714,8 +737,3 @@ bool Unit::checkTaskFailed() void Unit::resetTaskFailedCounter() { taskCheckCounter = TASK_CHECK_INIT_VALUE; } - -void Unit::setHasTask(bool newHasTask) -{ - hasTask = newHasTask; -} \ No newline at end of file diff --git a/src/core/src/unitsmanager.cpp b/src/core/src/unitsmanager.cpp index 66a976b5..9f4a30c3 100644 --- a/src/core/src/unitsmanager.cpp +++ b/src/core/src/unitsmanager.cpp @@ -154,11 +154,10 @@ void UnitsManager::runAILoop() { unit.second->runAILoop(); } -string UnitsManager::getUnitData(bool refresh) +string UnitsManager::getUnitData(stringstream &ss, unsigned long long time, bool refresh) { - stringstream ss; for (auto const& p : units) - p.second->getData(ss, refresh); + p.second->getData(ss, time, refresh); return to_base64(ss.str()); } diff --git a/src/utils/include/utils.h b/src/utils/include/utils.h index 3ab1a636..16b16c68 100644 --- a/src/utils/include/utils.h +++ b/src/utils/include/utils.h @@ -1,6 +1,7 @@ #pragma once #include "framework.h" +#pragma pack(push, 1) struct Coords { double lat = 0; double lng = 0; @@ -12,6 +13,7 @@ struct Offset { double y = 0; double z = 0; }; +#pragma pack(pop) // Get current date/time, format is YYYY-MM-DD.HH:mm:ss const DllExport std::string CurrentDateTime(); From 4d9dd364b649581ff74afbd664164eee31d852b2 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Mon, 26 Jun 2023 18:53:04 +0200 Subject: [PATCH 10/15] Converted data transfer to binary key-value method --- client/src/@types/unit.d.ts | 42 +-- client/src/atc/atcboard.ts | 2 +- client/src/atc/unitdatatable.ts | 2 +- client/src/constants/constants.ts | 44 ++- client/src/other/utils.ts | 38 +++ client/src/units/dataextractor.ts | 279 ++++++---------- client/src/units/unit.ts | 538 ++++++++++++++++++------------ client/src/units/unitsmanager.ts | 60 ++-- src/core/include/aircraft.h | 2 - src/core/include/airunit.h | 1 - src/core/include/groundunit.h | 1 - src/core/include/helicopter.h | 2 - src/core/include/navyunit.h | 1 - src/core/include/unit.h | 427 +++++++++++++----------- src/core/include/weapon.h | 7 - src/core/src/aircraft.cpp | 8 +- src/core/src/groundunit.cpp | 4 +- src/core/src/helicopter.cpp | 7 +- src/core/src/navyunit.cpp | 4 +- src/core/src/scheduler.cpp | 2 +- src/core/src/unit.cpp | 270 ++++++--------- src/core/src/weapon.cpp | 2 + 22 files changed, 858 insertions(+), 885 deletions(-) diff --git a/client/src/@types/unit.d.ts b/client/src/@types/unit.d.ts index 703b8852..ec6aaa39 100644 --- a/client/src/@types/unit.d.ts +++ b/client/src/@types/unit.d.ts @@ -46,42 +46,8 @@ interface Contact { detectionMethod: number } -interface UnitData { - ID: number, - alive: boolean, - human: boolean, - controlled: boolean, - hasTask: boolean, - desiredAltitudeType: string, - desiredSpeedType: string, - isTanker: boolean, - isAWACS: boolean, - onOff: boolean, - followRoads: boolean, - EPLRS: boolean, - generalSettings: GeneralSettings - position: LatLng, - speed: number, - heading: number, - fuel: number, - desiredSpeed: number, - desiredAltitude: number, - targetID: number, - leaderID: number, - targetPosition: LatLng, - state: string, - ROE: string, - reactionToThreat: string, - emissionsCountermeasures: string, - TACAN: TACAN, - radio: Radio, - activePath: LatLng[], - ammo: Ammo[], - contacts: Contact[], - name: string, - unitName: string, - groupName: string, - category: string, - coalition: string, - task: string +interface Offset { + x: number, + y: number, + z: number } \ No newline at end of file diff --git a/client/src/atc/atcboard.ts b/client/src/atc/atcboard.ts index a65f55bd..832cd691 100644 --- a/client/src/atc/atcboard.ts +++ b/client/src/atc/atcboard.ts @@ -119,7 +119,7 @@ export abstract class ATCBoard { const unitCanBeAdded = () => { - if ( baseData.category !== "Aircraft" ) { + if ( unit.getCategory() !== "Aircraft" ) { return false; } diff --git a/client/src/atc/unitdatatable.ts b/client/src/atc/unitdatatable.ts index f54a4553..1f06fc9c 100644 --- a/client/src/atc/unitdatatable.ts +++ b/client/src/atc/unitdatatable.ts @@ -48,7 +48,7 @@ export class UnitDataTable extends Panel { for (const unit of unitsArray) { - const dataset = [unit.getData().unitName, unit.getData().name, unit.getData().category, (unit.getData().controlled) ? "AI" : "Human"]; + const dataset = [unit.getData().unitName, unit.getData().name, unit.getCategory(), (unit.getData().controlled) ? "AI" : "Human"]; addRow(el, dataset); } diff --git a/client/src/constants/constants.ts b/client/src/constants/constants.ts index 1cff2f7d..aa9c1189 100644 --- a/client/src/constants/constants.ts +++ b/client/src/constants/constants.ts @@ -131,4 +131,46 @@ 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}; \ No newline at end of file +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 +}; \ No newline at end of file diff --git a/client/src/other/utils.ts b/client/src/other/utils.ts index 6043539d..39f95722 100644 --- a/client/src/other/utils.ts +++ b/client/src/other/utils.ts @@ -5,6 +5,7 @@ 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 @@ -253,3 +254,40 @@ export function getUnitDatabaseByCategory(category: string) { 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 ""; +} \ No newline at end of file diff --git a/client/src/units/dataextractor.ts b/client/src/units/dataextractor.ts index d3265a9a..7b5eaf32 100644 --- a/client/src/units/dataextractor.ts +++ b/client/src/units/dataextractor.ts @@ -1,9 +1,8 @@ import { LatLng } from "leaflet"; -import { UnitData } from "../@types/unit"; -import { ROEs, emissionsCountermeasures, reactionsToThreat, states } from "../constants/constants"; +import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN } from "../@types/unit"; export class DataExtractor { - #offset = 0; + #seekPosition = 0; #dataview: DataView; #decoder: TextDecoder; #buffer: ArrayBuffer; @@ -14,181 +13,59 @@ export class DataExtractor { this.#decoder = new TextDecoder("utf-8"); } - extractData(offset: number) { - this.#offset = offset; - - const ID = this.extractUInt32(); - const bitmask = this.extractUInt32(); - - const unitData: UnitData = { - ID: ID, - alive: this.extractFromBitmask(bitmask, 0), - human: this.extractFromBitmask(bitmask, 1), - controlled: this.extractFromBitmask(bitmask, 2), - hasTask: this.extractFromBitmask(bitmask, 3), - desiredAltitudeType: this.extractFromBitmask(bitmask, 16)? "AGL": "ASL", - desiredSpeedType: this.extractFromBitmask(bitmask, 17)? "GS": "CAS", - isTanker: this.extractFromBitmask(bitmask, 18), - isAWACS: this.extractFromBitmask(bitmask, 19), - onOff: this.extractFromBitmask(bitmask, 20), - followRoads: this.extractFromBitmask(bitmask, 21), - EPLRS: this.extractFromBitmask(bitmask, 22), - generalSettings: { - prohibitAA: this.extractFromBitmask(bitmask, 23), - prohibitAfterburner: this.extractFromBitmask(bitmask, 24), - prohibitAG: this.extractFromBitmask(bitmask, 25), - prohibitAirWpn: this.extractFromBitmask(bitmask, 26), - prohibitJettison: this.extractFromBitmask(bitmask, 27), - }, - position: new LatLng( - this.extractFloat64(), - this.extractFloat64(), - this.extractFloat64() - ), - speed: this.extractFloat64(), - heading: this.extractFloat64(), - fuel: this.extractUInt16(), - desiredSpeed: this.extractFloat64(), - desiredAltitude: this.extractFloat64(), - leaderID: this.extractUInt32(), - targetID: this.extractUInt32(), - targetPosition: new LatLng( - this.extractFloat64(), - this.extractFloat64(), - this.extractFloat64() - ), - state: this.#getState(this.extractUInt8()), - ROE: this.#getROE(this.extractUInt8()), - reactionToThreat: this.#getReactionToThreat(this.extractUInt8()), - emissionsCountermeasures: this.#getEmissionCountermeasure(this.extractUInt8()), - coalition: this.#getCoalition(this.extractUInt8()), - TACAN: { - isOn: this.extractBool(), - channel: this.extractUInt8(), - XY: this.extractChar(), - callsign: this.extractString(4) - }, - radio: { - frequency: this.extractUInt32(), - callsign: this.extractUInt8(), - callsignNumber: this.extractUInt8() - }, - activePath: [], - ammo: [], - contacts: [], - task: "", - name: "", - unitName: "", - groupName: "", - category: "", - } - - const pathLength = this.extractUInt16(); - const ammoLength = this.extractUInt16(); - const contactsLength = this.extractUInt16(); - const taskLength = this.extractUInt8(); - - if (pathLength > 0) { - unitData.activePath = []; - for (let idx = 0; idx < pathLength; idx++) { - unitData.activePath.push(new LatLng(this.extractFloat64(), this.extractFloat64(), this.extractFloat64())); - } - } - - if (ammoLength > 0) { - unitData.ammo = []; - for (let idx = 0; idx < pathLength; idx++) { - unitData.ammo.push({ - quantity: this.extractUInt16(), - name: this.extractString(32), - guidance: this.extractUInt8(), - category: this.extractUInt8(), - missileCategory: this.extractUInt8() - }); - } - } - - if (contactsLength > 0) { - unitData.contacts = []; - for (let idx = 0; idx < pathLength; idx++) { - unitData.contacts.push({ - ID: this.extractUInt32(), - detectionMethod: this.extractUInt8() - }); - } - } - - if (taskLength > 0) { - unitData.task = this.extractString(taskLength); - } - - const nameLength = this.extractUInt16(); - const unitNameLength = this.extractUInt16(); - const groupNameLength = this.extractUInt16(); - const categoryLength = this.extractUInt16(); - - if (nameLength > 0) { - unitData.name = this.extractString(nameLength); - } - - if (unitNameLength > 0) { - unitData.unitName = this.extractString(unitNameLength); - } - - if (groupNameLength > 0) { - unitData.groupName = this.extractString(groupNameLength); - } - - if (categoryLength > 0) { - unitData.category = this.extractString(categoryLength); - } - - return {data: unitData, offset: this.#offset}; + getSeekPosition() { + return this.#seekPosition; } extractBool() { - const value = this.#dataview.getUint8(this.#offset); - this.#offset += 1; + const value = this.#dataview.getUint8(this.#seekPosition); + this.#seekPosition += 1; return value > 0; } extractUInt8() { - const value = this.#dataview.getUint8(this.#offset); - this.#offset += 1; + const value = this.#dataview.getUint8(this.#seekPosition); + this.#seekPosition += 1; return value; } extractUInt16() { - const value = this.#dataview.getUint16(this.#offset, true); - this.#offset += 2; + const value = this.#dataview.getUint16(this.#seekPosition, true); + this.#seekPosition += 2; return value; } extractUInt32() { - const value = this.#dataview.getUint32(this.#offset, true); - this.#offset += 4; + const value = this.#dataview.getUint32(this.#seekPosition, true); + this.#seekPosition += 4; return value; } extractUInt64() { - const value = this.#dataview.getBigUint64(this.#offset, true); - this.#offset += 8; + const value = this.#dataview.getBigUint64(this.#seekPosition, true); + this.#seekPosition += 8; return value; } extractFloat64() { - const value = this.#dataview.getFloat64(this.#offset, true); - this.#offset += 8; + 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) { - const value = this.#decoder.decode(this.#buffer.slice(this.#offset, this.#offset +length)); - this.#offset += length; + 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; } @@ -196,44 +73,78 @@ export class DataExtractor { return this.extractString(1); } - getOffset() { - return this.#offset; + extractTACAN() { + const value: TACAN = { + isOn: this.extractBool(), + channel: this.extractUInt8(), + XY: this.extractChar(), + callsign: this.extractString(4) + } + return value; } - #getState(state: number) { - if (state < states.length) - return states[state]; - else - return states[0]; - } - - #getROE(ROE: number) { - if (ROE < ROEs.length) - return ROEs[ROE]; - else - return ROEs[0]; - } - - #getReactionToThreat(reactionToThreat: number) { - if (reactionToThreat < reactionsToThreat.length) - return reactionsToThreat[reactionToThreat]; - else - return reactionsToThreat[0]; - } - - #getEmissionCountermeasure(emissionCountermeasure: number) { - if (emissionCountermeasure < emissionsCountermeasures.length) - return emissionsCountermeasures[emissionCountermeasure]; - else - return emissionsCountermeasures[0]; - } - - #getCoalition(coalitionID: number) { - switch (coalitionID){ - case 0: return "neutral"; - case 1: return "red"; - case 2: return "blue"; + extractRadio() { + const value: Radio = { + frequency: this.extractUInt32(), + callsign: this.extractUInt8(), + callsignNumber: this.extractUInt8() } - return ""; + 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(32), + 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; } } \ No newline at end of file diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index c71d0d3f..fe90f149 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -1,13 +1,14 @@ import { Marker, LatLng, Polyline, Icon, DivIcon, CircleMarker, Map } from 'leaflet'; import { getMap, getUnitsManager } from '..'; -import { getMarkerCategoryByName, getUnitDatabaseByCategory, mToFt, msToKnots, rad2deg } from '../other/utils'; +import { enumToCoalition, enumToEmissioNCountermeasure, getMarkerCategoryByName, enumToROE, enumToReactionToThreat, enumToState, getUnitDatabaseByCategory, mToFt, msToKnots, rad2deg } from '../other/utils'; import { addDestination, attackUnit, changeAltitude, changeSpeed, createFormation as setLeader, deleteUnit, getUnits, landAt, setAltitude, setReactionToThreat, setROE, setSpeed, refuel, setAdvacedOptions, followUnit, setEmissionsCountermeasures, setSpeedType, setAltitudeType, setOnOff, setFollowRoads, bombPoint, carpetBomb, bombBuilding, fireAtArea } from '../server/server'; import { CustomMarker } from '../map/custommarker'; import { SVGInjector } from '@tanem/svg-injector'; import { UnitDatabase } from './unitdatabase'; import { TargetMarker } from '../map/targetmarker'; -import { BOMBING, CARPET_BOMBING, FIRE_AT_AREA, IDLE, MOVE_UNIT, ROEs, emissionsCountermeasures, reactionsToThreat, states } from '../constants/constants'; -import { GeneralSettings, Radio, TACAN, UnitData, UnitIconOptions } from '../@types/unit'; +import { BOMBING, CARPET_BOMBING, DataIndexes, FIRE_AT_AREA, IDLE, MOVE_UNIT, ROEs, emissionsCountermeasures, reactionsToThreat, states } from '../constants/constants'; +import { Ammo, Contact, GeneralSettings, Offset, Radio, TACAN, UnitIconOptions } from '../@types/unit'; +import { DataExtractor } from './dataextractor'; var pathIcon = new Icon({ iconUrl: '/resources/theme/images/markers/marker-icon.png', @@ -18,77 +19,74 @@ var pathIcon = new Icon({ export class Unit extends CustomMarker { ID: number; - #data: UnitData = { - ID: 0, - alive: false, - human: false, - controlled: false, - hasTask: false, - desiredAltitudeType: "AGL", - desiredSpeedType: "GS", - isTanker: false, - isAWACS: false, - onOff: false, - followRoads: false, - EPLRS: false, - generalSettings: { - prohibitAA: false, - prohibitAfterburner: false, - prohibitAG: false, - prohibitAirWpn: false, - prohibitJettison: false - }, - position: new LatLng(0, 0), - speed: 0, - heading: 0, - fuel: 0, - desiredSpeed: 0, - desiredAltitude: 0, - targetID: 0, - leaderID: 0, - targetPosition: new LatLng(0, 0), - state: states[0], - ROE: ROEs[0], - reactionToThreat: reactionsToThreat[0], - emissionsCountermeasures: emissionsCountermeasures[0], - TACAN: { - isOn: false, - XY: 'X', - callsign: '', - channel: 0 - }, - radio: { - frequency: 0, - callsign: 0, - callsignNumber: 0 - }, - activePath: [], - ammo: [], - contacts: [], - name: "", - unitName: "", - groupName: "", - category: "", - coalition: "", - task: "" + #alive: boolean = false; + #human: boolean = false; + #controlled: boolean = false; + #coalition: string = ""; + #country: number = 0; + #name: string = ""; + #unitName: string = ""; + #groupName: string = ""; + #state: string = states[0]; + #task: string = "" + #hasTask: boolean = false; + #position: LatLng = new LatLng(0, 0); + #speed: number = 0; + #heading: number = 0; + #isTanker: boolean = false; + #isAWACS: boolean = false; + #onOff: boolean = false; + #followRoads: boolean = false; + #fuel: number = 0; + #desiredSpeed: number = 0; + #desiredSpeedType: string = "GS"; + #desiredAltitude: number = 0; + #desiredAltitudeType: string = "AGL"; + #leaderID: number = 0; + #formationOffset: Offset = { + x: 0, + y: 0, + z: 0 }; + #targetID: number = 0; + #targetPosition: LatLng = new LatLng(0, 0); + #ROE: string = ROEs[0]; + #reactionToThreat: string = reactionsToThreat[0]; + #emissionsCountermeasures: string = emissionsCountermeasures[0]; + #TACAN: TACAN = { + isOn: false, + XY: 'X', + callsign: '', + channel: 0 + }; + #radio: Radio = { + frequency: 0, + callsign: 0, + callsignNumber: 0 + }; + #generalSettings: GeneralSettings = { + prohibitAA: false, + prohibitAfterburner: false, + prohibitAG: false, + prohibitAirWpn: false, + prohibitJettison: false + }; + #ammo: Ammo[] = []; + #contacts: Contact[] = []; + #activePath: LatLng[] = []; #selectable: boolean; #selected: boolean = false; #hidden: boolean = false; #highlighted: boolean = false; - #preventClick: boolean = false; - #pathMarkers: Marker[] = []; #pathPolyline: Polyline; #contactsPolylines: Polyline[]; #miniMapMarker: CircleMarker | null = null; #targetPositionMarker: TargetMarker; #targetPositionPolyline: Polyline; - #timer: number = 0; - #hotgroup: number | null = null; static getConstructor(type: string) { @@ -100,7 +98,7 @@ export class Unit extends CustomMarker { if (type === "NavyUnit") return NavyUnit; } - constructor(ID: number, data: UnitData) { + constructor(ID: number) { super(new LatLng(0, 0), { riseOnHover: true, keyboard: false }); this.ID = ID; @@ -126,9 +124,122 @@ export class Unit extends CustomMarker { document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => { window.setTimeout(() => { this.setSelected(this.getSelected() && !this.getHidden()) }, 300); }); + } - /* Set the unit data */ - this.setData(data); + getCategory() { + // Overloaded by child classes + return ""; + } + + /********************** Unit data *************************/ + setData(dataExtractor: DataExtractor) { + var updateMarker = !getMap().hasLayer(this); + + var datumIndex = dataExtractor.extractUInt8(); + if (datumIndex == DataIndexes.startOfData) { + while (datumIndex != DataIndexes.endOfData) { + datumIndex = dataExtractor.extractUInt8(); + switch (datumIndex) { + case DataIndexes.alive: this.setAlive(dataExtractor.extractBool()); updateMarker = true; break; + case DataIndexes.human: this.#human = dataExtractor.extractBool(); break; + case DataIndexes.controlled: this.#controlled = dataExtractor.extractBool(); updateMarker = true; break; + case DataIndexes.coalition: this.#coalition = enumToCoalition(dataExtractor.extractUInt8()); break; + case DataIndexes.country: this.#country = dataExtractor.extractUInt8(); break; + case DataIndexes.name: this.#name = dataExtractor.extractString(); break; + case DataIndexes.unitName: this.#unitName = dataExtractor.extractString(); break; + case DataIndexes.groupName: this.#groupName = dataExtractor.extractString(); break; + case DataIndexes.state: this.#state = enumToState(dataExtractor.extractUInt8()); updateMarker = true; break; + case DataIndexes.task: this.#task = dataExtractor.extractString(); break; + case DataIndexes.hasTask: this.#hasTask = dataExtractor.extractBool(); break; + case DataIndexes.position: this.#position = dataExtractor.extractLatLng(); updateMarker = true; break; + case DataIndexes.speed: this.#speed = dataExtractor.extractFloat64(); updateMarker = true; break; + case DataIndexes.heading: this.#heading = dataExtractor.extractFloat64(); updateMarker = true; break; + case DataIndexes.isTanker: this.#isTanker = dataExtractor.extractBool(); break; + case DataIndexes.isAWACS: this.#isAWACS = dataExtractor.extractBool(); break; + case DataIndexes.onOff: this.#onOff = dataExtractor.extractBool(); break; + case DataIndexes.followRoads: this.#followRoads = dataExtractor.extractBool(); break; + case DataIndexes.fuel: this.#fuel = dataExtractor.extractUInt16(); break; + case DataIndexes.desiredSpeed: this.#desiredSpeed = dataExtractor.extractFloat64(); break; + case DataIndexes.desiredSpeedType: this.#desiredSpeedType = dataExtractor.extractBool() ? "GS" : "CAS"; break; + case DataIndexes.desiredAltitude: this.#desiredAltitude = dataExtractor.extractFloat64(); break; + case DataIndexes.desiredAltitudeType: this.#desiredAltitudeType = dataExtractor.extractFloat64() ? "AGL" : "ASL"; break; + case DataIndexes.leaderID: this.#leaderID = dataExtractor.extractUInt32(); break; + case DataIndexes.formationOffset: dataExtractor.extractOffset(); break; + case DataIndexes.targetID: this.#targetID = dataExtractor.extractUInt32(); break; + case DataIndexes.targetPosition: this.#targetPosition = dataExtractor.extractLatLng(); break; + case DataIndexes.ROE: this.#ROE = enumToROE(dataExtractor.extractUInt8()); break; + case DataIndexes.reactionToThreat: this.#reactionToThreat = enumToReactionToThreat(dataExtractor.extractUInt8()); break; + case DataIndexes.emissionsCountermeasures: this.#emissionsCountermeasures = enumToEmissioNCountermeasure(dataExtractor.extractUInt8()); break; + case DataIndexes.TACAN: this.#TACAN = dataExtractor.extractTACAN(); break; + case DataIndexes.radio: this.#radio = dataExtractor.extractRadio(); break; + case DataIndexes.generalSettings: this.#generalSettings = dataExtractor.extractGeneralSettings(); break; + case DataIndexes.ammo: this.#ammo = dataExtractor.extractAmmo(); break; + case DataIndexes.contacts: this.#contacts = dataExtractor.extractContacts(); break; + case DataIndexes.activePath: this.#activePath = dataExtractor.extractActivePath(); break; + } + } + } + + /* Dead units can't be selected */ + this.setSelected(this.getSelected() && this.#alive && !this.getHidden()) + + if (updateMarker) + this.#updateMarker(); + + // TODO dont delete the polylines of the detected units + this.#clearDetectedUnits(); + if (this.getSelected()) { + this.#drawPath(); + this.#drawDetectedUnits(); + this.#drawTarget(); + } + else { + this.#clearPath(); + this.#clearTarget(); + } + + document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this })); + } + + getData() { + return { + alive: this.#alive, + human: this.#human, + controlled: this.#controlled, + coalition: this.#coalition, + country: this.#country, + name: this.#name, + unitName: this.#unitName, + groupName: this.#groupName, + state: this.#state, + task: this.#task, + hasTask: this.#hasTask, + position: this.#position, + speed: this.#speed, + heading: this.#heading, + isTanker: this.#isTanker, + isAWACS: this.#isAWACS, + onOff: this.#onOff, + followRoads: this.#followRoads, + fuel: this.#fuel, + desiredSpeed: this.#desiredSpeed, + desiredSpeedType: this.#desiredSpeedType, + desiredAltitude: this.#desiredAltitude, + desiredAltitudeType: this.#desiredAltitudeType, + leaderID: this.#leaderID, + formationOffset: this.#formationOffset, + targetID: this.#targetID, + targetPosition: this.#targetPosition, + ROE: this.#ROE, + reactionToThreat: this.#reactionToThreat, + emissionsCountermeasures: this.#emissionsCountermeasures, + TACAN: this.#TACAN, + radio: this.#radio, + generalSettings: this.#generalSettings, + ammo: this.#ammo, + contacts: this.#contacts, + activePath: this.#activePath + } } getMarkerCategory() { @@ -151,14 +262,20 @@ export class Unit extends CustomMarker { showShortLabel: false, showFuel: false, showAmmo: false, - showSummary: false, + showSummary: false, rotateToHeading: false } } + setAlive(newAlive: boolean) { + if (newAlive != this.#alive) + document.dispatchEvent(new CustomEvent("unitDeath", { detail: this })); + this.#alive = newAlive; + } + setSelected(selected: boolean) { /* Only alive units can be selected. Some units are not selectable (weapons) */ - if ((this.getData().alive || !selected) && this.getSelectable() && this.getSelected() != selected) { + if ((this.#alive || !selected) && this.getSelectable() && this.getSelected() != selected) { this.#selected = selected; this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-selected", selected); if (selected) { @@ -208,49 +325,7 @@ export class Unit extends CustomMarker { } getGroupMembers() { - return Object.values(getUnitsManager().getUnits()).filter((unit: Unit) => {return unit != this && unit.getData().groupName === this.getData().groupName;}); - } - - /********************** Unit data *************************/ - setData(data: UnitData) { - /* Check if data has changed comparing new values to old values */ - const positionChanged = this.getData().position.lat != data.position.lat || this.getData().position.lng != data.position.lng; - const headingChanged = this.getData().heading != data.heading; - const aliveChanged = this.getData().alive != data.alive; - const stateChanged = this.getData().state != data.state; - const controlledChanged = this.getData().controlled != data.controlled; - var updateMarker = positionChanged || headingChanged || aliveChanged || stateChanged || controlledChanged || !getMap().hasLayer(this); - - /* Assign the data */ - this.#data = data; - - /* Fire an event when a unit dies */ - if (aliveChanged && this.getData().alive == false) - document.dispatchEvent(new CustomEvent("unitDeath", { detail: this })); - - /* Dead units can't be selected */ - this.setSelected(this.getSelected() && this.getData().alive && !this.getHidden()) - - if (updateMarker) - this.#updateMarker(); - - // TODO dont delete the polylines of the detected units - this.#clearDetectedUnits(); - if (this.getSelected()) { - this.#drawPath(); - this.#drawDetectedUnits(); - this.#drawTarget(); - } - else { - this.#clearPath(); - this.#clearTarget(); - } - - document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this })); - } - - getData() { - return this.#data; + return Object.values(getUnitsManager().getUnits()).filter((unit: Unit) => { return unit != this && unit.#groupName === this.#groupName; }); } /********************** Icon *************************/ @@ -266,7 +341,7 @@ export class Unit extends CustomMarker { var el = document.createElement("div"); el.classList.add("unit"); el.setAttribute("data-object", `unit-${this.getMarkerCategory()}`); - el.setAttribute("data-coalition", this.getData().coalition); + el.setAttribute("data-coalition", this.#coalition); // Generate and append elements depending on active options // Velocity vector @@ -300,7 +375,7 @@ export class Unit extends CustomMarker { } // State icon - if (this.getIconOptions().showState){ + if (this.getIconOptions().showState) { var state = document.createElement("div"); state.classList.add("unit-state"); el.appendChild(state); @@ -310,7 +385,7 @@ export class Unit extends CustomMarker { if (this.getIconOptions().showShortLabel) { var shortLabel = document.createElement("div"); shortLabel.classList.add("unit-short-label"); - shortLabel.innerText = getUnitDatabaseByCategory(this.getMarkerCategory())?.getByName(this.getData().name)?.shortLabel || ""; + shortLabel.innerText = getUnitDatabaseByCategory(this.getMarkerCategory())?.getByName(this.#name)?.shortLabel || ""; el.append(shortLabel); } @@ -325,7 +400,7 @@ export class Unit extends CustomMarker { } // Ammo indicator - if (this.getIconOptions().showAmmo){ + if (this.getIconOptions().showAmmo) { var ammoIndicator = document.createElement("div"); ammoIndicator.classList.add("unit-ammo"); for (let i = 0; i <= 3; i++) @@ -339,7 +414,7 @@ export class Unit extends CustomMarker { summary.classList.add("unit-summary"); var callsign = document.createElement("div"); callsign.classList.add("unit-callsign"); - callsign.innerText = this.getData().unitName; + callsign.innerText = this.#unitName; var altitude = document.createElement("div"); altitude.classList.add("unit-altitude"); var speed = document.createElement("div"); @@ -357,15 +432,15 @@ export class Unit extends CustomMarker { updateVisibility() { var hidden = false; const hiddenUnits = getUnitsManager().getHiddenTypes(); - if (this.getData().human && hiddenUnits.includes("human")) + if (this.#human && hiddenUnits.includes("human")) hidden = true; - else if (this.getData().controlled == false && hiddenUnits.includes("dcs")) + else if (this.#controlled == false && hiddenUnits.includes("dcs")) hidden = true; else if (hiddenUnits.includes(this.getMarkerCategory())) hidden = true; - else if (hiddenUnits.includes(this.getData().coalition)) + else if (hiddenUnits.includes(this.#coalition)) hidden = true; - this.setHidden(hidden || !this.getData().alive); + this.setHidden(hidden || !this.#alive); } setHidden(hidden: boolean) { @@ -387,24 +462,24 @@ export class Unit extends CustomMarker { } getLeader() { - return getUnitsManager().getUnitByID(this.getData().leaderID); + return getUnitsManager().getUnitByID(this.#leaderID); } canFulfillRole(roles: string | string[]) { - if (typeof(roles) === "string") + if (typeof (roles) === "string") roles = [roles]; - return this.getDatabase()?.getByName(this.getData().name)?.loadouts.some((loadout: LoadoutBlueprint) => { - return (roles as string[]).some((role: string) => {return loadout.roles.includes(role)}); + return this.getDatabase()?.getByName(this.#name)?.loadouts.some((loadout: LoadoutBlueprint) => { + return (roles as string[]).some((role: string) => { return loadout.roles.includes(role) }); }); } /********************** Unit commands *************************/ addDestination(latlng: L.LatLng) { - if (!this.getData().human) { + if (!this.#human) { var path: any = {}; - if (this.getData().activePath.length > 0) { - path = this.getData().activePath; + if (this.#activePath.length > 0) { + path = this.#activePath; path[(Object.keys(path).length).toString()] = latlng; } else { @@ -415,86 +490,86 @@ export class Unit extends CustomMarker { } clearDestinations() { - if (!this.getData().human) - this.getData().activePath = []; + if (!this.#human) + this.#activePath = []; } attackUnit(targetID: number) { /* Units can't attack themselves */ - if (!this.getData().human) + if (!this.#human) if (this.ID != targetID) attackUnit(this.ID, targetID); } followUnit(targetID: number, offset: { "x": number, "y": number, "z": number }) { /* Units can't follow themselves */ - if (!this.getData().human) + if (!this.#human) if (this.ID != targetID) followUnit(this.ID, targetID, offset); } landAt(latlng: LatLng) { - if (!this.getData().human) + if (!this.#human) landAt(this.ID, latlng); } changeSpeed(speedChange: string) { - if (!this.getData().human) + if (!this.#human) changeSpeed(this.ID, speedChange); } changeAltitude(altitudeChange: string) { - if (!this.getData().human) + if (!this.#human) changeAltitude(this.ID, altitudeChange); } setSpeed(speed: number) { - if (!this.getData().human) + if (!this.#human) setSpeed(this.ID, speed); } setSpeedType(speedType: string) { - if (!this.getData().human) + if (!this.#human) setSpeedType(this.ID, speedType); } setAltitude(altitude: number) { - if (!this.getData().human) + if (!this.#human) setAltitude(this.ID, altitude); } setAltitudeType(altitudeType: string) { - if (!this.getData().human) + if (!this.#human) setAltitudeType(this.ID, altitudeType); } setROE(ROE: string) { - if (!this.getData().human) + if (!this.#human) setROE(this.ID, ROE); } setReactionToThreat(reactionToThreat: string) { - if (!this.getData().human) + if (!this.#human) setReactionToThreat(this.ID, reactionToThreat); } setEmissionsCountermeasures(emissionCountermeasure: string) { - if (!this.getData().human) + if (!this.#human) setEmissionsCountermeasures(this.ID, emissionCountermeasure); } setLeader(isLeader: boolean, wingmenIDs: number[] = []) { - if (!this.getData().human) + if (!this.#human) setLeader(this.ID, isLeader, wingmenIDs); } setOnOff(onOff: boolean) { - if (!this.getData().human) + if (!this.#human) setOnOff(this.ID, onOff); } setFollowRoads(followRoads: boolean) { - if (!this.getData().human) + if (!this.#human) setFollowRoads(this.ID, followRoads); } @@ -503,12 +578,12 @@ export class Unit extends CustomMarker { } refuel() { - if (!this.getData().human) + if (!this.#human) refuel(this.ID); } setAdvancedOptions(isTanker: boolean, isAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings) { - if (!this.getData().human) + if (!this.#human) setAdvacedOptions(this.ID, isTanker, isAWACS, TACAN, radio, generalSettings); } @@ -533,7 +608,7 @@ export class Unit extends CustomMarker { super.onAdd(map); /* If this is the first time adding this unit to the map, remove the temporary marker */ if (getUnitsManager().getUnitByID(this.ID) == null) - getMap().removeTemporaryMarker(new LatLng(this.getData().position.lat, this.getData().position.lng)); + getMap().removeTemporaryMarker(new LatLng(this.#position.lat, this.#position.lng)); return this; } @@ -541,7 +616,7 @@ export class Unit extends CustomMarker { #onClick(e: any) { if (!this.#preventClick) { if (getMap().getState() === IDLE || getMap().getState() === MOVE_UNIT || e.originalEvent.ctrlKey) { - if (!e.originalEvent.ctrlKey) + if (!e.originalEvent.ctrlKey) getUnitsManager().deselectAllUnits(); this.setSelected(!this.getSelected()); } @@ -558,34 +633,33 @@ export class Unit extends CustomMarker { } #onContextMenu(e: any) { - var options: {[key: string]: {text: string, tooltip: string}} = {}; + var options: { [key: string]: { text: string, tooltip: string } } = {}; const selectedUnits = getUnitsManager().getSelectedUnits(); const selectedUnitTypes = getUnitsManager().getSelectedUnitsTypes(); - options["center-map"] = {text: "Center map", tooltip: "Center the map on the unit and follow it"}; + options["center-map"] = { text: "Center map", tooltip: "Center the map on the unit and follow it" }; if (selectedUnits.length > 0 && !(selectedUnits.length == 1 && (selectedUnits.includes(this)))) { - options["attack"] = {text: "Attack", tooltip: "Attack the unit using A/A or A/G weapons"}; + options["attack"] = { text: "Attack", tooltip: "Attack the unit using A/A or A/G weapons" }; if (getUnitsManager().getSelectedUnitsTypes().length == 1 && getUnitsManager().getSelectedUnitsTypes()[0] === "Aircraft") - options["follow"] = {text: "Follow", tooltip: "Follow the unit at a user defined distance and position"};; + options["follow"] = { text: "Follow", tooltip: "Follow the unit at a user defined distance and position" };; } else if ((selectedUnits.length > 0 && (selectedUnits.includes(this))) || selectedUnits.length == 0) { - if (this.getData().category == "Aircraft") { - options["refuel"] = {text: "Air to air refuel", tooltip: "Refuel unit at the nearest AAR Tanker. If no tanker is available the unit will RTB."}; // TODO Add some way of knowing which aircraft can AAR + if (this.getCategory() == "Aircraft") { + options["refuel"] = { text: "Air to air refuel", tooltip: "Refuel unit at the nearest AAR Tanker. If no tanker is available the unit will RTB." }; // TODO Add some way of knowing which aircraft can AAR } } - if ((selectedUnits.length === 0 && this.getData().category == "Aircraft") || (selectedUnitTypes.length === 1 && ["Aircraft"].includes(selectedUnitTypes[0]))) - { - if (selectedUnits.concat([this]).every((unit: Unit) => {return unit.canFulfillRole(["CAS", "Strike"])})) { - options["bomb"] = {text: "Precision bombing", tooltip: "Precision bombing of a specific point"}; - options["carpet-bomb"] = {text: "Carpet bombing", tooltip: "Carpet bombing close to a point"}; + if ((selectedUnits.length === 0 && this.getCategory() == "Aircraft") || (selectedUnitTypes.length === 1 && ["Aircraft"].includes(selectedUnitTypes[0]))) { + if (selectedUnits.concat([this]).every((unit: Unit) => { return unit.canFulfillRole(["CAS", "Strike"]) })) { + options["bomb"] = { text: "Precision bombing", tooltip: "Precision bombing of a specific point" }; + options["carpet-bomb"] = { text: "Carpet bombing", tooltip: "Carpet bombing close to a point" }; } } - if ((selectedUnits.length === 0 && this.getData().category == "GroundUnit") || selectedUnitTypes.length === 1 && ["GroundUnit"].includes(selectedUnitTypes[0])) { - if (selectedUnits.concat([this]).every((unit: Unit) => {return unit.canFulfillRole(["Gun Artillery", "Rocket Artillery", "Infantry", "IFV", "Tank"])})) - options["fire-at-area"] = {text: "Fire at area", tooltip: "Fire at a large area"}; + if ((selectedUnits.length === 0 && this.getCategory() == "GroundUnit") || selectedUnitTypes.length === 1 && ["GroundUnit"].includes(selectedUnitTypes[0])) { + if (selectedUnits.concat([this]).every((unit: Unit) => { return unit.canFulfillRole(["Gun Artillery", "Rocket Artillery", "Infantry", "IFV", "Tank"]) })) + options["fire-at-area"] = { text: "Fire at area", tooltip: "Fire at a large area" }; } if (Object.keys(options).length > 0) { @@ -615,17 +689,17 @@ export class Unit extends CustomMarker { } #showFollowOptions(e: any) { - var options: {[key: string]: {text: string, tooltip: string}} = {}; + var options: { [key: string]: { text: string, tooltip: string } } = {}; options = { - 'trail': {text: "Trail", tooltip: "Follow unit in trail formation"}, - 'echelon-lh': {text: "Echelon (LH)", tooltip: "Follow unit in echelon left formation"}, - 'echelon-rh': {text: "Echelon (RH)", tooltip: "Follow unit in echelon right formation"}, - 'line-abreast-lh': {text: "Line abreast (LH)", tooltip: "Follow unit in line abreast left formation"}, - 'line-abreast-rh': {text: "Line abreast (RH)", tooltip: "Follow unit in line abreast right formation"}, - 'front': {text: "Front", tooltip: "Fly in front of unit"}, - 'diamond': {text: "Diamond", tooltip: "Follow unit in diamond formation"}, - 'custom': {text: "Custom", tooltip: "Set a custom formation position"}, + 'trail': { text: "Trail", tooltip: "Follow unit in trail formation" }, + 'echelon-lh': { text: "Echelon (LH)", tooltip: "Follow unit in echelon left formation" }, + 'echelon-rh': { text: "Echelon (RH)", tooltip: "Follow unit in echelon right formation" }, + 'line-abreast-lh': { text: "Line abreast (LH)", tooltip: "Follow unit in line abreast left formation" }, + 'line-abreast-rh': { text: "Line abreast (RH)", tooltip: "Follow unit in line abreast right formation" }, + 'front': { text: "Front", tooltip: "Fly in front of unit" }, + 'diamond': { text: "Diamond", tooltip: "Follow unit in diamond formation" }, + 'custom': { text: "Custom", tooltip: "Set a custom formation position" }, } getMap().getUnitContextMenu().setOptions(options, (option: string) => { @@ -651,12 +725,12 @@ export class Unit extends CustomMarker { this.updateVisibility(); /* Draw the minimap marker */ - if (this.getData().alive) { + if (this.#alive) { if (this.#miniMapMarker == null) { - this.#miniMapMarker = new CircleMarker(new LatLng(this.getData().position.lat, this.getData().position.lng), { radius: 0.5 }); - if (this.getData().coalition == "neutral") + this.#miniMapMarker = new CircleMarker(new LatLng(this.#position.lat, this.#position.lng), { radius: 0.5 }); + if (this.#coalition == "neutral") this.#miniMapMarker.setStyle({ color: "#CFD9E8" }); - else if (this.getData().coalition == "red") + else if (this.#coalition == "red") this.#miniMapMarker.setStyle({ color: "#ff5858" }); else this.#miniMapMarker.setStyle({ color: "#247be2" }); @@ -664,7 +738,7 @@ export class Unit extends CustomMarker { this.#miniMapMarker.bringToBack(); } else { - this.#miniMapMarker.setLatLng(new LatLng(this.getData().position.lat, this.getData().position.lng)); + this.#miniMapMarker.setLatLng(new LatLng(this.#position.lat, this.#position.lng)); this.#miniMapMarker.bringToBack(); } } @@ -677,39 +751,39 @@ export class Unit extends CustomMarker { /* Draw the marker */ if (!this.getHidden()) { - this.setLatLng(new LatLng(this.getData().position.lat, this.getData().position.lng)); + this.setLatLng(new LatLng(this.#position.lat, this.#position.lng)); var element = this.getElement(); if (element != null) { /* Draw the velocity vector */ - element.querySelector(".unit-vvi")?.setAttribute("style", `height: ${15 + this.getData().speed / 5}px;`); + element.querySelector(".unit-vvi")?.setAttribute("style", `height: ${15 + this.#speed / 5}px;`); /* Set fuel data */ - element.querySelector(".unit-fuel-level")?.setAttribute("style", `width: ${this.getData().fuel}%`); - element.querySelector(".unit")?.toggleAttribute("data-has-low-fuel", this.getData().fuel < 20); + element.querySelector(".unit-fuel-level")?.setAttribute("style", `width: ${this.#fuel}%`); + element.querySelector(".unit")?.toggleAttribute("data-has-low-fuel", this.#fuel < 20); /* Set dead/alive flag */ - element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.getData().alive); + element.querySelector(".unit")?.toggleAttribute("data-is-dead", !this.#alive); /* Set current unit state */ - if (this.getData().human) // Unit is human + if (this.#human) // Unit is human element.querySelector(".unit")?.setAttribute("data-state", "human"); - else if (!this.getData().controlled) // Unit is under DCS control (not Olympus) + else if (!this.#controlled) // Unit is under DCS control (not Olympus) element.querySelector(".unit")?.setAttribute("data-state", "dcs"); - else if ((this.getData().category == "Aircraft" || this.getData().category == "Helicopter") && !this.getData().hasTask) + else if ((this.getCategory() == "Aircraft" || this.getCategory() == "Helicopter") && !this.#hasTask) element.querySelector(".unit")?.setAttribute("data-state", "no-task"); else // Unit is under Olympus control - element.querySelector(".unit")?.setAttribute("data-state", this.getData().state.toLowerCase()); + element.querySelector(".unit")?.setAttribute("data-state", this.#state.toLowerCase()); /* Set altitude and speed */ if (element.querySelector(".unit-altitude")) - (element.querySelector(".unit-altitude")).innerText = "FL" + String(Math.floor(mToFt(this.getData().position.alt as number) / 100)); + (element.querySelector(".unit-altitude")).innerText = "FL" + String(Math.floor(mToFt(this.#position.alt as number) / 100)); if (element.querySelector(".unit-speed")) - (element.querySelector(".unit-speed")).innerText = String(Math.floor(msToKnots(this.getData().speed))) + "GS"; + (element.querySelector(".unit-speed")).innerText = String(Math.floor(msToKnots(this.#speed))) + "GS"; /* Rotate elements according to heading */ element.querySelectorAll("[data-rotate-to-heading]").forEach(el => { - const headingDeg = rad2deg(this.getData().heading); + const headingDeg = rad2deg(this.#heading); let currentStyle = el.getAttribute("style") || ""; el.setAttribute("style", currentStyle + `transform:rotate(${headingDeg}deg);`); }); @@ -724,7 +798,7 @@ export class Unit extends CustomMarker { var newHasFox2 = false; var newHasFox3 = false; var newHasOtherAmmo = false; - Object.values(this.getData().ammo).forEach((ammo: any) => { + Object.values(this.#ammo).forEach((ammo: any) => { if (ammo.desc.category == 1 && ammo.desc.missileCategory == 1) { if (ammo.desc.guidance == 4 || ammo.desc.guidance == 5) newHasFox1 = true; @@ -754,30 +828,30 @@ export class Unit extends CustomMarker { /* Set vertical offset for altitude stacking */ var pos = getMap().latLngToLayerPoint(this.getLatLng()).round(); - this.setZIndexOffset(1000 + Math.floor(this.getData().position.alt as number) - pos.y + (this.#highlighted || this.#selected ? 5000 : 0)); + this.setZIndexOffset(1000 + Math.floor(this.#position.alt as number) - pos.y + (this.#highlighted || this.#selected ? 5000 : 0)); } } #drawPath() { - if (this.getData().activePath != undefined) { + if (this.#activePath != undefined) { var points = []; - points.push(new LatLng(this.getData().position.lat, this.getData().position.lng)); + points.push(new LatLng(this.#position.lat, this.#position.lng)); /* Add markers if missing */ - while (this.#pathMarkers.length < Object.keys(this.getData().activePath).length) { + while (this.#pathMarkers.length < Object.keys(this.#activePath).length) { var marker = new Marker([0, 0], { icon: pathIcon }).addTo(getMap()); this.#pathMarkers.push(marker); } /* Remove markers if too many */ - while (this.#pathMarkers.length > Object.keys(this.getData().activePath).length) { + while (this.#pathMarkers.length > Object.keys(this.#activePath).length) { getMap().removeLayer(this.#pathMarkers[this.#pathMarkers.length - 1]); this.#pathMarkers.splice(this.#pathMarkers.length - 1, 1) } /* Update the position of the existing markers (to avoid creating markers uselessly) */ - for (let WP in this.getData().activePath) { - var destination = this.getData().activePath[WP]; + for (let WP in this.#activePath) { + var destination = this.#activePath[WP]; this.#pathMarkers[parseInt(WP)].setLatLng([destination.lat, destination.lng]); points.push(new LatLng(destination.lat, destination.lng)); this.#pathPolyline.setLatLngs(points); @@ -797,12 +871,12 @@ export class Unit extends CustomMarker { } #drawDetectedUnits() { - for (let index in this.getData().contacts) { - var targetData = this.getData().contacts[index]; + for (let index in this.#contacts) { + var targetData = this.#contacts[index]; var target = getUnitsManager().getUnitByID(targetData.ID) if (target != null) { - var startLatLng = new LatLng(this.getData().position.lat, this.getData().position.lng) - var endLatLng = new LatLng(target.getData().position.lat, target.getData().position.lng) + var startLatLng = new LatLng(this.#position.lat, this.#position.lng) + var endLatLng = new LatLng(target.#position.lat, target.#position.lng) var color; if (targetData.detectionMethod === 1) @@ -827,31 +901,31 @@ export class Unit extends CustomMarker { } #drawTarget() { - if (this.getData().targetPosition.lat != 0 && this.getData().targetPosition.lng != 0) { - this.#drawtargetPosition(this.getData().targetPosition); + if (this.#targetPosition.lat != 0 && this.#targetPosition.lng != 0) { + this.#drawtargetPosition(this.#targetPosition); } - else if (this.getData().targetID != 0 && getUnitsManager().getUnitByID(this.getData().targetID)) { - const position = getUnitsManager().getUnitByID(this.getData().targetID)?.getData().position; + else if (this.#targetID != 0 && getUnitsManager().getUnitByID(this.#targetID)) { + const position = getUnitsManager().getUnitByID(this.#targetID)?.getData().position; if (position) this.#drawtargetPosition(position); } - else + else this.#clearTarget(); } #drawtargetPosition(targetPosition: LatLng) { - if (!getMap().hasLayer(this.#targetPositionMarker)) + if (!getMap().hasLayer(this.#targetPositionMarker)) this.#targetPositionMarker.addTo(getMap()); if (!getMap().hasLayer(this.#targetPositionPolyline)) this.#targetPositionPolyline.addTo(getMap()); this.#targetPositionMarker.setLatLng(new LatLng(targetPosition.lat, targetPosition.lng)); - this.#targetPositionPolyline.setLatLngs([new LatLng(this.getData().position.lat, this.getData().position.lng), new LatLng(targetPosition.lat, targetPosition.lng)]) - } + this.#targetPositionPolyline.setLatLngs([new LatLng(this.#position.lat, this.#position.lng), new LatLng(targetPosition.lat, targetPosition.lng)]) + } #clearTarget() { if (getMap().hasLayer(this.#targetPositionMarker)) this.#targetPositionMarker.removeFrom(getMap()); - + if (getMap().hasLayer(this.#targetPositionPolyline)) this.#targetPositionPolyline.removeFrom(getMap()); } @@ -874,8 +948,12 @@ export class AirUnit extends Unit { } export class Aircraft extends AirUnit { - constructor(ID: number, data: UnitData) { - super(ID, data); + constructor(ID: number) { + super(ID); + } + + getCategory() { + return "Aircraft"; } getMarkerCategory() { @@ -884,8 +962,12 @@ export class Aircraft extends AirUnit { } export class Helicopter extends AirUnit { - constructor(ID: number, data: UnitData) { - super(ID, data); + constructor(ID: number) { + super(ID); + } + + getCategory() { + return "Helicopter"; } getMarkerCategory() { @@ -894,8 +976,8 @@ export class Helicopter extends AirUnit { } export class GroundUnit extends Unit { - constructor(ID: number, data: UnitData) { - super(ID, data); + constructor(ID: number) { + super(ID); } getIconOptions() { @@ -912,14 +994,18 @@ export class GroundUnit extends Unit { }; } + getCategory() { + return "GroundUnit"; + } + getMarkerCategory() { return getMarkerCategoryByName(this.getData().name); } } export class NavyUnit extends Unit { - constructor(ID: number, data: UnitData) { - super(ID, data); + constructor(ID: number) { + super(ID); } getIconOptions() { @@ -936,14 +1022,18 @@ export class NavyUnit extends Unit { }; } + getCategory() { + return "NavyUnit"; + } + getMarkerCategory() { return "navyunit"; } } export class Weapon extends Unit { - constructor(ID: number, data: UnitData) { - super(ID, data); + constructor(ID: number) { + super(ID); this.setSelectable(false); } @@ -963,8 +1053,12 @@ export class Weapon extends Unit { } export class Missile extends Weapon { - constructor(ID: number, data: UnitData) { - super(ID, data); + constructor(ID: number) { + super(ID); + } + + getCategory() { + return "Missile"; } getMarkerCategory() { @@ -973,8 +1067,12 @@ export class Missile extends Weapon { } export class Bomb extends Weapon { - constructor(ID: number, data: UnitData) { - super(ID, data); + constructor(ID: number) { + super(ID); + } + + getCategory() { + return "Bomb"; } getMarkerCategory() { diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index c9124df6..73246697 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -6,9 +6,8 @@ import { deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, import { CoalitionArea } from "../map/coalitionarea"; import { Airbase } from "../missionhandler/airbase"; import { groundUnitsDatabase } from "./groundunitsdatabase"; -import { IADSRoles, IDLE, MOVE_UNIT } from "../constants/constants"; +import { DataIndexes, IADSRoles, IDLE, MOVE_UNIT } from "../constants/constants"; import { DataExtractor } from "./dataextractor"; -import { UnitData } from "../@types/unit"; export class UnitsManager { #units: { [ID: number]: Unit }; @@ -33,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].getData(); - if (baseData.category === "Aircraft" && baseData.alive === true) { + if (units[unitId].getCategory() === "Aircraft" && units[unitId].getData().alive === true) { acc[unitId] = units[unitId]; } return acc; @@ -56,12 +54,12 @@ export class UnitsManager { return Object.values(this.#units).filter((unit: Unit) => { return unit.getData().alive && unit.getHotgroup() == hotgroup }); } - addUnit(ID: number, data: UnitData) { - if (data.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(data.category); + var constructor = Unit.getConstructor(category); if (constructor != undefined) { - this.#units[ID] = new constructor(ID, data); + this.#units[ID] = new constructor(ID); } } } @@ -71,42 +69,24 @@ export class UnitsManager { } update(buffer: ArrayBuffer) { - var updatedUnits: Unit[] = []; var dataExtractor = new DataExtractor(buffer); - var data: {[key: string]: UnitData} = {}; - var updateTime = Number(dataExtractor.extractUInt64()); - var offset = dataExtractor.getOffset(); - while (offset < buffer.byteLength) { - const result = dataExtractor.extractData(offset); - data[result.data.ID] = result.data; - offset = result.offset; + + 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); } - Object.keys(data) - .filter((ID: string) => !(ID in this.#units)) - .reduce((timeout: number, ID: string) => { - window.setTimeout(() => { - if (!(ID in this.#units)) - this.addUnit(parseInt(ID), data[ID]); - this.#units[parseInt(ID)]?.setData(data[ID]); - }, timeout); - return timeout + 10; - }, 10); - - Object.keys(data) - .filter((ID: string) => ID in this.#units) - .forEach((ID: string) => { - updatedUnits.push(this.#units[parseInt(ID)]); - this.#units[parseInt(ID)]?.setData(data[ID]); - }); - - // TODO why did we do this? - //this.getSelectedUnits().forEach((unit: Unit) => { - // if (!updatedUnits.includes(unit)) - // unit.setData(null); - //}); - setLastUpdateTime(updateTime); } diff --git a/src/core/include/aircraft.h b/src/core/include/aircraft.h index 87c48a5f..be27e35b 100644 --- a/src/core/include/aircraft.h +++ b/src/core/include/aircraft.h @@ -6,8 +6,6 @@ class Aircraft : public AirUnit public: Aircraft(json::value json, unsigned int ID); - virtual string getCategory() { return "Aircraft"; }; - virtual void changeSpeed(string change); virtual void changeAltitude(string change); }; \ No newline at end of file diff --git a/src/core/include/airunit.h b/src/core/include/airunit.h index 7c72c88d..04ebb1ee 100644 --- a/src/core/include/airunit.h +++ b/src/core/include/airunit.h @@ -14,7 +14,6 @@ public: virtual void setState(unsigned char newState); - virtual string getCategory() = 0; virtual void changeSpeed(string change) = 0; virtual void changeAltitude(string change) = 0; diff --git a/src/core/include/groundunit.h b/src/core/include/groundunit.h index fcb8da22..13b236e2 100644 --- a/src/core/include/groundunit.h +++ b/src/core/include/groundunit.h @@ -7,7 +7,6 @@ class GroundUnit : public Unit { public: GroundUnit(json::value json, unsigned int ID); - virtual string getCategory() { return "GroundUnit"; }; virtual void setState(unsigned char newState); diff --git a/src/core/include/helicopter.h b/src/core/include/helicopter.h index 94f3d09b..3c72693b 100644 --- a/src/core/include/helicopter.h +++ b/src/core/include/helicopter.h @@ -6,8 +6,6 @@ class Helicopter : public AirUnit public: Helicopter(json::value json, unsigned int ID); - virtual string getCategory() { return "Helicopter"; }; - virtual void changeSpeed(string change); virtual void changeAltitude(string change); }; \ No newline at end of file diff --git a/src/core/include/navyunit.h b/src/core/include/navyunit.h index d1c6e31e..c82ee83d 100644 --- a/src/core/include/navyunit.h +++ b/src/core/include/navyunit.h @@ -7,7 +7,6 @@ public: NavyUnit(json::value json, unsigned int ID); virtual void AIloop(); - virtual string getCategory() { return "NavyUnit"; }; virtual void changeSpeed(string change); }; \ No newline at end of file diff --git a/src/core/include/unit.h b/src/core/include/unit.h index 2022b0d4..c9670358 100644 --- a/src/core/include/unit.h +++ b/src/core/include/unit.h @@ -12,6 +12,50 @@ using namespace std::chrono; #define TASK_CHECK_INIT_VALUE 10 +namespace DataIndex { + 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 + }; +} + namespace State { enum States @@ -33,7 +77,7 @@ namespace State }; #pragma pack(push, 1) -namespace Options { +namespace DataTypes { struct TACAN { bool isOn = false; @@ -57,9 +101,7 @@ namespace Options { bool prohibitAfterburner = false; bool prohibitAirWpn = false; }; -} -namespace DataTypes { struct Ammo { unsigned short quantity = 0; char name[32]; @@ -72,31 +114,6 @@ namespace DataTypes { unsigned int ID = 0; unsigned char detectionMethod = 0; }; - - struct DataPacket { - unsigned int ID; - unsigned int bitmask; - Coords position; - double speed; - double heading; - unsigned short fuel; - double desiredSpeed; - double desiredAltitude; - unsigned int leaderID; - unsigned int targetID; - Coords targetPosition; - unsigned char coalition; - unsigned char state; - unsigned char ROE; - unsigned char reactionToThreat; - unsigned char emissionsCountermeasures; - Options::TACAN TACAN; - Options::Radio Radio; - unsigned short pathLength; - unsigned short ammoLength; - unsigned short contactsLength; - unsigned char taskLength; - }; } #pragma pack(pop) @@ -106,209 +123,213 @@ public: Unit(json::value json, unsigned int ID); ~Unit(); - /********** Public methods **********/ + /********** Methods **********/ void initialize(json::value json); void setDefaults(bool force = false); - unsigned int getID() { return ID; } + void runAILoop(); + void updateExportData(json::value json, double dt = 0); void updateMissionData(json::value json); - unsigned int getDataPacket(char* &data); - void getData(stringstream &ss, unsigned long long time, bool refresh); - virtual string getCategory() { return "No category"; }; - /********** Base data **********/ - void setControlled(bool newValue) { updateValue(controlled, newValue); } - void setName(string newValue) { updateValue(name, newValue); } - void setUnitName(string newValue) { updateValue(unitName, newValue); } - void setGroupName(string newValue) { updateValue(groupName, newValue); } - void setAlive(bool newValue) { updateValue(alive, newValue); } - void setCountry(unsigned int newValue) { updateValue(country, newValue); } - void setHuman(bool newValue) { updateValue(human, newValue); } - - bool getControlled() { return controlled; } - string getName() { return name; } - string getUnitName() { return unitName; } - string getGroupName() { return groupName; } - bool getAlive() { return alive; } - unsigned int getCountry() { return country; } - bool getHuman() { return human; } - - /********** Flight data **********/ - void setPosition(Coords newValue) { updateValue(position, newValue); } - void setHeading(double newValue) { updateValue(heading, newValue); } - void setSpeed(double newValue) { updateValue(speed, newValue); } - - Coords getPosition() { return position; } - double getHeading() { return heading; } - double getSpeed() { return speed; } - - /********** Mission data **********/ - void setFuel(unsigned short newValue) { updateValue(fuel, newValue); } - void setAmmo(vector newAmmo) { ammo = newAmmo; } - void setContacts(vector newContacts) { contacts = newContacts; } - void setHasTask(bool newValue) { updateValue(hasTask, newValue); } - void setCoalition(unsigned char newValue) { updateValue(coalition, newValue);} - - double getFuel() { return fuel; } - vector getAmmo() { return ammo; } - vector getTargets() { return contacts; } - bool getHasTask() { return hasTask; } - unsigned int getCoalition() { return coalition; } - - /********** Formation data **********/ - void setLeaderID(unsigned int newValue) { updateValue(leaderID, newValue); } - void setFormationOffset(Offset formationOffset); - - unsigned int getLeaderID() { return leaderID; } - Offset getFormationoffset() { return formationOffset; } - - /********** Task data **********/ - void setTask(string newValue) { updateValue(task, newValue); } - void setDesiredSpeed(double newValue); - void setDesiredAltitude(double newValue); - void setDesiredSpeedType(string newValue); - void setDesiredAltitudeType(string newValue); - void setActiveDestination(Coords newValue) { updateValue(activeDestination, newValue); } - void setActivePath(list newValue); - void setTargetID(unsigned int newValue) { updateValue(targetID, newValue); } - void setTargetPosition(Coords newValue) { updateValue(targetPosition, newValue); } - void setIsTanker(bool newValue); - void setIsAWACS(bool newValue); - virtual void setOnOff(bool newValue) { updateValue(onOff, newValue); }; - virtual void setFollowRoads(bool newValue) { updateValue(followRoads, newValue); }; - - string getTask() { return task; } - virtual double getDesiredSpeed() { return desiredSpeed; }; - virtual double getDesiredAltitude() { return desiredAltitude; }; - virtual bool getDesiredSpeedType() { return desiredSpeedType; }; - virtual bool getDesiredAltitudeType() { return desiredAltitudeType; }; + unsigned int getDataPacket(char*& data); + unsigned int getID() { return ID; } + void getData(stringstream& ss, unsigned long long time, bool refresh); Coords getActiveDestination() { return activeDestination; } - list getActivePath() { return activePath; } - unsigned int getTargetID() { return targetID; } - Coords getTargetPosition() { return targetPosition; } - bool getIsTanker() { return isTanker; } - bool getIsAWACS() { return isAWACS; } - bool getOnOff() { return onOff; }; - bool getFollowRoads() { return followRoads; }; - /********** Options data **********/ - void setROE(unsigned char newValue, bool force = false); - void setReactionToThreat(unsigned char newValue, bool force = false); - void setEmissionsCountermeasures(unsigned char newValue, bool force = false); - void setTACAN(Options::TACAN newValue, bool force = false); - void setRadio(Options::Radio newValue, bool force = false); - void setGeneralSettings(Options::GeneralSettings newValue, bool force = false); - void setEPLRS(bool newValue, bool force = false); - - unsigned char getROE() { return ROE; } - unsigned char getReactionToThreat() { return reactionToThreat; } - unsigned char getEmissionsCountermeasures() { return emissionsCountermeasures; }; - Options::TACAN getTACAN() { return TACAN; } - Options::Radio getRadio() { return radio; } - Options::GeneralSettings getGeneralSettings() { return generalSettings; } - bool getEPLRS() { return EPLRS; } - - /********** Control functions **********/ - void landAt(Coords loc); virtual void changeSpeed(string change) {}; virtual void changeAltitude(string change) {}; + bool setActiveDestination(); void resetActiveDestination(); - virtual void setState(unsigned char newState) { state = newState; }; - void resetTask(); + void landAt(Coords loc); + + bool updateActivePath(bool looping); void clearActivePath(); void pushActivePathFront(Coords newActivePathFront); void pushActivePathBack(Coords newActivePathBack); void popActivePathFront(); - void triggerUpdate() { lastUpdateTime = duration_cast(system_clock::now().time_since_epoch()).count(); } - unsigned long long getLastUpdateTime() { return lastUpdateTime; }; + void goToDestination(string enrouteTask = "nil"); + bool isDestinationReached(double threshold); -protected: - unsigned int ID; - - map measures; - unsigned int taskCheckCounter = 0; - - /********** Base data **********/ - bool controlled = false; - string name = "undefined"; - string unitName = "undefined"; - string groupName = "undefined"; - bool alive = true; - bool human = false; - unsigned int country = NULL; - - /********** Flight data **********/ - Coords position = Coords(NULL); - double speed = NULL; - double heading = NULL; - - /********** Mission data **********/ - unsigned short fuel = 0; - double initialFuel = 0; // Used internally to detect refueling completed - vector ammo; - vector contacts; - bool hasTask = false; - unsigned char coalition; - - /********** Formation data **********/ - unsigned int leaderID = NULL; - Offset formationOffset = Offset(NULL); - - /********** Task data **********/ - string task = ""; - double desiredSpeed = 0; - double desiredAltitude = 0; - bool desiredSpeedType = 1; - bool desiredAltitudeType = 1; - list activePath; - Coords activeDestination = Coords(NULL); - unsigned int targetID = NULL; - Coords targetPosition = Coords(NULL); - bool isTanker = false; - bool isAWACS = false; - bool onOff = true; - bool followRoads = false; - - /********** Options data **********/ - unsigned char ROE = ROE::OPEN_FIRE_WEAPON_FREE; - unsigned char reactionToThreat = ReactionToThreat::EVADE_FIRE; - unsigned char emissionsCountermeasures = EmissionCountermeasure::DEFEND; - Options::TACAN TACAN; - Options::Radio radio; - Options::GeneralSettings generalSettings; - bool EPLRS = false; - - /********** Data packet **********/ - DataTypes::DataPacket dataPacket; - - /********** State machine **********/ - unsigned char state = State::NONE; - - /********** Other **********/ - unsigned long long lastUpdateTime = 0; - Coords oldPosition = Coords(0); // Used to approximate speed - - /********** Functions **********/ string getTargetName(); string getLeaderName(); bool isTargetAlive(); bool isLeaderAlive(); - virtual void AIloop() = 0; - bool isDestinationReached(double threshold); - bool setActiveDestination(); - bool updateActivePath(bool looping); - void goToDestination(string enrouteTask = "nil"); + + void resetTask(); bool checkTaskFailed(); void resetTaskFailedCounter(); + void triggerUpdate(unsigned char datumIndex); + + /********** Setters **********/ + virtual void setCategory(string newValue) { updateValue(category, newValue, DataIndex::category); } + virtual void setAlive(bool newValue) { updateValue(alive, newValue, DataIndex::alive); } + virtual void setHuman(bool newValue) { updateValue(human, newValue, DataIndex::human); } + virtual void setControlled(bool newValue) { updateValue(controlled, newValue, DataIndex::controlled); } + virtual void setCoalition(unsigned char newValue) { updateValue(coalition, newValue, DataIndex::coalition); } + virtual void setCountry(unsigned int newValue) { updateValue(country, newValue, DataIndex::country); } + virtual void setName(string newValue) { updateValue(name, newValue, DataIndex::name); } + virtual void setUnitName(string newValue) { updateValue(unitName, newValue, DataIndex::unitName); } + virtual void setGroupName(string newValue) { updateValue(groupName, newValue, DataIndex::groupName); } + virtual void setState(unsigned char newValue) { updateValue(state, newValue, DataIndex::state); }; + virtual void setTask(string newValue) { updateValue(task, newValue, DataIndex::task); } + virtual void setHasTask(bool newValue) { updateValue(hasTask, newValue, DataIndex::hasTask); } + virtual void setPosition(Coords newValue) { updateValue(position, newValue, DataIndex::position); } + virtual void setSpeed(double newValue) { updateValue(speed, newValue, DataIndex::speed); } + virtual void setHeading(double newValue) { updateValue(heading, newValue, DataIndex::heading); } + virtual void setIsTanker(bool newValue); + virtual void setIsAWACS(bool newValue); + virtual void setOnOff(bool newValue) { updateValue(onOff, newValue, DataIndex::onOff); }; + virtual void setFollowRoads(bool newValue) { updateValue(followRoads, newValue, DataIndex::followRoads); }; + virtual void setFuel(unsigned short newValue) { updateValue(fuel, newValue, DataIndex::fuel); } + virtual void setDesiredSpeed(double newValue); + virtual void setDesiredSpeedType(string newValue); + virtual void setDesiredAltitude(double newValue); + virtual void setDesiredAltitudeType(string newValue); + virtual void setLeaderID(unsigned int newValue) { updateValue(leaderID, newValue, DataIndex::leaderID); } + virtual void setFormationOffset(Offset formationOffset); + virtual void setTargetID(unsigned int newValue) { updateValue(targetID, newValue, DataIndex::targetID); } + virtual void setTargetPosition(Coords newValue) { updateValue(targetPosition, newValue, DataIndex::targetPosition); } + virtual void setROE(unsigned char newValue, bool force = false); + virtual void setReactionToThreat(unsigned char newValue, bool force = false); + virtual void setEmissionsCountermeasures(unsigned char newValue, bool force = false); + virtual void setTACAN(DataTypes::TACAN newValue, bool force = false); + virtual void setRadio(DataTypes::Radio newValue, bool force = false); + virtual void setGeneralSettings(DataTypes::GeneralSettings newValue, bool force = false); + virtual void setAmmo(vector newAmmo) { ammo = newAmmo; } + virtual void setContacts(vector newContacts) { contacts = newContacts; } + virtual void setActivePath(list newValue); + + /********** Getters **********/ + virtual string getCategory() { return category; }; + virtual bool getAlive() { return alive; } + virtual bool getHuman() { return human; } + virtual bool getControlled() { return controlled; } + virtual unsigned int getCoalition() { return coalition; } + virtual unsigned int getCountry() { return country; } + virtual string getName() { return name; } + virtual string getUnitName() { return unitName; } + virtual string getGroupName() { return groupName; } + virtual unsigned char getState() { return state; } + virtual string getTask() { return task; } + virtual bool getHasTask() { return hasTask; } + virtual Coords getPosition() { return position; } + virtual double getSpeed() { return speed; } + virtual double getHeading() { return heading; } + virtual bool getIsTanker() { return isTanker; } + virtual bool getIsAWACS() { return isAWACS; } + virtual bool getOnOff() { return onOff; }; + virtual bool getFollowRoads() { return followRoads; }; + virtual double getFuel() { return fuel; } + virtual double getDesiredSpeed() { return desiredSpeed; }; + virtual bool getDesiredSpeedType() { return desiredSpeedType; }; + virtual double getDesiredAltitude() { return desiredAltitude; }; + virtual bool getDesiredAltitudeType() { return desiredAltitudeType; }; + virtual unsigned int getLeaderID() { return leaderID; } + virtual Offset getFormationoffset() { return formationOffset; } + virtual unsigned int getTargetID() { return targetID; } + virtual Coords getTargetPosition() { return targetPosition; } + virtual unsigned char getROE() { return ROE; } + virtual unsigned char getReactionToThreat() { return reactionToThreat; } + virtual unsigned char getEmissionsCountermeasures() { return emissionsCountermeasures; }; + virtual DataTypes::TACAN getTACAN() { return TACAN; } + virtual DataTypes::Radio getRadio() { return radio; } + virtual DataTypes::GeneralSettings getGeneralSettings() { return generalSettings; } + virtual vector getAmmo() { return ammo; } + virtual vector getTargets() { return contacts; } + virtual list getActivePath() { return activePath; } + +protected: + unsigned int ID; + + string category; + bool alive = true; + bool human = false; + bool controlled = false; + unsigned char coalition; + unsigned int country = NULL; + string name = "undefined"; + string unitName = "undefined"; + string groupName = "undefined"; + unsigned char state = State::NONE; + string task = ""; + bool hasTask = false; + Coords position = Coords(NULL); + double speed = NULL; + double heading = NULL; + bool isTanker = false; + bool isAWACS = false; + bool onOff = true; + bool followRoads = false; + unsigned short fuel = 0; + double desiredSpeed = 0; + bool desiredSpeedType = 1; + double desiredAltitude = 0; + bool desiredAltitudeType = 1; + unsigned int leaderID = NULL; + Offset formationOffset = Offset(NULL); + unsigned int targetID = NULL; + Coords targetPosition = Coords(NULL); + unsigned char ROE = ROE::OPEN_FIRE_WEAPON_FREE; + unsigned char reactionToThreat = ReactionToThreat::EVADE_FIRE; + unsigned char emissionsCountermeasures = EmissionCountermeasure::DEFEND; + DataTypes::TACAN TACAN; + DataTypes::Radio radio; + DataTypes::GeneralSettings generalSettings; + vector ammo; + vector contacts; + list activePath; + + /********** Other **********/ + unsigned int taskCheckCounter = 0; + Coords activeDestination = Coords(NULL); + double initialFuel = 0; + Coords oldPosition = Coords(0); + map updateTimeMap; + + /********** Private methods **********/ + virtual void AIloop() = 0; + + /********** Template methods **********/ template - void updateValue(T& value, T& newValue) + void updateValue(T& value, T& newValue, unsigned char datumIndex) { if (newValue != value) { - triggerUpdate(); + triggerUpdate(datumIndex); *(&value) = newValue; } } + + template + void appendNumeric(stringstream& ss, const unsigned char& datumIndex, T& datumValue) { + ss.write((const char*)&datumIndex, sizeof(unsigned char)); + ss.write((const char*)&datumValue, sizeof(T)); + } + + void appendString(stringstream& ss, const unsigned char& datumIndex, string& datumValue) { + const unsigned short size = datumValue.size(); + ss.write((const char*)&datumIndex, sizeof(unsigned char)); + ss.write((const char*)&size, sizeof(unsigned short)); + ss << datumValue; + } + + template + void appendVector(stringstream& ss, const unsigned char& datumIndex, vector& datumValue) { + const unsigned short size = datumValue.size(); + ss.write((const char*)&datumIndex, sizeof(unsigned char)); + ss.write((const char*)&size, sizeof(unsigned short)); + ss.write((const char*)&datumValue, size * sizeof(T)); + } + + template + void appendList(stringstream& ss, const unsigned char& datumIndex, list& datumValue) { + const unsigned short size = datumValue.size(); + ss.write((const char*)&datumIndex, sizeof(unsigned char)); + ss.write((const char*)&size, sizeof(unsigned short)); + + for (auto el: datumValue) + ss.write((const char*)&el, sizeof(T)); + } }; diff --git a/src/core/include/weapon.h b/src/core/include/weapon.h index 1597ffec..96744423 100644 --- a/src/core/include/weapon.h +++ b/src/core/include/weapon.h @@ -5,9 +5,6 @@ class Weapon : public Unit { public: Weapon(json::value json, unsigned int ID); - - virtual string getCategory() = 0; - protected: /* Weapons are not controllable and have no AIloop */ virtual void AIloop() {}; @@ -17,14 +14,10 @@ class Missile : public Weapon { public: Missile(json::value json, unsigned int ID); - - virtual string getCategory() { return "Missile"; }; }; class Bomb : public Weapon { public: Bomb(json::value json, unsigned int ID); - - virtual string getCategory() { return "Bomb"; }; }; \ No newline at end of file diff --git a/src/core/src/aircraft.cpp b/src/core/src/aircraft.cpp index c1cca92e..e8a932dd 100644 --- a/src/core/src/aircraft.cpp +++ b/src/core/src/aircraft.cpp @@ -17,10 +17,10 @@ Aircraft::Aircraft(json::value json, unsigned int ID) : AirUnit(json, ID) { log("New Aircraft created with ID: " + to_string(ID)); - double desiredSpeed = knotsToMs(300); - double desiredAltitude = ftToM(20000); - setDesiredSpeed(desiredSpeed); - setDesiredAltitude(desiredAltitude); + setCategory("Aircraft"); + setDesiredSpeed(knotsToMs(300)); + setDesiredAltitude(ftToM(20000)); + }; void Aircraft::changeSpeed(string change) diff --git a/src/core/src/groundunit.cpp b/src/core/src/groundunit.cpp index dc61510c..1811565d 100644 --- a/src/core/src/groundunit.cpp +++ b/src/core/src/groundunit.cpp @@ -17,8 +17,8 @@ GroundUnit::GroundUnit(json::value json, unsigned int ID) : Unit(json, ID) { log("New Ground Unit created with ID: " + to_string(ID)); - double desiredSpeed = 10; - setDesiredSpeed(desiredSpeed); + setCategory("GroundUnit"); + setDesiredSpeed(10); }; void GroundUnit::setState(unsigned char newState) diff --git a/src/core/src/helicopter.cpp b/src/core/src/helicopter.cpp index 5215a484..6d24022e 100644 --- a/src/core/src/helicopter.cpp +++ b/src/core/src/helicopter.cpp @@ -17,10 +17,9 @@ Helicopter::Helicopter(json::value json, unsigned int ID) : AirUnit(json, ID) { log("New Helicopter created with ID: " + to_string(ID)); - double desiredSpeed = knotsToMs(100); - double desiredAltitude = ftToM(5000); - setDesiredSpeed(desiredSpeed); - setDesiredAltitude(desiredAltitude); + setCategory("Helicopter"); + setDesiredSpeed(knotsToMs(100)); + setDesiredAltitude(ftToM(5000)); }; void Helicopter::changeSpeed(string change) diff --git a/src/core/src/navyunit.cpp b/src/core/src/navyunit.cpp index b3746803..7b86fb72 100644 --- a/src/core/src/navyunit.cpp +++ b/src/core/src/navyunit.cpp @@ -17,8 +17,8 @@ NavyUnit::NavyUnit(json::value json, unsigned int ID) : Unit(json, ID) { log("New Navy Unit created with ID: " + to_string(ID)); - double desiredSpeed = 10; - setDesiredSpeed(desiredSpeed); + setCategory("NavyUnit"); + setDesiredSpeed(10); }; void NavyUnit::AIloop() diff --git a/src/core/src/scheduler.cpp b/src/core/src/scheduler.cpp index 98c855ff..bcfe724e 100644 --- a/src/core/src/scheduler.cpp +++ b/src/core/src/scheduler.cpp @@ -288,7 +288,7 @@ void Scheduler::handleRequest(string key, json::value value) unit->setIsAWACS(value[L"isAWACS"].as_bool()); /* TACAN Options */ - Options::TACAN TACAN; + DataTypes::TACAN TACAN; TACAN.isOn = value[L"TACAN"][L"isOn"].as_bool(); TACAN.channel = static_cast(value[L"TACAN"][L"channel"].as_number().to_uint32()); TACAN.XY = to_string(value[L"TACAN"][L"XY"]).at(0); diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp index 3d2650b2..18748335 100644 --- a/src/core/src/unit.cpp +++ b/src/core/src/unit.cpp @@ -16,20 +16,20 @@ extern Scheduler* scheduler; extern UnitsManager* unitsManager; // TODO: Make dedicated file -bool operator==(const Options::TACAN& lhs, const Options::TACAN& rhs) +bool operator==(const DataTypes::TACAN& lhs, const DataTypes::TACAN& rhs) { return lhs.isOn == rhs.isOn && lhs.channel == rhs.channel && lhs.XY == rhs.XY && lhs.callsign == rhs.callsign; } -bool operator==(const Options::Radio& lhs, const Options::Radio& rhs) +bool operator==(const DataTypes::Radio& lhs, const DataTypes::Radio& rhs) { return lhs.frequency == rhs.frequency && lhs.callsign == rhs.callsign && lhs.callsignNumber == rhs.callsignNumber; } -bool operator==(const Options::GeneralSettings& lhs, const Options::GeneralSettings& rhs) +bool operator==(const DataTypes::GeneralSettings& lhs, const DataTypes::GeneralSettings& rhs) { return lhs.prohibitAA == rhs.prohibitAA && lhs.prohibitAfterburner == rhs.prohibitAfterburner && lhs.prohibitAG == rhs.prohibitAG && - lhs.prohibitAirWpn == rhs.prohibitAirWpn && lhs.prohibitJettison == rhs.prohibitJettison; + lhs.prohibitAirWpn == rhs.prohibitAirWpn && lhs.prohibitJettison == rhs.prohibitJettison; } Unit::Unit(json::value json, unsigned int ID) : @@ -87,7 +87,6 @@ void Unit::setDefaults(bool force) strcpy_s(TACAN.callsign, 4, "TKR"); setTACAN(TACAN, force); setRadio(radio, force); - setEPLRS(EPLRS, force); setGeneralSettings(generalSettings, force); } @@ -101,7 +100,7 @@ void Unit::runAILoop() { const bool isUnitAlive = getAlive(); const bool isUnitLeaderOfAGroupWithOtherUnits = unitsManager->isUnitInGroup(this) && unitsManager->isUnitGroupLeader(this); if (!(isUnitAlive || isUnitLeaderOfAGroupWithOtherUnits)) return; - + if (checkTaskFailed() && state != State::IDLE && State::LAND) setState(State::IDLE); @@ -113,14 +112,14 @@ void Unit::updateExportData(json::value json, double dt) Coords newPosition = Coords(NULL); double newHeading = 0; double newSpeed = 0; - + if (json.has_object_field(L"LatLongAlt")) { setPosition({ json[L"LatLongAlt"][L"Lat"].as_number().to_double(), json[L"LatLongAlt"][L"Long"].as_number().to_double(), json[L"LatLongAlt"][L"Alt"].as_number().to_double() - }); + }); } if (json.has_number_field(L"Heading")) setHeading(json[L"Heading"].as_number().to_double()); @@ -133,7 +132,7 @@ void Unit::updateExportData(json::value json, double dt) if (dt > 0) setSpeed(getSpeed() * 0.95 + (dist / dt) * 0.05); } - + oldPosition = position; } @@ -180,120 +179,67 @@ void Unit::updateMissionData(json::value json) setHasTask(json[L"hasTask"].as_bool()); } -unsigned int Unit::getDataPacket(char* &data) + +void Unit::getData(stringstream& ss, unsigned long long time, bool refresh) { - /* Reserve data for: - 1) DataPacket; - 2) Active path; - 3) Ammo vector; - 4) Contacts vector; - */ - data = (char*)malloc(sizeof(DataTypes::DataPacket) + - activePath.size() * sizeof(Coords) + - ammo.size() * sizeof(Coords) + - contacts.size() * sizeof(Coords)); - unsigned int offset = 0; - /* Prepare the data packet and copy it to memory */ - unsigned int bitmask = 0; - bitmask |= alive << 0; - bitmask |= human << 1; - bitmask |= controlled << 2; - bitmask |= hasTask << 3; - bitmask |= desiredAltitudeType << 16; - bitmask |= desiredSpeedType << 17; - bitmask |= isTanker << 18; - bitmask |= isAWACS << 19; - bitmask |= onOff << 20; - bitmask |= followRoads << 21; - bitmask |= EPLRS << 22; - bitmask |= generalSettings.prohibitAA << 23; - bitmask |= generalSettings.prohibitAfterburner << 24; - bitmask |= generalSettings.prohibitAG << 25; - bitmask |= generalSettings.prohibitAirWpn << 26; - bitmask |= generalSettings.prohibitJettison << 27; + /* If the unit is in a group, get the update data from the group leader and only replace the position: speed and heading */ + //if (unitsManager->isUnitInGroup(this) && !unitsManager->isUnitGroupLeader(this)) { + // DataTypes::DataPacket* p = (DataTypes::DataPacket*)data; + // p->position = position; + // p->speed = speed; + // p->heading = heading; + //} - DataTypes::DataPacket dataPacket{ - ID, - bitmask, - position, - speed, - heading, - fuel, - desiredSpeed, - desiredAltitude, - leaderID, - targetID, - targetPosition, - state, - ROE, - reactionToThreat, - emissionsCountermeasures, - coalition, - TACAN, - radio, - activePath.size(), - ammo.size(), - contacts.size(), - task.size() - }; + const unsigned char startOfData = DataIndex::startOfData; + const unsigned char endOfData = DataIndex::endOfData; - memcpy(data + offset, &dataPacket, sizeof(dataPacket)); - offset += sizeof(dataPacket); - - /* Prepare the path vector and copy it to memory */ - for (const Coords& c : activePath) { - memcpy(data + offset, &c, sizeof(Coords)); - offset += sizeof(Coords); + ss.write((const char*)&ID, sizeof(ID)); + ss.write((const char*)&startOfData, sizeof(startOfData)); + for (auto d : updateTimeMap) { + if (d.second > time) { + switch (d.first) { + case DataIndex::category: appendString(ss, d.first, category); break; + case DataIndex::alive: appendNumeric(ss, d.first, alive); break; + case DataIndex::human: appendNumeric(ss, d.first, human); break; + case DataIndex::controlled: appendNumeric(ss, d.first, controlled); break; + case DataIndex::coalition: appendNumeric(ss, d.first, coalition); break; + case DataIndex::country: appendNumeric(ss, d.first, country); break; + case DataIndex::name: appendString(ss, d.first, name); break; + case DataIndex::unitName: appendString(ss, d.first, unitName); break; + case DataIndex::groupName: appendString(ss, d.first, groupName); break; + case DataIndex::state: appendNumeric(ss, d.first, state); break; + case DataIndex::task: appendString(ss, d.first, task); break; + case DataIndex::hasTask: appendNumeric(ss, d.first, hasTask); break; + case DataIndex::position: appendNumeric(ss, d.first, position); break; + case DataIndex::speed: appendNumeric(ss, d.first, speed); break; + case DataIndex::heading: appendNumeric(ss, d.first, heading); break; + case DataIndex::isTanker: appendNumeric(ss, d.first, isTanker); break; + case DataIndex::isAWACS: appendNumeric(ss, d.first, isAWACS); break; + case DataIndex::onOff: appendNumeric(ss, d.first, onOff); break; + case DataIndex::followRoads: appendNumeric(ss, d.first, followRoads); break; + case DataIndex::fuel: appendNumeric(ss, d.first, fuel); break; + case DataIndex::desiredSpeed: appendNumeric(ss, d.first, desiredSpeed); break; + case DataIndex::desiredSpeedType: appendNumeric(ss, d.first, desiredSpeedType); break; + case DataIndex::desiredAltitude: appendNumeric(ss, d.first, desiredAltitude); break; + case DataIndex::desiredAltitudeType: appendNumeric(ss, d.first, desiredAltitudeType); break; + case DataIndex::leaderID: appendNumeric(ss, d.first, leaderID); break; + case DataIndex::formationOffset: appendNumeric(ss, d.first, formationOffset); break; + case DataIndex::targetID: appendNumeric(ss, d.first, targetID); break; + case DataIndex::targetPosition: appendNumeric(ss, d.first, targetPosition); break; + case DataIndex::ROE: appendNumeric(ss, d.first, ROE); break; + case DataIndex::reactionToThreat: appendNumeric(ss, d.first, reactionToThreat); break; + case DataIndex::emissionsCountermeasures: appendNumeric(ss, d.first, emissionsCountermeasures); break; + case DataIndex::TACAN: appendNumeric(ss, d.first, TACAN); break; + case DataIndex::radio: appendNumeric(ss, d.first, radio); break; + case DataIndex::generalSettings: appendNumeric(ss, d.first, generalSettings); break; + case DataIndex::ammo: appendVector(ss, d.first, ammo); break; + case DataIndex::contacts: appendVector(ss, d.first, contacts); break; + case DataIndex::activePath: appendList(ss, d.first, activePath); break; + } + } } - - /* Copy the ammo vector to memory */ - memcpy(data + offset, &ammo, ammo.size() * sizeof(DataTypes::Ammo)); - offset += ammo.size() * sizeof(DataTypes::Ammo); - - /* Copy the contacts vector to memory */ - memcpy(data + offset, &contacts, contacts.size() * sizeof(DataTypes::Contact)); - offset += contacts.size() * sizeof(DataTypes::Contact); - - return offset; -} - -void Unit::getData(stringstream &ss, unsigned long long time, bool refresh) -{ - if (time > lastUpdateTime && !refresh) return; - - char* data; - unsigned int size = getDataPacket(data); - - /* Prepare the data packet and copy it to memory */ - /* If the unit is in a group, get the update data from the group leader and only replace the position, speed and heading */ - if (unitsManager->isUnitInGroup(this) && !unitsManager->isUnitGroupLeader(this)) { - DataTypes::DataPacket* p = (DataTypes::DataPacket*)data; - p->position = position; - p->speed = speed; - p->heading = heading; - } - - ss.write(data, size); - ss << task; - - unsigned short nameLength = name.length(); - unsigned short unitNameLength = unitName.length(); - unsigned short groupNameLength = groupName.length(); - unsigned short categoryLength = getCategory().length(); - - ss.write((char*)&nameLength, sizeof(nameLength)); - ss.write((char*)&unitNameLength, sizeof(unitNameLength)); - ss.write((char*)&groupNameLength, sizeof(groupNameLength)); - ss.write((char*)&categoryLength, sizeof(categoryLength)); - - ss << name; - ss << unitName; - ss << groupName; - ss << getCategory(); - - - delete data; + ss.write((const char*)&endOfData, sizeof(endOfData)); } void Unit::setActivePath(list newPath) @@ -393,17 +339,17 @@ void Unit::setFormationOffset(Offset newFormationOffset) formationOffset = newFormationOffset; resetTask(); - triggerUpdate(); + triggerUpdate(DataIndex::formationOffset); } -void Unit::setROE(unsigned char newROE, bool force) +void Unit::setROE(unsigned char newROE, bool force) { if (ROE != newROE || force) { ROE = newROE; Command* command = dynamic_cast(new SetOption(groupName, SetCommandType::ROE, static_cast(ROE))); scheduler->appendCommand(command); - triggerUpdate(); + triggerUpdate(DataIndex::ROE); } } @@ -415,7 +361,7 @@ void Unit::setReactionToThreat(unsigned char newReactionToThreat, bool force) Command* command = dynamic_cast(new SetOption(groupName, SetCommandType::REACTION_ON_THREAT, static_cast(reactionToThreat))); scheduler->appendCommand(command); - triggerUpdate(); + triggerUpdate(DataIndex::reactionToThreat); } } @@ -465,39 +411,38 @@ void Unit::setEmissionsCountermeasures(unsigned char newEmissionsCountermeasures command = dynamic_cast(new SetOption(groupName, SetCommandType::ECM_USING, ECMEnum)); scheduler->appendCommand(command); - triggerUpdate(); + triggerUpdate(DataIndex::emissionsCountermeasures); } } -void Unit::landAt(Coords loc) +void Unit::landAt(Coords loc) { clearActivePath(); pushActivePathBack(loc); setState(State::LAND); } -void Unit::setIsTanker(bool newIsTanker) -{ +void Unit::setIsTanker(bool newIsTanker) +{ if (isTanker != newIsTanker) { isTanker = newIsTanker; resetTask(); - triggerUpdate(); + triggerUpdate(DataIndex::isTanker); } } void Unit::setIsAWACS(bool newIsAWACS) -{ +{ if (isAWACS != newIsAWACS) { isAWACS = newIsAWACS; resetTask(); - setEPLRS(isAWACS); - triggerUpdate(); + triggerUpdate(DataIndex::isAWACS); } } -void Unit::setTACAN(Options::TACAN newTACAN, bool force) +void Unit::setTACAN(DataTypes::TACAN newTACAN, bool force) { if (TACAN != newTACAN || force) { @@ -528,11 +473,11 @@ void Unit::setTACAN(Options::TACAN newTACAN, bool force) scheduler->appendCommand(command); } - triggerUpdate(); + triggerUpdate(DataIndex::TACAN); } } -void Unit::setRadio(Options::Radio newRadio, bool force) +void Unit::setRadio(DataTypes::Radio newRadio, bool force) { if (radio != newRadio || force) { @@ -564,30 +509,11 @@ void Unit::setRadio(Options::Radio newRadio, bool force) command = dynamic_cast(new SetCommand(groupName, commandSS.str())); scheduler->appendCommand(command); - triggerUpdate(); + triggerUpdate(DataIndex::radio); } } -void Unit::setEPLRS(bool newEPLRS, bool force) -{ - //addMeasure("EPLRS", json::value(newEPLRS)); - // - //if (EPLRS != newEPLRS || force) { - // EPLRS = newEPLRS; - // - // std::ostringstream commandSS; - // commandSS << "{" - // << "id = 'EPLRS'," - // << "params = {" - // << "value = " << (EPLRS ? "true" : "false") << ", " - // << "}" - // << "}"; - // Command* command = dynamic_cast(new SetCommand(ID, commandSS.str())); - // scheduler->appendCommand(command); - //} -} - -void Unit::setGeneralSettings(Options::GeneralSettings newGeneralSettings, bool force) +void Unit::setGeneralSettings(DataTypes::GeneralSettings newGeneralSettings, bool force) { if (generalSettings != newGeneralSettings) { @@ -605,22 +531,22 @@ void Unit::setGeneralSettings(Options::GeneralSettings newGeneralSettings, bool command = dynamic_cast(new SetOption(groupName, SetCommandType::ENGAGE_AIR_WEAPONS, !generalSettings.prohibitAirWpn)); scheduler->appendCommand(command); - triggerUpdate(); + triggerUpdate(DataIndex::generalSettings); } } -void Unit::setDesiredSpeed(double newDesiredSpeed) +void Unit::setDesiredSpeed(double newDesiredSpeed) { - desiredSpeed = newDesiredSpeed; + desiredSpeed = newDesiredSpeed; if (state == State::IDLE) resetTask(); else goToDestination(); /* Send the command to reach the destination */ - triggerUpdate(); + triggerUpdate(DataIndex::desiredSpeed); } -void Unit::setDesiredAltitude(double newDesiredAltitude) +void Unit::setDesiredAltitude(double newDesiredAltitude) { desiredAltitude = newDesiredAltitude; if (state == State::IDLE) @@ -628,10 +554,10 @@ void Unit::setDesiredAltitude(double newDesiredAltitude) else goToDestination(); /* Send the command to reach the destination */ - triggerUpdate(); + triggerUpdate(DataIndex::desiredAltitude); } -void Unit::setDesiredSpeedType(string newDesiredSpeedType) +void Unit::setDesiredSpeedType(string newDesiredSpeedType) { desiredSpeedType = newDesiredSpeedType.compare("GS") == 0; if (state == State::IDLE) @@ -639,10 +565,10 @@ void Unit::setDesiredSpeedType(string newDesiredSpeedType) else goToDestination(); /* Send the command to reach the destination */ - triggerUpdate(); + triggerUpdate(DataIndex::desiredSpeedType); } -void Unit::setDesiredAltitudeType(string newDesiredAltitudeType) +void Unit::setDesiredAltitudeType(string newDesiredAltitudeType) { desiredAltitudeType = newDesiredAltitudeType.compare("AGL") == 0; if (state == State::IDLE) @@ -650,14 +576,14 @@ void Unit::setDesiredAltitudeType(string newDesiredAltitudeType) else goToDestination(); /* Send the command to reach the destination */ - triggerUpdate(); + triggerUpdate(DataIndex::desiredAltitudeType); } void Unit::goToDestination(string enrouteTask) { if (activeDestination != NULL) { - Command* command = dynamic_cast(new Move(groupName, activeDestination, getDesiredSpeed(), getDesiredSpeedType()? "GS": "CAS", getDesiredAltitude(), getDesiredAltitudeType()? "AGL" : "ASL", enrouteTask, getCategory())); + Command* command = dynamic_cast(new Move(groupName, activeDestination, getDesiredSpeed(), getDesiredSpeedType() ? "GS" : "CAS", getDesiredAltitude(), getDesiredAltitudeType() ? "AGL" : "ASL", enrouteTask, getCategory())); scheduler->appendCommand(command); setHasTask(true); } @@ -668,7 +594,7 @@ bool Unit::isDestinationReached(double threshold) if (activeDestination != NULL) { /* Check if any unit in the group has reached the point */ - for (auto const& p: unitsManager->getGroupMembers(groupName)) + for (auto const& p : unitsManager->getGroupMembers(groupName)) { double dist = 0; Geodesic::WGS84().Inverse(p->getPosition().lat, p->getPosition().lng, activeDestination.lat, activeDestination.lng, dist); @@ -694,7 +620,7 @@ bool Unit::setActiveDestination() activeDestination = activePath.front(); log(unitName + " active destination set to queue front"); - triggerUpdate(); + triggerUpdate(DataIndex::activePath); return true; } else @@ -702,7 +628,7 @@ bool Unit::setActiveDestination() activeDestination = Coords(0); log(unitName + " active destination set to NULL"); - triggerUpdate(); + triggerUpdate(DataIndex::activePath); return false; } } @@ -723,9 +649,9 @@ bool Unit::updateActivePath(bool looping) } } -bool Unit::checkTaskFailed() +bool Unit::checkTaskFailed() { - if (getHasTask()) + if (getHasTask()) return false; else { if (taskCheckCounter > 0) @@ -737,3 +663,7 @@ bool Unit::checkTaskFailed() void Unit::resetTaskFailedCounter() { taskCheckCounter = TASK_CHECK_INIT_VALUE; } + +void Unit::triggerUpdate(unsigned char datumIndex) { + updateTimeMap[datumIndex] = duration_cast(system_clock::now().time_since_epoch()).count(); +} diff --git a/src/core/src/weapon.cpp b/src/core/src/weapon.cpp index 3308c1e8..64a1850a 100644 --- a/src/core/src/weapon.cpp +++ b/src/core/src/weapon.cpp @@ -22,10 +22,12 @@ Weapon::Weapon(json::value json, unsigned int ID) : Unit(json, ID) Missile::Missile(json::value json, unsigned int ID) : Weapon(json, ID) { log("New Missile created with ID: " + to_string(ID)); + setCategory("Missile"); }; /* Bomb */ Bomb::Bomb(json::value json, unsigned int ID) : Weapon(json, ID) { log("New Bomb created with ID: " + to_string(ID)); + setCategory("Bomb"); }; \ No newline at end of file From 211cf4868111e2ffc35048d0eae79cea84ea8451 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Thu, 29 Jun 2023 08:10:14 +0200 Subject: [PATCH 11/15] More fixes on binary data transmission --- client/src/units/unit.ts | 96 ++++++++++++++++---------------- src/core/core.vcxproj | 2 + src/core/core.vcxproj.filters | 6 ++ src/core/include/datatypes.h | 49 +++++++++++++++++ src/core/include/unit.h | 66 +++++----------------- src/core/src/core.cpp | 31 +++++++---- src/core/src/datatypes.cpp | 30 ++++++++++ src/core/src/unit.cpp | 100 +++++++++++++++------------------- 8 files changed, 213 insertions(+), 167 deletions(-) create mode 100644 src/core/include/datatypes.h create mode 100644 src/core/src/datatypes.cpp diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index fe90f149..13d7685b 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -22,7 +22,7 @@ export class Unit extends CustomMarker { #alive: boolean = false; #human: boolean = false; #controlled: boolean = false; - #coalition: string = ""; + #coalition: string = "neutral"; #country: number = 0; #name: string = ""; #unitName: string = ""; @@ -30,7 +30,7 @@ export class Unit extends CustomMarker { #state: string = states[0]; #task: string = "" #hasTask: boolean = false; - #position: LatLng = new LatLng(0, 0); + #position: LatLng = new LatLng(0, 0, 0); #speed: number = 0; #heading: number = 0; #isTanker: boolean = false; @@ -50,13 +50,13 @@ export class Unit extends CustomMarker { }; #targetID: number = 0; #targetPosition: LatLng = new LatLng(0, 0); - #ROE: string = ROEs[0]; - #reactionToThreat: string = reactionsToThreat[0]; - #emissionsCountermeasures: string = emissionsCountermeasures[0]; + #ROE: string = ROEs[1]; + #reactionToThreat: string = reactionsToThreat[2]; + #emissionsCountermeasures: string = emissionsCountermeasures[2]; #TACAN: TACAN = { isOn: false, XY: 'X', - callsign: '', + callsign: 'TKR', channel: 0 }; #radio: Radio = { @@ -135,50 +135,50 @@ export class Unit extends CustomMarker { setData(dataExtractor: DataExtractor) { var updateMarker = !getMap().hasLayer(this); - var datumIndex = dataExtractor.extractUInt8(); - if (datumIndex == DataIndexes.startOfData) { - while (datumIndex != DataIndexes.endOfData) { - datumIndex = dataExtractor.extractUInt8(); - switch (datumIndex) { - case DataIndexes.alive: this.setAlive(dataExtractor.extractBool()); updateMarker = true; break; - case DataIndexes.human: this.#human = dataExtractor.extractBool(); break; - case DataIndexes.controlled: this.#controlled = dataExtractor.extractBool(); updateMarker = true; break; - case DataIndexes.coalition: this.#coalition = enumToCoalition(dataExtractor.extractUInt8()); break; - case DataIndexes.country: this.#country = dataExtractor.extractUInt8(); break; - case DataIndexes.name: this.#name = dataExtractor.extractString(); break; - case DataIndexes.unitName: this.#unitName = dataExtractor.extractString(); break; - case DataIndexes.groupName: this.#groupName = dataExtractor.extractString(); break; - case DataIndexes.state: this.#state = enumToState(dataExtractor.extractUInt8()); updateMarker = true; break; - case DataIndexes.task: this.#task = dataExtractor.extractString(); break; - case DataIndexes.hasTask: this.#hasTask = dataExtractor.extractBool(); break; - case DataIndexes.position: this.#position = dataExtractor.extractLatLng(); updateMarker = true; break; - case DataIndexes.speed: this.#speed = dataExtractor.extractFloat64(); updateMarker = true; break; - case DataIndexes.heading: this.#heading = dataExtractor.extractFloat64(); updateMarker = true; break; - case DataIndexes.isTanker: this.#isTanker = dataExtractor.extractBool(); break; - case DataIndexes.isAWACS: this.#isAWACS = dataExtractor.extractBool(); break; - case DataIndexes.onOff: this.#onOff = dataExtractor.extractBool(); break; - case DataIndexes.followRoads: this.#followRoads = dataExtractor.extractBool(); break; - case DataIndexes.fuel: this.#fuel = dataExtractor.extractUInt16(); break; - case DataIndexes.desiredSpeed: this.#desiredSpeed = dataExtractor.extractFloat64(); break; - case DataIndexes.desiredSpeedType: this.#desiredSpeedType = dataExtractor.extractBool() ? "GS" : "CAS"; break; - case DataIndexes.desiredAltitude: this.#desiredAltitude = dataExtractor.extractFloat64(); break; - case DataIndexes.desiredAltitudeType: this.#desiredAltitudeType = dataExtractor.extractFloat64() ? "AGL" : "ASL"; break; - case DataIndexes.leaderID: this.#leaderID = dataExtractor.extractUInt32(); break; - case DataIndexes.formationOffset: dataExtractor.extractOffset(); break; - case DataIndexes.targetID: this.#targetID = dataExtractor.extractUInt32(); break; - case DataIndexes.targetPosition: this.#targetPosition = dataExtractor.extractLatLng(); break; - case DataIndexes.ROE: this.#ROE = enumToROE(dataExtractor.extractUInt8()); break; - case DataIndexes.reactionToThreat: this.#reactionToThreat = enumToReactionToThreat(dataExtractor.extractUInt8()); break; - case DataIndexes.emissionsCountermeasures: this.#emissionsCountermeasures = enumToEmissioNCountermeasure(dataExtractor.extractUInt8()); break; - case DataIndexes.TACAN: this.#TACAN = dataExtractor.extractTACAN(); break; - case DataIndexes.radio: this.#radio = dataExtractor.extractRadio(); break; - case DataIndexes.generalSettings: this.#generalSettings = dataExtractor.extractGeneralSettings(); break; - case DataIndexes.ammo: this.#ammo = dataExtractor.extractAmmo(); break; - case DataIndexes.contacts: this.#contacts = dataExtractor.extractContacts(); break; - case DataIndexes.activePath: this.#activePath = dataExtractor.extractActivePath(); break; - } + var datumIndex = 0; + while (datumIndex != DataIndexes.endOfData) { + datumIndex = dataExtractor.extractUInt8(); + switch (datumIndex) { + case DataIndexes.category: dataExtractor.extractString(); break; + case DataIndexes.alive: this.setAlive(dataExtractor.extractBool()); updateMarker = true; break; + case DataIndexes.human: this.#human = dataExtractor.extractBool(); break; + case DataIndexes.controlled: this.#controlled = dataExtractor.extractBool(); updateMarker = true; break; + case DataIndexes.coalition: this.#coalition = enumToCoalition(dataExtractor.extractUInt8()); break; + case DataIndexes.country: this.#country = dataExtractor.extractUInt8(); break; + case DataIndexes.name: this.#name = dataExtractor.extractString(); break; + case DataIndexes.unitName: this.#unitName = dataExtractor.extractString(); break; + case DataIndexes.groupName: this.#groupName = dataExtractor.extractString(); break; + case DataIndexes.state: this.#state = enumToState(dataExtractor.extractUInt8()); updateMarker = true; break; + case DataIndexes.task: this.#task = dataExtractor.extractString(); break; + case DataIndexes.hasTask: this.#hasTask = dataExtractor.extractBool(); break; + case DataIndexes.position: this.#position = dataExtractor.extractLatLng(); updateMarker = true; break; + case DataIndexes.speed: this.#speed = dataExtractor.extractFloat64(); updateMarker = true; break; + case DataIndexes.heading: this.#heading = dataExtractor.extractFloat64(); updateMarker = true; break; + case DataIndexes.isTanker: this.#isTanker = dataExtractor.extractBool(); break; + case DataIndexes.isAWACS: this.#isAWACS = dataExtractor.extractBool(); break; + case DataIndexes.onOff: this.#onOff = dataExtractor.extractBool(); break; + case DataIndexes.followRoads: this.#followRoads = dataExtractor.extractBool(); break; + case DataIndexes.fuel: this.#fuel = dataExtractor.extractUInt16(); break; + case DataIndexes.desiredSpeed: this.#desiredSpeed = dataExtractor.extractFloat64(); break; + case DataIndexes.desiredSpeedType: this.#desiredSpeedType = dataExtractor.extractBool() ? "GS" : "CAS"; break; + case DataIndexes.desiredAltitude: this.#desiredAltitude = dataExtractor.extractFloat64(); break; + case DataIndexes.desiredAltitudeType: this.#desiredAltitudeType = dataExtractor.extractFloat64() ? "AGL" : "ASL"; break; + case DataIndexes.leaderID: this.#leaderID = dataExtractor.extractUInt32(); break; + case DataIndexes.formationOffset: this.#formationOffset = dataExtractor.extractOffset(); break; + case DataIndexes.targetID: this.#targetID = dataExtractor.extractUInt32(); break; + case DataIndexes.targetPosition: this.#targetPosition = dataExtractor.extractLatLng(); break; + case DataIndexes.ROE: this.#ROE = enumToROE(dataExtractor.extractUInt8()); break; + case DataIndexes.reactionToThreat: this.#reactionToThreat = enumToReactionToThreat(dataExtractor.extractUInt8()); break; + case DataIndexes.emissionsCountermeasures: this.#emissionsCountermeasures = enumToEmissioNCountermeasure(dataExtractor.extractUInt8()); break; + case DataIndexes.TACAN: this.#TACAN = dataExtractor.extractTACAN(); break; + case DataIndexes.radio: this.#radio = dataExtractor.extractRadio(); break; + case DataIndexes.generalSettings: this.#generalSettings = dataExtractor.extractGeneralSettings(); break; + case DataIndexes.ammo: this.#ammo = dataExtractor.extractAmmo(); break; + case DataIndexes.contacts: this.#contacts = dataExtractor.extractContacts(); break; + case DataIndexes.activePath: this.#activePath = dataExtractor.extractActivePath(); break; } } + /* Dead units can't be selected */ this.setSelected(this.getSelected() && this.#alive && !this.getHidden()) diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj index 4759cb4f..2b70e07f 100644 --- a/src/core/core.vcxproj +++ b/src/core/core.vcxproj @@ -36,6 +36,7 @@ + @@ -52,6 +53,7 @@ + diff --git a/src/core/core.vcxproj.filters b/src/core/core.vcxproj.filters index 00e7a412..50797e65 100644 --- a/src/core/core.vcxproj.filters +++ b/src/core/core.vcxproj.filters @@ -48,6 +48,9 @@ Header Files + + Header Files + @@ -92,5 +95,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/src/core/include/datatypes.h b/src/core/include/datatypes.h new file mode 100644 index 00000000..14eb91b8 --- /dev/null +++ b/src/core/include/datatypes.h @@ -0,0 +1,49 @@ +#pragma once +#include "framework.h" + +#pragma pack(push, 1) +namespace DataTypes { + struct TACAN + { + bool isOn = false; + unsigned char channel = 40; + char XY = 'X'; + char callsign[4]; + }; + + struct Radio + { + unsigned int frequency = 124000000; // MHz + unsigned char callsign = 1; + unsigned char callsignNumber = 1; + }; + + struct GeneralSettings + { + bool prohibitJettison = false; + bool prohibitAA = false; + bool prohibitAG = false; + bool prohibitAfterburner = false; + bool prohibitAirWpn = false; + }; + + struct Ammo { + unsigned short quantity = 0; + char name[33]; + unsigned char guidance = 0; + unsigned char category = 0; + unsigned char missileCategory = 0; + }; + + struct Contact { + unsigned int ID = 0; + unsigned char detectionMethod = 0; + }; +} +#pragma pack(pop) + +bool operator==(const DataTypes::TACAN& lhs, const DataTypes::TACAN& rhs); +bool operator==(const DataTypes::Radio& lhs, const DataTypes::Radio& rhs); +bool operator==(const DataTypes::GeneralSettings& lhs, const DataTypes::GeneralSettings& rhs); +bool operator==(const DataTypes::Ammo& lhs, const DataTypes::Ammo& rhs); +bool operator==(const DataTypes::Contact& lhs, const DataTypes::Contact& rhs); diff --git a/src/core/include/unit.h b/src/core/include/unit.h index c9670358..fa973d5b 100644 --- a/src/core/include/unit.h +++ b/src/core/include/unit.h @@ -6,6 +6,7 @@ #include "measure.h" #include "logger.h" #include "commands.h" +#include "datatypes.h" #include using namespace std::chrono; @@ -76,47 +77,6 @@ namespace State }; }; -#pragma pack(push, 1) -namespace DataTypes { - struct TACAN - { - bool isOn = false; - unsigned char channel = 40; - char XY = 'X'; - char callsign[4]; - }; - - struct Radio - { - unsigned int frequency = 124000000; // MHz - unsigned char callsign = 1; - unsigned char callsignNumber = 1; - }; - - struct GeneralSettings - { - bool prohibitJettison = false; - bool prohibitAA = false; - bool prohibitAG = false; - bool prohibitAfterburner = false; - bool prohibitAirWpn = false; - }; - - struct Ammo { - unsigned short quantity = 0; - char name[32]; - unsigned char guidance = 0; - unsigned char category = 0; - unsigned char missileCategory = 0; - }; - - struct Contact { - unsigned int ID = 0; - unsigned char detectionMethod = 0; - }; -} -#pragma pack(pop) - class Unit { public: @@ -168,7 +128,7 @@ public: virtual void setHuman(bool newValue) { updateValue(human, newValue, DataIndex::human); } virtual void setControlled(bool newValue) { updateValue(controlled, newValue, DataIndex::controlled); } virtual void setCoalition(unsigned char newValue) { updateValue(coalition, newValue, DataIndex::coalition); } - virtual void setCountry(unsigned int newValue) { updateValue(country, newValue, DataIndex::country); } + virtual void setCountry(unsigned char newValue) { updateValue(country, newValue, DataIndex::country); } virtual void setName(string newValue) { updateValue(name, newValue, DataIndex::name); } virtual void setUnitName(string newValue) { updateValue(unitName, newValue, DataIndex::unitName); } virtual void setGroupName(string newValue) { updateValue(groupName, newValue, DataIndex::groupName); } @@ -197,8 +157,8 @@ public: virtual void setTACAN(DataTypes::TACAN newValue, bool force = false); virtual void setRadio(DataTypes::Radio newValue, bool force = false); virtual void setGeneralSettings(DataTypes::GeneralSettings newValue, bool force = false); - virtual void setAmmo(vector newAmmo) { ammo = newAmmo; } - virtual void setContacts(vector newContacts) { contacts = newContacts; } + virtual void setAmmo(vector newValue) { updateValue(ammo, newValue, DataIndex::ammo); } + virtual void setContacts(vector newValue) { updateValue(contacts, newValue, DataIndex::contacts); } virtual void setActivePath(list newValue); /********** Getters **********/ @@ -207,7 +167,7 @@ public: virtual bool getHuman() { return human; } virtual bool getControlled() { return controlled; } virtual unsigned int getCoalition() { return coalition; } - virtual unsigned int getCountry() { return country; } + virtual unsigned char getCountry() { return country; } virtual string getName() { return name; } virtual string getUnitName() { return unitName; } virtual string getGroupName() { return groupName; } @@ -244,14 +204,14 @@ protected: unsigned int ID; string category; - bool alive = true; + bool alive = false; bool human = false; bool controlled = false; - unsigned char coalition; - unsigned int country = NULL; - string name = "undefined"; - string unitName = "undefined"; - string groupName = "undefined"; + unsigned char coalition = NULL; + unsigned char country = NULL; + string name = ""; + string unitName = ""; + string groupName = ""; unsigned char state = State::NONE; string task = ""; bool hasTask = false; @@ -260,7 +220,7 @@ protected: double heading = NULL; bool isTanker = false; bool isAWACS = false; - bool onOff = true; + bool onOff = false; bool followRoads = false; unsigned short fuel = 0; double desiredSpeed = 0; @@ -329,7 +289,7 @@ protected: ss.write((const char*)&datumIndex, sizeof(unsigned char)); ss.write((const char*)&size, sizeof(unsigned short)); - for (auto el: datumValue) + for (auto& el: datumValue) ss.write((const char*)&el, sizeof(T)); } }; diff --git a/src/core/src/core.cpp b/src/core/src/core.cpp index 9dcc5a4b..b2e7263d 100644 --- a/src/core/src/core.cpp +++ b/src/core/src/core.cpp @@ -105,18 +105,27 @@ extern "C" DllExport int coreMissionData(lua_State * L) /* Lock for thread safety */ lock_guard guard(mutexLock); - lua_getglobal(L, "Olympus"); - lua_getfield(L, -1, "missionData"); - json::value missionData = luaTableToJSON(L, -1); + try + { + lua_getglobal(L, "Olympus"); + lua_getfield(L, -1, "missionData"); + json::value missionData = luaTableToJSON(L, -1); - if (missionData.has_object_field(L"unitsData")) - unitsManager->updateMissionData(missionData[L"unitsData"]); - if (missionData.has_object_field(L"airbases")) - airbases = missionData[L"airbases"]; - if (missionData.has_object_field(L"bullseyes")) - bullseyes = missionData[L"bullseyes"]; - if (missionData.has_object_field(L"mission")) - mission = missionData[L"mission"]; + if (missionData.has_object_field(L"unitsData")) + unitsManager->updateMissionData(missionData[L"unitsData"]); + if (missionData.has_object_field(L"airbases")) + airbases = missionData[L"airbases"]; + if (missionData.has_object_field(L"bullseyes")) + bullseyes = missionData[L"bullseyes"]; + if (missionData.has_object_field(L"mission")) + mission = missionData[L"mission"]; + } + catch (exception const& e) + { + log(e.what()); + } + + return(0); } diff --git a/src/core/src/datatypes.cpp b/src/core/src/datatypes.cpp new file mode 100644 index 00000000..eb0c52cd --- /dev/null +++ b/src/core/src/datatypes.cpp @@ -0,0 +1,30 @@ +#include "datatypes.h" + +bool operator==(const DataTypes::TACAN& lhs, const DataTypes::TACAN& rhs) +{ + return lhs.isOn == rhs.isOn && lhs.channel == rhs.channel && lhs.XY == rhs.XY && strcmp(lhs.callsign, rhs.callsign) == 0; +} + +bool operator==(const DataTypes::Radio& lhs, const DataTypes::Radio& rhs) +{ + return lhs.frequency == rhs.frequency && lhs.callsign == rhs.callsign && lhs.callsignNumber == rhs.callsignNumber; +} + +bool operator==(const DataTypes::GeneralSettings& lhs, const DataTypes::GeneralSettings& rhs) +{ + return lhs.prohibitAA == rhs.prohibitAA && lhs.prohibitAfterburner == rhs.prohibitAfterburner && lhs.prohibitAG == rhs.prohibitAG && + lhs.prohibitAirWpn == rhs.prohibitAirWpn && lhs.prohibitJettison == rhs.prohibitJettison; +} + +bool operator==(const DataTypes::Ammo& lhs, const DataTypes::Ammo& rhs) +{ + return lhs.category == rhs.category && lhs.guidance == rhs.guidance && lhs.missileCategory == rhs.missileCategory && + lhs.quantity == rhs.quantity && strcmp(lhs.name, rhs.name) == 0; +} + +bool operator==(const DataTypes::Contact& lhs, const DataTypes::Contact& rhs) +{ + return lhs.detectionMethod == rhs.detectionMethod && lhs.ID == rhs.ID; +} + + diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp index 18748335..5d3788de 100644 --- a/src/core/src/unit.cpp +++ b/src/core/src/unit.cpp @@ -15,23 +15,6 @@ using namespace GeographicLib; extern Scheduler* scheduler; extern UnitsManager* unitsManager; -// TODO: Make dedicated file -bool operator==(const DataTypes::TACAN& lhs, const DataTypes::TACAN& rhs) -{ - return lhs.isOn == rhs.isOn && lhs.channel == rhs.channel && lhs.XY == rhs.XY && lhs.callsign == rhs.callsign; -} - -bool operator==(const DataTypes::Radio& lhs, const DataTypes::Radio& rhs) -{ - return lhs.frequency == rhs.frequency && lhs.callsign == rhs.callsign && lhs.callsignNumber == rhs.callsignNumber; -} - -bool operator==(const DataTypes::GeneralSettings& lhs, const DataTypes::GeneralSettings& rhs) -{ - return lhs.prohibitAA == rhs.prohibitAA && lhs.prohibitAfterburner == rhs.prohibitAfterburner && lhs.prohibitAG == rhs.prohibitAG && - lhs.prohibitAirWpn == rhs.prohibitAirWpn && lhs.prohibitJettison == rhs.prohibitJettison; -} - Unit::Unit(json::value json, unsigned int ID) : ID(ID) { @@ -138,42 +121,51 @@ void Unit::updateExportData(json::value json, double dt) void Unit::updateMissionData(json::value json) { - //if (json.has_number_field(L"fuel")) - // setFuel(short(json[L"fuel"].as_number().to_double() * 100)); - // - //if (json.has_object_field(L"ammo")) { - // vector ammo; - // for (auto const& el : json[L"ammo"].as_object()) { - // DataTypes::Ammo ammoItem; - // auto ammoJson = el.second; - // ammoItem.quantity = ammoJson[L"count"].as_number().to_uint32(); - // ammoItem.name = to_string(ammoJson[L"desc"][L"displayName"]); - // ammoItem.guidance = ammoJson[L"desc"][L"guidance"].as_number().to_uint32(); - // ammoItem.category = ammoJson[L"desc"][L"category"].as_number().to_uint32(); - // ammoItem.missileCategory = ammoJson[L"desc"][L"missileCategory"].as_number().to_uint32(); - // ammo.push_back(ammoItem); - // } - // setAmmo(ammo); - //} - // - //if (json.has_object_field(L"contacts")) { - // vector contacts; - // for (auto const& el : json[L"ammo"].as_object()) { - // DataTypes::Contact contactItem; - // auto contactJson = el.second; - // contactItem.ID = contactJson[L"object"][L"id_"].as_number().to_uint32(); - // - // string detectionMethod = to_string(contactJson[L"detectionMethod"]); - // if (detectionMethod.compare("VISUAL")) contactItem.detectionMethod = 1; - // else if (detectionMethod.compare("OPTIC")) contactItem.detectionMethod = 2; - // else if (detectionMethod.compare("RADAR")) contactItem.detectionMethod = 4; - // else if (detectionMethod.compare("IRST")) contactItem.detectionMethod = 8; - // else if (detectionMethod.compare("RWR")) contactItem.detectionMethod = 16; - // else if (detectionMethod.compare("DLINK")) contactItem.detectionMethod = 32; - // contacts.push_back(contactItem); - // } - // setContacts(contacts); - //} + if (json.has_number_field(L"fuel")) { + setFuel(short(json[L"fuel"].as_number().to_double() * 100)); + } + + if (json.has_object_field(L"ammo")) { + vector ammo; + for (auto const& el : json[L"ammo"].as_object()) { + log(el.second.serialize()); + DataTypes::Ammo ammoItem; + auto ammoJson = el.second; + ammoItem.quantity = ammoJson[L"count"].as_number().to_uint32(); + string name = to_string(ammoJson[L"desc"][L"displayName"].as_string()).substr(0, sizeof(ammoItem.name) - 1); + strcpy_s(ammoItem.name, sizeof(ammoItem.name), name.c_str()); + + if (ammoJson[L"desc"].has_number_field(L"guidance")) + ammoItem.guidance = ammoJson[L"desc"][L"guidance"].as_number().to_uint32(); + + if (ammoJson[L"desc"].has_number_field(L"category")) + ammoItem.category = ammoJson[L"desc"][L"category"].as_number().to_uint32(); + + if (ammoJson[L"desc"].has_number_field(L"missileCategory")) + ammoItem.missileCategory = ammoJson[L"desc"][L"missileCategory"].as_number().to_uint32(); + ammo.push_back(ammoItem); + } + setAmmo(ammo); + } + + if (json.has_object_field(L"contacts")) { + vector contacts; + for (auto const& el : json[L"contacts"].as_object()) { + DataTypes::Contact contactItem; + auto contactJson = el.second; + contactItem.ID = contactJson[L"object"][L"id_"].as_number().to_uint32(); + + string detectionMethod = to_string(contactJson[L"detectionMethod"]); + if (detectionMethod.compare("VISUAL")) contactItem.detectionMethod = 1; + else if (detectionMethod.compare("OPTIC")) contactItem.detectionMethod = 2; + else if (detectionMethod.compare("RADAR")) contactItem.detectionMethod = 4; + else if (detectionMethod.compare("IRST")) contactItem.detectionMethod = 8; + else if (detectionMethod.compare("RWR")) contactItem.detectionMethod = 16; + else if (detectionMethod.compare("DLINK")) contactItem.detectionMethod = 32; + contacts.push_back(contactItem); + } + setContacts(contacts); + } if (json.has_boolean_field(L"hasTask")) setHasTask(json[L"hasTask"].as_bool()); @@ -191,11 +183,9 @@ void Unit::getData(stringstream& ss, unsigned long long time, bool refresh) // p->heading = heading; //} - const unsigned char startOfData = DataIndex::startOfData; const unsigned char endOfData = DataIndex::endOfData; ss.write((const char*)&ID, sizeof(ID)); - ss.write((const char*)&startOfData, sizeof(startOfData)); for (auto d : updateTimeMap) { if (d.second > time) { switch (d.first) { From 807cdfeb5b415d73398faccccb110d8ed3b40daf Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Sat, 1 Jul 2023 12:31:30 +0200 Subject: [PATCH 12/15] Added code to update ammo and contacts --- src/core/include/unit.h | 92 ++++++++++++++++++------------------ src/core/src/unit.cpp | 102 ++++++++++++++++++++++++++-------------- 2 files changed, 112 insertions(+), 82 deletions(-) diff --git a/src/core/include/unit.h b/src/core/include/unit.h index fa973d5b..df335df9 100644 --- a/src/core/include/unit.h +++ b/src/core/include/unit.h @@ -157,48 +157,48 @@ public: virtual void setTACAN(DataTypes::TACAN newValue, bool force = false); virtual void setRadio(DataTypes::Radio newValue, bool force = false); virtual void setGeneralSettings(DataTypes::GeneralSettings newValue, bool force = false); - virtual void setAmmo(vector newValue) { updateValue(ammo, newValue, DataIndex::ammo); } - virtual void setContacts(vector newValue) { updateValue(contacts, newValue, DataIndex::contacts); } + virtual void setAmmo(vector newValue); + virtual void setContacts(vector newValue); virtual void setActivePath(list newValue); /********** Getters **********/ - virtual string getCategory() { return category; }; - virtual bool getAlive() { return alive; } - virtual bool getHuman() { return human; } - virtual bool getControlled() { return controlled; } - virtual unsigned int getCoalition() { return coalition; } - virtual unsigned char getCountry() { return country; } - virtual string getName() { return name; } - virtual string getUnitName() { return unitName; } - virtual string getGroupName() { return groupName; } - virtual unsigned char getState() { return state; } - virtual string getTask() { return task; } - virtual bool getHasTask() { return hasTask; } - virtual Coords getPosition() { return position; } - virtual double getSpeed() { return speed; } - virtual double getHeading() { return heading; } - virtual bool getIsTanker() { return isTanker; } - virtual bool getIsAWACS() { return isAWACS; } - virtual bool getOnOff() { return onOff; }; - virtual bool getFollowRoads() { return followRoads; }; - virtual double getFuel() { return fuel; } - virtual double getDesiredSpeed() { return desiredSpeed; }; - virtual bool getDesiredSpeedType() { return desiredSpeedType; }; - virtual double getDesiredAltitude() { return desiredAltitude; }; - virtual bool getDesiredAltitudeType() { return desiredAltitudeType; }; - virtual unsigned int getLeaderID() { return leaderID; } - virtual Offset getFormationoffset() { return formationOffset; } - virtual unsigned int getTargetID() { return targetID; } - virtual Coords getTargetPosition() { return targetPosition; } - virtual unsigned char getROE() { return ROE; } - virtual unsigned char getReactionToThreat() { return reactionToThreat; } - virtual unsigned char getEmissionsCountermeasures() { return emissionsCountermeasures; }; - virtual DataTypes::TACAN getTACAN() { return TACAN; } - virtual DataTypes::Radio getRadio() { return radio; } - virtual DataTypes::GeneralSettings getGeneralSettings() { return generalSettings; } - virtual vector getAmmo() { return ammo; } - virtual vector getTargets() { return contacts; } - virtual list getActivePath() { return activePath; } + virtual const string& getCategory() { return category; }; + virtual const bool& getAlive() { return alive; } + virtual const bool& getHuman() { return human; } + virtual const bool& getControlled() { return controlled; } + virtual const unsigned char& getCoalition() { return coalition; } + virtual const unsigned char& getCountry() { return country; } + virtual const string& getName() { return name; } + virtual const string& getUnitName() { return unitName; } + virtual const string& getGroupName() { return groupName; } + virtual const unsigned char& getState() { return state; } + virtual const string& getTask() { return task; } + virtual const bool& getHasTask() { return hasTask; } + virtual const Coords& getPosition() { return position; } + virtual const double& getSpeed() { return speed; } + virtual const double& getHeading() { return heading; } + virtual const bool& getIsTanker() { return isTanker; } + virtual const bool& getIsAWACS() { return isAWACS; } + virtual const bool& getOnOff() { return onOff; }; + virtual const bool& getFollowRoads() { return followRoads; }; + virtual const unsigned short& getFuel() { return fuel; } + virtual const double& getDesiredSpeed() { return desiredSpeed; }; + virtual const bool& getDesiredSpeedType() { return desiredSpeedType; }; + virtual const double& getDesiredAltitude() { return desiredAltitude; }; + virtual const bool& getDesiredAltitudeType() { return desiredAltitudeType; }; + virtual const unsigned int& getLeaderID() { return leaderID; } + virtual const Offset& getFormationoffset() { return formationOffset; } + virtual const unsigned int& getTargetID() { return targetID; } + virtual const Coords& getTargetPosition() { return targetPosition; } + virtual const unsigned char& getROE() { return ROE; } + virtual const unsigned char& getReactionToThreat() { return reactionToThreat; } + virtual const unsigned char& getEmissionsCountermeasures() { return emissionsCountermeasures; }; + virtual const DataTypes::TACAN& getTACAN() { return TACAN; } + virtual const DataTypes::Radio& getRadio() { return radio; } + virtual const DataTypes::GeneralSettings& getGeneralSettings() { return generalSettings; } + virtual const vector& getAmmo() { return ammo; } + virtual const vector& getTargets() { return contacts; } + virtual const list& getActivePath() { return activePath; } protected: unsigned int ID; @@ -251,6 +251,13 @@ protected: /********** Private methods **********/ virtual void AIloop() = 0; + void appendString(stringstream& ss, const unsigned char& datumIndex, const string& datumValue) { + const unsigned short size = datumValue.size(); + ss.write((const char*)&datumIndex, sizeof(unsigned char)); + ss.write((const char*)&size, sizeof(unsigned short)); + ss << datumValue; + } + /********** Template methods **********/ template void updateValue(T& value, T& newValue, unsigned char datumIndex) @@ -268,13 +275,6 @@ protected: ss.write((const char*)&datumValue, sizeof(T)); } - void appendString(stringstream& ss, const unsigned char& datumIndex, string& datumValue) { - const unsigned short size = datumValue.size(); - ss.write((const char*)&datumIndex, sizeof(unsigned char)); - ss.write((const char*)&size, sizeof(unsigned short)); - ss << datumValue; - } - template void appendVector(stringstream& ss, const unsigned char& datumIndex, vector& datumValue) { const unsigned short size = datumValue.size(); diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp index 5d3788de..01cfbc71 100644 --- a/src/core/src/unit.cpp +++ b/src/core/src/unit.cpp @@ -174,64 +174,94 @@ void Unit::updateMissionData(json::value json) void Unit::getData(stringstream& ss, unsigned long long time, bool refresh) { - /* Prepare the data packet and copy it to memory */ - /* If the unit is in a group, get the update data from the group leader and only replace the position: speed and heading */ - //if (unitsManager->isUnitInGroup(this) && !unitsManager->isUnitGroupLeader(this)) { - // DataTypes::DataPacket* p = (DataTypes::DataPacket*)data; - // p->position = position; - // p->speed = speed; - // p->heading = heading; - //} + Unit* sourceUnit = this; + if (unitsManager->isUnitInGroup(this) && !unitsManager->isUnitGroupLeader(this)) + sourceUnit = unitsManager->getGroupLeader(this); const unsigned char endOfData = DataIndex::endOfData; - ss.write((const char*)&ID, sizeof(ID)); for (auto d : updateTimeMap) { if (d.second > time) { switch (d.first) { case DataIndex::category: appendString(ss, d.first, category); break; case DataIndex::alive: appendNumeric(ss, d.first, alive); break; - case DataIndex::human: appendNumeric(ss, d.first, human); break; - case DataIndex::controlled: appendNumeric(ss, d.first, controlled); break; - case DataIndex::coalition: appendNumeric(ss, d.first, coalition); break; - case DataIndex::country: appendNumeric(ss, d.first, country); break; + case DataIndex::human: appendNumeric(ss, d.first, sourceUnit->human); break; + case DataIndex::controlled: appendNumeric(ss, d.first, sourceUnit->controlled); break; + case DataIndex::coalition: appendNumeric(ss, d.first, sourceUnit->coalition); break; + case DataIndex::country: appendNumeric(ss, d.first, sourceUnit->country); break; case DataIndex::name: appendString(ss, d.first, name); break; case DataIndex::unitName: appendString(ss, d.first, unitName); break; - case DataIndex::groupName: appendString(ss, d.first, groupName); break; - case DataIndex::state: appendNumeric(ss, d.first, state); break; - case DataIndex::task: appendString(ss, d.first, task); break; - case DataIndex::hasTask: appendNumeric(ss, d.first, hasTask); break; + case DataIndex::groupName: appendString(ss, d.first, sourceUnit->groupName); break; + case DataIndex::state: appendNumeric(ss, d.first, sourceUnit->state); break; + case DataIndex::task: appendString(ss, d.first, sourceUnit->task); break; + case DataIndex::hasTask: appendNumeric(ss, d.first, sourceUnit->hasTask); break; case DataIndex::position: appendNumeric(ss, d.first, position); break; case DataIndex::speed: appendNumeric(ss, d.first, speed); break; case DataIndex::heading: appendNumeric(ss, d.first, heading); break; - case DataIndex::isTanker: appendNumeric(ss, d.first, isTanker); break; - case DataIndex::isAWACS: appendNumeric(ss, d.first, isAWACS); break; - case DataIndex::onOff: appendNumeric(ss, d.first, onOff); break; - case DataIndex::followRoads: appendNumeric(ss, d.first, followRoads); break; + case DataIndex::isTanker: appendNumeric(ss, d.first, sourceUnit->isTanker); break; + case DataIndex::isAWACS: appendNumeric(ss, d.first, sourceUnit->isAWACS); break; + case DataIndex::onOff: appendNumeric(ss, d.first, sourceUnit->onOff); break; + case DataIndex::followRoads: appendNumeric(ss, d.first, sourceUnit->followRoads); break; case DataIndex::fuel: appendNumeric(ss, d.first, fuel); break; - case DataIndex::desiredSpeed: appendNumeric(ss, d.first, desiredSpeed); break; - case DataIndex::desiredSpeedType: appendNumeric(ss, d.first, desiredSpeedType); break; - case DataIndex::desiredAltitude: appendNumeric(ss, d.first, desiredAltitude); break; - case DataIndex::desiredAltitudeType: appendNumeric(ss, d.first, desiredAltitudeType); break; - case DataIndex::leaderID: appendNumeric(ss, d.first, leaderID); break; - case DataIndex::formationOffset: appendNumeric(ss, d.first, formationOffset); break; - case DataIndex::targetID: appendNumeric(ss, d.first, targetID); break; - case DataIndex::targetPosition: appendNumeric(ss, d.first, targetPosition); break; - case DataIndex::ROE: appendNumeric(ss, d.first, ROE); break; - case DataIndex::reactionToThreat: appendNumeric(ss, d.first, reactionToThreat); break; - case DataIndex::emissionsCountermeasures: appendNumeric(ss, d.first, emissionsCountermeasures); break; - case DataIndex::TACAN: appendNumeric(ss, d.first, TACAN); break; - case DataIndex::radio: appendNumeric(ss, d.first, radio); break; - case DataIndex::generalSettings: appendNumeric(ss, d.first, generalSettings); break; + case DataIndex::desiredSpeed: appendNumeric(ss, d.first, sourceUnit->desiredSpeed); break; + case DataIndex::desiredSpeedType: appendNumeric(ss, d.first, sourceUnit->desiredSpeedType); break; + case DataIndex::desiredAltitude: appendNumeric(ss, d.first, sourceUnit->desiredAltitude); break; + case DataIndex::desiredAltitudeType: appendNumeric(ss, d.first, sourceUnit->desiredAltitudeType); break; + case DataIndex::leaderID: appendNumeric(ss, d.first, sourceUnit->leaderID); break; + case DataIndex::formationOffset: appendNumeric(ss, d.first, sourceUnit->formationOffset); break; + case DataIndex::targetID: appendNumeric(ss, d.first, sourceUnit->targetID); break; + case DataIndex::targetPosition: appendNumeric(ss, d.first, sourceUnit->targetPosition); break; + case DataIndex::ROE: appendNumeric(ss, d.first, sourceUnit->ROE); break; + case DataIndex::reactionToThreat: appendNumeric(ss, d.first, sourceUnit->reactionToThreat); break; + case DataIndex::emissionsCountermeasures: appendNumeric(ss, d.first, sourceUnit->emissionsCountermeasures); break; + case DataIndex::TACAN: appendNumeric(ss, d.first, sourceUnit->TACAN); break; + case DataIndex::radio: appendNumeric(ss, d.first, sourceUnit->radio); break; + case DataIndex::generalSettings: appendNumeric(ss, d.first, sourceUnit->generalSettings); break; case DataIndex::ammo: appendVector(ss, d.first, ammo); break; case DataIndex::contacts: appendVector(ss, d.first, contacts); break; - case DataIndex::activePath: appendList(ss, d.first, activePath); break; + case DataIndex::activePath: appendList(ss, d.first, sourceUnit->activePath); break; } } } ss.write((const char*)&endOfData, sizeof(endOfData)); } +void Unit::setAmmo(vector newValue) +{ + if (ammo.size() == newValue.size()) { + bool equal = true; + for (int i = 0; i < ammo.size(); i++) { + if (ammo.at(i) != newValue.at(i)) + { + equal = false; + break; + } + } + if (equal) + return; + } + ammo = newValue; + triggerUpdate(DataIndex::ammo); +} + +void Unit::setContacts(vector newValue) +{ + if (contacts.size() == newValue.size()) { + bool equal = true; + for (int i = 0; i < ammo.size(); i++) { + if (contacts.at(i) != newValue.at(i)) + { + equal = false; + break; + } + } + if (equal) + return; + } + contacts = newValue; + triggerUpdate(DataIndex::contacts); +} + void Unit::setActivePath(list newPath) { activePath = newPath; From ec91376da27a3fcfdbabf5ca325fbc5d907c86b3 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Sun, 2 Jul 2023 22:13:53 +0200 Subject: [PATCH 13/15] More fixes in binary data transmission --- client/src/panels/unitinfopanel.ts | 7 +-- client/src/units/dataextractor.ts | 2 +- client/src/units/unit.ts | 51 +++++++++---------- src/core/include/commands.h | 2 +- src/core/include/unit.h | 82 +++++++++++++++--------------- src/core/src/airunit.cpp | 2 + src/core/src/groundunit.cpp | 2 + src/core/src/scheduler.cpp | 9 ++-- src/core/src/unit.cpp | 15 +++--- 9 files changed, 89 insertions(+), 83 deletions(-) diff --git a/client/src/panels/unitinfopanel.ts b/client/src/panels/unitinfopanel.ts index 8ca140b0..c6e7d724 100644 --- a/client/src/panels/unitinfopanel.ts +++ b/client/src/panels/unitinfopanel.ts @@ -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"; @@ -77,10 +78,10 @@ export class UnitInfoPanel extends Panel { const ammo = Object.values(unit.getData().ammo); if (ammo.length > 0) { items.replaceChildren(...Object.values(unit.getData().ammo).map( - (ammo: any) => { + (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; } )); diff --git a/client/src/units/dataextractor.ts b/client/src/units/dataextractor.ts index 7b5eaf32..9390be62 100644 --- a/client/src/units/dataextractor.ts +++ b/client/src/units/dataextractor.ts @@ -109,7 +109,7 @@ export class DataExtractor { for (let idx = 0; idx < size; idx++) { value.push({ quantity: this.extractUInt16(), - name: this.extractString(32), + name: this.extractString(33), guidance: this.extractUInt8(), category: this.extractUInt8(), missileCategory: this.extractUInt8() diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index 13d7685b..0bc5ee69 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -35,7 +35,7 @@ export class Unit extends CustomMarker { #heading: number = 0; #isTanker: boolean = false; #isAWACS: boolean = false; - #onOff: boolean = false; + #onOff: boolean = true; #followRoads: boolean = false; #fuel: number = 0; #desiredSpeed: number = 0; @@ -60,9 +60,9 @@ export class Unit extends CustomMarker { channel: 0 }; #radio: Radio = { - frequency: 0, - callsign: 0, - callsignNumber: 0 + frequency: 124000000, + callsign: 1, + callsignNumber: 1 }; #generalSettings: GeneralSettings = { prohibitAA: false, @@ -187,10 +187,10 @@ export class Unit extends CustomMarker { this.#updateMarker(); // TODO dont delete the polylines of the detected units - this.#clearDetectedUnits(); + this.#clearContacts(); if (this.getSelected()) { this.#drawPath(); - this.#drawDetectedUnits(); + this.#drawContacts(); this.#drawTarget(); } else { @@ -284,7 +284,7 @@ export class Unit extends CustomMarker { } else { document.dispatchEvent(new CustomEvent("unitDeselection", { detail: this })); - this.#clearDetectedUnits(); + this.#clearContacts(); this.#clearPath(); this.#clearTarget(); } @@ -607,8 +607,7 @@ export class Unit extends CustomMarker { onAdd(map: Map): this { super.onAdd(map); /* If this is the first time adding this unit to the map, remove the temporary marker */ - if (getUnitsManager().getUnitByID(this.ID) == null) - getMap().removeTemporaryMarker(new LatLng(this.#position.lat, this.#position.lng)); + getMap().removeTemporaryMarker(new LatLng(this.#position.lat, this.#position.lng)); return this; } @@ -798,13 +797,13 @@ export class Unit extends CustomMarker { var newHasFox2 = false; var newHasFox3 = false; var newHasOtherAmmo = false; - Object.values(this.#ammo).forEach((ammo: any) => { - if (ammo.desc.category == 1 && ammo.desc.missileCategory == 1) { - if (ammo.desc.guidance == 4 || ammo.desc.guidance == 5) + Object.values(this.#ammo).forEach((ammo: Ammo) => { + if (ammo.category == 1 && ammo.missileCategory == 1) { + if (ammo.guidance == 4 || ammo.guidance == 5) newHasFox1 = true; - else if (ammo.desc.guidance == 2) + else if (ammo.guidance == 2) newHasFox2 = true; - else if (ammo.desc.guidance == 3) + else if (ammo.guidance == 3) newHasFox3 = true; } else @@ -870,31 +869,31 @@ export class Unit extends CustomMarker { this.#pathPolyline.setLatLngs([]); } - #drawDetectedUnits() { + #drawContacts() { for (let index in this.#contacts) { - var targetData = this.#contacts[index]; - var target = getUnitsManager().getUnitByID(targetData.ID) - if (target != null) { + var contactData = this.#contacts[index]; + var contact = getUnitsManager().getUnitByID(contactData.ID) + if (contact != null) { var startLatLng = new LatLng(this.#position.lat, this.#position.lng) - var endLatLng = new LatLng(target.#position.lat, target.#position.lng) + var endLatLng = new LatLng(contact.#position.lat, contact.#position.lng) var color; - if (targetData.detectionMethod === 1) + if (contactData.detectionMethod === 1) color = "#FF00FF"; - else if (targetData.detectionMethod === 4) + else if (contactData.detectionMethod === 4) color = "#FFFF00"; - else if (targetData.detectionMethod === 16) + else if (contactData.detectionMethod === 16) color = "#00FF00"; else color = "#FFFFFF"; - var targetPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 0.4, smoothFactor: 1, dashArray: "4, 8" }); - targetPolyline.addTo(getMap()); - this.#contactsPolylines.push(targetPolyline) + var contactPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 0.4, smoothFactor: 1, dashArray: "4, 8" }); + contactPolyline.addTo(getMap()); + this.#contactsPolylines.push(contactPolyline) } } } - #clearDetectedUnits() { + #clearContacts() { for (let index in this.#contactsPolylines) { getMap().removeLayer(this.#contactsPolylines[index]) } diff --git a/src/core/include/commands.h b/src/core/include/commands.h index 091eac61..5c1769e0 100644 --- a/src/core/include/commands.h +++ b/src/core/include/commands.h @@ -234,7 +234,7 @@ private: const bool explosion; }; -/* Follow command */ +/* SetTask command */ class SetTask : public Command { public: diff --git a/src/core/include/unit.h b/src/core/include/unit.h index df335df9..c380878e 100644 --- a/src/core/include/unit.h +++ b/src/core/include/unit.h @@ -162,43 +162,43 @@ public: virtual void setActivePath(list newValue); /********** Getters **********/ - virtual const string& getCategory() { return category; }; - virtual const bool& getAlive() { return alive; } - virtual const bool& getHuman() { return human; } - virtual const bool& getControlled() { return controlled; } - virtual const unsigned char& getCoalition() { return coalition; } - virtual const unsigned char& getCountry() { return country; } - virtual const string& getName() { return name; } - virtual const string& getUnitName() { return unitName; } - virtual const string& getGroupName() { return groupName; } - virtual const unsigned char& getState() { return state; } - virtual const string& getTask() { return task; } - virtual const bool& getHasTask() { return hasTask; } - virtual const Coords& getPosition() { return position; } - virtual const double& getSpeed() { return speed; } - virtual const double& getHeading() { return heading; } - virtual const bool& getIsTanker() { return isTanker; } - virtual const bool& getIsAWACS() { return isAWACS; } - virtual const bool& getOnOff() { return onOff; }; - virtual const bool& getFollowRoads() { return followRoads; }; - virtual const unsigned short& getFuel() { return fuel; } - virtual const double& getDesiredSpeed() { return desiredSpeed; }; - virtual const bool& getDesiredSpeedType() { return desiredSpeedType; }; - virtual const double& getDesiredAltitude() { return desiredAltitude; }; - virtual const bool& getDesiredAltitudeType() { return desiredAltitudeType; }; - virtual const unsigned int& getLeaderID() { return leaderID; } - virtual const Offset& getFormationoffset() { return formationOffset; } - virtual const unsigned int& getTargetID() { return targetID; } - virtual const Coords& getTargetPosition() { return targetPosition; } - virtual const unsigned char& getROE() { return ROE; } - virtual const unsigned char& getReactionToThreat() { return reactionToThreat; } - virtual const unsigned char& getEmissionsCountermeasures() { return emissionsCountermeasures; }; - virtual const DataTypes::TACAN& getTACAN() { return TACAN; } - virtual const DataTypes::Radio& getRadio() { return radio; } - virtual const DataTypes::GeneralSettings& getGeneralSettings() { return generalSettings; } - virtual const vector& getAmmo() { return ammo; } - virtual const vector& getTargets() { return contacts; } - virtual const list& getActivePath() { return activePath; } + virtual string getCategory() { return category; }; + virtual bool getAlive() { return alive; } + virtual bool getHuman() { return human; } + virtual bool getControlled() { return controlled; } + virtual unsigned char getCoalition() { return coalition; } + virtual unsigned char getCountry() { return country; } + virtual string getName() { return name; } + virtual string getUnitName() { return unitName; } + virtual string getGroupName() { return groupName; } + virtual unsigned char getState() { return state; } + virtual string getTask() { return task; } + virtual bool getHasTask() { return hasTask; } + virtual Coords getPosition() { return position; } + virtual double getSpeed() { return speed; } + virtual double getHeading() { return heading; } + virtual bool getIsTanker() { return isTanker; } + virtual bool getIsAWACS() { return isAWACS; } + virtual bool getOnOff() { return onOff; }; + virtual bool getFollowRoads() { return followRoads; }; + virtual unsigned short getFuel() { return fuel; } + virtual double getDesiredSpeed() { return desiredSpeed; }; + virtual bool getDesiredSpeedType() { return desiredSpeedType; }; + virtual double getDesiredAltitude() { return desiredAltitude; }; + virtual bool getDesiredAltitudeType() { return desiredAltitudeType; }; + virtual unsigned int getLeaderID() { return leaderID; } + virtual Offset getFormationoffset() { return formationOffset; } + virtual unsigned int getTargetID() { return targetID; } + virtual Coords getTargetPosition() { return targetPosition; } + virtual unsigned char getROE() { return ROE; } + virtual unsigned char getReactionToThreat() { return reactionToThreat; } + virtual unsigned char getEmissionsCountermeasures() { return emissionsCountermeasures; }; + virtual DataTypes::TACAN getTACAN() { return TACAN; } + virtual DataTypes::Radio getRadio() { return radio; } + virtual DataTypes::GeneralSettings getGeneralSettings() { return generalSettings; } + virtual vector getAmmo() { return ammo; } + virtual vector getTargets() { return contacts; } + virtual list getActivePath() { return activePath; } protected: unsigned int ID; @@ -220,7 +220,7 @@ protected: double heading = NULL; bool isTanker = false; bool isAWACS = false; - bool onOff = false; + bool onOff = true; bool followRoads = false; unsigned short fuel = 0; double desiredSpeed = 0; @@ -265,7 +265,7 @@ protected: if (newValue != value) { triggerUpdate(datumIndex); - *(&value) = newValue; + value = newValue; } } @@ -280,7 +280,9 @@ protected: const unsigned short size = datumValue.size(); ss.write((const char*)&datumIndex, sizeof(unsigned char)); ss.write((const char*)&size, sizeof(unsigned short)); - ss.write((const char*)&datumValue, size * sizeof(T)); + + for (auto& el : datumValue) + ss.write((const char*)&el, sizeof(T)); } template diff --git a/src/core/src/airunit.cpp b/src/core/src/airunit.cpp index 85eb71fe..9f32981d 100644 --- a/src/core/src/airunit.cpp +++ b/src/core/src/airunit.cpp @@ -113,6 +113,8 @@ void AirUnit::setState(unsigned char newState) log(unitName + " setting state from " + to_string(state) + " to " + to_string(newState)); state = newState; + + triggerUpdate(DataIndex::state); } void AirUnit::AIloop() diff --git a/src/core/src/groundunit.cpp b/src/core/src/groundunit.cpp index 1811565d..e8294d83 100644 --- a/src/core/src/groundunit.cpp +++ b/src/core/src/groundunit.cpp @@ -65,6 +65,8 @@ void GroundUnit::setState(unsigned char newState) log(unitName + " setting state from " + to_string(state) + " to " + to_string(newState)); state = newState; + + triggerUpdate(DataIndex::state); } void GroundUnit::AIloop() diff --git a/src/core/src/scheduler.cpp b/src/core/src/scheduler.cpp index bcfe724e..2346c1ea 100644 --- a/src/core/src/scheduler.cpp +++ b/src/core/src/scheduler.cpp @@ -57,6 +57,7 @@ void Scheduler::handleRequest(string key, json::value value) Command* command = nullptr; log("Received request with ID: " + key); + log(value.serialize()); if (key.compare("setPath") == 0) { unsigned int ID = value[L"ID"].as_integer(); @@ -146,10 +147,10 @@ void Scheduler::handleRequest(string key, json::value value) { unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); - unsigned int leaderID = value[L"targetID"].as_integer(); - unsigned int offsetX = value[L"offsetX"].as_integer(); - unsigned int offsetY = value[L"offsetY"].as_integer(); - unsigned int offsetZ = value[L"offsetZ"].as_integer(); + unsigned int leaderID = value[L"targetID"].as_double(); + double offsetX = value[L"offsetX"].as_double(); + double offsetY = value[L"offsetY"].as_double(); + double offsetZ = value[L"offsetZ"].as_double(); Unit* unit = unitsManager->getGroupLeader(ID); Unit* leader = unitsManager->getUnit(leaderID); diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp index 01cfbc71..4f6212bf 100644 --- a/src/core/src/unit.cpp +++ b/src/core/src/unit.cpp @@ -128,7 +128,6 @@ void Unit::updateMissionData(json::value json) if (json.has_object_field(L"ammo")) { vector ammo; for (auto const& el : json[L"ammo"].as_object()) { - log(el.second.serialize()); DataTypes::Ammo ammoItem; auto ammoJson = el.second; ammoItem.quantity = ammoJson[L"count"].as_number().to_uint32(); @@ -156,12 +155,12 @@ void Unit::updateMissionData(json::value json) contactItem.ID = contactJson[L"object"][L"id_"].as_number().to_uint32(); string detectionMethod = to_string(contactJson[L"detectionMethod"]); - if (detectionMethod.compare("VISUAL")) contactItem.detectionMethod = 1; - else if (detectionMethod.compare("OPTIC")) contactItem.detectionMethod = 2; - else if (detectionMethod.compare("RADAR")) contactItem.detectionMethod = 4; - else if (detectionMethod.compare("IRST")) contactItem.detectionMethod = 8; - else if (detectionMethod.compare("RWR")) contactItem.detectionMethod = 16; - else if (detectionMethod.compare("DLINK")) contactItem.detectionMethod = 32; + if (detectionMethod.compare("VISUAL") == 0) contactItem.detectionMethod = 1; + else if (detectionMethod.compare("OPTIC") == 0) contactItem.detectionMethod = 2; + else if (detectionMethod.compare("RADAR") == 0) contactItem.detectionMethod = 4; + else if (detectionMethod.compare("IRST") == 0) contactItem.detectionMethod = 8; + else if (detectionMethod.compare("RWR") == 0) contactItem.detectionMethod = 16; + else if (detectionMethod.compare("DLINK") == 0) contactItem.detectionMethod = 32; contacts.push_back(contactItem); } setContacts(contacts); @@ -248,7 +247,7 @@ void Unit::setContacts(vector newValue) { if (contacts.size() == newValue.size()) { bool equal = true; - for (int i = 0; i < ammo.size(); i++) { + for (int i = 0; i < contacts.size(); i++) { if (contacts.at(i) != newValue.at(i)) { equal = false; From 1af98cc54febaa90f817d3ad04f84bb0a16c4221 Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Wed, 5 Jul 2023 20:14:48 +0200 Subject: [PATCH 14/15] Tweaks and optimizations of front end --- client/src/atc/unitdatatable.ts | 6 +- .../{drawingmarker.ts => drawingcursor.ts} | 0 client/src/map/map.ts | 73 ++++++------ client/src/panels/mouseinfopanel.ts | 2 +- client/src/panels/unitcontrolpanel.ts | 58 +++++----- client/src/panels/unitinfopanel.ts | 14 +-- client/src/units/unit.ts | 73 +++++++----- client/src/units/unitsmanager.ts | 34 +++--- src/core/include/unit.h | 6 +- src/core/include/unitsmanager.h | 2 +- src/core/src/server.cpp | 4 +- src/core/src/unit.cpp | 104 ++++++++++-------- src/core/src/unitsmanager.cpp | 4 +- 13 files changed, 215 insertions(+), 165 deletions(-) rename client/src/map/{drawingmarker.ts => drawingcursor.ts} (100%) diff --git a/client/src/atc/unitdatatable.ts b/client/src/atc/unitdatatable.ts index 1f06fc9c..54b0674e 100644 --- a/client/src/atc/unitdatatable.ts +++ b/client/src/atc/unitdatatable.ts @@ -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.getData().unitName?.toLowerCase(); - const bVal = b.getData().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.getData().unitName, unit.getData().name, unit.getCategory(), (unit.getData().controlled) ? "AI" : "Human"]; + const dataset = [unit.getUnitName(), unit.getName(), unit.getCategory(), (unit.getControlled()) ? "AI" : "Human"]; addRow(el, dataset); } diff --git a/client/src/map/drawingmarker.ts b/client/src/map/drawingcursor.ts similarity index 100% rename from client/src/map/drawingmarker.ts rename to client/src/map/drawingcursor.ts diff --git a/client/src/map/map.ts b/client/src/map/map.ts index c81c2e82..18c74d99 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -16,7 +16,7 @@ import { layers as mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_ import { TargetMarker } from "./targetmarker"; import { CoalitionArea } from "./coalitionarea"; import { CoalitionAreaContextMenu } from "../controls/coalitionareacontextmenu"; -import { DrawingCursor } from "./drawingmarker"; +import { DrawingCursor } from "./drawingcursor"; L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect); @@ -37,6 +37,8 @@ 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; @@ -454,7 +456,7 @@ 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) + getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, this.#shiftKey, this.#destinationGroupRotation) this.#destinationGroupRotation = 0; this.#destinationRotationCenter = null; this.#computeDestinationRotation = false; @@ -513,13 +515,13 @@ export class Map extends L.Map { this.#lastMousePosition.x = e.originalEvent.x; this.#lastMousePosition.y = e.originalEvent.y; - this.#updateCursor(e); + 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.#updateDestinationCursors(e); + this.#updateDestinationCursors(); } else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) { this.#targetCursor.setLatLng(this.getMouseCoordinates()); @@ -532,13 +534,17 @@ export class Map extends L.Map { } #onKeyDown(e: any) { - this.#updateDestinationCursors(e); - this.#updateCursor(e); + this.#shiftKey = e.originalEvent.shiftKey; + this.#ctrlKey = e.originalEvent.ctrlKey; + this.#updateCursor(); + this.#updateDestinationCursors(); } #onKeyUp(e: any) { - this.#updateDestinationCursors(e); - this.#updateCursor(e); + this.#shiftKey = e.originalEvent.shiftKey; + this.#ctrlKey = e.originalEvent.ctrlKey; + this.#updateCursor(); + this.#updateDestinationCursors(); } #onZoom(e: any) { @@ -547,7 +553,7 @@ export class Map extends L.Map { } #panToUnit(unit: Unit) { - var unitPosition = new L.LatLng(unit.getData().position.lat, unit.getData().position.lng); + var unitPosition = new L.LatLng(unit.getPosition().lat, unit.getPosition().lng); this.setView(unitPosition, this.getZoom(), { animate: false }); } @@ -582,38 +588,41 @@ export class Map extends L.Map { document.getElementById(this.#ID)?.classList.add("hidden-cursor"); } - #showDestinationCursors(singleCursor: boolean) { - /* Don't create the cursors if there already are the correct number of them available */ - if (singleCursor || getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).length != this.#destinationPreviewCursors.length) { - /* Reset the cursors to start from a clean condition */ - this.#hideDestinationCursors(); + #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); + this.#destinationPreviewCursors = [marker]; + } + else if (!singleCursor) { + while (this.#destinationPreviewCursors.length > selectedUnitsCount) { + this.removeLayer(this.#destinationPreviewCursors[0]); + this.#destinationPreviewCursors.splice(0, 1); + } - if (getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).length > 0) { - if (singleCursor) { - var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), { interactive: false }); - marker.addTo(this); - this.#destinationPreviewCursors = [marker]; - } - else { - /* Create the cursors. If a group is selected only one cursor is shown for it, because you can't control single units in a group */ - this.#destinationPreviewCursors = getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).map((unit: Unit) => { - var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), { interactive: false }); - marker.addTo(this); - return marker; - }); + while (this.#destinationPreviewCursors.length < selectedUnitsCount) { + var cursor = new DestinationPreviewMarker(this.getMouseCoordinates(), { interactive: false }); + cursor.addTo(this); + this.#destinationPreviewCursors.push(cursor); } + + this.#updateDestinationCursors(); } } } - #updateDestinationCursors(e: any) { + #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(e.originalEvent.shiftKey ? latlng : this.getMouseCoordinates()); + this.#destinationPreviewCursors[idx].setLatLng(this.#shiftKey? latlng : this.getMouseCoordinates()); }) }; } @@ -653,9 +662,9 @@ export class Map extends L.Map { this.#drawingCursor.removeFrom(this); } - #updateCursor(e?: any) { + #updateCursor() { /* If the ctrl key is being pressed or we are performing an area selection, show the default cursor */ - if (e?.originalEvent.ctrlKey || this.#selecting) { + if (this.#ctrlKey || this.#selecting) { /* Hide all non default cursors */ this.#hideDestinationCursors(); this.#hideTargetCursor(); @@ -671,7 +680,7 @@ export class Map extends L.Map { /* 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(e && e.originaleEvent && !e.originalEvent.shiftKey); + 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(); } diff --git a/client/src/panels/mouseinfopanel.ts b/client/src/panels/mouseinfopanel.ts index dfb48d92..3a09436f 100644 --- a/client/src/panels/mouseinfopanel.ts +++ b/client/src/panels/mouseinfopanel.ts @@ -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].getData().position.lat, selectedUnits[0].getData().position.lng); + 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); diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index df2b1b0a..0b43acae 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -99,13 +99,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.getData().unitName || ""; - var label = unit.getDatabase()?.getByName(unit.getData().name)?.label || unit.getData().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.getData().coalition); + button.setAttribute("data-coalition", unit.getCoalition()); button.classList.add("pill", "highlight-coalition") button.addEventListener("click", () => { @@ -140,12 +140,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.getData().desiredAltitude}); - var desiredAltitudeType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getData().desiredAltitudeType}); - var desiredSpeed = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getData().desiredSpeed}); - var desiredSpeedType = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getData().desiredSpeedType}); - var onOff = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getData().onOff}); - var followRoads = getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getData().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); @@ -171,15 +171,15 @@ export class UnitControlPanel extends Panel { /* Option buttons */ this.#optionButtons["ROE"].forEach((button: HTMLButtonElement) => { - button.classList.toggle("selected", units.every((unit: Unit) => unit.getData().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.getData().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.getData().emissionsCountermeasures === button.value)) + button.classList.toggle("selected", units.every((unit: Unit) => unit.getEmissionsCountermeasures() === button.value)) }); this.#onOffSwitch.setValue(onOff, false); @@ -208,11 +208,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.getData().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.getData().radio.frequency / 1000000); - const radioDecimals = (unit.getData().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); @@ -224,28 +224,28 @@ export class UnitControlPanel extends Panel { /* Set common properties */ // Name - unitNameEl.innerText = unit.getData().unitName; + unitNameEl.innerText = unit.getUnitName(); // General settings - prohibitJettisonCheckbox.checked = unit.getData().generalSettings.prohibitJettison; - prohibitAfterburnerCheckbox.checked = unit.getData().generalSettings.prohibitAfterburner; - prohibitAACheckbox.checked = unit.getData().generalSettings.prohibitAA; - prohibitAGCheckbox.checked = unit.getData().generalSettings.prohibitAG; - prohibitAirWpnCheckbox.checked = unit.getData().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.getData().isTanker; - AWACSCheckbox.checked = unit.getData().isAWACS; + tankerCheckbox.checked = unit.getIsTanker(); + AWACSCheckbox.checked = unit.getIsAWACS(); // TACAN - TACANCheckbox.checked = unit.getData().TACAN.isOn; - TACANChannelInput.value = String(unit.getData().TACAN.channel); - TACANCallsignInput.value = String(unit.getData().TACAN.callsign); - this.#TACANXYDropdown.setValue(unit.getData().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.getData().radio.callsignNumber); + radioCallsignNumberInput.value = String(unit.getRadio().callsignNumber); this.#radioDecimalsDropdown.setValue("." + radioDecimals); if (tanker) /* Set tanker specific options */ @@ -256,7 +256,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.getData().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); } } diff --git a/client/src/panels/unitinfopanel.ts b/client/src/panels/unitinfopanel.ts index c6e7d724..cedb32bc 100644 --- a/client/src/panels/unitinfopanel.ts +++ b/client/src/panels/unitinfopanel.ts @@ -57,16 +57,16 @@ export class UnitInfoPanel extends Panel { /* Set the unit info */ this.#unitLabel.innerText = aircraftDatabase.getByName(baseData.name)?.label || baseData.name; this.#unitName.innerText = baseData.unitName; - if (unit.getData().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.getData().fuel + "%"); - this.#fuelPercentage.dataset.percentage = "" + unit.getData().fuel; - this.#currentTask.dataset.currentTask = unit.getData().task !== "" ? unit.getData().task : "No task"; - this.#currentTask.dataset.coalition = unit.getData().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 == ''); @@ -75,9 +75,9 @@ export class UnitInfoPanel extends Panel { const items = this.#loadoutContainer.querySelector("#loadout-items"); if (items) { - const ammo = Object.values(unit.getData().ammo); + const ammo = Object.values(unit.getAmmo()); if (ammo.length > 0) { - items.replaceChildren(...Object.values(unit.getData().ammo).map( + items.replaceChildren(...Object.values(unit.getAmmo()).map( (ammo: Ammo) => { var el = document.createElement("div"); el.dataset.qty = `${ammo.quantity}`; diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index 0bc5ee69..2d10fa62 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -73,7 +73,7 @@ export class Unit extends CustomMarker { }; #ammo: Ammo[] = []; #contacts: Contact[] = []; - #activePath: LatLng[] = []; + #activePath: LatLng[] = []; #selectable: boolean; #selected: boolean = false; @@ -89,6 +89,43 @@ export class Unit extends CustomMarker { #timer: number = 0; #hotgroup: number | null = null; + getAlive() {return this.#alive}; + getHuman() {return this.#human}; + getControlled() {return this.#controlled}; + getCoalition() {return this.#coalition}; + getCountry() {return this.#country}; + getName() {return this.#name}; + getUnitName() {return this.#unitName}; + getGroupName() {return this.#groupName}; + getState() {return this.#state}; + getTask() {return this.#task}; + getHasTask() {return this.#hasTask}; + getPosition() {return this.#position}; + getSpeed() {return this.#speed}; + getHeading() {return this.#heading}; + getIsTanker() {return this.#isTanker}; + getIsAWACS() {return this.#isAWACS}; + getOnOff() {return this.#onOff}; + getFollowRoads() {return this.#followRoads}; + getFuel() {return this.#fuel}; + getDesiredSpeed() {return this.#desiredSpeed}; + getDesiredSpeedType() {return this.#desiredSpeedType}; + getDesiredAltitude() {return this.#desiredAltitude}; + getDesiredAltitudeType() {return this.#desiredAltitudeType}; + getLeaderID() {return this.#leaderID}; + getFormationOffset() {return this.#formationOffset}; + getTargetID() {return this.#targetID}; + getTargetPosition() {return this.#targetPosition}; + getROE() {return this.#ROE}; + getReactionToThreat() {return this.#reactionToThreat}; + getEmissionsCountermeasures() {return this.#emissionsCountermeasures}; + getTACAN() {return this.#TACAN}; + getRadio() {return this.#radio}; + getGeneralSettings() {return this.#generalSettings}; + getAmmo() {return this.#ammo}; + getContacts() {return this.#contacts}; + getActivePath() {return this.#activePath}; + static getConstructor(type: string) { if (type === "GroundUnit") return GroundUnit; if (type === "Aircraft") return Aircraft; @@ -242,10 +279,8 @@ export class Unit extends CustomMarker { } } - getMarkerCategory() { - // Overloaded by child classes - // TODO convert to use getMarkerCategoryByName - return ""; + getMarkerCategory(): string { + return getMarkerCategoryByName(this.getName()); } getDatabase(): UnitDatabase | null { @@ -737,8 +772,10 @@ export class Unit extends CustomMarker { this.#miniMapMarker.bringToBack(); } else { - this.#miniMapMarker.setLatLng(new LatLng(this.#position.lat, this.#position.lng)); - this.#miniMapMarker.bringToBack(); + if (this.#miniMapMarker.getLatLng().lat !== this.getPosition().lat && this.#miniMapMarker.getLatLng().lng !== this.getPosition().lng) { + this.#miniMapMarker.setLatLng(new LatLng(this.#position.lat, this.#position.lng)); + this.#miniMapMarker.bringToBack(); + } } } else { @@ -750,7 +787,9 @@ export class Unit extends CustomMarker { /* Draw the marker */ if (!this.getHidden()) { - this.setLatLng(new LatLng(this.#position.lat, this.#position.lng)); + if (this.getLatLng().lat !== this.getPosition().lat && this.getLatLng().lng !== this.getPosition().lng) { + this.setLatLng(new LatLng(this.#position.lat, this.#position.lng)); + } var element = this.getElement(); if (element != null) { @@ -904,7 +943,7 @@ export class Unit extends CustomMarker { this.#drawtargetPosition(this.#targetPosition); } else if (this.#targetID != 0 && getUnitsManager().getUnitByID(this.#targetID)) { - const position = getUnitsManager().getUnitByID(this.#targetID)?.getData().position; + const position = getUnitsManager().getUnitByID(this.#targetID)?.getPosition(); if (position) this.#drawtargetPosition(position); } @@ -954,10 +993,6 @@ export class Aircraft extends AirUnit { getCategory() { return "Aircraft"; } - - getMarkerCategory() { - return "aircraft"; - } } export class Helicopter extends AirUnit { @@ -968,10 +1003,6 @@ export class Helicopter extends AirUnit { getCategory() { return "Helicopter"; } - - getMarkerCategory() { - return "helicopter"; - } } export class GroundUnit extends Unit { @@ -996,10 +1027,6 @@ export class GroundUnit extends Unit { getCategory() { return "GroundUnit"; } - - getMarkerCategory() { - return getMarkerCategoryByName(this.getData().name); - } } export class NavyUnit extends Unit { @@ -1024,10 +1051,6 @@ export class NavyUnit extends Unit { getCategory() { return "NavyUnit"; } - - getMarkerCategory() { - return "navyunit"; - } } export class Weapon extends Unit { diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index 73246697..5d222291 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -32,7 +32,7 @@ export class UnitsManager { getSelectableAircraft() { const units = this.getUnits(); return Object.keys(units).reduce((acc: { [key: number]: Unit }, unitId: any) => { - if (units[unitId].getCategory() === "Aircraft" && units[unitId].getData().alive === true) { + if (units[unitId].getCategory() === "Aircraft" && units[unitId].getAlive() === true) { acc[unitId] = units[unitId]; } return acc; @@ -51,7 +51,7 @@ export class UnitsManager { } getUnitsByHotgroup(hotgroup: number) { - return Object.values(this.#units).filter((unit: Unit) => { return unit.getData().alive && unit.getHotgroup() == hotgroup }); + return Object.values(this.#units).filter((unit: Unit) => { return unit.getAlive() && unit.getHotgroup() == hotgroup }); } addUnit(ID: number, category: string) { @@ -114,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].getData().position.lat, this.#units[ID].getData().position.lng); + var latlng = new LatLng(this.#units[ID].getPosition().lat, this.#units[ID].getPosition().lng); if (bounds.contains(latlng)) { this.#units[ID].setSelected(true); } @@ -131,11 +131,11 @@ export class UnitsManager { } if (options) { if (options.excludeHumans) - selectedUnits = selectedUnits.filter((unit: Unit) => { return !unit.getData().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.getData().groupName == otherUnit.getData().groupName)) + if (!temp.some((otherUnit: Unit) => unit.getGroupName() == otherUnit.getGroupName())) temp.push(unit); } selectedUnits = temp; @@ -180,7 +180,7 @@ export class UnitsManager { if (this.getSelectedUnits().length == 0) return undefined; return this.getSelectedUnits().map((unit: Unit) => { - return unit.getData().coalition + return unit.getCoalition() })?.reduce((a: any, b: any) => { return a == b ? a : undefined }); @@ -200,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.getData().state === "Follow") { - const leader = this.getUnitByID(unit.getData().leaderID) + if (unit.getState() === "Follow") { + const leader = this.getUnitByID(unit.getLeaderID()) if (leader && leader.getSelected()) leader.addDestination(latlng); else @@ -220,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.getData().state === "Follow") { - const leader = this.getUnitByID(unit.getData().leaderID) + if (unit.getState() === "Follow") { + const leader = this.getUnitByID(unit.getLeaderID()) if (leader && leader.getSelected()) leader.clearDestinations(); else @@ -332,13 +332,13 @@ export class UnitsManager { for (let idx in selectedUnits) { selectedUnits[idx].attackUnit(ID); } - this.#showActionMessage(selectedUnits, `attacking unit ${this.getUnitByID(ID)?.getData().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.getData().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?" ) ) { @@ -399,7 +399,7 @@ export class UnitsManager { } count++; } - this.#showActionMessage(selectedUnits, `following unit ${this.getUnitByID(ID)?.getData().unitName}`); + this.#showActionMessage(selectedUnits, `following unit ${this.getUnitByID(ID)?.getUnitName()}`); } selectedUnitsSetHotgroup(hotgroup: number) { @@ -421,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.getData().position.lat, unit.getData().position.lng); + var mercator = latLngToMercator(unit.getPosition().lat, unit.getPosition().lng); center.x += mercator.x / selectedUnits.length; center.y += mercator.y / selectedUnits.length; }); @@ -429,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.getData().position.lat, unit.getData().position.lat); + 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 */ @@ -559,8 +559,8 @@ export class UnitsManager { #showActionMessage(units: Unit[], message: string) { if (units.length == 1) - getInfoPopup().setText(`${units[0].getData().unitName} ${message}`); + getInfoPopup().setText(`${units[0].getUnitName()} ${message}`); else if (units.length > 1) - getInfoPopup().setText(`${units[0].getData().unitName} and ${units.length - 1} other units ${message}`); + getInfoPopup().setText(`${units[0].getUnitName()} and ${units.length - 1} other units ${message}`); } } \ No newline at end of file diff --git a/src/core/include/unit.h b/src/core/include/unit.h index c380878e..b3c12707 100644 --- a/src/core/include/unit.h +++ b/src/core/include/unit.h @@ -53,6 +53,7 @@ namespace DataIndex { ammo, contacts, activePath, + lastIndex, endOfData = 255 }; } @@ -94,7 +95,7 @@ public: unsigned int getDataPacket(char*& data); unsigned int getID() { return ID; } - void getData(stringstream& ss, unsigned long long time, bool refresh); + void getData(stringstream& ss, unsigned long long time); Coords getActiveDestination() { return activeDestination; } virtual void changeSpeed(string change) {}; @@ -122,6 +123,9 @@ public: void triggerUpdate(unsigned char datumIndex); + bool hasFreshData(unsigned long long time); + bool checkFreshness(unsigned char datumIndex, unsigned long long time); + /********** Setters **********/ virtual void setCategory(string newValue) { updateValue(category, newValue, DataIndex::category); } virtual void setAlive(bool newValue) { updateValue(alive, newValue, DataIndex::alive); } diff --git a/src/core/include/unitsmanager.h b/src/core/include/unitsmanager.h index 081545a9..71d5f247 100644 --- a/src/core/include/unitsmanager.h +++ b/src/core/include/unitsmanager.h @@ -20,7 +20,7 @@ public: void updateExportData(lua_State* L, double dt = 0); void updateMissionData(json::value missionData); void runAILoop(); - string getUnitData(stringstream &ss, unsigned long long time, bool refresh); + string getUnitData(stringstream &ss, unsigned long long time); void deleteUnit(unsigned int ID, bool explosion); void acquireControl(unsigned int ID); diff --git a/src/core/src/server.cpp b/src/core/src/server.cpp index 68398d1d..580b24d7 100644 --- a/src/core/src/server.cpp +++ b/src/core/src/server.cpp @@ -96,13 +96,11 @@ void Server::handle_get(http_request request) time = 0; } } - - bool refresh = (time == 0); unsigned long long updateTime = ms.count(); stringstream ss; ss.write((char*)&updateTime, sizeof(updateTime)); - unitsManager->getUnitData(ss, time, refresh); + unitsManager->getUnitData(ss, time); response.set_body(concurrency::streams::bytestream::open_istream(ss.str())); } else { diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp index 4f6212bf..dd912cf7 100644 --- a/src/core/src/unit.cpp +++ b/src/core/src/unit.cpp @@ -170,56 +170,72 @@ void Unit::updateMissionData(json::value json) setHasTask(json[L"hasTask"].as_bool()); } +bool Unit::checkFreshness(unsigned char datumIndex, unsigned long long time) { + auto it = updateTimeMap.find(datumIndex); + if (it == updateTimeMap.end()) + return false; + else + return it->second > time; +} -void Unit::getData(stringstream& ss, unsigned long long time, bool refresh) +bool Unit::hasFreshData(unsigned long long time) { + for (auto it : updateTimeMap) + if (it.second > time) + return true; + return false; +} + +void Unit::getData(stringstream& ss, unsigned long long time) { - Unit* sourceUnit = this; + Unit* leader = this; if (unitsManager->isUnitInGroup(this) && !unitsManager->isUnitGroupLeader(this)) - sourceUnit = unitsManager->getGroupLeader(this); + leader = unitsManager->getGroupLeader(this); + + if (!leader->hasFreshData(time)) return; const unsigned char endOfData = DataIndex::endOfData; ss.write((const char*)&ID, sizeof(ID)); - for (auto d : updateTimeMap) { - if (d.second > time) { - switch (d.first) { - case DataIndex::category: appendString(ss, d.first, category); break; - case DataIndex::alive: appendNumeric(ss, d.first, alive); break; - case DataIndex::human: appendNumeric(ss, d.first, sourceUnit->human); break; - case DataIndex::controlled: appendNumeric(ss, d.first, sourceUnit->controlled); break; - case DataIndex::coalition: appendNumeric(ss, d.first, sourceUnit->coalition); break; - case DataIndex::country: appendNumeric(ss, d.first, sourceUnit->country); break; - case DataIndex::name: appendString(ss, d.first, name); break; - case DataIndex::unitName: appendString(ss, d.first, unitName); break; - case DataIndex::groupName: appendString(ss, d.first, sourceUnit->groupName); break; - case DataIndex::state: appendNumeric(ss, d.first, sourceUnit->state); break; - case DataIndex::task: appendString(ss, d.first, sourceUnit->task); break; - case DataIndex::hasTask: appendNumeric(ss, d.first, sourceUnit->hasTask); break; - case DataIndex::position: appendNumeric(ss, d.first, position); break; - case DataIndex::speed: appendNumeric(ss, d.first, speed); break; - case DataIndex::heading: appendNumeric(ss, d.first, heading); break; - case DataIndex::isTanker: appendNumeric(ss, d.first, sourceUnit->isTanker); break; - case DataIndex::isAWACS: appendNumeric(ss, d.first, sourceUnit->isAWACS); break; - case DataIndex::onOff: appendNumeric(ss, d.first, sourceUnit->onOff); break; - case DataIndex::followRoads: appendNumeric(ss, d.first, sourceUnit->followRoads); break; - case DataIndex::fuel: appendNumeric(ss, d.first, fuel); break; - case DataIndex::desiredSpeed: appendNumeric(ss, d.first, sourceUnit->desiredSpeed); break; - case DataIndex::desiredSpeedType: appendNumeric(ss, d.first, sourceUnit->desiredSpeedType); break; - case DataIndex::desiredAltitude: appendNumeric(ss, d.first, sourceUnit->desiredAltitude); break; - case DataIndex::desiredAltitudeType: appendNumeric(ss, d.first, sourceUnit->desiredAltitudeType); break; - case DataIndex::leaderID: appendNumeric(ss, d.first, sourceUnit->leaderID); break; - case DataIndex::formationOffset: appendNumeric(ss, d.first, sourceUnit->formationOffset); break; - case DataIndex::targetID: appendNumeric(ss, d.first, sourceUnit->targetID); break; - case DataIndex::targetPosition: appendNumeric(ss, d.first, sourceUnit->targetPosition); break; - case DataIndex::ROE: appendNumeric(ss, d.first, sourceUnit->ROE); break; - case DataIndex::reactionToThreat: appendNumeric(ss, d.first, sourceUnit->reactionToThreat); break; - case DataIndex::emissionsCountermeasures: appendNumeric(ss, d.first, sourceUnit->emissionsCountermeasures); break; - case DataIndex::TACAN: appendNumeric(ss, d.first, sourceUnit->TACAN); break; - case DataIndex::radio: appendNumeric(ss, d.first, sourceUnit->radio); break; - case DataIndex::generalSettings: appendNumeric(ss, d.first, sourceUnit->generalSettings); break; - case DataIndex::ammo: appendVector(ss, d.first, ammo); break; - case DataIndex::contacts: appendVector(ss, d.first, contacts); break; - case DataIndex::activePath: appendList(ss, d.first, sourceUnit->activePath); break; - } + for (unsigned char datumIndex = DataIndex::startOfData + 1; datumIndex < DataIndex::lastIndex; datumIndex++) + { + /* When units are in a group, most data comes from the group leader */ + switch (datumIndex) { + case DataIndex::category: if (checkFreshness(datumIndex, time)) appendString(ss, datumIndex, category); break; + case DataIndex::alive: if (checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, alive); break; + case DataIndex::human: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->human); break; + case DataIndex::controlled: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->controlled); break; + case DataIndex::coalition: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->coalition); break; + case DataIndex::country: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->country); break; + case DataIndex::name: if (checkFreshness(datumIndex, time)) appendString(ss, datumIndex, name); break; + case DataIndex::unitName: if (checkFreshness(datumIndex, time)) appendString(ss, datumIndex, unitName); break; + case DataIndex::groupName: if (leader->checkFreshness(datumIndex, time)) appendString(ss, datumIndex, leader->groupName); break; + case DataIndex::state: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->state); break; + case DataIndex::task: if (leader->checkFreshness(datumIndex, time)) appendString(ss, datumIndex, leader->task); break; + case DataIndex::hasTask: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->hasTask); break; + case DataIndex::position: if (checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, position); break; + case DataIndex::speed: if (checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, speed); break; + case DataIndex::heading: if (checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, heading); break; + case DataIndex::isTanker: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->isTanker); break; + case DataIndex::isAWACS: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->isAWACS); break; + case DataIndex::onOff: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->onOff); break; + case DataIndex::followRoads: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->followRoads); break; + case DataIndex::fuel: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, fuel); break; + case DataIndex::desiredSpeed: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->desiredSpeed); break; + case DataIndex::desiredSpeedType: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->desiredSpeedType); break; + case DataIndex::desiredAltitude: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->desiredAltitude); break; + case DataIndex::desiredAltitudeType: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->desiredAltitudeType); break; + case DataIndex::leaderID: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->leaderID); break; + case DataIndex::formationOffset: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->formationOffset); break; + case DataIndex::targetID: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->targetID); break; + case DataIndex::targetPosition: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->targetPosition); break; + case DataIndex::ROE: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->ROE); break; + case DataIndex::reactionToThreat: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->reactionToThreat); break; + case DataIndex::emissionsCountermeasures: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->emissionsCountermeasures); break; + case DataIndex::TACAN: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->TACAN); break; + case DataIndex::radio: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->radio); break; + case DataIndex::generalSettings: if (leader->checkFreshness(datumIndex, time)) appendNumeric(ss, datumIndex, leader->generalSettings); break; + case DataIndex::ammo: if (checkFreshness(datumIndex, time)) appendVector(ss, datumIndex, ammo); break; + case DataIndex::contacts: if (checkFreshness(datumIndex, time)) appendVector(ss, datumIndex, contacts); break; + case DataIndex::activePath: if (leader->checkFreshness(datumIndex, time)) appendList(ss, datumIndex, leader->activePath); break; } } ss.write((const char*)&endOfData, sizeof(endOfData)); diff --git a/src/core/src/unitsmanager.cpp b/src/core/src/unitsmanager.cpp index 9f4a30c3..3c96ef9c 100644 --- a/src/core/src/unitsmanager.cpp +++ b/src/core/src/unitsmanager.cpp @@ -154,10 +154,10 @@ void UnitsManager::runAILoop() { unit.second->runAILoop(); } -string UnitsManager::getUnitData(stringstream &ss, unsigned long long time, bool refresh) +string UnitsManager::getUnitData(stringstream &ss, unsigned long long time) { for (auto const& p : units) - p.second->getData(ss, time, refresh); + p.second->getData(ss, time); return to_base64(ss.str()); } From 30568e54f70c40d1b059baae035d6b24dfd08d9d Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Thu, 6 Jul 2023 12:59:08 +0200 Subject: [PATCH 15/15] Converted demo data creation to binary data --- client/demo.js | 863 +++++++------------------- client/src/panels/unitcontrolpanel.ts | 3 +- client/src/units/unit.ts | 7 +- 3 files changed, 239 insertions(+), 634 deletions(-) diff --git a/client/demo.js b/client/demo.js index 5a76f8d1..be34fce8 100644 --- a/client/demo.js +++ b/client/demo.js @@ -1,641 +1,250 @@ +var enc = new TextEncoder(); const DEMO_UNIT_DATA = { - ["1"]:{ - baseData: { - AI: false, - name: "KC-135", - unitName: "Olympus 1-1 aka Mr. Very long name", - groupName: "Group 2", - alive: true, - category: "Aircraft", - }, - flightData: { - latitude: 37.20, - longitude: -115.80, - altitude: 2000, - heading: 0.5, - speed: 300 - }, - missionData: { - fuel: 50, - flags: {Human: false}, - ammo: [ - { - count: 4, - desc: { - displayName: "AIM-120" - } - }, - { - count: 2, - desc: { - displayName: "AIM-7" - } - } - ], - targets: [], - hasTask: true, - coalition: "blue" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Holding", - currentState: "Idle", - activePath: undefined, - desiredSpeed: 400, - desiredSpeedType: "CAS", - desiredAltitude: 3000, - desiredAltitudeType: "ASL", - isTanker: false, - - }, - optionsData: { - ROE: "Designated", - reactionToThreat: "Abort", - } + ["1"]:{ alive: true, human: false, controlled: true, coalition: 2, country: 0, name: "KC-135", unitName: "Cool guy 1-1", groupName: "Cool group 1", state: 3, task: "Being cool!", + hasTask: true, position: { lat: 37, lng: -116, alt: 1000 }, speed: 200, heading: 45, isTanker: true, isAWACS: false, onOff: true, followRoads: false, fuel: 50, + desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0, + formationOffset: { x: 0, y: 0, z: 0 }, + targetID: 2, + targetPosition: { lat: 0, lng: 0, alt: 0 }, + ROE: 2, + reactionToThreat: 1, + emissionsCountermeasures: 1, + TACAN: { isOn: false, XY: 'Y', callsign: 'TKR', channel: 40 }, + radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 }, + generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false }, + ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ], + contacts: [], + activePath: [ {lat: 38, lng: -115, alt: 0}, {lat: 38, lng: -114, alt: 0} ] }, - ["2"]:{ - baseData: { - AI: true, - name: "KC-135", - unitName: "Olympus 1-2", - groupName: "Group 3", - alive: true, - category: "Aircraft", - }, - flightData: { - latitude: 37.2, - longitude: -115.75, - altitude: 2000, - heading: 0.5, - speed: 300 - }, - missionData: { - fuel: 0.5, - flags: {human: false}, - ammo: [], - targets: [], - hasTask: true, - coalition: "red" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Example task", - activePath: undefined, - desiredSpeed: 300, - desiredAltitude: 3000 - }, - optionsData: { - ROE: "Designated", - reactionToThreat: "Abort", - } - }, - ["3"]:{ - baseData: { - AI: true, - name: "M-60", - unitName: "Olympus 1-3", - groupName: "Group 4", - alive: true, - category: "GroundUnit", - }, - flightData: { - latitude: 37.175, - longitude: -115.8, - altitude: 2000, - heading: 0.5, - speed: 300 - }, - missionData: { - fuel: 0.5, - flags: {human: false}, - ammo: [], - targets: [], - hasTask: true, - coalition: "blue" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Example task", - activePath: undefined, - desiredSpeed: 400, - desiredAltitude: 3000, - onOff: false - }, - optionsData: { - ROE: "None", - reactionToThreat: "None", - } - }, - ["4"]:{ - baseData: { - AI: true, - name: "2S6 Tunguska", - unitName: "Olympus 1-4", - alive: true, - category: "GroundUnit", - }, - flightData: { - latitude: 37.175, - longitude: -115.75, - altitude: 2000, - heading: 0.5, - speed: 300 - }, - missionData: { - fuel: 0.5, - flags: {human: false}, - ammo: [], - targets: [], - hasTask: true, - coalition: "red" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Example task", - activePath: undefined, - desiredSpeed: 400, - desiredAltitude: 3000 - }, - optionsData: { - ROE: "None", - reactionToThreat: "None", - } - }, - ["5"]:{ - baseData: { - AI: true, - name: "M-60", - unitName: "Olympus 1-3", - groupName: "Group 1", - alive: true, - category: "GroundUnit", - }, - flightData: { - latitude: 37.15, - longitude: -115.8, - altitude: 2000, - heading: 0.5, - speed: 300 - }, - missionData: { - fuel: 0.5, - flags: {human: false}, - ammo: [], - targets: [], - hasTask: true, - coalition: "blue" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Example task", - activePath: undefined, - desiredSpeed: 400, - desiredAltitude: 3000 - }, - optionsData: { - ROE: "None", - reactionToThreat: "None", - } - }, - ["6"]:{ - baseData: { - AI: true, - name: "M-60", - unitName: "Olympus 1-4", - groupName: "Group 1", - alive: true, - category: "GroundUnit", - }, - flightData: { - latitude: 37.15, - longitude: -115.75, - altitude: 2000, - heading: 0.5, - speed: 300 - }, - missionData: { - fuel: 0.5, - flags: {human: false}, - ammo: [], - targets: [], - hasTask: true, - coalition: "red" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Example task", - activePath: undefined, - desiredSpeed: 400, - desiredAltitude: 3000 - }, - optionsData: { - ROE: "None", - reactionToThreat: "None", - } - }, - ["7"]:{ - baseData: { - AI: true, - name: "CVN-75 Very long name", - unitName: "Olympus 1-7", - groupName: "Group 1", - alive: true, - category: "NavyUnit", - }, - flightData: { - latitude: 37.125, - longitude: -115.8, - altitude: 2000, - heading: 0.5, - speed: 300 - }, - missionData: { - fuel: 0.5, - flags: {human: false}, - ammo: [], - targets: [], - hasTask: true, - coalition: "blue" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Example task", - activePath: undefined, - desiredSpeed: 400, - desiredAltitude: 3000 - }, - optionsData: { - ROE: "None", - reactionToThreat: "None", - } - }, - ["8"]:{ - baseData: { - AI: true, - name: "CVN-75", - unitName: "Olympus 1-8", - groupName: "Group 1", - alive: true, - category: "NavyUnit", - }, - flightData: { - latitude: 37.125, - longitude: -115.75, - altitude: 2000, - heading: 0.5, - speed: 300 - }, - missionData: { - fuel: 0.5, - flags: {human: false}, - ammo: [], - targets: [], - hasTask: true, - coalition: "red" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Example task", - activePath: undefined, - desiredSpeed: 400, - desiredAltitude: 3000 - }, - optionsData: { - ROE: "None", - reactionToThreat: "None", - } - }, - ["9"]:{ - baseData: { - AI: true, - name: "CVN-75", - unitName: "Olympus 1-9", - groupName: "Group 1", - alive: true, - category: "Aircraft", - }, - flightData: { - latitude: 37.10, - longitude: -115.75, - altitude: 2000, - heading: 0.5, - speed: 300 - }, - missionData: { - fuel: 0.5, - flags: {human: false}, - ammo: [], - targets: [], - hasTask: true, - coalition: "red" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Example task", - activePath: undefined, - desiredSpeed: 400, - desiredAltitude: 3000 - }, - optionsData: { - ROE: "None", - reactionToThreat: "None", - } - }, - ["10"]:{ - baseData: { - AI: true, - name: "CVN-75", - unitName: "Olympus 1-10", - groupName: "Group 1", - alive: true, - category: "Aircraft", - }, - flightData: { - latitude: 37.10, - longitude: -115.8, - altitude: 2000, - heading: 0.5, - speed: 300 - }, - missionData: { - fuel: 0.5, - flags: {human: false}, - ammo: [], - targets: [], - hasTask: true, - coalition: "blue" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Example task", - activePath: undefined, - desiredSpeed: 400, - desiredAltitude: 3000 - }, - optionsData: { - ROE: "None", - reactionToThreat: "None", - } - }, - ["11"]:{ - baseData: { - AI: true, - name: "CVN-75", - unitName: "Olympus 1-11", - groupName: "Group 1", - alive: true, - category: "Missile", - }, - flightData: { - latitude: 37.075, - longitude: -115.80, - altitude: 2000, - heading: 0.5, - speed: 300 - }, - missionData: { - fuel: 0.5, - flags: {human: false}, - ammo: [], - targets: [], - hasTask: true, - coalition: "blue" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Example task", - activePath: undefined, - desiredSpeed: 400, - desiredAltitude: 3000 - }, - optionsData: { - ROE: "None", - reactionToThreat: "None", - } - }, - ["12"]:{ - baseData: { - AI: true, - name: "CVN-75", - unitName: "Olympus 1-12", - groupName: "Group 1", - alive: true, - category: "Missile", - }, - flightData: { - latitude: 37.075, - longitude: -115.75, - altitude: 2000, - heading: 0.6, - speed: 300 - }, - missionData: { - fuel: 0.5, - flags: {human: false}, - ammo: [], - targets: [], - hasTask: true, - coalition: "red" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Example task", - activePath: undefined, - desiredSpeed: 400, - desiredAltitude: 3000 - }, - optionsData: { - ROE: "None", - reactionToThreat: "None", - } - }, - ["13"]:{ - baseData: { - AI: true, - name: "CVN-75", - unitName: "Olympus 1-11", - groupName: "Group 1", - alive: true, - category: "Bomb", - }, - flightData: { - latitude: 37.05, - longitude: -115.8, - altitude: 2000, - heading: 0.5, - speed: 300 - }, - missionData: { - fuel: 0.5, - flags: {human: false}, - ammo: [], - targets: [], - hasTask: true, - coalition: "blue" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Example task", - activePath: undefined, - desiredSpeed: 400, - desiredAltitude: 3000 - }, - optionsData: { - ROE: "None", - reactionToThreat: "None", - } - }, - ["14"]:{ - baseData: { - AI: true, - name: "CVN-75", - unitName: "Olympus 1-12", - groupName: "Group 1", - alive: true, - category: "Bomb", - }, - flightData: { - latitude: 37.05, - longitude: -115.75, - altitude: 2000, - heading: 0.6, - speed: 300 - }, - missionData: { - fuel: 0.5, - flags: {human: false}, - ammo: [], - targets: [], - hasTask: true, - coalition: "red" - }, - formationData: { - formation: "Echelon", - isLeader: false, - isWingman: false, - leaderID: null, - wingmen: [], - wingmenIDs: [] - }, - taskData: { - currentTask: "Example task", - activePath: undefined, - desiredSpeed: 400, - desiredAltitude: 3000 - }, - optionsData: { - ROE: "None", - reactionToThreat: "None", - } + ["2"]:{ alive: true, human: false, controlled: false, coalition: 1, country: 0, name: "KC-135", unitName: "Cool guy 1-2", groupName: "Cool group 2", state: 1, task: "Being cool", + hasTask: false, position: { lat: 36.9, lng: -116, alt: 1000 }, speed: 200, heading: 0, isTanker: false, isAWACS: false, onOff: true, followRoads: false, fuel: 50, + desiredSpeed: 300, desiredSpeedType: 1, desiredAltitude: 1000, desiredAltitudeType: 1, leaderID: 0, + formationOffset: { x: 0, y: 0, z: 0 }, + targetID: 0, + targetPosition: { lat: 38, lng: -117, alt: 1000 }, + ROE: 2, + reactionToThreat: 1, + emissionsCountermeasures: 1, + TACAN: { isOn: false, XY: 'Y', callsign: 'TKR', channel: 40 }, + radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 }, + generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false }, + ammo: [{ quantity: 2, name: "A cool missile", guidance: 0, category: 0, missileCategory: 0 } ], + contacts: [{ID: 1, detectionMethod: 4}], + activePath: [ {lat: 38, lng: -115, alt: 0}, {lat: 38, lng: -114, alt: 0} ] } } class DemoDataGenerator { - constructor(unitsNumber) + constructor() { - this.demoUnits = this.generateRandomUnitsDemoData(unitsNumber); + } units(req, res){ - var ret = this.demoUnits; - for (let ID in this.demoUnits["units"]){ - this.demoUnits["units"][ID].flightData.latitude += 0.00001; + var array = new Uint8Array(); + var time = Date.now(); + array = this.concat(array, this.uint64ToByteArray(BigInt(time))); + for (let idx in DEMO_UNIT_DATA) { + const unit = DEMO_UNIT_DATA[idx]; + array = this.concat(array, this.uint32ToByteArray(idx)); + array = this.appendString(array, "Aircraft", 1); + array = this.appendUint8(array, unit.alive, 2); + array = this.appendUint8(array, unit.human, 3); + array = this.appendUint8(array, unit.controlled, 4); + array = this.appendUint16(array, unit.coalition, 5); + array = this.appendUint8(array, unit.country, 6); + array = this.appendString(array, unit.name, 7); + array = this.appendString(array, unit.unitName, 8); + array = this.appendString(array, unit.groupName, 9); + array = this.appendUint8(array, unit.state, 10); + array = this.appendString(array, unit.task, 11); + array = this.appendUint8(array, unit.hasTask, 12); + array = this.appendCoordinates(array, unit.position, 13); + array = this.appendDouble(array, unit.speed, 14); + array = this.appendDouble(array, unit.heading, 15); + array = this.appendUint8(array, unit.isTanker, 16); + array = this.appendUint8(array, unit.isAWACS, 17); + array = this.appendUint8(array, unit.onOff, 18); + array = this.appendUint8(array, unit.followRoads, 19); + array = this.appendUint16(array, unit.fuel, 20); + array = this.appendDouble(array, unit.desiredSpeed, 21); + array = this.appendUint8(array, unit.desiredSpeedType, 22); + array = this.appendDouble(array, unit.desiredAltitude, 23); + array = this.appendUint8(array, unit.desiredAltitudeType, 24); + array = this.appendUint32(array, unit.leaderID, 25); + array = this.appendOffset(array, unit.formationOffset, 26); + array = this.appendUint32(array, unit.targetID, 27); + array = this.appendCoordinates(array, unit.targetPosition, 28); + array = this.appendUint8(array, unit.ROE, 29); + array = this.appendUint8(array, unit.reactionToThreat, 30); + array = this.appendUint8(array, unit.emissionsCountermeasures, 31); + array = this.appendTACAN(array, unit.TACAN, 32); + array = this.appendRadio(array, unit.radio, 33); + array = this.appendRadio(array, unit.generalSettings, 34); + array = this.appendAmmo(array, unit.ammo, 35); + array = this.appendContacts(array, unit.contacts, 36); + array = this.appendActivePath(array, unit.activePath, 37); + array = this.concat(array, this.uint8ToByteArray(255)); } - ret.time = Date.now(); - res.send(JSON.stringify(ret)); + res.end(Buffer.from(array, 'binary')); }; + + concat(array1, array2) { + var mergedArray = new Uint8Array(array1.length + array2.length); + mergedArray.set(array1); + mergedArray.set(array2, array1.length); + return mergedArray; + } + + uint8ToByteArray(number) { + var buffer = new ArrayBuffer(1); + var longNum = new Uint8Array(buffer); + longNum[0] = number; + return Array.from(new Uint8Array(buffer)); + } + + uint16ToByteArray(number) { + var buffer = new ArrayBuffer(2); + var longNum = new Uint16Array(buffer); + longNum[0] = number; + return Array.from(new Uint8Array(buffer)); + } + + uint32ToByteArray(number) { + var buffer = new ArrayBuffer(4); + var longNum = new Uint32Array(buffer); + longNum[0] = number; + return Array.from(new Uint8Array(buffer)); + } + + uint64ToByteArray(number) { + var buffer = new ArrayBuffer(8); + var longNum = new BigUint64Array(buffer); + longNum[0] = number; + return Array.from(new Uint8Array(buffer)); + } + + doubleToByteArray(number) { + var buffer = new ArrayBuffer(8); + var longNum = new Float64Array(buffer); + longNum[0] = number; + return Array.from(new Uint8Array(buffer)); + } + + appendUint8(array, number, datumIndex) { + array = this.concat(array, this.uint8ToByteArray(datumIndex)); + array = this.concat(array, this.uint8ToByteArray(number)); + return array; + } + + appendUint16(array, number, datumIndex) { + array = this.concat(array, this.uint8ToByteArray(datumIndex)); + array = this.concat(array, this.uint16ToByteArray(number)); + return array; + } + + appendUint32(array, number, datumIndex) { + array = this.concat(array, this.uint8ToByteArray(datumIndex)); + array = this.concat(array, this.uint32ToByteArray(number)); + return array; + } + + appendDouble(array, number, datumIndex) { + array = this.concat(array, this.uint8ToByteArray(datumIndex)); + array = this.concat(array, this.doubleToByteArray(number)); + return array; + } + + appendCoordinates(array, coordinates, datumIndex) { + array = this.concat(array, this.uint8ToByteArray(datumIndex)); + array = this.concat(array, this.doubleToByteArray(coordinates.lat)); + array = this.concat(array, this.doubleToByteArray(coordinates.lng)); + array = this.concat(array, this.doubleToByteArray(coordinates.alt)); + return array; + } + + appendOffset(array, offset, datumIndex) { + array = this.concat(array, this.uint8ToByteArray(datumIndex)); + array = this.concat(array, this.doubleToByteArray(offset.x)); + array = this.concat(array, this.doubleToByteArray(offset.y)); + array = this.concat(array, this.doubleToByteArray(offset.z)); + return array; + } + + appendString(array, string, datumIndex) { + array = this.concat(array, this.uint8ToByteArray(datumIndex)); + array = this.concat(array, this.uint16ToByteArray(string.length)); + array = this.concat(array, enc.encode(string)); + return array; + } + + padString(string, length) { + while (string.length < length) + string += " "; + return string.substring(0, length); + } + + appendTACAN(array, TACAN, datumIndex) { + array = this.concat(array, this.uint8ToByteArray(datumIndex)); + array = this.concat(array, this.uint8ToByteArray(TACAN.isOn)); + array = this.concat(array, this.uint8ToByteArray(TACAN.channel)); + array = this.concat(array, enc.encode(TACAN.XY)); + array = this.concat(array, enc.encode(this.padString(TACAN.callsign, 4))); + return array; + } + + appendRadio(array, radio, datumIndex) { + array = this.concat(array, this.uint8ToByteArray(datumIndex)); + array = this.concat(array, this.uint32ToByteArray(radio.frequency)); + array = this.concat(array, this.uint8ToByteArray(radio.callsign)); + array = this.concat(array, this.uint8ToByteArray(radio.callsignNumber)); + return array; + } + + appendGeneralSettings(array, generalSettings, datumIndex) { + array = this.concat(array, this.uint8ToByteArray(datumIndex)); + array = this.concat(array, this.uint8ToByteArray(generalSettings.prohibitAA)); + array = this.concat(array, this.uint8ToByteArray(generalSettings.prohibitAfterburner)); + array = this.concat(array, this.uint8ToByteArray(generalSettings.prohibitAG)); + array = this.concat(array, this.uint8ToByteArray(generalSettings.prohibitAirWpn)); + array = this.concat(array, this.uint8ToByteArray(generalSettings.prohibitJettison)); + return array; + } + + appendAmmo(array, ammo, datumIndex) { + array = this.concat(array, this.uint8ToByteArray(datumIndex)); + array = this.concat(array, this.uint16ToByteArray(ammo.length)); + ammo.forEach((element) => { + array = this.concat(array, this.uint16ToByteArray(element.quantity)); + array = this.concat(array, enc.encode(this.padString(element.name, 33))); + array = this.concat(array, this.uint8ToByteArray(element.guidance)); + array = this.concat(array, this.uint8ToByteArray(element.category)); + array = this.concat(array, this.uint8ToByteArray(element.missileCategory)); + }) + return array; + } + + appendContacts(array, contacts, datumIndex) { + array = this.concat(array, this.uint8ToByteArray(datumIndex)); + array = this.concat(array, this.uint16ToByteArray(contacts.length)); + contacts.forEach((element) => { + array = this.concat(array, this.uint32ToByteArray(element.ID)); + array = this.concat(array, this.uint8ToByteArray(element.detectionMethod)); + }) + return array; + } + + appendActivePath(array, activePath, datumIndex) { + array = this.concat(array, this.uint8ToByteArray(datumIndex)); + array = this.concat(array, this.uint16ToByteArray(activePath.length)); + activePath.forEach((element) => { + array = this.concat(array, this.doubleToByteArray(element.lat)); + array = this.concat(array, this.doubleToByteArray(element.lng)); + array = this.concat(array, this.doubleToByteArray(element.alt)); + }) + return array; + } logs(req, res){ var ret = {logs: {}}; @@ -696,10 +305,6 @@ class DemoDataGenerator { res.send(JSON.stringify(ret)); } - generateRandomUnitsDemoData(unitsNumber) - { - return {"units": DEMO_UNIT_DATA}; - } } module.exports = DemoDataGenerator; \ No newline at end of file diff --git a/client/src/panels/unitcontrolpanel.ts b/client/src/panels/unitcontrolpanel.ts index 0b43acae..1abf45ef 100644 --- a/client/src/panels/unitcontrolpanel.ts +++ b/client/src/panels/unitcontrolpanel.ts @@ -34,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); }); }); diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index 2d10fa62..05b8772a 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -199,7 +199,7 @@ export class Unit extends CustomMarker { case DataIndexes.desiredSpeed: this.#desiredSpeed = dataExtractor.extractFloat64(); break; case DataIndexes.desiredSpeedType: this.#desiredSpeedType = dataExtractor.extractBool() ? "GS" : "CAS"; break; case DataIndexes.desiredAltitude: this.#desiredAltitude = dataExtractor.extractFloat64(); break; - case DataIndexes.desiredAltitudeType: this.#desiredAltitudeType = dataExtractor.extractFloat64() ? "AGL" : "ASL"; break; + case DataIndexes.desiredAltitudeType: this.#desiredAltitudeType = dataExtractor.extractBool() ? "AGL" : "ASL"; break; case DataIndexes.leaderID: this.#leaderID = dataExtractor.extractUInt32(); break; case DataIndexes.formationOffset: this.#formationOffset = dataExtractor.extractOffset(); break; case DataIndexes.targetID: this.#targetID = dataExtractor.extractUInt32(); break; @@ -216,7 +216,6 @@ export class Unit extends CustomMarker { } } - /* Dead units can't be selected */ this.setSelected(this.getSelected() && this.#alive && !this.getHidden()) @@ -772,7 +771,7 @@ export class Unit extends CustomMarker { this.#miniMapMarker.bringToBack(); } else { - if (this.#miniMapMarker.getLatLng().lat !== this.getPosition().lat && this.#miniMapMarker.getLatLng().lng !== this.getPosition().lng) { + if (this.#miniMapMarker.getLatLng().lat !== this.getPosition().lat || this.#miniMapMarker.getLatLng().lng !== this.getPosition().lng) { this.#miniMapMarker.setLatLng(new LatLng(this.#position.lat, this.#position.lng)); this.#miniMapMarker.bringToBack(); } @@ -787,7 +786,7 @@ export class Unit extends CustomMarker { /* Draw the marker */ if (!this.getHidden()) { - if (this.getLatLng().lat !== this.getPosition().lat && this.getLatLng().lng !== this.getPosition().lng) { + if (this.getLatLng().lat !== this.#position.lat || this.getLatLng().lng !== this.#position.lng) { this.setLatLng(new LatLng(this.#position.lat, this.#position.lng)); }