Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

API Syntax Error: unexpected token +

March 06 (5 years ago)

Edited March 06 (5 years ago)

Can someone please help me figure this out? I think it's an issue with the sendChat function but I don't know what is wrong with the way I put it in.


brawling = function (tar){ 

                var brawlingDamage = randomInteger(4),

                brawlingIsLethal = {type: 'boolean'};

                     

                        

                if(combatRoll.isLethal){ 

                    sendChat("CombatRoll", {"/w "+ 

                        combatRoll.characterToken.controlledby.displayname+ 

                        " (?{Is Brawling Damage Lethal&#63|"+ 

                        "[yes](!cr --combatupdate brawlingIsLethal|true)"+ 

                        "[no](!cr --combatupdate brawlingIsLethal|false)"+ 

                        "})" 

                        } 

                        ); 

                } else {

                    brawlingIsLethal = false;

                };

                        

                if(brawlingisLethal){

                    lethalDamage = lethalDamage + brawlingDamage;

                } else {

                    nonlethalDamage = nonlethalDamage + brawlingDamage;

                };

                

               

                if(getAttrByName(tar.id, is_monster)){

                    sendChat('CombatRoll', {'/em '+characterToken.name+' and a '+tar.name+' started brawling!'+ 

                    ' '+tar.name+' suffered an additional '+brawlingDamage+' points of damage.'});

                } else {

                    sendChat('CombatRoll', {'/em '+characterToken.name+' and '+tar.name+' started brawling!'+ 

                    ' '+tar.name+' suffered an additional '+brawlingDamage+' points of damage.'});    

                };

                

            },



March 06 (5 years ago)
GiGs
Pro
Sheet Author
API Scripter

You shouldnt have those brackets around the sendchat output. Try removing them, like so:

 sendChat('CombatRoll', '/em '+characterToken.name+' and a '+tar.name+' started brawling!'+ 
                    ' '+tar.name+' suffered an additional '+brawlingDamage+' points of damage.');
March 06 (5 years ago)
GiGs
Pro
Sheet Author
API Scripter

also, I'm not sure what you intend with this:

 brawlingIsLethal = {type: 'boolean'};

In javascript you dont set the type of variables, you just give them their value and javascript detects its type appropriately.

You probably just want to set it at whatever the default value should be, like

brawlingIsLethal = true;

This section is likely to cause issues:

  if(brawlingisLethal){
                    lethalDamage = lethalDamage + brawlingDamage;
                } else {
                    nonlethalDamage = nonlethalDamage + brawlingDamage;
                };

Have you defined these variables already?

It's better generally do use one variable to store the amount, and another to store the type (letla or non lethal), then you dont need to have special handling to find the amount.

I'll try the bracket thing and see what happens!!

Yea I picked that up the type-setting thing from studying Token Mod. The variable declaration just seemed so much cleaner and it would help me flag inappropriate input and outcomes 


March 07 (5 years ago)

Edited March 07 (5 years ago)

Thank you so much! It worked!



I did some more troubleshooting and I'm at another impasse. I'll be tinkering at it for a minute but it's a simpler issue involving instantiation and defining variables:  ReferenceError: characterEffects is not defined

