Robert Straughan - Capture Glory Garbage Treasure

From NWN Lexicon
Jump to: navigation, search

Robert Straughan - Capture Glory: Garbage Treasure

Starting Equipment


I've stripped the player of their equipment, so we'd better let them have something to get through the module with. What I'm about to do next is probably the most complex thing you will need to grasp.


Firstly, I've placed two garbage placeables into the cave. I've made them both usable, and they both have inventories. Into their OnOpen handlers, I've placed the following:


//Script "cave_garbage"
#include "tutorial_weapon"
#include "tutorial_lib"
void main()
{
     object oOpen = GetLastOpenedBy();
     int nClass = GetBestClass(oOpen);
     int nOnce = GetLocalInt(oOpen, "HAS_BEEN_TO_OTHER");
     if (nOnce == 1)
     {
          if (nClass == CLASS_TYPE_BARBARIAN)
               CreateItemOnObject("NW_AARCL004");
          if (nClass == CLASS_TYPE_BARD)
               CreateItemOnObject("NW_AARCL004");
          if (nClass == CLASS_TYPE_CLERIC)
               CreateItemOnObject("NW_AARCL006");
          if (nClass == CLASS_TYPE_DRUID)
               CreateItemOnObject("NW_AARCL002");
          if (nClass == CLASS_TYPE_FIGHTER)
               CreateItemOnObject("NW_AARCL004");
          if (nClass == CLASS_TYPE_MONK)
               CreateItemOnObject("NW_IT_MRING001");
          if (nClass == CLASS_TYPE_PALADIN)
               CreateItemOnObject("NW_AARCL006");
          if (nClass == CLASS_TYPE_RANGER)
               CreateItemOnObject("NW_AARCL002");
          if (nClass == CLASS_TYPE_ROGUE)
               CreateItemOnObject("NW_AARCL001");
          if (nClass == CLASS_TYPE_SORCERER)
               CreateItemOnObject("NW_IT_MRING001");
          if (nClass == CLASS_TYPE_WIZARD)
               CreateItemOnObject("NW_IT_MRING001");
 
          SetLocalInt(oOpen, "HAS_BEEN_TO_OTHER", 2);
     }
     if (nOnce == 0)
     {
          GetWeapon(oOpen);
          SetLocalInt (oOpen, "HAS_BEEN_TO_OTHER", 1);
     }
}

Okay, a long script, but by now this shouldn't be particularly difficult to figure out. A typical object returner based on the handler, and a pair of if statements with local variables used to prevent either one from happening more than once. You saw this on the well with the crowbar.


There are four lines in here that probably won't look familiar. Firstly the two #include lines at the top. #include is like a copy and paste function that runs when you compile the script. (Read the box for more details).


Which means there are lines of code that this script uses that you can't see here. They are what form the two functions that you probably don't recognize, GetBestClass, and GetWeapon. That's because these are custom functions that I've written.


Open up "tutorial_lib", and you'll find the following:


//Script "tutorial_lib"
int GetBestClass (object oTarget)
{
     int nClass1 = GetClassByPosition(1, oTarget);
     int nClass2 = GetClassByPosition(2, oTarget);
     int nClass3 = GetClassByPosition(3, oTarget);
     int nLevel1 = GetLevelByClass(nClass1, oTarget);
     int nLevel2 = GetLevelByClass(nClass2, oTarget);
     int nLevel3 = GetLevelByClass(nClass3, oTarget);
     if (nLevel1 >= nLevel2 && nLevel1 >= nLevel3)
          return nClass1;
     if (nLevel2 > nLevel1 && nLevel2 >= nLevel3)
          return nClass2;
     if (nLevel3 > nLevel1 && nLevel3 > nLevel2)
          return nClass3;
     return -1;
}

That's all there is to writing your own function. It must be done before the void main() line, but basically, a custom function is a process which can be called on through the name you give it within a script. You do it every time you write script within the void main().


