Celowin - Part V: Learning on Your Own, Non-NPC Scripts

Preamble


The purpose of this sequence of lessons is to take a complete beginner to programming, and teach him or her how to use NWNScript 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.


Other Resources


I've been getting a lot of requests for help on scripts lately. If I have time I'm glad to help point people in the right direction, but I don't always have the luxury of answering everyone that asks. So, I'm going to take a little bit of time to try and explain where else you can look to figure out what you need. Honestly, over 90% of the questions I see have been asked before, and it is only a matter of finding where the answers have been placed.


Really, there are four main places that you should look for answers, in no particular order:

Let's take a brief look at each of these, and how to use them. [There's also the NWN Lexicon, of course... but you know that already :) - ed.]


The Scripting FAQ


Well, they are called "Frequently Asked Questions" for a reason. The documents that have been compiled here answer many of the most confusing issues in scripting, and many easier issues that are common for people to want to know about. Really, if you have an issue on something, it is a good idea to look here first. There may not be something that specifically deals with what you need to know... but then, there might be. [ed. - most of this information will be reformatted and incorporated into the Lyceum]


Also, don't be afraid to look at a document in the FAQ that doesn't immediately seem applicable to your problem. Odds are, everything in there is something you'll want to do at one time or another. The more you learn about NWNScript, the more it starts to make sense. Often, while reading about how to do something totally unrelated to a problem I'm working on, I see something that can help me. It might be just a function that I wasn't aware of, or it might be a clever use of a variable that inspires me.


Because of the variety of different authors of the documents in the FAQ, some will be a bit beyond your current level of understanding. If so, fine... learn more, and come back to them later. You might be surprised at how much you can pick up on even now, though. Give it a try.


The NWN Scripting Forum


There is a constant stream of people asking questions, and there are many dedicated people answering questions. As I was saying, odds are that any question you are likely to have has been answered already.


How then to find the answer? Well, the search tool is probably a good place to start. It isn't perfectly reliable, but it can't hurt to try.


If I don't find what I'm looking for through the search, then I just start scanning the first few pages of posts. Since the same questions are repeated so frequently, there is actually a good chance that what you are looking for has been answered within the first three pages.


If neither of those works, then, and only then, make a post asking for help. A quick note: the more effort you put out to solve your problem on your own, the more effort people are going to put out to help you. Let me give an example. (Any similarities to actual posts made by people is intended in general, but not meant to make fun of any individuals.)

I can't speak for everyone, but I'm much more inclined to help out the second person. Even putting aside my bias against the use of "u" as a pronoun, I don't feel any particular sympathy for the first. Maybe that person has spent 15 hours trying to get it to work... but from what he has said, I don't know that. It comes across as "I'm not willing to put any effort into learning it, you do it for me." The second person, on the other hand, I can see is honestly giving it a try. It is easy to identify exactly where the problem is, and because of all the detail, I might even be able to write the code out for the person.


One final note about getting help from the forum: it is polite, though not strictly necessary, to give credit in your comments to the person who helped you fix your script. Just something like:


// On user defined script: tm_guard_ud
// Has a guard growl a warning when it sees a PC wielding a weapon.
//
// Last updated: 7/11/02
// Written by The Great Gatsby
// Some debugging help from Celowin of the NWN Scripting Forum

People who just play through your module will likely never see such credit, but those people that pick through your code will. If you don't know a person's real name, the user name you know them by is fine. It is a good idea, though, to put in a blurb as I did above about where that user name comes from.


The Official Campaign


Over and over again, I see questions like "How do I do (mumble) like they did in (mumble) part of the official campaign?" I have to hope that this type of question is from a lack of knowledge of how to look at the official campaign modules, rather than laziness.


