In this topic, I will explain only the script syntax, which is common to all the different scripts. The various script contexts will be exposed in other threads. I will suppose that readers are somewhat used to programming languages, although Maniascript is made to be as simple as can be (do you know what a variable is ???). If you're new to programming, you can safely ignore the Protips.
Basics
A script is a text, composed by lines (aka, instructions). Instructions are separated by semicolons, as in C/C++.
It looks like
Code: Select all
declare MyVar = 12;
MyVar += 1;
DoSomething(MyVar);
Simple data Types
Boolean : can be either True or False
Integer : numbers such as 2 or -5 or 31337
Real : decimal numbers such as -4.2 or 99. (do not forget the final dot, because 99 is an Integer. Beware, those are different)
Text : any character sequence between double quotes : "plop" "gouzi" or "456.32".
Protips :
- inside a Text, The usual escape sequences such as "\n" or "\\" are supported.
- you can also declare a Text value between 3 double quotes. When doing so, you won't need to escape chars, and it can expand on many lines. """plop="452.12.22" toto"""
Variable declaration
In Maniascript, variables must be declared, either by specifying a Type, or an initial value :
Code: Select all
declare Integer MyVariable;
Code: Select all
declare MyVariable = 42.;
If the variable is defined as a Integer, you will never be able to store anything else inside. The same goes for other types.
Protip : Variables are always defined and initialized when declared, meaning they always have a valid value. If not specified, this value will be a default value for the current type.
Variable affectation
Once declared, you can change the value of a variable with a single equal sign :
Code: Select all
MyVariable = 13+37;
Comments
Anything right of a double slash // is a comment
Anything between /* and */ is also a comment
Code: Select all
Var = 2 + 5; // This is a comment
Var = 2 /* This is a comment */ + 5;
Boolean operations are : ! && ||
Code: Select all
Var1 && (!Var2 || Var3)
Code: Select all
Var1 + (Var2 / Var3)*1000
To append strings, you can use the ^ operator. You can also append a Real or a Bool or an Integer. It will be converted to Text.
Code: Select all
MyVar = "Hello " ^ "world!";
Code: Select all
MyVar = """Hello {{{NameOfThePlayer}}}, how are you today??? Five = {{{2+3}}}. \o/ """;
To compare values, you can use the usual : == != < > <= >=
Greater/lower comparisons do not work with Booleans.
Debug
When trying to found bugs in a script, you'll use the Debugger. For now, you can only access it by pressing Ctrl+~. It works pretty much anywhere in game. It has 2 modes : a reduced mode where you can only see the logs without interacting, and the Full mode. In Full mode you can select the script that you want to debug (because many scripts can be running at once, for example a camera effect, and the rules of your game). It will show you only the log of the selected script, along with the code of the script, which you can not edit there.
Log and assertions
It's often useful to print some text in the log. You can do that with :
Code: Select all
log("Something went wrong!");
Sometimes it is more easy to check if some requirements are met.
Code: Select all
assert(MyVariable == 3);
Control structures
In maniascript, you'll find the usual control structures :
Code: Select all
if( /*Boolean*/ ) /*Instructions;*/
while ( /*Boolean*/ ) /*Instructions;*/
for( /*VariableName*/ , /*FirstValue*/ , /*LastValue*/ ) /*Instructions;*/
foreach( /*Element*/ in /*Array*/ ) /*Instructions;*/
foreach( /*Key*/ =>/*Element*/ in /*Array*/ ) /*Instructions;*/
switch(/*Expression*/) {
case Expression1: /*Instructions;*/
case Expression2: /*Instructions;*/ .....
default : /*Instructions;*/
}
Functions and main()
Most Maniascripts are to complicated to fit in one set of instructions. That's why you can define functions. A function definition looks like that :
Code: Select all
[TypeOfTheReturnedValue] [NameOfTheFunction] ([TypeArg1] [NameArg1], [TypeArg2] [NameArg2] .... )
{
[Instructions];
}
Code: Select all
Integer Minimum (Integer A, Integer B)
{
if (A<B) return A;
return B;
}
If your code is simple enough to fit entirely in the main() function, you can omit the function header, and write the instructions without any enclosing brackets.
Variable scope
After a declare instruction, the variables are only accessible in the directly enclosing curly-brackets {...}.
Directives
At the top of a script, some special code may be required : those special lines start with the '#' character. Note that directives are not finished with a semicolon ';'
- #RequiredContext XXX : the context of a script. This is meant to avoid trying to use an EditorPlugin as a GameMode, because it will always fail
#Const XXX YYYY : declare a constant named XXX, with value YYYY. This value can not be modified.
#Setting XXX YYYY : from the script's scope, #Setting behave exactly as #Const. But this value can be modified from "outside" the script
#Include "XXX" as YYYY : load a library or include a file, and bind the functions to the namespace YYYY.
Code: Select all
#Include "Library.Script.txt" as MyLib1
MyLib1::Function1();
Code: Select all
// contents of "Library.Script.txt"
Void Function1() {
log("Foo"^"bar");
}
You can declare Lists by using any type, followed by square brackets :
Code: Select all
declare Text[] MyList;
MyList= ["Alpha", "Beta", "Gamma", "Omega" ];
Code: Select all
log(MyList[0]); // Will log : Alpha
log(MyList[3]); // Will log : Omega
Valid operations are
Code: Select all
declare Size = List.count;
declare SortedList = List.sort();
List.add(ValueToBeAdded);
List.removekey(IndexToBeRemoved);
List.remove(ValueToBeRemoved);
declare DoesExist1 = List.existskey(Index); // equivalent to 0 <= Index < List.count
declare DoesExist2 = List.exists(ValueToBeFound);
declare Index = List.keyof(ValueToBeFound); // such as List[Index] == ValueToBeFound
List.clear();
Code: Select all
declare Text[Integer] MyArray1 = [15 => "Quinze", 42 => "Quarante-deux", 100 =>"Cent" ];
declare Real[Text] MyArray2 = ["Pi" => 3.14, "Tau" => 6.28, "Leet" => 13.37 ];
Code: Select all
log(MyArray1[42]); // Will log : Quarante-deux
log(MyArray2 ["Tau"]); // Will log : 6.28
MyArray2["SquareRootOfTwo"] = 1.41; // Add a new Value in the array
Code: Select all
declare Size = MyArray1.count;
declare SortedByValues = MyArray1.sort(); // Sort by Values
declare SortedByKeys = MyArray1.sortkey(); // Sort by Keys
List.removekey(KeyToBeRemoved);
List.remove(ElemToBeRemoved);
declare DoesExist1 = List.existskey(Key);
declare DoesExist2 = List.exists(ElemToBeFound);
declare Key= List.keyof(ElemToBeFound); // such as List[Key] == ElemToBeFound
List.clear();
Those instructions allow to pause the execution of the script. It is very useful, since during the execution script, nothing else happen : the display is not updated, the simulations of the game are stuck, the logs are not being updated, and so on.
yield; The script pauses for the shortest amout of time.
sleep(XXXX); The script pauses for XXXX miliseconds.
wait(YYYYY); The script pause until Boolean YYYYY is True. YYYYY will be evaluated repeteadly, so be careful when YYYYY is a function call : the fonction will be called many times.
Equivalents :
yield; is equivalent to : sleep(0);
sleep could be written :
Code: Select all
void Sleep(Integer XXXX){
Start = Now;
while(Now < Start + XXXX) {
yield;
}
}
Code: Select all
while(!YYYYY) {
yield;
}
Code: Select all
Start = Now;
wait(Now > Start + 1000 || PendingEvents.count >= 1);
In ManiaScript, you can not declare new classes or any kind of type. Also you can not directly instantiate objects of an existing class. You can only declare pointers to existing objects.
Yet, there are "2 kinds" of pointers. The first one is what we call an alias. It's fast, and quite powerful, yet its behaviour can be surprising, especially if you're used to common pointer programming. The second is a more regular affectation, roughly emulating pointers.
Aliases
Here's an example :
There's a array of players, sorted by descending score, called Players.
Important note : we're talking here about an API array, one that's pre declared as a system variable.
One can write :
Code: Select all
declare BestPlayer <=> Players[0];
// Alice is the best player, so BestPlayer "points" to Alice
Code: Select all
declare BestPlayer <=> Players[0];
// Alice is the best player, so BestPlayer "points" to Alice
{
... // Some code doing stuff
}
log(BestPlayer.Login);
// Will log Alice, right ???
Code: Select all
declare BestPlayer <=> Players[0];
// Alice is the best player, so BestPlayer "points" to Alice
Players[1].Score += 1000;
// Give 1000 points to the 2nd best player, which is Bob
// those 2 line are completely equivalent :
// they Will log Bob, because he has an higher score right now.
log(BestPlayer.Login);
log(Players[0].Login);
That's why we thought it would be better to use another symbol when setting variables : to remind that's not a plain affectation.
Now what if you want to keep Alice in a variable, and not the Best Player ?
The following code will work "as expected".
Code: Select all
declare BestPlayerId = Players[0].Id;
// BestPlayerId is an Ident : will never change
Players[1].Score += 1000;
// Give 1000 points to the 2nd best player, which is Bob
log(Players[BestPlayerId].Login);
// Will log Alice
ProTip #2 : Yes, this can also be written
Code: Select all
declare BestPlayer <=> Players[Players[0].Id];
// will be an alias to Players[AliceId] and not Players[0]. Huge difference !
Players[1].Score += 1000;
// Give 1000 points to the 2nd best player, which is Bob
log(BestPlayer.Login);
// Will log Alice. Will also costs more CPU, for the alias has to be resolved.
Code: Select all
declare BestPlayer = Players[0];
// Note the difference : I used = instead of <=>
// It will do the same as : declare BestPlayer <=> Players[Players[0].Id];
Players[1].Score += 1000;
// Give 1000 points to the 2nd best player, which is Bob
log(BestPlayer.Login);
// Will log Alice. Will also costs more CPU, for the alias has to be resolved.
Tricky alias cases
Aliases in arrays
Unfortunately, there are some edge cases where the aliases become a bit tricky....
What happens if you declare yourself an array of Classes.
Code: Select all
// Players[0] => Alice
// Players[1] => Bob
declare MyArray = [Players[0], Players[1]];
declare MyVal <=> MyArray[0];
MyArray = [Players[1], Players[0]];
log(MyVal.Login); // It will log "Alice", not what you may expect
This is because the value stored in MyArray is already an alias, so we copy the alias directly, instead of making an alias to the alias. There are technical reasons : we can not easily do alias to aliases T_T'
(By now, every sane person should be confused... so don't worry if you are...)
Functions returning classes
As with arrays, we have to make a difference between API functions and functions declared in script.
When you call an API function, the result will be a "simplified" alias. Those are unambiguous aliases referring to the object's Id, inside of an API-defined array.
Code: Select all
declare MyLabel <=> GetFirstChild("Label");
// MyLabel is an alias to Page.MainFrame.Controls[IdOfTheFirstChildFound]
Code: Select all
declare MyLabel <=> Page.MainFrame.Controls[GetFirstChild("Label").Id];
Code: Select all
declare BestPlayer <=> GetBestPlayer();
// Alice is the best player, so BestPlayer is an alias for Players[AliceId]
Players[1].Score += 1000;
// Give 1000 points to the 2nd best player, which is Bob
log(BestPlayer.Login); // Will log Alice
In both cases, the using a class value you obtained from a function call will never call the function again.
/// Work in progress.
Advanced types : enums
declare for
Presistence
Networking