Loop inside a spell script to reapply bonuses, depending on target's HP

Hey guys, need some help with custom feat that I’m working on.

What it does at the moment:

  • it checks the oPC’s percentage of HP loss via GetPercentageHPLoss(object) funtion
  • the less HP it has, the higher bonus it gets (e.g. 100% = +1 attack bonus, 75% = +2 attack bonus etc.)
  • bonuses are applied for certain duration, in this case 10 rounds + class level (max is gonna be 20 rounds)

How I want it to work:

  • if oPC loses HP to the point the bonus should change, I want to remove previously applied bonuses and reapply them with corrected values and duration time (initial duration - duration left)

Is it possible to create a loop inside that feat script that will check oPC’s HP every second or every round and if HP loss changes, to remove and reapply the bonuses?

I’d appreciate some guidance how can it be done (if possible at all).
Cheers!

Yes, it’s possible. In Enhanced Edition.
Put a tag on the applied effect. NWN EE added this function.
On top of the script, Remove the effect targeted by tag. Yes, before applying the effect, you remove it.
By then it’s pretty much done, despite the tag being the same, the effect will be applied depending on the percentage.

In the original edition this might be a little more complicated.

@Clangeddin Thanks for your reply. I have no problem with removing the effect (by spell ID or by it’s tag). I’m already doing it on the top of the script to avoid unnecessary stacking.

What I’m struggling with is creating that loop, that will check oPC’s HP for the whole duration of the effect (live I’ve mention above - check it every second of every round) in order to reapply those effects :frowning:

Ok, can you show me the script then? Because if you’re already removing the effect it should be pretty much done already. I don’t understand what’s the issue.
The script is already fired every 6 seconds on heartbeat, right? Or is it fired only once on other events?

#include "x2_i0_spells"

void main()
{
    // Declare major variables
	object oWolfhound = OBJECT_SELF;
	int nAttackBonus;
	int nLevel = GetLevelByClass(45);
	int nInitialDuration = nLevel + 10;
	int nInitialHPLoss = GetPercentageHPLoss(oWolfhound);

	effect eAttack;
	effect eAttackMod = EffectModifyAttacks(1);
	effect eDur = EffectVisualEffect(VFX_DUR_CESSATE_POSITIVE);
	effect eVis = EffectVisualEffect(VFX_IMP_IMPROVE_ABILITY_SCORE);

	if (!GetHasEffect(EFFECT_TYPE_SILENCE))
    {
		PlayVoiceChat(VOICE_CHAT_BATTLECRY1);
    }

	SignalEvent(oWolfhound, EventSpellCastAt(oWolfhound, spell.Id, FALSE));

	ApplyEffectToObject(DURATION_TYPE_INSTANT, eVis, oWolfhound);

	RemoveEffectsFromSpell(oWolfhound, spell.Id);

	if (nInitialHPLoss == 100)
	{
		nAttackBonus = 1;
		eAttack = EffectAttackIncrease(nAttackBonus);
		
		ApplyEffectToObject(DURATION_TYPE_TEMPORARY, ExtraordinaryEffect(eAttack), oWolfhound, RoundsToSeconds(nInitialDuration));
		ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eDur, oWolfhound, RoundsToSeconds(nInitialDuration));
	}
	else if (nInitialHPLoss >= 50)
	{
		nAttackBonus = 4;
		eAttack = EffectAttackIncrease(nAttackBonus);

		ApplyEffectToObject(DURATION_TYPE_TEMPORARY, ExtraordinaryEffect(eAttack), oWolfhound, RoundsToSeconds(nInitialDuration));
		ApplyEffectToObject(DURATION_TYPE_TEMPORARY, ExtraordinaryEffect(eAttackMod), oWolfhound, RoundsToSeconds(nInitialDuration));
		ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eDur, oWolfhound, RoundsToSeconds(nInitialDuration));
	}
}

I need some for loop, that will cycle through every secod/round and:

  • check object’s HP
  • if it is different than initial, when effect was applied -> check if there’s a need of remove and reapply the effect with different bonus and with corrected duration (so it won’t keep incrising the initial duration)
  • then I could remove the effect my spell ID or tag the effect, doesn’t matter and somehow recalculate the duration, so reapplied effect’s duration would be: initial duration - duration left
  • if HP hasn’t changed, keep looping until the initial duration expires

EDIT: it’s fired only once, as a spellike ability (feat/spell)

Ah, in that case it’s even easier, it’s probably not even necessary to remove the effect then.
I thought it was a passive. Give me a moment…

