Go to Part III
Download the reference
INTRODUCTION
1. About this tutorial
This tutorial is a little project of mine, where I am trying to make those who read it capable of controlling the AI in Age of Empires III through an AI script. This tutorial aims to be as complete as possible, so that once you finish it, you have a knowledge that is solid enough to make you capable of writing an AI script completely from scratch, with your own style and method. I hope you will enjoy learning things from it as much as I enjoy writing it.
2. Requirements
• Age of Empires III - Complete Collection. The equivalent of it is the combination of all of the three Age of Empires III games: Age of Empires III, The War Chiefs and The Asian Dynasties.
• Basic computer knowledge, such as going to a certain folder, copying, moving, renaming, deleting files and folders and editing files with programs. Especially, you must know where is your copy of Age of Empires III installed.
• A text editor. I recommend a text editor that supports syntax highlighting (to know more, google it), like Notepad++ or Sublime Text. I personally prefer Notepad++, which can be downloaded here
• Patience. Among all of the things we can test/verify, AI-related things are probably the most time consuming.
CONVENTIONS
Before we continue, there are a few things we have to agree on to keep problems and misunderstanding away from us:
1. Backups
Please, oh please backup your files before editing them. Making a backup simply consists of copying the file and pasting the copy somewhere so you can restore it in case something goes wrong with the original.
2. Game Folder
When I say "Game Folder", it means the folder where Age of Empires III is installed.
3. User Folder
When I say "User Folder", it means the Age of Empires 3 folder in System Drive\Users\Your Username\Documents\My games
SETTING UP AN AI SCRIPTING PROJECT
Every AI scripting project begins with the following steps:
1. Enabling the developer mode
Note: doing these modifications will make you unable to play Multiplayer games unless the other players have the same files as you. Make sure you make backups of the original files so you can restore them when you want to play in Multiplayer mode again.
When the developer mode is enabled, we get an access to a whole set of features that are disabled by default. Here is how to enable the developer mode:
• open Game Folder\Startup\gamey.cfg with your text editor
• paste these lines at the bottom, in the very end:
Code: Select all
developer
aiDebug
logAIErrors
The developer mode is now enabled.
But we also need some way to access the features that are unlocked. Here is how to do that:
• open Game Folder\Startup\gamey.con with your text editor
• paste these lines at the bottom, in the very end:
Code: Select all
map("alt-q", "root", "AIDebugInfoToggle()")
map("alt-e", "root", "configSet(\"showConsole\") console()")
map("esc", "console", "console()")
You may have figured out what do these hotkeys do. We'll use these later.
2. Creating an AI script
When you are familiar enough with AI scripting, you can make your AI script the way you want. But, for now, I assume that you are a fresh rookie so I will show you the rookie way for creating an AI script.
• open your text editor
• create a new file
• write these lines:
Code: Select all
void main( void )
{
}
And that's it, you created an AI script!
> Seriously? That's all?
Yes, the file AAI.xs is an AI script. However, it is unused for now. Let's see how to use it!
3. Loading an AI script
For the purpose of this tutorial, we are going to test AI scripts mostly in scenarios. Actually, scenarios and the scenario editor offer the best tools for testing, not only AI scripts but also mods, triggers and even random maps in some cases.
• run The Asian Dynasties
• go to Help & Tools>Scenario Editor
• go to Scenario>Player Data
• in the section of Player 1, change Human to Computer
• click on the button AI
• find and select our AI script AAI.xs
• click on the button Load
• if you get an error or an empty dialog box, simply repeat the process from the 3rd step
• close the Player Data dialog
• save as AIScen.age3Yscn
4. Testing
• in the main menu, go to Single Player>Custom Scenario
• find and select the testing scenario AIScen
• click on the button Load
If there is no error, then congratulations! You did everything correctly and made an AI script!
THE BASICS OF XS, AN AOE3 SCRIPTING LANGUAGE
WHAT IS XS?
Nice-to-know but unnecessary information, XS stands for eXternal Subroutine. It is a language that allows us to tell the game to do something for us. In Age of Empires 3, XS is an extremely simplified version of C++ that is used for writing AI scripts, random map scripts, triggers, UI and certain commands used in modding.
When we want to tell the AI what to do, we write a series of codes (instructions or statements) in a text document and we save it as an .xs file. Then, we tell the game to load it and the game will play the role of the dude between the AI (the virtual player) and the AI Script (or... more like the parsed result). Therefore, we have to know how to "speak in XS" (lol) before we can communicate properly with the AI. A super cool bonus: once you're familiar with XS, you can also write your own random map scripts, create your own trigger conditions/effects or even use more advanced tools if you're willing to do more than AI scripting.
The long talk ends here, let's get to the point.
I. COMMENTING THE CODES
Since we are writing codes, i.e. something that is not in plain English, it is sometimes useful to explain what are these codes for, especially when you work with other people on the same script. We can do that by commenting, because comments are ignored by the game when it reads the script, which means we can write comments in plain English.
There are two types of comments:
1. Single-line comments
Single-line comments begin with a double slash //, and anything that is written after it (on the same line) is a comment:
Code: Select all
// This is a comment. We are going to write a code below:
float PI = 3.141592; // This is another comment: we are defining a variable named PI
Multi-line comments are written between /* and */:
Code: Select all
/* Welcome to my script!
I love writing scripts!
I also love commenting!
Goodbye! */
II. VARIABLES (PART I)
Variables are elements of XS for storing and manipulating the informations that will be needed throughout the execution of the script. For example, imagine a scenario with two teams: the government (played by you) and the rebels (played by the AI). We want the rebels to resign when they lose their Barracks three times. For that, we can define a variable number_destructions in the AI script for storing a number (initially 0) and adding +1 to it each time a Barracks of the AI is destroyed, and write a code to make the AI resign when the value of number_destructions reaches 3.
1. Defining a variable
Before we can use and manipulate a variable, we must define it first. The definition of a variable looks like this:
Code: Select all
type name = value;
• name is the name of the variable. There are a few rules for naming:
- a variable name must be unique: several variables can't have the same name. In other words, we are not allowed to do more than one definition using a certain variable name. Note, though, that XS is case sensitive, which means, for example, that AA, aa, Aa and aA are all different.
- a variable can't have the same name as a function. See IV. FUNCTIONS further below.
- a variable name can only contain alphanumeric characters and underscore: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9 _
- a variable name cannot start with a number: 1stName is INCORRECT. For example, use firstName.
• = is known as assignment operator. It is for assigning the value on the right side of it to the variable on the left side of it.
• value is the value (or information) to be stored by the variable. It must correspond to type.
• ; (semicolon) indicates the end of an instruction. The instruction here is a variable definition, but we'll discover more instructions later. Most instructions end with a semicolon, but there are a few exceptions that we will talk about, later.
2. Data types
Integers (int)
These are integer numbers. They can be negative like, for example, -273 or positive like 100. To simplify, 0 (zero) counts as positive.
According to the template we've seen in 1. Defining a variable, here are a few examples of the definition of an integer variable:
Code: Select all
int zeroKelvinCelcius = -273;
int zeroCelcius = 0;
int numberResourceTypes = 8; // Food, Wood, Gold, Fame, Trade, XP, Ships, SkillPoints
int numberResourceSubTypes = 7; // Easy, Herdable, Hunt, HuntAggressive, Farm, Fish, Trade
These are numbers with a decimal part, like 3.141592. Like integers, they can be negative or positive. To simplify, 0.0 (zero) counts as positive.
According to the template we've seen in 1. Defining a variable, here are a few examples of float variable definition:
Code: Select all
float zeroKelvinCelciusForReal = -273.15;
float PI = 3.141592;
float treatyRadius = 80.0;
These are... uhm, texts? I don't know what exactly is accurate for explaining strings but yes, we can say strings are texts:
Code: Select all
string myFavUnit = "The Tommynator";
string reason = "Because I suck";
Booleans (bool)
A boolean variable is a variable that can store only true or false.
Code: Select all
bool aBoolVariable = true;
bool anotherBoolVariable = false;
Vector variables mainly store coordinates of the terrain, but their use isn't limited at that, though it is way too early to talk about vectors because I haven't even talked about functions yet.
It might be useful to know that a coordinate in Age of Empires III has 3 components:
• the X component corresponds to the X axis that starts from the extreme south to the extreme east
• the Z component corresponds to the Z axis that starts from the extreme south to the extreme west
• the Y component corresponds to the elevation
3. Updating a variable
Once a variable is defined, we can update it as much as we want:
Code: Select all
int hitpoints = 5;
hitpoints = 4;
hitpoints = 3;
float size = 10.0;
size = 5.0;
size = 2.5;
size = 1.25;
4. Getting the value of a variable
We can also get the value of a variable (of course we can, the whole point of a variable is for storing and retrieving values into and from it). For example, we can get the value of A and store it to B:
Code: Select all
int A = 1;
int B = A;
And we can do the following operations with variables:
• Addition +
• Subtraction -
• Multiplication *
• Division /
• Modulo % but it is rarely used. It's for calculating the remainder in a division, for example: the result of 3 % 2 is 1, which is useful for checking if a number is even or odd.
• Incrementation ++ which is the act of adding +1 to the value of a variable: i++ is exactly the same as i = i + 1
• Decrementation -- which is the act of adding -1 to the value of a variable: i-- is exactly the same as i = i - 1
• To negate the value of a variable, do it this way: a = -1 * a or a = 0 - a, because scripts Age of Empires III don't support the a = -a operation.
A few examples:
Code: Select all
int a = 0;
int b = 1;
int c = a + b; // The value of c is 1
a++; // It's like writing a = a + 1;
float d = 3.2 * 0.5;
d = d / 5.0;
c = 0 - c; // The value of c is now -1
You can put different strings together with the concatenation operator: +
Code: Select all
string firstName = "Alistair";
string lastName = "Jah";
string fullName = firstName+" "+lastName; // fullName is "Alistair Jah" now
Code: Select all
string one = "1";
string two = "2";
string twelve = one+two; // The result is "12", not "3"!
Code: Select all
string hello = "I just wanted to say \"Hello\"... Hello!";
Code: Select all
string hello = "I just wanted to write a backslash: \\";
We're getting back to booleans.
We can compare different values and when we do so, the result is a boolean value, i.e. true or false. There are 6 operators for comparisons:
• equal == (don't confuse it with the assignment operator!)
• not equal !=
• greater than >
• lesser than <
• greater than or equal >=
• lesser than or equal <=
We can store comparisons in boolean variables:
Code: Select all
bool b = ( 0 == 1 ); // false
b = ( 0 < 1 ); // true
We can also do logic operations with booleans, using the operators && (AND) and || (OR):
• true && true is true
• true && false is false
• false && true is false
• false && false is false
• true || true is true
• true || false is true
• false || true is true
• false || false is false
Code: Select all
bool a = ( 0 == 1 ); // false
bool b = ( 0 <= 1 ); // true
bool c = a && b; // false
c = a || b; // true
The order of operations rule applies to all operations (including the boolean ones) in an AOE3 script. Especially, with nested brackets like this one:
((a + b) * ( ( c + d ) / e ) + f ) * g
the innermost operations are executed first:
1. c + d (let's say it's w)
2. w / e (let's say it's x)
3. x + f (let's say it's y) and a + b (let's say it's z)
4. z * y * z
7. Constants
There are certain values that have a name and never change, like π (PI). You can store them in a special type of variable known as constant. To make a constant, add the keyword const when defining:
Code: Select all
// Syntax: const type name = value;
// Example:
const float PI = 3.141592;
const float treatyRadius = 80.0;
Code: Select all
// Something like this will instantly produce an error:
int thisOneIsNotConstant = 50;
const int thisOneIsConstant = thisOneIsNotConstant;
Code: Select all
// This, however, is fine:
const int thisIsAConstant = -1;
const int thisIsAnotherConstant = thisIsAConstant;
Code: Select all
// No further comment
const int cStrategyBoom = 0;
const int cStrategyRush = 1;
int gStrategy = cStrategyRush; // By default, we're rushing
It is possible to store the value of a float variable in an int variable and vice versa. That is known as data conversion. A data conversion from a bigger data type to a smaller one leads to data loss:
Code: Select all
float aCertainNumber = 23.145;
int thatNumberAsInteger = aCertainNumber; // The value of this variable is 23, meaning that some part of information has been lost
float to int
Code: Select all
float a = 23.145;
int b = a;
Code: Select all
int a = 10;
float b = a;
Code: Select all
bool a = true;
int b = a;
Code: Select all
bool a = true;
float b = a;
We must concatenate though:
Code: Select all
bool a = true;
string b = ""+a;
III. CONTROL STRUCTURES
A script is not just a linear sequence of instructions. Sometimes we want certain instructions to be executed only if a certain condition is fulfilled. Sometimes we want to repeat certain instructions while a certain condition is fulfilled. For that, we use control structures. This section gives me the opportunity to introduce the concept of compound statement, also known simply as block. A block is simply a set of instructions that are put together between braces {} to be executed as one instruction.
1. Conditional structures: if and else statements
These structures look like this:
Code: Select all
if ( boolean )
{
instructions A
}
else
{
instructions B
}
Code: Select all
if ( boolean )
{
instructions A
}
Code: Select all
if ( boolean )
{
instructions A
}
else if ( boolean )
{
instructions B
}
else
{
instructions C
}
The braces are not mandatory if there is only one instruction to execute:
Code: Select all
if ( boolean )
instruction; // Only one instruction
else if ( boolean )
instruction; // Only one instruction
else
instruction; // Only one instruction
A while loop is used for repeating a set of instructions while a certain condition is true. As soon as it's false, the loop is broken and the instructions aren't executed anymore.
Code: Select all
while( boolean )
{
instructions;
}
Code: Select all
int i = 0;
string message = "Counting from 0 to 9: ";
while( i < 10 )
{
message = message + i + ", ";
i++;
}
message = message + " done!";
• the while structure compares if it's lesser than 10
• it is, so the instruction block is executed
• after i++, the value of i is now 1
• the while structure compares again if it's lesser than 10
• it is, so the instruction block is executed
• and the process is repeated!
• at some point, the value of i is 10
• the while structure compares again if it's lesser than 10
• it isn't, so the loop is broken
By the end of the loop, the value of message is "Counting from 0 to 9: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, done!".
As long as boolean is true, the loop repeats, and as soon as it's false, the loop stops. So, please make sure you don't write an infinite loop because the game will freeze and sometimes, even the task manager won't help you.
3. Iteration structure: the for loop
The for loop is used for repeating a set of instructions a certain number of times. It looks like this:
Code: Select all
for( variableName = initialValue; comparisonOperator finalValue )
{
instructions
}
As for comparisonOperator finalValue, in order to avoid an infinite loop, do as follows:
• if the value of variableName is less than that of finalValue, comparisonOperator should be < or <=
• if the value of variableName is greater than that of finalValue, comparisonOperator should be > or >=
• if comparisonOperator is <, then instructions will be executed finalValue - initialValue times and the value of variableName is automatically incremented in each iteration.
• if comparisonOperator is <=, then instructions will be executed finalValue - initialValue + 1 times and the value of variableName is automatically incremented in each iteration.
• if comparisonOperator is >, then instructions will be executed initialValue - finalValue times and the value of variableName is automatically decremented in each iteration.
• if comparisonOperator is >=, then instructions will be executed initialValue - finalValue + 1 times and the value of variableName is automatically decremented in each iteration.
• the value of variableName can be used in instructions, but can't be modified (i.e. you can't assign any value to variableName)
Jump statements: the break statement
As soon as it sees a break in a loop, the AI stops that loop even when the boolean for allowing the loop is true, and the rest of the instructions after break is ignored. Here's an example: we are going to stop a while loop before its "true" end:
Code: Select all
int i = 0;
while( i < 50 )
{
if ( i == 10 )
break;
i++;
}
As soon as it sees a continue in a loop, the AI ignores the rest of the loop in the current iteration and jumps directly to the start of the next iteration. As an example, we are going to "skip" the loop for i == 5:
Code: Select all
string message = "Counting from 0 to 9: ";
for( i = 0; < 10 )
{
if ( i == 5 )
{
message = message + " skipped, ";
continue;
}
message = message + i + ", ";
}
4. The switch structure (also known as selective structure)
The basic principle of the switch structure is to take a value (valueToEvaluate), then check if it's equal to one of a few values (valueA, valueB, etc.), and in that case, execute the corresponding instructions:
Code: Select all
switch( valueToEvaluate )
{
case valueA:
{
instructions A
break;
}
case valueB:
{
instructions B
break;
}
case valueC:
{
instructions C
break;
}
// Add as many as necessary
default:
{
default instructions
break;
}
}
1. the switch structure evaluates valueToEvaluate (which can be a variable or an operation like a + b etc.)
2. now, it checks whether or not it is equal to valueA
3. if it is, then instructions A are executed, and break; makes the AI exit this switch structure.
4. if it isn't, then valueB is checked if it's equal to valueToEvaluate
5. if it is, then instructions B are executed, etc.
If none of the values is equal to valueToEvaluate, then the default instructions are executed, though the entire default:{} block is optional.
IV. FUNCTIONS
A function has the following characteristics:
• a return type: can be void, bool, int, float, string or vector. When the function's return type is void, it doesn't return any value, else it returns a value of the same type as its return type (a bool returns a boolean value, etc.)
• a name: the rules for naming a function are the same as those of a variable:
- a function name must be unique: several functions can't have the same name. The case sensitiveness also applies.
- a function can't have the same name as a variable.
- a function name can only contain alphanumeric characters and underscore _.
- a function name cannot start with a number.
• zero, one or several parameters: parameters are similar to variables (have a type, a name and a value), are specific to the function in which they are defined and can be used in that function to change the way that function works.
• a group of statements: when the function is called, those statements are executed. Those statements can use the values of the function's parameters.
1. Defining a function
Before we can call a function, we must define it first. The definition if a function looks like this:
Code: Select all
returnType functionName( parameter1Type parameter1Name = parameter1DefaultValue/*, parameter2Type parameter2Name = parameter2DefaultValue, ...*/ )
{
statements
return( returnValue );
}
Also, if the function is not supposed to have parameters, we simply write void:
Code: Select all
returnType functionName( void )
{
statements
return( returnValue );
}
Code: Select all
returnType functionName( void )
{
statements
if ( boolean )
{
return( returnValue ); // If boolean is true, the function ends here after returning returnValue
}
return( returnValue ); // If the function didn't end at this point, it ends here after returning returnValue
}
Once a function is defined, it can be called. A function call usually looks like this:
Code: Select all
// Assuming that the variable has already been defined before:
variable = functionName( parameters );
If the function's return type is void, it can't be assigned to a variable, so we simply call it:
Code: Select all
functionName( parameters );
I don't know about the rest of the game, but Ensemble Studios provided 619 functions to be used for AI script development. Some of them simply don't work, while some others are working but aren't really useful, but these are not many.
All these 619 functions are already defined in the game (we say that they are native to the game), we don't need to defined them - we can't anyway, doing that will produce an error - we directly call them. You can find them all here. They are split in 3 groups: XS functions, AI functions and KB functions.
XS functions
There are 44 XS functions, which are all used for scripting purpose - they are all elements of the XS language the same way as variables and operators are elements of many programming languages.
AI functions
There are 285 AI functions, most of them are used for AI - gathering, building, researching, etc.
KB functions
There are 290 KB functions. KB stands for Knowledge Base. KB functions are used for getting informations on the current state of the game (number of units, current scores, amount of resources, etc.)
Don't be afraid by these numbers, those mean nothing! This tutorial will try to show most of the most used functions in AI scripting. Later on, you can always look at the reference if you're curious and want to discover more things.
4. The main function
Do you remember our AAI.xs script? We already defined a function in it! That function is the main function. It is mandatory in all Age of Empires III scripts. It is the very first function that is called when the script is executed. We don't need to write main() anywhere, the AI calls it for us. If we want to call other functions, we simply write the call statement in the main function or in another function that we call in main().
At this point, we've got enough knowledge for a relatively proper test. For that, you are going to need a function: aiChat. It is used for the AI to send a chat to a player:
Code: Select all
// Syntax: aiChat( int playerID, string message );
// Example:
aiChat( 1, "Hey, what's up?" ); // This sends "Hey, what's up" to player 1
Code: Select all
void main( void )
{
aiChat( 1, "Hey, what's up?" );
}
Code: Select all
int randomNumber = aiRandInt( 10 ); // One of the numbers from 0 to 9
Here's an example of mine, but please imagine your own fictive message and write your own script based on it!
Code: Select all
void main( void )
{
string food = "";
switch( aiRandInt( 5 ) ) // aiRandInt( 5 ) returns either 0, 1, 2, 3 or 4
{
case 0:
{
food = "french fries";
break;
}
case 1:
{
food = "hamburger";
break;
}
case 2:
{
food = "pizza";
break;
}
case 3:
{
food = "sandwich";
break;
}
case 4:
{
food = "taco";
break;
}
}
aiChat( 1, "Guys, I'm gonna eat some "+food+"!" );
string countdown = "In ";
for( i = 3; > 1 )
{
countdown = countdown + i + ", ";
}
countdown = countdown + " I'm out!";
aiChat( 1, countdown );
if ( aiRandInt( 2 ) == 1 ) // aiRandInt( 2 ) returns either 0 or 1
aiChat( 1, "I'm back!" );
}
For the second test, you must define your own functions and call them. See 1. Defining a function and 2. Calling a function and the previous test if you need help.
Here is an example of mine, you can copy and try it to get familiar with functions first, and make your own later.
Code: Select all
float min( float a = 0.0, float b = 0.0 )
{
if ( a < b )
return( a );
return( b );
}
float max( float a = 0.0, float b = 0.0 )
{
if ( a < b )
return( b );
return( a );
}
float abs( float a = 0.0 )
{
if ( a >= 0.0 )
return( a );
return( 0.0 - a );
}
void main( void )
{
int a = aiRandInt( 10 );
int b = aiRandInt( 10 );
aiChat( 1, "The bigger number is " + max( a, b ) + " while the smaller number is " + min( a, b ) );
int c = min( a, b ) - max( a, b );
aiChat( 1, "The actual value of c is " + c + " and its absolute value is " + abs( c ) );
}
Nice, we can now talk about the rest of what there is to talk about variables. This part is rather short and easy to understand.
1. Variable scope
There are two places where you can define variables:
• Inside a function:
Code: Select all
void main( void )
{
int variable = -1; // This variable has been defined inside a function
}
Code: Select all
int variable = -1; // This variable has been defined outside a function
void main( void )
{
}
Code: Select all
void function( void )
{
int a = -1;
/* The variable a can only be accessed inside this function,
so it literally doesn't exist anywhere else*/
}
void anotherFunction( void )
{
int a = 0;
/* It particularly means that you can define a variable with
exactly the same name in another function, since these variables
are different (can contain different values)*/
}
A variable that is defined outside a function is a global variable. It is accessible anywhere after its definition (not before):
Code: Select all
/* The following variable is a global variable.
Any function can access it and modify its value,
but the name of it can't be used anymore when
defining a new variable or defining a function argument*/
int a = -1;
void function( void )
{
// Even here, you can't define a variable named "a"
a = 0; // But you can modify the value of the global variable "a"
}
void anotherFunction( void )
{
// In fact, any function can access the value of "a":
int b = a + 10;
// Or modify it:
a = 0;
// Obviously, the value will only change if the function is called somewhere
}
2. Static variables
A static variable is a local variable that is initialized at the first call of the function associated with it and that keeps its value between the calls of that function.
In other words, when that function is called for the first time, the static variable is defined and initialized, and when the function is terminated, the variable keeps the last value that was assigned to it. The next time the function is called, the definition statement is skipped and the variable still contains the value that was assigned to it in the previous function call. See the difference by testing this:
Code: Select all
void function( void )
{
int aLocalVariable = 0;
static int aStaticVariable = 0;
aLocalVariable++;
aStaticVariable++;
aiChat(1, "The value of aLocalVariable is "+aLocalVariable);
aiChat(1, "The value of aStaticVariable is "+aStaticVariable);
}
void main( void )
{
function();
function();
function();
function();
function();
}
An array is a series of elements of the same type that are indexed. In other words, there are several elements in one array, and each element has its own unique index which is a number.
For example, five string elements can be defined in one array so you don't have to define five string variables, and you can access these strings later using the array and the proper index.
1. Defining an array
Like a variable and a function, you must define an array before you can use it.
An array has five characteristics:
• its type: an array of type X can only contain values of type X. There are five types: bool, int, float, string and vector
• its ID: when you define an array, it gets an ID number that permits you to distinguish it from other arrays. To make it simpler for you to work with that array, you can store its ID in an int variable.
• its size: an array of size Y can only contain Y elements.
• its default value: if no value is assigned to array in a certain index, it gets the default value.
• its description: apart from the ID, it's an additional way to make the difference between different arrays. The description must be unique, i.e. several arrays can't have the same description.
The definition of an arrays looks like this:
Code: Select all
int arrayID = xsArrayCreateType( size, defaultValue, description );
• if the array is a bool array, defaultValue must be a bool value, and the function is xsArrayCreateBool
• if the array is an int array, defaultValue must be an int value, and the function is xsArrayCreateInt
• if the array is a float array, defaultValue must be a float value, and the function is xsArrayCreateFloat
• if the array is a string array, defaultValue must be a string value, and the function is xsArrayCreateString
• if the array is a vector array, defaultValue must be a vector value, and the function is xsArrayCreateVector
2. Assigning values
Value assignment looks like this:
Code: Select all
xsArraySetType( arrayID, index, value );
• index is the index of the element. It starts from 0 (zero), which means that the first element of the array is in index 0, the second element is in index 1, etc.
• if the array is a bool array, value must be a bool value, and the function is xsArraySetBool
• if the array is an int array, value must be an int value, and the function is xsArraySetInt
• if the array is a float array, value must be a float value, and the function is xsArraySetFloat
• if the array is a string array, value must be a string value, and the function is xsArraySetString
• if the array is a vector array, value must be a vector value, and the function is xsArraySetVector
3. Accessing values
Value retrieval looks like this:
Code: Select all
xsArrayGetType( arrayID, index );
Code: Select all
// Assuming that "variable" has already been defined before
variable = xsArrayGetType( arrayID, index );
4. Getting an array's size
For that, you use the function xsArrayGetSize:
Code: Select all
xsArrayGetSize( arrayID )
I was about to tell you to try to get it all on your own this time, by trying your own things as usual, but I ended up writing this small section anyway.
Code: Select all
void main( void )
{
int OttoDeck = xsArrayCreateString( 5, "Card", "Don't check the deck because this is just an array, not a deck builder" );
xsArraySetString( OttoDeck, 0, "HCShipSettlers3" );
xsArraySetString( OttoDeck, 1, "HCShipFoodCrates3" );
xsArraySetString( OttoDeck, 2, "HCShipFoodCrates2" );
xsArraySetString( OttoDeck, 3, "HCShipCoinCrates3" );
xsArraySetString( OttoDeck, 4, "HCShipJanissaries1" );
for( deckIndex = 0; < xsArrayGetSize( OttoDeck ) )
{
aiChat(1, "OttoDeck array, index "+deckIndex+": "+xsArrayGetString( OttoDeck, deckIndex ));
}
}
Rules are void functions that are called periodically in a certain interval of time. Plus a few more details.
1. Defining a rule
A rule has the following characteristics:
• a name. Since a rule is a (void) function, the rules for naming a rule are the same as those of a function. Especially, a function and a rule can't have the same name.
• a state: active or inactive. An active rule is called periodically in a certain interval of time and can be deactivated. An inactive rule is like a mere void function. Be it active or inactive, a rule can be called like a void function.
• a launch method: runImmediately or not. When a rule has the runImmediately symbol, it's immediately called upon activation, and the timer for periodical calls starts. When it doesn't have the runImmediately symbol, the timer starts first and the rule is called only when the countdown reaches 0 (zero).
• a call interval: highFrequency or minInterval and/or maxInterval. highFrequency makes it that the rule is called extremely frequently, on the level of hundreds (maybe thousands!) or calls per second. minInterval and maxInterval makes it that the rule is called every X seconds (must be an integer constant number, can't be 0 or negative).
• a rule group. Several rules can be assigned to one group so they can be activated/deactivated together by simply activating/deactivating the group.
Code: Select all
rule ruleName
active // or inactive. You can't write both
runImmediately // omit this line if you don't want the rule to run immediately
highFrequency // omit this line if you want to assign a minInterval or maxInterval
minInterval X // X is an integer number greater than 0. Omit this line if you want highFrequency
maxInterval X // X is an integer number greater than 0 and greater than minInterval. Omit this line if you want highFrequency
// Note that if highFrequency, minInterval and maxInterval are all missing, highFrequency is the default
// Also, you can put minInterval without maxInterval and vice versa
group groupName // groupName is a string without quotes
{
instructions // I told you, a rule is a void function: you can write instructions here.
}
Code: Select all
rule aSimpleRuleToCallEvery5Seconds
active
minInterval 5
{
static int aStaticVariable = 0;
aStaticVariable++;
aiChat(1, "The value of aStaticVariable is "+aStaticVariable);
}
void main( void )
{
}
You can activate an inactive rule by calling the function xsEnableRule:
Code: Select all
rule aSimpleRuleToCallEvery5Seconds
inactive
minInterval 5
{
static int aStaticVariable = 0;
aStaticVariable++;
aiChat(1, "The value of aStaticVariable is "+aStaticVariable);
}
void main( void )
{
xsEnableRule("aSimpleRuleToCallEvery5Seconds");
}
Code: Select all
rule aSimpleRuleToCallEvery5Seconds
active
minInterval 5
{
static int aStaticVariable = 0;
aStaticVariable++;
aiChat(1, "The value of aStaticVariable is "+aStaticVariable);
}
rule deactivatorThatWillAlsoDeactivateItself
active
minInterval 30
{
xsDisableRule("aSimpleRuleToCallEvery5Seconds");
xsDisableSelf();
}
void main( void )
{
}
To try your own things, add the runImmediately symbol for example to see the difference between its presence and absence.
3. Rule groups
You can create a group by simply writing group groupName in a rule (groupName is the name of the rule group).
Code: Select all
rule rule1
inactive
minInterval 3
group timingRules
{
static int call = 0;
call++;
int time = xsGetTime()/1000;
aiChat(1, "This is the call number "+call+" of rule1 "+time+" seconds after the game has started.");
}
rule rule2
inactive
minInterval 5
group timingRules
{
static int call = 0;
call++;
int time = xsGetTime()/1000;
aiChat(1, "This is the call number "+call+" of rule2 "+time+" seconds after the game has started.");
}
rule rule3
inactive
minInterval 7
group timingRules
{
static int call = 0;
call++;
int time = xsGetTime()/1000;
aiChat(1, "This is the call number "+call+" of rule3 "+time+" seconds after the game has started.");
}
void main( void )
{
xsEnableRuleGroup("timingRules");
}
VIII. Modular programming
When you are making a big big scripting project, it might be useful to split your big script file into several files of smaller size which serve different purposes. For example, you can have a file for all math functions, another file for gathering tasks, another file for attacking etc. You can then link them together using the include line:
Game Folder\AI3\math.xs
Code: Select all
float abs( float a = 0.0 )
{
if ( a >= 0.0 )
return( a );
return( 0.0 - a );
}
Code: Select all
void sendChatToAllies( string message = "" )
{
for( player = 0; < cNumberPlayers )
{
if ( kbIsPlayerAlly( player ) == true )
aiChat( player, message );
}
}
Code: Select all
include "math.xs";
include "chat.xs";
void main( void )
{
sendChatToAllies( "Dear ally, greetings!" );
}
Game Folder\AI3\utility scripts\chat.xs
Code: Select all
void sendChatToAllies( string message = "" )
{
for( player = 0; < cNumberPlayers )
{
if ( kbIsPlayerAlly( player ) == true )
aiChat( player, message );
}
}
Code: Select all
include "math.xs";
include "utility scripts/chat.xs";
void main( void )
{
sendChatToAllies( "Dear ally, greetings!" );
}
extern const float PI = 3.141592;
extern bool isMapWater = false;
CONCLUSION
Aaand that's it for the first part! Though, later, I could rewrite a tiny bit if I deem it necessary. I hope you were attentive enough and practiced a lot, because understanding this first part is important for the rest of the tutorial!
Also... well, that's all, I think
See you in Part II!