You are on page 1of 26

Scripting For Noobs

Version Date: May 03, 2010

This guide is meant for the total scripting noob. Someone who does not know a single thing about creating a
script. Hell, it’s for the person who doesn’t even know where the Script Editor is in the toolset. If that person is
you, you’re in the right place. However, this tutorial may be useful still to those who already know some of the
basics. While written with NWN2 in mind, the majority of this tutorial is also valid for NWN1.

Now, I’m not a scripting guru by any means. In fact, I’m perhaps only an advanced noob myself. But, what I do
know is mostly self-taught in the toolset and through the help on the Bioware forums from those who are the
gurus (thank you all). I’ll probably make a lot of mistakes in what I call this and that, but this guide is not really
about learning all the technical stuff, more it’s me trying to teach you how to teach yourself to learn NWN
scripting (based solely off my own scripting ventures). I’m not going to discuss the intricate details of how to
do a lot of stuff with scripting, there’s plenty of other great guides already available for that – but many of them
are written in such a way the author assumes you know at least something about scripting (I know, I tried a
bunch of them), even the “basic” ones. Hopefully this will help you to make some sense of those other excellent
guides.

Alright then, first things first – if you haven’t downloaded it already, go get yourself a most wonderful program
called “Lilac Soul’s NWN Script Generator” (LSSG) which is available here. This program kicks ass, input a
bunch of things, and it will output a completed script for you. It is officially meant for NWN1, but in only 1
case so far (out of countless hundreds) have I had it return a script that did not work just fine in NWN2. If
you’re lazy, and only will use basic scripts, then this is it, you’re done here. LSSG will produce pretty much
anything basic you’ll need. All you need to do is copy/paste. Go play around with it if you want, I’ll come back
to this in a little while.

First things … second, find the Script Editor. From File, choose New then Script. It will open with a tab called
“Script 1” at the top. Below that is a toolbar with some options on it. The main part of the screen below the tool-
bar is where we do our actual scripting. At the bottom is a message window with a “Compile Results” tab and
“Notes” tab.

Script Assist

In the top toolbar, check out “Show Script Assist” (which may be pre-selected by default and instead say “Hide
Script Assist”). This will open a window (along the right side of the screen by default) that has a whole bunch
of things listed. There are three tabs near the top of this window: “Functions”, “Globals” and “Templates.”
When first opened the Functions tab is pre-selected by default. The things listed below that are the various func-
tions we use to write scripts. Click one and a description for it will show up in the bottom message window.
This is just good to know for reference. Note that you are meant to replace the wording in these descriptions, not
add to them or set them equal to something. So if we click on one, “DestroyObject” for example, and look at the
bottom message box we will be shown the following:

// Destroy oObject (irrevocably).


// This will not work on modules and areas.
// RWT-OEI 08/15/07 - If nDisplayFeedback is false, and the object being
// destroyed is an item from a player's inventory, the player will not
// be notified of the item being destroyed.
void DestroyObject(object oDestroy, float fDelay=0.0f, int nDisplayFeedback=TRUE);

What does all that mean though? The first handful of lines, all beginning with the // symbol, are information
provided to us that helps explain what the function does and what the various entries do, or what the valid appli-
cable entries can be (known as the functions “parameters”). The last line listed that begins with the word “void,”
is the actual function itself. The word “void” tells us that this function returns a void statement (returns no
actual information). Others may return integers, floats, etc. (explained later on). As written the function will not
work, we need to enter the information into what are essentially place-holders in the example above. So, for
writing the function into the script, we don’t write the word “void”, we tell it what “object” to refer to, what
“float” to use (in this case the float is a timer measured in seconds), and whether or not we want to display a
feedback message to the player. Our final line of script for this function would look something like this:

DestroyObject(oObject, 0.0f, TRUE);

The next tab towards the top of Script Assist is labeled “Globals.” These are what are known as Constants. Any-
where that a function calls for the use of a constant in its parameters you can look in this list and find the one
you want. Constants are written in all upper-case letters such as “ABILITY_CONSTITUTION”. Clicking on
one of these will again give a very minor description of it in the message box. In this case, it again tells you
what sort of information it returns, most likely an integer, the name of the constant, followed by an equals sign
and a number. If you so desire, you can enter the number itself in the function in place of writing the constant
out in words. While it is just as valid a method, I advise against using the numbers simply because its difficult
to remember exactly what they mean when you later go back to look at the script. Seeing
“ABILITY_CONSTITUTION” is a lot easier to understand than just seeing a “2” entered there. As a warning,
not all of the constants listed here are valid. Many are left over from the NWN1 game and are no longer valid
(quite annoying).

The third tab, “Templates,” is just what it sounds like. Click one of those listed there and a pre-written script
template will appear in your code window for you to edit/alter as you need.

It may take a few seconds for the functions and constants to actually load and get listed, so don’t freak out if
you see an empty list box. If you double click on either a function or constant from the listing, it will insert that
function or constant into your script exactly where your cursor currently is. Also there is a Filter box at the top,
type in something like “traps” and all the various functions or constants that have “trap” in their name will get
listed. Script Assist is your friend.

“Reading” A Script

Ok, in my opinion the most important thing to understand about scripting, before creating your own, is to under-
stand the make up of a script. To be able to “read” the script and at least be able to tell ourselves what the script
is doing at different points. Aside from the fact that LSSG produces scripts for you, is the simple fact that it cre-
ates scripts for you. This was the single-most best teacher I had in learning to read a script. Input something,
look at the script to see what my input did, input something else, look and see what it did in the script, etc.
Here’s an example script made from LSSG using the following steps:

1. Choose Type of Script à Normal Script


2. Where is this script called from? à When a PC enters something
3. Choose No on next box
4. Spawn in a creature
5. (entered a resref of a creature “c_reddragon” no quotes), No visual effect, (entered At the Waypoint:
“spawn_here” no quotes), Talk to PC
6. Ok and Exit
7. Close

Here’s what I came up with:


/* Script generated by
Lilac Soul's NWN Script Generator, v. 2.3
For download info, please visit:
http://nwvault.ign.com/View.php?view=Other.Detail&id=4683&id=625 */

//Put this script OnEnter


void main()
{
object oPC = GetEnteringObject();

if (!GetIsPC(oPC)) return;

object oTarget;
object oSpawn;
location lTarget;
oTarget = GetWaypointByTag("spawn_here");
lTarget = GetLocation(oTarget);
oSpawn = CreateObject(OBJECT_TYPE_CREATURE, "c_reddragon", lTarget);
oTarget = oSpawn;
AssignCommand(oTarget, ActionStartConversation(oPC, ""));
}

Now, what does this script say? Let’s take it a section at a time:

/* Script generated by
Lilac Soul's NWN Script Generator, v. 2.3
For download info, please visit:
http://nwvault.ign.com/View.php?view=Other.Detail&id=4683&id=625 */

This section is not necessary in a script; it just serves to give us some information. Note though that it starts with
a /* symbol and ends with a */ symbol. This blocks out a section of code and tells the Script Editor that this is
not part of the script so ignore any typed characters between these symbols. This is useful when you want to
block out a whole bunch of lines at a time. Lines that are blocked out will appear as green text in the script edi-
tor.

//Put this script OnEnter

Lilac Soul was kind enough to include this information for us that tells us where to place the script in the toolset,
in this case the OnEnter slot of something, usually a trigger but perhaps an area. Important here to note is that it
starts with a // symbol. Similar to above it tells the Editor to ignore stuff, here it means anything typed on the
line starting with //. No need for a closing symbol at the end of the line. This is useful for placing comments
throughout your scripts at each section to explain what it does and remind yourself later. Comments are also of
great help for when somebody else reads your script. Again, lines that are blocked out will appear as green text
in the script editor.