var criticalEffects = [   
	    //make an array of functions so a roller can index easily !!!remember to include output/feedback as this in an unexpected additional effect
        //switch/case???
            
            //does the target creature have arms and armor?	        
	        characterEffects = [
	            function brawling(){ //tar is the individual target token !!!(synxtax)!!!
	                var brawlingDamage = randomInteger(4),
	                brawlingIsLethal = {type: 'boolean'};
                     
                //you trying to kill - or nah?	                
	                        if(combatRoll.isLethal){ // ask; this dmg can be nonlethal at no attack penalty to attack throw 
	                   sendChat('CombatRoll', '/w "'+combatRoll.characterToken.controlledby.displayname+'"'+
	                        ' (?{Is Brawling Damage Lethal&#63|'+ //the text of the query followed by button options
	                        '[yes](!cr --combatupdate brawlingIsLethal|true)'+ //yes, add to lethal
	                        '[no](!cr --combatupdate brawlingIsLethal|false)'+ //no, add to nonlethal
	                        '})' //end query
	                   ); //end sendChat command
	                        } else {
	                    brawlingIsLethal = false;
	                };
	                
                //add to appropriate damage tracker depending on lethality	                
	                        if(brawlingisLethal){
	                    lethalDamage = lethalDamage + brawlingDamage;
	                        } else {
	                    nonlethalDamage = nonlethalDamage + brawlingDamage;
	                };
	                
                //feedback for the additional dmg	                
	                        if(getAttrByName(tar.id, is_monster)){
	                    sendChat('CombatRoll', '/em "'+characterToken.name+'" and a "'+tar.name+'" started brawling!'+ 
	                    ' "'+tar.name+'" suffered an additional '+brawlingDamage+' points of damage.');
	                        } else {
	                    sendChat('CombatRoll', '/em "'+characterToken.name+'" and "'+tar.name+'" started brawling!'+ 
	                    ' "'+tar.name+'" suffered an additional "'+brawlingDamage+'" points of damage.');    
	                };
	                
	            },
	            function damageShield(){},
	            function damageArmor (){},
	            function disarm(){},
	            function forceBack(){},
	            function knockdown(){},
	            function sunderWeapon(){},
	            function stun(){},
	            function wrestleORclamor(){},
	            function attkrChoice(){}  //query to choose 
	        ]
	        
	        
	        
	    ],  




With all the preceding code:

var CombatRoll = CombatRoll || (function() {
    'use strict';
    
        var version = '0.0.1',
	    lastUpdate = 1583470878000,
	   
	    combatRoll = {
//who's in the kitchen	        
	        characterToken: {type: 'graphic', subtype: 'token'}, //reference; should be obtained by figuring out who launched the macro
/*ARRAY*/   targetToken: [{type: 'graphic', subtype: 'token'}], //reference; will launch a query to acertain target(s)

//what we cooking with?	        
	        weaponName: {type: 'text'}, //reference; might have use for it - might not. Knowledge it power, though
/*REQUIRED*/isLethal: {type: 'boolean'}, //reference; if non-lethal HP will stop at 1 and the opponent is incapacitated; default is true

//let's a goooooooo rolling	        
	        attackRoll: {type: 'number'}, //calculate (send roll request from API); let's roll 1d20
	        attackBonus: {type: 'number'}, //reference; should already be a value tracked and stored in a character sheet
	        attackResult: {type: 'number'}, //calculate; only added to track values from more than one attack roll (exploding 20s)
	        
	        exploding20: {type: 'boolean'}, //logic for calculation; if attackRoll == 20, roll the mofo again and add the value to attack result

//ok...wtf happened	        
	        attackThrow: {type: 'number'}, //reference; strored in character sheet; attackResult must be greater than or equal to hit
	        ACHit: {type: 'number'}, //calculate; if we hit, the AC we can hurt with our attack; attackResult - attackThrow || 0 if it's negative 
	        isHit: {type: 'boolean'}, //determine; is the attackResult >= attackThrow 
	        isFumble: {type: 'boolean'}, //determine; is the attackRoll == 1
	        
/*ARRAY*/         targetAC: [{type: 'number'}], //reference; should already be a value tracked and stored in a character sheet
	        goodHit: {type: 'boolean'}, //determine; if ACHit >= targetAC, we can deal damage; else we missed or hit with no damage (weak as fuuuuuuuck!!!)
	        isCritical: {type: 'boolean'}, //determine; if ACHit >= [targetAC + 10];

//if goodHit, let's do the deed - don't get ahead of yourself trying to do this step earlier 	        
	        damageRoll: {type: 'number'}, //calculate (from referenced weapon damage SRD formula); double the number of die if isCritical
	        damageBonus: {type: 'number'}, //reference; should already be a value tracked and stored in a character sheet
	        damageResult: {type: 'number'}, // [damageRoll + damageBonus]
	        
	        lethalDamage: {type: 'number'},
	        nonlethalDamage: {type: 'number'}
	    },
        
        creatureSize = [
            
        ],
        
//from the Barbarian Conquerors of Kanahu - page 14 (also covers Exploding 20s)	    
	    criticalEffects = [   
	    //make an array of functions so a roller can index easily !!!remember to include output/feedback as this in an unexpected additional effect
        //switch/case???
            
            //does the target creature have arms and armor?	        
	        characterEffects = [
	            function brawling(){ //tar is the individual target token !!!(synxtax)!!!
	                var brawlingDamage = randomInteger(4),
	                brawlingIsLethal = {type: 'boolean'};
                     
                //you trying to kill - or nah?	                
	                        if(combatRoll.isLethal){ // ask; this dmg can be nonlethal at no attack penalty to attack throw 
	                   sendChat('CombatRoll', '/w "'+combatRoll.characterToken.controlledby.displayname+'"'+
	                        ' (?{Is Brawling Damage Lethal&#63|'+ //the text of the query followed by button options
	                        '[yes](!cr --combatupdate brawlingIsLethal|true)'+ //yes, add to lethal
	                        '[no](!cr --combatupdate brawlingIsLethal|false)'+ //no, add to nonlethal
	                        '})' //end query
	                   ); //end sendChat command
	                        } else {
	                    brawlingIsLethal = false;
	                };
	                
                //add to appropriate damage tracker depending on lethality	                
	                        if(brawlingisLethal){
	                    lethalDamage = lethalDamage + brawlingDamage;
	                        } else {
	                    nonlethalDamage = nonlethalDamage + brawlingDamage;
	                };
	                
                //feedback for the additional dmg	                
	                        if(getAttrByName(tar.id, is_monster)){
	                    sendChat('CombatRoll', '/em "'+characterToken.name+'" and a "'+tar.name+'" started brawling!'+ 
	                    ' "'+tar.name+'" suffered an additional '+brawlingDamage+' points of damage.');
	                        } else {
	                    sendChat('CombatRoll', '/em "'+characterToken.name+'" and "'+tar.name+'" started brawling!'+ 
	                    ' "'+tar.name+'" suffered an additional "'+brawlingDamage+'" points of damage.');    
	                };
	                
	            },
	            function damageShield(){},
	            function damageArmor (){},
	            function disarm(){},
	            function forceBack(){},
	            function knockdown(){},
	            function sunderWeapon(){},
	            function stun(){},
	            function wrestleORclamor(){},
	            function attkrChoice(){}  //query to choose 
	        ]
	        
	        
	        
	    ],




March 07 (5 years ago)
GiGs
Pro
Sheet Author
API Scripter

There are sites that help you validate code, like the Closure Compiler.

That shows there's an error in your code, my guess is there's a missing closure somewhere in that maze of code. An ], ;, or } for example. But I cant tell where - maybe the very final comma?

