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

Get and Set attributes on all sheets

1538708836

Edited 1538709093
**I think this justifies a new post, but if it should be a comment in my previous thread please let me know and I'll reply to it instead** With the help of several folks here on the boards, I've come up with a workable scenario to achieve my goal of having a conditional attack modifier based on NPC type. However, I have to make it as painless as possible for my GM, and that's where I'm stuck now. I need to cycle through every NPC sheet in the game (and any new sheets he adds to the game), and create 2 new attributes that are based off of one existing attribute. Cycling through everything and only acting if npc_type already exists shouldn't hurt anything. I started with Invincible Spleen's awesome updater ( <a href="https://app.roll20.net/forum/post/2011765/add-attribute-api/?pageforid=2011765#post-2011765" rel="nofollow">https://app.roll20.net/forum/post/2011765/add-attribute-api/?pageforid=2011765#post-2011765</a>) ) but I'm falling short needing to use per-character existing information for the new value, rather than simply having a constant pre-set value. The default NPC sheets my GM is using all have the following attribute with a multiple string value that can have one of two formats:&nbsp; npc_type "&lt;size&gt; &lt;type&gt;,&lt;alignment&gt;" &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// "Medium undead, lawful evil" npc_type "&lt;size&gt; &lt;type&gt; (&lt;subtype&gt;),&lt;alignment&gt;" // "Small humanoid (goblinoid), neutral evil" I'll just look at the top case to start, but planning for the second version may change the way the split calls are made. I need to read in the attribute value, split the resulting string, then create and assign a new attribute value using the split info (and do this to all sheets, already existing and yet to be created) For example using a skeleton: npc_type = "Medium undead, lawful evil" The desire is for npc_type_split[x] to fill with npc_type_split[0] = "Medium" npc_type_split[1] = "undead" //as I type I realize i'll need another (or more robust) split to remove the "," npc_type_split[2] = "lawful" npc_type_split[3] = "evil" Then push [1] to the new attribute basic_type, resulting in basic_type = "undead" My butchered attempt at doing so follows if it's of any help. The only changes are to the top two sections. One glaring issue is that there's no definition for characterID since what I have is outside of the loop that cycles through each sheet/assigns the info as a new one is made. The killer is that the script uses attributehash[x] to determine when it has finished with each sheet, so I still need a global variable list so it does something, but the value i want to put in is not constant. var DefaultAttributes = DefaultAttributes || (function () { 'use strict'; /// /// Original Script by Invincible Spleen (<a href="https://app.roll20.net/forum/post/2011765/add-attribute-api/?pageforid=2011765#post-2011765" rel="nofollow">https://app.roll20.net/forum/post/2011765/add-attribute-api/?pageforid=2011765#post-2011765</a>) /// Mangled by Takryn /// /// Usage: /// !initattributes sets attributes to default values listed; creating new attributes if non-existing /// !setAttribute [attribute] [newvalue] sets attribute to value; can use incremental changes ie +1 /// // Pull and parse type value from npc_type var pullandparse = function(characterID) { var npc_type = getObjs({ _characterid: characterid, _type: "attribute", name: "npc_type" })[0]; var npc_type_split = {npc_type.split(' ')}; var basic_type = {npc_type_split[1]}; }, // Attributes all characters should have var attributeHash = { "basic_type": { "current": basic_type, } }, // Set an attribute's value, or create it if it does not exist setAttribute = function(characterID, attributeName, newValue, operator) { var mod_newValue = { "+": function (num) { return num; }, "-": function (num) { return -num; } }, foundAttribute = findObjs({ _characterid: characterID, _type: "attribute", name: attributeName })[0]; if (!foundAttribute) { if (typeof operator !== 'undefined' &amp;&amp; !isNaN(newValue)) { log (newValue + " is a number."); newValue = mod_newValue[operator](newValue); } log("DefaultAttributes: Initializing " + attributeName + " on character ID " + characterID + " with a value of " + newValue + "."); sendChat("DefaultAttributes:", "/w GM Initializing " + attributeName + " on character ID " + characterID + " with a value of " + newValue + "."); createObj("attribute", { name: attributeName, current: newValue, characterid: characterID }); } else { if (typeof operator !== 'undefined' &amp;&amp; !isNaN(newValue) &amp;&amp; !isNaN(foundAttribute.get("current"))) { newValue = parseFloat(foundAttribute.get("current")) + parseFloat(mod_newValue[operator](newValue)); } log("DefaultAttributes: Setting " + attributeName + " on character ID " + characterID + " to a value of " + newValue + "."); sendChat("DefaultAttributes:", "/w GM Setting " + attributeName + " on character ID " + characterID + " to a value of " + newValue + "."); foundAttribute.set("current", newValue); } }, // Add missing attributes and restore exiting ones to their default values initCharacterAttributes = function(char){ log("DefaultAttributes: Initializing default attributes for character ID " + char.id + "."); sendChat("DefaultAttributes:", "/w GM Initializing default attributes for character ID " + char.id + "."); for (var key in attributeHash) { if (attributeHash.hasOwnProperty(key)) { setAttribute(char.id, key, attributeHash[key]["current"]); } } }, showHelp = function () { sendChat("DefaultAttributes:", "/w GM Syntax is !setattribute &lt;i&gt;Attribute&lt;/i&gt; [+/-] &lt;i&gt;Value&lt;/i&gt;"); }, handleInput = function(msg) { if(msg.type == "api") { var args = msg.content.split(/\s+/); switch(args[0]) { case '!initattributes': if (playerIsGM(msg.playerid)) { log("DefaultAttributes: Initializing default attributes for all existing characters."); sendChat("DefaultAttributes:", "/w GM Initializing default attributes for all existing characters."); var allCharacters = findObjs({ _type: "character" }); _.each(allCharacters, function(char) { initCharacterAttributes(char); }); } break; case '!setattribute': if (args.length &lt; 3 || args.length &gt; 4) { return showHelp(); } var foundCharacter = findObjs({ _type: "character", name: msg.who })[0]; if (foundCharacter) { if (args[2] == "+" || args[2] == "-") { setAttribute(foundCharacter.id, args[1], args[3], args[2]); } else { setAttribute(foundCharacter.id, args[1], args[2]); } } else { log("DefaultAttributes: No character associated with " + msg.who); sendChat("DefaultAttributes:", "/w GM No character associated with " + msg.who); } break; } } }, // Event triggers registerEventHandlers = function() { on("add:character", initCharacterAttributes); on("chat:message", handleInput); }; return { RegisterEventHandlers: registerEventHandlers }; })(); on("ready", function() { 'use strict'; DefaultAttributes.RegisterEventHandlers(); }); As always any help is appreciated!
1538712394
GiGs
Pro
Sheet Author
API Scripter
First a disclaimer: if I was your GM, I wouldn't allow this script. Having to make global changes to all the characters to handle one script is a bad idea, because other players might need more changes later, and attribute bloat is one of the biggest causes of game lag in roll20. It's just best to avoid goign down that road. There is a better solution, which is to simply have your script check a target when you attack them, and extract the type information in real-time.&nbsp; This has the advantage you dont need to bloat the npc characters. When using the target keyword, you are prompted to pick a token, and can get the character_id linked to that token, and from there, you can get the npc_type. Then you can split it. the way I'd do the split is to do it twice: first split on the comma, and [1] is the alignment, and [0] is the types. Then do a split on [0] to get the different elements of type - which seeing that humanoid entry does present a few problems. You can't simply split on spaces. You can also build in some error checking to handle npcs where the npc_type is malformed, or absent. Then your weapon attack script can use those details however you need. If you do go ahead with looping through all the characters and making permanent changes (which I dont recommend), there's an old function Aaron posted in an old thread:&nbsp; <a href="https://app.roll20.net/forum/permalink/3281508/" rel="nofollow">https://app.roll20.net/forum/permalink/3281508/</a> . This cycles through every character and get an array of all characters with the specific attribute. Create this function: var getCharactersWithAttrByName = function(attributeName){ &nbsp; "use strict"; /* start the chain with all the attribute objects named 'player-name' */ &nbsp; return _.chain(filterObj((o)=&gt;{ &nbsp; &nbsp;return (o.get('type')==='attribute' &amp;&amp; &nbsp; &nbsp; &nbsp; o.get('name')===attributeName); &nbsp; &nbsp; })) /* IN: Array of Attribute Objects */ /* extract the characterid from each */ &nbsp; &nbsp; .map((o)=&gt;{return o.get('characterid');}) /* IN: Array of Character IDs (Possible Duplicates) */ /* Remove any duplicate IDs */ .uniq() /* IN: Array of Character IDs */ /* Get the character object for each id */ &nbsp; &nbsp; .map((cid)=&gt;{return getObj('character',cid);}) /* IN: Array of Character Objects or Undefined */ /* remove any entries that didn't have Characters */ &nbsp; &nbsp; .reject(_.isUndefined) /* IN: Array of Character Objects */ /* Unwrap Chain and return the array */ &nbsp; &nbsp; .value(); }; and then call it like: var chars = getCharactersWithAttrByName('npc-type'); That'll give you an array of character objects. Then you can loop through them, and extract the npc_type attribute and do all the stuff you need.
1538714955

