Robert Straughan - Capture Glory: Crowbar in a Well

Initial Area


For the first area, I decided to make a set of caves. You start out captive in one, and the exit is in the other. I placed the start point, and then a door to block the player's way.


Now, the first problem. I have locked the door. A thief could get past this with a reasonable roll, and a wizard with Knock could open it. A strong character could bash it down, though it might take some time. We need an easy way to open the door for those who can't do any of the above.



Pry the doors!


What about a crowbar? Okay, so where would the player find a crowbar in the cave? I've placed a well placeable in the cave, and I've checked the usable checkbox in its properties to allow the player to click on it.


I now need to tell the well how to give the crowbar to the player. I do this using the OnUsed handler, in which I place the following script:


//Script "cave_well"  
void main() 
{ 
     object oLast = GetLastUsedBy(); 
     int nDone = GetLocalInt(OBJECT_SELF, "DONE_ONCE"); 
     if (nDone == 0) 
     {
          SpeakString("You look down into the well, " +
          "and see some old rope still tied to " +
          "something dangling in the dark." );
          SetLocalInt(OBJECT_SELF, "DONE_ONCE", 1); 
     } 
     if (nDone == 1) 
     {
          SpeakString ("You pull the rope and haul " +
          "up what appears to be a crowbar." ); 
          CreateItemOnObject("crowbar", oLast);
          SetLocalInt(OBJECT_SELF, "DONE_ONCE", 2); 
     } 
}

Follow it through. I declare and initialise oLast using an object returner based on the handler being used. I then declare and initialise nDone using a function, defined as follows:


//Definition of GetLocalInt function
int GetLocalInt(object oObject, string sVarName)

This function returns a local integer variable, which is why nDone is an integer. OBJECT_SELF (ie. the well, since the script is firing from a handler on the well) is oObject, and "DONE_ONCE" is the sVarName (read the box for information on Local Variables).


Now, here's a new feature of scripting for you. Conditional statements. Think about the following: "I'm going to the shops tomorrow, unless its raining, in which case I'll watch television."


This is a statement. You are declaring what you will do. However, there is a conditional in there. If it is raining, you have stated that you will do something else. That's all there is to knowing about these in script. They do something if something is true, and might do something else if it isn't.


The script above has two conditional statements. They both look at nDone, and use a comparison operator (read the box) to determine whether or not they should do the lines of code within the curly brackets.


So, if nDone is equal to 0, then the top set of lines will get run, but the bottom set won't, since that conditional is not true. Also, if nDone is equal to 1, then the bottom set of lines will run, but the top set won't.


Remember that variables that are declared but not initialised are equal to zero. Also remember that any local variable that has not been set or changed will also default to zero. This means, the first time this script runs, nDone will be equal to zero, because it hasn't been set before now.


That means the first set of lines will run. Since the well is the caller of the script, I don't need to AssignCommand anything. Instead, I just tel it to SpeakString the given string value.


//Definition of SpeakString function
void SpeakString(string sStringToSpeak, 
     int nTalkVolume=TALKVOLUME_TALK)

Speaking a string is different to the floating text I used earlier. It will appear as though the object actually spoke the words. As you can see, I haven't declared the second parameter, since its already declared and I don't wish to change it.


//Definition of SetLocalInt function
void SetLocalInt(object oObject, string sVarName, int nValue)

Notice how I use this function in the first set of lines. The first two parameters are the same as those used when getting the local integer value for nDone. By doing this, if this script should run again, nDone will be equal to 1, preventing these lines from occuring again.


So the second time this script runs, it will do the bottom set of lines. Notice that the last line in that conditional statement is another SetLocalInt function. This one sets the value nDone becomes to 2, which means should this run again, nether conditional statement will occur.


There's yet another SpeakString function here. Notice in the functions list of the editor that there is an ActionSpeakString. I'm using this version because I'm dealing with a placeable, which can't have an action queue.


Now, CreateItemOnObject:


//Definition of CreateItemOnObject function
object CreateItemOnObject(string sItemTemplate, 
     object oTarget=OBJECT_SELF, int nStackSize=1)

