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

[Help] Push a resolved roll from one API into Powercards 2

Alrighty, spent the better part of the last few hours scratching my head over this. So, basically, the only thing I don't like about my own API is they all come out looking so bland at the end (on the chat side). Most things are resolved by just using sendChat() to relay information about whatever to the chat log. And, more often than not, it's just a lump of text. Sure, I might throw in an emote or something, but nothing's highlighted or ever looks good. I've used this idea similarly to spruce them up via something like;
sendChat(msg.who, "!powercard making-things-look-like-a-macro");
But, again, while dressed up it's lacking because none of the rolls are highlighted or anything. The only way I've been able to think of doing it, is to take the entirety of the Powercard2 script, and create a copy, changing the second one to a function of some sort. Then calling it in my other API on an as needed basis. While this does not sound like a horrible idea, there are a few problems I'm having.
1)I have no idea how to change an entire script into a function, especially something as intricate as Powercard2
2)I'm still working on figuring out the whole "calling functions from one API to work in another API" bit.
3)I wouldn't know what to tack onto the end of my api after all of the "invisible backdoor stuff" happens to give the visual output of Powercard2. (ie. Everything which does not actually have a chat output, but does things like altering attributes on character sheets.)

So here's the script for the healing API I've been working on.
var Healspell = Healspell || {};
/*******************************************************
 * Typical macro's for different spellcasters
 * Cleric: !healSpell --type|Spell --spell|[[3d8+@{Level}]] --targetMap|@{target|token_id} --targetName|?{Target?|Name}
 * Use Magic Item: !healSpell --type|Wand wand|CSW --skill|[[1d20+@{UMD}]] --spell|[[3d8+1]] --targetMap|@{target|token_id} --targetName|?{Target?|Name}
 * The reason for both targetMap and targetName is if party is on a world map traveling, they can still heal
 * party members by punching their name into the target field. Otherwise one would always have to either punch in the
 * name or be on the same page as the target token.
 */

on("chat:message", function (msg) {
    if (msg.type != "api") return;
    //log(msg);
    var command = msg.content.split(" ", 1);
    //log(command);
    
    if(_.has(msg,'inlinerolls')){
        msg.content = _.chain(msg.inlinerolls)
        .reduce(function(m,v,k){
            m['$[['+k+']]']=v.results.total || 0;
            return m;
            },{})
        .reduce(function(m,v,k){
            return m.replace(k,v);
        },msg.content)
        .value();
    };
    var n = msg.content.split(" --");
    var a = 1;
    var chatObj = {};
    var mTags = "";
    var mContent = "";
    while(n[a]) {
        mTag = n[a].substring(0,n[a].indexOf("|"));
        mContent = n[a].substring(n[a].indexOf("|") + 1);
        chatObj[mTag] = mContent;
        a++;
    }; 
    //log(chatObj);
    
    if (command == "!healSpell") {
        
        var HP_bar = 3;
        
        var caster = findObjs({
            _type: "character",
            name: msg.who
        })[0];
        //log(caster);
        
        //Make sure target is valid.
        if (chatObj.targetMap == undefined) {
            sendChat("System", "Please copy and paste this line into your macro. --targetMap|@{target|token_id}");
            return;
        };
        if (chatObj.targetName == undefined) {
            sendChat("System", "Please copy and paste this line into your macro. --targetName|?{Target?}");
            return;
        };
        if (chatObj.targetName != "") {
            var initTarget = chatObj.targetName;
        } else {
            var initTarget = findObjs ({
                _type: "graphic",
                _id: chatObj.targetMap
            })[0].get("name");
        };
        var target = findObjs ({
            _type: "character",
            name: initTarget
        })[0];
        //log(target);
        if (target == undefined) {
            var HP_field = "bar" + HP_bar + "_value";
            //assume it is a nameless NPC and check for valid min/max HP bars.
            var targetHP = findObjs ({
                _type: "graphic",
                _id: chatObj.targetMap
            })[0];
            var HP_cur = parseInt(targetHP.get("bar" + HP_bar + "_value"));
            var HP_max = parseInt(targetHP.get("bar" + HP_bar + "_max"));
            
            if (HP_cur == undefined) {
                //target does not have a valid HP bar
                sendChat("System", "Not a valid target, please try again.");
                return;
            };
            if (HP_max == undefined) {
                //target does not have a valid HP bar
                sendChat("System", "Not a valid target, please try again.");
                return;
            };
            
        } else {
            HP_field = "current";
            var targetHP = findObjs ({
                _type: "attribute",
                characterid: target.id,
                _name: "HP"
            })[0];
            if (targetHP == undefined) {
                sendChat("System", "Target does not have valid HP attribute. Please add one and try again.");
                return;
            }
            var HP_cur = parseInt(targetHP.get("current"));
            var HP_max = parseInt(targetHP.get("max"));
        };
        //log(HP_cur + " " + HP_max);
       
	//Check if Wand or Spell
        if (chatObj.type.toLowerCase() == "wand") {
            //Look for attribute containing charges of the wand.
            if (chatObj.wand == undefined) {
                sendChat("System", "Please add a field --wand| followed by the name of an attribute containing wand charges. Ex. --wand|Cure Light Wounds");
                return;
            };
            var wCharge = findObjs({
                _type: "attribute",
                _characterid: caster.id,
                name: chatObj.wand
            })[0];
            wCharge.set("current", parseInt(wCharge.get("current"))-1);
            
            //skill check
            //Look for UMD attribute in character sheet. (Use Magic Devce)
            var skill = findObjs({
                _type: "attribute",
                _characterid: caster.id,
                _name: "UMD"
            })[0];
            if (skill == undefined) {
                sendChat("System", "Caster does not have a Use Magic Device attribute (UMD). Please make one and assign it the appropriate skill value.");
                return;
            };
            var skill = parseInt(skill.get("current"));
            //log(skill);
            //Check the skill roll, make sure it is not a 1 or below 20.
            if (chatObj.skill == undefined) {
                sendChat("System", "Please add a skill check to your macro. --skill|1d20+UMD");
                return;
            };
            var skillRoll = parseInt(chatObj.skill);
            //log(skillRoll);
            if (skillRoll-skill == 1) {
                sendChat("System", msg.who + " has rolled a 1. The spell fizzles.");
            };
            if (skillRoll < 20){
                sendChat("System", msg.who + " has rolled a " + skillRoll + " the spell fizzles.");
                return;
            };
        } else if (chatObj.type.toLowerCase() != "spell") {
            sendChat("System", "Cast type not defined. Please define either Wand or Spell. --type|(Wand/Spell)");
            return;
        };
        
        //Perform healing math
        var HP_new = HP_cur + parseInt(chatObj.spell);
        if (HP_new > HP_max) {
            var HP_new = HP_max;
        };
        targetHP.set(HP_field, HP_new);
        sendChat(msg.who, "Heals " + initTarget + " for " + chatObj.spell + " points of damage. Putting them at " + HP_new + "/" + HP_max + ". ");
    };
}); 

I know it's crap, but I'm actually quite proud of this one.
So, essentially I would like I way to take that final line and just push (a version of) it and the inline rolls from msg into Powercards 2, then finally have PC2 spit its fancy goodness all over my chat. But I can't think of how to do that, without PC2 re-rolling all of the inline rolls for it's own use.


March 13 (10 years ago)
The Aaron
Pro
API Scripter
It is possible with the current power card script without modification. If you search the thread for !pcpp you'll fubd a few messages about a script I use to do this, which is linked there.
Unfortunately the pcpp script does what I'm trying to avoid. I'm trying to keep the highlighted rolls and crits and such.

First is !power second is !pcpp. A major part of why my party and I enjoy the Powercard display is because the thrill you get when it lights up on a crit and such. That's why I was looking for a way to push essentially the entire msg line into Powercard, because it contains the "formula" for the resolved inline rolls.
{"content":"!power--bgcolor|#9900ff --txcolor|#FFF --emote|Ken plants a powerful blow into his foe. --name|Unarmed Strike --leftsub|At-Will --rightsub|Standard Action --attack|$[[0]] Vs. AC --damage|$[[1]]DMG --Type|Bludgeoning --Special|Treated as Magic Weapons, and Adamintine Weapons. --Monks Gloves|+5, +2d4DMG, +1d6 Force","inlinerolls":[{"expression":"1d20+5+6+13","results":{"resultType":"sum","rolls":[{"dice":1,"results":[{"v":4}],"sides":20,"type":"R"},{"expr":"+5+6+13","type":"M"}],"total":28,"type":"V"},"rollid":"-JkLF3gtnA219AyPFZFS","signature":"3d4d76eccf084e1363407a3dd2e135ae3329721fbeae103f2ad8579ba274256eb415d5f0c07ddcd0e9da9e72bb692c56f1e81ff9f4b235768a06fc29a5dd5abe"},{"expression":"2d4+1d6[Force]+8d6+4d6+6","results":{"resultType":"sum","rolls":[{"dice":2,"results":[{"v":3},{"v":2}],"sides":4,"type":"R"},{"expr":"+","type":"M"},{"dice":1,"results":[{"v":6}],"sides":6,"type":"R"},{"text":"Force","type":"L"},{"expr":"+","type":"M"},{"dice":8,"results":[{"v":5},{"v":5},{"v":6},{"v":3},{"v":1},{"v":4},{"v":1},{"v":3}],"sides":6,"type":"R"},{"expr":"+","type":"M"},{"dice":4,"results":[{"v":4},{"v":4},{"v":5},{"v":6}],"sides":6,"type":"R"},{"expr":"+6","type":"M"}],"total":64,"type":"V"},"rollid":"-JkLF3gu_kPX8JBAIlVs","signature":"79b1c3d0b85b1471eb4d7a92d1155ef573df13d454362ac0f7b45ca01106be16e99ae24f5836ce5d8d571fece589fff5a3b34d80dfb6afbfe7f436edec8e60d9"}],"playerid":"-JEjyn_XUKzmCLYJiNjN","selected":[{"_id":"-JkHdyvCL19BFzcT5F2-","_type":"graphic"}],"type":"api","who":"DM (GM)"}