Edited 1538716320
I'm totally open to doing it real time! My reasoning for making the attribute was so that I could revert to using basic macros instead of scripting the whole thing. I picked up the macros far quicker than the javascript, so it seemed like the easy way out. I figured it would also work for non-api folks that way too, provided the templates the GM was using were consistent with the pertinent attribute value. To make sure I understand the realtime process: I need a macro that starts with @{target|token_id} and then sends the result via !XYZ call to the script The script will then pull the npc_type attribute (with getobj?) and work to extract the basic type from it, then run through if/thens to check what bonuses should be applied and add them up and label the output accordingly My follow up question is what tutorial do i search for the coding calls to convert what i have as a basic macro into js?&nbsp; Or since #@{target|type} works, is there a way to replace "@{target|type}" with output from the script, maybe #[[!xyz]] or perhaps to simply have the script activate a premade macro? I definitely see where y'all were coming from regards this hardly being worth all the effort, but it's piqued my interest now and learning all this stuff makes me happy.&nbsp;&nbsp;
1538716005
GiGs
Pro
Sheet Author
API Scripter
If your GM is okay with adding extra stats to the character sheet ,you could combine both approaches. Instead of modifying all characters in one fell swoop, have you weapon attack macro have two parts: 1) a script that gets the npc-type attribute of the target and updates that character with the new attributes 2) your standard macro that uses those attributes as you intend. Alternatively, step 1 could be replaced with a script that runs automatically when the gm adds a token to the map and updates the character linked to the token if needed, so by the time you get to attack it, the stats are guaranteed to be there. This accounts for the fact that the GM will add new npcs from time to time, and they;ll need these attributes added. Your script call would probably look something like !updateStats @{target|foe|character_id} /roll bla bla your macro goes here and you'd have an UpdateStats macro that would handle all the stat creation (and is smart enough to do nothing if the character already has the relevant stats). Then in your /roll line, you'd have whatever your attack macro is. But IIRC your macro is pretty complex, with a few conditional elements (like doing different things depending on whether the target is undead, etc.) That's not really possible in a basic macro without using queries which you dont want to use, so you probably need to do the full thing through the script. I still think the API button approach is the best future-proof way to do this. Your script would likely need tweaking every now and then as you add extra capabilities, or change weapons. It's a pain to keep that kind of thing updated, especially once you want to handle other attack options.
Thanks for taking the time to work with me on this. I know I'm like a clueless child spouting nonstop questions. I'll take another stab at it tomorrow.
1538717177
GiGs
Pro
Sheet Author
API Scripter
Can you post the the macro you have been using up to know, with all the queries you would have in it? That's so I can present a version of it using API buttons for your consideration. Then if you don't like it, I'll help you with the script approach. (It might take a little while, it's the weekend.)
1538723432

Edited 1538750138
So far I've just clicked the weapon attacks from my character sheet. I've baked in the bonus effects of each creature type and planar warrior ability into separate options. I also have stand alones to just click for PW and fav enemy after using one of the other weapons because I hadn't set them up yet. These are the same pre-built macros I was planning to use. I pulled them from hitting "up" in chat after clicking from the sheet and just copied them into macros. In this case I have 6 options (normal or pw enhanced for undead *weapon and fav en*/fiend *fav en*/other), but if my first favored enemy was humanoid, it would be 8 due to splitting out undead/humanoid/fiend/other. I agree using the pop up menu would likely be required just to declutter the macro bar if i was clicking from that instead of the char sheet on a second monitor. I'll use planar warrior almost every turn, but since it uses a bonus action I can't use it every time. It made sense to me to have something to click based on if i'm using it or not. I figure this is what the drop down queries will ask, but that adds an extra click to pick yes or no. Ideally I'll end up with a choice of two buttons to hit for each weapon (normal attack or pw enhanced attack) and then the coding will apply the appropriate bonuses. I almost exclusively use the sun blade though.&nbsp; // Script required: ExpandedExpressions // Manual attribute entries on player sheet for fav_en_1 = undead and fav_en_2 = fiend // These two work and actually call the other macro but have no checking mechanism, // and therefore will break outside of undead or fiend targets SB-attack #sb-@{target|type} SB-PW-attack #sbpw-@{target|type} // These two don't work, the OR functionality is only checking the first part // (though I swear it was earlier, and I can get it to work with numbers instead of strings) // but even when they evaluate correctly they just output the text as shown into chat without calling the macro SB-attack !extend `("@{target|type}" = ("@{Arkos|fav_en_1}"||"@{Arkos|fav_en_2}") ? "#sb-@{target|type}" : "#sb-base")` SB-PW-attack !extend `("@{target|type}" = ("@{Arkos|fav_en_1}"||"@{Arkos|fav_en_2}") ? "#sbpw-@{target|type}" : "#sbpw-base")` // Copied out of chat log after clicking from offense section of shaped character sheet sb-undead @{Arkos|output_option} &amp;{template:5e-shaped} {{character_name=@{Arkos|character_name}}} @{Arkos|show_character_name} {{title=Sun Blade (Undead)}} {{offense=1}}&nbsp; @{Arkos|attacher_offense} @{Arkos|hide_gm_info} {{@{Arkos|shaped_d20}=1}} {{attack_type_macro=[Melee Weapon Attack:](~-LNSkNQHTH31VWs3bonm|repeating_offense_-LNsUZ-Owz7xL8ZF9clN_attack)}} {{has_attack_damage=1}} {{attack_damage_crit=[[2d8]]}} {{attack_damage=[[2d8[damage] + 3[dex] + 8[bonus]]]}} {{attack_damage_type=radiant}} {{has_attack_damage=1}} {{attack_damage_macro=[Hit:](~-LNSkNQHTH31VWs3bonm|repeating_offense_-LNsUZ-Owz7xL8ZF9clN_attack_damage)}} {{attack_damage_crit_macro=[Crit:](~-LNSkNQHTH31VWs3bonm|repeating_offense_-LNsUZ-Owz7xL8ZF9clN_attack_damage_crit)}} {{attack1=[[@{Arkos|shaped_d20}@{Arkos|d20_mod}cs&gt;20 + 3[proficient] + 3[dex] + 2[bonus]]]}} {{reach=5}} sbpw-undead @{Arkos|output_option} &amp;{template:5e-shaped} {{character_name=@{Arkos|character_name}}} @{Arkos|show_character_name} {{title=Sun Blade (Undead + PW)}} {{offense=1}}&nbsp; @{Arkos|attacher_offense} @{Arkos|hide_gm_info} {{@{Arkos|shaped_d20}=1}} {{attack_type_macro=[Melee Weapon Attack:](~-LNSkNQHTH31VWs3bonm|repeating_offense_-LNsYH3NGU99ofSvOCNN_attack)}} {{has_attack_damage=1}} {{attack_damage_crit=[[2d8]]}} {{attack_damage=[[2d8[damage] + 3[dex] + 8[bonus]]]}} {{attack_damage_type=force}} {{has_attack_damage=1}} {{attack_damage_macro=[Hit:](~-LNSkNQHTH31VWs3bonm|repeating_offense_-LNsYH3NGU99ofSvOCNN_attack_damage)}} {{attack_damage_crit_macro=[Crit:](~-LNSkNQHTH31VWs3bonm|repeating_offense_-LNsYH3NGU99ofSvOCNN_attack_damage_crit)}}&nbsp; {{attack_second_damage_crit=[[1d8]]}} {{attack_second_damage=[[1d8[damage]]]}} {{attack_second_damage_type=radiant}} {{attack1=[[@{Arkos|shaped_d20}@{Arkos|d20_mod}cs&gt;20 + 3[proficient] + 3[dex] + 2[bonus]]]}}&nbsp; sb-fiend @{Arkos|output_option} &amp;{template:5e-shaped} {{character_name=@{Arkos|character_name}}} @{Arkos|show_character_name} {{title=Sun Blade (Fiend)}} {{offense=1}}&nbsp; @{Arkos|attacher_offense} @{Arkos|hide_gm_info} {{@{Arkos|shaped_d20}=1}} {{attack_type_macro=[Melee Weapon Attack:](~-LNSkNQHTH31VWs3bonm|repeating_offense_-LNsYqKzdsZ5m2YNtjkO_attack)}} {{has_attack_damage=1}} {{attack_damage_crit=[[1d8]]}} {{attack_damage=[[1d8[damage] + 3[dex] + 8[bonus]]]}} {{attack_damage_type=radiant}} {{has_attack_damage=1}} {{attack_damage_macro=[Hit:](~-LNSkNQHTH31VWs3bonm|repeating_offense_-LNsYqKzdsZ5m2YNtjkO_attack_damage)}} {{attack_damage_crit_macro=[Crit:](~-LNSkNQHTH31VWs3bonm|repeating_offense_-LNsYqKzdsZ5m2YNtjkO_attack_damage_crit)}} {{attack1=[[@{Arkos|shaped_d20}@{Arkos|d20_mod}cs&gt;20 + 3[proficient] + 3[dex] + 2[bonus]]]}} {{reach=5}}&nbsp; sbpw-fiend @{Arkos|output_option} &amp;{template:5e-shaped} {{character_name=@{Arkos|character_name}}} @{Arkos|show_character_name} {{title=Sun Blade (Fiend + PW)}} {{offense=1}}&nbsp; @{Arkos|attacher_offense} @{Arkos|hide_gm_info} {{@{Arkos|shaped_d20}=1}} {{attack_type_macro=[Melee Weapon Attack:](~-LNSkNQHTH31VWs3bonm|repeating_offense_-LNsZFQ5o-RazMf9giDO_attack)}} {{has_attack_damage=1}} {{attack_damage_crit=[[2d8]]}} {{attack_damage=[[2d8[damage] + 3[dex] + 8[bonus]]]}} {{attack_damage_type=force}} {{has_attack_damage=1}} {{attack_damage_macro=[Hit:](~-LNSkNQHTH31VWs3bonm|repeating_offense_-LNsZFQ5o-RazMf9giDO_attack_damage)}} {{attack_damage_crit_macro=[Crit:](~-LNSkNQHTH31VWs3bonm|repeating_offense_-LNsZFQ5o-RazMf9giDO_attack_damage_crit)}} {{attack1=[[@{Arkos|shaped_d20}@{Arkos|d20_mod}cs&gt;20 + 3[proficient] + 3[dex] + 2[bonus]]]}}&nbsp; sb-base @{Arkos|output_option} &amp;{template:5e-shaped} {{character_name=@{Arkos|character_name}}} @{Arkos|show_character_name} {{title=Sun Blade}} {{offense=1}} @{Arkos|attacher_offense} @{Arkos|hide_gm_info} {{@{Arkos|shaped_d20}=1}} {{attack_type_macro=[Melee Weapon Attack:](~-LNSkNQHTH31VWs3bonm|repeating_offense_-LNsFK7c7ngkvqIL-Hil_attack)}} {{has_attack_damage=1}} {{attack_damage_crit=[[1d8]]}} {{attack_damage=[[1d8[damage] + 3[dex] + 4[bonus]]]}} {{attack_damage_type=radiant}} {{has_attack_damage=1}} {{attack_damage_macro=[Hit:](~-LNSkNQHTH31VWs3bonm|repeating_offense_-LNsFK7c7ngkvqIL-Hil_attack_damage)}} {{attack_damage_crit_macro=[Crit:](~-LNSkNQHTH31VWs3bonm|repeating_offense_-LNsFK7c7ngkvqIL-Hil_attack_damage_crit)}} {{attack1=[[@{Arkos|shaped_d20}@{Arkos|d20_mod}cs&gt;20 + 3[proficient] + 3[dex] + 2[bonus]]]}} {{reach=5}} sbpw-base @{Arkos|output_option} &amp;{template:5e-shaped} {{character_name=@{Arkos|character_name}}} @{Arkos|show_character_name} {{title=Sun Blade (PW)}} {{offense=1}}&nbsp; @{Arkos|attacher_offense} @{Arkos|hide_gm_info} {{@{Arkos|shaped_d20}=1}} {{attack_type_macro=[Melee Weapon Attack:](~-LNSkNQHTH31VWs3bonm|repeating_offense_-LNsNrAHcesYEqXuceAV_attack)}} {{has_attack_damage=1}} {{attack_damage_crit=[[2d8]]}} {{attack_damage=[[2d8[damage] + 3[dex] + 4[bonus]]]}} {{attack_damage_type=force}} {{has_attack_damage=1}} {{attack_damage_macro=[Hit:](~-LNSkNQHTH31VWs3bonm|repeating_offense_-LNsNrAHcesYEqXuceAV_attack_damage)}} {{attack_damage_crit_macro=[Crit:](~-LNSkNQHTH31VWs3bonm|repeating_offense_-LNsNrAHcesYEqXuceAV_attack_damage_crit)}} {{attack1=[[@{Arkos|shaped_d20}@{Arkos|d20_mod}cs&gt;20 + 3[proficient] + 3[dex] + 2[bonus]]]}} {{reach=5}}&nbsp;
1538748008
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Just to throw something else into the mix. It looks like you are using the Shaped Sheet. You can save yourself a lot of pain by using the Modifiers section. You would only have one entry for each weapon, and one modifier for each condition. Your script could just bother with toggling the modifiers. This cleans up your sheet considerably, removing many repeating attributes, and allowing you to add new weapons easily as your character grows. Also, attachers can be your friend here, allowing you to easily add conditional damage with a single extra click. I would finally like to echo GG's advice on not adding campaign wide custom attributes. Besides being a bit clunky and bloating an already impressively large character sheet, your GM would need to remember to add this attribute in to all future characters.
Ah, so that does exist on Shaped sheets! I'd seen reference to it from the OGL sheets but didn't make the connection. Playing with the modifiers, I'm not seeing an option to change the damage type. I'm a big fan of the card output inherent to the Shaped sheet: it does all of the rolls, adds everything up into one clean number per damage type and displays it, while still being able to hover over and see the components in the equation. The Planar Warrior ability changing the damage type is a big driver towards making things messy; that's what I'm trying to clean up.&nbsp;
1538768998
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Ah, that is a bit sticky. You can do a lot with modifiers and attachers. You might be able to put a query into the damage type. I don't know; I've never tried.
1538769046
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Just as a heads up, the modifiers can be turned on and off programmatically with the ChatSetAttr script.
1538954067

