CTP2 Bureau : Locutus' Ultimate Guide To SLIC
CTP2 Bureau
The Modding
Knowledgebase
WePlayCiv: CtP2 Downloads
WePlayCiv: Call To Power forum
Apolyton Call To Power forums
Civilization Fanatics: Call To Power

Locutus' Ultimate Guide To SLIC

Not quite as 'ultimate' as the title suggests, because it's unfinished. It's an elaborate manual that intends to explain to programming newbies and veterans alike (but with a focus on newbies) the all the details of the SLIC programming language. The most important part that's missing is the part that deals with messages, other than that it's reasonably complete. However, I do hope to get around to doing a complete makeover of that guide, as a lot of new info, examples and other useful stuff could be added to all chapters. Still a very important resource for beginning SLIC programmers though.

by Wouter Snijders aka Locutus

1. Introduction/Elementary Information/Before you get started

1.1 What is SLIC?
1.2 What does a SLIC-script do?
1.3 How is a SLIC script built up?
1.4 What can/can't you do with SLIC?
1.5 What you need to know before you read on
1.5.1 Where to put your code
1.5.2 What should your code look like
1.6 Comments
1.7 Internal SLIC utility vs EditPlus vs NotePad

2. Arithmatic

2.1 Large calculator

The term 'computer' comes from the verb 'to compute' which means as much as 'to calculate'. (This may seem trivial to English readers but for some people who's first language isn't English it's not. But even most people who's first language *is* English might take the term 'computer' so much for granted that they don't even realize this.) But that would suggest that a computer is nothing more than a large calculator! Well, that is absolutely true. Proof of this is the fact that a large part of all instructions that the average CPU (or computer chip, which is the core of every PC) gets, consists of basic arithmetic instructions such as addition, substraction or multiplication. The majority of the rest of the instructions takes care of loading and storing information to/from memory and 'moving around' in the program code (for which control structures are responsible, see the next chaptor for that). More complicated mathematics is generally achieved by combining these basic arithmetic instructions in a smart way (a simple exampe: 3 squared is the same as 3 * 3 (which is the computer version of 3 x 3) or, even more simplified, 3 + 3 + 3).

2.2 Arithmatic in SLIC

Because of this importance of simple arithmetic, just about every programming or scripting language (every language that I know of anyway) is capable of performing at least basic arithmatics and SLIC is no exception. The basic arithmetic operations that SLIC can handle are the following (they are all explained below):

Numerical:
'-'  (substraction)
'+'  (addition)
'/'  (division)
'*'  (multiplication)

Relational:
'=='   ("equal to")
'!='  ("unequal to")
'>' & '<'  ("greater than" and "smaller than")
'>=' & '<='  ("greater than or equal to" and "smaller than or equal to")

Logical/Boolean:
'&&'  (AND)
'||'  (OR)
'!'  (NOT)

Miscellaneous:
'(' & ')' (brackets)

(For the (Java/C-)programmers out there: no, there is no increment (++) or decrement (--) and there's no power operation (^) either)

Though most people will know a lot of these operations, I'll give short expanations and examples of each one of them. All these examples can be tested in SLIC by using the following code:

 int_t x;
 int_t y;
 HandleEvent(BeginTurn) 'LOC_MathTest' post {
  x = <expression1>;
  y = <expression2>;
  Message(1, 'LOC_MathTest_M');
 }
 Messagebox 'LOC_MathTest_M' {
  Show();
  Text(ID_LOC_MATHTEST);
 }

The textfiles should contain something like this:

 LOC_MATHTEXT "The result of the first expression is {x} and the result of the second expression is {y}"

With this code you can calculate two expressions (an expression is a combination of operations, so either one operation or several operations combined) at once. To calculate only one expression remove all references to y (in the code as well as in the textfile) and to calculate three expressions add references to e.g. z. <expression1> and <expression2> can be any of the examples that I give below.

2.3 Numerical operations

Numerical operations in SLIC can only be applied to whole numbers, fractions or decimal numbers aren't supported. (You might want to mention someplace, not necessarily here, that in the Databases, floating point values are multiplied by 100 and converted to ints.) I still haven't figured out the exact range of numbers, but it's large enough for anything you'll ever need: I've been able to use numbers larger than 100 million and smaller and -100 million without losing accuracy in calculations (but if you actually ever need to use such large numbers double-check accuracy anyway, I didn't check extremely thoroughly and it's not impossible that accuracy is lost with such high numbers).

Addition
Addition is so basic it really doesn't need a example but here is one anyway:

 4 + 2

The result will be... Oh, come on, do I really have to say this?

Substraction
Substraction is, like addition so basic it needs no explanation. One thing that's worth mentioning is that SLIC can deal with both positive and negative numbers, something less trivial then it may seem. So, an example:

 4 - 12

Which will result in -8.

Multiplication
Everyone knows this operation as well, but for those people who don't speak English very well and may not be familiar with the word: multiplication is basicly the repeatedly adding the same number up with itself. Like this:

 4 * 3

This is the same as 4 + 4 + 4 and hence results in 12. Note that you don't use the 'x' symbol for this, like you may have learnt in primary school, but '*'. This is because if you use variables (which will be introduced further in this chapter) you could get in trouble. A computer doens't know what to do with something like "x x x" (hell, I don't think I would either), whereas "x * x" doesn't cause any problems.

Division
Division is the opposite of multiplication and, from the viewpoint of a computer, the most complicated of the basic operations. A simple example would be:

 15 / 3

The result for this is 5. A more complicated example, which illustrates exactly why this operation is so complicated for computers:

 25 / 2

or:

 25 / -2

What would the results for these expressions be? We humans would immediately say 12.5 and -12.5 but as I already mentioned earier, SLIC works with integers (a type of numbers that is often used in programming and scripting languages) and integers have to be whole numbers. How this problem is solved differs per programming/scripting language, but SLIC's solution is to cut off the decimal part of the result of any calculation that isn't an integer. This means 12.5 becomes 12 and -12.5 becomes -12. Partial results of expressions are also cut off, so:

 3 /4 + 3 / 4

is 0 + 0 = 0 and not 0.75 + 0.75 = 1.5 and thus 1, as you might expect.
Also, there the problem of division by zero. This is in normal mathematics not defined, so the result of:

 12 / 0

should be 'undefined', but a computer doesn't know what that is or how to deal with it. In most other languages a division by zero will result in an error (a division-by-zero error), but SLIC is in this aspect different, the result of a division by zero in SLIC results in 0.

The conclusion of all this is that you *can* use divisions in SLIC but that you will have to be very careful with them because it's very easy to get inaccurate or non-sense results if you make any mistakes with them or work carelessly. So if you ever get any unexpected results in your calculations, double-checking your divisions is probably one of the first things to do.

2.4 Brackets

Brackets aren't really arithmatic operations but they can be used in combining simple arithmatic combinations into more complex ones. For example, without brackets it may not be clear to you (or a computer) what the result of 3 * 2 + 5 would be. It could be 11 (6 + 5) or it could be 21 (3 * 7). There is a order of precedence that the computer applies and it is basicly the same as the one most people will have learnt in school (see the end of this chapter), but in complicated expressions it's wise to rely on brackets rather then on the order of precedence because it will prevent a lot of errors and make the code much more readable. An example of how the use of brackets (and divisions) can make a diffence:

 2 / 1 * 6 + 4 / 2 * 2 - 5 + 4 / 2 * 3 + 1

This would be so much clearer if it was written like this:

 (2 / 1) * 6 + (4 / 2) * 2 - 5 + (4 / 2) * 3 + 1

But it would be quite different if it was written like this:

 2 / (1 * 6) + 4 / (2 * 2) - (5 + (4 / 2) * 3 + 1)

The result of the the first two expressions is (2 * 6) + (2 * 2) - 5 + (2 * 3) + 1 = 18 whereas the last expression equals (2/ 6) + (4 / 4) - (5 + (2 * 3) + 1) = -10 2/3, or -10 for SLIC and approximately -10.667 for humans.

2.5 Boolean logic

But primary school math is not all the arithmetic SLIC can handle. There's also the logical, or Boolean, arithmatic. Many people will be less familiar with this than with the arithmetic discussed in the previous section, but it is in fact much more often used and hence more important then the 'regular' arithmetic, while being just as easy to understand, if not easier.

Boolean logic all revolves around two values, true and false. Usually false is represented by a 0 and true by 1 (or, more accurately, any non-zero value). The result of these calculations is often used to make decisions: if an expression is true, then do this, if it is false then do that (or do nothing at all). Boolean values are, at least in SLIC, stored in the same way as integers so you can use the same code to test these expressions as the code used for numerical expressions, only will the result now always be either 1 or 0.

2.5.1 Relational operators

One subgroup of Boolean logic is the group of relational expressions. The values that they get are normal integers but the result of these functions is a Boolean value. Though I presume everyone will know them, I'll give a explanations and examples anyway.

Greater than
Returns true if the left value is greater than the right value:

 (3 > 1)

returns true.

(Strictly speaking the brackets aren't necessary but if inserted in the code, they make the code more readable IMHO)

Smaller than
Returns true if the left value is smaller than the right value:

 (3 < 1)

returns false.

Greater than or equal to
Retuns true if the left value is greater than or equal to the right value (the symbol is '>=' because the classic symbol for it can't be found on regular keyboards):

 (-34 >= 3)

returns false.

Smaller than or equal to
Returns true if the left value is smaller than or equal to the right value (the symbol is '<='):

 (3 <= 3)

returns true.

Equal to
Returns true if the left value is equal to the right value (the symbol is '==' to prevent confusion for both computer and human, '=' is already used for assigning values to variables, see next chapter):

 (4 == 4)

returns true.

Unequal to:
Returns true if the left value is not equal to the right value (the symbol is '!=' because the classic symbol isn't available on regular keyboards and '!' already means NOT, see further):

 (4 != 4)

returns false.

2.5.2 Boolean operators

When calculating with integers we can simply make complex expressions out of simple ones by using the same operations in the correct manner (so not "3 3 + * 4" but "3 + 3 * 4"). In Boolean logic this works slightly different. We can't just say "6 < 3 >= 8". SLIC does give back a result for this, but it is something that we can't be satisfied with. In this specific example the result will be false return false (6 < 3 = false, hence 0 and 0 >= 8 is also false). This problem is caused by the fact that relational expressions have integers as 'input' but Booleans as 'output' while booleans are really just normal integers in SLIC. So what we need is a few operations that have Booleans as both 'input' and 'output'. These are the Boolean operations and there are only three of them, simply because more are not needed, with these three any logical expression (with two arguments) can be formed.

AND
This functions returns true if both operands are true. The symbol is '&&':

 (4 < 3) && (2 > -8)

equals "false AND true" and thus returns false.

OR
This function returns true if at least one of the operands is true (so it also returns true if both elements are true, which some people find illogical but makes perfect sense to others). The symbol is '||':

 (1 < 5) || (9 > 3)

equals "true OR true" and thus returns true.

NOT
This function only has one operands and retuns the opposite of its value. The symbol is '!':

 !(1 < 5)

equals "NOT true" and thus returns false.

So let's say you want to make sure that 8 is a number larger than or equal to 5 or smaller than 2, but not 9 (in this case a trivial exercise but it will prove very useful when variables are used instead of numbers, see the next paragraphs). Such a check would look like this:

 8 >= 5 || 8 < 2 && !8 == 9

Normally this is not sufficient though. SLIC can handle this and, at least in this case, will give the correct result. But in other cases and for humans who try to understand the code, it is necessary to place brackets where there is a difference in the 'type' of operation (relational or Boolean). This would result in the following:

 (8 >= 5) || (8 < 2) && !(8 == 9)

But even now it may not be not good enough. We could still be talking about two situations here (although SLIC wil apply order of precedence here and not have a problem):

 (8 >= 5) || ((8 < 2) && !(8 == 9))

or

 ((8 >= 5) || (8 < 2)) && !(8 == 9)

Right now there is no difference in the result but look what happens if you replace all 8s with a 9. Now the first situation says TRUE || (FALSE && NOT TRUE) and hence returns true, but the second situation says (TRUE || FALSE) && NOT TRUE and hence returns false. The second situation would be the desired one in that case.

Without brackets NOT has the highest precedence and it is followed by AND and OR, which are evaluated from left to right because they are equal in precedence, so without the extra brackets things would have been fine as well, no matter what you replace 8 with. But don't take any risks with these things and place brackets whenever appropriate.

2.6 Order of precedence

In conclusion of this chapter I will give you the order of precedence of arithmetic operations in SLIC. I didn't give this earlier because only now have you seen all the operations that are present in SLIC. All operations that are on the same level of precedence are evaluated from left to right. If at any time you are uncertain if brackets are necessesary , place them anyway, just to be on the safe side.

>From highest to lowest:

=   (assignment - not really an operation, see next chapter)
!   (NOT)
&&, ||   (AND and OR)
==, !=, <=, <, >, >=  (relational operations)
*, /   (multiplication and division)
+, -   (addition and substraction)

3. Variables and Datatypes

3.1 Variables

Like the end of the previous chapter already showed, you don't always work with the same values (that would be rather silly: instead of 5 + 3 you might as well use 8 right away). The power of a computer lies, among other things, in the fact that you don't have to work with fixed values, like with a calculator.  In SLIC, as well as most other programming languages, you can use variables.
Variables are, as the name suggests, objects (e.g. numbers - non-numerical variables are treated in paragraph 3.3) whose value can vary, change, during the game. They can be referred to by giving them a name. Almost any name can be given to variables, as long as it is unique (note that SLIC is case-insensitive, so 'x' is the same as 'X' and 'JoHNdOe' is the same as 'JohnDoe'), i.e. it isn't already used by some other variable, and as long as it isn't a so called reserved word, or keyword. Keywords are words that are part of the SLIC language. In the various appendices you can see which keywords are reserved in SLIC. Another thing to keep in mind is that certain symbols may not be allowed, these are mainly the symbols that SLIC uses itself: <, >, =, *, etc. but there may be others as well (I didn't try this out), such as ?, |, etc. One last piece of advise is too keep the names short but descriptive. Short, because you will often find yourself typing it often and longer names make for more typing, and descriptive because this will make it easier to understand for you and others what this variable is used for.
Personally I would advise to refrain from using any special symbols, but if you feel you have to, keep a good eye on this when you encounter problems in getting the code to run properly.
So instead of using 8 in the previous example we could also have used a variable name, such as x and assign a value to it elsewhere:

 ((x >= 5) || (x < 2)) && !(x == 9)

Assigning values to variables is very simple. Simply state the name of the variable followed by a '=' (now you see why '==' is used for expressing equality: one symbol for two different things would be confusing) and follow that by a value or an expression that gives a value, either Boolean or numerical. Make sure the assignment is followed by a ';' sign. Example (similar to what you saw in the previous chapter):

 HandleEvent(BeginTurn) 'AssignValue' post {
  x = 8;
  y = 4 * x - (1/2);
  Message(1, 'LOC_MathTest_M');
 }
 // note that this example uses the messagebox from the previous chapter to
 // display the values on screen, so make sure it's present when executing this code

This will assign the value 8 to x and the value 4 * x - (1/2) = 32 - 1/2 = 31 1/2, or 31 to y. As you can see, you can use variables to assign values to other variables. Even better, you can even use a variable itself to change it's own value, like this:

 HandleEvent(BeginTurn) 'AssignValue' post {
  x = x + 1;
  y = y * 3;
  Message(1, 'LOC_MathTest_M');
 }

Here the last known value of x (or y) replaces the x (or y) right of the '='-sign and the value of the whole expression is assigned to the x (or y) left of the '='-sign. So if x was declared 3 earlier in the program then the expression x = x + 1 will become x = 3 + 1 and hence x = 4. So x will get the value 4. In the same fashion if y has the value 6 then it will get the new value 18. Not that this only works when the variable already has a value earlier on in the program. If it doesn't, the game will either give an error or assume the previous value to be 0 (I should test this).

Boolean values can be replaced by variables as well, where any non-zero value is regarded true:

 HandleEvent(MoveUnits) 'BooleanValue' pre {
  x = 8;
  y = (3 < 5) && x;
  Message(1, 'LOC_MathTest_M');
 }

This will assign the value 8 to x and true to y (3 < 5 is true and x = 8 is not 0, so true, and true && true assigns true to y).

3.2 Declaration of variables

But you can't say 'x = 8;' just like that in the middle of your SLIC script. SLIC will not know how to deal with it. For a good administration of all these variables (there can be many in a large script) it is necessary to declare a variable first before using it. Though it means slightly more work for the programmer, you will find that it is very beneficial both for man and machine: the primary reason for this is that it makes debugging much, much easier, especially if you're as sloppy and chaotic in your working methods as I am: a typo is so easy to make and can be so difficult to detect. Declarations help prevent this as a typo in a variable name will result in an undeclared variable and hence an error.
To declare an integer variable (which we have been using so far, other types or variables will be treated later on), you must use the following syntax:

int_t <variablename>;

Where <variablename> can be any legal name for a variable. It doesn't matter where you declare variables, you can do this at any point in the program, but make sure you declare it before you use it (so higher up in the file(s)). For good readability it is advisable to declare your variables as early as possible, so at the beginning of the file for global variables and at the beginning of a trigger for local variables (for the concepts of global and local variables: see further). Examples:

 int_t x;
 int_t SLIC_IS_COOL;
 HandleEvent(UnitsMove) 'DeclareVariables' post {
 int_t LocutusOfBorg;
 int_t y;
  x = 3;
  SLIC_IS_COOL =120 * x + y / (SLIC_IS_COOL -1);
  y = 3 / x;
  LocutusOfBorg = 45;
  y = y + LocutusOfBorg;
  Message(1, 'LOC_VarTest_M');
 }
 HandleEvent(Battle) 'DeclareVariables2' post {
 int_t y;
  x = x + 1;
  y = 5;
  SLIC_IS_COOL = -23;
  Message(1, 'LOC_VarTest_M');
 }
 Messagebox 'LOC_VarTest_M' {
  Show();
  Text(ID_LOC_VARTEST);
 }

 LOC_VARTEXT "The result of y is {y}, the value of SLIC_IS_COOL is {SLIC_IS_COOL}, the value of  x is {x}, and the value of  LocutusOfBorg is {LocutusOfBorg}."

 --> TEST TO SEE WHAT HAPPENS WITH UNDECLARED VARIABLES <--

Note: if a variable is declared and then used before it has been assigned a value, the value will be 0. (The value is 'initialized' to 0). So inserting the line "x = y;" right after the declaration of y in the previous example will cause x to become 0. However, if you need a variable to be 0, it is for clarity reasons usually better to manually assign the value 0 to this variable anyway.

3.2 Scope of variables

In the above example you could see that some variables are declared within the eventhandler and some are declared outside it. This has to do with the scope of variables: with the area in which they can be used, in which they are valid, to which they apply. Variables that are defined outside any eventhandler, trigger, messagebox, alertbox or whatever are defined globally and variables inside these are defined locally. Global variables are variables that can be used anywhere in the script. In the previous exampe I defined x as a global variable so I could use it in the 'DeclareVariables' eventhandler and in the 'DeclareVariables2' eventhandler. LocutusOfBorg is a local variable and can hence only be used in the 'DeclareVariables' eventhandler. Assigning a value to it or using it in any way to assign a value to some other variable in 'DeclareVariables2' would be incorrect and result in an error.
You can also see that y has been defined twice, once in 'DeclareVariables' and once in 'DeclareVariables2'. If you'd declare/d x a second time, e.g. between the first and second eventhandler, this would result in an error: "variable x is already declared". In the case of y this is okay though, because it is local, it's existance ceases as soon as the 'DeclareVariables2' eventhandler is finished and can hence be declared and used again in another eventhandler. For the game, this second y is a totally different variable from the first one and just happens to have the same name, a mere coincidence which doesn't link these two variables in any way.

The advantage of this difference in scope is that everytime you use a global variable like x, it will always have the value it had the last time you used it, even if that is hours ago and/or in a totally different eventhandler. If you use a local variable like y, it will only have it's value within that particular trigger only for the time in which a particular instance of that eventhandler (or messagebox or whatever) is used.
For example, whenever a unit moves the 'DeclareVariables' eventhandler is called and y gets the value (3 / 3) + 45 = 46. The next time a units moves it will be called again and get this same value again. In the second event another (totally different and unrelated) variable y will be called and get the value 5 whenever a battle takes place. This never changes, the first y will always be (3 / x) + 45 and the second y always 5. For x this is different: x gets the value 3 once a unit moves and everytime after this (until a unit moves again) when a battle takes place it's value will be increased with one. So where y always has the same values under the same circumstances (which is desirable in some cases), x will have a value that might vary a great deal throughout the game, even under the same circumstances.

 int_t x;
 HandleEvent(BeginTurn) 'IntruigingSituation' post {
 int_t x;
  x = 3;
 }

One interesting question the above example provides is: what happens if we declare a variable both locally and globally? The answer is that this will work without problems. The result of this will be that for as long as the local variable exists, the local variable is used and the global version is temporarily stored 'elsewhere' and is unavailable for use. As soon as the eventhandler or messagebox or whatever it is in which the local variable is declared is 'finished', the game will 'destroy' the local variable and 'restore' the global variable. This isn't an accurate description of how things really work internally, but the general idea should be clear.
It is important to understand that under exceptional circumstances it is quite possible that this system might not work as well as it is described here (without knowing how SLIC works internally one can't say anything about this, even extensive testing might not reveal this). So try to avoid using the same variable name for both a local and a global variable. Not only might it not always work, is it also very difficult to prevent and detect bugs and it makes the code much harder to read.

 --> HOW ABOUT DECLARATIONS IN FOR-LUSSES? ETC <--

3.3 Datatypes

This is all very nice of course, but it's all a bit academic. This manipulating and storing of numbers won't help you much if you need to manipulate or store a unit or a city or whatever. Yes, theorically it would have been possible to represent cities and units by numbers somehow, Activision could have chosen to do that, but in practice it would be very inconvenient. Fortunately the Actigrammers were smarter than that and implemented a more convenient system. They provided SLIC with a couple of other 'datatypes', in other words, they added several different types of variables, variables that store other things than numbers. These other 'basic' types are city-, unit-, location-, player- and army-variables. Here are a couple of examples on how to declare them:

 int_t x;  // an integer (whole number)
 city_t c;  // a city
 unit_t tmpUnit;  // a unit
 army_t arm;  // an army
 location_t loc;  // a location

As you can see it all speaks for itself. One important note though: players are stored as integers and are usually interchangable with integers. Unfortunately this can sometimes cause problems and I haven't completely figured out yet when exactly it goes wrong and how to prevent it. If you encounter a problem with this though, you can solve it by using built-in variables (see further) instead of user-declared variables (the variables decribed until now).
 These datatypes are a more complex than integers and hence you can't just give a unit- or location-variable a value. Where you can simply assign a value to a integer-variable by saying "x = 3;", you can/'t do this with units because a unit is a very abstract object: you can't type it with your keyboard or anything. Therefore you must use either functions or built-in variables to give these variables a value, but once they have a value they can be used in the same way as numbers (though arithmetic expressions are of course still impossible). Practically this means you can use these non-integers for assignments and equality- and inequality-checks (=, == and !=). Example:

 unit_t v;
 city_t d;
 int_t boolean;
 int_t bool2;
 HandleEvent(BeginTurn) 'StructTest' post {
 city_t c;
 unit_t u;
  GetCityByIndex(1, 0, v); // now c contains a city
  GetUnitByIndex(1, 0, u); // now u contains a unit
  v = u;   // assigns a unit to v
  d = c;   // assigns a city to d
  boolean = (d == c);  // store if d is equal to c
  bool2 = (v != u);  // store if v isn't equal to u
  Message(1, 'StructTest_M');
 }
 Messagebox 'StructTest_M' { // display mesage
  Show();
  Text(ID_STRUCT_TEST);
 }

 STRUCT_TEST "Variable d contains the city of {d.name} and variable v contains a {v.name}. The expression (d == c) results in {boolean} and the expression (v != u) results in {bool2}."

Because these variables are abstract, they can't be displayed in a message either. So to refer to them in a message, you must use a description of them as I showed in the example by referring to them by their name.
 This abstract-ness also makes it difficult to determine what the default-value of these city/unit/army/location variables should be (to what value they are initialized), so using these variables before they have been assigned a value will result in an error. More accurately, there is a default value, but that's the invalid value, which is usually useless. So a city variable that has been declared but not initialized contains an invalid city (this is useful to know because you can use the CityIsValid event to check if a city is valid, see further), the same applies for units, armies and locations.

3.4 Arrays

Sometimes it's useful to store more than one variable. For example, if you want to store a list of all cities that have a certain building or all units of a certain type, it is difficult to do this with regular variables. You'd need to declare these variables one by one, assign the right value (or unit or city or location or whatever) to the right variable and you'd need to keep in mind that the number of elements you'd like to keep track of may vary. Example (no need to understand this, just an example of how complicated it might get):

 city_t c1;
 city_t c2;
 city_t c3;
 // c4, c5, ..., c1000
 HandleEvent(CreateCity) 'NoArrayExample' post {
  if (!CityIsValid(c1)) {
   c1 = city[0];
  } elseif (!CityIsValid(c2)) {
   c2 = city[0];
  } elseif (!CityIsValid(c3)) {
   c3 = city[0];
  } // c4, c5, ..., c1000
 }
 HandleEvent(CreateUnit) 'NoArrayExample2' {
  if (city[0] == c1) {
   // do something
  } elseif (city[0] == c2) {
   // do something else
  } elseif (city[0] == c3) {
   // do something else
  } // c4, c5, ..., c1000
 }

As you can see, this would be a very complicated and tedious job, if possible at all (in this case it is, but this is just a very simple example, more complex tasks may prove impossible to perform). For this reason something exists to make this process a lot easier. It's called a array and it is, as the name suggests, a whole row (array) of variables instead of just one. Arrays can be declared in a single line and you can make them contain as many variables as you need or you can even not specify a number at all and make the number of elements vary in the game, depending on how many you need. Using control structures (see next chapter) you can 'automate' the assigning and usage of values as well and hence make these operations very compact and simple as well in many cases.
 Declaring arrays works the same as declaring regular variables, only the name must be followed by []-brackets and optionally a number between these brackets to indicate the maximum size that array will have. If possible try to indicate the maximum size, this makes it easier for SLIC to detect how much memory this array will occupy and make the 'under-the-hood' functioning of SLIC a lot smoother. The main advantage of this is that it's generally more efficient in terms of resources. The main disadvantage is that you must stay under this limit if you specify it: if you define 100 as limit, your array can never contain more than 100 elements, which isn't always desirable. Arrays can be of any of the datatypes I already described above (integer, city, unit, location, army, player). Here are a couple of examples of declarations:

 int_t values[10]; // max 10 integers/players can be stored in this array
 city_t greeks[]; // no max size for this array
 unit_t tanks[]; // no max size here either
 location_t x[3]; // max 3 locations can be stored
 army_t test[6] // max 6 armies

Reading and writing values from and to an array happens by making the name of an array followed by []-brackets with a index-number between the brackets. The index starts with 0 and the highest index is the length of an array minus one (if the length of an array is 3 then the indices are 0, 1 and 2). If no length was specified then there is no max to this. Reading a index that doesn't have a value yet is the same as reading a value from a regular variable: it will either default to 0 or give an error. Reading a value that is higher than the highest index allowed (or lower than the lowest index allowed (0)), the result will be an error (array index out of bounds). Examples:

 int_t i;
 city_t cities[];
 HandleEvent(CreateCity) 'TestArray' post { // every time a city is founded
 int_t test;
 int_t values[10];
  values[0] = 3;
  values[1] = 5;
  // ...
  values[9] = 34;
  cities[i] = city[0]; // city[0] is city that was just created, see further
  i = i + 1;  // keeps track of index of cities[]-array so all cities
    // get stored in the array with a different index
  cities[1000] = city[0];
  test = cities.#;
  Message(1, 'TestArray_M');
 }
 Messagebox 'TestArray_M' { // message
  Show();
  Text(ID_ARRAY_TEST);
 }

 ARRAY_TEST "value[0]: {value[0]}, value[1]: {value[1]}, value[9]: {value[9]}, cities[0]: {cities[0]}, cities[1000]: {cities[1000]}, test: {test}."

To find out the length of an array you can use the name of the array followed by ".#", so "values.#" in the previous example would return 10. The length of variable-sized arrays is determined by the last element that was assigned, namely that last element plus one (the one comes from the fact that 0 is a valid index as well). In the previous example the variable test would get the value 1001 (1000 + 1), even if there are actually only five spots that actually contain a city. The maximum number of cities this array could currently contain is 1001(indices 0 to 1000) and that's what the # refers to. (I'm assuming of course that there are less than 1001 cities founded, if there are more, variable i and hence the length of the array will get larger than 1001).

3.5 Built-in variables

What I've discussed up to now are user-defined variables: they are declared/defined by users (i.e. programmers). However, there is also a whole collection of other variables in the game, which the game engine itself uses to temporarily store all kinds of important variables (when exactly what values are assigned to what built-in variables is explained in chapter 7 and the appendices). This is extremely useful for us as programmers, because we can use these variables for our own purposes. These built-in variables, as they are called, are stored in the form of arrays and have fixed names. This means that you can't use these names elsewhere in the program, they're reserved especially for the built-in variables. You can however store values in these built-in variables like you would in any other array, but when you do this you risk overwriting existing values that may be important later on and you also risk having your values overwritten if the game engine needs the same variable you used. So generally it's not too good an idea to use these variables for storage yourself, unless you have little choice or are confident that there won't be any problems.

--> LENGTH OF BUILT-IN ARRAYS, WHAT DOES THAT DO? <--

The names of all built-in variable arrays are: unit, city, player, army, location, advance, action, value, building, wonder, unitrecord, gold and good. Remember, these are arrays, so to access them, use unit[0], city[1], building[3], etc. The only non-array built-in variable is g (it stands for global and stores some info that is important at all times under all conditions, see appendices for details). These names are important to remember because they cannot be used as names for your own user-defined variables. If you need a unit variable (or array), call it aUnit or tmpUnit, but never just unit.

3.6 Structures

You may have guessed that the built-in array unit contains units and that city contains cities and that value contains integers, but building and advance FE are new. These are standard datatypes in SLIC, just like city and unit but the main difference is that you can't declare user defined variables of this type. This may seem rather odd but it isn't really, since they can and are only used in very specific situations. What makes these variables so interesting is not that you can (or rather can't) use them as regular variables to store buildings or advances in. The reason why these extra datatypes exist is that all datatypes in SLIC (every built-in variable class represents one datatype in SLIC and every datatype in SLIC is represented by one built-in variable class (unit, city, advance, etc)) are so-called structures. They are more than just variables, every datatype has certain properties. A city has an owner, a size, a name, a happinesslevel, etc and every player has a certain number of units, a certain number of cities, a pollutionlevel, a capital, a leader, etc and a building has a name and a type. All these properties and many more are stored 'inside' the variables and can be accessed by the programmer by following the variable that stores some datatype with a dot and the name of the 'holder' of the property. FE city.population returns the size of a city, unit.owner returns the owner of a unit, building.name returns the building name, etc. Most of these properties are integers but some are booleans, cities or strings (see chapter 6). See the appendices for details on what all possibilities are for all unit types. Here is an example of how built-in variables can be used inside the game:

 HandleEvent(BattleAftermath) 'TestStructures' post {
// this event gives a lot of built-in
variables a value so it's highly suitable for this example
 location_t tmpLoc;
 army_t tmpArmy;
 unit_t tmpUnit;
 city_t tmpCity;
 int_t tmpValue;
 int_t tmpValue2;
 int_t tmpValue3;
 int_t tmpPlayer;
 int_t tmpPlayer2;
 int_t tmpBoolean;
  tmpArmy = army[0];   // an army from context
  tmpPlayer2 = tmpArmy.owner;  // owner of this army
  tmpValue = army[0].size;  // size of army from context
  tmpUnit = unit[0];   // a unit from context
  tmpValue2 = tmpUnit.hp;   // # of hitpoints of this unit
  tmpLoc = location[0];   // a location from context
  tmpPlayer = player[0];   // a player from context
  player[2] = 2;   // player 2
  tmpValue3 = player[2].cities;  // # of cities of player 2
  tmpCity = player[1].largestcity;  // largest city of player from context
  tmpBoolean = (tmpCity.size < value[0]); // stores if size of this city is smaller
// than integer from context
 }

One important note here is that players and integers are messed up. You can't put players in their own user-defined variable, like you can with cities or units, but since they are normally interchangeable with integers, you can treat them as such. So an integer variable can store players as well. However, a number isn't a structure, so you can't say tmpPlayer.cities if you want to get the number of cities a player has when that player is stored in the integer variable tmpPlayer. To access these properties of players, you must store them in a built-in variable first. So if tmpPlayer contains a player and you want the number of cities, do this: (I suspect the same principle goes for advances, buildings, etc).

 player[2] = tmpPlayer;
 tmpValue = player[2].cities;

Note that I used player[2] since player[2] isn't used very often by the game engine so it's not likely to overwrite any important value and it's not likely it will be overwritten itself anytime soon either. player[0] and player[1] are used much more often by the game engine so you have to be take that into account when you use those.

--> WHAT HAPPENS IF player.capital.location(.x) IS USED? <--

--> CONSTANTS? <--

4. Control Structures

4.1 Non-linear execution

All the SLIC code you've seen so far is pretty straight-forward: within an eventhandler, lines are executed one by one, from top to bottom (of the eventhandler). This is called linear execution and it can be rather limiting: this way all instructions are always executed exactly one time (per time the eventhandler is called). Under some circumstances however, you may want to skip certain commands and execute others instead (FE when a player builds a city near water, create a unit; when a player builds a city away from water, create a building). Or maybe you want the execute the same (or very similar) commands 2, 3, 10 or even 100 times (FE create 4 barbarian archers). Maybe you want to execute the same piece of code over and over again until a certain condition is met (give a player random advances until the player obtains Masonry). With linear execution most of these things are impossible or at least very difficult to do. But as you may have guessed by now, something was created to take care of this: control structures.

Control structures allow you to make your code non-linear: you can use if- and if/else-conditions to execute code only under certain conditions, use for-loops to execute code a certain number of times and while-loops to execute code until a certain condition is met. These are powerful structures that can be found back in almost every programming or scripting language including SLIC, so I will describe them here. Control structures are one of the few types of code in SLIC that require the usage of {}-brackets instead of ';', just like EventHandlers and Message/Alertboxes.

4.2 If-clauses

As mentioned above, the if-clause can be used to execute certain code only under certain conditions. Here is the complete syntax of the SLIC command:

if (<condition>) {
 <code>;
[} elseif (<condition_1>) {
 <code_1>;]
[} elseif (<condition_2>) {
 <code_2>;]
.
.
.
[} elseif (<condition_n-1>) {
 <code_n-1>;]
[} else {
 <code_n>;]
}

The parts between []-brackets are all optional. The <code(_x)> parts can be any kind of code, either a single line or 2 or 10 or even 100 lines, as many you need. Just like 'regular' code that's outside any if-block, every line needs to be ended with ';' (I put the ';' behind <code> to stress this, so don't think it's necessary to end the last line of the code-block with two ';'s or anything). The <condition(_x)> parts represent boolean expressions, they can contain any kind of expression that returns either true (1) or false (0). Here are a bunch of examples of the simplest form of the if-clause, the form with the the necessary if-part and no else- or elseif-parts. This executes the code in the <code> block only if the <condition> is met and skips the <code> if the <condition> is not met.

 int_t i;
 int_t j;
 HandleEvent(BeginTurn) 'TestIf' post {
  i = 0;
  j = 0;
  Message(1, 'TestIf_M');  // display values i and j
  if (1) {   // always true
   i = 3;
  }
  Message(1, 'TestIf_M');
  if (0) {   // always false
   i = -100;
   i = player[0].cities * i / 23 + 12;
   j = 34;
   j = i - j * 3;
   i = j / i;
  }
  Message(1, 'TestIf_M');
  if (player[0].units > 4) { // true if player has more than 4 units
   i = i + 20;
  }
  Message(1, 'TestIf_M');
  if (i > 6) {  // false if i is smaller than or equal to 6
   j = i * 3;
   i = j - 2;
  }
  Message(1, 'TestIf_M');
  if (j < 100) {  // if j smaller than 100, do nothing
  }   // (not very useful but it's not wrong either)
 }
 Messagebox 'TestIf_M' {   // message
  Text(ID_TEST_IF);
 }

 TEST_IF "i = {i} and j = {j}."

These examples should be pretty much self-explanatory. What exactly the values of i and j are at any point doesn't really matter of course, the important thing is to see how the if-clauses work.

A slightly more complex form of the if-clause is the if/else-clause: if the <condition> is true, the <code> block is executed, if it is false however, the <code_n> block gets executed. One of the two <code> blocks is always executed, but never both. This is useful because now if you need two if-clauses with one saying 'x > 4' and the other saying 'x <= 4', you can replace this with one if/else-clause. Examples:

 int_t i;
 int_t j;
 HandleEvent(BeginTurn) 'TestIfElse' post {
  i = 0;
  j = 0;
  Message(1, 'TestIf_M');  // uses messagebox from previous example
  if (1) {   // because this condition is always true...
   i = 3;
  } else {   // ...this code is never actually executed
   i = -100;
   j = i - j * 3;
   i = j / i;
  }
  Message(1, 'TestIf_M');
  if (player[0].units > 4) { // execute this if player has more than 4 units
   i = i + 20;
  } else {   // execute this if player has 4 units or less
   j = i * 3;
   i = j - 2;
  }
  Message(1, 'TestIf_M');
  if (i > 6) {  // if i > 6, j = 100
   j = 100;
  } else {}   // else, do nothing (same as leaving else-part away altogether)
  if (i < 4) {  // if i < 4, do nothing, else j = 200
  } else {   // (same as if (i >= 4) but allowed)
   j = 200;
  }
  Message(1, 'TestIf_M');
 }

The third form of the if-clause is the most complicated one: it involves elseifs. This means that there's not one condition but several. It works by evaluating the <condition> and if the result is true, executing <code> and moving on to the code after the whole if-structure. If the result is false, the <condition_1> is evaluated. If that's true, <code_1> is executed and if it's false, the <condtion_2> is evaluated and if needed the next <condition_x> and after that the next, etc. There can be as many elseifs plus conditions as you need and whether you end this structure with a general, unconditional, else or not (to take care of the situations where none of the conditions are true), is up to you. Remember that in this structure, only one of the code blocks is executed, never more. This means that if several of the given conditions are true, only the code of the one that is evaluated first (the topmost) will be executed and the other code blocks will be skipped. If this isn't desirable, you should use two seperate if-clauses in a row instead of one if/elseif-clause. Also, If there's no unconditional else, it's possible that none of the code blocks is executed, but that depends on what exactly the conditions are. Examples:

 int_t i;
 int_t j;
 HandleEvent(BeginTurn) 'TestIfElseIf' post {
  i = 0;
  j = 0;
  Message(1, 'TestIf_M');   // uses messagebox from previous examples
  if (player[0].armies > 12) {  // if player has more than 12 armies
   i = 3;
  } elseif (player[0].armies < 5) {  // if player has less than 5 armies
   i = -100;
   j = i - j * 3;
  }    // if player has between 5 and 12 armies, nothing happens
  Message(1, 'TestIf_M');
  if (player[0].units > 4) {  // if player has more than 4 units
   i = i + 20;
  } elseif (player[0].units == 0) {  // if player has exactly 0 units
   j = i * 3;
   i = j - 2;
  } elseif (player[0].units > 0) {  // if player has more than 0 but less than 5 units
// (#units > 4 already takes care of 5 and larger)
   i = 4;
   j = 3 * i + j;
  }
  Message(1, 'TestIf_M');
  if (i > 6) {   // if i is larger than 6
   j = 100;
  } elseif (player[0].militaryunits > 45) { // if more than 45 military units
   j = 5;
   i = 3;
  } elseif (i < 2) {   // if i is smaller than 2
   i = 2100;
  } elseif (j == 3) {   // if j is equal to 3
   i = 2100;
  } else {    // in all other cases
   j = 200;
   i = 16;
  }
  Message(1, 'TestIf_M');
 }

One last thing to keep in mind is that <code> blocks can contain any kind of code, so if-clauses within if-clauses are allowed as well (this is called nesting, the inner if-clauses are nested ifs). This can be done as often as you need it (meaning that an if within an if within an if within an if within an if is also allowed if you want/need to use it):

 int_t i;
 int_t j;
 HandleEvent(BeginTurn) 'TestNestedIf' post {
  i = 0;
  j = 0;
  Message(1, 'TestIf_M');   // uses messagebox from previous examples
  if (player[0].armies > 12) {
   i = 3;
   if (g.year > 100) {
    j = 5;
   } else {
    j = 6;
   }
  } elseif (player[0].armies < 5) {
   i = -100;
   j = i - j * 3;
   if (player[0] == 4) {
    i = j * i;
   }
   if (player[0].cities > 12) {
    j = i * 2;
   } elseif (player[0].cities < 4) {
    j = i - 4;
   } elseif (player[0].cities == 6) {
    if (g.year > 23) {
     j = 7;
    } else {
     j = 6;
    }
   }
  }
  Message(1, 'TestIf_M');
 }

4.3 While-loop

The while-loop is basicly one step up from an if-clause: instead of executing a code-block *if* a condition is true, a while-loop executes a code-block *while* a condition is true. So it repeats the same code over and over again as long as a certain condition is still true. This does mean that you'll have to make sure that the condition will actually become false at some point or the game will enter a so called infinite loop: the loop will never end and be executed endlessly until the game runs out of available resources and crashes. Let me first give the complete syntax of the while-loop, you'll be pleased to see that it's much simpler than the if-clause:

while (<condition>) {
 <code>;
}

As you can see, there are no elses or elsewhiles to worry about, just plain old simple while. As you'll understand, the <condition> part represents any boolean expression and the <code> part represents any kind of code-block. Here too, every line must end with ';', just like regular code, and here too it's possible to create nested while-loops if needed.

To end a loop you will normally put some kind of variable in the condition part of the while-loop and at some point change that variable in the code-block to such a value that the condition becomes false. You can do this directly FE by putting '(x > 4)' in the condition and changing x from 8 to 2 somewhere in the code-block (either immediately or incrementally - FE decrease it with one every time you execute the code) or you can do this indirectly FE by putting (player[0].units < 12) in the condition and creating new units in the code block (how this can be achieved, you can read in the next chapter and in the appendices). Examples:

 int_t i;
 int_t j;
 HandleEvent(BeginTurn) 'TestWhile' post {
 location_t tmpLoc;
 int_t k;
  k = 0;
  i = 0;
  j = 0;
  Message(1, 'TestIf_M'); // uses messagebox from previous paragraph
  while (i < 12) { // execute this code 12 times, otherwise code does nothing
    // do stuff (in this case nothing)
   i = i + 1;
  }
  Message(1, 'TestIf_M');
  i = 0;
  while (i <= 10) {
   i = i + 1;
   j = i * 3;
   Message(1, 'TestIf_M');
  }  // this code displays 10 messageboxes, together forming the three times table
  while (player[0].units < 10) {
   MakeLocation(tmpLoc, 12, 12);
   CreateUnit(player[0], UnitDB(UNIT_ARCHER), tmpLoc, 0);
  }  // no need to understand how this works but it shows how you can change variables indirectly
// (more units are created with the CreateUnit line):
    // until player has 10 units or more, archers are created at location (12, 12).
  while (i == 100) { // i isn't 100, so this is false the first time it gets evaluated and
//code below will never be executed
   i = 122 * j;
   j = 3 * i + j * j *j / i;
  }
  while (i < 25) {
   j = 0;
   while (j < 10) {
    while (k < 12) {
     k = k + 1;
     if ((i / 2) == (k - 3)) {
      j = j * 2;
     }
    }
    j = j + 1;
    Message(1, 'TestIf_M');
   }
   i = i + 2;
  }
 }

WARNING: do not execute this code, it's an infinite loop and will only crash your computer, it merely serves as an example

 HandleEvent(BeginTurn) 'TestInfiniteLoop' post {
  j = 200;
  i = 100;   // i starts at 100
  while (i < 1000) {  // go on until i is 1000 or larger
   if (i == j) { // if i equals j (equals 200), make i 300 - 200 = 100
    i = 300 - i;
   } else {  // else i = i + 1, so 1 goes from 100 to 200 only to be made 100 again
    i = i + 1; // (so i never actually reaches 1000 or more)
   }
  }
 }

4.4 For-loop

The final control stucture that SLIC knows is actually redundant. The for-loop, although present in almost every programming and scripting lanuage, can easily be replaced by a while-loop plus a variable (though not all while-loops can be replaced with for-loops). There's no real logical reason for including it in SLIC, it's more of a sentimental thing: many programmers (including yours truly) simply prefer to use for over while when it's possible. The only solid argument for this preference I could think of is that everything that affects the condition-part of the for-loop is in a single line, so you can instantly see how often the code-block of the for-loop will be executed without have to look at the rest of the code. At least, if you program it properly. It is possible to affect the condition-part outside the regular line (the variable that forms the core of a for-loop is just a regular variable and can be changed like any variable), but this is not a smart thing to do since this would basicly cancel out the only advantage the for-loop has over the while-loop and because it would also make the code less readible (since the reader expects to find all related code on a single line). Generally the while-loop is used for decisionmaking (should this code-block be executed once more or should I continue?) and the for-loop for iterations (executing the code a certain number of times, where the number is determined in advance). Here is the complete syntax of the for loop:

--> WHAT HAPPENS IF VARIABLE IS CHANGED OUTSIDE FOR LOOP <--

int_t <var>;
.
.
.
for (<var> = <val>; <condition>; <var> = <expression>) {
 <code>;
}

and here is how you can do the same thing with while:

int_t <var>;
.
.
.
<var> = <val>;
.
.
.
while (<condition>);
 <code>;
 <var> = <expression>;
}

This may look more complicated then it is. <var> is the variable that ties the whole thing together, it is just a regular variable, just like any other. <val> is the starting value (or optionally a integer-variable containing a value), it determines what value <var> will have when the loop starts. <condition> is the same as the <condition> in if-clauses and while-loops and it uses <var> in some way (FE '<var> < 12' or '<var> >= player[0].units'). <var> = <expression> determines how <var> changes after every loop through the <code> block (FE '<var> = <var> + 1'). <code> is of course the same <code> block as the one in if and while. A few examples might clarify a few things:

 int_t i;
 int_t j;
 HandleEvent(BeginTurn) 'TestFor' post {
  j = 0;
  Message(1, 'TestIf_M');  // uses same messages again
  for (i = 0; i < 100; i = i + 1) {
     // i goes from 0 to 99 (= last number smaller than 100)
   Message(1, 'TestIf_M'); // displays values 0 to 99 in 100 messageboxes
  }
  while (j < 100) {  // same as above, only now required code is all over the place
   Message(1, 'TestIf_M');
   j = j + 1;
  }
 }

As you can see in this example, the code that affects the condition part of the for-loop is on a single line (apart from the 'int_t i;') while the code that affects the condition of the while loop is spread out all over the code and takes up 4 lines. This example also shows how easy it is to repeat a certain piece of code x times: '(i = 0; i < x; i = i + 1)' is all you need. This kind of thing is exactly what for-loops do best: plain and simple looping through the same code a number of times. If it's more convenient to have i start at a non-zero value or to decrease it with 1 or to increase it with 5 or whatever, then that's no problem and you can replace various values with variables as well, but generally it's not a good idea to change any of these variables in the code-block itself, either directly or indirectly. In such a situation I would advise to use a while-loop, because in that case you're making decisions rather than iterations.

It's of course also possible to use nested for-loops but keep in mind that the variable that ties the inner for-loop(s) together should be a different one from the outer for-loop(s).

Here's a couple more examples:

 int_t i;
 int_t j;
 HandleEvent(BeginTurn) 'TestFor' post {
  Message(1, 'TestIf_M');  // uses same messages again
  for (i = 0; i < 100; i = i + 1) {
   for (j = i; j < (100 * (player[0].units + player[0].cities)); j = j + i) {
    Message(1, 'TestIf_M');
   }
  }
  for (i = 1000; i < g.year; i = i - 4) {
   Message(1, 'TestIf_M');
  }
  i = 0;
  // the following is allowed but not very advisable: it is very difficult to predict
  // how often the code will be executed, so a while loop would be better
  for (j = 12; j < 30; j = j + (3 - i) * 3) {
   if (player[0].units > 10) {
    i = i + 1;
   } else {
    i = i - 2;
   }
   Message(1, 'TestIf_M');
  }
 }

4.5 Code layout

If you write large parts of code with all kinds of these control structures with a lot of functions (see next chapter), declarations, expressions and who knows what more before, after and between them and in all kinds of combinations and nests, your code can become quite complex and unreadable. To prevent this most programmers in languages other similar to SLIC add spaces, tabs and endline characters (i.e. 'enters' or 'returns') to have a better overview. Some of these same habits can, in slightly adapted form, be used in SLIC as well. Note that none of these habits are rules that you *have* to follow, but they are much more guidelines that make the code a lot more readable and easier to understand, for yourself and others. The most important 'rule' when it comes to layout is to do use a consistent layout: no matter how you layout your code, do it in the same fashion all the time. Nothing is more distracting and confusing than to mix different styles of layout for the same piece of code.

A very standardized form of layout in any programming language is the usage of tabs. Normally a tab (or a fixed number of spaces) is placed before each line after a line containing a '{'-sign. The line that contains the accompanying '}'-sign and all following lines don't have a tab preceding them anymore. If within this structure another '{'-sign occurs each line after that until the '}' is preceded by two tabs (or two times a certain number of spaces), etc. An exception to this rule (at least in SLIC) is that declarations of variables are usually placed on the previous 'tab-level' and a tab is by some people placed between the type and the name. So instead of

 HandleEvent(BeginTurn) 'Layout' post {
  int_t i;
  i = 0;
  ...

you'll often see

 HandleEvent(BeginTurn) 'Layout' post {
 int_t i;  // (space)
  i = 0;
  ...

or

 HandleEvent(BeginTurn) 'Layout' post {
 int_t i;  // (tab)
  i = 0;
  ...

Practically this means that #include lines and variable declarations as well as the headers of eventhandlers, message- and alertboxes, triggers, etc. all have no preceding tabs and all the code inside these eventhandlers, triggers and messages have 1 tab (and more if there are conditions or loops in this code). Example:

Not recommended:

 location_t myLocation;  // give these a value elsewhere
 location_t myLocation2;
 int_t y;
 
 HandleEvent(UnitMoves) 'TestTabs' post {
 int_t x;
 x = 1;
 if (unit.type == 2) { // if unit is archer
 while (x == 1) {
 if (unit.location == myLocation) {
 y = 10;
 while (y > 0) {
 Message(unit.owner, 'TestMessage');
 y = y - 1;
 }
 }
 elseif (unit.location == myLocation2) {
 MessageAll('TestMessage2');
 }
 else {
 x = 100;
 }
 x = x + 1;
 }
 }
 x = 1;
 Message(g.player, 'EndTrigger');
 }

Recommended:

 location_t myLocation;   // give these a value elsewhere
 location_t myLocation2;
 int_t y;
 HandleEvent(WakeUnit) 'TestTabs' post {
 int_t x;
  x = 1;
  if (unit[0].type == 2) { // if unit is archer
   while (x < 10) {
    if (unit[0].location == myLocation) {
     y = 10;
     while (y > 0) {
      Message(unit[0].owner, 'TestMessage');
      y = y - 1;
     }
    }
    elseif (unit[0].location == myLocation2) {
     MessageAll('TestMessage2');
    }
    else {
     x = 100;
    }
    x = x + 1;
   }
  }
  x = 1;
  Message(g.player, 'EndTrigger');
 }

Another layout issue that's fairly standardized is where to use endline characters ('enters' or 'returns'). Generally every ';' is followed by a endline character and often '}' as well, though some programmers prefer to put else or elseif on the same line as the '}'. Note that all this is of course only if the line has no comments, if a line ends with a comment, the '//' that marks the start of the comment should be regarded as the endline character. This may seem fairly trivial but some programmers like to stuff several commands on a single line, esp. in small code blocks or if-clauses. When you have a large block of code that consists of very similar but not quite identical code (in such a way that loops can't shorten the code significantly), this could be a good idea (it would actually be more clearer than putting all code in seperate lines), but otherwise it's not. Example:

Not recommended:

location_t myLocation;location_t myLocation2; int_t y;
 HandleEvent(WakeUnit) 'TestTabs' post {
  int_t x; x = 1;
  if (unit[0].type == 2) {
   while (x < 10) {
    if (unit[0].location == myLocation) {
     y = 10; while (y > 0) {Message(unit[0].owner, 'TestMessage'); y = y - 1;}
    } elseif (unit[0].location == myLocation2) {MessageAll('TestMessage2');}
    else {x = 100;}
    x = x + 1;
   }
  }
  x = 1; Message(g.player, 'EndTrigger');
 }

Recommended:

 (see above example of tab-usage)

or

 int_t activePlayer;
 HandleEvent(BeginTurn) 'TestEnters' post {
  activePlayer = -1;
  if (g.year == 0) {
   if (player[0] == 0) {activePlayer = 0;Message(player[0], 'WelcomeResistanceLeader');EnableTrigger('BarbarianPlayer');}
   elseif (player[0] == 1 {activePlayer = 0;Message(player[0], 'WelcomeHitler');EnableTrigger('GermanPlayer');}
   elseif (player[0] == 2 {activePlayer = 0;Message(player[0], 'WelcomeChurchill');EnableTrigger('BrittishPlayer');}
   elseif (player[0] == 3 {activePlayer = 0;Message(player[0], 'WelcomeRoosevelt');EnableTrigger('AmericanPlayer');}
   elseif (player[0] == 4 {activePlayer = 0;Message(player[0], 'WelcomeMussolini');EnableTrigger('ItalianPlayer');}
   elseif (player[0] == 5 {activePlayer = 0;Message(player[0], 'WelcomeDeGaulle');EnableTrigger('FrenchPlayer');}
   elseif (player[0] == 6 {activePlayer = 0;Message(player[0], 'WelcomeStalin');EnableTrigger('RussianPlayer');}
   elseif (player[0] == 7 {activePlayer = 0;Message(player[0], 'WelcomeWilhelmina');EnableTrigger('NeutralPlayer');}
  } else {
   TurnHandler();
  }
 }

Other layout issues to keep in mind are less standardized, so you'll have to decide for yourself which style you like best. One of these issues is when to use spaces: should it be 'x=x+1', 'x = x+1' or 'x = x + 1' or some other method? In theory you could even use tabs instead of spaces or endline characters (though I don't think that would make the code any clearer). Another example is whether is should be 'int_t x' (space) or 'int_t x' (tab). The use of regular or capitol letters is another example (is it 'eventHandler', 'EventHandler', 'Eventhandler' or 'eventhandler'?).

Yet another example is the usage of tabs and brackets: should it be 'if(...' (no space) or 'if (...' (space), 'Message(' or 'Message (...', 'EventHandler(...' or 'EventHandler (...'. Personally, I use the option with the space-option for control structures and the non-space options for functions (like Message) and eventhandlers. One could argue that it might be better to always use the non-space option but I don't do that, that's just not my style...

One last example I'd like to mention is that there are two conventional ways of using the '{' sign. Some people put it on the same line as the structure they belong to, others place it right under that line (without putting a tab in front of it). Both methods are conventional styles and which one you use doesn't matter, as long as you use it consistently. The first method has as main advantage that more code fits on a page/screen (and IMHO it looks better) and the second example has as main advantage that it makes it easier to spot missing '{' or '}' (you won't believe how often these are accidentally ommited). Example:

 if (x == 3) {
  x = 10;
 }
 if (x == 3)
 {
  x = 10;
 }

5. Events and Built-in Functions

5.1 The engine of SLIC

This chapter is really the keychapter of this book. The subjects of this chapter are absolutely key to SLIC. If SLIC was a car, then built-in functions and events (together with built-in variables) form the engine of SLIC. So why didn't I start with explaining them in chapter 1? Well, just like with the car: an engine without a car is absolutely useless, but a car without an engine still has some uses (FE as a storage place, a shelter or a museum piece). You may not be able to from A to B with a car without an engine but you can't do anything with an engine without a car, so before you can understand how an engine works, you must know something about the rest of the car: the wheels, the steering wheel, the fuel (in case it wasn't obvious already: no, I don't know much about cars). The chairs and radio are less vital so you can explain those later (next few chapter will deal with the SLIC equivilants of those), but you can't do much without understanding some basic things first. SLIC without fucntions is fairly pointless. The best evidence of this is that in the previous chapters I tried to use as little functions in my examples as possible, but doing without functions altogether made some of the examples so trivial or difficult that I was forced to use some functions after all. However, you can't use functions or events in SLIC without first understanding variables and control structures (though details of messageboxes and strings can wait until later).

5.1 Functions

The truth is however, there isn't all that much to tell about functions. They're extremely important for SLIC but their principle is also so simple I don't know exactly what to tell about them. Built-in functions are pieces of code in the game-engine that you can call from a SLIC script by referring to them by their name and that will perform some action for you on the gamestate. They will read a certain value (just like built-in variables), check if certain conditions are true (as sort of a built-in if-clause) or actually change the gamestate for you. They can create units, kill players, give or take gold, resize cities, etc. etc. Some of the perform more 'domestic' functions: call message- or alertboxes, check validity of certain variables, 'force' a certain type of variable into another (FE obtain a city from a location, presuming there is one at that location), exit the game, etc.

To refine your calls to these built-in blocks of code, you can (or rather must) pass on some arguments: a number, a unit, a location, etc, any kind of variable and in some cases even a (quoted or unquoted) word, like the name of a unit-type or a message. In one case this argument represents the unit that needs to be killed, in another case it's the type of the unit that needs to be created or the location where the presence of a trade good should be checked. The appendices contain elaborate lists with all the functions that are available, what they do and what arguments they require. A function should be called by using its name and adding the arguments after the name in brackets, just like the Message function you already saw a couple of dozen times by now. Examples (all functions have been commented):

 HandleEvent(BeginTurn) 'TestFunctions' post {
 location_t tmpLoc;
 city_t tmpCity;
 unit_t tmpUnit;
 int_t tmpPlayer;
 int_t i;
 int_t j;
  tmpPlayer = player[0];
  i = Cities(tmpPlayer);    // give the number of cities of the current player
  AddGold(tmpPlayer, 100);   // give this player 100 gold
  MakeLocation(tmpLoc, 12, 24);   // put location (12, 24) in tmpLoc
  if (i > 1) {
   GetCityByIndex(tmpPlayer, i - 1; tmpCity); // check player's last newest city
   if (CityIsValid(tmpCity)) {  // if that city is valid
    if (tmpCity[0].location == tmpLoc) {
     j = UnitDB(UNIT_WARRIOR);
     CreateUnit(tmpPlayer, j, tmpLoc, 0, tmpUnit); // create a warrior at the location of the city
         // and store it in tmpUnit
     DamageUnit(tmpUnit, 2);   // take 2 hitpoints away from unit
    }
   }
  }
  value[2] = i;
  value[3] = j;
  location[2] = tmpLoc;
  Message(1, 'TestFunctions_M'); // send this message to player 1
 }
 
 Messagebox 'TestFunctions_M' {
  Show();   // show this message on screen instead of just in message history
  Text(ID_TEST_FUNCTIONS); //show this text in the message
  Duration(2);  // automatically kill this message after 2 turns
  EyePoint(location[2]);  // add an eyepointer to this location
 }

 TEST_FUNCTIONS "At this location, ({location[2].x}, { location[2].y}) a unit will be created if an city exists here; i = {value[3]} and j = {value[3]}. Active player is {player[0].civ_name_singular}."

5.1.1 Return types

As I already explained and as you can see in the examples, some functions perform an action (like CreateUnit), some check a condition (like CityIsValid) and some return a value (like Cities). Usually an indication of what type they are is given by their type. There are three types or functions: void, int and bool functions (although the official Activision documentation treats bool and int as identical, which they basicly are since a boolean in SLIC is really just an integer). Functions that perform a function (/an action) usually don't return a value, which makes them void. Functions that check conditions return a boolean value (true or false) and are thus bool and functions that return a value are usually int, since they return a integer.

Exceptions are that some functions perform a certain action but aren't void bool. These functions return a boolean value that indicates if the operation they should have performed was successful or not. GetNeighbor is an example of this. Another exception are functions that return a unit or location or whatever as variable: since SLIC can't make functions return values other than integers, these functions are either void or bool and return their value in the form of an argument: one of the arguments isn't needed to perform the function but is used to put a value in after the execution the function. FE GetCityByIndex needs 3 variables: a player, an index and a city variable. The city variable isn't used in executing the function but after the execution this variable will contain the result of the operation: the city of the given player with the given index. And it's a rarity, but there are even those functions that return an integer and perform an action at the same time as well, like GetNearestWater, which puts the nearest ocean tile in a variable but also returns the distance to this variable.

--> DOES GETNEARESTWATER STILL EXIST IN SLIC2? <--

One last remark on functions: some functions only work when used in messages, which is explained in detail in the next chapter. However, if you start experimenting with functions yourself, you should keep an eye on this (they are marked in both my appendices and in the official Activision documentation) since it won't be easy to figure out why certain functions don't work when you try them out and you don't know this.

5.2 Events

Events are more than just the engine of SLIC, events are even more important. Their use is twofold: (1) they can be generated which basicly means that they are used as if they were functions (and form the 'engine' of SLIC) and (2) they can be used as triggers for blocks of SLIC code: all SLIC code is divided in blocks (eventhandlers, triggers, message- and alertboxes) and these blocks are only executed when something 'triggers' them. In the case of eventhandlers, these triggers are events (hence the name eventhandler: they handle the correct processing of events by executing SLIC code). In this second usage events are even more important than functions: the options of executing SLIC code without eventhandlers and events are extremely limited and without this usage of events SLIC would be (almost) completely useless. The best example of this is that I've been able to give lots of examples of SLIC code in the previous chapters to demonstrate things like variables and arithmatics without using functions (or events used as functions), but I haven't been able to give even a single example that didn't use events to trigger the actual example code (it's possible to do so, but that would force me to use much more exotic methods of triggering code, which I didn't find very desirable either).

5.2.1 Generating events

Generating an event means that you more or less force an event to happen. So if you generate a CreateUnit event, a unit will be created and if you generate a GrantAdvance event, a advance will be given to a player (the details of what type of unit and what advance is given to what player are taken care of in the arguments). So basicly generating an event is very similar to calling a function. The syntax is very similar and the effects of events are identical to or comparable with the effects of most functions (though events are still different enough to add a whole new layer of functionality to the game). As I mentioned, the syntax for generating events is very similar to that of calling functions:

Event:<eventname>(<arguments>);

A complete list of all events in the game and the variables they require can be found in the appendices. Examples:

 HandleEvent(BeginTurn) 'TestEventGeneration' post {
 location_t tmpLoc;
 city_t tmpCity;
 int_t tmpPlayer;
  tmpPlayer = player[0];
  if (tmpPlayer > 1) { // usually player 0 is barbarian, player 1 is human
   if (HasAdvance(1, ID_ADVANCE_NUCLEAR_POWER) { // if human has nuclear power
    if (!HasAdvance(tmpPlayer, ID_ADVANCE_NUCLEAR_POWER) {    // if AI doesn't have it already
     Event:GrantAdvance(tmpPlayer, AdvanceDB(ADVANCE_NUCLEAR_POWER, 0);  // give AI player nuclear
// power too (reason 0 often means
// 'unknown' or 'scenario' or something like that
     tmpCity = player[0].capital;
     Event:CreateUnit(tmpPlayer, tmpCity.location, tmpCity, UnitDB(UNIT_NUKE), 0); // give player 1 nuke in capital
     tmpCity = player[0].largestcity;
     Event:AddHappyTimer(tmpCity, 10, -3, 0);    // add -3 happiness for 10 turns (reason = 0)
     Event:CreateUnit(tmpPlayer tmpCity.location, tmpCity, UnitDB(UNIT_NUKE), 0); // give player
// 1 nuke in largest city (might be same
// city as capital)
     Event:AddHappyTimer(tmpCity, 10, -3, 0);    // add -3 happiness for 10 turns (reason = 0)
     GetRandomNeighbor(tmpCity.location,tmpLoc);
     Event:KillTile(tmpLoc);      // kill 1 tile in radius of biggest city (nuclear test-site)
    }
   }
  }
 }

WARNING: this code will crash the game, it's a demonstration on how using events could go wrong (see paragraph 5.2.2), don't use this in the game:

 HandleEvent(SleepUnit) 'TestTiming' post {
 unit_t tmpUnit;
 unit_t tmpUnit2;
  tmpUnit = unit[0];
  while (GetUnitsInCell(tmpUnit2.location)) > 0) { // while still units at this location
   GetUnitFromCell(tmpUnit2.location, 0, tmpUnit); // get unit with index 0 at this location
   Event:KillUnit(tmpUnit, 0, 0);  // kill this unit (if function had been used, other unit
// would have gotten index 0)
  }
 }
5.2.2 Restrictions & timing

There are a few things to keep in mind when generating events. First of all, not all events can be generated. Some events require special types of arguments that don't exist in SLIC and can thus not be given to events. Arguments like this are marked with GEA_ in front of their name. Example: the MoveOrder event requires (among others) as argument a move path. But a move path is a complex object that simply doesn't exist in SLIC, so this event can only be used to trigger code with and not to be generated.

Another type of event that can't be generated are those events that FE are part of the turn sequence. When an AI player has his turn, a whole list of actions need to be performed: visibility needs to be updated, production points need to be added, unit orders need to be processed, etc. All these actions have events tied to them (like UnitBeginTurnVision or CityBeginTurnVision) that can be used to trigger certain events on. However, generating these events outside the regular turn-sequence will either not work at all or cause all kinds of weird results, the game is simply not designed to add production points to the things cities are building while the human player is giving his units orders, so this should normally not be done either.

The third kind of event to keep an eye on are those events that are simply obsolete and don't do anything useful anymore. I don't know exactly which events there are but one should assume they exist, so if certain events have little effect when generated, you should keep in mind that this event might be obsolete or broken (the same goes for functions and variables and events as triggers BTW).

Another very important thing to keep in mind when generating events is timing. When a function is called, it will be executed immediately, so it's effects on the game are immediately noticable and can be taken into account in the rest of the code. FE, you could have some code that performs a loop as long as there are less than x units in some location. This works perfectly if units at that location are created using the function CreateUnit: the units will be created instantly and the execution of the code will not continue before the unit has been created, so the next time you evaluate the number of units at that location it will be higher than before. With events this would go horribly wrong: events are not executed immediately when they are generated, but rather put in a queue and executed one-by-one. An event isn't executed until all other events that have been added to the queue before this event have been completely executed. In this case 'completely' includes the SLIC code: a event isn't 'completely' executed until the event has been processed by the game-engine AND all the SLIC code tied to this events (through eventhandlers) has finished executing. Events are executed 'asynchronously', which in practice means that you have no control over when they will execute: it might be right after the current event has been executed, but there may also be 20 other events that come first. Whenever you generate events in your code, you should be very well aware of this, since it is very, very easy to make mistakes in this area: at the time of writing this, only a couple of thousand lines of code have been written by fans (hopefully when you read this the amount of existing SLIC code will be a multitude of this), but already dozens of mistakes have been made due to not realizing this (and I have to admit many of these mistakes were made by me).

  Administration
This site is currently maintained by: BureauBert (Webmaster)  Maquiladora (Manager)  Legal Notice: Legal Notice Statistics: 71194 unique users since 02.05.2004 (» Site Statistics) Hosted by: WebhostOne
Share this page:  VK Facebook Twitter LiveJournal OK MR Google+ LinkedIn tumblr Pinterest Blogger Digg Evernote