Add save editor

This commit is contained in:
rozlette 2022-04-03 14:47:24 -05:00 committed by Nicholas Estelami
parent f20ab2c260
commit 3e8c48c116
10 changed files with 641 additions and 7 deletions

View File

@ -977,8 +977,8 @@ uint16_t gfx_d3d11_get_pixel_depth_old(float x, float y) {
} // namespace
void* SohImGui::GetTextureByID(int id) {
return d3d.textures[id].resource_view.Get();
ImTextureID SohImGui::GetTextureByID(int id) {
return impl.backend == Backend::DX11 ? d3d.textures[id].resource_view.Get() : reinterpret_cast<ImTextureID>(id);
}
struct GfxRenderingAPI gfx_direct3d11_api = {

View File

@ -1,6 +1,7 @@
#include "SohImGuiImpl.h"
#include <iostream>
#include <map>
#include <utility>
#include "Archive.h"
@ -14,9 +15,11 @@
#include "TextureMod.h"
#include "Window.h"
#include "Cvar.h"
#include "Texture.h"
#include "../Fast3D/gfx_pc.h"
#include "Lib/stb/stb_image.h"
#include "Lib/Fast3D/gfx_rendering_api.h"
#include "Lib/spdlog/include/spdlog/common.h"
#include "Utils/StringHelper.h"
#ifdef ENABLE_OPENGL
@ -51,6 +54,9 @@ namespace SohImGui {
bool p_open = false;
bool needs_save = false;
std::map<std::string, std::vector<std::string>> windowCategories;
std::map<std::string, CustomWindow> customWindows;
void ImGuiWMInit() {
switch (impl.backend) {
case Backend::SDL:
@ -153,7 +159,7 @@ namespace SohImGui {
}
}
void LoadTexture(std::string name, std::string path) {
void LoadTexture(const std::string& name, const std::string& path) {
GfxRenderingAPI* api = gfx_get_current_rendering_api();
const auto res = GlobalCtx2::GetInstance()->GetResourceManager()->LoadFile(normalize(path));
@ -173,6 +179,25 @@ namespace SohImGui {
stbi_image_free(img_data);
}
void LoadResource(const std::string& name, const std::string& path) {
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) {
// TODO convert other image types
SPDLOG_WARN("SohImGui::LoadResource: Attempting to load unsupporting image type %s", path.c_str());
return;
}
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);
DefaultAssets[name] = asset;
}
void Init(WindowImpl window_impl) {
impl = window_impl;
Game::LoadSettings();
@ -219,7 +244,7 @@ namespace SohImGui {
ImGuiProcessEvent(event);
}
#define BindButton(btn, status) ImGui::Image(impl.backend == Backend::DX11 ? GetTextureByID(DefaultAssets[btn]->textureId) : (ImTextureID)(DefaultAssets[btn]->textureId), ImVec2(16.0f * scale, 16.0f * scale), ImVec2(0, 0), ImVec2(1.0f, 1.0f), ImVec4(255, 255, 255, (status) ? 255 : 0));
#define BindButton(btn, status) ImGui::Image(GetTextureByID(DefaultAssets[btn]->textureId), ImVec2(16.0f * scale, 16.0f * scale), ImVec2(0, 0), ImVec2(1.0f, 1.0f), ImVec4(255, 255, 255, (status) ? 255 : 0));
void BindAudioSlider(const char* name, const char* key, float* value, SeqPlayers playerId) {
ImGui::Text(name, static_cast<int>(100 * *(value)));
@ -278,7 +303,7 @@ namespace SohImGui {
if (ImGui::BeginMenuBar()) {
if (DefaultAssets.contains("Game_Icon")) {
ImGui::SetCursorPos(ImVec2(5, 2.5f));
ImGui::Image(impl.backend == Backend::DX11 ? GetTextureByID(DefaultAssets["Game_Icon"]->textureId) : reinterpret_cast<ImTextureID>(DefaultAssets["Game_Icon"]->textureId), ImVec2(16.0f, 16.0f));
ImGui::Image(GetTextureByID(DefaultAssets["Game_Icon"]->textureId), ImVec2(16.0f, 16.0f));
ImGui::SameLine();
ImGui::SetCursorPos(ImVec2(25, 0));
}
@ -425,6 +450,15 @@ namespace SohImGui {
ImGui::EndMenu();
}
for (const auto& category : windowCategories) {
if (ImGui::BeginMenu(category.first.c_str())) {
for (const std::string& name : category.second) {
HOOK(ImGui::MenuItem(name.c_str(), nullptr, &customWindows[name].enabled));
}
ImGui::EndMenu();
}
}
ImGui::EndMenuBar();
}
@ -527,6 +561,13 @@ namespace SohImGui {
console->Draw();
for (auto& windowIter : customWindows) {
CustomWindow& window = windowIter.second;
if (window.drawFunc != nullptr) {
window.drawFunc(window.enabled);
}
}
ImGui::Render();
ImGuiRenderDrawData(ImGui::GetDrawData());
if (UseViewports()) {
@ -538,4 +579,22 @@ namespace SohImGui {
void BindCmd(const std::string& cmd, CommandEntry entry) {
console->Commands[cmd] = std::move(entry);
}
}
void AddWindow(const std::string& category, const std::string& name, WindowDrawFunc drawFunc) {
if (customWindows.contains(name)) {
SPDLOG_ERROR("SohImGui::AddWindow: Attempting to add duplicate window name %s", name.c_str());
return;
}
customWindows[name] = {
.enabled = false,
.drawFunc = drawFunc
};
windowCategories[category].emplace_back(name);
}
ImTextureID GetTextureByName(const std::string& name) {
return GetTextureByID(DefaultAssets[name]->textureId);
}
}

View File

@ -47,11 +47,23 @@ namespace SohImGui {
} sdl;
} EventImpl;
extern WindowImpl impl;
using WindowDrawFunc = void(*)(bool& enabled);
typedef struct {
bool enabled;
WindowDrawFunc drawFunc;
} CustomWindow;
extern Console* console;
void Init(WindowImpl window_impl);
void Update(EventImpl event);
void Draw(void);
void ShowCursor(bool hide, Dialogues w);
void BindCmd(const std::string& cmd, CommandEntry entry);
void* GetTextureByID(int id);
void AddWindow(const std::string& category, const std::string& name, WindowDrawFunc drawFunc);
void LoadResource(const std::string& name, const std::string& path);
ImTextureID GetTextureByID(int id);
ImTextureID GetTextureByName(const std::string& name);
}

View File

@ -178,6 +178,8 @@
<ItemGroup>
<ClCompile Include="soh\Enhancements\bootcommands.c" />
<ClCompile Include="soh\Enhancements\debugconsole.cpp" />
<ClCompile Include="soh\Enhancements\debugger\debugger.cpp" />
<ClCompile Include="soh\Enhancements\debugger\debugSaveEditor.cpp" />
<ClCompile Include="soh\Enhancements\gameconsole.c" />
<ClCompile Include="soh\GbiWrap.cpp" />
<ClCompile Include="soh\gu_pc.c" />
@ -922,6 +924,8 @@
<ClInclude Include="soh\Enhancements\bootcommands.h" />
<ClInclude Include="soh\Enhancements\cvar.h" />
<ClInclude Include="soh\Enhancements\debugconsole.h" />
<ClInclude Include="soh\Enhancements\debugger\debugger.h" />
<ClInclude Include="soh\Enhancements\debugger\debugSaveEditor.h" />
<ClInclude Include="soh\gameconsole.h" />
<ClInclude Include="soh\OTRGlobals.h" />
<ClInclude Include="src\overlays\actors\ovl_Arms_Hook\z_arms_hook.h" />

View File

@ -76,6 +76,12 @@
<Filter Include="Source Files\src\overlays\actors">
<UniqueIdentifier>{18b9727f-30de-4ab8-a317-916090d4a110}</UniqueIdentifier>
</Filter>
<Filter Include="Header Files\soh\Enhancements\debugger">
<UniqueIdentifier>{9a4378ec-e30f-47b6-9ad6-5ce738b4cf99}</UniqueIdentifier>
</Filter>
<Filter Include="Source Files\soh\Enhancements\debugger">
<UniqueIdentifier>{04fc1c52-49ff-48e2-ae23-2c00867374f8}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\boot\assert.c">
@ -2169,6 +2175,12 @@
<ClCompile Include="src\overlays\actors\ovl_Shot_Sun\z_shot_sun.c">
<Filter>Source Files\src\overlays\actors</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\debugger\debugger.cpp">
<Filter>Source Files\soh\Enhancements\debugger</Filter>
</ClCompile>
<ClCompile Include="soh\Enhancements\debugger\debugSaveEditor.cpp">
<Filter>Source Files\soh\Enhancements\debugger</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\overlays\actors\ovl_kaleido_scope\z_kaleido_scope.h">
@ -3710,6 +3722,12 @@
<ClInclude Include="resource.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\debugger\debugger.h">
<Filter>Header Files\soh\Enhancements\debugger</Filter>
</ClInclude>
<ClInclude Include="soh\Enhancements\debugger\debugSaveEditor.h">
<Filter>Header Files\soh\Enhancements\debugger</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="include\macro.inc">

View File

@ -0,0 +1,527 @@
#include "debugSaveEditor.h"
#include "../libultraship/SohImGuiImpl.h"
#include <bit>
#include <map>
#include <string>
extern "C" {
#include <z64.h>
#include "variables.h"
#include "functions.h"
#include "macros.h"
extern GlobalContext* gGlobalCtx;
#include "textures/icon_item_static/icon_item_static.h"
}
typedef struct {
uint32_t id;
std::string name;
std::string texturePath;
} ItemMapEntry;
#define ITEM_MAP_ENTRY(id) \
{ \
id, { \
id, #id, static_cast<char*>(gItemIcons[id]) \
} \
}
// Maps items ids to info for use in ImGui
std::map<uint32_t, ItemMapEntry> itemMapping = {
ITEM_MAP_ENTRY(ITEM_STICK),
ITEM_MAP_ENTRY(ITEM_NUT),
ITEM_MAP_ENTRY(ITEM_BOMB),
ITEM_MAP_ENTRY(ITEM_BOW),
ITEM_MAP_ENTRY(ITEM_ARROW_FIRE),
ITEM_MAP_ENTRY(ITEM_DINS_FIRE),
ITEM_MAP_ENTRY(ITEM_SLINGSHOT),
ITEM_MAP_ENTRY(ITEM_OCARINA_FAIRY),
ITEM_MAP_ENTRY(ITEM_OCARINA_TIME),
ITEM_MAP_ENTRY(ITEM_BOMBCHU),
ITEM_MAP_ENTRY(ITEM_HOOKSHOT),
ITEM_MAP_ENTRY(ITEM_LONGSHOT),
ITEM_MAP_ENTRY(ITEM_ARROW_ICE),
ITEM_MAP_ENTRY(ITEM_FARORES_WIND),
ITEM_MAP_ENTRY(ITEM_BOOMERANG),
ITEM_MAP_ENTRY(ITEM_LENS),
ITEM_MAP_ENTRY(ITEM_BEAN),
ITEM_MAP_ENTRY(ITEM_HAMMER),
ITEM_MAP_ENTRY(ITEM_ARROW_LIGHT),
ITEM_MAP_ENTRY(ITEM_NAYRUS_LOVE),
ITEM_MAP_ENTRY(ITEM_BOTTLE),
ITEM_MAP_ENTRY(ITEM_POTION_RED),
ITEM_MAP_ENTRY(ITEM_POTION_GREEN),
ITEM_MAP_ENTRY(ITEM_POTION_BLUE),
ITEM_MAP_ENTRY(ITEM_FAIRY),
ITEM_MAP_ENTRY(ITEM_FISH),
ITEM_MAP_ENTRY(ITEM_MILK_BOTTLE),
ITEM_MAP_ENTRY(ITEM_LETTER_RUTO),
ITEM_MAP_ENTRY(ITEM_BLUE_FIRE),
ITEM_MAP_ENTRY(ITEM_BUG),
ITEM_MAP_ENTRY(ITEM_BIG_POE),
ITEM_MAP_ENTRY(ITEM_MILK_HALF),
ITEM_MAP_ENTRY(ITEM_POE),
ITEM_MAP_ENTRY(ITEM_WEIRD_EGG),
ITEM_MAP_ENTRY(ITEM_CHICKEN),
ITEM_MAP_ENTRY(ITEM_LETTER_ZELDA),
ITEM_MAP_ENTRY(ITEM_MASK_KEATON),
ITEM_MAP_ENTRY(ITEM_MASK_SKULL),
ITEM_MAP_ENTRY(ITEM_MASK_SPOOKY),
ITEM_MAP_ENTRY(ITEM_MASK_BUNNY),
ITEM_MAP_ENTRY(ITEM_MASK_GORON),
ITEM_MAP_ENTRY(ITEM_MASK_ZORA),
ITEM_MAP_ENTRY(ITEM_MASK_GERUDO),
ITEM_MAP_ENTRY(ITEM_MASK_TRUTH),
ITEM_MAP_ENTRY(ITEM_SOLD_OUT),
ITEM_MAP_ENTRY(ITEM_POCKET_EGG),
ITEM_MAP_ENTRY(ITEM_POCKET_CUCCO),
ITEM_MAP_ENTRY(ITEM_COJIRO),
ITEM_MAP_ENTRY(ITEM_ODD_MUSHROOM),
ITEM_MAP_ENTRY(ITEM_ODD_POTION),
ITEM_MAP_ENTRY(ITEM_SAW),
ITEM_MAP_ENTRY(ITEM_SWORD_BROKEN),
ITEM_MAP_ENTRY(ITEM_PRESCRIPTION),
ITEM_MAP_ENTRY(ITEM_FROG),
ITEM_MAP_ENTRY(ITEM_EYEDROPS),
ITEM_MAP_ENTRY(ITEM_CLAIM_CHECK),
ITEM_MAP_ENTRY(ITEM_BOW_ARROW_FIRE),
ITEM_MAP_ENTRY(ITEM_BOW_ARROW_ICE),
ITEM_MAP_ENTRY(ITEM_BOW_ARROW_LIGHT),
ITEM_MAP_ENTRY(ITEM_SWORD_KOKIRI),
ITEM_MAP_ENTRY(ITEM_SWORD_MASTER),
ITEM_MAP_ENTRY(ITEM_SWORD_BGS),
ITEM_MAP_ENTRY(ITEM_SHIELD_DEKU),
ITEM_MAP_ENTRY(ITEM_SHIELD_HYLIAN),
ITEM_MAP_ENTRY(ITEM_SHIELD_MIRROR),
ITEM_MAP_ENTRY(ITEM_TUNIC_KOKIRI),
ITEM_MAP_ENTRY(ITEM_TUNIC_GORON),
ITEM_MAP_ENTRY(ITEM_TUNIC_ZORA),
ITEM_MAP_ENTRY(ITEM_BOOTS_KOKIRI),
ITEM_MAP_ENTRY(ITEM_BOOTS_IRON),
ITEM_MAP_ENTRY(ITEM_BOOTS_HOVER),
ITEM_MAP_ENTRY(ITEM_BULLET_BAG_30),
ITEM_MAP_ENTRY(ITEM_BULLET_BAG_40),
ITEM_MAP_ENTRY(ITEM_BULLET_BAG_50),
ITEM_MAP_ENTRY(ITEM_QUIVER_30),
ITEM_MAP_ENTRY(ITEM_QUIVER_40),
ITEM_MAP_ENTRY(ITEM_QUIVER_50),
ITEM_MAP_ENTRY(ITEM_BOMB_BAG_20),
ITEM_MAP_ENTRY(ITEM_BOMB_BAG_30),
ITEM_MAP_ENTRY(ITEM_BOMB_BAG_40),
ITEM_MAP_ENTRY(ITEM_BRACELET),
ITEM_MAP_ENTRY(ITEM_GAUNTLETS_SILVER),
ITEM_MAP_ENTRY(ITEM_GAUNTLETS_GOLD),
ITEM_MAP_ENTRY(ITEM_SCALE_SILVER),
ITEM_MAP_ENTRY(ITEM_SCALE_GOLDEN),
ITEM_MAP_ENTRY(ITEM_SWORD_KNIFE),
ITEM_MAP_ENTRY(ITEM_WALLET_ADULT),
ITEM_MAP_ENTRY(ITEM_WALLET_GIANT),
ITEM_MAP_ENTRY(ITEM_SEEDS),
ITEM_MAP_ENTRY(ITEM_FISHING_POLE),
};
// Maps entries in the GS flag array to the area name it represents
std::vector<std::string> gsMapping = {
"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",
"Hyrule Field",
"Lon Lon Ranch",
"Kokiri Forest",
"Lost Woods, Sacred Forest Meadow",
"Castle Town and Ganon's Castle",
"Death Mountain Trail, Goron City",
"Kakariko Village",
"Zora Fountain, River",
"Lake Hylia",
"Gerudo Valley",
"Gerudo Fortress",
"Desert Colossus, Haunted Wasteland",
};
extern "C" u8 gAreaGsFlags[];
extern "C" u8 gAmmoItems[];
// Modification of gAmmoItems that replaces ITEM_NONE with the item in inventory slot it represents
u8 gAllAmmoItems[] = {
ITEM_STICK, ITEM_NUT, ITEM_BOMB, ITEM_BOW, ITEM_ARROW_FIRE, ITEM_DINS_FIRE,
ITEM_SLINGSHOT, ITEM_OCARINA_TIME, ITEM_BOMBCHU, ITEM_LONGSHOT, ITEM_ARROW_ICE, ITEM_FARORES_WIND,
ITEM_BOOMERANG, ITEM_LENS, ITEM_BEAN, ITEM_HAMMER,
};
// Adds a text tooltip for the previous ImGui item
void SetLastItemHoverText(const std::string& text) {
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::Text(text.c_str());
ImGui::EndTooltip();
}
}
// Adds a "?" next to the previous ImGui item with a custom tooltip
void InsertHelpHoverText(const std::string& text) {
ImGui::SameLine();
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "?");
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::Text(text.c_str());
ImGui::EndTooltip();
}
}
void DrawSaveEditor(bool& open) {
if (!open) {
return;
}
ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("Save Editor", &open)) {
ImGui::End();
return;
}
// 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;
for (int i = 0; i < 8; i++) {
char letter = gSaveContext.playerName[i] + 0x3D;
if (letter == '{') {
letter = '\0';
}
name += letter;
}
name += '\0';
if (ImGui::BeginTabBar("SaveContextTabBar", ImGuiTabBarFlags_NoCloseWithMiddleMouseButton)) {
if (ImGui::BeginTabItem("Info")) {
ImGui::PushItemWidth(ImGui::GetFontSize() * 6);
ImGui::Text("Name: %s", name.c_str());
InsertHelpHoverText("Player Name");
// Use an intermediary to keep the health from updating (and potentially killing the player)
// until it is done being edited
int16_t healthIntermediary = gSaveContext.healthCapacity;
ImGui::InputScalar("Max Health", ImGuiDataType_S16, &healthIntermediary);
if (ImGui::IsItemDeactivated()) {
gSaveContext.healthCapacity = healthIntermediary;
}
InsertHelpHoverText("Maximum health. 16 units per full heart");
if (gSaveContext.health > gSaveContext.healthCapacity) {
gSaveContext.health = gSaveContext.healthCapacity; // Clamp health to new max
}
const uint16_t healthMin = 0;
const uint16_t healthMax = gSaveContext.healthCapacity;
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 15);
ImGui::SliderScalar("Health", ImGuiDataType_S16, &gSaveContext.health, &healthMin, &healthMax);
InsertHelpHoverText("Current health. 16 units per full heart");
bool doubleDefense = gSaveContext.doubleDefense != 0;
if (ImGui::Checkbox("Double Defense", &doubleDefense)) {
gSaveContext.doubleDefense = doubleDefense;
gSaveContext.inventory.defenseHearts =
gSaveContext.doubleDefense ? 20 : 0; // Set to get the border drawn in the UI
}
InsertHelpHoverText("Is double defense unlocked?");
std::string magicName;
if (gSaveContext.magicLevel == 2) {
magicName = "Double";
} else if (gSaveContext.magicLevel == 1) {
magicName = "Single";
} else {
magicName = "None";
}
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 6);
if (ImGui::BeginCombo("Magic Level", magicName.c_str())) {
if (ImGui::Selectable("Double")) {
gSaveContext.magicLevel = 2;
gSaveContext.magicAcquired = true;
gSaveContext.doubleMagic = true;
}
if (ImGui::Selectable("Single")) {
gSaveContext.magicLevel = 1;
gSaveContext.magicAcquired = true;
gSaveContext.doubleMagic = false;
}
if (ImGui::Selectable("None")) {
gSaveContext.magicLevel = 0;
gSaveContext.magicAcquired = false;
gSaveContext.doubleMagic = false;
}
ImGui::EndCombo();
}
InsertHelpHoverText("Current magic level");
gSaveContext.unk_13F4 = gSaveContext.magicLevel * 0x30; // Set to get the bar drawn in the UI
if (gSaveContext.magic > gSaveContext.unk_13F4) {
gSaveContext.magic = gSaveContext.unk_13F4; // Clamp magic to new max
}
const uint8_t magicMin = 0;
const uint8_t magicMax = gSaveContext.unk_13F4;
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 15);
ImGui::SliderScalar("Magic", ImGuiDataType_S8, &gSaveContext.magic, &magicMin, &magicMax);
InsertHelpHoverText("Current magic. 48 units per magic level");
ImGui::InputScalar("Rupees", ImGuiDataType_S16, &gSaveContext.rupees);
InsertHelpHoverText("Current rupees");
const uint16_t dayTimeMin = 0;
const uint16_t dayTimeMax = 0xFFFF;
ImGui::SetNextItemWidth(ImGui::GetFontSize() * 15);
ImGui::SliderScalar("Time", ImGuiDataType_U16, &gSaveContext.dayTime, &dayTimeMin, &dayTimeMax);
InsertHelpHoverText("Time of day");
if (ImGui::Button("Dawn")) {
gSaveContext.dayTime = 0x4000;
}
ImGui::SameLine();
if (ImGui::Button("Noon")) {
gSaveContext.dayTime = 0x8000;
}
ImGui::SameLine();
if (ImGui::Button("Sunset")) {
gSaveContext.dayTime = 0xC000;
}
ImGui::SameLine();
if (ImGui::Button("Midnight")) {
gSaveContext.dayTime = 0;
}
ImGui::InputScalar("Total Days", ImGuiDataType_S32, &gSaveContext.totalDays);
InsertHelpHoverText("Total number of days elapsed since the start of the game");
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;
}
InsertHelpHoverText("Is Biggoron sword unlocked? Replaces Giant's knife");
ImGui::InputScalar("Sword Health", ImGuiDataType_U16, &gSaveContext.swordHealth);
InsertHelpHoverText("Giant's knife health. Default is 8. Must be >0 for Biggoron sword to work");
ImGui::InputScalar("Bgs Day Count", ImGuiDataType_S32, &gSaveContext.bgsDayCount);
InsertHelpHoverText("Total number of days elapsed since giving Biggoron the claim check");
// TODO Changing Link's age is more involved than just setting gSaveContext.linkAge
// It might not fit here and instead should be only changable when changing scenes
/*
if (ImGui::BeginCombo("Link Age", LINK_IS_ADULT ? "Adult" : "Child")) {
if (ImGui::Selectable("Adult")) {
gSaveContext.linkAge = 0;
}
if (ImGui::Selectable("Child")) {
gSaveContext.linkAge = 1;
}
ImGui::EndCombo();
}
*/
ImGui::PopItemWidth();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Inventory")) {
static bool restrictToValid = true;
ImGui::Checkbox("Restrict to valid items", &restrictToValid);
InsertHelpHoverText("Restricts items and ammo to only what is possible to legally acquire in-game");
for (int32_t y = 0; y < 4; y++) {
for (int32_t x = 0; x < 6; x++) {
int32_t index = x + y * 6;
static int32_t selectedIndex = -1;
static const char* itemPopupPicker = "itemPopupPicker";
ImGui::PushID(index);
if (x != 0) {
ImGui::SameLine();
}
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1, 1, 1, 0));
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f);
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)) {
selectedIndex = index;
ImGui::OpenPopup(itemPopupPicker);
}
} else {
if (ImGui::Button("##itemNone", ImVec2(32.0f, 32.0f))) {
selectedIndex = index;
ImGui::OpenPopup(itemPopupPicker);
}
}
ImGui::PopStyleVar();
ImGui::PopStyleColor();
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
if (ImGui::BeginPopup(itemPopupPicker)) {
if (ImGui::Button("##itemNonePicker", ImVec2(32.0f, 32.0f))) {
gSaveContext.inventory.items[selectedIndex] = ITEM_NONE;
ImGui::CloseCurrentPopup();
}
SetLastItemHoverText("ITEM_NONE");
std::vector<ItemMapEntry> possibleItems;
if (restrictToValid) {
// Scan gItemSlots to find legal items for this slot. Bottles are a special case
for (int slotIndex = 0; slotIndex < 56; slotIndex++) {
int testIndex = (selectedIndex == SLOT_BOTTLE_1 || selectedIndex == SLOT_BOTTLE_2 ||
selectedIndex == SLOT_BOTTLE_3 || selectedIndex == SLOT_BOTTLE_4)
? SLOT_BOTTLE_1
: selectedIndex;
if (gItemSlots[slotIndex] == testIndex) {
possibleItems.push_back(itemMapping[slotIndex]);
}
}
} else {
for (const auto& entry : itemMapping) {
possibleItems.push_back(entry.second);
}
}
for (int32_t pickerIndex = 0; pickerIndex < possibleItems.size(); pickerIndex++) {
if (((pickerIndex + 1) % 8) != 0) {
ImGui::SameLine();
}
const ItemMapEntry& slotEntry = possibleItems[pickerIndex];
if (ImGui::ImageButton(SohImGui::GetTextureByName(slotEntry.name), ImVec2(32.0f, 32.0f),
ImVec2(0, 0), ImVec2(1, 1), 0)) {
gSaveContext.inventory.items[selectedIndex] = slotEntry.id;
ImGui::CloseCurrentPopup();
}
SetLastItemHoverText(slotEntry.name);
}
ImGui::EndPopup();
}
ImGui::PopStyleVar();
ImGui::PopID();
}
}
ImGui::Text("Ammo");
for (uint32_t ammoIndex = 0, drawnAmmoItems = 0; ammoIndex < 16; ammoIndex++) {
uint8_t item = (restrictToValid) ? gAmmoItems[ammoIndex] : gAllAmmoItems[ammoIndex];
if (item != ITEM_NONE) {
// For legal items, display as 1 row of 7. For unrestricted items, display rows of 6 to match
// inventory
if ((restrictToValid && (drawnAmmoItems != 0)) || ((drawnAmmoItems % 6) != 0)) {
ImGui::SameLine();
}
drawnAmmoItems++;
ImGui::PushID(ammoIndex);
ImGui::PushItemWidth(32.0f);
ImGui::BeginGroup();
ImGui::Image(SohImGui::GetTextureByName(itemMapping[item].name), ImVec2(32.0f, 32.0f));
ImGui::InputScalar("##ammoInput", ImGuiDataType_S8, &AMMO(item));
ImGui::EndGroup();
ImGui::PopItemWidth();
ImGui::PopID();
}
}
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Flags")) {
static uint32_t selectedGsMap = 0;
ImGui::Text("Gold Skulltulas");
ImGui::Text("Map");
ImGui::SameLine();
if (ImGui::BeginCombo("##Gold Skulltula Map", gsMapping[selectedGsMap].c_str())) {
for (int32_t gsIndex = 0; gsIndex < gsMapping.size(); gsIndex++) {
if (ImGui::Selectable(gsMapping[gsIndex].c_str())) {
selectedGsMap = gsIndex;
}
}
ImGui::EndCombo();
}
// TODO We should write out descriptions for each one... ugh
ImGui::Text("Flags");
uint32_t currentFlags = GET_GS_FLAGS(selectedGsMap);
uint32_t allFlags = gAreaGsFlags[selectedGsMap];
uint32_t setMask = 1;
// Iterate over bitfield and create a checkbox for each skulltula
while (allFlags != 0) {
bool isThisSet = (currentFlags & 0x1) == 0x1;
ImGui::SameLine();
ImGui::PushID(allFlags);
if (ImGui::Checkbox("##gs", &isThisSet)) {
if (isThisSet) {
SET_GS_FLAGS(selectedGsMap, setMask);
} else {
// Have to do this roundabout method as the macro does not support clearing flags
uint32_t currentFlagsBase = GET_GS_FLAGS(selectedGsMap);
gSaveContext.gsFlags[selectedGsMap >> 2] &= ~gGsFlagsMasks[selectedGsMap & 3];
SET_GS_FLAGS(selectedGsMap, currentFlagsBase & ~setMask);
}
}
ImGui::PopID();
allFlags >>= 1;
currentFlags >>= 1;
setMask <<= 1;
}
static bool keepGsCountUpdated = true;
ImGui::Checkbox("Keep GS Count Updated", &keepGsCountUpdated);
InsertHelpHoverText("Automatically adjust the number of gold skulltula tokens acquired based on set flags");
int32_t gsCount = 0;
if (keepGsCountUpdated) {
for (int32_t gsFlagIndex = 0; gsFlagIndex < 6; gsFlagIndex++) {
gsCount += std::popcount(static_cast<uint32_t>(gSaveContext.gsFlags[gsFlagIndex]));
}
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
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
ImGui::End();
}
void InitSaveEditor() {
SohImGui::AddWindow("Debug", "Save Editor", DrawSaveEditor);
// Load item icons into ImGui
for (const auto& entry : itemMapping) {
SohImGui::LoadResource(entry.second.name, entry.second.texturePath);
}
}

View File

@ -0,0 +1,3 @@
#pragma once
void InitSaveEditor();

View File

@ -0,0 +1,6 @@
#include "debugger.h"
#include "debugSaveEditor.h"
void Debug_Init(void) {
InitSaveEditor();
}

View File

@ -0,0 +1,3 @@
#pragma once
void Debug_Init(void);

View File

@ -22,6 +22,7 @@
#include "Lib/stb/stb_image.h"
#include "AudioPlayer.h"
#include "../soh/Enhancements/debugconsole.h"
#include "../soh/Enhancements/debugger/debugger.h"
#include "Utils/BitConverter.h"
OTRGlobals* OTRGlobals::Instance;
@ -54,6 +55,7 @@ extern "C" void InitOTR() {
clearMtx = (uintptr_t)&gMtxClear;
OTRMessage_Init();
DebugConsole_Init();
Debug_Init();
}
extern "C" uint64_t GetFrequency() {