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 .
×
Advertisement Create a free account Compendium in Game, Join Today

[Script, Call For Testers] Universal Chat Menus

1558842820
GiGs
Pro
Sheet Author
API Scripter
Minor Update: --footer now works as Keith;s freetext suggestion. You can put any text in there, and it will be posted after the rolltemplate.
GiGs said: Minor Update: --footer now works as Keith;s freetext suggestion. You can put any text in there, and it will be posted after the rolltemplate. Noticed a small issue since this update: I added a footer to a macro then removed it, but a blank 'footer' is still posted after the chat menu. Also noticed the footer isn't whispered like the menu is.
1558850153
GiGs
Pro
Sheet Author
API Scripter
I'm genuinely confused why the extra line would be appearing after you have removed the footer call. Can you post the macro text you are using? Good point about it not being whispered. I need to add that. By default it just posts in local chat. You can add /w (player name) to the start of the text there, but that's not universal, so I need to change that.
Here's the macro, but I think it's the script because all chat menus now have the blank footer post directly after. !chatmenu @{selected|character_id} {template:pc}{{charname=@{selected|character_name}}} {{type=ability}} {{smallname=Traits}} {{showchar=[1]}} {{descflag=1}} {{desc=CHATMENU}} --title:XP—@{selected|xp}/@{selected|xp|max} --Senses|Languages --title:Money—@{selected|money_cp} cp, @{selected|money_sp} sp, @{selected|money_gp} gp, @{selected|money_pp} pp --Notes,background= --separator: |
This is in the test game I set up that you have access to, btw, in case you want to see exactly what all is at work there. Also added some npcs in case you want to test with those.
1558851746

Edited 1558852054
GiGs
Pro
Sheet Author
API Scripter
Oh you're right. I was a bit sloppy with the footer revision. I can fix that quickly.
1558852031
GiGs
Pro
Sheet Author
API Scripter
That's fixed, thanks for finding that! I updated it in the test game too.
Great, thanks GiGs!
1558888106
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
GiGs said: keithcurtis said: Hmm. I'm also seeing that I did not realize that the OGL sheet has an acrobatics roll, and an NPC acrobatics roll. Asking a PC to roll NPC acrobatics doesn't take proficiency into account, but it doesn't throw up an error, either. I'll either need to split these into two macros (not difficult), or wait to see what you come up with for filtering. I'll edit my posted code accordingly. Awesome job! Edit: Situation also applies to Saves. I don't think that having two statblock reporters is that huge an obstacle, though. I can make the PC version a universal token macro for my players, and reserve the NPC one for DM use. I'll have to look at the D&D5e sheet and how it does this. What sort of filtering would be needed to make this work properly? The top level filter would be the "npc" attribute, which has a value of 1 for NPCs and a value of 9 for PCs.
1558888311
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
GiGs said: I'm glad you like it Keith. The default template does take up so much space. I will probably add my own more compact version of it as a third formatting option, for sheets without rolltemplates of their own. That would be a blessing for no reason other than allowing the players of a game to avoid the big pink buttons without resorting to the GM sending everyone a Stylus definition.
Feature request For repeating sections, could a fifth parameter be added which works the same as the fourth parameter, but at lower priority? Example: For items, my chat menu splits them up by whether they are Consumable, Magic, or Mundane. Section for consumables would look like this --title:consumables --repeating_gear|name|-rollShowGear|slot|quantity where the fourth parameter is checked first, printing any items with a non-zero number in the 'slot' field as Consumable. The fifth parameter would then check if the 'quantity' field of each item is greater than zero, and print only those which meet that condition.
1559052976

