Creature AI Heartbeat Scipt (Advanced Info Required)

Hi All,

OK, this appears to throw me at any rate …

I have a heartbeat on a creature that calls a pseudo faster heartbeat from an attached include script. It all worked fine, except …

When I tried to add a variable to TERMINATE that call, it does not stop! It’s as if the saved game does not recognise the newly edited campaign heartbeat script function call. i.e. EVERY campaign script I have edited before does get recognised as changed when I have done so in the past. This latest change that calls the pseudo heartbeat, however, does not get terminated, even after I have compiled all associated scripts, which includes the original heartbeat script. E.g. If a global == 0, then the call to pseudo heartbeat is supposed to be skipped, but is ignored instead … and even the pseudo heartbeat called is also set to terminate if global == 0. However, neither stop!

EDIT: I even just changed the global check to check a positive value (TRUE), and still the heartbeat does not stop when it is supposed to see the global == 1 and end. ???

Am I missing something? Is this normal for such?

i.e. Do such changes require a complete restart in such cases?

Thanks in advance, Lance.

@Lance_Botelle Where are you trying to terminate from? In other words are you trying to terminate from the creature HB or the pseudo HB? I have an idea of what could be wrong but I need this information first. Thanks.


Hi TR,

Thanks for looking … Script sections below …

Basically, in the past, if I update a CAMPAIGN SCRIPT, it works in any saved game that has the newer script applied. However, this monster HB addition does not update between script updates …

i.e. The original version did not have the Global “TESTMODE” check in it and so the heartbeat started regardless. As I was having all sorts of problems (which may be due to this actual fact), I decided to add the additional GLOBAL check to prevent/reset the pseudo heartbeat when I reloaded the game to test any changes. However, the old hearbeat just kept running, even though TESTMODE was 0 (zer0) … and I also tested if it was TRUE too.

OK, here is the function called … EDIT: fixed logic for first check.

EDIT: Some functions are just some of my own and 1582 is my appearance for the ankheg.

EDIT: To duplicate the issue, just add these scripts without the GlobalInt “TESTMODE” section in place and save the game once the heartbeat is running. Then alter the scripts with the new GlobalInt “TESTMODE” in, and reload the game. Normally, if these are CAMPAIGN scripts (which they are), it usually applies the changes without any issues, but this time around it ignores my changes.

EDIT: You will need to add your own debug feedback.

EDIT: My “guess” is that somehow, once a PSEUDO heartbeat is running, it becomes “locked” to any external changes.

void FastHeartCheck();
void FastHeartCheck()
	if(GetIsDead(OBJECT_SELF) || GetGlobalInt("TESTMODE") == 0){return;}	
		object oNearTarget = GetNearestLiveEnemy(OBJECT_SELF, 1, 0);
		if(oNearTarget == OBJECT_INVALID)
			if(GetLocalInt(OBJECT_SELF, "UNDERGROUND") == 0)
				DelayCommand(2.0, BurrowDown(OBJECT_INVALID));			
				DelayCommand(2.0, DeleteLocalInt(OBJECT_SELF, "FASTRUNNING"));
		if(GetLocalInt(OBJECT_SELF, "UNDERGROUND") == 1 && !GetIsInCombat(OBJECT_SELF)
		&& GetLocalInt(OBJECT_SELF, "BURROWING") == 0)
		DelayCommand(2.0, FastHeartCheck());

And here is the creature HB that calls it:-

if(GetGlobalInt("TESTMODE") == 0 && GetAppearanceType(OBJECT_SELF) == 1582)
	else if(GetLocalInt(OBJECT_SELF, "UNDERGROUND") == 1)
		 if(GetLocalInt(OBJECT_SELF, "FASTRUNNING") == 0){FastHeartCheck();} 
1 Like

This is probably too simple a fix, but one step at a time. Might be a cut and paste error.

Looks like you might be missing a closing parenthesis in your first line:

if(GetIsDead(OBJECT_SELF) || GetGlobalInt("TESTMODE" == 0)

Probably should be:

if(GetIsDead(OBJECT_SELF) || GetGlobalInt("TESTMODE") == 0)

1 Like

Hi Tiny Giant,

That was just a typo when I copied it across for viewing … will update thanks!

EDIT: I added it after I copied what I had to demonstrate what was needed to test … and typoed! :crazy_face:

Cheers, Lance.

Yup, I kind of figured since not having it would have caused a compile issue and you said the scripts compiled. I have a script setup very much like this and it works fine. I’m probably out of time for the night. If you don’t have an answer by morn, I’ll try to take a closer look.

Thanks Tiny Giant, that would be appreciated.
Catch up tomorrow. :slight_smile:

Cheers, Lance.

I think I may know what is happening here (as long as the scripts still work like they do in NwN 1/EE).

Before I start I’ll explain a couple of things. This is just to make sure you understand. It’s not meant to be condescending. First a term - Recursion. A function that calls itself is said to be recursive. So all that a pseudo HB is recursion with a delay. In order to prevent infinite recursion there needs to be a variable (sometimes called a flag or alternatively a guard) that tells the recursive function to exit before it calls itself again. Something like -

    if(!(GetLocalInt(OBJECT_SELF, "bRunPHB"))) // Recursion guard -
        return;                                // terminate pseudo HB

I don’t see such a variable in your code (you only check if OBJECT_SELF is dead or if testmode is turned off). To me that suggests that your pseudo HB, once you start it running, never stops. You actually need to stop the pseudo HB before you stop the creature HB.

Another thing I noticed. That section of code that sets the pseudo HB running has the conditional instruction -


The thing is (assuming the pseudo HB is running) the pseudo HB has the instruction -


Now in NwN 1/EE, if you call that instruction and the variable doesn’t exist at the time, the scripting language will “helpfully” create a new variable with that name on OBJECT_SELF. So if the pseudo HB is running after you issue the DeleteLocalInt() call, a new variable of the name FASTRUNNING will be created.

Hope I’ve read your code correctly (if not apologies, it’s 2:30 am here in the UK).


Hi TR,

The explanations are welcome. :slight_smile: I apologise if it seems like people have to repeat themselves to me, but sometimes it can take time for me to grasp something nowadays.

Also, I wanted to make sure I have explained myself properly too. First, this behaviour I have seen for the first time may be something that is quite normal, but something I was simply not familiar with. However, its implication means I have to be cautious moving forward. Here is what I mean:-

  1. When I patch a module, I can normally simply update a campaign script and any loaded game from a saved game (that once used the old script) now normally works with any altered newer script.

  2. Even if I update a monster heartbeat script, any alterations normally works fine. e.g. If I place a line that says if(GetGlobalInt(“TESTMODE”) == 0){return;} at the top of the heartbeat script and replaced it, when I load the saved game, the script would stop running, because it is zero by default.

  3. So, when I added the same new bit of code to both the original heartbeat and its pseudo heartbeat call, I expected the pseudo heartbeat to now stop, as well as the original heartbeat script. In further testing, the original does (and so works as I expected), but the pseudo one does not.

  4. The bottom line being, when I introduce the GLOBAL check for zero in the normal heartbeat, it works for saved games, but when I introduce it to the pseudo heartbeat, it does not work for saved games.

  5. NOTE: The added GLOBAL test does work in the pseudo heartbeat, but only if I start the game afresh. It does not work “on the fly”, like it does with a normal heartbeat script.

Thinking about it some more, I think I am probably “missing the obvious” (nothing new there I guess), in that a saved game has already started the pseudo heartbeat function call and now has to “lock” it off to any outside influence. Perhaps because it’s one layer deeper in the function calls compared to the top level heartbeat script? That’s just my way of explaining it, as I don’t know how else to say it.

It is this “locked off to a newly added global conditional check” that I had not been expecting, due to the fact that any and all previous alterations to any campaign script always applied new changes in any saved game for me.

I’m in the same country and I was already in bed by then … :wink: Even so, I don’t sleep well at all, and so I know what concentration can be like at the best of times. Even now I cannot sleep, and yet I am shattered. May go back to bed again later.

Anyway, if this latest explanation helps to explain my surprise at the result, then maybe it is not such a surprise to others, who, like yourself, may know better. :slight_smile: I had just not been expecting my additional check added to the pseudo heartbeat to be “ignored”, even in a saved game. Now I know better.

With this knowledge in mind, to test this code I am currently working on (that uses this pseudo heartbeat), I am now having to always start a new fresh game to test any changes I have made, unlike other tests I can normally do with campaign script changes, which I can normally just replace and any saved game picks up the changes.

Thanks again … and if anything I wrote above helps explain or makes any difference, then please let me know. :slight_smile:

Cheers, Lance.

Let me know if I’m off-base on this one. I thought about the recursion issue also, but didn’t think it was an issue because of the use of DelayCommand(). True recursion calls the function directly. I was under the belief that the DelayCommand adds the desired action to the action queue and the script continues on its way. For example, if the delay was 5 minutes, the script wouldn’t sit there for five minutes in a recursion loop waiting for 5 minutes, it would finish the current script and fire the DelayCommand script when it was time. Also, the test (in this case, TESTMODE == 0) seems like it should work just fine. I have several loops set up just like this, with the execution check varaible at the beginning of the function, and they work fine.

One thing you might try due to the level the script is being run at is to wrap the initial call to FastHeartCheck() in a DelayCommandWrapper to allow the original heartbeat to complete without moving suspending to the PHB (in case that’s what’s happening).

else if(GetLocalInt(OBJECT_SELF, "UNDERGROUND") == 1)
		 if(GetLocalInt(OBJECT_SELF, "FASTRUNNING") == 0)
                        DelayCommand(1.0, FastHeartCheck()); 

Hi TG,

Well, it does work fine, if the pseudo heartbeat which carries it does so from the very start of its initial calling. My point was that in the past, if I changed a campaign script from not having such to including such, and then loaded a game which would now use its new version, I thought the change would be noted and the pseudo heartbeat would have stopped. However, that’s my point … it does NOT stop a pseudo heartbeat that is already running. Weirdly (<<< from my understanding) it DOES stop an ordinary heartbeat script in a saved game if I make any changes between loads.

The bottom line, the global setting works for a pseudo heartbeat only from a fresh run after any changes have been made. (ie Not from a loaded game, as that appears to have kept its previous rendition of the pseudo code.)

The fact that a normal heartbeat script can be changed, but a pseudo one not is what had me completely stumped between reloads of my game where I had changed some code in the pseudo call, but nothing was changing in the reloaded game.

See also my response to TR above, as that also explains what I am trying to say. :slight_smile:

Cheers, Lance.


Maybe I missed it in all the paragraphs above, but is TESTMODE dynamic? That is, when and where is it changed to 1, or is it a static variable set before the module is loaded/run?

It’s dynamic - I can switch it in game.

Note: It does work, but not with altered pseudo heartbeat scripts (to include its toggle) on a reloaded game.

And that’s my point … but maybe I am stating something obvious to everybody else … :slight_smile:

I just did not expect an altered/replaced campaign script NOT to work on an existing saved game. Every other altered campaign script always works with a saved game. This one did not.

Ok. I’m running out of knowledge here. Have you tried setting the variable on the module, or another object, just to see if it’s the global variable that’s the issue? I understand that the value of global variables are saved with saved games, so I’m wondering if that might cause an issue.

1 Like

No, I have not tried using a local one yet. However, I know the global is set as it will stop ANY other script it is used in - even dynamically.

I just think it’s something I stumbled across in my usage of pseudo heartbeat scripts - which I hardly ever use … and especially this AI involved.

Anyway, I have to go now, but thanks for the suggestions.

Also, now I know this happens, I am able to work around the issue - I just have to keep restarting the game to test the code rather than work from saved games. It’s a little frustrating, but I have done what I need to do now, so I won’t mess with it again. :wink:

Cheers, Lance.

Sorry for the delay. My knowledge as far as save games is limited to NwN1 so this may be different as far as NwN 2 goes. In NwN 1/EE, when you save a game you actually save the whole module with variables etc. in the state that they are in at the instant that you save the game. From what you are saying, it would appear that something similar happens in NwN 2. My guess is that there is a pointer to the function that is the pseudo HB that was saved and it points to the un-edited version.


Hi TR,

Yes, I have coded with NWN, but that was years ago now, and I think there are differences, especially with respect to campaign scripts prioritizing over module ones. That is why you can “patch” a NWN2 module with updated campaign scripts - normally. So you can imagine my surprise when this pseudo heartbeat acted differently to what I normally see with such scripts.

Anyway, I have put it up as an experience to remember when dealing with such now … and to make sure all pseudo heartbeats are correct prior release, as they cannot be patched for saved games like normal NWN2 campaign script behaviour.

For the record, this is why I switched all my module conversations and scripts to campaign versions a few months ago, because of the normal advantage of being able to patch it this way - i.e. I no longer have to rely on a priority hak system, which I worked out in previous NWN coding.

Thanks for looking anyway. :slight_smile: