From 5b14cf5f69c748d992df0aff64d1a33d508f6a5e Mon Sep 17 00:00:00 2001 From: Adam Bird Date: Wed, 19 Mar 2025 10:47:52 -0400 Subject: [PATCH] port over nametag improvements from 2ship --- soh/soh/Enhancements/debugger/actorViewer.cpp | 133 +++++++++++++----- soh/soh/Enhancements/debugger/actorViewer.h | 2 +- .../GameInteractor_HookTable.h | 1 + .../game-interactor/GameInteractor_Hooks.cpp | 7 + .../game-interactor/GameInteractor_Hooks.h | 1 + soh/soh/Enhancements/mods.cpp | 2 - soh/soh/Enhancements/nametag.cpp | 101 +++++++++---- soh/soh/Enhancements/nametag.h | 13 +- soh/src/code/z_actor.c | 3 + 9 files changed, 193 insertions(+), 70 deletions(-) diff --git a/soh/soh/Enhancements/debugger/actorViewer.cpp b/soh/soh/Enhancements/debugger/actorViewer.cpp index 8ae940ffb..7792f627b 100644 --- a/soh/soh/Enhancements/debugger/actorViewer.cpp +++ b/soh/soh/Enhancements/debugger/actorViewer.cpp @@ -5,6 +5,7 @@ #include "soh/ActorDB.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" #include "soh/Enhancements/nametag.h" +#include "soh/ShipInit.hpp" #include #include @@ -13,6 +14,7 @@ #include #include #include +#include #include "soh/OTRGlobals.h" #include "soh/cvar_prefixes.h" @@ -31,6 +33,10 @@ extern PlayState* gPlayState; #define DEKUNUTS_FLOWER 10 #define DEBUG_ACTOR_NAMETAG_TAG "debug_actor_viewer" +#define CVAR_ACTOR_NAME_TAGS(val) CVAR_DEVELOPER_TOOLS("ActorViewer.NameTags." val) +#define CVAR_ACTOR_NAME_TAGS_ENABLED_NAME CVAR_ACTOR_NAME_TAGS("Enabled") +#define CVAR_ACTOR_NAME_TAGS_ENABLED CVarGetInteger(CVAR_ACTOR_NAME_TAGS("Enabled"), 0) + typedef struct { u16 id; u16 params; @@ -67,6 +73,10 @@ const std::string GetActorDescription(u16 id) { return ActorDB::Instance->RetrieveEntry(id).entry.valid ? ActorDB::Instance->RetrieveEntry(id).entry.desc : "???"; } +const std::string GetActorDebugName(u16 id) { + return ActorDB::Instance->RetrieveEntry(id).entry.valid ? ActorDB::Instance->RetrieveEntry(id).entry.name : "???"; +} + template void DrawGroupWithBorder(T&& drawFunc, std::string section) { // First group encapsulates the inner portion and border ImGui::BeginChild(std::string("##" + section).c_str(), ImVec2(0, 0), @@ -812,25 +822,37 @@ std::vector GetActorsWithDescriptionContainingString(std::string s) { } void ActorViewer_AddTagForActor(Actor* actor) { - int val = CVarGetInteger(CVAR_DEVELOPER_TOOLS("ActorViewer.NameTags"), ACTORVIEWER_NAMETAGS_NONE); - auto entry = ActorDB::Instance->RetrieveEntry(actor->id); - std::string tag; - - if (val > 0 && entry.entry.valid) { - switch (val) { - case ACTORVIEWER_NAMETAGS_DESC: - tag = entry.desc; - break; - case ACTORVIEWER_NAMETAGS_NAME: - tag = entry.name; - break; - case ACTORVIEWER_NAMETAGS_BOTH: - tag = entry.name + '\n' + entry.desc; - break; - } - - NameTag_RegisterForActorWithOptions(actor, tag.c_str(), { .tag = DEBUG_ACTOR_NAMETAG_TAG }); + if (!CVarGetInteger(CVAR_ACTOR_NAME_TAGS("Enabled"), 0)) { + return; } + + std::vector parts; + + if (CVarGetInteger(CVAR_ACTOR_NAME_TAGS("DisplayID"), 0)) { + parts.push_back(GetActorDebugName(actor->id)); + } + if (CVarGetInteger(CVAR_ACTOR_NAME_TAGS("DisplayDescription"), 0)) { + parts.push_back(GetActorDescription(actor->id)); + } + if (CVarGetInteger(CVAR_ACTOR_NAME_TAGS("DisplayCategory"), 0)) { + parts.push_back(acMapping[actor->category]); + } + if (CVarGetInteger(CVAR_ACTOR_NAME_TAGS("DisplayParams"), 0)) { + parts.push_back(fmt::format("0x{:04X} ({})", (u16)actor->params, actor->params)); + } + + std::string tag = ""; + for (size_t i = 0; i < parts.size(); i++) { + if (i != 0) { + tag += "\n"; + } + tag += parts.at(i); + } + + bool withZBuffer = CVarGetInteger(CVAR_ACTOR_NAME_TAGS("WithZBuffer"), 0); + + NameTag_RegisterForActorWithOptions(actor, tag.c_str(), + { .tag = DEBUG_ACTOR_NAMETAG_TAG, .noZBuffer = !withZBuffer }); } void ActorViewer_AddTagForAllActors() { @@ -880,6 +902,57 @@ void ActorViewerWindow::DrawElement() { } 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); if (ImGui::BeginCombo("Actor Type", acMapping[category])) { for (int i = 0; i < acMapping.size(); i++) { @@ -1159,20 +1232,6 @@ void ActorViewerWindow::DrawElement() { ImGui::TreePop(); } PopStyleHeader(); - - static std::unordered_map nameTagOptions = { - { 0, "None" }, - { 1, "Short Description" }, - { 2, "Actor ID" }, - { 3, "Both" }, - }; - - if (CVarCombobox( - "Actor Name Tags", CVAR_DEVELOPER_TOOLS("ActorViewer.NameTags"), nameTagOptions, - ComboboxOptions().Color(THEME_COLOR).Tooltip("Adds \"name tags\" above actors for identification"))) { - NameTag_RemoveAllByTag(DEBUG_ACTOR_NAMETAG_TAG); - ActorViewer_AddTagForAllActors(); - } } else { ImGui::Text("Global Context needed for actor info!"); if (needs_reset) { @@ -1188,9 +1247,9 @@ void ActorViewerWindow::DrawElement() { } } -void ActorViewerWindow::InitElement() { - GameInteractor::Instance->RegisterGameHook([](void* refActor) { - Actor* actor = static_cast(refActor); - ActorViewer_AddTagForActor(actor); - }); +void ActorViewer_RegisterNameTagHooks() { + COND_HOOK(OnActorInit, CVAR_ACTOR_NAME_TAGS_ENABLED, + [](void* actor) { ActorViewer_AddTagForActor(static_cast(actor)); }); } + +RegisterShipInitFunc nametagInit(ActorViewer_RegisterNameTagHooks, { CVAR_ACTOR_NAME_TAGS_ENABLED_NAME }); diff --git a/soh/soh/Enhancements/debugger/actorViewer.h b/soh/soh/Enhancements/debugger/actorViewer.h index bab7d1646..fd0e66628 100644 --- a/soh/soh/Enhancements/debugger/actorViewer.h +++ b/soh/soh/Enhancements/debugger/actorViewer.h @@ -7,6 +7,6 @@ class ActorViewerWindow : public Ship::GuiWindow { using GuiWindow::GuiWindow; void DrawElement() override; - void InitElement() override; + void InitElement() override{}; void UpdateElement() override{}; }; diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h b/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h index 44d0e643a..2cbb6b27a 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_HookTable.h @@ -27,6 +27,7 @@ DEFINE_HOOK(OnShopSlotChange, (uint8_t cursorIndex, int16_t price)); DEFINE_HOOK(OnActorInit, (void* actor)); DEFINE_HOOK(OnActorUpdate, (void* actor)); DEFINE_HOOK(OnActorKill, (void* actor)); +DEFINE_HOOK(OnActorDestroy, (void* actor)); DEFINE_HOOK(OnEnemyDefeat, (void* actor)); DEFINE_HOOK(OnBossDefeat, (void* actor)); DEFINE_HOOK(OnTimestamp, (u8 item)); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp index 9231da843..186779255 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.cpp @@ -112,6 +112,13 @@ void GameInteractor_ExecuteOnActorKill(void* actor) { GameInteractor::Instance->ExecuteHooksForFilter(actor); } +void GameInteractor_ExecuteOnActorDestroy(void* actor) { + GameInteractor::Instance->ExecuteHooks(actor); + GameInteractor::Instance->ExecuteHooksForID(((Actor*)actor)->id, actor); + GameInteractor::Instance->ExecuteHooksForPtr((uintptr_t)actor, actor); + GameInteractor::Instance->ExecuteHooksForFilter(actor); +} + void GameInteractor_ExecuteOnEnemyDefeat(void* actor) { GameInteractor::Instance->ExecuteHooks(actor); GameInteractor::Instance->ExecuteHooksForID(((Actor*)actor)->id, actor); diff --git a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h index 33c8d2681..588f8a24d 100644 --- a/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h +++ b/soh/soh/Enhancements/game-interactor/GameInteractor_Hooks.h @@ -29,6 +29,7 @@ void GameInteractor_ExecuteOnCuccoOrChickenHatch(); void GameInteractor_ExecuteOnActorInit(void* actor); void GameInteractor_ExecuteOnActorUpdate(void* actor); void GameInteractor_ExecuteOnActorKill(void* actor); +void GameInteractor_ExecuteOnActorDestroy(void* actor); void GameInteractor_ExecuteOnEnemyDefeat(void* actor); void GameInteractor_ExecuteOnBossDefeat(void* actor); void GameInteractor_ExecuteOnTimestamp(u8 item); diff --git a/soh/soh/Enhancements/mods.cpp b/soh/soh/Enhancements/mods.cpp index 2c357fd34..a183680a8 100644 --- a/soh/soh/Enhancements/mods.cpp +++ b/soh/soh/Enhancements/mods.cpp @@ -12,7 +12,6 @@ #include "soh/Enhancements/randomizer/3drando/random.hpp" #include "soh/Enhancements/cosmetics/authenticGfxPatches.h" #include -#include "soh/Enhancements/nametag.h" #include "soh/Enhancements/timesaver_hook_handlers.h" #include "soh/Enhancements/TimeSavers/TimeSavers.h" #include "soh/Enhancements/randomizer/hook_handlers.h" @@ -1090,7 +1089,6 @@ void InitMods() { RegisterRandomizedEnemySizes(); RegisterOpenAllHours(); RegisterToTMedallions(); - NameTag_RegisterHooks(); RegisterFloorSwitchesHook(); RegisterPatchHandHandler(); RegisterHurtContainerModeHandler(); diff --git a/soh/soh/Enhancements/nametag.cpp b/soh/soh/Enhancements/nametag.cpp index 253c0980b..1989b3552 100644 --- a/soh/soh/Enhancements/nametag.cpp +++ b/soh/soh/Enhancements/nametag.cpp @@ -5,7 +5,7 @@ #include "soh/frame_interpolation.h" #include "soh/Enhancements/custom-message/CustomMessageInterfaceAddon.h" #include "soh/Enhancements/game-interactor/GameInteractor.h" -#include "soh/OTRGlobals.h" +#include "soh/ShipUtils.h" extern "C" { #include "z64.h" @@ -26,12 +26,16 @@ typedef struct { int16_t height; // Textbox height int16_t width; // Textbox width int16_t yOffset; // Addition Y offset + uint8_t noZBuffer; // Allow rendering over geometry Mtx* mtx; // Allocated Mtx for rendering Vtx* vtx; // Allocated Vtx for rendering } NameTag; static std::vector nameTags; static std::vector nameTagDl; +static bool sMirrorWorldActive = false; + +void NameTag_RegisterHooks(); void FreeNameTag(NameTag* nameTag) { if (nameTag->vtx != nullptr) { @@ -51,14 +55,14 @@ void DrawNameTag(PlayState* play, const NameTag* nameTag) { } // Name tag is too far away to meaningfully read, don't bother rendering it - if (nameTag->actor->xyzDistToPlayerSq > 200000.0f) { + if (nameTag->actor->xyzDistToPlayerSq > 440000.0f) { return; } // Fade out name tags that are far away float alpha = 1.0f; - if (nameTag->actor->xyzDistToPlayerSq > 160000.0f) { - alpha = (200000.0f - nameTag->actor->xyzDistToPlayerSq) / 40000.0f; + if (nameTag->actor->xyzDistToPlayerSq > 360000.0f) { + alpha = (440000.0f - nameTag->actor->xyzDistToPlayerSq) / 80000.0f; } float scale = 75.0f / 100.f; @@ -79,7 +83,7 @@ void DrawNameTag(PlayState* play, const NameTag* nameTag) { textColor = CVarGetColor(CVAR_COSMETIC("HUD.NameTagActorText.Value"), textColor); } - FrameInterpolation_RecordOpenChild(nameTag->actor, 10); + FrameInterpolation_RecordOpenChild(nameTag->actor, 0); // Prefer the highest between world position and focus position if targetable float posY = nameTag->actor->world.pos.y; @@ -92,7 +96,7 @@ void DrawNameTag(PlayState* play, const NameTag* nameTag) { // Set position, billboard effect, scale (with mirror mode), then center nametag Matrix_Translate(nameTag->actor->world.pos.x, posY, nameTag->actor->world.pos.z, MTXMODE_NEW); Matrix_ReplaceRotation(&play->billboardMtxF); - Matrix_Scale(scale * (CVarGetInteger(CVAR_ENHANCEMENT("MirroredWorld"), 0) ? -1 : 1), -scale, 1.0f, MTXMODE_APPLY); + Matrix_Scale(scale * (sMirrorWorldActive ? -1.0f : 1.0f), -scale, 1.0f, MTXMODE_APPLY); Matrix_Translate(-(float)nameTag->width / 2, -nameTag->height, 0, MTXMODE_APPLY); Matrix_ToMtx(nameTag->mtx, (char*)__FILE__, __LINE__); @@ -154,15 +158,27 @@ void DrawNameTags() { OPEN_DISPS(gPlayState->state.gfxCtx); // Setup before rendering name tags - Gfx_SetupDL_38Xlu(gPlayState->state.gfxCtx); - nameTagDl.push_back(gsDPSetAlphaDither(G_AD_DISABLE)); - nameTagDl.push_back(gsSPClearGeometryMode(G_SHADE)); + POLY_XLU_DISP = Gfx_SetupDL_39(POLY_XLU_DISP); + nameTagDl.push_back(gsDPSetAlphaCompare(G_AC_NONE)); nameTagDl.push_back( gsDPSetCombineLERP(0, 0, 0, PRIMITIVE, TEXEL0, 0, PRIMITIVE, 0, 0, 0, 0, PRIMITIVE, TEXEL0, 0, PRIMITIVE, 0)); + bool zbufferEnabled = false; + // Add all the name tags for (const auto& nameTag : nameTags) { + // Toggle ZBuffer mode based on last state and next tag + if (zbufferEnabled == nameTag.noZBuffer) { + if (nameTag.noZBuffer) { + nameTagDl.push_back(gsSPClearGeometryMode(G_ZBUFFER)); + zbufferEnabled = false; + } else { + nameTagDl.push_back(gsSPSetGeometryMode(G_ZBUFFER)); + zbufferEnabled = true; + } + } + DrawNameTag(gPlayState, &nameTag); } @@ -189,22 +205,22 @@ void UpdateNameTags() { return aDistToCamera > bDistToCamera; }); + + sMirrorWorldActive = CVarGetInteger(CVAR_ENHANCEMENT("MirroredWorld"), 0); } extern "C" void NameTag_RegisterForActorWithOptions(Actor* actor, const char* text, NameTagOptions options) { std::string processedText = std::string(Interface_ReplaceSpecialCharacters((char*)text)); // Strip out unsupported characters - processedText.erase(std::remove_if(processedText.begin(), processedText.end(), - [](const char& c) { - // 172 is max supported texture for the in-game font system, - // and filter anything less than a space but not the newline or nul - // characters - return (unsigned char)c > 172 || (c < ' ' && c != '\n' && c != '\0'); - }), - processedText.end()); + // 172 is max supported texture for the in-game font system, + // and filter anything less than a space but not the newline or nul characters + processedText.erase( + std::remove_if(processedText.begin(), processedText.end(), + [](const char& c) { return (uint8_t)c > 172 || (c < ' ' && c != '\n' && c != '\0'); }), + processedText.end()); - int16_t numChar = processedText.length(); + size_t numChar = processedText.length(); int16_t numLines = 1; int16_t offsetX = 0; int16_t maxOffsetX = 0; @@ -213,7 +229,7 @@ extern "C" void NameTag_RegisterForActorWithOptions(Actor* actor, const char* te Vtx* vertices = (Vtx*)calloc(sizeof(Vtx[4]), numChar + 1); // Set all the char vtx first to get the total size for the textbox - for (int16_t i = 0; i < numChar; i++) { + for (size_t i = 0; i < numChar; i++) { if (processedText[i] == '\n') { offsetX = 0; numLines++; @@ -249,10 +265,13 @@ extern "C" void NameTag_RegisterForActorWithOptions(Actor* actor, const char* te nameTag.height = height; nameTag.width = width; nameTag.yOffset = options.yOffset; + nameTag.noZBuffer = options.noZBuffer; nameTag.mtx = new Mtx(); nameTag.vtx = vertices; nameTags.push_back(nameTag); + + NameTag_RegisterHooks(); } extern "C" void NameTag_RegisterForActor(Actor* actor, const char* text) { @@ -268,6 +287,8 @@ extern "C" void NameTag_RemoveAllForActor(Actor* actor) { it++; } } + + NameTag_RegisterHooks(); } extern "C" void NameTag_RemoveAllByTag(const char* tag) { @@ -279,6 +300,8 @@ extern "C" void NameTag_RemoveAllByTag(const char* tag) { it++; } } + + NameTag_RegisterHooks(); } void RemoveAllNameTags() { @@ -287,23 +310,49 @@ void RemoveAllNameTags() { } nameTags.clear(); + + NameTag_RegisterHooks(); } -static bool sRegisteredHooks = false; - void NameTag_RegisterHooks() { - if (sRegisteredHooks) { + static HOOK_ID gameStatUpdateHookID = 0; + static HOOK_ID drawHookID = 0; + static HOOK_ID playDestroyHookID = 0; + static HOOK_ID actorDestroyHookID = 0; + static bool sRegisteredHooks = false; + + // Hooks already (un)registered based on nametags + if ((nameTags.size() > 0) == sRegisteredHooks) { + return; + } + + GameInteractor::Instance->UnregisterGameHook(gameStatUpdateHookID); + GameInteractor::Instance->UnregisterGameHook(drawHookID); + GameInteractor::Instance->UnregisterGameHook(playDestroyHookID); + GameInteractor::Instance->UnregisterGameHook(actorDestroyHookID); + gameStatUpdateHookID = 0; + drawHookID = 0; + playDestroyHookID = 0; + actorDestroyHookID = 0; + sRegisteredHooks = false; + + if (nameTags.size() == 0) { return; } sRegisteredHooks = true; // Reorder tags every frame to mimic depth rendering - GameInteractor::Instance->RegisterGameHook([]() { UpdateNameTags(); }); + gameStatUpdateHookID = + GameInteractor::Instance->RegisterGameHook(UpdateNameTags); - // Render name tags at the end of player draw to avoid overflowing the display buffers - GameInteractor::Instance->RegisterGameHook([]() { DrawNameTags(); }); + // Render name tags at the end of the Play World drawing + drawHookID = GameInteractor::Instance->RegisterGameHook(DrawNameTags); // Remove all name tags on play state destroy as all actors are removed anyways - GameInteractor::Instance->RegisterGameHook([]() { RemoveAllNameTags(); }); + playDestroyHookID = GameInteractor::Instance->RegisterGameHook(RemoveAllNameTags); + + // Remove all name tags for actor on destroy + actorDestroyHookID = GameInteractor::Instance->RegisterGameHook( + [](void* actor) { NameTag_RemoveAllForActor((Actor*)actor); }); } diff --git a/soh/soh/Enhancements/nametag.h b/soh/soh/Enhancements/nametag.h index 98b83f7cc..47908e875 100644 --- a/soh/soh/Enhancements/nametag.h +++ b/soh/soh/Enhancements/nametag.h @@ -1,11 +1,16 @@ -#ifndef _NAMETAG_H_ -#define _NAMETAG_H_ -#include +#ifndef NAMETAG_H +#define NAMETAG_H + +#include +#include + +struct Actor; typedef struct { const char* tag; // Tag identifier to filter/remove multiple tags int16_t yOffset; // Additional Y offset to apply for the name tag Color_RGBA8 textColor; // Text color override. Global color is used if alpha is 0 + uint8_t noZBuffer; // Allow rendering over geometry } NameTagOptions; // Register required hooks for nametags on startup @@ -28,4 +33,4 @@ void NameTag_RemoveAllByTag(const char* tag); } #endif -#endif // _NAMETAG_H_ +#endif // NAMETAG_H diff --git a/soh/src/code/z_actor.c b/soh/src/code/z_actor.c index 3ca67ad3b..ac9e4f3f6 100644 --- a/soh/src/code/z_actor.c +++ b/soh/src/code/z_actor.c @@ -3466,6 +3466,9 @@ Actor* Actor_Delete(ActorContext* actorCtx, Actor* actor, PlayState* play) { player = GET_PLAYER(play); + // Execute before actor memory is freed + GameInteractor_ExecuteOnActorDestroy(actor); + dbEntry = ActorDB_Retrieve(actor->id); if (HREG(20) != 0) {