From e90c8af4526f5d90d16b31dbe764c94f5aa32976 Mon Sep 17 00:00:00 2001 From: Adam Bird Date: Wed, 12 Jul 2023 21:55:24 -0400 Subject: Enhancement: Enemy Health Bar (#3035) * add enemy health bar * add more cosmetic editor options to health bar * add tooltip * fix enemy health texture when no magic bar --- soh/src/code/z_actor.c | 5 ++ soh/src/code/z_parameter.c | 174 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 179 insertions(+) (limited to 'soh/src/code') diff --git a/soh/src/code/z_actor.c b/soh/src/code/z_actor.c index 3b3b4a970..fd44a3abd 100644 --- a/soh/src/code/z_actor.c +++ b/soh/src/code/z_actor.c @@ -1212,6 +1212,11 @@ void Actor_Init(Actor* actor, PlayState* play) { //Actor_SetObjectDependency(play, actor); actor->init(actor, play); actor->init = NULL; + + // For enemy health bar we need to know the max health during init + if (actor->category == ACTORCAT_ENEMY) { + actor->maximumHealth = actor->colChkInfo.health; + } } } diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index 3895cb3a9..89b3f73d8 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -3643,6 +3643,175 @@ void Interface_DrawMagicBar(PlayState* play) { CLOSE_DISPS(play->state.gfxCtx); } +static Vtx sEnemyHealthVtx[12]; + +// Build vertex coordinates for a quad command +// In order of top left, top right, bottom left, then bottom right +// Supports flipping the texture horizontally +void Interface_CreateQuadVertexGroup(Vtx* vtxList, s32 xStart, s32 yStart, s32 width, s32 height, u8 flippedH) { + vtxList[0].v.ob[0] = xStart; + vtxList[0].v.ob[1] = yStart; + vtxList[0].v.tc[0] = (flippedH ? width : 0) << 5; + vtxList[0].v.tc[1] = 0 << 5; + + vtxList[1].v.ob[0] = xStart + width; + vtxList[1].v.ob[1] = yStart; + vtxList[1].v.tc[0] = (flippedH ? width * 2 : width) << 5; + vtxList[1].v.tc[1] = 0 << 5; + + vtxList[2].v.ob[0] = xStart; + vtxList[2].v.ob[1] = yStart + height; + vtxList[2].v.tc[0] = (flippedH ? width : 0) << 5; + vtxList[2].v.tc[1] = height << 5; + + vtxList[3].v.ob[0] = xStart + width; + vtxList[3].v.ob[1] = yStart + height; + vtxList[3].v.tc[0] = (flippedH ? width * 2 : width) << 5; + vtxList[3].v.tc[1] = height << 5; +} + +// Draws an enemy health bar using the magic bar textures and positions it in a similar way to Z-Targeting +void Interface_DrawEnemyHealthBar(TargetContext* targetCtx, PlayState* play) { + InterfaceContext* interfaceCtx = &play->interfaceCtx; + Player* player = GET_PLAYER(play); + Actor* actor = targetCtx->targetedActor; + + Vec3f projTargetCenter; + f32 projTargetCappedInvW; + + 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); + + if (targetCtx->unk_48 != 0 && actor != NULL && actor->category == ACTORCAT_ENEMY) { + s16 texHeight = 16; + s16 endTexWidth = 8; + f32 scaleY = -0.75f; + f32 scaledHeight = -texHeight * scaleY; + f32 halfBarWidth = endTexWidth + (healthbar_fillWidth / 2); + s16 healthBarFill = ((f32)actor->colChkInfo.health / actor->maximumHealth) * healthbar_fillWidth; + + 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.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); + // Health bar border middle + Interface_CreateQuadVertexGroup(&sEnemyHealthVtx[4], -halfBarWidth + endTexWidth, -texHeight / 2, + healthbar_fillWidth, texHeight, 0); + // Health bar border end right + Interface_CreateQuadVertexGroup(&sEnemyHealthVtx[8], halfBarWidth - endTexWidth, -texHeight / 2, endTexWidth, + texHeight, 1); + // Health bar fill + Interface_CreateQuadVertexGroup(&sEnemyHealthVtx[12], -halfBarWidth + endTexWidth, (-texHeight / 2) + 3, + healthBarFill, 7, 0); + + if (((!(player->stateFlags1 & 0x40)) || (actor != player->unk_664)) && targetCtx->unk_44 < 500.0f) { + f32 slideInOffsetY = 0; + + // Slide in the health bar from edge of the screen (mimic the Z-Target triangles fly in) + if (anchorType == ENEMYHEALTH_ANCHOR_ACTOR && targetCtx->unk_44 > 120.0f) { + slideInOffsetY = (targetCtx->unk_44 - 120.0f) / 2; + // 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 + Gfx_SetupDL_39Overlay(play->state.gfxCtx); + + Matrix_Translate(projTargetCenter.x, projTargetCenter.y - slideInOffsetY, 0, MTXMODE_NEW); + Matrix_Scale(1.0f, scaleY, 1.0f, MTXMODE_APPLY); + 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); + + gSPVertex(OVERLAY_DISP++, sEnemyHealthVtx, 16, 0); + + gDPLoadTextureBlock(OVERLAY_DISP++, gMagicMeterEndTex, G_IM_FMT_IA, G_IM_SIZ_8b, endTexWidth, 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); + + gSP1Quadrangle(OVERLAY_DISP++, 0, 2, 3, 1, 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); + + gSP1Quadrangle(OVERLAY_DISP++, 4, 6, 7, 5, 0); + + gDPLoadTextureBlock(OVERLAY_DISP++, gMagicMeterEndTex, G_IM_FMT_IA, G_IM_SIZ_8b, endTexWidth, texHeight, 0, + G_TX_MIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, 3, G_TX_NOMASK, G_TX_NOLOD, + G_TX_NOLOD); + + gSP1Quadrangle(OVERLAY_DISP++, 8, 10, 11, 9, 0); + + // Health bar fill + Matrix_Push(); + Matrix_Translate(-0.375f, -0.5f, 0, MTXMODE_APPLY); + gSPMatrix(OVERLAY_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_MODELVIEW | G_MTX_LOAD); + + gDPPipeSync(OVERLAY_DISP++); + gDPSetCombineLERP(OVERLAY_DISP++, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, 0, 0, 0, PRIMITIVE, + PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, 0, 0, 0, PRIMITIVE); + gDPSetEnvColor(OVERLAY_DISP++, 0, 0, 0, 255); + + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, healthbar_red.r, healthbar_red.g, healthbar_red.b, healthbar_red.a); + + gDPLoadMultiBlock_4b(OVERLAY_DISP++, gMagicMeterFillTex, 0, G_TX_RENDERTILE, G_IM_FMT_I, 16, 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); + + gSPVertex(OVERLAY_DISP++, &sEnemyHealthVtx[12], 4, 0); + + gSP1Quadrangle(OVERLAY_DISP++, 0, 2, 3, 1, 0); + + Matrix_Pop(); + } + } + + CLOSE_DISPS(play->state.gfxCtx); +} + void func_80088AA0(s16 arg0) { gSaveContext.timerX[1] = 140; gSaveContext.timerY[1] = 80; @@ -5096,6 +5265,11 @@ void Interface_Draw(PlayState* play) { if (CVarGetInteger("gMirroredWorld", 0)) { gSPMatrix(OVERLAY_DISP++, interfaceCtx->view.projectionPtr, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION); } + + // Render enemy health bar after Z-target to leverage set variables + if (CVarGetInteger("gEnemyHealthBar", 0)) { + Interface_DrawEnemyHealthBar(&play->actorCtx.targetCtx, play); + } } Gfx_SetupDL_39Overlay(play->state.gfxCtx); -- cgit v1.2.3