mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-17 23:17:06 +02:00
Settings has five more pages, under "SKATE. SETTINGS" in the rail, for the settings the game saves per PC: - GRAPHICS: window mode, resolution, HDR, brightness, UI brightness, HUD display, then the game's graphics options (frame rate limit, VSync, dynamic resolution, upscaler and its quality, antialiasing, textures, lighting, shadows, mesh, effects, post processing, global illumination, ambient occlusion, depth of field, motion blur) with buttons that set every low-to-ultra option at once. - AUDIO: volumes, voice chat, text and voice language, subtitles, party menu narration. - CAMERA: sensitivity, and the on board, off board and climbing options. - CONTROLS: vibration, trigger force feedback, show controller on HUD, highlight indicator, the experimental toggles. - REPLAY: editor speeds and sensitivity, effects, first-person keyframes. They are written straight into the game's save containers under %LOCALAPPDATA%\ReSkate\Game\Skate\data (game_settings.h): a DATA file of sections and an INDEX naming them, the body one binary object of named values. Only the values changed are rewritten, in both of the game's copies; every other byte is kept. A container that cannot be walked whole is left alone. The first change copies the untouched files to %LOCALAPPDATA%\ReSkate\game-settings-backup. Keys, kinds, ranges, steps and choices are the game's own (DingoUISettingAsset in its data, build 20260929). The labels are ours. Resolution is saved by the game as a place in the PC's list of display sizes; the launcher lists Windows' sizes smallest first, which matches the one PC it was checked on. A change is stored once the control is let go. The pages are disabled while a game this launcher started is running, and say so when the game has not saved its settings on the PC yet. Not offered: refresh rate, fullscreen monitor and audio devices (places in lists the game builds at run time), the mobile-only settings, and settings the game saves to the account rather than the PC.
479 lines
22 KiB
C++
479 lines
22 KiB
C++
#include "gui_internal.h"
|
|
|
|
#include <cmath>
|
|
#include <format>
|
|
|
|
// The Settings panel.
|
|
namespace dingosdk::launcher_gui::detail {
|
|
namespace {
|
|
|
|
// A muted explanation line.
|
|
void setting_note(const char* text) {
|
|
ImGui::PushTextWrapPos(0);
|
|
ImGui::TextDisabled("%s", text);
|
|
ImGui::PopTextWrapPos();
|
|
ImGui::Spacing();
|
|
}
|
|
|
|
// A checkbox with its label and a muted explanation stacked beside the box,
|
|
// so the explanation sits right under the label instead of under the box.
|
|
void setting_check(const char* label, const char* note, bool& value) {
|
|
ImGui::PushID(label);
|
|
const auto start = ImGui::GetCursorPos();
|
|
ImGui::Checkbox("##check", &value);
|
|
const float below_box = ImGui::GetCursorPosY();
|
|
// Not SameLine: that would drop the text to the box's frame padding. The
|
|
// label's top lines up with the box's top.
|
|
ImGui::SetCursorPos(ImVec2(start.x + ImGui::GetFrameHeight() + ImGui::GetStyle().ItemSpacing.x, start.y));
|
|
ImGui::BeginGroup();
|
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(ImGui::GetStyle().ItemSpacing.x, S(1)));
|
|
ImGui::TextUnformatted(label);
|
|
if (ImGui::IsItemClicked()) value = !value;
|
|
if (ImGui::IsItemHovered()) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
|
ImGui::PushTextWrapPos(0);
|
|
ImGui::TextDisabled("%s", note);
|
|
ImGui::PopTextWrapPos();
|
|
ImGui::PopStyleVar();
|
|
ImGui::EndGroup();
|
|
if (ImGui::GetCursorPosY() < below_box) ImGui::SetCursorPosY(below_box);
|
|
ImGui::PopID();
|
|
ImGui::Spacing();
|
|
ImGui::Spacing();
|
|
}
|
|
|
|
void section_caption(const Fonts& fonts, const char* text) {
|
|
ImGui::PushFont(fonts.caption);
|
|
ImGui::TextDisabled("%s", text);
|
|
ImGui::PopFont();
|
|
}
|
|
|
|
// ---------------------------------------------------------------- skate.'s own settings
|
|
|
|
namespace game = launcher_game_settings;
|
|
constexpr float game_control_x = 300, game_control_width = 260;
|
|
|
|
// Reads the game's settings the first time a page of them is drawn after Settings opens, and
|
|
// says why when there are none to show. False: nothing to draw.
|
|
bool game_settings_ready(Launcher& launcher, Ui& ui, bool& running) {
|
|
auto& pages = ui.game;
|
|
if (!pages.loaded) {
|
|
pages.loaded = true;
|
|
pages.error.clear();
|
|
try {
|
|
pages.saved = game::load(game::save_root());
|
|
} catch (const std::exception& failure) {
|
|
pages.saved = {};
|
|
pages.error = failure.what();
|
|
}
|
|
pages.values = pages.saved.values;
|
|
pages.resolutions = game::display_resolutions();
|
|
}
|
|
if (pages.saved.folders.empty()) {
|
|
setting_note(pages.error.empty() ? "skate. has not saved its settings on this PC yet. Start the game once and they "
|
|
"appear here." : pages.error.c_str());
|
|
return false;
|
|
}
|
|
running = launcher.game() != 0 || launcher.busy();
|
|
setting_note("skate.'s own settings, the ones in its Settings menu. The game reads them when it starts.");
|
|
if (running) setting_note("Skate is running: close it to change these here.");
|
|
if (!pages.error.empty()) {
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(color::danger));
|
|
ImGui::PushTextWrapPos(0);
|
|
ImGui::TextUnformatted(pages.error.c_str());
|
|
ImGui::PopTextWrapPos();
|
|
ImGui::PopStyleColor();
|
|
ImGui::Spacing();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Stores what the pages show once it differs from the game's save and no control is held (a
|
|
// slider being dragged writes when it is let go).
|
|
void store_game_settings(Ui& ui) {
|
|
auto& pages = ui.game;
|
|
if (!pages.loaded || pages.saved.folders.empty() || ImGui::IsAnyItemActive()) return;
|
|
game::Values changes;
|
|
for (const auto& [key, value] : pages.values) {
|
|
const auto before = pages.saved.values.find(key);
|
|
if (before != pages.saved.values.end() && before->second != value) changes.emplace(key, value);
|
|
}
|
|
if (changes.empty()) return;
|
|
try {
|
|
// (...\ReSkate\Game\Skate\data -> ...\ReSkate\game-settings-backup)
|
|
game::save(pages.saved, changes, game::save_root().parent_path().parent_path().parent_path() / L"game-settings-backup");
|
|
pages.saved.values = pages.values;
|
|
pages.error.clear();
|
|
} catch (const std::exception& failure) {
|
|
pages.error = std::string("Not saved: ") + failure.what() + ".";
|
|
pages.values = pages.saved.values;
|
|
}
|
|
}
|
|
|
|
// One of the game's settings pages: each setting a label and its control, under its section.
|
|
void game_settings_page(const Fonts& fonts, Ui& ui, std::string_view page, bool running) {
|
|
auto& pages = ui.game;
|
|
const char* section = "";
|
|
ImGui::BeginDisabled(running);
|
|
for (const auto& item : game::items()) {
|
|
if (page != item.page) continue;
|
|
// A setting this game version does not save is not offered.
|
|
const auto found = pages.values.find(item.key);
|
|
if (found == pages.values.end()) continue;
|
|
if (std::string_view(section) != item.section) {
|
|
if (*section) ImGui::Spacing(), ImGui::Spacing();
|
|
section = item.section;
|
|
section_caption(fonts, section);
|
|
}
|
|
ImGui::AlignTextToFramePadding();
|
|
ImGui::TextUnformatted(item.label);
|
|
ImGui::SameLine(S(game_control_x));
|
|
ImGui::PushID(item.key);
|
|
ImGui::SetNextItemWidth(S(game_control_width));
|
|
const auto steps = [&](float value) {
|
|
const float snapped = item.least + std::round((value - item.least) / item.step) * item.step;
|
|
return std::clamp(snapped, item.least, item.most);
|
|
};
|
|
switch (item.kind) {
|
|
case game::Kind::toggle:
|
|
if (auto* on = std::get_if<bool>(&found->second)) toggle("##value", on);
|
|
break;
|
|
case game::Kind::whole:
|
|
if (auto* number = std::get_if<std::int32_t>(&found->second)) {
|
|
int value = *number;
|
|
if (ImGui::SliderInt("##value", &value, static_cast<int>(item.least), static_cast<int>(item.most), "%d",
|
|
ImGuiSliderFlags_AlwaysClamp))
|
|
*number = static_cast<std::int32_t>(steps(static_cast<float>(value)));
|
|
}
|
|
break;
|
|
case game::Kind::real:
|
|
if (auto* number = std::get_if<float>(&found->second)) {
|
|
float value = *number;
|
|
if (ImGui::SliderFloat("##value", &value, item.least, item.most,
|
|
item.step >= 1 ? "%.0f" : item.step >= 0.1f ? "%.1f" : "%.2f", ImGuiSliderFlags_AlwaysClamp))
|
|
*number = steps(value);
|
|
}
|
|
break;
|
|
case game::Kind::choice:
|
|
if (auto* number = std::get_if<std::int32_t>(&found->second)) {
|
|
// A value the launcher has no name for shows as its number.
|
|
std::string preview = std::to_string(*number);
|
|
for (const auto& choice : item.choices)
|
|
if (choice.value == *number) preview = choice.label;
|
|
if (ImGui::BeginCombo("##value", preview.c_str())) {
|
|
for (const auto& choice : item.choices)
|
|
if (ImGui::Selectable(choice.label, choice.value == *number)) *number = choice.value;
|
|
ImGui::EndCombo();
|
|
}
|
|
}
|
|
break;
|
|
case game::Kind::resolution:
|
|
// The game saves a place in this PC's list of display sizes.
|
|
if (auto* number = std::get_if<std::int32_t>(&found->second)) {
|
|
const auto name = [&](int index) {
|
|
if (index < 0 || index >= static_cast<int>(pages.resolutions.size())) return std::format("Mode {}", index);
|
|
const auto [width, height] = pages.resolutions[static_cast<std::size_t>(index)];
|
|
return std::format("{} x {}", width, height);
|
|
};
|
|
if (ImGui::BeginCombo("##value", name(*number).c_str())) {
|
|
for (int index = 0; index < static_cast<int>(pages.resolutions.size()); ++index)
|
|
if (ImGui::Selectable(name(index).c_str(), index == *number)) *number = index;
|
|
ImGui::EndCombo();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
ImGui::EndDisabled();
|
|
}
|
|
|
|
// Settings > GRAPHICS, under the display settings: the game's graphics options, which it
|
|
// saves together as one text.
|
|
void graphics_options(const Fonts& fonts, Ui& ui, bool running) {
|
|
auto& pages = ui.game;
|
|
const auto text = pages.values.find(game::options_key);
|
|
if (text == pages.values.end() || !std::holds_alternative<std::string>(text->second)) return;
|
|
game::Options options;
|
|
try {
|
|
options = game::parse_options(std::get<std::string>(text->second));
|
|
} catch (const std::exception&) {
|
|
return;
|
|
}
|
|
const auto value_of = [&](std::string_view name) -> std::string* {
|
|
for (auto& option : options)
|
|
if (option.name == name) return &option.value;
|
|
return nullptr;
|
|
};
|
|
bool changed = false;
|
|
ImGui::BeginDisabled(running);
|
|
ImGui::Spacing();
|
|
ImGui::Spacing();
|
|
section_caption(fonts, "SET EVERY QUALITY OPTION TO");
|
|
static constexpr std::array<const char*, 4> levels{"Low", "Medium", "High", "Ultra"};
|
|
for (int level = 0; level < static_cast<int>(levels.size()); ++level) {
|
|
if (level) ImGui::SameLine();
|
|
if (!ImGui::Button(levels[static_cast<std::size_t>(level)], ImVec2(S(110), 0))) continue;
|
|
for (const auto& setting : game::graphics_options())
|
|
if (const auto* wanted = game::level_value(setting, level))
|
|
if (auto* value = value_of(setting.name)) *value = wanted, changed = true;
|
|
}
|
|
const char* category = "";
|
|
for (const auto& setting : game::graphics_options()) {
|
|
// An option this game version does not save is not offered.
|
|
auto* value = value_of(setting.name);
|
|
if (!value) continue;
|
|
if (std::string_view(category) != setting.category) {
|
|
category = setting.category;
|
|
ImGui::Spacing();
|
|
ImGui::Spacing();
|
|
section_caption(fonts, category);
|
|
}
|
|
const auto* needed = *setting.needs_name ? value_of(setting.needs_name) : nullptr;
|
|
ImGui::BeginDisabled(*setting.needs_name && (!needed || *needed != setting.needs_value));
|
|
ImGui::AlignTextToFramePadding();
|
|
ImGui::TextUnformatted(setting.label);
|
|
ImGui::SameLine(S(game_control_x));
|
|
ImGui::SetNextItemWidth(S(game_control_width));
|
|
// A value the launcher has no name for (set in game, or by a newer game) shows as it is.
|
|
const char* preview = value->c_str();
|
|
for (const auto& choice : setting.choices)
|
|
if (*value == choice.value) preview = choice.label;
|
|
ImGui::PushID(setting.name);
|
|
if (ImGui::BeginCombo("##value", preview)) {
|
|
for (const auto& choice : setting.choices)
|
|
if (ImGui::Selectable(choice.label, *value == choice.value) && *value != choice.value)
|
|
*value = choice.value, changed = true;
|
|
ImGui::EndCombo();
|
|
}
|
|
ImGui::PopID();
|
|
ImGui::EndDisabled();
|
|
}
|
|
ImGui::EndDisabled();
|
|
if (!changed) return;
|
|
// The game's preset no longer describes them.
|
|
if (auto* preset = value_of(game::preset_name)) *preset = game::preset_custom;
|
|
try {
|
|
text->second = game::join_options(options);
|
|
} catch (const std::exception&) {}
|
|
}
|
|
|
|
// Settings > KEYS: the keys that open ReSkate's menu and console in game.
|
|
// Returns true when a key changed, so the caller can save straight away.
|
|
bool key_bindings(const Fonts& fonts, Settings& settings, Ui& ui) {
|
|
bool changed = false;
|
|
if (ui.binding && !g_capturing_key) {
|
|
const auto key = g_captured_key.exchange(0);
|
|
int& target = ui.binding == 1 ? settings.menu_key : settings.console_key;
|
|
const int other = ui.binding == 1 ? settings.console_key : settings.menu_key;
|
|
if (key == VK_ESCAPE) ui.key_error.clear();
|
|
else if (!launcher::bindable_key(key))
|
|
ui.key_error = launcher::key_name(key) + " can't be used. Pick another key.";
|
|
else if (static_cast<int>(key) == other)
|
|
ui.key_error = launcher::key_name(key) + " already opens the " + (ui.binding == 1 ? "console." : "menu.");
|
|
else { changed = target != static_cast<int>(key); target = static_cast<int>(key); ui.key_error.clear(); }
|
|
ui.binding = 0;
|
|
}
|
|
section_caption(fonts, "IN-GAME KEYS");
|
|
setting_note("Click a key, then press the one you want. Esc cancels.");
|
|
const auto row = [&](int which, const char* label, const char* detail, int key) {
|
|
ImGui::AlignTextToFramePadding();
|
|
ImGui::TextUnformatted(label);
|
|
ImGui::SameLine(S(200));
|
|
const bool waiting = ui.binding == which;
|
|
const auto text = (waiting ? std::string("Press a key...")
|
|
: launcher::key_name(static_cast<unsigned>(key))) + "##key" + std::to_string(which);
|
|
if (waiting) push_primary_button();
|
|
if (ImGui::Button(text.c_str(), ImVec2(-1, 0)) && !waiting) {
|
|
ui.binding = which;
|
|
ui.key_error.clear();
|
|
g_captured_key = 0;
|
|
g_capturing_key = true;
|
|
}
|
|
if (waiting) pop_primary_button();
|
|
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + S(200));
|
|
ImGui::TextDisabled("%s", detail);
|
|
ImGui::Spacing();
|
|
};
|
|
row(1, "ReSkate menu", "Opens and closes the ReSkate menu.", settings.menu_key);
|
|
row(2, "Console", "Opens and closes the command console.", settings.console_key);
|
|
if (!ui.key_error.empty())
|
|
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(color::danger), "%s", ui.key_error.c_str());
|
|
ImGui::Spacing();
|
|
const bool defaults = settings.menu_key == static_cast<int>(launcher::default_menu_key) &&
|
|
settings.console_key == static_cast<int>(launcher::default_console_key);
|
|
ImGui::BeginDisabled(defaults || ui.binding);
|
|
if (ImGui::Button("Reset to defaults")) {
|
|
settings.menu_key = static_cast<int>(launcher::default_menu_key);
|
|
settings.console_key = static_cast<int>(launcher::default_console_key);
|
|
ui.key_error.clear();
|
|
changed = true;
|
|
}
|
|
ImGui::EndDisabled();
|
|
ImGui::Spacing();
|
|
setting_note("The chosen keys stop reaching the game while it runs, so avoid keys you skate with.");
|
|
return changed;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void settings_window(Launcher& launcher, const Fonts& fonts, ImVec2 size, Ui& ui, HWND window) {
|
|
bool& open = ui.settings;
|
|
// A page, like the mod manager: the same rail, the same way out.
|
|
const auto frame = begin_page("##settings_panel", size);
|
|
auto* draw = ImGui::GetWindowDrawList();
|
|
auto& settings = launcher.settings();
|
|
const auto& paths = launcher.session().paths;
|
|
const bool busy = launcher.busy();
|
|
const bool binding = ui.binding != 0;
|
|
|
|
window_buttons(draw, window, frame);
|
|
const float rail_x = S(28), rail_width = S(236);
|
|
const float content_x = rail_x + rail_width + S(26);
|
|
const float top = S(52), bottom = frame.y - S(24);
|
|
const float title_size = S(44);
|
|
page_title(draw, fonts, ImVec2(rail_x + S(2), top - S(4)), "SETTINGS", title_size);
|
|
|
|
// ------------------------------------------------ rail
|
|
const float rail_top = top + title_size + S(20);
|
|
ImGui::SetCursorPos(ImVec2(rail_x, rail_top));
|
|
// Flattened, so a controller's D-pad crosses from the rail into the page and back.
|
|
ImGui::BeginChild("##settings_rail", ImVec2(rail_width, bottom - rail_top), ImGuiChildFlags_NavFlattened);
|
|
// The launcher's own pages, then the game's (game_settings.h).
|
|
static constexpr std::array<const char*, 9> names{"GAME", "DISPLAY", "KEYS", "ADVANCED",
|
|
"GRAPHICS", "AUDIO", "CAMERA", "CONTROLS", "REPLAY"};
|
|
constexpr int game_pages = 4;
|
|
ImGui::BeginDisabled(binding);
|
|
for (int i = 0; i < static_cast<int>(names.size()); ++i) {
|
|
if (i == game_pages) {
|
|
ImGui::Dummy(ImVec2(0, S(4)));
|
|
section_caption(fonts, "SKATE. SETTINGS");
|
|
}
|
|
if (nav_tile(fonts, rail_width, names[static_cast<std::size_t>(i)], ui.settings_tab == i))
|
|
ui.settings_tab = i;
|
|
// A controller starts on the open tab's tile.
|
|
if (ui.settings_tab == i) default_focus();
|
|
}
|
|
ImGui::SetCursorPosY(ImGui::GetWindowHeight() - S(38));
|
|
push_primary_button();
|
|
if (ImGui::Button("\xe2\x86\x90 BACK", ImVec2(-1, S(34)))) open = false;
|
|
pop_primary_button();
|
|
ImGui::EndDisabled();
|
|
ImGui::EndChild();
|
|
|
|
// ------------------------------------------------ the open tab
|
|
// Kept to a readable column: settings are sentences, not a table.
|
|
ImGui::SetCursorPos(ImVec2(content_x, top));
|
|
ImGui::BeginChild("##settings_content",
|
|
ImVec2(std::min(S(760), frame.x - content_x - S(28)), bottom - top), ImGuiChildFlags_NavFlattened);
|
|
const float footer = ImGui::GetTextLineHeight() + S(16);
|
|
ImGui::BeginChild("##settings_page", ImVec2(0, ImGui::GetWindowHeight() - footer), ImGuiChildFlags_NavFlattened);
|
|
switch (ui.settings_tab) {
|
|
case 0: {
|
|
section_caption(fonts, "GAME FOLDER");
|
|
ImGui::PushTextWrapPos(0);
|
|
ImGui::TextUnformatted(utf8(paths.directory.wstring()).c_str());
|
|
ImGui::PopTextWrapPos();
|
|
if (ImGui::Button("Open folder")) open_path(paths.directory);
|
|
ImGui::SameLine();
|
|
ImGui::BeginDisabled(busy);
|
|
if (ImGui::Button("Verify game files")) { open = false; open_sign_in(launcher, ui, true); }
|
|
ImGui::EndDisabled();
|
|
ImGui::Spacing();
|
|
ImGui::Spacing();
|
|
section_caption(fonts, "PLAY");
|
|
setting_check("Offline mode", "Play without Steam as Unknown Player; multiplayer is hidden. "
|
|
"Used automatically whenever Steam isn't running.", settings.offline);
|
|
setting_check("Loose files", "Export the game's scripts/ and config/ beside Skate.exe so you can edit them.",
|
|
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);
|
|
ImGui::Spacing();
|
|
section_caption(fonts, "EXTRA GAME ARGUMENTS");
|
|
std::array<char, 1024> arguments{};
|
|
std::copy_n(settings.arguments.begin(), std::min(settings.arguments.size(), arguments.size() - 1), arguments.begin());
|
|
ImGui::SetNextItemWidth(-1);
|
|
if (ImGui::InputTextWithHint("##arguments", "Arguments for Skate.exe", arguments.data(), arguments.size()))
|
|
settings.arguments = arguments.data();
|
|
setting_note("Passed to Skate.exe as they are.");
|
|
break;
|
|
}
|
|
case 1:
|
|
section_caption(fonts, "WINDOW");
|
|
setting_check("Windowed", "Start in a window of this size instead of the game's saved display settings.",
|
|
settings.windowed);
|
|
ImGui::BeginDisabled(!settings.windowed);
|
|
ImGui::Indent(ImGui::GetFrameHeight() + ImGui::GetStyle().ItemSpacing.x);
|
|
ImGui::SetNextItemWidth(S(110));
|
|
ImGui::InputInt("##width", &settings.width, 0);
|
|
ImGui::SameLine();
|
|
ImGui::TextDisabled("x");
|
|
ImGui::SameLine();
|
|
ImGui::SetNextItemWidth(S(110));
|
|
ImGui::InputInt("##height", &settings.height, 0);
|
|
ImGui::Unindent(ImGui::GetFrameHeight() + ImGui::GetStyle().ItemSpacing.x);
|
|
ImGui::EndDisabled();
|
|
settings.width = std::clamp(settings.width, 320, 16384);
|
|
settings.height = std::clamp(settings.height, 200, 16384);
|
|
break;
|
|
case 2:
|
|
if (key_bindings(fonts, settings, ui)) launcher.save();
|
|
break;
|
|
default: {
|
|
bool running{};
|
|
if (!game_settings_ready(launcher, ui, running)) break;
|
|
const std::string_view page = names[static_cast<std::size_t>(std::clamp(ui.settings_tab, game_pages, static_cast<int>(names.size()) - 1))];
|
|
game_settings_page(fonts, ui, page, running);
|
|
if (page == "GRAPHICS") graphics_options(fonts, ui, running);
|
|
break;
|
|
}
|
|
case 3:
|
|
section_caption(fonts, "LAUNCHER");
|
|
setting_check("Keep the launcher open after launch",
|
|
"Hide while you play and return when Skate closes. Turn off to close after a successful launch.",
|
|
settings.keep_open_after_launch);
|
|
setting_check("Install ReSkate updates",
|
|
"Turn off to keep the ReSkate.dll and launcher you have, such as a test build someone gave you. "
|
|
"Skate's own Steam updates are not affected.",
|
|
settings.updates);
|
|
ImGui::Spacing();
|
|
section_caption(fonts, "TROUBLESHOOTING");
|
|
ImGui::AlignTextToFramePadding();
|
|
ImGui::TextUnformatted("Log level");
|
|
ImGui::SameLine(S(200));
|
|
ImGui::SetNextItemWidth(S(160));
|
|
ImGui::Combo("##log_level", &settings.log_level, log_levels.data(), static_cast<int>(log_levels.size()));
|
|
ImGui::SameLine();
|
|
if (ImGui::Button("Open logs")) open_path(paths.logs);
|
|
setting_note("How much ReSkate.log records. Use debug or trace when reporting a problem.");
|
|
ImGui::Spacing();
|
|
setting_check("GPU crash diagnostics (DRED)", "Records extra detail when the graphics driver crashes. Slightly slower.",
|
|
settings.gpu_diagnostics);
|
|
setting_check("Send crash reports",
|
|
"When the launcher or Skate crashes, upload a crash dump and that session's log so the ReSkate "
|
|
"developers can fix it. The launcher picks a change up when it next starts.",
|
|
settings.crash_reports);
|
|
break;
|
|
}
|
|
ImGui::EndChild();
|
|
store_game_settings(ui);
|
|
|
|
ImGui::SetCursorPosY(ImGui::GetWindowHeight() - footer + S(8));
|
|
ImGui::PushFont(fonts.caption);
|
|
ImGui::TextDisabled("%s", !update::binary_updates_enabled() ? "Development build: ReSkate files are never replaced."
|
|
: settings.updates ? "Release build: updates install automatically."
|
|
: "Updates are off: ReSkate files are never replaced.");
|
|
ImGui::PopFont();
|
|
ImGui::EndChild();
|
|
|
|
if (!binding && !ImGui::IsAnyItemActive() && ImGui::IsKeyPressed(ImGuiKey_Escape, false)) open = false;
|
|
g_drag_allowed = !ImGui::IsAnyItemHovered() && !ImGui::IsPopupOpen(nullptr, ImGuiPopupFlags_AnyPopupId);
|
|
ImGui::End();
|
|
if (!open) {
|
|
launcher.save();
|
|
// Read again the next time: the game may have run in between.
|
|
ui.game.loaded = false;
|
|
}
|
|
}
|
|
|
|
} // namespace dingosdk::launcher_gui::detail
|