Edited 1559109744
GiGs
Pro
Sheet Author
API Scripter
Ask and ye shall receive :) I have a version of the script that allows more complex filtering. You can add any number of filtering options, just add them to the end of the item's parameters. For example, in a repeating section: --repeating_gear|name|-rollShowGear|slot|quantity (Use commas for non-repeating items.) You can also check for specific values, using =, >, <. or ! (for not equal to. So --repeating_gear|name|-rollShowGear|slot|quantity>5 or --repeating_gear|name|-rollShowGear|type=magic|quantity<4 I havent thoroughly tested it, so I'm not adding it to the first page just yet. The code is below: (PS: Keith, I think the attribute name for prepared spells in 5e sheet is spellprepared , not prepared . I've changed the way names are checked, and just using prepared wont work anymore. ) Edit: updated OP, remoevd script from here.
1559062235
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Nice. I have now separated out ranged attacks from melee. I wanted to report on expendable resources, but the way the sheet reports them is frankly... not optimal. (Four different possible attribute names, depending on order added)
1559073548
When i use the universal chat menus together with the CLR script to trigger Roll20AM i get my sounds effects twice - both when the chat shows f.ex shortbow as a skill and when the roll happens - any tips on how i can disregard the menu chat?
1559097323
GiGs
Pro
Sheet Author
API Scripter
That sounds really clunky, Keith, I wonder why they are done that way. Torben, that sounds like an issue with how you have CLR set up, I'm guessing. Does it respond to all chat, or is there something in my script's output that is triggering it? I haven't used it, but I guess you are using it to detect certain words in chat? If you can share your setup, I may be able to spot something.
1559098245
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
GiGs said: That sounds really clunky, Keith, I wonder why they are done that way. My suspicion is that at least the first one (class_resource) is specific to charactermancer use. I can't remember what the second one is called offhand, but then they become _left and _right (2 columns). I didn't delve too deeply, because it looked like a rabbit hole.
Thanks for that update, GiGs! Seems like it will only work if the field contains exactly the filter word, rather than seeing if the filter word is somewhere in that field. Don't suppose there's a way around that? Example: --repeating_gear|name|-rollShowGear|special=aura|quantity where items are printed even if the "special" field says "aura of faint necromancy" or "aura of moderate enchantment"
1559101263
GiGs
Pro
Sheet Author
API Scripter
good point, i can fix that but people will have to be careful about unwanted matches.
Understood. Could another character like ~ or something could be used to indicate "field contains" rather than "field matches"?
1559101830
GiGs
Pro
Sheet Author
API Scripter
yes, I was thinking a ? but I like your ~ suggestion.
1559111696
GiGs
Pro
Sheet Author
API Scripter
I've updated the script in the second post, and instructions in the first. I havent used the ~ filter, I've changed the = to incorporate it. I'll repeat the rules here: The rules are: = (an equals sign):  tests if the attribute being tested is equal to the given value. If its a text field, it accepts partial matches (so if you supply a test of "=comb" it will match "combat"). ! (exclamation mark, not equal to):  the reverse of the above. For numbers, it tests if the value is different. ( !0  is not equal to zero, handy). Go text, it checks if the test is NOT included in the attribute value.  spellname!detect  will exclude any spells with detect in their name, for instance. > (at least equal) : checks if the attribute's value  is at least equal to  the given match value. Only for numbers. So >0 will include all numbers 0 or greater. >10 includes all numbers 10 or greater. < (less than):  Less than. <10 will only show values 9 and below. No Symbol:  if you just include an attribute to test, but enter no comparison, it is treated as  !0 . You are looking for any attribute value that exists and is not equal to 0 or "". This is handy for showing prepared spells, active powers or gear, and the like. Notice the behaviour of > and < is slightly different. > includes the number supplied, and < doesnt. I think this is the most likely needed behaviour, let me know if this is wrong.  Keith, I've also changed the non-roll button trigger from an = to a !, so those non-rolls that were named button= should now be button! Hope this isnt too tiresome!
1559113082
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
No worries. This is still early stages. It's better to do big revisions now.
1559135527
GiGs you are probably right.. This is my CRL - listener:  !crl \\create,name=Bow1,text=Shortbow \\sound of arrows... And this is the chat outcome: I cant find a way to get CRL to only include shortbow as a trigger if there also was a roll. /Torben
1559155760

Edited 1559156311
GiGs said: = (an equals sign):  tests if the attribute being tested is equal to the given value. If its a text field, it accepts partial matches (so if you supply a test of "=comb" it will match "combat"). I'm noticing a minor issue where items with no value in the field are being printed when using this filter. Any idea why this could be? For example, this --repeating_gear|name|-rollShowGear|special=aura|quantity prints all items with the word aura in the special field with a quantity greater than 0, but also prints all items with no value in the special field. (It won't, however, print items that have a value that doesn't include aura , like consumable , in the special field.) In the meantime, I can work around this by entering none in any special fields that would normally be empty.
1559159917

Edited 1559159936
Scott C.
Forum Champion
Sheet Author
API Scripter
Torben, I really need to update CRL at some point, but, you can add additional sections of text to look for like this (this should look for a roll and the text shortbow): !crl \\create,name=Bow1,text=Shortbow&&## \\sound of arrows... If you want more in depth help on CRL, let's address them in a separate thread so as not to take this off topic.
1559163244
Thx Scott, didnt may to highjack the thread :)
1559231746

