diff --git a/client/demo.js b/client/demo.js index d7f970df..6530f21b 100644 --- a/client/demo.js +++ b/client/demo.js @@ -15,7 +15,7 @@ const DEMO_UNIT_DATA = { radio: { frequency: 124000000, callsign: 1, callsignNumber: 1 }, generalSettings: { prohibitAA: false, prohibitAfterburner: false, prohibitAG: false, prohibitAirWpn: false, prohibitJettison: false }, ammo: [{ quantity: 2, name: "A cool missile\0Ciao", guidance: 0, category: 0, missileCategory: 0 } ], - contacts: [{ID: 2, detectionMethod: 1}, {ID: 3, detectionMethod: 4}], + contacts: [{ID: 2, detectionMethod: 1}, {ID: 3, detectionMethod: 4}, {ID: 5, detectionMethod: 4}], activePath: [{lat: 38, lng: -115, alt: 0}, {lat: 38, lng: -114, alt: 0}] }, ["2"]:{ category: "Aircraft", alive: true, human: false, controlled: false, coalition: 1, country: 0, name: "FA-18C_hornet", unitName: "Cool guy 1-2", groupName: "Cool group 2", state: 1, task: "Being cool", diff --git a/client/public/stylesheets/olympus.css b/client/public/stylesheets/olympus.css index fabc3105..9c40b7f1 100644 --- a/client/public/stylesheets/olympus.css +++ b/client/public/stylesheets/olympus.css @@ -1031,13 +1031,41 @@ nav.ol-panel> :last-child { } .ol-target-icon { - background-image: url("/resources/theme/images/markers/target.svg"); height: 52px; pointer-events: none; width: 52px; z-index: 9999; } +.ol-smoke-icon { + background-image: url("/resources/theme/images/markers/smoke.svg"); + height: 52px; + pointer-events: none; + width: 52px; + z-index: 9999; + opacity: 0.8; +} + +.ol-smoke-icon[data-color="white"] { + fill: white; +} + +.ol-smoke-icon[data-color="blue"] { + fill: blue; +} + +.ol-smoke-icon[data-color="red"] { + fill: red; +} + +.ol-smoke-icon[data-color="green"] { + fill: green; +} + +.ol-smoke-icon[data-color="orange"] { + fill: orange; +} + .ol-draw-icon { background-image: url("/resources/theme/images/markers/draw.svg"); height: 24px; diff --git a/client/public/themes/olympus/images/markers/smoke.svg b/client/public/themes/olympus/images/markers/smoke.svg new file mode 100644 index 00000000..4c058371 --- /dev/null +++ b/client/public/themes/olympus/images/markers/smoke.svg @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/themes/olympus/images/markers/target - Copy.svg b/client/public/themes/olympus/images/markers/target - Copy.svg new file mode 100644 index 00000000..7afbf612 --- /dev/null +++ b/client/public/themes/olympus/images/markers/target - Copy.svg @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + diff --git a/client/public/themes/olympus/images/markers/temporary-icon.png b/client/public/themes/olympus/images/markers/temporary-icon.png deleted file mode 100644 index 712221a0..00000000 Binary files a/client/public/themes/olympus/images/markers/temporary-icon.png and /dev/null differ diff --git a/client/src/@types/dom.d.ts b/client/src/@types/dom.d.ts index a3712343..dc70c183 100644 --- a/client/src/@types/dom.d.ts +++ b/client/src/@types/dom.d.ts @@ -19,7 +19,7 @@ interface CustomEventMap { "mapStateChanged": CustomEvent, "mapContextMenu": CustomEvent<>, "mapVisibilityOptionsChanged": CustomEvent<>, - "commandModeOptionsChanged": CustomEvent<>, + "commandModeOptionsChanged": CustomEvent<>, "contactsUpdated": CustomEvent, } diff --git a/client/src/constants/constants.ts b/client/src/constants/constants.ts index f4216afb..1013f6e3 100644 --- a/client/src/constants/constants.ts +++ b/client/src/constants/constants.ts @@ -138,15 +138,16 @@ export const MOVE_UNIT = "Move unit"; export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area"; export const visibilityControls: string[] = ["human", "dcs", "aircraft", "groundunit-sam", "groundunit-other", "navyunit", "airbase"]; export const visibilityControlsTypes: string[][] = [["human"], ["dcs"], ["aircraft"], ["groundunit-sam", "groundunit-sam-radar", "groundunit-sam-launcher"], ["groundunit-other", "groundunit-ewr"], ["navyunit"], ["airbase"]]; -export const visibilityControlsTootlips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"]; +export const visibilityControlsTooltips: string[] = ["Toggle human players visibility", "Toggle DCS controlled units visibility", "Toggle aircrafts visibility", "Toggle SAM units visibility", "Toggle ground units (not SAM) visibility", "Toggle navy units visibility", "Toggle airbases visibility"]; export const 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}; export const SHOW_CONTACT_LINES = "Show unit contact lines"; export const HIDE_GROUP_MEMBERS = "Hide group members when zoomed out"; -export const SHOW_UNIT_PATHS = "Show unit paths"; -export const SHOW_UNIT_TARGETS = "Show unit targets"; +export const SHOW_UNIT_LABELS = "Show unit labels"; +export const SHOW_UNIT_PATHS = "Show unit paths"; +export const SHOW_UNIT_TARGETS = "Show unit targets"; export enum DataIndexes { startOfData = 0, diff --git a/client/src/controls/mapcontextmenu.ts b/client/src/controls/mapcontextmenu.ts index 1d51952c..1dd906ad 100644 --- a/client/src/controls/mapcontextmenu.ts +++ b/client/src/controls/mapcontextmenu.ts @@ -12,6 +12,7 @@ import { ftToM } from "../other/utils"; import { GAME_MASTER } from "../constants/constants"; import { navyUnitDatabase } from "../unit/navyunitdatabase"; import { CoalitionArea } from "../map/coalitionarea"; +import { SmokeMarker } from "../map/smokemarker"; export class MapContextMenu extends ContextMenu { #coalitionSwitch: Switch; @@ -158,6 +159,8 @@ export class MapContextMenu extends ContextMenu { document.addEventListener("contextMenuDeploySmoke", (e: any) => { this.hide(); spawnSmoke(e.detail.color, this.getLatLng()); + var marker = new SmokeMarker(this.getLatLng(), e.detail.color); + marker.addTo(getMap()); }); document.addEventListener("contextMenuExplosion", (e: any) => { diff --git a/client/src/index.ts b/client/src/index.ts index 8a86d75e..e16f8562 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -137,9 +137,6 @@ function setupEvents() { return; } switch (ev.code) { - case "KeyL": - document.body.toggleAttribute("data-hide-labels"); - break; case "KeyT": toggleDemoEnabled(); break; diff --git a/client/src/map/map.ts b/client/src/map/map.ts index 6dfd0222..f809a388 100644 --- a/client/src/map/map.ts +++ b/client/src/map/map.ts @@ -12,7 +12,7 @@ import { DestinationPreviewMarker } from "./destinationpreviewmarker"; import { TemporaryUnitMarker } from "./temporaryunitmarker"; import { ClickableMiniMap } from "./clickableminimap"; import { SVGInjector } from '@tanem/svg-injector' -import { layers as mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, visibilityControls, visibilityControlsTootlips, MOVE_UNIT, SHOW_CONTACT_LINES, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, visibilityControlsTypes } from "../constants/constants"; +import { layers as mapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, visibilityControls, visibilityControlsTootlips, MOVE_UNIT, SHOW_CONTACT_LINES, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, visibilityControlsTypes, SHOW_UNIT_LABELS } from "../constants/constants"; import { TargetMarker } from "./targetmarker"; import { CoalitionArea } from "./coalitionarea"; import { CoalitionAreaContextMenu } from "../controls/coalitionareacontextmenu"; @@ -45,7 +45,7 @@ export class Map extends L.Map { #temporaryMarkers: TemporaryUnitMarker[] = []; #selecting: boolean = false; #isZooming: boolean = false; - + #destinationGroupRotation: number = 0; #computeDestinationRotation: boolean = false; #destinationRotationCenter: L.LatLng | null = null; @@ -65,7 +65,7 @@ export class Map extends L.Map { #mapSourceDropdown: Dropdown; #mapVisibilityOptionsDropdown: Dropdown; #optionButtons: { [key: string]: HTMLButtonElement[] } = {} - #visibiityOptions: { [key: string]: boolean } = {} + #visibilityOptions: { [key: string]: boolean } = {} constructor(ID: string) { /* Init the leaflet map */ @@ -91,7 +91,7 @@ export class Map extends L.Map { this.#mapSourceDropdown = new Dropdown("map-type", (layerName: string) => this.setLayer(layerName), this.getLayers()); /* Visibility options dropdown */ - this.#mapVisibilityOptionsDropdown = new Dropdown("map-visibility-options", () => {}); + this.#mapVisibilityOptionsDropdown = new Dropdown("map-visibility-options", (value: string) => { }); /* Init the state machine */ this.#state = IDLE; @@ -134,7 +134,7 @@ export class Map extends L.Map { }) } }); - + document.addEventListener("toggleCoalitionAreaDraw", (ev: CustomEventInit) => { this.getMapContextMenu().hide(); this.deselectAllCoalitionAreas(); @@ -151,6 +151,10 @@ export class Map extends L.Map { this.#panToUnit(this.#centerUnit); }); + document.addEventListener("mapVisibilityOptionsChanged", () => { + this.getContainer().toggleAttribute("data-hide-labels", !this.getVisibilityOptions()[SHOW_UNIT_LABELS]); + }); + /* Pan interval */ this.#panInterval = window.setInterval(() => { if (this.#panLeft || this.#panDown || this.#panRight || this.#panLeft) @@ -161,19 +165,19 @@ export class Map extends L.Map { /* 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`, visibilityControlsTootlips[index], "toggleMarkerVisibility", `{"types": [${typesArrayString}]}`); + 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.#visibiityOptions[SHOW_CONTACT_LINES] = false; - this.#visibiityOptions[HIDE_GROUP_MEMBERS] = true; - this.#visibiityOptions[SHOW_UNIT_PATHS] = true; - this.#visibiityOptions[SHOW_UNIT_TARGETS] = true; - this.#mapVisibilityOptionsDropdown.setOptionsElements(Object.keys(this.#visibiityOptions).map((option: string) => { - return createCheckboxOption(option, option, this.#visibiityOptions[option], (ev: any) => { + this.#visibilityOptions[SHOW_CONTACT_LINES] = false; + this.#visibilityOptions[HIDE_GROUP_MEMBERS] = true; + this.#visibilityOptions[SHOW_UNIT_PATHS] = true; + this.#visibilityOptions[SHOW_UNIT_TARGETS] = true; + this.#visibilityOptions[SHOW_UNIT_LABELS] = true; + this.#mapVisibilityOptionsDropdown.setOptionsElements(Object.keys(this.#visibilityOptions).map((option: string) => { + return createCheckboxOption(option, option, this.#visibilityOptions[option], (ev: any) => { this.#setVisibilityOption(option, ev); }); })); @@ -436,7 +440,7 @@ export class Map extends L.Map { } getVisibilityOptions() { - return this.#visibiityOptions; + return this.#visibilityOptions; } /* Event handlers */ @@ -482,7 +486,7 @@ export class Map extends L.Map { /* Coalition areas are ordered in the #coalitionAreas array according to their zindex. Select the upper one */ for (let coalitionArea of this.#coalitionAreas) { if (coalitionArea.getBounds().contains(e.latlng)) { - if (coalitionArea.getSelected()) + if (coalitionArea.getSelected()) clickedCoalitionArea = coalitionArea; else this.getMapContextMenu().setCoalitionArea(coalitionArea); @@ -704,7 +708,7 @@ export class Map extends L.Map { else { Object.values(getUnitsManager().selectedUnitsComputeGroupDestination(groupLatLng, this.#destinationGroupRotation)).forEach((latlng: L.LatLng, idx: number) => { if (idx < this.#destinationPreviewCursors.length) - this.#destinationPreviewCursors[idx].setLatLng(this.#shiftKey? latlng : this.getMouseCoordinates()); + this.#destinationPreviewCursors[idx].setLatLng(this.#shiftKey ? latlng : this.getMouseCoordinates()); }) }; } @@ -769,7 +773,7 @@ export class Map extends L.Map { } #setVisibilityOption(option: string, ev: any) { - this.#visibiityOptions[option] = ev.currentTarget.checked; + this.#visibilityOptions[option] = ev.currentTarget.checked; document.dispatchEvent(new CustomEvent("mapVisibilityOptionsChanged")); } } diff --git a/client/src/map/smokemarker.ts b/client/src/map/smokemarker.ts new file mode 100644 index 00000000..678912c2 --- /dev/null +++ b/client/src/map/smokemarker.ts @@ -0,0 +1,31 @@ +import { DivIcon, LatLngExpression, MarkerOptions } from "leaflet"; +import { CustomMarker } from "./custommarker"; +import { SVGInjector } from "@tanem/svg-injector"; +import { getMap } from ".."; + +export class SmokeMarker extends CustomMarker { + #color: string; + + constructor(latlng: LatLngExpression, color: string, options?: MarkerOptions) { + super(latlng, options); + this.setZIndexOffset(9999); + this.#color = color; + window.setTimeout(() => { this.removeFrom(getMap()); }, 300000) /* Remove the smoke after 5 minutes */ + } + + createIcon() { + this.setIcon(new DivIcon({ + iconSize: [52, 52], + iconAnchor: [26, 52], + className: "leaflet-smoke-marker", + })); + var el = document.createElement("div"); + el.classList.add("ol-smoke-icon"); + el.setAttribute("data-color", this.#color); + var img = document.createElement("img"); + img.src = "/resources/theme/images/markers/smoke.svg"; + img.onload = () => SVGInjector(img); + el.appendChild(img); + this.getElement()?.appendChild(el); + } +} diff --git a/client/src/unit/unit.ts b/client/src/unit/unit.ts index 59cc0035..4de688ce 100644 --- a/client/src/unit/unit.ts +++ b/client/src/unit/unit.ts @@ -76,7 +76,7 @@ export class Unit extends CustomMarker { }; #ammo: Ammo[] = []; #contacts: Contact[] = []; - #activePath: LatLng[] = []; + #activePath: LatLng[] = []; #isLeader: boolean = false; #selectable: boolean; @@ -94,43 +94,43 @@ export class Unit extends CustomMarker { #hotgroup: number | null = null; #detectionMethods: number[] = []; - getAlive() {return this.#alive}; - getHuman() {return this.#human}; - getControlled() {return this.#controlled}; - getCoalition() {return this.#coalition}; - getCountry() {return this.#country}; - getName() {return this.#name}; - getUnitName() {return this.#unitName}; - getGroupName() {return this.#groupName}; - getState() {return this.#state}; - getTask() {return this.#task}; - getHasTask() {return this.#hasTask}; - getPosition() {return this.#position}; - getSpeed() {return this.#speed}; - getHeading() {return this.#heading}; - getIsTanker() {return this.#isTanker}; - getIsAWACS() {return this.#isAWACS}; - getOnOff() {return this.#onOff}; - getFollowRoads() {return this.#followRoads}; - getFuel() {return this.#fuel}; - getDesiredSpeed() {return this.#desiredSpeed}; - getDesiredSpeedType() {return this.#desiredSpeedType}; - getDesiredAltitude() {return this.#desiredAltitude}; - getDesiredAltitudeType() {return this.#desiredAltitudeType}; - getLeaderID() {return this.#leaderID}; - getFormationOffset() {return this.#formationOffset}; - getTargetID() {return this.#targetID}; - getTargetPosition() {return this.#targetPosition}; - getROE() {return this.#ROE}; - getReactionToThreat() {return this.#reactionToThreat}; - getEmissionsCountermeasures() {return this.#emissionsCountermeasures}; - getTACAN() {return this.#TACAN}; - getRadio() {return this.#radio}; - getGeneralSettings() {return this.#generalSettings}; - getAmmo() {return this.#ammo}; - getContacts() {return this.#contacts}; - getActivePath() {return this.#activePath}; - getIsLeader() {return this.#isLeader}; + getAlive() { return this.#alive }; + getHuman() { return this.#human }; + getControlled() { return this.#controlled }; + getCoalition() { return this.#coalition }; + getCountry() { return this.#country }; + getName() { return this.#name }; + getUnitName() { return this.#unitName }; + getGroupName() { return this.#groupName }; + getState() { return this.#state }; + getTask() { return this.#task }; + getHasTask() { return this.#hasTask }; + getPosition() { return this.#position }; + getSpeed() { return this.#speed }; + getHeading() { return this.#heading }; + getIsTanker() { return this.#isTanker }; + getIsAWACS() { return this.#isAWACS }; + getOnOff() { return this.#onOff }; + getFollowRoads() { return this.#followRoads }; + getFuel() { return this.#fuel }; + getDesiredSpeed() { return this.#desiredSpeed }; + getDesiredSpeedType() { return this.#desiredSpeedType }; + getDesiredAltitude() { return this.#desiredAltitude }; + getDesiredAltitudeType() { return this.#desiredAltitudeType }; + getLeaderID() { return this.#leaderID }; + getFormationOffset() { return this.#formationOffset }; + getTargetID() { return this.#targetID }; + getTargetPosition() { return this.#targetPosition }; + getROE() { return this.#ROE }; + getReactionToThreat() { return this.#reactionToThreat }; + getEmissionsCountermeasures() { return this.#emissionsCountermeasures }; + getTACAN() { return this.#TACAN }; + getRadio() { return this.#radio }; + getGeneralSettings() { return this.#generalSettings }; + getAmmo() { return this.#ammo }; + getContacts() { return this.#contacts }; + getActivePath() { return this.#activePath }; + getIsLeader() { return this.#isLeader }; static getConstructor(type: string) { if (type === "GroundUnit") return GroundUnit; @@ -156,7 +156,7 @@ export class Unit extends CustomMarker { this.on('contextmenu', (e) => this.#onContextMenu(e)); this.on('mouseover', () => { if (this.belongsToCommandedCoalition()) this.setHighlighted(true); }) this.on('mouseout', () => { this.setHighlighted(false); }) - getMap().on("zoomend", () => {this.#onZoom();}) + getMap().on("zoomend", () => { this.#onZoom(); }) /* Deselect units if they are hidden */ document.addEventListener("toggleCoalitionVisibility", (ev: CustomEventInit) => { @@ -165,13 +165,13 @@ export class Unit extends CustomMarker { document.addEventListener("toggleUnitVisibility", (ev: CustomEventInit) => { window.setTimeout(() => { this.setSelected(this.getSelected() && !this.getHidden()) }, 300); - }); - + }); + document.addEventListener("mapVisibilityOptionsChanged", (ev: CustomEventInit) => { this.#updateMarker(); if (this.getSelected()) this.drawLines(); - }); + }); } getCategory() { @@ -222,12 +222,12 @@ export class Unit extends CustomMarker { case DataIndexes.radio: this.#radio = dataExtractor.extractRadio(); break; case DataIndexes.generalSettings: this.#generalSettings = dataExtractor.extractGeneralSettings(); break; case DataIndexes.ammo: this.#ammo = dataExtractor.extractAmmo(); break; - case DataIndexes.contacts: this.#contacts = dataExtractor.extractContacts(); document.dispatchEvent(new CustomEvent("contactsUpdated", {detail: this})); break; + case DataIndexes.contacts: this.#contacts = dataExtractor.extractContacts(); document.dispatchEvent(new CustomEvent("contactsUpdated", { detail: this })); break; case DataIndexes.activePath: this.#activePath = dataExtractor.extractActivePath(); break; case DataIndexes.isLeader: this.#isLeader = dataExtractor.extractBool(); break; } } - + /* Dead units can't be selected */ this.setSelected(this.getSelected() && this.#alive && !this.getHidden()) @@ -314,7 +314,7 @@ export class Unit extends CustomMarker { getLiveryID(): string { const liveryID = this.getDatabase()?.getByName(this.getName())?.liveryID; - return liveryID? liveryID: ""; + return liveryID ? liveryID : ""; } setAlive(newAlive: boolean) { @@ -327,7 +327,7 @@ export class Unit extends CustomMarker { /* Only alive units can be selected. Some units are not selectable (weapons) */ if ((this.#alive || !selected) && this.getSelectable() && this.getSelected() != selected && this.belongsToCommandedCoalition()) { this.#selected = selected; - + if (selected) { document.dispatchEvent(new CustomEvent("unitSelection", { detail: this })); this.#updateMarker(); @@ -346,7 +346,7 @@ export class Unit extends CustomMarker { else this.#updateMarker(); } - + } } @@ -390,7 +390,7 @@ export class Unit extends CustomMarker { belongsToCommandedCoalition() { if (getMissionHandler().getCommandModeOptions().commandMode !== GAME_MASTER && getMissionHandler().getCommandedCoalition() !== this.#coalition) return false; - return true; + return true; } getType() { @@ -438,7 +438,15 @@ export class Unit extends CustomMarker { var unitIcon = document.createElement("div"); unitIcon.classList.add("unit-icon"); var img = document.createElement("img"); - img.src = `/resources/theme/images/units/${this.getMarkerCategory()}.svg`; + var imgSrc; + + /* If a unit does not belong to the commanded coalition or it is not visually detected, show it with the generic aircraft square */ + if (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value))) + imgSrc = this.getMarkerCategory(); + else + imgSrc = "aircraft"; + + img.src = `/resources/theme/images/units/${imgSrc}.svg`; img.onload = () => SVGInjector(img); unitIcon.appendChild(img); unitIcon.toggleAttribute("data-rotate-to-heading", iconOptions.rotateToHeading); @@ -503,12 +511,12 @@ export class Unit extends CustomMarker { updateVisibility() { const hiddenUnits = getUnitsManager().getHiddenTypes(); var hidden = ((this.#human && hiddenUnits.includes("human")) || - (this.#controlled == false && hiddenUnits.includes("dcs")) || - (hiddenUnits.includes(this.getMarkerCategory())) || - (hiddenUnits.includes(this.#coalition)) || - (!this.belongsToCommandedCoalition() && (this.#detectionMethods.length == 0 || (this.#detectionMethods.length == 1 && this.#detectionMethods[0] === RWR))) || - (getMap().getVisibilityOptions()[HIDE_GROUP_MEMBERS] && !this.#isLeader && this.getCategory() == "GroundUnit" && getMap().getZoom() < 13 && (this.belongsToCommandedCoalition() || (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0)))) && - !(this.getSelected()); + (this.#controlled == false && hiddenUnits.includes("dcs")) || + (hiddenUnits.includes(this.getMarkerCategory())) || + (hiddenUnits.includes(this.#coalition)) || + (!this.belongsToCommandedCoalition() && (this.#detectionMethods.length == 0 || (this.#detectionMethods.length == 1 && this.#detectionMethods[0] === RWR))) || + (getMap().getVisibilityOptions()[HIDE_GROUP_MEMBERS] && !this.#isLeader && this.getCategory() == "GroundUnit" && getMap().getZoom() < 13 && (this.belongsToCommandedCoalition() || (!this.belongsToCommandedCoalition() && this.#detectionMethods.length == 0)))) && + !(this.getSelected()); this.setHidden(hidden || !this.#alive); } @@ -518,9 +526,9 @@ export class Unit extends CustomMarker { /* Add the marker if not present */ if (!getMap().hasLayer(this) && !this.getHidden()) { - if (getMap().isZooming()) - this.once("zoomend", () => {this.addTo(getMap())}) - else + if (getMap().isZooming()) + this.once("zoomend", () => { this.addTo(getMap()) }) + else this.addTo(getMap()); } @@ -563,10 +571,22 @@ export class Unit extends CustomMarker { return loadouts.some((loadout: LoadoutBlueprint) => { return (roles as string[]).some((role: string) => { return loadout.roles.includes(role) }); }); - } else + } else return false; } + isInViewport() { + + const mapBounds = getMap().getBounds(); + const unitPos = this.getPosition(); + + return (unitPos.lng > mapBounds.getWest() + && unitPos.lng < mapBounds.getEast() + && unitPos.lat > mapBounds.getSouth() + && unitPos.lat < mapBounds.getNorth()); + + } + /********************** Unit commands *************************/ addDestination(latlng: L.LatLng) { if (!this.#human) { @@ -708,24 +728,28 @@ export class Unit extends CustomMarker { #onClick(e: any) { if (!this.#preventClick) { if (getMap().getState() === IDLE || getMap().getState() === MOVE_UNIT || e.originalEvent.ctrlKey) { - if (!e.originalEvent.ctrlKey) + if (!e.originalEvent.ctrlKey) getUnitsManager().deselectAllUnits(); - - this.setSelected( !this.getSelected() ); - if ( this.getSelected() ) { - document.dispatchEvent( new CustomEvent( "unitSelection", { "detail": this })); - } else { - document.dispatchEvent( new CustomEvent( "unitDeselection", { "detail": this })); - } + + this.setSelected(!this.getSelected()); + const detail = { "detail": { "unit": this } }; + if (this.getSelected()) + document.dispatchEvent(new CustomEvent("unitSelected", detail)); + else + document.dispatchEvent(new CustomEvent("unitDeselection", { "detail": this })); } } - this.#timer = window.setTimeout(() => { - this.#preventClick = false; - }, 200); + this.#timer = window.setTimeout(() => { this.#preventClick = false; }, 200); } #onDoubleClick(e: any) { + const unitsManager = getUnitsManager(); + Object.values(unitsManager.getUnits()).forEach((unit: Unit) => { + if (unit.getAlive() === true && unit.getName() === this.getName() && unit.isInViewport()) + unitsManager.selectUnit(unit.ID, false); + }); + clearTimeout(this.#timer); this.#preventClick = true; } @@ -747,7 +771,7 @@ export class Unit extends CustomMarker { options["refuel"] = { text: "Air to air refuel", tooltip: "Refuel units at the nearest AAR Tanker. If no tanker is available the unit will RTB." }; // TODO Add some way of knowing which aircraft can AAR } } - + if (selectedUnitTypes.length === 1 && ["NavyUnit", "GroundUnit"].includes(selectedUnitTypes[0]) && getUnitsManager().getSelectedUnitsVariable((unit: Unit) => {return unit.getCoalition()}) !== undefined) options["group"] = { text: "Create group", tooltip: "Create a group from the selected units." }; @@ -791,7 +815,7 @@ export class Unit extends CustomMarker { getMap().hideUnitContextMenu(); this.#applyFollowOptions(option); }); - + getMap().showUnitContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng); } @@ -1050,16 +1074,17 @@ export class Unit extends CustomMarker { export class AirUnit extends Unit { getIconOptions() { + var belongsToCommandedCoalition = this.belongsToCommandedCoalition(); return { - showState: this.belongsToCommandedCoalition(), - showVvi: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), - showHotgroup: this.belongsToCommandedCoalition(), - showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), - showShortLabel: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value))), - showFuel: this.belongsToCommandedCoalition(), - showAmmo: this.belongsToCommandedCoalition(), - showSummary: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), - showCallsign: this.belongsToCommandedCoalition(), + showState: belongsToCommandedCoalition, + showVvi: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), + showHotgroup: belongsToCommandedCoalition, + showUnitIcon: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), + showShortLabel: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC].includes(value))), + showFuel: belongsToCommandedCoalition, + showAmmo: belongsToCommandedCoalition, + showSummary: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), + showCallsign: belongsToCommandedCoalition, rotateToHeading: false }; } @@ -1091,16 +1116,17 @@ export class GroundUnit extends Unit { } getIconOptions() { + var belongsToCommandedCoalition = this.belongsToCommandedCoalition(); return { - showState: this.belongsToCommandedCoalition(), + showState: belongsToCommandedCoalition, showVvi: false, - showHotgroup: this.belongsToCommandedCoalition(), - showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), + showHotgroup: belongsToCommandedCoalition, + showUnitIcon: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), showShortLabel: false, showFuel: false, showAmmo: false, showSummary: false, - showCallsign: this.belongsToCommandedCoalition(), + showCallsign: belongsToCommandedCoalition, rotateToHeading: false }; } @@ -1111,7 +1137,7 @@ export class GroundUnit extends Unit { getType() { var blueprint = groundUnitDatabase.getByName(this.getName()); - return blueprint?.type? blueprint.type: ""; + return blueprint?.type ? blueprint.type : ""; } } @@ -1121,16 +1147,17 @@ export class NavyUnit extends Unit { } getIconOptions() { + var belongsToCommandedCoalition = this.belongsToCommandedCoalition(); return { - showState: this.belongsToCommandedCoalition(), + showState: belongsToCommandedCoalition, showVvi: false, showHotgroup: true, - showUnitIcon: (this.belongsToCommandedCoalition() || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), + showUnitIcon: (belongsToCommandedCoalition || this.getDetectionMethods().some(value => [VISUAL, OPTIC, RADAR, IRST, DLINK].includes(value))), showShortLabel: false, showFuel: false, showAmmo: false, showSummary: false, - showCallsign: this.belongsToCommandedCoalition(), + showCallsign: belongsToCommandedCoalition, rotateToHeading: false }; } @@ -1145,6 +1172,6 @@ export class NavyUnit extends Unit { getType() { var blueprint = navyUnitDatabase.getByName(this.getName()); - return blueprint?.type? blueprint.type: ""; + return blueprint?.type ? blueprint.type : ""; } }