Module Creation: Part 2 (Scripting)

ScriptingThe heart and soul of the Aurora toolset is the limitless possibilities that are available via scripting. Scripting is a tough tool to get used to, but is very easy to use once you have written a few of your own scripts. This area of the guide will be really hard for me to write, but I will try to make it readable and easy to follow.

First thing I am going to do is describe some basic scripting tips and then I will run through the majority of the script types available to you. I will also include a few scripts as examples and will describe how to break down problems at the end. Hopefully, after reading this section, you will have some ideas of how to script events on your own. That and you should know more of what you can do! Also, it is a good idea to play through the single player campaign first ¿ that way you can have an idea of what can be done!

Making Scripting Easy

The first rule of thumb is to always comment any code that you write, especially at the beginning. Get used to do so, and you will have no problems analyzing the code later. Keep a notebook on the side and write down the name of the script and what it does. Also, putting in a script header will make it easier to identify the individual scripts later on. Don¿t bundle up multiple scripts into the same file at the beginning. Doing so may confuse you and may create very hard bugs to track down. Keep the scripts single and don't go the way of BioWare (their feedback conversation for doors and such is a nightmare to go through).

Mark down what each local variable is used for and what their values are allowed to be. Keep this list and make sure that you update it often. Also, you might want to mark down what each value of a local will affect in the module, if possible. An example would be to mark how a local variable triggers how a conversation will flow. Easy enough?

Keep variable names consistent across the board. Use the same style in all your scripts. Doing so will allow you to instantly recognize what everything means and how it is all set up. I will bring this up again a little later.

Go through the list of standard functions to get the return types (what the function calculates), as well as their parameters (required for calling them). Also, try to learn what they each do! BioWare made your job easy here. Any parameter that is followed by an = and a value does not have to be set when called. They are assumed to have a default value, and should only change when you want the value to change. In other words, you can omit the default values that you wish to keep ¿ you just have to fill in the rest.

Scripting Basics

Here I¿ll go into the basic scripting elements. Take into mind that I am not an expert at C, and I might forget something, or falsely relate some information. I will try my best though, and will now start listing what I think are the basics needed for successful Aurora scripting.

I hope that the following listing of information can help you start scripting. Otherwise, try looking through my scripts, as well as the ones that were supplied with the original campaign.

Structure

The basic structure of a script is held within a set of curly braces preceded by a function name. At the very basic there are two types of scripts ¿ StaringConditional() and main(). The main difference is that the former type is used to test certain conditions and the second will simply fire and run. StartingConditional() scripts are used to test for certain conditions and will return either TRUE or FALSE ¿ values that will be used elsewhere to determine whether an event will happen or not. This type of script will be seen a lot when creating complex conversations. In either case, the main body of the script will be written within the curly braces that follow the (). A main() script that does nothing will be written as follows ¿

void main(){}

Comments can be added in anywhere in the script. Commenting code can be done in two ways, as follows. Also, remember to indent like I do, to show the nesting level.

