You are here

NWScript Basics (Scripting Tutorial)

Author: 
Genisys / Guile
Old Vault Category: 
other

If you ever entertained the idea of becoming a scripter, but feared you couldn't do it, well let me assure you that NWScript is so easy that even a 12 year old could do it, I kid you not!  Now, they aren't going to be able to do it without some critical understanding of declarations, variables, and functions, but it shouldn't be too hard to explain it to them, therefore this tutorial will attempt to explain the basics in a most simplified fashion as possible.

Learning NWScript really boils down to solidifying your understanding of variables & defining things, as I explained in my Varaiables Preferred Tutorial, variables can be any name, you declare it's type by using int / float / string /or object before the name, e.g.   int nLevel  tells us that the name nLevel is an integer (meaning a whole number), or float VACANT_LUNACY tells us that the bizarre variable named VACANT_LUNACY is a decimal number.  You must learn to declare variables properly, but more importantly you should learn how to properly name them, to help other people reading your script understand what your varaible may represent, and for this reason coders use naming conventions in NWScript.

Let's look at a very basic gold reward script and analyze what each part means...

// Goes in the OnUsed Event of a placeable object (At the end of an adventure?)

void main() {

object oPC = GetLastUsedBy(); // When a Creature uses the object that holds this script in it's OnUsed Event

if(!GetIsPC(oPC)) { return; } // Stop here if it's NOT a PC using this object

int iUsed = GetLocalInt(oPC, "WAS_REWARDED_GP");

if(iUsed) { return; } // Stop here if the PC was given this reward before...

SetLocalInt(oPC, "WAS_REWARDED_GP", TRUE); // Set that the PC has recieved this reward!

int nXP = GetXP(oPC); // oPC's current total XP

float fReward = 0.05;  //   5%  (Scaled based upon level, because giving 10 Gold to a level 20 PC is just laugable!)

int nGiven = FloatToInt(nXP * fReward);  // Example 9,500 GP at level 20

string sMsg = "You were given " + IntToString(nGiven) + " GP as a reward!";  // Define the Message to be sent...

FloatingTextStringOnCreature(sMsg, oPC, FALSE);  // Send the reward message (Floaty Text)

GiveGoldToCreature(oPC, nGiven);  // Give the PC the gold now. ^.^

}

Every script that isn't an Include Script uses  " void main() {   }  " each { tells us that this is the code block of the function, in the case of void main () << The main function of the script, we know that we need and openting & closing curly bracket { } and all code that will execute within the script needs to be in between those brackets...  Every time we see a colon ;  this tells the script compiler (& you the coder) that we are at the end of a statement / declaration /or function.   I used to think scripting was hard; today I think it's very easy!  Notice that in the last sentence, we used a colon to declare there are two sentences in the setence, separated by a colon, and the ! of course would represent the } end bracket to let us know we have reached the end.

Scripts are something like a sentence / language, it has an obscure way of saying "This is this & that is that, but if this is that, then this is NOT this!" :D

object oPC = GetLastUsedBy();  this is a variable (object) that is declared (Named) as "oPC", which is defined (assigned data) by the function GetLastUsedBy(), which returns the last object to use the object that has this script in it's OnUsed Event...  Notice the conditional check, if(!GetIsPC(oPC)) { return; }, there is an exclamation point used before the function !GetIsPC(), like which means NOT, so if it's NOT a PC, then { ... do this... }  return; function stop the script cold / dead right there, preventing further execution, but because it's used brackets { return; }  Then it only stops the script if it's NOT a PC that is oPC...

int iUsed (Most of us either use n or i before the naming of an integer variable name) is an integer (whole number) variable named iUsed, which checking to see if the user (oPC) has a variable set on them to TRUE e.g.  if(iUsed)  tells us that if iUsed == TRUE then the PC indeed has used this device before... (This server reset at least)  return; is a function that stops the script, some lazy coders wouldn't have used the {  return; } brackets before & after return; but it's actually standard practice to do so after every declaration of a conditional check, which if / else if / else are, and whenever you see those, you know that the coder is indeed checking something, for example  if(oPC == OBJECT_INVALID) tells us the PC does NOT exist in the module, or the attempt to find them returned invalid object. this check is common in scripts, and as you read more script, you'll find a lot of conditional checks throughout them to ensure that scripts don't fail, or to control how the scrip works.

