Merge branch 'main' into 328-add-simple-miss-on-purpose-mode-for-aaa

This commit is contained in:
Pax1601 2023-10-04 21:21:08 +02:00
commit a0ac9eb285
24 changed files with 555 additions and 90 deletions

View File

@ -588,11 +588,11 @@ declare module "interfaces" {
name?: string;
shiftKey?: boolean;
}
export interface KeyboardShortcutOptions extends ShortcutOptions {
export interface ShortcutKeyboardOptions extends ShortcutOptions {
code: string;
event?: "keydown" | "keyup";
}
export interface MouseShortcutOptions extends ShortcutOptions {
export interface ShortcutMouseOptions extends ShortcutOptions {
button: number;
event: "mousedown" | "mouseup";
}
@ -1085,7 +1085,7 @@ declare module "unit/unit" {
carpetBomb(latlng: LatLng): void;
bombBuilding(latlng: LatLng): void;
fireAtArea(latlng: LatLng): void;
simulateFireFight(latlng: LatLng, groundElevation: number | null): void;
simulateFireFight(latlng: LatLng, targetGroundElevation: number | null): void;
/***********************************************/
onAdd(map: Map): this;
}
@ -1493,26 +1493,28 @@ declare module "plugin/pluginmanager" {
}
}
declare module "shortcut/shortcut" {
import { KeyboardShortcutOptions, MouseShortcutOptions, ShortcutOptions } from "interfaces";
import { ShortcutKeyboardOptions, ShortcutMouseOptions, ShortcutOptions } from "interfaces";
export abstract class Shortcut {
#private;
constructor(config: ShortcutOptions);
getConfig(): ShortcutOptions;
}
export class ShortcutKeyboard extends Shortcut {
constructor(config: KeyboardShortcutOptions);
constructor(config: ShortcutKeyboardOptions);
}
export class ShortcutMouse extends Shortcut {
constructor(config: MouseShortcutOptions);
constructor(config: ShortcutMouseOptions);
}
}
declare module "shortcut/shortcutmanager" {
import { ShortcutKeyboardOptions, ShortcutMouseOptions } from "interfaces";
import { Manager } from "other/manager";
import { Shortcut } from "shortcut/shortcut";
export class ShortcutManager extends Manager {
#private;
constructor();
add(name: string, shortcut: Shortcut): this;
add(name: string, shortcut: any): this;
addKeyboardShortcut(name: string, shortcutKeyboardOptions: ShortcutKeyboardOptions): this;
addMouseShortcut(name: string, shortcutMouseOptions: ShortcutMouseOptions): this;
getKeysBeingHeld(): string[];
keyComboMatches(combo: string[]): boolean;
onKeyDown(callback: CallableFunction): void;
@ -1967,6 +1969,19 @@ declare module "server/servermanager" {
getPaused(): boolean;
}
}
declare module "panels/unitlistpanel" {
import { OlympusApp } from "olympusapp";
import { Panel } from "panels/panel";
export class UnitListPanel extends Panel {
#private;
constructor(olympusApp: OlympusApp, panelElement: string, contentElement: string);
doUpdate(): void;
getContentElement(): HTMLElement;
startUpdates(): void;
stopUpdates(): void;
toggle(): void;
}
}
declare module "olympusapp" {
import { Map } from "map/map";
import { MissionManager } from "mission/missionmanager";

View File

@ -1,3 +1,5 @@
import { OlympusPlugin } from "interfaces";
const SHOW_CONTROL_TIPS = "Show control tips"
export class ControlTipsPlugin implements OlympusPlugin {
@ -41,7 +43,7 @@ export class ControlTipsPlugin implements OlympusPlugin {
this.#updateTips();
});
document.addEventListener("unitDeselection", (ev: CustomEvent) => {
document.addEventListener("unitDeselection", (ev: CustomEventInit ) => {
this.#updateTips();
});
@ -55,7 +57,7 @@ export class ControlTipsPlugin implements OlympusPlugin {
this.#updateTips();
});
document.addEventListener("unitSelection", (ev: CustomEvent) => {
document.addEventListener("unitSelection", (ev: CustomEventInit ) => {
this.#updateTips();
});

View File

@ -7,6 +7,7 @@
@import url("panels/unitcontrol.css");
@import url("panels/unitinfo.css");
@import url("panels/logpanel.css");
@import url("panels/unitlist.css");
@import url("other/contextmenus.css");
@import url("other/popup.css");

View File

@ -16,4 +16,25 @@
#connection-status-panel[data-is-connected] dd::after {
background: var(--accent-green);
}
#connection-status-panel[data-is-paused] dt::before {
content: "Server paused";
}
#connection-status-panel[data-is-paused] dd {
animation: pulse 1s infinite;
}
@keyframes pulse {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
#connection-status-panel[data-is-paused] dd::after {
background: var(--accent-amber);
}

View File

@ -248,6 +248,7 @@ body.feature-forceShowUnitControlPanel #unit-control-panel {
#advanced-settings-dialog:not([data-show-tanker]) #tanker-checkbox,
#advanced-settings-dialog:not([data-show-AWACS]) #AWACS-checkbox,
#advanced-settings-dialog:not([data-show-TACAN]) #TACAN-options,
#advanced-settings-dialog:not([data-show-radio]) #radio-options {
#advanced-settings-dialog:not([data-show-radio]) #radio-options,
#advanced-settings-dialog:not([data-show-air-unit-checkboxes]) .air-unit-checkbox {
display: none;
}

View File

@ -0,0 +1,92 @@
#unit-list-panel {
bottom:20px;
display:flex;
flex-direction: column;
justify-self:center;
position: absolute;
z-index:999;
}
#unit-list-panel h3 {
margin-bottom:4px;
}
#unit-list-panel-content {
display:flex;
flex-flow: column nowrap;
max-height: 200px;
row-gap: 4px;
}
.unit-list-unit {
border-radius: var( --border-radius-sm );
column-gap: 2px;
display:flex;
flex-flow: row nowrap;
justify-content: space-between;
}
.unit-list-unit:nth-of-type(even) {
background:#ffffff10;
overflow:visible;
}
.unit-list-unit.headers {
margin-bottom:3px;
margin-right:10px;
overflow: hidden;
}
.unit-list-unit.headers [data-sort-field] {
cursor:pointer;
}
.unit-list-unit.headers > * {
background-color: var( --background-grey );
text-align: center;
}
.unit-list-unit > * {
font-size:13px;
overflow: hidden;
padding:2px;
width:80px;
}
.unit-list-unit :first-child {
overflow-x: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width:150px;
}
.unit-list-unit :first-child:hover {
overflow:visible;
}
.unit-list-unit :first-child:hover span {
position:relative;
z-index:9999;
}
.unit-list-unit :first-child:hover span:hover {
background-color: white;
color: var( --background-steel );
}
.unit-list-unit :nth-child(2) {
width:120px;
}
.unit-list-unit > [data-unit-id] {
cursor:pointer;
}
#unit-list-panel-content > * {
cursor:pointer;
}
#unit-list-panel-content > .unit-list-unit:hover {
background-color: var( --background-grey );
}

