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

[Script Request] Replace Range values written in 'ft' with an apostrophe

1608220592
On the D&D 5e by Roll20 character sheet, all of the module/pre-created characters & NPCs have their attack range values written in feet, such as " 5 ft. ", but I'd like to have them written as " 5' " . I know I can use ChatSetAttr to update/change any values, even of repeating sections, but that is still a fairly manual process of seeing what the current value is, and then replacing it.  I can't figure out a way to use ChatSetAttr to only  switch the " ft. " with " ' " but leave the rest of the value intact. Is this something that ChatSetAttr can do, or is there another script that can do this?  I'm fine with running it on each character individually, or if there's a global way to run it on all NPCs that would be great. Currently my approach is to open each character sheet, and edit the repeating action sections individually. The two repeating sections I want to adjust are "@{selected|repeating_npcaction_$0_attack_range}" and "@{selected|repeating_npcaction-l_$0_attack_range}". Thanks for any help you're able to provide!
1608224439
The Aaron
Forum Champion
API Scripter
Is there ever a case where you wouldn't want to replace it on a character sheet?
1608224619

Edited 1608225137
Jordan C.
Pro
API Scripter
on('ready', () => { on('chat:message', (msg) => { if (msg.type !== 'api' || msg.content !== '!convert') return; const range = /\sft\./g; let character = findObjs({ type: 'character', }).forEach(cha => { let action = findObjs({ type:'attribute', _characterid: cha.id, }).forEach(attr => { current = attr.get('current'); oldAttr = JSON.stringify(current); if (oldAttr.match(range)) { log(cha.get('name')); let oldAttr = attr.get('current'); let newAttr = oldAttr.replace(range, "'"); attr.set('current', newAttr); } }) }); }); }); Super basic so no real error handling - but handles every sheet: you could add qualifiers like having the findObjs determine that its not in any players journals etc.  Edit: OH also isn't restricted by specific action names, which may not be desirable but it can change every instance of " ft." within all npc actions. Pretty sure you could add this regex to match only npc ranged actions in the findObjs for attiributes but it might need to be more robust than \w+ for IDs: /repeating_npcaction_-\w+_attack_range/gi
1608225013
The Aaron said: Is there ever a case where you wouldn't want to replace it on a character sheet? Me personally, not that I can think of.
1608225380
Jordan C. said: on('ready', () => { on('chat:message', (msg) => { if (msg.type !== 'api' || msg.content !== '!convert') return; const range = /\sft\./g; let character = findObjs({ type: 'character', }).forEach(cha => { let action = findObjs({ type:'attribute', _characterid: cha.id, }).forEach(attr => { current = attr.get('current'); oldAttr = JSON.stringify(current); if (oldAttr.match(range)) { log(cha.get('name')); let oldAttr = attr.get('current'); let newAttr = oldAttr.replace(range, "'"); attr.set('current', newAttr); } }) }); }); }); Super basic so no real error handling - but handles every sheet: you could add qualifiers like having the findObjs determine that its not in any players journals etc.  Edit: OH also isn't restricted by specific action names, which may not be desirable but it can change every instance of " ft." within all npc actions. Pretty sure you could add this regex to match only npc ranged actions in the findObjs for attiributes but it might need to be more robust than \w+ for IDs: /repeating_npcaction_-\w+_attack_range/gi I just ran it in a game copy and it looks like it worked perfectly. Thank you!
1608225383

Edited 1608226142
The Aaron
Forum Champion
API Scripter
I made one, too!  =D This will only replace `ft` if it appears after some number.  I haven't tested it, so caveat emptor. !replace-all-ft Code: on('ready',()=>{ const regex=/(\d+\s*)(ft\.?)(\s|$)/gmi; on('chat:message',msg=>{ if('api'===msg.type && /^!replace-all-ft(\b\s|$)/i.test(msg.content) && playerIsGM(msg.playerid)){ let who = (getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname'); let attrs = findObjs({ type: 'attribute'}); sendChat('Replace All Ft',`/w "${who}" <div>Looking through <code>${attrs.length}</code> attributes...</div>`); let count = 0; const burndown = () => { if(attrs.length){ let a = attrs.shift(); let c = attrs.get('current'); let m = attrs.get('max'); let c2 = c.replace(regex,`$1'$3`); let m2 = m.replace(regex,`$1'$3`); if(c2 !== c || m2 !== m){ a.set({current:c2, max: m2}); ++count; } setTimeout(burndown,0); } else { sendChat('Replace All ft', `/w "${who}" <div>Changed <code>${count}</code> attributes.</div>`); } }; burndown(); } }); });
1608225767
Jordan C.
Pro
API Scripter
Jarren K. said: I just ran it in a game copy and it looks like it worked perfectly. Thank you! Awesome! Glad it didn't break anything, lol. Aaron - literally the whole time I typed it I was thinking "Aaron is going to post a better one right when I do" 
1608226080
The Aaron
Forum Champion
API Scripter
HAHAHAHA, well, I got interrupted by a cat that wanted to lay on my keyboard and type her own codes... One thing to be aware of is protection from "possible infinite loop detected".  When you run on a small game, simply grabbing things with findObjs() parsing each one in turn is fine, but if you were working on a very large game, or had some more time consuming operations, it might timeout the API sandbox.  I use a technique I call burndown queues (but probably has some real name), where I build a list of work, then parse a single item at a time and defer the next parse with a setTimeout( ... , 0 ); call.  That lets the watchdog timer in the API sandbox reset between each operation, as well as other events to be processed. One other refinement, since every attribute is associated with a character, and you're processing every character, I just cut to the case and grabbed all the attributes to begin with. =D
1608226101
The Aaron
Forum Champion
API Scripter
(of course, I didn't test it, so it might not even work. =D)
1608226542
Jordan C.
Pro
API Scripter
AH that was definitely a redundancy that was leftover from when I was grabbing a specific character by name to test on, whoops. But that's genius I definitely am logging that burndown thing in my brain bank for future use.