GetLocalInt(oPC, "WAS_REWARDED_GP"); is a common function used to retrieve a variable from an object e.g. GetLocal....(), in this case we are retrieving an Integer, which can be a number or a TRUE or FALSE constant, now for the sake of avoiding confusion, TRUE = yes  FALSE = no, though technically you could say TRUE = 1 & FALSE = 0, but mainly they are constant variables used in scripting to tell us if something is indeed true or false.  If you see something all capitalized, it's generally a constant, if you'd like to learn more about constants, then I'd suggest you read my tutorial Constants Usage & Scripting which I created for the community.  As explained above, if(iUsed) { return; }  is a conditional check to see if the local integer we retrieved by getting the data using the GetLocalInt() function that we assigned to the int iUsed;

I know this should be more simple and some people may be scratching their heads going huh?  However, coding is NOT mystical, obscure, or hard to understand, once you realize that we declare names of variables, assign them data, and use function to get data / store data / etc, and also check data to ensure it's what we are wanting to use or verifying the data is valid. e.g. if(iUsed), that of course is a check to see if the PC indeed has the variable set on them & it's set to TRUE;

SetLocalInt(oPC, "WAS_REWARDED_GP", TRUE);  is a function to set a variable named "WAS_REWARDED_GP" on the PC, TRUE being the integer, & this is a very commonly used function to store data upon an object, in this case oPC, but if we used OBJECT_SELF (The actual objec that has this script in it's OnUsed Event Placeholder) instead of oPC , then the script would only fire 1 time ever, instead of 1 time for every PC that used the device.  Simple change, but big different yes?  (OBJECT_SELF) is a constant object that represents the caller of the script, which is to say WHO is firing THIS script, you will often see this is in scripts that were executed by the function ExecuteScript() or in scripts that go in the placeholder of NPCs' Script Events Tab, also in conversation scripting to identify the Object /or/ NPC that the PC is talking to...

int nXP = GetXP(oPC);  is an integer named nXP that was given the data retrieved by the function GetXP(oObject), which looked at the PC's current XP Total and stored that data in the variable nXP, which will later use, and it is very wise to not just declare a variable, but assign it data right away, unless we plan to use that variable numerable times throughout the script.  Variables that are not declared inside of { } are available throughout the entire script, those inside void main() { ....here...  }  are only available inside the main script, and those inside conditional checks, like if(iUsed) {  int nCheck = 0; } would mean nCheck can only be used inside of the { } for that conditional statement's function...

Everything that has ( ) or { } is considering a function (to me anyway), I try to simplify things by thinking this way, and every function serves a purpose, even conditionals are functions to me, though not technically, they still serve a purpose, and often the purpose of each function is different, so learning what each function does & how it's used is very critical to actually learning NWScript. 

float fReward = 0.05;  floats can be an enigma to some, but these decimal numbers are NOT integers, because integers are whole numbers, which is why we need to convert floats to integers using FloatToInt() function, because when we start dividing or multiplying by floats, we may likely need to get the results in a whole number, as we cannot reward a PC 0.25 XP, we can only reward them whole numbers of XP.  Hence the variable  int nGiven = FloatToInt(nXP * fReward);   We converted nXP times fReward, which would still be a float, back to an integer, and then assigned the results of that function to nGiven...

Strings can be a bit more tricky, which is why I used the + to connect multiple strings together, so let's review now the variable string sMsg = "You were given " + IntToString(nGiven) + " GP as a reward!";  every "" sets everything in between the "String"  as a string, when oyu see the + sign it means we are connecting multiple strings together, e.g. "This" + " & That!" = This & That!  Pretty simple, truly, so don't get confused, and IntToString(nGiven) converted the integer nGiven to a string which would be "9500" in this particular case (if the PC is level 20 that is).

FloatingTextStringOnCreature(sMsg, oPC, FALSE);  is a simple function that sends a floating text message over the PC's head, sMsg is the string we defined above, oPC is always defined at the top of the script unless conditionals are used, e.g. if(GetIsDM(oPC)) { oPC = OBJECT_INVALID; } would tell us that DMs are invalid creatures to the script...  How does this relate to floating text?  It doesn't ,but it does apply to defining variables, which is what a large part of scripting involves, declaring & defining variables is SO CRITICAL!  The last part of the FloatingTextStringOnCreature(...,...,...) function determines if other PCs that are in the PC's party should see the message or not, in this case FALSE = NO, do not show other PCs the msg!

Finally we have the function GiveGoldToCreature(oPC, nGiven);  which basically gives gold to the target, in this case oPC, who we defined at the beginning of the script, and they are rewarded nGiven gold, if you remember earlier we defined nGiven as 5 % of the PC's current total XP...  Now I hope that this tutorial wasn't too confusing, but there are a few more things you need to be aware of, like commenting & naming conventions...

Whenever you see /*  or   */  everything between them is green, this is how we block large sections of text out, making it unread by the script compiler, also we can comment short parts of a line or a whole line by using //  Everthing after // is green (Unread / Unused by NWScript), these comments serve to help those reading the script understand what they are reading, I have used these thoroughly thoughout almost all of my scripting to ensure novice scripters could follow along, but you know nobody is perfect, as we all get tired of doing things redundantly, so don't be lazy, and remember to comment scripts well!

Naming conventions are a bit obscure, depending upon the coder's scripting you are looking at, some prefer to name all of their variables in ALL_CAPS_WITH_UNDERSCORES, this is OK, but it can be a bit hard to follow, as they dont' use n / i / f / s / or o before the name declaration, so we don't know what type of data, example oLUCKYONE would tell the person reading the script that LUCKYONE is indeed an object variable, you should remember that using these simple short identifiers at the beginning of variables help us read stuff better.  You will often see coding that uses a simple 1 letter varible, like  int i = 0;  this is common because i will be used A LOT, and they want to cut down coding time...

I've even been lazy enought to use a1 / a2 / a3 etc, because I had to declare & use a lot of variables redundantly, but this is very bad practice, though if the script is short and you are going to use a particular variable a few times, it's ok to use i / o / n / f / etc...  Let's look at a short code example and explain in depth again, this time using a complex "for loop".

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

int i = 0; string s = ""; float f = 0.0;

for(i=0;i<10;i++) {   s = IntToString(i) + " Minutes Have Passed!"; 

DelayCommand(f, SendMessageToPC(o,s));  f += 60.0; } }

 

