NPC dying problem [Resolved]

I have a situation that I think is due to timing problems within NWN2. The whole scene goes like this:

  1. The PC and the party approaches a bunch of barbarians that have kidnapped a princess. The PC and the barbarians talk and there’s a fight.
  2. During all this the princess is set to immortal.
  3. Once she is down to lower than 5 HP, she gets knocked down. The trouble is, she can’t get knocked down if she’s immortal, so I set her to mortal just before being knocked down.
  4. Once all the barbarians are dead, the knockdown effect is lifted and she stands up, and a conversation starts.

For most of the time all this works splendidly. However, I have had a few instances when testing where the princess is dead instead of just very injured when knocked down. This has me believing that somehow in a short millisecond where she is made mortal, one of the barbarians might get a killing blow against her.

This is my script that’s on the OnDamage of the princess:

#include "s_deathinclude"


void main()
{
	
	int iHp = GetCurrentHitPoints(OBJECT_SELF);
	
    if (GetGlobalInt("princessfirstfight") && iHp < 5)
    {
		
	AssignCommand(OBJECT_SELF, ClearAllActions(TRUE));
	SetGlobalInt("princessfirstfight",0);
	
	ApplyKnockdown("semaphine","factionpig");
	return;

	}
	
	else
	ExecuteScript("gb_assoc_damage", OBJECT_SELF);
}

This is the OnDeath script for the barbarians:

#include "ginc_ipspeaker"
#include "s_deathinclude"

int CheckIfCreaturesWithTagAreAllDead(string EnemyTag)
{
    object oPC = GetFirstPC();
    int n = 1;
    object oNPC = GetNearestObjectByTag(EnemyTag + IntToString(n),oPC);
	
	while(GetIsObjectValid(oNPC))
    {
        if(!GetIsDead(oNPC)) 
		{
			return FALSE;
        }
        n++;
        oNPC = GetNearestObjectByTag(EnemyTag + IntToString(n),oPC);
    }
	return TRUE;
}



void main()
{

	
	object oPC = GetFirstPC();
	object oSemaphine = GetObjectByTag("semaphine");
		
		if (CheckIfCreaturesWithTagAreAllDead("native")) 
		{
		
		RemoveKnockdown("semaphine");
				
		SetCutsceneMode(oPC);
			
		CreateIPSpeaker("semaphine", "c_i3_semaphine", GetLocation(GetFirstPC()), 0.5f);
		
		}
		
		
}

Here’s the include script called s_deathinclude:

void Destroyable(object oObject)
{

        AssignCommand(oObject, SetIsDestroyable( FALSE,FALSE,TRUE ));
	    SetImmortal( oObject, FALSE );
	    SetPlotFlag( oObject, FALSE );
        effect eFX = EffectKnockdown();
		eFX = SetEffectSpellId(eFX, 9992);  //9991 <-- just for example here, you can use
                                            //any valid id that doesn't conflict with
                                            //existing ones in spells.2da or in other scripts
		//PrettyDebug("Name of oObject = " + GetName(oObject));		
        DelayCommand(0.1f, ApplyEffectToObject(DURATION_TYPE_PERMANENT, eFX, oObject));

}


void ApplyDeath(string sTag, string sFactionPig)
{
	//PrettyDebug("Applying Death Effect To: " + sTagString + " Instance = " + IntToString(iInstance));		
    
        object oObject = GetObjectByTag(sTag);
		
		object oPig = GetObjectByTag(sFactionPig);
		ChangeFaction(oObject,oPig);

        AssignCommand(oObject, SetIsDestroyable( FALSE,FALSE,TRUE ));
	    SetImmortal( oObject, FALSE );
	    SetPlotFlag( oObject, FALSE );
        effect eFX = EffectDeath();
		eFX = SetEffectSpellId(eFX, 9991);  //9991 <-- just for example here, you can use
                                            //any valid id that doesn't conflict with
                                            //existing ones in spells.2da or in other scripts
		//PrettyDebug("Name of oObject = " + GetName(oObject));		
        ApplyEffectToObject( DURATION_TYPE_INSTANT,eFX,oObject );
    
}

void ApplyKnockdown(string sTag, string sFactionPig)
{
	//PrettyDebug("Applying Death Effect To: " + sTagString + " Instance = " + IntToString(iInstance));		
    
        object oObject = GetObjectByTag(sTag);
		
		object oPig = GetObjectByTag(sFactionPig);
		ChangeFaction(oObject,oPig);
		
		DelayCommand(0.2,Destroyable(oObject));

    
}

