UNPKG

@jtff/miztemplate-lib

Version:
547 lines (501 loc) 25.1 kB
"use strict"; const jszip = require("jszip"); const path = require("path"); const {format, parse} = require("lua-json"); const {promisify} = require("util"); const fs = require("fs"); const {getInstalledPathSync} = require("get-installed-path"); const lstat = promisify(fs.lstat); let _ = require('lodash'); class Mizlib { constructor() { // no need for initialization } async injectLuaScriptIntoZipObjectAndUpdateWorkspace(workspacePath, folderPath, luaScriptsArray, titleString, zipObject, timing, colorString, fromLibrary=true) { let missionObject = await this.getMissionObjectFromZipObject(zipObject); let mapResourceObject = await this.getMapResourceObjectFromZipObject(zipObject); let tuple = this.injectLuaScriptAndUpdateWorkspace(workspacePath, folderPath, luaScriptsArray, titleString, missionObject, mapResourceObject, timing, colorString, fromLibrary); for (let luaScript of luaScriptsArray) { this.injectFileIntoZipObject(zipObject, [ workspacePath, fromLibrary ? "/.workspace/" : "/", folderPath, "/", path.basename(luaScript) ].join("") ); } return {tuple, zipObject}; } injectLuaScriptAndUpdateWorkspace(workspacePath, folderPath, luaScriptsArray, titleString, missionObject, mapResourceObject, timing, colorString ,fromLibrary=true) { let tuple = this.injectLuaScriptsInMissionObject( missionObject['trig'], missionObject['trigrules'], mapResourceObject, titleString, luaScriptsArray, timing, colorString ); for (let luaScript of luaScriptsArray) { if (! fs.existsSync([ workspacePath, fromLibrary ? "/.workspace/" : "/", folderPath, ].join("") )) { fs.mkdirSync([ workspacePath, fromLibrary ? "/.workspace/" : "/", folderPath, ].join("")); } fs.copyFileSync( [ getInstalledPathSync('@jtff/miztemplate-lib',{local: true}), '/lua/', folderPath, '/', luaScript ].join(""), [ workspacePath, fromLibrary ? "/.workspace/" : "/", folderPath, "/", path.basename(luaScript) ].join("") ); } return tuple; } injectLuaScriptsInMissionObject(tObject, trObject, mrObject, strTitle, scriptFilesArray, timingInSeconds, hexColor) { let nextIndex = Object.keys(trObject).length + 1; if (nextIndex === 1) { tObject['actions'] = {}; tObject['func'] = {}; tObject['conditions'] = {}; tObject['flag'] = {}; trObject = {}; } let actionSentence = ""; let actionsObject = {}; for (const [index, scriptFile] of scriptFilesArray.entries()) { actionSentence += "a_do_script_file(getValueResourceByKey(\"" + scriptFile + "\")); " actionsObject[index + 1] = { file: scriptFile, predicate: 'a_do_script_file', }; mrObject[scriptFile] = scriptFile; } actionSentence += "mission.trig.func[" + nextIndex + "]=nil;" tObject['actions'][nextIndex] = actionSentence; tObject['func'][nextIndex] = "if mission.trig.conditions[" + nextIndex + "]() then mission.trig.actions[" + nextIndex + "]() end"; tObject['conditions'][nextIndex] = "return(c_time_after(" + timingInSeconds + ") )"; tObject['flag'][nextIndex] = true; trObject[nextIndex] = { rules: { 1: { coalitionlist: 'red', seconds: timingInSeconds, predicate: 'c_time_after', zone: '' } }, eventlist: '', comment: strTitle, actions: actionsObject, predicate: 'triggerOnce', colorItem: hexColor }; return {tObject: tObject, trObject: trObject, mrObject: mrObject}; } async getZipObjectFromMizPath(mizPath) { var MizFile = new jszip(); const mizData = fs.readFileSync(mizPath); return MizFile.loadAsync(mizData); } async buildMizFileFromMizTemplate(mizPath, copyPath, mizSettingSubFolder= '') { const zipObject = await this.getZipObjectFromMizPath(mizPath); const missionObject = await this.getMissionObjectFromZipObject(zipObject); const strTheatreSettings = missionObject.theatre; const srcFilesFromMizLib = fs.readdirSync(getInstalledPathSync('@jtff/miztemplate-lib',{local: true}) + "/lua/src",{recursive: true}) .filter(filename => path.extname(filename).toLowerCase()==='.lua' && !(filename.startsWith('200-'))); const libFilesFromMizLib = fs.readdirSync(getInstalledPathSync('@jtff/miztemplate-lib',{local: true}) + "/lua/lib",{recursive: true}) .filter(filename => path.extname(filename).toLowerCase()==='.lua'); for (let file of srcFilesFromMizLib) { console.log('updating src/' + file + ' from the miztemplate-lib'); fs.copyFileSync(getInstalledPathSync('@jtff/miztemplate-lib',{local: true}) + '/lua/src/' + file, '.workspace/src/' + file ); } for (let file of libFilesFromMizLib) { console.log('updating lib/' + file + ' from the miztemplate-lib'); fs.copyFileSync(getInstalledPathSync('@jtff/miztemplate-lib',{local: true}) + '/lua/lib/' + file, '.workspace/lib/' + file ); } this.injectLuaFilesFromFolderIntoZipObject(zipObject, '.workspace/src', mizSettingSubFolder); this.injectLuaFilesFromFolderIntoZipObject(zipObject, '.workspace/lib', mizSettingSubFolder); this.injectLuaFilesFromFolderIntoZipObject(zipObject, ['src',mizSettingSubFolder.length > 0 ? '/' + mizSettingSubFolder : ''].join(''), mizSettingSubFolder); await this.injectRadioPresetsFromFolderIntoZipObject(zipObject, 'resources/radios/' + strTheatreSettings); zipObject.remove('KNEEBOARD'); await this.injectKneeboardsFromFolderIntoZipObject(zipObject, 'resources/kneeboards/common'); await this.injectKneeboardsFromFolderIntoZipObject(zipObject, 'resources/kneeboards/' + mizSettingSubFolder); this.injectSettingsLuaFilesFromFolderIntoZipObject(zipObject, mizSettingSubFolder.length > 0 ? mizSettingSubFolder : ''); await this.injectSoundFoldersIntoZipObject(zipObject); const inputZip = await zipObject.generateAsync({ type: 'nodebuffer', streamFiles: true, compression: "DEFLATE", compressionOptions: { level: 9 } }); fs.writeFileSync(copyPath ? copyPath : mizPath, inputZip); } async injectMissionObjectIntoMizFile(mizPath, missionObject) { const zip = this.injectMissionObjectIntoZipObject(await this.getZipObjectFromMizPath(mizPath), missionObject); const inputZip = await zip.generateAsync({ type: 'nodebuffer', streamFiles: true, compression: "DEFLATE", compressionOptions: { level: 9 } }); fs.writeFileSync(mizPath, inputZip); } async injectLuaSettingsFromFolderPathToMizPath(mizPath, settingsFolder) { const zip = await this.getZipObjectFromMizPath(mizPath); await this.injectLuaSettingsFromFolderPathToZipObject(zip, settingsFolder); const outputZip = await zip.generateAsync({ type: 'nodebuffer', streamFiles: true, compression: "DEFLATE", compressionOptions: { level: 9 } }); fs.writeFileSync(mizPath, outputZip); } async injectLuaSettingsFromFolderPathToZipObject(zip, settingsFolder) { for (let file of fs.readdirSync(settingsFolder).filter(file => file.endsWith(".lua"))) { this.injectFileIntoZipObject(zip, [ settingsFolder, "/", file].join("")); } } async injectMapResourceObjectIntoMizFile(mizPath, mapResourceObject) { const zip = await this.getZipObjectFromMizPath(mizPath); this.injectMapResourceObjectIntoZipObject(zip, mapResourceObject); const inputZip = await zip.generateAsync({ type: 'nodebuffer', streamFiles: true, compression: "DEFLATE", compressionOptions: { level: 9 } }); fs.writeFileSync(mizPath, inputZip); } injectFileIntoZipObject(zip, filePath) { zip.remove("l10n/DEFAULT/" + path.basename(filePath)); var stream = fs.createReadStream(filePath); zip.file("l10n/DEFAULT/" + path.basename(filePath), stream); } injectMissionObjectIntoZipObject(zip, missionObject) { zip.remove("mission"); let missionLuaT = format(missionObject, {singleQuote: false}) missionLuaT = missionLuaT .split('\n') .slice(1, -1) .join('\n') .slice(0, -1) .replace(/\[\"(\d+)\"\] = /g, "[$1] = "); zip.file("mission", missionLuaT); } injectMapResourceObjectIntoZipObject(zip, mapResourceObject) { zip.remove("l10n/DEFAULT/mapResource"); let mapResourceLuaT = format(mapResourceObject, {singleQuote: false}) mapResourceLuaT = mapResourceLuaT .split('\n') .slice(1, -1) .join('\n') .slice(0, -1) .replace(/\[\"(\d+)\"\] = /g, "[$1] = "); zip.file("l10n/DEFAULT/mapResource", mapResourceLuaT); } getMissionLuaStringFromZipObject(zipStream) { return zipStream.file("mission").async("string"); } getMapResourceLuaStringFromZipObject(zipStream) { return zipStream.file("l10n/DEFAULT/mapResource").async("string"); } injectLuaFilesFromFolderIntoZipObject(zip, folderPath, mizSettingSubFolder) { for (let file of fs.readdirSync(folderPath).filter(file => file.endsWith(".lua"))) { console.log('injecting up2date ' + folderPath + '/' + file + ' file in ' + mizSettingSubFolder + ' archive'); this.injectFileIntoZipObject(zip, folderPath + "/" + file); }; } async injectRadioPresetsFromFolderIntoZipObject(zip, preset_folder) { // Create folder Avionics to make sure it exists then delete it to remove any old preset in the template // Allows adding presets for A-10C zip.folder("Avionics"); zip.remove("Avionics"); const mission_object = await this.getMissionObjectFromZipObject(zip); for (let file of fs.readdirSync(preset_folder).filter(file => file.endsWith(".lua"))) { const file_data = fs.readFileSync(preset_folder + '/' + file).toString(); const lua_string = file_data.substring(0, file_data.indexOf("radio_descriptor_table =") - 1); const radio_descriptor_table = parse("return {" + lua_string + "}").descriptor; console.log('updating radio presets (aircraft: ' + radio_descriptor_table["aircraft"] + ', group_name: ' + radio_descriptor_table["group_name"] + ') with preset in ' + preset_folder + ' folder'); const dcs_radio_presets = file_data.substring(file_data.indexOf("radio_descriptor_table =") + 24); for (const coalition_key in mission_object.coalition) { const coalition = mission_object.coalition[coalition_key]; for (const country_list_key in coalition) { if (country_list_key != "country") continue; const country_list = coalition[country_list_key]; for (const country_key in country_list) { const country = country_list[country_key]; for (const plane_key in country["plane"]) { const plane = country["plane"][plane_key]; for (const group_key in plane) { const group = plane[group_key]; if (group["name"].match(radio_descriptor_table["group_name"]) == null) continue; for (const unit_key in group) { if (unit_key != "units") continue; const unit = group[unit_key]; for (const sub_unit_key in unit) { const sub_unit = unit[sub_unit_key]; if (sub_unit["skill"] != "Client") continue; // Aircraft is an A10CII, use A10C mode by creating files in the root with the unit id // if (radio_descriptor_table["aircraft"] == 'A-10C_2') { // const unit_id = sub_unit["unitId"]; // ["UHF_RADIO", "VHF_AM_RADIO", "VHF_FM_RADIO"].forEach(folder => { // var zip_folder = zip.folder("Avionics/A-10C_2/" + unit_id + "/" + folder); // var file = parse("return " + dcs_radio_presets)[folder]; // file = format(file, { singleQuote: false }); // file = file // .split('\n') // .slice(1, -1) // .join('\n') // .slice(0, -1) // .replace(/\[\"(\d+)\"\] = /g, "[$1] = "); // zip_folder.file("SETTINGS.lua", file); // }); // continue; // } if (sub_unit["type"] != radio_descriptor_table["aircraft"]) continue; // GROUP FOUND, SET RADIOS sub_unit["Radio"] = parse("return " + dcs_radio_presets) } } } } } } } }; this.injectMissionObjectIntoZipObject(zip, {mission: mission_object}); } async injectKneeboardsFromFolderIntoZipObject(zip, kneeboardsFolder){ console.log('adding kneeboards from folder ' + kneeboardsFolder); for (let file of fs.readdirSync(kneeboardsFolder)) { const fileStats = await lstat(kneeboardsFolder + '/' + file) const isDirectory = fileStats.isDirectory() if (isDirectory) { await this.addFilesToZip( zip.folder(['KNEEBOARD/', file, '/IMAGES/'].join('')), [kneeboardsFolder,'/', file].join(''), fs.readdirSync([kneeboardsFolder,'/', file].join(''))); } } } injectSettingsLuaFilesFromFolderIntoZipObject(zip, settingsSubFolder) { for (let file of fs.readdirSync(['settings',settingsSubFolder.length > 0 ? '/' + settingsSubFolder : ''].join('')).filter(file => path.extname(file).toLowerCase()==='.lua')) { console.log(['updating settings',settingsSubFolder.length > 0 ? '/' + settingsSubFolder : '','/',file,' file in miz file'].join('')); this.injectFileIntoZipObject(zip, ['settings',settingsSubFolder.length > 0 ? '/' + settingsSubFolder : '','/',file].join('')); } } async injectSoundFoldersIntoZipObject(zip) { if (fs.existsSync('resources/sounds') && fs.lstatSync('resources/sounds').isDirectory()) { await this.injectSingleSoundFolderIntoZipObject(zip, '.', 'resources/sounds', false); } if (fs.existsSync('.workspace/resources/sounds') && fs.lstatSync('.workspace/resources/sounds').isDirectory()) { const folderArray = fs.readdirSync('.workspace/resources/sounds'); for (const folder of folderArray) { await this.injectSingleSoundFolderIntoZipObject(zip, '.', folder, true); } } } copyRecursiveSync(src, dest) { let self = this; let exists = fs.existsSync(src); let stats = exists && fs.statSync(src); let isDirectory = exists && stats.isDirectory(); if (isDirectory) { fs.mkdirSync(dest, {recursive: true}); fs.readdirSync(src).forEach(function(childItemName) { self.copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName)); }); } else { fs.copyFileSync(src, dest); } }; async updateWorkspaceWithSingleSoundFolder(workspacePath, folderString) { if ( fs.existsSync( [ workspacePath, "/.workspace/resources/sounds/", folderString ].join("") ) && fs.lstatSync([ workspacePath, "/.workspace/resources/sounds/", folderString ].join("")).isDirectory() ) { fs.rmSync([ workspacePath, "/.workspace/resources/sounds/", folderString ].join(""), {recursive: true}); } if ( fs.existsSync(getInstalledPathSync('@jtff/miztemplate-lib',{local: true}) + '/resources/sounds/' + folderString) && fs.lstatSync(getInstalledPathSync('@jtff/miztemplate-lib',{local: true}) + '/resources/sounds/' + folderString).isDirectory()) { fs.mkdirSync([ workspacePath, "/.workspace/resources/sounds/", folderString ].join(""), {recursive: true}); this.copyRecursiveSync(getInstalledPathSync('@jtff/miztemplate-lib',{local: true}) + '/resources/sounds/' + folderString,[ workspacePath, "/.workspace/resources/sounds/", folderString ].join("")); // fs.cpSync(getInstalledPathSync('@jtff/miztemplate-lib',{local: true}) + '/resources/sounds/' + folderString,[ // workspacePath, // "/.workspace/resources/sounds/", // folderString // ].join(""), {recursive: true}); } } async injectSingleSoundFolderIntoZipObject(zip, workspacePath, folder, fromLibrary = true) { if ( fs.existsSync([workspacePath,fromLibrary? '/.workspace/resources/sounds/'+folder : '/resources/sounds'].join('')) && fs.lstatSync([workspacePath,fromLibrary? '/.workspace/resources/sounds/'+folder : '/resources/sounds'].join('')).isDirectory()) { console.log(['adding sound files from ',workspacePath,fromLibrary? '/.workspace/resources/sounds/'+folder : '/resources/sounds',' folder...'].join('')); if (fromLibrary) { zip = zip.remove(folder).folder(folder); } await this.addFilesToZip( zip, [workspacePath,fromLibrary? '/.workspace/resources/sounds/'+folder : '/resources/sounds'].join(''), fs.readdirSync([workspacePath,fromLibrary? '/.workspace/resources/sounds/'+folder : '/resources/sounds'].join(''))); } } async addFilesToZip(zip, directoryPath, filesToInclude) { const promiseArr = await filesToInclude.map(async file => { const filePath = path.join(directoryPath, file) try { const fileStats = await lstat(filePath) const isDirectory = fileStats.isDirectory() if (isDirectory) { const directory = zip.remove(file).folder(file) const subFiles = fs.readdirSync(filePath) return this.addFilesToZip(directory, filePath, subFiles) } else { // console.log('added file : '+file); return zip.file(file, fs.createReadStream(filePath)) } } catch (err) { console.log(err) return Promise.resolve() } }) return Promise.all(promiseArr) } async copyMiz(srcMizPath, dstMizPath) { await fs.createReadStream(srcMizPath).pipe(fs.createWriteStream(dstMizPath)); } async getMissionObjectFromMizPath(MizPath) { let luaTable = 'return { \n' + await this.getMissionLuaStringFromZipObject(await this.getZipObjectFromMizPath(MizPath)) + ' }'; return parse(luaTable).mission; } async getMissionObjectFromZipObject(zip) { let luaTable = 'return { \n' + await this.getMissionLuaStringFromZipObject(zip) + ' }'; return parse(luaTable).mission; } async getMapResourceObjectFromMizPath(MizPath) { let luaTable = 'return { \n' + await this.getMapResourceLuaStringFromZipObject(await this.getZipObjectFromMizPath(MizPath)) + ' }'; return parse(luaTable).mapResource; } async getMapResourceObjectFromZipObject(zip) { let luaTable = 'return { \n' + await this.getMapResourceLuaStringFromZipObject(zip) + ' }'; let mrObject = parse(luaTable).mapResource; if (Array.isArray(mrObject)) { if (mrObject.length === 0) { mrObject = {}; } } return mrObject; } js2Lua(data, depth = 0) { const indentation = _.range(0, depth + 1) .map(() => "") .join(" "); if (_.isArray(data)) { if (_.isEmpty(data)) { return `\n${indentation}{\n${indentation}}`; } return `\n${indentation}{\n${data .map((it, idx) => `${indentation} [${idx + 1}] = ${this.js2Lua(it, depth + 1)}`) .join(",\n")},\n${indentation}}`; } if (_.isObject(data)) { if (_.isEmpty(data)) { return `\n${indentation}{\n${indentation}}`; } return `\n${indentation}{\n${_.map( data, (value, key) => `${indentation} [${!_.isNaN(key)?key:this.js2Lua(key)}] = ${this.js2Lua(value, depth + 1)}${_.isObject(value)?`, -- end of [${!_.isNaN(key)?key:this.js2Lua(key)}]`:","}`, ).join("\n")}\n${indentation}}`; } if (_.isString(data)) { return JSON.stringify(data); } return `${data}`; }; generatePresetObjectFromMissionObject(missionObject, blnHidden){ const presetObject = {}; const missionDate = new Date(new Date( missionObject.date.Year + "-" + missionObject.date.Month + "-" + missionObject.date.Day + " 00:00:00").getTime() + 1000 * missionObject.start_time); presetObject.hidden = blnHidden; presetObject.date = [String(missionDate.getFullYear()).padStart(4,'0'),String(missionDate.getMonth()+1).padStart(2,'0'),String(missionDate.getDate()).padStart(2,'0')].join("-"); presetObject.start_time = String(missionDate.getHours()).padStart(2,'0') + ":" + String(missionDate.getMinutes()).padStart(2,'0'); presetObject.temperature = missionObject.weather.season.temperature; presetObject.clouds = missionObject.weather.clouds; presetObject.wind = missionObject.weather.wind; presetObject.qnh = missionObject.weather.qnh; presetObject.groundTurbulence = missionObject.weather.groundTurbulence; presetObject.enable_dust = missionObject.weather.enable_dust; presetObject.dust_density = missionObject.weather.dust_density; presetObject.enable_fog = missionObject.weather.enable_fog; presetObject.fog = missionObject.weather.fog; presetObject.halo = missionObject.weather.halo; return presetObject; } async generatePresetObjectFromZipObject(ZipObject, blnHidden) { const missionObject = await this.getMissionObjectFromZipObject(ZipObject); return this.generatePresetObjectFromMissionObject(missionObject, blnHidden); } } module.exports = Mizlib;