|
此为中文测试版,不支持中文输入,但支持中文复制。支持中文程序名自动转换。纯英文版见猛击此处
Galaxy___editor_chinese_test_2.zip
(1.61 MB, 下载次数: 14)
Documentation
General
Galaxy++ is a programming language that extends upon the original galaxy language, adding a lot more features. This means that any galaxy code you have should run exactly the same way when feeding it to the galaxy++ compiler.
Like most other programming languages, comments and whitespace is ignored by the compiler, so they can be placed pretty much anywhere without changing the behavior of the program.
If you feel some of your questions still go unanswered after reading this, or you find errors in the documentation, please pm me or post a reply. I will get back to you, and perhaps add to the documentation.
Compiler options
Although they might not classify as a language feature, I feel it's relevant to highlight some of the options in the compiler.
Obfuscation
There are a couple of tools for obfuscating the code. One is to rename everything to short names such as a, b, c. The other is to obfuscate string literals. What this will do is to "encrypt" every string so it is not readable by humans, and "decrypt" them when starting the map.
Please note that no code obfuscation can completely prevent others from reading, understanding and stealing your code. It just makes it difficult.
1: Declarations
1.1: Namespaces
You have the option to define a namespace for each file. The namespace must be declared as the first thing in the file. You declare a namespace with
#namespace myNameSpace
By default, you can only directly refer to things in your own namespace or the default namespace. If you want to refer to declarations outside your current namespace, you can use the dot notation. For example, if the method method() is in the namespace otherNS you can refer method with
otherNS.method()
If you want to be able to directly refer to everything in another namespace you can add a #using declaration. These must be at the top of the file, directly below the namespace declaration if present.
#using otherNS
1.2: Initializers and Library definitions
Initializes are basically methods that are run at first, when the map loads. Unless you made your own call to initialize the standard library, this call will be inserted before calling the initializers. You can make a simple initialize with
#Initializer{[statements]}
The initializers can also contain data about a library you are defining. There are 4 different library fields: #LibraryName, #LibraryVersion, #SupportedVersions and #RequiredLibraries. An example of a library initializer is
#Initializer(#LibraryName = "my library"#LibraryVersion = "enterprise",#SupportedVersions = "trial, basic"#RequiredLibraries = "other lib 1:version2, (otherLib2:v1.7.4)"){[statements]}
All the library fields are optional, except you need both a name and a version in order for others to add it as a requirement. If your library version is not on the list of supported versions, it will be added. You may not use “:”, “(“, “)” or “,” in the name or versions, and all leading and trailing whitespace is ignored. The method block is optional. It can be replaced with a semicolon.
If an initializer has defined any required libraries, the initializers of those libraries will be called first. In case of cyclic dependencies, a warning is reported, and as few dependencies as possible are ignored.
1.3: Includes
Any custom includes are ignored. Includes will be inserted by the compiler as needed, but all code except for the standard libraries are required in the project.
1.4: Methods
Unlike normal galaxy, it is not needed to have defined the method before using it.
1.4.1: Inline methods
Inline methods are marked with the keyword #inline. When compiling a call to an inline method, the contents of the method is inserted instead of calling the actual method. This is done in a way so that there is no semantic difference to calling the method instead.
#inline int method(...){...}
Note that inline methods may not be recursive i.e. they may not be called from themselves.
1.4.2: Trigger methods
There is a #trigger keyword in galaxy++, but it no longer has any effect. It was previously used to identify methods that were triggers, but this is now done by searching for a string in a call to TriggerCreate. The keyword is kept for backward compatibility.
1.4.3: Reference parameters and extra return values
You can mark parameters with the #ref or #out keywords. Arguments passed to parameters marked with one of these must be variables. The effect of #ref is that any changes done to the variable during the method call is reflected back in the calling method after the call. #out is similar. Like with #ref the changes done to this variable will also be done after the call, but unlike #ref, it is required that the called method assigns a value to an #out parameter, and he cannot read the parameter before he assigns a value to it. After compilation, an #out parameter is not actually passed to the function, it is only returned, so it can be used as extra return values.
string[17] strings;string Next(#ref int i, #out bool hasNext){hasNext = i + 1 < strings.length;if (hasNext)return strings[++i];return "";}
1.4.4: Passing bulkcopy data
Another thing to not is that it is now possible to pass types that would otherwise result in a bulk copy error between methods. This includes struct types and arrays. From your point of view, they are passed in exactly the same way as normal types. The compiler will send them via the data table instead.
1.5: Fields
Fields are pretty much what they are in galaxy, except that you don't have to have defined them above where you use them.
1.6: Structs
Like with fields and methods, you don't have to define structs above where you use them.
A new thing in galaxy++ is that structs can now contain methods. Simply place a method inside a struct, and that method will be a struct method. Struct methods will always be called with a specific instance of the struct. It is possible to directly refer to the members of the struct that is being called on by just typing the name of the member.
struct Foo{int[10] elements;int GetSum(){int sum = 0;for (int i = 0; i < elements.length; i++)sum += elements;return sum;}}...void Method(Foo foo){if (foo.GetSum() > 9000)......}
1.7 Bank preload statement
The preload bank statement from GUI is not an actual method call in code, so I added extra functionality to call this.
#PreloadBank("BankName", 2);
I felt that it would be misleading to place it inside functions, since it is not a statement which is executed there, but rather just a message to Starcraft II to load the bank when loading the map. Therefore it is placed out next to methods and fields. Note that the name and playerNr must be literals. I.e. you can not use any kind of variables or expressions other than what you see above.
2: Statements
A statement is basically the members of functions where it doesn't make sense to talk about it having a type. Examples of statements in galaxy are while statements, if statements and expression statements. In this documentation I will only cover statements that are new or have added functionality in galaxy++.
2.1: If statements
In galaxy you were forced to place a block inside if statements, now you can also place a single statement without the block.
if (i < ar.length)i++;
2.2: For statements
I added for statements in galaxy++.
for ([init]; [test]; [update])[body]
Basically, what it will do is first execute the init, then for as long as the test evaluates to true, the body and then the update is executed. Like GUI for sentences.
2.3: Switch statements
I also added switch statements.
int ammount;bool isRanged = false;#switch (GetUnitTypeString()){#case "Zergling":ammount = 100;break;#case "Marine":isRanged = true;#case "Zealot":ammount = 50;break;#case GetSuperUnitName():ammount = 1;break;#default:ammount = 10;break;}
It will test the expression it gets in the first parenthesis against every case in the order they are listed. When one of them equals, it will execute the contents of the case. If no cases were equal to the expression, the contents of default will be executed. Note that it is possible to fall through to the next case if one doesn't write break, like it is done from marine to zealot. Another thing to mention is that the method GetUnitTypeString() will only be called once, no matter how many cases there are. Also, GetSuperUnitName() is only called if none of the above cases matched.
2.4: AsyncInvoke statement
The AsyncInvoke statement will call a method in a new thread, and continue its own thread. This is done by running a new trigger, so the operator stack is also reset in the process (in case you are wondering, this is a good thing :)). If you wanted to call a method like
int CallMe(int a, bool b){...}
you could write
#AsyncInvoke<CallMe>(2, false);
In case CallMe is in another namespace called OtherNS, you can write
#AsyncInvoke<OtherNS.CallMe>(2, false);
Since the target function is called in a new thread, it is not possible to return a value from it, so any return values are ignored.
2.5: Local declarations
I made it possible to make multiple local declarations in one statement as long as they are of the same type.
int a = 2, b, c = a + 1;
3: Expressions
Expressions are everything that can have a type. Examples of this is method calls, references to local, global or struct variables, binary operations (+, -, *, /, %), etc. Like with statements, I won't cover the expressions that are the same in galaxy as in galaxy++.
3.1: The ++ and -- expressions
I added support for the ++ and -- expressions. They can be placed before and after a variable, and will increment or decrement the variable. When placed after the variable, the current value of the variable is placed where the expression is, and then the variable is updated. For instance, if you write
int i = 0;ar[i++] = i;
then then you will have set the 0th index of ar to 1. Placing it before the variable will update the variable, and then the updated value is placed where the expression is. E.g.
int i = 0;ar[++i] = i;
will set the 1st index of ar to 1. This is most commonly used as a quick way of writing i = i + 1;
3.2: Invoke
Similar to the AsyncInvoke statement, this expression will call the target method via a trigger in order to reset the operator stack. Unlike AsyncInvoke however, this is not done in a separate thread. This means that all return values can still be fetched.
#Invoke<namespace.methodName>(args);
3.3: Assignments
With galaxy++ you can now place assignments inside expressions rather than just in statements. For instance you can write
a = b = c = d = 2;
and all of them will have the value 2. Note that the type of the assignments are the type of the left side. What this means is that for instance if c is of type fixed, and b is an integer, you will get an error since fixed is not assignable to int.
3.4: Array length
You can get the length of an array by calling .length on it.
String[2] ar;return ar.length; //returns 2
3.5 Casts and implicit casts
I added cast expressions. They are a quick way of converting between types that can be converted between.
fixed f = 2.2;int i = (int) f;
I also made some implicit casts, which means that between some types the cast occurs automatically.
int i = 2;UIDisplayMessage(playergroupAll(), c_messageAreaSubtitle, "i = " + i);
In this example, the i is cast to a string, then the two strings are concatenated, and the whole string is cast to a text.
Pointers
This is placed a little out of place compared to the structure of the rest of the documentation, but I thought I would keep everything regarding pointers together.
Pointer types
You can define a type as a pointer by appending *. For instance
int* i;string** s;
Here i will be a pointer to an int, and s will be a pointer to a pointer to a string.
Dynamic array types
You can define a dynamic array type by not specifying array bounds. For instance
int[] ar1;int*[] ar2;
Here, ar1 is a dynamic array of type int, and ar2 is a dynamic array of pointers to int's.
The * expression
If you have an expression of pointer type, and you would like to get the value of that expression, you prefix it with *. For instance
void foo(string* s){UIDisplayMessage(PlayerGroupAll(), c_messageAreaSubtitle, (text)(*s));}
Here, the *is needed to get the actual contents of s. Also note that *s is in a parenthesis. If this was not the case the compiler would have tried to multiply a variable called text with the variable s.
The -> expression
The -> expression is really just a quick way of writing (*foo).bar.
struct Foo{int bar;}void method(Foo* foo){foo->bar = 2;(*foo).bar = 2;}
Here, the two statements are equivalent. In both cases, bar will be set to 2.
The #new expression
To create new pointers or dynamic arrays, use the #new expression. For instance
int* i = #new int();int[] ar1 = #new int[*i]();
Here, i is initialized to a new integer, and ar1 is initialized to a dynamic array of size *i (which will be 0 in this case).
Note that the #new expression allocates a new instance in the data table. To avoid memory leaks, remember to call #delete after you are done with the instance.
The #delete statement
The delete expression will remove the supplied pointer or dynamic array from the data table. You should make sure that you call #delete if you are done with your pointer or dynamic array. Note that the #delete statement will only remove the actual pointer you supply to it. So writing
void foo(int*[] intAr){#delete intAr;}
Will remove the array, but will not remove any of the child pointers. If you wish to purge all allocated instances, you can clear the global data table with the method.
DataTableClear(true);
So, note that you shouldn't call this unless you wish to lose all pointers and dynamic arrays. |
|