Celowin's Scripting Tutorial, Lesson
Five - Learning On Your Own
Introduction
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.
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.
(Note: those of us here at Stratics
also feel that our
Scripting Forum is a good place to ask
scripting questions, as well as #nwnscript
on our IRC server)
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.
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 NWScript, 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.)
One person posts, and the entire content of
the post is “How do u make a lever close a
door?”
Another person posts, and gives a detailed
explanation of what they are trying to
accomplish. They describe the tags they have
given to their door and to the lever. They
explain that they have tried attaching a
script to the “OnActivate” of the lever,
explain the local variables they have set,
and explain what they have attempted to do
in order to get the door to close.
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:
Inside your main Neverwinter Nights
directory, there is a subdirectory called
nwm. This is where the official campaign
modules are stored.
Copy the file or files you want to
look at from there into the modules
folder.
Rename the files in the modules folder
from .nwm to .mod (When you do this, you
won’t see the .mod in the final result.
That is, when you take Chapter1.nwm and
rename it to Chapter1.mod, you will only
see it show up as Chapter1)
Right click on the renamed file, and
go to “Properties.” Uncheck the “Read
Only” box, and then click ok.
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 doing 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:
NWScript:
// The subject will jump to lLocation instantly (even between areas).// If lLocation is invalid, nothing will happen.voidActionJumpToLocation(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
perseverance, 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 sometime 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.
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:
Open the toolset, load up the module,
and open the area you want to put your
portal (presumably the larger of the two
areas).
Put down two “Floor Levers” from the
“Placeable Objects”, “Containers and
Levers” menu
Give one the tag LEVER1, the other
LEVER2
Where you want the portal to appear,
put a waypoint. Tag it TM_INWP
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
Attach this script to the OnUsed
handle for each lever (same script for
both levers). Save it as tm_lever_ou
NWScript:
// 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 == 1)// If the lever is set to on, set it to off.SetLocalInt(OBJECT_SELF, "LEVER_STATE", 0);
else// If the lever is set to off, set it to onSetLocalInt(OBJECT_SELF, "LEVER_STATE", 1);
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.
Go now to the trigger that you
painted.
Open up the properties, and go to the
scripts tab there.
Put this script into the “OnEnter”
tab. Save it as tm_portal_en
NWScript:
// 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)));
}
Almost done. Ok out of the trigger.
Now, switch to the other area, where
you want to be teleported to.
Paint down a waypoint, and call it
TM_OUTWP
Save everything, and go out of the
editor.
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. 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
“blueprintresrefs”.
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”, a
plan for making the thing. That is what the
“plc_portal” is… it is the blueprint
reference for the portal object. (As a note,
to find that blueprint reference, I just
temporarily painted a portal somewhere. I
then looked under the properties of that
portal to find the blueprint.)
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.