port over nametag improvements from 2ship

This commit is contained in:
Adam Bird 2025-03-19 10:47:52 -04:00 committed by Archez
parent 8f126344a4
commit 5b14cf5f69
9 changed files with 193 additions and 70 deletions

View file

@ -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 <array>
#include <bit>
@ -13,6 +14,7 @@
#include <string>
#include <libultraship/bridge.h>
#include <libultraship/libultraship.h>
#include <spdlog/fmt/fmt.h>
#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 <typename T> 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<u16> 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;
if (!CVarGetInteger(CVAR_ACTOR_NAME_TAGS("Enabled"), 0)) {
return;
}
NameTag_RegisterForActorWithOptions(actor, tag.c_str(), { .tag = DEBUG_ACTOR_NAMETAG_TAG });
std::vector<std::string> 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<int32_t, const char*> 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<GameInteractor::OnActorInit>([](void* refActor) {
Actor* actor = static_cast<Actor*>(refActor);
ActorViewer_AddTagForActor(actor);
});
void ActorViewer_RegisterNameTagHooks() {
COND_HOOK(OnActorInit, CVAR_ACTOR_NAME_TAGS_ENABLED,
[](void* actor) { ActorViewer_AddTagForActor(static_cast<Actor*>(actor)); });
}
RegisterShipInitFunc nametagInit(ActorViewer_RegisterNameTagHooks, { CVAR_ACTOR_NAME_TAGS_ENABLED_NAME });

View file

@ -7,6 +7,6 @@ class ActorViewerWindow : public Ship::GuiWindow {
using GuiWindow::GuiWindow;
void DrawElement() override;
void InitElement() override;
void InitElement() override{};
void UpdateElement() override{};
};

View file

@ -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));

View file

@ -112,6 +112,13 @@ void GameInteractor_ExecuteOnActorKill(void* actor) {
GameInteractor::Instance->ExecuteHooksForFilter<GameInteractor::OnActorKill>(actor);
}
void GameInteractor_ExecuteOnActorDestroy(void* actor) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnActorDestroy>(actor);
GameInteractor::Instance->ExecuteHooksForID<GameInteractor::OnActorDestroy>(((Actor*)actor)->id, actor);
GameInteractor::Instance->ExecuteHooksForPtr<GameInteractor::OnActorDestroy>((uintptr_t)actor, actor);
GameInteractor::Instance->ExecuteHooksForFilter<GameInteractor::OnActorDestroy>(actor);
}
void GameInteractor_ExecuteOnEnemyDefeat(void* actor) {
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnEnemyDefeat>(actor);
GameInteractor::Instance->ExecuteHooksForID<GameInteractor::OnEnemyDefeat>(((Actor*)actor)->id, actor);

View file

@ -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);

View file

@ -12,7 +12,6 @@
#include "soh/Enhancements/randomizer/3drando/random.hpp"
#include "soh/Enhancements/cosmetics/authenticGfxPatches.h"
#include <soh/Enhancements/item-tables/ItemTableManager.h>
#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();

View file

@ -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<NameTag> nameTags;
static std::vector<Gfx> 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');
}),
// 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<GameInteractor::OnGameFrameUpdate>(gameStatUpdateHookID);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnPlayDrawEnd>(drawHookID);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnPlayDestroy>(playDestroyHookID);
GameInteractor::Instance->UnregisterGameHook<GameInteractor::OnActorDestroy>(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<GameInteractor::OnGameFrameUpdate>([]() { UpdateNameTags(); });
gameStatUpdateHookID =
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnGameFrameUpdate>(UpdateNameTags);
// Render name tags at the end of player draw to avoid overflowing the display buffers
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPlayDrawEnd>([]() { DrawNameTags(); });
// Render name tags at the end of the Play World drawing
drawHookID = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPlayDrawEnd>(DrawNameTags);
// Remove all name tags on play state destroy as all actors are removed anyways
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPlayDestroy>([]() { RemoveAllNameTags(); });
playDestroyHookID = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPlayDestroy>(RemoveAllNameTags);
// Remove all name tags for actor on destroy
actorDestroyHookID = GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorDestroy>(
[](void* actor) { NameTag_RemoveAllForActor((Actor*)actor); });
}

View file

@ -1,11 +1,16 @@
#ifndef _NAMETAG_H_
#define _NAMETAG_H_
#include <z64.h>
#ifndef NAMETAG_H
#define NAMETAG_H
#include <libultraship/color.h>
#include <libultraship/libultra.h>
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

View file

@ -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) {