So, here are the steps to making the campaign modules available for opening in the toolset:

  1. Inside your main Neverwinter Nights directory, there is a subdirectory called nwm. This is where the official campaign modules are stored.
  2. Copy the file or files you want to look at from there into the modules folder.
  3. Rename the files in the modules folder from .nwm to .mod (When you do this, you may see the '.mod' file extension disappear, depending on your Windows settings).
  4. Right click on the renamed file, and go to "Properties." Uncheck the "Read Only" box, and then click 'OK'.
  5. Now, whatever chapters you copied over can be opened in the toolset.

You can learn tons from seeing how BioWare did different things. Want to know how to have an NPC initiate a coversation? Look at Pavel from the Prelude. Want to know how to summon in creatures to attack the PCs? Look at Aribeth's conversation from the Prelude. Want to know how to have an NPC follow you until it sees a particular NPC, then head off? Look at the butler or maid from Chapter 1. And so on.... they pulled all sorts of tricks in the campaign, all of them are now available for your perusal.


I personally go to the prelude first if I'm trying to figure something out... for the simple reason that it is faster to load up into the toolset. Certainly, there are lots of tricks used throughout the campaign that were not used in the prelude... but there is a lot of useful information even in that first little introduction to the game.


Sometimes their code can be difficult to follow, particularly for complicated things. However, if you can't figure out how to modify their code, odds are it is a bit above your current level of scripting. Work on something else, and come back to it.


Which actually, brings to mind something I've been meaning to mention for awhile. Usually, when people are starting work on their modules, they right away want to jump to the "cool part" which unfortunately is almost always the most complicated script. You have to learn to walk before you can run, Grasshopper. Not every script in your module is going to be complicated, start out with something easy, even if it seems boring. Work your way up to the more difficult ones. I remember reading awhile ago about someone who wanted his first script to involve six levers, twelve gems, a portal with multiple destinations, and lots of other visual effects. What he was doing was certainly possible, and not even all that difficult... once you know what you're doing. For a first script, though, it would be a logical nightmare.


The Toolset

It may seem a bit strange to think of the toolset as a way to learn about scripting. Honestly, though, this is where I've learned most of what I know. Once you know the basics of scripting, you can pull a lot of information out of the documentation in the toolset, minimal as it may seem.


First off, notice when you are editing a script, there is a long list of functions over on the right side. If you click on any one of those commands, you get a bit of information about that command down at the bottom of the screen. Since most of the functions are pretty logically named, you often just scan the list until you find something that looks appropriate, and then look at it to see how it works.


Let's take an example from the script we're going to be looking at later in the lesson. I want to teleport a PC to a different location. I don't know off the top of my head what function to call to do that, so I start scanning through the list of functions.


We want to move the PC to a location, so ActionMoveToLocation sounds promising. However, we've used Move commands before, and it causes walking rather than a teleport. Right nearby in the list, though, is ActionJumpToLocation, and I have never seen my PCs playing any hopscotch in the game, so let's take a look at that. I click on it over on the right, and down below the following appears:

// The subject will jump to lLocation instantly (even between areas).
// If lLocation is invalid, nothing will happen.
void ActionJumpToLocation(location lLocation);

The first two lines are comments, and tell us a little bit about the function we're looking at. The first line tells us that it is pretty much what we want, the second tells us something special about the behavior. What is the third line, then?


Well, that third line is telling us important information about how to use that function in the code. This really goes back to Lesson 1... the void tells us that there is no "output" from this function – it does something in game, but doesn't give us an answer of any kind. Then comes the name of the function, we knew that already. Then, however, it tells us what kind of inputs we need to pass to the function – we need to give it a location.


We can then hunt further through the function list to find other things that might help us. First off, this is an "Action" command, and so would make the teleport part of the action queue. If we want it to happen immediately, we need to find an alternative... what about just JumpToLocation? What do you know, it is a command.


Next problem is how do we get a location to pass to the function? Most of the functions that return an answer start with "Get" so let's look at that section. Yep, there is a GetLocation command. Again, we can click on it and get information on how to use it.


Hmmmm... the JumpToLocation command doesn't take as an input what it is that you're trying to move. This one is a little bit tough to figure out, but I'll let you look for a bit. If you can't find it, it will be in the final scripts down below.