Edited 1559233242
GiGs
Pro
Sheet Author
API Scripter
Seph said: GiGs said: = (an equals sign):  tests if the attribute being tested is equal to the given value. If its a text field, it accepts partial matches (so if you supply a test of "=comb" it will match "combat"). I'm noticing a minor issue where items with no value in the field are being printed when using this filter. Any idea why this could be? For example, this --repeating_gear|name|-rollShowGear|special=aura|quantity prints all items with the word aura in the special field with a quantity greater than 0, but also prints all items with no value in the special field. (It won't, however, print items that have a value that doesn't include aura , like consumable , in the special field.) In the meantime, I can work around this by entering none in any special fields that would normally be empty. To be sure I'm understanding you correctly, special is printing items with no value (empty cells), when it's looking for the word aura in that cell? Edit:  aha, i see why that's happening. I'll fix it tonight.
1559308286
First of all, thanks for this script! Adding menus for use with the official pathfinder sheet is exactly what my group needs. I'm running into an issue that I'm not sure how to address. I'm pretty sure there has to be a way but so far I haven't been able to figure it out. I have set up the macro to list spells for all levels. The issue is that if a character doesn't have say level 2 or higher spells then I get a bunch of errors for no attribute found. Is there anyway to get it to skip spell levels which aren't present? Macro !chatmenu @{selected|character_id} {template:pc}{{charname=@{selected|character_name}}} {{type=ability}} {{smallname=All Menues}} {{showchar=[1]}} {{descflag=1}} {{desc=CHATMENU}} Action Menu --title:Attacks --repeating_attacks|atkname|fullattack --title:Abilities --repeating_spell-like|spelldisplay|spell --title:Cantrips --repeating_spell-0|spelldisplay|spell|spellprepared --title:Level 1 *(@{selected|caster1_spells_prepared_level_1}/@{selected|caster1_spells_total_level_1})* --repeating_spell-1|spelldisplay|spell|spellprepared --title:Level 2 *(@{selected|caster1_spells_prepared_level_2}/@{selected|caster1_spells_total_level_2})* --repeating_spell-2|spelldisplay|spell|spellprepared --title:Level 3 *(@{selected|caster1_spells_prepared_level_3}/@{selected|caster1_spells_total_level_3})* --repeating_spell-3|spelldisplay|spell|spellprepared --title:Level 4 *(@{selected|caster1_spells_prepared_level_4}/@{selected|caster1_spells_total_level_4})* --repeating_spell-4|spelldisplay|spell|spellprepared --title:Level 5 *(@{selected|caster1_spells_prepared_level_5}/@{selected|caster1_spells_total_level_5})* --repeating_spell-5|spelldisplay|spell|spellprepared --title:Level 6 *(@{selected|caster1_spells_prepared_level_6}/@{selected|caster1_spells_total_level_6})* --repeating_spell-6|spelldisplay|spell|spellprepared --title:Level 7 *(@{selected|caster1_spells_prepared_level_7}/@{selected|caster1_spells_total_level_7})* --repeating_spell-7|spelldisplay|spell|spellprepared --title:Level 8 *(@{selected|caster1_spells_prepared_level_8}/@{selected|caster1_spells_total_level_8})* --repeating_spell-8|spelldisplay|spell|spellprepared --title:Level 9 *(@{selected|caster1_spells_prepared_level_9}/@{selected|caster1_spells_total_level_9})* --repeating_spell-9|spelldisplay|spell|spellprepared --title:Skills --Appraise,appraise|Bluff,Bluff|Climb,climb|Craft,craft|Diplomacy,diplomacy --separator: |  Error Thanks, Meldogs
1559311256
GiGs
Pro
Sheet Author
API Scripter
It looks like the problem is this bit:  @{selected|caster1_spells_prepared_level_1}/@{selected|caster1_spells_total_level_1}) I think that error is caused by roll20 trying to get those attributes and finding they dont exist.  Thats not the script, that's roll20 parsing attribute values before it sends them to the script. Is there any way to to create those attributes and make sure they are zero? Although not technically part of menu creation, your question does have me thinking about adding a feature to just read attributes in the --title parameter, to avoid this problem. It does seem like a useful feature. It'll have to wait till after the weekend, I'm a bit busy right now.
Meldogs said: First of all, thanks for this script! Adding menus for use with the official pathfinder sheet is exactly what my group needs. I'm running into an issue that I'm not sure how to address. I'm pretty sure there has to be a way but so far I haven't been able to figure it out. I have set up the macro to list spells for all levels. The issue is that if a character doesn't have say level 2 or higher spells then I get a bunch of errors for no attribute found. Is there anyway to get it to skip spell levels which aren't present? Macro !chatmenu @{selected|character_id} {template:pc}{{charname=@{selected|character_name}}} {{type=ability}} {{smallname=All Menues}} {{showchar=[1]}} {{descflag=1}} {{desc=CHATMENU}} Action Menu --title:Attacks --repeating_attacks|atkname|fullattack --title:Abilities --repeating_spell-like|spelldisplay|spell --title:Cantrips --repeating_spell-0|spelldisplay|spell|spellprepared --title:Level 1 *(@{selected|caster1_spells_prepared_level_1}/@{selected|caster1_spells_total_level_1})* --repeating_spell-1|spelldisplay|spell|spellprepared --title:Level 2 *(@{selected|caster1_spells_prepared_level_2}/@{selected|caster1_spells_total_level_2})* --repeating_spell-2|spelldisplay|spell|spellprepared --title:Level 3 *(@{selected|caster1_spells_prepared_level_3}/@{selected|caster1_spells_total_level_3})* --repeating_spell-3|spelldisplay|spell|spellprepared --title:Level 4 *(@{selected|caster1_spells_prepared_level_4}/@{selected|caster1_spells_total_level_4})* --repeating_spell-4|spelldisplay|spell|spellprepared --title:Level 5 *(@{selected|caster1_spells_prepared_level_5}/@{selected|caster1_spells_total_level_5})* --repeating_spell-5|spelldisplay|spell|spellprepared --title:Level 6 *(@{selected|caster1_spells_prepared_level_6}/@{selected|caster1_spells_total_level_6})* --repeating_spell-6|spelldisplay|spell|spellprepared --title:Level 7 *(@{selected|caster1_spells_prepared_level_7}/@{selected|caster1_spells_total_level_7})* --repeating_spell-7|spelldisplay|spell|spellprepared --title:Level 8 *(@{selected|caster1_spells_prepared_level_8}/@{selected|caster1_spells_total_level_8})* --repeating_spell-8|spelldisplay|spell|spellprepared --title:Level 9 *(@{selected|caster1_spells_prepared_level_9}/@{selected|caster1_spells_total_level_9})* --repeating_spell-9|spelldisplay|spell|spellprepared --title:Skills --Appraise,appraise|Bluff,Bluff|Climb,climb|Craft,craft|Diplomacy,diplomacy --separator: |  Error Thanks, Meldogs I think the sheet doesn't create those attributes until those spell levels are made visible on the sheet. Try adding a value in every per day cell for all spell levels like so then test it out. If you don't get the error message anymore, try removing all the per day values besides the ones accurate for the character's level, and test it again. I've done that with most of the characters in my game for other reasons, so I haven't seen that error message yet myself. Hope it helps!
1559322870
Thanks GiGs and Seph for your time. I appreciate you looking at my issue. You were both right.  The problem is that the sheet doesn't have those attributes until you actually put a spell of that level in your list and then click the prepared box. You can then remove the spell and the error disappears because the attributes now exist on the sheet. It isn't totally ideal but I don't know what you could do differently since the sheet appears to create those attributes as needed. GiGs your script is awesome. I think this is really going to make the transition to the official pathfinder sheet for my group possible. They were spoiled by the community sheet's menus but with this I should be able to create something very similar for them.  - Meldogs
1559323019
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
You can probably create and blank those attributes automatically with chatsetattr, but depending on how they are implemented, you might need to wait until they have fixed the problem with the API not triggering sheetworkers.
1559323263
Interesting idea Keith. Also, thank you for the idea of how to setup the spell section. I forgot to mention that I might have borrowed that from the example that you posted. - Meldogs
1559324130
GiGs
Pro
Sheet Author
API Scripter
Meldogs said: Thanks GiGs and Seph for your time. I appreciate you looking at my issue. You were both right.  The problem is that the sheet doesn't have those attributes until you actually put a spell of that level in your list and then click the prepared box. You can then remove the spell and the error disappears because the attributes now exist on the sheet. It isn't totally ideal but I don't know what you could do differently since the sheet appears to create those attributes as needed. GiGs your script is awesome. I think this is really going to make the transition to the official pathfinder sheet for my group possible. They were spoiled by the community sheet's menus but with this I should be able to create something very similar for them.  - Meldogs Thanks!  The script already looks for some attributes, and doesnt display them if they dont exist. So I was thinking of including a syntax that let you create a placeholder for an attribute(say, instead of using the @{name|attributename} syntax which forces roll20 to look for it, you could include something like {UCM|attributename|defaultvalue} - no @ symbol, UCM in place of character name). Roll20 wouldnt attempt to parse it, and the script would then look for the attribute and replace that placeholder with its value if it exists, and defaultvalue if it doesnt. I'll fiddle around with that after the weekend, and see if how it goes.
1559324831

