Before we get into writing our own scripts, I want to cover good practices in scripting - or Etiquette. This is not only to help you, but also to help anyone that might be troubleshooting or simply looking at your script. Poorly written scripts can make it difficult to find problems.
Layout:
A script is nothing more than a single line of instructions. The compiler uses the semi-colon ";" as a "End this instruction" statement. Each instruction must end with a semi-colon. There are a few instructions called "conditionals" that do not end with a semi-colon, but that is much later in this tutorial so until then, remember that each instruction must end with a ";". Because of this, a script could be written like this;
void main(){int I = 10;PrintInterger(I);}
That isn't to bad to read, now imagine a script that has over 300 instructions all written on a single line! It would be a bit difficult to troubleshot. Usually, when scripting we use the Enter or Return key after each instruction. This makes the code look much cleaner and easier to read.
void main()
{
int I = 10;
PrintInterger(I);
}
Much nicer, is it not?
Tab is your friend:
Notice in the above and all my examples, I indent the code. This helps separate the different levels of scopes, and again, makes the code more legible.
Would you rather see this;
void main()
{
// Declare Variables
string sDB_Name = GetTag(OBJECT_SELF);
string sRes;
object oItem = GetFirstItemInInventory();
int iEntry, iMatch;
// Loop all items in inventory
while(GetIsObjectValid(oItem))
{
// Get the ResRef of the item and check for a match already
// sotred in the Database
sRes = GetResRef(oItem);
iMatch = CheckMatch(sRes,sDB_Name);
if(iMatch)
{
// If there is a match, simply increment the amount by one
SetCampaignInt(sDB_Name,sRes,iMatch+1);
DestroyObject(oItem);
}
else
{
// Else no match, Increment the Entry_# then record the
// ResRef in that Entry
iEntry++;
SetCampaignString(sDB_Name,"Entry_"+IntToString(iEntry)+"a",sRes);
// Record the amount for this ResRef, because it is new only set to 1
// Next item of this resref will show a match and be incremented
// above in the if(imatch) statement
SetCampaignInt(sDB_Name,sRes,1);
DestroyObject(oItem);
}
oItem = GetNextItemInInventory();
}
// Recored the number of entries for OnOpen reload.
SetCampaignInt(sDB_Name,"Entry_Count",iEntry);
}
Or would you rather look at this;
void main()
{
// Declare Variables
string sDB_Name = GetTag(OBJECT_SELF);
string sRes;
object oItem = GetFirstItemInInventory();
int iEntry, iMatch;
// Loop all items in inventory
while(GetIsObjectValid(oItem))
{
// Get the ResRef of the item and check for a match already
// sotred in the Database
sRes = GetResRef(oItem);
iMatch = CheckMatch(sRes,sDB_Name);
if(iMatch)
{
// If there is a match, simply increment the amount by one
SetCampaignInt(sDB_Name,sRes,iMatch+1);
DestroyObject(oItem);
}
else
{
// Else no match, Increment the Entry_# then record the
// ResRef in that Entry
iEntry++;
SetCampaignString(sDB_Name,"Entry_"+IntToString(iEntry)+"a",sRes);
// Record the amount for this ResRef, because it is new only set to 1
// Next item of this resref will show a match and be incremented
// above in the if(imatch) statement
SetCampaignInt(sDB_Name,sRes,1);
DestroyObject(oItem);
}
oItem = GetNextItemInInventory();
}
// Recored the number of entries for OnOpen reload.
SetCampaignInt(sDB_Name,"Entry_Count",iEntry);
}
The TAB key is on your keyboard, use it.
You may also have noticed that each curly bracket, or scope is on it's own line. This again helps in troubleshooting. If each set of {} are on their own line, it makes spotting forgotten brackets much easier. Some prefer to enter the opening scope bracket at the end of a line, like this;
while(GetIsObjectValid(oItem)) {
// Get the ResRef of the item and check for a match already
// sotred in the Database
sRes = GetResRef(oItem);
iMatch = CheckMatch(sRes,sDB_Name);
if(iMatch)
// If there is a match, simply increment the amount by one
SetCampaignInt(sDB_Name,sRes,iMatch+1);
DestroyObject(oItem);
}
else {
// Else no match, Increment the Entry_# then record the
// ResRef in that Entry
iEntry++;
SetCampaignString(sDB_Name,"Entry_"+IntToString(iEntry)+"a",sRes);
// Record the amount for this ResRef, because it is new only set to 1
// Next item of this resref will show a match and be incremented
// above in the if(imatch) statement
SetCampaignInt(sDB_Name,sRes,1);
DestroyObject(oItem);
}
}
Just quickly glancing over this script, can you be certain that none of the curly brackets were missed? Did you think "Yes" to that questions? if so, look again, one is missing. DO NOT DO THIS, it is simply annoying as hell to look at. In a actual programming class, the instructor would fail an entire project simply because of this (at least all the instructors I know). I have noticed a lot of Noobs doing this, basically because they saw it some place else so they follow suit thinking it is the proper way to do things. And guess what? when the noob post the script on the forums because it won't compile, it is usually because they missed a curly bracket. Also, when others try to help you troubleshoot your script we usually quickly scan over it looking for a type-o first. If the script is laid out as above, we must spend more time check to be sure each set of Curly brackets are there. Personally, I think it looks idiotic, no offence.
But how do I remember where all these curly brackets and parentheses go?: Simple, here is a little tip when starting a script (and nwn1 starts with this on a new script) Type the following first,
void main()
{
}
Then click back after the first "{" and enter your script.
When doing a if statement, start by typing this first;
if()
{
}
Then click in between the () and enter your conditions, then click between the {} and enter your results.
Prefix your variables:
You may have noticed that all my variable names have a lower case letter in front of the name. This lower case letter does nothing but remind me, or let anyone looking at my script know what type of variable it is. So iNum reminds me that the variable is a integer, oPC reminds me that PC is declared as a object, and so on. Here is a list of common prefix variable notation.
a for action
b for Boolean (TRUE or FALSE)
e for effect
f for float
i or n for int (Integer or number)
ip for itemproperty
l for location
o for object
s for string
v for vector
Use them, imagine looking at a 1000 line script and at the bottom you see a variable name of Player_ID. Will you remember what data type it was declared as? if your writing the script, probably so. But if someone else is looking at it, they will need to scroll back top and look threw the entire script until they find what type "Player_ID" is. On the other hand, if you see the variable name of "iPlayer_ID" we instantly know that the variable is declared as a integer.
Comment - Comment - Comment:
Use comments when writing a script. This not only helps you when viewing a script months later, but helps anyone else that may be looking at it. Use comments to leave your self notes as to what is going on within a code section. To make a comment, first enter two forward slash marks // followed by your comment text. This is know as a "Single Line Comment". As soon as you hit enter after the comment, your text is no longer a comment, but part of the script. Another way to leave a comment is to enter a single forward slash followed by a "*", like this /*. Enter yout text and you will notice that even after you press enter, the next line is also a comment. To end the comment block, enter a "*" followed by a forward slash, like this, */. Everything in between is a comment. This is known as a "Block Comment".
Comments are very helpful in troubleshooting a script - learn to use them.
Other Etiquette:
Declare variables as what they are instead of what you think they are, or what you want them to be. For example, in almost every OnEnter script you will see this,
object oPC = GetEnteringObject();
This is actually a false assumption (and I am guilty of this my self). Anything could enter the trigger and not necessary a Player Character. So to be "politically correct" it should actually be
object oEnteringObject = GetEnteringObject();
We may want the entering object to be a Player Character, but what actually enters, may not be. This simply reminds us to check that the entering object is in fact a PC. I have gotten into the habit of always using oPC simply because it is faster to type, but if we really want to use oPC, after we make sure that oEnteringObject is in fact a PC, then we can do this,
object oPC;
object oEnteringObject = GetEnteringObject();
if(GetIsPC(oEnterinfObject))
{
oPC = oEnteringObject;
//Rest of script here using oPC as the Player Character
}
oPC is probably the most abused variable name as it is used in almost every script event. It is often misunderstood by Noobs in thinking that naming the variable "oPC" it will be a player character. Just remember that it could be any object and not necessarily a player Character.
Unless there is reason to do so, declare all your variables at the top of the script, but inside the void main().
For example,
void main()
{
// Declare all variables here
}
At times you may see script that have the variables declared above the void main() statement. This will work, but in the case of declaring variables, there is absolutely no point to do so.