- The Line parser should now deal with quotes properly.
- The line parser no longer uses the large regex to search for [] and {}, this will fix a few of the problems with macros being too long causing exceptions.
- Using Strings like "[W]" or "{Longsword}" inside your macros will no longer cause a null pointer exception, instead if they can not be resolved the string itself will be returned. Where as previously if they could be resolved they would return the result of the expression otherwise an exception.
- setProperty()/setLibProperty() no longer case errors in token property editor (which make it appear that the property has dissapeared).
- you can now use <link rel='onChangeToken' type='macro' href='linkToMacro'>
- Calling abort() from a macro link no longer causes a mesage to be displayed (this was casuing the message "Abort() function called." when using dialogs or other macro links).
- the meta tags for dialogs for input and temporary should work under all circumstances now.
- Frames that are updated because of selection/impersonation/token change will no longer maximise.
- Removed a cheating exploit that can be used to fake rolls using string functions or list functions.
- Removed a cheating exploit that allowed very easy faking of rolls that has been in the code base for many many builds
- You will now get sensible feed back about trying to execute an unknown macro or a macro from an unknown lib:token.
Code: Select all
[h: a = '[' + dice + 'd' + sides +']' ] [r:a]
[h: a = '[e:' + dice + 'd' + sides +']' ] [r:a]
(Note you could do some tricks like this previously but you would run into a lot of occasions where it would throw up an error).
For reasons on paranioa the above can only be done in a trusted macro or if you type it in as GM. If you attempt to do it any other way you will not get the << >> etc.
Notes on the dialog call back for onChangeToken
Since MapTool has no idea about which token(s) you want to know about changes to this call back will be called any time any token changes, and this is not limited to property changes (and may be called even if a change does not occur). So two things should be taken into consideration when using this callback
- It is for updating displays only, it is NOT a reliable way for tracking if a change was made and acting on it.
- If you can do it reliably another way (e.g. by appending a dialog update call on the end of modification macros) you should consider doing it that way.
- Many frames/dialogs open with onChangeToken will slow things down.
- Did I mention that if you can do it reliably another way you should consider doing it that way.
There are 2 new functions for calling macros. evalMacro() and execMacro() which are similar to the eval() command. They both take a string and evaluate and execute the contents of that string.
Say you had a macro CreateNotes@Lib:Notes that creates a nicely formated HTML display to place in your tokens notes, you could do.
Code: Select all
[h, setNotes('[r,macro('CreateNotes@Lib:Notes'): ""]')
You can also do something like
Code: Select all
[r:evalMacro(strformat("[%{numDice}d%{sides}]"))]
The difference between evalMacro() and execMacro() is evalMacro() has access to the variables in the current macro and can modify them, with execMacro() the macro executes with its own set of variables and can not modify (or read) the current macros variables.
Note: You have to be careful with tool tips. If you are planing to to use tool tips or expanded output [e: ] in your generated macro you must use either [r: ] or { } for output. e.g
Code: Select all
[r:evalMacro(strformat("[%{numDice}d%{sides}]"))]
New String Functions
- startsWith(str, substr)
Returns 1 if str starts with substr or 0 otherwise. - endWith(str, substr)
Returns 1 if str ends with substr or 0 otherwise.
Trusted Macros Updates
Started additions to have macros that completely trusted. This is where the macro is called from a button that the user can not edit and calls no macros that are not trusted. If a macro meets these conditions then there will be a visual indication that the macro has run only code that the GM has written for both /say (which is default if no slash command is used) and /togm, this way the GM will be able to trust any such output. I am hoping for next build for the completion.
Retrieving Tokens
Two new functions
getVisibleTokens(delim)/getVisibleTokenNames(deim)
These two functions return a list of the tokens that are visible to the client that runs the function.
A token is visible if even 1 pixel of it is visible and remember even though your token looks round its image is a square (sorry nothing I can do about this).
JSON
As several people have wanted to be able to enbed property lists within property lists and some of the functionality of property lists depends on the way the work not changing I added a way to create/manipulate JSON objects from within yout MapTool macros. For those of you wanting Javascript macros, sorry they dont ever get executed its just a data structure. For those of you worried about security issues good news they never get executed its just a data structure.
Obviously using json is not as easy to use as it would be in javascript but there are several functions that understand or can return it so depending on what you want to do its not that hard. For thsoe functions that do not understand JSON there are easy ways to convert from String Properties and Lists to JSON objects and Arrays and then convert from JSON to String Properties and Lists.
JSON Functions
- json.fromStrProp(prop [, delim])
Converts a String property list into a json object.
If delim is specified then it will be used as the delimiter for the Proproprty string.
ExampleReturnsCode: Select all
[json.fromStrProp("a=1;b=44;c=12")]
Code: Select all
{"a":1,"c":12,"b":44}
- json.fromList(list [, delim][/i])
Convers a list to a json array.
If delim is specified then it will be used as the delimiter for the list.ProducesCode: Select all
[json.fromList("a,1,g,4")]
Code: Select all
["a",1,"g",4]
- json.toStrProp(jsonString[, delim])
Converts a json object to a property string using delim as the delimeter if it is specified or ";" if it is not. If json string is an object then the property list contains the field=value from the object, if it is an array then the property list contains index=value from the array.
ExampleProducesCode: Select all
[h:a=json.fromStrProp("a=1;b=44;c=12")] [json.toStrProp(a)] [h:a=json.fromList("a,1,g,4")][json.toStrProp(a)]
Code: Select all
a=1;c=12;b=44 0=a;1=1;2=g;3=4
- json.toList(jsonString[, delim])
Converts a json object to a list delim as the delimeter if it is specified or "," if it is not. If the json string is an array then a list of all the elments are returned, if it is a json object then a list of the keys are returned.ProducesCode: Select all
[h:a=json.fromList("a,1,g,4")][json.toList(a)] [h:a=json.fromStrProp("a=1;b=44;c=12")] [json.toList(a)]
Code: Select all
a=1;c=12;b=44 a,c,b
- json.type(jsonString)
Returns the type of json object contained in a string.
Returns- "ARRAY"
- "OBJECT"
- "UNKNOWN"
ProducesCode: Select all
[h:a=json.fromStrProp("a=1;b=44;c=12")] [json.type(a)] [h:a=json.fromList("a,1,g,4")][json.type(a)] [json.type("some thing or other")]
Code: Select all
OBJECT ARRAY UNKNOWN
- json.get(jsonString, key)
Gets the field key from the jsonString.ProducesCode: Select all
[h:a=json.fromStrProp("a=1;b=44;c=12")] [json.get(a,"b")]
Code: Select all
44
- json.contains(jsonString, key)
Returns 1 if the json object contains the key 0 if it doesn't.
If jsonString is an array then it checks to see if the value exsits
in the array.
ExampleProducesCode: Select all
[h:a=json.fromStrProp("a=1;b=44;c=12")] [json.contains(a, "b")] [json.contains(a, "z")]
Code: Select all
1 0
- json.set(jsonString, key, value, ...)
Sets the value of key in the json object. Value can be a number, or string (including another json string. You can use an empty string to create a new JSON object, you can also specify multiple key/value pairs.
ExampleProducesCode: Select all
[json.set("{}", "a", 5)] [json.set("", "a", 5, "b", 10)]
Code: Select all
{"a":5} {"a":5, "b":10}
- json.fields(pjsonString[, delim])
Returns the fields (keys) for a json object or the indexes for a json array as a string list. If delim is specified then it is used as a delimeter to the list otherwise the default of "," is used.
For the special case of a json object if the delim is "json" a json array of the fields will be returned.
ExampleProducesCode: Select all
[h:a=json.fromStrProp("a=1;b=44;c=12")] [json.fields(a)] [h:a=json.fromList("a,1,g,4")][json.fields(a)] [h:a=json.fromStrProp("a=1;b=44;c=12")] [json.fields(a, "json")]
Code: Select all
a,c,b 0,1,2,3 ["a","c","b"] <BS>
- json.length(jsonString)
Returns the number of fields in a json object or number of elements in a json array.
ExampleProducesCode: Select all
[h:a=json.fromStrProp("a=1;b=44;c=12")] [json.length(a)] [h:a=json.fromList("a,1,g,4")][json.length(a)]
Code: Select all
3 4
- json.append(jsonString, value, ...)
Appends a value to the end of a json array. You can use an empty string to create a new json array.
ExamplesProducesCode: Select all
[h:a=json.fromList("a,1,g,4")][json.append(a, 55)] [json.append("", 1, 4, 5, 6)]
Code: Select all
["a",1,"g",4,55] [1,4,5,6]
- json.remove(jsonString, field)
Removes a field from a json object, or the value at the specified index from a json array.
ProducesCode: Select all
[h:a=json.fromStrProp("a=1;b=44;c=12")] [json.remove(a, "c")] [h:a=json.fromList("a,1,g,4")][json.remove(a,3)]
Code: Select all
{"a":1,"b":44} ["a",1,"g"]
The following functions have been modified to return json arrays or objects if their delim argument is "json", otherwise they will function exactly the same as b48.
- getPropertyNames([delim])
- getAllPropertyNames([type [, delim]])
- getOwners(delim)
- getLights(type [, delim])
- getMacros(delim)
- getMacroProps(index, [, delim])
- getMacroIndexes(name [, [delim]])
- getTokens([delim]) / getTokenNames([delim])
- getSelected([delim]) / getSelectedNames([delim])
- getPCs([delim]) / getPCNames([delim])
- getNPCs([delim]) / getNPCNames([delim])
- getExposedTokens([delim]) / getExposedTokenNames([delim])
- getVisibleTokens([delim]) / getVisibleTokenNames([delim])
- getWithState(state [,delim]) / getWithStateNames(state [,delim])
- getOwned(name [,delim]) / getOwnedNames(state [,delim])
- getSpeechNames([delim])
Accepts a json object for props there is no need to specify delim if you pass in a json object.
[foreach(): ]
Works with json objects and arrays. If it is operating on a json object then the loop variable is set to each field in the object. If it is operating on an array then the loop variable is set to each element in the array.
setMacroCommand()
setMacroCommand() is now a trusted function and can only be run from trusted macros.
New Dialog/Frame Functons
- isDialogVisible(name)
Returns 1 if the dialog is visible or 0 if it is not. - isFrameVisible(name)
Returns 1 if the frame is visible or 0 if it is not. - closeDialog(name)
Closes the named dialog.
If a macro link points to a trusted macro (macro on a lib:token that is not owned by any player) and the autoexec flag is set on that macro then the macro will automatically be run by who ever it is sent to (not the sender). The recipient of the link will still see the link in the chat, the tool tip will indicate that it is an auto execute macro link. The link can also be clicked on to rerun the macro so if you do not want players running the link a second time you would have to code something into the macro to stop it from running. In most cases you would want to display/update a dialog/frame from an auto execute macro link so there is some feedback. There is nothing enforcing feedback of macro running or anything stopping you from clicking on them a second time is because the GM must write (or approve the macro) that is running so they are not limited or restricted that way the GM can build in what the checkes they want.
all can be used as a send output to option for macro links, the name of the the target token will be displayed along with the text, just as "/say" works.
executing an unknown macro on a lib:token
When maptool executes a macro on a lib:token it first checks to see if that macro exists, and if it does not then it will look for a macro called "!!unknown-macro!!" and will execute this if it exists. If neither are found then an error is returned. In the body of !!unknown-macro!! getMacroName() will return the name of the macro that was in the macro call.
So if you did
Code: Select all
[macro("foo@Lib:token"): ""]
You can use this facility to return a usage/help text for the Lib:Token or dynamically respond to macro calls, for example
Code: Select all
[h: name = getMacroName()]
[h,if(startsWith(name, "get")), code: {
[type = substring(name, 3)]
[macro.return = getStrProp(getProperty("stats", type))]
}]
[h,if(startsWith(name, "set")), code {
[type = substring(name, 3)]
[props = getProperty("stats", name)]
[props = setStrProp(props, type, macro.args)]
}]
Code: Select all
[h:macro("setStrength"): 10]
[h:macro("getStrength"): ""][h: str = macro.return]
[h:macro("setDexterity"): 13]
[h:macro("getDexterity"): ""][h: str = macro.return]