I still suck at scripting apparently - can't remove effect

I’m sorry I have to ask for help yet again. The thing with removing effects is something I still doesn’t understand that well when it comes to scripting. So, I have these creatures that I don’t want to die in a fight, thus I create a permanent knockdown effect when they have less then 15 hp left (and make them immortal). One of you guys here helped me with that when I was doing my second module, so I reused that script. I place code like this on OnDamage. They are all part of the same team so I gave them tags like “crew1” and “crew2”…

Here’s how the code for OnDamage looks at the moment, and it seems to work well:

	int iHp = GetCurrentHitPoints(OBJECT_SELF);
	object oCrew = OBJECT_SELF;
	string sTag = GetTag(oCrew);
       
            if (iHp < 15)
            {
             
			AssignCommand(oCrew, ClearAllActions(TRUE));
   		
			effect eFX = EffectKnockdown();			

			DelayCommand(1.f, ApplyEffectToObject(DURATION_TYPE_PERMANENT, eFX, oCrew)); 
			
			
            }

Then through a conversation I want the characters to rise up again, if they have been knocked down. I made a script like this (trying to look at other help I’ve had through the 2 years of me working with my modules) and it compiles but it doesn’t work, and I’m honestly not sure if the script does what I intend it to do:

void main()
{

	effect eFX = EffectKnockdown();
	
	object oCrew;
	int nCrew = 1;
	
	effect eSearch = GetFirstEffect(oCrew);
	
	
	while (nCrew <= 5)
	{
		
	oCrew = GetObjectByTag("crew" + IntToString(nCrew));
	
		while(GetIsEffectValid(eSearch))
		{
	
		RemoveEffect(oCrew,eFX);
		eSearch = GetNextEffect(oCrew);
		
		}
		
	nCrew = nCrew + 1;
	
	}


}

Last time I asked about this, but in another context and for my first module, people talked about giving the effect I used then a unique spell ID. Even though this is no spell, maybe I just need to do something like that? When looking at the explanation you guys gave of how to implement that, I still get confused, I’m sorry to say.

I tried something like this instead but it didn’t work either:

	int iHp = GetCurrentHitPoints(OBJECT_SELF);
		object oCrew = OBJECT_SELF;
		string sTag = GetTag(oCrew);
       
            if (iHp < 15)
            {
             
			AssignCommand(oCrew, ClearAllActions(TRUE));
   		
			effect eFX = EffectKnockdown();	
			
			eFX = SetEffectSpellId(eFX, 9998); //9998 <-- just for example here, you can use
                                         //any valid id that doesn't conflict with
                                         //existing ones in spells.2da or in other scripts
					

			DelayCommand(1.f, ApplyEffectToObject(DURATION_TYPE_PERMANENT, eFX, oCrew)); 
			
			
            }

and to take away the knockdown effect, I did this instead.

void main()
{

	effect eFX = EffectKnockdown();
	
	object oCrew;
	int nCrew = 1;
	
	effect eSearch = GetFirstEffect(oCrew);
	
	
	while (nCrew <= 5)
	{
		
	oCrew = GetObjectByTag("crew" + IntToString(nCrew));
	
		while(GetIsEffectValid(eSearch))
		{
	
			if(GetEffectSpellId(eSearch) == 9998) //your custom spell id
			{
			RemoveEffect(oCrew, eSearch);
			}
			eSearch = GetNextEffect(oCrew);
		
		}
		
	nCrew = nCrew + 1;
	
	}


}

@andgalf

I don’t know what Knockdown uses as a constant (or even if it does), so this just removes all effects from your crew members.

Let me know if it works … If there is a constant, the function can be easily changed to be more specific. NB: As this currently stands, this will remove all effects!

EDIT: Applying the effect via a spell ID should also work I believe, but is slightly more involved. It involves making a new spell completely.

Thanks, Lance.

