@jtff/miztemplate-lib
Version:
JTFF mission template library
547 lines (501 loc) • 25.1 kB
JavaScript
;
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;