That said, I dont know why you're writing it this way - it's going to be a  pain to actually use.

You are putting a bunch of named functions inside an array thats inside another array. You cant access things in an array by their name, only by their index position. So the only way to call -say - the brawling function would be by using something like 

something = criticalEffects[0][0];
Which isnt very readable or understandable, and you need to know the index position of each function.
My guess is you mean to use objects to store these arrays, not arrays (which use {} instead of []), but my advice would be not to do that, until you are much more conversant with javascript. For now, just put your functions at the top level of of your code, one after another, separated by a semi-colon. You can access them by name easily enough that way.

Youre also using a lot of variables that you aren't defining. When you create a variable, definie it using var, let, or (if its value is never going to change) const.
You have done with the initial critical effects, but have not done so with characterEffects, lethaldamage, nonlethaldamage, etc. Though maybe they were defined elsehwere.
You also have this reference: combatRoll.characterToken.controlledby.displayname which looks suspect to me. Maybe its defined properly elsewhere but I'm wary.

By the way, if you using TokenMod as a guide to building your function, you are jumping in waaaaay at the deep end, and I'd go looking for simpler scripts to learn from before you drown.

March 07 (5 years ago)

Edited March 07 (5 years ago)
GiGs
Pro
Sheet Author
API Scripter

I also want to reiterate: you should NOt be using this structure:

brawlingIsLethal = {type: 'boolean'};

Aaron is defining his variables in that way for a specific need in TokenMod, which isn't right for your function. You should be defining that just as one of these

brawlingIsLethal = false;
brawlingIsLethal = true;
brawlingIsLethal;

The last one leaves it undefined but creates a reference you can fill later.


Note that when you do this later in the code

brawlingIsLethal = false;

You completely overwrite the previous value of {type= 'boolean'}.

This construction

brawlingIsLethal = {type: 'boolean'};

is NOT defining a variable of type boolean.

