feat: Implemented import/export page

This commit is contained in:
Davide Passoni 2025-01-07 15:36:36 +01:00
parent c9c34f013f
commit 0376d020e7
14 changed files with 454 additions and 66 deletions

View File

@ -289,6 +289,7 @@ export enum OlympusState {
AUDIO = "Audio",
AIRBASE = "Airbase",
GAME_MASTER = "Game master",
IMPORT_EXPORT = "Import/export"
}
export const NO_SUBSTATE = "No substate";
@ -334,6 +335,13 @@ export enum OptionsSubstate {
KEYBIND = "Keybind",
}
export enum ImportExportSubstate {
NO_SUBSTATE = "No substate",
IMPORT = "IMPORT",
EXPORT = "EXPORT"
}
export type OlympusSubState = DrawSubState | JTACSubState | SpawnSubState | OptionsSubstate | string;
export const IADSTypes = ["AAA", "SAM Site", "Radar (EWR)"];

View File

@ -1,7 +1,7 @@
import { AudioSink } from "./audio/audiosink";
import { AudioSource } from "./audio/audiosource";
import { OlympusState, OlympusSubState } from "./constants/constants";
import { CommandModeOptions, OlympusConfig, ServerStatus, SessionData, SpawnRequestTable, UnitData } from "./interfaces";
import { CommandModeOptions, MissionData, OlympusConfig, ServerStatus, SessionData, SpawnRequestTable, UnitData } from "./interfaces";
import { CoalitionCircle } from "./map/coalitionarea/coalitioncircle";
import { CoalitionPolygon } from "./map/coalitionarea/coalitionpolygon";
import { Airbase } from "./mission/airbase";
@ -12,6 +12,7 @@ import { ContextAction } from "./unit/contextaction";
import { ContextActionSet } from "./unit/contextactionset";
import { Unit } from "./unit/unit";
import { LatLng } from "leaflet";
import { Weapon } from "./weapon/weapon";
export class BaseOlympusEvent {
static on(callback: () => void, singleShot = false) {
@ -348,8 +349,33 @@ export class UnitSelectedEvent extends BaseUnitEvent {}
export class UnitDeselectedEvent extends BaseUnitEvent {}
export class UnitDeadEvent extends BaseUnitEvent {}
export class SelectionClearedEvent extends BaseOlympusEvent {}
export class UnitsRefreshed extends BaseOlympusEvent {}
export class WeaponsRefreshed extends BaseOlympusEvent {}
export class UnitsRefreshedEvent {
static on(callback: (units: { [ID: number]: Unit }) => void, singleShot = false) {
document.addEventListener(this.name, (ev: CustomEventInit) => {
callback(ev.detail);
}, {once: singleShot});
}
static dispatch(units: { [ID: number]: Unit }) {
document.dispatchEvent(new CustomEvent(this.name, { detail: units }));
console.log(`Event ${this.name} dispatched`);
}
}
export class WeaponsRefreshedEvent {
static on(callback: (weapons: { [ID: number]: Weapon }) => void, singleShot = false) {
document.addEventListener(this.name, (ev: CustomEventInit) => {
callback(ev.detail);
}, {once: singleShot});
}
static dispatch(weapons: { [ID: number]: Weapon }) {
document.dispatchEvent(new CustomEvent(this.name, { detail: weapons }));
console.log(`Event ${this.name} dispatched`);
}
}
export class SelectedUnitsChangedEvent {
static on(callback: (selectedUnits: Unit[]) => void, singleShot = false) {
@ -361,7 +387,6 @@ export class SelectedUnitsChangedEvent {
static dispatch(selectedUnits: Unit[]) {
document.dispatchEvent(new CustomEvent(this.name, { detail: selectedUnits }));
console.log(`Event ${this.name} dispatched`);
console.log(selectedUnits);
}
}
@ -578,7 +603,7 @@ export class AudioManagerOutputChangedEvent {
}
/************** Mission data events ***************/
export class BullseyesDataChanged {
export class BullseyesDataChangedEvent {
static on(callback: (bullseyes: { [name: string]: Bullseye }) => void, singleShot = false) {
document.addEventListener(this.name, (ev: CustomEventInit) => {
callback(ev.detail.bullseyes);
@ -590,3 +615,16 @@ export class BullseyesDataChanged {
// Logging disabled since periodic
}
}
export class MissionDataChangedEvent {
static on(callback: (missionData: MissionData) => void, singleShot = false) {
document.addEventListener(this.name, (ev: CustomEventInit) => {
callback(ev.detail.missionData);
}, {once: singleShot});
}
static dispatch(missionData: MissionData) {
document.dispatchEvent(new CustomEvent(this.name, { detail: { missionData } }));
// Logging disabled since periodic
}
}

View File

@ -200,6 +200,7 @@ export interface Offset {
export interface UnitData {
category: string;
markerCategory: string;
ID: number;
alive: boolean;
human: boolean;

View File

@ -6,7 +6,7 @@ import { BLUE_COMMANDER, GAME_MASTER, NONE, RED_COMMANDER } from "../constants/c
import { AirbasesData, BullseyesData, CommandModeOptions, DateAndTime, MissionData } from "../interfaces";
import { Coalition } from "../types/types";
import { Carrier } from "./carrier";
import { AirbaseSelectedEvent, AppStateChangedEvent, BullseyesDataChanged, CommandModeOptionsChangedEvent, InfoPopupEvent } from "../events";
import { AirbaseSelectedEvent, AppStateChangedEvent, BullseyesDataChangedEvent, CommandModeOptionsChangedEvent, InfoPopupEvent, MissionDataChangedEvent } from "../events";
/** The MissionManager */
export class MissionManager {
@ -61,7 +61,7 @@ export class MissionManager {
this.#bullseyes[idx].setCoalition(bullseye.coalition);
}
BullseyesDataChanged.dispatch(this.#bullseyes)
BullseyesDataChangedEvent.dispatch(this.#bullseyes)
}
}
@ -96,6 +96,8 @@ export class MissionManager {
*/
updateMission(data: MissionData) {
if (data.mission) {
MissionDataChangedEvent.dispatch(data);
/* Set the mission theatre */
if (data.mission.theatre != this.#theatre) {
this.#theatre = data.mission.theatre;

View File

@ -488,7 +488,7 @@ export class ServerManager {
this.PUT(data, callback);
}
setAdvacedOptions(
setAdvancedOptions(
ID: number,
isActiveTanker: boolean,
isActiveAWACS: boolean,

View File

@ -0,0 +1,369 @@
import React, { useEffect, useState } from "react";
import { Modal } from "./components/modal";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faArrowRight } from "@fortawesome/free-solid-svg-icons";
import { getApp } from "../../olympusapp";
import { ImportExportSubstate, NO_SUBSTATE, OlympusState } from "../../constants/constants";
import { AppStateChangedEvent, MissionDataChangedEvent, UnitsRefreshedEvent } from "../../events";
import {
olButtonsVisibilityDcs,
olButtonsVisibilityGroundunit,
olButtonsVisibilityGroundunitSam,
olButtonsVisibilityNavyunit,
olButtonsVisibilityOlympus,
} from "../components/olicons";
import { OlToggle } from "../components/oltoggle";
import { deepCopyTable } from "../../other/utils";
import { OlCheckbox } from "../components/olcheckbox";
import { Unit } from "../../unit/unit";
import { MissionData, UnitData } from "../../interfaces";
export function ImportExportModal(props: { open: boolean }) {
const [appState, setAppState] = useState(OlympusState.NOT_INITIALIZED);
const [appSubState, setAppSubState] = useState(NO_SUBSTATE);
const [units, setUnits] = useState({} as { [ID: number]: Unit });
const [missionData, setMissionData] = useState({} as MissionData);
const [importData, setImportData] = useState({} as { [key: string]: UnitData[] });
function resetFilter() {
return {
control: {
dcs: true,
olympus: true,
},
blue: {
"groundunit-sam": true,
groundunit: true,
navyunit: true,
},
neutral: {
"groundunit-sam": true,
groundunit: true,
navyunit: true,
},
red: {
"groundunit-sam": true,
groundunit: true,
navyunit: true,
}
};
}
const [selectionFilter, setSelectionFilter] = useState(resetFilter);
useEffect(() => {
AppStateChangedEvent.on((appState, appSubState) => {
setAppState(appState);
setAppSubState(appSubState);
});
UnitsRefreshedEvent.on((units) => setUnits(units));
MissionDataChangedEvent.on((missionData) => setMissionData(missionData));
}, []);
useEffect(() => {
setSelectionFilter(resetFilter);
if (appState === OlympusState.IMPORT_EXPORT && appSubState === ImportExportSubstate.IMPORT) {
setImportData({});
var input = document.createElement("input");
input.type = "file";
input.onchange = async (e) => {
// @ts-ignore TODO
var file = e.target?.files[0];
var reader = new FileReader();
reader.readAsText(file, "UTF-8");
reader.onload = (readerEvent) => {
// @ts-ignore TODO
var content = readerEvent.target.result;
if (content) {
setImportData(JSON.parse(content as string));
}
};
};
input.click();
}
}, [appState, appSubState]);
const selectableUnits = Object.values(units).filter((unit) => {
return (
unit.getAlive() &&
!unit.getHuman() &&
((unit.isControlledByDCS() && selectionFilter.control.dcs) || (unit.isControlledByOlympus() && selectionFilter.control.olympus))
);
});
return (
<Modal
open={props.open}
className={`
inline-flex h-fit w-[600px] overflow-y-auto scroll-smooth bg-white p-10
dark:bg-olympus-800
max-md:h-full max-md:max-h-full max-md:w-full max-md:rounded-none
max-md:border-none
`}
>
<div className="flex h-full w-full flex-col gap-4">
<div className={`flex flex-col items-start gap-2`}>
<span
className={`
text-gray-800 text-md
dark:text-white
`}
>
{appSubState === ImportExportSubstate.EXPORT ? "Export to file" : "Import from file"}
</span>
<span className="text-gray-400">
{appSubState === ImportExportSubstate.EXPORT ? <>Select what units you want to export to file using the toggles below</> : <></>}
</span>
<div className="flex w-full flex-col gap-2">
<div
className={`
text-bold border-b-2 border-b-white/10 pb-2 text-gray-400
`}
>
Control mode
</div>
<div className="flex flex-col justify-start gap-2">
{Object.entries({
olympus: ["Olympus controlled", olButtonsVisibilityOlympus],
dcs: ["From DCS mission", olButtonsVisibilityDcs],
}).map((entry, idx) => {
return (
<div className="flex justify-between" key={idx}>
<span className="font-light text-white">{entry[1][0] as string}</span>
<OlToggle
key={entry[0]}
onClick={() => {
selectionFilter["control"][entry[0]] = !selectionFilter["control"][entry[0]];
setSelectionFilter(deepCopyTable(selectionFilter));
}}
toggled={selectionFilter["control"][entry[0]]}
/>
</div>
);
})}
</div>
<div
className={`
text-bold border-b-2 border-b-white/10 pb-2 text-gray-400
`}
>
Types and coalitions
</div>
<table className="mr-16">
<tbody>
<tr>
<td></td>
<td className="pb-4 text-center font-bold text-blue-500">BLUE</td>
<td className="pb-4 text-center font-bold text-gray-500">NEUTRAL</td>
<td className="pb-4 text-center font-bold text-red-500">RED</td>
</tr>
{Object.entries({
"groundunit-sam": olButtonsVisibilityGroundunitSam,
groundunit: olButtonsVisibilityGroundunit,
navyunit: olButtonsVisibilityNavyunit,
}).map((entry, idx) => {
return (
<tr key={idx}>
<td className="w-16 text-lg text-gray-200">
<FontAwesomeIcon icon={entry[1]} />
</td>
{["blue", "neutral", "red"].map((coalition) => {
return (
<td className="w-32 text-center" key={coalition}>
<OlCheckbox
checked={
(appSubState === ImportExportSubstate.EXPORT &&
selectionFilter[coalition][entry[0]] &&
selectableUnits.find((unit) => unit.getMarkerCategory() === entry[0] && unit.getCoalition() === coalition) !== undefined) ||
(appSubState === ImportExportSubstate.IMPORT &&
selectionFilter[coalition][entry[0]] &&
Object.values(importData).find((group) =>
group.find((unit) => unit.markerCategory === entry[0] && unit.coalition === coalition)
) !== undefined)
}
disabled={
(appSubState === ImportExportSubstate.EXPORT &&
selectableUnits.find((unit) => unit.getMarkerCategory() === entry[0] && unit.getCoalition() === coalition) === undefined) ||
(appSubState === ImportExportSubstate.IMPORT &&
Object.values(importData).find((group) =>
group.find((unit) => unit.markerCategory === entry[0] && unit.coalition === coalition)
) === undefined)
}
onChange={() => {
selectionFilter[coalition][entry[0]] = !selectionFilter[coalition][entry[0]];
setSelectionFilter(deepCopyTable(selectionFilter));
}}
/>
<span className="absolute ml-2 text-white">
{appSubState === ImportExportSubstate.EXPORT &&
selectableUnits.filter((unit) => unit.getMarkerCategory() === entry[0] && unit.getCoalition() === coalition).length}
{appSubState === ImportExportSubstate.IMPORT &&
Object.values(importData)
.flatMap((unit) => unit)
.filter((unit) => unit.markerCategory === entry[0] && unit.coalition === coalition).length}{" "}
units{" "}
</span>
</td>
);
})}
</tr>
);
})}
{
<tr>
<td className="text-gray-200"></td>
<td className="text-center">
<OlCheckbox
checked={Object.values(selectionFilter["blue"]).some((value) => value)}
onChange={() => {
const newValue = !Object.values(selectionFilter["blue"]).some((value) => value);
Object.keys(selectionFilter["blue"]).forEach((key) => {
selectionFilter["blue"][key] = newValue;
});
setSelectionFilter(deepCopyTable(selectionFilter));
}}
/>
</td>
<td className="text-center">
<OlCheckbox
checked={Object.values(selectionFilter["neutral"]).some((value) => value)}
onChange={() => {
const newValue = !Object.values(selectionFilter["neutral"]).some((value) => value);
Object.keys(selectionFilter["neutral"]).forEach((key) => {
selectionFilter["neutral"][key] = newValue;
});
setSelectionFilter(deepCopyTable(selectionFilter));
}}
/>
</td>
<td className="text-center">
<OlCheckbox
checked={Object.values(selectionFilter["red"]).some((value) => value)}
onChange={() => {
const newValue = !Object.values(selectionFilter["red"]).some((value) => value);
Object.keys(selectionFilter["red"]).forEach((key) => {
selectionFilter["red"][key] = newValue;
});
setSelectionFilter(deepCopyTable(selectionFilter));
}}
/>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
<div className="flex justify-end">
<button
type="button"
onClick={() => {
if (appSubState === ImportExportSubstate.EXPORT) {
var unitsToExport: { [key: string]: any } = {};
selectableUnits
.filter((unit) => selectionFilter[unit.getCoalition()][unit.getMarkerCategory()])
.forEach((unit: Unit) => {
var data: any = unit.getData();
if (unit.getGroupName() in unitsToExport) unitsToExport[unit.getGroupName()].push(data);
else unitsToExport[unit.getGroupName()] = [data];
});
const file = new Blob([JSON.stringify(unitsToExport)], {
type: "text/plain",
});
const defaultName = "olympus_export_" + missionData.mission.theatre + "_" + missionData.sessionHash + "_" + missionData.time + ".json";
//@ts-ignore TODO
if (window.showSaveFilePicker) {
const opts = {
types: [
{
description: "DCS Olympus export file",
accept: { "text/plain": [".json"] },
},
],
suggestedName: defaultName,
};
//@ts-ignore TODO
showSaveFilePicker(opts)
.then((handle) => handle.createWritable())
.then((writeable) => {
getApp().setState(OlympusState.IDLE);
getApp().addInfoMessage("Exporting data please wait...");
writeable
.write(file)
.then(() => writeable.close())
.catch(() => getApp().addInfoMessage("An error occurred while exporting the data"));
})
.then(() => getApp().addInfoMessage("Data exported correctly"))
.catch((err) => getApp().addInfoMessage("An error occurred while exporting the data"));
} else {
const a = document.createElement("a");
const file = new Blob([JSON.stringify(unitsToExport)], {
type: "text/plain",
});
a.href = URL.createObjectURL(file);
var filename = defaultName;
a.download = filename;
a.click();
}
} else {
for (const [groupName, groupData] of Object.entries(importData)) {
if (groupName === "" || groupData.length === 0 || !groupData.some((unitData: UnitData) => unitData.alive)) continue;
let { markerCategory, coalition, category } = groupData[0];
if (selectionFilter[coalition][markerCategory] !== true) continue;
let unitsToSpawn = groupData.map((unitData: UnitData) => {
return { unitType: unitData.name, location: unitData.position, liveryID: "", skill: "High" };
});
getApp().getUnitsManager().spawnUnits(category.toLocaleLowerCase(), unitsToSpawn, coalition, false);
}
getApp().setState(OlympusState.IDLE);
}
}}
className={`
mb-2 me-2 ml-auto flex content-center items-center gap-2
rounded-sm bg-blue-700 px-5 py-2.5 text-sm font-medium text-white
dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800
focus:outline-none focus:ring-4 focus:ring-blue-300
hover:bg-blue-800
`}
>
Continue
<FontAwesomeIcon icon={faArrowRight} />
</button>
<button
type="button"
onClick={() => getApp().setState(OlympusState.IDLE)}
className={`
mb-2 me-2 flex content-center items-center gap-2 rounded-sm
border-[1px] bg-blue-700 px-5 py-2.5 text-sm font-medium
text-white
dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400
dark:hover:bg-gray-700 dark:focus:ring-blue-800
focus:outline-none focus:ring-4 focus:ring-blue-300
hover:bg-blue-800
`}
>
Back
</button>
</div>
</div>
</Modal>
);
}

View File

@ -4,7 +4,7 @@ import { OlToggle } from "../components/oltoggle";
import { MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
import {
AWACSReferenceChangedEvent as AWACSReferenceUnitChangedEvent,
BullseyesDataChanged,
BullseyesDataChangedEvent,
HotgroupsChangedEvent,
MapOptionsChangedEvent,
UnitUpdatedEvent,
@ -27,13 +27,10 @@ export function AWACSMenu(props: { open: boolean; onClose: () => void; children?
MapOptionsChangedEvent.on((mapOptions) => setMapOptions({ ...mapOptions }));
HotgroupsChangedEvent.on((hotgroups) => setHotgroups({ ...hotgroups }));
AWACSReferenceUnitChangedEvent.on((unit) => setReferenceUnit(unit));
BullseyesDataChanged.on((bullseyes) => setBullseyes(bullseyes));
BullseyesDataChangedEvent.on((bullseyes) => setBullseyes(bullseyes));
UnitUpdatedEvent.on((unit) => setRefreshTime(Date.now()));
}, []);
return (
<Menu title={"AWACS Tools"} open={props.open} onClose={props.onClose} showBackButton={false} canBeHidden={true}>
<div

View File

@ -2,8 +2,8 @@ import React, { useEffect, useState } from "react";
import { OlLocation } from "../components/ollocation";
import { LatLng } from "leaflet";
import { FaBullseye, FaChevronDown, FaChevronUp, FaJetFighter, FaMountain } from "react-icons/fa6";
import { BullseyesDataChanged, MouseMovedEvent, SelectedUnitsChangedEvent } from "../../events";
import { bearing, computeBearingRangeString, mToFt } from "../../other/utils";
import { BullseyesDataChangedEvent, MouseMovedEvent, SelectedUnitsChangedEvent } from "../../events";
import { computeBearingRangeString, mToFt } from "../../other/utils";
import { Bullseye } from "../../mission/bullseye";
import { Unit } from "../../unit/unit";
@ -20,7 +20,7 @@ export function CoordinatesPanel(props: {}) {
if (elevation) setElevation(elevation);
});
BullseyesDataChanged.on((bullseyes) => setBullseyes(bullseyes));
BullseyesDataChangedEvent.on((bullseyes) => setBullseyes(bullseyes));
SelectedUnitsChangedEvent.on((selectedUnits) => setSelectedUnits(selectedUnits));
}, []);

View File

@ -1,6 +1,6 @@
import React, { useEffect, useRef, useState } from "react";
import { OlRoundStateButton, OlStateButton, OlLockStateButton } from "../components/olstatebutton";
import { faSkull, faCamera, faFlag, faLink, faUnlink, faBars, faVolumeHigh } from "@fortawesome/free-solid-svg-icons";
import { faSkull, faCamera, faFlag, faVolumeHigh, faDownload, faUpload } from "@fortawesome/free-solid-svg-icons";
import { OlDropdownItem, OlDropdown } from "../components/oldropdown";
import { OlLabelToggle } from "../components/ollabeltoggle";
import { getApp, IP } from "../../olympusapp";
@ -15,11 +15,11 @@ import {
olButtonsVisibilityNavyunit,
olButtonsVisibilityOlympus,
} from "../components/olicons";
import { FaChevronLeft, FaChevronRight, FaComputer, FaFloppyDisk, FaTabletScreenButton } from "react-icons/fa6";
import { FaChevronLeft, FaChevronRight, FaFloppyDisk } from "react-icons/fa6";
import { CommandModeOptionsChangedEvent, ConfigLoadedEvent, HiddenTypesChangedEvent, MapOptionsChangedEvent, MapSourceChangedEvent, SessionDataChangedEvent, SessionDataSavedEvent } from "../../events";
import { BLUE_COMMANDER, COMMAND_MODE_OPTIONS_DEFAULTS, MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS } from "../../constants/constants";
import { BLUE_COMMANDER, COMMAND_MODE_OPTIONS_DEFAULTS, ImportExportSubstate, MAP_HIDDEN_TYPES_DEFAULTS, MAP_OPTIONS_DEFAULTS, OlympusState } from "../../constants/constants";
import { OlympusConfig } from "../../interfaces";
import { FaCheck, FaSpinner } from "react-icons/fa";
import { FaCheck, FaSave, FaSpinner } from "react-icons/fa";
export function Header() {
const [mapHiddenTypes, setMapHiddenTypes] = useState(MAP_HIDDEN_TYPES_DEFAULTS);
@ -93,11 +93,11 @@ export function Header() {
>
<div
className={`
mr-auto hidden flex-none flex-row items-center justify-start gap-6
mr-auto hidden flex-none flex-row items-center justify-start gap-2
lg:flex
`}
>
<div className="flex flex-col items-start">
<div className="mr-2 flex flex-col items-start">
<div
className={`
pt-1 text-xs text-gray-800
@ -123,6 +123,8 @@ export function Header() {
`}/><FaCheck className={`
absolute left-[9px] top-[-6px] text-2xl text-olympus-900
`}/><FaCheck className={`absolute left-3 top-0 text-green-500`}/></div>}
<OlStateButton className="ml-8" icon={faDownload} onClick={() => {getApp().setState(OlympusState.IMPORT_EXPORT, ImportExportSubstate.EXPORT)}} checked={false}/>
<OlStateButton icon={faUpload} onClick={() => {getApp().setState(OlympusState.IMPORT_EXPORT, ImportExportSubstate.IMPORT)}} checked={false}/>
</div>
{commandModeOptions.commandMode === BLUE_COMMANDER && (
@ -130,16 +132,6 @@ export function Header() {
<span className="my-auto font-bold">BLUE Commander ({commandModeOptions.spawnPoints.blue} points)</span>
</div>
)}
<div
className={`
cursor-pointer rounded-full bg-blue-500 px-4 py-2 text-white
`}
onClick={() => {
getApp().getMap().setOption("tabletMode", !mapOptions.tabletMode);
}}
>
{mapOptions.tabletMode ? <FaTabletScreenButton /> : <FaComputer />}
</div>
<div className={`flex h-fit flex-row items-center justify-start gap-1`}>
<OlLockStateButton
checked={!mapOptions.protectDCSUnits}

View File

@ -33,6 +33,7 @@ import { CoordinatesPanel } from "./panels/coordinatespanel";
import { RadiosSummaryPanel } from "./panels/radiossummarypanel";
import { AWACSMenu } from "./panels/awacsmenu";
import { ServerOverlay } from "./serveroverlay";
import { ImportExportModal } from "./modals/importexportmodal";
export type OlympusUIState = {
mainMenuVisible: boolean;
@ -81,6 +82,7 @@ export function UI() {
<LoginModal open={appState === OlympusState.LOGIN} />
<ProtectionPromptModal open={appState === OlympusState.UNIT_CONTROL && appSubState == UnitControlSubState.PROTECTION} />
<KeybindModal open={appState === OlympusState.OPTIONS && appSubState === OptionsSubstate.KEYBIND} />
<ImportExportModal open={appState === OlympusState.IMPORT_EXPORT} />
</>
)}

View File

@ -632,6 +632,7 @@ export abstract class Unit extends CustomMarker {
getData(): UnitData {
return {
category: this.getCategory(),
markerCategory: this.getMarkerCategory(),
ID: this.ID,
alive: this.#alive,
human: this.#human,
@ -1229,7 +1230,7 @@ export abstract class Unit extends CustomMarker {
}
setAdvancedOptions(isActiveTanker: boolean, isActiveAWACS: boolean, TACAN: TACAN, radio: Radio, generalSettings: GeneralSettings) {
if (!this.#human) getApp().getServerManager().setAdvacedOptions(this.ID, isActiveTanker, isActiveAWACS, TACAN, radio, generalSettings);
if (!this.#human) getApp().getServerManager().setAdvancedOptions(this.ID, isActiveTanker, isActiveAWACS, TACAN, radio, generalSettings);
}
bombPoint(latlng: LatLng) {
@ -1549,7 +1550,7 @@ export abstract class Unit extends CustomMarker {
/* Set BRAA */
const reference = getApp().getUnitsManager().getAWACSReference();
if (reference && reference !== this) {
if (reference && reference !== this && reference.getAlive()) {
const BRAA = `${computeBearingRangeString(reference.getPosition(), this.getPosition())}`;
if (element.querySelector(".unit-braa")) (<HTMLElement>element.querySelector(".unit-braa")).innerText = `${BRAA}`;
} else if (element.querySelector(".unit-braa")) (<HTMLElement>element.querySelector(".unit-braa")).innerText = ``;

View File

@ -35,7 +35,7 @@ import {
UnitDeadEvent,
UnitDeselectedEvent,
UnitSelectedEvent,
UnitsRefreshed,
UnitsRefreshedEvent,
} from "../events";
import { UnitDatabase } from "./databases/unitdatabase";
import * as turf from "@turf/turf";
@ -80,7 +80,7 @@ export class UnitsManager {
});
SessionDataLoadedEvent.on((sessionData) => {
UnitsRefreshed.on(() => {
UnitsRefreshedEvent.on((units) => {
const localSessionData = deepCopyTable(sessionData);
if (localSessionData.hotgroups) {
Object.keys(localSessionData.hotgroups).forEach((hotgroup) => {
@ -311,7 +311,7 @@ export class UnitsManager {
/* Compute the base clusters */
this.#clusters = this.computeClusters();
if (fullUpdate) UnitsRefreshed.dispatch();
if (fullUpdate) UnitsRefreshedEvent.dispatch(this.#units);
return updateTime;
}

View File

@ -8,7 +8,7 @@ import { DataExtractor } from "../server/dataextractor";
import { ObjectIconOptions } from "../interfaces";
import { MapOptionsChangedEvent } from "../events";
export class Weapon extends CustomMarker {
export abstract class Weapon extends CustomMarker {
ID: number;
#alive: boolean = false;
@ -54,10 +54,9 @@ export class Weapon extends CustomMarker {
MapOptionsChangedEvent.on(() => this.#updateMarker());
}
getCategory() {
// Overloaded by child classes
return "";
}
abstract getCategory(): string;
abstract getMarkerCategory(): string;
abstract getIconOptions(): ObjectIconOptions;
/********************** Unit data *************************/
setData(dataExtractor: DataExtractor) {
@ -110,28 +109,7 @@ export class Weapon extends CustomMarker {
heading: this.#heading,
};
}
getMarkerCategory(): string {
return "";
}
getIconOptions(): ObjectIconOptions {
// Default values, overloaded by child classes if needed
return {
showState: false,
showVvi: false,
showHealth: false,
showHotgroup: false,
showUnitIcon: true,
showShortLabel: false,
showFuel: false,
showAmmo: false,
showSummary: true,
showCallsign: true,
rotateToHeading: false,
};
}
setAlive(newAlive: boolean) {
this.#alive = newAlive;
}

View File

@ -3,7 +3,7 @@ import { Weapon } from "./weapon";
import { DataIndexes } from "../constants/constants";
import { DataExtractor } from "../server/dataextractor";
import { Contact } from "../interfaces";
import { CommandModeOptionsChangedEvent, WeaponsRefreshed } from "../events";
import { CommandModeOptionsChangedEvent, WeaponsRefreshedEvent } from "../events";
/** The WeaponsManager handles the creation and update of weapons. Data is strictly updated by the server ONLY. */
export class WeaponsManager {
@ -83,7 +83,7 @@ export class WeaponsManager {
this.#weapons[ID]?.setData(dataExtractor);
}
if (fullUpdate) WeaponsRefreshed.dispatch();
if (fullUpdate) WeaponsRefreshedEvent.dispatch(this.#weapons);
return updateTime;
}