Notice that this is an object returner, yet I treat it in the script as though it were a void function. This is a minor nuance with the Create functions, where the object is actually being returned to the module itself, so you don't have to declare it.


The string sItemTemplate is the blueprint resref of the item (read the box on tags and resrefs). In this case, I went and made a custom item, and the resref was crowbar (as ever, a string must go in between quote marks).


oTarget is already declared as OBJECT_SELF, but since that is the well, and I want this to go on the player, I enter in a new parameter of oLast (which I've already declared earlier). The final value I've skipped, since I don't wish to change it (as its name suggests, it controls how many items there are in a stack).


So, the final result? The player clicks on the well, and the first time it says something. The next time it says something else, and gives them an object which I've made. Any further clicking will result in nothing.


(I am now dropping the lengthy explanations, you may need to have the editor open alongside this tutorial to look at definitions of script functions. Faster pussycat! Faster! [or you can refer to them directly within the Lexicon --Ed.])


Now I need to give the crowbar the ability to open the door. This is done by giving the crowbar a Cast Spell: Unique Power ability in the magical properties. These unique powers trigger the OnActivateItem handler for the module.


Read the following script, and see if you can tell what it does:


//Script "tutorial_oia"
void main()
{
     object oItem = GetItemActivated();
     object oTarget = GetItemActivatedTarget();
     object oUser = GetItemActivator();
     string sTag = GetTag (oItem);
     if (sTag == "CROWBAR")
     {
          AssignCommand(oUser, ActionMoveToObject(oTarget, TRUE));
          if (GetTag(oTarget) == "CAVE_JAIL_DOOR")
          {
               SetLocked(oTarget, FALSE);
               DelayCommand(2.0f, 
                    AssignCommand(oTarget, ActionOpenDoor (oTarget))
               );
          }
          if (GetObjectType (oTarget) == OBJECT_TYPE_CREATURE)
                    AssignCommand(oUser, 
                         ActionDoCommand(
                              ApplyEffectToObject(DURATION_TYPE_INSTANT, 
                                   EffectDamage(d6(2)), 
                                   oTarget
                              )
                         )
                    );
     }
     SendMessageToPC(oUser, "You have used a " + GetName(oItem) + ".");
}

Look challenging? It shouldn't. There's only one or two new things in there. Okay, follow me through.


First we've got a bunch of declarations, and they are all initialised to one value or another. The first three objects are all object returner functions used in conjunction with the handler we're dealing with (OnItemActivate).


The string sTag gets the tag value for the item that was activated. Why? Because there might be multiple items in a module, and we need to know which one was activated.


So, if the tag matches the string given in the first conditional statement, then it will run what is within the curly brackets. The first thing it does is assign a command to the activator of the item (oUser) to move to the items target. The command is an Action, so it won't happen immediately.


Next, we've got two more conditional statements. There's nothing that says you can't have a conditional statement within a conditional statement. So what do they do?


The first one checks the tag of the item power's target against the given string (notice that I haven't declared the string value for the target tag before, like with the item's tag. There's no technical reason for this, it was just the way I wrote it). The second one uses the GetObjectType function to learn what sort of object the item's target is.


If the target has a tag equal to CAVE_JAIL_DOOR, then it will unlock the target by using SetLocked, and after a further 2 seconds (DelayCommand), it will AssignCommand the target of the item to open itself (the caller is the module, so we need to use AssignCommand to do this).


If the target is a creature, then what it actually does is has the user of the item deal damage to the target. Notice the use of ActionDoCommand. This function allows us to add a void function that normally doesn't go in the action queue of a creature to that queue.


