Skip to content

Commit

Permalink
Initial implementation of PresetsManager
Browse files Browse the repository at this point in the history
  • Loading branch information
garrettjoecox committed Feb 6, 2025
1 parent ac1ad42 commit d932617
Show file tree
Hide file tree
Showing 6 changed files with 525 additions and 3 deletions.
5 changes: 5 additions & 0 deletions mm/2s2h/BenGui/BenMenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "DeveloperTools/CollisionViewer.h"
#include "DeveloperTools/EventLog.h"
#include "2s2h/Enhancements/GfxPatcher/AuthenticGfxPatches.h"
#include "2s2h/PresetManager/PresetManager.h"
#include "HudEditor.h"
#include "Notification.h"
#include <variant>
Expand Down Expand Up @@ -382,6 +383,10 @@ void BenMenu::AddSettings() {
});
})
.Options(ButtonOptions().Tooltip("Displays a test notification."));

path.sidebarName = "Presets";
AddSidebarEntry("Settings", "Presets", 1);
AddWidget(path, "Presets", WIDGET_CUSTOM).CustomFunction([](WidgetInfo& info) { PresetManager_Draw(); });
}
int32_t motionBlurStrength;

Expand Down
7 changes: 4 additions & 3 deletions mm/2s2h/BenPort.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ CrowdControl* CrowdControl::Instance;
#include "2s2h/BenGui/Notification.h"
#include "2s2h/ShipUtils.h"
#include "2s2h/ShipInit.hpp"
#include "2s2h/PresetManager/PresetManager.h"

// Resource Types/Factories
#include "resource/type/Blob.h"
Expand Down Expand Up @@ -451,9 +452,9 @@ void Ben_ProcessDroppedFiles(std::string filePath) {
// handled = Randomizer_HandleFileDropped(filePath);
// }

// if (!handled) {
// handled = Presets_HandleFileDropped(filePath);
// }
if (!handled) {
handled = PresetManager_HandleFileDropped(filePath);
}

if (!handled) {
auto gui = Ship::Context::GetInstance()->GetWindow()->GetGui();
Expand Down
295 changes: 295 additions & 0 deletions mm/2s2h/PresetManager/PresetManager.cpp
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, {});
10 changes: 10 additions & 0 deletions mm/2s2h/PresetManager/PresetManager.h
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
Loading

0 comments on commit d932617

Please sign in to comment.