Added minimap summary panel

This commit is contained in:
Davide Passoni 2024-06-06 17:14:07 +02:00
parent 9c53e0605b
commit a9a6b18943
10 changed files with 165 additions and 69 deletions

View File

@ -154,15 +154,9 @@ export const mapBounds = {
"SinaiMap": { bounds: new LatLngBounds([34.312222, 28.523333], [25.946944, 36.897778]), zoom: 4 },
}
export const DCSMapsZoomLevelsByTheatre: { [key: string]: { minNativeZoom?: number, maxNativeZoom?: number, minZoom?: number }[] } = {
"Syria": [],
"MarianaIslands": [{ minNativeZoom: 1, maxNativeZoom: 13, }, { minNativeZoom: 14, maxNativeZoom: 18, minZoom: 14 }],
"Nevada": [],
"PersianGulf": [],
"Caucasus": [],
"Falklands": [],
"Normandy": [],
"SinaiMap": [],
export const mapMirrors = {
"DCS Map mirror 1": "https://maps.dcsolympus.com/maps",
"DCS Map mirror 2": "https://refugees.dcsolympus.com/maps"
}
export const defaultMapLayers = {

View File

@ -1,3 +1,4 @@
import { ServerStatus } from "./interfaces";
import { Unit } from "./unit/unit";
interface CustomEventMap {
@ -6,25 +7,16 @@ interface CustomEventMap {
"unitsSelection": CustomEvent<Unit[]>,
"unitsDeselection": CustomEvent<Unit[]>,
"clearSelection": CustomEvent<any>,
"unitCreation": CustomEvent<Unit>,
"unitDeletion": CustomEvent<Unit>,
"unitDeath": CustomEvent<Unit>,
"unitUpdated": CustomEvent<Unit>,
"unitMoveCommand": CustomEvent<Unit>,
"unitAttackCommand": CustomEvent<Unit>,
"unitLandCommand": CustomEvent<Unit>,
"unitSetAltitudeCommand": CustomEvent<Unit>,
"unitSetSpeedCommand": CustomEvent<Unit>,
"unitSetOption": CustomEvent<Unit>,
"groupCreation": CustomEvent<Unit[]>,
"groupDeletion": CustomEvent<Unit[]>,
"mapStateChanged": CustomEvent<string>,
"mapContextMenu": CustomEvent<any>,
"mapOptionChanged": CustomEvent<any>,
"mapOptionsChanged": CustomEvent<any>,
"mapOptionsChanged": CustomEvent<any>, // TODO not very clear, why the two options?
"commandModeOptionsChanged": CustomEvent<any>,
"contactsUpdated": CustomEvent<Unit>,
"activeCoalitionChanged": CustomEvent<any>
"activeCoalitionChanged": CustomEvent<any>,
"serverStatusUpdated": CustomEvent<ServerStatus>
}
declare global {

View File

@ -282,4 +282,13 @@ export interface ShortcutMouseOptions extends ShortcutOptions {
export interface Manager {
add: CallableFunction;
}
export interface ServerStatus {
frameRate: number,
load: number,
elapsedTime: number,
missionTime: DateAndTime["time"],
connected: boolean,
paused: boolean
}

View File

@ -11,8 +11,7 @@ import { bearing, /*createCheckboxOption, createSliderInputOption, createTextInp
import { DestinationPreviewMarker } from "./markers/destinationpreviewmarker";
import { TemporaryUnitMarker } from "./markers/temporaryunitmarker";
import { ClickableMiniMap } from "./clickableminimap";
import { SVGInjector } from '@tanem/svg-injector'
import { 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, DCSMapsZoomLevelsByTheatre, 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 } from "../constants/constants";
import { CoalitionArea } from "./coalitionarea/coalitionarea";
//import { CoalitionAreaContextMenu } from "../contextmenus/coalitionareacontextmenu";
import { DrawingCursor } from "./coalitionarea/drawingcursor";
@ -133,7 +132,7 @@ export class Map extends L.Map {
this.#ID = ID;
this.setLayer("DCS Map");
this.setLayer("DCS Map mirror 2");
/* Minimap */
var minimapLayer = new L.TileLayer(this.#mapLayers[Object.keys(this.#mapLayers)[0]].urlTemplate, { minZoom: 0, maxZoom: 13 });
@ -268,25 +267,39 @@ export class Map extends L.Map {
} else {
this.#layer = new L.TileLayer(layerData.urlTemplate, layerData);
}
/* DCS core layers are handled here */
} else if (["DCS Map", "DCS Satellite"].includes(layerName)) {
/* DCS core layers are handled here */
} else if (["DCS Map mirror 1", "DCS Map mirror 2"].includes(layerName) ) {
let layerData = this.#mapLayers["ArcGIS Satellite"];
let layers = [new L.TileLayer(layerData.urlTemplate, layerData)];
let template = `https://maps.dcsolympus.com/maps/${layerName === "DCS Map" ? "alt" : "sat"}-{theatre}-modern/{z}/{x}/{y}.png`;
layers.push(...DCSMapsZoomLevelsByTheatre[theatre].map((nativeZoomLevels: any) => {
return new L.TileLayer(template.replace("{theatre}", theatre.toLowerCase()), { ...nativeZoomLevels, maxZoom: 20, crossOrigin: "" });
}));
this.#layer = new L.LayerGroup(layers);
/* Load the configuration file */
const mirror = mapMirrors[layerName as keyof typeof mapMirrors];
const request = new Request(mirror + "/config.json");
fetch(request).then((response) => {
if (response.status === 200) {
return response.json();
} else {
return {};
}
}).then((res: any) => {
if ("alt-" + theatre.toLowerCase() in res) {
let template = `${mirror}/alt-${theatre.toLowerCase()}/{z}/{x}/{y}.png`;
layers.push(...res["alt-" + theatre.toLowerCase()].map((layerConfig: any) => {
return new L.TileLayer(template, {...layerConfig, crossOrigin: ""});
}));
}
this.#layer = new L.LayerGroup(layers);
this.#layer?.addTo(this);
}).catch(() => {
this.#layer = new L.LayerGroup(layers);
this.#layer?.addTo(this);
})
}
this.#layer?.addTo(this);
this.#layerName = layerName;
}
getLayers() {
let layers = ["DCS Map", "DCS Satellite"]
let layers = ["DCS Map mirror 1", "DCS Map mirror 2"];
layers.push(...Object.keys(this.#mapLayers));
return layers;
}

View File

@ -18,6 +18,8 @@ export class MissionManager {
#airbases: { [name: string]: Airbase } = {};
#theatre: string = "";
#dateAndTime: DateAndTime = {date: {Year: 0, Month: 0, Day: 0}, time: {h: 0, m: 0, s: 0}, startTime: 0, elapsedTime: 0};
#load: number = 0;
#frameRate: number = 0;
#commandModeOptions: CommandModeOptions = {commandMode: NONE, restrictSpawns: false, restrictToCoalition: false, setupTime: Infinity, spawnPoints: {red: Infinity, blue: Infinity}, eras: []};
#remainingSetupTime: number = 0;
#spentSpawnPoint: number = 0;
@ -212,6 +214,22 @@ export class MissionManager {
this.refreshSpawnPoints();
}
setLoad(load: number) {
this.#load = load;
}
getLoad() {
return this.#load;
}
setFrameRate(frameRate: number) {
this.#frameRate = frameRate;
}
getFrameRate() {
return this.#frameRate;
}
showCommandModeDialog() {
//const options = this.getCommandModeOptions()
//const { restrictSpawns, restrictToCoalition, setupTime } = options;

View File

@ -5,8 +5,9 @@ import { AIRBASES_URI, BULLSEYE_URI, COMMANDS_URI, LOGS_URI, MISSION_URI, NONE,
//import { LogPanel } from '../panels/logpanel';
//import { Popup } from '../popups/popup';
//import { ConnectionStatusPanel } from '../panels/connectionstatuspanel';
import { AirbasesData, BullseyesData, GeneralSettings, MissionData, Radio, ServerRequestOptions, TACAN } from '../interfaces';
import { AirbasesData, BullseyesData, GeneralSettings, MissionData, Radio, ServerRequestOptions, ServerStatus, TACAN } from '../interfaces';
import { zeroAppend } from '../other/utils';
import { SiTheregister } from 'react-icons/si';
export class ServerManager {
#connected: boolean = false;
@ -80,8 +81,10 @@ export class ServerManager {
const result = JSON.parse(xmlHttp.responseText);
this.#lastUpdateTimes[uri] = callback(result);
//if (result.frameRate !== undefined && result.load !== undefined)
// (getApp().getPanelsManager().get("serverStatus") as ServerStatusPanel).update(result.frameRate, result.load);
if (result.frameRate !== undefined && result.load !== undefined) {
getApp().getMissionManager().setLoad(result.load);
getApp().getMissionManager().setFrameRate(result.frameRate);
}
}
} else if (xmlHttp.status == 401) {
/* Bad credentials */
@ -489,40 +492,25 @@ export class ServerManager {
var time = getApp().getUnitsManager()?.update(buffer);
return time;
}, true);
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();
//}
}
}, (this.getServerIsPaused() ? 500 : 5000)));
// Mission clock and elapsed time
this.#intervals.push(window.setInterval(() => {
if (!this.getConnected() || this.#serverIsPaused) {
return;
}
const elapsedMissionTime = getApp().getMissionManager().getDateAndTime().elapsedTime;
//const csp = (getApp().getPanelsManager().get("connectionStatus") as ConnectionStatusPanel);
//const mt = getApp().getMissionManager().getDateAndTime().time;
//csp.setMissionTime([mt.h, mt.m, mt.s].map(n => zeroAppend(n, 2)).join(":"));
//csp.setElapsedTime(new Date(elapsedMissionTime * 1000).toISOString().substring(11, 19));
this.#serverIsPaused = (elapsedMissionTime === this.#previousMissionElapsedTime);
this.#previousMissionElapsedTime = elapsedMissionTime;
document.dispatchEvent(new CustomEvent("serverStatusUpdated", {
detail: {
frameRate: getApp().getMissionManager().getFrameRate(),
load: getApp().getMissionManager().getLoad(),
elapsedTime: getApp().getMissionManager().getDateAndTime().elapsedTime,
missionTime: getApp().getMissionManager().getDateAndTime().time,
connected: this.getConnected(),
paused: this.getPaused()
} as ServerStatus
}));
}, 1000));

View File

@ -0,0 +1,79 @@
import React, { useState, useEffect } from "react";
import { zeroAppend } from "../../other/utils";
import { DateAndTime } from "../../interfaces";
export function MiniMapPanel(props: {
}) {
var [frameRate, setFrameRate] = useState(0);
var [load, setLoad] = useState(0);
var [elapsedTime, setElapsedTime] = useState(0);
var [missionTime, setMissionTime] = useState({ h: 0, m: 0, s: 0 } as DateAndTime["time"]);
var [connected, setConnected] = useState(false);
var [paused, setPaused] = useState(false);
var [showMissionTime, setShowMissionTime] = useState(false);
document.addEventListener("serverStatusUpdated", (ev) => {
setFrameRate(ev.detail.frameRate);
setLoad(ev.detail.load);
setElapsedTime(ev.detail.elapsedTime);
setMissionTime(ev.detail.missionTime);
setConnected(ev.detail.connected);
setPaused(ev.detail.paused);
})
// A bit of a hack to set the rounded borders to the minimap
useEffect(() => {
let miniMap = document.querySelector(".leaflet-control-minimap");
if (miniMap) {
miniMap.classList.add("rounded-t-lg")
}
})
// Compute the time string depending on mission or elapsed time
let hours = 0;
let minutes = 0;
let seconds = 0;
if (showMissionTime) {
hours = missionTime.h;
minutes = missionTime.m;
seconds = missionTime.s;
} else {
hours = Math.floor(elapsedTime / 3600);
minutes = Math.floor((elapsedTime / 60)) % 60;
seconds = Math.round(elapsedTime) % 60;
}
let timeString = `${zeroAppend(hours, 2)}:${zeroAppend(minutes, 2)}:${zeroAppend(seconds, 2)}`
// Choose frame rate string color
let frameRateColor = "#8BFF63";
if (frameRate < 30)
frameRateColor = "#F05252";
else if (frameRate >= 30 && frameRate < 60)
frameRateColor = "#FF9900"
// Choose load string color
let loadColor = "#8BFF63";
if (load > 1000)
loadColor = "#F05252";
else if (load >= 100 && load < 1000)
loadColor = "#FF9900"
return <div onClick={() => setShowMissionTime(!showMissionTime)} className="absolute w-[288px] top-[233px] right-[10px] z-ui-0 text-sm dark:text-gray-200 bg-gray-200 dark:bg-olympus-800/90 backdrop-blur-lg backdrop-grayscale flex items-center justify-between p-3 rounded-b-lg">
{
!connected ?
<div className="font-semibold animate-pulse flex items-center gap-2"><div className="bg-[#F05252] relative w-4 h-4 rounded-full"></div>Server disconnected</div> :
paused ?
<div className="font-semibold animate-pulse flex items-center gap-2"><div className="bg-[#FF9900] relative w-4 h-4 rounded-full"></div>Server paused</div> :
<>
<div className="font-semibold">FPS: <span style={{ 'color': frameRateColor }} className="font-semibold">{frameRate}</span> </div>
<div className="font-semibold">Load: <span style={{ 'color': loadColor }} className="font-semibold">{load}</span> </div>
<div className="font-semibold">{showMissionTime ? "MT" : "ET"}: {timeString} </div>
<div className="bg-[#8BFF63] relative w-4 h-4 rounded-full"></div>
</>
}
</div>
}

View File

@ -15,6 +15,7 @@ import { BLUE_COMMANDER, GAME_MASTER, MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEF
import { getApp, setupApp } from '../olympusapp'
import { LoginModal } from './modals/login'
import { sha256 } from 'js-sha256'
import { MiniMapPanel } from './panels/minimappanel'
export type OlympusState = {
mainMenuVisible: boolean,
@ -141,10 +142,12 @@ export function UI() {
open={optionsMenuVisible}
onClose={() => setOptionsMenuVisible(false)}
/>
<MiniMapPanel />
<UnitControlMenu />
<div id='map-container' className='h-full w-screen' />
</div>
</div>
<div id='map-container' className='fixed h-full w-screen top-0 left-0' />
</EventsProvider>
</StateProvider>
</div>

View File

@ -343,7 +343,7 @@ export class Map extends L.Map {
}
getLayers() {
let layers = ["DCS Map mirror 1", "DCS Map mirror 2"]
let layers = ["DCS Map mirror 1", "DCS Map mirror 2"];
layers.push(...Object.keys(this.#mapLayers));
return layers;
}

View File

@ -1,3 +1,3 @@
{
"version": "v1.0.4"
"version": "v2.0.0"
}