Skip to content

Commit

Permalink
Enhancement: Enemy Health Bar (HarbourMasters#3035)
Browse files Browse the repository at this point in the history
* add enemy health bar

* add more cosmetic editor options to health bar

* add tooltip

* fix enemy health texture when no magic bar
  • Loading branch information
Archez committed Aug 4, 2023
1 parent 1f3a0b8 commit 4ca6bb5
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 28 deletions.
3 changes: 3 additions & 0 deletions soh/include/z64actor.h
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ typedef struct Actor {
/* 0x138 */ ActorResetFunc reset;
/* */ u8 maxHealth; // Health value for an actor immediately after actor init is finished
/* 0x13C */ char dbgPad[0x10]; // Padding that only exists in the debug rom
// #region SOH [General]
/* */ u8 maximumHealth; // Max health value for use with health bars, set on actor init
// #endregion
} Actor; // size = 0x14C

typedef enum {
Expand Down
60 changes: 52 additions & 8 deletions soh/soh/Enhancements/cosmetics/CosmeticsEditor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,8 @@ static std::map<std::string, CosmeticOption> cosmeticOptions = {
COSMETIC_OPTION("Hud_Minimap", "Minimap", GROUP_HUD, ImVec4( 0, 255, 255, 255), false, true, false),
COSMETIC_OPTION("Hud_MinimapPosition", "Minimap Position", GROUP_HUD, ImVec4(200, 255, 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_EnemyHealthBorder", "Enemy Health Border", GROUP_HUD, ImVec4(255, 255, 255, 255), true, false, true),

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),
Expand Down Expand Up @@ -400,6 +402,10 @@ void CosmeticsUpdateTick() {
newColor.g = sin(frequency * (hue + index) + (2 * M_PI / 3)) * 127 + 128;
newColor.b = sin(frequency * (hue + index) + (4 * M_PI / 3)) * 127 + 128;
newColor.a = 255;
// For alpha supported options, retain the last set alpha instead of overwriting
if (cosmeticOption.supportsAlpha) {
newColor.a = cosmeticOption.currentColor.w * 255;
}

cosmeticOption.currentColor.x = newColor.r / 255.0;
cosmeticOption.currentColor.y = newColor.g / 255.0;
Expand Down Expand Up @@ -1425,6 +1431,32 @@ void Draw_Placements(){
ImGui::EndTable();
}
}
if (ImGui::CollapsingHeader("Enemy Health Bar position")) {
if (ImGui::BeginTable("enemyhealthbar", 1, FlagsTable)) {
ImGui::TableSetupColumn("Enemy Health Bar settings", FlagsCell, TablesCellsWidth);
Table_InitHeader(false);
std::string posTypeCVar = "gCosmetics.Hud_EnemyHealthBarPosType";
UIWidgets::EnhancementRadioButton("Anchor to Enemy", posTypeCVar.c_str(), ENEMYHEALTH_ANCHOR_ACTOR);
UIWidgets::Tooltip("This will use enemy on screen position");
UIWidgets::EnhancementRadioButton("Anchor to the top", posTypeCVar.c_str(), ENEMYHEALTH_ANCHOR_TOP);
UIWidgets::Tooltip("This will make your elements follow the top edge of your game window");
UIWidgets::EnhancementRadioButton("Anchor to the bottom", posTypeCVar.c_str(), ENEMYHEALTH_ANCHOR_BOTTOM);
UIWidgets::Tooltip("This will make your elements follow the bottom edge of your game window");
DrawPositionSlider("gCosmetics.Hud_EnemyHealthBar", -SCREEN_HEIGHT, SCREEN_HEIGHT, -ImGui::GetWindowViewport()->Size.x / 2, ImGui::GetWindowViewport()->Size.x / 2);
if (UIWidgets::EnhancementSliderInt("Health Bar Width: %d", "##EnemyHealthBarWidth", "gCosmetics.Hud_EnemyHealthBarWidth.Value", 32, 128, "", 64)) {
CVarSetInteger("gCosmetics.Hud_EnemyHealthBarWidth.Changed", 1);
}
UIWidgets::Tooltip("This will change the width of the health bar");
ImGui::SameLine();
if (ImGui::Button("Reset##EnemyHealthBarWidth")) {
CVarClear("gCosmetics.Hud_EnemyHealthBarWidth.Value");
CVarClear("gCosmetics.Hud_EnemyHealthBarWidth.Changed");
LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
}
ImGui::NewLine();
ImGui::EndTable();
}
}
}

void DrawSillyTab() {
Expand Down Expand Up @@ -1539,6 +1571,10 @@ void RandomizeColor(CosmeticOption& cosmeticOption) {
newColor.g = Random(0, 255);
newColor.b = Random(0, 255);
newColor.a = 255;
// For alpha supported options, retain the last set alpha instead of overwriting
if (cosmeticOption.supportsAlpha) {
newColor.a = cosmeticOption.currentColor.w * 255;
}

cosmeticOption.currentColor.x = newColor.r / 255.0;
cosmeticOption.currentColor.y = newColor.g / 255.0;
Expand Down Expand Up @@ -1607,7 +1643,13 @@ void ResetColor(CosmeticOption& cosmeticOption) {
}

void DrawCosmeticRow(CosmeticOption& cosmeticOption) {
if (ImGui::ColorEdit3(cosmeticOption.label.c_str(), (float*)&cosmeticOption.currentColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel)) {
bool colorChanged;
if (cosmeticOption.supportsAlpha) {
colorChanged = ImGui::ColorEdit4(cosmeticOption.label.c_str(), (float*)&cosmeticOption.currentColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel);
} else {
colorChanged = ImGui::ColorEdit3(cosmeticOption.label.c_str(), (float*)&cosmeticOption.currentColor, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel);
}
if (colorChanged) {
Color_RGBA8 color;
color.r = cosmeticOption.currentColor.x * 255.0;
color.g = cosmeticOption.currentColor.y * 255.0;
Expand All @@ -1628,13 +1670,15 @@ void DrawCosmeticRow(CosmeticOption& cosmeticOption) {
ApplyOrResetCustomGfxPatches();
LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
}
ImGui::SameLine();
bool isRainbow = (bool)CVarGetInteger((cosmeticOption.rainbowCvar), 0);
if (ImGui::Checkbox(("Rainbow##" + cosmeticOption.label).c_str(), &isRainbow)) {
CVarSetInteger((cosmeticOption.rainbowCvar), isRainbow);
CVarSetInteger((cosmeticOption.changedCvar), 1);
ApplyOrResetCustomGfxPatches();
LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
if (cosmeticOption.supportsRainbow) {
ImGui::SameLine();
bool isRainbow = (bool)CVarGetInteger((cosmeticOption.rainbowCvar), 0);
if (ImGui::Checkbox(("Rainbow##" + cosmeticOption.label).c_str(), &isRainbow)) {
CVarSetInteger((cosmeticOption.rainbowCvar), isRainbow);
CVarSetInteger((cosmeticOption.changedCvar), 1);
ApplyOrResetCustomGfxPatches();
LUS::Context::GetInstance()->GetWindow()->GetGui()->SaveConsoleVariablesOnNextTick();
}
}
ImGui::SameLine();
bool isLocked = (bool)CVarGetInteger((cosmeticOption.lockedCvar), 0);
Expand Down
8 changes: 7 additions & 1 deletion soh/soh/Enhancements/cosmetics/cosmeticsTypes.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
typedef enum {
COLORSCHEME_N64,
COLORSCHEME_GAMECUBE
} DefaultColorScheme;
} DefaultColorScheme;

typedef enum {
ENEMYHEALTH_ANCHOR_ACTOR,
ENEMYHEALTH_ANCHOR_TOP,
ENEMYHEALTH_ANCHOR_BOTTOM,
} EnemyHealthBarAnchorType;
2 changes: 2 additions & 0 deletions soh/soh/SohMenuBar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,8 @@ void DrawEnhancementsMenu() {

UIWidgets::PaddedEnhancementCheckbox("Disable Crit wiggle", "gDisableCritWiggle", true, false);
UIWidgets::Tooltip("Disable random camera wiggle at low health");
UIWidgets::PaddedEnhancementCheckbox("Enemy Health Bars", "gEnemyHealthBar", true, false);
UIWidgets::Tooltip("Renders a health bar for enemies when Z-Targeted");

ImGui::EndMenu();
}
Expand Down
7 changes: 4 additions & 3 deletions soh/src/code/z_actor.c
Original file line number Diff line number Diff line change
Expand Up @@ -1215,9 +1215,10 @@ void Actor_Init(Actor* actor, PlayState* play) {
//Actor_SetObjectDependency(play, actor);
actor->init(actor, play);
actor->init = NULL;
// Set actor's max health for enemy health bar
if (CVarGetInteger("gEnemyHealthBar", 0)) {
actor->maxHealth = actor->colChkInfo.health;

// For enemy health bar we need to know the max health during init
if (actor->category == ACTORCAT_ENEMY) {
actor->maximumHealth = actor->colChkInfo.health;
}
}
}
Expand Down
57 changes: 41 additions & 16 deletions soh/src/code/z_parameter.c
Original file line number Diff line number Diff line change
Expand Up @@ -3679,10 +3679,23 @@ void Interface_DrawEnemyHealthBar(TargetContext* targetCtx, PlayState* play) {
Vec3f projTargetCenter;
f32 projTargetCappedInvW;

Color_RGBA8 healthbar_red = { 255, 0, 0, 255 - 90 };
Color_RGBA8 healthbar_border = { 255, 255, 255, 255 - 90 };
Color_RGBA8 healthbar_red = { 255, 0, 0, 255 };
Color_RGBA8 healthbar_border = { 255, 255, 255, 255 };
s16 healthbar_fillWidth = 64;
s16 healthbar_actorOffset = 40;
s32 healthbar_offsetX = CVarGetInteger("gCosmetics.Hud_EnemyHealthBarPosX", 0);
s32 healthbar_offsetY = CVarGetInteger("gCosmetics.Hud_EnemyHealthBarPosY", 0);
s8 anchorType = CVarGetInteger("gCosmetics.Hud_EnemyHealthBarPosType", ENEMYHEALTH_ANCHOR_ACTOR);

if (CVarGetInteger("gCosmetics.Hud_EnemyHealthBar.Changed", 0)) {
healthbar_red = CVarGetColor("gCosmetics.Hud_EnemyHealthBar.Value", healthbar_red);
}
if (CVarGetInteger("gCosmetics.Hud_EnemyHealthBorder.Changed", 0)) {
healthbar_border = CVarGetColor("gCosmetics.Hud_EnemyHealthBorder.Value", healthbar_border);
}
if (CVarGetInteger("gCosmetics.Hud_EnemyHealthBarWidth.Changed", 0)) {
healthbar_fillWidth = CVarGetInteger("gCosmetics.Hud_EnemyHealthBarWidth.Value", healthbar_fillWidth);
}

OPEN_DISPS(play->state.gfxCtx);

Expand All @@ -3692,20 +3705,28 @@ void Interface_DrawEnemyHealthBar(TargetContext* targetCtx, PlayState* play) {
f32 scaleY = -0.75f;
f32 scaledHeight = -texHeight * scaleY;
f32 halfBarWidth = endTexWidth + (healthbar_fillWidth / 2);
s16 healthBarFill = ((f32)actor->colChkInfo.health / actor->maxHealth) * healthbar_fillWidth;
s16 healthBarFill = ((f32)actor->colChkInfo.health / actor->maximumHealth) * healthbar_fillWidth;

// Get actor projected position
func_8002BE04(play, &targetCtx->targetCenterPos, &projTargetCenter, &projTargetCappedInvW);
if (anchorType == ENEMYHEALTH_ANCHOR_ACTOR) {
// Get actor projected position
func_8002BE04(play, &targetCtx->targetCenterPos, &projTargetCenter, &projTargetCappedInvW);

projTargetCenter.x = (SCREEN_WIDTH / 2) * (projTargetCenter.x * projTargetCappedInvW);
projTargetCenter.x = projTargetCenter.x * (CVarGetInteger("gMirroredWorld", 0) ? -1 : 1);
projTargetCenter.x = CLAMP(projTargetCenter.x, (-SCREEN_WIDTH / 2) + halfBarWidth,
(SCREEN_WIDTH / 2) - halfBarWidth);
projTargetCenter.x = (SCREEN_WIDTH / 2) * (projTargetCenter.x * projTargetCappedInvW);
projTargetCenter.x = projTargetCenter.x * (CVarGetInteger("gMirroredWorld", 0) ? -1 : 1);
projTargetCenter.x = CLAMP(projTargetCenter.x, (-SCREEN_WIDTH / 2) + halfBarWidth,
(SCREEN_WIDTH / 2) - halfBarWidth);

projTargetCenter.y = (SCREEN_HEIGHT / 2) * (projTargetCenter.y * projTargetCappedInvW);
projTargetCenter.y = projTargetCenter.y + healthbar_actorOffset;
projTargetCenter.y = CLAMP(projTargetCenter.y, (-SCREEN_HEIGHT / 2) + (scaledHeight / 2),
(SCREEN_HEIGHT / 2) - (scaledHeight / 2));
projTargetCenter.y = (SCREEN_HEIGHT / 2) * (projTargetCenter.y * projTargetCappedInvW);
projTargetCenter.y = projTargetCenter.y - healthbar_offsetY + healthbar_actorOffset;
projTargetCenter.y = CLAMP(projTargetCenter.y, (-SCREEN_HEIGHT / 2) + (scaledHeight / 2),
(SCREEN_HEIGHT / 2) - (scaledHeight / 2));
} else if (anchorType == ENEMYHEALTH_ANCHOR_TOP) {
projTargetCenter.x = healthbar_offsetX;
projTargetCenter.y = (SCREEN_HEIGHT / 2) - (scaledHeight / 2) - healthbar_offsetY;
} else if (anchorType == ENEMYHEALTH_ANCHOR_BOTTOM) {
projTargetCenter.x = healthbar_offsetX;
projTargetCenter.y = (-SCREEN_HEIGHT / 2) + (scaledHeight / 2) - healthbar_offsetY;
}

// Health bar border end left
Interface_CreateQuadVertexGroup(&sEnemyHealthVtx[0], -halfBarWidth, -texHeight / 2, endTexWidth, texHeight, 0);
Expand All @@ -3723,9 +3744,12 @@ void Interface_DrawEnemyHealthBar(TargetContext* targetCtx, PlayState* play) {
f32 slideInOffsetY = 0;

// Slide in the health bar from edge of the screen (mimic the Z-Target triangles fly in)
if (targetCtx->unk_44 > 120.0f) {
if (anchorType == ENEMYHEALTH_ANCHOR_ACTOR && targetCtx->unk_44 > 120.0f) {
slideInOffsetY = (targetCtx->unk_44 - 120.0f) / 2;
slideInOffsetY *= -1;
// Slide in from the top if the bar is placed on the top half of the screen
if (healthbar_offsetY - healthbar_actorOffset <= 0) {
slideInOffsetY *= -1;
}
}

// Setup DL for overlay disp
Expand All @@ -3736,6 +3760,7 @@ void Interface_DrawEnemyHealthBar(TargetContext* targetCtx, PlayState* play) {
gSPMatrix(OVERLAY_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_MODELVIEW | G_MTX_LOAD);

// Health bar border
gDPPipeSync(OVERLAY_DISP++);
gDPSetPrimColor(OVERLAY_DISP++, 0, 0, healthbar_border.r, healthbar_border.g, healthbar_border.b,
healthbar_border.a);
gDPSetEnvColor(OVERLAY_DISP++, 100, 50, 50, 255);
Expand All @@ -3748,7 +3773,7 @@ void Interface_DrawEnemyHealthBar(TargetContext* targetCtx, PlayState* play) {

gSP1Quadrangle(OVERLAY_DISP++, 0, 2, 3, 1, 0);

gDPLoadTextureBlock(OVERLAY_DISP++, gMagicMeterMidTex, G_IM_FMT_IA, G_IM_SIZ_8b, endTexWidth, texHeight, 0,
gDPLoadTextureBlock(OVERLAY_DISP++, gMagicMeterMidTex, G_IM_FMT_IA, G_IM_SIZ_8b, 24, texHeight, 0,
G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK,
G_TX_NOLOD, G_TX_NOLOD);

Expand Down

0 comments on commit 4ca6bb5

Please sign in to comment.