diff --git a/client/public/stylesheets/style/style.css b/client/public/stylesheets/style/style.css index 58ee2aa5..dd5c7381 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 { 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/src/constants/constants.ts b/client/src/constants/constants.ts index 36cd6367..5d2c18ee 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"; @@ -147,6 +148,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/map/map.ts b/client/src/map/map.ts index 374cfab3..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() { diff --git a/client/src/unit/unit.ts b/client/src/unit/unit.ts index b28e88aa..c79c0c18 100644 --- a/client/src/unit/unit.ts +++ b/client/src/unit/unit.ts @@ -95,45 +95,47 @@ export class Unit extends CustomMarker { #doubleClickTimer: number = 0; #hotgroup: number | null = null; #detectionMethods: number[] = []; + #isProtected:boolean = false; + getActivePath() { return this.#activePath }; getAlive() { return this.#alive }; - getHuman() { return this.#human }; - getControlled() { return this.#controlled }; + getAmmo() { return this.#ammo }; getCoalition() { return this.#coalition }; + getContacts() { return this.#contacts }; + getControlled() { return this.#controlled }; getCountry() { return this.#country }; - getName() { return this.#name }; - getUnitName() { return this.#unitName }; - getGroupName() { return this.#groupName }; - getState() { return this.#state }; - getTask() { return this.#task }; - getHasTask() { return this.#hasTask }; - getPosition() { return this.#position }; - getSpeed() { return this.#speed }; - getHeading() { return this.#heading }; - getIsActiveTanker() { return this.#isActiveTanker }; - getIsActiveAWACS() { return this.#isActiveAWACS }; - getOnOff() { return this.#onOff }; - getFollowRoads() { return this.#followRoads }; - getFuel() { return this.#fuel }; - getDesiredSpeed() { return this.#desiredSpeed }; - getDesiredSpeedType() { return this.#desiredSpeedType }; getDesiredAltitude() { return this.#desiredAltitude }; getDesiredAltitudeType() { return this.#desiredAltitudeType }; - getLeaderID() { return this.#leaderID }; + getDesiredSpeed() { return this.#desiredSpeed }; + getDesiredSpeedType() { return this.#desiredSpeedType }; + getEmissionsCountermeasures() { return this.#emissionsCountermeasures }; + getFollowRoads() { return this.#followRoads }; getFormationOffset() { return this.#formationOffset }; + getFuel() { return this.#fuel }; + getGeneralSettings() { return this.#generalSettings }; + getGroupName() { return this.#groupName }; + getHasTask() { return this.#hasTask }; + getHeading() { return this.#heading }; + getHuman() { return this.#human }; + getIsActiveAWACS() { return this.#isActiveAWACS }; + getIsActiveTanker() { return this.#isActiveTanker }; + getIsLeader() { return this.#isLeader }; + getLeaderID() { return this.#leaderID }; + getName() { return this.#name }; + getOnOff() { return this.#onOff }; + getOperateAs() { return this.#operateAs }; + getPosition() { return this.#position }; + getIsProtected() { return this.#isProtected }; + getRadio() { return this.#radio }; + getReactionToThreat() { return this.#reactionToThreat }; + getROE() { return this.#ROE }; + getSpeed() { return this.#speed }; + getState() { return this.#state }; + getTACAN() { return this.#TACAN }; getTargetID() { return this.#targetID }; getTargetPosition() { return this.#targetPosition }; - getROE() { return this.#ROE }; - getReactionToThreat() { return this.#reactionToThreat }; - getEmissionsCountermeasures() { return this.#emissionsCountermeasures }; - getTACAN() { return this.#TACAN }; - getRadio() { return this.#radio }; - getGeneralSettings() { return this.#generalSettings }; - getAmmo() { return this.#ammo }; - getContacts() { return this.#contacts }; - getActivePath() { return this.#activePath }; - getIsLeader() { return this.#isLeader }; - getOperateAs() { return this.#operateAs }; + getTask() { return this.#task }; + getUnitName() { return this.#unitName }; static getConstructor(type: string) { if (type === "GroundUnit") return GroundUnit; @@ -336,6 +338,10 @@ export class Unit extends CustomMarker { } } + setIsProtected(isProtected:boolean) { + this.#isProtected = isProtected; + } + setAlive(newAlive: boolean) { if (newAlive != this.#alive) document.dispatchEvent(new CustomEvent("unitDeath", { detail: this })); @@ -630,6 +636,10 @@ export class Unit extends CustomMarker { return getApp().getMap().getBounds().contains(this.getPosition()); } + canLandAtPoint() { + return this.getCategory() === "Helicopter"; // Only choppers can do this currently + } + canTargetPoint() { return this.getDatabase()?.getByName(this.#name)?.canTargetPoint === true; } diff --git a/client/src/unit/unitsmanager.ts b/client/src/unit/unitsmanager.ts index 7e6fa97d..28b8639f 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`); } /*********************** Control operations on selected units ************************/ @@ -709,7 +730,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) { @@ -750,7 +771,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; }); @@ -760,7 +781,6 @@ export class UnitsManager { } const doDelete = (explosion = false, immediate = false) => { - const selectedUnits = this.getSelectedUnits(); for (let idx in selectedUnits) { selectedUnits[idx].delete(explosion, immediate); } @@ -786,7 +806,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) => { @@ -1065,6 +1085,7 @@ export class UnitsManager { window.setTimeout(() => { document.dispatchEvent(new CustomEvent("unitsSelection", { detail: this.getSelectedUnits() })); this.#selectionEventDisabled = false; + this.#showNumberOfSelectedProtectedUnits(); }, 100); this.#selectionEventDisabled = true; } @@ -1126,4 +1147,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