mirror of
https://github.com/Pax1601/DCSOlympus.git
synced 2025-10-29 16:56:34 +00:00
feat(map): added coordinates copy feature
This commit is contained in:
@@ -892,3 +892,19 @@ export class WeaponsRefreshedEvent {
|
|||||||
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class CoordinatesFreezeEvent {
|
||||||
|
static on(callback: () => void) {
|
||||||
|
document.addEventListener(
|
||||||
|
this.name,
|
||||||
|
(ev: CustomEventInit) => {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
static dispatch() {
|
||||||
|
document.dispatchEvent(new CustomEvent(this.name));
|
||||||
|
if (DEBUG) console.log(`Event ${this.name} dispatched`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ import {
|
|||||||
ConfigLoadedEvent,
|
ConfigLoadedEvent,
|
||||||
ContextActionChangedEvent,
|
ContextActionChangedEvent,
|
||||||
ContextActionSetChangedEvent,
|
ContextActionSetChangedEvent,
|
||||||
|
CoordinatesFreezeEvent,
|
||||||
HiddenTypesChangedEvent,
|
HiddenTypesChangedEvent,
|
||||||
MapContextMenuRequestEvent,
|
MapContextMenuRequestEvent,
|
||||||
MapOptionsChangedEvent,
|
MapOptionsChangedEvent,
|
||||||
@@ -509,7 +510,7 @@ export class Map extends L.Map {
|
|||||||
code: "ShiftLeft",
|
code: "ShiftLeft",
|
||||||
altKey: false,
|
altKey: false,
|
||||||
ctrlKey: false,
|
ctrlKey: false,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setLayerName(layerName: string) {
|
setLayerName(layerName: string) {
|
||||||
@@ -1031,6 +1032,7 @@ export class Map extends L.Map {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#onLeftShortClick(e: L.LeafletMouseEvent) {
|
#onLeftShortClick(e: L.LeafletMouseEvent) {
|
||||||
|
CoordinatesFreezeEvent.dispatch();
|
||||||
if (Date.now() - this.#leftMouseDownEpoch < SHORT_PRESS_MILLISECONDS) {
|
if (Date.now() - this.#leftMouseDownEpoch < SHORT_PRESS_MILLISECONDS) {
|
||||||
this.#debounceTimeout = window.setTimeout(() => {
|
this.#debounceTimeout = window.setTimeout(() => {
|
||||||
if (!this.#isSelecting) {
|
if (!this.#isSelecting) {
|
||||||
|
|||||||
@@ -769,4 +769,8 @@ export function secondsToTimeString(seconds: number) {
|
|||||||
const secs = Math.floor(seconds % 60);
|
const secs = Math.floor(seconds % 60);
|
||||||
|
|
||||||
return `${zeroPad(hours, 2)}:${zeroPad(minutes, 2)}:${zeroPad(secs, 2)}`;
|
return `${zeroPad(hours, 2)}:${zeroPad(minutes, 2)}:${zeroPad(secs, 2)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isTrustedEnvironment() {
|
||||||
|
return window.location.protocol === "https:";
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@ import React, { useState } from "react";
|
|||||||
import { LatLng } from "leaflet";
|
import { LatLng } from "leaflet";
|
||||||
import { ConvertDDToDMS, DDToDDM, latLngToMGRS, latLngToUTM, zeroAppend } from "../../other/utils";
|
import { ConvertDDToDMS, DDToDDM, latLngToMGRS, latLngToUTM, zeroAppend } from "../../other/utils";
|
||||||
|
|
||||||
export function OlLocation(props: { location: LatLng; className?: string; referenceSystem?: string; onClick?: () => void }) {
|
export function OlLocation(props: { location: LatLng; className?: string; referenceSystem?: string; onClick?: () => void; onRefSystemChange?: (refSystem: string) => any }) {
|
||||||
const [referenceSystem, setReferenceSystem] = props.referenceSystem ? [props.referenceSystem, () => {}] : useState("LatLngDec");
|
const [referenceSystem, setReferenceSystem] = props.referenceSystem ? [props.referenceSystem, () => {}] : useState("LatLngDec");
|
||||||
const MGRS = latLngToMGRS(props.location.lat, props.location.lng, 6);
|
const MGRS = latLngToMGRS(props.location.lat, props.location.lng, 6);
|
||||||
if (referenceSystem === "MGRS") {
|
if (referenceSystem === "MGRS") {
|
||||||
@@ -17,6 +17,7 @@ export function OlLocation(props: { location: LatLng; className?: string; refere
|
|||||||
? props.onClick
|
? props.onClick
|
||||||
: (ev) => {
|
: (ev) => {
|
||||||
setReferenceSystem("LatLngDec");
|
setReferenceSystem("LatLngDec");
|
||||||
|
props.onRefSystemChange ? props.onRefSystemChange("LatLngDec") : null;
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -44,6 +45,7 @@ export function OlLocation(props: { location: LatLng; className?: string; refere
|
|||||||
? props.onClick
|
? props.onClick
|
||||||
: (ev) => {
|
: (ev) => {
|
||||||
setReferenceSystem("LatLngDMS");
|
setReferenceSystem("LatLngDMS");
|
||||||
|
props.onRefSystemChange ? props.onRefSystemChange("LatLngDMS") : null;
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,6 +85,7 @@ export function OlLocation(props: { location: LatLng; className?: string; refere
|
|||||||
? props.onClick
|
? props.onClick
|
||||||
: (ev) => {
|
: (ev) => {
|
||||||
setReferenceSystem("LatLngDDM");
|
setReferenceSystem("LatLngDDM");
|
||||||
|
props.onRefSystemChange ? props.onRefSystemChange("LatLngDDM") : null;
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,6 +125,7 @@ export function OlLocation(props: { location: LatLng; className?: string; refere
|
|||||||
? props.onClick
|
? props.onClick
|
||||||
: (ev) => {
|
: (ev) => {
|
||||||
setReferenceSystem("MGRS");
|
setReferenceSystem("MGRS");
|
||||||
|
props.onRefSystemChange ? props.onRefSystemChange("MGRS") : null;
|
||||||
ev.stopPropagation();
|
ev.stopPropagation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
import { OlLocation } from "../components/ollocation";
|
import { OlLocation } from "../components/ollocation";
|
||||||
import { LatLng } from "leaflet";
|
import { LatLng } from "leaflet";
|
||||||
import { FaBullseye, FaChevronDown, FaChevronUp, FaJetFighter, FaMountain } from "react-icons/fa6";
|
import { FaBullseye, FaChevronDown, FaChevronUp, FaJetFighter, FaMountain, FaCopy, FaXmark } from "react-icons/fa6";
|
||||||
import { BullseyesDataChangedEvent, MouseMovedEvent, SelectedUnitsChangedEvent, SelectionClearedEvent } from "../../events";
|
import { BullseyesDataChangedEvent, CoordinatesFreezeEvent, MouseMovedEvent, SelectedUnitsChangedEvent, SelectionClearedEvent } from "../../events";
|
||||||
import { computeBearingRangeString, mToFt } from "../../other/utils";
|
import { computeBearingRangeString, ConvertDDToDMS, DDToDDM, isTrustedEnvironment, latLngToMGRS, mToFt, zeroAppend } from "../../other/utils";
|
||||||
import { Bullseye } from "../../mission/bullseye";
|
import { Bullseye } from "../../mission/bullseye";
|
||||||
import { Unit } from "../../unit/unit";
|
import { Unit } from "../../unit/unit";
|
||||||
|
import { getApp } from "../../olympusapp";
|
||||||
|
|
||||||
export function CoordinatesPanel(props: {}) {
|
export function CoordinatesPanel(props: {}) {
|
||||||
const [latlng, setLatlng] = useState(new LatLng(0, 0));
|
const [latlng, setLatlng] = useState(new LatLng(0, 0));
|
||||||
@@ -13,7 +14,10 @@ export function CoordinatesPanel(props: {}) {
|
|||||||
const [bullseyes, setBullseyes] = useState(null as null | { [name: string]: Bullseye });
|
const [bullseyes, setBullseyes] = useState(null as null | { [name: string]: Bullseye });
|
||||||
const [selectedUnits, setSelectedUnits] = useState([] as Unit[]);
|
const [selectedUnits, setSelectedUnits] = useState([] as Unit[]);
|
||||||
const [open, setOpen] = useState(true);
|
const [open, setOpen] = useState(true);
|
||||||
|
const [copyCoordsOpen, setCopyCoordsOpen] = useState(false);
|
||||||
|
const [refSystem, setRefSystem] = useState("LatLngDec");
|
||||||
|
const [copyableCoordinates, setCopyableCoordinates] = useState("To start, click any point on the map.");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
MouseMovedEvent.on((latlng, elevation) => {
|
MouseMovedEvent.on((latlng, elevation) => {
|
||||||
setLatlng(latlng);
|
setLatlng(latlng);
|
||||||
@@ -23,18 +27,43 @@ export function CoordinatesPanel(props: {}) {
|
|||||||
BullseyesDataChangedEvent.on((bullseyes) => setBullseyes(bullseyes));
|
BullseyesDataChangedEvent.on((bullseyes) => setBullseyes(bullseyes));
|
||||||
SelectedUnitsChangedEvent.on((selectedUnits) => setSelectedUnits(selectedUnits));
|
SelectedUnitsChangedEvent.on((selectedUnits) => setSelectedUnits(selectedUnits));
|
||||||
SelectionClearedEvent.on(() => setSelectedUnits([]));
|
SelectionClearedEvent.on(() => setSelectedUnits([]));
|
||||||
}, []);
|
CoordinatesFreezeEvent.on( () => {
|
||||||
|
setCopyableCoordinates(getCopyableCoordinates());
|
||||||
|
});
|
||||||
|
}, [refSystem, latlng, elevation]);
|
||||||
|
|
||||||
|
const getCopyableCoordinates = () => {
|
||||||
|
let returnString = '';
|
||||||
|
|
||||||
|
switch (refSystem) {
|
||||||
|
case "LatLngDec":
|
||||||
|
returnString = `${latlng.lat >= 0 ? "N" : "S"} ${zeroAppend(latlng.lat, 3, true, 6)}°, ${latlng.lng >= 0 ? "E" : "W"} ${zeroAppend(latlng.lng, 3, true, 6)}°,`
|
||||||
|
break;
|
||||||
|
case "LatLngDMS":
|
||||||
|
returnString = `${latlng.lat >= 0 ? "N" : "S"} ${ConvertDDToDMS(latlng.lat, false)}, ${latlng.lng >= 0 ? "E" : "W"} ${ConvertDDToDMS(latlng.lng, false)},`
|
||||||
|
break;
|
||||||
|
case "LatLngDDM":
|
||||||
|
returnString = `${latlng.lat >= 0 ? "N" : "S"} ${DDToDDM(latlng.lat)}, ${latlng.lng >= 0 ? "E" : "W"} ${DDToDDM(latlng.lng)}`;
|
||||||
|
break;
|
||||||
|
case "MGRS":
|
||||||
|
returnString = latLngToMGRS(latlng.lat, latlng.lng, 6)?.string || "Error";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
returnString += ` Elevation: ${Math.round(elevation)}`;
|
||||||
|
|
||||||
|
return returnString;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`
|
||||||
flex w-full flex-col items-center justify-between gap-2 rounded-lg
|
flex w-full flex-col justify-between gap-2 rounded-lg bg-gray-200 px-3
|
||||||
bg-gray-200 px-3 py-3 text-sm backdrop-blur-lg backdrop-grayscale
|
py-3 text-sm backdrop-blur-lg backdrop-grayscale
|
||||||
dark:bg-olympus-800/90 dark:text-gray-200
|
dark:bg-olympus-800/90 dark:text-gray-200
|
||||||
`}
|
`}
|
||||||
onClick={() => setOpen(!open)}
|
|
||||||
>
|
>
|
||||||
<div className="absolute right-[12px] top-[15px]">
|
<div className="absolute right-[12px] top-[15px]" onClick={() => setOpen(!open)}>
|
||||||
{open ? (
|
{open ? (
|
||||||
<FaChevronDown className="cursor-pointer" />
|
<FaChevronDown className="cursor-pointer" />
|
||||||
) : (
|
) : (
|
||||||
@@ -44,11 +73,7 @@ export function CoordinatesPanel(props: {}) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{open && bullseyes && (
|
{open && bullseyes && (
|
||||||
<div
|
<div className={`flex w-full flex-col items-start justify-start gap-2`}>
|
||||||
className={`
|
|
||||||
flex w-full flex-col items-start justify-start gap-2
|
|
||||||
`}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`
|
||||||
flex flex min-w-64 max-w-64 items-start justify-between gap-2
|
flex flex min-w-64 max-w-64 items-start justify-between gap-2
|
||||||
@@ -98,26 +123,74 @@ export function CoordinatesPanel(props: {}) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`flex w-full items-center justify-between pointer-events-all`}
|
||||||
flex w-full items-center justify-between pointer-events-all
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
<OlLocation className="!min-w-64 !max-w-64 bg-transparent !p-0" location={latlng} />
|
<OlLocation onRefSystemChange={(evt) => setRefSystem(evt)} className={`
|
||||||
|
!min-w-64 !max-w-64 bg-transparent !p-0
|
||||||
|
`} location={latlng} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{open && (
|
{open && [
|
||||||
<div className="flex w-full items-center justify-start">
|
<div
|
||||||
<span
|
className={`
|
||||||
className={`
|
flex w-full min-w-64 max-w-64 items-center justify-between
|
||||||
mr-2 rounded-sm bg-white px-1 py-1 text-center font-bold
|
`}
|
||||||
text-olympus-700
|
>
|
||||||
`}
|
<div className="flex">
|
||||||
|
<span
|
||||||
|
className={`
|
||||||
|
mr-2 rounded-sm bg-white px-1 py-1 text-center font-bold
|
||||||
|
text-olympus-700
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<FaMountain />
|
||||||
|
</span>
|
||||||
|
<div className="min-w-12">{mToFt(elevation).toFixed()}ft</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="ml-auto flex w-[50%]"
|
||||||
|
onClick={async (evt) => {
|
||||||
|
evt.stopPropagation();
|
||||||
|
setCopyCoordsOpen(true);
|
||||||
|
|
||||||
|
if (isTrustedEnvironment()) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(copyableCoordinates);
|
||||||
|
getApp().addInfoMessage(`Coordinates copied to clipboard: ${copyableCoordinates}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to copy text: ', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<FaMountain />
|
<span
|
||||||
</span>
|
className={`
|
||||||
<div className="min-w-12">{mToFt(elevation).toFixed()}ft</div>
|
mr-2 rounded-sm bg-white px-1 py-1 text-center font-bold
|
||||||
</div>
|
text-olympus-700
|
||||||
)}
|
`}
|
||||||
|
>
|
||||||
|
<FaCopy />
|
||||||
|
</span>
|
||||||
|
<div className="min-w-12">Copy Coords</div>
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
|
||||||
|
open && copyCoordsOpen && (
|
||||||
|
<div
|
||||||
|
className={`
|
||||||
|
relative mt-4 flex w-full min-w-64 items-center justify-between
|
||||||
|
`}
|
||||||
|
onClick={(evt) => evt.stopPropagation()}
|
||||||
|
>
|
||||||
|
<textarea readOnly={true} className="resize-none p-2 text-black" name="coordsTextArea" id="coordsTextArea" cols={25} rows={2} value={copyableCoordinates}></textarea>
|
||||||
|
|
||||||
|
<div className="absolute right-[0] top-[0px] background-transparent" onClick={() => setCopyCoordsOpen(false)}>
|
||||||
|
<FaXmark className="cursor-pointer" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
]}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -417,9 +417,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
<td className="flex gap-2 text-lg text-gray-200">
|
<td className="flex gap-2 text-lg text-gray-200">
|
||||||
<FontAwesomeIcon icon={entry[1][0] as IconDefinition} />{" "}
|
<FontAwesomeIcon icon={entry[1][0] as IconDefinition} />{" "}
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`text-sm text-gray-400`}
|
||||||
text-sm text-gray-400
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
{entry[1][1] as string}
|
{entry[1][1] as string}
|
||||||
</div>
|
</div>
|
||||||
@@ -799,9 +797,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
{" "}
|
{" "}
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={olButtonsRoeHold}
|
icon={olButtonsRoeHold}
|
||||||
className={`
|
className={`my-auto min-w-8 text-white`}
|
||||||
my-auto min-w-8 text-white
|
|
||||||
`}
|
|
||||||
/>{" "}
|
/>{" "}
|
||||||
Hold fire: The unit will not shoot in any circumstance
|
Hold fire: The unit will not shoot in any circumstance
|
||||||
</div>
|
</div>
|
||||||
@@ -809,9 +805,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
{" "}
|
{" "}
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={olButtonsRoeReturn}
|
icon={olButtonsRoeReturn}
|
||||||
className={`
|
className={`my-auto min-w-8 text-white`}
|
||||||
my-auto min-w-8 text-white
|
|
||||||
`}
|
|
||||||
/>{" "}
|
/>{" "}
|
||||||
Return fire: The unit will not fire unless fired upon
|
Return fire: The unit will not fire unless fired upon
|
||||||
</div>
|
</div>
|
||||||
@@ -819,17 +813,13 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
{" "}
|
{" "}
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={olButtonsRoeDesignated}
|
icon={olButtonsRoeDesignated}
|
||||||
className={`
|
className={`my-auto min-w-8 text-white`}
|
||||||
my-auto min-w-8 text-white
|
|
||||||
`}
|
|
||||||
/>{" "}
|
/>{" "}
|
||||||
<div>
|
<div>
|
||||||
{" "}
|
{" "}
|
||||||
Fire on target: The unit will not fire unless fired upon{" "}
|
Fire on target: The unit will not fire unless fired upon{" "}
|
||||||
<p
|
<p
|
||||||
className={`
|
className={`inline font-bold`}
|
||||||
inline font-bold
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
or
|
or
|
||||||
</p>{" "}
|
</p>{" "}
|
||||||
@@ -840,9 +830,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
{" "}
|
{" "}
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={olButtonsRoeFree}
|
icon={olButtonsRoeFree}
|
||||||
className={`
|
className={`my-auto min-w-8 text-white`}
|
||||||
my-auto min-w-8 text-white
|
|
||||||
`}
|
|
||||||
/>{" "}
|
/>{" "}
|
||||||
Free: The unit will fire at any detected enemy in range
|
Free: The unit will fire at any detected enemy in range
|
||||||
</div>
|
</div>
|
||||||
@@ -850,25 +838,19 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<div className="my-auto">
|
<div className="my-auto">
|
||||||
<FaExclamationCircle
|
<FaExclamationCircle
|
||||||
className={`
|
className={`animate-bounce text-xl`}
|
||||||
animate-bounce text-xl
|
|
||||||
`}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
Currently, DCS blue and red ground units do not respect{" "}
|
Currently, DCS blue and red ground units do not respect{" "}
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={olButtonsRoeReturn}
|
icon={olButtonsRoeReturn}
|
||||||
className={`
|
className={`my-auto text-white`}
|
||||||
my-auto text-white
|
|
||||||
`}
|
|
||||||
/>{" "}
|
/>{" "}
|
||||||
and{" "}
|
and{" "}
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={olButtonsRoeDesignated}
|
icon={olButtonsRoeDesignated}
|
||||||
className={`
|
className={`my-auto text-white`}
|
||||||
my-auto text-white
|
|
||||||
`}
|
|
||||||
/>{" "}
|
/>{" "}
|
||||||
rules of engagement, so be careful, they may start shooting when you don't want them to. Use neutral units for finer
|
rules of engagement, so be careful, they may start shooting when you don't want them to. Use neutral units for finer
|
||||||
control.
|
control.
|
||||||
@@ -929,9 +911,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
{" "}
|
{" "}
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={olButtonsThreatNone}
|
icon={olButtonsThreatNone}
|
||||||
className={`
|
className={`my-auto min-w-8 text-white`}
|
||||||
my-auto min-w-8 text-white
|
|
||||||
`}
|
|
||||||
/>{" "}
|
/>{" "}
|
||||||
No reaction: The unit will not react in any circumstance
|
No reaction: The unit will not react in any circumstance
|
||||||
</div>
|
</div>
|
||||||
@@ -939,9 +919,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
{" "}
|
{" "}
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={olButtonsThreatPassive}
|
icon={olButtonsThreatPassive}
|
||||||
className={`
|
className={`my-auto min-w-8 text-white`}
|
||||||
my-auto min-w-8 text-white
|
|
||||||
`}
|
|
||||||
/>{" "}
|
/>{" "}
|
||||||
Passive: The unit will use counter-measures, but will not alter its course
|
Passive: The unit will use counter-measures, but will not alter its course
|
||||||
</div>
|
</div>
|
||||||
@@ -949,9 +927,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
{" "}
|
{" "}
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={olButtonsThreatManoeuvre}
|
icon={olButtonsThreatManoeuvre}
|
||||||
className={`
|
className={`my-auto min-w-8 text-white`}
|
||||||
my-auto min-w-8 text-white
|
|
||||||
`}
|
|
||||||
/>{" "}
|
/>{" "}
|
||||||
Manouevre: The unit will try to evade the threat using manoeuvres, but no counter-measures
|
Manouevre: The unit will try to evade the threat using manoeuvres, but no counter-measures
|
||||||
</div>
|
</div>
|
||||||
@@ -959,9 +935,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
{" "}
|
{" "}
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={olButtonsThreatEvade}
|
icon={olButtonsThreatEvade}
|
||||||
className={`
|
className={`my-auto min-w-8 text-white`}
|
||||||
my-auto min-w-8 text-white
|
|
||||||
`}
|
|
||||||
/>{" "}
|
/>{" "}
|
||||||
Full evasion: the unit will try to evade the threat both manoeuvering and using counter-measures
|
Full evasion: the unit will try to evade the threat both manoeuvering and using counter-measures
|
||||||
</div>
|
</div>
|
||||||
@@ -1016,9 +990,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
{" "}
|
{" "}
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={olButtonsEmissionsSilent}
|
icon={olButtonsEmissionsSilent}
|
||||||
className={`
|
className={`my-auto min-w-8 text-white`}
|
||||||
my-auto min-w-8 text-white
|
|
||||||
`}
|
|
||||||
/>{" "}
|
/>{" "}
|
||||||
Radio silence: No radar or ECM will be used
|
Radio silence: No radar or ECM will be used
|
||||||
</div>
|
</div>
|
||||||
@@ -1026,9 +998,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
{" "}
|
{" "}
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={olButtonsEmissionsDefend}
|
icon={olButtonsEmissionsDefend}
|
||||||
className={`
|
className={`my-auto min-w-8 text-white`}
|
||||||
my-auto min-w-8 text-white
|
|
||||||
`}
|
|
||||||
/>{" "}
|
/>{" "}
|
||||||
Defensive: The unit will turn radar and ECM on only when threatened
|
Defensive: The unit will turn radar and ECM on only when threatened
|
||||||
</div>
|
</div>
|
||||||
@@ -1036,9 +1006,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
{" "}
|
{" "}
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={olButtonsEmissionsAttack}
|
icon={olButtonsEmissionsAttack}
|
||||||
className={`
|
className={`my-auto min-w-8 text-white`}
|
||||||
my-auto min-w-8 text-white
|
|
||||||
`}
|
|
||||||
/>{" "}
|
/>{" "}
|
||||||
Attack: The unit will use radar and ECM when engaging other units
|
Attack: The unit will use radar and ECM when engaging other units
|
||||||
</div>
|
</div>
|
||||||
@@ -1046,9 +1014,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
{" "}
|
{" "}
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={olButtonsEmissionsFree}
|
icon={olButtonsEmissionsFree}
|
||||||
className={`
|
className={`my-auto min-w-8 text-white`}
|
||||||
my-auto min-w-8 text-white
|
|
||||||
`}
|
|
||||||
/>{" "}
|
/>{" "}
|
||||||
Free: the unit will use the radar and ECM all the time
|
Free: the unit will use the radar and ECM all the time
|
||||||
</div>
|
</div>
|
||||||
@@ -1269,9 +1235,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<div className="my-auto">
|
<div className="my-auto">
|
||||||
<FaExclamationCircle
|
<FaExclamationCircle
|
||||||
className={`
|
className={`animate-bounce text-xl`}
|
||||||
animate-bounce text-xl
|
|
||||||
`}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -1451,9 +1415,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
{/* ============== Operate as toggle START ============== */}
|
{/* ============== Operate as toggle START ============== */}
|
||||||
{selectedUnits.every((unit) => unit.getCoalition() === "neutral") && (
|
{selectedUnits.every((unit) => unit.getCoalition() === "neutral") && (
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`flex content-center justify-between`}
|
||||||
flex content-center justify-between
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={`
|
className={`
|
||||||
@@ -1495,17 +1457,13 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
>
|
>
|
||||||
<div className="flex align-center gap-2">
|
<div className="flex align-center gap-2">
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`my-auto`}
|
||||||
my-auto
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
Barrel height:{" "}
|
Barrel height:{" "}
|
||||||
</div>
|
</div>
|
||||||
<OlNumberInput
|
<OlNumberInput
|
||||||
decimalPlaces={1}
|
decimalPlaces={1}
|
||||||
className={`
|
className={`ml-auto`}
|
||||||
ml-auto
|
|
||||||
`}
|
|
||||||
value={barrelHeight}
|
value={barrelHeight}
|
||||||
min={0}
|
min={0}
|
||||||
max={100}
|
max={100}
|
||||||
@@ -1520,26 +1478,20 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
}}
|
}}
|
||||||
></OlNumberInput>
|
></OlNumberInput>
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`my-auto`}
|
||||||
my-auto
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
m
|
m
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex align-center gap-2">
|
<div className="flex align-center gap-2">
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`my-auto`}
|
||||||
my-auto
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
Muzzle velocity:{" "}
|
Muzzle velocity:{" "}
|
||||||
</div>
|
</div>
|
||||||
<OlNumberInput
|
<OlNumberInput
|
||||||
decimalPlaces={0}
|
decimalPlaces={0}
|
||||||
className={`
|
className={`ml-auto`}
|
||||||
ml-auto
|
|
||||||
`}
|
|
||||||
value={muzzleVelocity}
|
value={muzzleVelocity}
|
||||||
min={0}
|
min={0}
|
||||||
max={10000}
|
max={10000}
|
||||||
@@ -1554,26 +1506,20 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
}}
|
}}
|
||||||
></OlNumberInput>
|
></OlNumberInput>
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`my-auto`}
|
||||||
my-auto
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
m/s
|
m/s
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex align-center gap-2">
|
<div className="flex align-center gap-2">
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`my-auto`}
|
||||||
my-auto
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
Aim time:{" "}
|
Aim time:{" "}
|
||||||
</div>
|
</div>
|
||||||
<OlNumberInput
|
<OlNumberInput
|
||||||
decimalPlaces={2}
|
decimalPlaces={2}
|
||||||
className={`
|
className={`ml-auto`}
|
||||||
ml-auto
|
|
||||||
`}
|
|
||||||
value={aimTime}
|
value={aimTime}
|
||||||
min={0}
|
min={0}
|
||||||
max={100}
|
max={100}
|
||||||
@@ -1588,25 +1534,19 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
}}
|
}}
|
||||||
></OlNumberInput>
|
></OlNumberInput>
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`my-auto`}
|
||||||
my-auto
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
s
|
s
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex align-center gap-2">
|
<div className="flex align-center gap-2">
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`my-auto`}
|
||||||
my-auto
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
Shots to fire:{" "}
|
Shots to fire:{" "}
|
||||||
</div>
|
</div>
|
||||||
<OlNumberInput
|
<OlNumberInput
|
||||||
className={`
|
className={`ml-auto`}
|
||||||
ml-auto
|
|
||||||
`}
|
|
||||||
value={shotsToFire}
|
value={shotsToFire}
|
||||||
min={0}
|
min={0}
|
||||||
max={100}
|
max={100}
|
||||||
@@ -1623,17 +1563,13 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex align-center gap-2">
|
<div className="flex align-center gap-2">
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`my-auto`}
|
||||||
my-auto
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
Shots base interval:{" "}
|
Shots base interval:{" "}
|
||||||
</div>
|
</div>
|
||||||
<OlNumberInput
|
<OlNumberInput
|
||||||
decimalPlaces={2}
|
decimalPlaces={2}
|
||||||
className={`
|
className={`ml-auto`}
|
||||||
ml-auto
|
|
||||||
`}
|
|
||||||
value={shotsBaseInterval}
|
value={shotsBaseInterval}
|
||||||
min={0}
|
min={0}
|
||||||
max={100}
|
max={100}
|
||||||
@@ -1648,26 +1584,20 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
}}
|
}}
|
||||||
></OlNumberInput>
|
></OlNumberInput>
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`my-auto`}
|
||||||
my-auto
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
s
|
s
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex align-center gap-2">
|
<div className="flex align-center gap-2">
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`my-auto`}
|
||||||
my-auto
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
Shots base scatter:{" "}
|
Shots base scatter:{" "}
|
||||||
</div>
|
</div>
|
||||||
<OlNumberInput
|
<OlNumberInput
|
||||||
decimalPlaces={2}
|
decimalPlaces={2}
|
||||||
className={`
|
className={`ml-auto`}
|
||||||
ml-auto
|
|
||||||
`}
|
|
||||||
value={shotsBaseScatter}
|
value={shotsBaseScatter}
|
||||||
min={0}
|
min={0}
|
||||||
max={50}
|
max={50}
|
||||||
@@ -1682,25 +1612,19 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
}}
|
}}
|
||||||
></OlNumberInput>
|
></OlNumberInput>
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`my-auto`}
|
||||||
my-auto
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
deg
|
deg
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex align-center gap-2">
|
<div className="flex align-center gap-2">
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`my-auto`}
|
||||||
my-auto
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
Engagement range:{" "}
|
Engagement range:{" "}
|
||||||
</div>
|
</div>
|
||||||
<OlNumberInput
|
<OlNumberInput
|
||||||
className={`
|
className={`ml-auto`}
|
||||||
ml-auto
|
|
||||||
`}
|
|
||||||
value={engagementRange}
|
value={engagementRange}
|
||||||
min={0}
|
min={0}
|
||||||
max={100000}
|
max={100000}
|
||||||
@@ -1715,25 +1639,19 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
}}
|
}}
|
||||||
></OlNumberInput>
|
></OlNumberInput>
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`my-auto`}
|
||||||
my-auto
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
m
|
m
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex align-center gap-2">
|
<div className="flex align-center gap-2">
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`my-auto`}
|
||||||
my-auto
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
Targeting range:{" "}
|
Targeting range:{" "}
|
||||||
</div>
|
</div>
|
||||||
<OlNumberInput
|
<OlNumberInput
|
||||||
className={`
|
className={`ml-auto`}
|
||||||
ml-auto
|
|
||||||
`}
|
|
||||||
value={targetingRange}
|
value={targetingRange}
|
||||||
min={0}
|
min={0}
|
||||||
max={100000}
|
max={100000}
|
||||||
@@ -1748,25 +1666,19 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
}}
|
}}
|
||||||
></OlNumberInput>
|
></OlNumberInput>
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`my-auto`}
|
||||||
my-auto
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
m
|
m
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex align-center gap-2">
|
<div className="flex align-center gap-2">
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`my-auto`}
|
||||||
my-auto
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
Aim method range:{" "}
|
Aim method range:{" "}
|
||||||
</div>
|
</div>
|
||||||
<OlNumberInput
|
<OlNumberInput
|
||||||
className={`
|
className={`ml-auto`}
|
||||||
ml-auto
|
|
||||||
`}
|
|
||||||
value={aimMethodRange}
|
value={aimMethodRange}
|
||||||
min={0}
|
min={0}
|
||||||
max={100000}
|
max={100000}
|
||||||
@@ -1781,25 +1693,19 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
}}
|
}}
|
||||||
></OlNumberInput>
|
></OlNumberInput>
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`my-auto`}
|
||||||
my-auto
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
m
|
m
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex align-center gap-2">
|
<div className="flex align-center gap-2">
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`my-auto`}
|
||||||
my-auto
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
Acquisition range:{" "}
|
Acquisition range:{" "}
|
||||||
</div>
|
</div>
|
||||||
<OlNumberInput
|
<OlNumberInput
|
||||||
className={`
|
className={`ml-auto`}
|
||||||
ml-auto
|
|
||||||
`}
|
|
||||||
value={acquisitionRange}
|
value={acquisitionRange}
|
||||||
min={0}
|
min={0}
|
||||||
max={100000}
|
max={100000}
|
||||||
@@ -1814,9 +1720,7 @@ export function UnitControlMenu(props: { open: boolean; onClose: () => void }) {
|
|||||||
}}
|
}}
|
||||||
></OlNumberInput>
|
></OlNumberInput>
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`my-auto`}
|
||||||
my-auto
|
|
||||||
`}
|
|
||||||
>
|
>
|
||||||
m
|
m
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ import * as turf from "@turf/turf";
|
|||||||
import { Carrier } from "../mission/carrier";
|
import { Carrier } from "../mission/carrier";
|
||||||
import {
|
import {
|
||||||
ContactsUpdatedEvent,
|
ContactsUpdatedEvent,
|
||||||
|
CoordinatesFreezeEvent,
|
||||||
HiddenTypesChangedEvent,
|
HiddenTypesChangedEvent,
|
||||||
MapOptionsChangedEvent,
|
MapOptionsChangedEvent,
|
||||||
UnitContextMenuRequestEvent,
|
UnitContextMenuRequestEvent,
|
||||||
@@ -1611,6 +1612,7 @@ export abstract class Unit extends CustomMarker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#onLeftShortClick(e: any) {
|
#onLeftShortClick(e: any) {
|
||||||
|
CoordinatesFreezeEvent.dispatch();
|
||||||
DomEvent.stop(e);
|
DomEvent.stop(e);
|
||||||
DomEvent.preventDefault(e);
|
DomEvent.preventDefault(e);
|
||||||
e.originalEvent.stopImmediatePropagation();
|
e.originalEvent.stopImmediatePropagation();
|
||||||
|
|||||||
Reference in New Issue
Block a user