Edited 1559399113
Btw, GiGs, I just noticed the ! filter doesn't seem to be working either; it currently doesn't print anything. Think it could have something to do with the issue with the = filter? EDIT: I did some more testing with the filters, and I've mostly gotten the desired results but not sure they're working as intended. My original attack macro worked as intended before one of the recent updates --title:Melee --repeating_attacks|atkname|fullattack|atktype=melee --title:Ranged --repeating_attacks|atkname|fullattack|atktype=ranged --title:Special --repeating_attacks|atkname|fullattack|atktype!melee|atktype!ranged All melee items printed under Melee, ranged under Ranged, and everything that was neither printed under Special. Now this same macro prints melee items under Melee and ranged under Ranged as intended, but all items with no attack value are printed twice—once under Melee, once under Ranged—while nothing is printed under Special. I've managed to get the desired results by changing the macro to the following, mainly through guesswork --title:Melee --repeating_attacks|atkname|fullattack|atktype=melee|atktype!- --title:Ranged --repeating_attacks|atkname|fullattack|atktype=ranged|atktype!- --title:Special --repeating_attacks|atkname|fullattack|atktype=bab|atktype!- --repeating_attacks|atkname|fullattack|atktype=fob|atktype!- --repeating_attacks|atkname|fullattack|atktype=- --repeating_attacks|atkname|fullattack|atktype=cmb|atktype!- No rush on this, obviously; I know you're busy!
1559573540
Is there anyway to make parts "unclickable" other than setting each as a title? I hope that makes sense. This is the macro I'm using. The Saves are working great but the Defense Statistics can be clicked and produce an error. !chatmenu @{selected|character_id} {template:pc}{{charname=@{selected|character_name}}} {{type=}} {{smallname=Defenses}} {{showchar=[1]}} {{descflag=1}} {{desc=CHATMENU}} {{shownotes=[1]}} {{notes=**- Fort:**@{selected|fortitude_notes} |**Ref:**@{selected|reflex_notes} |**Will:**@{selected|will_notes} |**AC:**@{selected|ac_notes} |**CMD:**@{selected|cmd_notes} |**SR:**@{selected|sr_notes}}} --separator: | --title:Saves --Fort(@{selected|fortitude}),fortitude --Ref(@{selected|reflex}),reflex --Will(@{selected|will}),will --title:Defense Statistics --AC @{selected|ac} |Touch @{selected|ac_touch} |Flat-Footed @{selected|ac_flatfooted} |CMD @{selected|cmd_mod} |SR @{selected|sr} This is what it is currently producing. Thanks, Meldogs
1559575741
GiGs
Pro
Sheet Author
API Scripter
Your earlier question made me think about handling situations like this more elegantly, and I hope to get some work done on this script tonight (first priority the bugs mentioned earlier).  But looking over this specific macro, I'm wondering, do you need to use a chat menu script for that one? There's no dynamically generated menus, there, you could just use standard macro text in the desc section.  The only buttons you need are for the 3 saving throws, and you can construct them like [Ref(@{selected|reflex})](~selected|reflex) or  [ Ref(@{selected|reflex}) ](!
%{selected|reflex}) (I think thats the right code, from memory.) I'm surprised to see the script being used for chat menus that have so little dynamically created content. I didnt anticipate that and need to think about handling layouts that mix dynamic and non-dynamic content better. But Scott's Menu Maker does this already, so you might want to check that out and see if its a better fit for your needs. 
1559575984
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
GiGs said: Keith, I've also changed the non-roll button trigger from an = to a !, so those non-rolls that were named button= should now be button! Hope this isnt too tiresome! I just got around to updating. button! gives a "no ability was found %{Goblin|repeating_npctrait_desc!}". The equals sign still works as it used to.
Meldogs, have you tried using --footer ? It prints the text as a whispered chat message following the menu
1559576452
GiGs
Pro
Sheet Author
API Scripter
keithcurtis said: GiGs said: Keith, I've also changed the non-roll button trigger from an = to a !, so those non-rolls that were named button= should now be button! Hope this isnt too tiresome! I just got around to updating. button! gives a "no ability was found %{Goblin|repeating_npctrait_desc!}". The equals sign still works as it used to. Thanks! I'll check that.
1559576672
GiGs said: Your earlier question made me think about handling situations like this more elegantly, and I hope to get some work done on this script tonight (first priority the bugs mentioned earlier).  But looking over this specific macro, I'm wondering, do you need to use a chat menu script for that one? There's no dynamically generated menus, there, you could just use standard macro text in the desc section.  The only buttons you need are for the 3 saving throws, and you can construct them like [Ref(@{selected|reflex})](~selected|reflex) or  [ Ref(@{selected|reflex}) ](!
%{selected|reflex}) (I think thats the right code, from memory.) I'm surprised to see the script being used for chat menus that have so little dynamically created content. I didnt anticipate that and need to think about handling layouts that mix dynamic and non-dynamic content better. But Scott's Menu Maker does this already, so you might want to check that out and see if its a better fit for your needs.  Thanks for the suggestion. I think I will try using the desc section. I'm not super good at putting this kind of thing together so I appreciate your time and patience. - Meldogs
1559576866
Seph said: Meldogs, have you tried using --footer ? It prints the text as a whispered chat message following the menu I have used the footer in another macro. I'll look into that too. Thanks Seph
1559576969
GiGs
Pro
Sheet Author
API Scripter
It occurred to me after posting, you could move the defence statistics into the desc section, and use chatmenu for the saves. Then you can format those defence stats however you want them. Something like (untested) !chatmenu @{selected|character_id} {template:pc}{{charname=@{selected|character_name}}} {{type=}} {{smallname=Defenses}} {{showchar=[1]}} {{descflag=1}} {{desc=CHATMENU \n**Defense Statistics** \nAC @{selected|ac} |Touch @{selected|ac_touch} |Flat-Footed @{selected|ac_flatfooted} |CMD @{selected|cmd_mod} |SR @{selected|sr} }} {{shownotes=[1]}} {{notes=**- Fort:**@{selected|fortitude_notes} |**Ref:**@{selected|reflex_notes} |**Will:**@{selected|will_notes} |**AC:**@{selected|ac_notes} |**CMD:**@{selected|cmd_notes} |**SR:**@{selected|sr_notes}}} --separator: | --title:Saves --Fort(@{selected|fortitude}),fortitude --Ref(@{selected|reflex}),reflex --Will(@{selected|will}),will 
1559577755
GiGs said: It occurred to me after posting, you could move the defence statistics into the desc section, and use chatmenu for the saves. Then you can format those defence stats however you want them. Something like (untested) !chatmenu @{selected|character_id} {template:pc}{{charname=@{selected|character_name}}} {{type=}} {{smallname=Defenses}} {{showchar=[1]}} {{descflag=1}} {{desc=CHATMENU \n**Defense Statistics** \nAC @{selected|ac} |Touch @{selected|ac_touch} |Flat-Footed @{selected|ac_flatfooted} |CMD @{selected|cmd_mod} |SR @{selected|sr} }} {{shownotes=[1]}} {{notes=**- Fort:**@{selected|fortitude_notes} |**Ref:**@{selected|reflex_notes} |**Will:**@{selected|will_notes} |**AC:**@{selected|ac_notes} |**CMD:**@{selected|cmd_notes} |**SR:**@{selected|sr_notes}}} --separator: | --title:Saves --Fort(@{selected|fortitude}),fortitude --Ref(@{selected|reflex}),reflex --Will(@{selected|will}),will  That is exactly what I was thinking after you mentioned it. Using desc for the defense statistics makes sense. I just couldn't see that before for some reason. Staring at my screen for too long had me stuck in a rut. Thanks again for all the help.
1559662759
GiGs
Pro
Sheet Author
API Scripter
Seph said: Btw, GiGs, I just noticed the ! filter doesn't seem to be working either; it currently doesn't print anything. Think it could have something to do with the issue with the = filter? EDIT: I did some more testing with the filters, and I've mostly gotten the desired results but not sure they're working as intended. My original attack macro worked as intended before one of the recent updates --title:Melee --repeating_attacks|atkname|fullattack|atktype=melee --title:Ranged --repeating_attacks|atkname|fullattack|atktype=ranged --title:Special --repeating_attacks|atkname|fullattack|atktype!melee|atktype!ranged All melee items printed under Melee, ranged under Ranged, and everything that was neither printed under Special. Now this same macro prints melee items under Melee and ranged under Ranged as intended, but all items with no attack value are printed twice—once under Melee, once under Ranged—while nothing is printed under Special. I've managed to get the desired results by changing the macro to the following, mainly through guesswork --title:Melee --repeating_attacks|atkname|fullattack|atktype=melee|atktype!- --title:Ranged --repeating_attacks|atkname|fullattack|atktype=ranged|atktype!- --title:Special --repeating_attacks|atkname|fullattack|atktype=bab|atktype!- --repeating_attacks|atkname|fullattack|atktype=fob|atktype!- --repeating_attacks|atkname|fullattack|atktype=- --repeating_attacks|atkname|fullattack|atktype=cmb|atktype!- No rush on this, obviously; I know you're busy! I havent had a chance to work on this, but starting to now. I'm curious about the second version of the macro above. Why is this part necessary in so many of your arguments? |atktype!-
GiGs said: I havent had a chance to work on this, but starting to now. I'm curious about the second version of the macro above. Why is this part necessary in so many of your arguments? |atktype!- That's the only workaround I've found to keep |atktype=melee and |atktype=ranged from printing special attack entries that have no type, like sneak attack. The attack types are selected from a drop-down rather than typed in, and a blank attack type actually uses the - character. I'm pretty certain it's linked to the issue I was having with listing gear, where |special=aura would print items that contained 'aura' somewhere in the special field, plus items with an empty special field. My workaround for that was filling empty special fields with 'none'.
1559680434