There is a change here through. Before I told you that return simply ends the script. When used in void main() this is the case. A return command in a void function simply stops the function, so in a void main(), you are stopping the script.


Here however, we are using it to return a value. This is how the functions you've been using return their information. Consider the above function.


Look at the first line. The function is called GetBestClass, and has a return type of int. It has one parameter, an object called oTarget. Nothing out of the ordinary there, you've seen plenty of functions like this.


The next bunch of lines declare the class types of the three classes of oTarget, and then works out the level of each of them. But I haven't declared oTarget... well, actually, I have. You see this is a function other than void main().


You know all those functions where they had some parameters already initialized? This is the same thing. If I wanted to, I could have put object oTarget = OBJECT_SELF into the function parameter, and oTarget would already have a value. But I haven't, so whenever I use this function, I must specify oTarget in the parameters.


Now, since I have made this function an int returner, I had better return the appropriate information. By using the return command with the appropriate variable to return, the function is complete.


In the case of the above function, it will return the class type of the first class of oTarget if it is the highest level class oTarget has, or the second class if it is higher than the first and higher or equal to the last, and only the last class returns if it is higher than both the previous ones.


Note the last line, return -1. You may notice in some function definitions, it states that the function returns a certain value, like -1 or OBJECT_INVALID, when there is an error.


The scripts don't generate these errors internally, they are values that have been returned because the script we used for defining how this function works did not do its job.


Consider the process of the conditional statements. If it has not found any of them to be true, then it will hit that last line, and return -1. This means something went wrong, and the function did not determine the highest class of the target for some reason.


It is a way in which we can determine errors. Additionally, when building a function that returns a value, you must always return a value of that kind. If I hadn't added that return -1 line, the compiler wouldn't do its job, because there's a chance that none of the three return commands would fire.


Before moving on, I'd better point out the use of the && operator. This is equivalent to logical AND. Basically, you use this to have multiple conditionals that must all be true before the following lines will fire.


Now, although I've put this function into a separate script file, since I've used #include, it's effectively in place over the void main(). So the void main() section now knows how to get the highest level class of the target.


Although I've only used this function once, and could probably have made the script it uses part of the main script here, this method leaves me free to do the whole process multiple times in the same script, or even use it in other scripts, simply by using #include.


So, back to the script. It gets the best class of the target, in this case, the last person to open the container. The first time it runs this script, it runs the bottom section. Ignore that for the moment.


The second time, it runs the top section. This is a bunch of if statements that looks at the class type returned by our custom function. Based on which class is the highest for the target, it will create an item in the garbage's inventory.


Now, that bottom bit. I've got a function called GetWeapon. What does it do? Well, it works out what weapon would be the best one to spawn for the player. How?


I'm going to steal script from Bioware. Some of you may be familiar with this process already. Specifically, I want what is in the script file "nw_o2_classweap" If you open it up, you'll find around 2000 lines of code.


This is why I don't want to have to write it out myself. So, how do we use this for our purposes. The script is designed to be put on a container, to spawn a weapon appropriate for the player.


Since I already have a script that runs for the container in question (ie. this script), what I'm going to do is change the void main() function here to a custom one to call on from this script.


I opened it up and saved it as "tutorial_weapon" I then changed the initial three lines of the void main() from this:


