diff --git a/client/public/stylesheets/style/style.css b/client/public/stylesheets/style/style.css index e78dfc8a..abee50b0 100644 --- a/client/public/stylesheets/style/style.css +++ b/client/public/stylesheets/style/style.css @@ -605,6 +605,12 @@ nav.ol-panel> :last-child { align-items: center; } +.ol-navbar-buttons-group > div { + align-items: center; + display:flex; + flex-direction: row; +} + .ol-navbar-buttons-group button { border: none; height: 32px; @@ -638,6 +644,47 @@ nav.ol-panel> :last-child { stroke: var(--background-steel) !important; } +.ol-navbar-buttons-group .protectable button:first-of-type { + border-bottom-right-radius: 0; + border-top-right-radius: 0; + width:28px; +} + +.ol-navbar-buttons-group > .protectable > button.lock { + align-items: center; + background-color: var(--primary-red); + border-bottom-left-radius: 0; + border-top-left-radius: 0; + display:flex; + justify-content: center; + width:18px; +} + +.ol-navbar-buttons-group > .protectable > button[data-protected].lock { + background-color: var(--background-grey); +} + +.ol-navbar-buttons-group > .protectable > button.lock svg { + height:10px; + width:10px; +} + +.ol-navbar-buttons-group > .protectable > button.lock svg.locked { + filter:invert(100); +} + +.ol-navbar-buttons-group > .protectable > button:not([data-protected]).lock svg.unlocked, +.ol-navbar-buttons-group > .protectable > button[data-protected].lock svg.locked { + display:flex; +} + +.ol-navbar-buttons-group > .protectable > button[data-protected].lock svg.unlocked, +.ol-navbar-buttons-group > .protectable > button:not([data-protected]).lock svg.locked { + display:none; +} + + + #roe-buttons-container button, #reaction-to-threat-buttons-container button, #emissions-countermeasures-buttons-container button, @@ -707,7 +754,6 @@ nav.ol-panel> :last-child { @media (min-width: 1700px) { #splash-screen { - background-image: url("/resources/theme/images/splash/1.png"); background-position: 100% 50%; background-size: contain; } @@ -739,7 +785,7 @@ nav.ol-panel> :last-child { top: 0; transform: rotate(-23deg); transform-origin: top right; - width: 200px; + width: 300px; z-index: -1; } diff --git a/client/public/themes/olympus/images/buttons/other/lock-open-solid.svg b/client/public/themes/olympus/images/buttons/other/lock-open-solid.svg new file mode 100644 index 00000000..444c234e --- /dev/null +++ b/client/public/themes/olympus/images/buttons/other/lock-open-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/themes/olympus/images/buttons/other/lock-solid.svg b/client/public/themes/olympus/images/buttons/other/lock-solid.svg new file mode 100644 index 00000000..fe66baea --- /dev/null +++ b/client/public/themes/olympus/images/buttons/other/lock-solid.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/themes/olympus/images/splash/1.jpg b/client/public/themes/olympus/images/splash/1.jpg new file mode 100644 index 00000000..93121bcb Binary files /dev/null and b/client/public/themes/olympus/images/splash/1.jpg differ diff --git a/client/public/themes/olympus/images/splash/2.jpg b/client/public/themes/olympus/images/splash/2.jpg new file mode 100644 index 00000000..e211ee22 Binary files /dev/null and b/client/public/themes/olympus/images/splash/2.jpg differ diff --git a/client/public/themes/olympus/images/splash/3.jpg b/client/public/themes/olympus/images/splash/3.jpg new file mode 100644 index 00000000..890f186b Binary files /dev/null and b/client/public/themes/olympus/images/splash/3.jpg differ diff --git a/client/public/themes/olympus/images/splash/4.jpg b/client/public/themes/olympus/images/splash/4.jpg new file mode 100644 index 00000000..d8775404 Binary files /dev/null and b/client/public/themes/olympus/images/splash/4.jpg differ diff --git a/client/public/themes/olympus/images/splash/5.jpg b/client/public/themes/olympus/images/splash/5.jpg new file mode 100644 index 00000000..a4e0c084 Binary files /dev/null and b/client/public/themes/olympus/images/splash/5.jpg differ diff --git a/client/public/themes/olympus/images/splash/6.jpg b/client/public/themes/olympus/images/splash/6.jpg new file mode 100644 index 00000000..5b5e3498 Binary files /dev/null and b/client/public/themes/olympus/images/splash/6.jpg differ diff --git a/client/public/themes/olympus/images/splash/7.jpg b/client/public/themes/olympus/images/splash/7.jpg new file mode 100644 index 00000000..bee73fda Binary files /dev/null and b/client/public/themes/olympus/images/splash/7.jpg differ diff --git a/client/public/themes/olympus/images/splash/8.jpg b/client/public/themes/olympus/images/splash/8.jpg new file mode 100644 index 00000000..eafdf2a1 Binary files /dev/null and b/client/public/themes/olympus/images/splash/8.jpg differ diff --git a/client/src/constants/constants.ts b/client/src/constants/constants.ts index 98fc38b4..8eb1663f 100644 --- a/client/src/constants/constants.ts +++ b/client/src/constants/constants.ts @@ -1,4 +1,5 @@ import { LatLng, LatLngBounds } from "leaflet"; +import { MapMarkerControl } from "../map/map"; export const UNITS_URI = "units"; export const WEAPONS_URI = "weapons"; @@ -117,25 +118,25 @@ export const mapLayers = { "ArcGIS Satellite": { urlTemplate: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}", minZoom: 1, - maxZoom: 16, + maxZoom: 18, attribution: "Tiles © Esri — Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, GetApp().getMap()ping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community" }, "USGS Topo": { urlTemplate: 'https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}', minZoom: 1, - maxZoom: 16, + maxZoom: 18, attribution: 'Tiles courtesy of the U.S. Geological Survey' }, "OpenStreetMap Mapnik": { urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', minZoom: 1, - maxZoom: 16, + maxZoom: 18, attribution: '© OpenStreetMap contributors' }, "OPENVKarte": { urlTemplate: 'https://tileserver.memomaps.de/tilegen/{z}/{x}/{y}.png', minZoom: 1, - maxZoom: 16, + maxZoom: 18, attribution: 'Map memomaps.de CC-BY-SA, map data © OpenStreetMap contributors' }, "Esri.DeLorme": { @@ -147,7 +148,7 @@ export const mapLayers = { "CyclOSM": { urlTemplate: 'https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png', minZoom: 1, - maxZoom: 16, + maxZoom: 18, attribution: 'CyclOSM | Map data: © OpenStreetMap contributors' } } @@ -159,6 +160,49 @@ export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area"; export const visibilityControls: string[] = ["human", "dcs", "aircraft", "helicopter", "groundunit-sam", "groundunit-other", "navyunit", "airbase"]; export const visibilityControlsTypes: string[][] = [["human"], ["dcs"], ["aircraft"], ["helicopter"], ["groundunit-sam", "groundunit-sam-radar", "groundunit-sam-launcher"], ["groundunit-other", "groundunit-ewr"], ["navyunit"], ["airbase"]]; export const visibilityControlsTooltips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle helicopter visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"]; +export const MAP_MARKER_CONTROLS:MapMarkerControl[] = [{ + "name":"Human", + "image": "visibility/human.svg", + "toggles": [ "human" ], + "tooltip": "Toggle human players' visibility" +}, { + "image": "visibility/dcs.svg", + "isProtected": true, + "name":"DCS", + "protectable": true, + "toggles": [ "dcs" ], + "tooltip": "Toggle DCS-controlled units' visibility" +}, { + "image": "visibility/aircraft.svg", + "name":"Aircraft", + "toggles": [ "aircraft" ], + "tooltip": "Toggle aircraft's visibility" +}, { + "image": "visibility/helicopter.svg", + "name":"Helicopter", + "toggles": [ "helicopter" ], + "tooltip": "Toggle helicopters' visibility" +}, { + "image": "visibility/groundunit-sam.svg", + "name":"Air defence", + "toggles": [ "groundunit-sam" ], + "tooltip": "Toggle air defence units' visibility" +}, { + "image": "visibility/groundunit-other.svg", + "name":"Ground units", + "toggles": [ "groundunit-other" ], + "tooltip": "Toggle ground units' visibility" +}, { + "image": "visibility/navyunit.svg", + "name":"Naval", + "toggles": [ "navyunit" ], + "tooltip": "Toggle naval units' visibility" +}, { + "image": "visibility/airbase.svg", + "name":"Airbase", + "toggles": [ "airbase" ], + "tooltip": "Toggle airbase' visibility" +}]; export const IADSTypes = ["AAA", "MANPADS", "SAM Site", "Radar"]; export const IADSDensities: {[key: string]: number}= {"AAA": 0.8, "MANPADS": 0.3, "SAM Site": 0.1, "Radar": 0.05}; diff --git a/client/src/context/context.ts b/client/src/context/context.ts new file mode 100644 index 00000000..f92f5168 --- /dev/null +++ b/client/src/context/context.ts @@ -0,0 +1,11 @@ +export interface ContextInterface { + +} + +export class Context { + + constructor( config:ContextInterface ) { + + } + +} \ No newline at end of file diff --git a/client/src/context/contextmanager.ts b/client/src/context/contextmanager.ts new file mode 100644 index 00000000..46bcb7dd --- /dev/null +++ b/client/src/context/contextmanager.ts @@ -0,0 +1,43 @@ +import { Manager } from "../other/manager"; +import { Context, ContextInterface } from "./context"; + +export class ContextManager extends Manager { + + #currentContext!:string; + + constructor() { + super(); + } + + add( name:string, contextConfig:ContextInterface ) { + super.add( name, new Context( contextConfig ) ); + + if ( Object.values( this.getAll() ).length === 1 ) { + this.#currentContext = name; + } + + return this; + } + + currentContextIs( contextName:string ) { + return contextName === this.#currentContext; + } + + getCurrentContext() { + const contexts = this.getAll(); + + return ( contexts.hasOwnProperty( this.#currentContext ) ) ? contexts[this.#currentContext] : false; + } + + setContext( contextName:string ) { + + if ( !this.get( contextName ) ) { + console.error( `setContext(): context name "${contextName}" does not exist.` ); + return false; + } + this.#currentContext = contextName; + + console.log( `Setting context to "${this.#currentContext}".` ); + } + +} \ No newline at end of file diff --git a/client/src/context/contextmenumanager.ts b/client/src/context/contextmenumanager.ts new file mode 100644 index 00000000..296fc7ec --- /dev/null +++ b/client/src/context/contextmenumanager.ts @@ -0,0 +1,15 @@ +import { ContextMenu } from "../contextmenus/contextmenu"; +import { Manager } from "../other/manager"; + +export class ContextMenuManager extends Manager { + + constructor() { + super(); + } + + add( name:string, contextMenu:ContextMenu ) { + super.add( name, contextMenu ); + return this; + } + +} \ No newline at end of file diff --git a/client/src/interfaces.ts b/client/src/interfaces.ts index 8ff8102b..e5166ccc 100644 --- a/client/src/interfaces.ts +++ b/client/src/interfaces.ts @@ -272,6 +272,7 @@ export interface Listener { export interface ShortcutOptions { altKey?: boolean; callback: CallableFunction; + context?: string; ctrlKey?: boolean; name?: string; shiftKey?: boolean; diff --git a/client/src/map/map.ts b/client/src/map/map.ts index 51dff111..06a95d15 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -12,7 +12,7 @@ import { DestinationPreviewMarker } from "./markers/destinationpreviewmarker"; import { TemporaryUnitMarker } from "./markers/temporaryunitmarker"; import { ClickableMiniMap } from "./clickableminimap"; import { SVGInjector } from '@tanem/svg-injector' -import { mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, visibilityControls, visibilityControlsTooltips, MOVE_UNIT, SHOW_UNIT_CONTACTS, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, visibilityControlsTypes, SHOW_UNIT_LABELS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING } from "../constants/constants"; +import { mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, visibilityControls, visibilityControlsTooltips, MOVE_UNIT, SHOW_UNIT_CONTACTS, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, visibilityControlsTypes, SHOW_UNIT_LABELS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, MAP_MARKER_CONTROLS } from "../constants/constants"; import { TargetMarker } from "./markers/targetmarker"; import { CoalitionArea } from "./coalitionarea/coalitionarea"; import { CoalitionAreaContextMenu } from "../contextmenus/coalitionareacontextmenu"; @@ -38,6 +38,15 @@ L.Map.addInitHook("addHandler", "gestureHandling", GestureHandling); require("../../public/javascripts/leaflet.nauticscale.js") require("../../public/javascripts/L.Path.Drag.js") +export type MapMarkerControl = { + "image": string; + "isProtected"?: boolean, + "name":string, + "protectable"?: boolean, + "toggles": string[], + "tooltip": string +} + export class Map extends L.Map { #ID: string; #state: string; @@ -80,6 +89,7 @@ export class Map extends L.Map { #coalitionAreaContextMenu: CoalitionAreaContextMenu = new CoalitionAreaContextMenu("coalition-area-contextmenu"); #mapSourceDropdown: Dropdown; + #mapMarkerControls:MapMarkerControl[] = MAP_MARKER_CONTROLS; #mapVisibilityOptionsDropdown: Dropdown; #optionButtons: { [key: string]: HTMLButtonElement[] } = {} #visibilityOptions: { [key: string]: boolean } = {} @@ -201,12 +211,7 @@ export class Map extends L.Map { }, 20); /* Option buttons */ - this.#optionButtons["visibility"] = visibilityControls.map((option: string, index: number) => { - var typesArrayString = `"${visibilityControlsTypes[index][0]}"`; - visibilityControlsTypes[index].forEach((type: string, idx: number) => { if (idx > 0) typesArrayString = `${typesArrayString}, "${type}"` }); - return this.#createOptionButton(option, `visibility/${option.toLowerCase()}.svg`, visibilityControlsTooltips[index], "toggleMarkerVisibility", `{"types": [${typesArrayString}]}`); - }); - document.querySelector("#unit-visibility-control")?.append(...this.#optionButtons["visibility"]); + this.#createUnitMarkerControlButtons(); /* Create the checkboxes to select the advanced visibility options */ this.addVisibilityOption(SHOW_UNIT_CONTACTS, false); @@ -608,16 +613,16 @@ export class Map extends L.Map { const selectedUnitTypes = getApp().getUnitsManager().getSelectedUnitsCategories(); if (selectedUnitTypes.length === 1 && ["Aircraft", "Helicopter"].includes(selectedUnitTypes[0])) { - if (selectedUnitTypes[0] === "Helicopter") { + if (selectedUnits.every((unit: Unit) => { return unit.canLandAtPoint()})) options["land-at-point"] = { text: "Land here", tooltip: "Land at this precise location" }; - } if (selectedUnits.every((unit: Unit) => { return unit.canTargetPoint()})) { 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" }; - } else { + } + + if (Object.keys(options).length === 0) (getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Selected units can not perform point actions.`); - } } else if (selectedUnitTypes.length === 1 && ["GroundUnit", "NavyUnit"].includes(selectedUnitTypes[0])) { if (selectedUnits.every((unit: Unit) => { return unit.canTargetPoint() })) { @@ -724,17 +729,62 @@ export class Map extends L.Map { return minimapBoundaries; } - #createOptionButton(value: string, url: string, title: string, callback: string, argument: string) { - var button = document.createElement("button"); - const img = document.createElement("img"); - img.src = `/resources/theme/images/buttons/${url}`; - img.onload = () => SVGInjector(img); - button.title = title; - button.value = value; - button.appendChild(img); - button.setAttribute("data-on-click", callback); - button.setAttribute("data-on-click-params", argument); - return button; + #createUnitMarkerControlButtons() { + const unitVisibilityControls = document.getElementById("unit-visibility-control"); + const makeTitle = (isProtected:boolean) => { + return ( isProtected ) ? "Unit type is protected and will ignore orders" : "Unit is NOT protected and will respond to orders"; + } + this.#mapMarkerControls.forEach( (control:MapMarkerControl) => { + const toggles = `["${control.toggles.join('","')}"]`; + const div = document.createElement("div"); + div.className = control.protectable === true ? "protectable" : ""; + div.innerHTML = ` + + `; + unitVisibilityControls.appendChild(div); + + if ( control.protectable ) { + div.innerHTML += ` + `; + + const btn = div.querySelector("button.lock"); + btn.addEventListener("click", (ev:MouseEventInit) => { + control.isProtected = !control.isProtected; + btn.toggleAttribute("data-protected", control.isProtected); + btn.title = makeTitle( control.isProtected ); + document.dispatchEvent(new CustomEvent("toggleMarkerProtection", { + detail: { + "_element": btn, + "control": control + } + })); + }); + } + }); + + unitVisibilityControls.querySelectorAll(`img[src$=".svg"]`).forEach(img => SVGInjector(img)); + } + + unitIsProtected(unit:Unit) { + const toggles = this.#mapMarkerControls.reduce((list, control:MapMarkerControl) => { + if (control.isProtected) { + list = list.concat(control.toggles); + } + return list; + }, [] as string[]); + + if (toggles.length === 0) + return false; + + return toggles.some((toggle:string) => { + // Specific coding for robots - extend later if needed + return (toggle === "dcs" && !unit.getControlled() && !unit.getHuman()); + }); } #deselectCoalitionAreas() { @@ -753,37 +803,35 @@ export class Map extends L.Map { #showDestinationCursors() { const singleCursor = !this.#shiftKey; const selectedUnitsCount = getApp().getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).length; - if (selectedUnitsCount > 0) { - if (singleCursor) { - if ( this.#destinationPreviewCursors.length != 1) { - this.#hideDestinationCursors(); - var marker = new DestinationPreviewMarker(this.getMouseCoordinates(), { interactive: false }); - marker.addTo(this); - this.#destinationPreviewCursors = [marker]; - } - - this.#destinationPreviewHandleLine.removeFrom(this); - this.#destinationPreviewHandle.removeFrom(this); + if (singleCursor) { + if ( 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); - } - this.#destinationPreviewHandleLine.addTo(this); - this.#destinationPreviewHandle.addTo(this); - - while (this.#destinationPreviewCursors.length < selectedUnitsCount) { - var cursor = new DestinationPreviewMarker(this.getMouseCoordinates(), { interactive: false }); - cursor.addTo(this); - this.#destinationPreviewCursors.push(cursor); - } - - this.#updateDestinationCursors(); - } + this.#destinationPreviewHandleLine.removeFrom(this); + this.#destinationPreviewHandle.removeFrom(this); } - } + else if (!singleCursor) { + while (this.#destinationPreviewCursors.length > selectedUnitsCount) { + this.removeLayer(this.#destinationPreviewCursors[0]); + this.#destinationPreviewCursors.splice(0, 1); + } + + this.#destinationPreviewHandleLine.addTo(this); + this.#destinationPreviewHandle.addTo(this); + + while (this.#destinationPreviewCursors.length < selectedUnitsCount) { + var cursor = new DestinationPreviewMarker(this.getMouseCoordinates(), { interactive: false }); + cursor.addTo(this); + this.#destinationPreviewCursors.push(cursor); + } + + this.#updateDestinationCursors(); + } +} #updateDestinationCursors() { const groupLatLng = this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : this.getMouseCoordinates(); diff --git a/client/src/olympusapp.ts b/client/src/olympusapp.ts index 2d9b7be7..ac2bc16e 100644 --- a/client/src/olympusapp.ts +++ b/client/src/olympusapp.ts @@ -25,6 +25,8 @@ import { groundUnitDatabase } from "./unit/databases/groundunitdatabase"; import { navyUnitDatabase } from "./unit/databases/navyunitdatabase"; import { ConfigurationOptions } from "./interfaces"; import { UnitListPanel } from "./panels/unitlistpanel"; +import { ContextManager } from "./context/contextmanager"; +import { Context } from "./context/context"; export class OlympusApp { /* Global data */ @@ -34,13 +36,14 @@ export class OlympusApp { #map: Map | null = null; /* Managers */ + #contextManager!: ContextManager; #dialogManager!: Manager; #missionManager: MissionManager | null = null; #panelsManager: Manager | null = null; #pluginsManager: PluginsManager | null = null; #popupsManager: Manager | null = null; #serverManager: ServerManager | null = null; - #shortcutManager: ShortcutManager | null = null; + #shortcutManager!: ShortcutManager; #toolbarsManager: Manager | null = null; #unitsManager: UnitsManager | null = null; #weaponsManager: WeaponsManager | null = null; @@ -58,6 +61,14 @@ export class OlympusApp { return this.#map as Map; } + getCurrentContext() { + return this.getContextManager().getCurrentContext() as Context; + } + + getContextManager() { + return this.#contextManager as ContextManager; + } + getServerManager() { return this.#serverManager as ServerManager; } @@ -166,6 +177,10 @@ export class OlympusApp { start() { /* Initialize base functionalitites */ + + this.#contextManager = new ContextManager(); + this.#contextManager.add( "olympus", {} ); + this.#map = new Map('map-container'); this.#missionManager = new MissionManager(); @@ -214,6 +229,13 @@ export class OlympusApp { /* Setup all global events */ this.#setupEvents(); + + /* Set the splash background image to a random image */ + var splashScreen = document.getElementById("splash-screen"); + if (splashScreen) { + let i = Math.round(Math.random() * 7 + 1); + splashScreen.style.backgroundImage = `url('/resources/theme/images/splash/${i}.jpg')`; + } } #setupEvents() { @@ -244,22 +266,28 @@ export class OlympusApp { const shortcutManager = this.getShortcutManager(); shortcutManager.addKeyboardShortcut("toggleDemo", { + "altKey": false, "callback": () => { this.getServerManager().toggleDemoEnabled(); }, - "code": "KeyT" + "code": "KeyT", + "context": "olympus", + "ctrlKey": false, + "shiftKey": false }).addKeyboardShortcut("togglePause", { "altKey": false, "callback": () => { this.getServerManager().setPaused(!this.getServerManager().getPaused()); }, "code": "Space", + "context": "olympus", "ctrlKey": false }).addKeyboardShortcut("deselectAll", { "callback": (ev: KeyboardEvent) => { this.getUnitsManager().deselectAllUnits(); }, - "code": "Escape" + "code": "Escape", + "context": "olympus" }).addKeyboardShortcut("toggleUnitLabels", { "altKey": false, "callback": () => { @@ -269,6 +297,7 @@ export class OlympusApp { } }, "code": "KeyL", + "context": "olympus", "ctrlKey": false, "shiftKey": false }).addKeyboardShortcut("toggleAcquisitionRings", { @@ -280,6 +309,7 @@ export class OlympusApp { } }, "code": "KeyE", + "context": "olympus", "ctrlKey": false, "shiftKey": false }).addKeyboardShortcut("toggleEngagementRings", { @@ -291,6 +321,7 @@ export class OlympusApp { } }, "code": "KeyQ", + "context": "olympus", "ctrlKey": false, "shiftKey": false }).addKeyboardShortcut("toggleHideShortEngagementRings", { @@ -302,6 +333,7 @@ export class OlympusApp { } }, "code": "KeyR", + "context": "olympus", "ctrlKey": false, "shiftKey": false }).addKeyboardShortcut("toggleFillEngagementRings", { @@ -313,6 +345,7 @@ export class OlympusApp { } }, "code": "KeyF", + "context": "olympus", "ctrlKey": false, "shiftKey": false }); @@ -324,6 +357,7 @@ export class OlympusApp { this.getMap().handleMapPanning(ev); }, "code": code, + "context": "olympus", "ctrlKey": false, "event": "keydown" }); @@ -332,7 +366,8 @@ export class OlympusApp { "callback": (ev: KeyboardEvent) => { this.getMap().handleMapPanning(ev); }, - "code": code + "code": code, + "context": "olympus" }); }); @@ -353,10 +388,8 @@ export class OlympusApp { }, "code": code }); - }); - // Stop hotgroup controls sending the browser to another tab - digits.forEach(code => { + // Stop hotgroup controls sending the browser to another tab document.addEventListener("keydown", (ev: KeyboardEvent) => { if (ev.code === code && ev.ctrlKey === true && ev.altKey === false && ev.shiftKey === false) { ev.preventDefault(); diff --git a/client/src/other/manager.ts b/client/src/other/manager.ts index 6ac10ea8..c889e713 100644 --- a/client/src/other/manager.ts +++ b/client/src/other/manager.ts @@ -1,7 +1,11 @@ +import { Context } from "../context/context"; + export class Manager { + #items: { [key: string]: any } = {}; constructor() { + } add(name: string, item: any) { diff --git a/client/src/shortcut/shortcut.ts b/client/src/shortcut/shortcut.ts index 09fbe79a..39c6d362 100644 --- a/client/src/shortcut/shortcut.ts +++ b/client/src/shortcut/shortcut.ts @@ -1,3 +1,4 @@ +import { getApp } from ".."; import { ShortcutKeyboardOptions, ShortcutMouseOptions, ShortcutOptions } from "../interfaces"; import { keyEventWasInInput } from "../other/utils"; @@ -19,6 +20,10 @@ export class ShortcutKeyboard extends Shortcut { super(config); document.addEventListener(config.event, (ev: any) => { + if ( typeof config.context === "string" && !getApp().getContextManager().currentContextIs( config.context ) ) { + return; + } + if (ev instanceof KeyboardEvent === false || keyEventWasInInput(ev)) { return; } diff --git a/client/src/shortcut/shortcutmanager.ts b/client/src/shortcut/shortcutmanager.ts index be8b70fe..2e80ea66 100644 --- a/client/src/shortcut/shortcutmanager.ts +++ b/client/src/shortcut/shortcutmanager.ts @@ -1,5 +1,6 @@ import { ShortcutKeyboardOptions, ShortcutMouseOptions } from "../interfaces"; import { Manager } from "../other/manager"; + import { ShortcutKeyboard, ShortcutMouse } from "./shortcut"; export class ShortcutManager extends Manager { diff --git a/client/src/unit/unitsmanager.ts b/client/src/unit/unitsmanager.ts index 148b0ea2..f50e0d23 100644 --- a/client/src/unit/unitsmanager.ts +++ b/client/src/unit/unitsmanager.ts @@ -47,6 +47,8 @@ export class UnitsManager { document.addEventListener('unitDeselection', (e: CustomEvent) => this.#onUnitDeselection(e.detail)); document.addEventListener('unitSelection', (e: CustomEvent) => this.#onUnitSelection(e.detail)); + document.addEventListener("toggleMarkerProtection", (ev:CustomEventInit) => { this.#showNumberOfSelectedProtectedUnits() }); + this.#slowDeleteDialog = new Dialog( "slow-delete-dialog" ); } @@ -221,16 +223,29 @@ export class UnitsManager { * @param options Selection options * @returns Array of selected units */ - getSelectedUnits(options?: { excludeHumans?: boolean, onlyOnePerGroup?: boolean }) { - var selectedUnits = []; - for (let ID in this.#units) { - if (this.#units[ID].getSelected()) { - selectedUnits.push(this.#units[ID]); + getSelectedUnits(options?: { excludeHumans?: boolean, excludeProtected?:boolean, onlyOnePerGroup?: boolean, showProtectionReminder?:boolean }) { + let selectedUnits:Unit[] = []; + let numProtectedUnits = 0; + for (const [ID, unit] of Object.entries(this.#units)) { + if (unit.getSelected()) { + if (options) { + if (options.excludeHumans && unit.getHuman()) + continue + + if (options.excludeProtected === true && this.#unitIsProtected(unit)) { + numProtectedUnits++; + continue; + } + } + selectedUnits.push(unit); } } if (options) { - if (options.excludeHumans) - selectedUnits = selectedUnits.filter((unit: Unit) => { return !unit.getHuman() }); + if (options.showProtectionReminder === true && numProtectedUnits > selectedUnits.length && selectedUnits.length === 0) { + const messageText = (numProtectedUnits === 1) ? `Unit is protected` : `All selected units are protected`; + (getApp().getPopupsManager().get("infoPopup") as Popup).setText(messageText); + } + if (options.onlyOnePerGroup) { var temp: Unit[] = []; for (let unit of selectedUnits) { @@ -320,7 +335,10 @@ export class UnitsManager { * @param rotation Rotation in radians by which the formation will be rigidly rotated. E.g. a ( V ) formation will look like this ( < ) if rotated pi/4 radians (90 degrees) */ selectedUnitsAddDestination(latlng: L.LatLng, mantainRelativePosition: boolean, rotation: number) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); + + if (selectedUnits.length === 0) + return; /* Compute the destination for each unit. If mantainRelativePosition is true, compute the destination so to hold the relative positions */ var unitDestinations: { [key: number]: LatLng } = {}; @@ -353,7 +371,7 @@ export class UnitsManager { * */ selectedUnitsClearDestinations() { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: false }); for (let idx in selectedUnits) { const unit = selectedUnits[idx]; if (unit.getState() === "follow") { @@ -373,7 +391,7 @@ export class UnitsManager { * @param latlng Location where to land at */ selectedUnitsLandAt(latlng: LatLng) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].landAt(latlng); } @@ -385,7 +403,7 @@ export class UnitsManager { * @param speedChange Speed change, either "stop", "slow", or "fast". The specific value depends on the unit category */ selectedUnitsChangeSpeed(speedChange: string) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].changeSpeed(speedChange); } @@ -396,7 +414,7 @@ export class UnitsManager { * @param altitudeChange Altitude change, either "climb" or "descend". The specific value depends on the unit category */ selectedUnitsChangeAltitude(altitudeChange: string) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].changeAltitude(altitudeChange); } @@ -407,7 +425,7 @@ export class UnitsManager { * @param speed Value to set, in m/s */ selectedUnitsSetSpeed(speed: number) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].setSpeed(speed); } @@ -419,7 +437,7 @@ export class UnitsManager { * @param speedType Value to set, either "CAS" or "GS". If "CAS" is selected, the unit will try to maintain the selected Calibrated Air Speed, but DCS will still only maintain a Ground Speed value so errors may arise depending on wind. */ selectedUnitsSetSpeedType(speedType: string) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].setSpeedType(speedType); } @@ -431,7 +449,7 @@ export class UnitsManager { * @param altitude Value to set, in m */ selectedUnitsSetAltitude(altitude: number) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].setAltitude(altitude); } @@ -443,7 +461,7 @@ export class UnitsManager { * @param altitudeType Value to set, either "ASL" or "AGL". If "AGL" is selected, the unit will try to maintain the selected Above Ground Level altitude. Due to a DCS bug, this will only be true at the final position. */ selectedUnitsSetAltitudeType(altitudeType: string) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].setAltitudeType(altitudeType); } @@ -455,7 +473,7 @@ export class UnitsManager { * @param ROE Value to set, see constants for acceptable values */ selectedUnitsSetROE(ROE: string) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].setROE(ROE); } @@ -467,7 +485,7 @@ export class UnitsManager { * @param reactionToThreat Value to set, see constants for acceptable values */ selectedUnitsSetReactionToThreat(reactionToThreat: string) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].setReactionToThreat(reactionToThreat); } @@ -479,7 +497,7 @@ export class UnitsManager { * @param emissionCountermeasure Value to set, see constants for acceptable values */ selectedUnitsSetEmissionsCountermeasures(emissionCountermeasure: string) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].setEmissionsCountermeasures(emissionCountermeasure); } @@ -491,7 +509,7 @@ export class UnitsManager { * @param onOff If true, the unit will be turned on */ selectedUnitsSetOnOff(onOff: boolean) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].setOnOff(onOff); } @@ -503,7 +521,7 @@ export class UnitsManager { * @param followRoads If true, units will follow roads */ selectedUnitsSetFollowRoads(followRoads: boolean) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].setFollowRoads(followRoads); } @@ -516,7 +534,7 @@ export class UnitsManager { */ selectedUnitsSetOperateAs(operateAsBool: boolean) { var operateAs = operateAsBool? "blue": "red"; - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].setOperateAs(operateAs); } @@ -528,7 +546,7 @@ export class UnitsManager { * @param ID ID of the unit to attack */ selectedUnitsAttackUnit(ID: number) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].attackUnit(ID); } @@ -539,7 +557,7 @@ export class UnitsManager { * */ selectedUnitsRefuel() { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].refuel(); } @@ -565,7 +583,10 @@ export class UnitsManager { else offset = undefined; } - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); + + if ( selectedUnits.length === 0) + return; var count = 1; var xr = 0; var yr = 1; var zr = -1; @@ -601,7 +622,7 @@ export class UnitsManager { * @param latlng Location to bomb */ selectedUnitsBombPoint(latlng: LatLng) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].bombPoint(latlng); } @@ -613,7 +634,7 @@ export class UnitsManager { * @param latlng Location to bomb */ selectedUnitsCarpetBomb(latlng: LatLng) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].carpetBomb(latlng); } @@ -625,7 +646,7 @@ export class UnitsManager { * @param latlng Location to fire at */ selectedUnitsFireAtArea(latlng: LatLng) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].fireAtArea(latlng); } @@ -637,7 +658,7 @@ export class UnitsManager { * @param latlng Location to fire at */ selectedUnitsSimulateFireFight(latlng: LatLng) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); getGroundElevation(latlng, (response: string) => { var groundElevation: number | null = null; try { @@ -656,7 +677,7 @@ export class UnitsManager { * */ selectedUnitsScenicAAA() { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].scenicAAA(); } @@ -667,11 +688,11 @@ export class UnitsManager { * */ selectedUnitsMissOnPurpose() { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].missOnPurpose(); } - this.#showActionMessage(selectedUnits, `unit set to perform miss on purpose AAA`); + this.#showActionMessage(selectedUnits, `unit set to perform miss-on-purpose AAA`); } /** Instruct units to land at specific point @@ -679,12 +700,12 @@ export class UnitsManager { * @param latlng Point where to land */ selectedUnitsLandAtPoint(latlng: LatLng) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].landAtPoint(latlng); } - this.#showActionMessage(selectedUnits, `unit simulating fire fight`); + this.#showActionMessage(selectedUnits, `unit landing at point`); } /** Set a specific shots scatter to all the selected units @@ -733,7 +754,7 @@ export class UnitsManager { * */ selectedUnitsCreateGroup() { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: false }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: false, showProtectionReminder: true }); if (this.getUnitsCategories(selectedUnits).length == 1) { var units: { ID: number, location: LatLng }[] = []; for (let idx in selectedUnits) { @@ -774,7 +795,7 @@ export class UnitsManager { * @returns */ selectedUnitsDelete(explosion: boolean = false) { - var selectedUnits = this.getSelectedUnits(); /* Can be applied to humans too */ + var selectedUnits = this.getSelectedUnits({excludeProtected:true}); /* Can be applied to humans too */ const selectionContainsAHuman = selectedUnits.some((unit: Unit) => { return unit.getHuman() === true; }); @@ -784,7 +805,6 @@ export class UnitsManager { } const doDelete = (explosion = false, immediate = false) => { - const selectedUnits = this.getSelectedUnits(); for (let idx in selectedUnits) { selectedUnits[idx].delete(explosion, immediate); } @@ -810,7 +830,7 @@ export class UnitsManager { * @returns Array of positions for each unit, in order */ selectedUnitsComputeGroupDestination(latlng: LatLng, rotation: number) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true }); /* Compute the center of the group */ var center = { x: 0, y: 0 }; selectedUnits.forEach((unit: Unit) => { @@ -1089,6 +1109,7 @@ export class UnitsManager { window.setTimeout(() => { document.dispatchEvent(new CustomEvent("unitsSelection", { detail: this.getSelectedUnits() })); this.#selectionEventDisabled = false; + this.#showNumberOfSelectedProtectedUnits(); }, 100); this.#selectionEventDisabled = true; } @@ -1150,4 +1171,21 @@ export class UnitsManager { }, 250); }); } + + #showNumberOfSelectedProtectedUnits() { + const map = getApp().getMap(); + const selectedUnits = this.getSelectedUnits(); + const numSelectedUnits = selectedUnits.length; + const numProtectedUnits = selectedUnits.filter((unit:Unit) => map.unitIsProtected(unit) ).length; + + if (numProtectedUnits === 1 && numSelectedUnits === numProtectedUnits) + (getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Notice: unit is protected`); + + if (numProtectedUnits > 1) + (getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Notice: selection contains ${numProtectedUnits} protected units.`); + } + + #unitIsProtected(unit:Unit) { + return getApp().getMap().unitIsProtected(unit) + } } \ No newline at end of file diff --git a/src/core/include/scheduler.h b/src/core/include/scheduler.h index a33dca3a..02f7b1c4 100644 --- a/src/core/include/scheduler.h +++ b/src/core/include/scheduler.h @@ -37,8 +37,8 @@ public: private: list commands; list executedCommandsHashes; - unsigned int load; - double frameRate; + unsigned int load = 0; + double frameRate = 0; bool restrictSpawns = false; bool restrictToCoalition = false; diff --git a/src/core/src/scheduler.cpp b/src/core/src/scheduler.cpp index a915ae6b..af57c77d 100644 --- a/src/core/src/scheduler.cpp +++ b/src/core/src/scheduler.cpp @@ -7,12 +7,9 @@ extern UnitsManager* unitsManager; -Scheduler::Scheduler(lua_State* L) : - load(0) +Scheduler::Scheduler(lua_State* L) { LogInfo(L, "Scheduler constructor called successfully"); - - } Scheduler::~Scheduler() @@ -20,6 +17,7 @@ Scheduler::~Scheduler() } +/* Appends a */ void Scheduler::appendCommand(Command* newCommand) { for (auto command : commands) { @@ -143,6 +141,7 @@ void Scheduler::handleRequest(string key, json::value value, string username, js log("Received request with ID: " + key); log(L"Incoming command raw value: " + value.serialize()); + /************************/ if (key.compare("setPath") == 0) { unsigned int ID = value[L"ID"].as_integer(); @@ -150,7 +149,6 @@ void Scheduler::handleRequest(string key, json::value value, string username, js Unit* unit = unitsManager->getGroupLeader(ID); if (unit != nullptr) { - string unitName = unit->getUnitName(); json::value path = value[L"path"]; list newPath; for (unsigned int i = 0; i < path.as_array().size(); i++) @@ -164,19 +162,22 @@ void Scheduler::handleRequest(string key, json::value value, string username, js unit->setActivePath(newPath); unit->setState(State::REACH_DESTINATION); - log(username + " updated destination path for unit " + unitName, true); + log(username + " updated destination path for unit " + unit->getUnitName() + "(" + unit->getName() + ")", true); } } + /************************/ else if (key.compare("smoke") == 0) { 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(username + " added a " + color + " smoke at (" + to_string(lat) + ", " + to_string(lng) + ")", true); + Coords loc; loc.lat = lat; loc.lng = lng; command = dynamic_cast(new Smoke(color, loc)); + log(username + " added a " + color + " smoke at (" + to_string(lat) + ", " + to_string(lng) + ")", true); } - else if (key.compare("spawnAircrafts") == 0) + /************************/ + else if (key.compare("spawnAircrafts") == 0 || key.compare("spawnHelicopters") == 0) { bool immediate = value[L"immediate"].as_bool(); string coalition = to_string(value[L"coalition"]); @@ -200,35 +201,13 @@ void Scheduler::handleRequest(string key, json::value value, string username, js log(username + " spawned a " + coalition + " " + unitType, true); } - command = dynamic_cast(new SpawnAircrafts(coalition, spawnOptions, airbaseName, country, immediate)); + if (key.compare("spawnAircrafts") == 0) + command = dynamic_cast(new SpawnAircrafts(coalition, spawnOptions, airbaseName, country, immediate)); + else + command = dynamic_cast(new SpawnHelicopters(coalition, spawnOptions, airbaseName, country, immediate)); } - else if (key.compare("spawnHelicopters") == 0) - { - bool immediate = value[L"immediate"].as_bool(); - string coalition = to_string(value[L"coalition"]); - string airbaseName = to_string(value[L"airbaseName"]); - string country = to_string(value[L"country"]); - - int spawnPoints = value[L"spawnPoints"].as_number().to_int32(); - if (!checkSpawnPoints(spawnPoints, coalition)) return; - - vector spawnOptions; - for (auto unit : value[L"units"].as_array()) { - string unitType = to_string(unit[L"unitType"]); - double lat = unit[L"location"][L"lat"].as_double(); - double lng = unit[L"location"][L"lng"].as_double(); - double alt = unit[L"altitude"].as_double(); - Coords location; location.lat = lat; location.lng = lng; location.alt = alt; - string loadout = to_string(unit[L"loadout"]); - string liveryID = to_string(unit[L"liveryID"]); - - spawnOptions.push_back({ unitType, location, loadout, liveryID }); - log(username + " spawned a " + coalition + " " + unitType, true); - } - - command = dynamic_cast(new SpawnHelicopters(coalition, spawnOptions, airbaseName, country, immediate)); - } - else if (key.compare("spawnGroundUnits") == 0) + /************************/ + else if (key.compare("spawnGroundUnits") == 0 || key.compare("spawnNavyUnits") == 0) { bool immediate = value[L"immediate"].as_bool(); string coalition = to_string(value[L"coalition"]); @@ -249,31 +228,12 @@ void Scheduler::handleRequest(string key, json::value value, string username, js log(username + " spawned a " + coalition + " " + unitType, true); } - command = dynamic_cast(new SpawnGroundUnits(coalition, spawnOptions, country, immediate)); - } - else if (key.compare("spawnNavyUnits") == 0) - { - bool immediate = value[L"immediate"].as_bool(); - string coalition = to_string(value[L"coalition"]); - string country = to_string(value[L"country"]); - - int spawnPoints = value[L"spawnPoints"].as_number().to_int32(); - if (!checkSpawnPoints(spawnPoints, coalition)) return; - - vector spawnOptions; - for (auto unit : value[L"units"].as_array()) { - string unitType = to_string(unit[L"unitType"]); - double lat = unit[L"location"][L"lat"].as_double(); - double lng = unit[L"location"][L"lng"].as_double(); - Coords location; location.lat = lat; location.lng = lng; - string liveryID = to_string(unit[L"liveryID"]); - - spawnOptions.push_back({ unitType, location, "", liveryID}); - log(username + " spawned a " + coalition + " " + unitType, true); - } - - command = dynamic_cast(new SpawnNavyUnits(coalition, spawnOptions, country, immediate)); + if (key.compare("spawnGroundUnits") == 0) + command = dynamic_cast(new SpawnGroundUnits(coalition, spawnOptions, country, immediate)); + else + command = dynamic_cast(new SpawnNavyUnits(coalition, spawnOptions, country, immediate)); } + /************************/ else if (key.compare("attackUnit") == 0) { unsigned int ID = value[L"ID"].as_integer(); @@ -283,23 +243,13 @@ void Scheduler::handleRequest(string key, json::value value, string username, js Unit* unit = unitsManager->getGroupLeader(ID); Unit* target = unitsManager->getUnit(targetID); - string unitName; - string targetName; - - if (unit != nullptr) - unitName = unit->getUnitName(); - else - return; - - if (target != nullptr) - targetName = target->getUnitName(); - else - return; - - log(username + " tasked unit " + unitName + " to attack unit " + targetName, true); - unit->setTargetID(targetID); - unit->setState(State::ATTACK); + if (unit != nullptr && target != nullptr) { + log(username + " tasked unit " + unit->getUnitName() + "(" + unit->getName() + ") to attack unit " + target->getUnitName() + "(" + target->getName() + ")", true); + unit->setTargetID(targetID); + unit->setState(State::ATTACK); + } } + /************************/ else if (key.compare("followUnit") == 0) { unsigned int ID = value[L"ID"].as_integer(); @@ -312,72 +262,80 @@ void Scheduler::handleRequest(string key, json::value value, string username, js Unit* unit = unitsManager->getGroupLeader(ID); Unit* leader = unitsManager->getUnit(leaderID); - string unitName; - string leaderName; - - if (unit != nullptr) - unitName = unit->getUnitName(); - else - return; - - if (leader != nullptr) - leaderName = leader->getUnitName(); - else - return; - - log(username + " tasked unit " + unitName + " to follow unit " + leaderName, true); - unit->setFormationOffset(Offset(offsetX, offsetY, offsetZ)); - unit->setLeaderID(leaderID); - unit->setState(State::FOLLOW); + if (unit != nullptr && leader != nullptr) { + log(username + " tasked unit " + unit->getUnitName() + "(" + unit->getName() + ") to follow unit " + leader->getUnitName() + "(" + leader->getName() + ")", true); + unit->setFormationOffset(Offset(offsetX, offsetY, offsetZ)); + unit->setLeaderID(leaderID); + unit->setState(State::FOLLOW); + } } + /************************/ else if (key.compare("changeSpeed") == 0) { unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); - if (unit != nullptr) + if (unit != nullptr) { unit->changeSpeed(to_string(value[L"change"])); + log(username + " changed " + unit->getUnitName() + "(" + unit->getName() + ") speed: " + to_string(value[L"change"]), true); + } } + /************************/ else if (key.compare("changeAltitude") == 0) { unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); - if (unit != nullptr) + if (unit != nullptr) { unit->changeAltitude(to_string(value[L"change"])); + log(username + " changed " + unit->getUnitName() + "(" + unit->getName() + ") altitude: " + to_string(value[L"change"]), true); + } } + /************************/ else if (key.compare("setSpeed") == 0) { unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); - if (unit != nullptr) + if (unit != nullptr) { unit->setDesiredSpeed(value[L"speed"].as_double()); + log(username + " set " + unit->getUnitName() + "(" + unit->getName() + ") speed: " + to_string(value[L"speed"].as_double()), true); + } } + /************************/ 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) + if (unit != nullptr) { unit->setDesiredSpeedType(to_string(value[L"speedType"])); + log(username + " set " + unit->getUnitName() + "(" + unit->getName() + ") speed type: " + to_string(value[L"speedType"]), true); + } } + /************************/ else if (key.compare("setAltitude") == 0) { unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); - if (unit != nullptr) + if (unit != nullptr) { unit->setDesiredAltitude(value[L"altitude"].as_double()); + log(username + " set " + unit->getUnitName() + "(" + unit->getName() + ") altitude: " + to_string(value[L"altitude"].as_double()), true); + } } + /************************/ else if (key.compare("setAltitudeType") == 0) { unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); - if (unit != nullptr) + if (unit != nullptr) { unit->setDesiredAltitudeType(to_string(value[L"altitudeType"])); + log(username + " set " + unit->getUnitName() + "(" + unit->getName() + ") altitude type: " + to_string(value[L"altitudeType"]), true); + } } + /************************/ else if (key.compare("cloneUnits") == 0) { vector cloneOptions; @@ -395,61 +353,80 @@ void Scheduler::handleRequest(string key, json::value value, string username, js command = dynamic_cast(new Clone(cloneOptions, deleteOriginal)); } + /************************/ else if (key.compare("setROE") == 0) { unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); - unsigned char ROE = value[L"ROE"].as_number().to_uint32(); - unit->setROE(ROE); - log(username + " set unit " + unit->getName() + " ROE to " + to_string(ROE), true); + if (unit != nullptr) { + unsigned char ROE = value[L"ROE"].as_number().to_uint32(); + unit->setROE(ROE); + log(username + " set unit " + unit->getUnitName() + "(" + unit->getName() + ") ROE to " + to_string(ROE), true); + } } + /************************/ else if (key.compare("setReactionToThreat") == 0) { unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); - unsigned char reactionToThreat = value[L"reactionToThreat"].as_number().to_uint32(); - unit->setReactionToThreat(reactionToThreat); - log(username + " set unit " + unit->getName() + " reaction to threat to " + to_string(reactionToThreat), true); + if (unit != nullptr) { + unsigned char reactionToThreat = value[L"reactionToThreat"].as_number().to_uint32(); + unit->setReactionToThreat(reactionToThreat); + log(username + " set unit " + unit->getUnitName() + "(" + unit->getName() + ") reaction to threat to " + to_string(reactionToThreat), true); + } } + /************************/ else if (key.compare("setEmissionsCountermeasures") == 0) { unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); - unsigned char emissionsCountermeasures = value[L"emissionsCountermeasures"].as_number().to_uint32(); - unit->setEmissionsCountermeasures(emissionsCountermeasures); - log(username + " set unit " + unit->getName() + " emissions and countermeasures to " + to_string(emissionsCountermeasures), true); + if (unit != nullptr) { + unsigned char emissionsCountermeasures = value[L"emissionsCountermeasures"].as_number().to_uint32(); + unit->setEmissionsCountermeasures(emissionsCountermeasures); + log(username + " set unit " + unit->getUnitName() + "(" + unit->getName() + ") emissions and countermeasures to " + to_string(emissionsCountermeasures), true); + } } + /************************/ else if (key.compare("landAt") == 0) { 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(); - double lng = value[L"location"][L"lng"].as_double(); - Coords loc; loc.lat = lat; loc.lng = lng; - unit->landAt(loc); - log(username + " tasked unit " + unit->getName() + " to land", true); + if (unit != nullptr) { + 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->landAt(loc); + log(username + " tasked unit " + unit->getUnitName() + "(" + unit->getName() + ") to land", true); + } } + /************************/ else if (key.compare("deleteUnit") == 0) { unsigned int ID = value[L"ID"].as_integer(); bool explosion = value[L"explosion"].as_bool(); bool immediate = value[L"immediate"].as_bool(); - unitsManager->deleteUnit(ID, explosion, immediate); Unit* unit = unitsManager->getUnit(ID); - log(username + " deleted unit " + unit->getName(), true); + if (unit != nullptr) { + unitsManager->deleteUnit(ID, explosion, immediate); + log(username + " deleted unit " + unit->getUnitName() + "(" + unit->getName() + ")", true); + } } + /************************/ else if (key.compare("refuel") == 0) { unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); - unit->setState(State::REFUEL); - log(username + " tasked unit " + unit->getName() + " to refuel", true); + if (unit != nullptr) { + unit->setState(State::REFUEL); + log(username + " tasked unit " + unit->getUnitName() + "(" + unit->getName() + ") to refuel", true); + } } + /************************/ else if (key.compare("setAdvancedOptions") == 0) { unsigned int ID = value[L"ID"].as_integer(); @@ -489,24 +466,35 @@ void Scheduler::handleRequest(string key, json::value value, string username, js }); unit->resetActiveDestination(); + + log(username + " updated unit " + unit->getUnitName() + "(" + unit->getName() + ") advancedOptions", true); } } + /************************/ else if (key.compare("setFollowRoads") == 0) { 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); + if (unit != nullptr) { + unit->setFollowRoads(followRoads); + log(username + " set unit " + unit->getUnitName() + "(" + unit->getName() + ") followRoads to: " + (followRoads ? "true" : "false"), true); + } } + /************************/ else if (key.compare("setOnOff") == 0) { 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); + if (unit != nullptr) { + unit->setOnOff(onOff); + log(username + " set unit " + unit->getUnitName() + "(" + unit->getName() + ") onOff to: " + (onOff? "true": "false"), true); + } } + /************************/ else if (key.compare("explosion") == 0) { unsigned int intensity = value[L"intensity"].as_integer(); @@ -516,6 +504,7 @@ void Scheduler::handleRequest(string key, json::value value, string username, js Coords loc; loc.lat = lat; loc.lng = lng; command = dynamic_cast(new Explosion(intensity, loc)); } + /************************/ else if (key.compare("bombPoint") == 0) { unsigned int ID = value[L"ID"].as_integer(); @@ -524,10 +513,13 @@ void Scheduler::handleRequest(string key, json::value value, string username, js 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->setTargetPosition(loc); - log(username + " tasked unit " + unit->getName() + " to bomb a point", true); + if (unit != nullptr) { + unit->setState(State::BOMB_POINT); + unit->setTargetPosition(loc); + log(username + " tasked unit " + unit->getUnitName() + "(" + unit->getName() + ") to bomb a point", true); + } } + /************************/ else if (key.compare("carpetBomb") == 0) { unsigned int ID = value[L"ID"].as_integer(); @@ -536,10 +528,14 @@ void Scheduler::handleRequest(string key, json::value value, string username, js 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->setTargetPosition(loc); - log(username + " tasked unit " + unit->getName() + " to perform carpet bombing", true); + if (unit != nullptr) { + unit->setState(State::CARPET_BOMB); + unit->setTargetPosition(loc); + log(username + " tasked unit " + unit->getUnitName() + "(" + unit->getName() + ") to perform carpet bombing", true); + } } + /************************/ + /* TODO: this command does not appear to be working in DCS and has been disabled */ else if (key.compare("bombBuilding") == 0) { unsigned int ID = value[L"ID"].as_integer(); @@ -548,9 +544,12 @@ void Scheduler::handleRequest(string key, json::value value, string username, js 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->setTargetPosition(loc); + if (unit != nullptr) { + unit->setState(State::BOMB_BUILDING); + unit->setTargetPosition(loc); + } } + /************************/ else if (key.compare("fireAtArea") == 0) { unsigned int ID = value[L"ID"].as_integer(); @@ -559,10 +558,13 @@ void Scheduler::handleRequest(string key, json::value value, string username, js 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->setTargetPosition(loc); - log(username + " tasked unit " + unit->getName() + " to fire at area", true); + if (unit != nullptr) { + unit->setState(State::FIRE_AT_AREA); + unit->setTargetPosition(loc); + log(username + " tasked unit " + unit->getUnitName() + "(" + unit->getName() + ") to fire at area", true); + } } + /************************/ else if (key.compare("simulateFireFight") == 0) { unsigned int ID = value[L"ID"].as_integer(); @@ -572,34 +574,45 @@ void Scheduler::handleRequest(string key, json::value value, string username, js double alt = value[L"altitude"].as_double(); Coords loc; loc.lat = lat; loc.lng = lng; loc.alt = alt; Unit* unit = unitsManager->getGroupLeader(ID); - unit->setState(State::SIMULATE_FIRE_FIGHT); - unit->setTargetPosition(loc); - log(username + " tasked unit " + unit->getName() + " to simulate a fire fight", true); + if (unit != nullptr) { + unit->setState(State::SIMULATE_FIRE_FIGHT); + unit->setTargetPosition(loc); + log(username + " tasked unit " + unit->getUnitName() + "(" + unit->getName() + ") to simulate a fire fight", true); + } } + /************************/ else if (key.compare("scenicAAA") == 0) { unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); - unit->setState(State::SCENIC_AAA); - log(username + " tasked unit " + unit->getName() + " to enter scenic AAA state", true); + if (unit != nullptr) { + unit->setState(State::SCENIC_AAA); + log(username + " tasked unit " + unit->getUnitName() + "(" + unit->getName() + ") to enter scenic AAA state", true); + } } + /************************/ else if (key.compare("missOnPurpose") == 0) { unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); - unit->setState(State::MISS_ON_PURPOSE); - log(username + " tasked unit " + unit->getName() + " to enter Miss On Purpose state", true); + if (unit != nullptr) { + unit->setState(State::MISS_ON_PURPOSE); + log(username + " tasked unit " + unit->getUnitName() + "(" + unit->getName() + ") to enter Miss On Purpose state", true); + } } + /************************/ else if (key.compare("setOperateAs") == 0) { unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); unsigned char operateAs = value[L"operateAs"].as_number().to_uint32(); Unit* unit = unitsManager->getGroupLeader(ID); - unit->setOperateAs(operateAs); + if (unit != nullptr) + unit->setOperateAs(operateAs); } + /************************/ else if (key.compare("landAtPoint") == 0) { unsigned int ID = value[L"ID"].as_integer(); @@ -609,39 +622,50 @@ void Scheduler::handleRequest(string key, json::value value, string username, js Coords loc; loc.lat = lat; loc.lng = lng; Unit* unit = unitsManager->getGroupLeader(ID); - list newPath; - newPath.push_back(loc); - unit->setActivePath(newPath); - unit->setState(State::LAND_AT_POINT); - - log(username + " tasked unit " + unit->getName() + " to land at point", true); + if (unit != nullptr) { + list newPath; + newPath.push_back(loc); + unit->setActivePath(newPath); + unit->setState(State::LAND_AT_POINT); + + log(username + " tasked unit " + unit->getUnitName() + "(" + unit->getName() + ") to land at point", true); + } } + /************************/ else if (key.compare("setShotsScatter") == 0) { unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); - unsigned char shotsScatter = value[L"shotsScatter"].as_number().to_uint32(); - unit->setShotsScatter(shotsScatter); - log(username + " set unit " + unit->getName() + " shots scatter to " + to_string(shotsScatter), true); + if (unit != nullptr) { + unsigned char shotsScatter = value[L"shotsScatter"].as_number().to_uint32(); + unit->setShotsScatter(shotsScatter); + log(username + " set unit " + + unit->getUnitName() + "(" + unit->getName() + ") shots scatter to " + to_string(shotsScatter), true); + } } + /************************/ else if (key.compare("setShotsIntensity") == 0) { unsigned int ID = value[L"ID"].as_integer(); unitsManager->acquireControl(ID); Unit* unit = unitsManager->getGroupLeader(ID); - unsigned char shotsIntensity = value[L"shotsIntensity"].as_number().to_uint32(); - unit->setShotsIntensity(shotsIntensity); - log(username + " set unit " + unit->getName() + " shots intensity to " + to_string(shotsIntensity), true); + if (unit != nullptr) { + unsigned char shotsIntensity = value[L"shotsIntensity"].as_number().to_uint32(); + unit->setShotsIntensity(shotsIntensity); + log(username + " set unit " + unit->getUnitName() + "(" + unit->getName() + ") shots intensity to " + to_string(shotsIntensity), true); + } } + /************************/ else if (key.compare("setCommandModeOptions") == 0) { setCommandModeOptions(value); log(username + " updated the Command Mode Options", true); } + /************************/ else if (key.compare("reloadDatabases") == 0) { unitsManager->loadDatabases(); } + /************************/ else { log("Unknown command: " + key);