void RemoveAllTheEffects(object oPC)
{
	effect eLoop=GetFirstEffect(oPC);

	while (GetIsEffectValid(eLoop))
   	{
	RemoveEffect(oPC, eLoop);
   	eLoop=GetNextEffect(oPC);
   	}
}


void RemoveDeath(string sTag)
{
    
        object oDeadObject = GetObjectByTag(sTag);
		ChangeToStandardFaction(oDeadObject,STANDARD_FACTION_COMMONER);

        if (GetIsObjectValid(oDeadObject))
        {
            effect eSearch = GetFirstEffect(oDeadObject);
            while (GetIsEffectValid(eSearch))
            {
                if (GetEffectSpellId(eSearch) == 9991) //your custom spell id
                {
                    RemoveEffect(oDeadObject, eSearch);
                    eSearch = GetFirstEffect(oDeadObject); //safety (removing an iterator from the list)
                }
                else
                    eSearch = GetNextEffect(oDeadObject);
            }
        }
 
}

void RemoveKnockdown(string sTag)
{
    
        object oDeadObject = GetObjectByTag(sTag);
		ChangeToStandardFaction(oDeadObject,STANDARD_FACTION_COMMONER);

        if (GetIsObjectValid(oDeadObject))
        {
            effect eSearch = GetFirstEffect(oDeadObject);
            while (GetIsEffectValid(eSearch))
            {
                if (GetEffectSpellId(eSearch) == 9991) //your custom spell id
                {
                    RemoveEffect(oDeadObject, eSearch);
                    eSearch = GetFirstEffect(oDeadObject); //safety (removing an iterator from the list)
                }
                else
                    eSearch = GetNextEffect(oDeadObject);
            }
        }
 
}

My first thought when all this occured was to maybe add something in the OnDeath script of the barbarians to check if the princess is dead and in that case resurrect her (even though she should not be killable). However, I don’t know how that resurrect spell thing works and how to script that. I found only a EffectResurrection function but I’m not sure if that’s enough to use that in this situation.

Can anyone help with this? It must be a timing problem that the princess somehow dies on rare occations…

You should “cheat”, don’ t apply the knockdown on the princess.

Make your princess immortal (not plot, plot can’ t be damaged), and in the on damage script check her hp.

when it reach the value you want, don’t apply the knockdown on her but make her play the animation.

PlayCustomAnimation(oPrincess,“pronedamageB”,0,1.0);
DelayCommand(1.0,WrapedLoopCustomAnimation(oPrincess,“proneB”));

you need to “wrap” playcustomanimation to be able to use a delay with it.

void WrapedLoopCustomAnimation(object oObject,string MyAnim){

PlayCustomAnimation(oObject,MyAnim,1,1.0);

}

1 Like

Ok, I’ll try something like this then, even though I don’t understand why you in your script play the animation two times after the other:

Ok, so I kind of did like suggested but…for some reason she won’t lie down. Maybe it’s a timing thing again…groan

The include script looks like this now:

void PlayCustomAnimationVoid(object oObject, string sAnimation, int iLoop = 0, float fSpeed = 1.f)
{
	PlayCustomAnimation(oObject, sAnimation, iLoop, fSpeed);
}


void Destroyable(object oObject)
{

        AssignCommand(oObject, SetIsDestroyable( FALSE,FALSE,TRUE ));
	    SetImmortal( oObject, FALSE );
	    SetPlotFlag( oObject, FALSE );
        //effect eFX = EffectKnockdown();
		//eFX = SetEffectSpellId(eFX, 9992);  //9991 <-- just for example here, you can use
                                            //any valid id that doesn't conflict with
                                            //existing ones in spells.2da or in other scripts
		//PrettyDebug("Name of oObject = " + GetName(oObject));		
        //DelayCommand(0.1f, ApplyEffectToObject(DURATION_TYPE_PERMANENT, eFX, oObject));

}


void ApplyDeath(string sTag, string sFactionPig)
{
	//PrettyDebug("Applying Death Effect To: " + sTagString + " Instance = " + IntToString(iInstance));		
    
        object oObject = GetObjectByTag(sTag);
		
		object oPig = GetObjectByTag(sFactionPig);
		ChangeFaction(oObject,oPig);

        AssignCommand(oObject, SetIsDestroyable( FALSE,FALSE,TRUE ));
	    SetImmortal( oObject, FALSE );
	    SetPlotFlag( oObject, FALSE );
        effect eFX = EffectDeath();
		eFX = SetEffectSpellId(eFX, 9991);  //9991 <-- just for example here, you can use
                                            //any valid id that doesn't conflict with
                                            //existing ones in spells.2da or in other scripts
		//PrettyDebug("Name of oObject = " + GetName(oObject));		
        ApplyEffectToObject( DURATION_TYPE_INSTANT,eFX,oObject );
    
}