Basically the coding in the script above is hard to read, because the person wanted to reduce time & didn't want to properly code the script using proper coding standards, so let's look at how the code should look, then properly outline what it does...

// Goes in the OnEnter event of an Area or TracksTrigger

void main() {

 object oPC = GetEnteringObject();

 if(!GetIsPC(oPC)) { return; }

 int nTimes = 0; string sMsg = ""; float fDelay = 0.0;

 for(nTimes = 0;nTimes<11;nTimes++)  {   // Loop  11 times (0 - 10)

 sMsg = IntToString(nTimes) + " Minutes Have Passed!";

 DelayCommand(fDelay, SendMessageToPC(oPC, sMsg));

 fDelay += 60.0;  }  // Add a 1 minute delay per loop

}

This script basically will let the PC know when each minute has passed once they enter the object (e.g. an area or tracks trigger), it could be used as a message system to notify the PC that they are running out of time, if they before hand knew they only had 10 minutes to complete a mission...  Each variable is properly declared, each function uses the { } including the conditional if(!GetIsPC(oPC)) { return ; }  Though the first script will work perfectly fine, it's just really hard to read, & lacks comments...

the For loop allows us to do something X times, in this case 0 - 10 (11 times), so the PC would get the message 0 minutes have passed when they first enter, then X Minutes have passed each minute that passes, once the script is fired, of course if they leave the area / trigger & re-enter, then of course they would get a new set of messages firing off telling them how much time has passed, and this of course would confuse the hell out of the PC, not to mention spam them to oblivion. :D

