Skip to content

HeavenGames








Big Huge Games MS Game Studios devbox

Rise of Legends Scenario Design - Become a Scripting Legend


By Archaeopetrix of Ambition Designs
Scenario Design Workshop

Becoming a Scripting Legend
Or at least a decent beginner
By Archaeopterix.

| Variables | Creation and assignment | Functions | Callbacks | Heirachy | Operators |

A script looks awefully scary at first sight, but it is delicious to work with once you know how to. In this article I hope to provide you a good start on understanding scripting for Rise of Legends. I will try to do so by means of the script file of a scenario from the Rise of Legends campaign: the Vinci scenario Battle at the Ranconi Bridge.bhs. Why that one? It is one of the shortest scripts, and more fun to analyse than some boring example script.

Scripts are for Rise of Legends what the regular "triggers" were in the "Age of" series, and more. Scripts for scenarios are written in the JavaScript language and are contained in .bhs files. You can write such a file in NotePad or any other plain text editor. I use ConTEXT myself because it is fast, has a great interface, and it supports so-called syntax-highlighters for many languages, including JavaScript! A syntax-highlighter gives the script you're viewing or working on nice colours to make it easier to read. So, you can get ConTEXT here, but there are many other good editors.

When you've written a script and saved it as a .bhs file, you can go to the scenario editor in Rise of Legends and load your map. Then, you go to the scripting tools and add the .bhs file or files to your scenario. Once you've done that, the script file is "compiled" and, so to say, copied into the map file itself. After that, you can throw the .bhs file away if you like (but don't*), because it is contained in the scenario file itself. This is also what the scenario designers at BHG did, and it is the reason you won't find any .bhs scripts in the campaign folder at first.

*Note: I assume it all works like this, but I am by no means sure. Plus it's always good to have a backup.

However, the BHG guys have been nice to us designers. To be able to view the scripts BHG used for the campaign's scenarios, go to your main Rise of Legends folder (the one in Program Files, not in My Documents) and launch the file ModPack. The file will be unpacked and many .bhs files will appear in the campaign folder and subfolders! Having done that, let's go to the Vinci folder, then to Maps, and from there open the Battle at the Ranconi Bridge.bhs file. Notice that there is also the .map file, which is the map file itself (simply put this contains where all the trees are, which terrain types are where, etc) written in .XML. The latter however is none of our business. In summary, open the following file in a suitable text editor:

C:\Program Files\Microsoft Games\Rise Of Legends\campaigns\Vinci\maps\Battle at the Ranconi Bridge.bhs

Make sure to select a JavaScript syntax-highlighter if available. In ConTEXT, click JavaScript from the dropdown box in the toolbar. So, how does it feel? You probably only recognise a few words here and there, but we'll change that. However, I'm not going to analyse each and every word in the script - I will teach you how to do so yourself.

The first thing you see is this:

...
include "..\..\Campaigns Script Library.bhs"

int function sf_blow_up_bridge();
int bridge_blown;
...

The first line imports a different script file into this one. That script file, the Campaigns Script Library, contains some handy things that are used often in the campaign. Instead of writing those things all over again in each script, you just type them once in a seperate file and add "include ..." to each file where you need them. Which things this library contains is up to you to explore, after you finish this guide. It is unimportant for now.

Next are these two things with "int" in front of it. Int stands for the data type "integer" - a whole number. There are different types, "double" containing decimal numbers too, or String containing a series of keyboard characters, like "apple juice"! Before we continue to go down the script, I will explain what these types are and what you need them for. We will put the script aside for a while, but don't worry!
 

Variables
In all programming languages, you don't work with real existing things alone, such as the number "2" or a word "monkey". The computational power of those languages comes from a different thing: so called "variables" that can contain those real existing things like "2" or "monkey". For those of you who had maths, this is very basic, but I'll give an example anyway:

6 = 5 + X.

Here, X represents a variable, because it isn't a real value. However, you can calculate X by saying "Hmm, to get six when I have only five, I have to add one. The variable X must equal 1!". Easy eh? Of course you can work with multiple variables in one line, like:

Y = 5 + X.

Now you can say things like: If X equals 3, then Y equals 8 (for 5+3=8)! But if I set X to 1, Y will equal 6! Y's value depends on the value of X, and that's where the great computational power of programming languages comes from.

Now, in the above examples, it was quite clear that the letters X and Y stood for numbers. However, your computer doesn't have a thing like "intuition" that we humans have. It needs to know exactly which value types can be put in a variable, and which value types cannot. A variable made for numbers can never contain a letter or word, or a piece of fruit - and vice versa. There is a bunch of types, the "primitive types", that you can use for variables in JavaScript. There are more, but the scripting manual says not all are supported by RoL... I wouldn't believe that though, but these are the important ones anyway:

- int (integer): may only contain a whole number, positive and negative. In example: -3, -29384, 0, 2938, 1.
- float: may contain whole or decimal numbers, positive and negative. In example: 1.5, -283.34562, 0, -23.
- boolean: may contain a truth value, so only either true or false. It can be seen as a switch that can be turned on or off (1 or 0 for you digital computer geeks).
- String*: may contain any series of keyboard characters. Correct strings are "abcde", "ab ˝# zd&%a sd" and "123asdh565ja" - basically any key you can hit on your keyboard, enclosed in two "double quotes", may form a string.

*String is in fact not a primitive type, but a so called "literal". Why exactly is beyond the scope of this guide, but a dummy explanation is that once a String has been created, you cannot change the value anymore by adding or substracting letters. For Rise of Legends purposes, considering a string as a type works fine though. Mind that String, because it is not a real type, is the only "type" that begins with a capital letter!

In addition, the BHG guys defined some nice new RoL-related types, so called "cast types" for Rise of Legends use only! I copied these from the scripting manual by BHG (don't read them all through, just scan the list to get the idea):

- Unit can keep track of an individual unit id.
- UnitType is a specific type of unit, like “Imperial Musketeer.”
- UnitGroup keeps track of a group of unit ids.
- Build can keep track of an individual building id.
- BuildType is a specific type of building, like “Barracks.”
- BuildGroup keeps track of a group of building ids.
- Location keeps track of a specific point on the map.
- Region keeps track of a region of space on the map.
- Timer is used by the script to keep track of a timed event in seconds.
- CastTimer is a special type of timer that is defined inside the scenario editor.
- Path keeps track of a collection of locations in a direction.
- City keeps track of a specific city on the map.
- Leader keeps track of a specific player. “1”, “2”, “3”, …, “8” are valid.
- SpellType keeps track of a specific type of spell, such as “Summon Army 1”.
- Quest is used to reference a specific quest
- SubQuest is used to reference a subquest of a quest.

The great thing is that you can define many of these things in the scenario editor itself. Using the "scripting tools" available you can define and name groups of units, locations, paths, areas, etcetera, and you can simply refer to them in the script by using those names! We will first do some stuff with the primitive types (int, boolean, etc), and have a look at the cast types (Unit, UnitGroup, etc) later when we're looking at the script again - so forget about those, for now.
 

Creation and assignment
Now, we were talking about variables. Variables are created, and then a value is assigned to them. How do you create a variable of a certain type? Creating it reserves a little bit of memory and gives it a name for future reference. Each bit of memory created like that has to be of a certain type and will only contain values of that type. You create a variable of a certain type simply by stating [TYPE] [NAME];, replacing [TYPE] with a primitive (or cast) type and [NAME] with the desired name. Let's create some variables!

...
int x;
String y;
...

This reserves a piece of memory called "x" for integer-type values, and another one called "y" which will contain string type values. The names x and y are totally random: you could as well have picked the names "monkey" or "poop12345". In general, pick a name that suits you and that displays what the variable is for. By the way, notice how in JavaScript every statement ends with a ";". Anyway, after creating a variable, you can assign values to it. You do this with the assignment operator "=". Notice that "=" pretty much stands for "becomes" - it is not the "equals" check that you learned at school. Example:

...
x = 5;
y = "apple juice";
...

This assigns the number 5 to the previously created integer type space "x" (in short: "x becomes 5"), and the value "apple juice" to the previously created string type scace "y". Notice that the value type you want to assign to it has to correspond to the variable type! The following will cause an error:

...
int x;
x = "apple juice";
...

This crashes because a string cannot be stored in a variable created for integers. Furthermore, notice that each variable name (x and y in this example) can only be used once. The following will cause an error "x already defined":

...
int x;
int x;
..

And the following will cause the same error:

...
int x;
String x;
...

So, types have to correspond at any time, and names may only be used once - regardless of the type! Now, specified values aren't the only things you can assign to variables. You can also assign variables to variables! Check this:

...
int x;
int y;
x = 5;
y = x;
...

The integer "y" will now also contain the value "5". Notice that again the types need to correspond. You cannot assign a String variable to an integer. One more thing about creation and assignment is a handy shortcut: you can create variables and assign values to them at once. Example:

...
int x = 5;
String y = "apple juice";
...

This creates the integer "x" with value 5, and the string "y" with value "apple juice". So, one of the first things that happen in the Ranconi Bridge script is that two integers are created:

...
int function sf_blow_up_bridge();
int bridge_blown;
...

But no values are assigned to them, yet. That's probably done later on, when things are actually happening. If you've been paying attention you will see that "int function sf_blow_up_bridge()" isn't in accordance with the regular format of creating variables. The type is integer, yes, but it contains another weird word: "function". This means that sf_blow_up_bridge() is not going to be a set variable, but a function that will be defined later. By any means, do not continue before you understand how creation and assignment work.
 

Functions
So, put the script aside again. Here's a dummy explanation of what a function is. Imagine you need to remember a random list of numbers, say 173847. Now, you take your friend Johnny and ask him to remember the sequence 847. You'll then only have to remember "173 johnny" - assuming that Johnny won't forget his three numbers. "173 johnny" is, you have to admit, much easier to remember than the full sequence! In this example, johnny can be seen as a very simple function - a function that returns the value 847. Now, how are functions used in Javascript?

To be able to illustrate functions in a proper way, it's time for some basic maths - which you'll need anyway. We want to do some calculations. We can just use the variable type integer combined with regular symbols for adding (+), substracting (-), multiplying (*), and dividing (/), and some more I will not state here. In example:

...
int x = 3+7;
float y = (3.54 * 1.23) / 3;
...

This is all valid. Integer "x" now contains 10, and the floating point "y" contains some very ugly number with many decimals. Now, a great thing is that we can also refer to the variable itself when we assign a value to it. Sounds odd, but have a look at this:

...
int x = 2;
x = x + 5;
x = x * 3;
...

The program goes through these lines top-down. So, this script first creates a variable "x" of type integer and assigns the value 2 to it. Next, it says "x becomes x plus 5", so it adds five to itself, so x is then 7 (2+5). Next, it says "x becomes x times 3", which means that x is then 21 (7*3). Very useful, you will see!

Now imagine we want to do some complicated calculation, and we want to do it several times. Below, two variables are created and we want them both to obtain the result of a "complicated" (hey, it's just a dummy guide) calculation.

...
int x;
int y;
x = ((5+3)/(19/4)) * 3;
y = ((5+3)/(19/4)) * 3;
...

But that is a terrible way to code it. In example we want to change the number 3 to 4 in the above calculation - we'd have to change it twice, once for the x and once for the y. This is called redundancy, and it is the worst thing a script can have. Because when changing, you might forget one, or make a typo. So, we have to avoid this redundancy so you'll only have to make that kind of adjustments once. Have a look at this:

...
int function calculation() { // The function calculation() of type integer.
return ((5+3)/(19/4)) * 3; // Return the result of the calculation.
}

int x;
int y;
x = calculation();
y = calculation();
...

We have now created a function: a little part of the script that kind of stands on it's own, that - just like johnny - returns a certain value. The function has a name and a type, just like any simple variable like x and y. The type of the function in the example is integer, and its name is "calculation()". Note that you have to include the brackets "()" to indicate that it's a function, not a simple variable! What this little script does is the following. First it defines the function - below I'll explain how it works. Then it creates two integers x and y, and then a value is assigned to both of them. But this value is not a simple value like 5 or 19283, no, it is a reference to the function calculation() we defined earlier in the script. So it will go to calculation(), does whatever it says between the { curly brackets } and assigns the resulting value to x and y. (Remember Johnny? In that example you could just say x = johnny, and x would then contain the value 847 that Johnny had to remember).

In between the curly brackets of the function, we only see a return statement. You could as well put lots of other things in there, anything which you can put outside of a function, but for now the return statement is sufficient to get what we want. What does it do? Return [something] gives you a value. In this case, it gives you the result of the calculation, it gives you the function's (Johnny's) output. Every function requires such a return statement, and every function has to return a value of it's own type. So an integer function may never return "apple juice".

As you can see, if we now want to change the number 3 to 4 in the calculation, we only have to make one adjustment instead of two. It is no longer redundant! Make sure you understand the way functions work, and why it is your weapon against redundancy! If you understand, then let's have a look at a somewhat more flexible function:

...
int x = 5;

int function add_to_x(int to_be_added) {
int the_result = x + to_be_added;
return the_result;
}

int y = add_to_x(7);
...

As you can see for yourself, this script creates the integer "x" with value 5. Then it defines a function of type integer, called "add_to_x". Unlike the previous example, this function has something in between the ( round brackets ). And between those round brackets is the input of a function. Apparently, the function "add_to_x()" requires an input of type integer. Then, between the { curly brackets } of the function, we see that it adds this input (which it called "to_be_added") to the value of "x" defined earlier, and returns the result. Further down, integer "y" is created, and assigned to it is the value add_to_x(7). So, the program calls for add_to_x(7), finds the function, assigns 7 to the integer to_be_added and looks in the { curly brackets }. It adds it's input (7) to x (5) and returns the result (12). So, the integer y will have the value 12!

So, functions have an output, but can also have inputs! In addition, a function can have any type and you wish, as long as it returns what it's supposed to return (an integer function should always return an integer value). Also, a function can have as many inputs as you wish! One last example to illustrate this:

...
int function add_this_to_that(int x, int y) {
return x + y;
}
...

You should be able to figure out what this function does by yourself. What happens if I say "int my_integer = add_this_to_that(3,7);"? If you have no idea, you shouldn't continue this guide but re-read everythingIf you have no idea, you shouldn't continue this guide but re-read everything above, step by step - don't panic, sooner or later you'll get it! And when you understand functions, you understand a very important building block of scripts! On a sidenote, notice that when you call for a function, you do not have to specify that it is indeed a function you're calling. Just mentioning the name (with round brackets) is enough, like "add_this_to_that(3,7);".

Now, if you payed attention, you will remember line 3 in the script:

...
int function sf_blow_up_bridge();
...

This doesn't really mean anything yet. The function isn't defined yet, you've only told the program that a definition of this function will follow. Every custom function you are going to use and define later one, has to be mentioned at the start of the script first (at least above the first place in the script where you call for it). So, on line 3 is the first mentioning of a function, now let's see where its definition can be found - scroll down until you see:

...
int function sf_blow_up_bridge() {
find_unit("trucks", "2", "Demolition Truck");
Unit the_unit;
int scan;
int trucks_in_region = 0;

for (scan = 0; scan < unit_group_length("trucks"); scan++) {
the_unit = unit_at_index("trucks", scan);
if (is_unit_in_region(the_unit, "bridge_region")) {
trucks_in_region = trucks_in_region + 1;
}
}

return trucks_in_region;

}
...

Starting on line 286. Here, the function that you mentioned on line 3, is defined. As you can see, this piece of code has the same format as the simple integer functions we used to illustrate things earlier in this chapter. The thing between the { curly brackets } is the part that will execute when you call for the function. As you can see further, it returns "trucks_in_region", which has been created before, as an integer. So, the types match: the function type is int, and it will return an int. Great!

There is one special type of function: "void" - no int, float, boolean, or String, but void! What the heck is a void? A void is the type of a thing that doesn't return any value. It does certain things, but doesn't return anything. Because thingeys of type void don't return anything, there is no "return;" statement to be found, unlike in the integer functions above*. Of course voids can, like the functions we've seen before, have parameters between the ( round brackets ).

*Note: Sometimes there is a return statement in a void function, but then it's effect is just that it aborts the function execution.

Below is an example of a very simple void function:

...
int x = 3;

void function some_void_function(int to_be_added){
x = x + 5;
}
...

Now, what happens if you say "some_void_function(5);"? The number 5 will be added to the variable x. So x will then become 3+5=8. If you then call the function again, say "some_void_function(9);", then x will become 8+9=17. And so on. In summary, voids can be called like any other function but they only do stuff, they don't return anything. If all the function stuff is clear, let's continue to the next line in the script, line 6:

...
void solo_callback on_solo_game_frame () {
...

This is the start of what looks like another void function, sort of. It's type is void, it has a name, and some part between curly brackets. However, it isn't a function like the ones we've seen before. Instead, it is defined as a "solo_callback" - whatever that means. Let's see how these void "callbacks" are used in the Rise of Legends script.
 

Callbacks
Scripts for Rise of Legends are event-driven. When something happens, the game itself "sends" and event message to your script, and then the script responds to that. The script can respond to that through a so-called callback. In example, there is a callback called "on_spell_cast" that will be activated when a certain player in the game cast a certain spell. There is one called "on_mouse_down" that will be activated when you click your mousebutton. There are dozens of callback types, all to be found in the Script Function Reference Sheet found in BHG's scripting manual.

Can you guess what the type of these callback things is? It doesn't return any value, but it surely does stuff. It is a callback of the type void, of course! Take again the line from the script:

...
void solo_callback on_solo_game_frame () {
...

This is apparently a callback, that responds on each game frame. A game frame is the shortest period of time in a game - it lies in the range of milliseconds. So, you can pretty much say that this void is always activated, doing stuff, time and time again, during the entire game. Notice, by the way, that in addition to the "void" type, callbacks need another "type"-ish thing. Callbacks are either "game_callback" or "solo_callback". I'm not sure what the difference is, but as far as I know there is only one callback that only works when defined as a solo_callback: on_game_frame! Make sure that every single player scenario contains this one as a solo_callback - the other callbacks it has can be game_callbacks. Multiplayer game scripts can only contain game_callbacks I think.

Back to the script. The void callback "on_solo_game_frame()" runs every frame. It then does everything within its { curly brackets }, and that's quite a lot. Scroll all the way down until you see the closing bracket }. Mind that there might be other opening and closing brackets { in the script. The bracket that closes the entire block belonging to this void is on line 280 (displaying the line numbers is a great feature of many text editors!) - you can see which closing bracket belongs to which opening bracket by looking at the amount of space in front of it. Found it? Good. And what do we see there? On line 282 starts a new void!

...
void game_callback on_unit_trained(Unit the_unit, Leader the_leader, UnitType the_type, Build the_build) {
...

Another callback, this time it's the callback "on_unit_trained()", with several parameters - the variables between ( round brackets ). Remember that these parameters are the input of a function (scroll back to the functions part if you've forgotten), or in this case of the callback.

Now, when does this one launch? It is activated when a player trains a unit in the game. The unit ID of the created unit is assigned to the variable "the_unit", the player that created it is assigned to the variable the_leader. Notice that the types correspond: a Unit type of variable may contain unit IDs, whereas a Leader type variable may contain "1", "2", "3" etc.

Furthermore, there is the parameter the_type, to which the unit type of the unit (in example "imperial musketeer") will be assigned, and the parameter the_build which - I guess - will contain the building ID it was created from. Within the { curly brackets } that belong to this void, you can refer to these four variables, but we will see how that is done later on. Let's scroll down to the closing bracket and see what's beyond on line 308:

...
void game_callback on_cutscene_finished (String cutscene_name, int was_skipped) {
...

Another callback! "on_cutscene_finished" it's called, and it has two variable parameters. This callback will probably run when a cutscene, an in-game cinematic, is finished. The parameters of this void callback are a simple string, that will contain the name of the finished cutscene, and an integer that will probably contain a 1 if the cutscene was skipped by the player, and -1 if it wasn't - so it's pretty much like a switch that can be turned on or off.

If you've payed attention during the part about types, you will see that here the integer in fact acts like a boolean (true/false) variable. True is equal to the integer 1 and false is equal to the integer -1. Why didn't they use a regular boolean? Well, the engine of RoL (as do many other applications) uses the values -1 and 1 for these kind of things, and I guess it was too much work for BHG to add an automatic converter-to-booleans. Just get used to integers representing booleans!

Next, on line 333 we see another callback:

...
void game_callback on_spell_cast(SpellType the_spell, Unit unit_caster, Leader leader_caster, Location target_loc, Unit target_unit, Build target_building) {
...

That callback has a shitload of parameters! But you can guess what it does by looking at the names and types of the parameters. My guess is that this callback will run when a spell is cast, and that the parameter variables will contain the spell that is cast, the unit that cast it, the player (leader) that owned the unit, and the location, the unit or the building at which it was targeted. Easy eh? Again, we will soon see how these variables belonging to a callback will be used to actually do something.

Just for the sake of satisfaction, we scroll down to the last thing in the script, even though it doesn't particularly belong to this chapter and though we've examined it before:

...
int function sf_blow_up_bridge() {
...

Here we see the function that I've already explained. It is the integer function called "sf_blow_up_bridge". Later, we will have a look at what it actually does (and what it returns). For now, check line 352. Apparently this integer function returns an integer variable named "trucks_in_region" - which probably contains a value that represents the amount of demolition trucks in the bridge region. Just a guess.

Now that we've reached the end of the script, we know what the biggest building blocks of the script globally are, and it is time for some more theory. Because when you create a variable, you cannot call for it on every place in the script. A variable created in the void "on_cutscene_finished" will only exist there. It has to do with the [i]hierarchy[/i] of the script...
 

Hierarchy
A very, very important feature of JavaScript and most other programming languages is that it is heirarchical. It isn't a long list of variables and functions with equal power. No, a script consists of many sub-functions and sub-sub-functions - the functions and callbacks we've seen before. Where a variable can be seen or accessed is what we call the "scope" of a variable. A variable can only be seen within it's scope - outside of it it doesn't exist. Now, what is the scope of a variable? The scope consists of its own block plus the scopes of its parent blocks. A block is a piece of code in between the previously seen { curly brackets }.

You could see this block-hierarchy as a number of levels to which the different parts of a script belong. When you open a new curly bracket {, you begin a new lower level. In general, you will see that the more tabs there are in front of the line, the lower the level. You can look at the variables created in higher levels, but the variables in lower levels are totally invisible. Take a look at the following examples of a nonsense function.

...
int x = 3;

int function some_function() {
int y;
y = 7+x; // We can call "x", because it was created on a higher level.
return y;
}

int z = y; // now we try to call "y" from a higher level, but this won't work.
...

The integer "x" can be seen inside the function, because it was created on a higher level. However, the integer "result" was created inside the function, which is a lower level, so it doesn't exist outside of it. Another example:

...
int function some_function() {
int x = 5;
return x;
}

String x = "This is allowed!";
...

Now, why can the name "x" be used twice? Isn't that forbidden?! In this case, the first "x", an integer, was created (and assigned) within the function. When you create the string "x", you are on a higher level, where the integer "x" doesn't even exist. Another example:

...
int function some_function() {
int x = 5;
return x;
}

String function some_other_function() {
String x = "This is allowed";
return x;
}
...

Can you tell me why this is allowed? And one more:

...
String x = "Apple Juice"

int function some_function() {
int x; // This isn't allowed!
x = 5;
return x;
}
...

Why is the latter not allowed? If you know the answers to the last two questions, you pretty much know the basics of creation, assignment and scopes. Often, we refer to high-level variables (that can be accessed on all the lower levels) as "Global variables". Variables created within a low level function can be called "Local variables". Notice that Global and Local is only relative. A global variable in your script is a local variable when you look at the entire computer as a whole. On the other hand, a local variable in some function is in fact a global variable if you look at the sub-functions of that function.

However, when we say "global" we usually mean "accessable in the entire script". In general, you will see most global variables defined at the first lines in a script. In example, the integer bridge_blown created at the top in the Ranconi Bridge script can be considered a global variable.

Now let's have a look at the script again! We know the bigger structure of it, and we already know a good deal of the JavaScript language, so let's scroll up and zoom in on the first callback this script has.
 

On with the script
As mentioned, this callback runs on every game frame. Therefore, this will be the part of the script in which you should place the things that, throughout the entire game, check the state of the game and do things according to it: triggers! But before we get to the triggers, let us go through the script bit by bit.

... line 7-12
Unit unit_id;
String unit_type;
Location loc;
Location attack_loc;
int hide_map;
int i;
...

What do we have here? The creation of variables, of course. A Unit variable called unit_id, a String containing a unit type, two Location variables, and two integers. No values are assigned to them yet, but they are already created. Why? Because if you create them above the rest of the callback, the variables will be accessable in the entire block between this callback's { curly brackets }. Like global variables in the entire script, these variables then exist in the entire callback "on_solo_game_frame()". You will see why this will come in handy later. For now, just keep in mind that we've got these variables ready for us to initialise and use!

... line 14-16
static Unit venza_id;
static int truck_move_time;
static int defense_spawn;
...

Next, some more variables are created. Another Unit variable and two integers. These variables have an extra property: they are static. When you do not specify this, they are automatically dynamic. I only know the use for "static" in object-oriented programming, where a static variable does not belong to an object but to an entire class of objects. It's fine if you understand this difference but it's fine if you don't, too, because as far as I know the Rise of Legends scripts don't do much with object oriented programming. Here however, only one thing is important: when some function is declared static, it can only call other functions or variables if they are static, too. And there are standard functions in RoL that are static, which we will see later.

... line 18
run_once {
...

This is a special function that will only run in the first game frame (even though it's in a callback that will run every game frame, mind that!). So, inside the { curly brackets } belonging to this run_once thing, you will find all the stuff that will only run once, when the game is started. Notice how that with every { bracket opening, we enter a deeper level! This is indicated by the amount of tabs in front of the lines. Variables created here will not be seen on the higher levels, but we can see the variables that were created in those higher levels. If you've forgotten, just check the hierarchy chapter.

... line 19-28
sf_ctw_quick_battle_setup("Vinci", 6);//enable QB setup
//sf_turn_off_all_ai();

// Get some random seeding
//ice- don't do this - rand_seed(get_rand_seed());

disable_city_defeat("2");

disable_trigger("MyEyeNow");
disable_trigger("BoomBridge");
...

sf_ctw_quick_battle_setup is a function that is in the "Campaigns Script Library" that was imported into this script on line 1 (remember?). For those interested, this function enables campaign scenarios to work about right in a quick battle type of game - in case one ever tried. If you want to know how it works, check the "Campaigns Script Library.bhs" file in "...\Rise of Legends\campaigns\" - it starts on line 75. All functions that begin with "sf_" are found in that script library, by the way.

Next, you see these weird things with "//" in front of it. "//" means that it is a comment. The things that follow such a "//" are seen as comments and are ignored when the script actually runs. It is to disable things you wrote earlier without deleting them, or to explain the stuff in the code. It's quite fun to look at the comments of BHG guys this way.

Anyway, on line 25 the script continues with disable_city_defeat("2"), which is apparently a void function that requires a string parameter. Why can I see this? It is a function because of the brackets (), and it must be a void, because if it had been a function that returned something (in example an int, boolean or string), it should've said something like "x = disable_city_defeat("2")", where the value the function returns is assigned to variable x. But nay, it is just a function that is called totally on it's own, so it must be a void. Now, this can be a function either defined in this script itself, or defined in the standard functions of Rise of Legends. If you look through the entire script, you won't find a definition of the function that is called, so it must be some existing function. Now, many of those are listed in the Script Functions Reference Sheet (downloadable somewhere), but this one isn't, so we can only guess what it does. The "2" parameter probably refers to player 2, meaning that when you call this function, you disable the feature that player 2's city can be defeated, or something.

disable_trigger("blablabla") however, on lines 27 and 28, is a command that we can understand. It disables a trigger, and makes it do nothing until it is activated again. We'll look at how triggers really work later on, when we actually see some of them. For now, apparently there are triggers called MyEyeNow and BoomBridge that need to be turned off at the start of the game. Fine. Continue.

... lines 30-35
leader_gain_tech("2", "Territory 1");
leader_gain_tech("2", "Territory 2");
leader_gain_tech("2", "Territory 3");
leader_gain_tech("2", "Prosperity 1");
leader_gain_tech("2", "Prosperity 2");
leader_gain_tech("2", "Industry 1");
...

leader_gain_tech("2", "Territory 1") is not that hard to understand, but it can be found in the reference doc as well, because there we find:

"void leader_gain_tech(Leader the_leader, Type the_type)"

It is a void function (doh), and it takes two parameters, one of type Leader and one of type Type. I assume that this void instantly grants some technology to player 2. Territory 1, 2 and 3 are probably the ones that extend your territory's borders, and the others you can guess. These names do not correspond simply to the names displayed in the game - you'll have to look em up somewhere or try.

... lines 37-38
get_difficulty_data("ranconi_defense_time", defense_spawn);
get_difficulty_data("truck_timers", truck_move_time);
...

get_difficulty_data() is a function I cannot find either. But I guess that it obtains the difficulty data that you created in the scenario editor, and then stores it in the variables in the second parameter place (defense_spawn and truck_move_time). Notice, by the way, that the variables it's stored in have been created earlier, and they were static (remember?). Apparently, the function get_difficulty_data() is a static function, which can thus only work with static variables. Okidoki! Maybe what this difficulty data really is will be more clear when we continue to go down the script - just remember to look out for variables called "defense_spawn" and "truck_move_time".

... lines 40-48
unit_group_move_to("entourage", "army_start");

unit_group_patrol("patrol1", "p1");
unit_group_patrol("patrol2", "p2");
unit_group_patrol("patrol3", "p3");
unit_group_patrol("patrol4", "p4");

//JFR: some spideys patrolling and stuff.
unit_group_patrol("Group Patrol Spiders", "base_camp3")
...

Here, six more void functions are called. Let's have a look at the descriptions from the reference sheet:

"void unit_group_move_to (UnitGroup group, Location dest)"

This seems pretty easy. It doesn't return anything - it's a void - and probably sends a unit group to move to a destination. In the editor, apparently a unit group called "entourage" was defined, and a location called "army_start". In this run_once part (because we are still in the part that runs only once), it seems sensible to send some army to an army starting point - why not. Next we see some called functions of this type, again copied from the reference sheet:

"int unit_group_patrol (UnitGroup group, Location dest)"

Now, I have no idea how an integer function can stand on it's own, without, in example, a "x =" in front of it. It would make more sense to me if these were void functions, because I guess that with these functions, (in the script) five armies are being ordered to patrol to certain points. I will update this guide once I've found out how this works exactly. Make sure to tell me if you know! One thing I do know is that integers often function as booleans (but with values 1 and -1 instead of true and false) - remember all the talk about variable types?

On lines 50 to 67, more things are set and initialised. Notice the funny comment added by BHG on lines 65 - apparently they couldn't get the stuff on line 66 to work, so they just set the army on defensive mode hoping it would be a sufficiently good solution. Notice the set_global() command:

...
set_global("hide_map", 0);
...

It sets a certain global variable called "hide_map" to zero - for some reason I don't understand they didn't just use the variable created on line 11 (which is - within this script - global as well). Instead there is apparently some other, more global variable with the same name, that has to be set using set_global() and called using get_global() - again, if anyone can explain this better then please do so, and I will update this guide. Anyway, you will see later how this global variable is used.

First, let's have a look at this very sweet kind of construction: the IF!

... line 69-74
if (get_difficulty_id() == "Easy" || get_difficulty_id() == "Moderate") {
remove_unit_group("patrol1");
remove_unit_group("patrol2");
remove_unit_group("patrol3");
remove_unit_group("patrol4");
}
...

The "if" statement works fairly intuitive. It's built up like this:

if ( this condition is true ) {then do everything between these curly brackets}.

In the script, the condition is this part: get_difficulty_id() == "Easy" || get_difficulty_id() == "Moderate". If I tell you that the "||" means "OR", I'm sure you can guess what this thing checks. The condition is true when the game difficulty is easy or moderate! So, when the difficulty is easy or moderate, it does what is between the { curly brackets }: it removes unit groups. Apparently when the difficulty set at something not-too-hard, some armies have to be removed from the map because otherwhise the game would be too difficult. I think here it's time to introduce you to some logic.
 

Operators
Remember the boolean type, the one that can only be set on true or false? You can actually calculate things with it, like with numbers. We have seen it happening above, in the "if" statement, only we did not notice. There, || meant "or", which is a so-called logical operator. Logical operators can be seen as functions that require one or two booleans and return a boolean. || requires two booleans as input, and returns a new boolean. The "or" is, thank god, not the only one, here are the important ones:

A || B is true if either A is true, or B, or if both are true. If A and B are both false, A || B returns false.
A && B is true only if A and B are both true. If one of them is false, the entire A && B is false.
!A is true only if A is not true. If A is true, !A is false.

So, || takes two booleans are input, and returns the boolean value true if at least one of the inputs is true, && returns true if both inputs are true, and ! returns true when the input is not true (false). If you want to know more about logic, I suggest you sign up for our Age of Mythology triggers course (even if you don't have the game Age of Mythology itself). If you don't make your scripts too complicated, however, you hereby know enough of the logical operators.

There are however also other kind of operators that return boolean values, what about these:

A == B is true only if A equals B.
A < B is true only if A is a smaller number than B.

The second one is a mathematical operator, which can only compare numbers, like integers or floats. The == one however can also compare two booleans, or two custom types Rise of Legends uses. Mind the difference between "x=y" (the statement x becomes y) and "x==y" (the boolean function that is true if x equals y). Let's zoom in on line 69 in the script:

... line 69-74
if (get_difficulty_id() == "Easy" || get_difficulty_id() == "Moderate") {
...

Here the == compares a function that returns a string, to a string, twice. First the function is compared to the value "Easy" and then to the value "Moderate". If the first == returns true, OR if the second == returns true, the whole || "method" returns true meaning that the condition is true and the thing within this if's { curly brackets } will be done. Anyway, you have to see that these operators can for now be seen and used as regular boolean functions. So you can say:

...
boolean x = true;
boolean y = !x;
boolean z = x || y;
...

In this example, the variable called "y" will be false, but "z" will still be true, because x is true, and that makes "x or y" true. Anyway, you can do all kind of complicated things with the operators. Let's say A, B and C are variables. When will the following x be true and when will it be false?

...
boolean x = !(A && B) || (A && !(B || C));
...

Is x set to true when A is true but B and C aren't? Is x set to true if A is false, B is false but C is true? See if you can figure it out!
 

Back to the script
After this logical interuption, let's go back to the script.

... line 76-81
diplo_disable("1");
diplo_disable("2");

//intro cutscene
queue_cutscene("intro");
}
...

Apparently the diplomacy is disabled or locked for players 1 and 2, meaning that they cannot change teams anymore. Then, a cutscene named "intro" is started. This cutscene is created and named in the editor, so "intro" just refers to that one. And then, what do we see: a closing curly bracket }! It is the end of run_once, but not yet the end of the void on_solo_game_frame(), of course.

On line 83, then, we see:

... line 83
get_global("hide_map", hide_map);
...

So the first thing that ever happens, in each game frame (except for the first frame, where run_once first runs), is that the global variable hide_map is updated. We've seen it before, and we will see it more later on. It is the variable that keeps track of your progress through the game. Based on the value assigned to this variable, some triggers will fire and others won't. Triggers, I say? Yep, triggers. A fantastic feature of RoL that makes scripting much easier. Have a look at this:

... line 88-92
trigger Startup(hide_map == 1) {
play_music("ATBEconomic", 0);
activate_quest("river_dash");
build_group_set_seen("1", "initially_seen");
}
...

"hide_map == 1" returns a boolean value, and can be seen as the condition of the trigger. The effects of the trigger, what will happen if the condition is true, are the statements following in between the { curly brackets }. This is very similar to an if-then construction, check:

... example
if(hide_map == 1) {
play_music("ATBEconomic", 0);
activate_quest("river_dash");
build_group_set_seen("1", "initially_seen");
}
...

However, working with triggers has some great advantages over working with ifs and thens. Firstly, you can name your triggers. The trigger starting on line 88 is called "Startup". And if something has a name, you can refer to it! Using the enable_trigger() and disable_trigger() statements, you can - yes - enable and disable them. If you enable a trigger, it becomes active, meaning that it will actually run in the next on_solo_game_frame. If it is disabled, it becomes inactive, and it'll be ignored by the script until you enable it again. There are also shortcuts enable_all and disable_all, that - you guessed it - enable respectively disable all triggers at once. You aren't able to do all this with the regular ifs and thens!

Notice that naming your triggers is optional. In example, the trigger on line 104 doesn't have a name. Now, the rest of script until line 280 is filled with these triggers. I will not discuss any of the triggers here - it is up to you to see what they do and how they are used. All the functions used in those triggers can be found on the functions and callbacks reference sheet, and often you can also just guess what they do. Just remember how javascript works: variables, types, assignment, functions, hierarchy, etc. On line 280, we finally see a closing curly bracket again, on the same level as the opening bracket of the on_solo_game_frame callback. So, indeed, we're done with the analysis of this callback, on to the next, on line 282!

"on_unit_trained()", this callback is activated whenever a unit is trained. Four parameters will then contain the stuff like the unit ID, the player who owns it, the type of the unit, and the building from which it was created. Then, using a lot of if-then-else constructions, some things are checked and stuff is done as a result. Understanding this is basically a matter of going through it line by line. I will just explain it by splitting up and commenting the code a bit.

... line 282-306
void game_callback on_unit_trained(Unit the_unit, Leader the_leader, UnitType the_type, Build the_build) {

// Create a variable "loc" that'll hold a location.
Location loc;

// And then: if the one that created the unit is player 2,
if (the_leader == "2") {

// If the unit was created from barracks1 or barracks2,
// add the unit to a group,
// and send the group to attack something.
if (the_build == "barracks1" || the_build == "barracks2") {
add_unit_to_group("city_defenders", the_unit);
unit_group_attack_to("city_defenders", "ranconi_ping");
}

// else, if it was created from the building "bomb_factory",
// or if it was created from bomb_barracks and bridge_blown == 0, then:
else if (the_build == "bomb_factory" || the_build == "bomb_barracks" && bridge_blown == 0) {

// if the unit type is a Demo Truck, and if the break_on_through quest is active,
// and if the first subquest is not completed, and if the break_on_through quest isn't failed, then:
if (the_type == "Demolition Truck" && is_quest_active("break_on_through") && !is_subquest_completed("break_on_through_sub1") && !is_quest_failed("break_on_through")) {

// If the function sf_blow_up_bridge returns a value bigger than 0,
// then send a chat message (to player 1, with a voice sound, and some icons).
if (sf_blow_up_bridge() > 0) {
add_chat_with_voice("A Demolition Truck is headed for the bridge!", "1", "Imperial Musketeer", "Imperial Musketeer", "vv_ranconi_imp_musk_0001");
}

// else (so if the function returns a value equal to or smaller than 0),
// then send a different chat message.
else {
add_chat_with_voice("A Demolition Truck is headed for the bridge!", "1", "Imperial Musketeer", "Imperial Musketeer", "vv_ranconi_imp_musk_0001");
}

// Anyhow, whatever that function's value is, update the variable loc with the value of "bomb1" (created in the editor).
loc = "bomb1";
}

// else (so if the unit type is not a Demo Truck, or if the break_on_through quest is not active,
// or if the first subquest is completed, or if the break_on_through quest is failed),
// then assign "bomb2" to the variable "loc".
else {
loc = "bomb2";
}

// After everything's done, send the unit to attack the location stored in "loc".
unit_attack_to(the_unit, loc);

// And of course end with enough closing brackets.
}
}
}
...


It seems quite complicated, but the key to analysing and understanding this is to look at the amount of tabs in front of each line. That way, you can see which else belongs to which if, and which closing bracket belongs to which opening bracket. Just read it through yourself - maybe it's easier to read without my comments. On to the next callback:

... line 308
void game_callback on_cutscene_finished (String cutscene_name, int was_skipped) {
...

This one is activated whenever a cutscene is finished. I won't go into this with much detail, so just check the script yourself. The script checks the name of the cutscene that has just finished, and based on the name it will assign a value to the global variable we've seen before - the variable "hide_map". Remember how on each game frame, the value of this variable is checked, and also in some triggers the value is checked (see for yourself!).

... line 333
void game_callback on_spell_cast(...){
...

This callback speaks for itself. It is activated whenever a spell is called, and then if the spell "explode_truck" is cast, the truck must be killed. Easy peasy.

The last thing we're going to look at is the function that starts on line 339. It uses another very neat construction - the for-loop:

... line 345-350
for (scan = 0; scan < unit_group_length("trucks"); scan++) {
the_unit = unit_at_index("trucks", scan);
if (is_unit_in_region(the_unit, "bridge_region")) {
trucks_in_region = trucks_in_region + 1;
}
}
...

How does this work? Check this:

for(initial action; check; end action;) {actions}

The for statement first performs the initial actions, and then continues to run as long as the check returns true. In each loop, it performs the actions between the { curly brackets }, followed by the end action. Have another look at the script:

... line 345
for (scan = 0; scan < unit_group_length("trucks"); scan++) {
...

First, the variable scan is set to 0. Then, as long as the value in scan is lower than the size of the unit group "trucks", whatever is between the curly brackets is performed again and again. Eventually, the value of the scan variable is increased. "scan++" is actually a shortcut for "scan = scan +1", because it is used so often. In summary, this thing loops through all units in the unit group, one by one. Understood?
 

The end
If so, then you know enough to analyse the rest of the script, mainly the triggers, on your own. You know everything you need to know about variable creation and assignment, about types, about functions, callbacks, and hierarchy. You've met the three most important constructions: the if-then construction, the trigger, and the for-loop. You know how to calculate with numbers and with truth values, using the mathematical respectively the logical operators. I hope this helped, and I wish you all the best with your scripting carreer!

Archaeopterix.



Have a guide you would like to submit to the RoLH Scenario Design Workshop? Email it to Alex. Be sure to include your forum username and the guide's title.

Note - Only the best guides will be added here. Please keep your guides clear and to the point. Feel free to post your guide in the Scenario Design forums at Rise of Legends Heaven where your guide will be looked over by the staff and may be added to the Workshop.