Edited 1559680965
GiGs
Pro
Sheet Author
API Scripter
Here's a bugfix release, which should (crosses fingers) address Keith and Seph's reports. Give it a try, and if it works, I'll update the 2nd post in the thread. var universalChatMenu = universalChatMenu || (function () { 'use strict'; const version = '0.3.7', scriptName = 'Universal Chat Menu', lastUpdate = 1559680306108, //defaultTitle = 'Actions', defaultHeader = (who, actions = 'Actions') => `${who} ${actions} Menu`, BUTTONSYMBOL = '!', MENUPLACE = 'CHATMENU', SEPARATOR = ' ', checkInstall = () => { log('-=> ' + scriptName + ' v' + version + ' <=- [' + (new Date(lastUpdate)) + ']'); }, ch = function (c) { var entities = { '<': 'lt', '>': 'gt', "'": '#39', '@': '#64', '{': '#123', '|': '#124', '}': '#125', '[': '#91', ']': '#93', '"': 'quot', '-': 'mdash', ' ': 'nbsp' }; if (_.has(entities, c)) { return ('&' + entities[c] + ';'); } return ''; }, // build attribute name for a repeating section repname = (section, id, name) => `repeating_${section}_${id}_${name}`, // get an array of attributes on this character - handy to shorten code elsewhere. getAttributes = (cid) => { return findObjs({ _type: 'attribute', _characterid: cid }); }, // find a list of all the row ids in a repeating section. getRepeatingIDs = (cid, section, attribute) => { const repeating = getAttributes(cid) .filter((obj) => { return obj.get('name').startsWith(`repeating_${section}`) && obj.get('name').endsWith(attribute); }) .map(obj => obj.get('name').replace(`repeating_${section}_`, '').replace('_' + attribute, '').trim()); return repeating; }, checkFilter = (check, cid, prefix = '') => { // prefix is for repeating stats, supply 'repeating_${section}_${id}_' as the prefix //log('=== FILTER ===='); //log(`check: ${check} prefix: ${prefix}`); let tests = check.toString().split(','); let rules = []; let pass = 1; // if tests are passed, return 1, otherwise 0; tests.forEach(test => { // need to check it has the three results, if only one, use rule.match = 0, and rule.type = '>' let s = test.split(/[!<>=]/); let rule = {}; rule.stat = s[0]; rule.match = s.length > 1 ? s[1] : ''; rule.type = s.length > 1 ? test.replace(s[0], '').replace(s[1], '') : '#'; // # is used for no filter rules.push(rule); // rules is an array of objects. don't need a key since will just be looping through it. }); //log(`rules: ${JSON.stringify(rules)}`); // now have a set of rules can use to test the filters // will return true or false for (let rule of rules) { let value = getAttrByName(cid, `${prefix}${rule.stat}`) || ''; // if cell is empty, it is set to null switch (rule.type) { case '#': //no filter supplied, just include if attribute exists pass = (0 === +value || (typeof value === 'string' && value.trim() === '')) ? false : true; break; case '=': // if rule.match exists in the target cell; if its a number treat as perfect match. Problem: null ||0 turns empty cells into 0. if(isNaN(value)) { pass = value.toString().toLowerCase().includes(rule.match.toString().toLowerCase()); } else { pass = (+value || 0) === (+rule.match || 0); } break; case '>': pass = (+value || 0) >= (+rule.match || Infinity); break; case '<': pass = (parseInt(value, 10) || 0) < (parseInt(rule.match, 10) || 0); break; case '!': if(isNaN(value)) { pass = !value.toString().toLowerCase().includes(rule.match.toString().toLowerCase()); } else { pass = (+value || 0) !== (+rule.match || 0); } break; } if (!pass) break; } // at this point, pass will be 0 or 1, true or false; return pass; }, buildButtons = (cid, who, args) => { let parameters = {}; let title = ''; parameters[title] = []; let settings_allowed = ['title', 'footer', 'separator']; for (let arg of args) { let settings = arg.split(':'); let setting = settings[0].toLowerCase(); let setting_details = settings.length > 0 ? settings[1] : ''; if (settings_allowed.includes(setting)) { // this is a config setting; only one supported right now is title // if new title, create an empty array in parameters to hold the following attributes. switch (setting) { case 'title': title = setting_details; // dont need defaulttitle here any more //|| defaultTitle; if (!parameters.hasOwnProperty(title)) parameters[title] = []; break; case 'footer': case 'separator': parameters[setting] = setting_details; break; } } else if (arg.toLowerCase().startsWith('repeating_')) { // this is a repeating section, need to split on "|" const repeatingAttribute = arg.split('|'); if (repeatingAttribute.length < 3) { // not valid, skip it parameters['error'] = 'Error in Repeating Attributue: ' + arg; break; } const section = repeatingAttribute[0].replace('repeating_', '').trim(); //repeating section name const display = repeatingAttribute[1].trim() || 'N/A'; //display name let button = repeatingAttribute[2].trim() || 'N/A'; let buttonAttr = getButtonType(button); if (buttonAttr) button = button.split(BUTTONSYMBOL)[0].trim(); if (display === 'N/A' || button === 'N/A') { // this will likely never trigger, it just wont print that section. parameters['error'] = 'Error in Repeating Attributue: ' + arg; break; } let repeating = getRepeatingIDs(cid, section, display); let checkAttribute = repeatingAttribute.length > 3 ? repeatingAttribute.slice(3) : []; // build chat menu buttons repeating.forEach(id => { let showButton = 1; if (checkAttribute.length > 0) { showButton = checkFilter(checkAttribute,cid, `repeating_${section}_${id}_`); } if (showButton > 0) { parameters[title].push( getButtonCode(getAttrByName(cid, repname(section, id, display)), who, repname(section, id, button), buttonAttr)); } }); } else { // this is a non-repeating set of attributes, split on | and then , let singleAttributes = arg.split('|'); singleAttributes.forEach(attr => { let attr_array = attr.split(','); let label = attr_array[0]; let button = attr_array[1] || label; let check = attr_array.length > 2 ? attr_array.slice(2) : []; let buttonAttr = getButtonType(button); if (buttonAttr) button = button.split(BUTTONSYMBOL)[0].trim(); // if check doesnt exist, or if it does exist but has a value of zero, print it. Otherwise don';'t let pass = 1; if(check.length > 0) { //log(`${label}: ${check}`); pass = checkFilter(check,cid,''); } if (pass) { parameters[title].push( getButtonCode(label, who, button, buttonAttr)); } }); } } return parameters; }, getButtonType = (button) => button.split(BUTTONSYMBOL).length > 1, getButtonCode = (label, who, button, attribute = false) => { let code = `[${label}](`; if (attribute) { code += `!
/w gm &${ch('{')}template:default${ch('}')}${ch('{')}${ch('{')}name=${ch('@')}${ch('{')}selected|character_name${ch('}')} ${label}${ch('}')}${ch('}')}${ch('{')}${ch('{')}=${ch('@')}${ch('{')}selected${ch('|')}${button}${ch('}')}${ch('}')}${ch('}')})`; } else { code += `~${who}|${button})`; } return code; }, extractFrom = (buttons, field, fallback) => { let found = fallback; if (buttons.hasOwnProperty(field)) { found = buttons[field]; delete buttons[field]; } return found; }, handleInput = (msg) => { // copy the body of your on(chat:message) event to this function if ('api' === msg.type && msg.content.toLowerCase().startsWith('!chatmenu ')) { let args = msg.content.split(/\s+--/); // get character and heading let parameters = args[0].split(/\s+/); const cid = parameters[1] || ''; //character id if (cid === '') { sendChat(scriptName, 'No recognised character.'); return; } const who = getAttrByName(cid, 'character_name'); if (!who) { sendChat(scriptName, 'No recognised character.'); return; } const sender = 'character|' + cid; const caller = (getObj('player', msg.playerid) || { get: () => 'API' }).get('_displayname'); let header = parameters.slice(2).join(' ') || defaultHeader(who); // if this starts with {template: it is a rolltemplate // and if it contains CHATMENU, the menu buttons are placed there. Otherwise they are placed at the end. // get buttons let combineSections = false; // if false, defaultTemplate sections; if true, combine all sections into one args.shift(); let buttons = buildButtons(cid, who, args); // buttons will be an object, each item key is a section title, and each item value is an array of buttons in that section // build chat menu buttons if (buttons.hasOwnProperty('error')) { sendChat(scriptName, buttons['error'].toString()); return; } let headerPrint = `&{template:default}{{name=${header}}}`; if (header.includes('template:')) { headerPrint = `&${header}`; } headerPrint = headerPrint.replace(/(\[)/g, '[[').replace(/(\])/g, ']]'); // make sure no inline rolls present let footer = extractFrom(buttons, 'footer', ''); let separator = extractFrom(buttons, 'separator', SEPARATOR); let output = ''; let sections = Object.keys(buttons); if (headerPrint.includes(MENUPLACE)) combineSections = true; for (let section of sections) { if (buttons[section].length > 0) { output += (combineSections ? `**${section.toUpperCase()}**\n` : `{{**${section}**=`); output += buttons[section].join(separator); output += (combineSections ? `\n` : `}}`); } } let print = combineSections ? headerPrint.replace(MENUPLACE, output) : headerPrint + output; print += footer ? `\n/w ${caller} ${footer}` : ''; sendChat(sender, `/w ${caller} ${print}`); } }, registerEventHandlers = () => { on('chat:message', handleInput); }; return { CheckInstall: checkInstall, RegisterEventHandlers: registerEventHandlers }; }()); on('ready', () => { 'use strict'; universalChatMenu.CheckInstall(); universalChatMenu.RegisterEventHandlers(); });
1559681632
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
! is now doing what = used to do. I'd do some more testing, but I had some bad results from a rollback and don't want to mess with the game too much until it gets fixed. I'll post the latest iteration then, since I've added more functionality. Thanks again. This script makes the OGL sheet usable for me.
1559682023
GiGs
Pro
Sheet Author
API Scripter
I'm glad to hear it (not the rollback part - that's awful!)
Awesome! I'll be home to test it out in a few hours, and will let you know how it goes.