Notice the ++ sign after nTimes, which means each loop, let's make nTime + 1, so nTimes = 0 to start, and after the first loop it becomes 1 and then 2 after that loop, loops are a bit more of a complex subject, but you should learn what every symbol or function does individually, so take your time & go slow, but be sure to read the Lexicon to get ultra clarity on things, most functions were very well commented in NWN to let you know what they do, so if you double click on a function to the right to add it to your script, you will be able to look down and see what the function actually does in most cases.

DelayCommand() is used to make the function which is inside of the ( ) only happen after fTime has passed, fTime in this case fDelay (or f above) is a float decimal time in seconds, so 30.0 = 30 seconds, and if you notice fDelay += 60.0; we added (+=) 60.0 to the fDelay float variable, += is an adding function, unlike just +, which is simply adding two variables together, see string example above, += actually adds to the existing variable data, so if I said  int i = 0;  & then later use i ++; (+1 to current total) or i += 3; (add 3 to current total) or i-= 2 (Subtract 2 from the current total) or i  /= 3; (divide i current total by 3) or i-- (Subtract 1 from i) or i *= 2 (multiply i current total by 2)  I'm assigning a new value to the integer i after modifying.  These are all called "operators", you can learn a lot more about them here.

Now, this is a bit more complex, but it needs to be understood very clearly, for we will work with variables A LOT while in coding, we will need to assign, change, reassign, modify, multiply, divide, subtract from, or add to variables all the time, so make sure you learn what these functions do by testing them in simple scripts, and if you want to know what the best advice I can give you as a novice scripters, it's this, start small & test test test & test some more!  Don't worry if your script fails or the game locks up (press ctrl + alt + Delete or  Alt + F4 to end the game), simply go back and try to figure out what went wrong.  (Loops are generally the only thing that will lock up a module, so make sure you learn looping code WELL!)

Variable declaration (Where it's declared) Variable Naming Convention (How you name the variable), and Variable Usage are all very critical to learning coding, also functions, because every function uses variables, so learn how to  look at functions as a way to do something with data provided, e.g.  SendMessageToPC(object  oPC, string sMsg);  the function SendMessageToPC( Var1, Var2) is working with the variable object oPC, and requires you to define oPC & sMsg, for sMsg is a string that needs to be provided...  You must supply any varaibles the function requires, so learn that functions work with variables, and you must difine those variables before you use the function, and then properly use the function...

Every function has a purpose, as you read more scripts, you'll see their purpose, so take your time to read a lot of scripting, this will also increase your skills, if you ever want to be able to write 1,000 lines of code or much more, then you must first learn to read 1,000 lines of code and understand EVERY symbol, character, name, and function!

 

Thanks for reading, let me know if there was something that confused you or didn't help you, or you felt was an error...

 

Genisys / Guile

Migrate Wizard: 
First Release: 
  • up
    100%
  • down
    0%
Tonden Ockay

I haven't read this all over yet, I just seen it and I'm off to work. However its at the top of my must read list. 

I just want to say thanks a lot for making this " Genisys / Guile " I have really been wanting to learn how to work with and make my own scripts, but I don't know the first thing about scripting. So anything that could help a non scripter like my self to learn how to script then I'm 100% behind it.

 

Thanks a lot for this and I hope to see more like it in the future.

 

  • up
    100%
  • down
    0%
HorrorByGaslight

I did not even realize I could respond to this:

 

Thank you for the post! I can't wait to read over this in depth.

Please do more of these. Do you think you could cover the topic of include scripts?

  • up
    100%
  • down
    0%
Black Rider

Not for just this post, but it fits (kind of):

Genisys, I generally would like to thank you for all your releases and inputs you're pumping out atm! This is great stuff! Much to pick and learn of!

THANK YOU!

  • up
    100%
  • down
    0%
Genisys

Yes, I can do a topic on includes, not a problem. :D

 

Thanks for all the positive feedback guys. ^.^

(It encourages me.)

  • up
    100%
  • down
    0%