Rework ActorViewer to use hooks

This commit is contained in:
rozlette 2025-05-23 00:04:29 -05:00
commit f71aa90c5f
6 changed files with 144 additions and 201 deletions

View file

@ -7,6 +7,7 @@
#include "soh/Enhancements/nametag.h" #include "soh/Enhancements/nametag.h"
#include "soh/ShipInit.hpp" #include "soh/ShipInit.hpp"
#include <algorithm>
#include <array> #include <array>
#include <bit> #include <bit>
#include <map> #include <map>
@ -45,13 +46,6 @@ typedef struct {
Vec3s rot; Vec3s rot;
} ActorInfo; } ActorInfo;
typedef enum {
LIST,
TARGET,
HELD,
INTERACT,
} RetrievalMethod;
std::array<const char*, 12> acMapping = { std::array<const char*, 12> acMapping = {
"Switch", "Background (Prop type 1)", "Switch", "Background (Prop type 1)",
"Player", "Bomb", "Player", "Bomb",
@ -873,88 +867,14 @@ void ActorViewer_AddTagForAllActors() {
void ActorViewerWindow::DrawElement() { void ActorViewerWindow::DrawElement() {
ImGui::BeginDisabled(CVarGetInteger(CVAR_SETTING("DisableChanges"), 0)); ImGui::BeginDisabled(CVarGetInteger(CVAR_SETTING("DisableChanges"), 0));
static Actor* display;
static Actor empty{};
static Actor* fetch = NULL;
static ActorInfo newActor = { 0, 0, { 0, 0, 0 }, { 0, 0, 0 } }; static ActorInfo newActor = { 0, 0, { 0, 0, 0 }, { 0, 0, 0 } };
static bool needs_reset = false;
static ImU16 one = 1; static ImU16 one = 1;
static int actor;
static int category = 0;
static RetrievalMethod rm;
static std::string filler = "Please select"; static std::string filler = "Please select";
static std::vector<Actor*> list;
static u16 lastSceneId = 0;
static std::string searchString = ""; static std::string searchString = "";
static s16 currentSelectedInDropdown; static s16 currentSelectedInDropdown = -1;
static std::vector<u16> actors; static std::vector<u16> actorSearchResults;
if (gPlayState != nullptr) { if (gPlayState != nullptr) {
needs_reset = lastSceneId != gPlayState->sceneNum;
if (needs_reset) {
display = &empty;
fetch = nullptr;
actor = category = 0;
filler = "Please Select";
list.clear();
needs_reset = false;
searchString = "";
currentSelectedInDropdown = -1;
actors.clear();
}
lastSceneId = gPlayState->sceneNum;
if (ImGui::BeginChild("options", ImVec2(0, 0), ImGuiChildFlags_Border | ImGuiChildFlags_AutoResizeY)) {
bool toggled = false;
bool optionChange = false;
ImGui::SeparatorText("Options");
toggled = UIWidgets::CVarCheckbox("Actor Name Tags", CVAR_ACTOR_NAME_TAGS("Enabled"),
{ { .tooltip = "Adds \"name tags\" above actors for identification" } });
ImGui::SameLine();
UIWidgets::Button("Display Items", { { .tooltip = "Click to add display items on the name tags" } });
if (ImGui::BeginPopupContextItem(nullptr, ImGuiPopupFlags_MouseButtonLeft | ImGuiPopupFlags_NoReopen)) {
optionChange |= UIWidgets::CVarCheckbox("ID", CVAR_ACTOR_NAME_TAGS("DisplayID"));
optionChange |= UIWidgets::CVarCheckbox("Description", CVAR_ACTOR_NAME_TAGS("DisplayDescription"));
optionChange |= UIWidgets::CVarCheckbox("Category", CVAR_ACTOR_NAME_TAGS("DisplayCategory"));
optionChange |= UIWidgets::CVarCheckbox("Params", CVAR_ACTOR_NAME_TAGS("DisplayParams"));
ImGui::EndPopup();
}
optionChange |= UIWidgets::CVarCheckbox(
"Name tags with Z-Buffer", CVAR_ACTOR_NAME_TAGS("WithZBuffer"),
{ { .tooltip = "Allow name tags to be obstructed when behind geometry and actors" } });
if (toggled || optionChange) {
bool tagsEnabled = CVarGetInteger(CVAR_ACTOR_NAME_TAGS("Enabled"), 0);
bool noOptionsEnabled = !CVarGetInteger(CVAR_ACTOR_NAME_TAGS("DisplayID"), 0) &&
!CVarGetInteger(CVAR_ACTOR_NAME_TAGS("DisplayDescription"), 0) &&
!CVarGetInteger(CVAR_ACTOR_NAME_TAGS("DisplayCategory"), 0) &&
!CVarGetInteger(CVAR_ACTOR_NAME_TAGS("DisplayParams"), 0);
// Save the user an extra click and prevent adding "empty" tags by enabling,
// disabling, or setting an option based on what changed
if (tagsEnabled && noOptionsEnabled) {
if (toggled) {
CVarSetInteger(CVAR_ACTOR_NAME_TAGS("DisplayID"), 1);
} else {
CVarSetInteger(CVAR_ACTOR_NAME_TAGS("Enabled"), 0);
}
} else if (optionChange && !tagsEnabled && !noOptionsEnabled) {
CVarSetInteger(CVAR_ACTOR_NAME_TAGS("Enabled"), 1);
}
NameTag_RemoveAllByTag(DEBUG_ACTOR_NAMETAG_TAG);
ActorViewer_AddTagForAllActors();
}
}
ImGui::EndChild();
PushStyleCombobox(THEME_COLOR); PushStyleCombobox(THEME_COLOR);
if (ImGui::BeginCombo("Actor Type", acMapping[category])) { if (ImGui::BeginCombo("Actor Type", acMapping[category])) {
for (int i = 0; i < acMapping.size(); i++) { for (int i = 0; i < acMapping.size(); i++) {
@ -967,21 +887,19 @@ void ActorViewerWindow::DrawElement() {
ImGui::EndCombo(); ImGui::EndCombo();
} }
if (ImGui::BeginCombo("Actor", filler.c_str())) { if (display == nullptr) {
if (gPlayState != nullptr && lastSceneId != gPlayState->sceneNum) { filler = "Please select";
PopulateActorDropdown(category, list);
lastSceneId = gPlayState->sceneNum;
} }
if (ImGui::BeginCombo("Actor", filler.c_str())) {
for (int i = 0; i < list.size(); i++) { for (int i = 0; i < list.size(); i++) {
std::string label = std::to_string(i) + ": " + ActorDB::Instance->RetrieveEntry(list[i]->id).name; std::string label = std::to_string(i) + ": " + ActorDB::Instance->RetrieveEntry(list[i]->id).name;
std::string description = GetActorDescription(list[i]->id); std::string description = GetActorDescription(list[i]->id);
if (description != "") if (description != "")
label += " (" + description + ")"; label += " (" + description + ")";
if (ImGui::Selectable(label.c_str())) { if (ImGui::Selectable(label.c_str(), list[i] == display)) {
rm = LIST;
display = list[i]; display = list[i];
actor = i;
filler = label; filler = label;
break; break;
} }
@ -992,6 +910,7 @@ void ActorViewerWindow::DrawElement() {
PushStyleHeader(THEME_COLOR); PushStyleHeader(THEME_COLOR);
if (ImGui::TreeNode("Selected Actor")) { if (ImGui::TreeNode("Selected Actor")) {
if (display != nullptr) {
DrawGroupWithBorder( DrawGroupWithBorder(
[&]() { [&]() {
ImGui::Text("Name: %s", ActorDB::Instance->RetrieveEntry(display->id).name.c_str()); ImGui::Text("Name: %s", ActorDB::Instance->RetrieveEntry(display->id).name.c_str());
@ -1054,61 +973,42 @@ void ActorViewerWindow::DrawElement() {
}, },
"bgCheckFlags"); "bgCheckFlags");
if (Button("Refresh", ButtonOptions().Color(THEME_COLOR))) {
PopulateActorDropdown(category, list);
switch (rm) {
case INTERACT:
case HELD:
case TARGET:
display = fetch;
break;
case LIST:
display = list[actor];
break;
default:
break;
}
}
if (Button("Go to Actor", ButtonOptions().Color(THEME_COLOR))) { if (Button("Go to Actor", ButtonOptions().Color(THEME_COLOR))) {
Player* player = GET_PLAYER(gPlayState); Player* player = GET_PLAYER(gPlayState);
Math_Vec3f_Copy(&player->actor.world.pos, &display->world.pos); Math_Vec3f_Copy(&player->actor.world.pos, &display->world.pos);
Math_Vec3f_Copy(&player->actor.home.pos, &player->actor.world.pos); Math_Vec3f_Copy(&player->actor.home.pos, &player->actor.world.pos);
} }
} else {
ImGui::Text("Select an actor to display information.");
}
if (Button("Fetch from Target", if (Button("Fetch from Target",
ButtonOptions() ButtonOptions()
.Color(THEME_COLOR) .Color(THEME_COLOR)
.Tooltip("Grabs actor with target arrow above it. You might need C-Up for enemies"))) { .Tooltip("Grabs actor with target arrow above it. You might need C-Up for enemies"))) {
Player* player = GET_PLAYER(gPlayState); Player* player = GET_PLAYER(gPlayState);
fetch = player->talkActor; if (player->talkActor != NULL) {
if (fetch != NULL) { display = player->talkActor;
display = fetch; category = display->category;
category = fetch->category;
PopulateActorDropdown(category, list); PopulateActorDropdown(category, list);
rm = TARGET;
} }
} }
if (Button("Fetch from Held", if (Button("Fetch from Held",
ButtonOptions().Color(THEME_COLOR).Tooltip("Grabs actor that Link is holding"))) { ButtonOptions().Color(THEME_COLOR).Tooltip("Grabs actor that Link is holding"))) {
Player* player = GET_PLAYER(gPlayState); Player* player = GET_PLAYER(gPlayState);
fetch = player->heldActor; if (player->heldActor != NULL) {
if (fetch != NULL) { display = player->heldActor;
display = fetch; category = display->category;
category = fetch->category;
PopulateActorDropdown(category, list); PopulateActorDropdown(category, list);
rm = HELD;
} }
} }
if (Button("Fetch from Interaction", if (Button("Fetch from Interaction",
ButtonOptions().Color(THEME_COLOR).Tooltip("Grabs actor from \"interaction range\""))) { ButtonOptions().Color(THEME_COLOR).Tooltip("Grabs actor from \"interaction range\""))) {
Player* player = GET_PLAYER(gPlayState); Player* player = GET_PLAYER(gPlayState);
fetch = player->interactRangeActor; if (player->interactRangeActor != NULL) {
if (fetch != NULL) { display = player->interactRangeActor;
display = fetch; category = display->category;
category = fetch->category;
PopulateActorDropdown(category, list); PopulateActorDropdown(category, list);
rm = INTERACT;
} }
} }
@ -1119,21 +1019,22 @@ void ActorViewerWindow::DrawElement() {
// ImGui::PushItemWidth(ImGui::GetFontSize() * 10); // ImGui::PushItemWidth(ImGui::GetFontSize() * 10);
if (InputString("Search Actor", &searchString, InputOptions().Color(THEME_COLOR))) { if (InputString("Search Actor", &searchString, InputOptions().Color(THEME_COLOR))) {
actors = GetActorsWithDescriptionContainingString(searchString); actorSearchResults = GetActorsWithDescriptionContainingString(searchString);
currentSelectedInDropdown = -1; currentSelectedInDropdown = -1;
} }
if (!SohUtils::IsStringEmpty(searchString) && !actors.empty()) { if (!SohUtils::IsStringEmpty(searchString) && !actorSearchResults.empty()) {
std::string preview = currentSelectedInDropdown == -1 std::string preview =
currentSelectedInDropdown == -1
? "Please Select" ? "Please Select"
: ActorDB::Instance->RetrieveEntry(actors[currentSelectedInDropdown]).desc; : ActorDB::Instance->RetrieveEntry(actorSearchResults[currentSelectedInDropdown]).desc;
PushStyleCombobox(THEME_COLOR); PushStyleCombobox(THEME_COLOR);
if (ImGui::BeginCombo("Results", preview.c_str())) { if (ImGui::BeginCombo("Results", preview.c_str())) {
for (u8 i = 0; i < actors.size(); i++) { for (u8 i = 0; i < actorSearchResults.size(); i++) {
if (ImGui::Selectable(ActorDB::Instance->RetrieveEntry(actors[i]).desc.c_str(), if (ImGui::Selectable(ActorDB::Instance->RetrieveEntry(actorSearchResults[i]).desc.c_str(),
i == currentSelectedInDropdown)) { i == currentSelectedInDropdown)) {
currentSelectedInDropdown = i; currentSelectedInDropdown = i;
newActor.id = actors[i]; newActor.id = actorSearchResults[i];
} }
} }
ImGui::EndCombo(); ImGui::EndCombo();
@ -1237,20 +1138,38 @@ void ActorViewerWindow::DrawElement() {
PopStyleHeader(); PopStyleHeader();
} else { } else {
ImGui::Text("Global Context needed for actor info!"); ImGui::Text("Global Context needed for actor info!");
if (needs_reset) {
fetch = nullptr;
actor = category = 0;
filler = "Please Select";
list.clear();
needs_reset = false;
searchString = "";
currentSelectedInDropdown = -1;
actors.clear();
}
} }
ImGui::EndDisabled(); ImGui::EndDisabled();
} }
void ActorViewerWindow::InitElement() {
ResetData();
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorSpawn>([this](void* refActor) {
Actor* actor = static_cast<Actor*>(refActor);
// Reload actor list if the new actor belongs to the selected category
if (category == actor->category) {
PopulateActorDropdown(actor->category, list);
}
});
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorDestroy>([this](void* refActor) {
Actor* actor = static_cast<Actor*>(refActor);
// If the actor belongs to the selected category, we need to manually remove it, as it has not been removed from
// the global actor array yet
if (category == actor->category) {
list.erase(std::remove(list.begin(), list.end(), actor), list.end());
}
if (display == actor) {
display = nullptr;
}
});
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnSceneInit>([this](int16_t sceneNum) { ResetData(); });
}
void ActorViewer_RegisterNameTagHooks() { void ActorViewer_RegisterNameTagHooks() {
COND_HOOK(OnActorInit, CVAR_ACTOR_NAME_TAGS_ENABLED, COND_HOOK(OnActorInit, CVAR_ACTOR_NAME_TAGS_ENABLED,
[](void* actor) { ActorViewer_AddTagForActor(static_cast<Actor*>(actor)); }); [](void* actor) { ActorViewer_AddTagForActor(static_cast<Actor*>(actor)); });

View file

@ -2,6 +2,10 @@
#include <libultraship/libultraship.h> #include <libultraship/libultraship.h>
#include "z64actor.h"
#include <vector>
class ActorViewerWindow final : public Ship::GuiWindow { class ActorViewerWindow final : public Ship::GuiWindow {
public: public:
using GuiWindow::GuiWindow; using GuiWindow::GuiWindow;
@ -9,4 +13,11 @@ class ActorViewerWindow final : public Ship::GuiWindow {
void DrawElement() override; void DrawElement() override;
void InitElement() override{}; void InitElement() override{};
void UpdateElement() override{}; void UpdateElement() override{};
private:
void ResetData();
Actor* display = nullptr;
int category = ACTORCAT_SWITCH;
std::vector<Actor*> list;
}; };

View file

@ -27,6 +27,7 @@ DEFINE_HOOK(OnOcarinaSongAction, ());
DEFINE_HOOK(OnCuccoOrChickenHatch, ()); DEFINE_HOOK(OnCuccoOrChickenHatch, ());
DEFINE_HOOK(OnShopSlotChange, (uint8_t cursorIndex, int16_t price)); DEFINE_HOOK(OnShopSlotChange, (uint8_t cursorIndex, int16_t price));
DEFINE_HOOK(OnActorInit, (void* actor)); DEFINE_HOOK(OnActorInit, (void* actor));
DEFINE_HOOK(OnActorSpawn, (void* actor));
DEFINE_HOOK(OnActorUpdate, (void* actor)); DEFINE_HOOK(OnActorUpdate, (void* actor));
DEFINE_HOOK(OnActorKill, (void* actor)); DEFINE_HOOK(OnActorKill, (void* actor));
DEFINE_HOOK(OnActorDestroy, (void* actor)); DEFINE_HOOK(OnActorDestroy, (void* actor));

View file

@ -108,6 +108,13 @@ void GameInteractor_ExecuteOnActorInit(void* actor) {
GameInteractor::Instance->ExecuteHooksForFilter<GameInteractor::OnActorInit>(actor); GameInteractor::Instance->ExecuteHooksForFilter<GameInteractor::OnActorInit>(actor);
} }
void GameInteractor_ExecuteOnActorSpawn(void* actor) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnActorSpawn>(actor);
GameInteractor::Instance->ExecuteHooksForID<GameInteractor::OnActorSpawn>(((Actor*)actor)->id, actor);
GameInteractor::Instance->ExecuteHooksForPtr<GameInteractor::OnActorSpawn>((uintptr_t)actor, actor);
GameInteractor::Instance->ExecuteHooksForFilter<GameInteractor::OnActorSpawn>(actor);
}
void GameInteractor_ExecuteOnActorUpdate(void* actor) { void GameInteractor_ExecuteOnActorUpdate(void* actor) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnActorUpdate>(actor); GameInteractor::Instance->ExecuteHooks<GameInteractor::OnActorUpdate>(actor);
GameInteractor::Instance->ExecuteHooksForID<GameInteractor::OnActorUpdate>(((Actor*)actor)->id, actor); GameInteractor::Instance->ExecuteHooksForID<GameInteractor::OnActorUpdate>(((Actor*)actor)->id, actor);

View file

@ -29,6 +29,7 @@ void GameInteractor_ExecuteOnSetDoAction(uint16_t action);
void GameInteractor_ExecuteOnOcarinaSongAction(); void GameInteractor_ExecuteOnOcarinaSongAction();
void GameInteractor_ExecuteOnCuccoOrChickenHatch(); void GameInteractor_ExecuteOnCuccoOrChickenHatch();
void GameInteractor_ExecuteOnActorInit(void* actor); void GameInteractor_ExecuteOnActorInit(void* actor);
void GameInteractor_ExecuteOnActorSpawn(void* actor);
void GameInteractor_ExecuteOnActorUpdate(void* actor); void GameInteractor_ExecuteOnActorUpdate(void* actor);
void GameInteractor_ExecuteOnActorKill(void* actor); void GameInteractor_ExecuteOnActorKill(void* actor);
void GameInteractor_ExecuteOnActorDestroy(void* actor); void GameInteractor_ExecuteOnActorDestroy(void* actor);

View file

@ -3401,6 +3401,8 @@ Actor* Actor_Spawn(ActorContext* actorCtx, PlayState* play, s16 actorId, f32 pos
Actor_Init(actor, play); Actor_Init(actor, play);
gSegments[6] = temp; gSegments[6] = temp;
GameInteractor_ExecuteOnActorSpawn(actor);
return actor; return actor;
} }
@ -3474,6 +3476,8 @@ Actor* Actor_Delete(ActorContext* actorCtx, Actor* actor, PlayState* play) {
Actor* newHead; Actor* newHead;
ActorDBEntry* dbEntry; ActorDBEntry* dbEntry;
GameInteractor_ExecuteOnActorDelete(actor);
player = GET_PLAYER(play); player = GET_PLAYER(play);
// Execute before actor memory is freed // Execute before actor memory is freed