///////////////////////////////////////////////////////////////////////////////////////////////////
// LBREMOVEALLEFFECTS - SIMPLY REMOVES ALL EFFECTS (NOT INSTANT ONES THOUGH)
///////////////////////////////////////////////////////////////////////////////////////////////////
void LBRemoveAllEffects(object oPC);
void LBRemoveAllEffects(object oPC)
{
	effect eLoop=GetFirstEffect(oPC);

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

void main()
{
	
	object oCrew;
	int nCrew = 1;	
	
	while (nCrew <= 5)
	{
		
		oCrew = GetObjectByTag("crew" + IntToString(nCrew));
		
		LBRemoveAllEffects(oCrew);
		
		nCrew = nCrew + 1;
	
	}
}
2 Likes

Yep, your script worked. Thanks!

1 Like

nope, just set a spellid when constructing the effect.

void ApplyKnockdown()
{
    object oCrew = OBJECT_SELF;

    int iHp = GetCurrentHitPoints(oCrew);
    if (iHp < 15)
    {
        AssignCommand(oCrew, ClearAllActions(TRUE));

        effect eFX = EffectKnockdown();

        eFX = SetEffectSpellId(eFX, 9998);  //9998 <-- just for example here, you can use
                                            //any valid id that doesn't conflict with
                                            //existing ones in spells.2da or in other scripts

        DelayCommand(1.f, ApplyEffectToObject(DURATION_TYPE_PERMANENT, eFX, oCrew));
    }
}

void RemoveKnockdown()
{
    object oCrew;

    int nCrew = 1;
    while (nCrew <= 5)
    {
        oCrew = GetObjectByTag("crew" + IntToString(nCrew));

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

you guys have it worked out which is great. Just wanted to say that Get/SetEffectSpellId() doesn’t actually require a row in Spells.2da

3 Likes

To remove a permanent Knockdown effect, just reapply another knockdown effect but set the nDurationType to DURATION_TYPE_TEMPORARY and the fDuration left at 0.0 like so:

	ApplyEffectToObject(DURATION_TYPE_TEMPORARY, EffectKnockdown(), oPC);

This should make the character standup from the permanent knockdown effect.

5 Likes

@travus - You are smart, travus! That’s a really simple solution. Why can’t I think of such things? Seems I’m always overcomplicating things.

@kevL_s - Your functions seems quite similar to what I came up with (well, I can’t take credit since I’ve just combined scripts from you, Aqvilinus, Clangeddin and others in this particular case, I just read through a previous thread and copied and tried to apply code while trying to also understand what I was doing) so what did I do wrong, if I may ask?

Excellent! :slight_smile:

this was defined before the crew-loop:

effect eSearch = GetFirstEffect(oCrew);

meaning that oCrew wasn’t even defined yet, and things just went borkdy bork from there …

try to make it a point to define your iterator on the line immediately above while() loops … helps keep the head on straight  :)

1 Like

Good to know! :slight_smile:

1 Like

Ah, I see it now when again comparing our scripts. Thanks!

1 Like

Another question - The part with:

if (GetIsObjectValid(oCrew))

Is this strictly necessary, or is it just a safety precaution? And I’m not entirely sure of the

eSearch = GetFirstEffect(oCrew); //safety (removing an iterator from the list)

You already define effect eSearch = GetFirstEffect(oCrew); before in the script…Why then is it needed again? Just trying to learn…
As a matter of fact, I’m not familiar with the word “iterator”. Tried google translate but apparently it’s called the same in swedish so I’m a bit at a loss here. :slightly_smiling_face: Guess it’s a technical term of some sort? Sorry if I’m being dumb now…

Edit: Googled iterator some more and it’s apparently part of the programming language. An object that makes it possible to do a sequencial go through, or something like that. Ok. Still, how does defining the GetFirstEffect again remove something from the list? I’m not quite following.

Because if during the iteration the effect is found (on the current crew member being checked), you move onto the next crew member where the iteration needs to start again. Otherwise, the iteration would continue after the point it was last searching, thereby missing the effect to remove on the next crew member.

The GetIsObjectValid is just a security measure in case it is no longer around as an object or possibly dead I guess.

Lance.

Ah, now I get it.

more stuff

NwScript is very lenient so it’s probably just a safety. But why make the CPU look for effects on a creature that may have died and disappeared if a simple GetIsObjectValid() can tell it, don’t bother move on now.

For me it’s a habit to do that test for (object != null) because c/c++/c# etc will either throw an exception or just crash your program …

This gets into what vectors (c++) or collections (c#) are behind the scenes. When there is a collection of effects that are being iterated over, like the effects on a creature, they are accessed according to the state of the iterator.

the iterator in that case is handled by the NwScript implementation of GetFirst/NextEffect(). So basically we can think of those functions as the iterators.

Now say there’s a sequence {1,2,3,4,5}

the iterator increments by 1 to access each element in turn. so you remove element #3 … the iterator moves on to 4, but by removing element #3, #4 has dropped down to #3 slot, and #5 has dropped down to #4 slot. So the iterator is going to skip the old #4 element. Because, it’s already handled #3 and doesn’t know that #4 dropped down a slot.

If you’re using an integer-iterator, you can just decrement the iterator value and deal with it that way. But with GetFirst/NextEffect() the iterator needs to be sent right back to the beginning. The loop starts all over again, and again etc, until a successful pass is made through all elements without any elements getting removed.

Look at the parameter passed into GetFirstEffect() → oCrew

if eSearch isn’t redefined for each new crew member, huh … FirstEffect has to be redefined for each creature when it’s its turn in the crew-loop.

It doesn’t, RemoveEffect() removes something from the list. That is, go back to FirstEffect ONLY if an effect is removed; if no effect is removed, fine let it go through NextEffect, NextEffect, NextEffect until the iteration (aka loop) runs out of effects to check and none check true, none get removed

2 Likes

Deleted Post.

TR

1 Like

Alright, I hate this, but apparently I have to ask yet again.

I just added another small sidequest to my module. I was about to use the elusive apply effect thing again. And lo and behold, as usual it for some reason doesn’t work for me. I have been at it for 2 hours now and soon I’m about to scream at the screen and throw my computer out the window.

I am spawning two creatures into an area when the PC enters a trigger and one of the creatures, a small boy, is supposed to get knocked down (at first I wanted and tried the EffectDeath, which actually worked, but I couldn’t revive him after that) Now when trying a variation of KevL_s’ script and another script that has worked before, and variations upon variations…well, it just doesn’t work! Why!! The boy won’t lie down on the ground.

#include "ginc_object"
#include "nw_i0_generic"


void main()
{

	object oPC = GetEnteringObject();
	
	if(!GetIsPC(oPC)) return;
		
	if(GetLocalInt(OBJECT_SELF,"done")) return;
    
	object oPC1 = GetOwnedCharacter(GetFactionLeader(GetFirstPC()));
	
	int nInt=GetLocalInt(oPC1,"NW_JOURNAL_ENTRYq_missingson");

	if (nInt == 11)
	{

    object oTimmy = SpawnCreatureAtWP("timmy", "timwp");
   	object oDireBoar = SpawnCreatureAtWP("l_boardire2", "de_direb");
   
	SetLocalInt(OBJECT_SELF,"done",1);
	
	//AssignCommand(oTimmy, ClearAllActions(TRUE));
   		
	effect eFX = EffectKnockdown();			

	DelayCommand(1.f, ApplyEffectToObject(DURATION_TYPE_PERMANENT, eFX, oTimmy)); 
	}	
   
}

@andgalf - have you tried some debug to see how far your script is getting ?

#include "ginc_object"

void main()
{
    SendMessageToPC(GetFirstPC(FALSE), "run Tim");

    if (!GetLocalInt(OBJECT_SELF, "done"))
    {
        SendMessageToPC(GetFirstPC(FALSE), ". not done yet");

        object oEnter = GetEnteringObject();
        if (GetIsPC(oEnter))
        {
            SendMessageToPC(GetFirstPC(FALSE), ". . oEnter is PC");

            oEnter = GetOwnedCharacter(GetFactionLeader(oEnter));
            if (GetJournalEntry("q_missingson", oEnter) == 11)
            {
                SendMessageToPC(GetFirstPC(FALSE), ". . . MissingSon is 11 - do knockdown");

                SetLocalInt(OBJECT_SELF, "done", TRUE);

                SpawnCreatureAtWP("l_boardire2", "de_direb");

                object oTimmy = SpawnCreatureAtWP("timmy", "timwp");
                effect eKd = EffectKnockdown();
                DelayCommand(1.f, ApplyEffectToObject(DURATION_TYPE_PERMANENT, eKd, oTimmy));
            }
        }
    }
}

edit: i guess you have since the critters are spawning …

you could try having Tim play one of the “prone” animations instead of knockdown

1 Like

Thanks @kevL_s! I was just about to write some debug script. I might as well use yours. Before I try yours I again tried with EffectDeath, and for some reason that works. Here’s the current script I’m using (and it works, I just want to use EffectKnockdown instead):

#include "ginc_object"
//#include "nw_i0_generic"

void Knockdown()
{

object oTimmy = GetNearestObjectByTag("timmy");

AssignCommand(oTimmy, ClearAllActions(TRUE));

    
        //object oObject = GetObjectByTag(sTagString, iInstance);

        AssignCommand(oTimmy, SetIsDestroyable( FALSE,FALSE,TRUE ));
	    SetImmortal( oTimmy, FALSE );
	    SetPlotFlag( oTimmy, FALSE );
        effect eFX = EffectDeath();
		//PrettyDebug("Name of oObject = " + GetName(oObject));		
        ApplyEffectToObject( DURATION_TYPE_INSTANT,eFX,oTimmy );


//effect eFX = EffectKnockdown();			

//DelayCommand(1.f, ApplyEffectToObject(DURATION_TYPE_PERMANENT, eFX, oTimmy));
}


void main()
{

	object oPC = GetEnteringObject();
	
	if(!GetIsPC(oPC)) return;
		
	if(GetLocalInt(OBJECT_SELF,"done")) return;
    
	object oPC1 = GetOwnedCharacter(GetFactionLeader(GetFirstPC()));
	
	int nInt=GetLocalInt(oPC1,"NW_JOURNAL_ENTRYq_missingson");

	if (nInt == 11)
	{

    object oTimmy = SpawnCreatureAtWP("timmy", "timwp");
   	object oDireBoar = SpawnCreatureAtWP("l_boardire2", "de_direb");
   
	SetLocalInt(OBJECT_SELF,"done",1);
	
	DelayCommand(1.0,Knockdown());
   		
	
	}	
   
} 

Tried your script @kevL_s and everything seems to be running. I get every message, but the kid won’t lie down, he just stands there.

I’ll try another thing…