Script help: Display Damage as floating text over creature

Dear Scripting Deities of NWN2. I was curious if there was a way to alter a creature’s default script to display the quantity of damage above it’s head every time it takes damage (like a speak string). I was hoping to incorporate this feature into a NWN2 style cutscene and have had a lot of success with LB_HitPointBars mod by @Lance_Botelle but if I could show the damage quantity instead, that might work better for the JRPG style.

This Final Fantasy GIF expresses what I want to do:
22-Gif_3

https://www.nexusmods.com/neverwinter2/mods/832/?

@silentfaction00,

I’m glad you managed to work with one of my pieces of code. (It’s changed a bit since that upload, but the principle is still the same.)

The HP Bar method uses SEFs (special effects) that show above the creature in question, which are updated as required. As you say, you can have a creature “shout” their HP damage using the speakstring function, but you are limited to its font and look. (I used this approach in my NWN1 module, “Soul Shaker” to show a percentage of how much the creature had been damaged.)

Also, you may need to consider your approach depending upon the HP damage is on the “monster” or the “PC” (or party member), due to differing event hooks.

This is a bit of code I use in my Soul Shaker module from a monster’s “OnDamaged”, from which I imagine you could write the type of script you mean (replacing the percentage function for an algorithm where you calculate actual damage done).:

int PercHPS = GetPercentageHPLoss(OBJECT_SELF);
    if(PercHPS > 0){SpeakString("SOUL >>> " + IntToString(PercHPS) + "%", TALKVOLUME_SHOUT);}

However, if you wanted to duplicate the visual display effect in the image you show, then that would involve a lot more work, and the bouncy effect may not even be possible. :thinking: i.e. It may require adding many SEFs with every possible number of damage, as you can only play one SEF at a time, meaning I cannot see any easy way to drop in 0-9 based upon a calculation for each digit, as each digit would obscure the last one used. Although, you may be able to create an invisible “box” SEF above the creature, which you could send a dedicated font string value, which may duplicate the numbers without the “jumpiness”. i.e. It would just appear for a second or two.

EDIT: I suppose if you were really set on this, you could potentially increase the height of the “box” and then “drop in” a number (animate top to bottom) to give an effect of the number falling. As far as I am aware you would not be able to have more than one “box” space for the final number (due to engine limitations I believe), but I have never tested this. If it was possible, you could have had three boxes (side by side) and drop in the digits as required (a slight delay between each one) to give a similar effect.

Using Speakstring would certainly be the easiest way to go, but may not look so eye-catching.

Thanks, Lance.

3 Likes

Hi @Lance_Botelle! Thank you for this thoughtful reply. I think I can live without the bouncy effect - it sounds like it is much more trouble to implement than it is worth. I would much prefer using the built-in speak string functionality to have the creature issue speak strings above its head that indicate real-time damage taken. I will definitely want the script to be written for Creatures (not PC/Party members) - as the system I have created does not use Party functions to issue battle commands. The battle system occurs entirely within the context of NWN2 style cutscenes which is what attracted me to your LB bars because they worked even during cutscenes. (I use the ga_do_damage script to inflict damage).

Anyway, it looks like you have already created a way to call damage as a %. I know that the game engine keeps track of damage taken but is there a built-in way to display that exact number (after reductions/resistances/immunities are applied)?

EDIT: If there is no way to call up the damage taken in real-time. Would you be willing to share the NWN1 scripts you wrote that called up the % of damage taken? I’m a total noob with scripting and have survived by using the resources on the vault.

Thanks again!

@silentfaction00,

Those two lines of code above (in my previous post) you can add to your ga_do_damage script for the creature to shout their remaining % HPs. If you want to see the script within the full context of the entire script, check out my NWN1 module, Soul Shaker.

However, the mod is not required, as those two lines should do what you need in NWN2 as well. HOWEVER, rather than use the GetPercentageHPLoss(OBJECT_SELF); function, you can instead keep a track of the creatures actual HPs in a local variable (on itself), so you can compare from damage to damage and report back on the difference (damage taken).

Here is an example of some code from my NWN2 module, The Scroll, which plays around with this sort of thing … play around with it as you may require …

///////////////////////////////////////////////////////////////////////////////////
	// CALCULATE THE AMOUNT OF DAMAGE THIS TIME AROUND (FOR LIFE ESSENCE REBIRTH CHECKS)
	///////////////////////////////////////////////////////////////////////////////////
	
	int iDAMAGETODATE = GetLocalInt(OBJECT_SELF, "DAMAGETAKENTODATE");
	int iTOTALDAMAGE = GetTotalDamageDealt();
	int iTHISDAMAGE = iTOTALDAMAGE - iDAMAGETODATE;
	SetLocalInt(OBJECT_SELF, "DAMAGETAKENTODATE", iTOTALDAMAGE);
	int iCURHPS = GetCurrentHitPoints();	
		
	// DAMAGE TYPE DEBUG
	//if(GetGlobalInt("TESTMODE") == 1)
	{
		int iMAX = GetMaxHitPoints(OBJECT_SELF);
		int iLASTREC = GetLocalInt(OBJECT_SELF, "RECORDED");
		if(iLASTREC == 0){iLASTREC = iMAX;}	
		
		int iHPS = GetCurrentHitPoints(OBJECT_SELF);
		SetLocalInt(OBJECT_SELF, "RECORDED", iHPS);
		
		int iTOTDamage = GetTotalDamageDealt();
		int iDamage = iLASTREC - iHPS;	
		
		SendMessageToPC(oDamager, "PREVIOUS>>> " + IntToString(iLASTREC) + " DAMAGED>>> " + IntToString(iDamage)
		 + " REMAIN HPS>>> " + IntToString(iHPS));	
		 
		 string sShout = IntToString(iTHISDAMAGE);	SpeakString(sShout, TALKVOLUME_SHOUT);
	}	

Thanks, Lance.

1 Like

Hi Lance,

I tried appending the code to the ga_do_damage script and keep getting an ERROR: Parsing Variable List when I save and compile. Would you mind taking a quick look to see if I did this correctly? I inserted your code shortly before the script calls in VFX and animations

// Damage types:

// 1 bludgeoning, 2 piercing, 4 slashing,
// 8 magical, 16 acid, 32 cold,
// 64 divine, 128 electrical, 256 fire,
// 512 negative, 1024 positive, 2048 sonic
//
// Use sTagOverride to target others, otherwise
// applies damage to PC Speaker
void main(int iAmount, int iType, string sTagOverride)
{
if (iAmount < 1 || iType < 1)
return;

int iDamagePower = DAMAGE_POWER_ENERGY;
if (iType < 8)
iDamagePower = DAMAGE_POWER_NORMAL;

object oPC = GetPCSpeaker();
if (sTagOverride != “”)
oPC = GetNearestObjectByTag(sTagOverride, OBJECT_SELF);

int iVFX;
string sSound;
string sSoundNo = IntToString(Random(2)+1);

switch (iType)
{
case 1:
{
iVFX = 460;
sSound = “cb_ht_fleshleth”;
break;
}
case 2:
{
iVFX = 109;
sSound = “cb_ht_daggrleth”;
break;
}
case 4:
{
iVFX = 109;
sSound = “cb_ht_bladeleth”;
break;
}
case 8:
iVFX = 76;
break;
case 16:
iVFX = 44;
break;
case 32:
iVFX = 63;
break;
case 64:
iVFX = 98;
break;
case 128:
iVFX = 74;
break;
case 256:
iVFX = 61;
break;
case 512:
iVFX = 81;
break;
case 1024:
iVFX = 584;
break;
case 2048:
iVFX = 588;
break;
}

int PercHPS = GetPercentageHPLoss(OBJECT_SELF);
if(PercHPS > 0){SpeakString("SOUL >>> " + IntToString(PercHPS) + “%”, TALKVOLUME_SHOUT);}
int iCurrentHP = GetCurrentHitPoints(oPC);
effect eAmount = EffectDamage(iAmount, iType, iDamagePower, FALSE);
ApplyEffectToObject(DURATION_TYPE_INSTANT, eAmount, oPC);
int iDamagedHP = GetCurrentHitPoints(oPC);
if (iType <= 4 && iCurrentHP == iDamagedHP)
iVFX = 118;// change physical VFX if damage resisted
effect eVFX = EffectVisualEffect(iVFX);
if (iType > 4)// Do energy hit VFX
ApplyEffectToObject(DURATION_TYPE_INSTANT, eVFX, oPC);
else// Do physical hit VFX
{
float fZ = GetCreatureSize(oPC)*0.3;
vector vLoc = GetPosition(oPC)+Vector(0.0,0.0,fZ);
location lLoc = Location(GetArea(oPC), vLoc, GetFacing(oPC));
ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eVFX, lLoc);
}
if (iCurrentHP == iDamagedHP && sSound != “”)
sSound = “cb_pa_bld0”;// Physical damage resisted
if (sSound != “”)// If physical damage, play sound
AssignCommand(oPC, PlaySound(sSound+sSoundNo));
if (oPC == GetPCSpeaker() && iDamagedHP > 0 && iCurrentHP != iDamagedHP)
PlayCustomAnimation(oPC, “damage01”, 0, 1.0);// Flinch if damage taken
}

