The-Frozen-North / tfn-module

A persistent world set around Neverwinter and the Wailing Death, with content from HotU and NWN2
https://github.com/b5635/the-frozen-north/wiki
GNU Affero General Public License v3.0
31 stars 24 forks source link

Boss healthbars #770

Open b5635 opened 1 year ago

b5635 commented 1 year ago

Cjreek has very kindly donated some code for adding boss health bars. I think it would be pretty neat to add, but we'll want to consider some things.

  1. Making it optional via a setting
  2. Ensuring it is displayed in the center of the screen like Dark Souls or smth

The bottom will need to be adjusted, but would serve as a great boilerplate / starting point.


#include "pw_colors"
#include "pw_combat"
#include "pw_debuffcore"

const string NUI_WINDOW_TARGETFRAME_ID = "TARGETFRAME";
const string NUI_WINDOW_TARGETFRAME = "NUI_WINDOW_TARGETFRAME";
const string NUI_VAR_TARGETFRAME_TARGET = "NUI_VAR_TARGETFRAME_TARGET";

const int TARGETFRAME_MAX_BUFFS = 12;
const float TARGETFRAME_DEBUFFIMAGE_SIZE = 16.0f;

void TF_ShowTargetWindow(object oPlayer, object oTarget);
void TF_UpdateTargetWindows(object oTarget);

json TF_CreateDebuffCell(string sIcon)
{
    if (sIcon == "") return NuiSpacer();

    json jDebuffImage = NuiImage(JsonString(sIcon), JsonInt(NUI_ASPECT_STRETCH), JsonInt(NUI_HALIGN_CENTER), JsonInt(NUI_VALIGN_MIDDLE));
    jDebuffImage = NuiMargin(jDebuffImage, 0.0f);
    jDebuffImage = NuiHeight(jDebuffImage, TARGETFRAME_DEBUFFIMAGE_SIZE);

    return jDebuffImage;
}

json TF_CreateTargetWindowTemplate()
{
    json jRootRow = JsonArray();

    json jPortraitCol = JsonArray();
    json jPortraitImage = NuiImage(NuiBind("portrait"), JsonInt(NUI_ASPECT_EXACTSCALED), JsonInt(NUI_HALIGN_LEFT), JsonInt(NUI_VALIGN_TOP));
    jPortraitImage = NuiHeight(jPortraitImage, 75.0f);
    jPortraitImage = NuiMargin(jPortraitImage, 0.0f);
    jPortraitCol = JsonArrayInsert(jPortraitCol, jPortraitImage);
    jPortraitCol = NuiCol(jPortraitCol);
    jPortraitCol = NuiWidth(jPortraitCol, 48.0f);
    jPortraitCol = NuiMargin(jPortraitCol, 0.0f);
    jRootRow = JsonArrayInsert(jRootRow, jPortraitCol);

    json jNameLifeBuffCol = JsonArray();

    json jNameRow = JsonArray();
    json jLabel = NuiLabel(NuiBind("name"), JsonInt(NUI_HALIGN_CENTER), JsonInt(NUI_VALIGN_MIDDLE));
    jLabel = NuiStyleForegroundColor(jLabel, NuiBind("textcolor"));
    jNameRow = JsonArrayInsert(jNameRow, jLabel);
    jNameRow = NuiRow(jNameRow);
    jNameRow = NuiHeight(jNameRow, 20.0f);
    jNameRow = NuiMargin(jNameRow, 0.0f);
    jNameLifeBuffCol = JsonArrayInsert(jNameLifeBuffCol, jNameRow);

    json jLifeRow = JsonArray();
    json jLifeBar = NuiProgress(NuiBind("life"));
    jLifeBar = NuiStyleForegroundColor(jLifeBar, NuiColor(255,80,64,128));
    jLifeRow = JsonArrayInsert(jLifeRow, jLifeBar);
    jLifeRow = NuiRow(jLifeRow);
    jLifeRow = NuiHeight(jLifeRow, 23.0f);
    jLifeRow = NuiMargin(jLifeRow, 0.0f);
    jNameLifeBuffCol = JsonArrayInsert(jNameLifeBuffCol, jLifeRow);

    json jBuffRow = JsonArray();

    int i;
    for (i=0; i < TARGETFRAME_MAX_BUFFS; i++)
    {
        json jGroup = NuiGroup(TF_CreateDebuffCell(""), FALSE, NUI_SCROLLBARS_NONE);
        jGroup = NuiId(jGroup, "debuffGroup" + IntToString(i));

        jBuffRow = JsonArrayInsert(jBuffRow, jGroup);
    }

    jBuffRow = NuiRow(jBuffRow);
    jBuffRow = NuiHeight(jBuffRow, 24.0f);
    jBuffRow = NuiMargin(jBuffRow, 0.0f);
    jNameLifeBuffCol = JsonArrayInsert(jNameLifeBuffCol, jBuffRow);

    jNameLifeBuffCol = NuiCol(jNameLifeBuffCol);
    jNameLifeBuffCol = NuiWidth(jNameLifeBuffCol, 242.0f);
    jRootRow = JsonArrayInsert(jRootRow, jNameLifeBuffCol);
    jRootRow = NuiRow(jRootRow);
    jRootRow = NuiHeight(jRootRow, 60.0f);

    json jRoot = NuiGroup(jRootRow, FALSE, NUI_SCROLLBARS_NONE);
    json jWindow = NuiWindow(jRoot, JsonString(""), NuiBind("geometry"), JsonBool(FALSE), JsonBool(FALSE), JsonBool(FALSE), JsonBool(FALSE), JsonBool(TRUE));

    return jWindow;
}