So, you see, with a little bit of perseverence, and maybe a bit of intuition (which comes with time) we can learn an awful lot about what commands to use. Also, if you're bored sometime, I recommend just looking through the list and clicking on random things that look interesting. Maybe you'll find something that you didn't know about that will make your life easier.


NWN Lexicon


[Iskander:] Chances are you're reading this from the Lexicon... need I say more?


Moving On


OK, enough about places to learn things, let's go ahead and make a script. Everything we've done in previous lessons has all dealt with NPC scripting, so let's try something different. Let's do some placeable scripting, and some trigger scripting.


In general, no matter what you're scripting for, it is pretty much the same. The "triggers" for calling the script vary, and you have to be careful with what OBJECT_SELF refers to, but the basic structure and commands are exactly the same.


In our test module, if you've been following along with what I've been doing, we have two areas that aren't connected in any way. We've just been painting our module start point in whatever area as needed. While that is a great testing tool, it is boring. We could make an area transition from one place to the other... boring, again. So, let's try something a bit more interesting.


How about this: In one of the areas, we'll make two levers, that start in the "off" position. When both levers are moved to the "on" position, we'll summon a portal which a PC can step through to get to the other place. OK, step by step, here we go:


  1. Open the toolset, load up the module, and open the area you want to put your portal (presumably the larger of the two areas).
  2. Put down two "Floor Levers" from the "Placeable Objects", "Containers and Levers" menu
  3. Give one the tag LEVER1, the other LEVER2
  4. Paint a waypoint where you want the portal to appear, and tag it TM_INWP.
  5. Paint a trigger around that waypoint (Click places for segments, after you have it pretty much enclosed, double click to close the polygon). Tag the Trigger PORTTRIG.
  6. Attach this script to the OnUsed handle for each lever (same script for both levers). Save it as tm_lever_ou.
    // OnUsed script: tm_lever_ou
    //
    // This script sets up levers named LEVER1 and LEVER2.
    // They both start deactivated. If both get turned on simultaneously,
    // a portal is summoned at the waypoint TM_INWP and the trigger
    // PORTTRIG is turned on.
    //
    // Written by Celowin
    // Last Modified: 7/12/02
    //
    void main()
    {
      int nUsed1 = GetLocalInt(OBJECT_SELF, "LEVER_STATE");
      int nUsed2;
      //
      // nUsed1 and nUsed2 are used to temporarily hold the states of the two levers.
      // 0 is off, 1 is on
      // LEVER_STATE is the permanent holding spot for the variables.
      // Each lever stores its own LEVER_STATE
      //
      if(nUsed1 == 0)
      {
        ActionPlayAnimation(ANIMATION_PLACEABLE_ACTIVATE);
        SetLocalInt(OBJECT_SELF, "LEVER_STATE", 1);
        nUsed1 = 1;
      }
      else
      {
        PlayAnimation(ANIMATION_PLACEABLE_DEACTIVATE);
        SetLocalInt(OBJECT_SELF, "LEVER_STATE", 0);
        nUsed1 = 0;
      }
      nUsed1 = GetLocalInt(GetObjectByTag("LEVER1"), "LEVER_STATE");
      nUsed2 = GetLocalInt(GetObjectByTag("LEVER2"), "LEVER_STATE");
      if ((nUsed1 == 1) && (nUsed2 == 1))  // Are both levers on?
      {  // If so, create the portal, and tell the trigger to get ready to port.
        object oPortalSpot = GetWaypointByTag("TM_INWP");
        CreateObject(OBJECT_TYPE_PLACEABLE,"plc_portal",GetLocation(oPortalSpot), TRUE);
        SetLocalInt(GetObjectByTag("PORTTRIG"), "READY", 1);
      }
    }
    

    There are things to discuss about this script, but for the most part I hope the comments make it clear. I'll hold off on explaining everything until the whole shebang has been set up.

  7. Go now to the trigger that you painted.
  8. Open up the properties, and go to the scripts tab there.
  9. Put this script into the "OnEnter" tab. Save it as tm_portal_en.
    // OnEnter script: tm_portal_en
    //
    // If the portal has been turned on, and a PC enters, warp that PC to
    // the waypoint TM_OUTWP
    //
    // Written by Celowin
    // Last Updated: 7/12/02
    //
    void main()
    {
      // Set up the temporary variables that we need.
      object oPC = GetEnteringObject();
      object oDest = GetWaypointByTag("TM_OUTWP");
      int nReady = GetLocalInt(OBJECT_SELF, "READY");
    
      // Check: Is it a PC and is the portal turned on?
      // If so, cause the PC to jump to the exit.
      if ((nReady == 1) && (GetIsPC(oPC)))
        AssignCommand(oPC, JumpToLocation(GetLocation(oDest)));
    }
    
  10. Almost done. 'OK' out of the trigger.
  11. Now, switch to the other area, where you want to be teleported to.
  12. Paint down a waypoint, and call it TM_OUTWP
  13. Save everything, and go out of the editor.
  14. Load it up, and test it out.

