mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2025-08-20 13:23:45 -07:00
Merge 0caa33ddee
into 7b4df9bdb2
This commit is contained in:
commit
8b07f9ec56
5 changed files with 271 additions and 20 deletions
243
soh/soh/Enhancements/mod_menu.cpp
Normal file
243
soh/soh/Enhancements/mod_menu.cpp
Normal file
|
@ -0,0 +1,243 @@
|
||||||
|
#include "mod_menu.h"
|
||||||
|
#include "utils/StringHelper.h"
|
||||||
|
#include <libultraship/classes.h>
|
||||||
|
#include "soh/SohGui/SohGui.hpp"
|
||||||
|
#include "soh/OTRGlobals.h"
|
||||||
|
#include "soh/resource/type/Skeleton.h"
|
||||||
|
#include <map>
|
||||||
|
#include <ranges>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
std::vector<std::string> enabledModFiles;
|
||||||
|
std::vector<std::string> disabledModFiles;
|
||||||
|
|
||||||
|
#define CVAR_ENABLED_MODS_NAME CVAR_GENERAL("EnabledMods")
|
||||||
|
#define CVAR_ENABLED_MODS_DEFAULT ""
|
||||||
|
#define CVAR_ENABLED_MODS_VALUE CVarGetString(CVAR_ENABLED_MODS_NAME, CVAR_ENABLED_MODS_DEFAULT)
|
||||||
|
|
||||||
|
// "|" was chosen as the separator due to
|
||||||
|
// it being an invalid character in NTFS
|
||||||
|
// and being rarely used in ext4
|
||||||
|
// it is also an ASCII character
|
||||||
|
// improving portability
|
||||||
|
|
||||||
|
// if being an ASCII character is not a requirement,
|
||||||
|
// other possible candidates include:
|
||||||
|
// - U+FFFF: non-character
|
||||||
|
// - any private use character
|
||||||
|
#define SEPARATOR "|"
|
||||||
|
|
||||||
|
void SetEnabledModsCVarValue() {
|
||||||
|
std::string s = "";
|
||||||
|
|
||||||
|
for (auto& modPath : enabledModFiles) {
|
||||||
|
s += modPath + SEPARATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove trailing separator if present
|
||||||
|
if (s.length() != 0) {
|
||||||
|
s.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
CVarSetString(CVAR_ENABLED_MODS_NAME, s.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> GetEnabledModsFromCVar() {
|
||||||
|
std::string enabledModsCVarValue = CVAR_ENABLED_MODS_VALUE;
|
||||||
|
return StringHelper::Split(enabledModsCVarValue, SEPARATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string>& GetModFiles(bool enabled) {
|
||||||
|
return enabled ? enabledModFiles : disabledModFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Ship::ArchiveManager> GetArchiveManager() {
|
||||||
|
return Ship::Context::GetInstance()->GetResourceManager()->GetArchiveManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateModFiles(bool init = false) {
|
||||||
|
if (init) {
|
||||||
|
enabledModFiles.clear();
|
||||||
|
}
|
||||||
|
disabledModFiles.clear();
|
||||||
|
std::vector<std::string> enabledMods = GetEnabledModsFromCVar();
|
||||||
|
std::string modsPath = Ship::Context::LocateFileAcrossAppDirs("mods", appShortName);
|
||||||
|
if (modsPath.length() > 0 && std::filesystem::exists(modsPath)) {
|
||||||
|
if (std::filesystem::is_directory(modsPath)) {
|
||||||
|
for (const std::filesystem::directory_entry& p : std::filesystem::recursive_directory_iterator(
|
||||||
|
modsPath, std::filesystem::directory_options::follow_directory_symlink)) {
|
||||||
|
std::string extension = p.path().extension().string();
|
||||||
|
if (
|
||||||
|
#ifndef EXCLUDE_MPQ_SUPPORT
|
||||||
|
StringHelper::IEquals(extension, ".otr") || StringHelper::IEquals(extension, ".mpq") ||
|
||||||
|
#endif
|
||||||
|
StringHelper::IEquals(extension, ".o2r") || StringHelper::IEquals(extension, ".zip")) {
|
||||||
|
std::string path = p.path().generic_string();
|
||||||
|
bool shouldBeEnabled = std::find(enabledMods.begin(), enabledMods.end(), path) != enabledMods.end();
|
||||||
|
|
||||||
|
if (shouldBeEnabled) {
|
||||||
|
if (init) {
|
||||||
|
enabledModFiles.push_back(path);
|
||||||
|
GetArchiveManager()->AddArchive(path);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
disabledModFiles.push_back(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void gfx_texture_cache_clear();
|
||||||
|
|
||||||
|
void AfterModChange() {
|
||||||
|
SetEnabledModsCVarValue();
|
||||||
|
// TODO: runtime changes
|
||||||
|
/*
|
||||||
|
gfx_texture_cache_clear();
|
||||||
|
SOH::SkeletonPatcher::ClearSkeletons();
|
||||||
|
*/
|
||||||
|
Ship::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesNextFrame();
|
||||||
|
|
||||||
|
// disabled mods are always sorted
|
||||||
|
std::sort(disabledModFiles.begin(), disabledModFiles.end(), [](const std::string& a, const std::string& b) {
|
||||||
|
return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(),
|
||||||
|
[](char c1, char c2) { return std::tolower(c1) < std::tolower(c2); });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void EnableMod(std::string file) {
|
||||||
|
disabledModFiles.erase(std::find(disabledModFiles.begin(), disabledModFiles.end(), file));
|
||||||
|
enabledModFiles.insert(enabledModFiles.begin(), file);
|
||||||
|
|
||||||
|
// TODO: runtime changes
|
||||||
|
// GetArchiveManager()->AddArchive(file);
|
||||||
|
AfterModChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisableMod(std::string file) {
|
||||||
|
enabledModFiles.erase(std::find(enabledModFiles.begin(), enabledModFiles.end(), file));
|
||||||
|
disabledModFiles.insert(disabledModFiles.begin(), file);
|
||||||
|
|
||||||
|
// TODO: runtime changes
|
||||||
|
// GetArchiveManager()->RemoveArchive(file);
|
||||||
|
AfterModChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawModInfo(std::string file) {
|
||||||
|
ImGui::SameLine();
|
||||||
|
ImGui::Text(file.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawMods(bool enabled) {
|
||||||
|
std::vector<std::string>& selectedModFiles = GetModFiles(enabled);
|
||||||
|
if (selectedModFiles.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool madeAnyChange = false;
|
||||||
|
int switchFromIndex = -1;
|
||||||
|
int switchToIndex = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < selectedModFiles.size(); i += 1) {
|
||||||
|
std::string file = selectedModFiles[i];
|
||||||
|
if (UIWidgets::StateButton((file + "_left_right").c_str(), enabled ? ICON_FA_ARROW_LEFT : ICON_FA_ARROW_RIGHT,
|
||||||
|
ImVec2(25, 25), UIWidgets::ButtonOptions().Color(THEME_COLOR))) {
|
||||||
|
if (enabled) {
|
||||||
|
DisableMod(file);
|
||||||
|
} else {
|
||||||
|
EnableMod(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// it's not relevant to reorder disabled mods
|
||||||
|
if (enabled) {
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (i == 0) {
|
||||||
|
ImGui::BeginDisabled();
|
||||||
|
}
|
||||||
|
if (UIWidgets::StateButton((file + "_up").c_str(), ICON_FA_ARROW_UP, ImVec2(25, 25),
|
||||||
|
UIWidgets::ButtonOptions().Color(THEME_COLOR))) {
|
||||||
|
madeAnyChange = true;
|
||||||
|
switchFromIndex = i;
|
||||||
|
switchToIndex = i - 1;
|
||||||
|
}
|
||||||
|
if (i == 0) {
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (i == selectedModFiles.size() - 1) {
|
||||||
|
ImGui::BeginDisabled();
|
||||||
|
}
|
||||||
|
if (UIWidgets::StateButton((file + "_down").c_str(), ICON_FA_ARROW_DOWN, ImVec2(25, 25),
|
||||||
|
UIWidgets::ButtonOptions().Color(THEME_COLOR))) {
|
||||||
|
madeAnyChange = true;
|
||||||
|
switchFromIndex = i;
|
||||||
|
switchToIndex = i + 1;
|
||||||
|
}
|
||||||
|
if (i == selectedModFiles.size() - 1) {
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawModInfo(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (madeAnyChange) {
|
||||||
|
std::iter_swap(selectedModFiles.begin() + switchFromIndex, selectedModFiles.begin() + switchToIndex);
|
||||||
|
AfterModChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModMenuWindow::DrawElement() {
|
||||||
|
ImGui::BeginDisabled(CVarGetInteger(CVAR_SETTING("DisableChanges"), 0));
|
||||||
|
|
||||||
|
const ImVec4 yellow = ImVec4(1, 1, 0, 1);
|
||||||
|
|
||||||
|
ImGui::TextColored(
|
||||||
|
yellow, "Mods are currently not reloaded at runtime.\nClose and re-open Ship for the changes to take effect.");
|
||||||
|
|
||||||
|
const std::string updateButtonTooltip = "Re-check the mods folder for new files";
|
||||||
|
|
||||||
|
if (UIWidgets::Button("Update", UIWidgets::ButtonOptions().Size(ImVec2(250.0f, 0.0f)).Color(THEME_COLOR))) {
|
||||||
|
UIWidgets::Tooltip(updateButtonTooltip.c_str());
|
||||||
|
UpdateModFiles();
|
||||||
|
} else {
|
||||||
|
UIWidgets::Tooltip(updateButtonTooltip.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::BeginTable("tableMods", 2, ImGuiTableFlags_BordersH | ImGuiTableFlags_BordersV)) {
|
||||||
|
ImGui::TableSetupColumn("Disabled Mods", ImGuiTableColumnFlags_WidthStretch, 200.0f);
|
||||||
|
ImGui::TableSetupColumn("Enabled Mods", ImGuiTableColumnFlags_WidthStretch, 200.0f);
|
||||||
|
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
|
||||||
|
ImGui::TableHeadersRow();
|
||||||
|
ImGui::PopItemFlag();
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
|
||||||
|
if (ImGui::BeginChild("Disabled Mods", ImVec2(0, -8))) {
|
||||||
|
DrawMods(false);
|
||||||
|
|
||||||
|
ImGui::EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
|
||||||
|
if (ImGui::BeginChild("Enabled Mods", ImVec2(0, -8))) {
|
||||||
|
DrawMods(true);
|
||||||
|
|
||||||
|
ImGui::EndChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndDisabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModMenuWindow::InitElement() {
|
||||||
|
UpdateModFiles(true);
|
||||||
|
}
|
14
soh/soh/Enhancements/mod_menu.h
Normal file
14
soh/soh/Enhancements/mod_menu.h
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <libultraship/libultraship.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
class ModMenuWindow : public Ship::GuiWindow {
|
||||||
|
public:
|
||||||
|
using GuiWindow::GuiWindow;
|
||||||
|
|
||||||
|
void InitElement() override;
|
||||||
|
void DrawElement() override;
|
||||||
|
void UpdateElement() override{};
|
||||||
|
};
|
||||||
|
#endif
|
|
@ -280,26 +280,7 @@ void OTRGlobals::Initialize() {
|
||||||
if (std::filesystem::exists(sohOtrPath)) {
|
if (std::filesystem::exists(sohOtrPath)) {
|
||||||
OTRFiles.push_back(sohOtrPath);
|
OTRFiles.push_back(sohOtrPath);
|
||||||
}
|
}
|
||||||
std::string patchesPath = Ship::Context::LocateFileAcrossAppDirs("mods", appShortName);
|
|
||||||
std::vector<std::string> patchOTRs = {};
|
|
||||||
if (patchesPath.length() > 0 && std::filesystem::exists(patchesPath)) {
|
|
||||||
if (std::filesystem::is_directory(patchesPath)) {
|
|
||||||
for (const auto& p : std::filesystem::recursive_directory_iterator(
|
|
||||||
patchesPath, std::filesystem::directory_options::follow_directory_symlink)) {
|
|
||||||
if (StringHelper::IEquals(p.path().extension().string(), ".otr") ||
|
|
||||||
StringHelper::IEquals(p.path().extension().string(), ".mpq") ||
|
|
||||||
StringHelper::IEquals(p.path().extension().string(), ".o2r") ||
|
|
||||||
StringHelper::IEquals(p.path().extension().string(), ".zip")) {
|
|
||||||
patchOTRs.push_back(p.path().generic_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::sort(patchOTRs.begin(), patchOTRs.end(), [](const std::string& a, const std::string& b) {
|
|
||||||
return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(),
|
|
||||||
[](char c1, char c2) { return std::tolower(c1) < std::tolower(c2); });
|
|
||||||
});
|
|
||||||
OTRFiles.insert(OTRFiles.end(), patchOTRs.begin(), patchOTRs.end());
|
|
||||||
std::unordered_set<uint32_t> ValidHashes = {
|
std::unordered_set<uint32_t> ValidHashes = {
|
||||||
OOT_PAL_MQ, OOT_NTSC_JP_MQ, OOT_NTSC_US_MQ, OOT_PAL_GC_MQ_DBG, OOT_NTSC_US_10,
|
OOT_PAL_MQ, OOT_NTSC_JP_MQ, OOT_NTSC_US_MQ, OOT_PAL_GC_MQ_DBG, OOT_NTSC_US_10,
|
||||||
OOT_NTSC_US_11, OOT_NTSC_US_12, OOT_PAL_10, OOT_PAL_11, OOT_NTSC_JP_GC_CE,
|
OOT_NTSC_US_11, OOT_NTSC_US_12, OOT_PAL_10, OOT_PAL_11, OOT_NTSC_JP_GC_CE,
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
#include "soh/Enhancements/debugger/MessageViewer.h"
|
#include "soh/Enhancements/debugger/MessageViewer.h"
|
||||||
#include "soh/Notification/Notification.h"
|
#include "soh/Notification/Notification.h"
|
||||||
#include "soh/Enhancements/TimeDisplay/TimeDisplay.h"
|
#include "soh/Enhancements/TimeDisplay/TimeDisplay.h"
|
||||||
|
#include "soh/Enhancements/mod_menu.h"
|
||||||
|
|
||||||
namespace SohGui {
|
namespace SohGui {
|
||||||
|
|
||||||
|
@ -72,6 +73,7 @@ std::shared_ptr<SohStatsWindow> mStatsWindow;
|
||||||
std::shared_ptr<Ship::GuiWindow> mGfxDebuggerWindow;
|
std::shared_ptr<Ship::GuiWindow> mGfxDebuggerWindow;
|
||||||
|
|
||||||
std::shared_ptr<SohMenu> mSohMenu;
|
std::shared_ptr<SohMenu> mSohMenu;
|
||||||
|
std::shared_ptr<ModMenuWindow> mModMenuWindow;
|
||||||
std::shared_ptr<AudioEditor> mAudioEditorWindow;
|
std::shared_ptr<AudioEditor> mAudioEditorWindow;
|
||||||
std::shared_ptr<InputViewer> mInputViewer;
|
std::shared_ptr<InputViewer> mInputViewer;
|
||||||
std::shared_ptr<InputViewerSettingsWindow> mInputViewerSettings;
|
std::shared_ptr<InputViewerSettingsWindow> mInputViewerSettings;
|
||||||
|
@ -134,6 +136,8 @@ void SetupGuiElements() {
|
||||||
SPDLOG_ERROR("Could not find input editor window");
|
SPDLOG_ERROR("Could not find input editor window");
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
mModMenuWindow = std::make_shared<ModMenuWindow>(CVAR_WINDOW("ModMenu"), "Mod Menu", ImVec2(820, 630));
|
||||||
|
gui->AddGuiWindow(mModMenuWindow);
|
||||||
mAudioEditorWindow = std::make_shared<AudioEditor>(CVAR_WINDOW("AudioEditor"), "Audio Editor", ImVec2(820, 630));
|
mAudioEditorWindow = std::make_shared<AudioEditor>(CVAR_WINDOW("AudioEditor"), "Audio Editor", ImVec2(820, 630));
|
||||||
gui->AddGuiWindow(mAudioEditorWindow);
|
gui->AddGuiWindow(mAudioEditorWindow);
|
||||||
mInputViewer = std::make_shared<InputViewer>(CVAR_WINDOW("InputViewer"), "Input Viewer");
|
mInputViewer = std::make_shared<InputViewer>(CVAR_WINDOW("InputViewer"), "Input Viewer");
|
||||||
|
@ -225,6 +229,7 @@ void Destroy() {
|
||||||
mColViewerWindow = nullptr;
|
mColViewerWindow = nullptr;
|
||||||
mActorViewerWindow = nullptr;
|
mActorViewerWindow = nullptr;
|
||||||
mCosmeticsEditorWindow = nullptr;
|
mCosmeticsEditorWindow = nullptr;
|
||||||
|
mModMenuWindow = nullptr;
|
||||||
mAudioEditorWindow = nullptr;
|
mAudioEditorWindow = nullptr;
|
||||||
mStatsWindow = nullptr;
|
mStatsWindow = nullptr;
|
||||||
mConsoleWindow = nullptr;
|
mConsoleWindow = nullptr;
|
||||||
|
|
|
@ -1883,6 +1883,14 @@ void SohMenu::AddMenuEnhancements() {
|
||||||
.CVar(timer.timeEnable)
|
.CVar(timer.timeEnable)
|
||||||
.Callback([](WidgetInfo& info) { TimeDisplayUpdateDisplayOptions(); });
|
.Callback([](WidgetInfo& info) { TimeDisplayUpdateDisplayOptions(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mod Menu
|
||||||
|
path.sidebarName = "Mod Menu";
|
||||||
|
AddSidebarEntry("Enhancements", path.sidebarName, 1);
|
||||||
|
AddWidget(path, "Toggle Mod Menu Window", WIDGET_WINDOW_BUTTON)
|
||||||
|
.CVar(CVAR_WINDOW("ModMenu"))
|
||||||
|
.WindowName("Mod Menu")
|
||||||
|
.Options(WindowButtonOptions().Tooltip("Enables the separate Mod Menu Window."));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace SohGui
|
} // namespace SohGui
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue