scripting for noobs 050310

Post on 07-Apr-2015

167 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Scripting For NoobsVersion Date: May 03, 2010

This guide is meant for the total scripting noob. Someone who does not know a single thing about creating ascript. Hell, it’s for the person who doesn’t even know where the Script Editor is in the toolset. If that person isyou, you’re in the right place. However, this tutorial may be useful still to those who already know some of thebasics. 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 doknow is mostly self-taught in the toolset and through the help on the Bioware forums from those who are thegurus (thank you all). I’ll probably make a lot of mistakes in what I call this and that, but this guide is not reallyabout learning all the technical stuff, more it’s me trying to teach you how to teach yourself to learn NWNscripting (based solely off my own scripting ventures). I’m not going to discuss the intricate details of how todo a lot of stuff with scripting, there’s plenty of other great guides already available for that – but many of themare written in such a way the author assumes you know at least something about scripting (I know, I tried abunch of them), even the “basic” ones. Hopefully this will help you to make some sense of those other excellentguides.

Alright then, first things first – if you haven’t downloaded it already, go get yourself a most wonderful programcalled “Lilac Soul’s NWN Script Generator” (LSSG) which is available here. This program kicks ass, input abunch of things, and it will output a completed script for you. It is officially meant for NWN1, but in only 1case so far (out of countless hundreds) have I had it return a script that did not work just fine in NWN2. Ifyou’re lazy, and only will use basic scripts, then this is it, you’re done here. LSSG will produce pretty muchanything basic you’ll need. All you need to do is copy/paste. Go play around with it if you want, I’ll come backto 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 “HideScript Assist”). This will open a window (along the right side of the screen by default) that has a whole bunchof 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, notadd to them or set them equal to something. So if we click on one, “DestroyObject” for example, and look at thebottom 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 informationprovided 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 noactual information). Others may return integers, floats, etc. (explained later on). As written the function will notwork, we need to enter the information into what are essentially place-holders in the example above. So, forwriting 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 afeedback 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 oneyou want. Constants are written in all upper-case letters such as “ABILITY_CONSTITUTION”. Clicking onone of these will again give a very minor description of it in the message box. In this case, it again tells youwhat sort of information it returns, most likely an integer, the name of the constant, followed by an equals signand a number. If you so desire, you can enter the number itself in the function in place of writing the constantout in words. While it is just as valid a method, I advise against using the numbers simply because its difficultto 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 scripttemplate 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 ifyou see an empty list box. If you double click on either a function or constant from the listing, it will insert thatfunction 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 getlisted. 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 scriptis 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 Script2. Where is this script called from?à When a PC enters something3. Choose No on next box4. Spawn in a creature5. (entered a resref of a creature “c_reddragon” no quotes), No visual effect, (entered At the Waypoint:

“spawn_here” no quotes), Talk to PC6. Ok and Exit7. Close

Here’s what I came up with:

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

//Put this script OnEntervoid 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 byLilac Soul's NWN Script Generator, v. 2.3For 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 witha /* symbol and ends with a */ symbol. This blocks out a section of code and tells the Script Editor that this isnot part of the script so ignore any typed characters between these symbols. This is useful when you want toblock 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 itstarts with a // symbol. Similar to above it tells the Editor to ignore stuff, here it means anything typed on theline starting with //. No need for a closing symbol at the end of the line. This is useful for placing commentsthroughout your scripts at each section to explain what it does and remind yourself later. Comments are also ofgreat help for when somebody else reads your script. Again, lines that are blocked out will appear as green textin 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 “voidmain ()” 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 willcome later, here is an example of the first. This line tells the script what we want to reference; in this case, whatentered the trigger to fire the script. The word “object” tells the script we are looking for the thing that enteredthe 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 thatobject that enters our trigger for later reference. Here we have called it oPC. So, we are looking for the objectthat we are calling oPC and we want it to reference the thing that entered our trigger GetEnteringObject();. Wedon’t have to use oPC, it could be anything (even if it is meant to be a PC entering the trigger), and we couldcall it oEntering, oObject, or oGetMeAnotherBeer. You don’t even need the little “o” in front. People typicallydo 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 theword “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 orsome other NPC/Creature. We only want the script to fire if it is an actual PC that enters. So the basic structurehere 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 wehave 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 endtells the script that if our test returns as true (it is true the entering object is not a PC) then end the script rightthen and there. Nothing beyond that point in the script will be read by the game. We could write the same lineas (GetIsPC(oPC)) and forget the return; part. In this case the script will check to see if the entering object is aPC, 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 moreon 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 wehave 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 lookingfor 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 tagby 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 thisline.