Edited 1538954433
GG: to further answer your request, the queries would be as follows: Tier 1: Am I using Planar Warrior? (only affects 1 attack each turn - and my character generally makes 2). Tier 2: Which weapon am I using? (SunBlade or Long Bow - and very rarely a short sword) Tier 3: Is the target type one that is pertinent to my character? (in my case: Undead or Fiend) There's actually another Tier 1 character level ability that should be checked for, but I don't think its worth adding another extra level to the process yet. Keith: How does one call the ChatSetAttr script to set a specific attribute if I'm determining what that attribute is in another script? It's my current hangup in option (a) below. Following the guidelines from GG up above, I've cobbled together a script that takes input of &lt;character_id&gt; and &lt;attribute name&gt; and parses the "npc_type" attribute to split the string and make it usable as a check against Tier 3 above. It's a bit more generic than I need since I wanted it to be functional for any character attribute from that line (size, type, subtype, alignment, attitude, morality) to future-proof other abilities (Paladin gets bonus against evil, etc.). I'm struggling with making the final string usable. I could either a: send it back to a macro (but have been running into the problem of just getting #sb-undead printed instead of functioning as a macro). I could hard code the script to do the check and output appropriately if I could run the script inline such that the return from the script isn't what the original request was, but one of 3 options to make my macros work (undead, fiend, base). I'm thinking along the lines of #sb-!learn @{target|character_id} type but it seems to get angry, and doesn't like #sb-[[!learn @{target|character_id} type]] either. I've seen some other scripts use a !!! feature to make them work inline, but I couldn't get my head around the implementation within the script. Hard coding it also reduces it's flexibility, unless I increase the inputs to accept the player attributes to compare against as well. b.1: assign it to a new attribute (on my character sheet - to be deleted after running the script/macro to prevent attribute bloat) b.2: have extenededexpressions (or some other script) evaluate if it matches my favored enemy attributes I've manually added to my sheet already b.3: run the appropriate attack/damage rolls macro (b) is effectively what I have outlined above, just eventually checking against my char sheet instead of the target. // base code from: <a href="https://app.roll20.net/forum/post/6068411/how-do-i-get-api-commands-and-macros-to-work-in-order" rel="nofollow">https://app.roll20.net/forum/post/6068411/how-do-i-get-api-commands-and-macros-to-work-in-order</a> on('ready',function(){ &nbsp; &nbsp; 'use strict'; &nbsp; &nbsp; var getSpeaker = function(msg) { &nbsp; &nbsp; &nbsp; &nbsp; var characters = findObjs({_type: 'character'}); &nbsp; &nbsp; &nbsp; &nbsp; var speaking; &nbsp; &nbsp; &nbsp; &nbsp; characters.forEach(function(chr) { if(chr.get('name') == msg.who) speaking = chr; }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(speaking) return 'character|'+speaking.id; &nbsp; &nbsp; &nbsp; &nbsp; else return 'player|'+msg.playerid; &nbsp; &nbsp; &nbsp; &nbsp; //if(speaking) return speaking.id; &nbsp; &nbsp; &nbsp; &nbsp; //else return msg.playerid; &nbsp; &nbsp; }; &nbsp; &nbsp; //var getSpeakerRaw = function(msg) { &nbsp; &nbsp; //&nbsp; &nbsp; var characters = findObjs({_type: 'character'}); &nbsp; &nbsp; //&nbsp; &nbsp; var speaking; &nbsp; &nbsp; //&nbsp; &nbsp; characters.forEach(function(chr) { if(chr.get('name') == msg.who) speaking = chr; }); &nbsp; &nbsp; //&nbsp; &nbsp; &nbsp; //&nbsp; &nbsp; if(speaking) return speaking.id; &nbsp; &nbsp; //&nbsp; &nbsp; else return msg.playerid; &nbsp; &nbsp; //}; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; var getCharNameById = function (id) { const character = getObj("character", id); return (character) ? character.get("name") : ""; }; // FIX THIS SO THAT YOU CAN STILL FUNCTION IF GIVEN NAME INSTEAD OF ID // var getCharIdByName = function (name) { // const character = findObjs({ //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;type: 'character', //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; represents: obj.get('character_id')}) // //getObj("_id", name); // return (character) ? character.id : ""; // }; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; on('chat:message',function(msg){ &nbsp; &nbsp; &nbsp; &nbsp; if('api' === msg.type &amp;&amp; msg.content.match(/^!learn/)&nbsp; ){&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let args = msg.content.split(/\s+/); &nbsp;// Error catcher is not working because of splitting entry. API error trying to split an invalid input breaks sandbox before being caught. How to ensure clean input? &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (args[1] === undefined) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat("Error (Learn)","Input error. Invalid character id. Check syntax \"character_id"); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (args[2] === undefined) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat("Error (Learn)","Input error. 2 arguments required: !learn [character_id] [attribute name]") &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let tarid = args[1]; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let tarname = getCharNameById(tarid); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let attname = args[2]; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; // Non-functioning section - trying to get id from name or name from id automatically //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (args[1][0] === "-") { //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let tarid = args[1]; //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let tarname = getCharNameById(tarid); //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let tarid = getIdByCharName(tarid); //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let tarname = args[1]; //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let tarid2 = getCharIdByName(args[1]); //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; {{tarid2 (get id by name) = '+tarid2+'}} //*&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let vardata = '&amp;{template:default} {{name=Basic Var Check}} {{args ='+args+'}} {{tarname = '+tarname+'}} {{tarid = '+tarid+'}} {{attname = '+attname+'}}))'; //*&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat(getSpeaker(msg),vardata); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; // original error check //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(tarid === undefined) { //msg.inlinerolls // sendChat("Error (Learn)","No character to query."); // return; // }; // if (attname === undefined) { //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat("Error (Learn)","No attribute to search for."); // return; //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }; let att = getAttrByName(tarid, "npc_type", "current"); // Parse the attribute string [npc_type]&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // att =&nbsp; &nbsp; &nbsp; &nbsp; [medium undead, lawful evil]&nbsp; &nbsp; |&nbsp; &nbsp;[small humanoid (goblinoid), neutral] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let attstr = att.split(', ');&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// attstr =&nbsp; &nbsp; &nbsp;[medium undead],[lawful evil]&nbsp; &nbsp;|&nbsp; &nbsp;[small humanoid (goblinoid)],[neutral] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let sizetype = attstr[0].split(/\s+/);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // sizetype =&nbsp; &nbsp;[medium],[undead]&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|&nbsp; &nbsp;[small],[humanoid],[(goblinoid)] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let charsize = sizetype[0];&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// charsize =&nbsp; &nbsp;[medium]&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |&nbsp; &nbsp;[small] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let chartype = sizetype[1];&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// chartype =&nbsp; &nbsp;[undead]&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |&nbsp; &nbsp;[humanoid] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let charsubtype = ""; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (sizetype[2] != undefined) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// if subtype exists, strip () &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; charsubtype = sizetype[2].slice(1,sizetype[2].length-1); // no action&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|&nbsp; &nbsp;[goblinoid] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let charalign = attstr[1].split(/\s+/);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// charalign =&nbsp; [lawful],[evil]&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|&nbsp; &nbsp;[neutral] //*&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let checktest = '&amp;{template:default} {{name=Misc Variable Tester}} {{attstr = '+attstr+'}} {{attstr[1] = '+attstr[1]+'}} {{charalign = '+charalign+'}} {{charalign[0] = '+charalign[0]+'}} {{charalign[1] = '+charalign[1]+'}} {{charalign[1] !\=undef = '+(charalign[1] != undefined)+'}}))'; //*&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat(getSpeaker(msg),checktest); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (charalign[1] != undefined) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // if morality exists, separate attitude and morality &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let charatti = charalign[0];&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // charatti =&nbsp; &nbsp;[lawful]&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |&nbsp; &nbsp;no action &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let charmoral = charalign[1];&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// charmoral =&nbsp; [neutral]&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|&nbsp; &nbsp;no action &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let charatti = charalign;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// no action&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |&nbsp; &nbsp;[neutral] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let charmoral = charalign;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // no action&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; |&nbsp; &nbsp;[neutral] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; switch (attname) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case "size": &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return (charsize); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case "type": &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat("","#"+chartype); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //return (chartype); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case "subtype": &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return (charsubtype); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case "alignment": &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return (attrstr[1]); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case "attitude": &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return (charatti); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case "morality": &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return (morality); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } // Troubleshooting outputs&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let vardata2 = '&amp;{template:default} {{name=Type Parser Info}} {{attstr[0] ='+attstr[0]+'}} {{attstr[1] ='+attstr[1]+'}} {{sizetype = '+sizetype+'}} {{sizetype[2] = '+sizetype[2]+'}} {{size ='+charsize+'}} {{type ='+chartype+'}} {{subtype ='+charsubtype+'}} {{charalign ='+charalign+'}}{{charatti ='+charatti+'}} {{charmoral ='+charmoral+'}}))'; // sendChat(getSpeaker(msg),vardata2); // let outPut = '&amp;{template:default} {{name=Attribute Query}} {{Target='+tarname+'}} {{Attribute='+attname+'}} {{Value='+att+'}}))'; //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat(getSpeaker(msg),outPut); //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return(chartype); &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }); });
1538955453

