9. The GMSI API
This section will tell you which functionalities GMSI has for doing your favorite task (especially altering maps ;) ). However you should first learn basic GSL Syntax, since this is just a reference for some GSL Libraries that are provided by GMSI. If you don't know the GSL Syntax you won't understand anything here! Libraries are gsl files which contain gsl functions and system calls (calls to JAVA functions) which offer the functionalities you need. Libraries are stored in the script/lib folder and in the script/autoexec folder. All libraries in the autoexec folder are common libraries which many scripts need. So, the program executes all these libraries before any script file. This means for you, that you don't have to include them, if you want to use their functionalities. The libraries in the script/lib folder have to be included before you can use any of their functions (See: including functions). The folders script/lib and script are on the autolookup path of GMSI. So if you want to include any files in the folder, you can just write their names, you dont have to prefix them with the folders. Example: include("dialog.gsl");
will include dialog.gsl which is located in the script/lib folder.
Note that the folder where the script to execute was is also on the lookup path, so no matter where you script is, if you store libraries it needs in the same folder, the script folder or the script/lib folder, include will find them. The type Map is a struct which is defined in the library wc3MapDefinitions.gsl. This library, which is included automatically defines ALL necessary data types that a map contains. When a map is loaded, the loadMap function returns an instance of type Map. You can then use alter this struct. If you then hand it to the function saveMap, it will save your map to the desired path with the changes you have made. If you want to alter a particular part of the map, you should check the library's source. It is commented well, you will find what you search for very fast. Let's look at the definition of the type Map: typedef Map struct{
string fileName = null; //The name of the map file, when it was opened
InfoSection info = null; //General map information
EnvSection environment = null; //The environment (the tileset and the specific tiles)
object mapHandle = null; //An internal handle for loading / saving / deleting files from the maps archive. DON'T CHANGE or you won't be able to load or save files from this map anymore!
array<Wc3obj> objects = null; //The World Editor objects of this map (unis, abilities,...)
PlacedObjects placed = null; //The placed units, items, destructables and trees on the map
ScriptSection script = null; //The triggers of this map (including gui trigger, comments and JASS)
array<Rect> rects = null; //The rects (regions) of this map
array<Sound> sounds = null; //The sounds of this map
array<Import> imports = null; //The list of imported files
InternSection internal = null; //Internal stuff, which is only there because gosi doesn't parse really the whole map yet
}
As you can see, GMSI devides the map in different sections which are again struct types defined in the wc3MapDefinitions.gsl. So, if you want to alter something concerning triggers, you should probably check the definition of the type ScriptSection, since this is the place where the triggers are put. Here is an overview of the sections and for what you can use them:
This was just a coarse overview over the sections, you can check the detailed functionalities of the different sections, by checking their definitions in the file, they should be well commented. Debugging tip: If you want to check the content of the different sections, use the echo command and just cast the section you want to view to string. You will get all the content displayed on GMSI's console. Example: Map m = loadMap(.....);
echo(m.triggers + ""); //Lists, the trigger section (concatenation with empty string casts the struct to string)
echo(m.triggers.hpea); //Lists the attributes of the peasant This library is in the autoexec folder and is auto included. It contains many functions to load and save a map. Here are the most important ones: The basic function for loading maps: Map loadMap(string mapName)
This loads the map from the path <mapName> and returns a struct that contains it.
All sections are loaded automatically except for the environment section (since this one requires much memory and time to load/save). If you want to load also the environment, you have to use the extended loadMap function (described below). <mapName> is the path from your GMSI main path to the map. So for maps in the input folder it is "input/mapName.w3x". You can also use the [ref]external variable[/ref] @inputPath to refer to your input folder. This will ensure that your script also works on platforms where the user has changed the input path in the GMSI.ini. So it would be then: Map myMap = loadMap(@inputPath + "\\" + "myMap.w3x");
The extended loadMap function does the same as the normal one, but it offers more freedom in what you want to load and what you don't: Map loadMap(string mapName, bool loadEnvironment, bool loadUnchangedObjects)
<mapName> is again the map's path. <loadEnvironment> specifies whether to load the environment of the map or not (which is not done by the normal loading function).
If <loadUnchangedObjects> is true, than all object editor objects will be loaded to your object section. If this is false, then only changed objects will be loaded (such which appear pink in the World Edit's object editor).
This parameter is set to true in the normal loading function, so ALL objects are loaded by default.
Which advantages may it have to NOT load all objects? Well, then the unchanged objects don't appear when you iterate over all objects. Many times you want to change only user defined objects, not the basic ones. Since the number of basic objects is around 3000, your script will run much faster if you only iterate over the changed ones (which are normally less than 3000). However if you don't load these objects, you won't be able to access their values! So for the most situations, I encourage you to load all objects (this is why it is on by default). After we have loaded a map and altered it we need a function to save it back to a desired file. This does the saveMap function: saveMap(Map map,string outputName)
It takes a Map struct <map> and saves the data contained in it to the Map <outputName> (which is created or overwritten if it already exists).
That is all you have to know to do changes to a map. Just load it, alter the sections and save it, that's it. Note: This library is only recommended for macroing. If you need pattern matching rather use pattern.gsl, which allows much mightier pattern syntax This library is in the autoexec folder and is auto included. It contains calls to get and apply and pattern matcher and replacer, called "Matcher". A matcher is an object that stores pattern/replacement pairs. Then, this matcher can be applied onto a string: It will search the string for all stored patterns, replace them with the appropriate replacements and return the string with the included replacements. This is very useful to define macros that you can use anywhere in your map (for example in the tooltips of objects and in your trigger code. You get a new matcher by invoking the function Matcher getNewMatcher()
This new matcher contains no pattern/replacement rules yet. You can add such rules with the two functions: void addReplacement(Matcher m, string searchFor, string replaceWith){
void addMacro(Matcher m, string searchFor, string replaceWith){
They share the same signature. First they take a matcher <m> to which the replacement rule should be added. Then they take a pattern to <searchFor>. If such a pattern is found the matcher will replace it with <replaceWith>.
This would be very unusefull if only plain patterns were allowed, however wildcards are allowed: For the <searchFor> string, following wildcards are allowed:
Note at %e: Reluctant means, that %e matches basically everything, but it will try to match as few characters as possible. So if it is surrounded by boundery characters, like for example in the pattern "|%e|", then it will try to find pattern where something is between two pipe signs (|) This pattern would for example match the string "|a|b|c|" twice at "|a|" and at "|c|". If %e wasn't reluctant but "greedy", it would just match once at "|a|b|c|" treating the inner two pipes as "everything". These wildcards can be again inserted in the <replaceWith> string. Here the syntax "%NUM" is used, where NUM is the number of the wildcard in the <searchFor> string, counted from left to right, starting with 1. So, if for example, <searchfor> was "%i %i %w" and <replaceWith> was "%3 %2 %1", then the matcher would, upon application, search the string for two integers followed by a word and replace them with a string that contains first the word, then the second integer, then the first integer. But as you can see, there are two functions: addMacro and addReplacement, so where is the difference? The difference is the behaviour: addReplacement will add a rule that checks for the pattern and replaces it with the replaceWith string. addMacro will add a rule that checks for the pattern, assembles the replacement string, treats this one as GSL code, execute it and replace the pattern with the script output of the execution of that code. The script output of the code is the internal buffer that is assembled with the script output operator $ Note that the matcher resets this buffer before executing a replacement. So no previous content will be in the buffer, only the replacement may fill it. I know that is a bit complicated when explained like that, so if you don't understand that yet, read on and check the examples below, then it will hopefully get clear. After building the rules for a matcher, you probably also want to finally use the matcher on one (or more) strings: A single string is checked by calling: string applyMatcher(Matcher m, string applyTo){
which takes a Matcher <m> that does the job and a string <applyTo> onto which the matcher is applied. The result of this function is the string, with all appearances replaced according to the predefined rules.
Note that if two rules match on the same part of the string, then the rule that was added to the matcher first will be applied. Now you got everything for using the pattern matching system, so let's see some examples: Replacement demonstration: Matcher m = getNewMatcher(); //get a new matcher
addReplacement(m,"#%i,%i#","@%2,%1@"); //add a replacement rule
This sets up a matcher that checks for two integer appearances between #'s seperated with a comma and swaps them and changes the separating #'s to @'s.
Let's use this matcher: string input = "#5,2# bla #4,1#" //input for the matcher
string output = applyMatcher(m,input); //application result is stored in output
echo(output);
The echoed output will be "@2,5@ bla @1,4@" since that pattern was found and applied twice.
Macro demonstration Matcher m = getNewMatcher(); //get a new matcher
addMacro(m,"#%i#","$(%1*10);"); //add a macro rule
This rule will do the following, it will check the input for integers, surrounded by #'s. if it finds one it will insert it into the replacement expression, which multiplies it with 10 and then outputs it using the script output operator $.
Let's use this matcher:
string input = "#5# bla #2#" //input for the matcher
string output = applyMatcher(m,input); //application result is stored in output
echo(output);
This will echo "50 bla 20".
This can be used to parse your map for macros. For convenience, two functions have been added to macros.gsl. The first one is the getBasicMatcher function, which gets you a matcher that checks for common patterns in maps, it uses the #'s as surroundings for macro areas. Lets check its source code: Matcher getBasicMatcher(){
include("utilities.gsl");
Matcher m = getNewMatcher();
addMacro(m,"#%w#","$format(%1);"); //A single word a in #a# is treated as an identifier which has to be formated and then displayed
addMacro(m,"#%w.%w#","$format(%1.%2);"); //Two words a,b in #a.b# seperated with a point are treated as a struct name which has to be formated and then displayed
addMacro(m,"#%w%#","$formatPerc(%1);"); //Same as above, if a % is at the end the stuff should be percent formatted
addMacro(m,"#%w.%w%#","$formatPerc(%1.%2);"); //Same with % and a.b
addMacro(m,"//#%e#","%1"); //Everything else in //#a# (for triggers) is just treated as gsl code and executed
addMacro(m,"#%e#","%1"); //Everything in #a# (for non triggers) is also treated as gsl and executed
return m;
}
As you can see it gets a new matcher and adds some basic rules to it.
To see how useful this is, lets first see the second convenience function: applyRecursive: int applyRecursive(Matcher m, var a){
This function applies a matcher recursive on the variable a. That means if a is a struct or array it will check all members/entries for the pattern and do the replacings. If some of the members are again structs or arrays, it will also apply recursively to them. It returns the number of strings it has checked. Do you get alerted? Structs that contain structs again? Exactly, the type Map is one of these! So if you use applyRecursive on your Map struct or a part of it, it will check all your map's strings for the patterns and replace them. But it does more: It opens a namespace for each struct/array it checks for scripts. So, in your macros you can for example use the name of unit values in a unit's tooltip since the namespace points to that unit's array. So lets get back to the basic matcher, as first rule it has: addMacro(m,"#%w#","$format(%1);");
So, if we use for example "#HP#" in the tooltip of a unit and apply the basic matcher on the map, then the #HP# will be unfolded to "$format(HP);" which then will be treated as GSL code. Due to applyRecursive, we are in the namespace of the unit, so HP will evaluate to the unit's Hitpoints and thus the script output will be the unit's HP (format just formats the number that it looks nice for floating point values).
The other rules in the basic matcher add some more functionality.
The last rule is for example:
addMacro(m,"#%e#","%1");
So if the matcher finds just ANYTHING between #'s that doesn't match one of the other rules it will just treat the stuff between the #'s as GSL code and execute it.
With this rule you can quickly use arbitrary script areas in your map.
If you need another example on how to use the basic matcher check out the example script buildTooltip.gsl in the script folder. It just uses the basic matcher on the entire map.
If these basic rules look ugly to you or you need special rules, then just build your own matcher! You could for example specify the version of your map somewhere in the script and then add the macro "@@vers" that is just replaced by the version number or "@@home" that is replaced by the map's author's homepage. If you then use these multiple times in your map, you just have to change one script line if you want to change them all, and not search your entire map for appearances of this string. Some kinds of scripts are only designed for one map. However, many scripts like a tooltip creation script can be designed for many maps. Nobody wants to alter the source code of the script when he wants to apply it to another map. So you want to "parameterize" your script somehow with the maps name or other parameters, like which appearance of tooltip should be chosen or other stuff. There are two approaches for gathering parameters: 1)As aditional command line parameters when starting GMSI from the console. 2)A dialog appears and asks the user to choose a map/enter a string/... . Both approaches have advantages. 1) allows you to call GMSI from another program without having to open the GUI. 2) is more convenient, if you are using the GUI. So the best and encouraged way is to combine them: First check if a command line parameter was specified and if not then display a dialog. The functions for input dialogs are defined in the library dialogs.gsl. This one is not auto included, so you have to include it yourself if you want to use it. Here are their declarations: string fileDialog(string title,string startFolder,string allowedExtensions)
bool confirmDialog(string title, string text, int buttons)
string inputDialog(string title, string text, string defaultValue)
string optionDialog(string title, string text, array<string> options, string defaultValue)
If you need more information just browse the library's source, it is well commented. Command line parameters can be checked with the external array @args. So the encouraged way to gather parameters is like this: First check if the desired argument is null. If it is, then show a display. Example: Lets say the script takes one map which it should load. It can be entered as a command line argument if no argument is set then from a file dialog: string inputMap; //this var will contain the desired map
if(@args[0] != null){ //check if a command line argument was used
inputMap = @args[0]; //use the command line argument
} else { //if not display a dialog (restricted to .w3x files)
inputMap = fileDialog("Choose a map file",@inputPath,".w3x");
}
This can be written shorter using the conditional expression operators: string inputmap = (@args[0] != null ? @args[0] : fileDialog("Choose a map file",@inputPath,".w3x")); There are some more libraries which are useful in some situations. They will just be listed here with their basic functionality. If you want to see how to use them, just browse the well commented source code.
|