It's creating an object literal, which has a property type: boolean.

This is confusing to explain! trust me, you dont want to be doing that in your script. As noted above, you replace that data anyway when you assign it to false, and having that data there will cause errors when you try to test the value of your variables.

Seeing the full code, you have created a lot of variables this way. You want to remove all those references.

For reference if you wanted to keep using that structure (DON'T!), you would assign values to it like so. Instead of this

brawlingIsLethal = false;

you would do

brawlingIsLethal.value = false;

That would create the following object:

{type: 'boolean', value: false}

and you could test the value by 

if(brawlingIsLethal.value)


But as I said, you do not want to do this. It adds needless complexity to your code, and is going to be a source of difficult-to-identify errors. Just get rid of all the {type: something } references in your code.




March 07 (5 years ago)
GiGs
Pro
Sheet Author
API Scripter

Now that you've posted your full code, it looks like the source of the current error is incomplete closure.

You've ended it like so

    ],

But that should be

    ];
});

Ty ty for all the feedback! I was trying to find simpler code but his was so large that I felt like I had more to work analyze (if that makes sense)..

the only reason I was using arrays of arrays is because the critical effect is chosen on a 1d10 roll if the attack lands a crit. I figured I could access the function with an index call from something like:

if(combatRoll.isCritical){
     let r = randomInteger(10);
    criticalrandomInteger[0][r-1];
 
}


March 07 (5 years ago)

Edited March 07 (5 years ago)

And i think I finally understand the {type: 'boolean'} format he was using and why he was doing it. 

I originally learned coding in Java and I'm used to labeling all my variables with int, boolean, String, etc. I thought this was what he was doing but I now realize he was probably using a comparator to determine if correct input was being assigned and used throughout the rest of his code (maybe). I'll take it out tho and just replace it with inline comments so I can keep track.

March 07 (5 years ago)
GiGs
Pro
Sheet Author
API Scripter

If you want to define combatRoll like that, this is the way it should be done:

combatRoll = {
            //who's in the kitchen          
            characterToken: undefined, //reference; should be obtained by figuring out who launched the macro
            /*ARRAY*/
            targetToken: [], //reference; will launch a query to acertain target(s)

            //what we cooking with?         
            weaponName: '', //reference; might have use for it - might not. Knowledge it power, though
            /*REQUIRED*/
            isLethal: false, //reference; if non-lethal HP will stop at 1 and the opponent is incapacitated; default is true

            //let's a goooooooo rolling         
            attackRoll: 0, //calculate (send roll request from API); let's roll 1d20
            attackBonus: 0, //reference; should already be a value tracked and stored in a character sheet
            attackResult: 0, //calculate; only added to track values from more than one attack roll (exploding 20s)

            exploding20: false, //logic for calculation; if attackRoll == 20, roll the mofo again and add the value to attack result

            //ok...wtf happened         
            attackThrow: 0, //reference; strored in character sheet; attackResult must be greater than or equal to hit
            ACHit: 0, //calculate; if we hit, the AC we can hurt with our attack; attackResult - attackThrow || 0 if it's negative 
            isHit: false, //determine; is the attackResult >= attackThrow 
            isFumble: false, //determine; is the attackRoll == 1

            /*ARRAY*/
            targetAC: [], //reference; should already be a value tracked and stored in a character sheet
            goodHit: false, //determine; if ACHit >= targetAC, we can deal damage; else we missed or hit with no damage (weak as fuuuuuuuck!!!)
            isCritical: false, //determine; if ACHit >= [targetAC + 10];

            //if goodHit, let's do the deed - don't get ahead of yourself trying to do this step earlier            
            damageRoll: 0, //calculate (from referenced weapon damage SRD formula); double the number of die if isCritical
            damageBonus: 0, //reference; should already be a value tracked and stored in a character sheet
            damageResult: 0, // [damageRoll + damageBonus]

            lethalDamage: 0,
            nonlethalDamage: 0
        },

This sets up the variables of the property types, in a format where you can use if() against their values without fear of failure. I'm curious why your two target variables are arrays, but attack and damage rolls are not. Wouldnt you have different damage rolls per attack/target?


I'm not convinced this is a great approach for your purpose. It would be a good idea to build a pseudo-code version of your script first, before actual coding. Pseudo-code is where you write something like this:

If Target Token is selected
    get token AC
    make attack roll against AC
    If HIT
        roll damage

And so on. You basically map out what the script has to do, using english, not javascript, but in a  format that looks like code.

Having a complete pseudo code version is very handy for complex scripts, it shows you what structures you need to use, helps you figure out how you should set up your data, and so on. If you can do that, we can help you turn it into a working script.

March 07 (5 years ago)
GiGs
Pro
Sheet Author
API Scripter


Stephen G. said:

And i think I finally understand the {type: 'boolean'} format he was using and why he was doing it. 

I originally learned coding in Java and I'm used to labeling all my variables with int, boolean, String, etc. I thought this was what he was doing but I now realize he was probably using a comparator to determine if correct input was being assigned and used throughout the rest of his code (maybe). I'll take it out tho and just replace it with inline comments so I can keep track.

Yes, that's correct. Javascript is looser and messier than other languages usually :)



GiGs said:

Now that you've posted your full code, it looks like the source of the current error is incomplete closure.

You've ended it like so

    ],

But that should be

    ];
});


This is also only the preceding code Cuz I think I added the correct closure. I'll go back and double check the whole code.  I'll post make the edits and post without comments so it's easier for others to decipher (I guess I'm the only one who needs all the comments haha)

March 07 (5 years ago)
GiGs
Pro
Sheet Author
API Scripter


Stephen G. said:

Ty ty for all the feedback! I was trying to find simpler code but his was so large that I felt like I had more to work analyze (if that makes sense)..

the only reason I was using arrays of arrays is because the critical effect is chosen on a 1d10 roll if the attack lands a crit. I figured I could access the function with an index call from something like:

if(combatRoll.isCritical){
     let r = randomInteger(10);
    criticalrandomInteger[0][r-1];
 
}


I realised after the seeing the rest of your code that's what you were doing. I dont think its the best approach though, because you might want to access those functions by name. IMO it would be better to have the functions defined outside the array, and in the array have calls to them. That way you get the best of both worlds-  being able to access the functions by name if you need to, and also have more readable code.


March 07 (5 years ago)
GiGs
Pro
Sheet Author
API Scripter


Stephen G. said:


This is also only the preceding code Cuz I think I added the correct closure. I'll go back and double check the whole code.  I'll post make the edits and post without comments so it's easier for others to decipher (I guess I'm the only one who needs all the comments haha)

I'd keep the comments! They helped me a lot. If you're posting for help, its a good idea to comment the code so we can figure out what you intend more easily.



GiGs said:

If you want to define combatRoll like that, this is the way it should be done:

combatRoll = {
            //who's in the kitchen          
            characterToken: undefined, //reference; should be obtained by figuring out who launched the macro
            /*ARRAY*/
            targetToken: [], //reference; will launch a query to acertain target(s)

            //what we cooking with?         
            weaponName: '', //reference; might have use for it - might not. Knowledge it power, though
            /*REQUIRED*/
            isLethal: false, //reference; if non-lethal HP will stop at 1 and the opponent is incapacitated; default is true

            //let's a goooooooo rolling         
            attackRoll: 0, //calculate (send roll request from API); let's roll 1d20
            attackBonus: 0, //reference; should already be a value tracked and stored in a character sheet
            attackResult: 0, //calculate; only added to track values from more than one attack roll (exploding 20s)

            exploding20: false, //logic for calculation; if attackRoll == 20, roll the mofo again and add the value to attack result

            //ok...wtf happened         
            attackThrow: 0, //reference; strored in character sheet; attackResult must be greater than or equal to hit
            ACHit: 0, //calculate; if we hit, the AC we can hurt with our attack; attackResult - attackThrow || 0 if it's negative 
            isHit: false, //determine; is the attackResult >= attackThrow 
            isFumble: false, //determine; is the attackRoll == 1

            /*ARRAY*/
            targetAC: [], //reference; should already be a value tracked and stored in a character sheet
            goodHit: false, //determine; if ACHit >= targetAC, we can deal damage; else we missed or hit with no damage (weak as fuuuuuuuck!!!)
            isCritical: false, //determine; if ACHit >= [targetAC + 10];

            //if goodHit, let's do the deed - don't get ahead of yourself trying to do this step earlier            
            damageRoll: 0, //calculate (from referenced weapon damage SRD formula); double the number of die if isCritical
            damageBonus: 0, //reference; should already be a value tracked and stored in a character sheet
            damageResult: 0, // [damageRoll + damageBonus]

            lethalDamage: 0,
            nonlethalDamage: 0
        },