void TF_DestroyTargetWindow(object oPlayer, int nToken)
{
    NuiDestroy(oPlayer, nToken);
    SetLocalObject(oPlayer, NUI_VAR_TARGETFRAME_TARGET, OBJECT_INVALID);
}

void TF_UpdateTargetDebuffs(object oPlayer, int nToken, object oTarget)
{
    int i;
    int nCurrentSlot = 0;
    json jDebuffs = Debuffs_GetAppliedDebuffIds(oTarget);
    for (i=0; i < JsonGetLength(jDebuffs); i++)
    {
        string sDebuffId = JsonGetString(JsonArrayGet(jDebuffs, i));
        if (sDebuffId != "")
        {
            effect eDebuff = Debuffs_GetDebuffById(oTarget, sDebuffId);
            string sIcon = Debuffs_GetIcon(eDebuff);

            NuiSetGroupLayout(oPlayer, nToken, "debuffGroup" + IntToString(nCurrentSlot), TF_CreateDebuffCell(sIcon));
            nCurrentSlot++;
        }
    }

    for (i=nCurrentSlot; i < TARGETFRAME_MAX_BUFFS; i++)
        NuiSetGroupLayout(oPlayer, nToken, "debuffGroup" + IntToString(i), TF_CreateDebuffCell(""));
}

void TF_ShowTargetWindow(object oPlayer, object oTarget)
{
    int nToken = NuiFindWindow(oPlayer, NUI_WINDOW_TARGETFRAME_ID);
    if (!nToken)
    {
        json jWindow = GetLocalJson(GetModule(), NUI_WINDOW_TARGETFRAME);
        if (jWindow == JSON_NULL)
        {
            jWindow = TF_CreateTargetWindowTemplate();
            SetLocalJson(GetModule(), NUI_WINDOW_TARGETFRAME, jWindow);
        }

        nToken = NuiCreate(oPlayer, jWindow, NUI_WINDOW_TARGETFRAME_ID);
    }

    if (!GetIsObjectValid(oTarget))
    {
        TF_DestroyTargetWindow(oPlayer, nToken);
        return;
    }

    float fScale = NUI_GetPlayerUIScale(oPlayer);
    NuiSetBind(oPlayer, nToken, "geometry", NuiRect(4.0f * fScale, 55.0f * fScale, 300.0f, 70.0f));
    NuiSetBind(oPlayer, nToken, "portrait", JsonString(GetPortraitResRef(oTarget) + "m"));
    NuiSetBind(oPlayer, nToken, "name", JsonString(GetName(oTarget, TRUE)));
    NuiSetBind(oPlayer, nToken, "textcolor", GetTargetObjectNuiColor(oPlayer, oTarget));
    NuiSetBind(oPlayer, nToken, "life", JsonFloat(IntToFloat(GetCurrentHitPoints(oTarget)) / IntToFloat(GetMaxHitPoints(oTarget))));

    TF_UpdateTargetDebuffs(oPlayer, nToken, oTarget);

    SetLocalObject(oPlayer, NUI_VAR_TARGETFRAME_TARGET, oTarget);
}

void TF_UpdateTargetWindows(object oTarget)
{
    float fHitPointPercent = IntToFloat(GetCurrentHitPoints(oTarget)) / IntToFloat(GetMaxHitPoints(oTarget));

    object oPC = GetFirstPC();
    while (GetIsObjectValid(oPC))
    {
        object oAttackTarget = GetLocalObject(oPC, NUI_VAR_TARGETFRAME_TARGET);
        if (oTarget == oAttackTarget)
        {
            int nToken = NuiFindWindow(oPC, NUI_WINDOW_TARGETFRAME_ID);
            if (nToken)
            {
                if (fHitPointPercent <= 0.0f)
                    TF_DestroyTargetWindow(oPC, nToken);
                else
                {
                    NuiSetBind(oPC, nToken, "life", JsonFloat(fHitPointPercent));
                    TF_UpdateTargetDebuffs(oPC, nToken, oTarget);
                }
            }
        }

        oPC = GetNextPC();
    }
}```
b5635 commented 1 year ago

Some followup comments he provided: Also the creature portrait isn't quite right as well 😄 it's cropped

You can play with the NUI_ASPECT of the NuiImage NUI_ASPECT_FIT100 should be better But some portraits have that blank space below which will then show as well

And a demo picture! This is the uncropped one image