From d1bdb0f8a68f3d66db70dfc34ebf07d4b777232b Mon Sep 17 00:00:00 2001 From: AyeZeeBB Date: Wed, 21 Oct 2026 20:12:18 -0500 Subject: [PATCH] Launcher: a start map in Settings, GAME, instead of always San Vansterdam The launcher lists San Vansterdam, the game's other maps and the custom maps of the mods that load (start_maps.h), keeps the choice with its settings and starts the game in it, as its -map does: a "load" at the front of the startup commands. A map no longer listed when the world can load it (a map mod switched off or left out of the merge) is dropped after 20 s, and the game stays in San Vansterdam. A launcher kept open no longer adds a launch's map to the last one's commands. Tests: Launcher/Test/start_maps_tests.cpp. --- Launcher/Test/start_maps_tests.cpp | 74 ++++++++++++++++++++++++++++++ Launcher/gui_internal.h | 6 +++ Launcher/gui_launcher.cpp | 30 +++++++++++- Launcher/gui_settings.cpp | 54 +++++++++++++++++++++- Launcher/launch.cpp | 17 +++---- Launcher/start_maps.cpp | 73 +++++++++++++++++++++++++++++ Launcher/start_maps.h | 31 +++++++++++++ README.md | 2 +- Runtime/client_tick.cpp | 18 +++++++- cmake/Apps.cmake | 8 +++- 10 files changed, 298 insertions(+), 15 deletions(-) create mode 100644 Launcher/Test/start_maps_tests.cpp create mode 100644 Launcher/start_maps.cpp create mode 100644 Launcher/start_maps.h diff --git a/Launcher/Test/start_maps_tests.cpp b/Launcher/Test/start_maps_tests.cpp new file mode 100644 index 00000000..f6b22e63 --- /dev/null +++ b/Launcher/Test/start_maps_tests.cpp @@ -0,0 +1,74 @@ +// The maps Settings offers to start in, and the "load" the launcher asks the game for. +#include "Launcher/start_maps.h" + +#include +#include +#include +#include + +using namespace dingosdk; +namespace maps = launcher_start_maps; +namespace fs = std::filesystem; + +namespace { + +int failures = 0; + +void check(bool condition, const char* what) { + if (condition) return; + std::cerr << "FAIL: " << what << '\n'; + ++failures; +} + +void write(const fs::path& path, const std::string& text) { + fs::create_directories(path.parent_path()); + std::ofstream(path, std::ios::binary) << text; +} + +} // namespace + +int main(int argc, char** argv) { + const fs::path root = argc > 1 ? fs::path(argv[1]) : fs::temp_directory_path() / "reskate-start-maps-tests"; + std::error_code ignored; + fs::remove_all(root, ignored); + + // No Mods folder: the game's own maps, San Vansterdam first. + auto list = maps::start_maps(root); + check(list.size() == 6, "The game's six maps are not all offered"); + check(!list.empty() && list.front().asset == maps::san_vansterdam && list.front().name == "San Vansterdam", + "San Vansterdam is not first"); + check(maps::load_arguments(list.front()).empty(), "San Vansterdam asks for a load"); + const auto* grom = maps::find_start_map(list, "levels/game/dingolevel_isle_of_grom/dingolevel_isle_of_grom"); + check(grom && grom->name == "Isle of Grom" && grom->mod.empty(), "A retail map is not found whatever the case"); + check(grom && maps::load_arguments(*grom) == L"Levels/Game/DingoLevel_Isle_of_Grom/DingoLevel_Isle_of_Grom", + "A retail map does not load by its asset"); + + write(root / "Mods" / "Map Pack" / "reskate-levels.json", R"({"schema":1,"levels":[ + {"asset":"levels/game/dingolevel_reskate_one/dingolevel_reskate_one","displayName":"One","startPoints":["only"]}, + {"asset":"levels/game/dingolevel_reskate_two/dingolevel_reskate_two","displayName":"Two","startPoints":["Front Gate","back"]}, + {"asset":"levels/game/dingolevel_reskate_three/dingolevel_reskate_three","displayName":"Three","startPoints":["a","b"]}]})"); + write(root / "Mods" / "Switched Off" / "reskate-levels.json", R"({"schema":1,"levels":[ + {"asset":"levels/game/dingolevel_reskate_off/dingolevel_reskate_off","displayName":"Off","startPoints":[]}]})"); + write(root / "Mods" / "Broken" / "reskate-levels.json", R"({"schema":1,"levels":[{"asset":"../x"}]})"); + write(root / "Mods" / "mods.json", R"({"schema":1,"mods":[{"name":"Switched Off","enabled":false}]})"); + + list = maps::start_maps(root); + check(list.size() == 9, "The switched-on map mod's maps are not all offered, or others are"); + check(!maps::find_start_map(list, "levels/game/dingolevel_reskate_off/dingolevel_reskate_off"), + "A switched-off mod's map is offered"); + const auto* one = maps::find_start_map(list, "levels/game/dingolevel_reskate_one/dingolevel_reskate_one"); + check(one && one->name == "One" && one->mod == "Map Pack", "A map mod's map is not named with its mod"); + check(one && maps::load_arguments(*one) == L"levels/game/dingolevel_reskate_one/dingolevel_reskate_one", + "A map with one start point is given one"); + const auto* two = maps::find_start_map(list, "levels/game/dingolevel_reskate_two/dingolevel_reskate_two"); + check(two && maps::load_arguments(*two) == L"levels/game/dingolevel_reskate_two/dingolevel_reskate_two 'Front Gate'", + "A map with several start points does not start at its first, quoted"); + const auto* three = maps::find_start_map(list, "levels/game/dingolevel_reskate_three/dingolevel_reskate_three"); + check(three && maps::load_arguments(*three) == L"levels/game/dingolevel_reskate_three/dingolevel_reskate_three a", + "A plain start point is quoted"); + + fs::remove_all(root, ignored); + if (failures) return 1; + std::cout << "start maps: ok\n"; + return 0; +} diff --git a/Launcher/gui_internal.h b/Launcher/gui_internal.h index 4c976168..610cf583 100644 --- a/Launcher/gui_internal.h +++ b/Launcher/gui_internal.h @@ -5,6 +5,7 @@ #include "launch.h" #include "text_encoding.h" #include "mod_profiles.h" +#include "start_maps.h" #include "thunderstore.h" #include "Engine/Vfs/mod_conflicts.h" @@ -194,6 +195,8 @@ struct Settings { bool gpu_diagnostics{}; bool discord_status{true}; bool offline{}; + // The map the game starts in, by its level asset (start_maps.h); empty: San Vansterdam. + std::string start_map; int menu_key{static_cast(launcher::default_menu_key)}; int console_key{static_cast(launcher::default_console_key)}; int log_level{2}; @@ -433,6 +436,9 @@ struct Ui { Changelog notes; Legal legal; int settings_tab{}; // GAME, DISPLAY, KEYS, ADVANCED, then the game's own pages + // The maps Settings offers to start in, read from the Mods folder when GAME is first drawn + // after Settings opens. + std::optional> start_maps; int binding{}; // 1 = menu key, 2 = console key, while waiting for a press std::string key_error; bool mods{}; diff --git a/Launcher/gui_launcher.cpp b/Launcher/gui_launcher.cpp index a0ed4ac2..8fa842ef 100644 --- a/Launcher/gui_launcher.cpp +++ b/Launcher/gui_launcher.cpp @@ -1,6 +1,7 @@ #include "gui_internal.h" #include "depot_output.h" +#include "start_maps.h" #include "Engine/Core/Json/json.h" #include "Engine/Core/Log/logging.h" @@ -50,6 +51,7 @@ Settings load_settings(const fs::path& path) { settings.gpu_diagnostics = json.value("gpu_diagnostics", settings.gpu_diagnostics); settings.discord_status = json.value("discord_status", settings.discord_status); settings.offline = json.value("offline", settings.offline); + settings.start_map = json.value("start_map", std::string()); settings.menu_key = json.value("menu_key", settings.menu_key); settings.console_key = json.value("console_key", settings.console_key); if (!launcher::bindable_key(static_cast(settings.menu_key)) || @@ -89,6 +91,7 @@ void save_settings(const fs::path& path, const Settings& settings) { json["gpu_diagnostics"] = settings.gpu_diagnostics; json["discord_status"] = settings.discord_status; json["offline"] = settings.offline; + json["start_map"] = settings.start_map; json["menu_key"] = settings.menu_key; json["console_key"] = settings.console_key; json["log_level"] = log_levels[static_cast(settings.log_level)]; @@ -134,6 +137,29 @@ launcher::LaunchOptions launch_options(const Settings& settings) { return launcher::parse_launch_options(arguments); } +// The start map chosen in Settings, as the -map the game is given (empty: San Vansterdam). Read at +// each launch, after the merge: a map whose mod is gone, switched off or left out is not asked for, +// because the game would wait for it for ever; the game then starts in San Vansterdam. +std::wstring start_map_option(const Settings& settings, const fs::path& game_directory) { + if (settings.start_map.empty()) return {}; + const auto maps = launcher_start_maps::start_maps(game_directory); + if (const auto* map = launcher_start_maps::find_start_map(maps, settings.start_map)) { + logging::log(logging::Level::info, logging::Channel::launcher, "Start map: {}{}.", map->name, + map->mod.empty() ? "" : " (mod " + map->mod + ")"); + return launcher_start_maps::load_arguments(*map); + } + logging::log(logging::Level::warning, logging::Channel::launcher, + "Start map {} is not installed, or its mod is switched off or could not be merged; starting in San Vansterdam.", + settings.start_map); + return {}; +} + +launcher::LaunchOptions launch_options(const Settings& settings, const fs::path& game_directory) { + auto options = launch_options(settings); + options.map = start_map_option(settings, game_directory); + return options; +} + } // namespace Launcher::Launcher(const launcher_app::Session& session, std::vector arguments) @@ -536,7 +562,7 @@ void Launcher::run_play() { ignore_mod_problems_ = false; set(Phase::launching, "Starting Skate"); save(); - auto options = launch_options(settings_); + auto options = launch_options(settings_, session_.paths.directory); // The launcher's own command line can request offline mode too. if (std::find(arguments_.begin(), arguments_.end(), L"-offline") != arguments_.end() || std::find(arguments_.begin(), arguments_.end(), L"--offline") != arguments_.end()) @@ -563,7 +589,7 @@ bool prereleases_enabled(const launcher_app::Session& session) { return detail::load_settings(detail::settings_path(session)).prereleases; } launcher::LaunchOptions saved_launch_options(const launcher_app::Session& session) { - return detail::launch_options(detail::load_settings(detail::settings_path(session))); + return detail::launch_options(detail::load_settings(detail::settings_path(session)), session.paths.directory); } void apply_crash_report_setting() noexcept { try { diff --git a/Launcher/gui_settings.cpp b/Launcher/gui_settings.cpp index 01bf3ac6..2655fe73 100644 --- a/Launcher/gui_settings.cpp +++ b/Launcher/gui_settings.cpp @@ -1,5 +1,7 @@ #include "gui_internal.h" +#include "Engine/Game/World/world_names.h" + #include #include @@ -314,6 +316,54 @@ bool key_bindings(const Fonts& fonts, Settings& settings, Ui& ui) { return changed; } +// Settings > GAME: the map the game starts in. Returns true when it changed, so the caller can +// save straight away. +bool start_map_choice(Settings& settings, const launcher_app::Session& session, Ui& ui) { + if (!ui.start_maps) { + try { + ui.start_maps = launcher_start_maps::start_maps(session.paths.directory); + } catch (const std::exception&) { + ui.start_maps.emplace(); + } + } + const auto& maps = *ui.start_maps; + const auto* chosen = settings.start_map.empty() ? nullptr : launcher_start_maps::find_start_map(maps, settings.start_map); + std::string preview = settings.start_map.empty() ? world_level_name(launcher_start_maps::san_vansterdam) + : chosen ? chosen->name : world_level_short_name(settings.start_map) + " (not installed)"; + bool changed = false; + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Start map"); + ImGui::SameLine(S(200)); + ImGui::SetNextItemWidth(-1); + if (ImGui::BeginCombo("##start_map", preview.c_str())) { + bool mods_shown = false; + for (std::size_t index = 0; index < maps.size(); ++index) { + const auto& map = maps[index]; + if (!map.mod.empty() && !mods_shown) { + mods_shown = true; + ImGui::SeparatorText("MAP MODS"); + } + const bool san_vansterdam = map.asset == launcher_start_maps::san_vansterdam; + const bool selected = san_vansterdam ? settings.start_map.empty() : &map == chosen; + ImGui::PushID(static_cast(index)); + if (ImGui::Selectable(map.name.c_str(), selected) && !selected) { + settings.start_map = san_vansterdam ? std::string() : map.asset; + changed = true; + } + if (!map.mod.empty() && ImGui::IsItemHovered()) ImGui::SetTooltip("%s", map.mod.c_str()); + if (selected) ImGui::SetItemDefaultFocus(); + ImGui::PopID(); + } + ImGui::EndCombo(); + } + setting_note(!settings.start_map.empty() && !chosen ? + "This map's mod is not installed, is switched off, or could not be merged: Skate starts in San Vansterdam " + "until it is back." : + "skate. always opens in San Vansterdam; another map loads as soon as the game is up. A map mod that is " + "switched off or removed later falls back to San Vansterdam."); + return changed; +} + } // namespace void settings_window(Launcher& launcher, const Fonts& fonts, ImVec2 size, Ui& ui, HWND window) { @@ -387,6 +437,7 @@ void settings_window(Launcher& launcher, const Fonts& fonts, ImVec2 size, Ui& ui settings.loose_files); setting_check("Discord status", "Show on your Discord profile where you are skating: the map, the server or lobby " "and how many are in it. Off, the game does not talk to Discord at all.", settings.discord_status); + if (start_map_choice(settings, launcher.session(), ui)) launcher.save(); ImGui::Spacing(); section_caption(fonts, "EXTRA GAME ARGUMENTS"); std::array arguments{}; @@ -497,8 +548,9 @@ void settings_window(Launcher& launcher, const Fonts& fonts, ImVec2 size, Ui& ui if (!open) { launcher.save(); launcher.check_again_if_update_choices_changed(); - // Read again the next time: the game may have run in between. + // Read again the next time: the game may have run in between, and mods may have changed. ui.game.loaded = false; + ui.start_maps.reset(); } } diff --git a/Launcher/launch.cpp b/Launcher/launch.cpp index bb15ab0f..18730d9d 100644 --- a/Launcher/launch.cpp +++ b/Launcher/launch.cpp @@ -852,14 +852,15 @@ DWORD start_game(const Session& session, const launcher::LaunchOptions& options, } set_environment(L"RESKATE_GPU_DIAGNOSTICS", options.gpu_diagnostics ? L"1" : L"0"); // -map: the game loads it once it is up, as the console's "load" would (client_tick.cpp), - // ahead of any startup commands already asked for. - if (!options.map.empty()) { - std::wstring commands = L"load " + options.map; - std::wstring already(4096, L'\0'); - const auto length = GetEnvironmentVariableW(L"RESKATE_STARTUP_COMMANDS", already.data(), 4096); - if (length && length < 4096) commands += L";" + already.substr(0, length); - set_environment(L"RESKATE_STARTUP_COMMANDS", commands.c_str()); - } + // ahead of any startup commands already asked for. Those are the launcher's own, read once: a + // launcher kept open sets the variable again at each launch, and must not add to the last one. + static const std::wstring asked = [] { + std::wstring value(4096, L'\0'); + const auto length = GetEnvironmentVariableW(L"RESKATE_STARTUP_COMMANDS", value.data(), 4096); + return length && length < 4096 ? value.substr(0, length) : std::wstring{}; + }(); + std::wstring commands = options.map.empty() ? asked : L"load " + options.map + (asked.empty() ? L"" : L";" + asked); + set_environment(L"RESKATE_STARTUP_COMMANDS", commands.empty() ? nullptr : commands.c_str()); set_environment(L"RESKATE_DISCORD", options.discord ? L"1" : L"0"); if (options.menu_key == options.console_key) throw std::runtime_error("The menu and console need different keys. Change one in Settings."); diff --git a/Launcher/start_maps.cpp b/Launcher/start_maps.cpp new file mode 100644 index 00000000..4d86dafb --- /dev/null +++ b/Launcher/start_maps.cpp @@ -0,0 +1,73 @@ +#include "start_maps.h" + +#include "Engine/Game/World/custom_level_manifest.h" +#include "Engine/Game/World/world_names.h" +#include "Engine/Vfs/mod_list.h" + +#include +#include + +namespace dingosdk::launcher_start_maps { +namespace { + +bool same(std::string_view a, std::string_view b) { + const auto fold = [](char c) { return c >= 'A' && c <= 'Z' ? static_cast(c + ('a' - 'A')) : c; }; + return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin(), [&](char x, char y) { return fold(x) == fold(y); }); +} + +// The retail maps, as a dedicated server lists them (server_config.cpp). +constexpr std::array retail{ + san_vansterdam, + "Levels/Game/DingoLevel_Isle_of_Grom/DingoLevel_Isle_of_Grom", + "Levels/Game/DingoLevel_MPR/DingoLevel_MPR", + "Levels/Game/DingoLevel_FTUE_Island/DingoLevel_FTUE_Island", + "Levels/Game/DingoLevel_SDM/DingoLevel_SDM_Int_001/DingoLevel_SDM_Int_001", + "Levels/Game/DingoLevel_SDM/DingoLevel_SDM_Int_002/DingoLevel_SDM_Int_002"}; + +} // namespace + +std::vector start_maps(const std::filesystem::path& game_directory) { + std::vector maps; + for (const auto asset : retail) maps.push_back({std::string(asset), world_level_name(asset), {}, {}}); + // The mods the game's level list reads (mod_catalog.cpp, level_manifest_paths): switched on, + // not left out by the last merge, and made for this game build. + const auto list = mods::scan_mods(game_directory); + if (!list.issue.empty()) return maps; + for (const auto& entry : list.entries) { + const auto& mod = entry.mod; + if (!entry.enabled || !mod.provides_levels || !mod.outdated.empty() || list.excluded.contains(mod.name)) continue; + const auto manifest = read_custom_level_manifest(mod.directory / "reskate-levels.json"); + if (!manifest.issue.empty()) continue; + for (const auto& level : manifest.levels) { + if (find_start_map(maps, level.asset)) continue; + StartMap map{level.asset, level.display_name, mod.name, {}}; + // A map with one start point starts there; one with several must be given one. + if (level.start_points.size() > 1 && level.manifest_start_point.empty()) + map.start_point = level.start_points.front().name; + maps.push_back(std::move(map)); + } + } + return maps; +} + +const StartMap* find_start_map(const std::vector& maps, std::string_view asset) { + for (const auto& map : maps) + if (same(map.asset, asset)) return ↦ + return nullptr; +} + +std::wstring load_arguments(const StartMap& map) { + if (same(map.asset, san_vansterdam)) return {}; + // Asset and start point names are plain ASCII (custom_level_manifest.cpp); a start point with + // a space in it is quoted, as the console reads it. + std::wstring text(map.asset.begin(), map.asset.end()); + if (!map.start_point.empty()) { + const bool spaced = map.start_point.find(' ') != std::string::npos; + text += spaced ? L" '" : L" "; + text.append(map.start_point.begin(), map.start_point.end()); + if (spaced) text += L"'"; + } + return text; +} + +} // namespace dingosdk::launcher_start_maps diff --git a/Launcher/start_maps.h b/Launcher/start_maps.h new file mode 100644 index 00000000..34b0bc45 --- /dev/null +++ b/Launcher/start_maps.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include + +// The map the game starts in (Settings > GAME > Start map). The game always starts in San +// Vansterdam; another map is loaded once it is up, as the console's "load" would (the launcher's +// -map, launch.cpp), so San Vansterdam is chosen by asking for nothing. +namespace dingosdk::launcher_start_maps { + +inline constexpr std::string_view san_vansterdam = "Levels/Game/BAM_LevelRoot/BAM_LevelRoot"; + +struct StartMap { + std::string asset; // the level, as reskate-levels.json or the game names it + std::string name; // as the game's menus show it + std::string mod; // the mod folder that adds it; empty for one of the game's own + std::string start_point; // its first, for a custom map with several; empty: the map's own choice +}; + +// San Vansterdam first, the game's other maps, then the custom maps of the mods in +// `game_directory`\Mods that load (switched on, merged, made for this game build), in load order. +std::vector start_maps(const std::filesystem::path& game_directory); +// The map with this asset (ASCII case ignored), or null. +const StartMap* find_start_map(const std::vector& maps, std::string_view asset); +// What follows "load " for the map: its asset, and its start point when it has several and names +// none to start at. Empty for San Vansterdam, which the game is in already. +std::wstring load_arguments(const StartMap& map); + +} // namespace dingosdk::launcher_start_maps diff --git a/README.md b/README.md index 57e1593c..b86922de 100644 --- a/README.md +++ b/README.md @@ -248,7 +248,7 @@ Useful launcher flags: | `--log-trace` | the same as `--log-level=trace`; an explicit `--log-level` wins | | `-wconsole` | also show the log live in a console window while the game runs | | `--menu-key=0x2D`, `--console-key=0xC0` | menu and console keys (virtual-key codes) | -| `-map ` | load that map as soon as the game is up, e.g. `-map "San Vansterdam"`; the name is a map's name, short name or asset path, or the start of one | +| `-map ` | load that map as soon as the game is up, e.g. `-map "San Vansterdam"`; the name is a map's name, short name or asset path, or the start of one. Over the start map chosen in Settings > GAME | | `--no-loose-files` | ignore loose Lua and config files beside the game | | `--gpu-diagnostics` | record extra detail when the graphics driver crashes (DRED) | diff --git a/Runtime/client_tick.cpp b/Runtime/client_tick.cpp index b7a45ff8..ae697d24 100644 --- a/Runtime/client_tick.cpp +++ b/Runtime/client_tick.cpp @@ -583,7 +583,9 @@ void update_model(std::uintptr_t client, TickState& frame) { // RESKATE_STARTUP_COMMANDS="load skate1map;noclip 1" (development, and the launcher's // -map , which is a "load" at the front of it): console commands // queued in order once the world is up and can accept them, so a build can be exercised - // without anyone driving the overlay. A load waits for its destination to be listed. + // without anyone driving the overlay. A load waits for its destination to be listed, for 20 + // seconds once the world can load (the launcher's start map, start_maps.h, falls back to the + // map the game opened in). { static std::optional> startup_commands; if (!startup_commands) { @@ -624,12 +626,26 @@ void update_model(std::uintptr_t client, TickState& frame) { } } bool ready = !wait && r.model.can_queue_load && !r.requests.loading(); + // A destination still not listed this long after the world could load it is not + // installed (a map mod switched off or left out of the merge): the load is dropped and + // the game stays where it started, instead of the list waiting on it for ever. + static std::optional missing_since; if (ready && lower(text).starts_with("load ")) { const auto wanted = lower(text.substr(5, text.find(' ', 5) == std::string::npos ? std::string::npos : text.find(' ', 5) - 5)); ready = std::ranges::any_of(r.model.levels, [&](const dingosdk::overlay::Level& level) { return level.can_load && (lower(level.asset).find(wanted) != std::string::npos || lower(level.display_name) == wanted); }); + const auto now = std::chrono::steady_clock::now(); + if (ready) missing_since.reset(); + else if (!missing_since) missing_since = now; + else if (now - *missing_since >= std::chrono::seconds(20)) { + dingosdk::logging::log(dingosdk::logging::Level::warning, dingosdk::logging::Channel::level, + "Startup command dropped: {} is not an installed map (is its mod switched off?); staying in this map.", + text.substr(5)); + missing_since.reset(); + startup_commands->erase(startup_commands->begin()); + } } if (ready && r.requests.enqueue(ConsoleRequest{text}, request_context(r), GetCurrentThreadId())) { dingosdk::logging::log(dingosdk::logging::Level::info, dingosdk::logging::Channel::level, diff --git a/cmake/Apps.cmake b/cmake/Apps.cmake index 12a84d84..3e4e99d2 100644 --- a/cmake/Apps.cmake +++ b/cmake/Apps.cmake @@ -4,9 +4,9 @@ if(WIN32) Launcher/gui_settings.cpp Launcher/gui_sign_in.cpp Launcher/gui_mods.cpp Launcher/gui_mods_browse.cpp Launcher/gui_mods_profiles.cpp Launcher/gui_gamepad.cpp Launcher/gamepad_input.cpp Launcher/gui_changelog.cpp Launcher/gui_legal.cpp Launcher/game_settings.cpp Launcher/updater.cpp Launcher/mod_manager.cpp Launcher/mod_profiles.cpp Launcher/thunderstore.cpp - Launcher/problem.h) + Launcher/start_maps.cpp Launcher/problem.h) target_link_libraries(dingosdk_launcher PRIVATE dingosdk_logging dingosdk_content_cache_install dingosdk_world_layer_scan dingosdk_launcher_support dingosdk_initfs - dingosdk_mod_list dingosdk_mods dingosdk_json dingosdk_miniz dingosdk_imgui dingosdk_playstation_input winhttp shell32 dwmapi windowscodecs ole32) + dingosdk_mod_list dingosdk_custom_level_manifest dingosdk_mods dingosdk_json dingosdk_miniz dingosdk_imgui dingosdk_playstation_input winhttp shell32 dwmapi windowscodecs ole32) set_target_properties(dingosdk_launcher PROPERTIES OUTPUT_NAME "ReSkateLauncher") dingosdk_version_info(dingosdk_launcher "ReSkate launcher" "ReSkateLauncher.exe" VFT_APP) endif() @@ -40,6 +40,10 @@ if(DINGOSDK_BUILD_LAUNCHER_TESTS AND WIN32) target_link_libraries(dingosdk_thunderstore_package_tests PRIVATE dingosdk_json) target_include_directories(dingosdk_thunderstore_package_tests PRIVATE "${PROJECT_SOURCE_DIR}") add_test(NAME thunderstore_package COMMAND dingosdk_thunderstore_package_tests) + add_executable(dingosdk_start_maps_tests Launcher/Test/start_maps_tests.cpp Launcher/start_maps.cpp) + target_link_libraries(dingosdk_start_maps_tests PRIVATE dingosdk_mod_list dingosdk_custom_level_manifest) + target_include_directories(dingosdk_start_maps_tests PRIVATE "${PROJECT_SOURCE_DIR}") + add_test(NAME launcher_start_maps COMMAND dingosdk_start_maps_tests "${CMAKE_CURRENT_BINARY_DIR}/start-maps-fixture") add_executable(dingosdk_game_settings_tests Launcher/Test/game_settings_tests.cpp Launcher/game_settings.cpp) target_include_directories(dingosdk_game_settings_tests PRIVATE "${PROJECT_SOURCE_DIR}") add_test(NAME launcher_game_settings COMMAND dingosdk_game_settings_tests)