WARNING: This is not a C syntax tutorial. This document focuses on what happens when you write compiling code, not how to write the code to begin with.
Though NWScript appears in all aspects to be a derivative of C, the way it functions is quite different. For beginners, these fundamental differences might be easier to which to become accustomed. But for experienced programmers, the structure of how scripts compile (and therefore how they perform) in NWScript is quite alien, and may seem a little cumbersome.
I chose to write this information because it took me a considerable amount of extra debugging in my module to come to this conclusion about NWScript's behavior, and it is not entirely apparent. It is also not documented anywhere. So, hopefully this information will remedy these problems and answer the question, "why is my module doing this and not that?"
Background: Program Execution
When any program is executing, it does so sequentially: meaning, the processor can only do one job at a time. Even though we have "multi-tasking" operating systems, the processor is still only doing on command at a time; the operating system merely "shuffles" the jobs that the processor should be executing so many different programs can all share the processor at once. (This is related to the notion of a task's priority; in Windows, for example, setting a task to "high" causes proportionally more of that task's executions to be sent to the processor than others).
The "program queue" is essentially the list of commands that the processor will execute. Think of a queue like a line of people (in fact, in Britain they use the word "queue" in place of what we call a "line" in America). If you're waiting at the grocery store, and you're forming a line, the first person to enter the line is "served" (and therefore exits the line) first. An abbreviated way of thinking about a queue is by saying that it behaves "first in, first out."
So, essentially, every line of code you write gets added sequentially to this list of executions to run, referred to henceforth as the "program queue."
Execution in NWScript
When you write script in Neverwinter Nights, you should think of the script like any other program, such that each line of code you write gets added to the "program queue" to be executed in order.
However, there is one detail (and a very important one) that BioWare forgot to tell anyone before they started scripting. That is, the very strong differentiation that exists between a scripting "command" and an "action."
In fact, they operate independently of each other, as though they were being added to two different queues. And in effect, that is exactly what is happening.
Commands vs. Actions
You may have heard rumors floating around of something called the "action queue." That is effectually the separate queue which any "action" is added to, independently of the rest of your script.
These commands are any of the functions which begin with "Action", such as ActionTakeItem() or ActionCastSpellAt() or ActionMoveTo().
If a command does not start with the prefix "Action" it will be added to the command queue rather than the action queue.
So what, you may ask?
Here is the problem:
Let's say I have written the following block of script:
int nothing = 0;
int walking = 1;
// if "status" hasn't been set yet, current_status will = 0
int current_status = GetLocalInt(OBJECT_SELF,"status");
if (current_status == nothing) { // if the actor isn't busy
SetLocalInt(OBJECT_SELF,"status",walking);
ActionMoveToObject("waypoint01");
ActionPutDownItem("longsword");
SetLocalInt(OBJECT_SELF,"status",nothing);
}
This block of code might be common in something like an OnHeartbeat() event. By using a variable to keep track of his "state" you can prevent all of those commands from being executed every six seconds. If you didn't keep track of the state and just told him to walk to the waypoint, every six seconds he would attempt to walk there, regardless of whether or not he's supposed to be doing anything else (like engaged in battle, for example).
This block of code should first set his state to "walking", then he'll move to the waypoint, then he'll put down his long sword, and then he'll set his state back to "nothing". I can expect that this operation may take a long time if he's far away from the waypoint. He may not be doing "nothing" again for another minute or two. Right?
Wrong!
In this example script, we encounter the common command vs. action pitfall. The command SetLocalInt() is classified as a "command" whereas the ActionMoveToObject() and the ActionPutDownItem() are both classified as "actions."
In a C program (or virtually any other programming language) you would expect that the second SetLocalInt() statement (the one that sets his state back to "nothing") would execute only after the last two actions have been completed. This, however, is not how NWScript behaves.
When NWScript sees a call to one of the Action* commands, such as ActionMoveToObject(), the program considers a successful completion of that command has occurred when the Action has been added to the action queue, not when that action has completed. Thus, the simple act of adding the action to the action queue is good enough for the program to continue on to the next line, while saying nothing whatsoever about whether or not the action that you added has taken place or not.
The result of the above block of code would look internally more like this (in pseudo code):
Set this local int to 1 (which means to us, "walking")
Add this move action to the action queue
Add this drop action to the action queue
Set this local int to 0 )which means to us, "nothing")
The problem is that this code executes extremely fast, which means that these set statements have basically no effect, because they'll be set and reset within a matter of nanoseconds, regardless of the fact that those two actions in between may take many seconds or minutes.
Why, BioWare? WHY?!
The reason for this is because unlike script executions (referred to as "commands" so far) will always operate within a fixed amount of time (usually in nanoseconds, dependent on the speed of your processor). Actions take longer to complete, so they can't be run "instantly."
If actions were computed like script commands, and were only run on one central stack, imagine how poorly that would run! Since we can have many NPCs doing many different things at once, it is clear that the BioWare engine evaluates script commands like a multitasking operating system running many tasks at once, which works quite nicely as long as all of the operations are being calculated incredibly quickly (and consistently in regards to execution time). But what happens when you have an NPC walk across a 32x32 map?
If his "walking across a 32x32 map" were inserted into the command queue with all of the other commands, your game might freeze up; nothing would be moving, or doing anything, except for that one NPC walking across the (large) map. As soon as he was done, the game would universally freeze again until the next NPC who was lucky enough to get an action on to the command queue finished his action. And so on, and so forth. You would basically have a game that is entirely unplayable.
Of course, that is speculation. I didn't design NWScript or the engine. So, I don't know exactly what happens internally, but that's a guess. It's clear that there was some very good reason for establishing the notion of an action queue and implementing it, whatever that reason may be.
Manipulating the Action Queue
The action queue is executed automatically and internally. BioWare hasn't seemed to supply us with any way to manipulate the action queue except with ClearAllActions(); which blanks the entire queue.
However, they do provide us with a command that allows us to insert functions that would otherwise be added to the command queue into the action queue instead. This function is called ActionDoCommand().
ActionDoCommand() takes one parameter, of type action.
What the heck is an "action"? It's not like an int or a string which has an obvious value, or even an event which has a list of constants corresponding to valid values for an event type variable.
An "action" type is a void function.
What does that mean?
ActionDoCommand() expects a void function to be its parameter.
So, if I have the following declaration:
void MyFunction()
{
PrintString("This string gets added to the log file.");
}
We would use this in conjunction with ActionDoCommand() with the following syntax:
ActionDoCommnad(MyFunction());
In this example, PrintString() is one of those functions I've classified as belonging to the "command" queue, which means it gets executed immediately wherever it's placed.
Since PrintString() is a void function itself, I could have also accomplished the same thing by this line:
ActionDoCommand(PrintString("This string gets added to the log file."));
If you attempt to use a non-void function (meaning, a function which returns a value) you will get a compiler error when trying to save the script.
(Note: you can tell what the function returns by looking at the function prototype. This is the word immediately preceding the name of the function itself. Function prototypes are shown in the compiler messages panel in the script editor when you click on them in the function list on the right hand side. If the function returns no value, the keyword "void" will be used)
The effect of using ActionDoCommand() is to insert that command into the action queue! In this way, we can keep the linear flow of the program intact, and also cause lines of code to only fire when they've waited for their corresponding actions to finish. Let's revisit our previous example, this time using ActionDoCommand().
int nothing = 0;
int walking = 1;
// if "status" hasn't been set yet, current_status will = 0
int current_status = GetLocalInt(OBJECT_SELF,"status");
if (current_status == nothing) { // if the actor isn't busy
ActionDoCommand(SetLocalInt(OBJECT_SELF,"status",walking));
ActionMoveToObject("waypoint01");
ActionPutDownItem("longsword");
ActionDoCommand(SetLocalInt(OBJECT_SELF,"status",nothing));
}
This time, everything in the if { } block will be added to the action queue.
Our NPC will not be set to "walking" until the action queue gets to that point (meaning if he had other actions previously assigned to him before this code executed, those would finish first, and then and only then would the SetLocalInt() command encapsulated in the ActionDoCommand() fire).
He will proceed to move to his way point and put down his sword. After both of those actions have been completed, the action queue will move to the second set command, which will then cause him to be set back to idle.
A side note:
If you tried this code, you would find a strange behavior - the NPC would walk to the waypoint, and then he would RUN back to where he was before he started moving and put down the sword at his OLD location! Why? When ActionPutDownItem() is called, its location is predetermined. Meaning, rather than evaluate where on the ground to put the object down when the action comes up to the front of the queue, it chooses a location as soon as it's added to the queue. I find this to be particularly poor design, but unless BioWare decides to change it, that's how it will act. There are ways to get around this; one is to delay the action from being added into the queue. Another is to use an ActionDoCommand(ActionPutDownItem()). Why should this work?
If you've understood the logic so far, it should make sense.
When an ActionPutDownItem() is called, what is actually executed is a command to add that action to the action queue, which means the action won't actually execute until it gets to the front of the queue (meaning all outstanding actions in front of it in the queue will have to finish first). And our problem is that the item that he's putting down gets a location immediately - the location is decided, and then the action is queued. That means the location will be at the feet of the NPC wherever he is standing when the action is added to the queue, not the location he is standing when the action is actually executed.
By encapsulating an action in the ActionDoCommand(), you are telling the script to add a command which adds the action to the queue in the queue itself. That means that when this action gets to the front of the queue, rather than actually execute the PutDownItem() behavior, it will see an ActionPutDownItem(), and as you know, that will cause the PutDownItem() behavior to be added to the action queue rather than be executed.
There's a small detail that should be noted, though: since as far as I know at the time of writing this document there is no way to cause an action to "cut" in front of others in the action queue. So, your PutDownItem() behavior will get put at the end of the action queue.
This works okay if your ActionPutDownItem() is the last behavior an NPC will add to the queue in any given script. It doesn't work so well if he has other actions lined up, especially if those actions tell him to move anywhere.
At present, there doesn't seem to be a good solution to this problem. By good, I really mean elegant. Some commands, like the trigger to start a conversation, have two forms: one is ActionStartConversation which causes the initiation of the conversation to get put into the action queue, and then there's BeginConversation(), which gets executed immediately.
In my experience, BeginConversation() seems to shut down an NPC - meaning, even after the conversation is over, it doesn't appear as though his action queue resumes. I think this function has an implied ClearAllActions() in it (which causes all queued actions to be forgotten).
There is no way to explicitly (and immediately) cause a creature to drop an item - it's only doable with an action. And you'll encounter the scenario I just described when you try to use it. One solution might be to write your own (void) function that will simulate an item being dropped - the way to go about doing this is to use DestroyObject() on the item in the creature's inventory and CreateObject() at a location near the creature's feet. This isn't perfect either, but it should probably work, since DestroyObject() and CreateObject() are "commands" which get executed immediately. This custom function used in conjunction with an ActionDoCommand() should work. (I haven't tried or tested this idea yet - if you do, please e-mail me the results!)
Remember
A good way to test (and see this for yourself) is by using the Print*() commands, which print values to log files. By using the time-stamped log functions, you can see how the times will differ depending on whether you use a flat PrintString() command or an ActionDoCommand(PrintString());.
Be careful with ClearAllActions(). Using it will cause whatever action you want to happen to be executed immediately (since it is the only function left in the queue, therefore it's at the front of the line). However, remember that there is absolutely no way of recovering the actions they had lined up before the ClearAllActions() was called. There is also no way of saving those actions somehow, calling that one, and then copying the "saved" queue back into the real, active queue. This is very serious limitation that I personally intend to complain to BioWare about, since it makes the concept of certain actions taking priority over others more difficult to implement than it could be. Unless, of course, there is a way to do exactly, and I just haven't figured it out yet. If that's the case, and you find out how to do it, it's your responsibility to write it up and let us all know!
Have a function that you want to be added to the action queue, but it returns a value that you're not using? You can handle this by writing a void function that just encapsulates the non-void function. For example, CreateObject returns the object that it has just created. But, you will very rarely use its return type, because it seems as though this return was included for the sake of making sure the object was successfully created. You can assume that the objects will always be created. However, ActionDoCommand(CreateObject( )) is a compiler error. To get around this, you could write an encapsulating function, like this:
void CreateObjectVoid( ) {
object oErrorCheck = CreateObject( );
if (!GetIsObjectValid(oErrorCheck))
PrintString("Creation of object failed");
}
ActionDoCommand(CreateObjectVoid( );
Keep in mind that the ellipsis ( ) should be replaced with CreateObject()'s parameters. This function just creates an object and checks internally whether or not the object was valid (meaning it was created without error). If it wasn't, print an error out to the log (you might want to timestamp that error and add some more information to it, like the object's id, tag, etc. This was just a brief example). Now, you can add this function to the action queue without a hitch, and make sure to create an object only AFTER the blacksmith has hammered the anvil for 180 seconds (for example).
Be very conscientious of the fact that there are two separate command queues, and make sure you understand to which queue you are adding, and why. Though you might be tempted to put every thing in the action queue, there are plenty of times when having a command execute independently of the entity's current actions is necessary.
Keep in mind that when you are adding an action to the action queue, if that action requires other information (like a location, for ActionPutDownItem() for instance), that information is taken at the time the action is entered into the queue, not at the time the action is executed.
Also, remember that anything you enter in the command queue must be based on information that's presently known at the time the command is entered into the queue.
Here's an example of a script that will not work:
// worry about the nearest player character
object oCreature = GetNearestPC();
// get a waypoint object that marks a center of a circle
object oMarker = GetObjectByTag("center_of_circle");
while(getDistanceBetween(oMarker,oCreature) > 10) {
// if the creature is still outside of the
// the circle, just wait a second, and check
// again.
ActionWait(1.0)
}
ActionSpeakString("Excellent! You made it into the circle!");
Let's examine the loop. The loop is set to terminate when the distance between some marker object (at the center of a circle) and the nearest player character is more than 10 units, wait for 1.0 second and then check again if the creature has moved since.
You may ask: why shouldn't that work?
Well, the truth is that it does work but it works too fast. The desired result is that the check happens every 1.0 second (as per the ActionWait() command in its body). But the problem is that, as you know, ActionWait() does not delay the execution of the script by 1.0 seconds as the script intends, but rather just slaps something to the end of the action queue. The result? while() loop fires a thousand times a second, and adds a thousand ActionWaits() to the action queue. The action queue does have a limit (though what that limit is would be nice to know - it's a good research project) and when that limit is hit, you get a message in your console in yellow similar to the format: "OBJECT objectID TAG tag ERROR: too many instructions".
A way to get around that? Put it in a heartbeat, which does check every 6.0 seconds.
Or, translate that while loop into a recursive function which gets added to the action queue rather than getting executed immediately like the conditional check in a while loop does. Recursion, however, is a topic for another day.
Conclusion
I hope you find this useful and informative. If you're having trouble, or discover anything quirky about this method, please e-mail me.