@silentfaction00,

I’m not at my main computer at the moment, but will take a closer look at your script when I get there.

Cheers, Lance.

@silentfaction00 If you post code on here you really ought to post in a code block because one feature of the forum software is that ordinary quotation marks get converted to the 6699 style of such punctuations -

“”

To put your code into such a block just enclose it between [code] and [/code]. Doing so will make your code look like (I’ve slightly rearranged it) -

// Damage types:
// 1 bludgeoning, 2 piercing, 4 slashing,
// 8 magical, 16 acid, 32 cold,
// 64 divine, 128 electrical, 256 fire,
// 512 negative, 1024 positive, 2048 sonic
//
// Use sTagOverride to target others, otherwise
// applies damage to PC Speaker

void main(int iAmount, int iType, string sTagOverride)
{
    int iDamagePower = DAMAGE_POWER_ENERGY;
    object oPC = GetPCSpeaker();
    int iVFX;
    string sSound;
    string sSoundNo = IntToString(Random(2) + 1);
    int PercHPS = GetPercentageHPLoss(OBJECT_SELF);
    int iCurrentHP = GetCurrentHitPoints(oPC);
    effect eAmount = EffectDamage(iAmount, iType, iDamagePower, FALSE);
    int iDamagedHP = GetCurrentHitPoints(oPC);

    if (iAmount < 1 || iType < 1)
        return;

    if (iType < 8)
        iDamagePower = DAMAGE_POWER_NORMAL;

    if (sTagOverride != "")
        oPC = GetNearestObjectByTag(sTagOverride, OBJECT_SELF);

    switch (iType)
    {
        case 1:
            iVFX = 460;
            sSound = "cb_ht_fleshleth";
            break;
        case 2:
            iVFX = 109;
            sSound = "cb_ht_daggrleth";
            break;
        case 4:
            iVFX = 109;
            sSound = "cb_ht_bladeleth";
            break;
        case 8:
            iVFX = 76;
            break;
        case 16:
            iVFX = 44;
            break;
        case 32:
            iVFX = 63;
            break;
        case 64:
            iVFX = 98;
            break;
        case 128:
            iVFX = 74;
            break;
        case 256:
            iVFX = 61;
            break;
        case 512:
            iVFX = 81;
            break;
        case 1024:
            iVFX = 584;
            break;
        case 2048:
            iVFX = 588;
            break;
    }

    if(PercHPS > 0)
        SpeakString("SOUL >>> " + IntToString(PercHPS) + "%", TALKVOLUME_SHOUT);
        
    ApplyEffectToObject(DURATION_TYPE_INSTANT, eAmount, oPC);

    if (iType <= 4 && iCurrentHP == iDamagedHP)
        iVFX = 118;// change physical VFX if damage resisted
        
    effect eVFX = EffectVisualEffect(iVFX);
    
    if (iType > 4)// Do energy hit VFX
        ApplyEffectToObject(DURATION_TYPE_INSTANT, eVFX, oPC);
    else// Do physical hit VFX
    {
        float fZ = GetCreatureSize(oPC) * 0.3;
        vector vLoc = GetPosition(oPC) + Vector(0.0, 0.0, fZ);
        location lLoc = Location(GetArea(oPC), vLoc, GetFacing(oPC));
        ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eVFX, lLoc);
    }

    if (iCurrentHP == iDamagedHP && sSound != "")
        sSound = "cb_pa_bld0";// Physical damage resisted
        
    if (sSound != "")// If physical damage, play sound
        AssignCommand(oPC, PlaySound(sSound+sSoundNo));
        
    if (oPC == GetPCSpeaker() && iDamagedHP > 0 && iCurrentHP != iDamagedHP)
        PlayCustomAnimation(oPC, "damage01", 0, 1.0);// Flinch if damage taken
}