Edited 1538955771
GiGs
Pro
Sheet Author
API Scripter
It'll take me a while to get my head around this script, but from a quick scan I notice one issue with one of the non-functional sections: //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (args[1][0] === "-") { //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let tarid = args[1]; //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let tarname = getCharNameById(tarid); //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let tarid = getIdByCharName(tarid); //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let tarname = args[1]; //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let tarid2 = getCharIdByName(args[1]); There's a thing called scope: when you use let to define an attribute, it exists only within it's stated scope. One area of scope is the block within a pair of curly brackets { }. So you are defining tarid and tarname within the if block, but they stop existing as soon as you leave that if statement. What you really need is something like this: //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let tarid, tarname; // you can set some default value here if needed. //&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (args[1][0] === "-") { //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; tarid = args[1]; //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; tarname = getCharNameById(tarid); //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; tarid = getIdByCharName(tarid); //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; tarname = args[1]; //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } This ensures the variables exist in their proper scope, and the values assigned within the if statement persist when you leave it. Also this looks iffy: //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (args[1][0] === "-") { //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let tarid = args[1]; Are you meaning to assign an array to tarid? The if statement use args[1][0], so args[1] must be an array.
1538957282
GiGs
Pro
Sheet Author
API Scripter
oh hey, I recognise some of the contributors on that page // base code from: <a href="https://app.roll20.net/forum/post/6068411/how-do-i-get-api-commands-and-macros-to-work-in-order" rel="nofollow">https://app.roll20.net/forum/post/6068411/how-do-i-get-api-commands-and-macros-to-work-in-order</a> A question about your process. You list these tiers: Tier 1: Am I using Planar Warrior? (only affects 1 attack each turn - and my character generally makes 2). Tier 2: Which weapon am I using? (SunBlade or Long Bow - and very rarely a short sword) These are selections you have to make. There is no way to write a script that just knows which of these you are using, you need to have some input to define it.&nbsp; Keith, I think, mentioned the concept of attachers in the sheet you are using, I'd strongly recommend investigating how they work because I think they are meant to handle these kind of situations. Other approaches would be to creating an attribute on your character (on the attributes &amp; abilities tab) where you set them (either manually or with chatsetattr), and your attack macro draws the values from them when used. Regarding the Tier 3 problem, getting the npc_data. My impression is your script is way more complex than it needs to be for this. I don't think you need a getspeaker function or a getcharacter id / name function, because you'll have these every time you use your macro (you are the speaker, and the other details can be gained via target or selected keywords). So everything before this line seems like it could be removed: on('chat:message',function(msg){ You mention the split function is causing a sandbox error, but I'm not sure why that is. The syntax looks fine to me. I'll look at it in more detail later.
1538957391
GiGs
Pro
Sheet Author
API Scripter
PS: I'd recommend testing the function only with perfectly formed inputs and targets with complete data. Once the function is working, you can add the error checking to account for problems. But you need to make sure it actually works first.
1538957502
GiGs
Pro
Sheet Author
API Scripter
also I'm not sure how you are using this: #sb-!learn @{target|character_id} type but API commands need to start with the ! at the very start of the line. They wont work otherwise. What does #sb- represent in this context, how is it being used?
1538958554
GiGs
Pro
Sheet Author
API Scripter
Thinking about it, I'm also wondering if having a split function to get the various npc type elements is really necessary. If you have an npc_type of (say) "medium undead, lawful evil" and you need to know if it is undead, you can just&nbsp; if(npctype.toLowerCase().indexOf('undead') !== -1) or if(npctype.toLowerCase() . includes ( 'undead' ) ) where npctype is a variable that contains the contents of the npctype attribute. the split method is handy to make sure you aren't grabbing data from the wrong part of the string, but of the top of my head, i cant think of situations where that would happen with npc_type. There aren't going to be any alignments that match creature types, or sizes that match creature types, etc.
1538959988