lTarget = GetLocation(oTarget);

Here we’ve declared what lTarget should reference. We want to GetLocation (a function from Script Assist) ofsomething, we want to know where it is located in the module area. Its target is oTarget. We are looking for thelocation 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 fingersfor 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 tospawn in a creature (which falls under the “object” category remember), so we want to use CreateObject, but weneed to say both what we want to spawn in and were to spawn it (which are this functions parameters, look it upin 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 lastlywhere 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, itcould 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, evenback 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 withwho (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 infact 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 asingle functional one. The thing to remember here though: The scripts we want to combine should be relevantwith each other. Also remember that they will get fired from a single triggering event, so if you don’t wantsomething to happen at that time, don’t combine it with something you do want to happen. I suppose that maybe obvious, but it needed stating anyway. Lastly remember that each subsection needs to be started and endedwith 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 examplethey will be fired from the same event (an object entering something) it is a good candidate for combining. Wecan just take the differing line from the second script and insert it in the first. The trick here, and the point I’mlooking 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, ourcombined 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 eachother; 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 combinescripts 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 (dowhatever 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 followingpseudo-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 todo part). If the some condition is not true, then the subscript associated with it will not fire and the game willbypass the subscript and move on to the next section of code (if there is more, if not the script simply won’t doanything - 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 througheach 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 returnsas 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 theothers 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 setof 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 insertinga couple comment lines starting with the // symbol):

void main(){object oPC = GetEnteringObject();// If the Entering Object is not a PCif (!GetIsPC(oPC)) { FloatingTextStringOnCreature("These words will float over the NPC's head", oPC); }// Else if the Entering Object is a PCelse 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 enteringobject’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 theirsubscripts), they result in different things, though both refer to the entering object. The first will only fire if theentering object is a NPC (i.e. not a PC) and the floating words will appear over the entering NPCs head. Thesecond 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 conditionalreturned true, it would execute that subscript. The advantage here was that I only had a single script that wouldcontrol 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 ofcode. Placing comments throughout the script helps me to keep the different module areas separated in thescript.

What if you want to have a conditional that says “If condition 1 and condition 2” return true? This would bewritten 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 conditionsreturn 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, thesubscript 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 acomma or other designator)

· Strings – these are typed words such as “this” and “that” – strings must be enclosed in quote marks andcan 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 atthe 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 aswell as integer, float or string), what to set it on (i.e. where to store the information), and what to set thevariable’s value to. Similar to things explained above, we have the luxury of calling the variable whatever wewant 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 referencesomething 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 alocal integer stored on the PC named “test1”. Note that the words “int”, “float” or “string” at the beginning mustbe 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 PCif (!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 PCelse 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. Justremember 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 successin retrieving a local variable off of a PC that was set in a different module. These variables will be saved in theircurrent 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 havenot figured out how to do that. They can be retrieved from any module that is associated with the campaign. Avery important difference here from the other two types is that their state is not saved with the game. Thismeans that if a saved game is reloaded, the campaign variables will not be reverted to their settings when thegame was saved. Instead they will remain at whatever settings they last had. So, if the player saved their gameand then continued playing and did something that altered a campaign variable, if they later reloaded from theirsaved 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 speaka little bit about some slightly more advanced things you can do with them. I’m sure a knowledgeable scripterwould 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. Tobegin 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 mathpossible 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 fromour 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 floatfTest2 to an integer using int nTest3 = FloatToInt(fTest2);. If we wanted, that same line could be written as intnTest3 = FloatToInt(GetLocalFloat(oPC, “test1”));. It’s just my personal taste to list the GetLocal* in its ownline before doing the math as I have in the example above. The other simple math functions are written in thefollowing way:

// Subtractionint nTest4 = nTest1 - nTest3;

// Multiplicationint nTest4 = nTest1 * nTest3;

// Divisionint 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 againsave that information back on oPC. So, if nTest1 originally equaled 2, and we added 3, the next time we getnTest1 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 decimalis 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 Intthe 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 tostring 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 astring, 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 weadd at the end will get placed right at the very end of the words inside the quote marks. Without that space theresult would be “The value of nTest1 =3”. This concept can be used to good effect when trying to debug yourscripted systems and making sure that the correct variable values are being returned. It can be used for otherstuff 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 walksthrough 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 thereBob how are you today?” Note again where I included spaces between the words and the quote marks. Withoutit 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 stringwould normally go. For example, look at the parameters for the function SetLocalInt(object oObject, stringsVarName, int nValue); If we wanted to create a dynamically named LocalInt on our PC named Bob, we coulddo 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”);}

Creating a Prefab/Template Script

Anybody who has written a conversation in the toolset should be aware of the various ga_* type script. Youknow, 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 defaultsettings). Hit the “Add” button along the top of that tab section. An empty drop down box will appear with abutton 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 buttonand two boxes will appear: “sDestination (string)” and “bIsPartyTransition (int).” If you enter something intothose 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 canbe used to repeatedly use the same script that does everything the same except to check for or set a differentnamed 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);}

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

Notice that, unlike any other script we have done so far in this tutorial, there is something listed in between theparenthesis 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 iswhat will how up when we hit the “Refresh button. However, we have to be sure to tell the script in that spotexactly what type of information goes in there (for example, “int” or “string” or “object”, etc.) We also need aspot somewhere (as appropriate) in the lines of code for that information to be inserted. When we put it in thelines 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 lineof code is appropriate to the parameters of the function you are using them in. Lastly, putting these things insidethe “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 toreference 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 scriptsthat determine if a given node in a conversation tree will be shown or not. The first thing to remember aboutthese is that conversations follow a “fall-through” effect. Similar to our “if/else if” type conditionals, it willcheck 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 orFALSE 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 themwill 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 withint StartingConditional() followed, as usual, by our opening curly bracket. This tells the game that we are tocheck 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 casewe 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 nottrue, 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 valuewhen 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 thescript.” 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 targetobject is not a PC, then return a value of FALSE.” Confused? Well, the logic doesn’t really translate well intowritten or spoken words.

Starting conditionals can be more involved just like any other script. You can declare objects, get variables ordo pretty much anything else. However, it all boils down to the “if” conditional part of the script and wetherthat 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 fromsomething 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 functionthat does that, then call that function in our multiple scripts. Therefore we only have to write out the code forgetting that information a single time. In essence, all of the functions we use in scripting for NWN2 has what itactually does coded out somewhere else. For the most part though (for those functions that show up normally inScript Assist) we don’t need to use an include to use those functions. Speaking of Script Assist, those functionsthat are listed there in blue text are those that don’t need an include for their use. Those that do need an includewill 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 areworking on that we are using functions from the include so that it can then look there and see what the functiondoes. To do that we use the line #include “name_of_include_script_here”. This must be placed in our scriptbefore the “void main()” at the beginning of our script and the “#include” part will appear in blue text in theeditor. 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 thisintroductory tutorial, but if you have made it this far and understand the things I have been talking about, thenperhaps 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 inwhich 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 Assistwhere 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 callthat 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 hasthe 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 featint 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 theline so the script ignores it and doesn’t try to actually make it part of the function. Also, once we have ourinclude script added to the toolset, this line will appear in the description box in Script Assist just like for all theother functions. Though in order for it to show up there, you can not have an empty line between this one andthe 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 itwhatever we want, but of course that name is best served in some descriptive form. Since this custom functionchecks to see if oTarget has the Exotic Weapon Feat, I have appropriately made the function namedGetHasExoticWeaponFeat(). 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 seethat we have put object oTarget inside the parenthesis. Here we have set what the appropriate parameters are forthis function. This one will only accept an object as the target of the function which I have chosen to simplyname oTarget. Similar to when we looked at creating our own ga_* type prefab scripts before, whatever getsentered here is the information that will be passed on into the script at the appropriate spots. When you (orsomebody else) goes to actually use this function in another script, you do not need to use oTarget as the nameof your declared object. It can still be whatever you want to call it. It does need to be an object though or elseyou 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 ExoticWeapon 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 thecalling script that this entire function has a value of TRUE (it is true that the object passed to the function doeshave the Exotic Weapon Feat). Remember that something that causes a return to fire will end the script rightthen 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 WeaponFeat). This line can be looked at as an “else” statement, though here we do not need to actually use one. Thisline will tell the calling script that the entire function has returned as FALSE (since our conditional statementfailed 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 thecalling 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 includescript 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, createan 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 itemsvoid 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 thereflagged 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 foundobject 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 acharacter. For the sake of space I cut it short here and am only showing the first couple checks. This functionreturns 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 addedon to it. Note our return value is the object found. As the default (our so-called “else” statement) we have it setto 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 sortof 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: NOTALL CONTROL PATHS RETURN A VALUE”. Also, when you compile and save an include script, because itdoes not have a void main() in it (and is therefore not a “true” script) it will always compile correctly - unlessyou have some gross syntax errors. Its when you go to compile the calling script that you will get the errornotice. You can check for these errors by adding in a void main() { } at the very end of your custom functionsin 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 maynot 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 andinvolved. My “record” length so far is a single custom function that is over 4200 lines of code - no way I’d wantto rewrite that (or even copy/paste it) into the various other calling scripts I have! It is just so much easier (andquicker) to call the custom function itself from my other scripts. Lastly, it is possible to call one customfunction inside another custom function, as long as you either define the first custom function before the callingcustom 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 agood idea to makes a comments listing of those scripts that call your custom function (within/after yourcustom 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 thatuses the word “while.” This command causes the script to loop through objects (think of the term “loop” as verysimilar to the term “search”). What it loops/searches through depends on what target parameters we give thecommand. In the above example, we used the command to loop through the inventory of oTarget. This was justa 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 toloop through the objects for infinity (or more likely until your game crashes out in an error). So, to go back tothe 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 onwhat. We are searching through the inventory of oTarget and we are looking through the items in theirinventory. As we did way back towards the beginning of this tutorial, we have simply declared what oItem is toreference.

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 ithad 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 fromthere 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 tendto 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 thisas 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 conditionalstatements. Like normal, it will apply the conditional to the object/item property/ item/etc. that it finds. So, wecould 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 thenand 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 isneither 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 knownas “Switch/Case” commands. What this is usually used for is to have the script roll a set of virtual dice and dosomething 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 ifthe NPC is found to have either the Fighter or Paladin class. Remember back when I mentioned some “coolthings you can do with String type variables”? Well, here’s a prime example. As my Case Statements, I havesimply 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 theGetItemPossessedBy() 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. FinallyI 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 casestatement and execute what it says to do there. It would continue to do this until it either found a break; or ranout 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 automotivevehicle. If you don’t break, you will continue moving forward until you do hit the breaks - or until you run outof 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 isbecause 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 andsuch 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 reallymean? 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. Theseare 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 successmessage in the bottom message box. Note that just because your script compiles successfully does not mean itwill function as intended, only that your scripting syntax is correct. The compiler just gives notice of the veryfirst error it comes a crossed, what gets listed may not be the only error, or even the actual error itself in somecases. Remember to save your module as well. To change the name of your script, find it under the Scripts tabalong 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 yourscripts:

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 anopening {.

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 thesame 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 afunction (such as “GetIsPc()” - which should be “GetIsPC()”) or you have used a custom function and eitherforgot to do the #include “name_of_include_script” at the top of your script, or for some reason the includescript 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 callsa “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 simpletypo, 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 havesomething typed between the series. These types of series must be grouped together without any interveningcode lines.

ERROR: PARSING VARIABLE LIST - Again this could be a variety of things. Perhaps the most commoncause is forgetting a semi-colon at the end of the line previous to the one indicated in the message box. So, if itsays 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 (rightbracket) 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 inlayman'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 itwhen 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 scriptto be fired when that situation arises, not where you would actually set any variables (though of course thescript 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 sodesired - 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 Ihope you found at least some of this useful. If you have questions, comments, suggestions or found this tutorialhelpful and would like to leave a vote, please post them to this tutorial’s listing page on the NWVault.

- Knightmare

top related