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 for counting the spells prepared

1608727645
Kai
Sheet Author
As the title says - is there a script that shows the number of the spells prepared on a given 5e OGL sheet? I would like to have this implemented directly into the sheet, but this feature is missing. Any information is appreciated.
1608734131

Edited 1608734407
Oosh
Sheet Author
API Scripter
Well it's a bit rough and thrown together, but you can try this. Very basic input, it only takes: !prepared <character_id> So either !prepared @{selected|character_id} for the selected token, or !prepared @{bob|character_id} for a character called bob. const spellsPrepared = (() => { // eslint-disable-line no-unused-vars const getRepAttrs = (charId, sectionName, rowIds, attrNamesArray, createMissing = false) => { if (getObj('character', charId) && sectionName.search(/repeating.*_\$/i) !== -1) { let sectionPrefix = sectionName.match(/(repeating.*_)\$/i)[1]; attrNamesArray = (Array.isArray(attrNamesArray)) ? attrNamesArray : [attrNamesArray]; attrNamesArray = attrNamesArray.filter((a) => (a) && a != '0') rowIds = (Array.isArray(rowIds)) ? rowIds : [rowIds]; let attrArray = [], index = 0; rowIds.forEach(row => { let rowObj = {prefix: sectionPrefix, rowId: row}; attrNamesArray.forEach(attrName => { let attr = findObjs({_type: 'attribute', _characterid: charId, name: `${sectionPrefix}${row}_${attrName}`}, {caseInsensitive: true}); if (attr.length === 0) { //log(`Can't find attribute: ${sectionPrefix}${row}_${attrName} on character id: ${charId} -- ${getObj('character', charId).get('name')}`) if (createMissing) { let x = createObj('attribute', {characterid: charId, name: `${sectionPrefix}${row}_${attrName}`, current: '', max: ''}); //log(`Missing attribute created!`); rowObj[attrName] = {id: x.id, current: '', max: ''} } } else { let currentAttr = findObjs({type:'attribute', characterid: charId, name: `${sectionPrefix}${row}_${attrName}`})[0]; rowObj[attrName] = {id: currentAttr.get('_id'), current: currentAttr.get('current'), max: currentAttr.get('max')} } }) attrArray.push(rowObj); index ++; }) return attrArray; } else {log(`invalid charId (${charId}) or repeating section name (${sectionName}), aborting...`)} } const getRepIds = (charId, sectionName, ignoreBlanks = false) => { if (getObj('character', charId)) { let repSecParts = sectionName.trim().split(/_[$*].*_/); let regex = new RegExp(`${repSecParts[0]}_(-.*)_${repSecParts[1]}`) if (repSecParts.length !== 2 || repSecParts[0].search(/repeating/i) === -1) { log(`Bad repeating section name: ${sectionName}, aborting...`); return; } let idArray = []; findObjs({type: 'attribute', characterid: charId}).filter(attr => { if (attr.get('name').search(repSecParts[0]) !== -1 && attr.get('name').search(repSecParts[1]) !== -1 && attr.get('name').match(regex) && attr.get('current')) { if (!ignoreBlanks || attr.get('current').trim() !== '') idArray.push(attr.get('name').match(regex)[1]); else log(`Row found, but name field blank - skipping ${charId} -- ${attr.get('name').current}`); } }) let reporder = findObjs({type: 'attribute', characterid: charId, name: `_reporder_${repSecParts[0]}`})[0]; if (reporder) { reporder = reporder.get('current').trim().split(','); idArray = [...new Set(reporder.filter((id) => idArray.includes(id)).concat(idArray))]; } idArray.filter((attr) => attr.current != '') return idArray; } } const handleInput = (msg) => { if (msg.type === 'api' && msg.content.match(/^!prepared\s*(-[^\s]*)/i)) { let charId = msg.content.match(/!prepared\s*(-[^\s]*)/)[1]; let charName = (getObj('character', charId)) ? getObj('character', charId).get('name') : null; if (charId && charName) { let preparedNamesAll = [], preparedTotal = 0; const spellLevels = [1,2,3,4,5,6,7,8,9] spellLevels.forEach(lvl => { let idArray = getRepIds(charId, `repeating_spell-${lvl}_$0_spellname`, true); let attrArray = getRepAttrs(charId, `repeating_spell-${lvl}_$0`, idArray, ['spellprepared','spellname'], true); let preparedNamesLevel = []; attrArray.forEach(row => { if (row.spellprepared && row.spellprepared.current == 1) { preparedNamesLevel.push(row.spellname.current); preparedTotal ++; } }) if (preparedNamesLevel.length > 0) preparedNamesAll.push(`***Spell Level ${lvl}:***<br>  ${preparedNamesLevel.join('<br>  ')}`); }); sendChat('', `/w ${msg.who} &{template:npcaction} {{rname=${charName} Prepared Spells}} {{description=${preparedNamesAll.join('<br>')}<br><br>Total spells: ${preparedTotal}}}`) } else return sendChat('', `Invalid character ID supplied: ${charId}`) } } on('ready', () => { on('chat:message', handleInput) }); return { }; })();
1608802090
Kai
Sheet Author
Thanks Oosh, that script is working great. Thank you for your work. Is there any chance I could have a field on my spell sheet that shows the number of total spells? So i don't have to count while I prepare spells?
1608807759
Oosh
Sheet Author
API Scripter
That would, unfortunately, require editing the sheet. It's no longer a community sheet, so that isn't possible (okay.... it is possible, but would require using a custom sheet based on the Legacy 5e sheet which is a year or two out of date). The best you can do is write a macro to call the script, and assign it to a button on the macro bar. If you are running a custom sheet then yes - you could quite easily add this in. The required sheetworker would be alot simpler than the code above, since the helper functions already do most of the legwork.