void ApplyKnockdown(string sTag, string sFactionPig)
{
	//PrettyDebug("Applying Death Effect To: " + sTagString + " Instance = " + IntToString(iInstance));		
    
        object oObject = GetObjectByTag(sTag);
		
		object oPig = GetObjectByTag(sFactionPig);
		ChangeFaction(oObject,oPig);
		
		DelayCommand(0.2,PlayCustomAnimationVoid(oObject,"*proneB",1));
		
		//DelayCommand(0.2,Destroyable(oObject));

    
}

void RemoveAllTheEffects(object oPC)
{
	effect eLoop=GetFirstEffect(oPC);

	while (GetIsEffectValid(eLoop))
   	{
	RemoveEffect(oPC, eLoop);
   	eLoop=GetNextEffect(oPC);
   	}
}


void RemoveDeath(string sTag)
{
    
        object oDeadObject = GetObjectByTag(sTag);
		ChangeToStandardFaction(oDeadObject,STANDARD_FACTION_COMMONER);

        if (GetIsObjectValid(oDeadObject))
        {
            effect eSearch = GetFirstEffect(oDeadObject);
            while (GetIsEffectValid(eSearch))
            {
                if (GetEffectSpellId(eSearch) == 9991) //your custom spell id
                {
                    RemoveEffect(oDeadObject, eSearch);
                    eSearch = GetFirstEffect(oDeadObject); //safety (removing an iterator from the list)
                }
                else
                    eSearch = GetNextEffect(oDeadObject);
            }
        }
 
}

void RemoveKnockdown(string sTag)
{
    
        object oDeadObject = GetObjectByTag(sTag);
		ChangeToStandardFaction(oDeadObject,STANDARD_FACTION_COMMONER);

        if (GetIsObjectValid(oDeadObject))
        {
            effect eSearch = GetFirstEffect(oDeadObject);
            while (GetIsEffectValid(eSearch))
            {
                if (GetEffectSpellId(eSearch) == 9991) //your custom spell id
                {
                    RemoveEffect(oDeadObject, eSearch);
                    eSearch = GetFirstEffect(oDeadObject); //safety (removing an iterator from the list)
                }
                else
                    eSearch = GetNextEffect(oDeadObject);
            }
        }
 
}

And the OnDamage script of the princess looks like this:

#include "s_deathinclude"

void main()
{
	
	int iHp = GetCurrentHitPoints(OBJECT_SELF);
	
    if (GetGlobalInt("princessfirstfight") && iHp < 5)
    {
		
	AssignCommand(OBJECT_SELF, ClearAllActions(TRUE));
	SetGlobalInt("princessfirstfight",0);
	

	ApplyKnockdown("semaphine","factionpig");
	return;

	}
	
	else
	ExecuteScript("gb_assoc_damage", OBJECT_SELF);
}

I made sure to update all the scripts containing the s_deathinclude since I know that’s necessary when changing things in an include script.

its “proneB” not *proneB the name of the animation you want to play needs to be exact

I use 2 differents animation, one for the fall one when on the ground.

check :

1 Like

Well, I’ve used *proneB in a previous module where it worked perfectly, but ok.

// Place on the OnDamaged event of semaphine. Keep her set to immortal.
// Do not set her to plot TRUE.

void main()
{
    if (GetGlobalInt("princessfirstfight") && GetCurrentHitPoints(OBJECT_SELF) < 5)
    {
		SetGlobalInt("princessfirstfight", 0);
		AssignCommand(GetLastDamager(), ClearAllActions(TRUE));		
		AssignCommand(OBJECT_SELF, ClearAllActions(TRUE));
		ChangeFaction(OBJECT_SELF, GetObjectByTag("factionpig"));
		ApplyEffectToObject(DURATION_TYPE_PERMANENT, EffectKnockdown(), OBJECT_SELF);
	}
	
	ExecuteScript("gb_assoc_damage", OBJECT_SELF);
}
// Place on the OnDeath event of each native.

#include "ginc_ipspeaker"

int CheckIfCreaturesWithTagAreAllDead(object oPC, string EnemyTag)
{
    int n = 1;
    object oNPC = GetNearestObjectByTag(EnemyTag + IntToString(n), oPC);
	
	while(GetIsObjectValid(oNPC))
    {
        if(!GetIsDead(oNPC)) return FALSE;
        n++;
        oNPC = GetNearestObjectByTag(EnemyTag + IntToString(n), oPC);
    }
	
	return TRUE;
}

