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
|
|
||
Screenshots |
|||
author: Robert Straughan, editor: Charles Feduke