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>&nbsp;&nbsp;${preparedNamesLevel.join('<br>&nbsp;&nbsp;')}`); }); 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 { }; })();