From 1d4ecf53163787ec81200944f51ace9d2b52fcfc Mon Sep 17 00:00:00 2001 From: Pax1601 Date: Sun, 11 Jun 2023 18:21:12 +0200 Subject: [PATCH] Completed advanced controls also for units --- client/demo.js | 2 +- client/public/stylesheets/aic/aic.css | 2 +- client/public/stylesheets/layout/layout.css | 10 +- client/public/stylesheets/markers/units.css | 13 +- client/public/stylesheets/olympus.css | 19 +- .../public/stylesheets/other/contextmenus.css | 16 +- .../icons/triangle-exclamation-solid.svg | 1 + .../themes/olympus/images/markers/move.svg | 116 ++++++++++++ .../themes/olympus/images/markers/target.svg | 101 +++++++++++ .../themes/olympus/images/states/no-task.svg | 51 ++++++ client/src/@types/unit.d.ts | 2 + client/src/map/map.ts | 166 +++++++++++------- client/src/map/targetmarker.ts | 18 ++ client/src/server/server.ts | 4 +- client/src/units/aircraftdatabase.ts | 2 +- client/src/units/unit.ts | 116 ++++++++++-- client/src/units/unitsmanager.ts | 12 +- client/views/other/contextmenus.ejs | 8 +- scripts/OlympusCommand.lua | 143 ++++++++++----- src/core/include/commands.h | 44 ++--- src/core/include/unit.h | 9 +- src/core/include/unitsmanager.h | 5 + src/core/src/airunit.cpp | 15 +- src/core/src/commands.cpp | 47 +++-- src/core/src/groundunit.cpp | 5 +- src/core/src/scheduler.cpp | 59 +++---- src/core/src/unit.cpp | 134 +++++++++----- src/core/src/unitsmanager.cpp | 61 +++++++ 28 files changed, 896 insertions(+), 285 deletions(-) create mode 100644 client/public/themes/olympus/images/icons/triangle-exclamation-solid.svg create mode 100644 client/public/themes/olympus/images/markers/move.svg create mode 100644 client/public/themes/olympus/images/markers/target.svg create mode 100644 client/public/themes/olympus/images/states/no-task.svg create mode 100644 client/src/map/targetmarker.ts diff --git a/client/demo.js b/client/demo.js index 4dc6a2b4..63e7940c 100644 --- a/client/demo.js +++ b/client/demo.js @@ -107,7 +107,7 @@ const DEMO_UNIT_DATA = { ["3"]:{ baseData: { AI: true, - name: "2S6 Tunguska", + name: "M-60", unitName: "Olympus 1-3", groupName: "Group 4", alive: true, diff --git a/client/public/stylesheets/aic/aic.css b/client/public/stylesheets/aic/aic.css index 16022448..ea972d71 100644 --- a/client/public/stylesheets/aic/aic.css +++ b/client/public/stylesheets/aic/aic.css @@ -130,7 +130,7 @@ padding: 10px; position: absolute; width: fit-content; - z-index: 1000; + z-index: 9999; } .aic-enabled #aic-teleprompt { diff --git a/client/public/stylesheets/layout/layout.css b/client/public/stylesheets/layout/layout.css index f6fa464c..7ae3c7b5 100644 --- a/client/public/stylesheets/layout/layout.css +++ b/client/public/stylesheets/layout/layout.css @@ -11,7 +11,7 @@ left: 10px; position: absolute; top: 10px; - z-index: 1000; + z-index: 9999; } #app-icon>.ol-select-options { @@ -39,7 +39,7 @@ position: absolute; right: 10px; width: 180px; - z-index: 1000; + z-index: 9999; } #mouse-info-panel { @@ -51,7 +51,7 @@ right: 10px; row-gap: 10px; width: 180px; - z-index: 1000; + z-index: 9999; } #unit-control-panel { @@ -60,7 +60,7 @@ position: absolute; top: 80px; width: 320px; - z-index: 1000; + z-index: 9999; } #unit-info-panel { @@ -69,7 +69,7 @@ left: 10px; position: absolute; width: fit-content; - z-index: 1000; + z-index: 9999; padding: 24px 30px; } diff --git a/client/public/stylesheets/markers/units.css b/client/public/stylesheets/markers/units.css index 8447f4d1..65ec1d4b 100644 --- a/client/public/stylesheets/markers/units.css +++ b/client/public/stylesheets/markers/units.css @@ -260,7 +260,14 @@ background-image: url("/resources/theme/images/states/idle.svg"); } -[data-object|="unit"][data-state="attack"] .unit-state { +[data-object*="groundunit"][data-state="idle"] .unit-state { + background-image: url(""); /* To avoid clutter, dont show the idle state for non flying units */ +} + +[data-object|="unit"][data-state="attack"] .unit-state, +[data-object|="unit"][data-state="bombing point"] .unit-state, +[data-object|="unit"][data-state="carpet bombing"] .unit-state, +[data-object|="unit"][data-state="firing at area"] .unit-state { background-image: url("/resources/theme/images/states/attack.svg"); } @@ -280,6 +287,10 @@ background-image: url("/resources/theme/images/states/dcs.svg"); } +[data-object|="unit"][data-state="no-task"] .unit-state { + background-image: url("/resources/theme/images/states/no-task.svg"); +} + /*** Dead unit ***/ [data-object|="unit-aircraft"][data-is-dead] .unit-selected-spotlight, [data-object|="unit-aircraft"][data-is-dead] .unit-short-label, diff --git a/client/public/stylesheets/olympus.css b/client/public/stylesheets/olympus.css index 7fe17ff9..d4afca30 100644 --- a/client/public/stylesheets/olympus.css +++ b/client/public/stylesheets/olympus.css @@ -34,6 +34,10 @@ body { width: 100%; } +.hidden-cursor { + cursor: none !important; +} + a { text-decoration: none; } @@ -203,7 +207,7 @@ form>div { max-height: 0; overflow: hidden; position: absolute; - z-index: 1000; + z-index: 9999; } .ol-select-options.scrollbar-visible { @@ -709,7 +713,6 @@ nav.ol-panel> :last-child { position: relative; row-gap: 10px; width: 50%; - z-index: 10; } #splash-content::after { @@ -860,16 +863,18 @@ nav.ol-panel> :last-child { } .ol-destination-preview-icon { - background-color: var(--secondary-yellow); - border-radius: 999px; - cursor: grab; + background-image: url("/resources/theme/images/markers/move.svg"); height: 52px; pointer-events: none; width: 52px; } -.ol-destination-preview { +.ol-target-icon { + background-image: url("/resources/theme/images/markers/target.svg"); + height: 52px; pointer-events: none; + width: 52px; + z-index: 9999; } dl.ol-data-grid { @@ -933,7 +938,7 @@ dl.ol-data-grid dd { color: white; justify-self: center; position: absolute; - z-index: 1000; + z-index: 9999; } .ol-panel.ol-dialog { diff --git a/client/public/stylesheets/other/contextmenus.css b/client/public/stylesheets/other/contextmenus.css index 57fc1341..9f50a4d4 100644 --- a/client/public/stylesheets/other/contextmenus.css +++ b/client/public/stylesheets/other/contextmenus.css @@ -271,7 +271,7 @@ position: absolute; row-gap: 5px; width: fit-content; - z-index: 1000; + z-index: 9999; } #unit-contextmenu button { @@ -307,6 +307,18 @@ content: url("/resources/theme/images/icons/sword.svg"); } +#bomb::before { + content: url("/resources/theme/images/icons/crosshairs-solid.svg"); +} + +#carpet-bomb::before { + content: url("/resources/theme/images/icons/explosion-solid.svg"); +} + +#fire-at-area::before { + content: url("/resources/theme/images/icons/crosshairs-solid.svg"); +} + #follow::before { content: url("/resources/theme/images/icons/follow.svg"); } @@ -393,5 +405,5 @@ position: absolute; row-gap: 5px; width: 180px; - z-index: 1000; + z-index: 9999; } diff --git a/client/public/themes/olympus/images/icons/triangle-exclamation-solid.svg b/client/public/themes/olympus/images/icons/triangle-exclamation-solid.svg new file mode 100644 index 00000000..bb69b55f --- /dev/null +++ b/client/public/themes/olympus/images/icons/triangle-exclamation-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/themes/olympus/images/markers/move.svg b/client/public/themes/olympus/images/markers/move.svg new file mode 100644 index 00000000..4af0aa8e --- /dev/null +++ b/client/public/themes/olympus/images/markers/move.svg @@ -0,0 +1,116 @@ + + + + + + + + + + + + + diff --git a/client/public/themes/olympus/images/markers/target.svg b/client/public/themes/olympus/images/markers/target.svg new file mode 100644 index 00000000..7afbf612 --- /dev/null +++ b/client/public/themes/olympus/images/markers/target.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + diff --git a/client/public/themes/olympus/images/states/no-task.svg b/client/public/themes/olympus/images/states/no-task.svg new file mode 100644 index 00000000..2e1e906d --- /dev/null +++ b/client/public/themes/olympus/images/states/no-task.svg @@ -0,0 +1,51 @@ + + + + + + + + + diff --git a/client/src/@types/unit.d.ts b/client/src/@types/unit.d.ts index d6163f68..9f70f291 100644 --- a/client/src/@types/unit.d.ts +++ b/client/src/@types/unit.d.ts @@ -40,10 +40,12 @@ interface TaskData { targetSpeedType: string; targetAltitude: number; targetAltitudeType: string; + targetLocation: any; isTanker: boolean; isAWACS: boolean; onOff: boolean; followRoads: boolean; + targetID: number; } interface OptionsData { diff --git a/client/src/map/map.ts b/client/src/map/map.ts index eaf0951d..83339a7a 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -13,6 +13,7 @@ import { TemporaryUnitMarker } from "./temporaryunitmarker"; import { ClickableMiniMap } from "./clickableminimap"; import { SVGInjector } from '@tanem/svg-injector' import { layers as mapLayers, mapBounds, minimapBoundaries } from "../constants/constants"; +import { TargetMarker } from "./targetmarker"; L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect); @@ -20,12 +21,16 @@ L.Map.addInitHook('addHandler', 'boxSelect', BoxSelect); require("../../public/javascripts/leaflet.nauticscale.js") /* Map constants */ -export const IDLE = "IDLE"; -export const UNIT_SELECTED = "MOVE_UNIT"; +export const IDLE = "Idle"; +export const MOVE_UNIT = "Move unit"; +export const BOMBING = "Bombing"; +export const CARPET_BOMBING = "Carpet bombing"; +export const FIRE_AT_AREA = "Fire at area"; export const visibilityControls: string[] = ["human", "dcs", "aircraft", "groundunit-sam", "groundunit-other", "navyunit", "airbase"]; export const visibilityControlsTootlips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"]; export class Map extends L.Map { + #ID: string; #state: string; #layer: L.TileLayer | null = null; #preventLeftClick: boolean = false; @@ -40,8 +45,9 @@ export class Map extends L.Map { #centerUnit: Unit | null = null; #miniMap: ClickableMiniMap | null = null; #miniMapLayerGroup: L.LayerGroup; - #temporaryMarkers: L.Marker[] = []; - #destinationPreviewMarkers: L.Marker[] = []; + #temporaryMarkers: TemporaryUnitMarker[] = []; + #destinationPreviewMarkers: DestinationPreviewMarker[] = []; + #targetMarker: TargetMarker; #destinationGroupRotation: number = 0; #computeDestinationRotation: boolean = false; #destinationRotationCenter: L.LatLng | null = null; @@ -55,10 +61,13 @@ export class Map extends L.Map { constructor(ID: string) { /* Init the leaflet map */ + //@ts-ignore 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); + this.#ID = ID; + this.setLayer(Object.keys(mapLayers)[0]); /* Minimap */ @@ -122,6 +131,9 @@ 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) { @@ -149,31 +161,20 @@ export class Map extends L.Map { setState(state: string) { this.#state = state; if (this.#state === IDLE) { - /* Remove all the destination preview markers */ - this.#destinationPreviewMarkers.forEach((marker: L.Marker) => { - this.removeLayer(marker); - }) - this.#destinationPreviewMarkers = []; - - this.#destinationGroupRotation = 0; - this.#computeDestinationRotation = false; - this.#destinationRotationCenter = null; + this.#resetDestinationMarkers(); + this.#resetTargetMarker(); + this.#showCursor(); } - else if (this.#state === UNIT_SELECTED) { - /* Remove all the exising destination preview markers */ - this.#destinationPreviewMarkers.forEach((marker: L.Marker) => { - this.removeLayer(marker); - }) - this.#destinationPreviewMarkers = []; - - if (getUnitsManager().getSelectedUnits({ excludeHumans: true }).length > 1 && getUnitsManager().getSelectedUnits({ excludeHumans: true }).length < 20) { - /* Create the unit destination preview markers */ - this.#destinationPreviewMarkers = getUnitsManager().getSelectedUnits({ excludeHumans: true }).map((unit: Unit) => { - var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), {interactive: false}); - marker.addTo(this); - return marker; - }) - } + else if (this.#state === MOVE_UNIT) { + this.#resetTargetMarker(); + this.#createDestinationMarkers(); + if (this.#destinationPreviewMarkers.length > 0) + this.#hideCursor(); + } + else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) { + this.#resetDestinationMarkers(); + this.#createTargetMarker(); + this.#hideCursor(); } document.dispatchEvent(new CustomEvent("mapStateChanged")); } @@ -363,7 +364,7 @@ export class Map extends L.Map { if (this.#state === IDLE) { } - else if (this.#state === UNIT_SELECTED) { + else { this.setState(IDLE); getUnitsManager().deselectAllUnits(); } @@ -381,38 +382,26 @@ export class Map extends L.Map { this.showMapContextMenu(e); } } - else if (this.#state === UNIT_SELECTED) { - if (e.originalEvent.shiftKey) { - var options: {[key: string]: {text: string, tooltip: string}} = {}; - var selectedUnitTypes = getUnitsManager().getSelectedUnitsTypes(); - - if (selectedUnitTypes.length === 1 && ["Aircraft"].includes(selectedUnitTypes[0])) - { - options["bomb"] = {text: "Bomb here", tooltip: "Precision bombing of this specific point"}; - options["carpet-bomb"] = {text: "Carpet bomb", tooltip: "Carpet bombing around this point"}; - options["building-bomb"] = {text: "Bomb building", tooltip: "Precision bombing of the building closest to this point"}; - } - - if (selectedUnitTypes.length === 1 && ["GroundUnit"].includes(selectedUnitTypes[0])) - options["fire-at-area"] = {text: "Fire at area", tooltip: "Fire at this point"}; - - if (Object.keys(options).length > 0) { - this.showUnitContextMenu(e); - this.getUnitContextMenu().setOptions(options, (option: string) => { - this.hideUnitContextMenu(); - this.#executeAction(e, option); - }); - } - - } else { - if (!e.originalEvent.ctrlKey) { - getUnitsManager().selectedUnitsClearDestinations(); - } - getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, e.originalEvent.shiftKey, this.#destinationGroupRotation) - this.#destinationGroupRotation = 0; - this.#destinationRotationCenter = null; - this.#computeDestinationRotation = false; + else if (this.#state === MOVE_UNIT) { + if (!e.originalEvent.ctrlKey) { + getUnitsManager().selectedUnitsClearDestinations(); } + getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, e.originalEvent.shiftKey, this.#destinationGroupRotation) + this.#destinationGroupRotation = 0; + 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()); + } + else if (this.#state === CARPET_BOMBING) { + 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()); } } @@ -439,7 +428,7 @@ export class Map extends L.Map { #onMouseDown(e: any) { this.hideAllContextMenus(); - if (this.#state == UNIT_SELECTED) { + if (this.#state == MOVE_UNIT) { this.#destinationGroupRotation = 0; this.#destinationRotationCenter = null; this.#computeDestinationRotation = false; @@ -457,10 +446,14 @@ export class Map extends L.Map { this.#lastMousePosition.x = e.originalEvent.x; this.#lastMousePosition.y = e.originalEvent.y; - if (this.#computeDestinationRotation && this.#destinationRotationCenter != null) - this.#destinationGroupRotation = -bearing(this.#destinationRotationCenter.lat, this.#destinationRotationCenter.lng, this.getMouseCoordinates().lat, this.getMouseCoordinates().lng); - - this.#updateDestinationPreview(e); + 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()); + } } #onZoom(e: any) { @@ -497,5 +490,48 @@ export class Map extends L.Map { button.setAttribute("data-on-click-params", argument); return button; } + + #createDestinationMarkers() { + this.#resetDestinationMarkers(); + + if (getUnitsManager().getSelectedUnits({ excludeHumans: true }).length > 0 && getUnitsManager().getSelectedUnits({ excludeHumans: true }).length < 20) { + /* Create the unit destination preview markers */ + this.#destinationPreviewMarkers = getUnitsManager().getSelectedUnits({ excludeHumans: true }).map((unit: Unit) => { + var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), {interactive: false}); + marker.addTo(this); + return marker; + }) + } + } + + #resetDestinationMarkers() { + /* Remove all the destination preview markers */ + this.#destinationPreviewMarkers.forEach((marker: L.Marker) => { + this.removeLayer(marker); + }) + this.#destinationPreviewMarkers = []; + + this.#destinationGroupRotation = 0; + this.#computeDestinationRotation = false; + this.#destinationRotationCenter = null; + } + + #createTargetMarker(){ + this.#resetTargetMarker(); + this.#targetMarker.addTo(this); + } + + #resetTargetMarker() { + this.#targetMarker.setLatLng(new L.LatLng(0, 0)); + this.removeLayer(this.#targetMarker); + } + + #showCursor() { + document.getElementById(this.#ID)?.classList.remove("hidden-cursor"); + } + + #hideCursor() { + document.getElementById(this.#ID)?.classList.add("hidden-cursor"); + } } diff --git a/client/src/map/targetmarker.ts b/client/src/map/targetmarker.ts new file mode 100644 index 00000000..30232dc1 --- /dev/null +++ b/client/src/map/targetmarker.ts @@ -0,0 +1,18 @@ +import { DivIcon } from "leaflet"; +import { CustomMarker } from "./custommarker"; + +export class TargetMarker extends CustomMarker { + #interactive: boolean = false; + + 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"); + el.classList.toggle("ol-target-icon-interactive", this.#interactive) + this.getElement()?.appendChild(el); + } +} diff --git a/client/src/server/server.ts b/client/src/server/server.ts index f92a1204..01317c18 100644 --- a/client/src/server/server.ts +++ b/client/src/server/server.ts @@ -127,8 +127,8 @@ export function spawnSmoke(color: string, latlng: LatLng) { POST(data, () => { }); } -export function spawnExplosion(strength: number, latlng: LatLng) { - var command = { "strength": strength, "location": latlng }; +export function spawnExplosion(intensity: number, latlng: LatLng) { + var command = { "intensity": intensity, "location": latlng }; var data = { "explosion": command } POST(data, () => { }); } diff --git a/client/src/units/aircraftdatabase.ts b/client/src/units/aircraftdatabase.ts index f2448468..845f5aba 100644 --- a/client/src/units/aircraftdatabase.ts +++ b/client/src/units/aircraftdatabase.ts @@ -2603,7 +2603,7 @@ export class AircraftDatabase extends UnitDatabase { } ], "roles": [ - "Recon" + "Reconnaissance" ], "code": "R-60M*2", "name": "Heavy / Fox 2 / Long Range" diff --git a/client/src/units/unit.ts b/client/src/units/unit.ts index fb4ab994..0fd1d761 100644 --- a/client/src/units/unit.ts +++ b/client/src/units/unit.ts @@ -7,6 +7,8 @@ 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'; var pathIcon = new Icon({ iconUrl: '/resources/theme/images/markers/marker-icon.png', @@ -52,10 +54,12 @@ export class Unit extends CustomMarker { targetSpeedType: "GS", targetAltitude: 0, targetAltitudeType: "AGL", + targetLocation: {}, isTanker: false, isAWACS: false, onOff: true, - followRoads: false + followRoads: false, + targetID: 0 }, optionsData: { ROE: "", @@ -78,6 +82,8 @@ export class Unit extends CustomMarker { #pathPolyline: Polyline; #targetsPolylines: Polyline[]; #miniMapMarker: CircleMarker | null = null; + #targetLocationMarker: TargetMarker; + #targetLocationPolyline: Polyline; #timer: number = 0; @@ -109,6 +115,9 @@ export class Unit extends CustomMarker { this.#pathPolyline.addTo(getMap()); this.#targetsPolylines = []; + 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); @@ -152,11 +161,16 @@ export class Unit extends CustomMarker { if ((this.getBaseData().alive || !selected) && this.getSelectable() && this.getSelected() != selected) { this.#selected = selected; this.getElement()?.querySelector(`[data-object|="unit"]`)?.toggleAttribute("data-is-selected", selected); - if (selected) + if (selected) { document.dispatchEvent(new CustomEvent("unitSelection", { detail: this })); - else + this.#updateMarker(); + } + else { document.dispatchEvent(new CustomEvent("unitDeselection", { detail: this })); - this.getGroupMembers().forEach((unit: Unit) => unit.setSelected(selected)); + this.#clearDetectedUnits(); + this.#clearPath(); + this.#clearTarget(); + } } } @@ -228,13 +242,17 @@ export class Unit extends CustomMarker { if (updateMarker) this.#updateMarker(); - this.#clearTargets(); + this.#clearDetectedUnits(); if (this.getSelected()) { this.#drawPath(); - this.#drawTargets(); + this.#drawDetectedUnits(); + this.#drawTarget(); } - else + else { this.#clearPath(); + this.#clearTarget(); + } + document.dispatchEvent(new CustomEvent("unitUpdated", { detail: this })); } @@ -404,6 +422,15 @@ export class Unit extends CustomMarker { return getUnitsManager().getUnitByID(this.getFormationData().leaderID); } + canRole(roles: string | string[]) { + if (typeof(roles) === "string") + roles = [roles]; + + return this.getDatabase()?.getByName(this.getBaseData().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) { @@ -543,10 +570,9 @@ 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 (getMap().getState() === IDLE || getMap().getState() === MOVE_UNIT || e.originalEvent.ctrlKey) { + if (!e.originalEvent.ctrlKey) getUnitsManager().deselectAllUnits(); - } this.setSelected(!this.getSelected()); } } @@ -563,20 +589,35 @@ export class Unit extends CustomMarker { #onContextMenu(e: any) { 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"}; - if (getUnitsManager().getSelectedUnits().length > 0 && !(getUnitsManager().getSelectedUnits().length == 1 && (getUnitsManager().getSelectedUnits().includes(this)))) { + 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"}; if (getUnitsManager().getSelectedUnitsTypes().length == 1 && getUnitsManager().getSelectedUnitsTypes()[0] === "Aircraft") options["follow"] = {text: "Follow", tooltip: "Follow the unit at a user defined distance and position"};; } - else if ((getUnitsManager().getSelectedUnits().length > 0 && (getUnitsManager().getSelectedUnits().includes(this))) || getUnitsManager().getSelectedUnits().length == 0) { + else if ((selectedUnits.length > 0 && (selectedUnits.includes(this))) || selectedUnits.length == 0) { if (this.getBaseData().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.concat([this]).every((unit: Unit) => {return unit.canRole(["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"])})) + options["fire-at-area"] = {text: "Fire at area", tooltip: "Fire at a large area"}; + } + if (Object.keys(options).length > 0) { getMap().showUnitContextMenu(e); getMap().getUnitContextMenu().setOptions(options, (option: string) => { @@ -595,6 +636,12 @@ export class Unit extends CustomMarker { getUnitsManager().selectedUnitsRefuel(); else if (action === "follow") this.#showFollowOptions(e); + else if (action === "bomb") + getMap().setState(BOMBING); + else if (action === "carpet-bomb") + getMap().setState(CARPET_BOMBING); + else if (action === "fire-at-area") + getMap().setState(FIRE_AT_AREA); } #showFollowOptions(e: any) { @@ -679,6 +726,8 @@ export class Unit extends CustomMarker { element.querySelector(".unit")?.setAttribute("data-state", "human"); else if (!this.getBaseData().AI) // 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) + element.querySelector(".unit")?.setAttribute("data-state", "no-task"); else // Unit is under Olympus control element.querySelector(".unit")?.setAttribute("data-state", this.getTaskData().currentState.toLowerCase()); @@ -777,7 +826,7 @@ export class Unit extends CustomMarker { this.#pathPolyline.setLatLngs([]); } - #drawTargets() { + #drawDetectedUnits() { for (let index in this.getMissionData().targets) { var targetData = this.getMissionData().targets[index]; if (targetData.object != undefined){ @@ -795,7 +844,7 @@ export class Unit extends CustomMarker { color = "#00FF00"; else color = "#FFFFFF"; - var targetPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 0.4, smoothFactor: 1 }); + var targetPolyline = new Polyline([startLatLng, endLatLng], { color: color, weight: 3, opacity: 0.4, smoothFactor: 1, dashArray: "4, 8" }); targetPolyline.addTo(getMap()); this.#targetsPolylines.push(targetPolyline) } @@ -803,11 +852,48 @@ export class Unit extends CustomMarker { } } - #clearTargets() { + #clearDetectedUnits() { for (let index in this.#targetsPolylines) { getMap().removeLayer(this.#targetsPolylines[index]) } } + + #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)); + } + 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 + 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)]) + } + + #clearTarget() { + if (getMap().hasLayer(this.#targetLocationMarker)) + this.#targetLocationMarker.removeFrom(getMap()); + + if (getMap().hasLayer(this.#targetLocationPolyline)) + this.#targetLocationPolyline.removeFrom(getMap()); + } } export class AirUnit extends Unit { diff --git a/client/src/units/unitsmanager.ts b/client/src/units/unitsmanager.ts index 05f839ee..dc3fd58b 100644 --- a/client/src/units/unitsmanager.ts +++ b/client/src/units/unitsmanager.ts @@ -3,7 +3,7 @@ import { getHotgroupPanel, getInfoPopup, getMap, getUnitDataTable } from ".."; import { Unit } from "./unit"; import { cloneUnit } from "../server/server"; import { deg2rad, keyEventWasInInput, latLngToMercator, mToFt, mercatorToLatLng, msToKnots } from "../other/utils"; -import { IDLE, UNIT_SELECTED } from "../map/map"; +import { IDLE, MOVE_UNIT } from "../map/map"; export class UnitsManager { #units: { [ID: number]: Unit }; @@ -52,10 +52,12 @@ export class UnitsManager { } 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 (constructor != undefined) { - this.#units[ID] = new constructor(ID, data); + var constructor = Unit.getConstructor(data.baseData.category); + if (constructor != undefined) { + this.#units[ID] = new constructor(ID, data); + } } } @@ -498,7 +500,7 @@ export class UnitsManager { #onUnitSelection(unit: Unit) { if (this.getSelectedUnits().length > 0) { - getMap().setState(UNIT_SELECTED); + getMap().setState(MOVE_UNIT); /* Disable the firing of the selection event for a certain amount of time. This avoids firing many events if many units are selected */ if (!this.#selectionEventDisabled) { window.setTimeout(() => { diff --git a/client/views/other/contextmenus.ejs b/client/views/other/contextmenus.ejs index 77a6006b..0940f076 100644 --- a/client/views/other/contextmenus.ejs +++ b/client/views/other/contextmenus.ejs @@ -84,10 +84,10 @@
- - - - + + + +
diff --git a/scripts/OlympusCommand.lua b/scripts/OlympusCommand.lua index bde0dc53..71007d8d 100644 --- a/scripts/OlympusCommand.lua +++ b/scripts/OlympusCommand.lua @@ -140,50 +140,57 @@ function Olympus.buildTask(options) } } elseif options['id'] == 'Bombing' and options['lat'] and options['lng'] then + local point = coord.LLtoLO(options['lat'], options['lng'], 0) task = { id = 'Bombing', params = { - point = coord.LLtoLO(options['lat'], options['lng'], 0), + point = {x = point.x, y = point.z}, attackQty = 1 } } elseif options['id'] == 'CarpetBombing' and options['lat'] and options['lng'] then + local point = coord.LLtoLO(options['lat'], options['lng'], 0) task = { id = 'CarpetBombing', params = { - point = coord.LLtoLO(options['lat'], options['lng'], 0), - attackQty = 1, + x = point.x, + y = point.z, carpetLength = 1000, - attackType = 'Carpet' + attackType = 'Carpet', + expend = "All", + attackQty = 1, + attackQtyLimit = true } } elseif options['id'] == 'AttackMapObject' and options['lat'] and options['lng'] then + local point = coord.LLtoLO(options['lat'], options['lng'], 0) task = { id = 'AttackMapObject', params = { - point = coord.LLtoLO(options['lat'], options['lng'], 0), + point = {x = point.x, y = point.z} } } - end elseif options['id'] == 'FireAtPoint' and options['lat'] and options['lng'] and options['radius'] then + local point = coord.LLtoLO(options['lat'], options['lng'], 0) task = { - id = 'AttackMapObject', + id = 'FireAtPoint', params = { - point = coord.LLtoLO(options['lat'], options['lng'], 0), + point = {x = point.x, y = point.z}, radius = options['radius'] } } end + end return task end -- Move a unit. Since many tasks in DCS are Enroute tasks, this function is an important way to control the unit AI -function Olympus.move(ID, lat, lng, altitude, altitudeType, speed, speedType, category, taskOptions) - Olympus.debug("Olympus.move " .. ID .. " (" .. lat .. ", " .. lng ..") " .. altitude .. "m " .. altitudeType .. " ".. speed .. "m/s " .. category .. " " .. Olympus.serializeTable(taskOptions), 2) - local unit = Olympus.getUnitByID(ID) - if unit then +function Olympus.move(groupName, lat, lng, altitude, altitudeType, speed, speedType, category, taskOptions) + Olympus.debug("Olympus.move " .. groupName .. " (" .. lat .. ", " .. lng ..") " .. altitude .. "m " .. altitudeType .. " ".. speed .. "m/s " .. category .. " " .. Olympus.serializeTable(taskOptions), 2) + local group = Group.getByName(groupName) + if group then if category == "Aircraft" then - local startPoint = mist.getLeadPos(unit:getGroup()) + local startPoint = mist.getLeadPos(group) local endPoint = coord.LLtoLO(lat, lng, 0) if altitudeType == "AGL" then @@ -221,7 +228,6 @@ function Olympus.move(ID, lat, lng, altitude, altitudeType, speed, speedType, ca }, }, } - group = unit:getGroup() local groupCon = group:getController() if groupCon then groupCon:setTask(missionTask) @@ -230,7 +236,7 @@ function Olympus.move(ID, lat, lng, altitude, altitudeType, speed, speedType, ca elseif category == "GroundUnit" then vars = { - group = unit:getGroup(), + group = group, point = coord.LLtoLO(lat, lng, 0), heading = 0, speed = speed @@ -249,7 +255,7 @@ function Olympus.move(ID, lat, lng, altitude, altitudeType, speed, speedType, ca Olympus.debug("Olympus.move not implemented yet for " .. category, 2) end else - Olympus.debug("Error in Olympus.move " .. ID, 2) + Olympus.debug("Error in Olympus.move " .. groupName, 2) end end @@ -408,8 +414,61 @@ function Olympus.spawnAircraft(coalition, unitType, lat, lng, alt, spawnOptions) }, } end + else + route = { + ["points"] = + { + [1] = + { + ["alt"] = alt, + ["alt_type"] = "BARO", + ["task"] = + { + ["id"] = "ComboTask", + ["params"] = + { + ["tasks"] = + { + [1] = + { + ["number"] = 1, + ["auto"] = true, + ["id"] = "WrappedAction", + ["enabled"] = true, + ["params"] = + { + ["action"] = + { + ["id"] = "EPLRS", + ["params"] = + { + ["value"] = true + }, + }, + }, + }, + [2] = + { + ["number"] = 2, + ["auto"] = false, + ["id"] = "Orbit", + ["enabled"] = true, + ["params"] = + { + ["pattern"] = "Circle" + }, + }, + }, + }, + }, + ["type"] = "Turning Point", + ["x"] = spawnLocation.x, + ["y"] = spawnLocation.z, + }, -- end of [1] + }, -- end of ["points"] + } -- end of ["route"] end - + local vars = { units = unitTable, @@ -461,51 +520,51 @@ function Olympus.delete(ID, explosion) end end -function Olympus.setTask(ID, taskOptions) - Olympus.debug("Olympus.setTask " .. ID .. " " .. Olympus.serializeTable(taskOptions), 2) - local unit = Olympus.getUnitByID(ID) - if unit then +function Olympus.setTask(groupName, taskOptions) + Olympus.debug("Olympus.setTask " .. groupName .. " " .. Olympus.serializeTable(taskOptions), 2) + local group = Group.getByName(groupName) + if group then local task = Olympus.buildTask(taskOptions); Olympus.debug("Olympus.setTask " .. Olympus.serializeTable(task), 20) if task then - unit:getGroup():getController():setTask(task) + group:getController():setTask(task) Olympus.debug("Olympus.setTask completed successfully", 2) end end end -function Olympus.resetTask(ID) - Olympus.debug("Olympus.resetTask " .. ID, 2) - local unit = Olympus.getUnitByID(ID) - if unit then - unit:getGroup():getController():resetTask() +function Olympus.resetTask(groupName) + Olympus.debug("Olympus.resetTask " .. groupName, 2) + local group = Group.getByName(groupName) + if group then + group:getController():resetTask() Olympus.debug("Olympus.resetTask completed successfully", 2) end end -function Olympus.setCommand(ID, command) - Olympus.debug("Olympus.setCommand " .. ID .. " " .. Olympus.serializeTable(command), 2) - local unit = Olympus.getUnitByID(ID) - if unit then - unit:getGroup():getController():setCommand(command) +function Olympus.setCommand(groupName, command) + Olympus.debug("Olympus.setCommand " .. groupName .. " " .. Olympus.serializeTable(command), 2) + local group = Group.getByName(groupName) + if group then + group:getController():setCommand(command) Olympus.debug("Olympus.setCommand completed successfully", 2) end end -function Olympus.setOption(ID, optionID, optionValue) - Olympus.debug("Olympus.setOption " .. ID .. " " .. optionID .. " " .. tostring(optionValue), 2) - local unit = Olympus.getUnitByID(ID) - if unit then - unit:getGroup():getController():setOption(optionID, optionValue) +function Olympus.setOption(groupName, optionID, optionValue) + Olympus.debug("Olympus.setOption " .. groupName .. " " .. optionID .. " " .. tostring(optionValue), 2) + local group = Group.getByName(groupName) + if group then + group:getController():setOption(optionID, optionValue) Olympus.debug("Olympus.setOption completed successfully", 2) end end -function Olympus.setOnOff(ID, onOff) - Olympus.debug("Olympus.setOnOff " .. ID .. " " .. tostring(onOff), 2) - local unit = Olympus.getUnitByID(ID) - if unit then - unit:getGroup():getController():setOnOff(onOff) +function Olympus.setOnOff(groupName, onOff) + Olympus.debug("Olympus.setOnOff " .. groupName .. " " .. tostring(onOff), 2) + local group = Group.getByName(groupName) + if group then + group:getController():setOnOff(onOff) Olympus.debug("Olympus.setOnOff completed successfully", 2) end end diff --git a/src/core/include/commands.h b/src/core/include/commands.h index b244bc58..d4e0bab3 100644 --- a/src/core/include/commands.h +++ b/src/core/include/commands.h @@ -97,14 +97,15 @@ protected: class Move : public Command { public: - Move(int ID, Coords destination, double speed, wstring speedType, double altitude, wstring altitudeType, wstring taskOptions): - ID(ID), + Move(wstring groupName, Coords destination, double speed, wstring speedType, double altitude, wstring altitudeType, wstring taskOptions, wstring category): + groupName(groupName), destination(destination), speed(speed), speedType(speedType), altitude(altitude), altitudeType(altitudeType), - taskOptions(taskOptions) + taskOptions(taskOptions), + category(category) { priority = CommandPriority::HIGH; }; @@ -112,13 +113,14 @@ public: virtual int getLoad() { return 5; } private: - const int ID; + const wstring groupName; const Coords destination; const double speed; const wstring speedType; const double altitude; const wstring altitudeType; const wstring taskOptions; + const wstring category; }; /* Smoke command */ @@ -223,8 +225,8 @@ private: class SetTask : public Command { public: - SetTask(int ID, wstring task) : - ID(ID), + SetTask(wstring groupName, wstring task) : + groupName(groupName), task(task) { priority = CommandPriority::MEDIUM; @@ -233,7 +235,7 @@ public: virtual int getLoad() { return 10; } private: - const int ID; + const wstring groupName; const wstring task; }; @@ -241,8 +243,8 @@ private: class ResetTask : public Command { public: - ResetTask(int ID) : - ID(ID) + ResetTask(wstring groupName) : + groupName(groupName) { priority = CommandPriority::HIGH; }; @@ -250,15 +252,15 @@ public: virtual int getLoad() { return 10; } private: - const int ID; + const wstring groupName; }; /* Set command */ class SetCommand : public Command { public: - SetCommand(int ID, wstring command) : - ID(ID), + SetCommand(wstring groupName, wstring command) : + groupName(groupName), command(command) { priority = CommandPriority::HIGH; @@ -267,7 +269,7 @@ public: virtual int getLoad() { return 10; } private: - const int ID; + const wstring groupName; const wstring command; }; @@ -275,8 +277,8 @@ private: class SetOption : public Command { public: - SetOption(int ID, int optionID, int optionValue) : - ID(ID), + SetOption(wstring groupName, int optionID, int optionValue) : + groupName(groupName), optionID(optionID), optionValue(optionValue), optionBool(false), @@ -285,8 +287,8 @@ public: priority = CommandPriority::HIGH; }; - SetOption(int ID, int optionID, bool optionBool) : - ID(ID), + SetOption(wstring groupName, int optionID, bool optionBool) : + groupName(groupName), optionID(optionID), optionValue(0), optionBool(optionBool), @@ -298,7 +300,7 @@ public: virtual int getLoad() { return 10; } private: - const int ID; + const wstring groupName; const int optionID; const int optionValue; const bool optionBool; @@ -309,8 +311,8 @@ private: class SetOnOff : public Command { public: - SetOnOff(int ID, bool onOff) : - ID(ID), + SetOnOff(wstring groupName, bool onOff) : + groupName(groupName), onOff(onOff) { priority = CommandPriority::HIGH; @@ -319,7 +321,7 @@ public: virtual int getLoad() { return 10; } private: - const int ID; + const wstring groupName; const bool onOff; }; diff --git a/src/core/include/unit.h b/src/core/include/unit.h index 13e47945..5d6dafb3 100644 --- a/src/core/include/unit.h +++ b/src/core/include/unit.h @@ -6,6 +6,8 @@ #include "measure.h" #include "logger.h" +#define TASK_CHECK_INIT_VALUE 10 + namespace State { enum States @@ -63,7 +65,7 @@ public: int getID() { return ID; } void updateExportData(json::value json); void updateMissionData(json::value json); - json::value getData(long long time); + json::value getData(long long time, bool getAll = false); virtual wstring getCategory() { return L"No category"; }; /********** Base data **********/ @@ -100,7 +102,7 @@ public: 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 setTargets(json::value newTargets) {targets = newTargets; addMeasure(L"targets", json::value(newTargets));} - void setHasTask(bool newHasTask) { hasTask = newHasTask; addMeasure(L"hasTask", json::value(newHasTask)); } + void setHasTask(bool newHasTask); void setCoalitionID(int newCoalitionID); void setFlags(json::value newFlags) { flags = newFlags; addMeasure(L"flags", json::value(newFlags));} @@ -181,6 +183,7 @@ protected: int ID; map measures; + int taskCheckCounter = 0; /********** Base data **********/ bool AI = false; @@ -252,4 +255,6 @@ protected: bool setActiveDestination(); bool updateActivePath(bool looping); void goToDestination(wstring enrouteTask = L"nil"); + bool checkTaskFailed(); + void resetTaskFailedCounter(); }; diff --git a/src/core/include/unitsmanager.h b/src/core/include/unitsmanager.h index 5dfc1dfb..d3d5d65b 100644 --- a/src/core/include/unitsmanager.h +++ b/src/core/include/unitsmanager.h @@ -11,6 +11,11 @@ public: ~UnitsManager(); 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 updateMissionData(json::value missionData); void getData(json::value& answer, long long time); diff --git a/src/core/src/airunit.cpp b/src/core/src/airunit.cpp index 4cb7c798..331e3ff8 100644 --- a/src/core/src/airunit.cpp +++ b/src/core/src/airunit.cpp @@ -143,7 +143,7 @@ void AirUnit::AIloop() else { taskSS << "{ id = 'Orbit', pattern = 'Circle' }"; } - Command* command = dynamic_cast(new SetTask(ID, taskSS.str())); + Command* command = dynamic_cast(new SetTask(groupName, taskSS.str())); scheduler->appendCommand(command); setHasTask(true); } @@ -248,7 +248,7 @@ void AirUnit::AIloop() << "z = " << formationOffset.z << "}," << "}"; - Command* command = dynamic_cast(new SetTask(ID, taskSS.str())); + Command* command = dynamic_cast(new SetTask(groupName, taskSS.str())); scheduler->appendCommand(command); setHasTask(true); } @@ -264,7 +264,7 @@ void AirUnit::AIloop() taskSS << "{" << "id = 'Refuel'" << "}"; - Command* command = dynamic_cast(new SetTask(ID, taskSS.str())); + Command* command = dynamic_cast(new SetTask(groupName, taskSS.str())); scheduler->appendCommand(command); setHasTask(true); } @@ -279,7 +279,7 @@ void AirUnit::AIloop() if (!getHasTask()) { std::wostringstream taskSS; taskSS << "{id = 'Bombing', lat = " << targetLocation.lat << ", lng = " << targetLocation.lng << "}"; - Command* command = dynamic_cast(new SetTask(ID, taskSS.str())); + Command* command = dynamic_cast(new SetTask(groupName, taskSS.str())); scheduler->appendCommand(command); setHasTask(true); } @@ -290,10 +290,11 @@ void AirUnit::AIloop() if (!getHasTask()) { std::wostringstream taskSS; taskSS << "{id = 'CarpetBombing', lat = " << targetLocation.lat << ", lng = " << targetLocation.lng << "}"; - Command* command = dynamic_cast(new SetTask(ID, taskSS.str())); + Command* command = dynamic_cast(new SetTask(groupName, taskSS.str())); scheduler->appendCommand(command); setHasTask(true); } + break; } case State::BOMB_BUILDING: { currentTask = L"Bombing building"; @@ -301,14 +302,16 @@ void AirUnit::AIloop() if (!getHasTask()) { std::wostringstream taskSS; taskSS << "{id = 'AttackMapObject', lat = " << targetLocation.lat << ", lng = " << targetLocation.lng << "}"; - Command* command = dynamic_cast(new SetTask(ID, taskSS.str())); + Command* command = dynamic_cast(new SetTask(groupName, taskSS.str())); scheduler->appendCommand(command); setHasTask(true); } + break; } 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 279a9700..87f33218 100644 --- a/src/core/src/commands.cpp +++ b/src/core/src/commands.cpp @@ -9,27 +9,20 @@ extern UnitsManager* unitsManager; /* Move command */ wstring Move::getString(lua_State* L) { - Unit* unit = unitsManager->getUnit(ID); - if (unit != nullptr) - { - std::wostringstream commandSS; - commandSS.precision(10); - commandSS << "Olympus.move, " - << ID << ", " - << destination.lat << ", " - << destination.lng << ", " - << altitude << ", " - << "\"" << altitudeType << "\"" << ", " - << speed << ", " - << "\"" << speedType << "\"" << ", " - << "\"" << unit->getCategory() << "\"" << ", " - << taskOptions; - return commandSS.str(); - } - else - { - return L""; - } + + std::wostringstream commandSS; + commandSS.precision(10); + commandSS << "Olympus.move, " + << "\"" << groupName << "\"" << ", " + << destination.lat << ", " + << destination.lng << ", " + << altitude << ", " + << "\"" << altitudeType << "\"" << ", " + << speed << ", " + << "\"" << speedType << "\"" << ", " + << "\"" << category << "\"" << ", " + << taskOptions; + return commandSS.str(); } /* Smoke command */ @@ -117,7 +110,7 @@ wstring SetTask::getString(lua_State* L) std::wostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.setTask, " - << ID << ", " + << "\"" << groupName << "\"" << ", " << task; return commandSS.str(); @@ -129,7 +122,7 @@ wstring ResetTask::getString(lua_State* L) std::wostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.resetTask, " - << ID; + << "\"" << groupName << "\""; return commandSS.str(); } @@ -140,7 +133,7 @@ wstring SetCommand::getString(lua_State* L) std::wostringstream commandSS; commandSS.precision(10); commandSS << "Olympus.setCommand, " - << ID << ", " + << "\"" << groupName << "\"" << ", " << command; return commandSS.str(); @@ -154,12 +147,12 @@ wstring SetOption::getString(lua_State* L) if (!isBoolean) { commandSS << "Olympus.setOption, " - << ID << ", " + << "\"" << groupName << "\"" << ", " << optionID << ", " << optionValue; } else { commandSS << "Olympus.setOption, " - << ID << ", " + << "\"" << groupName << "\"" << ", " << optionID << ", " << (optionBool? "true": "false"); } @@ -173,7 +166,7 @@ wstring SetOnOff::getString(lua_State* L) commandSS.precision(10); commandSS << "Olympus.setOnOff, " - << ID << ", " + << "\"" << groupName << "\"" << ", " << (onOff ? "true" : "false"); return commandSS.str(); diff --git a/src/core/src/groundunit.cpp b/src/core/src/groundunit.cpp index 7de80307..c75ea2c0 100644 --- a/src/core/src/groundunit.cpp +++ b/src/core/src/groundunit.cpp @@ -73,7 +73,6 @@ void GroundUnit::setState(int newState) void GroundUnit::AIloop() { - switch (state) { case State::IDLE: { currentTask = L"Idle"; @@ -112,7 +111,7 @@ void GroundUnit::AIloop() if (!getHasTask()) { std::wostringstream taskSS; taskSS << "{id = 'FireAtPoint', lat = " << targetLocation.lat << ", lng = " << targetLocation.lng << ", radius = 1000}"; - Command* command = dynamic_cast(new SetTask(ID, taskSS.str())); + Command* command = dynamic_cast(new SetTask(groupName, taskSS.str())); scheduler->appendCommand(command); setHasTask(true); } @@ -140,7 +139,7 @@ void GroundUnit::changeSpeed(wstring change) void GroundUnit::setOnOff(bool newOnOff) { Unit::setOnOff(newOnOff); - Command* command = dynamic_cast(new SetOnOff(ID, onOff)); + Command* command = dynamic_cast(new SetOnOff(groupName, onOff)); scheduler->appendCommand(command); } diff --git a/src/core/src/scheduler.cpp b/src/core/src/scheduler.cpp index d15a1947..d6fc9f51 100644 --- a/src/core/src/scheduler.cpp +++ b/src/core/src/scheduler.cpp @@ -59,7 +59,8 @@ void Scheduler::handleRequest(wstring key, json::value value) if (key.compare(L"setPath") == 0) { int ID = value[L"ID"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); + Unit* unit = unitsManager->getGroupLeader(ID); + if (unit != nullptr) { wstring unitName = unit->getUnitName(); @@ -75,15 +76,9 @@ void Scheduler::handleRequest(wstring key, json::value value) newPath.push_back(dest); } - Unit* unit = unitsManager->getUnit(ID); - if (unit != nullptr) - { - unit->setActivePath(newPath); - unit->setState(State::REACH_DESTINATION); - log(unitName + L" new path set successfully"); - } - else - log(unitName + L" not found, request will be discarded"); + unit->setActivePath(newPath); + unit->setState(State::REACH_DESTINATION); + log(unitName + L" new path set successfully"); } } else if (key.compare(L"smoke") == 0) @@ -123,7 +118,7 @@ void Scheduler::handleRequest(wstring key, json::value value) int ID = value[L"ID"].as_integer(); int targetID = value[L"targetID"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); + Unit* unit = unitsManager->getGroupLeader(ID); Unit* target = unitsManager->getUnit(targetID); wstring unitName; @@ -151,7 +146,7 @@ void Scheduler::handleRequest(wstring key, json::value value) int offsetY = value[L"offsetY"].as_integer(); int offsetZ = value[L"offsetZ"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); + Unit* unit = unitsManager->getGroupLeader(ID); Unit* leader = unitsManager->getUnit(leaderID); wstring unitName; @@ -175,42 +170,42 @@ void Scheduler::handleRequest(wstring key, json::value value) else if (key.compare(L"changeSpeed") == 0) { int ID = value[L"ID"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); + Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) unit->changeSpeed(value[L"change"].as_string()); } else if (key.compare(L"changeAltitude") == 0) { int ID = value[L"ID"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); + Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) unit->changeAltitude(value[L"change"].as_string()); } else if (key.compare(L"setSpeed") == 0) { int ID = value[L"ID"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); + Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) unit->setTargetSpeed(value[L"speed"].as_double()); } else if (key.compare(L"setSpeedType") == 0) { int ID = value[L"ID"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); + Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) unit->setTargetSpeedType(value[L"speedType"].as_string()); } else if (key.compare(L"setAltitude") == 0) { int ID = value[L"ID"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); + Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) unit->setTargetAltitude(value[L"altitude"].as_double()); } else if (key.compare(L"setAltitudeType") == 0) { int ID = value[L"ID"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); + Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) unit->setTargetAltitudeType(value[L"altitudeType"].as_string()); } @@ -226,28 +221,28 @@ void Scheduler::handleRequest(wstring key, json::value value) else if (key.compare(L"setROE") == 0) { int ID = value[L"ID"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); + Unit* unit = unitsManager->getGroupLeader(ID); wstring ROE = value[L"ROE"].as_string(); unit->setROE(ROE); } else if (key.compare(L"setReactionToThreat") == 0) { int ID = value[L"ID"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); + Unit* unit = unitsManager->getGroupLeader(ID); wstring reactionToThreat = value[L"reactionToThreat"].as_string(); unit->setReactionToThreat(reactionToThreat); } else if (key.compare(L"setEmissionsCountermeasures") == 0) { int ID = value[L"ID"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); + Unit* unit = unitsManager->getGroupLeader(ID); wstring emissionsCountermeasures = value[L"emissionsCountermeasures"].as_string(); unit->setEmissionsCountermeasures(emissionsCountermeasures); } else if (key.compare(L"landAt") == 0) { int ID = value[L"ID"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); + Unit* unit = unitsManager->getGroupLeader(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; @@ -262,13 +257,13 @@ void Scheduler::handleRequest(wstring key, json::value value) else if (key.compare(L"refuel") == 0) { int ID = value[L"ID"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); + Unit* unit = unitsManager->getGroupLeader(ID); unit->setState(State::REFUEL); } else if (key.compare(L"setAdvancedOptions") == 0) { int ID = value[L"ID"].as_integer(); - Unit* unit = unitsManager->getUnit(ID); + Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) { /* Advanced tasking */ @@ -306,14 +301,14 @@ void Scheduler::handleRequest(wstring key, json::value value) { int ID = value[L"ID"].as_integer(); bool followRoads = value[L"followRoads"].as_bool(); - Unit* unit = unitsManager->getUnit(ID); + Unit* unit = unitsManager->getGroupLeader(ID); unit->setFollowRoads(followRoads); } else if (key.compare(L"setOnOff") == 0) { int ID = value[L"ID"].as_integer(); bool onOff = value[L"onOff"].as_bool(); - Unit* unit = unitsManager->getUnit(ID); + Unit* unit = unitsManager->getGroupLeader(ID); unit->setOnOff(onOff); } else if (key.compare(L"explosion") == 0) @@ -331,8 +326,9 @@ void Scheduler::handleRequest(wstring key, json::value value) 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->getUnit(ID); + Unit* unit = unitsManager->getGroupLeader(ID); unit->setState(State::BOMB_POINT); + unit->setTargetLocation(loc); } else if (key.compare(L"carpetBomb") == 0) { @@ -340,8 +336,9 @@ void Scheduler::handleRequest(wstring key, json::value value) 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->getUnit(ID); + Unit* unit = unitsManager->getGroupLeader(ID); unit->setState(State::CARPET_BOMB); + unit->setTargetLocation(loc); } else if (key.compare(L"bombBuilding") == 0) { @@ -349,8 +346,9 @@ void Scheduler::handleRequest(wstring key, json::value value) 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->getUnit(ID); + Unit* unit = unitsManager->getGroupLeader(ID); unit->setState(State::BOMB_BUILDING); + unit->setTargetLocation(loc); } else if (key.compare(L"fireAtArea") == 0) { @@ -358,8 +356,9 @@ void Scheduler::handleRequest(wstring key, json::value value) 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->getUnit(ID); + Unit* unit = unitsManager->getGroupLeader(ID); unit->setState(State::FIRE_AT_AREA); + unit->setTargetLocation(loc); } else { diff --git a/src/core/src/unit.cpp b/src/core/src/unit.cpp index 160a176e..b0a86a73 100644 --- a/src/core/src/unit.cpp +++ b/src/core/src/unit.cpp @@ -116,8 +116,21 @@ void Unit::updateExportData(json::value json) setAI(getUnitName().find(L"Olympus") != wstring::npos); /* 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) */ - if (getAI() && getAlive() && getFlags()[L"Human"].as_bool() == false) + // TODO at the moment groups will stop moving correctly if the leader dies + const bool isUnitControlledByOlympus = getAI(); + 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(); + + // 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); + AIloop(); + } } void Unit::updateMissionData(json::value json) @@ -132,10 +145,14 @@ void Unit::updateMissionData(json::value json) setHasTask(json[L"hasTask"].as_bool()); } -json::value Unit::getData(long long time) +json::value Unit::getData(long long time, bool sendAll) { auto json = json::value::object(); + /* 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); + /********** Base data **********/ json[L"baseData"] = json::value::object(); for (auto key : { L"AI", L"name", L"unitName", L"groupName", L"alive", L"category"}) @@ -146,7 +163,7 @@ json::value Unit::getData(long long time) if (json[L"baseData"].size() == 0) json.erase(L"baseData"); - if (alive) { + if (alive || sendAll) { /********** Flight data **********/ json[L"flightData"] = json::value::object(); for (auto key : { L"latitude", L"longitude", L"altitude", L"speed", L"heading" }) @@ -177,25 +194,28 @@ json::value Unit::getData(long long time) if (json[L"formationData"].size() == 0) json.erase(L"formationData"); - /********** Task data **********/ - json[L"taskData"] = json::value::object(); - for (auto key : { L"currentState", L"currentTask", L"targetSpeed", L"targetAltitude", L"targetSpeedType", L"targetAltitudeType", 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"); + /* 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"targetSpeed", L"targetAltitude", L"targetSpeedType", L"targetAltitudeType", 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(); + /********** 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 (json[L"optionsData"].size() == 0) - json.erase(L"optionsData"); } return json; @@ -322,9 +342,10 @@ void Unit::resetActiveDestination() void Unit::resetTask() { - Command* command = dynamic_cast(new ResetTask(ID)); + Command* command = dynamic_cast(new ResetTask(groupName)); scheduler->appendCommand(command); setHasTask(false); + resetTaskFailedCounter(); } void Unit::setFormationOffset(Offset newFormationOffset) @@ -353,7 +374,7 @@ void Unit::setROE(wstring newROE) { else return; - Command* command = dynamic_cast(new SetOption(ID, SetCommandType::ROE, ROEEnum)); + Command* command = dynamic_cast(new SetOption(groupName, SetCommandType::ROE, ROEEnum)); scheduler->appendCommand(command); } } @@ -378,7 +399,7 @@ void Unit::setReactionToThreat(wstring newReactionToThreat) { else return; - Command* command = dynamic_cast(new SetOption(ID, SetCommandType::REACTION_ON_THREAT, reactionToThreatEnum)); + Command* command = dynamic_cast(new SetOption(groupName, SetCommandType::REACTION_ON_THREAT, reactionToThreatEnum)); scheduler->appendCommand(command); } } @@ -421,13 +442,13 @@ void Unit::setEmissionsCountermeasures(wstring newEmissionsCountermeasures) { Command* command; - command = dynamic_cast(new SetOption(ID, SetCommandType::RADAR_USING, radarEnum)); + command = dynamic_cast(new SetOption(groupName, SetCommandType::RADAR_USING, radarEnum)); scheduler->appendCommand(command); - command = dynamic_cast(new SetOption(ID, SetCommandType::FLARE_USING, flareEnum)); + command = dynamic_cast(new SetOption(groupName, SetCommandType::FLARE_USING, flareEnum)); scheduler->appendCommand(command); - command = dynamic_cast(new SetOption(ID, SetCommandType::ECM_USING, ECMEnum)); + command = dynamic_cast(new SetOption(groupName, SetCommandType::ECM_USING, ECMEnum)); scheduler->appendCommand(command); } } @@ -474,7 +495,7 @@ void Unit::setTACAN(Options::TACAN newTACAN) { << "frequency = " << TACANChannelToFrequency(TACAN.channel, TACAN.XY) << "," << "}" << "}"; - Command* command = dynamic_cast(new SetCommand(ID, commandSS.str())); + Command* command = dynamic_cast(new SetCommand(groupName, commandSS.str())); scheduler->appendCommand(command); } else { @@ -484,7 +505,7 @@ void Unit::setTACAN(Options::TACAN newTACAN) { << "params = {" << "}" << "}"; - Command* command = dynamic_cast(new SetCommand(ID, commandSS.str())); + Command* command = dynamic_cast(new SetCommand(groupName, commandSS.str())); scheduler->appendCommand(command); } } @@ -512,7 +533,7 @@ void Unit::setRadio(Options::Radio newRadio) { << "frequency = " << radio.frequency << "," << "}" << "}"; - command = dynamic_cast(new SetCommand(ID, commandSS.str())); + command = dynamic_cast(new SetCommand(groupName, commandSS.str())); scheduler->appendCommand(command); // Clear the stringstream @@ -525,7 +546,7 @@ void Unit::setRadio(Options::Radio newRadio) { << "number = " << radio.callsignNumber << "," << "}" << "}"; - command = dynamic_cast(new SetCommand(ID, commandSS.str())); + command = dynamic_cast(new SetCommand(groupName, commandSS.str())); scheduler->appendCommand(command); } } @@ -564,15 +585,15 @@ void Unit::setGeneralSettings(Options::GeneralSettings newGeneralSettings) { generalSettings = newGeneralSettings; Command* command; - command = dynamic_cast(new SetOption(ID, SetCommandType::PROHIBIT_AA, generalSettings.prohibitAA)); + command = dynamic_cast(new SetOption(groupName, SetCommandType::PROHIBIT_AA, generalSettings.prohibitAA)); scheduler->appendCommand(command); - command = dynamic_cast(new SetOption(ID, SetCommandType::PROHIBIT_AG, generalSettings.prohibitAG)); + command = dynamic_cast(new SetOption(groupName, SetCommandType::PROHIBIT_AG, generalSettings.prohibitAG)); scheduler->appendCommand(command); - command = dynamic_cast(new SetOption(ID, SetCommandType::PROHIBIT_JETT, generalSettings.prohibitJettison)); + command = dynamic_cast(new SetOption(groupName, SetCommandType::PROHIBIT_JETT, generalSettings.prohibitJettison)); scheduler->appendCommand(command); - command = dynamic_cast(new SetOption(ID, SetCommandType::PROHIBIT_AB, generalSettings.prohibitAfterburner)); + command = dynamic_cast(new SetOption(groupName, SetCommandType::PROHIBIT_AB, generalSettings.prohibitAfterburner)); scheduler->appendCommand(command); - command = dynamic_cast(new SetOption(ID, SetCommandType::ENGAGE_AIR_WEAPONS, !generalSettings.prohibitAirWpn)); + command = dynamic_cast(new SetOption(groupName, SetCommandType::ENGAGE_AIR_WEAPONS, !generalSettings.prohibitAirWpn)); scheduler->appendCommand(command); } } @@ -605,7 +626,7 @@ void Unit::goToDestination(wstring enrouteTask) { if (activeDestination != NULL) { - Command* command = dynamic_cast(new Move(ID, activeDestination, getTargetSpeed(), getTargetSpeedType(), getTargetAltitude(), getTargetAltitudeType(), enrouteTask)); + Command* command = dynamic_cast(new Move(groupName, activeDestination, getTargetSpeed(), getTargetSpeedType(), getTargetAltitude(), getTargetAltitudeType(), enrouteTask, getCategory())); scheduler->appendCommand(command); setHasTask(true); } @@ -615,16 +636,20 @@ bool Unit::isDestinationReached(double threshold) { if (activeDestination != NULL) { - double dist = 0; - Geodesic::WGS84().Inverse(latitude, longitude, activeDestination.lat, activeDestination.lng, dist); - if (dist < threshold) + /* Check if any unit in the group has reached the point */ + for (auto const& p: unitsManager->getGroupMembers(groupName)) { - log(unitName + L" destination reached"); - return true; - } - else { - return false; - } + double dist = 0; + Geodesic::WGS84().Inverse(p->getLatitude(), p->getLongitude(), activeDestination.lat, activeDestination.lng, dist); + if (dist < threshold) + { + log(unitName + L" destination reached"); + return true; + } + else { + return false; + } + } } else return true; @@ -668,4 +693,23 @@ void Unit::setTargetLocation(Coords newTargetLocation) { json[L"latitude"] = json::value(newTargetLocation.lat); json[L"longitude"] = json::value(newTargetLocation.lng); addMeasure(L"targetLocation", json::value(json)); +} + +bool Unit::checkTaskFailed() { + if (getHasTask()) + return false; + else { + if (taskCheckCounter > 0) + taskCheckCounter--; + return taskCheckCounter == 0; + } +} + +void Unit::resetTaskFailedCounter() { + taskCheckCounter = TASK_CHECK_INIT_VALUE; +} + +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 d6bacc2e..b5ec63be 100644 --- a/src/core/src/unitsmanager.cpp +++ b/src/core/src/unitsmanager.cpp @@ -32,6 +32,67 @@ Unit* UnitsManager::getUnit(int ID) } } +bool UnitsManager::isUnitInGroup(Unit* unit) +{ + if (unit != nullptr) { + wstring groupName = unit->getGroupName(); + for (auto const& p : units) + { + if (p.second->getGroupName().compare(groupName) == 0 && p.second != unit) + return true; + } + } + return false; +} + +bool UnitsManager::isUnitGroupLeader(Unit* unit) +{ + if (unit != nullptr) + return unit == getGroupLeader(unit); + else + return false; +} + +// The group leader is the unit with the lowest ID that is part of the group. This is different from DCS's concept of leader, which will change if the leader is destroyed +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) + { + Unit* tempUnit = getUnit(tempID); + if (tempUnit != nullptr && tempUnit->getGroupName().compare(groupName) == 0) + return tempUnit; + } + } + return nullptr; +} + +vector UnitsManager::getGroupMembers(wstring groupName) +{ + vector members; + for (auto const& p : units) + { + if (p.second->getGroupName().compare(groupName) == 0) + members.push_back(p.second); + } + return members; +} + +Unit* UnitsManager::getGroupLeader(int ID) +{ + Unit* unit = getUnit(ID); + return getGroupLeader(unit); +} + void UnitsManager::updateExportData(lua_State* L) { map unitJSONs = getAllUnits(L);