Celowin - Part IV: User Defined Events

From NWN Lexicon
Jump to: navigation, search


The purpose of this sequence of lessons is to take a complete beginner to programming, and teach him or her how to use NWScript to write modules. The early lessons will be very basic, and anyone that has done any coding at all will be able to skip over them. The goal here is to make the lessons so that even the people that just shudder at any type of code can learn.

Feel free to post these lessons on any forum, print them out, or modify them. However, just give me credit for doing them. Any comments on these lessons, good or bad, can be sent to me, Celowin.

I am going to assume that anyone looking at these lessons has at least played around with the Aurora Toolset a bit. If there is enough feedback that people don't know how to do the simple placements that I have in these lessons, I will consider spelling out in more detail what needs to be done.

Clean Up on Lesson Three

The more astute people following this sequence of lessons noticed that the guard script we came up with in Lesson Three was "good enough", but didn't behave exactly as we wanted it to. Every time the guard was "friendly", it would call out the greeting twice. Only people paying close attention would notice, but it is something that we should probably fix before going on. Besides, it gives me a chance to explain another concept.

First, we need to pinpoint why the guard was behaving that way... and it boils down to the OnPerception script handle. There are four things that can cause the attached script to fire:

  • The NPC sees something
  • The NPC hears something
  • The NPC notices something disappear from sight
  • The NPC notices something stop making sounds

So, what was happening is that the NPC was both seeing and hearing the PC, and so the script was being executed twice. Not the end of the world, but not the behavior we wanted, either.

Let's fix this in the following way: we'll have the guard only react if it sees something. After all, the guard is looking for a ring, and those are usually pretty tough to hear.

We could do this by nesting another level of if statements, but it was already getting a bit confusing with all the different layers we had. Instead, what we want is for the script to check two things at once: we want to be sure the object perceived was a PC and that it was perceived by sight. Only if both things are true will we do all the rest.

We can do this just by modifying the condition, along with the operator "&&" (read as simply "and"). When put into a condition, "&&" is a way of linking two conditions together. The entire thing will be true only if both parts are true. To fix our script, then, all we have to do is change the line:

if (GetIsPC(oSeen))


Then, the script will only run if it was a PC noticed by the guard, and also the guard saw the PC, instead of noticing the PC some other way.

The GetLastPerceptionSeen() is a little function that returns TRUE if the last perception was by sight, FALSE if any of the other three.

I won't do an example right now, but another way of linking conditions together, instead of "&&" is "||". "||" is read "or" and means that the condition is true if either one of the parts is true. This might be used if there were multiple reasons why the guard might attack. (Perhaps guard would attack if the PC didn't have the ring, or if the PC was carrying the head of the mayor. Either one by itself is a reason for the guard to attack.)

A Confession

Right now, I have to admit that I've been lying to you all throughout the past three lessons. Multiple times, I've said that I use the methodology I do is because I am doing everything the way I would inside a real module. But now I have to come clean —€“ not a single script I have done here is really the way I would put it in final form.

The problem is, that our NPCs so far have been totally unresponsive to most stimuli. The guard we finished up top is starting to get there, but I would hardly call its behavior realistic. If you're interested, here is an experiment you could do: beef up the HP and level of the guard. Start up the module. Get the ring, so the guard will be friendly to you. Then go attack the guard. It will just stand there, and let you beat on it. This is definitely not what we want from most of our NPCs.

The reason that we have been creating such morons is that we threw away all of BioWare's hard work in writing scripts. The default scripts that we have been deleting define a number of useful "standard" behaviors that we really probably want to keep in almost every instance. (Actually, one of the NPCs we'll be creating today we will want to throw away the default scripts, but more on that when we get to it.)

So, how do we define our own behavior, without throwing away all the default stuff? We use the "user defined" script handle. If we are clever, we can really use every other handle —€“ we'll make only minor modifications to the OnSpawn script, and do most of our scripting in the OnUserDefined script.

We've spent so much time on our guard so far, let's go ahead and fix it up to the way it should be.

  1. Open up the Test Module
  2. Remove the guard
  3. Create a new NPC where the guard was (So we have all the default scripts back)
  4. Change the NPC tag to GUARD
  5. On the "Advanced" tab, make sure the guard has faction of "Commoner."
  6. Go to the scripts tab
  7. Edit the OnSpawn script. There is a lot of stuff here, we'll ignore most of it.
  8. Go down to the bottom of the script. Find the line:
    //SetSpawnInCondition(NW_FLAG_PERCIEVE_EVENT);         //OPTIONAL BEHAVIOR - Fire User Defined Event 1002
  9. On that line, remove the first //. (Keep the second, in front of OPTIONAL)
  10. Save the script as tm_guard_os
  11. Close the script window, and go to the OnUserDefined handle.
  12. Select the script tm_guard_op
  13. Open it up into the editor. Save it as tm_guard_ud
  14. Update the comments to reflect that it is in a new place, and has a new name.
  15. Save again.
  16. "OK" your way out of the guard, and save your module.

