Add flag, equipment, and quest status editors (#164)

This commit is contained in:
Rozelette 2022-04-17 10:24:43 -05:00 committed by GitHub
parent 1dcd24e7e2
commit a11038f515
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 1281 additions and 309 deletions

View File

@ -179,21 +179,46 @@ namespace SohImGui {
stbi_image_free(img_data);
}
void LoadResource(const std::string& name, const std::string& path) {
void LoadResource(const std::string& name, const std::string& path, const ImVec4& tint) {
GfxRenderingAPI* api = gfx_get_current_rendering_api();
const auto res = static_cast<Ship::Texture*>(GlobalCtx2::GetInstance()->GetResourceManager()->LoadResource(normalize(path)).get());
if (res->texType != Ship::TextureType::RGBA32bpp) {
std::vector<uint8_t> texBuffer;
texBuffer.reserve(res->width * res->height * 4);
switch (res->texType) {
case Ship::TextureType::RGBA32bpp:
texBuffer.assign(res->imageData, res->imageData + (res->width * res->height * 4));
break;
case Ship::TextureType::GrayscaleAlpha8bpp:
for (int32_t i = 0; i < res->width * res->height; i++) {
uint8_t ia = res->imageData[i];
uint8_t color = ((ia >> 4) & 0xF) * 255 / 15;
uint8_t alpha = (ia & 0xF) * 255 / 15;
texBuffer.push_back(color);
texBuffer.push_back(color);
texBuffer.push_back(color);
texBuffer.push_back(alpha);
}
break;
default:
// TODO convert other image types
SPDLOG_WARN("SohImGui::LoadResource: Attempting to load unsupporting image type %s", path.c_str());
return;
}
for (size_t pixel = 0; pixel < texBuffer.size() / 4; pixel++) {
texBuffer[pixel * 4 + 0] *= tint.x;
texBuffer[pixel * 4 + 1] *= tint.y;
texBuffer[pixel * 4 + 2] *= tint.z;
texBuffer[pixel * 4 + 3] *= tint.w;
}
const auto asset = new GameAsset{ api->new_texture() };
api->select_texture(0, asset->textureId);
api->set_sampler_parameters(0, false, 0, 0);
api->upload_texture(res->imageData, res->width, res->height);
api->upload_texture(texBuffer.data(), res->width, res->height);
DefaultAssets[name] = asset;
}

View File

@ -1,5 +1,6 @@
#pragma once
#include "Lib/ImGui/imgui.h"
#include "SohConsole.h"
struct GameAsset {
@ -63,7 +64,7 @@ namespace SohImGui {
void ShowCursor(bool hide, Dialogues w);
void BindCmd(const std::string& cmd, CommandEntry entry);
void AddWindow(const std::string& category, const std::string& name, WindowDrawFunc drawFunc);
void LoadResource(const std::string& name, const std::string& path);
void LoadResource(const std::string& name, const std::string& path, const ImVec4& tint = ImVec4(1, 1, 1, 1));
ImTextureID GetTextureByID(int id);
ImTextureID GetTextureByName(const std::string& name);
}

View File

@ -185,6 +185,7 @@
<ClCompile Include="soh\gu_pc.c" />
<ClCompile Include="soh\OTRGlobals.cpp" />
<ClCompile Include="soh\stubs.c" />
<ClCompile Include="soh\util.cpp" />
<ClCompile Include="soh\z_message_OTR.cpp" />
<ClCompile Include="soh\z_play_otr.cpp" />
<ClCompile Include="soh\z_scene_otr.cpp" />
@ -928,6 +929,7 @@
<ClInclude Include="soh\Enhancements\debugger\debugSaveEditor.h" />
<ClInclude Include="soh\gameconsole.h" />
<ClInclude Include="soh\OTRGlobals.h" />
<ClInclude Include="soh\util.h" />
<ClInclude Include="src\overlays\actors\ovl_Arms_Hook\z_arms_hook.h" />
<ClInclude Include="src\overlays\actors\ovl_Arrow_Fire\z_arrow_fire.h" />
<ClInclude Include="src\overlays\actors\ovl_Arrow_Ice\z_arrow_ice.h" />

View File

@ -2181,6 +2181,9 @@
<ClCompile Include="soh\Enhancements\debugger\debugSaveEditor.cpp">
<Filter>Source Files\soh\Enhancements\debugger</Filter>
</ClCompile>
<ClCompile Include="soh\util.cpp">
<Filter>Source Files\soh</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\overlays\actors\ovl_kaleido_scope\z_kaleido_scope.h">
@ -3728,6 +3731,9 @@
<ClInclude Include="soh\Enhancements\debugger\debugSaveEditor.h">
<Filter>Header Files\soh\Enhancements\debugger</Filter>
</ClInclude>
<ClInclude Include="soh\util.h">
<Filter>Header Files\soh</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="include\macro.inc">

View File

@ -1,6 +1,8 @@
#include "debugSaveEditor.h"
#include "../../util.h"
#include "../libultraship/SohImGuiImpl.h"
#include <array>
#include <bit>
#include <map>
#include <string>
@ -13,18 +15,20 @@ extern "C" {
extern GlobalContext* gGlobalCtx;
#include "textures/icon_item_static/icon_item_static.h"
#include "textures/icon_item_24_static/icon_item_24_static.h"
}
typedef struct {
uint32_t id;
std::string name;
std::string nameFaded;
std::string texturePath;
} ItemMapEntry;
#define ITEM_MAP_ENTRY(id) \
{ \
id, { \
id, #id, static_cast<char*>(gItemIcons[id]) \
id, #id, #id "_Faded", static_cast<char*>(gItemIcons[id]) \
} \
}
@ -120,6 +124,10 @@ std::map<uint32_t, ItemMapEntry> itemMapping = {
ITEM_MAP_ENTRY(ITEM_WALLET_GIANT),
ITEM_MAP_ENTRY(ITEM_SEEDS),
ITEM_MAP_ENTRY(ITEM_FISHING_POLE),
ITEM_MAP_ENTRY(ITEM_KEY_BOSS),
ITEM_MAP_ENTRY(ITEM_COMPASS),
ITEM_MAP_ENTRY(ITEM_DUNGEON_MAP),
ITEM_MAP_ENTRY(ITEM_KEY_SMALL),
};
// Maps entries in the GS flag array to the area name it represents
@ -147,6 +155,7 @@ std::vector<std::string> gsMapping = {
"Gerudo Fortress",
"Desert Colossus, Haunted Wasteland",
};
extern "C" u8 gAreaGsFlags[];
extern "C" u8 gAmmoItems[];
@ -158,6 +167,63 @@ u8 gAllAmmoItems[] = {
ITEM_BOOMERANG, ITEM_LENS, ITEM_BEAN, ITEM_HAMMER,
};
typedef struct {
uint32_t id;
std::string name;
std::string nameFaded;
std::string texturePath;
} QuestMapEntry;
#define QUEST_MAP_ENTRY(id, tex) \
{ \
id, { \
id, #id, #id "_Faded", tex \
} \
}
// Maps quest items ids to info for use in ImGui
std::map<uint32_t, QuestMapEntry> questMapping = {
QUEST_MAP_ENTRY(QUEST_MEDALLION_FOREST, gForestMedallionIconTex),
QUEST_MAP_ENTRY(QUEST_MEDALLION_FIRE, gFireMedallionIconTex),
QUEST_MAP_ENTRY(QUEST_MEDALLION_WATER, gWaterMedallionIconTex),
QUEST_MAP_ENTRY(QUEST_MEDALLION_SPIRIT, gSpiritMedallionIconTex),
QUEST_MAP_ENTRY(QUEST_MEDALLION_SHADOW, gShadowMedallionIconTex),
QUEST_MAP_ENTRY(QUEST_MEDALLION_LIGHT, gLightMedallionIconTex),
QUEST_MAP_ENTRY(QUEST_KOKIRI_EMERALD, gKokiriEmeraldIconTex),
QUEST_MAP_ENTRY(QUEST_GORON_RUBY, gGoronRubyIconTex),
QUEST_MAP_ENTRY(QUEST_ZORA_SAPPHIRE, gZoraSapphireIconTex),
QUEST_MAP_ENTRY(QUEST_STONE_OF_AGONY, gStoneOfAgonyIconTex),
QUEST_MAP_ENTRY(QUEST_GERUDO_CARD, gGerudosCardIconTex),
};
typedef struct {
uint32_t id;
std::string name;
std::string nameFaded;
ImVec4 color;
} SongMapEntry;
#define SONG_MAP_ENTRY(id, r, g, b) \
{ \
id, #id, #id "_Faded", ImVec4(r / 255.0f, g / 255.0f, b / 255.0f, 1.0f) \
}
// Maps song ids to info for use in ImGui
std::array<SongMapEntry, 12> songMapping = { {
SONG_MAP_ENTRY(QUEST_SONG_LULLABY, 255, 255, 255),
SONG_MAP_ENTRY(QUEST_SONG_EPONA, 255, 255, 255),
SONG_MAP_ENTRY(QUEST_SONG_SARIA, 255, 255, 255),
SONG_MAP_ENTRY(QUEST_SONG_SUN, 255, 255, 255),
SONG_MAP_ENTRY(QUEST_SONG_TIME, 255, 255, 255),
SONG_MAP_ENTRY(QUEST_SONG_STORMS, 255, 255, 255),
SONG_MAP_ENTRY(QUEST_SONG_MINUET, 150, 255, 100),
SONG_MAP_ENTRY(QUEST_SONG_BOLERO, 255, 80, 40),
SONG_MAP_ENTRY(QUEST_SONG_SERENADE, 100, 150, 255),
SONG_MAP_ENTRY(QUEST_SONG_REQUIEM, 255, 160, 0),
SONG_MAP_ENTRY(QUEST_SONG_NOCTURNE, 255, 100, 255),
SONG_MAP_ENTRY(QUEST_SONG_PRELUDE, 255, 240, 100),
} };
// Adds a text tooltip for the previous ImGui item
void SetLastItemHoverText(const std::string& text) {
if (ImGui::IsItemHovered()) {
@ -178,17 +244,33 @@ void InsertHelpHoverText(const std::string& text) {
}
}
void DrawSaveEditor(bool& open) {
if (!open) {
return;
}
ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("Save Editor", &open)) {
ImGui::End();
return;
// Encapsulates what is drawn by the passed-in function within a border
template<typename T>
void DrawGroupWithBorder(T&& drawFunc) {
// First group encapsulates the inner portion and border
ImGui::BeginGroup();
ImVec2 padding = ImGui::GetStyle().FramePadding;
ImVec2 p0 = ImGui::GetCursorScreenPos();
ImGui::SetCursorScreenPos(ImVec2(p0.x + padding.x, p0.y + padding.y));
// Second group encapsulates just the inner portion
ImGui::BeginGroup();
drawFunc();
ImGui::Dummy(padding);
ImGui::EndGroup();
ImVec2 p1 = ImGui::GetItemRectMax();
p1.x += padding.x;
ImVec4 borderCol = ImGui::GetStyle().Colors[ImGuiCol_Border];
ImGui::GetWindowDrawList()->AddRect(p0, p1, IM_COL32(borderCol.x * 255, borderCol.y * 255, borderCol.z * 255, borderCol.w * 255));
ImGui::EndGroup();
}
void DrawInfoTab() {
// TODO This is the bare minimum to get the player name showing
// There will need to be more effort to get it robust and editable
std::string name;
@ -201,8 +283,6 @@ void DrawSaveEditor(bool& open) {
}
name += '\0';
if (ImGui::BeginTabBar("SaveContextTabBar", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) {
if (ImGui::BeginTabItem("Info")) {
ImGui::PushItemWidth(ImGui::GetFontSize() * 6);
ImGui::Text("Name: %s", name.c_str());
@ -304,10 +384,6 @@ void DrawSaveEditor(bool& open) {
ImGui::InputScalar("Deaths", ImGuiDataType_U16, &gSaveContext.deaths);
InsertHelpHoverText("Total number of deaths");
// TODO Move to quest status screen once the page is created
ImGui::InputScalar("GS Count", ImGuiDataType_S16, &gSaveContext.inventory.gsTokens);
InsertHelpHoverText("Number of gold skulltula tokens aquired");
bool bgsFlag = gSaveContext.bgsFlag != 0;
if (ImGui::Checkbox("Has BGS", &bgsFlag)) {
gSaveContext.bgsFlag = bgsFlag;
@ -336,10 +412,9 @@ void DrawSaveEditor(bool& open) {
*/
ImGui::PopItemWidth();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Inventory")) {
void DrawInventoryTab() {
static bool restrictToValid = true;
ImGui::Checkbox("Restrict to valid items", &restrictToValid);
@ -362,8 +437,8 @@ void DrawSaveEditor(bool& open) {
uint8_t item = gSaveContext.inventory.items[index];
if (item != ITEM_NONE) {
const ItemMapEntry& slotEntry = itemMapping.find(item)->second;
if (ImGui::ImageButton(SohImGui::GetTextureByName(slotEntry.name), ImVec2(32.0f, 32.0f),
ImVec2(0, 0), ImVec2(1, 1), 0)) {
if (ImGui::ImageButton(SohImGui::GetTextureByName(slotEntry.name), ImVec2(32.0f, 32.0f), ImVec2(0, 0),
ImVec2(1, 1), 0)) {
selectedIndex = index;
ImGui::OpenPopup(itemPopupPicker);
}
@ -382,7 +457,7 @@ void DrawSaveEditor(bool& open) {
gSaveContext.inventory.items[selectedIndex] = ITEM_NONE;
ImGui::CloseCurrentPopup();
}
SetLastItemHoverText("ITEM_NONE");
SetLastItemHoverText("None");
std::vector<ItemMapEntry> possibleItems;
if (restrictToValid) {
@ -412,7 +487,7 @@ void DrawSaveEditor(bool& open) {
gSaveContext.inventory.items[selectedIndex] = slotEntry.id;
ImGui::CloseCurrentPopup();
}
SetLastItemHoverText(slotEntry.name);
SetLastItemHoverText(SohUtils::GetItemName(slotEntry.id));
}
ImGui::EndPopup();
@ -446,11 +521,180 @@ void DrawSaveEditor(bool& open) {
ImGui::PopID();
}
}
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Flags")) {
// Draw a flag bitfield as an grid of checkboxes
void DrawFlagArray(const std::string& name, uint32_t& flags) {
ImGui::PushID(name.c_str());
for (int32_t flagIndex = 0; flagIndex < 32; flagIndex++) {
if ((flagIndex % 8) != 0) {
ImGui::SameLine();
}
ImGui::PushID(flagIndex);
uint32_t bitMask = 1 << flagIndex;
bool flag = (flags & bitMask) != 0;
if (ImGui::Checkbox("##check", &flag)) {
if (flag) {
flags |= bitMask;
} else {
flags &= ~bitMask;
}
}
ImGui::PopID();
}
ImGui::PopID();
}
void DrawFlagsTab() {
if (ImGui::TreeNode("Current Scene")) {
if (gGlobalCtx != nullptr) {
ActorContext* act = &gGlobalCtx->actorCtx;
DrawGroupWithBorder([&]() {
ImGui::Text("Switch");
InsertHelpHoverText("Permanently-saved switch flags");
DrawFlagArray("Switch", act->flags.swch);
});
ImGui::SameLine();
DrawGroupWithBorder([&]() {
ImGui::Text("Temp Switch");
InsertHelpHoverText("Temporary switch flags. Unset on scene transitions");
DrawFlagArray("Temp Switch", act->flags.tempSwch);
});
DrawGroupWithBorder([&]() {
ImGui::Text("Clear");
InsertHelpHoverText("Permanently-saved room-clear flags");
DrawFlagArray("Clear", act->flags.clear);
});
ImGui::SameLine();
DrawGroupWithBorder([&]() {
ImGui::Text("Temp Clear");
InsertHelpHoverText("Temporary room-clear flags. Unset on scene transitions");
DrawFlagArray("Temp Clear", act->flags.tempClear);
});
DrawGroupWithBorder([&]() {
ImGui::Text("Collect");
InsertHelpHoverText("Permanently-saved collect flags");
DrawFlagArray("Collect", act->flags.collect);
});
ImGui::SameLine();
DrawGroupWithBorder([&]() {
ImGui::Text("Temp Collect");
InsertHelpHoverText("Temporary collect flags. Unset on scene transitions");
DrawFlagArray("Temp Collect", act->flags.tempCollect);
});
DrawGroupWithBorder([&]() {
ImGui::Text("Chest");
InsertHelpHoverText("Permanently-saved chest flags");
DrawFlagArray("Chest", act->flags.chest);
});
ImGui::SameLine();
ImGui::BeginGroup();
if (ImGui::Button("Reload Flags")) {
act->flags.swch = gSaveContext.sceneFlags[gGlobalCtx->sceneNum].swch;
act->flags.clear = gSaveContext.sceneFlags[gGlobalCtx->sceneNum].clear;
act->flags.collect = gSaveContext.sceneFlags[gGlobalCtx->sceneNum].collect;
act->flags.chest = gSaveContext.sceneFlags[gGlobalCtx->sceneNum].chest;
}
SetLastItemHoverText("Load flags from saved scene flags. Normally happens on scene load");
if (ImGui::Button("Save Flags")) {
gSaveContext.sceneFlags[gGlobalCtx->sceneNum].swch = act->flags.swch;
gSaveContext.sceneFlags[gGlobalCtx->sceneNum].clear = act->flags.clear;
gSaveContext.sceneFlags[gGlobalCtx->sceneNum].collect = act->flags.collect;
gSaveContext.sceneFlags[gGlobalCtx->sceneNum].chest = act->flags.chest;
}
SetLastItemHoverText("Save current scene flags. Normally happens on scene exit");
ImGui::EndGroup();
} else {
ImGui::Text("Current game state does not have an active scene");
}
ImGui::TreePop();
}
if (ImGui::TreeNode("Saved Scene Flags")) {
static uint32_t selectedSceneFlagMap = 0;
ImGui::Text("Map");
ImGui::SameLine();
if (ImGui::BeginCombo("##Map", SohUtils::GetSceneName(selectedSceneFlagMap).c_str())) {
for (int32_t sceneIndex = 0; sceneIndex < SCENE_ID_MAX; sceneIndex++) {
if (ImGui::Selectable(SohUtils::GetSceneName(sceneIndex).c_str())) {
selectedSceneFlagMap = sceneIndex;
}
}
ImGui::EndCombo();
}
// Don't show current scene button if there is no current scene
if (gGlobalCtx != nullptr) {
ImGui::SameLine();
if (ImGui::Button("Current")) {
selectedSceneFlagMap = gGlobalCtx->sceneNum;
}
SetLastItemHoverText("Open flags for current scene");
}
DrawGroupWithBorder([&]() {
ImGui::Text("Switch");
InsertHelpHoverText("Switch flags");
DrawFlagArray("Switch", gSaveContext.sceneFlags[selectedSceneFlagMap].swch);
});
ImGui::SameLine();
DrawGroupWithBorder([&]() {
ImGui::Text("Clear");
InsertHelpHoverText("Room-clear flags");
DrawFlagArray("Clear", gSaveContext.sceneFlags[selectedSceneFlagMap].clear);
});
DrawGroupWithBorder([&]() {
ImGui::Text("Collect");
InsertHelpHoverText("Collect flags");
DrawFlagArray("Collect", gSaveContext.sceneFlags[selectedSceneFlagMap].collect);
});
ImGui::SameLine();
DrawGroupWithBorder([&]() {
ImGui::Text("Chest");
InsertHelpHoverText("Chest flags");
DrawFlagArray("Chest", gSaveContext.sceneFlags[selectedSceneFlagMap].chest);
});
DrawGroupWithBorder([&]() {
ImGui::Text("Rooms");
InsertHelpHoverText("Flags for visted rooms");
DrawFlagArray("Rooms", gSaveContext.sceneFlags[selectedSceneFlagMap].rooms);
});
ImGui::SameLine();
DrawGroupWithBorder([&]() {
ImGui::Text("Floors");
InsertHelpHoverText("Flags for visted floors");
DrawFlagArray("Floors", gSaveContext.sceneFlags[selectedSceneFlagMap].floors);
});
ImGui::TreePop();
}
DrawGroupWithBorder([&]() {
static uint32_t selectedGsMap = 0;
ImGui::Text("Gold Skulltulas");
ImGui::Text("Map");
@ -504,10 +748,368 @@ void DrawSaveEditor(bool& open) {
}
gSaveContext.inventory.gsTokens = gsCount;
}
});
}
// TODO other flag types, like switch, clear, etc.
// These flags interact with the actor context, so it's a bit more complicated
// Draws a combo that lets you choose and upgrade value from a drop-down of text values
void DrawUpgrade(const std::string& categoryName, int32_t categoryId, const std::vector<std::string>& names) {
ImGui::Text(categoryName.c_str());
ImGui::SameLine();
ImGui::PushID(categoryName.c_str());
if (ImGui::BeginCombo("##upgrade", names[CUR_UPG_VALUE(categoryId)].c_str())) {
for (int32_t i = 0; i < names.size(); i++) {
if (ImGui::Selectable(names[i].c_str())) {
Inventory_ChangeUpgrade(categoryId, i);
}
}
ImGui::EndCombo();
}
ImGui::PopID();
SetLastItemHoverText(categoryName.c_str());
}
// Draws a combo that lets you choose and upgrade value from a popup grid of icons
void DrawUpgradeIcon(const std::string& categoryName, int32_t categoryId, const std::vector<uint8_t>& items) {
static const char* upgradePopupPicker = "upgradePopupPicker";
ImGui::PushID(categoryName.c_str());
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1, 1, 1, 0));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
uint8_t item = items[CUR_UPG_VALUE(categoryId)];
if (item != ITEM_NONE) {
const ItemMapEntry& slotEntry = itemMapping[item];
if (ImGui::ImageButton(SohImGui::GetTextureByName(slotEntry.name), ImVec2(32.0f, 32.0f), ImVec2(0, 0),
ImVec2(1, 1), 0)) {
ImGui::OpenPopup(upgradePopupPicker);
}
} else {
if (ImGui::Button("##itemNone", ImVec2(32.0f, 32.0f))) {
ImGui::OpenPopup(upgradePopupPicker);
}
}
ImGui::PopStyleVar();
ImGui::PopStyleColor();
SetLastItemHoverText(categoryName.c_str());
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
if (ImGui::BeginPopup(upgradePopupPicker)) {
for (int32_t pickerIndex = 0; pickerIndex < items.size(); pickerIndex++) {
if ((pickerIndex % 8) != 0) {
ImGui::SameLine();
}
if (items[pickerIndex] == ITEM_NONE) {
if (ImGui::Button("##upgradePopupPicker", ImVec2(32.0f, 32.0f))) {
Inventory_ChangeUpgrade(categoryId, pickerIndex);
ImGui::CloseCurrentPopup();
}
SetLastItemHoverText("None");
} else {
const ItemMapEntry& slotEntry = itemMapping[items[pickerIndex]];
if (ImGui::ImageButton(SohImGui::GetTextureByName(slotEntry.name), ImVec2(32.0f, 32.0f), ImVec2(0, 0),
ImVec2(1, 1), 0)) {
Inventory_ChangeUpgrade(categoryId, pickerIndex);
ImGui::CloseCurrentPopup();
}
SetLastItemHoverText(SohUtils::GetItemName(slotEntry.id));
}
}
ImGui::EndPopup();
}
ImGui::PopStyleVar();
ImGui::PopID();
}
void DrawEquipmentTab() {
const std::vector<uint8_t> equipmentValues = {
ITEM_SWORD_KOKIRI, ITEM_SWORD_MASTER, ITEM_SWORD_BGS, ITEM_SWORD_BROKEN,
ITEM_SHIELD_DEKU, ITEM_SHIELD_HYLIAN, ITEM_SHIELD_MIRROR, ITEM_NONE,
ITEM_TUNIC_KOKIRI, ITEM_TUNIC_GORON, ITEM_TUNIC_ZORA, ITEM_NONE,
ITEM_BOOTS_KOKIRI, ITEM_BOOTS_IRON, ITEM_BOOTS_HOVER, ITEM_NONE,
};
for (int32_t i = 0; i < equipmentValues.size(); i++) {
// Skip over unused 4th slots for shields, boots, and tunics
if (equipmentValues[i] == ITEM_NONE) {
continue;
}
if ((i % 4) != 0) {
ImGui::SameLine();
}
ImGui::PushID(i);
uint32_t bitMask = 1 << i;
bool hasEquip = (bitMask & gSaveContext.inventory.equipment) != 0;
const ItemMapEntry& entry = itemMapping[equipmentValues[i]];
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
if (ImGui::ImageButton(SohImGui::GetTextureByName(hasEquip ? entry.name : entry.nameFaded),
ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1), 0)) {
if (hasEquip) {
gSaveContext.inventory.equipment &= ~bitMask;
} else {
gSaveContext.inventory.equipment |= bitMask;
}
}
ImGui::PopStyleColor();
ImGui::PopID();
SetLastItemHoverText(SohUtils::GetItemName(entry.id));
}
const std::vector<uint8_t> bulletBagValues = {
ITEM_NONE,
ITEM_BULLET_BAG_30,
ITEM_BULLET_BAG_40,
ITEM_BULLET_BAG_50,
};
DrawUpgradeIcon("Bullet Bag", UPG_BULLET_BAG, bulletBagValues);
ImGui::SameLine();
const std::vector<uint8_t> quiverValues = {
ITEM_NONE,
ITEM_QUIVER_30,
ITEM_QUIVER_40,
ITEM_QUIVER_50,
};
DrawUpgradeIcon("Quiver", UPG_QUIVER, quiverValues);
ImGui::SameLine();
const std::vector<uint8_t> bombBagValues = {
ITEM_NONE,
ITEM_BOMB_BAG_20,
ITEM_BOMB_BAG_30,
ITEM_BOMB_BAG_40,
};
DrawUpgradeIcon("Bomb Bag", UPG_BOMB_BAG, bombBagValues);
ImGui::SameLine();
const std::vector<uint8_t> scaleValues = {
ITEM_NONE,
ITEM_SCALE_SILVER,
ITEM_SCALE_GOLDEN,
};
DrawUpgradeIcon("Scale", UPG_SCALE, scaleValues);
ImGui::SameLine();
const std::vector<uint8_t> strengthValues = {
ITEM_NONE,
ITEM_BRACELET,
ITEM_GAUNTLETS_SILVER,
ITEM_GAUNTLETS_GOLD,
};
DrawUpgradeIcon("Strength", UPG_STRENGTH, strengthValues);
// There is no icon for child wallet, so default to a text list
const std::vector<std::string> walletNames = {
"Child (99)",
"Adult (200)",
"Giant (500)",
};
DrawUpgrade("Wallet", UPG_WALLET, walletNames);
const std::vector<std::string> stickNames = {
"None",
"10",
"20",
"30",
};
DrawUpgrade("Sticks", UPG_STICKS, stickNames);
const std::vector<std::string> nutNames = {
"None",
"20",
"30",
"40",
};
DrawUpgrade("Deku Nuts", UPG_NUTS, nutNames);
}
// Draws a toggleable icon for a quest item that is faded when disabled
void DrawQuestItemButton(uint32_t item) {
const QuestMapEntry& entry = questMapping[item];
uint32_t bitMask = 1 << entry.id;
bool hasQuestItem = (bitMask & gSaveContext.inventory.questItems) != 0;
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
if (ImGui::ImageButton(SohImGui::GetTextureByName(hasQuestItem ? entry.name : entry.nameFaded),
ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1), 0)) {
if (hasQuestItem) {
gSaveContext.inventory.questItems &= ~bitMask;
} else {
gSaveContext.inventory.questItems |= bitMask;
}
}
ImGui::PopStyleColor();
SetLastItemHoverText(SohUtils::GetQuestItemName(entry.id));
}
// Draws a toggleable icon for a dungeon item that is faded when disabled
void DrawDungeonItemButton(uint32_t item, uint32_t scene) {
const ItemMapEntry& entry = itemMapping[item];
uint32_t bitMask = 1 << (entry.id - ITEM_KEY_BOSS); // Bitset starts at ITEM_KEY_BOSS == 0. the rest are sequential
bool hasItem = (bitMask & gSaveContext.inventory.dungeonItems[scene]) != 0;
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
if (ImGui::ImageButton(SohImGui::GetTextureByName(hasItem ? entry.name : entry.nameFaded),
ImVec2(32.0f, 32.0f), ImVec2(0, 0), ImVec2(1, 1), 0)) {
if (hasItem) {
gSaveContext.inventory.dungeonItems[scene] &= ~bitMask;
} else {
gSaveContext.inventory.dungeonItems[scene] |= bitMask;
}
}
ImGui::PopStyleColor();
SetLastItemHoverText(SohUtils::GetItemName(entry.id));
}
void DrawQuestStatusTab() {
ImGui::PushItemWidth(ImGui::GetFontSize() * 6);
for (int32_t i = QUEST_MEDALLION_FOREST; i < QUEST_MEDALLION_LIGHT + 1; i++) {
if (i != QUEST_MEDALLION_FOREST) {
ImGui::SameLine();
}
DrawQuestItemButton(i);
}
for (int32_t i = QUEST_KOKIRI_EMERALD; i < QUEST_ZORA_SAPPHIRE + 1; i++) {
if (i != QUEST_KOKIRI_EMERALD) {
ImGui::SameLine();
}
DrawQuestItemButton(i);
}
// Put Stone of Agony and Gerudo Card on the same line with a little space between them
ImGui::SameLine();
ImGui::Dummy(ImVec2(20, 0));
ImGui::SameLine();
DrawQuestItemButton(QUEST_STONE_OF_AGONY);
ImGui::SameLine();
DrawQuestItemButton(QUEST_GERUDO_CARD);
for (const SongMapEntry& entry : songMapping) {
if ((entry.id != QUEST_SONG_MINUET) && (entry.id != QUEST_SONG_LULLABY)) {
ImGui::SameLine();
}
uint32_t bitMask = 1 << entry.id;
bool hasQuestItem = (bitMask & gSaveContext.inventory.questItems) != 0;
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
if (ImGui::ImageButton(SohImGui::GetTextureByName(hasQuestItem ? entry.name : entry.nameFaded),
ImVec2(16.0f, 24.0f), ImVec2(0, 0), ImVec2(1, 1), 0)) {
if (hasQuestItem) {
gSaveContext.inventory.questItems &= ~bitMask;
} else {
gSaveContext.inventory.questItems |= bitMask;
}
}
ImGui::PopStyleColor();
SetLastItemHoverText(SohUtils::GetQuestItemName(entry.id));
}
ImGui::InputScalar("GS Count", ImGuiDataType_S16, &gSaveContext.inventory.gsTokens);
InsertHelpHoverText("Number of gold skulltula tokens aquired");
uint32_t bitMask = 1 << QUEST_SKULL_TOKEN;
bool gsUnlocked = (bitMask & gSaveContext.inventory.questItems) != 0;
if (ImGui::Checkbox("GS unlocked", &gsUnlocked)) {
if (gsUnlocked) {
gSaveContext.inventory.questItems |= bitMask;
} else {
gSaveContext.inventory.questItems &= ~bitMask;
}
}
InsertHelpHoverText("If unlocked, enables showing the gold skulltula count in the quest status menu");
int32_t pohCount = (gSaveContext.inventory.questItems & 0xF0000000) >> 28;
if (ImGui::BeginCombo("PoH count", std::to_string(pohCount).c_str())) {
for (int32_t i = 0; i < 4; i++) {
if (ImGui::Selectable(std::to_string(i).c_str(), pohCount == i)) {
gSaveContext.inventory.questItems &= ~0xF0000000;
gSaveContext.inventory.questItems |= (i << 28);
}
}
ImGui::EndCombo();
}
InsertHelpHoverText("The number of pieces of heart acquired towards the next heart container");
DrawGroupWithBorder([&]() {
ImGui::Text("Dungeon Items");
static int32_t dungeonItemsScene = SCENE_YDAN;
ImGui::PushItemWidth(-ImGui::GetWindowWidth() * 0.35f);
if (ImGui::BeginCombo("##DungeonSelect", SohUtils::GetSceneName(dungeonItemsScene).c_str())) {
for (int32_t dungeonIndex = SCENE_YDAN; dungeonIndex < SCENE_BDAN_BOSS + 1; dungeonIndex++) {
if (ImGui::Selectable(SohUtils::GetSceneName(dungeonIndex).c_str(),
dungeonIndex == dungeonItemsScene)) {
dungeonItemsScene = dungeonIndex;
}
}
ImGui::EndCombo();
}
ImGui::PopItemWidth();
DrawDungeonItemButton(ITEM_KEY_BOSS, dungeonItemsScene);
ImGui::SameLine();
DrawDungeonItemButton(ITEM_COMPASS, dungeonItemsScene);
ImGui::SameLine();
DrawDungeonItemButton(ITEM_DUNGEON_MAP, dungeonItemsScene);
if (dungeonItemsScene != SCENE_BDAN_BOSS) {
float lineHeight = ImGui::GetTextLineHeightWithSpacing();
ImGui::Image(SohImGui::GetTextureByName(itemMapping[ITEM_KEY_SMALL].name), ImVec2(lineHeight, lineHeight));
ImGui::SameLine();
ImGui::InputScalar("##Keys", ImGuiDataType_S8, gSaveContext.inventory.dungeonKeys + dungeonItemsScene);
} else {
// dungeonItems is size 20 but dungeonKeys is size 19, so there are no keys for the last scene (Barinade's Lair)
ImGui::Text("Barinade's Lair does not have small keys");
}
});
ImGui::PopItemWidth();
}
void DrawSaveEditor(bool& open) {
if (!open) {
return;
}
ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("Save Editor", &open)) {
ImGui::End();
return;
}
if (ImGui::BeginTabBar("SaveContextTabBar", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) {
if (ImGui::BeginTabItem("Info")) {
DrawInfoTab();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Inventory")) {
DrawInventoryTab();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Flags")) {
DrawFlagsTab();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Equipment")) {
DrawEquipmentTab();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Quest Status")) {
DrawQuestStatusTab();
ImGui::EndTabItem();
}
@ -523,5 +1125,16 @@ void InitSaveEditor() {
// Load item icons into ImGui
for (const auto& entry : itemMapping) {
SohImGui::LoadResource(entry.second.name, entry.second.texturePath);
SohImGui::LoadResource(entry.second.nameFaded, entry.second.texturePath, ImVec4(1, 1, 1, 0.3f));
}
for (const auto& entry : questMapping) {
SohImGui::LoadResource(entry.second.name, entry.second.texturePath);
SohImGui::LoadResource(entry.second.nameFaded, entry.second.texturePath, ImVec4(1, 1, 1, 0.3f));
}
for (const auto& entry : songMapping) {
SohImGui::LoadResource(entry.name, gSongNoteTex, entry.color);
ImVec4 fadedCol = entry.color;
fadedCol.w = 0.3f;
SohImGui::LoadResource(entry.nameFaded, gSongNoteTex, fadedCol);
}
}

314
soh/soh/util.cpp Normal file
View File

@ -0,0 +1,314 @@
#include "util.h"
#include <vector>
std::vector<std::string> sceneNames = {
"Inside the Deku Tree",
"Dodongo's Cavern",
"Inside Jabu-Jabu's Belly",
"Forest Temple",
"Fire Temple",
"Water Temple",
"Spirit Temple",
"Shadow Temple",
"Bottom of the Well",
"Ice Cavern",
"Ganon's Tower",
"Gerudo Training Ground",
"Thieves' Hideout",
"Inside Ganon's Castle",
"Ganon's Tower (Collapsing)",
"Inside Ganon's Castle (Collapsing)",
"Treasure Box Shop",
"Gohma's Lair",
"King Dodongo's Lair",
"Barinade's Lair",
"Phantom Ganon's Lair",
"Volvagia's Lair",
"Morpha's Lair",
"Twinrova's Lair & Nabooru's Mini-Boss Room",
"Bongo Bongo's Lair",
"Ganondorf's Lair",
"Tower Collapse Exterior",
"Market Entrance (Child - Day)",
"Market Entrance (Child - Night)",
"Market Entrance (Ruins)",
"Back Alley (Child - Day)",
"Back Alley (Child - Night)",
"Market (Child - Day)",
"Market (Child - Night)",
"Market (Ruins)",
"Temple of Time Exterior (Child - Day)",
"Temple of Time Exterior (Child - Night)",
"Temple of Time Exterior (Ruins)",
"Know-It-All Brothers' House",
"House of Twins",
"Mido's House",
"Saria's House",
"Carpenter Boss's House",
"Back Alley House (Man in Green)",
"Bazaar",
"Kokiri Shop",
"Goron Shop",
"Zora Shop",
"Kakariko Potion Shop",
"Market Potion Shop",
"Bombchu Shop",
"Happy Mask Shop",
"Link's House",
"Back Alley House (Dog Lady)",
"Stable",
"Impa's House",
"Lakeside Laboratory",
"Carpenters' Tent",
"Gravekeeper's Hut",
"Great Fairy's Fountain (Upgrades)",
"Fairy's Fountain",
"Great Fairy's Fountain (Spells)",
"Grottos",
"Grave (Redead)",
"Grave (Fairy's Fountain)",
"Royal Family's Tomb",
"Shooting Gallery",
"Temple of Time",
"Chamber of the Sages",
"Castle Hedge Maze (Day)",
"Castle Hedge Maze (Night)",
"Cutscene Map",
"Dampé's Grave & Windmill",
"Fishing Pond",
"Castle Courtyard",
"Bombchu Bowling Alley",
"Ranch House & Silo",
"Guard House",
"Granny's Potion Shop",
"Ganon's Tower Collapse & Battle Arena",
"House of Skulltula",
"Spot 00 - Hyrule Field",
"Spot 01 - Kakariko Village",
"Spot 02 - Graveyard",
"Spot 03 - Zora's River",
"Spot 04 - Kokiri Forest",
"Spot 05 - Sacred Forest Meadow",
"Spot 06 - Lake Hylia",
"Spot 07 - Zora's Domain",
"Spot 08 - Zora's Fountain",
"Spot 09 - Gerudo Valley",
"Spot 10 - Lost Woods",
"Spot 11 - Desert Colossus",
"Spot 12 - Gerudo's Fortress",
"Spot 13 - Haunted Wasteland",
"Spot 15 - Hyrule Castle",
"Spot 16 - Death Mountain Trail",
"Spot 17 - Death Mountain Crater",
"Spot 18 - Goron City",
"Spot 20 - Lon Lon Ranch",
"Ganon's Castle Exterior",
"Jungle Gym",
"Ganondorf Test Room",
"Depth Test",
"Stalfos Mini-Boss Room",
"Stalfos Boss Room",
"Sutaru",
"Castle Hedge Maze (Early)",
"Sasa Test",
"Treasure Chest Room",
};
std::vector<std::string> itemNames = {
"Deku Stick",
"Deku Nut",
"Bomb",
"Fairy Bow",
"Fire Arrow",
"Din's Fire",
"Fairy Slingshot",
"Fairy Ocarina",
"Ocarina of Time",
"Bombchu",
"Hookshot",
"Longshot",
"Ice Arrow",
"Farore's Wind",
"Boomerang",
"Lens of Truth",
"Magic Bean",
"Megaton Hammer",
"Light Arrow",
"Nayru's Love",
"Empty Bottle",
"Red Potion",
"Green Potion",
"Blue Potion",
"Bottled Fairy",
"Fish",
"Lon Lon Milk & Bottle",
"Ruto's Letter",
"Blue Fire",
"Bug",
"Big Poe",
"Lon Lon Milk (Half)",
"Poe",
"Weird Egg",
"Chicken",
"Zelda's Letter",
"Keaton Mask",
"Skull Mask",
"Spooky Mask",
"Bunny Hood",
"Goron Mask",
"Zora Mask",
"Gerudo Mask",
"Mask of Truth",
"SOLD OUT",
"Pocket Egg",
"Pocket Cucco",
"Cojiro",
"Odd Mushroom",
"Odd Potion",
"Poacher's Saw",
"Goron's Sword (Broken)",
"Prescription",
"Eyeball Frog",
"Eye Drops",
"Claim Check",
"Fairy Bow & Fire Arrow",
"Fairy Bow & Ice Arrow",
"Fairy Bow & Light Arrow",
"Kokiri Sword",
"Master Sword",
"Giant's Knife & Biggoron's Sword",
"Deku Shield",
"Hylian Shield",
"Mirror Shield",
"Kokiri Tunic",
"Goron Tunic",
"Zora Tunic",
"Kokiri Boots",
"Iron Boots",
"Hover Boots",
"Bullet Bag (30)",
"Bullet Bag (40)",
"Bullet Bag (50)",
"Quiver (30)",
"Big Quiver (40)",
"Biggest Quiver (50)",
"Bomb Bag (20)",
"Big Bomb Bag (30)",
"Biggest Bomb Bag (40)",
"Goron's Bracelet",
"Silver Gauntlets",
"Golden Gauntlets",
"Silver Scale",
"Golden Scale",
"Giant's Knife (Broken)",
"Adult's Wallet",
"Giant's Wallet",
"Deku Seeds (5)",
"Fishing Pole",
"Minuet of Forest",
"Bolero of Fire",
"Serenade of Water",
"Requiem of Spirit",
"Nocturne of Shadow",
"Prelude of Light",
"Zelda's Lullaby",
"Epona's Song",
"Saria's Song",
"Sun's Song",
"Song of Time",
"Song of Storms",
"Forest Medallion",
"Fire Medallion",
"Water Medallion",
"Spirit Medallion",
"Shadow Medallion",
"Light Medallion",
"Kokiri's Emerald",
"Goron's Ruby",
"Zora's Sapphire",
"Stone of Agony",
"Gerudo's Card",
"Gold Skulltula Token",
"Heart Container",
"Piece of Heart [?]",
"Big Key",
"Compass",
"Dungeon Map",
"Small Key",
"Small Magic Jar",
"Large Magic Jar",
"Piece of Heart",
"[Removed]",
"[Removed]",
"[Removed]",
"[Removed]",
"[Removed]",
"[Removed]",
"[Removed]",
"Lon Lon Milk",
"Recovery Heart",
"Green Rupee",
"Blue Rupee",
"Red Rupee",
"Purple Rupee",
"Huge Rupee",
"[Removed]",
"Deku Sticks (5)",
"Deku Sticks (10)",
"Deku Nuts (5)",
"Deku Nuts (10)",
"Bombs (5)",
"Bombs (10)",
"Bombs (20)",
"Bombs (30)",
"Arrows (Small)",
"Arrows (Medium)",
"Arrows (Large)",
"Deku Seeds (30)",
"Bombchu (5)",
"Bombchu (20)",
"Deku Stick Upgrade (20)",
"Deku Stick Upgrade (30)",
"Deku Nut Upgrade (30)",
"Deku Nut Upgrade (40)",
};
std::vector<std::string> questItemNames = {
"Forest Medallion",
"Fire Medallion",
"Water Medallion",
"Spirit Medallion",
"Shadow Medallion",
"Light Medallion",
"Minuet of Forest",
"Bolero of Fire",
"Serenade of Water",
"Requiem of Spirit",
"Nocturne of Shadow",
"Prelude of Light",
"Zelda's Lullaby",
"Epona's Song",
"Saria's Song",
"Sun's Song",
"Song of Time",
"Song of Storms",
"Kokiri's Emerald",
"Goron's Ruby",
"Zora's Sapphire",
"Stone of Agony",
"Gerudo's Card",
"Gold Skulltula Token",
};
const std::string& SohUtils::GetSceneName(int32_t scene) {
return sceneNames[scene];
}
const std::string& SohUtils::GetItemName(int32_t item) {
return itemNames[item];
}
const std::string& SohUtils::GetQuestItemName(int32_t item) {
return questItemNames[item];
}

11
soh/soh/util.h Normal file
View File

@ -0,0 +1,11 @@
#pragma once
#include <string>
#include <stdint.h>
namespace SohUtils {
const std::string& GetSceneName(int32_t scene);
const std::string& GetItemName(int32_t item);
const std::string& GetQuestItemName(int32_t item);
} // namespace SohUtils