-
Notifications
You must be signed in to change notification settings - Fork 93
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial implementation of PresetsManager
- Loading branch information
1 parent
ac1ad42
commit d932617
Showing
6 changed files
with
525 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,295 @@ | ||
#include "PresetManager.h" | ||
#include <libultraship/libultraship.h> | ||
#include <filesystem> | ||
#include <fstream> | ||
#include <set> | ||
#include "2s2h/BenPort.h" | ||
#include "2s2h/BenGui/UIWidgets.hpp" | ||
#include "2s2h/BenGui/Notification.h" | ||
|
||
std::unordered_map<std::string, std::string> tagMap = { | ||
{ "gEventLog", "Developer Tools" }, | ||
{ "gDeveloperTools", "Developer Tools" }, | ||
{ "gCollisionViewer", "Developer Tools" }, | ||
{ "gCheats", "Enhancements" }, | ||
{ "gEnhancements", "Enhancements" }, | ||
{ "gFixes", "Enhancements" }, | ||
{ "gModes", "Enhancements" }, | ||
{ "gHudEditor", "HUD" }, | ||
{ "ItemTracker", "HUD" }, | ||
{ "gRando", "Rando" }, | ||
}; | ||
|
||
std::unordered_map<std::string, std::set<std::string>> presets; | ||
const std::filesystem::path presetsFolderPath(Ship::Context::GetPathRelativeToAppDirectory("presets", appShortName)); | ||
|
||
void PresetManager_RefreshPresets() { | ||
presets.clear(); | ||
|
||
// ensure the presets folder exists | ||
if (!std::filesystem::exists(presetsFolderPath)) { | ||
std::filesystem::create_directory(presetsFolderPath); | ||
} | ||
|
||
// Add all files in the presets folder to the list of presets | ||
for (const auto& entry : std::filesystem::directory_iterator(presetsFolderPath)) { | ||
if (entry.is_regular_file()) { | ||
std::string fileName = entry.path().filename().string(); | ||
|
||
try { | ||
// Read the file | ||
nlohmann::json j; | ||
std::ifstream file(entry.path()); | ||
file >> j; | ||
|
||
// Ensure the file is a valid preset | ||
if (!j.contains("type") || j["type"] != "2S2H_PRESET") { | ||
continue; | ||
} | ||
|
||
// TODO: Migrate preset if it's an old version | ||
|
||
presets[fileName] = {}; | ||
|
||
if (j.contains("ClearCVars")) { | ||
std::vector<std::string> clearCVars = j["ClearCVars"].get<std::vector<std::string>>(); | ||
for (const auto& cvar : clearCVars) { | ||
if (tagMap.contains(cvar)) { | ||
presets[fileName].insert(tagMap[cvar]); | ||
} | ||
} | ||
} | ||
|
||
if (j.contains("CVars")) { | ||
for (const auto& [key, value] : j["CVars"].items()) { | ||
if (tagMap.contains(key)) { | ||
presets[fileName].insert(tagMap[key]); | ||
} | ||
} | ||
} | ||
// Add the file to the list of presets | ||
} catch (...) {} | ||
} | ||
} | ||
} | ||
|
||
void PresetManager_ApplyPreset(nlohmann::json j) { | ||
if (!j.contains("type") || j["type"] != "2S2H_PRESET") { | ||
throw std::runtime_error("Invalid preset"); | ||
} | ||
|
||
if (j.contains("ClearCVars")) { | ||
auto clearCVars = j["ClearCVars"].get<std::vector<std::string>>(); | ||
|
||
for (const auto& cvar : clearCVars) { | ||
// Replace slashes with dots in key, and remove leading dot | ||
std::string path = cvar; | ||
std::replace(path.begin(), path.end(), '/', '.'); | ||
if (path[0] == '.') { | ||
path.erase(0, 1); | ||
} | ||
CVarClearBlock(path.c_str()); | ||
CVarClear(path.c_str()); | ||
} | ||
} | ||
|
||
if (j.contains("CVars")) { | ||
auto cvars = j["CVars"].flatten(); | ||
|
||
for (auto& [key, value] : cvars.items()) { | ||
// Replace slashes with dots in key, and remove leading dot | ||
std::string path = key; | ||
std::replace(path.begin(), path.end(), '/', '.'); | ||
if (path[0] == '.') { | ||
path.erase(0, 1); | ||
} | ||
if (value.is_string()) { | ||
CVarSetString(path.c_str(), value.get<std::string>().c_str()); | ||
} else if (value.is_number_integer()) { | ||
CVarSetInteger(path.c_str(), value.get<int>()); | ||
} else if (value.is_number_float()) { | ||
CVarSetFloat(path.c_str(), value.get<float>()); | ||
} | ||
} | ||
} | ||
|
||
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame(); | ||
ShipInit::Init("*"); | ||
Notification::Emit({ .message = "Preset Loaded" }); | ||
} | ||
|
||
// Copies 2ship2harkinian.json to the presets folder, then removes everything except the CVars block | ||
void PresetManager_CreatePreset(std::string presetName) { | ||
try { | ||
std::ifstream existingFileStream(Ship::Context::GetPathRelativeToAppDirectory("2ship2harkinian.json")); | ||
|
||
nlohmann::json existingJson; | ||
existingFileStream >> existingJson; | ||
|
||
nlohmann::json newJson; | ||
newJson["type"] = "2S2H_PRESET"; | ||
newJson["version"] = 1; | ||
newJson["CVars"] = existingJson["CVars"]; | ||
|
||
std::string presetFileName = presetName + ".json"; | ||
const std::filesystem::path newPresetFilePath = presetsFolderPath / presetFileName; | ||
std::ofstream newFileStream(newPresetFilePath); | ||
newFileStream << newJson.dump(4); | ||
|
||
newFileStream.close(); | ||
|
||
PresetManager_RefreshPresets(); | ||
} catch (...) { Notification::Emit({ .suffix = "Failed to create preset" }); } | ||
} | ||
|
||
bool PresetManager_HandleFileDropped(std::string filePath) { | ||
try { | ||
std::ifstream fileStream(filePath); | ||
|
||
if (!fileStream.is_open()) { | ||
return false; | ||
} | ||
|
||
// Check if first byte is "{" | ||
if (fileStream.peek() != '{') { | ||
return false; | ||
} | ||
|
||
nlohmann::json j; | ||
|
||
// Attempt to parse the file | ||
try { | ||
fileStream >> j; | ||
} catch (nlohmann::json::exception& e) { return false; } | ||
|
||
// Check if the file is a spoiler file | ||
if (!j.contains("type") || j["type"] != "2S2H_PRESET") { | ||
return false; | ||
} | ||
|
||
// Save the spoiler file to the presets folder | ||
std::string presetFileName = std::filesystem::path(filePath).filename().string(); | ||
const std::filesystem::path newPresetFilePath = presetsFolderPath / presetFileName; | ||
std::filesystem::copy_file(filePath, newPresetFilePath, std::filesystem::copy_options::overwrite_existing); | ||
|
||
PresetManager_RefreshPresets(); | ||
PresetManager_ApplyPreset(j); | ||
|
||
return true; | ||
} catch (std::exception& e) { return false; } catch (...) { | ||
return false; | ||
} | ||
} | ||
|
||
void PresetManager_Draw() { | ||
ImGui::BeginChild("PresetManager", ImVec2(500, 0)); | ||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 1, 1, 0.5f)); | ||
ImGui::TextWrapped("Drag and drop a preset file into the window to load it, or drop it into the presets folder and " | ||
"refresh the list."); | ||
ImGui::PopStyleColor(); | ||
if (UIWidgets::Button("Open Presets Folder", { .size = ImVec2(ImGui::GetContentRegionAvail().x - 42, 0) })) { | ||
std::string path = "file:///" + std::filesystem::absolute(presetsFolderPath).string(); | ||
SDL_OpenURL(path.c_str()); | ||
} | ||
ImGui::SameLine(); | ||
if (UIWidgets::Button(ICON_FA_REFRESH)) { | ||
PresetManager_RefreshPresets(); | ||
} | ||
ImGui::PushStyleVar(ImGuiStyleVar_SeparatorTextPadding, ImVec2(20, 0)); | ||
ImGui::SeparatorText("Available Presets"); | ||
|
||
std::string clickedPreset; | ||
|
||
for (const auto& [preset, categories] : presets) { | ||
ImGui::PushID(preset.c_str()); | ||
std::string presetName = preset; | ||
presetName.erase(presetName.find_last_of('.')); | ||
|
||
ImGui::BeginGroup(); | ||
ImGui::TextWrapped("%s", presetName.c_str()); | ||
int index = 0; | ||
ImGui::PushStyleColor(ImGuiCol_Button, UIWidgets::ColorValues.at(UIWidgets::Colors::DarkGray)); | ||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, UIWidgets::ColorValues.at(UIWidgets::Colors::DarkGray)); | ||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, UIWidgets::ColorValues.at(UIWidgets::Colors::DarkGray)); | ||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f); | ||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6, 4)); | ||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(5, 6)); | ||
for (const auto& category : categories) { | ||
if (index++ > 0) { | ||
ImGui::SameLine(); | ||
} | ||
ImGui::Button(category.c_str()); | ||
} | ||
ImGui::PopStyleVar(3); | ||
ImGui::PopStyleColor(3); | ||
ImGui::EndGroup(); | ||
auto lastGroupSize = ImGui::GetItemRectSize(); | ||
auto lastGroupPos = ImGui::GetCursorPosY(); | ||
// Vertically align to center, horizontally align to the right | ||
ImGui::BeginGroup(); | ||
ImGui::SetCursorPos({ ImGui::GetContentRegionAvail().x - 80, lastGroupPos - (lastGroupSize.y / 2) - 22 }); | ||
if (UIWidgets::Button("Apply", { .color = UIWidgets::Colors::Orange })) { | ||
clickedPreset = preset; | ||
} | ||
ImGui::EndGroup(); | ||
ImGui::SeparatorText(""); | ||
ImGui::PopID(); | ||
} | ||
|
||
if (clickedPreset != "") { | ||
try { | ||
nlohmann::json j; | ||
std::ifstream file(presetsFolderPath / clickedPreset); | ||
file >> j; | ||
|
||
PresetManager_ApplyPreset(j); | ||
} catch (...) { Notification::Emit({ .suffix = "Failed to load preset" }); } | ||
} | ||
|
||
ImGui::PopStyleVar(1); | ||
|
||
static bool showAddPresetForm = false; | ||
static bool focusPresetName = false; | ||
|
||
if (showAddPresetForm) { | ||
ImGui::Text("New Preset"); | ||
|
||
static char presetName[256] = ""; | ||
|
||
if (focusPresetName) { | ||
ImGui::SetKeyboardFocusHere(); | ||
focusPresetName = false; | ||
} | ||
|
||
UIWidgets::PushStyleSlider(UIWidgets::Colors::Gray); | ||
ImGui::InputText("##Name", presetName, sizeof(presetName)); | ||
UIWidgets::PopStyleSlider(); | ||
|
||
ImGui::SameLine(); | ||
|
||
if (UIWidgets::Button(ICON_FA_FLOPPY_O, { .size = ImVec2(0, 0), .color = UIWidgets::Colors::Green })) { | ||
PresetManager_CreatePreset(presetName); | ||
presetName[0] = '\0'; | ||
showAddPresetForm = false; | ||
} | ||
|
||
ImGui::SameLine(); | ||
if (UIWidgets::Button(ICON_FA_TIMES, { .size = ImVec2(0, 0), .color = UIWidgets::Colors::Red })) { | ||
presetName[0] = '\0'; | ||
showAddPresetForm = false; | ||
} | ||
} else { | ||
if (UIWidgets::Button("Create Preset from Current Config", { .color = UIWidgets::Colors::Green })) { | ||
showAddPresetForm = true; | ||
focusPresetName = true; | ||
} | ||
} | ||
|
||
ImGui::EndChild(); | ||
} | ||
|
||
void PresetManager_RegisterHooks() { | ||
PresetManager_RefreshPresets(); | ||
} | ||
|
||
static RegisterShipInitFunc initFunc(PresetManager_RegisterHooks, {}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
|
||
#ifndef PRESET_MANAGER_H | ||
#define PRESET_MANAGER_H | ||
|
||
#include <libultraship/libultraship.h> | ||
|
||
bool PresetManager_HandleFileDropped(std::string filePath); | ||
void PresetManager_Draw(); | ||
|
||
#endif // PRESET_MANAGER_H |
Oops, something went wrong.