From ade4f183cdea0f6a3a9dabe3b479dc2dd231c3ae Mon Sep 17 00:00:00 2001 From: PeekabooSteam Date: Thu, 2 Nov 2023 18:22:42 +0000 Subject: [PATCH 1/6] Commit so I can switch to another branch. --- client/src/constants/constants.ts | 36 +++++++++++++++++++++++++++++++ client/src/map/map.ts | 29 ++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/client/src/constants/constants.ts b/client/src/constants/constants.ts index 36cd6367..b2ad78bc 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,41 @@ 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" ] +}, { + "image": "visibility/dcs.svg", + "initialState": "protected", + "name":"dcs", + "protectable": true, + "toggles": [ "dcs" ] +}, { + "image": "visibility/aircraft.svg", + "name":"aircraft", + "toggles": [ "aircraft" ] +}, { + "image": "visibility/helicopter.svg", + "name":"helicopter", + "toggles": [ "helicopter" ] +}, { + "image": "visibility/groundunit-sam.svg", + "name":"groundunit-sam", + "toggles": [ "groundunit-sam" ] +}, { + "image": "visibility/groundunit-other.svg", + "name":"groundunit-other", + "toggles": [ "groundunit-other" ] +}, { + "image": "visibility/navyunit.svg", + "name":"navyunit", + "toggles": [ "navyunit" ] +}, { + "image": "visibility/airbase.svg", + "name":"airbase", + "toggles": [ "airbase" ] +}]; 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..734a8742 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,14 @@ L.Map.addInitHook("addHandler", "gestureHandling", GestureHandling); require("../../public/javascripts/leaflet.nauticscale.js") require("../../public/javascripts/L.Path.Drag.js") +export type MapMarkerControl = { + "image": string; + "initialState"?: "protected" | null, + "name":string, + "protectable"?: boolean, + "toggles": string[] +} + export class Map extends L.Map { #ID: string; #state: string; @@ -201,12 +209,16 @@ export class Map extends L.Map { }, 20); /* Option buttons */ + this.#createOptionButtons(); + + /* 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"]); + //*/ /* Create the checkboxes to select the advanced visibility options */ this.addVisibilityOption(SHOW_UNIT_CONTACTS, false); @@ -724,6 +736,21 @@ export class Map extends L.Map { return minimapBoundaries; } + #createOptionButtons() { + const unitVisibilityControls = document.getElementById("unit-visibility-control"); + const lockHTML = ``; + MAP_MARKER_CONTROLS.forEach( (control:MapMarkerControl) => { + console.log(`{"types":"${control.toggles.join('","')}"}`); + const html = ` + + `; + unitVisibilityControls.innerHTML += html; + }); + unitVisibilityControls?.querySelectorAll(`img[src$=".svg"]`).forEach(img => SVGInjector(img)); + } + #createOptionButton(value: string, url: string, title: string, callback: string, argument: string) { var button = document.createElement("button"); const img = document.createElement("img"); From 4465f4a3bbe88f6c570a4b449486adc5b6ecd080 Mon Sep 17 00:00:00 2001 From: PeekabooSteam Date: Thu, 2 Nov 2023 23:03:17 +0000 Subject: [PATCH 2/6] PoC of robot protection --- client/public/stylesheets/style/style.css | 28 +++++++ client/src/constants/constants.ts | 2 +- client/src/map/map.ts | 73 ++++++++++-------- client/src/unit/unit.ts | 66 ++++++++-------- client/src/unit/unitsmanager.ts | 93 ++++++++++++++--------- 5 files changed, 165 insertions(+), 97 deletions(-) diff --git a/client/public/stylesheets/style/style.css b/client/public/stylesheets/style/style.css index 58ee2aa5..d1537c93 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,28 @@ 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 > div > button.lock { + display:none; +} + +.ol-navbar-buttons-group > div.protectable > button.lock { + background-color: #990000; + border-bottom-left-radius: 0; + border-top-left-radius: 0; + display:inline-block; + width:18px; +} + +.ol-navbar-buttons-group > div.protectable > button[data-protected].lock { + background-color: #008800; +} + #roe-buttons-container button, #reaction-to-threat-buttons-container button, #emissions-countermeasures-buttons-container button { diff --git a/client/src/constants/constants.ts b/client/src/constants/constants.ts index b2ad78bc..1fc27028 100644 --- a/client/src/constants/constants.ts +++ b/client/src/constants/constants.ts @@ -154,7 +154,7 @@ export const MAP_MARKER_CONTROLS:MapMarkerControl[] = [{ "toggles": [ "human" ] }, { "image": "visibility/dcs.svg", - "initialState": "protected", + "isProtected": true, "name":"dcs", "protectable": true, "toggles": [ "dcs" ] diff --git a/client/src/map/map.ts b/client/src/map/map.ts index 734a8742..952e0fe5 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -40,7 +40,7 @@ require("../../public/javascripts/L.Path.Drag.js") export type MapMarkerControl = { "image": string; - "initialState"?: "protected" | null, + "isProtected"?: boolean, "name":string, "protectable"?: boolean, "toggles": string[] @@ -88,6 +88,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 } = {} @@ -209,16 +210,7 @@ export class Map extends L.Map { }, 20); /* Option buttons */ - this.#createOptionButtons(); - - /* - 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); @@ -736,32 +728,53 @@ export class Map extends L.Map { return minimapBoundaries; } - #createOptionButtons() { + #createUnitMarkerControlButtons() { const unitVisibilityControls = document.getElementById("unit-visibility-control"); - const lockHTML = ``; - MAP_MARKER_CONTROLS.forEach( (control:MapMarkerControl) => { - console.log(`{"types":"${control.toggles.join('","')}"}`); - const html = ` - + `; - unitVisibilityControls.innerHTML += html; + unitVisibilityControls.appendChild(div); + + if ( control.protectable ) { + const btn = div.querySelector("button.lock"); + btn.addEventListener("click", (ev:MouseEventInit) => { + control.isProtected = !control.isProtected; + btn.toggleAttribute("data-protected", control.isProtected); + document.dispatchEvent(new CustomEvent("toggleMarkerProtection", { + detail: { + "_element": btn, + "control": control + } + })); + }); + } }); - unitVisibilityControls?.querySelectorAll(`img[src$=".svg"]`).forEach(img => SVGInjector(img)); + + unitVisibilityControls.querySelectorAll(`img[src$=".svg"]`).forEach(img => SVGInjector(img)); } - #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; + 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..87e9a2f1 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 })); diff --git a/client/src/unit/unitsmanager.ts b/client/src/unit/unitsmanager.ts index 7e6fa97d..47dbbc86 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,18 @@ 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 }) { + var selectedUnits:Unit[] = []; + for (const [ID, unit] of Object.entries(this.#units)) { + if (unit.getSelected()) { + if (options) { + if ((options.excludeHumans && unit.getHuman()) || (options.excludeProtected === true && this.#unitIsProtected(unit))) + continue + } + selectedUnits.push(unit); } } if (options) { - if (options.excludeHumans) - selectedUnits = selectedUnits.filter((unit: Unit) => { return !unit.getHuman() }); if (options.onlyOnePerGroup) { var temp: Unit[] = []; for (let unit of selectedUnits) { @@ -320,7 +324,7 @@ 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 }); /* 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 +357,7 @@ export class UnitsManager { * */ selectedUnitsClearDestinations() { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true }); for (let idx in selectedUnits) { const unit = selectedUnits[idx]; if (unit.getState() === "follow") { @@ -373,7 +377,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 }); for (let idx in selectedUnits) { selectedUnits[idx].landAt(latlng); } @@ -385,7 +389,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 }); for (let idx in selectedUnits) { selectedUnits[idx].changeSpeed(speedChange); } @@ -396,7 +400,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 }); for (let idx in selectedUnits) { selectedUnits[idx].changeAltitude(altitudeChange); } @@ -407,7 +411,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 }); for (let idx in selectedUnits) { selectedUnits[idx].setSpeed(speed); } @@ -419,7 +423,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 }); for (let idx in selectedUnits) { selectedUnits[idx].setSpeedType(speedType); } @@ -431,7 +435,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 }); for (let idx in selectedUnits) { selectedUnits[idx].setAltitude(altitude); } @@ -443,7 +447,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 }); for (let idx in selectedUnits) { selectedUnits[idx].setAltitudeType(altitudeType); } @@ -455,7 +459,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 }); for (let idx in selectedUnits) { selectedUnits[idx].setROE(ROE); } @@ -467,7 +471,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 }); for (let idx in selectedUnits) { selectedUnits[idx].setReactionToThreat(reactionToThreat); } @@ -479,7 +483,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 }); for (let idx in selectedUnits) { selectedUnits[idx].setEmissionsCountermeasures(emissionCountermeasure); } @@ -491,7 +495,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 }); for (let idx in selectedUnits) { selectedUnits[idx].setOnOff(onOff); } @@ -503,7 +507,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 }); for (let idx in selectedUnits) { selectedUnits[idx].setFollowRoads(followRoads); } @@ -516,7 +520,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 }); for (let idx in selectedUnits) { selectedUnits[idx].setOperateAs(operateAs); } @@ -528,7 +532,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 }); for (let idx in selectedUnits) { selectedUnits[idx].attackUnit(ID); } @@ -539,7 +543,7 @@ export class UnitsManager { * */ selectedUnitsRefuel() { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true }); for (let idx in selectedUnits) { selectedUnits[idx].refuel(); } @@ -565,7 +569,7 @@ export class UnitsManager { else offset = undefined; } - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true }); var count = 1; var xr = 0; var yr = 1; var zr = -1; @@ -601,7 +605,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 }); for (let idx in selectedUnits) { selectedUnits[idx].bombPoint(latlng); } @@ -613,7 +617,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 }); for (let idx in selectedUnits) { selectedUnits[idx].carpetBomb(latlng); } @@ -625,7 +629,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 }); for (let idx in selectedUnits) { selectedUnits[idx].fireAtArea(latlng); } @@ -637,7 +641,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 }); getGroundElevation(latlng, (response: string) => { var groundElevation: number | null = null; try { @@ -656,7 +660,7 @@ export class UnitsManager { * */ selectedUnitsScenicAAA() { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true }); for (let idx in selectedUnits) { selectedUnits[idx].scenicAAA(); } @@ -667,7 +671,7 @@ export class UnitsManager { * */ selectedUnitsMissOnPurpose() { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true }); for (let idx in selectedUnits) { selectedUnits[idx].missOnPurpose(); } @@ -679,7 +683,7 @@ 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 }); for (let idx in selectedUnits) { selectedUnits[idx].landAtPoint(latlng); @@ -709,7 +713,7 @@ export class UnitsManager { * */ selectedUnitsCreateGroup() { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: false }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: false }); if (this.getUnitsCategories(selectedUnits).length == 1) { var units: { ID: number, location: LatLng }[] = []; for (let idx in selectedUnits) { @@ -750,7 +754,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 +764,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 +789,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 +1068,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 +1130,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 From f981a661d58004bc116d835bb139e0b34b75dd4d Mon Sep 17 00:00:00 2001 From: PeekabooSteam Date: Fri, 3 Nov 2023 13:29:09 +0000 Subject: [PATCH 3/6] Added lock image and tooltip/titles. --- client/public/stylesheets/style/style.css | 30 ++++++++++---- .../images/buttons/other/lock-open-solid.svg | 1 + .../images/buttons/other/lock-solid.svg | 1 + client/src/constants/constants.ts | 40 +++++++++++-------- client/src/map/map.ts | 16 ++++++-- 5 files changed, 62 insertions(+), 26 deletions(-) create mode 100644 client/public/themes/olympus/images/buttons/other/lock-open-solid.svg create mode 100644 client/public/themes/olympus/images/buttons/other/lock-solid.svg diff --git a/client/public/stylesheets/style/style.css b/client/public/stylesheets/style/style.css index d1537c93..28247d9c 100644 --- a/client/public/stylesheets/style/style.css +++ b/client/public/stylesheets/style/style.css @@ -650,22 +650,38 @@ nav.ol-panel> :last-child { width:28px; } -.ol-navbar-buttons-group > div > button.lock { - display:none; -} - -.ol-navbar-buttons-group > div.protectable > button.lock { +.ol-navbar-buttons-group > .protectable > button.lock { + align-items: center; background-color: #990000; border-bottom-left-radius: 0; border-top-left-radius: 0; - display:inline-block; + display:flex; + justify-content: center; width:18px; } -.ol-navbar-buttons-group > div.protectable > button[data-protected].lock { +.ol-navbar-buttons-group > .protectable > button[data-protected].lock { background-color: #008800; } +.ol-navbar-buttons-group > .protectable > button.lock svg { + filter:invert(100); + height:10px; + width:10px; +} + +.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 1fc27028..5d2c18ee 100644 --- a/client/src/constants/constants.ts +++ b/client/src/constants/constants.ts @@ -149,39 +149,47 @@ export const visibilityControls: string[] = ["human", "dcs", "aircraft", "helico 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", + "name":"Human", "image": "visibility/human.svg", - "toggles": [ "human" ] + "toggles": [ "human" ], + "tooltip": "Toggle human players' visibility" }, { "image": "visibility/dcs.svg", "isProtected": true, - "name":"dcs", + "name":"DCS", "protectable": true, - "toggles": [ "dcs" ] + "toggles": [ "dcs" ], + "tooltip": "Toggle DCS-controlled units' visibility" }, { "image": "visibility/aircraft.svg", - "name":"aircraft", - "toggles": [ "aircraft" ] + "name":"Aircraft", + "toggles": [ "aircraft" ], + "tooltip": "Toggle aircraft's visibility" }, { "image": "visibility/helicopter.svg", - "name":"helicopter", - "toggles": [ "helicopter" ] + "name":"Helicopter", + "toggles": [ "helicopter" ], + "tooltip": "Toggle helicopters' visibility" }, { "image": "visibility/groundunit-sam.svg", - "name":"groundunit-sam", - "toggles": [ "groundunit-sam" ] + "name":"Air defence", + "toggles": [ "groundunit-sam" ], + "tooltip": "Toggle air defence units' visibility" }, { "image": "visibility/groundunit-other.svg", - "name":"groundunit-other", - "toggles": [ "groundunit-other" ] + "name":"Ground units", + "toggles": [ "groundunit-other" ], + "tooltip": "Toggle ground units' visibility" }, { "image": "visibility/navyunit.svg", - "name":"navyunit", - "toggles": [ "navyunit" ] + "name":"Naval", + "toggles": [ "navyunit" ], + "tooltip": "Toggle naval units' visibility" }, { "image": "visibility/airbase.svg", - "name":"airbase", - "toggles": [ "airbase" ] + "name":"Airbase", + "toggles": [ "airbase" ], + "tooltip": "Toggle airbase' visibility" }]; export const IADSTypes = ["AAA", "MANPADS", "SAM Site", "Radar"]; diff --git a/client/src/map/map.ts b/client/src/map/map.ts index 952e0fe5..dc8f216a 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -43,7 +43,8 @@ export type MapMarkerControl = { "isProtected"?: boolean, "name":string, "protectable"?: boolean, - "toggles": string[] + "toggles": string[], + "tooltip": string } export class Map extends L.Map { @@ -730,23 +731,32 @@ export class Map extends L.Map { #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, From 2e5ee933615875c0de2e31ca9f11856f459274ff Mon Sep 17 00:00:00 2001 From: PeekabooSteam Date: Fri, 3 Nov 2023 14:05:39 +0000 Subject: [PATCH 4/6] Tweaked colours. --- client/public/stylesheets/style/style.css | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/client/public/stylesheets/style/style.css b/client/public/stylesheets/style/style.css index 28247d9c..dd5c7381 100644 --- a/client/public/stylesheets/style/style.css +++ b/client/public/stylesheets/style/style.css @@ -652,7 +652,7 @@ nav.ol-panel> :last-child { .ol-navbar-buttons-group > .protectable > button.lock { align-items: center; - background-color: #990000; + background-color: var(--primary-red); border-bottom-left-radius: 0; border-top-left-radius: 0; display:flex; @@ -661,15 +661,18 @@ nav.ol-panel> :last-child { } .ol-navbar-buttons-group > .protectable > button[data-protected].lock { - background-color: #008800; + background-color: var(--background-grey); } .ol-navbar-buttons-group > .protectable > button.lock svg { - filter:invert(100); 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; From 31405119f01a077e95d7a6bebed95c0a049e7b42 Mon Sep 17 00:00:00 2001 From: PeekabooSteam Date: Fri, 3 Nov 2023 18:17:40 +0000 Subject: [PATCH 5/6] Enabled mixed-group actions, fixed some helicopter messages. --- client/src/map/map.ts | 2 +- client/src/unit/unitsmanager.ts | 79 ++++++++++++++++++++------------- 2 files changed, 49 insertions(+), 32 deletions(-) diff --git a/client/src/map/map.ts b/client/src/map/map.ts index dc8f216a..d350327d 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -620,7 +620,7 @@ export class Map extends L.Map { 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 { + } else if (selectedUnitTypes[0] !== "Helicopter") { (getApp().getPopupsManager().get("infoPopup") as Popup).setText(`Selected units can not perform point actions.`); } } diff --git a/client/src/unit/unitsmanager.ts b/client/src/unit/unitsmanager.ts index 47dbbc86..28b8639f 100644 --- a/client/src/unit/unitsmanager.ts +++ b/client/src/unit/unitsmanager.ts @@ -223,18 +223,29 @@ export class UnitsManager { * @param options Selection options * @returns Array of selected units */ - getSelectedUnits(options?: { excludeHumans?: boolean, excludeProtected?:boolean, onlyOnePerGroup?: boolean }) { - var selectedUnits:Unit[] = []; + 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()) || (options.excludeProtected === true && this.#unitIsProtected(unit))) + if (options.excludeHumans && unit.getHuman()) continue + + if (options.excludeProtected === true && this.#unitIsProtected(unit)) { + numProtectedUnits++; + continue; + } } selectedUnits.push(unit); } } if (options) { + 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) { @@ -324,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, excludeProtected: 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 } = {}; @@ -357,7 +371,7 @@ export class UnitsManager { * */ selectedUnitsClearDestinations() { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: 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") { @@ -377,7 +391,7 @@ export class UnitsManager { * @param latlng Location where to land at */ selectedUnitsLandAt(latlng: LatLng) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].landAt(latlng); } @@ -389,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, excludeProtected: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].changeSpeed(speedChange); } @@ -400,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, excludeProtected: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].changeAltitude(altitudeChange); } @@ -411,7 +425,7 @@ export class UnitsManager { * @param speed Value to set, in m/s */ selectedUnitsSetSpeed(speed: number) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].setSpeed(speed); } @@ -423,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, excludeProtected: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].setSpeedType(speedType); } @@ -435,7 +449,7 @@ export class UnitsManager { * @param altitude Value to set, in m */ selectedUnitsSetAltitude(altitude: number) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].setAltitude(altitude); } @@ -447,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, excludeProtected: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].setAltitudeType(altitudeType); } @@ -459,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, excludeProtected: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].setROE(ROE); } @@ -471,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, excludeProtected: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].setReactionToThreat(reactionToThreat); } @@ -483,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, excludeProtected: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].setEmissionsCountermeasures(emissionCountermeasure); } @@ -495,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, excludeProtected: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].setOnOff(onOff); } @@ -507,7 +521,7 @@ export class UnitsManager { * @param followRoads If true, units will follow roads */ selectedUnitsSetFollowRoads(followRoads: boolean) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].setFollowRoads(followRoads); } @@ -520,7 +534,7 @@ export class UnitsManager { */ selectedUnitsSetOperateAs(operateAsBool: boolean) { var operateAs = operateAsBool? "blue": "red"; - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].setOperateAs(operateAs); } @@ -532,7 +546,7 @@ export class UnitsManager { * @param ID ID of the unit to attack */ selectedUnitsAttackUnit(ID: number) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].attackUnit(ID); } @@ -543,7 +557,7 @@ export class UnitsManager { * */ selectedUnitsRefuel() { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].refuel(); } @@ -569,7 +583,10 @@ export class UnitsManager { else offset = undefined; } - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: 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; @@ -605,7 +622,7 @@ export class UnitsManager { * @param latlng Location to bomb */ selectedUnitsBombPoint(latlng: LatLng) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].bombPoint(latlng); } @@ -617,7 +634,7 @@ export class UnitsManager { * @param latlng Location to bomb */ selectedUnitsCarpetBomb(latlng: LatLng) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].carpetBomb(latlng); } @@ -629,7 +646,7 @@ export class UnitsManager { * @param latlng Location to fire at */ selectedUnitsFireAtArea(latlng: LatLng) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].fireAtArea(latlng); } @@ -641,7 +658,7 @@ export class UnitsManager { * @param latlng Location to fire at */ selectedUnitsSimulateFireFight(latlng: LatLng) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: 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 { @@ -660,7 +677,7 @@ export class UnitsManager { * */ selectedUnitsScenicAAA() { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true }); + var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: true, onlyOnePerGroup: true, showProtectionReminder: true }); for (let idx in selectedUnits) { selectedUnits[idx].scenicAAA(); } @@ -671,11 +688,11 @@ export class UnitsManager { * */ selectedUnitsMissOnPurpose() { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: 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 @@ -683,12 +700,12 @@ export class UnitsManager { * @param latlng Point where to land */ selectedUnitsLandAtPoint(latlng: LatLng) { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: 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 ************************/ @@ -713,7 +730,7 @@ export class UnitsManager { * */ selectedUnitsCreateGroup() { - var selectedUnits = this.getSelectedUnits({ excludeHumans: true, excludeProtected: 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) { From 71066460425dd352c388008d212e06d4ffe2b0f6 Mon Sep 17 00:00:00 2001 From: PeekabooSteam Date: Fri, 3 Nov 2023 18:28:05 +0000 Subject: [PATCH 6/6] Updated landAtPoint logic --- client/src/map/map.ts | 8 ++++---- client/src/unit/unit.ts | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/client/src/map/map.ts b/client/src/map/map.ts index d350327d..06a95d15 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -613,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 (selectedUnitTypes[0] !== "Helicopter") { + } + + 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() })) { diff --git a/client/src/unit/unit.ts b/client/src/unit/unit.ts index 87e9a2f1..c79c0c18 100644 --- a/client/src/unit/unit.ts +++ b/client/src/unit/unit.ts @@ -636,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; }