//Script "nw_o2_classweap"
void main()
{
     object oLastOpener = GetLastOpener();
     object oContainer = OBJECT_SELF;
     /* rest of script */

to this:


//Script "tutorial_weapon"
void GetWeapon (object oLastOpener, object oContainer = OBJECT_SELF)
{
     /* rest of script */

Can you see what I have done? I've changed the void main() into a custom function called GetWeapon. It too is a void, since I don't want to return any information, just get the script to run.


The two parameters used for the function are actually the variables used by the original script. So essentially, I've just changed the way this script gets its information to perform its processes.


Instead of being a standalone script that runs by itself, I can now call upon it at any time to work out the best weapon for oLastOpener (which I would point out, since this is now a custom function, does not have to be the last opener of a container).


So, this script will mean that, regardless of what order the player goes to them in, they will first get a weapon that is best suited to them (I believe this works by focus and proficiency), and then they will get an item that I have designated as being the item for their best class.



What was that?


As I said, this is the most complex thing you will have to learn in this tutorial. If you're still not sure about it, try writing a little script for yourself, and creating a custom function for that.


When you're sure you have understood all this, proceed.



Tasks


Previously I've made a mention about efficiency of scripting by declaring certain things only once to cut down on the amount of processing done. While that does not actually apply to this script, there is a more efficient way of writing this. Try rewriting the script so that it is more efficient.


If you're stuck on what to do for your little test script to see if you can create a custom function, try the following. Create a custom function which will place a badger at a point, and apply a visual effect to it. Have the main script fire this function four times at 2 second intervals.



Hint


You need to have a CreateObject function within a void returner function that you can place in a DelayCommand. You then have the value of the DelayCommand calculated within a for loop, which increments the counter by two each time. That way, it runs the loop four times, and Delays the command by an increasing 2 seconds each time.
The actual reason I chose the CreateObject function for the custom function task is to point out that you cannot use DelayCommand on the CreateObject function.


#include


The #include function is a benefit, but can give some absolute headaches when debugging.


It works with scripts, and effectively copies and pastes the attached script to the script you are working on. It must always go before the void main() function.


This allows us to keep various functions or processes in one script, but quickly being able to use them again in multiple scripts without having to copy and paste for real.


Note that since it is literally a copy and paste, you can't have a void main() in both the script you are compiling and any script which you attach.


You also cannot have global variables of the same name in multiple #included scripts. The compiler will give you an error if this occurs.


I said there was a headache, well here it is. When you get an error in a script which has a #include statement, always check the line number it gives for the error.


Sometimes you may notice that the error it gives is not applicable to the line it says, or that the script does not go up to that line number.


This is because scripts without a void main() in don't actually compile normally. Any errors in them will not show up. They only show up when you compile a script that #includes them.


So when error correcting in your scripts, if the error doesn't make sense, or you fix it and keep getting it, check the line number given in the #include scripts, as it may be an error in one of those.


Another headache is that the #include is not a link. If you change something in a script which is #included in another, you must compile the original script again, for the new change to take effect.


The #include is an import function, not a link. This means that all scripts which #include a script that has been changed must then be recompiled. When you have lots of scripts to recompile, use the Build option in the menu.


Custom Functions


There's nothing difficult about custom functions, if you follow the tutorial, you'll do fine.


What I haven't mentioned about the custom functions I used in the tutorial is the extra line of code directly above them in the script.


It's exactly the same as the first line which indicates the function information, but has a semi-colon after it.


This is a declaration of the function. You do not need to do this at all if you don't want to but there are two reasons why you may wish to do so.


Firstly, if you open the "cave_garbage" script and scroll down the list of functions in the editor, you may find the GetBestClass function listed in bold.


The script editor can recognize and list custom functions if you have declared them at the start of the script, whether through the use of a #include command or by writing them into the script being compiled.


The second reason for doing this is if you wish to leave the custom function definition until after the void main() section of script.


Any function which utilizes another function defined within the same script must have the custom function it uses placed prior to it in the script, or else it won't know what you're talking about (like with variable declarations).


By declaring the function before the void main() it now realizes there is a custom function written into this script, and knows that it is okay if you have typed that custom function into its main block of script.


This leaves you free to define the custom function at the bottom of the script, after the void main(). This can be useful if your custom function is a long list of if statements, or large processes that you wanted to get out of the way so you could see what the main script is doing quickly and easily.



Screenshots


  • Garbage




 author: Robert Straughan, editor: Charles Feduke