Now, if we start up the module, the guard will behave more realistically. He still does the "friend or foe" reaction that we scripted into him, but also reacts to other stimuli. If you attack him, he fights back. There are lots of other behaviors that are scripted in there, many of which happen "behind the scenes" that you will probably never notice.

So, what did we actually do? Well, when removing the // in the OnSpawn script, we "uncommented" something. Remember that anything after // on a line in a script is ignored. So what BioWare did was put in a bunch of "optional" stuff into the OnSpawn script, and put // in front of it so that it wouldn't happen. But now, we want some of it to happen. By removing the //, now we are saying that we want that line to actually take effect.

So, what does that line we "put back in" actually do? In essence, it is saying: "When you are running the OnPerception script, also do what I put into the OnUserDefined script."

Now, the more astute of you might be thinking ahead, and asking "What if I want to have multiple new behaviors from an NPC? What if I want to have it do something special on OnPerception and also on OnHeartbeat?"

It can still be done, but takes a bit of extra work. Just to keep the script small, let's make an NPC that does something simple. It will say "I'm bored" every six seconds, and bow when it sees a PC.

  1. Open the toolset, paint the NPC, and give it the tag BORED
  2. Open the OnSpawn script, and uncomment the lines for the OnHeartBeat and OnPerception lines.
  3. Notice that each of these has a "number" associated with it. 1002 for OnPerception, and 1001 for OnHeartBeat.
  4. Save it as tm_bored_os.
  5. Put the following script into OnUserDefined, and save as tm_bored_ud:
// On User Defined Script: tm_bored_ud
// Will be called by the OnHeartbeat and OnPerception scripts
// The NPC will complain about being bored every six seconds,
// and will bow if it sees a PC.
void main()
    switch (GetUserDefinedEventNumber())
        case 1001:  // Called by OnHeartbeat
            ActionSpeakString("I'm bored.");
        case 1002:  // Called by OnPerception
            object oSeen = GetLastPerceived();
            if (GetIsPC(oSeen) && GetLastPerceptionSeen())
        } break;

Question and Answer time...

What is this GetUserDefinedEventNumber()?
This is exactly the number I told you to pay attention to up there. BioWare was very clever... not only can each different handle call the user-defined script, but each one passes a different number to it when it does so. So, what I'm doing in that first line is checking which one of the scripts called this one. Was it the Heartbeat (1001) or the Perception (1002)?
What about this switch command?
I don't want to go a whole lot into detail on this one. Basically, you give it an input of an integer, and the script then "jumps" to the line marked with "case" and that number. So if our event number is 1001, the script looks down for the line "case 1001:". It starts doing commands at that point, until it gets to a break. A few notes on formatting: the switch command only looks within the lines tied to it with { and }. Also, like the if statement, there is no semicolon after the switch line. When I'm writing OnUserDefined events, I usually try to always set them up with a switch like this, even if I only plan on having one type... just because I might change my mind later. It is better to be prepared for a possibility, than have to monkey with a bunch of work because you were too lazy to plan ahead.
Hey, you didn't use { and } with your if statement!
If there is only one command attached to the if statement, the curly braces aren't needed. To me, sometimes it looks better with them, and sometimes without them. I go with whichever makes the script look cleaner. In this case, I decided that they would just clutter up the script.

One More Example

At this point, you know a good deal about scripting. I shouldn't be calling these Absolute Beginner lessons any more, because you aren't. There are still a lot of functions you need to learn, and a few more tricks. But really, there are lots of cool things that we can do with what we've learned so far, if we put the pieces together.