Edited 1538960303
Sorry for all the confusion with what I've got. For what it's worth, the actual gathering and parsing actually "works" with perfectly formed inputs and assigns values as expected. It handles both scenarios for the npc_type as well, so all that is good. The non-working bits are just in there as a legacy for me to get back to later.&nbsp; I don't have a grasp on the return/chat side of things to actually get those values out and back into the tabletop/macro side of things though. The scope info is super helpful, and probably explains several errors I'd been fighting. For this section //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (args[1][0] === "-") { //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let tarid = args[1]; I was trying to use the "-" at the start of the character_id to determine which input the script received, the ID or a Name, and then I was going to call the getNamebyID or getIDbyName accordingly. Regarding the tiers: A question about your process. You list these tiers: Tier 1: Am I using Planar Warrior? (only affects 1 attack each turn - and my character generally makes 2). Tier 2: Which weapon am I using? (SunBlade or Long Bow - and very rarely a short sword) I have no problem selecting from two options per weapon, at least for the time being. As it stands I currently have 6 choices per weapon, so cutting that to a third is plenty. The getSpeaker is there because it was in the script I started with and worked to put the text back into chat like I needed. I'd love to have a simple echo or something that posts it back without any excess effort. The split error is only when given imperfect input, so that can be put aside. The macro call is from a preset macro, in this case #sb-undead sb- is a prefix i'm using for sunblade undead is the creature type i'm attacking so i have a prebuilt attack macro for Sunblade vs Undead, see the second post above where i listed out the macros. If the attribute for just "type" was properly filled (it's not, hence the need for the parsing script), i could simply call like this: #sb-@{target|type} which evaluates to #sb-undead and properly rolls as expected That's how i end up with 6 macros per weapon (base and with pw, and then the two favored enemy types: sb-base, sbpw-base, sb-undead, sbpw-undead, sb-fiend, sbpw-fiend So what I'm looking for at the end of the day is for me to choose the weapon (sb) and whether i'm using pw (sbpw), which calls a macro starting with either #sb- or #sbpw- and then for the script/remaining macro to determine &lt;undead&gt;, &lt;fiend&gt;, or &lt;base&gt; to get pushed back to the macro, completing it and making it fire. Using @{target|type} works, but trying to push the text back out from the script hasn't worked for me so far.&nbsp; The workaround I mentioned is to have the script push the type attribute to my own character sheet as something like target_type, then i could just call #sb-@{arkos|target_type} or #sbpw-@{arkos|target_type}.
Ha! of course there's a search function. Initially I needed to split it so i could assign it to an attribute. If everything ends up happening in the script, the search aspect should be fine.
1538962195
GiGs
Pro
Sheet Author
API Scripter
To clarify, you already have macros by name of&nbsp; #sb-undead #sb-fiend etc, and you are just looking for a way to call them correctly? You dont need to build the macro string, they already exist in your macro list?
Correct, see the last code block of my previous post:&nbsp; <a href="https://app.roll20.net/forum/post/6858810/get-and-set-attributes-on-all-sheets/?pageforid=6859169#post-6859169" rel="nofollow">https://app.roll20.net/forum/post/6858810/get-and-set-attributes-on-all-sheets/?pageforid=6859169#post-6859169</a> Those macros are all set up based on what i created on my character sheet, so yes, I'm looking for a way to automatically apply the correct suffix based on my target selection when the macro fires. (or script fires or whatever ends up working) The penultimate code block shows how i was originally attempting to make the comparison against my character sheet values for favored enemy as well.
1538962727

Edited 1538963321
distilled as much as possible, i'm looking for a functioning version of these: if (@{target|type} = (@{player|fav_en_1} OR @{player|fav_en_2})) &nbsp; &nbsp; return #sb-@{target|type} else &nbsp; &nbsp; return #sb-base if (@{target|type} = (@{player|fav_en_1} OR @{player|fav_en_2})) &nbsp; &nbsp; return #sbpw-@{target|type} else &nbsp; &nbsp; return #sbpw-base ***where @{target|type} is either pulled from a properly assigned attribute on the target itself, or from a call against my character sheet, or having a script manipulate everything. As y'all have pointed out, it's much easier to manage macros than the script, which is why I've been trying to minimize the scripting necessary and just using it to facilitate the attributes to call macros.
1538963496
GiGs
Pro
Sheet Author
API Scripter
That simplifies things a lot. I had badly misunderstood where you were going with this.&nbsp; I think i'll have a solution for you before long.
It's taken me a while to come to terms with how to explain it. All of the terminology being completely foreign hasn't helped, so thanks for working with me. Out of curiosity, what had you gleaned my end goal to be?
1538965244
GiGs
Pro
Sheet Author
API Scripter
I assumed you wanted to build the output string (the huge monstrosity that starts with&nbsp; @{Arkos|output_option} &amp;{template:5e-shaped} etc ) from its component parts.&nbsp;My unfamiliarity with that sheet and system made it hard to figure out what was needed.
Ah, gotcha! Maybe someday when I'm bored and feel like torturing myself I'll look into that. Or when my next crazy idea hits. :)
1538967583

Edited 1539135656
GiGs
Pro
Sheet Author
API Scripter
Here's a&nbsp; first draft. It works on my limited testing, but likely needs some error trapping. Some notes on shortcomings below. Edit: Updated to handle abilities, and report character types. /* Launch with attack macro like !chooseam @{target|character_id} attackbase types|to|check&nbsp; !chooseam @{target|character_id} ?{Attack Type|Standard,sb|With PW,sbpw} fiend|Undead If you want to use abilities, replace&nbsp; !chooseam with&nbsp; !chooseaa @{YOURNAME|character_id} example: !chooseaa @{Thundarr|character_id} @{target|character_id} ?{Attack Type|Standard,sb|With PW,sbpw} fiend|Undead */ on('ready',function(){ 'use strict'; let npc_types = [ 'type','npc_type', 'npcd_type', 'npc_typebase','type_from_srd']; // let getNPCType = function(target) { for (let i = 0; i &lt; npc_types.length; i++) { let npc_Type = getAttrByName(target, npc_types[i]); if(npc_Type !== undefined &amp;&amp; npc_Type !== '' ) { return npc_Type; } } }; let getSpeaker = function(msg) { var characters = findObjs({_type: 'character'}); var speaking; characters.forEach(function(chr) { if(chr.get('name') == msg.who) speaking = chr; }); if(speaking) return 'character|'+speaking.id; else return'player|'+msg.playerid; }; let getMacro = function(name,action,pcid,who) { let m; if (action === 'ability') { m = findObjs({ _characterid: pcid, _type: action, name: name }); } else if (action === 'macro') { m = findObjs({ _playerid: pcid, _type: action, name: name }); } if(m[0] === undefined) { sendChat("Choose Macro","/w " + who + " Error: Macro Not Found"); return; } return m[0].get('action'); }; let handleInput = function(msg, action) { let args = msg.content.split(/\s+/); if (args.length &lt; 3) { sendChat("Choose Macro","/w " + msg.who + " Input error: Not enough parameters."); return; } let pcid = msg.playerid; if (action === 'ability') { args.shift(); pcid = args[0]; } let target = args[1]; let macrobase = args[2]; let enemytypes = args[3] !== undefined ? args[3].split('|') : 'N/A'; let tartype = getNPCType(target); let foundtype = 'base'; if(tartype === undefined) { sendChat("Choose Macro","/w " + msg.who + " Error: NPC Type not found, proceeding with base macro"); } else if ( enemytypes !== 'N/A') { for (let i = 0; i &lt; enemytypes.length; i++) { if(tartype.toLowerCase().includes(enemytypes[i].toLowerCase())) { foundtype = enemytypes[i].toLowerCase(); break; } } } sendChat(getSpeaker(msg),'foundtype = ' + foundtype) ; let macro = getMacro(`${macrobase}-${foundtype}`,action,pcid,msg.who); sendChat(getSpeaker(msg),macro) ; }; on('chat:message',function(msg){ if('api' === msg.type &amp;&amp; msg.content.match(/^!chooseam/) ){ handleInput(msg,'macro'); } else if('api' === msg.type &amp;&amp; msg.content.match(/^!chooseaa/) ){ handleInput(msg,'ability'); } else if('api' === msg.type &amp;&amp; msg.content.match(/^!checktype/) ){ var npcs = findObjs({ type: 'character', controlledby: '' }); let types = filterObjs(function(obj) { if(obj.get("type") === 'attribute' &amp;&amp; obj.get('name').toLowerCase().includes('type') &amp;&amp; !obj.get('name').toLowerCase().includes('repeating') &amp;&amp; obj.get('current') !== "" ) return true; //NPCNew Test else return false; }); types.forEach(type =&gt; { let character = getObj("character", type.get('characterid')).get('name'); sendChat("testing","/w " + msg.who + " " + type.get('name') + ": " + type.get('current') + "; " + character); }); } }); }); It assumes macros are built with a base: sb or sbpw or whatever and that if no favoured enemy applies, it is named base (eg sb-base ) You can feed it a list of favoured enemies separated by a pipe (fiend, fiend|undead, undead|beast|elf) and the script will loop through the npc type to see if any are found. If they are, it will assume a match macro exists (sb-elf, sb-beast, sbpw-undead, etc.) It will then find the relevant macro and roll it. However, it doesn't (yet) output the name of the target. Do you need that? I think this covers the cases you described, if there's anything I've missed let me know. It probably needs a bit more testing. It is probably more elegant to go the other route and build the attack string from scratch, that can be more flexible, but requires that I know all the details of how that system works and how your feats apply to it, but i don't. So, to summarise, you probably launch it like so: !chooseam @{target|character_id} ?{Attack Type|Standard,sb|With PW,pw} undead|fiend And can expand it if you add new macros.
1538968116
GiGs
Pro
Sheet Author
API Scripter
By the way, I notice in your macros you have static values for things like dex. It would be a good idea to change them to attribute calls, so that you dont have to update the macros every time your stats change Like, one of the macros has this in it: {{attack1=[[@{Arkos|shaped_d20}@{Arkos|d20_mod}cs&gt;20 + 3[proficient] + 3[dex] + 2[bonus]]]}} The proficient, dex, and bonus are attributes somewhere on yoru character sheet and you could change it to something like {{attack1=[[@{Arkos|shaped_d20}@{Arkos|d20_mod}cs&gt;20 + @{Arkos|proficiency_bonus)[proficient] + @{Arkos|dex_bonus}[dex] + @{Arkos|weapon_bonus}[bonus]]]}} I dont know what the attributes are actually called, but replacing all the numbers in your macros with the appropriate attribute will make your life easier.
1538968217
GiGs
Pro
Sheet Author
API Scripter
And finally for the night, since you have these macros created, have you considered ticking the token action box on them? If you do that, whenever you have your token selected, you'll get a button for each macro floating above your token, and you can click it to launch it. This may be way easier than using a script!
Wow, that is so clean! Thank you so much! You'd mentioned before the getSpeaker was likely superfluous. Did you find a reason to keep it set up that way? I'm not sure what I'm doing wrong to make it fail to work though. There are no API errors, but nothing comes back to chat after hitting enter with the entry: !choosam @{target|character_id} sb undead|fiend sb-base, sb-undead, and sb-fiend are all confirmed working macros from the macros tab. Does being on the macro bar affect their usability? I don't think outputting the target name is necessary, I should be able to output that separately in a larger macro with the @{target|name} call, and then the subsequent @{target|character_id} will be based on the same target selection if my understanding is correct.
Spelling...spelling would be the issue. Choose has an 'e' on the end. *headdesk*
1538970331

