From e61a5eac9f3927668e460583e3255994475af1a9 Mon Sep 17 00:00:00 2001 From: Garrett Cox Date: Wed, 10 Jan 2024 02:32:45 +0000 Subject: [PATCH] First draft of save editor (#8) --- mm/2s2h/DeveloperTools/SaveEditor.cpp | 477 ++++++++++++++++++++++++++ mm/2s2h/DeveloperTools/SaveEditor.h | 12 + mm/2s2h/SohGui.cpp | 9 +- mm/2s2h/SohGui.hpp | 1 + mm/2s2h/SohMenuBar.cpp | 13 +- mm/2s2h/SohMenuBar.h | 1 + mm/2s2h/UIWidgets.cpp | 433 +++++++++++++++++++++++ mm/2s2h/UIWidgets.hpp | 120 +++++++ mm/CMakeLists.txt | 8 + mm/include/variables.h | 4 +- mm/src/code/code_8012EC80.c | 2 +- 11 files changed, 1066 insertions(+), 14 deletions(-) create mode 100644 mm/2s2h/DeveloperTools/SaveEditor.cpp create mode 100644 mm/2s2h/DeveloperTools/SaveEditor.h diff --git a/mm/2s2h/DeveloperTools/SaveEditor.cpp b/mm/2s2h/DeveloperTools/SaveEditor.cpp new file mode 100644 index 000000000..55365af46 --- /dev/null +++ b/mm/2s2h/DeveloperTools/SaveEditor.cpp @@ -0,0 +1,477 @@ +#include "SaveEditor.h" +#include "2s2h/UIWidgets.hpp" + +extern "C" { +#include +#include +extern PlayState* gPlayState; +extern SaveContext gSaveContext; +extern TexturePtr gItemIcons[131]; +extern u8 gItemSlots[77]; +void Interface_LoadItemIconImpl(PlayState* play, u8 btn); +} + +bool safeMode = true; +const float INV_GRID_WIDTH = 46.0f; +const float INV_GRID_HEIGHT = 58.0f; +const float INV_GRID_ICON_SIZE = 40.0f; +const float INV_GRID_PADDING = 10.0f; +const float INV_GRID_TOP_MARGIN = 20.0f; +const uint16_t HEART_COUNT_MIN = 3; +const uint16_t HEART_COUNT_MAX = 20; +const int16_t S16_ZERO = 0; +const int8_t S8_ZERO = 0; +const char* MAGIC_LEVEL_NAMES[3] = { "No Magic", "Single Magic", "Double Magic" }; +const int8_t MAGIC_LEVEL_MAX = 2; + +InventorySlot selectedInventorySlot = SLOT_NONE; +std::vector safeItemsForInventorySlot[SLOT_MASK_FIERCE_DEITY] = {}; + +void initSafeItemsForInventorySlot() { + for (int i = 0; i < sizeof(gItemSlots); i++) { + InventorySlot slot = static_cast(gItemSlots[i]); + switch (slot) { + case SLOT_BOTTLE_1: + if (i != ITEM_LONGSHOT) { // No longshot in bottles + safeItemsForInventorySlot[SLOT_BOTTLE_1].push_back(static_cast(i)); + safeItemsForInventorySlot[SLOT_BOTTLE_2].push_back(static_cast(i)); + safeItemsForInventorySlot[SLOT_BOTTLE_3].push_back(static_cast(i)); + safeItemsForInventorySlot[SLOT_BOTTLE_4].push_back(static_cast(i)); + safeItemsForInventorySlot[SLOT_BOTTLE_5].push_back(static_cast(i)); + safeItemsForInventorySlot[SLOT_BOTTLE_6].push_back(static_cast(i)); + } + break; + case SLOT_BOW: + if (i == ITEM_BOW) { // No elemental bows here + safeItemsForInventorySlot[slot].push_back(static_cast(i)); + } + break; + case SLOT_TRADE_KEY_MAMA: + if (i != ITEM_SLINGSHOT) { // No slingshot in trade items + safeItemsForInventorySlot[slot].push_back(static_cast(i)); + } + break; + case SLOT_TRADE_DEED: + if (i != ITEM_OCARINA_FAIRY) { // No fairy ocarina in trade items + safeItemsForInventorySlot[slot].push_back(static_cast(i)); + } + break; + default: + safeItemsForInventorySlot[slot].push_back(static_cast(i)); + break; + } + } +} + +char z2ASCII(int code) { + int ret; + if (code < 10) { //Digits + ret = code + 0x30; + } else if (code >= 10 && code < 36) { //Uppercase letters + ret = code + 0x37; + } else if (code >= 36 && code < 62) { //Lowercase letters + ret = code + 0x3D; + } else if (code == 62) { //Space + ret = code - 0x1E; + } else if (code == 63 || code == 64) { // _ and . + ret = code - 0x12; + } else { + ret = code; + } + return char(ret); +} + +char ASCII2z(int code) { + int ret; + if (code >= 0x30 && code < 0x3A) { //Digits + ret = code - 0x30; + } else if (code >= 0x41 && code < 0x5B) { //Uppercase letters + ret = code - 0x37; + } else if (code >= 0x61 && code < 0x7B) { //Lowercase letters + ret = code - 0x3D; + } else if (code == 0x20 || code == 0x0) { //Space + ret = 62; + } else if (code == 0x5F || code == 0x2E) { // _ and . + ret = code + 0x12; + } else { + ret = code; + } + return char(ret); +} + +char* getPlayerName(char* playerNameBuf) { + for (int i = 0; i < 8; i++) { + playerNameBuf[i] = z2ASCII(gSaveContext.save.saveInfo.playerData.playerName[i]); + } + playerNameBuf[8] = '\0'; + return playerNameBuf; +} + +int setPlayerName(ImGuiInputTextCallbackData* data) { + for (int i = 0; i < 8; i++) { + gSaveContext.save.saveInfo.playerData.playerName[i] = ASCII2z(data->Buf[i]); + } + return 0; +}; + +void DrawGeneralTab() { + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.0f, 8.0f)); + ImGui::BeginChild("generalTab", ImVec2(0, 0), true); + ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x * 0.5f - 4.0f); + ImGui::BeginGroup(); + ImGui::Text("Player Name"); + ImGui::PushStyleColor(ImGuiCol_FrameBg, UIWidgets::Colors::Gray); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 8.0f)); + char playerNameBuf[9]; + ImGui::InputText("##playerNameInput", getPlayerName(playerNameBuf), 9, ImGuiInputTextFlags_CallbackAlways | ImGuiInputTextFlags_AutoSelectAll, setPlayerName); + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(1); + ImGui::EndGroup(); + ImGui::SameLine(); + ImGui::SetCursorPosY(0.0f); + ImGui::BeginGroup(); + ImGui::Text("Time"); + UIWidgets::PushStyleSlider(); + static u16 minTime = 0; + static u16 maxTime = 0xFFFF; + ImGui::SliderScalar("##timeInput", ImGuiDataType_U16, &gSaveContext.save.time, &minTime, &maxTime, "%x"); + UIWidgets::PopStyleSlider(); + ImGui::EndGroup(); + ImGui::BeginGroup(); + if (UIWidgets::Button("Max Health", { .color = UIWidgets::Colors::Red, .size = UIWidgets::Sizes::Inline })) { + gSaveContext.save.saveInfo.playerData.doubleDefense = 1; + gSaveContext.save.saveInfo.inventory.defenseHearts = 20; + gSaveContext.save.saveInfo.playerData.healthCapacity = gSaveContext.save.saveInfo.playerData.health = 20 * 16; + gSaveContext.save.saveInfo.inventory.questItems &= ~0xF0000000; + } + ImGui::SameLine(); + if (UIWidgets::Button("Reset##resetHealthButton", { .color = UIWidgets::Colors::Gray, .size = UIWidgets::Sizes::Inline })) { + gSaveContext.save.saveInfo.playerData.doubleDefense = 0; + gSaveContext.save.saveInfo.inventory.defenseHearts = 0; + gSaveContext.save.saveInfo.playerData.healthCapacity = gSaveContext.save.saveInfo.playerData.health = 3 * 16; + gSaveContext.save.saveInfo.inventory.questItems &= ~0xF0000000; + } + ImGui::SameLine(); + if (UIWidgets::Checkbox("DD", (bool *)&gSaveContext.save.saveInfo.playerData.doubleDefense, { .color = UIWidgets::Colors::Red })) { + if (gSaveContext.save.saveInfo.playerData.doubleDefense) { + gSaveContext.save.saveInfo.inventory.defenseHearts = 20; + } else { + gSaveContext.save.saveInfo.inventory.defenseHearts = 0; + } + } + UIWidgets::PushStyleSlider(UIWidgets::Colors::DarkRed); + int16_t heartCount = (int16_t)gSaveContext.save.saveInfo.playerData.healthCapacity / 16; + if (ImGui::SliderScalar("##heartCountSlider", ImGuiDataType_U16, &heartCount, &HEART_COUNT_MIN, &HEART_COUNT_MAX, "Max Hearts: %d")) { + gSaveContext.save.saveInfo.playerData.healthCapacity = heartCount * 16; + gSaveContext.save.saveInfo.playerData.health = MIN(gSaveContext.save.saveInfo.playerData.health, gSaveContext.save.saveInfo.playerData.healthCapacity); + } + ImGui::SliderScalar("##healthSlider", ImGuiDataType_S16, &gSaveContext.save.saveInfo.playerData.health, &S16_ZERO, &gSaveContext.save.saveInfo.playerData.healthCapacity, "Health: %d"); + UIWidgets::PopStyleSlider(); + ImGui::EndGroup(); + ImGui::SameLine(); + ImGui::BeginGroup(); + if (UIWidgets::Button("Max Magic", { .color = UIWidgets::Colors::Green, .size = UIWidgets::Sizes::Inline })) { + gSaveContext.magicCapacity = gSaveContext.save.saveInfo.playerData.magic = MAGIC_DOUBLE_METER; + gSaveContext.save.saveInfo.playerData.magicLevel = 2; + gSaveContext.save.saveInfo.playerData.isMagicAcquired = true; + gSaveContext.save.saveInfo.playerData.isDoubleMagicAcquired = true; + } + ImGui::SameLine(); + if (UIWidgets::Button("Reset##resetMagicButton", { .color = UIWidgets::Colors::Gray, .size = UIWidgets::Sizes::Inline })) { + gSaveContext.magicCapacity = gSaveContext.save.saveInfo.playerData.magic = 0; + gSaveContext.save.saveInfo.playerData.magicLevel = 0; + gSaveContext.save.saveInfo.playerData.isMagicAcquired = false; + gSaveContext.save.saveInfo.playerData.isDoubleMagicAcquired = false; + } + UIWidgets::PushStyleSlider(UIWidgets::Colors::DarkGreen); + if (ImGui::SliderScalar("##magicLevelSlider", ImGuiDataType_S8, &gSaveContext.save.saveInfo.playerData.magicLevel, &S8_ZERO, &MAGIC_LEVEL_MAX, MAGIC_LEVEL_NAMES[gSaveContext.save.saveInfo.playerData.magicLevel])) { + gSaveContext.magicCapacity = gSaveContext.save.saveInfo.playerData.magicLevel * MAGIC_NORMAL_METER; + gSaveContext.save.saveInfo.playerData.magic = MIN(gSaveContext.save.saveInfo.playerData.magic, gSaveContext.magicCapacity); + switch (gSaveContext.save.saveInfo.playerData.magicLevel) { + case 0: + gSaveContext.save.saveInfo.playerData.isMagicAcquired = false; + gSaveContext.save.saveInfo.playerData.isDoubleMagicAcquired = false; + break; + case 1: + gSaveContext.save.saveInfo.playerData.isMagicAcquired = true; + gSaveContext.save.saveInfo.playerData.isDoubleMagicAcquired = false; + break; + case 2: + gSaveContext.save.saveInfo.playerData.isMagicAcquired = true; + gSaveContext.save.saveInfo.playerData.isDoubleMagicAcquired = true; + break; + } + } + ImGui::SliderScalar("##magicSlider", ImGuiDataType_S8, &gSaveContext.save.saveInfo.playerData.magic, &S8_ZERO, &gSaveContext.magicCapacity, "Magic: %d"); + UIWidgets::PopStyleSlider(); + ImGui::EndGroup(); + ImGui::PopItemWidth(); + ImGui::EndChild(); + ImGui::PopStyleVar(2); +} + +template +bool contains(C&& c, T e) { + return std::find(std::begin(c), std::end(c), e) != std::end(c); +}; + +void DrawAmmoInput(InventorySlot slot) { + int x = slot % 6; + int y = ((int)floor(slot / 6) % 4); + ItemId currentItemId = static_cast(gSaveContext.save.saveInfo.inventory.items[slot]); + + ImGui::SetCursorPos(ImVec2(x * INV_GRID_WIDTH + INV_GRID_PADDING + 7.0f, y * INV_GRID_HEIGHT + INV_GRID_TOP_MARGIN + INV_GRID_PADDING + (INV_GRID_ICON_SIZE - 2.0f))); + ImGui::PushItemWidth(24.0f); + ImGui::PushStyleColor(ImGuiCol_FrameBg, UIWidgets::Colors::Gray); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, 4.0f)); + if (ImGui::InputScalar("##ammoInput", ImGuiDataType_S8, &AMMO(currentItemId))) { + // Crashes when < 0 and graphical issues when > 99 + AMMO(currentItemId) = MAX(0, MIN(AMMO(currentItemId), 99)); + } + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(1); + ImGui::PopItemWidth(); +} + +void DrawEquipItemMenu(InventorySlot slot) { + ItemId currentItemId = static_cast(gSaveContext.save.saveInfo.inventory.items[slot]); + + if (ImGui::BeginMenu("Equip")) { + if (ImGui::MenuItem("C-Left")) { + gSaveContext.save.saveInfo.equips.buttonItems[CUR_FORM][EQUIP_SLOT_C_LEFT] = currentItemId; + gSaveContext.save.saveInfo.equips.cButtonSlots[CUR_FORM][EQUIP_SLOT_C_LEFT] = slot; + Interface_LoadItemIconImpl(gPlayState, EQUIP_SLOT_C_LEFT); + } + if (ImGui::MenuItem("C-Down")) { + gSaveContext.save.saveInfo.equips.buttonItems[CUR_FORM][EQUIP_SLOT_C_DOWN] = currentItemId; + gSaveContext.save.saveInfo.equips.cButtonSlots[CUR_FORM][EQUIP_SLOT_C_DOWN] = slot; + Interface_LoadItemIconImpl(gPlayState, EQUIP_SLOT_C_DOWN); + } + if (ImGui::MenuItem("C-Right")) { + gSaveContext.save.saveInfo.equips.buttonItems[CUR_FORM][EQUIP_SLOT_C_RIGHT] = currentItemId; + gSaveContext.save.saveInfo.equips.cButtonSlots[CUR_FORM][EQUIP_SLOT_C_RIGHT] = slot; + Interface_LoadItemIconImpl(gPlayState, EQUIP_SLOT_C_RIGHT); + } + // Todo: DPAD equips support + // if (ImGui::MenuItem("D-Up")) { + // gSaveContext.save.saveInfo.equips.buttonItems[CUR_FORM][4] = currentItemId; + // gSaveContext.save.saveInfo.equips.cButtonSlots[CUR_FORM][3] = slot; + // } + // if (ImGui::MenuItem("D-Down")) { + // gSaveContext.save.saveInfo.equips.buttonItems[CUR_FORM][5] = currentItemId; + // gSaveContext.save.saveInfo.equips.cButtonSlots[CUR_FORM][4] = slot; + // } + // if (ImGui::MenuItem("D-Left")) { + // gSaveContext.save.saveInfo.equips.buttonItems[CUR_FORM][6] = currentItemId; + // gSaveContext.save.saveInfo.equips.cButtonSlots[CUR_FORM][5] = slot; + // } + // if (ImGui::MenuItem("D-Right")) { + // gSaveContext.save.saveInfo.equips.buttonItems[CUR_FORM][7] = currentItemId; + // gSaveContext.save.saveInfo.equips.cButtonSlots[CUR_FORM][6] = slot; + // } + ImGui::EndMenu(); + } +} + +void NextItemInSlot(InventorySlot slot) { + ItemId currentItemId = static_cast(gSaveContext.save.saveInfo.inventory.items[slot]); + int currentItemIndex = find(safeItemsForInventorySlot[slot].begin(), safeItemsForInventorySlot[slot].end(), currentItemId) - safeItemsForInventorySlot[slot].begin(); + + if (currentItemId == ITEM_NONE) { + gSaveContext.save.saveInfo.inventory.items[slot] = safeItemsForInventorySlot[slot][0]; + } else if (currentItemIndex < safeItemsForInventorySlot[slot].size() - 1) { + Inventory_ReplaceItem(gPlayState, currentItemId, safeItemsForInventorySlot[slot][currentItemIndex + 1]); + } else { + Inventory_DeleteItem(gSaveContext.save.saveInfo.inventory.items[slot], slot); + } +} + +void DrawSlot(InventorySlot slot) { + int x = slot % 6; + int y = ((int)floor(slot / 6) % 4); + ItemId currentItemId = static_cast(gSaveContext.save.saveInfo.inventory.items[slot]); + + ImGui::PushID(slot); + + if ( + currentItemId != ITEM_NONE && + currentItemId <= ITEM_BOW_LIGHT && // gItemSlots only has entries till 77 (ITEM_BOW_LIGHT) + gItemSlots[currentItemId] <= SLOT_BOTTLE_6 && // There is only ammo data for the first page + ( + (safeMode && gAmmoItems[gItemSlots[currentItemId]] != ITEM_NONE) || !safeMode + ) + ) { + DrawAmmoInput(slot); + } + + ImGui::SetCursorPos(ImVec2(x * INV_GRID_WIDTH + INV_GRID_PADDING, y * INV_GRID_HEIGHT + INV_GRID_TOP_MARGIN + INV_GRID_PADDING)); + + // isEquipped border + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 1.0f, 1.0f, 0.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.2f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 1.0f, 1.0f, 0.1f)); + if ( + gSaveContext.save.saveInfo.equips.cButtonSlots[CUR_FORM][EQUIP_SLOT_C_LEFT] == slot || + gSaveContext.save.saveInfo.equips.cButtonSlots[CUR_FORM][EQUIP_SLOT_C_DOWN] == slot || + gSaveContext.save.saveInfo.equips.cButtonSlots[CUR_FORM][EQUIP_SLOT_C_RIGHT] == slot + ) { + ImGui::PushStyleColor(ImGuiCol_Border, UIWidgets::Colors::White); + } else { + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 1.0f, 0.0f)); + } + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f); + + ImTextureID textureId = LUS::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName((const char*)gItemIcons[safeItemsForInventorySlot[slot][0]]); + + if (currentItemId != ITEM_NONE) { + textureId = LUS::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName((const char*)gItemIcons[currentItemId]); + } + + if (ImGui::ImageButton(textureId, ImVec2(INV_GRID_ICON_SIZE, INV_GRID_ICON_SIZE), ImVec2(0, 0), ImVec2(1, 1), 0, ImVec4(0, 0, 0, 0), ImVec4(1, 1, 1, currentItemId == ITEM_NONE ? 0.4f : 1.0f))) { + if (safeMode && safeItemsForInventorySlot[slot].size() < 2) { + NextItemInSlot(slot); + } else { + selectedInventorySlot = slot; + ImGui::OpenPopup("InventorySlotPopup"); + } + } + + ImGui::PopStyleVar(); + ImGui::PopStyleColor(4); + + if (ImGui::BeginPopup("InventorySlotPopup")) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 1.0f, 1.0f, 0.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.2f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 1.0f, 1.0f, 0.1f)); + if (ImGui::Button("##invNonePicker", ImVec2(INV_GRID_ICON_SIZE, INV_GRID_ICON_SIZE))) { + Inventory_DeleteItem(gSaveContext.save.saveInfo.inventory.items[selectedInventorySlot], selectedInventorySlot); + ImGui::CloseCurrentPopup(); + } + UIWidgets::SetLastItemHoverText("None"); + + size_t availableItems = safeMode ? safeItemsForInventorySlot[selectedInventorySlot].size() : ITEM_FISHING_ROD; + for (int32_t pickerIndex = 0; pickerIndex < availableItems; pickerIndex++) { + if (((pickerIndex + 1) % 8) != 0) { + ImGui::SameLine(); + } + ItemId id = safeMode ? safeItemsForInventorySlot[selectedInventorySlot][pickerIndex] : static_cast(pickerIndex); + if (ImGui::ImageButton(LUS::Context::GetInstance()->GetWindow()->GetGui()->GetTextureByName((const char*)gItemIcons[id]), ImVec2(INV_GRID_ICON_SIZE, INV_GRID_ICON_SIZE), ImVec2(0, 0), ImVec2(1, 1), 0)) { + gSaveContext.save.saveInfo.inventory.items[selectedInventorySlot] = id; + ImGui::CloseCurrentPopup(); + } + // TODO: Add names for items + // UIWidgets::SetLastItemHoverText(SohUtils::GetItemName(id)); + } + ImGui::PopStyleColor(3); + ImGui::EndPopup(); + } + + ImGui::PopID(); + + if (currentItemId != ITEM_NONE && ImGui::BeginPopupContextItem()) { + DrawEquipItemMenu(slot); + ImGui::EndPopup(); + } +} + +void DrawItemsAndMasksTab() { + ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.0f, 8.0f)); + ImGui::BeginGroup(); + ImGui::BeginChild("itemsBox", ImVec2(INV_GRID_WIDTH * 6 + INV_GRID_PADDING * 2, INV_GRID_HEIGHT * 4 + INV_GRID_PADDING * 2 + INV_GRID_TOP_MARGIN), true); + ImGui::Text("Items"); + for (uint32_t i = SLOT_OCARINA; i <= SLOT_BOTTLE_6; i++) { + InventorySlot slot = static_cast(i); + + DrawSlot(slot); + } + ImGui::EndChild(); + ImGui::BeginChild("masksBox", ImVec2(INV_GRID_WIDTH * 6 + INV_GRID_PADDING * 2, 0), true); + ImGui::Text("Masks"); + for (uint32_t i = SLOT_MASK_POSTMAN; i <= SLOT_MASK_FIERCE_DEITY; i++) { + InventorySlot slot = static_cast(i); + + DrawSlot(slot); + } + ImGui::EndChild(); + ImGui::EndGroup(); + ImGui::SameLine(); + ImGui::BeginChild("equipsBox", ImVec2(0, 0), true); + if (UIWidgets::Button("Give All##items")) { + for (int32_t i = 0; i <= SLOT_MASK_FIERCE_DEITY; i++) { + if (i >= SLOT_BOTTLE_1 && i <= SLOT_BOTTLE_6) { + gSaveContext.save.saveInfo.inventory.items[i] = ITEM_BOTTLE; + } else { + gSaveContext.save.saveInfo.inventory.items[i] = safeItemsForInventorySlot[i].back(); + } + } + } + if (UIWidgets::Button("Reset##items")) { + for (int32_t i = 0; i <= SLOT_MASK_FIERCE_DEITY; i++) { + Inventory_DeleteItem(gSaveContext.save.saveInfo.inventory.items[i], i); + } + } + UIWidgets::Checkbox("Safe Mode", &safeMode); + // Expose inputs to edit raw number values of equips + // ImGui::Text("Equips"); + // ImGui::Text("C-Buttons"); + // ImGui::Text("D-Pad"); + ImGui::EndChild(); + ImGui::PopStyleVar(2); +} + +void SaveEditorWindow::DrawElement() { + ImGui::SetNextWindowSize(ImVec2(480,600), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("Save Editor", &mIsVisible, ImGuiWindowFlags_NoFocusOnAppearing)) { + ImGui::End(); + return; + } + + if (ImGui::BeginTabBar("SaveContextTabBar", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) { + if (ImGui::BeginTabItem("General")) { + DrawGeneralTab(); + ImGui::EndTabItem(); + } + + if (ImGui::BeginTabItem("Items & Masks")) { + DrawItemsAndMasksTab(); + ImGui::EndTabItem(); + } + + // if (ImGui::BeginTabItem("Equipment")) { + // DrawEquipmentTab(); + // ImGui::EndTabItem(); + // } + + // if (ImGui::BeginTabItem("Quest Status")) { + // DrawQuestStatusTab(); + // ImGui::EndTabItem(); + // } + + // if (ImGui::BeginTabItem("Flags")) { + // DrawFlagsTab(); + // ImGui::EndTabItem(); + // } + + // if (ImGui::BeginTabItem("Player")) { + // DrawPlayerTab(); + // ImGui::EndTabItem(); + // } + + ImGui::EndTabBar(); + } + + ImGui::End(); +} + +void SaveEditorWindow::InitElement() { + initSafeItemsForInventorySlot(); + + for (TexturePtr entry : gItemIcons) { + const char* path = static_cast(entry); + LUS::Context::GetInstance()->GetWindow()->GetGui()->LoadGuiTexture(path, path, ImVec4(1, 1, 1, 1)); + } +} \ No newline at end of file diff --git a/mm/2s2h/DeveloperTools/SaveEditor.h b/mm/2s2h/DeveloperTools/SaveEditor.h new file mode 100644 index 000000000..a5b9f570d --- /dev/null +++ b/mm/2s2h/DeveloperTools/SaveEditor.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +class SaveEditorWindow : public LUS::GuiWindow { + public: + using GuiWindow::GuiWindow; + + void InitElement() override; + void DrawElement() override; + void UpdateElement() override {}; +}; \ No newline at end of file diff --git a/mm/2s2h/SohGui.cpp b/mm/2s2h/SohGui.cpp index 6771ff921..f6dac4ed4 100644 --- a/mm/2s2h/SohGui.cpp +++ b/mm/2s2h/SohGui.cpp @@ -117,7 +117,7 @@ namespace SohGui { //std::shared_ptr mCosmeticsEditorWindow; //std::shared_ptr mActorViewerWindow; //std::shared_ptr mColViewerWindow; - //std::shared_ptr mSaveEditorWindow; + std::shared_ptr mSaveEditorWindow; //std::shared_ptr mDLViewerWindow; //std::shared_ptr mGameplayStatsWindow; //std::shared_ptr mCheckTrackerSettingsWindow; @@ -161,6 +161,9 @@ namespace SohGui { SPDLOG_ERROR("Could not find input GfxDebuggerWindow"); } + mSaveEditorWindow = std::make_shared("gSaveEditorEnabled", "Save Editor"); + gui->AddGuiWindow(mSaveEditorWindow); + /* mAudioEditorWindow = std::make_shared("gAudioEditor.WindowOpen", "Audio Editor"); gui->AddGuiWindow(mAudioEditorWindow); @@ -172,8 +175,6 @@ namespace SohGui { gui->AddGuiWindow(mActorViewerWindow); mColViewerWindow = std::make_shared("gCollisionViewerEnabled", "Collision Viewer"); gui->AddGuiWindow(mColViewerWindow); - mSaveEditorWindow = std::make_shared("gSaveEditorEnabled", "Save Editor"); - gui->AddGuiWindow(mSaveEditorWindow); mDLViewerWindow = std::make_shared("gDLViewerEnabled", "Display List Viewer"); gui->AddGuiWindow(mDLViewerWindow); mGameplayStatsWindow = std::make_shared("gGameplayStatsEnabled", "Gameplay Stats"); @@ -202,7 +203,7 @@ namespace SohGui { //mCheckTrackerSettingsWindow = nullptr; //mGameplayStatsWindow = nullptr; //mDLViewerWindow = nullptr; - //mSaveEditorWindow = nullptr; + mSaveEditorWindow = nullptr; //mColViewerWindow = nullptr; //mActorViewerWindow = nullptr; //mCosmeticsEditorWindow = nullptr; diff --git a/mm/2s2h/SohGui.hpp b/mm/2s2h/SohGui.hpp index 8e5fc9447..daee855e8 100644 --- a/mm/2s2h/SohGui.hpp +++ b/mm/2s2h/SohGui.hpp @@ -10,6 +10,7 @@ #include #include "SohMenuBar.h" +#include "DeveloperTools/SaveEditor.h" //#include "Enhancements/audio/AudioEditor.h" //#include "Enhancements/controls/GameControlEditor.h" //#include "Enhancements/cosmetics/CosmeticsEditor.h" diff --git a/mm/2s2h/SohMenuBar.cpp b/mm/2s2h/SohMenuBar.cpp index cbc502839..cf503c7cf 100644 --- a/mm/2s2h/SohMenuBar.cpp +++ b/mm/2s2h/SohMenuBar.cpp @@ -447,7 +447,7 @@ void DrawSettingsMenu() { extern std::shared_ptr mStatsWindow; extern std::shared_ptr mConsoleWindow; extern std::shared_ptr mGfxDebuggerWindow; -// extern std::shared_ptr mSaveEditorWindow; +extern std::shared_ptr mSaveEditorWindow; // extern std::shared_ptr mColViewerWindow; // extern std::shared_ptr mActorViewerWindow; // extern std::shared_ptr mDLViewerWindow; @@ -516,12 +516,11 @@ void DrawDeveloperToolsMenu() { } UIWidgets::Spacer(0); - // if (mSaveEditorWindow) { - // if (ImGui::Button(GetWindowButtonText("Save Editor", CVarGetInteger("gSaveEditorEnabled", 0)).c_str(), - // ImVec2(-1.0f, 0.0f))) { - // //mSaveEditorWindow->ToggleVisibility(); - // } - // } + if (mSaveEditorWindow) { + if (ImGui::Button(GetWindowButtonText("Save Editor", CVarGetInteger("gSaveEditorEnabled", 0)).c_str(), ImVec2(-1.0f, 0.0f))) { + mSaveEditorWindow->ToggleVisibility(); + } + } // UIWidgets::Spacer(0); // if (mColViewerWindow) { // if (ImGui::Button(GetWindowButtonText("Collision Viewer", CVarGetInteger("gCollisionViewerEnabled", diff --git a/mm/2s2h/SohMenuBar.h b/mm/2s2h/SohMenuBar.h index ecfb2764e..3d0759943 100644 --- a/mm/2s2h/SohMenuBar.h +++ b/mm/2s2h/SohMenuBar.h @@ -3,6 +3,7 @@ //#include #include "window/gui/GuiMenuBar.h" #include "window/gui/GuiElement.h" +#include "DeveloperTools/SaveEditor.h" namespace SohGui { class SohMenuBar : public LUS::GuiMenuBar { diff --git a/mm/2s2h/UIWidgets.cpp b/mm/2s2h/UIWidgets.cpp index 3086f32ad..b3810e6e6 100644 --- a/mm/2s2h/UIWidgets.cpp +++ b/mm/2s2h/UIWidgets.cpp @@ -713,4 +713,437 @@ namespace UIWidgets { float sz = ImGui::GetFrameHeight(); return StateButtonEx(str_id, label, ImVec2(sz, sz), ImGuiButtonFlags_None); } + + // UIWidgets V2 + + void PushStyleMenu(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(color.x, color.y, color.z, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_PopupBg, UIWidgets::Colors::DarkGray); + ImGui::PushStyleColor(ImGuiCol_Border, UIWidgets::Colors::DarkGray); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.0f, 15.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 3.0f); + } + + void PopStyleMenu() { + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(4); + } + + bool BeginMenu(const char* label, const ImVec4& color) { + bool dirty = false; + PushStyleMenu(color); + if (ImGui::BeginMenu(label)) { + dirty = true; + } + PopStyleMenu(); + return dirty; + } + + void PushStyleMenuItem(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(20.0f, 15.0f)); + } + + void PopStyleMenuItem() { + ImGui::PopStyleVar(1); + ImGui::PopStyleColor(1); + } + + bool MenuItem(const char* label, const char* shortcut, const ImVec4& color) { + bool dirty = false; + PushStyleMenuItem(color); + if (ImGui::MenuItem(label, shortcut)) { + dirty = true; + } + PopStyleMenuItem(); + return dirty; + } + + void PushStyleButton(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.3f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 8.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 5.0f); + } + + void PopStyleButton() { + ImGui::PopStyleVar(3); + ImGui::PopStyleColor(4); + } + + bool Button(const char* label, const ButtonOptions& options) { + ImGui::BeginDisabled(options.disabled); + PushStyleButton(options.color); + bool dirty = ImGui::Button(label, options.size); + PopStyleButton(); + ImGui::EndDisabled(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(options.disabledTooltip, "") != 0) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip)); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(options.tooltip, "") != 0) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip)); + } + return dirty; + } + + bool WindowButton(const char* label, const char* cvarName, std::shared_ptr windowPtr, const ButtonOptions& options) { + ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0)); + std::string buttonText = label; + bool dirty = false; + if (CVarGetInteger(cvarName, 0)) { + buttonText = ICON_FA_WINDOW_CLOSE " " + buttonText; + } else { + buttonText = ICON_FA_EXTERNAL_LINK_SQUARE " " + buttonText; + } + if (Button(buttonText.c_str(), options)) { + windowPtr->ToggleVisibility(); + dirty = true; + } + ImGui::PopStyleVar(); + return dirty; + } + + void PushStyleCheckbox(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.3f)); + ImGui::PushStyleColor(ImGuiCol_CheckMark, ImVec4(1.0f, 1.0f, 1.0f, 0.7f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 6.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 5.0f); + } + + void PopStyleCheckbox() { + ImGui::PopStyleVar(3); + ImGui::PopStyleColor(5); + } + + bool Checkbox(const char* label, bool* value, const CheckboxOptions& options) { + ImGui::PushID(label); + bool dirty = false; + float startX = ImGui::GetCursorPosX(); + std::string invisibleLabelStr = "##" + std::string(label); + const char* invisibleLabel = invisibleLabelStr.c_str(); + ImGui::BeginDisabled(options.disabled); + PushStyleCheckbox(options.color); + if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Near || options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x * 2 - ImGui::GetStyle().ItemSpacing.x); + } else if (options.labelPosition == LabelPosition::Above) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x); + ImGui::Text(label); + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x * 2 - ImGui::GetStyle().ItemSpacing.x); + } + } else if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text(label); + } + } + dirty = ImGui::Checkbox(invisibleLabel, value); + if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x - ImGui::GetStyle().FramePadding.x * 2 - ImGui::GetStyle().ItemSpacing.x * 2); + ImGui::Text(label); + } else if (options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(); + ImGui::SetCursorPosX(startX); + ImGui::Text(label); + } + } else if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + ImGui::Text(label); + } else if (options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x); + ImGui::Text(label); + } + } + PopStyleCheckbox(); + ImGui::EndDisabled(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(options.disabledTooltip, "") != 0) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip)); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(options.tooltip, "") != 0) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip)); + } + ImGui::PopID(); + return dirty; + } + + bool CVarCheckbox(const char* label, const char* cvarName, const CheckboxOptions& options) { + bool dirty = false; + bool value = (bool)CVarGetInteger(cvarName, options.defaultValue); + if (Checkbox(label, &value, options)) { + CVarSetInteger(cvarName, value); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + dirty = true; + } + return dirty; + } + + void PushStyleCombobox(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(color.x, color.y, color.z, 0.8f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(color.x, color.y, color.z, 0.5f)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(color.x, color.y, color.z, 0.6f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_PopupRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 6.0f)); + } + + void PopStyleCombobox() { + ImGui::PopStyleVar(4); + ImGui::PopStyleColor(9); + } + + bool Combobox(const char* label, uint8_t* value, std::span comboArray, const ComboboxOptions& options) { + bool dirty = false; + float startX = ImGui::GetCursorPosX(); + std::string invisibleLabelStr = "##" + std::string(label); + const char* invisibleLabel = invisibleLabelStr.c_str(); + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleCombobox(options.color); + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text(label); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } else if (options.labelPosition == LabelPosition::Near) { + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x - ImGui::GetStyle().ItemSpacing.x * 2); + } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + ImGui::SetNextItemWidth(ImGui::CalcTextSize(comboArray[*value]).x + ImGui::GetStyle().FramePadding.x * 4 + ImGui::GetStyle().ItemSpacing.x); + } + } else if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x); + ImGui::Text(label); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } else if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(ImGui::CalcTextSize(label).x + ImGui::GetStyle().ItemSpacing.x * 2); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } else if (options.labelPosition == LabelPosition::Far || options.labelPosition == LabelPosition::None) { + float width = ImGui::CalcTextSize(comboArray[*value]).x + ImGui::GetStyle().FramePadding.x * 4; + ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } + } + if (ImGui::BeginCombo(invisibleLabel, comboArray[*value], options.flags)) { + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(10.0f, 10.0f)); + for (uint8_t i = 0; i < comboArray.size(); i++) { + if (strlen(comboArray[i]) > 1) { + if (ImGui::Selectable(comboArray[i], i == *value)) { + *value = i; + dirty = true; + } + } + } + ImGui::PopStyleVar(); + ImGui::EndCombo(); + } + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Near) { + ImGui::SameLine(); + ImGui::Text(label); + } else if (options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x); + ImGui::Text(label); + } + } else if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Near || options.labelPosition == LabelPosition::Far) { + ImGui::SameLine(startX); + ImGui::Text(label); + } + } + PopStyleCombobox(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(options.disabledTooltip, "") != 0) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip)); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(options.tooltip, "") != 0) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip)); + } + ImGui::PopID(); + return dirty; + } + + bool CVarCombobox(const char* label, const char* cvarName, std::span comboArray, const ComboboxOptions& options) { + bool dirty = false; + uint8_t value = (uint8_t)CVarGetInteger(cvarName, options.defaultIndex); + if (Combobox(label, &value, comboArray, options)) { + CVarSetInteger(cvarName, value); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + dirty = true; + } + return dirty; + } + + void PushStyleSlider(const ImVec4& color) { + ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(color.x, color.y, color.z, 1.0f)); + ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(1.0, 1.0, 1.0, 0.4f)); + ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, ImVec4(1.0, 1.0, 1.0, 0.5f)); + ImGui::PushStyleVar(ImGuiStyleVar_GrabRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 8.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f); + } + + void PopStyleSlider() { + ImGui::PopStyleVar(4); + ImGui::PopStyleColor(6); + } + + bool SliderInt(const char* label, int32_t* value, int32_t min, int32_t max, const IntSliderOptions& options) { + bool dirty = false; + std::string invisibleLabelStr = "##" + std::string(label); + const char* invisibleLabel = invisibleLabelStr.c_str(); + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleSlider(options.color); + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text(label, *value); + } + } else if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x); + ImGui::Text(label, *value); + } + } + if (options.showButtons) { + if (Button("-", { .color = options.color, .size = Sizes::Inline }) && *value > min) { + *value -= options.step; + if (*value < min) *value = min; + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + dirty = true; + } + ImGui::SameLine(0, 3.0f); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - (ImGui::CalcTextSize("+").x + 20.0f + 3.0f)); + } else { + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } + if (ImGui::SliderScalar(invisibleLabel, ImGuiDataType_S32, value, &min, &max, options.format, options.flags)) { + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + dirty = true; + } + if (options.showButtons) { + ImGui::SameLine(0, 3.0f); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (Button("+", { .color = options.color, .size = Sizes::Inline }) && *value < max) { + *value += options.step; + if (*value > max) *value = max; + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + dirty = true; + } + } + PopStyleSlider(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(options.disabledTooltip, "") != 0) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip)); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(options.tooltip, "") != 0) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip)); + } + ImGui::PopID(); + return dirty; + } + + bool CVarSliderInt(const char* label, const char* cvarName, int32_t min, int32_t max, const int32_t defaultValue, const IntSliderOptions& options) { + bool dirty = false; + int32_t value = CVarGetInteger(cvarName, defaultValue); + if (SliderInt(label, &value, min, max, options)) { + CVarSetInteger(cvarName, value); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + dirty = true; + } + return dirty; + } + + bool SliderFloat(const char* label, float* value, float min, float max, const FloatSliderOptions& options) { + bool dirty = false; + std::string invisibleLabelStr = "##" + std::string(label); + const char* invisibleLabel = invisibleLabelStr.c_str(); + float valueToDisplay = options.isPercentage ? *value * 100.0f : *value; + float maxToDisplay = options.isPercentage ? max * 100.0f : max; + float minToDisplay = options.isPercentage ? min * 100.0f : min; + ImGui::PushID(label); + ImGui::BeginGroup(); + ImGui::BeginDisabled(options.disabled); + PushStyleSlider(options.color); + if (options.alignment == ComponentAlignment::Left) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::Text(label, valueToDisplay); + } + } else if (options.alignment == ComponentAlignment::Right) { + if (options.labelPosition == LabelPosition::Above) { + ImGui::NewLine(); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(label).x); + ImGui::Text(label, valueToDisplay); + } + } + if (options.showButtons) { + if (Button("-", { .color = options.color, .size = Sizes::Inline }) && *value > min) { + *value -= options.step; + if (*value < min) *value = min; + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + dirty = true; + } + ImGui::SameLine(0, 3.0f); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - (ImGui::CalcTextSize("+").x + 20.0f + 3.0f)); + } else { + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + } + if (ImGui::SliderScalar(invisibleLabel, ImGuiDataType_Float, &valueToDisplay, &minToDisplay, &maxToDisplay, options.format, options.flags)) { + *value = options.isPercentage ? valueToDisplay / 100.0f : valueToDisplay; + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + dirty = true; + } + if (options.showButtons) { + ImGui::SameLine(0, 3.0f); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (Button("+", { .color = options.color, .size = Sizes::Inline }) && *value < max) { + *value += options.step; + if (*value > max) *value = max; + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + dirty = true; + } + } + PopStyleSlider(); + ImGui::EndDisabled(); + ImGui::EndGroup(); + if (options.disabled && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(options.disabledTooltip, "") != 0) { + ImGui::SetTooltip("%s", WrappedText(options.disabledTooltip)); + } else if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && strcmp(options.tooltip, "") != 0) { + ImGui::SetTooltip("%s", WrappedText(options.tooltip)); + } + ImGui::PopID(); + return dirty; + } + + bool CVarSliderFloat(const char* label, const char* cvarName, float min, float max, const float defaultValue, const FloatSliderOptions& options) { + bool dirty = false; + float value = CVarGetFloat(cvarName, defaultValue); + if (SliderFloat(label, &value, min, max, options)) { + CVarSetFloat(cvarName, value); + LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick(); + dirty = true; + } + return dirty; + } } diff --git a/mm/2s2h/UIWidgets.hpp b/mm/2s2h/UIWidgets.hpp index b18487977..56136f99a 100644 --- a/mm/2s2h/UIWidgets.hpp +++ b/mm/2s2h/UIWidgets.hpp @@ -13,6 +13,7 @@ #include #include #include +#include namespace UIWidgets { @@ -96,6 +97,125 @@ namespace UIWidgets { void DrawFlagArray16(const std::string& name, uint16_t& flags); void DrawFlagArray8(const std::string& name, uint8_t& flags); bool StateButton(const char* str_id, const char* label); + + // UIWidgets V2 + + namespace Colors { + const ImVec4 White = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + const ImVec4 Gray = ImVec4(0.4f, 0.4f, 0.4f, 1.0f); + const ImVec4 DarkGray = ImVec4(0.1f, 0.1f, 0.1f, 1.0f); + const ImVec4 Indigo = ImVec4(0.24f, 0.31f, 0.71f, 1.0f); + const ImVec4 Red = ImVec4(0.5f, 0.0f, 0.0f, 1.0f); + const ImVec4 DarkRed = ImVec4(0.3f, 0.0f, 0.0f, 1.0f); + const ImVec4 LightGreen = ImVec4(0.0f, 0.7f, 0.0f, 1.0f); + const ImVec4 Green = ImVec4(0.0f, 0.5f, 0.0f, 1.0f); + const ImVec4 DarkGreen = ImVec4(0.0f, 0.3f, 0.0f, 1.0f); + const ImVec4 Yellow = ImVec4(1.0f, 0.627f, 0.0f, 1.0f); + }; + + namespace Sizes { + const ImVec2 Inline = ImVec2(0.0f, 0.0f); + const ImVec2 Fill = ImVec2(-1.0f, 0.0f); + } + + enum LabelPosition { + Near, + Far, + Above, + None, + Within, + }; + + enum ComponentAlignment { + Left, + Right, + }; + + void PushStyleMenu(const ImVec4& color = Colors::Indigo); + void PopStyleMenu(); + bool BeginMenu(const char* label, const ImVec4& color = Colors::Indigo); + + void PushStyleMenuItem(const ImVec4& color = Colors::Indigo); + void PopStyleMenuItem(); + bool MenuItem(const char* label, const char* shortcut = NULL, const ImVec4& color = Colors::Indigo); + + struct ButtonOptions { + const ImVec4 color = Colors::Gray; + const ImVec2 size = Sizes::Fill; + const char* tooltip = ""; + bool disabled = false; + const char* disabledTooltip = ""; + }; + + void PushStyleButton(const ImVec4& color = Colors::Gray); + void PopStyleButton(); + bool Button(const char* label, const ButtonOptions& options = {}); + bool WindowButton(const char* label, const char* cvarName, std::shared_ptr windowPtr, const ButtonOptions& options = {}); + + struct CheckboxOptions { + const ImVec4 color = Colors::Indigo; + const char* tooltip = ""; + bool disabled = false; + const char* disabledTooltip = ""; + bool defaultValue = false; // Only applicable to CVarCheckbox + ComponentAlignment alignment = ComponentAlignment::Left; + LabelPosition labelPosition = LabelPosition::Near; + }; + + void PushStyleCheckbox(const ImVec4& color = Colors::Indigo); + void PopStyleCheckbox(); + bool Checkbox(const char* label, bool* v, const CheckboxOptions& options = {}); + bool CVarCheckbox(const char* label, const char* cvarName, const CheckboxOptions& options = {}); + + struct ComboboxOptions { + const ImVec4 color = Colors::Indigo; + const char* tooltip = ""; + bool disabled = false; + const char* disabledTooltip = ""; + uint32_t defaultIndex = 0; // Only applicable to CVarCombobox + ComponentAlignment alignment = ComponentAlignment::Left; + LabelPosition labelPosition = LabelPosition::Above; + ImGuiComboFlags flags = 0; + }; + + void PushStyleCombobox(const ImVec4& color = Colors::Indigo); + void PopStyleCombobox(); + bool Combobox(const char* label, uint8_t* value, std::span comboArray, const ComboboxOptions& options = {}); + bool CVarCombobox(const char* label, const char* cvarName, std::span comboArray, const ComboboxOptions& options = {}); + + struct IntSliderOptions { + const ImVec4 color = Colors::Gray; + const char* tooltip = ""; + bool disabled = false; + const char* disabledTooltip = ""; + bool showButtons = true; + ImGuiSliderFlags flags = 0; + const char* format = "%d"; + const uint32_t step = 1; + ComponentAlignment alignment = ComponentAlignment::Left; + LabelPosition labelPosition = LabelPosition::Above; + }; + + struct FloatSliderOptions { + const ImVec4 color = Colors::Gray; + const char* tooltip = ""; + bool disabled = false; + const char* disabledTooltip = ""; + bool showButtons = true; + ImGuiSliderFlags flags = 0; + const char* format = "%f"; + const float step = 0.01f; + bool isPercentage = false; // Multiplies visual value by 100 + ComponentAlignment alignment = ComponentAlignment::Left; + LabelPosition labelPosition = LabelPosition::Above; + }; + + void PushStyleSlider(const ImVec4& color = Colors::Indigo); + void PopStyleSlider(); + bool SliderInt(const char* label, int32_t* value, int32_t min, int32_t max, const IntSliderOptions& options = {}); + bool CVarSliderInt(const char* label, const char* cvarName, int32_t min, int32_t max, const int32_t defaultValue, const IntSliderOptions& options = {}); + bool SliderFloat(const char* label, float* value, float min, float max, const FloatSliderOptions& options = {}); + bool CVarSliderFloat(const char* label, const char* cvarName, float min, float max, const float defaultValue, const FloatSliderOptions& options = {}); } #endif /* UIWidgets_hpp */ diff --git a/mm/CMakeLists.txt b/mm/CMakeLists.txt index 192ef0cab..c50fd207c 100644 --- a/mm/CMakeLists.txt +++ b/mm/CMakeLists.txt @@ -135,6 +135,13 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") set_source_files_properties(2s2h/BenPort.cpp PROPERTIES COMPILE_FLAGS "/utf-8") endif() +# 2s2h/DeveloperTools {{{ +file(GLOB_RECURSE soh__DeveloperTools RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + "2s2h/DeveloperTools/*.h" + "2s2h/DeveloperTools/*.cpp" +) +# }}} + # soh/config {{{ #file(GLOB_RECURSE soh__config RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} # "soh/config/*.h" @@ -260,6 +267,7 @@ set(ALL_FILES ${Header_Files__include} ${soh__} ${libultra_headers} + ${soh__DeveloperTools} # ${soh__config} # ${soh__Enhancements} ${soh__Extractor} diff --git a/mm/include/variables.h b/mm/include/variables.h index 8f25d218e..9141bd540 100644 --- a/mm/include/variables.h +++ b/mm/include/variables.h @@ -173,8 +173,8 @@ extern u8 gUpgradeShifts[8]; extern u16 gUpgradeCapacities[][4]; extern u32 gGsFlagsMask[]; extern u32 gGsFlagsShift[]; -extern TexturePtr gItemIcons[]; -extern u8 gItemSlots[]; +extern TexturePtr gItemIcons[131]; +extern u8 gItemSlots[77]; extern s16 gItemPrices[]; extern u16 gSceneIdsPerRegion[11][27]; extern u8 gPlayerFormItemRestrictions[PLAYER_FORM_MAX][114]; diff --git a/mm/src/code/code_8012EC80.c b/mm/src/code/code_8012EC80.c index 8d3d2dcf9..c05512573 100644 --- a/mm/src/code/code_8012EC80.c +++ b/mm/src/code/code_8012EC80.c @@ -232,7 +232,7 @@ TexturePtr gItemIcons[] = { }; // Used to map item IDs to inventory slots -u8 gItemSlots[] = { +u8 gItemSlots[77] = { SLOT_OCARINA, // ITEM_OCARINA_OF_TIME SLOT_BOW, // ITEM_BOW SLOT_ARROW_FIRE, // ITEM_ARROW_FIRE