I'm going to do an example like that now. The script is pretty complicated, requires a lot of setup, and uses some new commands. But I think the end result is worth it.

  1. Open up the Test Module
  2. I want to get away from our guard, so go back to the first area that we created.
  3. Remove the SINGER NPC that is there
  4. Paint in a module start point
  5. Paint a commoner NPC in one corner of the room. (Doesn't really matter if it is a commoner, we're going to change pretty much everything.)
  6. On the "Basic" tab: Change the first name to Dartboard, the tag to DARTBRD, the race to "Construct", the appearance to Archery Target, the gender to none, and the portrait to po_PLC_F01_ (found by clicking "Placeable Objects and Doors")
  7. On the "Advanced" tab, check the "Plot" box.
  8. Still on the Advanced tab, go to the faction editor. Create a new faction "Target", whose parent faction is "Hostile." Set both Target-Commoner and Commoner-Target to 50.
  9. Changet the Dartboard faction to Target.
  10. On the "Scripts" tab, delete all the scripts. (In this case we don't want the dartboard going on a rampage and attacking people.)
  11. Edit the "OnDamaged" handle. Put in the following script:
// OnDamaged Script: tm_dartbrd_dm
// Dartboard script
// Goes "thunk" when hit with a ranged weapon.
void main()
    if (GetWeaponRanged(GetLastWeaponUsed(GetLastAttacker())))

If you want, you can test out just the dartboard by saving and loading up the module, but that isn't the cool part.

  1. Paint a waypoint about 1 square away from the dartboard.
  2. Give it the tag DARTWP001
  3. Paint a commoner NPC near the waypoint.
  4. Change the NPC tag to DARTPLAY
  5. For fun, click the "random name" button.
  6. Under the "Feats" tab, give it "Weapon Proficiency (simple)"
  7. Click the "Inventory" button, under the full body picture of the NPC
  8. On the right, go to "Weapons" > "Throwing" > "Dart" and drag it to the "Contents" spot.
  9. Right click the dart you just dragged over, edit the properties, and change the stack size to 3
  10. Click OK, then click and drag the dart up to equip it on the NPC.
  11. Click OK again, to get out of the inventory.
  12. Now, we go to the scripts tab.
  13. Edit the OnSpawn script, uncomment the HeartBeat line.
  14. There is another line to uncomment as well: SetSpawnInCondition (NW_FLAG_SET_WARNINGS);
  15. Also, add in a line somewhere: SetLocalInt(OBJECT_SELF, "DARTSTATE", 1);
  16. Save the script as tm_dartplay_os
  17. Now go to the OnUserDefined handle, and add in the following script:
// On User Defined Script
// tm_dartplay_ud
// Used to have someone play darts.  Called by the OnHeartBeat script.
//  The Dart Player will throw all darts in inventory (should start with 3),
//  walk to the dartboard, get 3 darts, walk back, and repeat.
void main()
    int nCalledBy = GetUserDefinedEventNumber();
    object oTarget = GetNearestObjectByTag("DARTBRD");
    int nDartsReady = GetLocalInt(OBJECT_SELF, "DARTSTATE");
        case 1001:  // Called by OnHeartbeat
            // nDartsReady will be 1 if ready to throw, 2 if not.
            if (GetIsObjectValid(GetItemInSlot(INVENTORY_SLOT_RIGHTHAND)) && nDartsReady == 1)
                // If we have darts in the right hand, and we're ready to throw, go for it.
                ActionAttack(oTarget, TRUE);
                // Otherwise, there are two cases.  We've either just run out,
                // or we are in the process of getting darts.  We don't want to
                // interrupt the cycle if we're already working on it.
                if (nDartsReady == 1)
                    SetLocalInt(OBJECT_SELF, "DARTSTATE", 2);
                    ActionPlayAnimation(ANIMATION_LOOPING_GET_MID, 1.0, 1.0);
                    ActionPlayAnimation(ANIMATION_LOOPING_GET_MID, 1.0, 1.0);
                    ActionPlayAnimation(ANIMATION_LOOPING_GET_MID, 1.0, 1.0);
                    object oDestination = GetNearestObjectByTag("DARTWP001");
                    CreateItemOnObject("nw_wthdt001", OBJECT_SELF, 3);
                    ActionDoCommand(SetLocalInt(OBJECT_SELF, "DARTSTATE", 1));
        } break;

Well, this is a really complicated script. I'm not going to explain absolutely everything about it, but I'll point out a few key things.

I'm using the local variable DARTSTATE to make sure that we don't attack when not ready, or go through the "get darts" sequence multiple times.

The ActionDoCommand() is amazingly handy. It takes a non-queued command, and turns it into a queued one. Normally, as soon as the script sees a SetLocalInt() instruction, it sets the local variable. This forces the script to wait until the NPC has completed all the previous actions.

The CreateItemOnObject() is what is used to make the new darts for the NPC. nw_wthdt001 is the blueprint ref for a dart, and the final 3 is the stack size.

Other than that, see if you can trace through the script yourself. There are other new commands, but most of the names are pretty self explanatory. As always, ask questions if you can't figure it out.


Previous Article Next Article
Part III: Conditionals Part V: Learning On Your Own, Non-NPC Scripts

 author: Celowin, editor: Charles Feduke, additional contributor(s): Jenn Jimerson, Sergio Paschoal