A small number of questions -

  1. Is sTagOverride built into NwN 2 (I only build with NwN EE)? I ask because that variable is not declared in your code.
  2. Is there any reason why your switch statement doesn’t have a default term? It would be useful to have one for catching any errors that might crop up in the value carried by iType.
  3. Final question and this is just checking and not an error in your code. Near the end of your function you have this -
    else// Do physical hit VFX
    {
        float fZ = GetCreatureSize(oPC) * 0.3;
        vector vLoc = GetPosition(oPC) + Vector(0.0, 0.0, fZ);
        location lLoc = Location(GetArea(oPC), vLoc, GetFacing(oPC));
        ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eVFX, lLoc);
    }

Just double checking that you know that the three variables you declare there wouldn’t be visible anywhere else in your function. As they are not used anywhere else this question is moot anyway.

TR

2 Likes

@Tarot_Redhand

Thanks for tidying @silentfaction00 script. :+1:

Also, I’ll take a stab at answering the questions, but I guess @silentfaction00 will correct me if I’m wrong …

A small number of questions (by TR) -

  1. Is sTagOverride built into NwN 2 (I only build with NwN EE)? I ask because that variable is not declared in your code.

The sTagOverride is declared in the void main section. It’s a conversation action script where you can define and pass variables.

  1. Is there any reason why your switch statement doesn’t have a default term? It would be useful to have one for catching any errors that might crop up in the value carried by iType.

Agreed. Although my guess is one of these values has to pass for a damage type - although there is also a DAMAGE_TYPE_ALL (0)

  1. Final question and this is just checking and not an error in your code. Near the end of your function you have this … Just double checking that you know that the three variables you declare there wouldn’t be visible anywhere else in your function. As they are not used anywhere else this question is moot anyway.

I don’t think they are going to be used anywhere else, as the script ends thereafter and are used by then.

@silentfaction00

// GetPercentageHPLoss REQUIRES THIS INCLUDE LIBRARY
#include “ginc_behavior”

So, all you needed to do to have your script compile was to add it at the start like I have below …

// Damage types:
// 1 bludgeoning, 2 piercing, 4 slashing,
// 8 magical, 16 acid, 32 cold,
// 64 divine, 128 electrical, 256 fire,
// 512 negative, 1024 positive, 2048 sonic
//
// Use sTagOverride to target others, otherwise
// applies damage to PC Speaker

// GetPercentageHPLoss REQUIRES THIS INCLUDE LIBRARY 
#include "ginc_behavior"

void main(int iAmount, int iType, string sTagOverride)
{
    int iDamagePower = DAMAGE_POWER_ENERGY;
    object oPC = GetPCSpeaker();
    int iVFX;
    string sSound;
    string sSoundNo = IntToString(Random(2) + 1);
    int PercHPS = GetPercentageHPLoss(OBJECT_SELF);
    int iCurrentHP = GetCurrentHitPoints(oPC);
    effect eAmount = EffectDamage(iAmount, iType, iDamagePower, FALSE);
    int iDamagedHP = GetCurrentHitPoints(oPC);

    if (iAmount < 1 || iType < 1)
        return;

    if (iType < 8)
        iDamagePower = DAMAGE_POWER_NORMAL;

    if (sTagOverride != "")
        oPC = GetNearestObjectByTag(sTagOverride, OBJECT_SELF);

    switch (iType)
    {
        case 1:
            iVFX = 460;
            sSound = "cb_ht_fleshleth";
            break;
        case 2:
            iVFX = 109;
            sSound = "cb_ht_daggrleth";
            break;
        case 4:
            iVFX = 109;
            sSound = "cb_ht_bladeleth";
            break;
        case 8:
            iVFX = 76;
            break;
        case 16:
            iVFX = 44;
            break;
        case 32:
            iVFX = 63;
            break;
        case 64:
            iVFX = 98;
            break;
        case 128:
            iVFX = 74;
            break;
        case 256:
            iVFX = 61;
            break;
        case 512:
            iVFX = 81;
            break;
        case 1024:
            iVFX = 584;
            break;
        case 2048:
            iVFX = 588;
            break;
    }

    if(PercHPS > 0)
        SpeakString("SOUL >>> " + IntToString(PercHPS) + "%", TALKVOLUME_SHOUT);
        
    ApplyEffectToObject(DURATION_TYPE_INSTANT, eAmount, oPC);

    if (iType <= 4 && iCurrentHP == iDamagedHP)
        iVFX = 118;// change physical VFX if damage resisted
        
    effect eVFX = EffectVisualEffect(iVFX);
    
    if (iType > 4)// Do energy hit VFX
        ApplyEffectToObject(DURATION_TYPE_INSTANT, eVFX, oPC);
    else// Do physical hit VFX
    {
        float fZ = GetCreatureSize(oPC) * 0.3;
        vector vLoc = GetPosition(oPC) + Vector(0.0, 0.0, fZ);
        location lLoc = Location(GetArea(oPC), vLoc, GetFacing(oPC));
        ApplyEffectAtLocation(DURATION_TYPE_INSTANT, eVFX, lLoc);
    }

    if (iCurrentHP == iDamagedHP && sSound != "")
        sSound = "cb_pa_bld0";// Physical damage resisted
        
    if (sSound != "")// If physical damage, play sound
        AssignCommand(oPC, PlaySound(sSound+sSoundNo));
        
    if (oPC == GetPCSpeaker() && iDamagedHP > 0 && iCurrentHP != iDamagedHP)
        PlayCustomAnimation(oPC, "damage01", 0, 1.0);// Flinch if damage taken
}

LASTLY: I have NOT looked at the script any closer, so there may be other changes you need to consider, but do what you can first with it, and ask later if you need more help. :smiley:

1 Like

Thank you @Tarot_Redhand and @Lance_Botelle for your help with this. The script is now compiling! And it looks very cool to see the damage percentage display over the monsters as they are damaged. In the future I will post my code experiments on the Forum with the correct formatting. Confession: I actually don’t know how to script :sweat_smile::rofl: - I’ve managed to accomplish things in NWN/NWN2 by tweaking and tinkering with all of the wonderful resources on the vault with mixed success. In my module in progress, nearly all of the scripting is executed using the conversation editor and the library of supported ga_ and gc_ scripts on there. The battle system is essentially an interactive NWN2 style cutscene with HUD elements. This is a huge help! I will now try to see if I can download Lance’s “The Scroll” and take a look at how this “damage to date” function works. Maybe I can figure out how to incorporate that into the ga_do_damage or the OnDamaged script event.

1 Like