From 4782596e3c9e3cd6e54c184a5eda38bbace310e4 Mon Sep 17 00:00:00 2001 From: Davide Passoni Date: Thu, 29 Feb 2024 10:54:52 +0100 Subject: [PATCH] Added installation/deletion of camera control plugin from manager --- frontend/website/@types/olympus/index.d.ts | 16 +++- manager/ejs/camera.ejs | 25 ++++++ manager/ejs/connections.ejs | 2 +- manager/ejs/connectionsType.ejs | 2 +- manager/ejs/expertsettings.ejs | 7 ++ manager/ejs/folder.ejs | 2 +- manager/ejs/passwords.ejs | 2 +- manager/ejs/type.ejs | 2 +- manager/javascripts/dcsinstance.js | 28 +++++-- manager/javascripts/filesystem.js | 52 +++++++++++- manager/javascripts/manager.js | 79 +++++++++++++------ .../OlympusCameraControl.lua | 0 12 files changed, 175 insertions(+), 42 deletions(-) create mode 100644 manager/ejs/camera.ejs rename scripts/lua/{camera => backend}/OlympusCameraControl.lua (100%) diff --git a/frontend/website/@types/olympus/index.d.ts b/frontend/website/@types/olympus/index.d.ts index 8886a56c..58296eed 100644 --- a/frontend/website/@types/olympus/index.d.ts +++ b/frontend/website/@types/olympus/index.d.ts @@ -176,7 +176,7 @@ declare module "constants/constants" { zoom: number; }; }; - export const mapLayers: { + export const defaultMapLayers: { "ArcGIS Satellite": { urlTemplate: string; minZoom: number; @@ -235,6 +235,7 @@ declare module "constants/constants" { export const SHOW_UNIT_CONTACTS = "Show selected units contact lines"; export const SHOW_UNIT_PATHS = "Show selected unit paths"; export const SHOW_UNIT_TARGETS = "Show selected unit targets"; + export const DCS_LINK_PORT = "DCS Camera link port"; export enum DataIndexes { startOfData = 0, category = 1, @@ -362,6 +363,7 @@ declare module "controls/dropdown" { setOptionsElements(optionsElements: HTMLElement[]): void; getOptionElements(): HTMLCollection; addOptionElement(optionElement: HTMLElement): void; + addHorizontalDivider(): void; /** Select the active value of the dropdown * * @param idx The index of the element to select @@ -851,10 +853,11 @@ declare module "other/utils" { export function enumToCoalition(coalitionID: number): "" | "blue" | "red" | "neutral"; export function coalitionToEnum(coalition: string): 0 | 1 | 2; export function convertDateAndTimeToDate(dateAndTime: DateAndTime): Date; - export function createCheckboxOption(value: string, text: string, checked?: boolean, callback?: CallableFunction, options?: any): HTMLElement; + export function createCheckboxOption(text: string, description: string, checked?: boolean, callback?: CallableFunction, options?: any): HTMLElement; export function getCheckboxOptions(dropdown: Dropdown): { [key: string]: boolean; }; + export function createTextInputOption(text: string, description: string, initialValue: string, type: string, callback?: CallableFunction, options?: any): HTMLElement; export function getGroundElevation(latlng: LatLng, callback: CallableFunction): void; } declare module "controls/slider" { @@ -1624,7 +1627,9 @@ declare module "map/map" { * @param ID - the ID of the HTML element which will contain the context menu */ constructor(ID: string); - addVisibilityOption(option: string, defaultValue: boolean): void; + addVisibilityOption(option: string, defaultValue: boolean | number | string, options?: { + [key: string]: any; + }): void; setLayer(layerName: string): void; getLayers(): string[]; setState(state: string): void; @@ -1660,12 +1665,14 @@ declare module "map/map" { getSelectedCoalitionArea(): CoalitionArea | undefined; bringCoalitionAreaToBack(coalitionArea: CoalitionArea): void; getVisibilityOptions(): { - [key: string]: boolean; + [key: string]: string | number | boolean; }; isZooming(): boolean; getPreviousZoom(): number; getIsUnitProtected(unit: Unit): boolean; getMapMarkerVisibilityControls(): MapMarkerVisibilityControl[]; + setSlaveDCSCamera(newSlaveDCSCamera: boolean): void; + setCameraControlMode(newCameraControlMode: string): void; } } declare module "mission/bullseye" { @@ -2612,6 +2619,7 @@ declare module "olympusapp" { */ setLoginStatus(status: string): void; start(): void; + getConfig(): any; } } declare module "index" { diff --git a/manager/ejs/camera.ejs b/manager/ejs/camera.ejs new file mode 100644 index 00000000..ba6d3748 --- /dev/null +++ b/manager/ejs/camera.ejs @@ -0,0 +1,25 @@ + +
+
+
+ Step <%= instances.length === 1? "5": "6" %> of <%= instances.length === 1? "5": "6" %> +
+
+ Do you want to install the camera control plugin? +
+
+ The camera control plugin allows you to control the camera position in DCS from Olympus.
+ It is necessary to install it in DCS even if you plan to use Olympus on a remote machine via your browser. +
+
+
+
+ Install +
+
+ Do not install +
+
+
diff --git a/manager/ejs/connections.ejs b/manager/ejs/connections.ejs index 4ee16210..5c33209c 100644 --- a/manager/ejs/connections.ejs +++ b/manager/ejs/connections.ejs @@ -4,7 +4,7 @@
- Step <%= instances.length === 1? "3": "4" %> of <%= instances.length === 1? "4": "5" %> + Step <%= instances.length === 1? "3": "4" %> of <%= instances.length === 1? "5": "6" %>
Manually set Olympus port and address settings diff --git a/manager/ejs/connectionsType.ejs b/manager/ejs/connectionsType.ejs index 73724f1a..3d9a67ed 100644 --- a/manager/ejs/connectionsType.ejs +++ b/manager/ejs/connectionsType.ejs @@ -4,7 +4,7 @@
- Step <%= instances.length === 1? "2": "3" %> of <%= instances.length === 1? "4": "5" %> + Step <%= instances.length === 1? "2": "3" %> of <%= instances.length === 1? "5": "6" %>
Do you want to set port and address settings? diff --git a/manager/ejs/expertsettings.ejs b/manager/ejs/expertsettings.ejs index 110d4142..fc44d79b 100644 --- a/manager/ejs/expertsettings.ejs +++ b/manager/ejs/expertsettings.ejs @@ -73,6 +73,13 @@ title="Allows services to connect to Olympus directly. This is NOT NEEDED for normal Olympus operation, even for dedicated servers. Leave it unchecked if in doubt.">
+
+ +
Enable camera control plugin + +
+
diff --git a/manager/ejs/folder.ejs b/manager/ejs/folder.ejs index 51f24901..2bd0e181 100644 --- a/manager/ejs/folder.ejs +++ b/manager/ejs/folder.ejs @@ -5,7 +5,7 @@
<% if (instances.length > 0) { %>
- Step 1 of <%= instances.length === 1? "4": "5" %> + Step 1 of <%= instances.length === 1? "5": "6" %>
Which DCS instance you want to add Olympus to? diff --git a/manager/ejs/passwords.ejs b/manager/ejs/passwords.ejs index 68a52f0e..cf8f45d0 100644 --- a/manager/ejs/passwords.ejs +++ b/manager/ejs/passwords.ejs @@ -4,7 +4,7 @@
- Step <%= instances.length === 1? "4": "5" %> of <%= instances.length === 1? "4": "5" %> + Step <%= instances.length === 1? "4": "5" %> of <%= instances.length === 1? "5": "6" %>
Enter your passwords for Olympus diff --git a/manager/ejs/type.ejs b/manager/ejs/type.ejs index b0781fb6..beb9a206 100644 --- a/manager/ejs/type.ejs +++ b/manager/ejs/type.ejs @@ -4,7 +4,7 @@
- Step <%= instances.length === 1? "1": "2" %> of <%= instances.length === 1? "4": "5" %> + Step <%= instances.length === 1? "1": "2" %> of <%= instances.length === 1? "5": "6" %>
Do you want to add Olympus for singleplayer or multiplayer? diff --git a/manager/javascripts/dcsinstance.js b/manager/javascripts/dcsinstance.js index 2235b50d..73ce935e 100644 --- a/manager/javascripts/dcsinstance.js +++ b/manager/javascripts/dcsinstance.js @@ -6,7 +6,7 @@ const { checkPort, fetchWithTimeout, getFreePort } = require('./net') const dircompare = require('dir-compare'); const { spawn } = require('child_process'); const find = require('find-process'); -const { installHooks, installMod, installJSON, applyConfiguration, installShortCuts, deleteMod, deleteHooks, deleteJSON, deleteShortCuts } = require('./filesystem') +const { installHooks, installMod, installJSON, applyConfiguration, installShortCuts, deleteMod, deleteHooks, deleteJSON, deleteShortCuts, installCameraPlugin, deleteCameraPlugin } = require('./filesystem') const { showErrorPopup, showConfirmPopup, showWaitLoadingPopup, setPopupLoadingProgress } = require('./popup') const { logger } = require("./filesystem") const { hidePopup } = require('./popup'); @@ -129,6 +129,7 @@ class DCSInstance { fps = 0; installationType = 'singleplayer'; connectionsType = 'auto'; + installCameraPlugin = 'install'; gameMasterPasswordEdited = false; blueCommanderPasswordEdited = false; redCommanderPasswordEdited = false; @@ -154,6 +155,7 @@ class DCSInstance { this.error = false; this.installationType = 'singleplayer'; this.connectionsType = 'auto'; + this.installCameraPlugin = 'install'; /* Check if the olympus.json file is detected. If true, Olympus is considered to be installed */ if (fs.existsSync(path.join(this.folder, "Config", "olympus.json"))) { @@ -518,22 +520,28 @@ class DCSInstance { await sleep(100); await installHooks(getManager().getActiveInstance().folder); - setPopupLoadingProgress("Installing mod folder...", 20); + setPopupLoadingProgress("Installing mod folder...", 16); await sleep(100); await installMod(getManager().getActiveInstance().folder, getManager().getActiveInstance().name); - setPopupLoadingProgress("Installing JSON file...", 40); + setPopupLoadingProgress("Installing JSON file...", 33); await sleep(100); await installJSON(getManager().getActiveInstance().folder); - setPopupLoadingProgress("Applying configuration...", 60); + setPopupLoadingProgress("Applying configuration...", 50); await sleep(100); await applyConfiguration(getManager().getActiveInstance().folder, getManager().getActiveInstance()); - setPopupLoadingProgress("Creating shortcuts...", 80); + setPopupLoadingProgress("Creating shortcuts...", 67); await sleep(100); await installShortCuts(getManager().getActiveInstance().folder, getManager().getActiveInstance().name); + if (getManager().getActiveInstance().installCameraPlugin === 'install') { + setPopupLoadingProgress("Installing camera plugin...", 83); + await sleep(100); + await installCameraPlugin(getManager().getActiveInstance().folder); + } + setPopupLoadingProgress("Installation completed!", 100); await sleep(500); logger.log(`Installation completed successfully`); @@ -575,18 +583,22 @@ class DCSInstance { await sleep(100); await deleteMod(this.folder, this.name); - setPopupLoadingProgress("Deleting hook scripts...", 25); + setPopupLoadingProgress("Deleting hook scripts...", 20); await sleep(100); await deleteHooks(this.folder); - setPopupLoadingProgress("Deleting JSON...", 50); + setPopupLoadingProgress("Deleting JSON...", 40); await sleep(100); await deleteJSON(this.folder); - setPopupLoadingProgress("Deleting shortcuts...", 75); + setPopupLoadingProgress("Deleting shortcuts...", 60); await sleep(100); await deleteShortCuts(this.folder, this.name); + setPopupLoadingProgress("Deleting camera plugin...", 80); + await sleep(100); + await deleteCameraPlugin(this.folder); + await sleep(500); setPopupLoadingProgress("Instance removed!", 100); logger.log(`Olympus removed from ${this.folder}`) diff --git a/manager/javascripts/filesystem.js b/manager/javascripts/filesystem.js index 96c2fd7e..75b394e7 100644 --- a/manager/javascripts/filesystem.js +++ b/manager/javascripts/filesystem.js @@ -11,6 +11,8 @@ var logger = new Console(output, output); const date = new Date(); output.write(` ======================= New log starting at ${date.toString()} =======================\n`); +var EXPORT_STRING = "pcall(function() local olympusLFS=require('lfs');dofile(olympusLFS.writedir()..[[Mods\\Services\\Olympus\\Scripts\\OlympusCameraControl.lua]]); end,nil) "; + /** Conveniency function to asynchronously delete a single file, with error catching * * @param {String} filePath The path to the file to delete @@ -172,6 +174,29 @@ async function applyConfiguration(folder, instance) { } } +/** Asynchronously install the camera control plugin + * + * @param {String} folder The base Saved Games folder where Olympus is installed + */ +async function installCameraPlugin(folder) { + logger.log(`Installing camera support plugin to DCS in ${folder}`); + /* If the export file doesn't exist, create it */ + if (!(await exists(path.join(folder, "Scripts", "export.lua")))) { + await fsp.writeFile(path.join(folder, "Scripts", "export.lua"), EXPORT_STRING); + } else { + let content = await fsp.readFile(path.join(folder, "Scripts", "export.lua"), { encoding: 'utf8' }); + if (content.indexOf(EXPORT_STRING) != -1) { + /* Looks like the export string is already installed, nothing to do */ + } + else { + /* Append the export string at the end of the file */ + content += ("\n" + EXPORT_STRING); + } + /* Write the content of the file */ + await fsp.writeFile(path.join(folder, "Scripts", "export.lua"), content) + } +} + /** Asynchronously deletes the Hooks script * * @param {String} folder The base Saved Games folder where Olympus is installed @@ -231,15 +256,40 @@ async function deleteShortCuts(folder, name) { logger.log(`ShortCuts deleted from ${folder} and desktop`); } +/** Asynchronously removes the camera plugin string from the export lua file + * + * @param {String} folder The base Saved Games folder where Olympus is installed + */ +async function deleteCameraPlugin(folder) { + logger.log(`Deleting camera support plugin to DCS in ${folder}`); + if (!(await exists(path.join(folder, "Scripts", "export.lua")))) { + /* If the export file doesn't exist, nothing to do */ + } else { + let content = await fsp.readFile(path.join(folder, "Scripts", "export.lua"), { encoding: 'utf8' }); + if (content.indexOf(EXPORT_STRING) ==+ -1) { + /* Looks like the export string is not installed, nothing to do */ + } + else { + /* Remove the export string from the file */ + content = content.replace(EXPORT_STRING, "") + + /* Write the content of the file */ + await fsp.writeFile(path.join(folder, "Scripts", "export.lua"), content) + } + } +} + module.exports = { applyConfiguration: applyConfiguration, installJSON: installJSON, installHooks: installHooks, installMod: installMod, - installShortCuts, installShortCuts, + installShortCuts: installShortCuts, + installCameraPlugin: installCameraPlugin, deleteHooks: deleteHooks, deleteJSON: deleteJSON, deleteMod: deleteMod, deleteShortCuts: deleteShortCuts, + deleteCameraPlugin: deleteCameraPlugin, logger: logger } diff --git a/manager/javascripts/manager.js b/manager/javascripts/manager.js index 353e8a3e..91848635 100644 --- a/manager/javascripts/manager.js +++ b/manager/javascripts/manager.js @@ -32,6 +32,7 @@ class Manager { connectionsTypePage = null; connectionsPage = null; passwordsPage = null; + cameraPage = null; resultPage = null; instancesPage = null; expertSettingsPage = null; @@ -103,9 +104,9 @@ class Manager { /* Get my public IP */ this.getPublicIP().then( (IP) => { this.setIP(IP); }, - (err) => { + (err) => { logger.log(err) - this.setIP(undefined); + this.setIP(undefined); } ) @@ -142,6 +143,7 @@ class Manager { this.connectionsTypePage = new WizardPage(this, "./ejs/connectionsType.ejs"); this.connectionsPage = new WizardPage(this, "./ejs/connections.ejs"); this.passwordsPage = new WizardPage(this, "./ejs/passwords.ejs"); + this.cameraPage = new WizardPage(this, "./ejs/camera.ejs"); this.resultPage = new ManagerPage(this, "./ejs/result.ejs"); this.instancesPage = new ManagerPage(this, "./ejs/instances.ejs"); this.expertSettingsPage = new WizardPage(this, "./ejs/expertsettings.ejs"); @@ -159,7 +161,7 @@ class Manager { this.setPort('backend', this.getActiveInstance().backendPort); } } - + /* Always force the IDLE state when reaching the menu page */ this.menuPage.options.onShow = async () => { await this.setState('IDLE'); @@ -337,6 +339,17 @@ class Manager { } } + /* When the camera control installation is selected */ + async onInstallCameraControlClicked(type) { + this.connectionsTypePage.getElement().querySelector(`.install`).classList.toggle("selected", type === 'install'); + this.connectionsTypePage.getElement().querySelector(`.no-install`).classList.toggle("selected", type === 'no-install'); + if (this.getActiveInstance()) + this.getActiveInstance().installCameraPlugin = type; + else { + showErrorPopup(`
A critical error occurred!
Check ${this.getLogLocation()} for more info.
`); + } + } + /* When the next button of a wizard page is clicked */ async onNextClicked() { /* Choose which page to show depending on the active page */ @@ -360,11 +373,11 @@ class Manager { this.activePage.hide(); this.typePage.show(); } - /* Installation type page */ + /* Installation type page */ } else if (this.activePage == this.typePage) { this.activePage.hide(); this.connectionsTypePage.show(); - /* Connection type page */ + /* Connection type page */ } else if (this.activePage == this.connectionsTypePage) { if (this.getActiveInstance()) { if (this.getActiveInstance().connectionsType === 'auto') { @@ -374,24 +387,28 @@ class Manager { else { this.activePage.hide(); this.connectionsPage.show(); - (this.getMode() === 'basic'? this.connectionsPage: this.expertSettingsPage).getElement().querySelector(".backend-address .checkbox").classList.toggle("checked", this.getActiveInstance().backendAddress === '*') + (this.getMode() === 'basic' ? this.connectionsPage : this.expertSettingsPage).getElement().querySelector(".backend-address .checkbox").classList.toggle("checked", this.getActiveInstance().backendAddress === '*') } } else { showErrorPopup(`
A critical error occurred!
Check ${this.getLogLocation()} for more info.
`) } - /* Connection page */ + /* Connection page */ } else if (this.activePage == this.connectionsPage) { if (await this.checkPorts()) { this.activePage.hide(); this.passwordsPage.show(); - } - /* Passwords page */ + } + /* Passwords page */ } else if (this.activePage == this.passwordsPage) { if (await this.checkPasswords()) { this.activePage.hide(); - this.getState() === 'INSTALL' ? this.getActiveInstance().install() : this.getActiveInstance().edit(); + this.cameraPage.show() } - /* Expert settings page */ + /* Installation type page */ + } else if (this.activePage == this.cameraPage) { + this.activePage.hide(); + this.getState() === 'INSTALL' ? this.getActiveInstance().install() : this.getActiveInstance().edit(); + /* Expert settings page */ } else if (this.activePage == this.expertSettingsPage) { if (await this.checkPorts() && await this.checkPasswords()) { this.activePage.hide(); @@ -416,7 +433,7 @@ class Manager { async onCancelClicked() { this.activePage.hide(); await this.setState('IDLE'); - if (this.getMode() === "basic") + if (this.getMode() === "basic") this.menuPage.show(true); else this.instancesPage.show(true); @@ -441,7 +458,7 @@ class Manager { if (this.getActiveInstance()) this.getActiveInstance().setBlueCommanderPassword(value); - else + else showErrorPopup(`
A critical error occurred!
Check ${this.getLogLocation()} for more info.
`); } @@ -450,9 +467,9 @@ class Manager { input.placeholder = ""; } - if (this.getActiveInstance()) + if (this.getActiveInstance()) this.getActiveInstance().setRedCommanderPassword(value); - else + else showErrorPopup(`
A critical error occurred!
Check ${this.getLogLocation()} for more info.
`); } @@ -485,6 +502,20 @@ class Manager { } } + /* When the "Enable camera control plugin" checkbox is clicked */ + async onEnableCameraPluginClicked() { + if (this.getActiveInstance()) { + if (this.getActiveInstance().installCameraPlugin === 'install') { + this.getActiveInstance().installCameraPlugin = 'no-install'; + } else { + this.getActiveInstance().installCameraPlugin = 'install'; + } + this.expertSettingsPage.getElement().querySelector(".camera-plugin .checkbox").classList.toggle("checked", this.getActiveInstance().installCameraPlugin === 'install') + } else { + showErrorPopup(`
A critical error occurred!
Check ${this.getLogLocation()} for more info.
`) + } + } + /* When the "Return to manager" button is pressed */ async onReturnClicked() { await this.reload(); @@ -562,7 +593,7 @@ class Manager { this.setActiveInstance(instance); await this.setState('EDIT'); this.activePage.hide(); - (this.getMode() === 'basic'? this.typePage: this.expertSettingsPage).show(); + (this.getMode() === 'basic' ? this.typePage : this.expertSettingsPage).show(); } } @@ -571,7 +602,7 @@ class Manager { this.setActiveInstance(instance); await this.setState('INSTALL'); this.activePage.hide(); - (this.getMode() === 'basic'? this.typePage: this.expertSettingsPage).show(); + (this.getMode() === 'basic' ? this.typePage : this.expertSettingsPage).show(); } async onUninstallClicked(name) { @@ -579,7 +610,7 @@ class Manager { this.setActiveInstance(instance); await this.setState('UNINSTALL'); if (instance.webserverOnline || instance.backendOnline) - showErrorPopup("
The selected Olympus instance is currently active
Please stop DCS and Olympus Server/Client before removing it!
") + showErrorPopup("
The selected Olympus instance is currently active
Please stop DCS and Olympus Server/Client before removing it!
") else await instance.uninstall(); } @@ -620,11 +651,11 @@ class Manager { this.getActiveInstance().setBackendPort(value); } - var successEls = (this.getMode() === 'basic'? this.connectionsPage: this.expertSettingsPage).getElement().querySelector(`.${port}-port`).querySelectorAll(".success"); + var successEls = (this.getMode() === 'basic' ? this.connectionsPage : this.expertSettingsPage).getElement().querySelector(`.${port}-port`).querySelectorAll(".success"); for (let i = 0; i < successEls.length; i++) { successEls[i].classList.toggle("hide", !success); } - var errorEls = (this.getMode() === 'basic'? this.connectionsPage: this.expertSettingsPage).getElement().querySelector(`.${port}-port`).querySelectorAll(".error"); + var errorEls = (this.getMode() === 'basic' ? this.connectionsPage : this.expertSettingsPage).getElement().querySelector(`.${port}-port`).querySelectorAll(".error"); for (let i = 0; i < errorEls.length; i++) { errorEls[i].classList.toggle("hide", success); } @@ -693,7 +724,7 @@ class Manager { document.getElementById("loader").style.opacity = "0%"; window.setTimeout(() => { document.getElementById("loader").classList.add("hide"); - }, 250); + }, 250); } async setActiveInstance(newActiveInstance) { @@ -718,12 +749,12 @@ class Manager { async setLogLocation(newLogLocation) { this.options.logLocation = newLogLocation; - } - + } + async setState(newState) { this.options.state = newState; await DCSInstance.reloadInstances(); - if (newState === 'IDLE') + if (newState === 'IDLE') this.setActiveInstance(undefined); } diff --git a/scripts/lua/camera/OlympusCameraControl.lua b/scripts/lua/backend/OlympusCameraControl.lua similarity index 100% rename from scripts/lua/camera/OlympusCameraControl.lua rename to scripts/lua/backend/OlympusCameraControl.lua