From a2ae88987a2eabb53dc502f21457bbd6ef7fdab2 Mon Sep 17 00:00:00 2001 From: Ralphie Morell Date: Sun, 18 Feb 2024 22:47:14 -0500 Subject: [PATCH] Actor Viewer (#123) --- mm/2s2h/BenGui/BenGui.cpp | 5 + mm/2s2h/BenGui/BenGui.hpp | 1 + mm/2s2h/BenGui/BenMenuBar.cpp | 6 + mm/2s2h/BenGui/BenMenuBar.h | 1 + mm/2s2h/BenGui/UIWidgets.cpp | 69 ++++++ mm/2s2h/BenGui/UIWidgets.hpp | 3 + mm/2s2h/DeveloperTools/ActorViewer.cpp | 295 +++++++++++++++++++++++++ mm/2s2h/DeveloperTools/ActorViewer.h | 12 + mm/src/code/z_scene.c | 3 +- 9 files changed, 394 insertions(+), 1 deletion(-) create mode 100644 mm/2s2h/DeveloperTools/ActorViewer.cpp create mode 100644 mm/2s2h/DeveloperTools/ActorViewer.h diff --git a/mm/2s2h/BenGui/BenGui.cpp b/mm/2s2h/BenGui/BenGui.cpp index a8e42cba9..cb6ac34a6 100644 --- a/mm/2s2h/BenGui/BenGui.cpp +++ b/mm/2s2h/BenGui/BenGui.cpp @@ -35,6 +35,7 @@ namespace BenGui { std::shared_ptr mSaveEditorWindow; std::shared_ptr mHudEditorWindow; + std::shared_ptr mActorViewerWindow; void SetupGuiElements() { auto gui = LUS::Context::GetInstance()->GetWindow()->GetGui(); @@ -80,11 +81,15 @@ namespace BenGui { mHudEditorWindow = std::make_shared("gWindows.HudEditor", "Hud Editor"); gui->AddGuiWindow(mHudEditorWindow); + + mActorViewerWindow = std::make_shared("gWindows.ActorViewer", "Actor Viewer"); + gui->AddGuiWindow(mActorViewerWindow); } void Destroy() { mSaveEditorWindow = nullptr; mHudEditorWindow = nullptr; + mActorViewerWindow = nullptr; mStatsWindow = nullptr; mConsoleWindow = nullptr; mBenMenuBar = nullptr; diff --git a/mm/2s2h/BenGui/BenGui.hpp b/mm/2s2h/BenGui/BenGui.hpp index 80bb04fbd..dbb7f783b 100644 --- a/mm/2s2h/BenGui/BenGui.hpp +++ b/mm/2s2h/BenGui/BenGui.hpp @@ -4,6 +4,7 @@ #include #include "BenMenuBar.h" #include "DeveloperTools/SaveEditor.h" +#include "DeveloperTools/ActorViewer.h" namespace BenGui { void SetupHooks(); diff --git a/mm/2s2h/BenGui/BenMenuBar.cpp b/mm/2s2h/BenGui/BenMenuBar.cpp index fa2af35ec..a8b6ad06e 100644 --- a/mm/2s2h/BenGui/BenMenuBar.cpp +++ b/mm/2s2h/BenGui/BenMenuBar.cpp @@ -297,6 +297,7 @@ extern std::shared_ptr mStatsWindow; extern std::shared_ptr mConsoleWindow; extern std::shared_ptr mGfxDebuggerWindow; extern std::shared_ptr mSaveEditorWindow; +extern std::shared_ptr mActorViewerWindow; void DrawDeveloperToolsMenu() { if (UIWidgets::BeginMenu("Developer Tools", UIWidgets::Colors::Yellow)) { @@ -345,6 +346,11 @@ void DrawDeveloperToolsMenu() { .tooltip = "Enables the Save Editor window, allowing you to edit your save file" }); } + if (mActorViewerWindow) { + UIWidgets::WindowButton("Actor Viewer", "gWindows.ActorViewer", mActorViewerWindow, { + .tooltip = "Enables the Actor Viewer window, allowing you to view actors in the world." + }); + } ImGui::EndMenu(); } } diff --git a/mm/2s2h/BenGui/BenMenuBar.h b/mm/2s2h/BenGui/BenMenuBar.h index a36d5308e..17b8b116f 100644 --- a/mm/2s2h/BenGui/BenMenuBar.h +++ b/mm/2s2h/BenGui/BenMenuBar.h @@ -4,6 +4,7 @@ #include "window/gui/GuiMenuBar.h" #include "window/gui/GuiElement.h" #include "DeveloperTools/SaveEditor.h" +#include "DeveloperTools/ActorViewer.h" namespace BenGui { class BenMenuBar : public LUS::GuiMenuBar { diff --git a/mm/2s2h/BenGui/UIWidgets.cpp b/mm/2s2h/BenGui/UIWidgets.cpp index b7752968c..33d5be450 100644 --- a/mm/2s2h/BenGui/UIWidgets.cpp +++ b/mm/2s2h/BenGui/UIWidgets.cpp @@ -393,4 +393,73 @@ namespace UIWidgets { } return dirty; } + + void DrawFlagArray32(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; + std::string label = std::to_string(flagIndex); + if (UIWidgets::Checkbox(label.c_str(), &flag, {.tooltip = label.c_str(), + .labelPosition = LabelPosition::None })) { + if (flag) { + flags |= bitMask; + } else { + flags &= ~bitMask; + } + } + ImGui::PopID(); + } + ImGui::PopID(); + } + + void DrawFlagArray16(const std::string& name, uint16_t& flags) { + ImGui::PushID(name.c_str()); + for (int16_t flagIndex = 0; flagIndex < 16; flagIndex++) { + if ((flagIndex % 8) != 0) { + ImGui::SameLine(); + } + ImGui::PushID(flagIndex); + uint16_t bitMask = 1 << flagIndex; + bool flag = (flags & bitMask) != 0; + std::string label = std::to_string(flagIndex); + if (UIWidgets::Checkbox(label.c_str(), &flag, {.tooltip = label.c_str(), + .labelPosition = LabelPosition::None })) { + if (flag) { + flags |= bitMask; + } else { + flags &= ~bitMask; + } + } + ImGui::PopID(); + } + ImGui::PopID(); + } + + void DrawFlagArray8(const std::string& name, uint8_t& flags) { + ImGui::PushID(name.c_str()); + for (int8_t flagIndex = 0; flagIndex < 8; flagIndex++) { + if ((flagIndex % 8) != 0) { + ImGui::SameLine(); + } + ImGui::PushID(flagIndex); + uint8_t bitMask = 1 << flagIndex; + bool flag = (flags & bitMask) != 0; + std::string label = std::to_string(flagIndex); + if (UIWidgets::Checkbox(label.c_str(), &flag, {.tooltip = label.c_str(), + .labelPosition = LabelPosition::None })) { + if (flag) { + flags |= bitMask; + } else { + flags &= ~bitMask; + } + } + ImGui::PopID(); + } + ImGui::PopID(); + } } diff --git a/mm/2s2h/BenGui/UIWidgets.hpp b/mm/2s2h/BenGui/UIWidgets.hpp index 632f6b58f..fa9de88b8 100644 --- a/mm/2s2h/BenGui/UIWidgets.hpp +++ b/mm/2s2h/BenGui/UIWidgets.hpp @@ -409,6 +409,9 @@ namespace UIWidgets { 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 = {}); + void DrawFlagArray32(const std::string& name, uint32_t& flags); + void DrawFlagArray16(const std::string& name, uint16_t& flags); + void DrawFlagArray8(const std::string& name, uint8_t& flags); } #endif /* UIWidgets_hpp */ diff --git a/mm/2s2h/DeveloperTools/ActorViewer.cpp b/mm/2s2h/DeveloperTools/ActorViewer.cpp new file mode 100644 index 000000000..4c537dbeb --- /dev/null +++ b/mm/2s2h/DeveloperTools/ActorViewer.cpp @@ -0,0 +1,295 @@ +#include "ActorViewer.h" +#include "2s2h/BenGui/UIWidgets.hpp" + +extern "C" { + #include "global.h" + extern PlayState* gPlayState; + +} + +typedef struct ActorInfo { + u16 id; + u16 params; + Vec3f pos; + Vec3s rot; +} ActorInfo; + +typedef enum Method { + LIST, + TARGET, + HOLD, +} Method; + +std::array acMapping = { + "Switch", + "Background", + "Player", + "Explosive", + "NPC", + "Enemy", + "Prop", + "Item/Action", + "Misc.", + "Boss", + "Door", + "Chest" +}; + + +#define DEFINE_ACTOR(name, _enumValue, _allocType, _debugName, _humanName) {_enumValue, _humanName}, +#define DEFINE_ACTOR_INTERNAL(_name, _enumValue, _allocType, _debugName, _humanName) {_enumValue, _humanName}, +#define DEFINE_ACTOR_UNSET(_enumValue) {_enumValue, "Unset"}, + +std::unordered_map actorDescriptions = { +#include "tables/actor_table.h" +}; + +#undef DEFINE_ACTOR +#undef DEFINE_ACTOR_INTERNAL +#undef DEFINE_ACTOR_UNSET + +std::string GetActorDescription(u16 actorNum) { + return actorDescriptions.contains(actorNum) ? actorDescriptions[actorNum] : "???"; +} + +std::vector GetCurrentSceneActors() { + if (!gPlayState) return {}; + + std::vector sceneActors; + for (size_t category = ACTORCAT_SWITCH; category < ACTORCAT_MAX; category++) { + Actor* currentActor = gPlayState->actorCtx.actorLists[category].first; + if (currentActor != nullptr) { + while (currentActor != nullptr) { + sceneActors.push_back(currentActor); + currentActor = currentActor->next; + } + } + } + return sceneActors; + +} + +static bool needs_reset = false; + +static Actor* display{}; +static Actor* fetch; +static ActorInfo newActor; +static std::vector list; + +static s16 lastSceneId = 0; +static s16 newActorId = 0; +static u8 category = 0; +static s8 method = -1; +static std::string filler = "Please select"; + +void ResetVariables() { + display = fetch = {}; + newActor = {}; + filler = "Please select"; +} + +void ActorViewerWindow::DrawElement() { + ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("Actor Viewer", &mIsVisible, ImGuiWindowFlags_NoFocusOnAppearing)) { + ImGui::End(); + return; + } + + if (gPlayState != nullptr) { + needs_reset = lastSceneId != gPlayState->sceneId; + if (needs_reset) { + ResetVariables(); + lastSceneId = gPlayState->sceneId; + needs_reset = false; + } + + if (ImGui::BeginCombo("Actor", filler.c_str())) { + if (gPlayState != nullptr) { + list = GetCurrentSceneActors(); + lastSceneId = gPlayState->sceneId; + } + if (!list.empty()) { + u8 count = 0; + u8 prev_category = 0xff; + for (size_t i = 0; i < list.size(); i++) { + std::string label = acMapping[list[i]->category]; + if (list[i]->category != prev_category) { + prev_category = list[i]->category; + count = 1; + } else { + count++; + } + label += " " + std::to_string(count) + ": " + GetActorDescription(list[i]->id); + if (ImGui::Selectable(label.c_str())) { + method = LIST; + display = list[i]; + newActorId = i; + filler = label; + break; + } + } + } + ImGui::EndCombo(); + } + + if (ImGui::TreeNode("Selected Actor")) { + if (display != nullptr) { + ImGui::BeginGroup(); + ImGui::Text("ID: %hd", display->id); + ImGui::Text("Description: %s", GetActorDescription(display->id).c_str()); + ImGui::Text("Category: %s", acMapping[display->category]); + ImGui::Text("Params: %hd", display->params); + ImGui::EndGroup(); + + ImGui::PushItemWidth(ImGui::GetFontSize() * 10); + + ImGui::BeginGroup(); + ImGui::Text("Actor Position"); + ImGui::InputScalar("x", ImGuiDataType_Float, &display->world.pos.x); + ImGui::SameLine(); + ImGui::InputScalar("y", ImGuiDataType_Float, &display->world.pos.y); + ImGui::SameLine(); + ImGui::InputScalar("z", ImGuiDataType_Float, &display->world.pos.z); + ImGui::EndGroup(); + + ImGui::BeginGroup(); + ImGui::Text("Actor Rotation"); + ImGui::InputScalar("rx", ImGuiDataType_S16, &display->world.rot.x); + ImGui::SameLine(); + ImGui::InputScalar("ry", ImGuiDataType_S16, &display->world.rot.y); + ImGui::SameLine(); + ImGui::InputScalar("rz", ImGuiDataType_S16, &display->world.rot.z); + ImGui::EndGroup(); + + ImGui::PopItemWidth(); + ImGui::PushItemWidth(ImGui::GetFontSize() * 5); + + if ((display->category == ACTORCAT_BOSS || display->category == ACTORCAT_ENEMY) && + display->colChkInfo.health > 0) { + ImGui::InputScalar("Enemy Health", ImGuiDataType_U8, &display->colChkInfo.health); + } + ImGui::PopItemWidth(); + + ImGui::BeginGroup(); + ImGui::Text("Flags"); + UIWidgets::DrawFlagArray32("flags", display->flags); + ImGui::EndGroup(); + + ImGui::BeginGroup(); + ImGui::Text("BG Check"); + UIWidgets::DrawFlagArray16("bgCheckFlags", display->bgCheckFlags); + ImGui::EndGroup(); + } + + if (UIWidgets::Button("Refresh")) { + list = GetCurrentSceneActors(); + display = list[newActorId]; + } + + if (UIWidgets::Button("Go to Actor")) { + Player* player = GET_PLAYER(gPlayState); + if (display != nullptr) { + Math_Vec3f_Copy(&player->actor.world.pos, &display->world.pos); + Math_Vec3f_Copy(&player->actor.home.pos, &display->world.pos); + } + } + + if (UIWidgets::Button("Fetch: Target", {.tooltip = "Grabs actor with target arrow above it."})) { + Player* player = GET_PLAYER(gPlayState); + fetch = player->lockOnActor; + if (fetch != nullptr) { + display = fetch; + category = fetch->category; + list = GetCurrentSceneActors(); + method = TARGET; + } else { + display = {}; + } + } + if (UIWidgets::Button("Fetch: Held", {.tooltip = "Grabs actor Link is currently holding."})) { + Player* player = GET_PLAYER(gPlayState); + fetch = player->heldActor; + if (fetch != nullptr) { + display = fetch; + category = fetch->category; + list = GetCurrentSceneActors(); + method = HOLD; + } else { + display = {}; + } + } + + + if (UIWidgets::Button("Kill", {.color = UIWidgets::Colors::Red}) && display != nullptr && + display->id != ACTOR_PLAYER) { + Actor_Kill(display); + } + ImGui::TreePop(); + + } + + if (ImGui::TreeNode("New...")) { + ImGui::PushItemWidth(ImGui::GetFontSize() * 10); + ImU16 one = 1; + + ImGui::Text("%s", GetActorDescription(newActor.id).c_str()); + ImGui::InputScalar("ID", ImGuiDataType_S16, &newActor.id, &one); + ImGui::InputScalar("Params", ImGuiDataType_S16, &newActor.params, &one); + + ImGui::BeginGroup(); + ImGui::Text("New Actor Position"); + ImGui::InputScalar("x", ImGuiDataType_Float, &newActor.pos.x); + ImGui::SameLine(); + ImGui::InputScalar("y", ImGuiDataType_Float, &newActor.pos.y); + ImGui::SameLine(); + ImGui::InputScalar("z", ImGuiDataType_Float, &newActor.pos.z); + ImGui::EndGroup(); + + ImGui::BeginGroup(); + ImGui::Text("New Actor Rotation"); + ImGui::InputScalar("rX", ImGuiDataType_S16, &newActor.rot.x); + ImGui::SameLine(); + ImGui::InputScalar("rY", ImGuiDataType_S16, &newActor.rot.y); + ImGui::SameLine(); + ImGui::InputScalar("rZ", ImGuiDataType_S16, &newActor.rot.z); + ImGui::EndGroup(); + + UIWidgets::CVarCheckbox("Remove Obj Dep?", "gObjDep", { .tooltip = "Allows actors to spawn where/when they normally wouldn't."}); + + if (UIWidgets::Button("Fetch from Link")) { + Player* player = GET_PLAYER(gPlayState); + newActor.pos = player->actor.world.pos; + newActor.rot = player->actor.world.rot; + } + + if (UIWidgets::Button("Spawn", {.color = UIWidgets::Colors::Green})) { + Actor_Spawn(&gPlayState->actorCtx, gPlayState, newActor.id, newActor.pos.x, newActor.pos.y, + newActor.pos.z, newActor.rot.x, newActor.rot.y, newActor.rot.z, newActor.params); + } + + if (UIWidgets::Button("Spawn as Child", {.color = UIWidgets::Colors::Green})) { + Actor* parent = display; + if (parent != nullptr) { + Actor_SpawnAsChild(&gPlayState->actorCtx, parent, gPlayState, newActor.id, newActor.pos.x, + newActor.pos.y, newActor.pos.z, newActor.rot.x, newActor.rot.y, newActor.rot.z, newActor.params); + } else { + Audio_PlaySfx(NA_SE_SY_ERROR); + } + } + + if (UIWidgets::Button("Reset")) { + newActor = {}; + } + + ImGui::TreePop(); + } + } else { + ImGui::Text("Playstate needed for actors!"); + } + ImGui::End(); +} + +void ActorViewerWindow::InitElement() { + +} + diff --git a/mm/2s2h/DeveloperTools/ActorViewer.h b/mm/2s2h/DeveloperTools/ActorViewer.h new file mode 100644 index 000000000..6c239104c --- /dev/null +++ b/mm/2s2h/DeveloperTools/ActorViewer.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +class ActorViewerWindow : 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/src/code/z_scene.c b/mm/src/code/z_scene.c index 4da709541..38b031e83 100644 --- a/mm/src/code/z_scene.c +++ b/mm/src/code/z_scene.c @@ -1,4 +1,5 @@ #include "global.h" +#include /** * Spawn an object file of a specified ID that will persist through room changes. @@ -115,7 +116,7 @@ s32 Object_GetSlot(ObjectContext* objectCtx, s16 objectId) { } } - return -1; + return CVarGetInteger("gObjDep", 0) ? 0 : -1; } s32 Object_IsLoaded(ObjectContext* objectCtx, s32 slot) {