I made this with NWN2 editor, since NWN EE won’t install properly today (I’m not sure what’s wrong, but it’ts giving me error messages all the time).
It should work, if it compiles, if it doesn’t work then tell me which lines it’s giving you problems and we’ll see if we can replace it with something else.

#include "x2_i0_spells"

void LoopWolfEffects(object oPC, float fDUR)
{
	if (fDUR <= 0.0f) return;
	if (GetIsDead(oPC) == TRUE) return;
	
	effect eFX = EffectAttackIncrease(1);
	int nHP = GetPercentageHPLoss(oPC);
	if (nHP <= 50)
	{
		eFX = EffectAttackIncrease(4);;
		eFX = EffectLinkEffects(eFX, EffectModifyAttacks(1));
	}
	eFX = ExtraordinaryEffect(eFX);
	ApplyEffectToObject(DURATION_TYPE_TEMPORARY, eFX, oPC, 1.0);
	DelayCommand(1.0, LoopWolfEffects(oPC, fDUR - 1.0));
}


void main()
{
	object oPC = OBJECT_SELF;
	SignalEvent(oPC, EventSpellCastAt(oPC, GetSpellId(), FALSE));
	if (!GetHasEffect(EFFECT_TYPE_SILENCE))	PlayVoiceChat(VOICE_CHAT_BATTLECRY1);
	float fDUR = RoundsToSeconds(10 + GetLevelByClass(45));	
	ApplyEffectToObject(DURATION_TYPE_INSTANT, EffectVisualEffect(VFX_IMP_IMPROVE_ABILITY_SCORE), oPC);
	ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectVisualEffect(VFX_DUR_CESSATE_POSITIVE), oPC, fDUR);
	LoopWolfEffects(oPC, fDUR);
}

What you’re looking for sounds like a pseudo-heartbeat. From the main script, use ExecuteScript to run a second script that stops if no longer required, else does the business, then (using DelayCommand and Execute Script) schedules itself to run again in 1 second.

Thanks! Rewrote it a bit to fit my needs and it works almost perfectly fine.

Two issues tho:

  • Setting fDur to 1.0 on applied effect makes the effect’s icon blinking on the bar - it’s not a big deal, but I wonder if there’s some workaround to set the duration to fDur-1 after every loop of that pseudo-heartbeat
  • it keeps looping even after player’s rest - is it possible to make it stop firing when player uses rest (it might also keep looping after player’s death/resurrection, haven’t tested it yet, but I’m pretty sure, that it does)?

For the firtst issue simply set the duration of the temporary effect to 1.1 seconds, while keeping the delay command with 1.0.
There will be a 0.1 sec time window where the effects might (?) stack, but it shouldn’t be a big issue either.

For the second issue, try adding the following line below the second return in the LoopWolfEffects.

if (GetIsResting(oPC) == TRUE) return;

This function exists in NWN2, I’m not sure about NWN1 at the moment.

It will not keep looping after death/resurrection, unless the resurrection happens in less than 1 second from the PC’s death. When the PC is dead, the script returns and the loop ends.

Changing fDur to 1.1 won’t fix the issue, since pretty much every effect that lasts less than a round almost immediately starts blinking on the effects list :stuck_out_tongue:

And I’ve figured out that GetIsResting(object) == TRUE conditional, but it also has a loophole, when it comes to ForceRest funtion, that won’t stop it from firing.

So there are 3 possible issues, might be some more that I haven’t thought of yet:

  • blinking effect icon due to low duration
  • possibility of player’s respawn/resurrection before it checks if player is dead, that will cause the function to keep firing
  • firing function after player’s rest (via ForceRest)

Ah the NWN1 blinking when effect is on low duration, I thought the problem was something else, like icon disappearing and reappering like it somehow does in NWN2, well that one you won’t fix it at all, and quite frankly I don’t even see reason to do it.

The possibility of player respawn resurrection before the check the player function is next to zero. You have to do some stress testing on purpose to get it, on normal gameplay it will happen one time out of thousand deaths.

What you do via ForceRest is handled by script, so you can add a tag to the effect and decide to remove it on whatever script uses that ForceRest. Again, in normal conditions, ForceRest does not happen, it’s a scripted event.

1 Like

After giving it some more thought, points 1 and 2 are insignificant.

But that ForceRest bothers me a bit, sice I’ve got a few scripts that use this function and simple remove effect won’t work here, since it only removes the effect, but the issue is that the function LoopWolfEffects will still keep reapplying it.

Set a variable on the PC when the spell is in effect. Have the LoopWolfEffects check for that as well as duration (and clear it when the duration expires). Then have a wrapper for forceRest where you also clear the variable (and any other things you want to clean up when PC rests). Replace all your calls to ForceRest with the wrapper function.

1 Like