This way, the damage will actually be attributed to having been caused by the player using the item. Additionally, the EffectDamage (which itself shouldn't be a surprise) uses a nice little function called d6. This rolls a number of d6 dice equal to the value in the brackets (default is 1) and returns the value rolled.


Why did I add this in? I don't know, but I thought it would be the sort of thing most players would like to do with it (after all, what did you do with a crowbar in Half Life?).


So what's new in this script? Two thing, both found in the last two lines. Firstly, you may have noticed I didn't have any curly brackets after the bottom conditional statement. This is a nuance in the scripting language.


If statements don't need to have curly brackets after them when only one line is being run when it returns true. It is technically improper scripting practice to do this, and a properly written script would include the brackets, but I've opted for readability.


Secondly, the last function I run. The function itself is simple, enter an object and a string parameter, and the function sends the string to the object. However, I've done a bit of string manipulation here.


The following line "I have a ball." is exactly the same as "I have " + "a ball." in NWScript. The operator + adds integer and float values, totalling them up, and in a way, that is exactly what it is doing with the string.


In the above script, I have used the GetName function to create a string value equal to the name of any object which uses its unique power. The SendMessageToPC function will therefore send a piece of text to the player using the item saying "You have used a Crowbar." in this case.


The fact that I've left the function outside the conditional statements that tell the script what to do if it recognises the item being used means that if I go and throw in a bunch of extra items with unique powers, even though nothing will happen when you use them, it will always return that message, telling them what they used.



Keeping Up?


So far, I've been teaching you script by showing you how to build them. I hope this is a productive method, otherwise the rest of this tutorial isn't going to do much, is it? If you've understood all this, carry on. If not, try writing the scripts yourself, to see if you can get the same effect.



Tasks


Here's a challenge for you. Using the knowledge from this chapter about the OnItemActivate handler, can you make a compass? (I know there's one in the game, but see if you can make an item that will inform the player which way they are facing. Use the function list in the editor.)



Hint


The trick here is to make an item that when you use it, it tells you which direction you're facing in. For this, you need the OnItemActivate script to recognise the compass, work out the facing of the user, and then send a message to the user informing them of the direction gotten.

Variables


int nDone;
string sMyString;
object oObject;


Up until now I've refered to these as information, or values. Another term for these is variables.


Variables are called this because they can change, either by themselves or because you change them within the script.


Some variables do not ever change, and these are referred to as constants. You've already seen an example in the DURATION_TYPE used in previous scripts.


Although you could change those values (as they are declared in the file "nwscript.nss"), they are always the same value every time you use them.


Local Variables


One of the nice things about NWScript is that variables you define in one place can be used again elsewhere without fear of getting them mixed up.


These are called local variables. Within scripting for instance, you can declare a variable, lets say nDone, in one place, and refer to that throughout your script.


But you can use the term nDone in another script, and they won't at all get mixed up.


You'll see local variables used a lot in NWScript, even though they won't always be apparent.


The most common and useful form of a local variable is the stored kind. Nearly every object in the module can store variables upon themselves.


These are manipulated using the Get and Set Local Variable functions. It allows us to store information to use from one script to the next.


Think of them as books. The object itself is the book, and within that book, we store information under chapter headings.


Local variables work in this same way. We can define a variable, and store it upon an object under a name, which allows us to retrieve or change that variable at a later stage.


Global Variables


Global variables are variables that are declared and even initialised outside the void main() function. These are most useful when working with custom functions, which will be explained later.


Semi-Colons


In the previous chapter I said that lines of code are seperated by semi-colons because the compiler ignores white space. I also said there are exceptions.


The exception to this is if you have a conditional statement. They are easily identified within a void main() because they have a set of curly brackets after them.


When writing code, the conditional statement ignores the rule about semi-colons. You do not need to place one after the conditional, nor after the curly brackets.


Technically the semi-colon marks the point at which the code performs whatever script you have given it. This is mainly important to remember when dealing with pre-fix and post-fix operators.


Operators


The signs we use in maths are operators. They manipulate values, such as adding them together.


They can also do comparisons. We can determine if a variable is equal to, less than, greater than, or not equal to another variable.


It is important to note that a standard operator has only one symbol, eg. +, where as comparison operators have two, eg. >=.


Tag Vs ResRef


There are two ways of identify any object in the module, the tag and the resref.


Tags can be recognised by any script, regardless of the object being checked is.


ResRefs are the blueprint value in the palette for an object, so you can only work with the resref of an object which exists in the palette of the module.


When creating objects via script, bare this in mind. Objects in the standard palette use the tag value for sItemTemplate, and objects in the custom palette use the resref value.



Screenshots





 author: Robert Straughan, editor: Charles Feduke