This sets up the variables of the property types, in a format where you can use if() against their values without fear of failure. I'm curious why your two target variables are arrays, but attack and damage rolls are not. Wouldnt you have different damage rolls per attack/target?


So I was confused at first and then realized the disconnect. I am temporarily storing values so I can use them to update token HP. This program is essentially scrap paper for my mental math and I had no intention to reference some variables after they were used so I didn't care about them being overriden.  I was also planning to update the HP values as the code ran through the array of target tokens so didn't care to store the values in an array. 

Every variable past 

 isLethal

is meant to be calculated, used to update token attribute, then disregarded. 


Now I'm realizing this might cause issues with asynchronous functions and keeping the data straight so I'll probably change it.  

March 08 (5 years ago)

Edited March 08 (5 years ago)

If 

combatRoll = {.....}

is just making an object with those parameters (whatever's in the brackets), could I instantiate an object of that type an assign it to each target to track all the data?


Or does that require making an object class??

March 08 (5 years ago)
GiGs
Pro
Sheet Author
API Scripter

I dont have any experience creating classes in javascript, so you can get by without doing that. 

when you say, "instantiate an object of that type an assign it to each target" I have to be careful how to respond because different terminology here could lead to confusion. 

So, lets say you get a reference to a target token. That will be a specific object, which has some predefined properties (like id, name, and so on). With that object, you can access properties of the character sheet that's linked to the token, like armour class, hit points, and so on.

Now, you should not attempt to attach extra properties to that object (unless you want to add permanent attributes, like hit points).

If you want to track data within your script that's associated with that token, you can create your own object variable (if you need to track of a bunch of properties for easy use within your script). It's not assigned to that token object.

If you have multiple targets selected, you likely have an array of tokens, then one approach to handle that is to have an array of variables associated with them. But again, they aren't 'assigned' to that token in any meaningful sense. And since you said you were planning to throw away the variables, you might be better off just looping through the tokens, and creating individual variables as you need them.

March 08 (5 years ago)

Edited March 08 (5 years ago)

I've stumbled down a rabbit hole of coding:


Do you know of any scripts that track combat conditions via tokens? Basically a textual equivalent of status markers?


Buffs/debuffs, DoT, aoe, etc. - there are a bunch of factors that are enduring and only affect a few tokens at a time.  It can easily be tracked for special characters that will only ever have one token down per page, but it's trickier for generic monster tokens. I'm using a script to generate random HP from monster stat blocks in stored in character sheets. I can't update the sheet else every other generic mob will get that effect. 

March 08 (5 years ago)

Edited March 08 (5 years ago)


Stephen G. said:

I've stumbled down a rabbit hole of coding:


Do you know of any scripts that track combat conditions via tokens? Basically a textual equivalent of status markers?


Buffs/debuffs, DoT, aoe, etc. - there are a bunch of factors that are enduring and only affect a few tokens at a time.  It can easily be tracked for special characters that will only ever have one token down per page, but it's trickier for generic monster tokens. I'm using a script to generate random HP from monster stat blocks in stored in character sheets. I can't update the sheet else every other generic mob will get that effect. 


NVM. I can reference conditions added via CombatTracker. I'll just tweak the CombatTracker script to have calculated conditions - like 'modMeleeHit', 'modMeleeDmg', 'modMissileHit', 'modMissileDmg', 'modFinesseHit', 'modFinesseDmg'. 


That way I can have the mod change as buffs and debuffs wear off as the combat progress through rounds

So I tumbled down a rabbit hole and decided to do a massive overhaul to allow me to click through my ACKS game.  I'll mostly be tweaking other scripts (I've already started on StatusInfo and CombatTracker) and adding a couple ACKS-related scripts (like a monster index). 

I'll move this to a new thread cuz I'll probably be asking a lot of tangentially related questions.

Here's a preview of my custom combat buff token marker: bonus to missile attack throws


March 09 (5 years ago)
GiGs
Pro
Sheet Author
API Scripter

Those are very nice token markers!