void main()
{
	object oPC = GetFirstPC();	

	if (CheckIfCreaturesWithTagAreAllDead(oPC, "native")) 
	{
		object oSemaphine = GetObjectByTag("semaphine");	

		ChangeToStandardFaction(oSemaphine, STANDARD_FACTION_COMMONER);
		ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectKnockdown(), oSemaphine);		
		SetCutsceneMode(oPC);
		CreateIPSpeaker("semaphine", "c_i3_semaphine", GetLocation(oPC), 0.5f);
	}
}
1 Like

Are you really sure that would work, @travus? From my experience she won’t be able to be knocked down if she is immortal, and if I’m reading your script correctly you try to make her be knocked down anyway.

Since changing to proneB and using the pronedamageB it seems to work now, so I don’t know if I should change this when it finally seems to work. I also wonder why *proneB has worked before and not now. I found an old thread discussing things with @kevL_s where he suggested me using this. Here’s the thread where he says:

"generally, use a “*” in front of the animation-string – it means “find any stance” that has a corresponding animation.

  • proneB

eg."

So why Shallina claims this doesn’t work, I don’t know. It has worked for me before, but in this instance it didn’t, so who is right and who is wrong, I have no idea about.

It worked great in my mockup test.

Ok. I think I’ll leave this be now though, since I have something that works. If I encounter new problems with this after further testing down the line, I might go back here and change to your scripts instead, but for now I think I’ll leave this be.

@travus Do you have any insights to share about the * thing? Shallina didn’t seem to know about anything about this thing.

It’s called a wildcard character:
What is a Wildcard? (computerhope.com)

i don’t know much about animations. As travus says it’s a wildcard character meaning (roughly?) any race/weapon combination. It makes sense when Shallina says they shouldn’t be used for animations that are the same for any race/weapon combination – eg. prone is just lie down regardless of race/weapon. On the other hand an attack animation depends on what race a creature is and what weapon-type it has equipped …

perhaps @4760 can say a bit more …

I just wonder why *proneB worked in my other module and not here.

idk either. I’m pretty sure *proneB has worked for me too … but apparently there are situations where it just dont

For sure *proneB always worked for me. I can’t see why “*proneB” wouldn’t work but “proneB” does…
Maybe timing? (checking what stance the character is in takes longer than just playing the default proneB animation?)

1 Like

Well in that case this was wrong then:

That was all I really needed to know, since that statement really confused me (saying that it was totally wrong to use *proneB). However, I’m still using proneB here now since that seemed to work here for some reason. There seems to be a lot of timing problems when it comes to this (and in NWN2 in general), so if it takes longer for the game to do the proneB (in the middle of a fight and everything) maybe that’s an issue here, as I think 4760 was hinting at.

I think I will actually test one more time and try and use *proneB, just for the sake of it. Maybe it was something else I did that caused that to not work…

EDIT: Well, it’s difficult to say if *proneB works in this situation. Since I also use the pronedamageB (as suggested by Shallina) it looks like she falls down, lies there for a while but then rises again. If I use proneB she doesn’t rise again (at least not during my tests), so it may be that *proneB doesn’t work at all in this situation (maybe it’s because of the use of pronedamageB first, I don’t know, or just a timing issue, or something else). In any case, I will stick to using proneB for now. I think I’ll also do a test with travus’ script, but I think I’ll use my own just to be on the safe side, since then I don’t have to worry about the immortal or plot stuff.

EDIT2: Trying travus’ script gave me the same results as using *proneB. She falls down, but doesn’t stay down. Therefore I will use the thing that has worked best out of everything here, and that turned out to be (in this case, as stated *proneB has worked for me, and many others it seems, before) Shallina’s take on the script.

EDIT3: Maybe it would be best then to not use a “wildcard character” or “*” when it comes to animations?

On the contrary, you should use “*”, unless you want to test if the character is male or female, human or not, with shield and sword or other weapons. It’s definitely quicker to use “*proneB” than “p_hhf_una_proneb” for an unarmed female human or “p_eem_c2h_proneb” for an elven male wielding a two-handed sword!

1 Like

Ok, but how come it works in this instance with just “proneB” then? If what you’re saying is correct, it seems to me at least that you have to check if the character is female, male, human, using what weapon etc. when using the proneB animation? If this is true, then it shouldn’t work to use just proneB? I must be missing something since it works with just proneB here. This is quite confusing.

There is probably a trick to know with this * . NWN/NWN2 scripting are full of things that should works but don’t.

But there is most of the time a way which work. Most of difficulties when you start to do non standard stuff or advanced stuff with NWN2 is to know where are the engine bugs or unfinished buisseness and how to turn around them.

There are most of the time several way to do something with NWN2. My advice is: if you are confident with one of the way and fully know its limitations, and how to avoid them, just stick with it.

4 Likes