mirror of
https://github.com/HarbourMasters/Shipwright.git
synced 2024-11-22 17:32:19 -05:00
Actor Nametag System (#3083)
* initial nametag system * add debug name tags in actor viewer
This commit is contained in:
parent
2da8be331b
commit
f19f303651
@ -2447,6 +2447,8 @@ void Heaps_Free(void);
|
|||||||
|
|
||||||
CollisionHeader* BgCheck_GetCollisionHeader(CollisionContext* colCtx, s32 bgId);
|
CollisionHeader* BgCheck_GetCollisionHeader(CollisionContext* colCtx, s32 bgId);
|
||||||
|
|
||||||
|
void Interface_CreateQuadVertexGroup(Vtx* vtxList, s32 xStart, s32 yStart, s32 width, s32 height, u8 flippedH);
|
||||||
|
|
||||||
// Exposing these methods to leverage them from the file select screen to render messages
|
// Exposing these methods to leverage them from the file select screen to render messages
|
||||||
void Message_OpenText(PlayState* play, u16 textId);
|
void Message_OpenText(PlayState* play, u16 textId);
|
||||||
void Message_Decode(PlayState* play);
|
void Message_Decode(PlayState* play);
|
||||||
|
@ -259,6 +259,8 @@ static std::map<std::string, CosmeticOption> cosmeticOptions = {
|
|||||||
COSMETIC_OPTION("Hud_MinimapEntrance", "Minimap Entrance", GROUP_HUD, ImVec4(200, 0, 0, 255), false, true, true),
|
COSMETIC_OPTION("Hud_MinimapEntrance", "Minimap Entrance", GROUP_HUD, ImVec4(200, 0, 0, 255), false, true, true),
|
||||||
COSMETIC_OPTION("Hud_EnemyHealthBar", "Enemy Health Bar", GROUP_HUD, ImVec4(255, 0, 0, 255), true, true, false),
|
COSMETIC_OPTION("Hud_EnemyHealthBar", "Enemy Health Bar", GROUP_HUD, ImVec4(255, 0, 0, 255), true, true, false),
|
||||||
COSMETIC_OPTION("Hud_EnemyHealthBorder", "Enemy Health Border", GROUP_HUD, ImVec4(255, 255, 255, 255), true, false, true),
|
COSMETIC_OPTION("Hud_EnemyHealthBorder", "Enemy Health Border", GROUP_HUD, ImVec4(255, 255, 255, 255), true, false, true),
|
||||||
|
COSMETIC_OPTION("Hud_NameTagActorText", "Nametag Text", GROUP_HUD, ImVec4(255, 255, 255, 255), true, true, false),
|
||||||
|
COSMETIC_OPTION("Hud_NameTagActorBackground", "Nametag Background", GROUP_HUD, ImVec4(0, 0, 0, 80), true, false, true),
|
||||||
|
|
||||||
COSMETIC_OPTION("Title_FileChoose", "File Choose", GROUP_TITLE, ImVec4(100, 150, 255, 255), false, true, false),
|
COSMETIC_OPTION("Title_FileChoose", "File Choose", GROUP_TITLE, ImVec4(100, 150, 255, 255), false, true, false),
|
||||||
COSMETIC_OPTION("Title_NintendoLogo", "Nintendo Logo", GROUP_TITLE, ImVec4( 0, 0, 255, 255), false, true, true),
|
COSMETIC_OPTION("Title_NintendoLogo", "Nintendo Logo", GROUP_TITLE, ImVec4( 0, 0, 255, 255), false, true, true),
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
#include "../../util.h"
|
#include "../../util.h"
|
||||||
#include "../../UIWidgets.hpp"
|
#include "../../UIWidgets.hpp"
|
||||||
#include "soh/ActorDB.h"
|
#include "soh/ActorDB.h"
|
||||||
|
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||||
|
#include "soh/Enhancements/nametag.h"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <bit>
|
#include <bit>
|
||||||
@ -22,6 +24,8 @@ extern PlayState* gPlayState;
|
|||||||
#include "textures/icon_item_24_static/icon_item_24_static.h"
|
#include "textures/icon_item_24_static/icon_item_24_static.h"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define DEBUG_ACTOR_NAMETAG_TAG "debug_actor_viewer"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
u16 id;
|
u16 id;
|
||||||
u16 params;
|
u16 params;
|
||||||
@ -51,6 +55,13 @@ std::array<const char*, 12> acMapping = {
|
|||||||
"Chest"
|
"Chest"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ACTORVIEWER_NAMETAGS_NONE,
|
||||||
|
ACTORVIEWER_NAMETAGS_DESC,
|
||||||
|
ACTORVIEWER_NAMETAGS_NAME,
|
||||||
|
ACTORVIEWER_NAMETAGS_BOTH,
|
||||||
|
} ActorViewerNameTagsType;
|
||||||
|
|
||||||
const std::string GetActorDescription(u16 id) {
|
const std::string GetActorDescription(u16 id) {
|
||||||
return ActorDB::Instance->RetrieveEntry(id).entry.valid ? ActorDB::Instance->RetrieveEntry(id).entry.desc : "???";
|
return ActorDB::Instance->RetrieveEntry(id).entry.valid ? ActorDB::Instance->RetrieveEntry(id).entry.desc : "???";
|
||||||
}
|
}
|
||||||
@ -96,6 +107,42 @@ void PopulateActorDropdown(int i, std::vector<Actor*>& data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ActorViewer_AddTagForActor(Actor* actor) {
|
||||||
|
int val = CVarGetInteger("gDebugActorViewerNameTags", 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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActorViewer_AddTagForAllActors() {
|
||||||
|
if (gPlayState == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < ARRAY_COUNT(gPlayState->actorCtx.actorLists); i++) {
|
||||||
|
ActorListEntry currList = gPlayState->actorCtx.actorLists[i];
|
||||||
|
Actor* currAct = currList.head;
|
||||||
|
while (currAct != nullptr) {
|
||||||
|
ActorViewer_AddTagForActor(currAct);
|
||||||
|
currAct = currAct->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ActorViewerWindow::DrawElement() {
|
void ActorViewerWindow::DrawElement() {
|
||||||
ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
|
ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
|
||||||
@ -330,6 +377,22 @@ void ActorViewerWindow::DrawElement() {
|
|||||||
|
|
||||||
ImGui::TreePop();
|
ImGui::TreePop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char* nameTagOptions[] = {
|
||||||
|
"None",
|
||||||
|
"Short Description",
|
||||||
|
"Actor ID",
|
||||||
|
"Both"
|
||||||
|
};
|
||||||
|
|
||||||
|
UIWidgets::Spacer(0);
|
||||||
|
|
||||||
|
ImGui::Text("Actor Name Tags");
|
||||||
|
if (UIWidgets::EnhancementCombobox("gDebugActorViewerNameTags", nameTagOptions, ACTORVIEWER_NAMETAGS_NONE)) {
|
||||||
|
NameTag_RemoveAllByTag(DEBUG_ACTOR_NAMETAG_TAG);
|
||||||
|
ActorViewer_AddTagForAllActors();
|
||||||
|
}
|
||||||
|
UIWidgets::Tooltip("Adds \"name tags\" above actors for identification");
|
||||||
} else {
|
} else {
|
||||||
ImGui::Text("Global Context needed for actor info!");
|
ImGui::Text("Global Context needed for actor info!");
|
||||||
if (needs_reset) {
|
if (needs_reset) {
|
||||||
@ -341,6 +404,12 @@ void ActorViewerWindow::DrawElement() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ActorViewerWindow::InitElement() {
|
||||||
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnActorInit>([](void* refActor) {
|
||||||
|
Actor* actor = static_cast<Actor*>(refActor);
|
||||||
|
ActorViewer_AddTagForActor(actor);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -7,6 +7,6 @@ class ActorViewerWindow : public LUS::GuiWindow {
|
|||||||
using GuiWindow::GuiWindow;
|
using GuiWindow::GuiWindow;
|
||||||
|
|
||||||
void DrawElement() override;
|
void DrawElement() override;
|
||||||
void InitElement() override {};
|
void InitElement() override;
|
||||||
void UpdateElement() override {};
|
void UpdateElement() override {};
|
||||||
};
|
};
|
@ -151,9 +151,11 @@ public:
|
|||||||
DEFINE_HOOK(OnSceneSpawnActors, void());
|
DEFINE_HOOK(OnSceneSpawnActors, void());
|
||||||
DEFINE_HOOK(OnPlayerUpdate, void());
|
DEFINE_HOOK(OnPlayerUpdate, void());
|
||||||
DEFINE_HOOK(OnOcarinaSongAction, void());
|
DEFINE_HOOK(OnOcarinaSongAction, void());
|
||||||
|
DEFINE_HOOK(OnActorInit, void(void* actor));
|
||||||
DEFINE_HOOK(OnActorUpdate, void(void* actor));
|
DEFINE_HOOK(OnActorUpdate, void(void* actor));
|
||||||
DEFINE_HOOK(OnPlayerBonk, void());
|
DEFINE_HOOK(OnPlayerBonk, void());
|
||||||
|
DEFINE_HOOK(OnPlayDestroy, void());
|
||||||
|
DEFINE_HOOK(OnPlayDrawEnd, void());
|
||||||
|
|
||||||
DEFINE_HOOK(OnSaveFile, void(int32_t fileNum));
|
DEFINE_HOOK(OnSaveFile, void(int32_t fileNum));
|
||||||
DEFINE_HOOK(OnLoadFile, void(int32_t fileNum));
|
DEFINE_HOOK(OnLoadFile, void(int32_t fileNum));
|
||||||
|
@ -42,6 +42,10 @@ void GameInteractor_ExecuteOnOcarinaSongAction() {
|
|||||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnOcarinaSongAction>();
|
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnOcarinaSongAction>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameInteractor_ExecuteOnActorInit(void* actor) {
|
||||||
|
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnActorInit>(actor);
|
||||||
|
}
|
||||||
|
|
||||||
void GameInteractor_ExecuteOnActorUpdate(void* actor) {
|
void GameInteractor_ExecuteOnActorUpdate(void* actor) {
|
||||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnActorUpdate>(actor);
|
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnActorUpdate>(actor);
|
||||||
}
|
}
|
||||||
@ -50,6 +54,14 @@ void GameInteractor_ExecuteOnPlayerBonk() {
|
|||||||
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnPlayerBonk>();
|
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnPlayerBonk>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameInteractor_ExecuteOnPlayDestroy() {
|
||||||
|
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnPlayDestroy>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameInteractor_ExecuteOnPlayDrawEnd() {
|
||||||
|
GameInteractor::Instance->ExecuteHooks<GameInteractor::OnPlayDrawEnd>();
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Save Files
|
// MARK: - Save Files
|
||||||
|
|
||||||
void GameInteractor_ExecuteOnSaveFile(int32_t fileNum) {
|
void GameInteractor_ExecuteOnSaveFile(int32_t fileNum) {
|
||||||
|
@ -14,9 +14,12 @@ void GameInteractor_ExecuteOnSceneInit(int16_t sceneNum);
|
|||||||
void GameInteractor_ExecuteOnSceneSpawnActors();
|
void GameInteractor_ExecuteOnSceneSpawnActors();
|
||||||
void GameInteractor_ExecuteOnPlayerUpdate();
|
void GameInteractor_ExecuteOnPlayerUpdate();
|
||||||
void GameInteractor_ExecuteOnOcarinaSongAction();
|
void GameInteractor_ExecuteOnOcarinaSongAction();
|
||||||
|
void GameInteractor_ExecuteOnActorInit(void* actor);
|
||||||
void GameInteractor_ExecuteOnActorUpdate(void* actor);
|
void GameInteractor_ExecuteOnActorUpdate(void* actor);
|
||||||
void GameInteractor_ExecuteOnPlayerBonk();
|
void GameInteractor_ExecuteOnPlayerBonk();
|
||||||
void GameInteractor_ExecuteOnOcarinaSongAction();
|
void GameInteractor_ExecuteOnOcarinaSongAction();
|
||||||
|
void GameInteractor_ExecuteOnPlayDestroy();
|
||||||
|
void GameInteractor_ExecuteOnPlayDrawEnd();
|
||||||
|
|
||||||
// MARK: - Save Files
|
// MARK: - Save Files
|
||||||
void GameInteractor_ExecuteOnSaveFile(int32_t fileNum);
|
void GameInteractor_ExecuteOnSaveFile(int32_t fileNum);
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include "soh/Enhancements/enhancementTypes.h"
|
#include "soh/Enhancements/enhancementTypes.h"
|
||||||
#include "soh/Enhancements/randomizer/3drando/random.hpp"
|
#include "soh/Enhancements/randomizer/3drando/random.hpp"
|
||||||
#include "soh/Enhancements/cosmetics/authenticGfxPatches.h"
|
#include "soh/Enhancements/cosmetics/authenticGfxPatches.h"
|
||||||
|
#include "soh/Enhancements/nametag.h"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <z64.h>
|
#include <z64.h>
|
||||||
@ -621,4 +622,5 @@ void InitMods() {
|
|||||||
RegisterBonkDamage();
|
RegisterBonkDamage();
|
||||||
RegisterMenuPathFix();
|
RegisterMenuPathFix();
|
||||||
RegisterMirrorModeHandler();
|
RegisterMirrorModeHandler();
|
||||||
|
NameTag_RegisterHooks();
|
||||||
}
|
}
|
||||||
|
313
soh/soh/Enhancements/nametag.cpp
Normal file
313
soh/soh/Enhancements/nametag.cpp
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
#include "nametag.h"
|
||||||
|
#include <libultraship/bridge.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
#include "soh/frame_interpolation.h"
|
||||||
|
#include "soh/Enhancements/custom-message/CustomMessageInterfaceAddon.h"
|
||||||
|
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include "z64.h"
|
||||||
|
#include "macros.h"
|
||||||
|
#include "functions.h"
|
||||||
|
#include "variables.h"
|
||||||
|
#include "textures/message_static/message_static.h"
|
||||||
|
extern PlayState* gPlayState;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Actor* actor;
|
||||||
|
std::string text; // Original text
|
||||||
|
std::string processedText; // Text filtered for supported font textures
|
||||||
|
const char* tag; // Tag identifier
|
||||||
|
Color_RGBA8 textColor; // Text color override. Global color is used if alpha is 0
|
||||||
|
int16_t height; // Textbox height
|
||||||
|
int16_t width; // Textbox width
|
||||||
|
int16_t yOffset; // Addition Y offset
|
||||||
|
Mtx* mtx; // Allocated Mtx for rendering
|
||||||
|
Vtx* vtx; // Allocated Vtx for rendering
|
||||||
|
} NameTag;
|
||||||
|
|
||||||
|
static std::vector<NameTag> nameTags;
|
||||||
|
static std::vector<Gfx> nameTagDl;
|
||||||
|
|
||||||
|
void FreeNameTag(NameTag* nameTag) {
|
||||||
|
if (nameTag->vtx != nullptr) {
|
||||||
|
free(nameTag->vtx);
|
||||||
|
nameTag->vtx = nullptr;
|
||||||
|
}
|
||||||
|
if (nameTag->mtx != nullptr) {
|
||||||
|
free(nameTag->mtx);
|
||||||
|
nameTag->mtx = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawNameTag(PlayState* play, const NameTag* nameTag) {
|
||||||
|
if (nameTag->actor == nullptr || nameTag->actor->draw == nullptr || !nameTag->actor->isDrawn ||
|
||||||
|
nameTag->vtx == nullptr || nameTag->mtx == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name tag is too far away to meaningfully read, don't bother rendering it
|
||||||
|
if (nameTag->actor->xyzDistToPlayerSq > 200000.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
float scale = 75.0f / 100.f;
|
||||||
|
|
||||||
|
size_t numChar = nameTag->processedText.length();
|
||||||
|
// No text to render
|
||||||
|
if (numChar == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Color_RGBA8 textboxColor = { 0, 0, 0, 80};
|
||||||
|
Color_RGBA8 textColor = { 255, 255, 255, 255 };
|
||||||
|
|
||||||
|
if (CVarGetInteger("gCosmetics.Hud_NameTagActorBackground.Changed", 0)) {
|
||||||
|
textboxColor = CVarGetColor("gCosmetics.Hud_NameTagActorBackground.Value", textboxColor);
|
||||||
|
}
|
||||||
|
if (CVarGetInteger("gCosmetics.Hud_NameTagActorText.Changed", 0)) {
|
||||||
|
textColor = CVarGetColor("gCosmetics.Hud_NameTagActorText.Value", textColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameInterpolation_RecordOpenChild(nameTag->actor, 10);
|
||||||
|
|
||||||
|
// Prefer the highest between world position and focus position if targetable
|
||||||
|
float posY = nameTag->actor->world.pos.y;
|
||||||
|
if (nameTag->actor->flags & ACTOR_FLAG_TARGETABLE) {
|
||||||
|
posY = std::max(posY, nameTag->actor->focus.pos.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
posY += nameTag->yOffset + 16;
|
||||||
|
|
||||||
|
// 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("gMirroredWorld", 0) ? -1 : 1), -scale, 1.0f, MTXMODE_APPLY);
|
||||||
|
Matrix_Translate(-(float)nameTag->width / 2, -nameTag->height, 0, MTXMODE_APPLY);
|
||||||
|
Matrix_ToMtx(nameTag->mtx, (char*)__FILE__, __LINE__);
|
||||||
|
|
||||||
|
nameTagDl.push_back(gsSPMatrix(nameTag->mtx, G_MTX_PUSH | G_MTX_LOAD | G_MTX_MODELVIEW));
|
||||||
|
|
||||||
|
// textbox
|
||||||
|
nameTagDl.push_back(gsSPVertex(nameTag->vtx, 4, 0));
|
||||||
|
nameTagDl.push_back(gsDPSetPrimColor(0, 0, textboxColor.r, textboxColor.g, textboxColor.b, textboxColor.a * alpha));
|
||||||
|
|
||||||
|
// Multi-instruction macro, need to insert all to the dl buffer
|
||||||
|
Gfx textboxTexture[] = { gsDPLoadTextureBlock_4b(gDefaultMessageBackgroundTex, G_IM_FMT_I, 128, 64, 0, G_TX_MIRROR,
|
||||||
|
G_TX_NOMIRROR, 7, 0, G_TX_NOLOD, G_TX_NOLOD) };
|
||||||
|
nameTagDl.insert(nameTagDl.end(), std::begin(textboxTexture), std::end(textboxTexture));
|
||||||
|
|
||||||
|
nameTagDl.push_back(gsSP1Quadrangle(0, 2, 3, 1, 0));
|
||||||
|
|
||||||
|
// text
|
||||||
|
if (nameTag->textColor.a == 0) {
|
||||||
|
nameTagDl.push_back(gsDPSetPrimColor(0, 0, textColor.r, textColor.g, textColor.b, textColor.a * alpha));
|
||||||
|
} else {
|
||||||
|
nameTagDl.push_back(gsDPSetPrimColor(0, 0, nameTag->textColor.r, nameTag->textColor.g, nameTag->textColor.b,
|
||||||
|
nameTag->textColor.a * alpha));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0, vtxGroup = 0; i < numChar; i++) {
|
||||||
|
uint16_t texIndex = nameTag->processedText[i] - 32;
|
||||||
|
|
||||||
|
// A maximum of 64 Vtx can be loaded at once by gSPVertex, or basically 16 characters
|
||||||
|
// handle loading groups of 16 chars at a time until there are no more left to load
|
||||||
|
if (i % 16 == 0) {
|
||||||
|
size_t numVtxToLoad = std::min<size_t>(numChar - i, 16) * 4;
|
||||||
|
nameTagDl.push_back(gsSPVertex(&(nameTag->vtx)[4 + (vtxGroup * 16 * 4)], numVtxToLoad, 0));
|
||||||
|
vtxGroup++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (texIndex != 0 && nameTag->processedText[i] != '\n') {
|
||||||
|
uintptr_t texture = (uintptr_t)Font_FetchCharTexture(texIndex);
|
||||||
|
int16_t vertexStart = 4 * (i % 16);
|
||||||
|
|
||||||
|
// Multi-instruction macro, need to insert all to the dl buffer
|
||||||
|
Gfx charTexture[] = { gsDPLoadTextureBlock_4b(
|
||||||
|
texture, G_IM_FMT_I, FONT_CHAR_TEX_WIDTH, FONT_CHAR_TEX_HEIGHT, 0, G_TX_NOMIRROR | G_TX_CLAMP,
|
||||||
|
G_TX_NOMIRROR | G_TX_CLAMP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD) };
|
||||||
|
nameTagDl.insert(nameTagDl.end(), std::begin(charTexture), std::end(charTexture));
|
||||||
|
|
||||||
|
nameTagDl.push_back(gsSP1Quadrangle(vertexStart, vertexStart + 2, vertexStart + 3, vertexStart + 1, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nameTagDl.push_back(gsSPPopMatrix(G_MTX_MODELVIEW));
|
||||||
|
|
||||||
|
FrameInterpolation_RecordCloseChild();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw all the name tags by leveraging a system heap buffer for majority of the graphics commands
|
||||||
|
void DrawNameTags() {
|
||||||
|
if (gPlayState == nullptr || nameTags.size() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nameTagDl.clear();
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
nameTagDl.push_back(
|
||||||
|
gsDPSetCombineLERP(0, 0, 0, PRIMITIVE, TEXEL0, 0, PRIMITIVE, 0, 0, 0, 0, PRIMITIVE, TEXEL0, 0, PRIMITIVE, 0));
|
||||||
|
|
||||||
|
// Add all the name tags
|
||||||
|
for (const auto& nameTag : nameTags) {
|
||||||
|
DrawNameTag(gPlayState, &nameTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
// End the display list buffer
|
||||||
|
nameTagDl.push_back(gsSPEndDisplayList());
|
||||||
|
|
||||||
|
gSPDisplayList(POLY_XLU_DISP++, nameTagDl.data());
|
||||||
|
|
||||||
|
CLOSE_DISPS(gPlayState->state.gfxCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateNameTags() {
|
||||||
|
// Leveraging ZBuffer above allows the name tags to be obscured by OPA surfaces based on depth.
|
||||||
|
// However, XLU surfaces do not calculate depth with regards to other XLU surfaces.
|
||||||
|
// With multiple name tags, a tag can only obscure other tags based on draw order.
|
||||||
|
// Here we sort the tags so that actors further away from the camera are ordered first.
|
||||||
|
std::sort(nameTags.begin(), nameTags.end(), [](NameTag a, NameTag b) {
|
||||||
|
if (a.actor == nullptr || b.actor == nullptr) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
f32 aDistToCamera = Actor_WorldDistXZToPoint(a.actor, &gPlayState->mainCamera.eye);
|
||||||
|
f32 bDistToCamera = Actor_WorldDistXZToPoint(b.actor, &gPlayState->mainCamera.eye);
|
||||||
|
|
||||||
|
return aDistToCamera > bDistToCamera;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 c > 172 || (c < ' ' && c != '\n' && c != '\0');
|
||||||
|
}), processedText.end());
|
||||||
|
|
||||||
|
int16_t numChar = processedText.length();
|
||||||
|
int16_t numLines = 1;
|
||||||
|
int16_t offsetX = 0;
|
||||||
|
int16_t maxOffsetX = 0;
|
||||||
|
|
||||||
|
// 4 vertex per character plus one extra group of 4 for the textbox
|
||||||
|
Vtx* vertices = (Vtx*)calloc(sizeof(Vtx[4]), numChar + 1);
|
||||||
|
|
||||||
|
// Set all the char vtx first to get the total size for the textbox
|
||||||
|
for (size_t i = 0; i < numChar; i++) {
|
||||||
|
if (processedText[i] == '\n') {
|
||||||
|
offsetX = 0;
|
||||||
|
numLines++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t charIndex = processedText[i] - 32;
|
||||||
|
int16_t charWidth = 0;
|
||||||
|
// Don't add width for newline chars
|
||||||
|
if (charIndex >= 0) {
|
||||||
|
charWidth = (int16_t)(Message_GetCharacterWidth(charIndex) * (100.0f / R_TEXT_CHAR_SCALE));
|
||||||
|
}
|
||||||
|
|
||||||
|
Interface_CreateQuadVertexGroup(&(vertices)[(i + 1) * 4], offsetX, (numLines - 1) * 16, charWidth, 16, 0);
|
||||||
|
offsetX += charWidth;
|
||||||
|
|
||||||
|
if (offsetX > maxOffsetX) {
|
||||||
|
maxOffsetX = offsetX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vtx for textbox, add +/- 4 in all directions
|
||||||
|
int16_t height = (numLines * 16) + 8;
|
||||||
|
int16_t width = maxOffsetX + 8;
|
||||||
|
Interface_CreateQuadVertexGroup(vertices, -4, -4, width, height, 0);
|
||||||
|
|
||||||
|
// Update the texture coordinates to consume the full textbox texture size (including mirror region)
|
||||||
|
vertices[1].v.tc[0] = 256 << 5;
|
||||||
|
vertices[2].v.tc[1] = 64 << 5;
|
||||||
|
vertices[3].v.tc[0] = 256 << 5;
|
||||||
|
vertices[3].v.tc[1] = 64 << 5;
|
||||||
|
|
||||||
|
NameTag nameTag;
|
||||||
|
nameTag.actor = actor;
|
||||||
|
nameTag.text = std::string(text);
|
||||||
|
nameTag.processedText = processedText;
|
||||||
|
nameTag.tag = options.tag;
|
||||||
|
nameTag.textColor = options.textColor;
|
||||||
|
nameTag.height = height;
|
||||||
|
nameTag.width = width;
|
||||||
|
nameTag.yOffset = options.yOffset;
|
||||||
|
nameTag.mtx = new Mtx();
|
||||||
|
nameTag.vtx = vertices;
|
||||||
|
|
||||||
|
nameTags.push_back(nameTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void NameTag_RegisterForActor(Actor* actor, const char* text) {
|
||||||
|
NameTag_RegisterForActorWithOptions(actor, text, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void NameTag_RemoveAllForActor(Actor* actor) {
|
||||||
|
for (auto it = nameTags.begin(); it != nameTags.end();) {
|
||||||
|
if (it->actor == actor) {
|
||||||
|
FreeNameTag(&(*it));
|
||||||
|
it = nameTags.erase(it);
|
||||||
|
} else {
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void NameTag_RemoveAllByTag(const char* tag) {
|
||||||
|
for (auto it = nameTags.begin(); it != nameTags.end();) {
|
||||||
|
if (it->tag != nullptr && strcmp(it->tag, tag) == 0) {
|
||||||
|
FreeNameTag(&(*it));
|
||||||
|
it = nameTags.erase(it);
|
||||||
|
} else {
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RemoveAllNameTags() {
|
||||||
|
for (auto& nameTag : nameTags) {
|
||||||
|
FreeNameTag(&nameTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
nameTags.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool sRegisteredHooks = false;
|
||||||
|
|
||||||
|
void NameTag_RegisterHooks() {
|
||||||
|
if (sRegisteredHooks) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sRegisteredHooks = true;
|
||||||
|
|
||||||
|
// Reorder tags every frame to mimic depth rendering
|
||||||
|
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(); });
|
||||||
|
|
||||||
|
// Remove all name tags on play state destroy as all actors are removed anyways
|
||||||
|
GameInteractor::Instance->RegisterGameHook<GameInteractor::OnPlayDestroy>([]() { RemoveAllNameTags(); });
|
||||||
|
}
|
31
soh/soh/Enhancements/nametag.h
Normal file
31
soh/soh/Enhancements/nametag.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#ifndef _NAMETAG_H_
|
||||||
|
#define _NAMETAG_H_
|
||||||
|
#include <z64.h>
|
||||||
|
|
||||||
|
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
|
||||||
|
} NameTagOptions;
|
||||||
|
|
||||||
|
// Register required hooks for nametags on startup
|
||||||
|
void NameTag_RegisterHooks();
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Registers a name tag to an actor with additional options applied
|
||||||
|
void NameTag_RegisterForActorWithOptions(Actor* actor, const char* text, NameTagOptions options);
|
||||||
|
// Registers a name tag to an actor. Multiple name tags can exist for the same actor
|
||||||
|
void NameTag_RegisterForActor(Actor* actor, const char* text);
|
||||||
|
// Remove all name tags registered to a specific actor
|
||||||
|
void NameTag_RemoveAllForActor(Actor* actor);
|
||||||
|
// Remove all name tags that share the same tag identifier
|
||||||
|
void NameTag_RemoveAllByTag(const char* tag);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // _NAMETAG_H_
|
@ -10,6 +10,7 @@
|
|||||||
#include "soh/Enhancements/enemyrandomizer.h"
|
#include "soh/Enhancements/enemyrandomizer.h"
|
||||||
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
#include "soh/Enhancements/game-interactor/GameInteractor.h"
|
||||||
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
|
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
|
||||||
|
#include "soh/Enhancements/nametag.h"
|
||||||
|
|
||||||
#include "soh/ActorDB.h"
|
#include "soh/ActorDB.h"
|
||||||
|
|
||||||
@ -1213,6 +1214,8 @@ void Actor_Init(Actor* actor, PlayState* play) {
|
|||||||
actor->init(actor, play);
|
actor->init(actor, play);
|
||||||
actor->init = NULL;
|
actor->init = NULL;
|
||||||
|
|
||||||
|
GameInteractor_ExecuteOnActorInit(actor);
|
||||||
|
|
||||||
// For enemy health bar we need to know the max health during init
|
// For enemy health bar we need to know the max health during init
|
||||||
if (actor->category == ACTORCAT_ENEMY) {
|
if (actor->category == ACTORCAT_ENEMY) {
|
||||||
actor->maximumHealth = actor->colChkInfo.health;
|
actor->maximumHealth = actor->colChkInfo.health;
|
||||||
@ -1228,6 +1231,8 @@ void Actor_Destroy(Actor* actor, PlayState* play) {
|
|||||||
// "No Actor class destruct [%s]"
|
// "No Actor class destruct [%s]"
|
||||||
osSyncPrintf("Actorクラス デストラクトがありません [%s]\n" VT_RST, ActorDB_Retrieve(actor->id)->name);
|
osSyncPrintf("Actorクラス デストラクトがありません [%s]\n" VT_RST, ActorDB_Retrieve(actor->id)->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NameTag_RemoveAllForActor(actor);
|
||||||
}
|
}
|
||||||
|
|
||||||
void func_8002D7EC(Actor* actor) {
|
void func_8002D7EC(Actor* actor) {
|
||||||
|
@ -171,6 +171,7 @@ void Play_Destroy(GameState* thisx) {
|
|||||||
PlayState* play = (PlayState*)thisx;
|
PlayState* play = (PlayState*)thisx;
|
||||||
Player* player = GET_PLAYER(play);
|
Player* player = GET_PLAYER(play);
|
||||||
|
|
||||||
|
GameInteractor_ExecuteOnPlayDestroy();
|
||||||
|
|
||||||
// Only initialize the frame counter when exiting the title screen
|
// Only initialize the frame counter when exiting the title screen
|
||||||
if (gSaveContext.fileNum == 0xFF) {
|
if (gSaveContext.fileNum == 0xFF) {
|
||||||
@ -1713,6 +1714,8 @@ void Play_Draw(PlayState* play) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GameInteractor_ExecuteOnPlayDrawEnd();
|
||||||
|
|
||||||
// Reset the inverted culling
|
// Reset the inverted culling
|
||||||
if (CVarGetInteger("gMirroredWorld", 0)) {
|
if (CVarGetInteger("gMirroredWorld", 0)) {
|
||||||
gSPClearExtraGeometryMode(POLY_OPA_DISP++, G_EX_INVERT_CULLING);
|
gSPClearExtraGeometryMode(POLY_OPA_DISP++, G_EX_INVERT_CULLING);
|
||||||
|
Loading…
Reference in New Issue
Block a user