void main()
{

Now we’re getting to the first part that is actually the real script. All standard scripts start with the words “void
main ()” which is followed by a { sign. The words void main must be all lower case letters and while the word
“void” will appear in blue text, the word “main” will be in black text.
object oPC = GetEnteringObject();

There are 5 things I’m going to talk about: objects, locations, integers, floats and strings. While the others will
come later, here is an example of the first. This line tells the script what we want to reference; in this case, what
entered the trigger to fire the script. The word “object” tells the script we are looking for the thing that entered
the trigger. Pretty much if it’s a “physical” thing in the game or toolset (creature, PC, placeable, waypoint, door,
item, etc.) then the script would call that an object. As the creators of the script we have the option to name that
object that enters our trigger for later reference. Here we have called it oPC. So, we are looking for the object
that we are calling oPC and we want it to reference the thing that entered our trigger GetEnteringObject();. We
don’t have to use oPC, it could be anything (even if it is meant to be a PC entering the trigger), and we could
call it oEntering, oObject, or oGetMeAnotherBeer. You don’t even need the little “o” in front. People typically
do that so later on they can remember that “PC” is an object. Notice the semi-colon at the end of the line. Ex-
cept for lines that have void main () or only a { in them, then unless told different, it has to end with a semi-co-
lon. It tells the script that this is the end of that particular command. Note that the word “object” (and later the
word “location”) must be all lower case letters and will appear in blue text.

if (!GetIsPC(oPC)) return;

Here we come to a check or test. In this case, we want to know if the object that is entering the trigger is a PC or
some other NPC/Creature. We only want the script to fire if it is an actual PC that enters. So the basic structure
here says “If (something is true) then (do something).” Here we want to say “If (the entering object is not a PC)
then (do not run the rest of the script).” Note the exclamation point in this line, it means “not” or “isn’t.” So we
have if, then the parameters of our test enclosed in parenthesis (!GetIsPC (which is a function from Script As-
sist), then the target of our test (oPC)). Note that the closing parenthesis for the test parameters follows the tar-
get of our parameter. This is because the entire line (!GetIsPC(oPC)) is our actual test. The return; at the end
tells the script that if our test returns as true (it is true the entering object is not a PC) then end the script right
then and there. Nothing beyond that point in the script will be read by the game. We could write the same line
as (GetIsPC(oPC)) and forget the return; part. In this case the script will check to see if the entering object is a
PC, and if so, do whatever else we tell it. Note here though, that this is a case where we would not want the com-
mand to end as we want it to do our “whatever else” too, so we have no semi-colon at the end. I’ll speak more
on if statements later. The words “if” and “return” will be in blue text.

object oTarget;
object oSpawn;

Similar to object oPC above, we are telling the script that we are about to reference two more objects though we
have not yet set them equal to anything (defined what they are to reference). Again, we don’t have to use oTar-
get or oSpawn, they are just descriptive names.

location lTarget;

The second thing from my list above, here we are telling the script that we are about to reference an actual loca-
tion within the module inside an area. Similar to object oPC, we have declared that we are going to be looking
for a location that we have chosen to call lTarget. The word “location” will be in blue text.

oTarget = GetWaypointByTag ("spawn_here");

Now we’ve finally declared what oTarget should reference, we want it to reference a waypoint of a specific tag
by calling GetWaypointByTag (a function from Script Assist) and that waypoint has the tag ("spawn_here").
Note that since we previously told the script that oTarget was an object then we don’t need to do so again in this
line.
lTarget = GetLocation(oTarget);

Here we’ve declared what lTarget should reference. We want to GetLocation (a function from Script Assist) of
something, we want to know where it is located in the module area. Its target is oTarget. We are looking for the
location of the waypoint. This line could be written lTarget = GetLocation(GetWaypointByTag
("spawn_here")); - but see how much extra typing that is? By previously telling the script what oTarget refer-
enced, we can thereafter just reference oTarget and let the script figure out what we mean and save our fingers
for something more worth while. Same goes for oPC above and oSpawn next.

oSpawn = CreateObject(OBJECT_TYPE_CREATURE, "c_reddragon", lTarget);

Ok, now we’re finally getting to the meat of the script. While we’re still declaring what something is to refer-
ence, oSpawn, we’re actually telling the script to do the first thing the player in game will see. We want to
spawn in a creature (which falls under the “object” category remember), so we want to use CreateObject, but we
need to say both what we want to spawn in and were to spawn it (which are this functions parameters, look it up
in Script Assist for more info). We want to create a creature (and not a placeable, ect.) so we have
(OBJECT_TYPE_CREATURE, we want to spawn a red dragon so we tell it’s resref "c_reddragon", and lastly
where to spawn it lTarget); which of course is the location of the waypoint.

oTarget = oSpawn;

Here we’re telling the script that oTarget should now reference something else, we now want it to be oSpawn
(the resulting spawned in red dragon from our last line). Note again we did not have to use oTarget here, it
could have been anything. But, since we did reuse it, any time from here on out in the script, oTarget will refer-
ence the dragon, not the waypoint as it previously did (unless oTarget was again set to something else, even
back to the waypoint).

AssignCommand(oTarget, ActionStartConversation(oPC, ""));

Now that we have our dragon spawned, we want him to begin a conversation with the PC. So we AssignCom-
mand to our target (oTarget (which is now the dragon) and tell it what to do ActionStartConversation and with
who (oPC, and if we had a specific convo to be used it would be in the empty set of quote marks "")); the paren-
thesis here are for closing the opening ones earlier. The empty quotes in this case means to use the default con-
vo as listed on the creature’s blueprint.

This brings our script to a close. Every subsection of our script needs to start with { and end with }. In our exam-
ple above we only have a single sub-section.

Now, what does this script say? It says “If the object that enters this trigger (or causes this script to fire) is in
fact a PC, spawn in a red dragon at a specified location and have the dragon begin a conversation with the PC.”

Combining Scripts

In learning to write my own scripts, my next step was to simply attempt to combine two or more scripts into a
single functional one. The thing to remember here though: The scripts we want to combine should be relevant
with each other. Also remember that they will get fired from a single triggering event, so if you don’t want
something to happen at that time, don’t combine it with something you do want to happen. I suppose that may
be obvious, but it needed stating anyway. Lastly remember that each subsection needs to be started and ended
with curly brackets { }. Let’s take the following two example scripts (made from LSSG):
void main()
{
object oPC = GetEnteringObject();
if (!GetIsPC(oPC)) return;
FloatingTextStringOnCreature("These words will float over the PC's head", oPC);
}

And

void main()
{
object oPC = GetEnteringObject();
if (!GetIsPC(oPC)) return;
CreateItemOnObject("item resref", oPC);
}

The obvious thing here is that the first few lines are the same between the two scripts. Since for our example
they will be fired from the same event (an object entering something) it is a good candidate for combining. We
can just take the differing line from the second script and insert it in the first. The trick here, and the point I’m
looking to make, is that scripts are read by the game from the top down. So it makes a difference where you in-
sert the lines. Since we want the floating text to appear over the PC’s head before giving them the new item, our
combined script would look like this:

void main()
{
object oPC = GetEnteringObject();
if (!GetIsPC(oPC)) return;
FloatingTextStringOnCreature("These words will float over the PC's head", oPC);
CreateItemOnObject("item resref", oPC);
}

While it would be rather dumb to do so, the two scripts could be combined like this:

void main()
{
{
object oPC = GetEnteringObject();
if (!GetIsPC(oPC)) return;
FloatingTextStringOnCreature("These words will float over the NPC's head", oPC);
}

{
object oPC = GetEnteringObject();
if (!GetIsPC(oPC)) return;
CreateItemOnObject("item resref", OBJECT_SELF);
}
}

The point here is to notice how the two subscripts are enclosed in their own curly brackets, while both are them-
selves enclosed in another set of curly brackets. Also, the contents of each subscript are independent of each
other; barring something causing the script to stop continuing to fire (something causes a return to trigger).
Conditionals

I’m going to mention an overview on conditionals now, as it was the next level of me being able to combine
scripts together. A conditional is a test, also known as an if/then statement. The line if (!GetIsPC(oPC)) return;
that we have been using is an example of this. Basically we are saying “If (some condition is true) then (do
whatever we need it to do).” With the exception of making a check and deciding whether or not to end the script,
such as our if (!GetIsPC(oPC)) return; line, most conditionals have a different format as shown in the following
pseudo-code:

void main()
{
if (some condition is true)
{
do whatever we need it to do;
}
}

Note again the inclusion of curly brackets for our subscript and the lack of a semi-colon at the end of the if line
(since we want the script to execute the entire command which includes the subscript do whatever we need it to
do part). If the some condition is not true, then the subscript associated with it will not fire and the game will
bypass the subscript and move on to the next section of code (if there is more, if not the script simply won’t do
anything - which is what we want it to do since it should only do something in a certain condition). You could,
in theory, have an unlimited number of if conditional lines (and their subscripts). The game will go through
each and every one, for each one that returns as true, that subsection of the script will be fired.

We can expand on this by adding an “else if” conditional. This expands what we are saying to “If (some condi-
tion is true) then (do something). Else if (some other condition is true) then (do something else).”

void main()
{
if (some condition is true)
{
do something;
}
else if (some other condition is true)
{
do something else;
}
}

Again, you could have any number of “else if” conditionals in a single script. A major difference here from mul-
tiple “if” statements (as opposed to this which has one or more “else if” statements) is that as soon as a condi-
tion returns true (and fires the “if” or “else if” subscript), that will end that specific subscript. Further code,
including subsequent “if” and “else if” statements will not be fired off. Therefore if our some condition returns
as true, the game will not go on and bother to check out our some other condition.

We can further expand on this by the inclusion of an “else” statement. The “else” is usually the fallback state-
ment as it more or less says “If (some condition is true) then (do something). Else (none of the “if” or “else if”
condition are true) then (do something else).”
void main()
{
if (some condition is true)
{
do something;
}
else if (some other condition is true)
{
do something else;
}
else // none of the above conditions are true
{
do some third/fallback thing;
}
}

Remember the game reads the script from top to bottom, so place your “else” conditional at the end of all the
others to be tested.

We should take a second to study the layout of the curly brackets once again. Note that all three of the condition-
al test lines lies within the first/main set of brackets while the subscripts for each conditional has their own set
of brackets. The words “if”, “else if” and “else” must be all lower case letters.

Going back to our last true example script, we could alter it a bit and come up with the following (and inserting
a couple comment lines starting with the // symbol):

void main()
{
object oPC = GetEnteringObject();
// If the Entering Object is not a PC
if (!GetIsPC(oPC))
{
FloatingTextStringOnCreature("These words will float over the NPC's head", oPC);
}
// Else if the Entering Object is a PC
else if (GetIsPC(oPC))
{
CreateItemOnObject("item resref", oPC);
}
}

What does this script say? “If the Entering Object is not a PC, then place a floating text string over the entering
object’s head. If the Entering Object is a PC, give the PC an item (create an item in their inventory).” Remem-
ber the exclamation point means “not.” Also, even though I referenced oPC in both conditional tests (and their
subscripts), they result in different things, though both refer to the entering object. The first will only fire if the
entering object is a NPC (i.e. not a PC) and the floating words will appear over the entering NPCs head. The
second will only fire if the entering object is an actual PC and they will be given the item specified.

By using if/else if conditionals I put together a combined OnClientEnter script for my various module areas.
The conditional tests check to see the tag of the area the player had just entered, and when the right conditional
returned true, it would execute that subscript. The advantage here was that I only had a single script that would
control the OnClientEnter for all areas within my module, instead of making a different script for each area. In
the end it has dozens of if/else if statements (with their associated subscripts) and totals hundreds of lines of
code. Placing comments throughout the script helps me to keep the different module areas separated in the
script.

What if you want to have a conditional that says “If condition 1 and condition 2” return true? This would be
written as such:

if ((condition 1) && (condition 2))

Note that the if condition will only return as true (and therefore only fire its subscript) when both conditions
return true. If condition 1 returns false, the script will not bother to check condition 2.

But what if we wanted to say “If condition 1 or condition 2” returns true?

if ((condition 1) || (condition 2))

Here if condition 1 returns true the script will not bother to check condition 2. If either 1 or 2 returns true, the
subscript will fire off.

Variables

There are three different types of variables that can be used for scripting in NWN:
· Integers – whole numbers such as 1 or 4 or 17 or 346793, but can also be a TRUE or FALSE value – for
scripting, this is abbreviated and typed simply as int
· Floats – fractional numbers represented by a decimal point such as 1.23 or 0.9658 (for those who’s
country’s numbering system does not use decimal points, I believe you must still use them and not a
comma or other designator)
· Strings – these are typed words such as “this” and “that” – strings must be enclosed in quote marks and
can have spaces in them such as “this and that” - letter case does matter, so to a script “This” and “this”
are two completely different things.

Through the use of variables you can do some really amazing things, especially when coupled with a condition-
al statement. In order to really make use of them, we must first set a variable on something for it to be stored.
We can then retrieve that stored variable and do whatever we need with it. If you look through Script Assist at
the functions, you will find the various ways to set a variable such as (where * is either Integer, Float or String):
SetLocal*
SetGlobal*
SetCampaign*

Reading about the different functions by highlighting it in Script Assist and looking at the bottom message win-
dow, in order to use them we need to tell the script what kind of variable to set (local, global or campaign as
well as integer, float or string), what to set it on (i.e. where to store the information), and what to set the
variable’s value to. Similar to things explained above, we have the luxury of calling the variable whatever we
want for later reference. So, as an example, we want to set a local integer on the PC, name that integer “test1”
and set it’s value equal to 3:

void main ()
{
object oPC = GetEnteringObject();
if (!GetIsPC(oPC)) return;
SetLocalInt(oPC, “test1”, 3);
}
In a similar fashion, there are functions for retrieving the stored data such as:
GetLocal*
GetGlobal*
GetCampaign*

Our script to get the stored variable would be something like:

void main ()
{
object oPC = GetEnteringObject();
if (!GetIsPC(oPC)) return;

GetLocalInt(oPC, “test1”);
}

Similar to declaring objects and locations from before, we can tell a script that we want our variable to reference
something else, so along the same lines as object oPC = GetEnteringObject(); we could do the following:

int nTest2 = GetLocalInt(oPC, “test1”);


or;
float fTest2 = GetLocalFloat(oPC, “test1”);
or;
string sTest2 = GetLocalString(oPC, “test1”);

Again, we have an integer (or float or string) that we have chosen to call nTest2, and we want it to reference a
local integer stored on the PC named “test1”. Note that the words “int”, “float” or “string” at the beginning must
be lower case letters. If we add some of this into our earlier example scripts we could get the following:

void main()
{
object oPC = GetEnteringObject();

// If the Entering Object is not a PC


if (!GetIsPC(oPC))
{
// if the Local Integer stored on the NPC named “test1” is currently equal to 1
if (GetLocalInt(oPC, “test1”) == 1)
{
FloatingTextStringOnCreature("These words will float over the NPC's head", oPC);
}
}
// Else if the Entering Object is a PC
else if (GetIsPC(oPC))
{
// if the Local Float stored on the PC named “test1” is currently greater than 1.5
if (GetLocalFloat(oPC, “test1”) > 1.5)
{
CreateItemOnObject("item resref", oPC);
}
/* declare float fTest2 and then else if the Local Float stored on the PC named “test1” is
currently less than or equal to 10.5 */
float fTest2 = GetLocalFloat(oPC, “test1”);
else if (fTest2 <= 10.5)
{
GiveXPToCreature(oPC, 100);
}
// else if the Local String stored on the PC named “test1” does not equal “butthead”
else if (GetLocalString(oPC, “test1”) != “butthead”)
{
object oTarget;
object oSpawn;
location lTarget;
oTarget = GetWaypointByTag("spawn_here");
lTarget = GetLocation(oTarget);
oSpawn = CreateObject(OBJECT_TYPE_CREATURE, "c_reddragon", lTarget);
oTarget = oSpawn;
AssignCommand(oTarget, ActionStartConversation(oPC, ""));
}
}
}

Were you able to follow what was going on there? Notice that even though I named each of my variables “test1”
they are different things. As far as the script is concerned integer “test1” is not the same variable as float “test1”
and neither is the same as string “test1”. You can store a variable on pretty much any object, including the PC,
the module itself, a module area, the campaign (if you are using one) and objects in the player’s inventory. Just
remember to choose wisely on where you store them, not all things are permanent in the game, so setting a vari-
able on an NPC who dies and no longer exists can present problems when you later go to find that variable.

As a quick note on the differences between the types of variables:

- Local variables can be stored on and retrieved from most things that exist in the module and I have had success
in retrieving a local variable off of a PC that was set in a different module. These variables will be saved in their
current state with a saved game.
- Global variables are not set or stored on anything in particular, they simply exist and can be retrieved from any-
where within the same module. Again their state will be saved with the game.
- Campaign variables are saved in a similar fashion as Global variables but can also be applied to a specific play-
er in the campaign (and obviously require that you are using a campaign in the first place), but I personally have
not figured out how to do that. They can be retrieved from any module that is associated with the campaign. A
very important difference here from the other two types is that their state is not saved with the game. This
means that if a saved game is reloaded, the campaign variables will not be reverted to their settings when the
game was saved. Instead they will remain at whatever settings they last had. So, if the player saved their game
and then continued playing and did something that altered a campaign variable, if they later reloaded from their
saved game the campaign variable would remain in that altered state/setting.

Variables – More Advanced Concepts

While the above information about variables should be enough to get you started in their use I’m going to speak
a little bit about some slightly more advanced things you can do with them. I’m sure a knowledgeable scripter
would still look at this stuff as “basic” variable use, but it’s probably a step up for the beginning scripter.
The first thing I’ll talk about is using simple math functions (like addition and subtraction) and variables. To
begin it must be noted that you can not mix the different types of variables in a math equation. So, for example,
you can not add an integer to a float. However, it is possible to convert one to the other, thus making that math
possible to be done. To do this, we use the functions FloatToInt() (which of course converts a float to an inte-
ger) and IntToFloat(). Beyond that it is more or less simple math as learned in grade school. Using things from
our earlier script examples we could do:

void main ()
{
object oPC = GetEnteringObject();
int nTest1 = (GetLocalInt(oPC, “test1”);
float fTest2 = GetLocalFloat(oPC, “test1”);

int nTest3 = FloatToInt(fTest2);

int nTest4 = nTest1 + nTest3;


}

Notice that when it came to the actual addition I only added two different integers, having first converted float
fTest2 to an integer using int nTest3 = FloatToInt(fTest2);. If we wanted, that same line could be written as int
nTest3 = FloatToInt(GetLocalFloat(oPC, “test1”));. It’s just my personal taste to list the GetLocal* in its own
line before doing the math as I have in the example above. The other simple math functions are written in the
following way:

// Subtraction
int nTest4 = nTest1 - nTest3;

// Multiplication
int nTest4 = nTest1 * nTest3;

// Division
int nTest4 = nTest1 / nTest3;

Perhaps though, you just want to increase (or decrease, etc.) a variable by a specific amount. For example, per-
haps we want to increase the integer nTest1 by 3 (maybe as a part of a quest being completed or something).
We could do this by the following (incomplete code):

int nTest1;
nTest1 = GetLocalInt(oPC, "test1");
nTest1 += 3;
SetLocalInt(oPC, "test1", nTest1);

This will add +3 to nTest1 each time the code is run. It will then reset the variable to the new value and again
save that information back on oPC. So, if nTest1 originally equaled 2, and we added 3, the next time we get
nTest1 it will return a value of 5. If we wanted to decrease the value if nTest1, the snip of code would look es-
sentially the same except for the line nTest1 += 3; which would instead be written nTest1 -= 3;.

We could do the same thing with floats of course. But one thing to remember is that sometimes a fractional num-
ber written as a decimal number can stretch on to infinity. For example, the fraction 1/3 (one third) in a decimal
is 0.33333333333333333333 on to an infinite number of 3s. Point being, while adding 1/3 + 1/3+ 1/3 = 3/3 = 1,
the same is not true when we add them as decimals. Instead it would be 0.99999999999999 to infinite 9s. Also,
concerning Floats, note that because Ints do not have decimal points in them, when you convert a Float to an Int
the decimal places are dropped and only the whole number aspect is kept. The script does not follow the tradi-
tional system of “rounding off” so float 4.1 and float 4.9 would both be converted to integer 4.

Strings can be used to some cool effects. Among other things listed before, you can use them to create
“dynamic” variables. For instance, you can use them to dynamically display an integer or float in a FloatString.
Just like converting integers or floats to the other, we can use IntToString() or FloatToString() to convert to
string variables. As an example:

void main ()
{
object oPC = GetEnteringObject();
int nTest1 = (GetLocalInt(oPC, “test1”);
string sTest1 = IntToString(nTest1);
FloatingTextStringOnCreature("The value of nTest1 = " + sTest1, oPC);
}

This will detect the value of nTest1 (which for this example we will say returns a value of 3), convert it into a
string, and make a FloatingTextString over the entering object’s head that will say “The value of nTest1 = 3”.
Notice that I put an extra space in between the = sign and the closing quote mark. This is because whatever we
add at the end will get placed right at the very end of the words inside the quote marks. Without that space the
result would be “The value of nTest1 =3”. This concept can be used to good effect when trying to debug your
scripted systems and making sure that the correct variable values are being returned. It can be used for other
stuff as well to add a little bit of immersion. Perhaps our PC, named “Bob” walks up to an NPC who has the tag
“NPC_Joe” and we want the NPC to greet the PC by name using a FloatingTextString when the PC walks
through a trigger that Joe is standing in:

void main ()
{
object oPC = GetEnteringObject();
object oNPC = GetObjectByTag(“NPC_Joe”);
if (!GetIsPC(oPC)) return;
string sName = GetFirstName(oPC);
FloatingTextStringOnCreature("Why hello there " + sName + “ how are you today?”, oNPC);
}

So when the PC walks into the trigger NPC_Joe will have text float above his head that says “Why hello there
Bob how are you today?” Note again where I included spaces between the words and the quote marks. Without
it the resulting floating text would be “Why hello thereBobhow are you today?”.

We can also use a dynamically created string for any place in a function’s parameters where a typed string
would normally go. For example, look at the parameters for the function SetLocalInt(object oObject, string
sVarName, int nValue); If we wanted to create a dynamically named LocalInt on our PC named Bob, we could
do something like this:

void main ()
{
object oPC = GetEnteringObject();
if (!GetIsPC(oPC)) return;
string sName = GetFirstName(oPC);
SetLocalInt(oPC, sName + “1”, 12);
}
When Bob entered our trigger that had this script on it, a LocalInt would be set on the PC that is named “Bob1”
and the value would be set to 12. Likewise we could look for that variable in a similar fashion:

void main ()
{
object oPC = GetEnteringObject();
if (!GetIsPC(oPC)) return;
string sName = GetFirstName(oPC);
int nTest1 = GetLocalInt(oPC, sName + “1”);
}

TIP: When calling variables that were set in another script, use a comment to tell yourself in which other
script the variable was originally set. This may come in very useful when 3 months and 400 variables later
you go through and try to figure out where you set that damned thing in the first place.

Creating a Prefab/Template Script

Anybody who has written a conversation in the toolset should be aware of the various ga_* type script. You
know, the ones where you can hit the “Refresh” button and enter the things you want into the script? For any-
body who has no idea what I’m talking about, try creating a new conversation and write a single line of text.
Select that written line and then select the “Actions” tab at the bottom left corner of your screen (as per default
settings). Hit the “Add” button along the top of that tab section. An empty drop down box will appear with a
button that says “Refresh” next to it. Either type directly into the box, or select from the drop down list,
ga_area_transition. The script will appear in the tabs area below your drop down box. Hit the Refresh button
and two boxes will appear: “sDestination (string)” and “bIsPartyTransition (int).” If you enter something into
those boxes (which say “0” by default), whatever you add there will become inserted into the script at the cor-
rect place. This is useful for people who either do not know how to write their own scripts, or for repeatedly us-
ing a script that is the exact same except for those specific aspects we enter into the boxes. For example it can
be used to repeatedly use the same script that does everything the same except to check for or set a different
named variable each time.

So, looking at the ga_area_transition example, we see in the bottom script box:

// ga_area_transition
/*
Performs an area transition the same as per the standard area transition rules.

string sDestination - tag of the location to be transferred to.


int bIsPartyTranstion - determines whether single party transition is used.
*/
// ChazM 7/13/07

#include "ginc_param_const"
#include "ginc_transition"

void main(string sDestination, int bIsPartyTranstion)


{
object oPC = (GetPCSpeaker()==OBJECT_INVALID?OBJECT_SELF:GetPCSpeaker());
object oDestination = GetTarget(sDestination);
StandardAttemptAreaTransition(oPC, oDestination, bIsPartyTranstion);
}
Notice that, unlike any other script we have done so far in this tutorial, there is something listed in between the
parenthesis in the “void main” line. Also, lo-and-behold it is the two things that we found when we hit the
“Refresh” button. If we look a bit further into the lines of code, we will find where those entries are again repeat-
ed inside the script itself.

So, if you haven’t figured out the obvious, whatever we put inside the parenthesis on the “void main” line is
what will how up when we hit the “Refresh button. However, we have to be sure to tell the script in that spot
exactly what type of information goes in there (for example, “int” or “string” or “object”, etc.) We also need a
spot somewhere (as appropriate) in the lines of code for that information to be inserted. When we put it in the
lines of code we drop the information type (ie. “int” or “string” etc.) and just put in our chosen name itself (ie.
sDestination or bIsPartyTranstion). Make sure that the information type you are passing to that spot in the line
of code is appropriate to the parameters of the function you are using them in. Lastly, putting these things inside
the “void main” parenthesis serves the purpose of declaring the reference type as I described back near the be-
ginning of this tutorial. So that means we don’t need to declare “object oPC” further on inside the script, for ex-
ample, we can just use “oPC” in the lines of code since in the parenthesis we already declared that “oPC” is to
reference an “object.”

Conversation Starting Conditionals

While I’m on the subject of dealing with conversation scripts, I should take the time to talk about Starting Condi-
tionals and this section assumes you already know how to create a conversation in the toolset. These are scripts
that determine if a given node in a conversation tree will be shown or not. The first thing to remember about
these is that conversations follow a “fall-through” effect. Similar to our “if/else if” type conditionals, it will
check the conditional in the first given possible node. If the check returns true, that node will be shown. If it re-
turns false, the game will move on to the next conversation node in the tree. You can add a conditional to a con-
versation node by selecting the node itself, then clicking the “Conditions” tab (lower left corner by default).
Select “Add” and type in the name of the conditional script.

The next thing to know about these scripts is that they are all integer scripts. They either return a TRUE or
FALSE value. If TRUE, display the node. If FALSE, move on to the next one and check that. If there are multi-
ple choices as a response to a given node, any that do not have a Starting Conditional script attached to them
will always be displayed, unless prevented from doing so via some other means.

Third, these scripts look different than any other we have seen so far in this tutorial:

int StartingConditional()
{
object oPC = GetPCSpeaker();

if(!Condition To Be Checked) return FALSE;

return TRUE;
}

Like our other “normal” scripts, which always start with “void main(),” Starting Conditionals always start with
int StartingConditional() followed, as usual, by our opening curly bracket. This tells the game that we are to
check if a condition is true or false for the conversation.

Because this script is fired from a conversation, for the most part our target object, which in this example case
we have chosen to call oPC, will refer to the PCSpeaker. However, it doesn’t have to.
Next we have our condition to be checked. Notice how its written above. If the condition to be checked is not
true, then return a value of FALSE.

The final line gives the command to return a value of TRUE. It sounds weird to say, but this is the return value
when the “if” is not not false. To give a more layman’s example, lets go back to our old if(!GetIsPC(oTarget))
return; line from previous scripts. Remember, this line literally reads “If the target object is not a PC end the
script.” What I didn’t mention before was that it also gave the command to “return” nothing. Here with a start-
ing conditional we would be saying with if(!GetIsPC(oTarget)) return FALSE; “If it is not true that the target
object is not a PC, then return a value of FALSE.” Confused? Well, the logic doesn’t really translate well into
written or spoken words.

Starting conditionals can be more involved just like any other script. You can declare objects, get variables or
do pretty much anything else. However, it all boils down to the “if” conditional part of the script and wether
that statement is true or not.

These are similar to “Include Scripts” which is the next section of this tutorial. If you need some further exam-
ples of how return values work then press on to read through there.

Include Scripts

Time to talk about what are known as “Include Scripts.” What is an include script? Its just a script of sorts that
(usually) has custom made functions in it. There we can create our own functions for use in other scripts
(hereafter referred to as the “calling script”) instead of writing out the entire code within each individual script.
For instance, perhaps we had a need over and over throughout multiple scripts to get some information from
something about something else. The code in order to do that may be dozens of lines long. Instead of rewriting
(or even doing a copy/paste) over and over throughout our multiple scripts, we can just make a new function
that does that, then call that function in our multiple scripts. Therefore we only have to write out the code for
getting that information a single time. In essence, all of the functions we use in scripting for NWN2 has what it
actually does coded out somewhere else. For the most part though (for those functions that show up normally in
Script Assist) we don’t need to use an include to use those functions. Speaking of Script Assist, those functions
that are listed there in blue text are those that don’t need an include for their use. Those that do need an include
will be listed in black text.

In order to make use of those custom functions from the include script, we need to tell the calling script we are
working on that we are using functions from the include so that it can then look there and see what the function
does. To do that we use the line #include “name_of_include_script_here”. This must be placed in our script
before the “void main()” at the beginning of our script and the “#include” part will appear in blue text in the
editor. There are a number of include scripts that Obsidian made and ship with the stock game (and/or its expan-
sions). Most (perhaps all) done by Obsidian or BioWare will have the letters “inc” in the script’s name.

How do I make my own functions in a custom include you ask? Well, that may be beyond the scope of this
introductory tutorial, but if you have made it this far and understand the things I have been talking about, then
perhaps you have what it takes after all. For the most part they are done in the same fashion as any normal script.
There are a few distinct differences though:

- Include scripts do not have a “void main()” line in them. Having one will cause an error in the other scripts in
which you use the custom include functions.
- You have to tell the include script what sort of function this is you are creating. Remember in Script Assist
where you can look in the message box and the function description says something like “void” or “int” or
“float” at the beginning? That’s from the include file that defines what that function actually does.
- If you alter what a function does in the include file, then you must recompile and save all the scripts that call
that function. Failing to do so will have those other script call the previous version of your custom function.
Now, on to some insight into making your own functions. As an example I’ll use one that I made myself. Look-
ing at it, what this function does is check to see if the object oTarget passed into the parameters of the script has
the Exotic Weapon Feat. If they do, it will return as TRUE. If they do not it will return as FALSE.

// Returns TRUE if oTarget has the Exotic Weapon feat


int GetHasExoticWeaponFeat(object oTarget)
{
if(GetHasFeat(FEAT_WEAPON_PROFICIENCY_EXOTIC, oTarget, TRUE))
{
return TRUE;
}
return FALSE;
}

Similar to before I’ll take this a section at a time to explain what it is that I’m doing here.

// Returns TRUE if oTarget has the Exotic Weapon feat

This line is just me telling what the function does. Notice that as before, I used the // symbol to comment out the
line so the script ignores it and doesn’t try to actually make it part of the function. Also, once we have our
include script added to the toolset, this line will appear in the description box in Script Assist just like for all the
other functions. Though in order for it to show up there, you can not have an empty line between this one and
the function itself (the next line). You can place multiple lines of descriptive text like this one and (again,
providing there’s no skipped line between them) they will all show up in the Script Assist message box.

int GetHasExoticWeaponFeat(object oTarget)

This is the actual function that we are building. Like other things in scripting we have the luxury of naming it
whatever we want, but of course that name is best served in some descriptive form. Since this custom function
checks to see if oTarget has the Exotic Weapon Feat, I have appropriately made the function named
GetHasExoticWeaponFeat(). Note that the line starts with “int”. This means that this function will return infor-
mation back to the script it is called from in the form of an integer. In this case a TRUE/FALSE value. Also see
that we have put object oTarget inside the parenthesis. Here we have set what the appropriate parameters are for
this function. This one will only accept an object as the target of the function which I have chosen to simply
name oTarget. Similar to when we looked at creating our own ga_* type prefab scripts before, whatever gets
entered here is the information that will be passed on into the script at the appropriate spots. When you (or
somebody else) goes to actually use this function in another script, you do not need to use oTarget as the name
of your declared object. It can still be whatever you want to call it. It does need to be an object though or else
you will get an error.

{
if(GetHasFeat(FEAT_WEAPON_PROFICIENCY_EXOTIC, oTarget, TRUE))

As usual the { is my opening curly bracket just like we did for any other script. The next line, my “if” statement,
works the same way as well. The conditional tests to see if the object passed to it (oTarget) has the Exotic
Weapon feat.

{
return TRUE;
}
Again as normal for a conditional, this is the “do what we need it to do” section of the if statement, appropriate-
ly surrounded by curly brackets. Here, what we want it to do if our conditional is True, is to return a value to the
calling script that this entire function has a value of TRUE (it is true that the object passed to the function does
have the Exotic Weapon Feat). Remember that something that causes a return to fire will end the script right
then and there. Nothing further will be checked or executed.

return FALSE;
}

This is what happens if our conditional returns as False (the object passed to it does not have the Exotic Weapon
Feat). This line can be looked at as an “else” statement, though here we do not need to actually use one. This
line will tell the calling script that the entire function has returned as FALSE (since our conditional statement
failed to return it as TRUE). Lastly, the curly bracket just closes our opening one in the usual fashion.

So this function more or less says “If the object passed in the parameters has the Exotic Weapon Feat, tell the
calling script this is TRUE. Else, if the passed object does not have the feat, tell the calling script this is FALSE.”

Putting this new function into use is basically the same as any other, but we must remember to call the include
script at the beginning. Here’s an example of a script that uses our new custom function:

#include “name_of_include_script”

void main()
{
object oPC = GetEnteringObject();
if(!GetIsPC(oPC)) return;

if(GetHasExoticWeaponFeat(oPC))
{
CreateItemOnObject("item_resref", oPC);
}
}

Similar to other scripts we have written in this tutorial, this one says “If oPC has the Exotic Weapon Feat, create
an item in oPC’s inventory.”

Here’s a few of my other custom created functions as examples for you to see how some of the other informa-
tion types might be written. For the most part their separate sections work as described above:

// Sets all items in inventory of oTarget undroppable


// Does not include equiped items
void SetItemsNoDrop(object oTarget)
{
object oItem = GetFirstItemInInventory(oTarget);
while(oItem != OBJECT_INVALID)
{
SetDroppableFlag(oItem, FALSE);

oItem = GetNextItemInInventory(oTarget);
}
}
Unlike our first example, this one above does not return any information to the calling script, thus we have used
“void” as our little preamble. It will loop through the inventory of oTarget and set all the items it finds there
flagged as undroppable.

// Gets the first item on oTarget that has nSkill bonus


// Searches equiped slots first, then general inventory
// Ignores actual skill bonus so returns first item found
// with ANY bonus to specified skill
// nSkill = SKILL_*
// Returns OBJECT_INVALID if no item is found
object GetItemWithSkillBonus(object oTarget, int nSkill)
{
object oChest = GetItemInSlot(INVENTORY_SLOT_CHEST, oTarget);
if(IPGetItemHasProperty(oChest, ItemPropertySkillBonus(nSkill, 1), -1, FALSE))
{
return oChest;
}
object oArms = GetItemInSlot(INVENTORY_SLOT_ARMS, oTarget);
if(IPGetItemHasProperty(oArms, ItemPropertySkillBonus(nSkill, 1), -1, FALSE))
{
return oArms;
}
return OBJECT_INVALID;
}

While the actual custom function is much longer, it just goes on the check the other possible inventory slots on a
character. For the sake of space I cut it short here and am only showing the first couple checks. This function
returns an object in an equipped slot or in the inventory of oTarget that fits the parameters of what we are seek-
ing. Here we are looking for the first item found on the character that has a Skill Bonus magical ability added
on to it. Note our return value is the object found. As the default (our so-called “else” statement) we have it set
to return OBJECT_INVALID if no item is found in oTarget’s inventory that fits our parameters.

That leads me into my next comment on custom functions and include files. Any function that returns some sort
of information (so any non-void functions) must have a return value for every possible outcome of the function.
If you forget to add one in, then the calling script will throw an error at you along the lines of “ERROR: NOT
ALL CONTROL PATHS RETURN A VALUE”. Also, when you compile and save an include script, because it
does not have a void main() in it (and is therefore not a “true” script) it will always compile correctly - unless
you have some gross syntax errors. Its when you go to compile the calling script that you will get the error
notice. You can check for these errors by adding in a void main() { } at the very end of your custom functions
in the include script, just remember to comment it out before trying to compile any calling scripts.

To close this section of the tutorial it has to be said that yes, the examples given here are fairly short and may
not necessitate making a custom function for them. You could just do a similar check in the calling script itself.
However, they are just short for example’s sake. An include function could end up being quite long and
involved. My “record” length so far is a single custom function that is over 4200 lines of code - no way I’d want
to rewrite that (or even copy/paste it) into the various other calling scripts I have! It is just so much easier (and
quicker) to call the custom function itself from my other scripts. Lastly, it is possible to call one custom
function inside another custom function, as long as you either define the first custom function before the calling
custom function within the same include script, or use the #include method before the calling custom function.
TIP: Since you will need to recompile any calling script if you change what your custom function does, it is a
good idea to makes a comments listing of those scripts that call your custom function (within/after your
custom function itself). That way you are not searching for them. Also, note that custom functions are not
“true scripts” and therefore will not fire on there own, they need to be called from within another script.
Looping Through Objects (“while” command)

You may have noticed that in one of the examples in the Include Scripts section above there is a command that
uses the word “while.” This command causes the script to loop through objects (think of the term “loop” as very
similar to the term “search”). What it loops/searches through depends on what target parameters we give the
command. In the above example, we used the command to loop through the inventory of oTarget. This was just
a shortcut method of doing so, instead of coding in to search each inventory spot individually. The basic struc-
ture of a “while loop” is this:

oWhatever = The first in the type of thing we are searching;


while (some condition remains true)
{
look through whatever it is we are searching;
when we find what we are looking for, do something;

oWhatever = The next in the type of thing we are searching;


}

Important to note is that if our “some condition” is something that can never become False, it will continue to
loop through the objects for infinity (or more likely until your game crashes out in an error). So, to go back to
the previous example we had the following:

object oItem = GetFirstItemInInventory(oTarget);


while(oItem != OBJECT_INVALID)
{
SetDroppableFlag(oItem, FALSE);

oItem = GetNextItemInInventory(oTarget);
}

Our first line here: object oItem = GetFirstItemInInventory(oTarget); sets what it is we are searching for and on
what. We are searching through the inventory of oTarget and we are looking through the items in their
inventory. As we did way back towards the beginning of this tutorial, we have simply declared what oItem is to
reference.

The next line is our “while” command: while(oItem != OBJECT_INVALID). It tells the script that while it re-
mains True that oItem is a valid object (i.e. as long as it finds another item in the inventory beyond the one it
had just previously found) then continue searching. When the next item it is looking for is an invalid object
(because there is nothing left to find), then end the loop.

The line SetDroppableFlag(oItem, FALSE); is our command of what to do when it finds an object we are look-
ing for (the first/next found item in the inventory).

The final line: oItem = GetNextItemInInventory(oTarget); just declares that oItem is now to reference some-
thing else. It is to reference the next item in oTarget’s inventory and no longer reference the first item. As from
there on out we will always be looking for the next item beyond the first, we can leave it at that.

There are a variety of pre-built functions listed in Script Assist that allow you to loop through different things.
They all start with GetFirst* and GetNext*. Here’s some quick examples of the more common ones people tend
to loop through (part pseudo-code):
object oMember = GetFirstFactionMember(oPC, TRUE);
while(oMember != OBJECT_INVALID)
{
Do something when we find a faction member;
oMember = GetNextFactionMember(oPC);
}

This one written above loops through the PC faction and does something to each one we find.

itemproperty iProp = GetFirstItemProperty(oItem);


while(GetIsPropertyValid(iProp))
{
Do something when we find a valid item property;
iProp = GetNextItemProperty(oItem);
}

This loops through all the properties on an item and does something to each one found.

object oSomething = GetFirstObjectInArea(oArea);


while(oSomething != OBJECT_INVALID)
{
Do something when we find a valid something in the area;
oSomething = GetNextObjectInArea(oArea);
}

This loops through all the objects in an area and does whatever to each one. Be careful using something like this
as there can be hundreds or thousands of objects in a single area. It will search through everything in the area:
placeables, creatures, waypoints, triggers, etc. Anything that falls under the “object” category.

As always we can expand on this concept by adding in some of the stuff we learned before, such as conditional
statements. Like normal, it will apply the conditional to the object/item property/ item/etc. that it finds. So, we
could do something like this:

object oSomething = GetFirstObjectInArea(oArea);


while(oSomething != OBJECT_INVALID)
{
// Only look for objects that are creatures, if they are not, ignore and go on to the next object in the area
if(GetObjectType(oSomething) == OBJECT_TYPE_CREATURE)
{
// If we do find an object that is a creature check the found creatures tag
if(GetTag(oSomething) == “My_NPC”)
{
// If this found creature has the correct tag of “My_NPC” give that NPC an item in their inventory
CreateItemOnObject("item resref", oSomething);
// We have found what we are looking for and done what we wanted to do, so end the script
return;
}
}
oSomething = GetNextObjectInArea(oArea);
}

I’m sure by now you can see for yourself what is going on at each line of the script, so I won’t list it out again.
However, I do want to call attention to the “return;” line. Remember that “return;” will end a script right then
and there. Since we had found what we were searching for and done to the found object what we wanted to do,
the script can be ended. This is a second way that you can end a search loop. If the object found in the loop is
neither a creature nor has the correct tag, it will move on to the next object and run the same conditional tests.

Switch/Case Command

While I’m getting into the more advanced things you can do with scripts, I may as well explain what are known
as “Switch/Case” commands. What this is usually used for is to have the script roll a set of virtual dice and do
something based on that random result. Here’s the basic pseudo-code layout:

int nInteger;
nInteger = some declared die roll;
switch (nInteger)
{
case 1 : If the die roll = 1, do this; break;
case 2 : If the die roll = 2, do this; break;
case 3 : If the die roll = 3, do this; break;
case 4 : If the die roll ... Etc. ; break;
}

So, for a more “real world” example, here’s a snip from another of my personal scripts:

int nClassType = GetClassByPosition(1, oSelf);


if ((nClassType == CLASS_TYPE_FIGHTER) || (nClassType == CLASS_TYPE_PALADIN))
{
// Give random items
// Armor
int nArmor;
nArmor = d3(); // Roll a 3-sided die, only 1 of them - if we wanted more there would be a number in the ()
switch (nArmor)
{
case 1 : sItem = "nw_aarcl007"; sTag = "NW_AARCL007"; break; // Full Plate
case 2 : sItem = "nw_aarcl011"; sTag = "NW_AARCL011"; break; // Banded Mail
case 3 : sItem = "nw_aarcl006"; sTag = "NW_AARCL006"; break; // Half Plate
}
oItem = CreateItemOnObject(sItem, oSelf, 1, "", FALSE);
oItem = GetItemPossessedBy(oSelf, sTag);
SetIdentified(oItem, TRUE);
SetDroppableFlag(oItem, FALSE);
AssignCommand(oSelf, ActionEquipItem(oItem, INVENTORY_SLOT_CHEST));
}

This is part of a random equipping script I wrote. It looks for the class of a spawned NPC and gives them appro-
priate random items based upon that finding. In the case above, it randomly “decides” which armor to give if
the NPC is found to have either the Fighter or Paladin class. Remember back when I mentioned some “cool
things you can do with String type variables”? Well, here’s a prime example. As my Case Statements, I have
simply declared what both sItem and sTag should reference, and then created sItem (which is the items ResRef)
onto oSelf (which I previously defined in the script). Then to get that item and do other stuff to it I called the
GetItemPossessedBy() command, which uses a Tag parameter (thus sTag also being defined) and not a ResRef
(which sItem references). Once I have the item I then set it flagged as being identified and undroppable. Finally
I AssignCommand to oSelf to equip the new armor.
Important here to note is the term break; at the end of each line. This tells that script that this is where we “break”
(separate) one case statement from the next. Failing to put this in will have the script move on to the next case
statement and execute what it says to do there. It would continue to do this until it either found a break; or ran
out of case statements to execute. So, if you find that only your last case statement seems to be getting executed,
then you probably forgot your breaks. Think of this as similar to putting the brakes on in your automotive
vehicle. If you don’t break, you will continue moving forward until you do hit the breaks - or until you run out
of road to be on.

You don’t have to use the Case Statements to just declare something, it can be an entire script in and of itself.
Taking parts from earlier example scripts we had, we can do something like:

void main()
{
object oPC = GetEnteringObject();
object oArea = GetArea(oPC);
if (GetIsPC(oPC))
{
int nRandom;
nRandom = d4();
switch (nRandom)
{
case 1 : FloatingTextStringOnCreature("These words will float over the PC's head", oPC); break;
case 2: CreateItemOnObject("item resref", oPC); break;
case 3 : if (GetLocalString(oPC, "test1") != "butthead")
{
object oTarget;
object oSpawn;
location lTarget;
oTarget = GetWaypointByTag("spawn_here");
lTarget = GetLocation(oTarget);
oSpawn = CreateObject(OBJECT_TYPE_CREATURE, "c_reddragon", lTarget);
oTarget = oSpawn;
AssignCommand(oTarget, ActionStartConversation(oPC, ""));
}
break;
case 4 : object oSomething = GetFirstObjectInArea(oArea);
while(oSomething != OBJECT_INVALID)
{
// Only look for objects that are creatures
if(GetObjectType(oSomething) == OBJECT_TYPE_CREATURE)
{
// If we do find an object that is a creature
// Check the found creatures tag
if(GetTag(oSomething) == "My_NPC")
{
CreateItemOnObject("item resref", oSomething);
}
}
oSomething = GetNextObjectInArea(oArea);
}
break;
}
}
Did you happen to notice the lack of a opening and closing set of curly brackets on the case statements? This is
because the opening/closing curly brackets in the Switch/Case section itself will serve as the ones usually need-
ed for a sub-script. As you can see, things can get pretty complicated, so a good layout (as far as indents and
such are concerned) can be crucial to keeping things in order - and for maintaining your sanity.

Equivalency Tests

Throughout this tutorial I have been using symbols such as “=” and “==” and “!=” but what do they really
mean? A little explanation is due I am sure:

= (a single equals sign) means we are declaring something should equal something else, we are assigning some-
thing a value. Examples include:
int nTeast = 1; // The integer named “Test1” is being assigned a value of “1”
object oPC = GetEnteringObject(); // The object we called “oPC” is being assigned the value of the Entering
Object

== (a double equals sign) means that we are checking to see if the value of X is equal to the value of Y. These
are usually used in a conditional check, such as:
if(nTest1 == nTest2) // “If integer nTest1 is equal to integer nTest2”
if(nTest1 == 1) // “If integer nTest1 is equal to 1”

!= means we are testing to see if two values are not equal. Again, usually used in a conditional. Example:
if(nTest1 != nTest2) // “If integer nTest1 is not equal to integer nTest2”
if(nTest1 != 1) // “If integer nTest1 is not equal to 1”

> we are testing to see if one value is greater than another. Used in conditionals:
if(nTest1 > nTest2) // “If integer nTest1 is greater than integer nTest2”
if(nTest1 > 1) // “If integer nTest1 is greater than 1”

< we are testing to see if one value is less than another. Used in conditionals:
if(nTest1 < nTest2) // “If integer nTest1 is less than integer nTest2”
if(nTest1 < 1) // “If integer nTest1 is less than 1”

>= we are testing to see if one value is greater than or equal to another. Used in conditionals:
if(nTest1 >= nTest2) // “If integer nTest1 is greater than or equal to integer nTest2”
if(nTest1 >= 1) // “If integer nTest1 is greater than or equal to 1”

<= we are testing to see if one value is less than or equal to another. Used in conditionals:
if(nTest1 <= nTest2) // “If integer nTest1 is less than or equal to integer nTest2”
if(nTest1 <= 1) // “If integer nTest1 is less than or equal to 1”

Saving Your Script / Common Errors

Once you have your script written you will need to select the “Save & Compile (F7)” button along the top tool-
bar. The toolset will go through and verify the accuracy of your script and if correct, it will give you a success
message in the bottom message box. Note that just because your script compiles successfully does not mean it
will function as intended, only that your scripting syntax is correct. The compiler just gives notice of the very
first error it comes a crossed, what gets listed may not be the only error, or even the actual error itself in some
cases. Remember to save your module as well. To change the name of your script, find it under the Scripts tab
along the left side (by default), right click on it and choose Rename.
Here are some of the most common errors given in the bottom message box when trying to compile your
scripts:

ERROR: UNEXPECTED END COMPOUND STATEMENT - This means you have forgotten a closing }
somewhere.

ERROR: UNKNOWN STATE IN COMPILER - This could be a variety of things. One might be forgetting an
opening {.

ERROR: VARIABLE ALREADY USED WITHIN SCOPE - A “scope” is what I have been calling a
“subscript” throughout this tutorial. Here it means we have declared the same variable more than once within the
same subscript section. While its fine to do something like this:
int nInt = something;
nInt = something else;
You can’t do this:
int nInt = something;
int nInt = something else;
The difference being that in the second example both times I declared nInt was an int. You only declare some-
thing like this once. While its not necessary, you can re-declare a variable type in a separate scope/subscript.

ERROR: UNDEFINED IDENTIFIER - This is usually one of two problems. Either you have a typo in writing a
function (such as “GetIsPc()” - which should be “GetIsPC()”) or you have used a custom function and either
forgot to do the #include “name_of_include_script” at the top of your script, or for some reason the include
script does not exist (perhaps it was in a HAK file no longer associated with your module).

ERROR: “if” CONDITION CAN NOT BE FOLLOWED BY A NULL STATEMENT - You have put a semi-
colon at the end of your “if” conditional line. What the script calls a “null-statement” the rest of the world calls
a “semi-colon.”

ERROR: DECLARATION DOES NOT MATCH PARAMETERS - You have an error somewhere in the pa-
rameters you have set for a function ( the part inside the parenthesis of the function). Could be either a simple
typo, invalid entry or something that is required but not entered at all.

ERROR: “else” WITHOUT “if” STATEMENT - You have used a “if/else” series or “if/else if” series but have
something typed between the series. These types of series must be grouped together without any intervening
code lines.

ERROR: PARSING VARIABLE LIST - Again this could be a variety of things. Perhaps the most common
cause is forgetting a semi-colon at the end of the line previous to the one indicated in the message box. So, if it
says the error is on Line 5, the true error might be the missing semi-colon at the end of Line 4.

ERROR: VARIABLE DEFINED WITHOUT TYPE - You have used a variable such as “nInt” without previ-
ously declaring the nInt is an integer.

ERROR: NO RIGHT(LEFT) BRACKET ON EXPRESSION - You have forgotten a closing parenthesis (right
bracket) or an opening parenthesis (left bracket) in the line.

Where Do I Put My Script?

For the most part this is fairly straight forward with a small bit of thinking. First thing to do is ask yourself in
layman's terms "What type of event in the game should trigger this script?" Is it when the PC enters a trigger?
Then use OnEnter. Is it when they are seen by the nasty dragon? Then use OnPercieved (of the dragon). Is it
when they use a placeable? Then use OnUsed. Etc. This can be generally applied to any script as far as where
to place it. Note that things like OnEnter, OnUsed, OnPercieved, etc. is where you place the name of the script
to be fired when that situation arises, not where you would actually set any variables (though of course the
script listed there itself can set those variables). Under those script slots is a spot actually called Variables, click-
ing on that will open a new window and from there you can directly enter a variable and set its value if you so
desired - no script needed here. Those variables set in this fashion can be retrieved and altered in the usual meth-
ods as described above.

In Closing

In the immortal words of Forrest, Forrest Gump “That’s all I have to say about that.” Thanks for reading and I
hope you found at least some of this useful. If you have questions, comments, suggestions or found this tutorial
helpful and would like to leave a vote, please post them to this tutorial’s listing page on the NWVault.

- Knightmare

You might also like