Edited 1538971855
OK, so new issue brought up by the lack of consistency between character attribute lists. I just grabbed a chain devil from the compendium and dropped it in, but the template for that doesn't have an "npc_type" attribute. It does have the "type" attribute that I had originally hoped was standard. Trying to use the macro on a target without the "npc_type" attribute crashes the API as well. Is there an efficient way to search every attribute on the target whos attribute name contains the word "type", without knowing what all the iterations are? In just the few monsters I've looked at, I've seen "npc_type" "npcd_type" "type" "npc_typebase" and several more. If all of those strings were just combined, the same loop function should catch them right? Even if it catches "hit_dice" and things like that, since we're only searching for the target string any extraneous info shouldn't have any affect.
1538973797
GiGs
Pro
Sheet Author
API Scripter
getSpeaker was needed so that when the macro was sent to chat, it would show the calling player as the sender. I'd forgotten that was needed. The type/npc-type/etc problem is a big problem. Why aren't they consistent??? Are these creatures all from the same source? It is possible to filter out all attributes to those with only type in the name. What happens though if there's more than one such attribute, or they identifying attribute doesn't have type in the name? I'll add some error correction to stop it crashing the sandbox. Have you checked out the token action method I suggested? That may be the smoothest way and you dont have to deal with script issues.
1538974508

Edited 1538974885
For the token action part, from what I'm seeing, it just moves all 6 macros to the quick bar instead of clicking them from the sheet. It doesn't do anything as far as having the automation to pick the correct one based on target, does it? I don't know why they are not consistent. Perhaps some come from the purchased modules, and those differ from the standard 5e compendium on R20? Short of asking my GM to create/update a field with the appropriate data on every char/monster sheet in the game, I'm not sure what to do other than cycle through them all. My hope with the attributes is that by concatenating the values for everything that says has "*type*" in the name, it will capture something that has what I need. Luckily, I suppose, I will always be acting as a final check, so if I know something matches but the appropriate macro didn't fire, I can activate them manually. Then I could send the GM a request to fix that specific sheet. It is possible to filter out all attributes to those with only type in the name. What happens though if there's more than one such attribute, or they identifying attribute doesn't have type in the name? Duplicates in the final searched string shouldn't be a problem. As long as it either finds a match or doesn't, the number of hits is irrelevant. If none of the fields that get searched have it, it should still return "base" and carry on as normal. I'm planning to leave the descriptors in the macros (ie Sun Blade (vs Undead)) so I'll hopefully notice the discrepancy of "base" showing up when it should be something else.
1538976063
GiGs
Pro
Sheet Author
API Scripter
Takryn said: For the token action part, from what I'm seeing, it just moves all 6 macros to the quick bar instead of clicking them from the sheet. It doesn't do anything as far as having the automation to pick the correct one based on target, does it? Just to be clear we are talking about the same thing. I'm not talking about the macro bar across the bottom of the screen but the token action bar that appears above the token. Though it doesnt matter too much. The macro bar would work just as well (you can rename the buttons to something short and sweet.) My point is: your concern was that you didnt want to select several dropdowns everytime you launched the attack macro. But if you have the six buttons floating there, and you know which one you need to press, it's very quick to just click one of them. You dont need to do any fancy scripting, you have something that is a single click, easy-peasy.&nbsp; I don't know why they are not consistent. Perhaps some come from the purchased modules, and those differ from the standard 5e compendium on R20? Short of asking my GM to create/update a field with the appropriate data on every char/monster sheet in the game, I'm not sure what to do other than cycle through them all. My hope with the attributes is that by concatenating the values for&nbsp; everything &nbsp;that says has "*type*" in the name, it will capture something that has what I need. Luckily, I suppose, I will always be acting as a final check, so if I know something matches but the appropriate macro didn't fire, I can activate them manually. Then I could send the GM a request to fix that specific sheet. It is possible to filter out all attributes to those with only type in the name. What happens though if there's more than one such attribute, or they identifying attribute doesn't have type in the name? Duplicates in the final searched string shouldn't be a problem. As long as it either finds a match or doesn't, the number of hits is irrelevant. If none of the fields that get searched have it, it should still return "base" and carry on as normal. I'm planning to leave the descriptors in the macros (ie Sun Blade (vs Undead)) so I'll hopefully notice the discrepancy of "base" showing up when it should be something else. My question about consistency was rhetorical, really. But it is strange, it makes me wonder how many other attributes are differently named, and how tricky that makes it for macro writers. I just dont understand why there wasn't a plan when making the characters to have a single scheme for naming the attributes that was set for all supplements. I'll tweak the script when i get a chance, but its bedtime here now.
1538976533

Edited 1538976708
GiGs
Pro
Sheet Author
API Scripter
I made a quick change to the script before bed, and updated it in the post above .&nbsp;&nbsp; If the npc_type attribute isnt found, it whispers you a message telling you this, and uses the base macro as a fallback. I'll put a loop to search for type variants tomorrow. If you could make a list of all the type variants you have found, that would be good too. I can search for those explicitly, and include a way for you to add extras as you find them. I'll also have a general "type" search as a fallback (because it will catch any attributes including type in the name, and they could be a lot of those, especially with repeating attributes, and gm's manually created attributes, so it can't be relied on and could lead to errors).&nbsp;
1539012326
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
G G said: also I'm not sure how you are using this: #sb-!learn @{target|character_id} type but API commands need to start with the ! at the very start of the line. They wont work otherwise. What does #sb- represent in this context, how is it being used? Dredging something up from the past, but this isn't quite correct. If you want the command to not be displayed in chat it needs to start with "!", but the API responds to any chat message, and that's really all that api commands are. Now, of course if you are gating your response based on the formatting or API type chat message that is a different story.
Types found in Character Sheet (shaped 5e), monster directly from R20 5e compendium, and from at least one module (Tomb of Annihilation) Ones that matter (or could have the useful information in them): type type_from_srd npc_typebase npcd_type npc_type Ones that don't matter, but will get caught in the full "type" search anyway (probably not helpful, but included for completeness) hitdie_final npcd_actype npc_actype dtype drop_itemtype drop_damagetype drop_attack_type drop_damagetype2 drop_spellhldietype drop_type caster_type item_type_from_srd damage_type_from_srd
1539030490