March 14 (10 years ago)

Edited March 14 (10 years ago)
Or I'm a moron and should probably read directions more carefully. I'll just shut up now. I got the rolls to light up.

But I'm still facing the same problem. As far as I can tell, pcpp is to stall and let one macro change attributes before pushing the powercard script so it'll call the newly updated attributes. But I have an issue of the heal api takes resolved rolls, does things, and then spits out a line in chat. It's not just a matter of changed attributes.
This is the macro I've written and tacked on the !pcpp api. Standard Cure Serious Wounds bard spell.
!healSpell --type|Spell --targetMap|@{Target|token_id} --targetName|?{Target name?}  --spell|[[3d8+@{Level}]]
!pcpp --name|Cure Serious Wounds --spell|[#[3d8+@{Level}]#]
Now, the heal api will look for a selected targeton the map, unless a name has been filled in, in which case that takes priority. It looks for the relevant info, changes relevant attributes and spits out a report in chat. !healSpell and !pcpp both use their own versions of the resolved roll.

What I want, is to take the resolved info from !heal and have !power/!pcpp, whichever, use the exact same resolved information to do things, rather than looking for/making their own. That way I can have the look of Powercard, but still have my api use the same info to do things.
Something like
sendChat(msg.who, "!power --name|Cure Light Wounds --spell||$[[0]]","inlinerolls":[{"expression":"3d8+13","results":{"resultType":"sum","rolls":[{"dice":3,"results":[{"v":2},{"v":6},{"v":5}],"sides":8,"type":"R"},{"expr":"+13","type":"M"}],"total":26,"type":"V"}");
I think that's the best way I could put it.
March 14 (10 years ago)
The Aaron
Pro
API Scripter
Right, I just intended it as an example for you to look at on how to call PowerCards from your own script. :). You should be able to use the same technique I'm using to place the same roll from your message into the message for the powercard, or add a preprocess command to it that outputs a separate healing message, or the like.
I'm going to be honest. I am pretty much a 3rd rate rookie when it comes to coding. I genuinely tried reading the pcpp script. I can only tell the relevant bits that I'm looking for, because it's relevantly obvious.
(/@#\{([^|]*)\|([^\|]*)\|?(.*)?\}/)
I assume it has something to do with looking for inline rolls inside of referenced attributes, and that's only because it bears a striking resemblance to
@{ [[ ]] }
I've spent the last hour or so going over it. It's like in advanced calculus when you reach that point where you run out of numbers and just start using letters. You might know what it means, but to the uneducated it's just Greek.
I don't know what/how/why it does things. The only grasp I have on it is the description you gave in the forum.

No matter how many times I read this, I seem to sound like I'm complaining. It is hard to convey emotional(?) context properly via text. As always I am grateful for your assistance across the forums to all. I am just...I think baffled is the right word.

March 14 (10 years ago)
The Aaron
Pro
API Scripter
No worries, I totally understand what you mean. I had this physics professor in collage who seemed to have forgotten the existence of numbers. "Dr. Broerman, can we just see this with a real example?" "Sure, let's assume you have a point P with a mass M, traveling with a velocity V..." "Sigh...."

I'd forgotten how complicated that script was, I'll jump back on here in a bit and boil it down to the relevant lines. Sorry to about that!
March 14 (10 years ago)
Lithl
Pro
Sheet Author
API Scripter

Kerberos said:

I'm going to be honest. I am pretty much a 3rd rate rookie when it comes to coding. I genuinely tried reading the pcpp script. I can only tell the relevant bits that I'm looking for, because it's relevantly obvious.
(/@#\{([^|]*)\|([^\|]*)\|?(.*)?\}/)
I assume it has something to do with looking for inline rolls inside of referenced attributes, and that's only because it bears a striking resemblance to
@{ [[ ]] }
I've spent the last hour or so going over it. It's like in advanced calculus when you reach that point where you run out of numbers and just start using letters. You might know what it means, but to the uneducated it's just Greek.
I don't know what/how/why it does things. The only grasp I have on it is the description you gave in the forum.

This is regular expression matching, which can in fact be quite hard to wrap your head around. It's basically another language inside of JavaScript. Let's see if I can break this particular one down for you:

[ ] Creates a "character class", and matches any character between the square brackets... unless the class begins with ^, in which case it matches the opposite (any character not in the class). So, [^|] matches any character that isn't "|", and [^\|] matches any character that is neither "\" nor "|".

* means "repeat the previous thing 0 or more times". [^|]*, therefore, matches a series of characters which are not "|" in a row.

\ (outside of a character class) escapes the next character. Because "|" has special meaning normally, it needs to be escaped if you want to match "|", by using "\|". Similarly, the curly braces are escaped, because { } also has meaning in regex.

?, in the context that it is used twice in the above regex, means "repeat the previous thing exactly zero or one time". "\|?" means either zero "|" or one "|", nothing else.

. matches any character.

( ) creates a "capturing group" which can be referenced later.

So, the regex above is looking for a string with the pattern:

  • "@#{"
  • Zero or more characters which are not "|", and these characters can all be referenced later
  • "|"
  • Zero or more characters which are neither "\" nor "|", and these characters can all be referenced later
  • Zero or one copy of "|"
  • Zero or one copy of: zero or more characters, and these characters can all be referenced later
  • "}"

There's a lot more to what regex can do, and the syntax is, frankly, a bit obtuse. But if you understand how to wield it, it is an exceptionally powerful tool.

March 14 (10 years ago)
The Aaron
Pro
API Scripter
Ok, so there are a few things going on in PCPP. I'll try to cover the parts you might find useful:

1) setting up to call the powercard script. Near the top, it defines an empty function called powerCardFunction, then in the checkInstall() function (which gets called from an on('ready',function(){}) later on) it looks to see if the either version 1 or version 2 of the powercard script is installed, and gets the Process() function from it, replacing the powerCardFunction with that one. (This is an example of the Null Object Pattern -- the basic idea being you make things (functions in this case) that support an interface but do nothing, allowing you to simplify logic by always having a 'valid' object at the point where you are accessing it, so you don't have to check.).
    powerCardFunction = function() {
        },

    checkInstall = function() {
        if("undefined" !== typeof PowerCard && _.isFunction(PowerCard.Process)) {
            powerCardFunction = PowerCard.Process;
        } else if("undefined" !== typeof PowerCardScript && _.isFunction(PowerCardScript.Process)) {
            powerCardFunction = PowerCardScript.Process;
        } else {
            log('No Powercard Script Found.');
        }
    },
2) In the case of PCPP, I'm taking the current message object, and enhancing it by stuffing in additional inline rolls. You could certainly do that, but probably for you it makes more sense to have a command which you run that takes the same arguments as the !power command, then extract the extra information out of it. So taking a modification of the command you posted earlier:

{
  "content": "!fakepower --bgcolor|#9900ff --txcolor|#FFF --emote|Ken plants a powerful blow into his foe. --name|Unarmed Strike --leftsub|At-Will --rightsub|Standard Action --attack|$[[0]] Vs. AC --damage|$[[1]]DMG --Type|Bludgeoning --Special|Treated as Magic Weapons, and Adamintine Weapons. --Monks Gloves|+5, +2d4DMG, +1d6 Force",
  "inlinerolls": [
    {
      "expression": "1d20+5+6+13",
      "results": {
        "resultType": "sum",
        "rolls": [
          {
            "dice": 1,
            "results": [
              {
                "v": 4
              }
            ],
            "sides": 20,
            "type": "R"
          },
          {
            "expr": "+5+6+13",
            "type": "M"
          }
        ],
        "total": 28,
        "type": "V"
      },
      "rollid": "-JkLF3gtnA219AyPFZFS",
      "signature": "3d4d76eccf084e1363407a3dd2e135ae3329721fbeae103f2ad8579ba274256eb415d5f0c07ddcd0e9da9e72bb692c56f1e81ff9f4b235768a06fc29a5dd5abe"
    },
    {
      "expression": "2d4+1d6[Force]+8d6+4d6+6",
      "results": {
        "resultType": "sum",
        "rolls": [
          {
            "dice": 2,
            "results": [
              {
                "v": 3
              },
              {
                "v": 2
              }
            ],
            "sides": 4,
            "type": "R"
          },
          {
            "expr": "+",
            "type": "M"
          },
          {
            "dice": 1,
            "results": [
              {
                "v": 6
              }
            ],
            "sides": 6,
            "type": "R"
          },
          {
            "text": "Force",
            "type": "L"
          },
          {
            "expr": "+",
            "type": "M"
          },
          {
            "dice": 8,
            "results": [
              {
                "v": 5
              },
              {
                "v": 5
              },
              {
                "v": 6
              },
              {
                "v": 3
              },
              {
                "v": 1
              },
              {
                "v": 4
              },
              {
                "v": 1
              },
              {
                "v": 3
              }
            ],
            "sides": 6,
            "type": "R"
          },
          {
            "expr": "+",
            "type": "M"
          },
          {
            "dice": 4,
            "results": [
              {
                "v": 4
              },
              {
                "v": 4
              },
              {
                "v": 5
              },
              {
                "v": 6
              }
            ],
            "sides": 6,
            "type": "R"
          },
          {
            "expr": "+6",
            "type": "M"
          }
        ],
        "total": 64,
        "type": "V"
      },
      "rollid": "-JkLF3gu_kPX8JBAIlVs",
      "signature": "79b1c3d0b85b1471eb4d7a92d1155ef573df13d454362ac0f7b45ca01106be16e99ae24f5836ce5d8d571fece589fff5a3b34d80dfb6afbfe7f436edec8e60d9"
    }
  ],
  "playerid": "-JEjyn_XUKzmCLYJiNjN",
  "selected": [
    {
      "_id": "-JkHdyvCL19BFzcT5F2-",
      "_type": "graphic"
    }
  ],
  "type": "api",
  "who": "DM (GM)"
}
So, you could look at this message and extract the roll for attack:
var attackIndexMatch=msg.content.match(/--attack\|.*?\$\[\[(\d+)\]\]/);
This will find the first inline roll marker after --attack| (.*? means match the minimal amount of anything, including nothing until the next thing matches, which in this case is \$, a $. ($ means "end of line" in regexs, so it must be escaped to match a real $) ). attackIndexMatch will look like this:
["--attack|$[[0]]", "0"]
in a regular expression ( and ) define a capturing group, so above, (\d+) says "remember a sequence of 1 or more digits. The return from .match() is an array. For single matches (as opposed to matching every occurrence of something), the 0th element of the array is the full string that was matched by the regular expression. The 1st, and up are the results of the capturing groups. So, attackIndexMatch[1] is the index of the inline roll used for the attack. msg.inlinerolls[attackIndexMatch[1]] then is the inline roll to look at. You could do the same thing for a --heal|[[2d6+8]] that was added to a !fakepower roll. You can then take that information and spit out a message about healing, or heal some target pc, etc.

3) Calling the PowerCard script. In the case where you are just harvesting rolls and doing additional functionality, this becomes pretty easy. The powercard script v2 expects a few things to have happened before the function is called. One is getting a player object to pass as the second argument:
var player_obj = getObj("player", msg.playerid);
Another is stripping (GM) out of the message:
msg.who = msg.who.replace(" (GM)", "");
msg.content = msg.content.replace("(GM) ", "");
At the point where you are calling, it actually doesn't care if the beginning says !power, so now you're just ready to call it:
powerCardFunction(msg,player_obj);

That's pretty much it. If you are stuffing in more rolls and such (which is what PCPP does), then you'd need to do a bit more with adding the rolls to the msg.inlinerolls array, and subbing in text with $[[###]] where needed with the right index. (The order that indexes occur in the message doesn't actually matter, so $[[1]] $[[0]] is just fine. It just has to have the right index to match up the right roll.)

Hope that helps

Brian said:

There's a lot more to what regex can do, and the syntax is, frankly, a bit obtuse. But if you understand how to wield it, it is an exceptionally powerful tool.

That actually does seem powerful once you can understand how to properly use it. I look forward to one day figuring it out.


The Aaron said:

So, you could look at this message and extract the roll for attack:
var attackIndexMatch=msg.content.match(/--attack\|.*?\$\[\[(\d+)\]\]/);
This will find the first inline roll marker after --attack| (.*? means match the minimal amount of anything, including nothing until the next thing matches, which in this case is \$, a $. ($ means "end of line" in regexs, so it must be escaped to match a real $) ). attackIndexMatch will look like this:
["--attack|$[[0]]", "0"]
in a regular expression ( and ) define a capturing group, so above, (\d+) says "remember a sequence of 1 or more digits. The return from .match() is an array. For single matches (as opposed to matching every occurrence of something), the 0th element of the array is the full string that was matched by the regular expression. The 1st, and up are the results of the capturing groups. So, attackIndexMatch[1] is the index of the inline roll used for the attack. msg.inlinerolls[attackIndexMatch[1]] then is the inline roll to look at. You could do the same thing for a --heal|[[2d6+8]] that was added to a !fakepower roll. You can then take that information and spit out a message about healing, or heal some target pc, etc.

I think I get it. But basically attackIndexMatch is looking through the msg.content for the already indexed roll of "--attack", and grabbing it's reference? (Terminology might not be correct, but I hope I'm getting the point across.)
So, correct me if I'm wrong (I probably am) but for a more general purpose thing where it'll look for any indexed rolls and keep track of the associated tags would something like this work?
var rollIndex = msg.content.match(/--.*?\|.*?\$\[\[(\d+)\]\]/);
Just based on what Brain said earlier,

Brian said:

[ ] Creates a "character class", and matches any character between the square brackets... unless the class begins with ^, in which case it matches the opposite (any character not in the class). So, [^|] matches any character that isn't "|", and [^\|] matches any character that is neither "\" nor "|".
* means "repeat the previous thing 0 or more times". [^|]*, therefore, matches a series of characters which are not "|" in a row.
\ (outside of a character class) escapes the next character. Because "|" has special meaning normally, it needs to be escaped if you want to match "|", by using "\|". Similarly, the curly braces are escaped, because { } also has meaning in regex.
?, in the context that it is used twice in the above regex, means "repeat the previous thing exactly zero or one time". "\|?" means either zero "|" or one "|", nothing else.
. matches any character.
( ) creates a "capturing group" which can be referenced later.
As far as I understand (again, which is probably wrong) by adding ".*?" after "/--" that translates to "keep logging characters after this until "\|" " so rollIndex would look like
[" --thisTag|$[[0]]", "0"]
Rather than requiring an actual, pre-defined tag. (Not that I couldn't see the advantage, just making sure I get at least a basic grasp of the system)
Edit: Yes, I played around with the api and got that to work. Cool.

At this point, I would like to make a note that I have been working on figuring this out for the better part of 6 hours now. And have yet to actually post this. I like to try things out before just asking for answers (you learn faster that way sometimes) I have the basic concept down and can get it to work. But I have a few questions with the regex that I'm just not figuring out via experimentation.
Basic code block to test me theories without breaking things that work:
var TestAPI = TestAPI || {};

on("chat:message", function (msg) {
    if (msg.type != "api") return;
    log(msg);
    var command = msg.content.split(" ", 1);
    log(command);
    
    if (command == "!test") {
	//Used pcpp's function like you said and immediately had it install the necessary bit from powercard.
        powerCardFunction = function() {
        },

        checkInstall = function() {
            if("undefined" !== typeof PowerCard && _.isFunction(PowerCard.Process)) {
                powerCardFunction = PowerCard.Process;
            } else if("undefined" !== typeof PowerCardScript && _.isFunction(PowerCardScript.Process)) {
                powerCardFunction = PowerCardScript.Process;
            } else {
                log('No Powercard Script Found.');
            }
        };
        checkInstall();
    	
	//Necessary bit to get powercard to wrk properly as-per instructed.
        var player_obj = getObj("player", msg.playerid);
        msg.who = msg.who.replace(" (GM)", "");
        msg.content = msg.content.replace("(GM) ", "");
        
	//Test to see if I could pull out the roll reference then just replace
	//the content with purely the intended tag.
        var rollIndex=msg.content.match(/--.*?\|.*?\$\[\[(\d+)\]\]/);
        log(rollIndex);
        msg.content = " " + rollIndex[0];
        log(msg.content);
        powerCardFunction(msg,player_obj);        
    };
});
Now, the problem I'm facing is with the end of the regex bit. If I use this:
!test --thisTag|One [[1d4+2]] Two
I can't figure out how to get it to also record the "Two" rather it just comes out as "[" --thing|One $[[0]]","0"]" I tried various ways of getting the "Two" into it, but I can't seem to figure it out. (I would also like to note for the purpose of powercards, putting the space before "+ rollIndex[0]" is really important when doing this. Took longer than I actually care to admit to figure that out.)

It is also at this time I would like to ask an opinion. Is it better to used defined tags while using the regex ie:
var attackIndex=msg.content.match(/ --attack\|.*?\$\[\[(\d+)\]\]/);
var damageIndex=msg.content.match(/ --damage\|.*?\$\[\[(\d+)\]\]/);
Or to stick with the undefined version I've written, and have a piece of code that sets rollIndex[0] into an object using the generated tags as, well, the tags?
ie:
var rollObj = {"attack":"One $[[0]] Two"};

March 15 (10 years ago)

Edited March 15 (10 years ago)
The Aaron
Pro
API Scripter
Wow, I like how you've jumped in and embraced the regular expressions!

A few notes before we begin.
As far as I understand (again, which is probably wrong) by adding ".*?" after "/--" that translates to "keep logging characters after this until "\|" " so rollIndex would look like
You've got the right understanding here, but I just want to clarify the difference between the two ?s. The one Brian was talking about is a sibling to the *. So A? means "match 0 or 1 As", and A* means "match 0 or more As". The ? I'm using is not a sibling of *, but a modifier telling the * to match as few things as possible to allow the regular expression to match. So A*? means "match the fewest As (0 or more) before the next thing matches". Generally, this is most useful with .* which is a greedy match of any character. This means it will match as much as possible. Adding the ? makes it a lazy match. For example:
> var test="This is (in) parenthesis, so is (this), but this is not.";

> test.match(/\(.*\)/g);  // Greedy Match
["(in) parenthesis, so is (this)"]
> test.match(/\(.*?\)/g); // Lazy Match ["(in)", "(this)"]
In the case of the greedy match, the .* happily matched as much as possible until the next ). In the lazy match, it matched the minimum. (The g at the end means 'global', which is effectively "match as many times as possible".)

On to matching more than one thing. In the example above, lets say I wanted to extract the first character of the thing in parenthesis:
> test.match(/\((.).*?\)/); // Match the first letter of the first match
["(in)", "i"]

> test.match(/\((.).*?\)/g); // Match the first letter of each match (doesn't work!)
["(in)", "(this)"]
Capturing groups work for a single match, but once you add the global, .match() won't give them to you. You can get around this two ways, but both involve a loop. First, you could make a Regex object and execute it on the string:
> var match;
> var parafirstletter = /\((.).*?\)/g;             // Creates the Regex object
> while( match = parafirstletter.exec(test) ){     // Keep applying while there are matches
>   log(match); 
> }
["(in)", "i", index: 8, input: "This is (in) parenthesis, so is (this), but this is not."]
["(this)", "t", index: 32, input: "This is (in) parenthesis, so is (this), but this is not."]
Regex objects maintain internal state for where they have matched and continue from that point forward on each call to .exec(). It also gives you a bit more information.

The other thing you can do is extract the parts you're interested in first, then loop over that set to extract additional data. I do this all the time using the _.chain() function and it's ilk:
> _.chain(test.match(/\(.*?\)/g))   // find the part I'm interested in
>   .map(function(part){               // call a function on each part
>     log(part.match(/\((.).*?\)/));   // do the match and log it
>   });
["(in)", "i", index: 0, input: "(in)"]
["(this)", "t", index: 0, input: "(this)"]
With those established in your mind, lets move on to your specific question about matching all of the tag



Let's use this as an example:
var content="!test --thisTag|One $[[0]] Two --thatTag|Three $[[1]] Four $[[2]] --theOtherTag$[[3]]|Five $[[4]] Six --thingamajigTag|Seven Eight";
So, there are 4 different tag groups here, we'll see about matching all of them, starting with how I would do it (the second example above) then how you might do it with the Regex object.

If I were doing this, I wouldn't match the tag, I'd split the arguments first, then look at each argument:
> _.chain(content.split(/\s+--/))  // break content into parts on either side of " --"
>   .rest()                        // strip off the first thing (!test)
>   .map(function(part){           // apply a function to each remaining thing
>     var bits = part.split(/\|/), // break into the tag and the body
>         tag = bits[0],           // make a variable for the tag part
>         body = bits[1],          // make a variable for the body part (ha ha!)
>         idxs = _.map(part.match(/\$\[\[\d+\]\]/g),function(r){return r.match(/\d+/)[0];});
>     log("Tag: "+tag+"  Body: "+body+"  Inline Roll Indexes: "+idxs.join(', '));
>   });
Tag: thisTag  Body: One $[[0]] Two  Inline Roll Indexes: 0
Tag: thatTag  Body: Three $[[1]] Four $[[2]]  Inline Roll Indexes: 1, 2
Tag: theOtherTag$[[3]]  Body: Five $[[4]] Six  Inline Roll Indexes: 3, 4
Tag: thingamajigTag  Body: Seven Eight  Inline Roll Indexes: 
That idxs part looks complicated, it's just finding all the $[[#]] parts and then pulling the number out of them to get an array of indexes.


On to the Regex way.

Ok, I kind of cheated and set up an example that would be hard to reconcile with capturing groups. But let's explore it anyway! =D

First, we'll deal with matching the whole tag, the "record the Two" part. The reason you aren't getting the two is because the regular expression ends at the last element, which is the Inline Roll Marker. You could add .*? (which I bet you tried) but it's going to match everything else. You could add .*? -- and you would get the Two, but only if it's followed by another tag. Here's what you need to know.

First, you can match the end of a string with a $, as Brian pointed out. In your simple example, this would work:
var rollIndex=msg.content.match(/--.*?\|.*?\$\[\[(\d+)\]\].*?$/);
However, that will match everything till the end of the string (you could really just put .* at that point and get about the same thing.). You really only want to match until the end of the string or the beginning of the next tag. You can do this with or groups:
var rollIndex=msg.content.match(/--.*?\|.*?\$\[\[(\d+)\]\].*?( --|$)/);
The | tells the Regex to match either " --" or $ (end of line). However, that's still not exactly what you want, because you want to be able to match the " --" on the beginning of the next tag, so you don't want to consume it here. To do this, you have to use a Positive Lookahead. This is a construct in the Regex that tells it to peek ahead at something, but not actually consume it. It's pretty arcane and rarely used (and I find there is usually a better way to do things if you're using it, but I digress). Here's what that looks like:
var rollIndex=msg.content.match(/--.*?\|.*?\$\[\[(\d+)\]\].*?(?= --|$)/);
So, let's see how that works against the longer example above (I added a global to the end):
> content.match(/--.*?\|.*?\$\[\[(\d+)\]\].*?(?= --|$)/g);
["--thisTag|One $[[0]] Two", "--thatTag|Three $[[1]] Four $[[2]]", "--theOtherTag$[[3]]|Five $[[4]] Six"]
So, you can see that it has matched the tags as desired... maybe. Note that there are only 3 tags here. That's because the 4th tag had no inline rolls, so it wasn't matched. That may or may not be a problem. Now to go deeper with capturing the groups!
> var getTagsRegex = /--.*?\|.*?\$\[\[(\d+)\]\].*?(?= --|$)/g;
> while(match = getTagsRegex.exec(content)){
>   log(match);
> }
["--thisTag|One $[[0]] Two", "0", index: 6, input: "<omitted or brevity>"]
["--thatTag|Three $[[1]] Four $[[2]]", "1", index: 31, input: "<omitted or brevity>"]
["--theOtherTag$[[3]]|Five $[[4]] Six", "4", index: 66, input: "<omitted or brevity>"]
There you go, prefect! ... well, almost. Where did index 2 and index 3 go? They didn't match the Regex's single Inline Roll capturing group. You could probably construct a regular expression that would match any number of Inline Rolls before and after the | in an argument, but it rapidly gets overly complicated. To me, it just seems easier to take the arguments and split them into the individual tag groups (which is basically how powercards handles them), then process each one however you like.


So to get back to your actual question:
Is it better to used defined tags while using the regex ie or to stick with the undefined version I've written, and have a piece of code that sets rollIndex[0] into an object using the generated tags as, well, the tags?

I'll start with everybody's favorite answer and say "It depends":
  • If you actually want to process every tag, and can come up with a way to process every tag that works across all tags, then definitely do it.
  • If you only want to deal with certain tags, make regular expressions to just match those tags.

I think I would suggest you take the second route initially. Try adding in a check for healing or automating damage. I think you will probably learn a bunch about what you really want by trying to do one. There's a saying in computer science called the Rule of 3 , paraphrased "Do something once and move on, do something twice and think about the duplication, do something a third time and refactor." (Refactor basically being extracting the duplicate parts to a single place and using it in all 3+). So, try writing it a few times and then see if you can combine it into one. =D

And of course, feel free to tell us how it goes or ask if you want help!

Rule of 3: http://en.wikipedia.org/wiki/Rule_of_three_%28computer_programming%29