diff --git a/CMakeLists.txt b/CMakeLists.txt index b5a2518..4f03f3f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,9 @@ if (TARGET wzmaplib) message(DEBUG "wzmaplib is already available - no need to use FetchContent") else() message(STATUS "maptools: wzmaplib must be fetched") - set(_cmake_min_ver_supported 3.11) # CMake 3.11 is the minimum version for FetchContent + # CMake 3.11 is the minimum version for FetchContent + # CMake 3.14 is the minimum version for FetchContent_MakeAvailable, CMake 3.18 is required for "SOURCE_SUBDIR" to work with it, and CMake 3.28 is required for EXCLUDE_FROM_ALL to work with it + set(_cmake_min_ver_supported 3.11) endif() cmake_minimum_required(VERSION ${_cmake_min_ver_supported}...3.24) @@ -47,16 +49,23 @@ if(NOT TARGET wzmaplib) FetchContent_Declare( warzone2100_repo GIT_REPOSITORY https://github.com/Warzone2100/warzone2100.git - GIT_TAG 85310b635635955e453901c308c343667b6f0a35 + GIT_TAG 6847a6500248bffbbaaa786f1a9b122d22307a15 GIT_PROGRESS TRUE USES_TERMINAL_DOWNLOAD TRUE + SOURCE_SUBDIR "lib/wzmaplib" + EXCLUDE_FROM_ALL ) - FetchContent_GetProperties(warzone2100_repo) - if(NOT warzone2100_repo_POPULATED) - FetchContent_Populate(warzone2100_repo) + if(NOT CMAKE_VERSION VERSION_LESS 3.28) + FetchContent_MakeAvailable(warzone2100_repo) + message(STATUS "maptools: Added wzmaplib") + else() + FetchContent_GetProperties(warzone2100_repo) + if(NOT warzone2100_repo_POPULATED) + FetchContent_Populate(warzone2100_repo) + endif() + message(STATUS "maptools: Adding wzmaplib") + add_subdirectory("${warzone2100_repo_SOURCE_DIR}/lib/wzmaplib" "lib/wzmaplib" EXCLUDE_FROM_ALL) endif() - message(STATUS "maptools: Adding wzmaplib") - add_subdirectory("${warzone2100_repo_SOURCE_DIR}/lib/wzmaplib" "lib/wzmaplib" EXCLUDE_FROM_ALL) endif() find_package(PNG 1.2 REQUIRED) diff --git a/ChangeLog b/ChangeLog index fd11878..4b081d5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2025-01-29: Version 1.3.1: + * Update wzmaplib to 1.3.1 + +2025-01-28: Version 1.3.0: + * Update wzmaplib to 1.3.0 + * Fix loading script-generated maps + * Add --map-seed option + 2024-08-01: Version 1.2.7: * Update wzmaplib to 1.2.7 * Add --layers option for genpreview command diff --git a/README.md b/README.md index 7fea57d..3b7c89b 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ Convert a map from one format to another | `--preserve-mods` | Copy other files from the original map package (i.e. the extra files / modifications in a map-mod) | | | | `--output-uncompressed` | Output uncompressed to a folder (not in a .wz file) | | | | `--set-name` | Set / override the map name when converting | | | +| `--map-seed` | Specify the script-generated map seed | uint32_t | DEFAULTS to `rand()` | > Note: When converting a script-generated map: > - If the output format is `jsonv2` (or later) the map script will be preserved @@ -76,6 +77,8 @@ Generate a map preview PNG | `-o`,`--output` | Output PNG filename (+ path) | TEXT:PATH | REQUIRED (may also be specified as positional parameter) | | `-c`,`--playercolors` | Player colors | ENUM:value in {`simple`, `wz`} | DEFAULTS to `simple` | | `--scavcolor` | Specify the scavengers hex color | RGB hex color code | DEFAULTS to `#800000` (maroon) | +| `--layers` | Specify layers to draw | Either `all` or a comma-separated list of any of: {`terrain`, `structures`, `oil`} | DEFAULTS to `all` | +| `--map-seed` | Specify the script-generated map seed | uint32_t | DEFAULTS to `rand()` | ## `maptools package info` @@ -90,6 +93,7 @@ Extract info / stats from a map package to JSON | `-h`,`--help` | Print help message and exit | | | | `-i`,`--input` | Input map package (.wz package, or extracted package folder) | TEXT:PATH | REQUIRED (may also be specified as positional parameter) | | `-o`,`--output` | Output filename (+ path) | TEXT:PATH | | +| `--map-seed` | Specify the script-generated map seed | uint32_t | DEFAULTS to `rand()` | > If `--output` is not specified, the JSON result is output to stdout @@ -116,6 +120,7 @@ Both `inputmapdir` and `outputmapdir` must exist. | `-f`,`--format` | [Output map format](#output-map-formats) | ENUM:value in { `bjo`, `json`, `jsonv2`, `latest`} | REQUIRED | | `-i`,`--input` | Input map directory | TEXT:DIR | REQUIRED (may also be specified as positional parameter) | | `-o`,`--output` | Output map directory | TEXT:DIR | REQUIRED (may also be specified as positional parameter) | +| `--map-seed` | Specify the script-generated map seed | uint32_t | DEFAULTS to `rand()` | ## `maptools map genpreview` @@ -133,6 +138,7 @@ Both `inputmapdir` and the parent directory for the output filename (`output`) m | `-c`,`--playercolors` | Player colors | ENUM:value in {`simple`, `wz`} | DEFAULTS to `simple` | | `--scavcolor` | Specify the scavengers hex color | RGB hex color code | DEFAULTS to `#800000` (maroon) | | `--layers` | Specify layers to draw | Either `all` or a comma-separated list of any of: {`terrain`, `structures`, `oil`} | DEFAULTS to `all` | +| `--map-seed` | Specify the script-generated map seed | uint32_t | DEFAULTS to `rand()` | # Output Level Info Formats | [format] | Description | flaME | WZ < 3.4 | WZ 3.4+ | WZ 4.1+ | WZ 4.3+ | diff --git a/VERSION.in b/VERSION.in index c04c650..3a3cd8c 100644 --- a/VERSION.in +++ b/VERSION.in @@ -1 +1 @@ -1.2.7 +1.3.1 diff --git a/src/maptools.cpp b/src/maptools.cpp index a5507f5..50160c3 100644 --- a/src/maptools.cpp +++ b/src/maptools.cpp @@ -240,7 +240,7 @@ bool lexical_cast(const std::string &input, MapPreviewColorScheme::DrawOptions & } // namespace WzMap -static bool convertMapPackage(const std::string& mapPackageContentsPath, const std::string& outputPath, WzMap::LevelFormat levelFormat, WzMap::OutputFormat outputFormat, bool copyAdditionalFiles, bool verbose, bool exportUncompressed, bool fixedLastMod, optional override_map_name = nullopt, std::shared_ptr mapIO = std::shared_ptr(new WzMap::StdIOProvider())) +static bool convertMapPackage(const std::string& mapPackageContentsPath, const std::string& outputPath, WzMap::LevelFormat levelFormat, WzMap::OutputFormat outputFormat, uint32_t mapSeed, bool copyAdditionalFiles, bool verbose, bool exportUncompressed, bool fixedLastMod, optional override_map_name = nullopt, std::shared_ptr mapIO = std::shared_ptr(new WzMap::StdIOProvider())) { auto logger = std::make_shared(new MapToolDebugLogger(verbose)); @@ -251,7 +251,7 @@ static bool convertMapPackage(const std::string& mapPackageContentsPath, const s return false; } - auto wzMap = wzMapPackage->loadMap(rand(), logger); + auto wzMap = wzMapPackage->loadMap(mapSeed, logger); if (!wzMap) { // Failed to load map @@ -324,7 +324,7 @@ static bool convertMapPackage(const std::string& mapPackageContentsPath, const s } #if !defined(WZ_MAPTOOLS_DISABLE_ARCHIVE_SUPPORT) -static bool convertMapPackage_FromArchive(const std::string& mapArchive, const std::string& outputPath, WzMap::LevelFormat levelFormat, WzMap::OutputFormat outputFormat, bool copyAdditionalFiles, bool verbose, bool outputUncompressed, bool fixedLastMod, optional override_map_name) +static bool convertMapPackage_FromArchive(const std::string& mapArchive, const std::string& outputPath, WzMap::LevelFormat levelFormat, WzMap::OutputFormat outputFormat, uint32_t mapSeed, bool copyAdditionalFiles, bool verbose, bool outputUncompressed, bool fixedLastMod, optional override_map_name) { auto zipArchive = WzMapZipIO::openZipArchiveFS(mapArchive.c_str()); if (!zipArchive) @@ -333,13 +333,13 @@ static bool convertMapPackage_FromArchive(const std::string& mapArchive, const s return false; } - return convertMapPackage("", outputPath, levelFormat, outputFormat, copyAdditionalFiles, verbose, outputUncompressed, fixedLastMod, override_map_name, zipArchive); + return convertMapPackage("", outputPath, levelFormat, outputFormat, mapSeed, copyAdditionalFiles, verbose, outputUncompressed, fixedLastMod, override_map_name, zipArchive); } #endif // !defined(WZ_MAPTOOLS_DISABLE_ARCHIVE_SUPPORT) -static bool convertMap(WzMap::MapType mapType, uint32_t mapMaxPlayers, const std::string& inputMapDirectory, const std::string& outputMapDirectory, WzMap::OutputFormat outputFormat, bool verbose) +static bool convertMap(WzMap::MapType mapType, uint32_t mapMaxPlayers, const std::string& inputMapDirectory, const std::string& outputMapDirectory, WzMap::OutputFormat outputFormat, uint32_t mapSeed, bool verbose) { - auto wzMap = WzMap::Map::loadFromPath(inputMapDirectory, mapType, mapMaxPlayers, rand(), std::make_shared(new MapToolDebugLogger(verbose))); + auto wzMap = WzMap::Map::loadFromPath(inputMapDirectory, mapType, mapMaxPlayers, mapSeed, std::make_shared(new MapToolDebugLogger(verbose))); if (!wzMap) { // Failed to load map @@ -489,7 +489,7 @@ enum class MapToolsPreviewColorProvider WZPlayerColors }; -static bool generateMapPreviewPNG_FromMapObject(WzMap::Map& map, const std::string& outputPNGPath, MapToolsPreviewColorProvider playerColorProvider, WzMap::MapPreviewColor scavsColor, const WzMap::MapPreviewColorScheme::DrawOptions& drawOptions, const WzMap::LevelDetails &levelDetails) +static std::unique_ptr generateMapPreview_FromMapObject_Impl(WzMap::Map& map, MapToolsPreviewColorProvider playerColorProvider, WzMap::MapPreviewColor scavsColor, const WzMap::MapPreviewColorScheme::DrawOptions& drawOptions, const WzMap::LevelDetails &levelDetails) { WzMap::MapPreviewColorScheme previewColorScheme; previewColorScheme.hqColor = {255, 0, 255, 255}; @@ -518,12 +518,18 @@ static bool generateMapPreviewPNG_FromMapObject(WzMap::Map& map, const std::stri } previewColorScheme.drawOptions = drawOptions; - auto previewResult = WzMap::generate2DMapPreview(map, previewColorScheme, WzMap::MapStatsConfiguration(levelDetails.type)); + return WzMap::generate2DMapPreview(map, previewColorScheme, WzMap::MapStatsConfiguration(levelDetails.type)); +} + +static bool generateMapPreviewPNG_FromMapObject(WzMap::Map& map, const std::string& outputPNGPath, MapToolsPreviewColorProvider playerColorProvider, WzMap::MapPreviewColor scavsColor, const WzMap::MapPreviewColorScheme::DrawOptions& drawOptions, const WzMap::LevelDetails &levelDetails) +{ + auto previewResult = generateMapPreview_FromMapObject_Impl(map, playerColorProvider, scavsColor, drawOptions, levelDetails); if (!previewResult) { std::cerr << "Failed to generate map preview" << std::endl; return false; } + if (!savePng(outputPNGPath.c_str(), previewResult->imageData.data(), static_cast(previewResult->width), static_cast(previewResult->height))) { std::cerr << "Failed to save preview PNG" << std::endl; @@ -536,7 +542,7 @@ static bool generateMapPreviewPNG_FromMapObject(WzMap::Map& map, const std::stri return true; } -static bool generateMapPreviewPNG_FromPackageContents(const std::string& mapPackageContentsPath, const std::string& outputPNGPath, MapToolsPreviewColorProvider playerColorProvider, WzMap::MapPreviewColor scavsColor, const WzMap::MapPreviewColorScheme::DrawOptions& drawOptions, bool verbose, std::shared_ptr mapIO = std::shared_ptr(new WzMap::StdIOProvider())) +static bool generateMapPreviewPNG_FromPackageContents(const std::string& mapPackageContentsPath, const std::string& outputPNGPath, MapToolsPreviewColorProvider playerColorProvider, WzMap::MapPreviewColor scavsColor, const WzMap::MapPreviewColorScheme::DrawOptions& drawOptions, uint32_t mapSeed, bool verbose, std::shared_ptr mapIO = std::shared_ptr(new WzMap::StdIOProvider())) { auto logger = std::make_shared(new MapToolDebugLogger(verbose)); @@ -547,7 +553,7 @@ static bool generateMapPreviewPNG_FromPackageContents(const std::string& mapPack return false; } - auto wzMap = wzMapPackage->loadMap(rand(), logger); + auto wzMap = wzMapPackage->loadMap(mapSeed, logger); if (!wzMap) { // Failed to load map @@ -559,7 +565,7 @@ static bool generateMapPreviewPNG_FromPackageContents(const std::string& mapPack } #if !defined(WZ_MAPTOOLS_DISABLE_ARCHIVE_SUPPORT) -static bool generateMapPreviewPNG_FromArchive(const std::string& mapArchive, const std::string& outputPNGPath, MapToolsPreviewColorProvider playerColorProvider, WzMap::MapPreviewColor scavsColor, const WzMap::MapPreviewColorScheme::DrawOptions& drawOptions, bool verbose) +static bool generateMapPreviewPNG_FromArchive(const std::string& mapArchive, const std::string& outputPNGPath, MapToolsPreviewColorProvider playerColorProvider, WzMap::MapPreviewColor scavsColor, const WzMap::MapPreviewColorScheme::DrawOptions& drawOptions, uint32_t mapSeed, bool verbose) { auto zipArchive = WzMapZipIO::openZipArchiveFS(mapArchive.c_str()); if (!zipArchive) @@ -568,13 +574,13 @@ static bool generateMapPreviewPNG_FromArchive(const std::string& mapArchive, con return false; } - return generateMapPreviewPNG_FromPackageContents("", outputPNGPath, playerColorProvider, scavsColor, drawOptions, verbose, zipArchive); + return generateMapPreviewPNG_FromPackageContents("", outputPNGPath, playerColorProvider, scavsColor, drawOptions, mapSeed, verbose, zipArchive); } #endif // !defined(WZ_MAPTOOLS_DISABLE_ARCHIVE_SUPPORT) -static bool generateMapPreviewPNG_FromMapDirectory(WzMap::MapType mapType, uint32_t mapMaxPlayers, const std::string& inputMapDirectory, const std::string& outputPNGPath, MapToolsPreviewColorProvider playerColorProvider, WzMap::MapPreviewColor scavsColor, const WzMap::MapPreviewColorScheme::DrawOptions& drawOptions, bool verbose) +static bool generateMapPreviewPNG_FromMapDirectory(WzMap::MapType mapType, uint32_t mapMaxPlayers, const std::string& inputMapDirectory, const std::string& outputPNGPath, MapToolsPreviewColorProvider playerColorProvider, WzMap::MapPreviewColor scavsColor, const WzMap::MapPreviewColorScheme::DrawOptions& drawOptions, uint32_t mapSeed, bool verbose) { - auto wzMap = WzMap::Map::loadFromPath(inputMapDirectory, mapType, mapMaxPlayers, rand(), std::make_shared(new MapToolDebugLogger(verbose))); + auto wzMap = WzMap::Map::loadFromPath(inputMapDirectory, mapType, mapMaxPlayers, mapSeed, std::make_shared(new MapToolDebugLogger(verbose))); if (!wzMap) { // Failed to load map @@ -803,7 +809,7 @@ static nlohmann::ordered_json generateMapInfoJSON_FromPackage(WzMap::MapPackage& return output; } -static optional generateMapInfoJSON_FromPackageContents(const std::string& mapPackageContentsPath, std::shared_ptr logger, std::shared_ptr mapIO = std::shared_ptr(new WzMap::StdIOProvider())) +static optional generateMapInfoJSON_FromPackageContents(const std::string& mapPackageContentsPath, uint32_t mapSeed, std::shared_ptr logger, std::shared_ptr mapIO = std::shared_ptr(new WzMap::StdIOProvider())) { auto wzMapPackage = WzMap::MapPackage::loadPackage(mapPackageContentsPath, logger, mapIO); if (!wzMapPackage) @@ -812,7 +818,7 @@ static optional generateMapInfoJSON_FromPackageContents( return nullopt; } - auto mapStatsResult = wzMapPackage->calculateMapStats(); + auto mapStatsResult = wzMapPackage->calculateMapStats(mapSeed); if (!mapStatsResult.has_value()) { std::cerr << "Failed to calculate map info / stats from: " << mapPackageContentsPath << std::endl; @@ -823,7 +829,7 @@ static optional generateMapInfoJSON_FromPackageContents( } #if !defined(WZ_MAPTOOLS_DISABLE_ARCHIVE_SUPPORT) -static optional generateMapInfoJSON_FromArchive(const std::string& mapArchive, std::shared_ptr logger) +static optional generateMapInfoJSON_FromArchive(const std::string& mapArchive, uint32_t mapSeed, std::shared_ptr logger) { auto zipArchive = WzMapZipIO::openZipArchiveFS(mapArchive.c_str()); if (!zipArchive) @@ -832,7 +838,7 @@ static optional generateMapInfoJSON_FromArchive(const st return nullopt; } - return generateMapInfoJSON_FromPackageContents("", logger, zipArchive); + return generateMapInfoJSON_FromPackageContents("", mapSeed, logger, zipArchive); } #endif // !defined(WZ_MAPTOOLS_DISABLE_ARCHIVE_SUPPORT) @@ -898,9 +904,77 @@ class AsHexColorValue : public CLI::Validator { } }; -static void addSubCommand_Package(CLI::App& app, int& retVal, bool& verbose) +class WzMapToolsAppInstance : public CLI::App +{ +protected: + explicit WzMapToolsAppInstance() + : CLI::App("WZ2100 Map Tools") + { } +public: + static std::shared_ptr makeWzMapToolsAppInstance() + { + class make_shared_enabler: public WzMapToolsAppInstance {}; + auto app = std::make_shared(); + + #if defined(MAPTOOLS_CLI_VERSION_MAJOR) && defined(MAPTOOLS_CLI_VERSION_MINOR) && defined(MAPTOOLS_CLI_VERSION_REV) + app->set_version_flag("--version", []() -> std::string { + return generateMapToolsVersionInfo(); + }); + #else + #error Missing maptools version defines + #endif + + // Initialize rand() + srand((unsigned int)time(NULL)); + (void)rand(); + app->mapSeed = rand(); + + std::stringstream footerInfo; + footerInfo << "License: GPL-2.0-or-later" << std::endl; + footerInfo << "Source: https://github.com/Warzone2100/maptools-cli" << std::endl; + app->footer(footerInfo.str()); + + app->add_flag("-v,--verbose", app->verbose, "Verbose output"); + + WzMapToolsAppInstance::addSubCommand_Package(app); + WzMapToolsAppInstance::addSubCommand_Map(app); + + return app; + } + int getRetVal() const { return retVal; } +private: + static void addSubCommand_Package(const std::shared_ptr& app); + static void addSubCommand_Map(const std::shared_ptr& app); +private: + int retVal = 0; + bool verbose = false; + + std::string inputPath; + std::string outputPath; + uint32_t mapSeed; + WzMap::OutputFormat outputMapFormat = WzMap::LatestOutputFormat; + + // package variables + WzMap::LevelFormat outputLevelFormat = WzMap::LevelFormat::JSON; + bool sub_convert_copyadditionalfiles = false; + bool sub_convert_fixed_last_mod = false; + bool sub_convert_uncompressed = false; + std::string override_map_name; + + MapToolsPreviewColorProvider preview_PlayerColorProvider = MapToolsPreviewColorProvider::Simple; + WzMap::MapPreviewColor preview_scavsColor = ScavsColorDefault; + WzMap::MapPreviewColorScheme::DrawOptions preview_drawOptions; + + // map commands variables + WzMap::MapType mapType = WzMap::MapType::SKIRMISH; + uint32_t mapMaxPlayers = 0; +}; + +void WzMapToolsAppInstance::addSubCommand_Package(const std::shared_ptr& app) { - CLI::App* sub_package = app.add_subcommand("package", "Manipulating a map package"); + std::weak_ptr weakAppInstance = std::weak_ptr(app); + + CLI::App* sub_package = app->add_subcommand("package", "Manipulating a map package"); sub_package->fallthrough(); std::string inputOptionDescription; @@ -921,53 +995,52 @@ static void addSubCommand_Package(CLI::App& app, int& retVal, bool& verbose) // [CONVERTING MAP PACKAGE] CLI::App* sub_convert = sub_package->add_subcommand("convert", "Convert a map from one format to another"); sub_convert->fallthrough(); - static WzMap::LevelFormat outputLevelFormat = WzMap::LevelFormat::JSON; - sub_convert->add_option("-l,--levelformat", outputLevelFormat, "Output level info format") + sub_convert->add_option("-l,--levelformat", app->outputLevelFormat, "Output level info format") ->transform(CLI::CheckedTransformer(levelformat_map, CLI::ignore_case).description("value in {\n\t\tlev -> LEV (flaME-compatible / old),\n\t\tjson -> JSON level file (WZ 4.3+),\n\t\tlatest -> " + CLI::detail::to_string(WzMap::LatestLevelFormat) + "}")) ->default_val("latest"); - static WzMap::OutputFormat outputMapFormat = WzMap::LatestOutputFormat; - sub_convert->add_option("-f,--format", outputMapFormat, "Output map format") + sub_convert->add_option("-f,--format", app->outputMapFormat, "Output map format") ->required() ->transform(CLI::CheckedTransformer(outputformat_map, CLI::ignore_case).description("value in {\n\t\tbjo -> Binary .BJO (flaME-compatible / old),\n\t\tjson -> JSONv1 (WZ 3.4+),\n\t\tjsonv2 -> JSONv2 (WZ 4.1+),\n\t\tlatest -> " + CLI::detail::to_string(WzMap::LatestOutputFormat) + "}")); - static std::string inputMapPackage; - sub_convert->add_option("-i,--input,input", inputMapPackage, inputOptionDescription) + sub_convert->add_option("-i,--input,input", app->inputPath, inputOptionDescription) ->required() ->check(CLI::ExistingPath); - static std::string outputPath; - sub_convert->add_option("-o,--output,output", outputPath, "Output path") + sub_convert->add_option("-o,--output,output", app->outputPath, "Output path") ->required() ->check(CLI::NonexistentPath); - static bool sub_convert_copyadditionalfiles = false; - sub_convert->add_flag("--preserve-mods", sub_convert_copyadditionalfiles, "Copy other files from the original map package (i.e. the extra files / modifications in a map-mod)"); - static bool sub_convert_fixed_last_mod = false; - sub_convert->add_flag("--fixed-lastmod", sub_convert_fixed_last_mod, "Fixed last modification date (if outputting to a .wz archive)"); - static bool sub_convert_uncompressed = false; - sub_convert->add_flag("--output-uncompressed", sub_convert_uncompressed, "Output uncompressed to a folder (not in a .wz file)"); - static std::string override_map_name; - sub_convert->add_option("--set-name", override_map_name, "Set / override the map name when converting"); - sub_convert->callback([&]() { + sub_convert->add_flag("--preserve-mods", app->sub_convert_copyadditionalfiles, "Copy other files from the original map package (i.e. the extra files / modifications in a map-mod)"); + sub_convert->add_flag("--fixed-lastmod", app->sub_convert_fixed_last_mod, "Fixed last modification date (if outputting to a .wz archive)"); + sub_convert->add_flag("--output-uncompressed", app->sub_convert_uncompressed, "Output uncompressed to a folder (not in a .wz file)"); + sub_convert->add_option("--set-name", app->override_map_name, "Set / override the map name when converting"); + sub_convert->add_option("--map-seed", app->mapSeed, "Specify the script-generated map seed"); + sub_convert->callback([weakAppInstance, inputPathIsFile]() { + auto app = weakAppInstance.lock(); + if (!app) + { + std::cerr << "ERROR: Invalid instance" << std::endl; + return; + } optional override_map_name_opt = nullopt; - if (!override_map_name.empty()) + if (!app->override_map_name.empty()) { - override_map_name_opt = override_map_name; + override_map_name_opt = app->override_map_name; } - if (inputPathIsFile(inputMapPackage)) + if (inputPathIsFile(app->inputPath)) { #if !defined(WZ_MAPTOOLS_DISABLE_ARCHIVE_SUPPORT) - if (!convertMapPackage_FromArchive(inputMapPackage, outputPath, outputLevelFormat, outputMapFormat, sub_convert_copyadditionalfiles, verbose, sub_convert_uncompressed, sub_convert_fixed_last_mod, override_map_name_opt)) + if (!convertMapPackage_FromArchive(app->inputPath, app->outputPath, app->outputLevelFormat, app->outputMapFormat, app->mapSeed, app->sub_convert_copyadditionalfiles, app->verbose, app->sub_convert_uncompressed, app->sub_convert_fixed_last_mod, override_map_name_opt)) { - retVal = 1; + app->retVal = 1; } #else - std::cerr << "ERROR: maptools was compiled without support for .wz archives, and cannot open: " << inputMapPackage << std::endl; + std::cerr << "ERROR: maptools was compiled without support for .wz archives, and cannot open: " << app->inputPath << std::endl; retVal = 1; #endif } else { - if (!convertMapPackage(inputMapPackage, outputPath, outputLevelFormat, outputMapFormat, sub_convert_copyadditionalfiles, verbose, sub_convert_uncompressed, sub_convert_fixed_last_mod, override_map_name_opt)) + if (!convertMapPackage(app->inputPath, app->outputPath, app->outputLevelFormat, app->outputMapFormat, app->mapSeed, app->sub_convert_copyadditionalfiles, app->verbose, app->sub_convert_uncompressed, app->sub_convert_fixed_last_mod, override_map_name_opt)) { - retVal = 1; + app->retVal = 1; } } }); @@ -975,42 +1048,44 @@ static void addSubCommand_Package(CLI::App& app, int& retVal, bool& verbose) // [GENERATING MAP PREVIEW PNG] CLI::App* sub_preview = sub_package->add_subcommand("genpreview", "Generate a map preview PNG"); sub_preview->fallthrough(); - static std::string preview_inputMap; - sub_preview->add_option("-i,--input,input", preview_inputMap, inputOptionDescription) + sub_preview->add_option("-i,--input,input", app->inputPath, inputOptionDescription) ->required() ->check(CLI::ExistingPath); - static std::string preview_outputPNGFilename; - sub_preview->add_option("-o,--output,output", preview_outputPNGFilename, "Output PNG filename (+ path)") + sub_preview->add_option("-o,--output,output", app->outputPath, "Output PNG filename (+ path)") ->required() ->check(FileExtensionValidator(".png")); - static MapToolsPreviewColorProvider preview_PlayerColorProvider = MapToolsPreviewColorProvider::Simple; - sub_preview->add_option("-c,--playercolors", preview_PlayerColorProvider, "Player colors") + sub_preview->add_option("-c,--playercolors", app->preview_PlayerColorProvider, "Player colors") ->transform(CLI::CheckedTransformer(previewcolors_map, CLI::ignore_case).description("value in {\n\t\tsimple -> use one color for scavs, one color for players,\n\t\twz -> use WZ colors for players (distinct)\n\t}")) ->default_val("simple"); - static WzMap::MapPreviewColor preview_scavsColor = ScavsColorDefault; - sub_preview->add_option("--scavcolor", preview_scavsColor, "Specify the scavengers hex color") + sub_preview->add_option("--scavcolor", app->preview_scavsColor, "Specify the scavengers hex color") ->check(AsHexColorValue()); - static WzMap::MapPreviewColorScheme::DrawOptions preview_drawOptions; - sub_preview->add_option("--layers", preview_drawOptions, "Specify layers to draw\n\t\teither \"all\" or a comma-separated list of any of:\n\t\t\"terrain\",\"structures\",\"oil\"") + sub_preview->add_option("--layers", app->preview_drawOptions, "Specify layers to draw\n\t\teither \"all\" or a comma-separated list of any of:\n\t\t\"terrain\",\"structures\",\"oil\"") ->default_val("all"); - sub_preview->callback([&]() { - if (inputPathIsFile(preview_inputMap)) + sub_preview->add_option("--map-seed", app->mapSeed, "Specify the script-generated map seed"); + sub_preview->callback([weakAppInstance, inputPathIsFile]() { + auto app = weakAppInstance.lock(); + if (!app) + { + std::cerr << "ERROR: Invalid instance" << std::endl; + return; + } + if (inputPathIsFile(app->inputPath)) { #if !defined(WZ_MAPTOOLS_DISABLE_ARCHIVE_SUPPORT) - if (!generateMapPreviewPNG_FromArchive(preview_inputMap, preview_outputPNGFilename, preview_PlayerColorProvider, preview_scavsColor, preview_drawOptions, verbose)) + if (!generateMapPreviewPNG_FromArchive(app->inputPath, app->outputPath, app->preview_PlayerColorProvider, app->preview_scavsColor, app->preview_drawOptions, app->mapSeed, app->verbose)) { - retVal = 1; + app->retVal = 1; } #else - std::cerr << "ERROR: maptools was compiled without support for .wz archives, and cannot open: " << preview_inputMap << std::endl; - retVal = 1; + std::cerr << "ERROR: maptools was compiled without support for .wz archives, and cannot open: " << app->inputPath << std::endl; + app->retVal = 1; #endif } else { - if (!generateMapPreviewPNG_FromPackageContents(preview_inputMap, preview_outputPNGFilename, preview_PlayerColorProvider, preview_scavsColor, preview_drawOptions, verbose)) + if (!generateMapPreviewPNG_FromPackageContents(app->inputPath, app->outputPath, app->preview_PlayerColorProvider, app->preview_scavsColor, app->preview_drawOptions, app->mapSeed, app->verbose)) { - retVal = 1; + app->retVal = 1; } } }); @@ -1018,52 +1093,57 @@ static void addSubCommand_Package(CLI::App& app, int& retVal, bool& verbose) // [EXTRACTING INFORMATION FROM A MAP PACKAGE] CLI::App* sub_info = sub_package->add_subcommand("info", "Extract info / stats from a map package"); sub_info->fallthrough(); - static std::string info_inputMap; - sub_info->add_option("-i,--input,input", info_inputMap, inputOptionDescription) + sub_info->add_option("-i,--input,input", app->inputPath, inputOptionDescription) ->required() ->check(CLI::ExistingPath); - static std::string info_outputFilename; - sub_info->add_option("-o,--output", info_outputFilename, "Output filename (+ path)") + sub_info->add_option("-o,--output", app->outputPath, "Output filename (+ path)") ->check(FileExtensionValidator(".json")); - sub_info->callback([&]() { + sub_info->add_option("--map-seed", app->mapSeed, "Specify the script-generated map seed"); + sub_info->callback([weakAppInstance, inputPathIsFile]() { + auto app = weakAppInstance.lock(); + if (!app) + { + std::cerr << "ERROR: Invalid instance" << std::endl; + return; + } optional mapInfoJSON; std::shared_ptr logger; - if (!info_outputFilename.empty()) + if (!app->outputPath.empty()) { - logger = std::make_shared(new MapToolDebugLogger(verbose)); + logger = std::make_shared(new MapToolDebugLogger(app->verbose)); } - if (inputPathIsFile(info_inputMap)) + if (inputPathIsFile(app->inputPath)) { #if !defined(WZ_MAPTOOLS_DISABLE_ARCHIVE_SUPPORT) - mapInfoJSON = generateMapInfoJSON_FromArchive(info_inputMap, logger); + mapInfoJSON = generateMapInfoJSON_FromArchive(app->inputPath, app->mapSeed, logger); #else - std::cerr << "ERROR: maptools was compiled without support for .wz archives, and cannot open: " << info_inputMap << std::endl; - retVal = 1; + std::cerr << "ERROR: maptools was compiled without support for .wz archives, and cannot open: " << app->inputPath << std::endl; + app->retVal = 1; #endif } else { - mapInfoJSON = generateMapInfoJSON_FromPackageContents(info_inputMap, logger); + mapInfoJSON = generateMapInfoJSON_FromPackageContents(app->inputPath, app->mapSeed, logger); } if (!mapInfoJSON.has_value()) { - retVal = 1; + app->retVal = 1; return; } std::string jsonStr = mapInfoJSON.value().dump(4, ' ', false, nlohmann::ordered_json::error_handler_t::ignore); - if (!info_outputFilename.empty()) + if (!app->outputPath.empty()) { WzMap::StdIOProvider stdOutput; - if (!stdOutput.writeFullFile(info_outputFilename, jsonStr.c_str(), static_cast(jsonStr.size()))) + if (!stdOutput.writeFullFile(app->outputPath, jsonStr.c_str(), static_cast(jsonStr.size()))) { - std::cerr << "Failed to output JSON to: " << info_outputFilename << std::endl; - retVal = 1; + std::cerr << "Failed to output JSON to: " << app->outputPath << std::endl; + app->retVal = 1; return; } - std::cout << "Wrote output JSON to: " << info_outputFilename << std::endl; + std::cout << "Wrote output JSON to: " << app->outputPath << std::endl; } else { @@ -1072,102 +1152,85 @@ static void addSubCommand_Package(CLI::App& app, int& retVal, bool& verbose) }); } -static void addSubCommand_Map(CLI::App& app, int& retVal, bool& verbose) +void WzMapToolsAppInstance::addSubCommand_Map(const std::shared_ptr& app) { - CLI::App* sub_map = app.add_subcommand("map", "Manipulating a map folder"); + std::weak_ptr weakAppInstance = std::weak_ptr(app); + + CLI::App* sub_map = app->add_subcommand("map", "Manipulating a map folder"); sub_map->fallthrough(); // [CONVERTING MAP FORMAT] CLI::App* sub_convert = sub_map->add_subcommand("convert", "Convert a map from one format to another"); sub_convert->fallthrough(); - static WzMap::MapType mapType = WzMap::MapType::SKIRMISH; - sub_convert->add_option("-t,--maptype", mapType, "Map type") + sub_convert->add_option("-t,--maptype", app->mapType, "Map type") ->transform(CLI::CheckedTransformer(maptype_map, CLI::ignore_case)) ->default_val(WzMap::MapType::SKIRMISH); - static uint32_t mapMaxPlayers = 0; - sub_convert->add_option("-p,--maxplayers", mapMaxPlayers, "Map max players") + sub_convert->add_option("-p,--maxplayers", app->mapMaxPlayers, "Map max players") ->required() ->check(CLI::Range(1, 10)); - static WzMap::OutputFormat outputFormat = WzMap::LatestOutputFormat; - sub_convert->add_option("-f,--format", outputFormat, "Output map format") + sub_convert->add_option("-f,--format", app->outputMapFormat, "Output map format") ->required() ->transform(CLI::CheckedTransformer(outputformat_map, CLI::ignore_case).description("value in {\n\t\tbjo -> Binary .BJO (flaME-compatible / old),\n\t\tjson -> JSONv1 (WZ 3.4+),\n\t\tjsonv2 -> JSONv2 (WZ 4.1+),\n\t\tlatest -> " + CLI::detail::to_string(WzMap::LatestOutputFormat) + "}")); - static std::string inputMapDirectory; - sub_convert->add_option("-i,--input,inputmapdir", inputMapDirectory, "Input map directory") + sub_convert->add_option("-i,--input,inputmapdir", app->inputPath, "Input map directory") ->required() ->check(CLI::ExistingDirectory); - static std::string outputMapDirectory; - sub_convert->add_option("-o,--output,outputmapdir", outputMapDirectory, "Output map directory") + sub_convert->add_option("-o,--output,outputmapdir", app->outputPath, "Output map directory") ->required() ->check(CLI::ExistingDirectory); - sub_convert->callback([&]() { - if (!convertMap(mapType, mapMaxPlayers, inputMapDirectory, outputMapDirectory, outputFormat, verbose)) + sub_convert->add_option("--map-seed", app->mapSeed, "Specify the script-generated map seed"); + sub_convert->callback([weakAppInstance]() { + auto app = weakAppInstance.lock(); + if (!app) { - retVal = 1; + std::cerr << "ERROR: Invalid instance" << std::endl; + return; + } + if (!convertMap(app->mapType, app->mapMaxPlayers, app->inputPath, app->outputPath, app->outputMapFormat, app->mapSeed, app->verbose)) + { + app->retVal = 1; } }); // [GENERATING MAP PREVIEW PNG] CLI::App* sub_preview = sub_map->add_subcommand("genpreview", "Generate a map preview PNG"); sub_preview->fallthrough(); - static WzMap::MapType preview_mapType = WzMap::MapType::SKIRMISH; - sub_preview->add_option("-t,--maptype", preview_mapType, "Map type") + sub_preview->add_option("-t,--maptype", app->mapType, "Map type") ->transform(CLI::CheckedTransformer(maptype_map, CLI::ignore_case)) ->default_val(WzMap::MapType::SKIRMISH); - static uint32_t preview_mapMaxPlayers = 0; - sub_preview->add_option("-p,--maxplayers", preview_mapMaxPlayers, "Map max players") + sub_preview->add_option("-p,--maxplayers", app->mapMaxPlayers, "Map max players") ->required() ->check(CLI::Range(1, 10)); - static std::string preview_inputMapDirectory; - sub_preview->add_option("-i,--input,inputmapdir", preview_inputMapDirectory, "Input map directory") + sub_preview->add_option("-i,--input,inputmapdir", app->inputPath, "Input map directory") ->required() ->check(CLI::ExistingDirectory); - static std::string preview_outputPNGFilename; - sub_preview->add_option("-o,--output,output", preview_outputPNGFilename, "Output PNG filename (+ path)") + sub_preview->add_option("-o,--output,output", app->outputPath, "Output PNG filename (+ path)") ->required() ->check(FileExtensionValidator(".png")); - static MapToolsPreviewColorProvider preview_PlayerColorProvider = MapToolsPreviewColorProvider::Simple; - sub_preview->add_option("-c,--playercolors", preview_PlayerColorProvider, "Player colors") + sub_preview->add_option("-c,--playercolors", app->preview_PlayerColorProvider, "Player colors") ->transform(CLI::CheckedTransformer(previewcolors_map, CLI::ignore_case).description("value in {\n\t\tsimple -> use one color for scavs, one color for players,\n\t\twz -> use WZ colors for players (distinct)\n\t}")) ->default_val("simple"); - static WzMap::MapPreviewColor preview_scavsColor = ScavsColorDefault; - sub_preview->add_option("--scavcolor", preview_scavsColor, "Specify the scavengers hex color") + sub_preview->add_option("--scavcolor", app->preview_scavsColor, "Specify the scavengers hex color") ->check(AsHexColorValue()); - static WzMap::MapPreviewColorScheme::DrawOptions preview_drawOptions; - sub_preview->add_option("--layers", preview_drawOptions, "Specify layers to draw\n\t\teither \"all\" or a comma-separated list of any of:\n\t\t\"terrain\",\"structures\",\"oil\"") + sub_preview->add_option("--layers", app->preview_drawOptions, "Specify layers to draw\n\t\teither \"all\" or a comma-separated list of any of:\n\t\t\"terrain\",\"structures\",\"oil\"") ->default_val("all"); - sub_preview->callback([&]() { - if (!generateMapPreviewPNG_FromMapDirectory(preview_mapType, preview_mapMaxPlayers, preview_inputMapDirectory, preview_outputPNGFilename, preview_PlayerColorProvider, preview_scavsColor, preview_drawOptions, verbose)) + sub_preview->add_option("--map-seed", app->mapSeed, "Specify the script-generated map seed"); + sub_preview->callback([weakAppInstance]() { + auto app = weakAppInstance.lock(); + if (!app) { - retVal = 1; + std::cerr << "ERROR: Invalid instance" << std::endl; + return; + } + if (!generateMapPreviewPNG_FromMapDirectory(app->mapType, app->mapMaxPlayers, app->inputPath, app->outputPath, app->preview_PlayerColorProvider, app->preview_scavsColor, app->preview_drawOptions, app->mapSeed, app->verbose)) + { + app->retVal = 1; } }); } int main(int argc, char **argv) { - int retVal = 0; - CLI::App app{"WZ2100 Map Tools"}; - -#if defined(MAPTOOLS_CLI_VERSION_MAJOR) && defined(MAPTOOLS_CLI_VERSION_MINOR) && defined(MAPTOOLS_CLI_VERSION_REV) - app.set_version_flag("--version", []() -> std::string { - return generateMapToolsVersionInfo(); - }); -#else - #error Missing maptools version defines -#endif - - std::stringstream footerInfo; - footerInfo << "License: GPL-2.0-or-later" << std::endl; - footerInfo << "Source: https://github.com/Warzone2100/maptools-cli" << std::endl; - app.footer(footerInfo.str()); - - bool verbose = false; - app.add_flag("-v,--verbose", verbose, "Verbose output"); - - addSubCommand_Package(app, retVal, verbose); - addSubCommand_Map(app, retVal, verbose); - - CLI11_PARSE(app, argc, argv); - return retVal; + std::shared_ptr app = WzMapToolsAppInstance::makeWzMapToolsAppInstance(); + CLI11_PARSE((*app), argc, argv); + return app->getRetVal(); }