From 064426ad5177daf0aa03402faefa4426fb358215 Mon Sep 17 00:00:00 2001 From: David Pierron Date: Wed, 8 Jan 2020 17:44:46 +0100 Subject: [PATCH] 1.2.0 - Added the normalizer and radio presets editor tools see the documentation in the mission-editor-tools sub-folders --- .../normalizer/extract-example.cmd | 71 ++ mission-editor-tools/normalizer/readme.md | 33 + .../radio-editor/radioSettings-example.lua | 697 ++++++++++++++++++ mission-editor-tools/radio-editor/readme.md | 21 + package.json | 2 +- scripts/veaf/veafMissionEditor.lua | 273 +++++++ scripts/veaf/veafMissionNormalizer.lua | 137 ++++ .../veaf/veafMissionRadioPresetsEditor.lua | 210 ++++++ 8 files changed, 1443 insertions(+), 1 deletion(-) create mode 100644 mission-editor-tools/normalizer/extract-example.cmd create mode 100644 mission-editor-tools/normalizer/readme.md create mode 100644 mission-editor-tools/radio-editor/radioSettings-example.lua create mode 100644 mission-editor-tools/radio-editor/readme.md create mode 100644 scripts/veaf/veafMissionEditor.lua create mode 100644 scripts/veaf/veafMissionNormalizer.lua create mode 100644 scripts/veaf/veafMissionRadioPresetsEditor.lua diff --git a/mission-editor-tools/normalizer/extract-example.cmd b/mission-editor-tools/normalizer/extract-example.cmd new file mode 100644 index 00000000..00734605 --- /dev/null +++ b/mission-editor-tools/normalizer/extract-example.cmd @@ -0,0 +1,71 @@ +@echo off +set MISSION_NAME=Ka50-Training +echo. +echo ---------------------------------------- +echo extracting %MISSION_NAME% +echo ---------------------------------------- +echo. + +rem -- default options values +echo This script can use these environment variables to customize its behavior : + +echo ---------------------------------------- +echo LUA_SCRIPTS_DEBUG_PARAMETER can be set to "-debug" or "-trace" (or not set) ; this will be passed to the lua helper scripts (e.g. veafMissionRadioPresetsEditor and veafMissionNormalizer) +echo defaults to not set +IF [%LUA_SCRIPTS_DEBUG_PARAMETER%] == [] GOTO DefineDefaultLUA_SCRIPTS_DEBUG_PARAMETER +goto DontDefineDefaultLUA_SCRIPTS_DEBUG_PARAMETER +:DefineDefaultLUA_SCRIPTS_DEBUG_PARAMETER +set LUA_SCRIPTS_DEBUG_PARAMETER= +:DontDefineDefaultLUA_SCRIPTS_DEBUG_PARAMETER +echo current value is "%LUA_SCRIPTS_DEBUG_PARAMETER%" + +echo ---------------------------------------- +echo SEVENZIP (a string) points to the 7za executable +echo defaults "7za", so it needs to be in the path +IF [%SEVENZIP%] == [] GOTO DefineDefaultSEVENZIP +goto DontDefineDefaultSEVENZIP +:DefineDefaultSEVENZIP +set SEVENZIP=7za +:DontDefineDefaultSEVENZIP +echo current value is "%SEVENZIP%" + +echo ---------------------------------------- +echo LUA (a string) points to the lua executable +echo defaults "lua", so it needs to be in the path +IF [%LUA%] == [] GOTO DefineDefaultLUA +goto DontDefineDefaultLUA +:DefineDefaultLUA +set LUA=lua +:DontDefineDefaultLUA +echo current value is "%LUA%" +echo ---------------------------------------- + +echo. +echo fetching the veaf-mission-creation-tools package +call npm update +rem echo on + +rem extracting MIZ files +echo extracting MIZ files +set MISSION_PATH=%cd%\src\mission +"%SEVENZIP%" x -y %MISSION_NAME%*.miz -o"%MISSION_PATH%\" + +rem removing unwanted scripts +echo removing unwanted scripts +del /f /q src\mission\l10n\Default\*.lua + +rem normalizing the mission files +echo normalizing the mission files +pushd node_modules\veaf-mission-creation-tools\scripts\veaf +"%LUA%" veafMissionNormalizer.lua %MISSION_PATH% %LUA_SCRIPTS_DEBUG_PARAMETER% +popd + +rem -- cleanup +del %MISSION_NAME%*.miz + +echo. +echo ---------------------------------------- +rem -- done ! +echo Extracted %MISSION_NAME% +echo ---------------------------------------- +pause \ No newline at end of file diff --git a/mission-editor-tools/normalizer/readme.md b/mission-editor-tools/normalizer/readme.md new file mode 100644 index 00000000..3a9b8b67 --- /dev/null +++ b/mission-editor-tools/normalizer/readme.md @@ -0,0 +1,33 @@ +# VEAF mission normalizer tool for DCS World + +By Zip (2020) + +## Features: +This tool processes all files in a mission, apply filters to normalize them and writes them back. +Usually, DCSW Mission Editor shuffles the data in the mission files each time the mission is saved, making it all but impossible to compare with a previous version. +With this tool, it becomes easy to compare mission files after an edition in DCS World Mission Editor. + +## Prerequisite: +* The mission file archive must already be exploded ; the script only works on the mission files, not directly on the .miz archive + +## Usage: +The following workflow should be used : +* explode the mission (unzip it) +* run the normalizer on the exploded mission +* version the exploded mission files (save it, back it up, commit it to a source control system, whatever fits your routine) +* compile the mission (zip the exploded files again) +* edit the compiled mission with DCSW Mission Editor +* explode the mission (unzip it) +* run the normalizer on the exploded mission +* now you can run a comparison between the exploded mission and its previous version + +Call the script by running it in a lua environment ; it needs the veafMissionEditor library, so the script working directory must contain the veafMissionEditor.lua file + +```veafMissionNormalizer.lua [-debug|-trace]``` + +Command line options: +* ** the path to the exploded mission files (no trailing backslash) +* *-debug* if set, the script will output some information ; useful to find out which units were edited +* *-trace* if set, the script will output a lot of information : useful to understand what went wrong + +See extract-example.cmd for an example of command script that will explode, normalize and clean up a DCS mission file \ No newline at end of file diff --git a/mission-editor-tools/radio-editor/radioSettings-example.lua b/mission-editor-tools/radio-editor/radioSettings-example.lua new file mode 100644 index 00000000..37426bd9 --- /dev/null +++ b/mission-editor-tools/radio-editor/radioSettings-example.lua @@ -0,0 +1,697 @@ +radioSettings = +{ + ["blue F-14B"] = + { + type = "F-14B", + coalition = "blue", + country = nil, + + ["Radio"] = + { + [1] = + { + ["modulations"] = + { + [1] = 0, + [2] = 0, + [4] = 0, + [8] = 0, + [16] = 0, + [17] = 0, + [9] = 0, + [18] = 0, + [5] = 0, + [10] = 0, + [20] = 0, + [11] = 0, + [3] = 0, + [6] = 0, + [12] = 0, + [13] = 0, + [7] = 0, + [14] = 0, + [19] = 0, + [15] = 0, + }, -- end of ["modulations"] + ["channels"] = + { + [1] = 243, + [2] = 291, + [4] = 293, + [8] = 271.1, + [16] = 271.5, + [17] = 271.55, + [9] = 271.15, + [18] = 271.6, + [5] = 294, + [10] = 271.2, + [20] = 271.7, + [11] = 271.25, + [3] = 292, + [6] = 271, + [12] = 271.3, + [13] = 271.35, + [7] = 271.05, + [14] = 271.4, + [15] = 271.45, + [19] = 271.65, + }, -- end of ["channels"] + }, -- end of [1] + [2] = + { + ["modulations"] = + { + [1] = 0, + [2] = 0, + [4] = 0, + [8] = 0, + [16] = 0, + [17] = 0, + [9] = 0, + [18] = 0, + [5] = 0, + [10] = 0, + [20] = 0, + [30] = 0, + [21] = 0, + [11] = 0, + [22] = 0, + [3] = 0, + [6] = 0, + [12] = 0, + [24] = 0, + [19] = 0, + [25] = 0, + [13] = 0, + [26] = 0, + [27] = 0, + [7] = 0, + [14] = 0, + [28] = 0, + [23] = 0, + [29] = 0, + [15] = 0, + }, -- end of ["modulations"] + ["channels"] = + { + [1] = 121.5, + [2] = 133, + [4] = 138, + [8] = 118.1, + [16] = 126.625, + [17] = 126.725, + [9] = 118.2, + [18] = 130.2, + [5] = 129, + [10] = 119.1, + [20] = 130.4, + [30] = 306, + [21] = 271.75, + [11] = 119.2, + [22] = 271.8, + [3] = 132, + [6] = 140, + [12] = 119.3, + [24] = 271.9, + [19] = 130.3, + [25] = 271.95, + [13] = 119.4, + [26] = 272, + [7] = 134, + [27] = 310.3, + [14] = 119.5, + [28] = 310.4, + [23] = 271.85, + [29] = 284, + [15] = 121.975, + }, -- end of ["channels"] + }, -- end of [2] + }, -- end of ["Radio"] + }, + + ["blue F/A-18C"] = + { + type = "FA-18C_hornet", + coalition = "blue", + country = nil, + ["Radio"] = + { + [1] = + { + ["modulations"] = + { + [1] = 0, + [2] = 0, + [4] = 0, + [8] = 0, + [16] = 0, + [17] = 0, + [9] = 0, + [18] = 0, + [5] = 0, + [10] = 0, + [20] = 0, + [11] = 0, + [3] = 0, + [6] = 0, + [12] = 0, + [13] = 0, + [7] = 0, + [14] = 0, + [19] = 0, + [15] = 0, + }, -- end of ["modulations"] + ["channels"] = + { + [1] = 243, + [2] = 291, + [4] = 293, + [8] = 271.1, + [16] = 271.5, + [17] = 271.55, + [9] = 271.15, + [18] = 271.6, + [5] = 294, + [10] = 271.2, + [20] = 271.7, + [11] = 271.25, + [3] = 292, + [6] = 271, + [12] = 271.3, + [13] = 271.35, + [7] = 271.05, + [14] = 271.4, + [15] = 271.45, + [19] = 271.65, + }, -- end of ["channels"] + }, -- end of [1] + [2] = + { + ["modulations"] = + { + [1] = 0, + [2] = 0, + [4] = 0, + [8] = 0, + [16] = 0, + [17] = 0, + [9] = 0, + [18] = 0, + [5] = 0, + [10] = 0, + [20] = 0, + [11] = 0, + [3] = 0, + [6] = 0, + [12] = 0, + [13] = 0, + [7] = 0, + [14] = 0, + [19] = 0, + [15] = 0, + }, -- end of ["modulations"] + ["channels"] = + { + [1] = 121.5, + [2] = 133, + [4] = 138, + [8] = 118.1, + [16] = 126.625, + [17] = 126.725, + [9] = 118.2, + [18] = 130.2, + [5] = 129, + [10] = 119.1, + [20] = 130.4, + [11] = 119.2, + [3] = 132, + [6] = 140, + [12] = 119.3, + [13] = 119.4, + [7] = 134, + [14] = 119.5, + [19] = 130.3, + [15] = 121.975, + }, -- end of ["channels"] + }, -- end of [2] + } + }, + + ["blue F-16C"] = + { + type = "F-16C_50", + coalition = "blue", + country = nil, + ["Radio"] = + { + [1] = + { + ["modulations"] = + { + [1] = 0, + [2] = 0, + [4] = 0, + [8] = 0, + [16] = 0, + [17] = 0, + [9] = 0, + [18] = 0, + [5] = 0, + [10] = 0, + [20] = 0, + [11] = 0, + [3] = 0, + [6] = 0, + [12] = 0, + [13] = 0, + [7] = 0, + [14] = 0, + [19] = 0, + [15] = 0, + }, -- end of ["modulations"] + ["channels"] = + { + [1] = 243, + [2] = 291, + [4] = 293, + [8] = 271.1, + [16] = 271.5, + [17] = 271.55, + [9] = 271.15, + [18] = 271.6, + [5] = 294, + [10] = 271.2, + [20] = 271.7, + [11] = 271.25, + [3] = 292, + [6] = 271, + [12] = 271.3, + [13] = 271.35, + [7] = 271.05, + [14] = 271.4, + [15] = 271.45, + [19] = 271.65, + }, -- end of ["channels"] + }, -- end of [1] + [2] = + { + ["modulations"] = + { + [1] = 0, + [2] = 0, + [4] = 0, + [8] = 0, + [16] = 0, + [17] = 0, + [9] = 0, + [18] = 0, + [5] = 0, + [10] = 0, + [20] = 0, + [11] = 0, + [3] = 0, + [6] = 0, + [12] = 0, + [13] = 0, + [7] = 0, + [14] = 0, + [19] = 0, + [15] = 0, + }, -- end of ["modulations"] + ["channels"] = + { + [1] = 121.5, + [2] = 133, + [4] = 138, + [8] = 118.1, + [16] = 126.625, + [17] = 126.725, + [9] = 118.2, + [18] = 130.2, + [5] = 129, + [10] = 119.1, + [20] = 130.4, + [11] = 119.2, + [3] = 132, + [6] = 140, + [12] = 119.3, + [13] = 119.4, + [7] = 134, + [14] = 119.5, + [19] = 130.3, + [15] = 121.975, + }, -- end of ["channels"] + }, -- end of [2] + } + }, + + ["blue Mirage"] = + { + type = "M-2000C", + coalition = "blue", + country = nil, + ["Radio"] = + { + [1] = + { + ["channels"] = + { + [1] = 243, + [2] = 291, + [4] = 293, + [8] = 271.1, + [16] = 271.5, + [17] = 271.55, + [9] = 271.15, + [18] = 271.6, + [5] = 294, + [10] = 271.2, + [20] = 271.7, + [11] = 271.25, + [3] = 292, + [6] = 271, + [12] = 271.3, + [13] = 271.35, + [7] = 271.05, + [14] = 271.4, + [15] = 271.45, + [19] = 271.65, + }, -- end of ["channels"] + }, -- end of [1] + [2] = + { + ["channels"] = + { + [1] = 121.5, + [2] = 133, + [4] = 138, + [8] = 118.1, + [16] = 126.625, + [17] = 126.725, + [9] = 118.2, + [18] = 130.2, + [5] = 129, + [10] = 119.1, + [20] = 130.4, + [11] = 119.2, + [3] = 132, + [6] = 140, + [12] = 119.3, + [13] = 119.4, + [7] = 134, + [14] = 119.5, + [19] = 130.3, + [15] = 121.975, + }, -- end of ["channels"] + }, -- end of [2] + } + }, + + ["blue Harrier"] = + { + type = "AV8BNA", + coalition = "blue", + country = nil, + ["Radio"] = + { + [1] = + { + ["modulations"] = + { + [1] = 0, + [2] = 0, + [4] = 0, + [8] = 0, + [16] = 0, + [17] = 0, + [9] = 0, + [18] = 0, + [5] = 0, + [10] = 0, + [20] = 0, + [21] = 0, + [11] = 0, + [22] = 0, + [3] = 0, + [6] = 0, + [12] = 0, + [24] = 0, + [25] = 0, + [13] = 0, + [26] = 0, + [7] = 0, + [14] = 0, + [15] = 0, + [19] = 0, + [23] = 0, + }, -- end of ["modulations"] + ["channels"] = + { + [1] = 243, + [2] = 291, + [4] = 293, + [8] = 271.1, + [16] = 271.5, + [17] = 271.55, + [9] = 271.15, + [18] = 271.6, + [5] = 294, + [10] = 271.2, + [20] = 271.7, + [21] = 385, + [11] = 271.25, + [22] = 270, + [3] = 292, + [6] = 271, + [12] = 271.3, + [24] = 122.1, + [25] = 253, + [13] = 271.35, + [26] = 263, + [7] = 271.05, + [14] = 271.4, + [23] = 260, + [15] = 271.45, + [19] = 271.65, + }, -- end of ["channels"] + }, -- end of [1] + [2] = + { + ["modulations"] = + { + [1] = 0, + [2] = 0, + [4] = 0, + [8] = 0, + [16] = 0, + [17] = 0, + [9] = 0, + [18] = 0, + [5] = 0, + [10] = 0, + [20] = 0, + [21] = 0, + [11] = 0, + [22] = 0, + [3] = 0, + [6] = 0, + [12] = 0, + [24] = 0, + [25] = 0, + [13] = 0, + [26] = 0, + [7] = 0, + [14] = 0, + [15] = 0, + [19] = 0, + [23] = 0, + }, -- end of ["modulations"] + ["channels"] = + { + [1] = 121.5, + [2] = 133, + [4] = 138, + [8] = 118.1, + [16] = 126.625, + [17] = 126.725, + [9] = 118.2, + [18] = 130.2, + [5] = 129, + [10] = 119.1, + [20] = 130.4, + [21] = 127, + [11] = 119.2, + [22] = 130, + [3] = 132, + [6] = 140, + [12] = 119.3, + [24] = 135, + [25] = 128, + [13] = 119.4, + [26] = 138, + [7] = 134, + [14] = 119.5, + [23] = 129, + [19] = 130.3, + [15] = 121.975, + }, -- end of ["channels"] + }, -- end of [2] + [3] = + { + ["modulations"] = + { + [1] = 0, + [2] = 0, + [4] = 0, + [8] = 0, + [16] = 0, + [17] = 0, + [9] = 0, + [18] = 0, + [5] = 0, + [10] = 0, + [20] = 0, + [15] = 0, + [21] = 0, + [11] = 0, + [22] = 0, + [3] = 0, + [6] = 0, + [12] = 0, + [24] = 0, + [19] = 0, + [25] = 0, + [13] = 0, + [26] = 0, + [23] = 0, + [27] = 0, + [14] = 0, + [28] = 0, + [7] = 0, + [29] = 0, + [30] = 0, + }, -- end of ["modulations"] + ["channels"] = + { + [1] = 177, + [2] = 264, + [4] = 256, + [8] = 257, + [16] = 261, + [17] = 267, + [9] = 255, + [18] = 251, + [5] = 254, + [10] = 262, + [20] = 266, + [15] = 263, + [21] = 133, + [11] = 259, + [22] = 257.8, + [3] = 265, + [6] = 250, + [12] = 268, + [24] = 123.3, + [19] = 253, + [25] = 344, + [13] = 269, + [26] = 385, + [23] = 122.1, + [27] = 133, + [14] = 260, + [28] = 257.8, + [7] = 270, + [29] = 122.1, + [30] = 123.3, + }, -- end of ["channels"] + }, -- end of [3] + } + }, + + ["blue Viggen"] = + { + type = "AJS37", + coalition = "blue", + country = nil, + ["Radio"] = + { + [1] = + { + ["modulations"] = + { + [6] = 0, + [2] = 0, + [3] = 0, + [1] = 0, + [4] = 0, + [5] = 0, + [7] = 0, + }, -- end of ["modulations"] + ["channels"] = + { + [6] = 138, + [2] = 118.2, + [3] = 118.1, + [1] = 121.975, + [4] = 291, + [5] = 271, + [7] = 271.15, + }, -- end of ["channels"] + }, -- end of [1] + } + }, + + ["blue Ka-50"] = + { + type = "Ka-50", + coalition = "blue", + country = nil, + ["Radio"] = + { + [1] = + { + ["modulations"] = + { + }, -- end of ["modulations"] + ["channels"] = + { + [1] = 21, + [2] = 22, + [3] = 23, + [4] = 24, + [5] = 25, + [6] = 26, + [7] = 27, + [8] = 28, + [9] = 29, + [10] = 30, + }, -- end of ["channels"] + }, -- end of [1] + [2] = + { + ["modulations"] = + { + }, -- end of ["modulations"] + ["channels"] = + { + [1] = 0.441, + [2] = 0.442, + [3] = 0.443, + [4] = 0.444, + [5] = 0.445, + [6] = 0.446, + [7] = 0.447, + [8] = 0.448, + [9] = 0.449, + [10] = 0.450, + [11] = 0.451, + [12] = 0.452, + [13] = 0.453, + [14] = 0.454, + [15] = 0.455, + [16] = 0.456, + }, -- end of ["channels"] + }, -- end of [2] + }, -- end of ["Radio"] + }, +} diff --git a/mission-editor-tools/radio-editor/readme.md b/mission-editor-tools/radio-editor/readme.md new file mode 100644 index 00000000..f57590c8 --- /dev/null +++ b/mission-editor-tools/radio-editor/readme.md @@ -0,0 +1,21 @@ +# VEAF radio presets editor tool for DCS World + +By Zip (2020) + +## Features: +* This tool processes a mission and sets predefined radio presets. +* The preset templates can be customized (see radioSettings-example.lua) + +## Prerequisite: +* The mission file archive must already be exploded ; the script only works on the mission files, not directly on the .miz archive + +## Usage: +Call the script by running it in a lua environment ; it needs the veafMissionEditor library, so the script working directory must contain the veafMissionEditor.lua file + +```veafMissionRadioPresetsEditor.lua [-debug|-trace]``` + +Command line options: +* ** the path to the exploded mission files (no trailing backslash) +* ** the path to the preset templates file (see radioSettings-example.lua) +* *-debug* if set, the script will output some information ; useful to find out which units were edited +* *-trace* if set, the script will output a lot of information : useful to understand what went wrong diff --git a/package.json b/package.json index e5e288c9..26570371 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "veaf-mission-creation-tools", - "version": "1.1.7", + "version": "1.2.0", "description": "A set of scripts that help set up a dynamic mission", "main": "readme.md", "directories": { diff --git a/scripts/veaf/veafMissionEditor.lua b/scripts/veaf/veafMissionEditor.lua new file mode 100644 index 00000000..8b2f2125 --- /dev/null +++ b/scripts/veaf/veafMissionEditor.lua @@ -0,0 +1,273 @@ +------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- VEAF mission editor tool for DCS World +-- By zip (2020) +-- +-- Features: +-- --------- +-- * This tool can read a mission file, apply filters on it and spit it back. +-- +-- Prerequisite: +-- ------------ +-- * The mission file archive must already be exploded +-- TODO +-- +-- Basic Usage: +-- ------------ +-- TODO +-- +------------------------------------------------------------------------------------------------------------------------------------------------------------- + +veafMissionEditor = {} + +------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Global settings. Stores the script constants +------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Identifier. All output in the log will start with this. +veafMissionEditor.Id = "MISSION EDITOR - " + +--- Version. +veafMissionEditor.Version = "1.0.0" + +-- trace level, specific to this module +veafMissionEditor.Debug = false +veafMissionEditor.Trace = false + +------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Do not change anything below unless you know what you are doing! +------------------------------------------------------------------------------------------------------------------------------------------------------------- + +------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Serpent serialization library by Paul Kulchenko (paul@kulchenko.com) ; https://github.com/pkulchenko/serpent +------------------------------------------------------------------------------------------------------------------------------------------------------------- +local n, v = "serpent", "0.302" -- (C) 2012-18 Paul Kulchenko; MIT License +local c, d = "Paul Kulchenko", "Lua serializer and pretty printer" +local snum = {[tostring(1/0)]='1/0 --[[math.huge]]',[tostring(-1/0)]='-1/0 --[[-math.huge]]',[tostring(0/0)]='0/0'} +local badtype = {thread = true, userdata = true, cdata = true} +local getmetatable = debug and debug.getmetatable or getmetatable +local pairs = function(t) return next, t end -- avoid using __pairs in Lua 5.2+ +local keyword, globals, G = {}, {}, (_G or _ENV) +for _,k in ipairs({'and', 'break', 'do', 'else', 'elseif', 'end', 'false', + 'for', 'function', 'goto', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat', + 'return', 'then', 'true', 'until', 'while'}) do keyword[k] = true end +for k,v in pairs(G) do globals[v] = k end -- build func to name mapping +for _,g in ipairs({'coroutine', 'debug', 'io', 'math', 'string', 'table', 'os'}) do + for k,v in pairs(type(G[g]) == 'table' and G[g] or {}) do globals[v] = g..'.'..k end end + +local function s(t, opts) + local name, indent, fatal, maxnum = opts.name, opts.indent, opts.fatal, opts.maxnum + local sparse, custom, huge = opts.sparse, opts.custom, not opts.nohuge + local space, maxl = (opts.compact and '' or ' '), (opts.maxlevel or math.huge) + local maxlen, metatostring = tonumber(opts.maxlength), opts.metatostring + local iname, comm = '_'..(name or ''), opts.comment and (tonumber(opts.comment) or math.huge) + local numformat = opts.numformat or "%.17g" + local seen, sref, syms, symn = {}, {'local '..iname..'={}'}, {}, 0 + local function gensym(val) return '_'..(tostring(tostring(val)):gsub("[^%w]",""):gsub("(%d%w+)", + -- tostring(val) is needed because __tostring may return a non-string value + function(s) if not syms[s] then symn = symn+1; syms[s] = symn end return tostring(syms[s]) end)) end + local function safestr(s) return type(s) == "number" and tostring(huge and snum[tostring(s)] or numformat:format(s)) + or type(s) ~= "string" and tostring(s) -- escape NEWLINE/010 and EOF/026 + or ("%q"):format(s):gsub("\010","n"):gsub("\026","\\026") end + local function comment(s,l) return comm and (l or 0) < comm and ' --[['..select(2, pcall(tostring, s))..']]' or '' end + local function globerr(s,l) return globals[s] and globals[s]..comment(s,l) or not fatal + and safestr(select(2, pcall(tostring, s))) or error("Can't serialize "..tostring(s)) end + local function safename(path, name) -- generates foo.bar, foo[3], or foo['b a r'] + local n = name == nil and '' or name + local plain = type(n) == "string" and n:match("^[%l%u_][%w_]*$") and not keyword[n] + local safe = plain and n or '['..safestr(n)..']' + return (path or '')..(plain and path and '.' or '')..safe, safe end + local alphanumsort = type(opts.sortkeys) == 'function' and opts.sortkeys or function(k, o, n) -- k=keys, o=originaltable, n=padding + local maxn, to = tonumber(n) or 12, {number = 'a', string = 'b'} + local function padnum(d) return ("%0"..tostring(maxn).."d"):format(tonumber(d)) end + table.sort(k, function(a,b) + -- sort numeric keys first: k[key] is not nil for numerical keys + return (k[a] ~= nil and 0 or to[type(a)] or 'z')..(tostring(a):gsub("%d+",padnum)) + < (k[b] ~= nil and 0 or to[type(b)] or 'z')..(tostring(b):gsub("%d+",padnum)) end) end + local function val2str(t, name, indent, insref, path, plainindex, level) + local ttype, level, mt = type(t), (level or 0), getmetatable(t) + local spath, sname = safename(path, name) + local tag = plainindex and + ((type(name) == "number") and '' or name..space..'='..space) or + (name ~= nil and sname..space..'='..space or '') + if seen[t] then -- already seen this element + sref[#sref+1] = spath..space..'='..space..seen[t] + return tag..'nil'..comment('ref', level) end + -- protect from those cases where __tostring may fail + if type(mt) == 'table' and metatostring ~= false then + local to, tr = pcall(function() return mt.__tostring(t) end) + local so, sr = pcall(function() return mt.__serialize(t) end) + if (to or so) then -- knows how to serialize itself + seen[t] = insref or spath + t = so and sr or tr + ttype = type(t) + end -- new value falls through to be serialized + end + if ttype == "table" then + if level >= maxl then return tag..'{}'..comment('maxlvl', level) end + seen[t] = insref or spath + if next(t) == nil then return tag..'{}'..comment(t, level) end -- table empty + if maxlen and maxlen < 0 then return tag..'{}'..comment('maxlen', level) end + local maxn, o, out = math.min(#t, maxnum or #t), {}, {} + for key = 1, maxn do o[key] = key end + if not maxnum or #o < maxnum then + local n = #o -- n = n + 1; o[n] is much faster than o[#o+1] on large tables + for key in pairs(t) do if o[key] ~= key then n = n + 1; o[n] = key end end end + if maxnum and #o > maxnum then o[maxnum+1] = nil end + if opts.sortkeys and #o > maxn then alphanumsort(o, t, opts.sortkeys) end + local sparse = sparse and #o > maxn -- disable sparsness if only numeric keys (shorter output) + for n, key in ipairs(o) do + local value, ktype, plainindex = t[key], type(key), n <= maxn and not sparse + if opts.valignore and opts.valignore[value] -- skip ignored values; do nothing + or opts.keyallow and not opts.keyallow[key] + or opts.keyignore and opts.keyignore[key] + or opts.valtypeignore and opts.valtypeignore[type(value)] -- skipping ignored value types + or sparse and value == nil then -- skipping nils; do nothing + elseif ktype == 'table' or ktype == 'function' or badtype[ktype] then + if not seen[key] and not globals[key] then + sref[#sref+1] = 'placeholder' + local sname = safename(iname, gensym(key)) -- iname is table for local variables + sref[#sref] = val2str(key,sname,indent,sname,iname,true) end + sref[#sref+1] = 'placeholder' + local path = seen[t]..'['..tostring(seen[key] or globals[key] or gensym(key))..']' + sref[#sref] = path..space..'='..space..tostring(seen[value] or val2str(value,nil,indent,path)) + else + out[#out+1] = val2str(value,key,indent,nil,seen[t],plainindex,level+1) + if maxlen then + maxlen = maxlen - #out[#out] + if maxlen < 0 then break end + end + end + end + local prefix = string.rep(indent or '', level) + local head = indent and '{\n'..prefix..indent or '{' + local body = table.concat(out, ','..(indent and '\n'..prefix..indent or space)) + local tail = indent and "\n"..prefix..'}' or '}' + return (custom and custom(tag,head,body,tail,level) or tag..head..body..tail)..comment(t, level) + elseif badtype[ttype] then + seen[t] = insref or spath + return tag..globerr(t, level) + elseif ttype == 'function' then + seen[t] = insref or spath + if opts.nocode then return tag.."function() --[[..skipped..]] end"..comment(t, level) end + local ok, res = pcall(string.dump, t) + local func = ok and "((loadstring or load)("..safestr(res)..",'@serialized'))"..comment(t, level) + return tag..(func or globerr(t, level)) + else return tag..safestr(t) end -- handle all other types + end + local sepr = indent and "\n" or ";"..space + local body = val2str(t, name, indent) -- this call also populates sref + local tail = #sref>1 and table.concat(sref, sepr)..sepr or '' + local warn = opts.comment and #sref>1 and space.."--[[incomplete output with shared/self-references skipped]]" or '' + return not name and body..warn or "do local "..body..sepr..tail.."return "..name..sepr.."end" +end + +local function deserialize(data, opts) + local env = (opts and opts.safe == false) and G + or setmetatable({}, { + __index = function(t,k) return t end, + __call = function(t,...) error("cannot call functions") end + }) + local f, res = (loadstring or load)('return '..data, nil, nil, env) + if not f then f, res = (loadstring or load)(data, nil, nil, env) end + if not f then return f, res end + if setfenv then setfenv(f, env) end + return pcall(f) +end + +local function merge(a, b) if b then for k,v in pairs(b) do a[k] = v end end; return a; end +serpent = { _NAME = n, _COPYRIGHT = c, _DESCRIPTION = d, _VERSION = v, serialize = s, + load = deserialize, + dump = function(a, opts) return s(a, merge({name = '_', compact = true, sparse = true}, opts)) end, + line = function(a, opts) return s(a, merge({sortkeys = true, comment = true}, opts)) end, + block = function(a, opts) return s(a, merge({indent = ' ', sortkeys = true, comment = true}, opts)) end } + + +------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Utility methods +------------------------------------------------------------------------------------------------------------------------------------------------------------- + +function veafMissionEditor.logError(message) + print(veafMissionEditor.Id .. message) +end + +function veafMissionEditor.logInfo(message) + print(veafMissionEditor.Id .. message) +end + +function veafMissionEditor.logDebug(message) + if message and veafMissionEditor.Debug then + print(veafMissionEditor.Id .. message) + end +end + +function veafMissionEditor.logTrace(message) + if message and veafMissionEditor.Trace then + print(veafMissionEditor.Id .. message) + end +end + +function veafMissionEditor.sortCaseInsensitive (k, o) -- k=keys, o=original table + local maxn, to = 12, {number = 'a', string = 'b'} + local function padnum(d) return ("%0"..maxn.."d"):format(d) end + local sort = function(a,b) + -- this -vvvvvvvvvv- is needed to sort array keys first + return ((k[a] and 0 or to[type(a)] or 'z')..(tostring(a):gsub("%d+",padnum))):upper() + < ((k[b] and 0 or to[type(b)] or 'z')..(tostring(b):gsub("%d+",padnum))):upper() + end + table.sort(k, sort) + end +------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Core methods +------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Reads a mission file +function veafMissionEditor.readMissionFile(filePath, tableName) + local file = assert(loadfile(filePath)) + if not file then + veafMissionEditor.logError(string.format("Error while loading mission file [%s]",filePath)) + return + end + + file() + returner = loadstring("return "..tableName) + local table = returner() + return table +end + +--- Processes a mission object +function veafMissionEditor.processMission(mission) + return mission -- do nothing +end + +function veafMissionEditor.writeMissionFile(filePath, tableAsLua, tableName) + local file, e = io.open(filePath, "w+"); + if not file then + veafMissionEditor.logError(string.format("Error while writing mission to file [%s]",filePath)) + return error(e); + end + + file:write(string.format("%s = \n%s",tableName, tableAsLua)) + file:close(); +end + +function veafMissionEditor.editMission(inFilePath, outFilePath, tableName, exportOptions, processFunction) + local _exportOptions = _exportOptions + if not _exportOptions then + _exportOptions = {indent=" ", metatostring=false, comment=false, nocode=true, sortkeys=veafMissionEditor.sortCaseInsensitive} + end + + local _processFunction = processFunction + if not _processFunction then + _processFunction = veafMissionEditor.processMission + end + + veafMissionEditor.logDebug(string.format("Reading lua table from [%s]",inFilePath)) + local table = veafMissionEditor.readMissionFile(inFilePath, tableName) + veafMissionEditor.logDebug("Processing lua table") + table = _processFunction(table) + veafMissionEditor.logDebug("Exporting table as lua") + local tableAsLua = serpent.block(table, _exportOptions) + veafMissionEditor.logDebug(string.format("Writing lua table to [%s]",outFilePath)) + veafMissionEditor.writeMissionFile(outFilePath, tableAsLua, tableName) +end + diff --git a/scripts/veaf/veafMissionNormalizer.lua b/scripts/veaf/veafMissionNormalizer.lua new file mode 100644 index 00000000..b21248d9 --- /dev/null +++ b/scripts/veaf/veafMissionNormalizer.lua @@ -0,0 +1,137 @@ +------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- VEAF mission normalizer tool for DCS World +-- By Zip (2020) +-- +-- Features: +-- --------- +-- This tool processes all files in a mission, apply filters to normalize them and writes them back. +-- Usually, DCSW Mission Editor shuffles the data in the mission files each time the mission is saved, making it all but impossible to compare with a previous version. +-- With this tool, it becomes easy to compare mission files after an edition in DCS World Mission Editor. +-- +-- Prerequisite: +-- ------------ +-- * The mission file archive must already be exploded ; the script only works on the mission files, not directly on the .miz archive +-- +-- Basic Usage: +-- ------------ +-- The following workflow should be used : +-- * explode the mission (unzip it) +-- * run the normalizer on the exploded mission +-- * version the exploded mission files (save it, back it up, commit it to a source control system, whatever fits your routine) +-- * compile the mission (zip the exploded files again) +-- * edit the compiled mission with DCSW Mission Editor +-- * explode the mission (unzip it) +-- * run the normalizer on the exploded mission +-- * now you can run a comparison between the exploded mission and its previous version +-- +-- Call the script by running it in a lua environment ; it needs the veafMissionEditor library, so the script working directory must contain the veafMissionEditor.lua file +-- +-- veafMissionNormalizer.lua [-debug|-trace] +-- +-- Command line options: +-- * the path to the exploded mission files (no trailing backslash) +-- * -debug if set, the script will output some information ; useful to find out which units were edited +-- * -trace if set, the script will output a lot of information : useful to understand what went wrong +-- +------------------------------------------------------------------------------------------------------------------------------------------------------------- + +veafMissionNormalizer = {} + +------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Global settings. Stores the script constants +------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Identifier. All output in the log will start with this. +veafMissionNormalizer.Id = "NORMALIZER - " + +--- Version. +veafMissionNormalizer.Version = "1.0.0" + +-- trace level, specific to this module +veafMissionNormalizer.Trace = false +veafMissionNormalizer.Debug = false +------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Do not change anything below unless you know what you are doing! +------------------------------------------------------------------------------------------------------------------------------------------------------------- + +------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Utility methods +------------------------------------------------------------------------------------------------------------------------------------------------------------- + +function veafMissionNormalizer.logError(message) + print(veafMissionNormalizer.Id .. message) +end + +function veafMissionNormalizer.logInfo(message) + print(veafMissionNormalizer.Id .. message) +end + +function veafMissionNormalizer.logDebug(message) + if message and veafMissionNormalizer.Debug then + print(veafMissionNormalizer.Id .. message) + end +end + +function veafMissionNormalizer.logTrace(message) + if message and veafMissionNormalizer.Trace then + print(veafMissionNormalizer.Id .. message) + end +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Core methods +------------------------------------------------------------------------------------------------------------------------------------------------------------- +require("veafMissionEditor") + +function veafMissionNormalizer.normalizeMission(filePath) + -- normalize "mission" file + local _filePath = filePath .. "\\mission" + veafMissionEditor.editMission(_filePath, _filePath, "mission") + + -- normalize "warehouses" file + _filePath = filePath .. "\\warehouses" + veafMissionEditor.editMission(_filePath, _filePath, "warehouses") + + -- normalize "options" file + _filePath = filePath .. "\\options" + local _processFunction = function(table) + return {} -- delete all the content + end + veafMissionEditor.editMission(_filePath, _filePath, "options", nil, _processFunction) + + -- normalize "dictionary" file + _filePath = filePath .. "\\l10n\\DEFAULT\\dictionary" + veafMissionEditor.editMission(_filePath, _filePath, "dictionary") + + -- normalize "mapResource" file + _filePath = filePath .. "\\l10n\\DEFAULT\\mapResource" + veafMissionEditor.editMission(_filePath, _filePath, "mapResource") + +end + +veafMissionNormalizer.logDebug(string.format("#arg=%d",#arg)) +for i=0, #arg do + veafMissionNormalizer.logDebug(string.format("arg[%d]=%s",i,arg[i])) +end +if #arg < 1 then + veafMissionNormalizer.logError("USAGE : veafMissionNormalizer.lua ") + return +end +local debug = arg[2] and arg[2]:upper() == "-DEBUG" +local trace = arg[2] and arg[2]:upper() == "-TRACE" +if debug or trace then + veafMissionNormalizer.Debug = true + veafMissionEditor.Debug = true + if trace then + veafMissionNormalizer.Trace = true + veafMissionEditor.Trace = true + end +else + veafMissionNormalizer.Debug = false + veafMissionEditor.Debug = false + veafMissionNormalizer.Trace = false + veafMissionEditor.Trace = false +end + +local filePath = arg[1] +veafMissionNormalizer.normalizeMission(filePath) \ No newline at end of file diff --git a/scripts/veaf/veafMissionRadioPresetsEditor.lua b/scripts/veaf/veafMissionRadioPresetsEditor.lua new file mode 100644 index 00000000..20686eea --- /dev/null +++ b/scripts/veaf/veafMissionRadioPresetsEditor.lua @@ -0,0 +1,210 @@ +------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- VEAF radio presets editor tool for DCS World +-- By Zip (2020) +-- +-- Features: +-- --------- +-- * This tool processes a mission and sets predefined radio presets. +-- * The preset templates can be customized (see radioSettings-example.lua) +-- +-- Prerequisite: +-- ------------ +-- * The mission file archive must already be exploded ; the script only works on the mission files, not directly on the .miz archive +-- +-- Basic Usage: +-- ------------ +-- Call the script by running it in a lua environment ; it needs the veafMissionEditor library, so the script working directory must contain the veafMissionEditor.lua file +-- +-- veafMissionRadioPresetsEditor.lua [-debug|-trace] +-- +-- Command line options: +-- * the path to the exploded mission files (no trailing backslash) +-- * the path to the preset templates file (see radioSettings-example.lua) +-- * -debug if set, the script will output some information ; useful to find out which units were edited +-- * -trace if set, the script will output a lot of information : useful to understand what went wrong +------------------------------------------------------------------------------------------------------------------------------------------------------------- + +veafMissionRadioPresetsEditor = {} + +------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Global settings. Stores the script constants +------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Identifier. All output in the log will start with this. +veafMissionRadioPresetsEditor.Id = "RADIOPRESETS_EDITOR - " + +--- Version. +veafMissionRadioPresetsEditor.Version = "1.0.0" + +-- trace level, specific to this module +veafMissionRadioPresetsEditor.Trace = false +veafMissionRadioPresetsEditor.Debug = false + +------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Do not change anything below unless you know what you are doing! +------------------------------------------------------------------------------------------------------------------------------------------------------------- + +------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Utility methods +------------------------------------------------------------------------------------------------------------------------------------------------------------- + +function veafMissionRadioPresetsEditor.logError(message) + print(veafMissionRadioPresetsEditor.Id .. message) +end + +function veafMissionRadioPresetsEditor.logInfo(message) + print(veafMissionRadioPresetsEditor.Id .. message) +end + +function veafMissionRadioPresetsEditor.logDebug(message) + if message and veafMissionRadioPresetsEditor.Debug then + print(veafMissionRadioPresetsEditor.Id .. message) + end +end + +function veafMissionRadioPresetsEditor.logTrace(message) + if message and veafMissionRadioPresetsEditor.Trace then + print(veafMissionRadioPresetsEditor.Id .. message) + end +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Core methods +------------------------------------------------------------------------------------------------------------------------------------------------------------- +require("veafMissionEditor") + +function veafMissionRadioPresetsEditor.editUnit(coa_name, country_name, unit_t) + --veafMissionRadioPresetsEditor.logTrace(string.format("editUnit(%s)",serpent.line(unit_t))) + local hasBeenEdited = false + local unitName = unit_t["name"] + local unitId = unit_t["unitId"] + local unitType = unit_t["type"] + veafMissionRadioPresetsEditor.logTrace(string.format("Testing [%s/%s] %s %s (%s) ",serpent.line(coa_name), serpent.line(country_name), serpent.line(unitType), serpent.line(unitName), serpent.line(unitId))) + + if unit_t["skill"] and unit_t["skill"] == "Client" then -- only human players + veafMissionRadioPresetsEditor.logTrace("Client found") + if unitType then + for setting, setting_t in pairs(radioSettings) do + local type = setting_t["type"] + veafMissionRadioPresetsEditor.logTrace(string.format("type=[%s]",serpent.line(type))) + local coalition = setting_t["coalition"] + veafMissionRadioPresetsEditor.logTrace(string.format("coalition=[%s]",serpent.line(coalition))) + local country = setting_t["country"] + veafMissionRadioPresetsEditor.logTrace(string.format("country=[%s]",serpent.line(country))) + if not(coalition) or coalition == coa_name then + veafMissionRadioPresetsEditor.logTrace("Coalition checked") + if not(country) or country == country_name then + veafMissionRadioPresetsEditor.logTrace("Country checked") + if not(type) or type == unitType then + veafMissionRadioPresetsEditor.logTrace("Unit type checked") + -- edit the unit + veafMissionRadioPresetsEditor.logDebug(string.format("Edited [%s/%s] %s %s (%s) ",coa_name, country_name, unitType, unitName, unitId)) + unit_t["Radio"] = nil + unit_t["Radio"] = setting_t["Radio"] + hasBeenEdited = true + break + end + end + end + end + end + end + + return hasBeenEdited +end + +function veafMissionRadioPresetsEditor.editRadioPresets(missionTable) + local _editGroups = function(coa_name, country_name, container) + local groups_t = container["group"] + for group, group_t in pairs(groups_t) do + veafMissionRadioPresetsEditor.logTrace(string.format("Browsing group [%s]",group)) + local units_t = group_t["units"] + for unit, unit_t in pairs(units_t) do + local hasBeenEdited = veafMissionRadioPresetsEditor.editUnit(coa_name, country_name, unit_t) + if hasBeenEdited then + -- set the "radioSet" value to false + veafMissionRadioPresetsEditor.logTrace("seting the radioSet value to false") + group_t["radioSet"] = false + end + end + end + end + + local coalitions_t = missionTable["coalition"] + -- browse coalitions + for coa, coa_t in pairs(coalitions_t) do + local coa_name = coa_t["name"] + veafMissionRadioPresetsEditor.logTrace(string.format("Browsing coalition [%s]",coa_name)) + local countries_t = coa_t["country"] + -- browse countries + for country, country_t in pairs(countries_t) do + local country_name = country_t["name"] + veafMissionRadioPresetsEditor.logTrace(string.format("Browsing country [%s]",country_name)) + --veafMissionRadioPresetsEditor.logTrace(string.format("country_t=%s",serpent.line(country_t))) + -- process helicopters + veafMissionRadioPresetsEditor.logTrace("Processing helicopters") + local helicopters_t = country_t["helicopter"] + if helicopters_t then + --veafMissionRadioPresetsEditor.logTrace(string.format("helicopters_t=%s",serpent.line(helicopters_t))) + _editGroups(coa_name, country_name, helicopters_t) + end + -- process airplanes + veafMissionRadioPresetsEditor.logTrace("Processing airplanes") + local planes_t = country_t["plane"] + if planes_t then + --veafMissionRadioPresetsEditor.logTrace(string.format("planes_t=%s",serpent.line(planes_t))) + _editGroups(coa_name, country_name, planes_t) + end + end + end + + return missionTable +end + +function veafMissionRadioPresetsEditor.processMission(filePath, radioSettingsPath) + -- load the radioSettings file + veafMissionRadioPresetsEditor.logDebug(string.format("Loading radio settings from [%s]",radioSettingsPath)) + local file = assert(loadfile(radioSettingsPath)) + if not file then + veafMissionEditor.logError(string.format("Error while loading radio settings file [%s]",radioSettingsPath)) + return + end + file() + veafMissionRadioPresetsEditor.logDebug("Radio settings loaded") + + -- edit the "mission" file + veafMissionRadioPresetsEditor.logDebug(string.format("Processing mission at [%s]",filePath)) + local _filePath = filePath .. "\\mission" + local _processFunction = veafMissionRadioPresetsEditor.editRadioPresets + veafMissionEditor.editMission(_filePath, _filePath, "mission", nil, _processFunction) + veafMissionRadioPresetsEditor.logDebug("Mission edited") +end + +veafMissionRadioPresetsEditor.logDebug(string.format("#arg=%d",#arg)) +for i=0, #arg do + veafMissionRadioPresetsEditor.logDebug(string.format("arg[%d]=%s",i,arg[i])) +end +if #arg < 2 then + veafMissionRadioPresetsEditor.logError("USAGE : veafMissionRadioPresetsEditor.lua [-debug|-trace]") + return +end + +local filePath = arg[1] +local radioSettingsPath = arg[2] +local debug = arg[3] and arg[3]:upper() == "-DEBUG" +local trace = arg[3] and arg[3]:upper() == "-TRACE" +if debug or trace then + veafMissionRadioPresetsEditor.Debug = true + veafMissionEditor.Debug = true + if trace then + veafMissionRadioPresetsEditor.Trace = true + veafMissionEditor.Trace = true + end +else + veafMissionRadioPresetsEditor.Debug = false + veafMissionEditor.Debug = false + veafMissionRadioPresetsEditor.Trace = false + veafMissionEditor.Trace = false +end + +veafMissionRadioPresetsEditor.processMission(filePath, radioSettingsPath) \ No newline at end of file