View File

@ -1054,7 +1054,9 @@ nav.ol-panel> :last-child {
}
.ol-coalitionarea-handle-icon,
.ol-coalitionarea-middle-handle-icon {
.ol-coalitionarea-middle-handle-icon,
.ol-destination-preview-icon,
.ol-destination-preview-handle-icon {
pointer-events: none;
z-index: 9999;
border-radius: 999px;
@ -1072,6 +1074,13 @@ nav.ol-panel> :last-child {
height: 16px;
}
.ol-destination-preview-handle-icon {
background-color: #247be2;
border: 2px solid white;
width: 18px;
height: 18px;
}
dl.ol-data-grid {
align-items: center;
display: flex;

View File

@ -20,6 +20,7 @@
--unit-background-red: #FF5858;
/*** UI Colours **/
--accent-amber: #ffd828;
--accent-green: #8bff63;
--accent-light-blue: #5ca7ff;
--transparent-accent-light-blue: rgba(92, 167, 255, .33);

View File

@ -259,12 +259,12 @@ export interface ShortcutOptions {
shiftKey?: boolean;
}
export interface KeyboardShortcutOptions extends ShortcutOptions {
export interface ShortcutKeyboardOptions extends ShortcutOptions {
code: string;
event?: "keydown" | "keyup";
}
export interface MouseShortcutOptions extends ShortcutOptions {
export interface ShortcutMouseOptions extends ShortcutOptions {
button: number;
event: "mousedown" | "mouseup";
}

View File

@ -21,6 +21,7 @@ import { AirbaseSpawnContextMenu } from "../contextmenus/airbasespawnmenu";
import { Popup } from "../popups/popup";
import { GestureHandling } from "leaflet-gesture-handling";
import { TouchBoxSelect } from "./touchboxselect";
import { DestinationPreviewHandle } from "./markers/destinationpreviewHandle";
var hasTouchScreen = false;
if ("maxTouchPoints" in navigator)
@ -67,6 +68,8 @@ export class Map extends L.Map {
#targetCursor: TargetMarker = new TargetMarker(new L.LatLng(0, 0), { interactive: false });
#destinationPreviewCursors: DestinationPreviewMarker[] = [];
#drawingCursor: DrawingCursor = new DrawingCursor();
#destinationPreviewHandle: DestinationPreviewHandle = new DestinationPreviewHandle(new L.LatLng(0, 0));
#destinationPreviewHandleLine: L.Polyline = new L.Polyline([], { color: "#000000", weight: 3, opacity: 0.5, smoothFactor: 1, dashArray: "4, 8" });
#longPressHandled: boolean = false;
#longPressTimer: number = 0;
@ -133,6 +136,7 @@ export class Map extends L.Map {
this.on("click", (e: any) => this.#onClick(e));
this.on("dblclick", (e: any) => this.#onDoubleClick(e));
this.on("zoomstart", (e: any) => this.#onZoomStart(e));
this.on("zoom", (e: any) => this.#onZoom(e));
this.on("zoomend", (e: any) => this.#onZoomEnd(e));
this.on("drag", (e: any) => this.centerOnUnit(null));
this.on("contextmenu", (e: any) => this.#onContextMenu(e));
@ -141,6 +145,7 @@ export class Map extends L.Map {
this.on('mousedown', (e: any) => this.#onMouseDown(e));
this.on('mouseup', (e: any) => this.#onMouseUp(e));
this.on('mousemove', (e: any) => this.#onMouseMove(e));
this.on('drag', (e: any) => this.#onMouseMove(e));
this.on('keydown', (e: any) => this.#onKeyDown(e));
this.on('keyup', (e: any) => this.#onKeyUp(e));
@ -523,36 +528,38 @@ export class Map extends L.Map {
}
this.hideMapContextMenu();
if (this.#state === IDLE) {
if (this.#state == IDLE) {
this.showMapContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng);
var clickedCoalitionArea = null;
if (!this.#shiftKey) {
if (this.#state === IDLE) {
if (this.#state == IDLE) {
this.showMapContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng);
var clickedCoalitionArea = null;
/* 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())
clickedCoalitionArea = coalitionArea;
else
this.getMapContextMenu().setCoalitionArea(coalitionArea);
/* 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())
clickedCoalitionArea = coalitionArea;
else
this.getMapContextMenu().setCoalitionArea(coalitionArea);
}
}
if (clickedCoalitionArea)
this.showCoalitionAreaContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng, clickedCoalitionArea);
}
if (clickedCoalitionArea)
this.showCoalitionAreaContextMenu(e.originalEvent.x, e.originalEvent.y, e.latlng, clickedCoalitionArea);
}
}
else if (this.#state === MOVE_UNIT) {
if (!e.originalEvent.ctrlKey) {
getApp().getUnitsManager().selectedUnitsClearDestinations();
else if (this.#state === MOVE_UNIT) {
if (!e.originalEvent.ctrlKey) {
getApp().getUnitsManager().selectedUnitsClearDestinations();
}
getApp().getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, this.#shiftKey, this.#destinationGroupRotation)
this.#destinationGroupRotation = 0;
this.#destinationRotationCenter = null;
this.#computeDestinationRotation = false;
}
else {
this.setState(IDLE);
}
getApp().getUnitsManager().selectedUnitsAddDestination(this.#computeDestinationRotation && this.#destinationRotationCenter != null ? this.#destinationRotationCenter : e.latlng, this.#shiftKey, this.#destinationGroupRotation)
this.#destinationGroupRotation = 0;
this.#destinationRotationCenter = null;
this.#computeDestinationRotation = false;
}
else {
this.setState(IDLE);
}
}
@ -586,7 +593,7 @@ export class Map extends L.Map {
}
this.#longPressTimer = window.setTimeout(() => {
if (e.originalEvent.button != 2 || e.originalEvent.ctrlKey)
if (e.originalEvent.button != 2 || e.originalEvent.ctrlKey || e.originalEvent.shiftKey)
return;
this.hideMapContextMenu();
@ -684,6 +691,11 @@ export class Map extends L.Map {
this.#isZooming = true;
}
#onZoom(e: any) {
if (this.#centerUnit != null)
this.#panToUnit(this.#centerUnit);
}
#onZoomEnd(e: any) {
this.#isZooming = false;
}
@ -726,7 +738,7 @@ export class Map extends L.Map {
#showDestinationCursors() {
const singleCursor = !this.#shiftKey;
const selectedUnitsCount = getApp().getUnitsManager().getSelectedUnits({ excludeHumans: false, onlyOnePerGroup: true }).length;
const selectedUnitsCount = getApp().getUnitsManager().getSelectedUnits({ excludeHumans: true, onlyOnePerGroup: true }).length;
if (selectedUnitsCount > 0) {
if (singleCursor && this.#destinationPreviewCursors.length != 1) {
this.#hideDestinationCursors();
@ -740,6 +752,9 @@ export class Map extends L.Map {
this.#destinationPreviewCursors.splice(0, 1);
}
this.#destinationPreviewHandleLine.addTo(this);
this.#destinationPreviewHandle.addTo(this);
while (this.#destinationPreviewCursors.length < selectedUnitsCount) {
var cursor = new DestinationPreviewMarker(this.getMouseCoordinates(), { interactive: false });
cursor.addTo(this);
@ -761,6 +776,9 @@ export class Map extends L.Map {
this.#destinationPreviewCursors[idx].setLatLng(this.#shiftKey ? latlng : this.getMouseCoordinates());
})
};
this.#destinationPreviewHandleLine.setLatLngs([groupLatLng, this.getMouseCoordinates()]);
this.#destinationPreviewHandle.setLatLng(this.getMouseCoordinates());
}
#hideDestinationCursors() {
@ -770,22 +788,15 @@ export class Map extends L.Map {
})
this.#destinationPreviewCursors = [];
this.#destinationPreviewHandleLine.removeFrom(this);
this.#destinationPreviewHandle.removeFrom(this);
/* Reset the variables used to compute the rotation of the group cursors */
this.#destinationGroupRotation = 0;
this.#computeDestinationRotation = false;
this.#destinationRotationCenter = null;
}
#showTargetCursor() {
this.#hideTargetCursor();
this.#targetCursor.addTo(this);
}
#hideTargetCursor() {
this.#targetCursor.setLatLng(new L.LatLng(0, 0));
this.removeLayer(this.#targetCursor);
}
#showDrawingCursor() {
this.#hideDefaultCursor();
if (!this.hasLayer(this.#drawingCursor))
@ -803,7 +814,6 @@ export class Map extends L.Map {
if (this.#ctrlKey || this.#selecting) {
/* Hide all non default cursors */
this.#hideDestinationCursors();
this.#hideTargetCursor();
this.#hideDrawingCursor();
this.#showDefaultCursor();
@ -811,13 +821,11 @@ export class Map extends L.Map {
/* Hide all the unnecessary cursors depending on the active state */
if (this.#state !== IDLE) this.#hideDefaultCursor();
if (this.#state !== MOVE_UNIT) this.#hideDestinationCursors();
//if (![BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) this.#hideTargetCursor();
if (this.#state !== COALITIONAREA_DRAW_POLYGON) this.#hideDrawingCursor();
/* Show the active cursor depending on the active state */
if (this.#state === IDLE) this.#showDefaultCursor();
else if (this.#state === MOVE_UNIT) this.#showDestinationCursors();
//else if ([BOMBING, CARPET_BOMBING, FIRE_AT_AREA].includes(this.#state)) this.#showTargetCursor();
else if (this.#state === COALITIONAREA_DRAW_POLYGON) this.#showDrawingCursor();
}
}

View File

@ -0,0 +1,19 @@
import { DivIcon, LatLng } from "leaflet";
import { CustomMarker } from "../markers/custommarker";
export class DestinationPreviewHandle extends CustomMarker {
constructor(latlng: LatLng) {
super(latlng, {interactive: true, draggable: true});
}
createIcon() {
this.setIcon(new DivIcon({
iconSize: [18, 18],
iconAnchor: [9, 9],
className: "leaflet-destination-preview-handle-marker",
}));
var el = document.createElement("div");
el.classList.add("ol-destination-preview-handle-icon");
this.getElement()?.appendChild(el);
}
}

View File

@ -36,8 +36,16 @@ export class MissionManager {
}
updateBullseyes(data: BullseyesData) {
const commandMode = getApp().getMissionManager().getCommandModeOptions().commandMode;
for (let idx in data.bullseyes) {
const bullseye = data.bullseyes[idx];
// Prevent Red and Blue coalitions seeing each other's bulleye(s)
if ((bullseye.coalition === "red" && commandMode === BLUE_COMMANDER)
|| (bullseye.coalition === "blue" && commandMode === RED_COMMANDER)) {
continue;
}
if (!(idx in this.#bullseyes))
this.#bullseyes[idx] = new Bullseye([0, 0]).addTo(getApp().getMap());

View File

@ -25,6 +25,7 @@ import { helicopterDatabase } from "./unit/databases/helicopterdatabase";
import { groundUnitDatabase } from "./unit/databases/groundunitdatabase";
import { navyUnitDatabase } from "./unit/databases/navyunitdatabase";
import { ConfigurationOptions } from "./interfaces";
import { UnitListPanel } from "./panels/unitlistpanel";
export class OlympusApp {
/* Global data */
@ -181,6 +182,7 @@ export class OlympusApp {
.add("serverStatus", new ServerStatusPanel("server-status-panel"))
.add("unitControl", new UnitControlPanel("unit-control-panel"))
.add("unitInfo", new UnitInfoPanel("unit-info-panel"))
.add("unitList", new UnitListPanel("unit-list-panel", "unit-list-panel-content"))
// Popups
this.getPopupsManager().add("infoPopup", new Popup("info-popup"));
@ -196,8 +198,8 @@ export class OlympusApp {
if (config && config.address != undefined && config.port != undefined) {
const address = config.address;
const port = config.port;
if (typeof address === 'string' && typeof port == 'number') {
this.getServerManager().setAddress(address == "*" ? window.location.hostname : address, port);
if (typeof address === 'string' && typeof port == 'number') {
this.getServerManager().setAddress(address == "*" ? window.location.hostname : address, port);
}
}
else {
@ -236,22 +238,38 @@ export class OlympusApp {
});
const shortcutManager = this.getShortcutManager();
shortcutManager.add("toggleDemo", new ShortcutKeyboard({
shortcutManager.addKeyboardShortcut("toggleDemo", {
"callback": () => {
this.getServerManager().toggleDemoEnabled();
},
"code": "KeyT"
})).add("togglePause", new ShortcutKeyboard({
}).addKeyboardShortcut("togglePause", {
"altKey": false,
"callback": () => {
this.getServerManager().setPaused(!this.getServerManager().getPaused());
},
"code": "Space",
"ctrlKey": false
}));
}).addKeyboardShortcut("deselectAll", {
"callback": (ev: KeyboardEvent) => {
this.getUnitsManager().deselectAllUnits();
},
"code": "Escape"
}).addKeyboardShortcut("toggleUnitLabels", {
"altKey": false,
"callback": () => {
const chk = document.querySelector(`label[title="Show unit labels"] input[type="checkbox"]`);
if (chk instanceof HTMLElement) {
chk.click();
}
},
"code": "KeyL",
"ctrlKey": false,
"shiftKey": false
});
["KeyW", "KeyA", "KeyS", "KeyD", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].forEach(code => {
shortcutManager.add(`pan${code}keydown`, new ShortcutKeyboard({
shortcutManager.addKeyboardShortcut(`pan${code}keydown`, {
"altKey": false,
"callback": (ev: KeyboardEvent) => {
this.getMap().handleMapPanning(ev);
@ -259,30 +277,42 @@ export class OlympusApp {
"code": code,
"ctrlKey": false,
"event": "keydown"
}));
});
});
["KeyW", "KeyA", "KeyS", "KeyD", "ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"].forEach(code => {
shortcutManager.add(`pan${code}keyup`, new ShortcutKeyboard({
shortcutManager.addKeyboardShortcut(`pan${code}keyup`, {
"callback": (ev: KeyboardEvent) => {
this.getMap().handleMapPanning(ev);
},
"code": code
}));
});
});
["Digit1", "Digit2", "Digit3", "Digit4", "Digit5", "Digit6", "Digit7", "Digit8", "Digit9"].forEach(code => {
shortcutManager.add(`hotgroup${code}`, new ShortcutKeyboard({
const digits = ["Digit1", "Digit2", "Digit3", "Digit4", "Digit5", "Digit6", "Digit7", "Digit8", "Digit9"];
digits.forEach(code => {
shortcutManager.addKeyboardShortcut(`hotgroup${code}`, {
"altKey": false,
"callback": (ev: KeyboardEvent) => {
if (ev.ctrlKey && ev.shiftKey)
this.getUnitsManager().selectedUnitsAddToHotgroup(parseInt(ev.code.substring(5)));
else if (ev.ctrlKey && !ev.shiftKey)
this.getUnitsManager().selectedUnitsSetHotgroup(parseInt(ev.code.substring(5)));
else if (!ev.ctrlKey && ev.shiftKey)
this.getUnitsManager().selectUnitsByHotgroup(parseInt(ev.code.substring(5)), false);
else
this.getUnitsManager().selectUnitsByHotgroup(parseInt(ev.code.substring(5)));
},
"code": code
}));
});
});
// Stop hotgroup controls sending the browser to another tab
digits.forEach(code => {
document.addEventListener("keydown", (ev: KeyboardEvent) => {
if (ev.code === code && ev.ctrlKey === true && ev.altKey === false && ev.shiftKey === false) {
ev.preventDefault();
}
});
});
// TODO: move from here in dedicated class

View File

@ -1,12 +1,27 @@
import { Panel } from "./panel";
export class ConnectionStatusPanel extends Panel {
constructor(ID: string) {
super( ID );
}
update(connected: boolean) {
this.getElement().toggleAttribute( "data-is-connected", connected );
showDisconnected() {
this.getElement().toggleAttribute( "data-is-connected", false );
this.getElement().toggleAttribute( "data-is-paused", false );
}
showConnected() {
this.getElement().toggleAttribute( "data-is-connected", true );
this.getElement().toggleAttribute( "data-is-paused", false );
}
showServerPaused() {
this.getElement().toggleAttribute( "data-is-connected", false );
this.getElement().toggleAttribute( "data-is-paused", true );
}
}

View File

@ -26,6 +26,7 @@ export class MouseInfoPanel extends Panel {
getApp().getMap()?.on("click", (e: any) => this.#onMapClick(e));
getApp().getMap()?.on('zoom', (e: any) => this.#onZoom(e));
getApp().getMap()?.on('mousemove', (e: any) => this.#onMouseMove(e));
getApp().getMap()?.on('drag', (e: any) => this.#onMouseMove(e));
document.addEventListener('unitsSelection', (e: CustomEvent<Unit[]>) => this.#update());
document.addEventListener('clearSelection', () => this.#update());
@ -107,14 +108,15 @@ export class MouseInfoPanel extends Panel {
#drawMeasureLine() {
var mouseLatLng = getApp().getMap().containerPointToLatLng(getApp().getMap().getMousePosition());
const mousePosition = getApp().getMap().getMousePosition();
if (this.#measurePoint != null) {
var points = [this.#measurePoint, mouseLatLng];
this.#measureLine.setLatLngs(points);
var dist = distance(this.#measurePoint.lat, this.#measurePoint.lng, mouseLatLng.lat, mouseLatLng.lng);
var bear = bearing(this.#measurePoint.lat, this.#measurePoint.lng, mouseLatLng.lat, mouseLatLng.lng);
var startXY = getApp().getMap().latLngToContainerPoint(this.#measurePoint);
var dx = (getApp().getMap().getMousePosition().x - startXY.x);
var dy = (getApp().getMap().getMousePosition().y - startXY.y);
var dx = mousePosition.x - startXY.x;
var dy = mousePosition.y - startXY.y;
var angle = Math.atan2(dy, dx);
if (angle > Math.PI / 2)
@ -133,8 +135,8 @@ export class MouseInfoPanel extends Panel {
let data = [`${bng}°`, `${str} ${unit}`];
this.#measureBox.innerText = data.join(" / ");
this.#measureBox.style.left = (getApp().getMap().getMousePosition().x + startXY.x) / 2 - this.#measureBox.offsetWidth / 2 + "px";
this.#measureBox.style.top = (getApp().getMap().getMousePosition().y + startXY.y) / 2 - this.#measureBox.offsetHeight / 2 + "px";
this.#measureBox.style.left = (mousePosition.x + startXY.x) / 2 - this.#measureBox.offsetWidth / 2 + "px";
this.#measureBox.style.top = (mousePosition.y + startXY.y) / 2 - this.#measureBox.offsetHeight / 2 + "px";
this.#measureBox.style.rotate = angle + "rad";
}
}

View File

@ -295,7 +295,7 @@ export class UnitControlPanel extends Panel {
const TACANCallsignInput = this.#advancedSettingsDialog.querySelector("#tacan-callsign")?.querySelector("input") as HTMLInputElement;
const radioMhzInput = this.#advancedSettingsDialog.querySelector("#radio-mhz")?.querySelector("input") as HTMLInputElement;
const radioCallsignNumberInput = this.#advancedSettingsDialog.querySelector("#radio-callsign-number")?.querySelector("input") as HTMLInputElement;
const unit = units[0];
const roles = aircraftDatabase.getByName(unit.getName())?.loadouts?.map((loadout) => {return loadout.roles})
const tanker = roles != undefined && Array.prototype.concat.apply([], roles)?.includes("Refueling");
@ -305,6 +305,7 @@ export class UnitControlPanel extends Panel {
/* Activate the correct options depending on unit type */
this.#advancedSettingsDialog.toggleAttribute("data-show-settings", !tanker && !AWACS);
this.#advancedSettingsDialog.toggleAttribute("data-show-air-unit-checkboxes", ["Aircraft", "Helicopter"].includes(units[0].getCategory()));
this.#advancedSettingsDialog.toggleAttribute("data-show-tasking", tanker || AWACS);
this.#advancedSettingsDialog.toggleAttribute("data-show-tanker", tanker);
this.#advancedSettingsDialog.toggleAttribute("data-show-AWACS", AWACS);

View File

@ -0,0 +1,191 @@
import { getApp } from "..";
import { ShortcutKeyboard } from "../shortcut/shortcut";
import { Unit } from "../unit/unit";
import { Panel } from "./panel";
export class UnitListPanel extends Panel {
#contentElement: HTMLElement;
#currentSortAlgorithm: string = "unitName";
#currentSortDirection: string = "ASC";
#units: Unit[] = [];
#unitNameCache: {[key:string]: string} = {};
#updatesInterval!: ReturnType<typeof setInterval>;
constructor(panelElement: string, contentElement: string) {
super(panelElement);
const getElement = document.getElementById(contentElement);
if (getElement instanceof HTMLElement)
this.#contentElement = getElement;
else
throw new Error(`UnitList: unable to find element "${contentElement}".`);
// Add the header click listener and sorting
[].slice.call(this.getElement().querySelectorAll(".headers > *")).forEach((header: HTMLElement) => {
header.addEventListener("click", (ev: MouseEvent) => {
const el = ev.target;
if (el instanceof HTMLElement) {
const newSort = el.getAttribute("data-sort-field") || "unitName";
if (this.#currentSortAlgorithm === newSort)
this.#currentSortDirection = (this.#currentSortDirection === "ASC") ? "DESC" : "ASC";
else
this.#currentSortDirection = "ASC";
this.#currentSortAlgorithm = newSort;
this.doUpdate();
}
});
});
// Dynamically listen for clicks in order to do stuff with units
this.getElement().addEventListener("click", (ev: MouseEvent) => {
const t = ev.target;
if (t instanceof HTMLElement) {
const el = t.closest( "[data-unit-id]");
if (el instanceof HTMLElement) {
let id: number = Number(el.getAttribute("data-unit-id") || 0);
getApp().getUnitsManager().selectUnit(id);
}
}
});
new ShortcutKeyboard({
"callback": () => { this.toggle() },
"code": "KeyU"
});
this.startUpdates();
}
doUpdate() {
if (!this.getVisible())
return;
this.#contentElement.innerHTML = "";
this.#units = Object.values(getApp().getUnitsManager().getUnits());
if (this.#currentSortAlgorithm === "coalition")
this.#sortUnitsByCoalition();
if (this.#currentSortAlgorithm === "name")
this.#sortUnitsByName();
if (this.#currentSortAlgorithm === "unitName")
this.#sortUnitsByUnitName();
Object.values(this.#units).forEach((unit: Unit) => {
// Exclude dead units
if (!unit.getAlive()) {
return;
}
const name = unit.getName();
if ( this.#unitNameCache.hasOwnProperty( name ) === false ) {
this.#unitNameCache[name] = unit.getDatabase()?.getByName(unit.getName())?.label || unit.getName();
}
this.#contentElement.innerHTML += `
<div class="unit-list-unit" data-unit-id="${unit.ID}" data-value-unitName="${unit.getUnitName()}" data-value-name="${unit.getName()}" data-value-coalition="${unit.getCoalition()}" data-value-human="${unit.getHuman() ? "Human" : "AI"}">
<div><span>${unit.getUnitName()}</span></div>
<div>${this.#unitNameCache[name]}</div>
<div>${unit.getCategory()}</div>
<div>${unit.getCoalition()}</div>
<div>${unit.getHuman() ? "Human" : "AI"}</div>
</div>`;
});
}
getContentElement() {
return this.#contentElement;
}
#sortStringsCompare(str1: string, str2: string) {
if (str1 > str2) {
return (this.#currentSortDirection === "ASC") ? 1 : -1;
} else if (str1 < str2) {
return (this.#currentSortDirection === "ASC") ? -1 : 1;
}
return 0;
}
#sortUnitsByUnitName() {
this.#units.sort((unit1: Unit, unit2: Unit) => {
const str1 = unit1.getUnitName().toLowerCase();
const str2 = unit2.getUnitName().toLowerCase();
return this.#sortStringsCompare(str1, str2);
});
}
#sortUnitsByCategory() {
this.#units.sort((unit1: Unit, unit2: Unit) => {
let str1 = unit1.getCategory();
let str2 = unit2.getCategory();
let cmp = this.#sortStringsCompare(str1, str2);
if (cmp !== 0)
return cmp;
str1 = unit1.getUnitName().toLowerCase();
str2 = unit2.getUnitName().toLowerCase();
return this.#sortStringsCompare(str1, str2);
});
}
#sortUnitsByCoalition() {
this.#units.sort((unit1: Unit, unit2: Unit) => {
let str1 = unit1.getCoalition();
let str2 = unit2.getCoalition();
let cmp = this.#sortStringsCompare(str1, str2);
if (cmp !== 0)
return cmp;
str1 = unit1.getUnitName().toLowerCase();
str2 = unit2.getUnitName().toLowerCase();
return this.#sortStringsCompare(str1, str2);
});
}
#sortUnitsByName() {
this.#units.sort((unit1: Unit, unit2: Unit) => {
const str1 = unit1.getName().toLowerCase();
const str2 = unit2.getName().toLowerCase();
return this.#sortStringsCompare(str1, str2);
});
}
startUpdates() {
this.doUpdate();
this.#updatesInterval = setInterval(() => {
this.doUpdate();
}, 4000);
}
stopUpdates() {
clearInterval(this.#updatesInterval);
}
toggle() {
if (this.getVisible())
this.stopUpdates();
else
this.startUpdates();
super.toggle();
}
}

View File

@ -17,6 +17,8 @@ export class ServerManager {
#sessionHash: string | null = null;
#lastUpdateTimes: {[key: string]: number} = {}
#demoEnabled = false;
#previousMissionElapsedTime:number = 0; // Track if mission elapsed time is increasing (i.e. is the server paused)
#serverIsPaused: boolean = false;
constructor() {
this.#lastUpdateTimes[UNITS_URI] = Date.now();
@ -445,9 +447,25 @@ export class ServerManager {
var time = getApp().getUnitsManager()?.update(buffer);
return time;
}, true);
(getApp().getPanelsManager().get("connectionStatus") as ConnectionStatusPanel).update(this.getConnected());
const elapsedMissionTime = getApp().getMissionManager().getDateAndTime().elapsedTime;
this.#serverIsPaused = ( elapsedMissionTime === this.#previousMissionElapsedTime );
this.#previousMissionElapsedTime = elapsedMissionTime;
const csp = (getApp().getPanelsManager().get("connectionStatus") as ConnectionStatusPanel);
if ( this.getConnected() ) {
if ( this.getServerIsPaused() ) {
csp.showServerPaused();
} else {
csp.showConnected();
}
} else {
csp.showDisconnected();
}
}
}, 5000);
}, ( this.getServerIsPaused() ? 500 : 5000 ));
window.setInterval(() => {
if (!this.getPaused() && getApp().getMissionManager().getCommandModeOptions().commandMode != NONE) {
@ -521,4 +539,8 @@ export class ServerManager {
getPaused() {
return this.#paused;
}
getServerIsPaused() {
return this.#serverIsPaused;
}
}

View File

@ -1,4 +1,4 @@
import { KeyboardShortcutOptions, MouseShortcutOptions, ShortcutOptions } from "../interfaces";
import { ShortcutKeyboardOptions, ShortcutMouseOptions, ShortcutOptions } from "../interfaces";
import { keyEventWasInInput } from "../other/utils";
export abstract class Shortcut {
@ -14,7 +14,7 @@ export abstract class Shortcut {
}
export class ShortcutKeyboard extends Shortcut {
constructor(config: KeyboardShortcutOptions) {
constructor(config: ShortcutKeyboardOptions) {
config.event = config.event || "keyup";
super(config);
@ -37,7 +37,7 @@ export class ShortcutKeyboard extends Shortcut {
}
export class ShortcutMouse extends Shortcut {
constructor(config: MouseShortcutOptions) {
constructor(config: ShortcutMouseOptions) {
super(config);
}
}

View File

@ -1,5 +1,6 @@
import { ShortcutKeyboardOptions, ShortcutMouseOptions } from "../interfaces";
import { Manager } from "../other/manager";
import { Shortcut } from "./shortcut";
import { ShortcutKeyboard, ShortcutMouse } from "./shortcut";
export class ShortcutManager extends Manager {
@ -25,8 +26,18 @@ export class ShortcutManager extends Manager {
}
add(name: string, shortcut: Shortcut) {
super.add(name, shortcut);
add( name: string, shortcut:any ) {
console.error( "ShortcutManager:add() cannot be used. Use addKeyboardShortcut or addMouseShortcut." );
return this;
}
addKeyboardShortcut( name:string, shortcutKeyboardOptions:ShortcutKeyboardOptions ) {
super.add( name, new ShortcutKeyboard( shortcutKeyboardOptions ) );
return this;
}
addMouseShortcut( name:string, shortcutMouseOptions:ShortcutMouseOptions ) {
super.add( name, new ShortcutMouse( shortcutMouseOptions ) );
return this;
}

View File

@ -201,8 +201,12 @@ export class UnitsManager {
*
* @param hotgroup The hotgroup number
*/
selectUnitsByHotgroup(hotgroup: number) {
this.deselectAllUnits();
selectUnitsByHotgroup(hotgroup: number, deselectAllUnits: boolean = true ) {
if ( deselectAllUnits ) {
this.deselectAllUnits();
}
this.getUnitsByHotgroup(hotgroup).forEach((unit: Unit) => unit.setSelected(true))
}

View File

@ -31,6 +31,7 @@
<%- include('panels/serverstatus.ejs') %>
<%- include('panels/hotgroup.ejs') %>
<%- include('panels/logpanel.ejs') %>
<%- include('panels/unitlist.ejs') %>
<!-- Context menus -->
<%- include('contextmenus/airbase.ejs') %>

View File

@ -38,28 +38,28 @@
<hr>
</div>
<div id="general-settings-grid">
<div id="prohibit-jettison-checkbox" class="ol-checkbox">
<div id="prohibit-jettison-checkbox" class="ol-checkbox air-unit-checkbox">
<label title="The unit will not jettison external stores">
<input type="checkbox"/>
Prohibit jettison
</label>
</div>
<div id="prohibit-afterburner-checkbox" class="ol-checkbox">
<div id="prohibit-afterburner-checkbox" class="ol-checkbox air-unit-checkbox">
<label title="The unit will not engage the afterburner">
<input type="checkbox" />
Prohibit afterburner
</label>
</div>
<div id="prohibit-AA-checkbox" class="ol-checkbox">
<div id="prohibit-AA-checkbox" class="ol-checkbox air-unit-checkbox">
<label title="The unit will not engage airborne targets">
<input type="checkbox" />
Prohibit A/A
</label>
</div>
<div id="prohibit-AG-checkbox" class="ol-checkbox">
<div id="prohibit-AG-checkbox" class="ol-checkbox air-unit-checkbox">
<label title="The unit will not engage ground targets">
<input type="checkbox" />
Prohibit A/G

View File

@ -0,0 +1,11 @@
<div id="unit-list-panel" class="ol-panel">
<h3>Unit List</h3>
<div class="unit-list-unit headers">
<div data-sort-field="unitName">Name</div>
<div data-sort-field="name">Vehicle</div>
<div data-sort-field="category">Category</div>
<div data-sort-field="coalition">Coalition</div>
<div>Human/AI</div>
</div>
<div id="unit-list-panel-content" class="ol-scrollable"></div>
</div>