More work on unit context actions

This commit is contained in:
Davide Passoni 2024-06-11 16:19:29 +02:00
parent b089a8cfd8
commit 79016d72e3
12 changed files with 258 additions and 82 deletions

View File

@ -178,6 +178,7 @@ export const defaultMapLayers = {
export const IDLE = "Idle";
export const MOVE_UNIT = "Move unit";
export const SPAWN_UNIT = "Spawn unit";
export const CONTEXT_ACTION = "Context action";
export const COALITIONAREA_DRAW_POLYGON = "Draw Coalition Area";
export const IADSTypes = ["AAA", "SAM Site", "Radar (EWR)"];

View File

@ -11,7 +11,7 @@ import { bearing, /*createCheckboxOption, createSliderInputOption, createTextInp
import { DestinationPreviewMarker } from "./markers/destinationpreviewmarker";
import { TemporaryUnitMarker } from "./markers/temporaryunitmarker";
import { ClickableMiniMap } from "./clickableminimap";
import { mapMirrors, defaultMapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, MOVE_UNIT, SHOW_UNIT_CONTACTS, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, SHOW_UNIT_LABELS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, /*MAP_MARKER_CONTROLS,*/ DCS_LINK_PORT, DCS_LINK_RATIO, MAP_OPTIONS_DEFAULTS, MAP_HIDDEN_TYPES_DEFAULTS, SPAWN_UNIT } from "../constants/constants";
import { mapMirrors, defaultMapLayers, mapBounds, minimapBoundaries, IDLE, COALITIONAREA_DRAW_POLYGON, MOVE_UNIT, SHOW_UNIT_CONTACTS, HIDE_GROUP_MEMBERS, SHOW_UNIT_PATHS, SHOW_UNIT_TARGETS, SHOW_UNIT_LABELS, SHOW_UNITS_ENGAGEMENT_RINGS, SHOW_UNITS_ACQUISITION_RINGS, HIDE_UNITS_SHORT_RANGE_RINGS, FILL_SELECTED_RING, /*MAP_MARKER_CONTROLS,*/ DCS_LINK_PORT, DCS_LINK_RATIO, MAP_OPTIONS_DEFAULTS, MAP_HIDDEN_TYPES_DEFAULTS, SPAWN_UNIT, CONTEXT_ACTION } from "../constants/constants";
import { CoalitionArea } from "./coalitionarea/coalitionarea";
//import { CoalitionAreaContextMenu } from "../contextmenus/coalitionareacontextmenu";
import { DrawingCursor } from "./coalitionarea/drawingcursor";
@ -30,6 +30,7 @@ import './markers/stylesheets/units.css'
import './theme.css'
import { Coalition, MapHiddenTypes, MapOptions } from "../types/types";
import { SpawnRequestTable, UnitBlueprint, UnitSpawnTable } from "../interfaces";
import { ContextAction } from "../unit/contextaction";
var hasTouchScreen = false;
//if ("maxTouchPoints" in navigator)
@ -109,6 +110,8 @@ export class Map extends L.Map {
#bradcastPositionXmlHttp: XMLHttpRequest | null = null;
#cameraZoomRatio: number = 1.0;
#contextAction: null | ContextAction = null;
/**
*
* @param ID - the ID of the HTML element which will contain the context menu
@ -305,7 +308,7 @@ export class Map extends L.Map {
}
/* State machine */
setState(state: string, options?: { spawnRequestTable: SpawnRequestTable }) {
setState(state: string, options?: { spawnRequestTable?: SpawnRequestTable, contextAction?: ContextAction }) {
this.#state = state;
/* Operations to perform if you are NOT in a state */
@ -319,6 +322,9 @@ export class Map extends L.Map {
this.#spawnCursor?.removeFrom(this);
this.#spawnCursor = new TemporaryUnitMarker(new L.LatLng(0, 0), this.#spawnRequestTable?.unit.unitType ?? "unknown", this.#spawnRequestTable?.coalition ?? 'blue');
}
else if (this.#state === CONTEXT_ACTION ) {
this.#contextAction = options?.contextAction ?? null;
}
else if (this.#state === COALITIONAREA_DRAW_POLYGON) {
this.#coalitionAreas.push(new CoalitionArea([]));
this.#coalitionAreas[this.#coalitionAreas.length - 1].addTo(this);
@ -440,10 +446,10 @@ export class Map extends L.Map {
return this.#lastMouseCoordinates;
}
centerOnUnit(ID: number | null) {
if (ID != null) {
centerOnUnit(unit: Unit | null) {
if (unit !== null) {
this.options.scrollWheelZoom = 'center';
this.#centerUnit = getApp().getUnitsManager().getUnitByID(ID);
this.#centerUnit = unit;
}
else {
this.options.scrollWheelZoom = undefined;
@ -707,6 +713,10 @@ export class Map extends L.Map {
this.#computeDestinationRotation = false;
}
}
else if (this.#state === CONTEXT_ACTION) {
if (this.#contextAction)
this.#contextAction.executeCallback(null, e.latlng);
}
else {
this.setState(IDLE);
}

View File

@ -49,7 +49,7 @@ export function OlDropdown(props: {
if (cxr > window.innerWidth)
offsetX -= (cxr - window.innerWidth)
if (cyb > window.innerHeight)
offsetY = -ch - 8;
offsetY -= bh + ch + 16;
/* Apply the offset */
content.style.left = `${offsetX}px`

View File

@ -1,42 +1,61 @@
import { IconProp } from "@fortawesome/fontawesome-svg-core";
import { faExternalLink, faLock, faLockOpen, faUnlock, faUnlockAlt } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import React from "react"
import React, { useRef, useState } from "react"
import { OlTooltip } from "./oltooltip";
export function OlStateButton(props: {
className?: string,
checked: boolean,
icon: IconProp,
tooltip: string,
onClick: () => void
}) {
var [hover, setHover] = useState(false);
var buttonRef = useRef(null);
const className = (props.className ?? '') + ` h-[40px] w-[40px] flex-none font-medium rounded-md text-lg dark:bg-olympus-600 dark:hover:bg-olympus-300 dark:data-[checked='true']:bg-blue-500 dark:data-[checked='true']:text-white dark:text-gray-300 dark:border-gray-600 `;
return <button onClick={props.onClick} data-checked={props.checked} type="button" className={className}>
return <><button ref={buttonRef} onClick={props.onClick} data-checked={props.checked} type="button" className={className} onMouseEnter={() => { setHover(true) }} onMouseLeave={() => { setHover(false) }}>
<FontAwesomeIcon icon={props.icon} />
</button>
{hover && <OlTooltip buttonRef={buttonRef} content={props.tooltip} />}
</>
}
export function OlRoundStateButton(props: {
className?: string,
checked: boolean,
icon: IconProp,
tooltip: string,
onClick: () => void
}) {
var [hover, setHover] = useState(false);
var buttonRef = useRef(null);
const className = (props.className ?? '') + ` h-8 w-8 flex-none m-auto border-2 border-gray-900 font-medium rounded-full text-sm dark:bg-[transparent] dark:data-[checked='true']:bg-white dark:text-gray-400 dark:data-[checked='true']:text-gray-900 dark:data-[checked='true']:border-white dark:border-gray-400 dark:data-[checked='true']:hover:bg-gray-200 dark:data-[checked='true']:hover:border-gray-200 dark:hover:bg-gray-800`;
return <button onClick={props.onClick} data-checked={props.checked} type="button" className={className}>
return <><button ref={buttonRef} onClick={props.onClick} data-checked={props.checked} type="button" className={className} onMouseEnter={() => { setHover(true) }} onMouseLeave={() => { setHover(false) }}>
<FontAwesomeIcon className="pt-[3px]" icon={props.icon} />
</button>
{ hover && <OlTooltip buttonRef={buttonRef} content={props.tooltip} /> }
</>
}
export function OlLockStateButton(props: {
className?: string,
checked: boolean,
tooltip: string,
onClick: () => void
}) {
var [hover, setHover] = useState(false);
var buttonRef = useRef(null);
const className = (props.className ?? '') + ` h-8 w-8 flex-none m-auto border-gray-900 font-medium rounded-full text-sm dark:bg-red-500 dark:data-[checked='true']:bg-green-500 dark:text-olympus-900 dark:data-[checked='true']:text-green-900 dark:data-[checked='true']:hover:bg-green-400 dark:hover:bg-red-400`;
return <button onClick={props.onClick} data-checked={props.checked} type="button" className={className}>
<FontAwesomeIcon className="pt-[3px]" icon={props.checked==true ? faUnlockAlt:faLock} />
return <><button ref={buttonRef} onClick={props.onClick} data-checked={props.checked} type="button" className={className} onMouseEnter={() => { setHover(true) }} onMouseLeave={() => { setHover(false) }}>
<FontAwesomeIcon className="pt-[3px]" icon={props.checked == true ? faUnlockAlt : faLock} />
</button>
{ hover && <OlTooltip buttonRef={buttonRef} content={props.tooltip} /> }
</>
}

View File

@ -0,0 +1,63 @@
import React, { useEffect, useRef, useState } from "react";
export function OlTooltip(props: {
content: string,
buttonRef: React.MutableRefObject<null>
}) {
var contentRef = useRef(null);
function setPosition(content: HTMLDivElement, button: HTMLButtonElement) {
/* Reset the position of the content */
content.style.left = "0px";
content.style.top = "0px";
content.style.height = "";
/* Get the position and size of the button and the content elements */
var [cxl, cyt, cxr, cyb, cw, ch] = [content.getBoundingClientRect().x, content.getBoundingClientRect().y, content.getBoundingClientRect().x + content.clientWidth, content.getBoundingClientRect().y + content.clientHeight, content.clientWidth, content.clientHeight];
var [bxl, byt, bxr, byb, bbw, bh] = [button.getBoundingClientRect().x, button.getBoundingClientRect().y, button.getBoundingClientRect().x + button.clientWidth, button.getBoundingClientRect().y + button.clientHeight, button.clientWidth, button.clientHeight];
/* Limit the maximum height */
if (ch > 400) {
ch = 400;
content.style.height = `${ch}px`;
}
/* Compute the horizontal position of the center of the button and the content */
var cxc = (cxl + cxr) / 2;
var bxc = (bxl + bxr) / 2;
/* Compute the x and y offsets needed to align the button and element horizontally, and to put the content below the button */
var offsetX = bxc - cxc;
var offsetY = byb - cyt + 8;
/* Compute the new position of the left and right margins of the content */
cxl += offsetX;
cxr += offsetX;
cyb += offsetY;
/* Try and move the content so it is inside the screen */
if (cxl < 0)
offsetX -= cxl;
if (cxr > window.innerWidth)
offsetX -= (cxr - window.innerWidth)
if (cyb > window.innerHeight)
offsetY -= bh + ch + 16;
/* Apply the offset */
content.style.left = `${offsetX}px`
content.style.top = `${offsetY}px`
}
useEffect(() => {
if (contentRef.current && props.buttonRef.current) {
const content = contentRef.current as HTMLDivElement;
const button = props.buttonRef.current as HTMLButtonElement;
setPosition(content, button);
}
})
return props.content !== "" && <div ref={contentRef} className={`absolute whitespace-nowrap z-ui-4 px-3 py-2 text-sm font-medium text-white bg-gray-900 rounded-lg shadow-sm dark:bg-gray-700`}>
{ props.content }
</div>
}

View File

@ -24,7 +24,7 @@ export function Header() {
</div>
</div>
<div className="ml-auto">
<OlLockStateButton checked={false} onClick={() => {}}/>
<OlLockStateButton checked={false} onClick={() => {}} tooltip="Lock/unlock protected units (from scripted mission)"/>
</div>
<div className="flex flex-row h-fit items-center justify-start gap-1">
{
@ -36,7 +36,8 @@ export function Header() {
getApp().getMap().setHiddenType(entry[0], !appState.mapHiddenTypes[entry[0]]);
}}
checked={!appState.mapHiddenTypes[entry[0]]}
icon={entry[1]} />
icon={entry[1]}
tooltip={"Hide/show " + entry[0] + " units" } />
})
}
</div>
@ -52,7 +53,8 @@ export function Header() {
getApp().getMap().setHiddenType(entry[0], !appState.mapHiddenTypes[entry[0]]);
}}
checked={!appState.mapHiddenTypes[entry[0]]}
icon={entry[1]} />
icon={entry[1]}
tooltip={"Hide/show " + entry[0] + " units" } />
})
}
</div>
@ -61,18 +63,21 @@ export function Header() {
<OlRoundStateButton
onClick={() => getApp().getMap().setHiddenType( 'blue', !appState.mapHiddenTypes['blue'] )}
checked={!appState.mapHiddenTypes['blue']}
icon={faFlag} className={"!text-blue-500"} />
icon={faFlag} className={"!text-blue-500"}
tooltip={"Hide/show blue units" } />
<OlRoundStateButton
onClick={() => getApp().getMap().setHiddenType('red', !appState.mapHiddenTypes['red'] )}
checked={!appState.mapHiddenTypes['red']}
icon={faFlag} className={"!text-red-500"} />
icon={faFlag} className={"!text-red-500"}
tooltip={"Hide/show red units" } />
<OlRoundStateButton
onClick={() => getApp().getMap().setHiddenType('neutral', !appState.mapHiddenTypes['neutral'] )}
checked={!appState.mapHiddenTypes['neutral']}
icon={faFlag} className={"!text-gray-500"} />
icon={faFlag} className={"!text-gray-500"}
tooltip={"Hide/show neutral units" } />
</div>
<OlLabelToggle toggled={false} leftLabel={"Live"} rightLabel={"Map"} onClick={() => {}}></OlLabelToggle>
<OlStateButton checked={false} icon={faCamera} onClick={() => {}} />
<OlStateButton checked={false} icon={faCamera} onClick={() => {}} tooltip="Activate/deactivate camera plugin" />
<OlDropdown label="DCS Sat" className="w-40">
<OlDropdownItem className="w-full">DCS Sat</OlDropdownItem>
<OlDropdownItem className="w-full">DCS Alt</OlDropdownItem>

View File

@ -12,17 +12,17 @@ export function SideBar() {
<nav className="flex flex-col z-ui-1 h-full bg-gray-300 dark:bg-olympus-900">
<div className="flex-1 flex-wrap items-center justify-center p-4 w-16">
<div className="flex flex-col items-center justify-center gap-2.5">
<OlStateButton onClick={events.toggleMainMenuVisible} checked={appState.mainMenuVisible} icon={faEllipsisV}></OlStateButton>
<OlStateButton onClick={events.toggleSpawnMenuVisible} checked={appState.spawnMenuVisible} icon={faPlusSquare}></OlStateButton>
<OlStateButton onClick={events.toggleUnitControlMenuVisible} checked={appState.unitControlMenuVisible} icon={faGamepad}></OlStateButton>
<OlStateButton onClick={events.toggleMeasureMenuVisible} checked={appState.measureMenuVisible} icon={faRuler}></OlStateButton>
<OlStateButton onClick={events.toggleDrawingMenuVisible} checked={appState.drawingMenuVisible} icon={faPencil}></OlStateButton>
<OlStateButton onClick={events.toggleMainMenuVisible} checked={appState.mainMenuVisible} icon={faEllipsisV} tooltip="Hide/show main menu"></OlStateButton>
<OlStateButton onClick={events.toggleSpawnMenuVisible} checked={appState.spawnMenuVisible} icon={faPlusSquare} tooltip="Hide/show unit spawn menu"></OlStateButton>
<OlStateButton onClick={events.toggleUnitControlMenuVisible} checked={appState.unitControlMenuVisible} icon={faGamepad} tooltip=""></OlStateButton>
<OlStateButton onClick={events.toggleMeasureMenuVisible} checked={appState.measureMenuVisible} icon={faRuler} tooltip=""></OlStateButton>
<OlStateButton onClick={events.toggleDrawingMenuVisible} checked={appState.drawingMenuVisible} icon={faPencil} tooltip="Hide/show drawing menu"></OlStateButton>
</div>
</div>
<div className="flex flex-wrap content-end justify-center p-4 w-16">
<div className="flex flex-col items-center justify-center gap-2.5">
<OlStateButton onClick={() => window.open("https://github.com/Pax1601/DCSOlympus/wiki")} checked={false} icon={faQuestionCircle}></OlStateButton>
<OlStateButton onClick={events.toggleOptionsMenuVisible} checked={appState.optionsMenuVisible} icon={faCog}></OlStateButton>
<OlStateButton onClick={() => window.open("https://github.com/Pax1601/DCSOlympus/wiki")} checked={false} icon={faQuestionCircle} tooltip="Open user guide on separate window"></OlStateButton>
<OlStateButton onClick={events.toggleOptionsMenuVisible} checked={appState.optionsMenuVisible} icon={faCog} tooltip="Hide/show settings menu"></OlStateButton>
</div>
</div>
</nav>

View File

@ -5,15 +5,19 @@ import { OlStateButton } from '../components/olstatebutton';
import { faAccessibleIcon } from '@fortawesome/free-brands-svg-icons';
import { faCamera } from '@fortawesome/free-solid-svg-icons';
import { getApp } from '../../olympusapp';
import { ContextAction } from '../../unit/contextaction';
import { CONTEXT_ACTION } from '../../constants/constants';
import { FaInfoCircle, FaQuestionCircle } from 'react-icons/fa';
export function UnitMouseControlBar(props: {
}) {
var [open, setOpen] = useState(false);
var [open, setOpen] = useState(false);
var [selectedUnits, setSelectedUnits] = useState([] as Unit[]);
var [contextActionsSet, setContextActionsSet] = useState(new ContextActionSet());
var [activeContextAction, setActiveContextAction] = useState(null as null | ContextAction);
/* When a unit is selected, open the menu */
/* When a unit is selected, open the menu */
document.addEventListener("unitsSelection", (ev: CustomEventInit) => {
setOpen(true);
setSelectedUnits(ev.detail as Unit[]);
@ -34,22 +38,43 @@ export function UnitMouseControlBar(props: {
updateData();
})
/* Update the current values of the shown data */
/* Update the current values of the shown data */
function updateData() {
var newContextActionSet = new ContextActionSet();
getApp().getUnitsManager().getSelectedUnits().forEach((unit: Unit) => {
unit.appendContextActions(newContextActionSet);
})
getApp().getUnitsManager().getSelectedUnits().forEach((unit: Unit) => {
unit.appendContextActions(newContextActionSet);
})
setContextActionsSet(newContextActionSet);
}
setActiveContextAction(null);
}
return <div className='flex gap-2 rounded-md absolute top-20 left-[50%] translate-x-[-50%] bg-gray-200 dark:bg-olympus-900 z-ui-1 p-2'>
{
Object.values(contextActionsSet.getContextActions()).map((contextAction) => {
return <OlStateButton checked={false} icon={contextAction.getIcon()} onClick={() => {}} />
})
return <> {
open && <>
<div className='flex gap-2 rounded-md absolute top-20 left-[50%] translate-x-[-50%] bg-gray-200 dark:bg-olympus-900 z-ui-1 p-2'>
{
Object.values(contextActionsSet.getContextActions()).map((contextAction) => {
return <OlStateButton checked={contextAction === activeContextAction} icon={contextAction.getIcon()} tooltip={contextAction.getLabel()} onClick={() => {
if (contextAction.getOptions().executeImmediately) {
setActiveContextAction(null);
contextAction.executeCallback(null, null);
} else {
setActiveContextAction(contextAction);
getApp().getMap().setState(CONTEXT_ACTION, { contextAction: contextAction });
}
}} />
})
}
</div>
{ activeContextAction && <div className='flex gap-2 p-4 items-center rounded-md absolute top-36 left-[50%] translate-x-[-50%] bg-gray-200 dark:bg-olympus-800 z-ui-1'>
<FaInfoCircle className="text-blue-500 mr-2 text-sm"/>
<div className='border-l-[1px] px-5 dark:text-gray-400'>
{activeContextAction.getDescription()}
</div>
</div>
}
</div>
</>
}
</>
}

View File

@ -1,29 +1,30 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { Unit } from "./unit";
import { LatLng } from "leaflet";
export interface ContextActionOptions {
isScenic?: boolean
executeImmediately?: boolean
}
export type ContextActionCallback = (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => void;
export class ContextAction {
#id: string = "";
#label: string = "";
#description: string = "";
#callback: CallableFunction | null = null;
#callback: ContextActionCallback | null = null;
#units: Unit[] = [];
#hideContextAfterExecution: boolean = true
#icon: IconDefinition;
#options: ContextActionOptions;
constructor(id: string, label: string, description: string, icon: IconDefinition, callback: CallableFunction, hideContextAfterExecution: boolean = true, options: ContextActionOptions) {
constructor(id: string, label: string, description: string, icon: IconDefinition, callback: ContextActionCallback, options: ContextActionOptions) {
this.#id = id;
this.#label = label;
this.#description = description;
this.#callback = callback;
this.#icon = icon;
this.#hideContextAfterExecution = hideContextAfterExecution;
this.#options = {
"isScenic": false,
executeImmediately: false,
...options
}
}
@ -56,12 +57,8 @@ export class ContextAction {
return this.#icon;
}
executeCallback() {
executeCallback(targetUnit: Unit | null, targetPosition: LatLng | null) {
if (this.#callback)
this.#callback(this.#units);
}
getHideContextAfterExecution() {
return this.#hideContextAfterExecution;
this.#callback(this.#units, targetUnit, targetPosition);
}
}

View File

@ -1,16 +1,15 @@
import { LatLng } from "leaflet";
import { ContextAction, ContextActionOptions } from "./contextaction";
import { ContextAction, ContextActionCallback, ContextActionOptions } from "./contextaction";
import { Unit } from "./unit";
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
export class ContextActionSet {
#contextActions: {[key: string]: ContextAction} = {};
#contextActions: { [key: string]: ContextAction } = {};
addContextAction(unit: Unit, id: string, label: string, description: string, icon: IconDefinition, callback: (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => void, hideContextAfterExecution: boolean = true, options?:ContextActionOptions) {
addContextAction(unit: Unit, id: string, label: string, description: string, icon: IconDefinition, callback: ContextActionCallback, options?: ContextActionOptions) {
options = options || {};
if (!(id in this.#contextActions)) {
this.#contextActions[id] = new ContextAction(id, label, description, icon, callback, hideContextAfterExecution, options);
this.#contextActions[id] = new ContextAction(id, label, description, icon, callback, options);
}
this.#contextActions[id].addUnit(unit);
}

View File

@ -1000,6 +1000,7 @@ export abstract class Unit extends CustomMarker {
showFollowOptions(units: Unit[]) {
var contextActionSet = new ContextActionSet();
// TODO FIX
contextActionSet.addContextAction(this, 'trail', "Trail", "Follow unit in trail formation", olIconsTrail, () => this.applyFollowOptions('trail', units));
contextActionSet.addContextAction(this, 'echelon-lh', "Echelon (LH)", "Follow unit in echelon left formation", olIconsEchelonLh, () => this.applyFollowOptions('echelon-lh', units));
contextActionSet.addContextAction(this, 'echelon-rh', "Echelon (RH)", "Follow unit in echelon right formation", olIconsEchelonRh, () => this.applyFollowOptions('echelon-rh', units));
@ -1111,11 +1112,11 @@ export abstract class Unit extends CustomMarker {
if (this.#miniMapMarker == null) {
this.#miniMapMarker = new CircleMarker(new LatLng(this.#position.lat, this.#position.lng), { radius: 0.5 });
if (this.#coalition == "neutral")
this.#miniMapMarker.setStyle({ color: "#CFD9E8" });
this.#miniMapMarker.setStyle({ color: "#CFD9E8", radius: 2 });
else if (this.#coalition == "red")
this.#miniMapMarker.setStyle({ color: "#ff5858" });
this.#miniMapMarker.setStyle({ color: "#ff5858", radius: 2 });
else
this.#miniMapMarker.setStyle({ color: "#247be2" });
this.#miniMapMarker.setStyle({ color: "#247be2", radius: 2 });
this.#miniMapMarker.addTo(getApp().getMap().getMiniMapLayerGroup());
this.#miniMapMarker.bringToBack();
}
@ -1498,12 +1499,33 @@ export abstract class AirUnit extends Unit {
}
appendContextActions(contextActionSet: ContextActionSet) {
contextActionSet.addContextAction(this, "attack", "Attack unit", "Attack the unit using A/A or A/G weapons", olStatesAttack, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().attackUnit(targetUnit.ID, units) });
contextActionSet.addContextAction(this, "follow", "Follow unit", "Follow this unit in formation", olIconsFollow, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { targetUnit.showFollowOptions(units); }, false); // Don't hide the context menu after the execution (to show the follow options)
contextActionSet.addContextAction(this, "refuel", "Refuel", "Refuel units at the nearest AAR Tanker. If no tanker is available the unit will RTB", olStatesRefuel, (units: Unit[]) => { getApp().getUnitsManager().refuel(units) });
contextActionSet.addContextAction(this, "center-map", "Center map", "Center the map on the unit and follow it", faQuestionCircle, () => { getApp().getMap().centerOnUnit(this.ID); });
contextActionSet.addContextAction(this, "bomb", "Precision bombing", "Precision bombing of a specific point", faQuestionCircle, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().bombPoint(targetPosition, units) });
contextActionSet.addContextAction(this, "carpet-bomb", "Carpet bombing", "Carpet bombing close to a point", faQuestionCircle, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().carpetBomb(targetPosition, units) });
/* Context actions to be executed immediately */
contextActionSet.addContextAction(this, "refuel", "Refuel", "Refuel units at the nearest AAR Tanker. If no tanker is available the unit will RTB", olStatesRefuel, (units: Unit[]) => {
getApp().getUnitsManager().refuel(units)
}, { executeImmediately: true });
contextActionSet.addContextAction(this, "center-map", "Center map", "Center the map on the unit and follow it", faQuestionCircle, (units: Unit[]) => {
getApp().getMap().centerOnUnit(units[0]);
}, { executeImmediately: true });
/* Context actions with a target unit */
contextActionSet.addContextAction(this, "attack", "Attack unit", "Click on a unit to attack it using A/A or A/G weapons", olStatesAttack, (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => {
if (targetUnit)
getApp().getUnitsManager().attackUnit(targetUnit.ID, units)
});
contextActionSet.addContextAction(this, "follow", "Follow unit", "Click on a unit to follow it in formation", olIconsFollow, (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => {
if (targetUnit)
targetUnit.showFollowOptions(units);
});
/* Context actions with a target position */
contextActionSet.addContextAction(this, "bomb", "Precision bombing", "Click on a point to execute a precision bombing attack", faQuestionCircle, (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => {
if (targetPosition)
getApp().getUnitsManager().bombPoint(targetPosition , units)
});
contextActionSet.addContextAction(this, "carpet-bomb", "Carpet bombing", "Click on a point to execute a carpet bombing attack", faQuestionCircle, (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => {
if (targetPosition)
getApp().getUnitsManager().carpetBomb(targetPosition , units)
});
}
}
@ -1540,7 +1562,10 @@ export class Helicopter extends AirUnit {
appendContextActions(contextActionSet: ContextActionSet) {
super.appendContextActions(contextActionSet);
contextActionSet.addContextAction(this, "land-at-point", "Land here", "Land at this precise location", olIconsLandAtPoint, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().landAtPoint(targetPosition, units) });
contextActionSet.addContextAction(this, "land-at-point", "Land here", "Click on a point to land there", olIconsLandAtPoint, (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => {
if (targetPosition)
getApp().getUnitsManager().landAtPoint(targetPosition , units)
});
}
getMarkerCategory() {
@ -1575,21 +1600,38 @@ export class GroundUnit extends Unit {
}
appendContextActions(contextActionSet: ContextActionSet) {
contextActionSet.addContextAction(this, "group-ground", "Group ground units", "Create a group of ground units", olIconsGroupGround, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().createGroup(units) });
contextActionSet.addContextAction(this, "attack", "Attack unit", "Attack the unit using A/A or A/G weapons", faQuestionCircle, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().attackUnit(targetUnit.ID, units) });
contextActionSet.addContextAction(this, "center-map", "Center map", "Center the map on the unit and follow it", faQuestionCircle, () => { getApp().getMap().centerOnUnit(this.ID); });
if (this.canTargetPoint()) {
contextActionSet.addContextAction(this, "fire-at-area", "Fire at area", "Fire at a specific area on the ground", faQuestionCircle, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().fireAtArea(targetPosition, units) });
contextActionSet.addContextAction(this, "simulate-fire-fight", "Simulate fire fight", "Simulate a fire fight by shooting randomly in a certain large area.\nWARNING: works correctly only on neutral units, blue or red units will aim", faQuestionCircle, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().simulateFireFight(targetPosition, units) });
}
/* Context actions to be executed immediately */
contextActionSet.addContextAction(this, "group-ground", "Group ground units", "Create a group of ground units", olIconsGroupGround, (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => {
getApp().getUnitsManager().createGroup(units)
}, { executeImmediately: true });
contextActionSet.addContextAction(this, "center-map", "Center map", "Center the map on the unit and follow it", faQuestionCircle, (units: Unit[]) => {
getApp().getMap().centerOnUnit(units[0]);
}, { executeImmediately: true });
if (this.canAAA()) {
contextActionSet.addContextAction(this, "scenic-aaa", "Scenic AAA", "Shoot AAA in the air without aiming at any target, when an enemy unit gets close enough.\nWARNING: works correctly only on neutral units, blue or red units will aim", faQuestionCircle, (units: Unit[]) => { getApp().getUnitsManager().scenicAAA(units) }, undefined, {
"isScenic": true
contextActionSet.addContextAction(this, "scenic-aaa", "Scenic AAA", "Shoot AAA in the air without aiming at any target, when an enemy unit gets close enough. WARNING: works correctly only on neutral units, blue or red units will aim", faQuestionCircle, (units: Unit[]) => {
getApp().getUnitsManager().scenicAAA(units)
}, { executeImmediately: true });
contextActionSet.addContextAction(this, "miss-aaa", "Dynamic accuracy AAA", "Shoot AAA towards the closest enemy unit, but don't aim precisely. WARNING: works correctly only on neutral units, blue or red units will aim", faQuestionCircle, (units: Unit[]) => {
getApp().getUnitsManager().missOnPurpose(units)
}, { executeImmediately: true });
}
/* Context actions that require a target unit */
contextActionSet.addContextAction(this, "attack", "Attack unit", "Click on a unit to attack it", faQuestionCircle, (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => {
if (targetUnit)
getApp().getUnitsManager().attackUnit(targetUnit.ID, units)
});
/* Context actions that require a target position */
if (this.canTargetPoint()) {
contextActionSet.addContextAction(this, "fire-at-area", "Fire at area", "Click on a point to precisely fire at it (if possible)", faQuestionCircle, (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => {
if (targetPosition)
getApp().getUnitsManager().fireAtArea(targetPosition , units)
});
contextActionSet.addContextAction(this, "miss-aaa", "Dynamic accuracy AAA", "Shoot AAA towards the closest enemy unit, but don't aim precisely.\nWARNING: works correctly only on neutral units, blue or red units will aim", faQuestionCircle, (units: Unit[]) => { getApp().getUnitsManager().missOnPurpose(units) }, undefined, {
"isScenic": true
contextActionSet.addContextAction(this, "simulate-fire-fight", "Simulate fire fight", "Simulate a fire fight by shooting randomly in a certain large area. WARNING: works correctly only on neutral units, blue or red units will aim", faQuestionCircle, (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => {
if (targetPosition)
getApp().getUnitsManager().simulateFireFight(targetPosition , units)
});
}
}
@ -1663,10 +1705,25 @@ export class NavyUnit extends Unit {
}
appendContextActions(contextActionSet: ContextActionSet) {
contextActionSet.addContextAction(this, "group-navy", "Group navy units", "Create a group of navy units", faQuestionCircle, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().createGroup(units) });
contextActionSet.addContextAction(this, "attack", "Attack unit", "Attack the unit using A/A or A/G weapons", faQuestionCircle, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().attackUnit(targetUnit.ID, units) });
contextActionSet.addContextAction(this, "center-map", "Center map", "Center the map on the unit and follow it", faQuestionCircle, () => { getApp().getMap().centerOnUnit(this.ID); });
contextActionSet.addContextAction(this, "fire-at-area", "Fire at area", "Fire at a specific area on the ground", faQuestionCircle, (units: Unit[], targetUnit: Unit, targetPosition: LatLng) => { getApp().getUnitsManager().fireAtArea(targetPosition, units) });
/* Context actions to be executed immediately */
contextActionSet.addContextAction(this, "group-navy", "Group navy units", "Create a group of navy units", faQuestionCircle, (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => {
getApp().getUnitsManager().createGroup(units)
}, { executeImmediately: true });
contextActionSet.addContextAction(this, "center-map", "Center map", "Center the map on the unit and follow it", faQuestionCircle, (units: Unit[]) => {
getApp().getMap().centerOnUnit(units[0]);
}, { executeImmediately: true });
/* Context actions that require a target unit */
contextActionSet.addContextAction(this, "attack", "Attack unit", "Click on a unit to attack it", faQuestionCircle, (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => {
if (targetUnit)
getApp().getUnitsManager().attackUnit(targetUnit.ID, units)
});
/* Context actions that require a target position */
contextActionSet.addContextAction(this, "fire-at-area", "Fire at area", "Click on a point to precisely fire at it (if possible)", faQuestionCircle, (units: Unit[], targetUnit: Unit | null, targetPosition: LatLng | null) => {
if (targetPosition)
getApp().getUnitsManager().fireAtArea(targetPosition , units)
});
}
getCategory() {

View File

@ -462,7 +462,7 @@ module.exports = function (configLocation) {
};
mission(req, res){
var ret = {mission: {theatre: "PersianGulf"}};
var ret = {mission: {theatre: "Nevada"}};
ret.time = Date.now();
ret.mission.dateAndTime = {