-
Notifications
You must be signed in to change notification settings - Fork 160
New compiler's end user cheat sheet
This is an end-user friendly reference of the new script compiler's features and script syntax changes. Based on the information from Features of the new Script Compiler.
Both regular and managed structs can now contain nested regular or managed structs:
struct Pair {
int X, Y;
};
struct TwoPairs {
Pair p1;
Pair p2;
};
managed struct ManagedStruct {
TwoPairs tp;
ManagedStruct* partner;
};
IMPORTANT: Initially there have been an engine limitation that prevented managed structs from containing other managed structs (pointers to structs) or managed arrays. This limitation is now removed in the latest version of ags4 engine.
You may now omit *
symbol when declaring pointers to managed structs. So, for instance, instead of
ManagedStruct* obj;
you can write simply
ManagedStruct obj;
and compiler will guess that it's a pointer, as managed structs may only be used as pointers in script.
You can now omit this
when writing struct functions and want to reference this struct's member variable or function:
struct Warrior {
int Health;
bool Dead;
import function LooseHealh(int hp);
};
function Warrior::LooseHealh(int hp) {
Health -= hp;
if (Health < 0) {
Dead = true;
}
}
You can now have extender attributes, that is - custom attributes attached to existing types, including builtin types.
Declaration examples:
attribute int AttrEx1(this struct S);
readonly attribute float AttrEx2(this struct S);
attribute float StaticAttrEx(static struct S);
attribute float ArrayAttrEx1[](this struct S);
readonly attribute float ArrayAttrEx2[](this struct S);
attribute float StaticArrayAttrEx2[](static struct S);
In all cases, you are still obliged to define the respective get_
and set_
functions with body (geti_
and seti_
for array attributes).
You can have multi-dimensional regular arrays:
You can declare or equivalently,
. .
// Declare two-dimensional array of size 5x3
int a[5, 3];
int b[5][3];
You can have any positive number of dimensions, e.g. int a[2][3][5][7][11]
;
Accessing elements is done similarily to declaration, that is:
int a[5, 3];
int e11 = a[1, 1];
int e23 = a[2][3];
Dynamic arrays now have Length
property that returns their length:
int arr[];
arr = new int[10];
int length = arr.Length;
Multi-dimensional dynamic arrays are also known as "jagged arrays". In these each element of all dimensions except last one is a pointer to another dynamic array, and elements of last dimension are values of the array's type. All sub-arrays must be created individually, but also may have a independent length.
Jagged arrays must be declared with all dimension sizes undefined, like:
int dyn_arr[][][]; // a three-dimensional dynamic array
First you must create the "parent" array, then you may create sub-arrays for each of its elements. Each nesting level the sub-array has one dimension less. When you create actual arrays, only the first dimension must have a fixed size, and following dimensions still have undefined size:
int dyn_arr[][][];
// create 3-dimensional array of size 10
dyn_arr = new int[10][][];
// for the 5th element of the first dimension,
// create a 2-dimensional array of size 20
dyn_arr[5] = new int[20][];
// for the 6th element of the first dimension,
// create a 2-dimensional array of size 25
dyn_arr[6] = new int[25][];
// for the 15th element of the 5th element,
// create a 1-dimensional array of size 40
dyn_arr[5][15] = new int[40];
// finally we may access the actual integer values
dyn_arr[5][15][35] = 100;
Currently supported are regular arrays of dynamic arrays. This is done by declaring array which have first dimensions of fixed size, and last of undefined size. The regular dimensions "end" at the last fixed-sized dimension.
Following is a regular array of 10 dynamic arrays of int:
int mixed_array[10][];
Following is a regular multi-dimensional array, which contains pointers to multi-dimensional dynamic arrays:
int overcomplicated_array[10][20][][];
Dynamic arrays may now be used to store regular structs too, in addition to managed pointers:
struct Pair {
int X, Y;
};
Pair many_pairs[];
many_pairs = new Pair[10];
many_pairs[0].X = 50;
many_pairs[0].Y = 60;
You can now define global variables right after the struct
or enum
declaration, for example:
struct Pair {
int X, Y;
} pair1, pair2, pair3; // declares 3 global variables of type Pair
enum TrafficLightColour {
red,
amber,
green,
} col1, col2, col3; // declares 3 global variables of type TrafficLightColour
Functions can now be defined in any order within same script. So you can reference a function first and define it afterwards. This makes recursive algorithms much easier to code.
int Func1(int a) {
return 2 * Func2(a); // uses Func2()
}
int Func2(int a) { // defines Func2() and is below its first use (this used to be illegal)
// some code here
}
You may still do forward declarations of a function, that is declare a function with import
keyword before it is first used.
Compiler can now evaluate integer or float expressions at compile time whenever result is actually constant and can be calculated at compile time. This allows to use expressions where syntax normally requires a constant. For example:
int arr[7 * 9 + 1]; // the size of array will be calculated by compiler
const
keyword now may be used to define compile-time constants, that is values that always stay the same. Only variable types allowed to be used with const
are those that may be calculated at compile-time, that is: int
and float
.
const int MainMenuRoom = 301; // main menu room number
const float PI = 3.14;
NOTE: there was also a const string
type, and it is still supported, but that's a special case, and it defines a unmodifiable string pointer, which may point to any kind of strings: old-style string
, new-style String
, and char array (e.g. to char text[100];
).
While const
defines a compile-time constant, readonly
defines a runtime constant. This means that the actual value may be determined at runtime, and be different each time, but the variable that stores it cannot be modified. You may have readonly
global variables, local variables, function parameters, and struct attributes.
Two most common uses of a readonly variable are function parameters and local variables. This works as a "failproof" method to ensure that they are only assigned, but are never changed within the function by programmer's mistake. For example:
function MyFunc(readonly int param) {
readonly int game_speed = GetGameSpeed(); // notice you can assign, but not change later
param = 10; // compiler will error
game_speed = 100; // compiler will error
}
NOTE: readonly
only tags the variable as not-writeable, but it does not prevent the engine or another script from modifying it behind the scenes. For example, you may import a variable as readonly
in one script module and as not readonly
in another.
readonly
struct attributes have another meaning: that defines attributes that can only be read (have getters, but not setters). This is left unchanged since the older AGS script rules.
In switch
, the compiler tries to find out whether code execution falls through the end of a non-empty case into the next case. Such "fall through" is usually unintended (a break;
is missing). So the compiler warns when it detects the situation. In order to shut up the warning and declare that the fall through
is intended, you can code fallthrough;
immediately in front of the next case.
Examples:
switch(inventory_item)
{
case iBlueCup: // no statements inside, falling through, but is fine
case iYellowCup:
player.Say("I fill the cup with water.");
break; // break present, all is good
case iVase:
player.Say("I fill the vase with water.");
// missing break, will yield a warning
case iBigTowel:
player.Say("That's my favourite towel. Oh well...");
fallthrough; // fallthrough keyword tells compiler that it's intended
case iSmallTowel:
player.Say("The towel is now wet.");
break;
default:
player.Say("I think this is useless.");
}
Previously compiler supported only post-increment and -decrement: x++
and x--
;
The new compiler also supports pre-increment and -decrement: ++x
and --x
;
The difference between these is that if this operation is used in another expression:
- With post-increment/decrement: the old variable's value will be used in calculation, and variable changed afterwards.
- With pre-increment/decrement: the variable will be changed and its new value will be used in calculation.
For example:
int x = 5;
int a = x++; // a = 5, x becomes 6
int b = ++x; // x becomes 7, b = 7
Bitwise negation is ~
operator, which converts each bit in the value to its opposite: 1 to 0 and 0 to 1.
The classic ? :
ternary operator is now supported.
Standard form: foo = (x > 5) ? 1 : 2;
means that foo is set to 1 if (x > 5), to 2 otherwise.
Can leave out the second part of a ternary so that "?" and ":" go right next to each other. For instance, int foo = bar ?: 15;
This means that foo is normally set to bar, but if bar is 0 or null then foo is 15 instead. Particularly handy for specifying a default in case a pointer variable turns out to be null. (This is the same functionality as the ??
operator in C#.)
If the function returns a managed object, you can now directly access properties and functions of returned object in the same expression.
For example: Character.GetAtScreenXY(x, y).Walk(100, 200);
Also sequences can now be undefinitely long.
String literals that are next to each other (separated only by whitespace) are treated as if they were one concatenated string literal.
player.Say("Hello" " world"); // same as "Hello world"