void main(){// This is a standard, single line comment./* This is the beginning of a multiple line comment.This line is also commented out.This is the last line of the comment. */}

Sometimes you will want to include a separate list of functions into your script. You might do this to save time when designing your module (say you want to use the same method of assigning a value to a PC based on what choices they made during character creation ¿ you can write the functions in one file and have other functions call it, saving you from retyping it). Just type in #include "[filename]"

The semicolon is a very important thing to remember! You have to put it in to signify the end of every line of code, barring certain flow control elements. I¿ll show you where to put them in some code segments that follow.

To allow you a chance to see how a script is built, let me take you through an example. Say that there is an NPC that gives the player a quest called "Finding Forrester". When entering the Forest of Undeniable Evil, the area¿s OnEnter() script fires and will update the player¿s journal, but only if they got the quest from the NPC. A very basic script that will help you see what every element will do. Remember that the scripts run in a top-down manner.

Script Variables and Constants

Script variables are used to save data while within the script. These values are deleted once the script is done. They are used for storing information and to promote efficiency by not having to call the same function over and over again.

A variable has to be declared before it can be used. To do so, simply state the variable type, the variable name (start with a lower case letter to designate the type, and follow with the name) and whether it has a starting value or not. Of course, the line has to be ended with a semicolon, or the compiler will complain.

There are five main variable types to choose from. Integers (int) are used to store whole numbers or Boolean values (more on this in the flow control section). Floating point digits (float) are used to store decimal value numbers (real numbers). Locations (location) are used to define certain points within a module ¿ used when customizing events or teleporting the player from place to place. Objects (object) is the most called upon variable type ¿ it is used to store any item that can be found in the module (some examples - players, monsters, specific items, or even the module as a whole!). Character strings (string) is used to pass names and messages, among other things.

I am not going to talk about constants much. There is no real need to set one up at all ¿ constants are simply variables with values that can not change. I never needed to create one ¿ I suggest using a simple local variable that has a static (non-changing) value instead. The included functions rely heavily on constants, as looking at some of the scripts will show. The OBJECT_SELF constant is a value that will always return the current active object. I don't really understand how it works, but it is quite useful. The last thing to say on the subject is that all constants are made up of only consonants.

As for our script, we will need two different variables to make the script work. We can¿t get the values for them yet, so I will leave them uninstantiated for now. Remember that everything in the toolset is case sensitive, including variable names and tags.

void main(){

// oPC is a standard variable to store the PC.object oPC;// iToCheck is my standard variable for an integer// check.int iToCheck;}

Functions

Functions are used to do things. There are plenty of included functions that come with the game. You can write your own core functions that bundle together more function calls, but I leave that to more advanced users, as it is beyond the scope of the guide. I have no intention of going through all the types of functions here! Instead I will pull one out of the script editor and will break it down for you.

void ActionGiveItem(object oItem, object oGiveTo)

The above is a basic function that makes an object give an item to a PC or NPC. It is important to remember that when an object is being used, you need to remember that the tag of the object cannot be used (you have to convert the tag to an object first!).

Let¿s break it down now. The first element is ¿void¿. The first element determines what the return type of the function is. In this case, there is no return value. The function runs and does nothing afterwards. Any variable type can be the return type of a function. The second element is the ActionGiveItem. This is the function name. It can¿t be used when alone, as it needs a number of parameters equal to or greater than the number of non-default parameters in the function definition. In this case, the function needs an item and a person to give it to. Let¿s add a basic function to our script-in-progress. More will be added in later.

void main(){

// oPC is a standard variable to store the PC./* Added in a function to give the variable a value.GetEnteringObject() is used to return an object that is entering the current area.*/object oPC = GetEnteringObject();// iToCheck is my standard variable for an integer// check.int iToCheck;}

It is possible to write your own functions. Just declare a function at the start of the script (before the void main() part) and enter its vital data (example ¿ int ReturnLevel(object oPC);). Once a function has been declared, make sure that you define it after the main script! Remember that each branch of the function must have a return line ¿ or the compiler will complain. This is similar to how a StartingConditional() script works.

For example, let¿s say we want to get the level of a PC and then return it to them using the SendMessageToPC() function. Here is how we could do it, if we wanted to use a function. I included many features that I will be discussing later in this document. For more information on the functions that I used, just go to the script editor and look them up.

int ReturnLevel(object oCreature);void main(){object oPC = GetEnteringObject();int iLevel;if (GetIsPC(oPC)){iLevel = ReturnLevel(oPC);string sToSay = IntToString(iLevel);SendMessageToPC(oPC, sToSay);}}/* This function returns the level of a creature or player, even if have more than one class. Note: I limited the security of the function by not checking for the validity of the object being used.*/int ReturnLevel(object oCreature){int iClass1 = GetLevelByPosition(1, oCreature);int iClass2 = GetLevelByPosition(2, oCreature);int iClass3 = GetLevelByPosition(3, oCreature);return (iClass1 + iClass2 + iClass3);}

Local Variables

Local variables are variables that can be used by any script throughout the module. They are very important and powerful tools when used properly. I suggest using them for quest control, conversation control, and for consequence control.

Local variables don't have to declared like regulars variables. They are automatically created with default values (based on type ¿ 0 for int, 0.0 for float, "" for string, and I don't know about location or object.), if they aren¿t being set. What I mean is that you can check for the value of a local without even setting it. For example, imagine an NPC that will say something different, if talked to a second time. Set up a local variable that will control the situation and then set the starting branches of the conversation to only start, if the local is at 0 or 1. Once at the end of the first branch of the conversation you can have the value set to be 1, to signify that the NPC has been spoken to. More on this will appear in the conversation section below.

Lets put in some of the basic local variable access functions to out script.

void main(){

// oPC is a standard variable to store the PC./* Added in a function to give the variable a value.GetEnteringObject() is used to return an object that is entering the current area.*/object oPC = GetEnteringObject();// iToCheck is my standard variable for an integer// check.// "finding" is the local variable¿s tag.int iToCheck = GetLocalInt(oPC, "finding");SetLocalInt(oPC, "finding", (iToCheck + 1));}

The GetLocalInt() function will check the given PC¿s local variable table for the given tag and will return the value. The SetLocalInt() function will change the value (or add it in) of the variable in the local variable table. That last line will not be part of the final script, but was just used for illustrating its use.

Flow Control

So what happens, if you want a script to do the same thing ten times, or an undetermined amount of times? What happens, if you want things to be done only when certain conditions are met? These questions are vital and will allow scripts to do a lot more than they previously could.

The first to take a look at it the ¿if-else¿ clause ¿ a very powerful tool to set up conditions. The basic use is to check for a condition, or conditions, and do something if they pass, or do something else if they don't . A very easy concept that can be easily screwed up. Let¿s take a look at the basic structure and then at some Boolean logic with a basic example.

This script segment may seem a bit scary to you at first, but it is really easy to see what is going on. I purposefully nested some if-statements and used multiple branches. What is happening is the program is checking to see if the passed object is a PC AND (&& is Boolean and ¿ true if both sides is true) and iToCheck is 1 (== is used for equality testing, = is used for assignment). If the condition is true, a fake spell is cast ay the PC and there is a chance that a message is sent to him to tell him that he is lucky (|| means logical or, as in either side has to be true for the statement to be true - >= means greater than or equal to, =< is less than or equal to, != is not equal to). If it wasn¿t true, and iToCheck is less than 1, the character is blessed with a stone skin spell, otherwise the PC is healed. A similar thing can be done via a switch statement, but I won't be covering it here.

Looping is simple to do. There are two different methods, as shown below. My favorite is the for-loop, because in other languages they offer efficient memory use. Here they are identical to while loops. The-for loop will send the message "Huzzah!" to the player ten times, shown by the arguments within the for-loop declaration. The first is the controlling variable assignment, the second is the looping condition, and the third is the looping effect. The first argument is looked at the first time the loop is run, the second is done at the start of each looping to see if another loop is necessary, and the third is done at the end of the loop to change the control variable. Thus, it will run ten times and then exit to the next line of code. The while loop does the same thing, but the control variable must be given a value before the loops and the looping effect must be included in the body of the loop, or an infinite loop will occur. Both have their uses, but I will not go into the specifics of when each shall be done.

Security is basically type-checking. Whenever you get an object, or location, you should always check to see, if they are valid. I wrote earlier a simple GetIsPC(oPC) command, which does exactly that. All you have to know is that by placing this statement into an if-statement¿s condition, you can use it as a condition. It checks to see, if the passed object is a PC, and if so, allows the action to be done ¿ otherwise the action is deemed invalid and is not allowed to happen.

Basically, you will not have to worry about security that much, unless you want to do some real low level scripting. Imagine a script that gives any entering objects an item. Does that mean you, your items, or your henchman? While I do know that it only applies to the PC that enters, the check is meant as a backup measure to prevent some weirdness. I usually add the checks in when I remember to, and add the others that I forgot when I get an associated weird bug. It isn¿t that difficult to do, but it is really up to you.

Back to the Example

Here is the final version of our script. I took out the basic comments and instead added in suggestions for improvements, some of which I already implemented. If the entering object is a PC, then the value of the local int is checked. If it is greater or equal to 1, a new entry is added to the journal ("finding" entry 10) and a local integer quest tracking variable is updated too. Otherwise, the player gets told that they don't belong in the forest. See -- scripting isn¿t too hard, right?

void main(){object oPC = GetEnteringObject();int iToCheck;if (GetIsPC(oPC)){iToCheck = GetLocalInt(oPC, "finding");if (iToCheck >= 1){AddJournalQuestEntry("findingforrester", 10, oPC);// Alternately I could also set all the encounters to trigger// right now.SetLocalInt(oPC, "findingstep", 10);}else{// The PC hears some stuff when they have no reason to be in// the forest.SpeakString("You enter a dark and ominous forest.");ActionWait(4.0);SpeakString("You feel unwelcome here.");ActionWait(4.0);SpeakString("You should leave this forest.");}}}