The portal script is really straightforward. The only thing worth mentioning is the "AssignCommand". This is another one of those things that once you find, you wonder how you ever got by without it. Basically, it is used to make something else do an action. In this case, we want the PC to do the JumpToLocation... so we assign the command to the PC. This comes in handy all over the place.


The other script isn't really that hard to parse through either. The key is just the storing of the local variables to keep track of "on" and "off". There are a few things that I think are worth mentioning, though. It is sort of bad form for me to use nUsed1 twice in the script. Once I use it as the state of the current lever, whichever one it is. Later, I used it to mean the state of LEVER1 in particular. Some programmers would whack me with a large stick and say that I should use different variables for these. Why didn't I? Well, just so I can raspberry the programmers that would try to tell me what to do. Seriously, the two uses are close enough to the same thing, and it is a simple enough script, that I felt there was no point in putting in another temporary variable. For a longer script, I probably would.


The other thing worth mentioning is the CreateObject call. For this, we need to understand the difference between "tags" and "blueprint ResRefs". Objects that are in the game have tags. You can find already created things through their tags. However, if something hasn't been made yet it can't have a tag. Imagine a physical nametag... you can't put a nametag on something that doesn't exist yet.


Instead, we have to use a "blueprint", the plan to make the thing, and we need to label the blueprint to identify it uniquely. That is what the "plc_portal" is... it is the blueprint 'resource reference' (ResRef) for the portal object. Note: to find that ResRef, I temporarily painted a portal object somewhere in my Area. I then looked under the properties of that portal to find the ResRef, and deleted the temporary portal. Don't try to look up a ResRef by right-clicking on in the palette and selecting 'Edit Copy'... you'll get the (new) unique ResRef of a copy of the original blueprint - not what you want. As a guide, if you're looking for the ResRef of a standard object it will never end with '001'.


Other than that, I hope things are pretty clear. I tried to comment pretty extensively, so you can follow what I did.


Further Exercises


OK, here is where my background as a teacher comes out.... homework time! I can't force you to do this, but I think it is worthwhile for those of you trying to learn this stuff to maybe take the time to extend what we've done.


So, try to do this: Right now, turning on both levers activates the portal. Fair enough. If we turn one lever on then off, then turn the other lever on, no portal. Still reasonable. However, if we activate both levers so the portal comes on, and then turn off one of the levers, the portal still stays up. This doesn't make as much sense. If you can, try to go through and fix it so that the portal turns off again when you turn off either one of the switches. Of course, it should then be possible to turn it back on again.


Another extension: Right now, our portal is "one way." We go in one place, and come out another. Try to make it two way.


I'd rate the first one as moderately challenging for someone that has followed the lessons so far, the second one as easy.


At any rate, good luck!





 author: Celowin, editor: Charles Feduke, additional contributor(s): Iskander Merriman, Mads Eibe Sørensen