Edited 1539030665
GiGs
Pro
Sheet Author
API Scripter
Why is hitdie_final in that second list? Here's a quick script to find all the attributes with type in the name, and their contents. Run this and see if you can identify all the ones that are appropriate to use. It'll search through all characters in your campaign, and list the attribute name, its contents, and the creature that posses the attribute. I'm curious if there are duplicate types on the same creature, and whether matches with (undead, fiend, elf, etc) are possible with types that aren't creature types. on('ready',function(){ &nbsp; &nbsp; 'use strict'; &nbsp; &nbsp; on('chat:message',function(msg){ &nbsp; &nbsp; &nbsp; &nbsp; if('api' === msg.type &amp;&amp; msg.content.match(/^!checktype/)&nbsp; ){&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var npcs = findObjs({ type: 'character', controlledby: '' }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var types = filterObjs(function(obj) {&nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(obj.get("type") === 'attribute' &amp;&amp; obj.get('name').toLowerCase().includes('type') &amp;&amp; !obj.get('name').toLowerCase().includes('repeating') &amp;&amp; obj.get('current') !== "") return true;&nbsp; &nbsp; //NPCNew Test &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; else return false; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; types.forEach(type =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let character = getObj("character", type.get('characterid')).get('name'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat("testing","/w " + msg.who + " " + type.get('name') + ": " + type.get('current') + "; " + character); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }); });
1539048264

Edited 1539052511
All of the useful ones are there, the ones it didn't report are because they are empty. The hitdie_final was a mistake on my part when copy/pasting; "dietype" appears in the attribute value, but not the name. I haven't been able to come up with why a creature that wasn't undead would have something like "undead' in one of it's attributes. I think it's safe to continue with the assumption that if we find a valid creature type, it's a match to the creature description itself. I'll just be especially careful not to have "type" in any attributes I add for things like favored enemy on my sheet, to avoid just such a mismatch. The Goblin and Skeleton are from the module, the Chain Demon from the Compendium, and Arkos is my Shaped sheet. (From testing): (GM) npcd_type: Small humanoid (goblinoid), neutral; Goblin (From testing): (GM) npcd_actype: (leather armor, shield); Goblin (From testing): (GM) npc_type: Small humanoid (goblinoid), neutral evil; Goblin (From testing): (GM) npc_actype: leather armor, shield; Goblin (From testing): (GM) dtype: full; Goblin (From testing): (GM) type: humanoid (goblinoid); Goblin (From testing): (GM) npcd_type: Medium undead, lawful evil; Skeleton (From testing): (GM) npcd_actype: (armor scraps); Skeleton (From testing): (GM) npc_type: Medium undead, lawful evil; Skeleton (From testing): (GM) npc_actype: armor scraps; Skeleton (From testing): (GM) dtype: full; Skeleton (From testing): (GM) type: undead; Skeleton (From testing): (GM) type: fiend (devil); Chain Devil (From testing): (GM) caster_type: half; Arkos (From testing): (GM) type: human; Arkos I'm checking with my DM to verify that I populated the "type" field and that it was blank as original. OK, confirmed that the inconsistency is correct. Some sheets have "type" some have "npc_type" or one of the others. The filled variant is only in the sandbox game I'm testing in.
1539054500
GiGs
Pro
Sheet Author
API Scripter
It looks like the goblin has three attributes with goblinoid in them, and the skeleton has three with undead in them. Is this true (and if so, any idea why this is)?
1539055173

Edited 1539055401
It is true, and I can only speculate as to why; however, in the main game, the "type" attribute is blank, so it's really only two that are readily filled. Same for skeleton. My best guess is the npc_type and npcd_type must be from different import macros, or perhaps just the sheet used for the monsters. It definitely strikes me as odd that the most basic of fields aren't consistent between modules/compendiums. I don't see it being an issue though, assuming the script will load up types into an array the same as your last snippet and then search each item in the array to see if it matches, then break out when it finds one. Trying to add in a catch for hitting &lt;favored enemy 1&gt; AND &lt;favored enemy 2&gt; instead of just checking for an OR would add a level of complexity that is unnecessary for this. I greatly appreciate your help, and don't want to send you down any tangential rabbit holes that I don't see an immediate use case for.
1539055625
GiGs
Pro
Sheet Author
API Scripter
Here's a new version of the script for you to try (it includes the check_types script too, so you'll want to delete that). This doesnt cycle through every type attribute, only those that have been identified as containing appropriate type data. There's a line near the top: &nbsp; &nbsp; let npc_types = [ 'type','npc_type', 'npcd_type', 'npc_typebase','type_from_srd']; // edit this line if you find more types You can add extra types there if you find any that contain creature data. The script will look at them in the order listed, and stop when it finds the first that a) exists, and b) isn't empty. If it finds none, it will use the base macro. Here goes: /* Launch with attack macro like !chooseam @{target|character_id} attackbase types|to|check&nbsp; e.g. !chooseam @{target|character_id} ?{Attack Type|Standard,sb|With PW,sbpw} fiend|undead|goblin */ on('ready',function(){ &nbsp; &nbsp; 'use strict'; &nbsp; &nbsp; let npc_types = [ 'type','npc_type', 'npcd_type', 'npc_typebase','type_from_srd']; // edit this line if you find more types &nbsp; &nbsp; let getNPCType = function(target) { &nbsp; &nbsp; &nbsp; &nbsp; for (let i = 0; i &lt; npc_types.length; i++) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let npc_Type = getAttrByName(target, npc_types[i]); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(npc_Type !== undefined &amp;&amp; npc_Type !== '' ) { //&amp;&amp; getAttrByName(target,npc_Type) !== '' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return npc_Type; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; let getSpeaker = function(msg) { &nbsp; &nbsp; &nbsp; &nbsp; var characters = findObjs({_type: 'character'}); &nbsp; &nbsp; &nbsp; &nbsp; var speaking; &nbsp; &nbsp; &nbsp; &nbsp; characters.forEach(function(chr) { if(chr.get('name') == msg.who) speaking = chr; }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(speaking) return 'character|'+speaking.id; &nbsp; &nbsp; &nbsp; &nbsp; else return'player|'+msg.playerid; &nbsp; &nbsp; }; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; let getMacro = function(msg,name) { &nbsp; &nbsp; let m = findObjs({ &nbsp; &nbsp; &nbsp; &nbsp; _playerid: msg.playerid, &nbsp; &nbsp; &nbsp; &nbsp; name: name &nbsp; &nbsp; }); &nbsp; &nbsp; if(m[0] === undefined) { &nbsp; &nbsp; &nbsp; &nbsp; sendChat("Choose Macro","/w " + msg.who + " Error: Macro Not Found"); &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; return m[0].get('action'); &nbsp; &nbsp;&nbsp; } &nbsp; &nbsp; on('chat:message',function(msg){ &nbsp; &nbsp; &nbsp; &nbsp; if('api' === msg.type &amp;&amp; msg.content.match(/^!chooseam/)&nbsp; ){&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let args = msg.content.split(/\s+/); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (args.length &lt; 3) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat("Choose Macro","/w " + msg.who + " Input error: Not enough parameters."); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let target = args[1]; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let macrobase = args[2]; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let enemytypes = args[3].split('|'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let tartype = getNPCType(target); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let foundtype = 'base'; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(tartype === undefined) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat("Choose Macro","/w " + msg.who + " Error: NPC Type not found, proceeding with base macro"); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for (let i = 0; i &lt; enemytypes.length; i++) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(tartype.toLowerCase().includes(enemytypes[i].toLowerCase())) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; foundtype = enemytypes[i].toLowerCase(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let macro = getMacro(msg, `${macrobase}-${foundtype}`); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat(getSpeaker(msg),macro) ; &nbsp; &nbsp; &nbsp; &nbsp; } else if('api' === msg.type &amp;&amp; msg.content.match(/^!checktype/)&nbsp; ){&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var npcs = findObjs({ type: 'character', controlledby: '' }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let types = filterObjs(function(obj) {&nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(obj.get("type") === 'attribute' &amp;&amp; obj.get('name').toLowerCase().includes('type') &amp;&amp; !obj.get('name').toLowerCase().includes('repeating') &amp;&amp; obj.get('current') !== "" ) return true;&nbsp; &nbsp; //NPCNew Test &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; else return false; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; types.forEach(type =&gt; { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; let character = getObj("character", type.get('characterid')).get('name'); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sendChat("testing","/w " + msg.who + " " + type.get('name') + ": " + type.get('current') + "; " + character); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; }); });
1539055745
GiGs
Pro
Sheet Author
API Scripter
Takryn said: Trying to add in a catch for hitting &lt;favored enemy 1&gt; AND &lt;favored enemy 2&gt; instead of just checking for an OR would add a level of complexity that is unnecessary for this. I greatly appreciate your help, and don't want to send you down any tangential rabbit holes that I don't see an immediate use case for. Can a creature ever be both, and do you get any advantage if they are?
1539056405

Edited 1539056529
As far as I know, there is no overlapping of type (at least in 5e). For instance if a goblin dies and is reanimated, I'd expect it to be reclassified simply as undead, not undead goblin. Unless I come across a reason to need it, what you've created should work like a charm. I'll begin playing with it here shortly. There could certainly be a bonus for another aspect of the npc_type line, but that would be another call with a different search word. As in looking for "lawful" or "evil" or some such. Again, the lack of consistency poses input problems, but that would be a reason to not break out when the first match is found.
1539060410
GiGs
Pro
Sheet Author
API Scripter
There might be another attribute that holds alignment data. I notice the chain devil type doesnt list alignment.
1539061724

Edited 1539062126
Right, the alignment and size are included in "npc_type" as far as I've seen. The API is reporting&nbsp;"Error: When using sendChat() you must specify a speakingas and input property." when I push the case for not having the macro created on my character. The API also crashes when not having sufficient inputs, ie if&nbsp; I leave off the search types. The error catcher is there though, so I've got no idea. !chooseam @{target|character_id} ?{Attack Type|Base,sb|PW,sbpw} TypeError: Cannot read property 'split' of undefined I have no expectation for you to keep tweaking the script, I'm just letting you know what I've come across. As it stands it'll do everything I need it to. Thank you a ton for setting this up for me. Once I get a play session under my belt using it, I plan to write up a description of the macro system to go with the script then post it as a reference for others to use.
1539062807

Edited 1539064925
Ah, I do have a follow up question: If I were to create abilities on my character sheet rather than the global macros (so that they go with my character from game to game rather than having to recreate them each time), what would need to change in the script call? How is&nbsp; &nbsp; let macro = getMacro(msg, `${macrobase}-${foundtype}`); let getMacro = function(msg,name) { &nbsp; &nbsp; let m = findObjs({ &nbsp; &nbsp; &nbsp; &nbsp; _playerid: msg.playerid, &nbsp; &nbsp; &nbsp; &nbsp; name: name &nbsp; &nbsp; }); &nbsp; &nbsp; if(m[0] === undefined) { &nbsp; &nbsp; &nbsp; &nbsp; sendChat("Choose Macro","/w " + msg.who + " Error: Macro Not Found"); &nbsp; &nbsp; &nbsp; &nbsp; return; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; return m[0].get('action'); &nbsp; &nbsp;&nbsp; } converting it to have the # in front? I never see any reference to the # so can't change it to %{player|ability-name} format.
1539068127
GiGs
Pro
Sheet Author
API Scripter
Takryn said: Right, the alignment and size are included in "npc_type" as far as I've seen. The API is reporting&nbsp;"Error: When using sendChat() you must specify a speakingas and input property." when I push the case for not having the macro created on my character. The API also crashes when not having sufficient inputs, ie if&nbsp; I leave off the search types. The error catcher is there though, so I've got no idea. !chooseam @{target|character_id} ?{Attack Type|Base,sb|PW,sbpw} TypeError: Cannot read property 'split' of undefined I have no expectation for you to keep tweaking the script, I'm just letting you know what I've come across. As it stands it'll do everything I need it to. Thank you a ton for setting this up for me. Once I get a play session under my belt using it, I plan to write up a description of the macro system to go with the script then post it as a reference for others to use. The first API isn't anything to worry about. The second one is a bit weird, I'll investigate later. For now you should be able to avoid it by adding an extra argument that will never be matched, like "none".&nbsp; !chooseam @{target|character_id} ?{Attack Type|Base,sb|PW,sbpw} none It is possible to change it to use abilities, but it'll need a tweak of the getmacro function. I just realised I left off an important element:&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;let m = findObjs({ &nbsp; &nbsp; &nbsp; &nbsp; _playerid: msg.playerid, _type: 'macro', &nbsp; &nbsp; &nbsp; &nbsp; name: name &nbsp; &nbsp; }); You should be able to find abilities the same way, but will need to change the _type to 'ability' and add a character.id, too. Too late for me to work on this tonight, I'll look at it tomorrow. You don't use the # in this script. The way the script works is to find the macro, extract the text within the macro, and print it directly to chat.&nbsp; Abilities will work the same way. Honestly though, I'm thinking of rewriting the script to rebuild the attack text. I had a look at those 6 attacks you supplied a few posts back, and the bulk of them is identical, so it would be more elegant to use one of them as base, and just add in the extra bits you need for the different versions. It can include attribute calls for relevant stats, so it wont get out of date when your stats change. I'll have a think about that in the next couple of days.
1539111888

Edited 1539123027
I'll preface this post by saying I understand this could have been approached differently. My primary drivers have been to automate as much of the process as possible that isn't based on my active choices, and to create "pretty" output to the chat, where all of the damage is nicely combined by damage type and no additional calculations need be performed. Having seen them, I am planning to use a combination of the token buttons, rather than queries, and the script for automation. I would rather have my inputs be to click one button for the weapon, and then to select my target and have the rest done automatically. That leaves me with 4 buttons per weapon (Base, Planar Warrior, Radiant Soul, PW+RS).&nbsp; I do like the idea of building the macros automatically as well since there are 12 macros per weapon. It would only be 8 for any non-SunBlade weapon, but due to the way we return the target type, each possible option from the search entries must have its own macro. The relevant stat changes I'm definitely planning to implement. I'm not sure why the shapedSheet call uses a snapshot of the ability mod instead of calling the ability_mod attribute itself. Probably some internal efficiency gains since the sheet macro is being generated by the sheet every time it's clicked, so maybe it will always snapshot it from the current state anyway. Overall, I know I've spent far more time working on this than I'll ever save. Nonetheless, I've learned a ton and the play during gaming sessions will be smoother for me (and my cohorts once I roll this out to them). In addition, anyone else who decides to go this route will be positive from the start.&nbsp; Weapon Planar Warrior Radiant Soul Undead Fiend Macro Name Macro # Sun Blade - - - - SB-base 1 Sun Blade Yes - - - SBpw-base 2 Sun Blade - Yes - - SBrs-base 3 Sun Blade Yes Yes - - SBpwrs-base 4 Sun Blade - - Yes - SB-undead 5 Sun Blade Yes - Yes - SBpw-undead 6 Sun Blade - Yes Yes - SBrs-undead 7 Sun Blade Yes Yes Yes - SBpwrs-undead 8 Sun Blade - - - Yes SB-fiend 9 Sun Blade Yes - - Yes SBpw-fiend 10 Sun Blade - Yes - Yes SBrs-fiend 11 Sun Blade Yes Yes - Yes SBpwrs-fiend 12 Long Bow - - - - LB-base 13 Long Bow Yes - - - LBpw-base 14 Long Bow - Yes - - LBrs-base 15 Long Bow Yes Yes - - LBpwrs-base 16 Long Bow - - Yes - LB-undead 17 Long Bow Yes - Yes - LBpw-undead 18 Long Bow - Yes Yes - LBrs-undead 19 Long Bow Yes Yes Yes - LBpwrs-undead 20 Long Bow - - - Yes LB-fiend 21 = LB-undead Long Bow Yes - - Yes LBpw-fiend 22 = LBpw-undead Long Bow - Yes - Yes LBrs-fiend 23 = LBrs-undead Long Bow Yes Yes - Yes LBpwrs-fiend 24 = LBpwrs-undead Short Sword - - - - SS-base 25 Short Sword Yes - - - SSpw-base 26 Short Sword - Yes - - SSrs-base 27 Short Sword Yes Yes - - SSpwrs-base 28 Short Sword - - Yes - SS-undead 29 Short Sword Yes - Yes - SSpw-undead 30 Short Sword - Yes Yes - SSrs-undead 31 Short Sword Yes Yes Yes - SSpwrs-undead 32 Short Sword - - - Yes SS-fiend 33 = SS-undead Short Sword Yes - - Yes SSpw-fiend 34 = SSpw-undead Short Sword - Yes - Yes SSrs-fiend 35 = SSrs-undead Short Sword Yes Yes - Yes Spwrs-fiend 36 = SSpwrs-undead
1539127022

Edited 1539135618
GiGs
Pro
Sheet Author
API Scripter
I just updated the origin script to handle abilities. See this post . I also fixed that issue when favoured enemies are missing. When using abilities, you need to supply the id of the character who has the abilities, which you can do with @{WHATEVER CHARACTER NAME IS|character_id}, like so: !chooseaa @{Thundarr|character_id} @{target|character_id} ?{Attack Type|Standard,sb|With PW,sbpw} fiend|undead Note that you launch it with choosea a , not chooseam, to distinguish the two versions. I'll have a look over your last post as soon as I get a chance.