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

Custom Compendium?

1714321030
Quinn
Pro
Sheet Author
The official D&D 5e sheet has a fun feature where you can drag monsters straight from the compendium onto a character sheet and it'll automatically fill it out. Is that possible in any form for custom sheets? Could I create my own compendium? It seems like the answer is no, but I wanted to be sure before I give up on it.
1714322838

Edited 1714325075
When you're using the D&amp;D 5e by Roll20 character sheet for your game, you don't even need to have a character sheet open. Just drag the monster directly from the any of your compendiums onto the map and it will create the token on the map and add the monster's character sheet to the journal. I don't know if that works for any other of the D&amp;D 5e character sheets. There is a long-existing thread that's been asking for a custom compendium:&nbsp; <a href="https://app.roll20.net/forum/post/2940484/custom-compendium" rel="nofollow">https://app.roll20.net/forum/post/2940484/custom-compendium</a> What many people do is to created a "library" game specifically for storing customized NPCs, monsters, handouts, maps, etc., Then use Transmogrifier (which you have access to because of your Pro subscription) to copy items between the "library" game and your "live" game.
1714323755
Quinn
Pro
Sheet Author
Aha, clever! I'll do that, thanks.
1714342374
GiGs
Pro
Sheet Author
API Scripter
Quinn said: It seems like the answer is no, but I wanted to be sure before I give up on it. In case it needs clarification, the answer is no. There are workarounds (like Library Campaign already mentioned), but they aren't available to everyone. At this time, there is no way to create a custom compendium.
1714393579
Finderski
Plus
Sheet Author
Compendium Curator
And for further clarification...given that no custom compendium is available, that does mean, while the workaround is great for fully stated characters, it does NOT give you the ability to drag and drop items (e.g. weapons or other gear) onto a character using Transmogrifier. For me, the power of a compendium is less about the monsters/NPC I can drag and drop, but more about the power of re-usable weapons, spells, etc. that I can drag and drop onto a character sheet.
1714421757
GiGs
Pro
Sheet Author
API Scripter
Finderski said: For me, the power of a compendium is less about the monsters/NPC I can drag and drop, but more about the power of re-usable weapons, spells, etc. that I can drag and drop onto a character sheet. Yeah, thats my main interest too, since other uses can easily be substituted for with the Transmogrifier.
Yes! I love using customized items. Having the ability to drag and drop them into character sheets instead of having to manually edit them every time PCs obtain or trade them would be great. Finderski said: For me, the power of a compendium is less about the monsters/NPC I can drag and drop, but more about the power of re-usable weapons, spells, etc. that I can drag and drop onto a character sheet.
1714540826
Lithl
Pro
Sheet Author
API Scripter
Finderski said: And for further clarification...given that no custom compendium is available, that does mean, while the workaround is great for fully stated characters, it does NOT give you the ability to drag and drop items (e.g. weapons or other gear) onto a character using Transmogrifier. For me, the power of a compendium is less about the monsters/NPC I can drag and drop, but more about the power of re-usable weapons, spells, etc. that I can drag and drop onto a character sheet. You could &nbsp;create an API script to generate data on the character sheet automatically in similar fashion to a custom compendium. I did this for a collection of custom spells in one of my 5e campaigns. Similar scripts could be made for other parts of the character sheet. on('ready', () =&gt; { let spellData = {}; function initSpellData() { spellData = {}; const spellHandouts = filterObjs((o) =&gt; o.get('type') === 'handout' &amp;&amp; o.get('name').indexOf('Custom Spell: ') === 0); spellHandouts.forEach((spell) =&gt; { spell.get('gmnotes', (notes) =&gt; { notes = notes.replace(/&lt;.*?&gt;/g, '').replace(/(&amp;nbsp;|\s)+/g, ' '); try { notes = JSON.parse(notes); } catch { log(` ### JSON PARSE ERROR: Skipping ${spell.get('name')}`); log(notes); } spellData[spell.get('name').substring(14)] = notes; }); }); } initSpellData(); const generateUUID = (() =&gt; { let a = 0; let b = []; return () =&gt; { let c = (new Date()).getTime() + 0; let f = 7; let e = new Array(8); let d = c === a; a = c; for (; 0 &lt;= f; f--) { e[f] = "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".charAt(c % 64); c = Math.floor(c / 64); } c = e.join(""); if (d) { for (f = 11; 0 &lt;= f &amp;&amp; 63 === b[f]; f--) { b[f] = 0; } b[f]++; } else { for (f = 0; 12 &gt; f; f++) { b[f] = Math.floor(64 * Math.random()); } } for (f = 0; 12 &gt; f; f++) { c += "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".charAt(b[f]); } return c; }; })(); function generateRowID() { return generateUUID().replace(/_/g, "Z"); } function createAttrs(prefix, characterid, attrs) { const createdObjs = []; Object.keys(attrs).forEach((name) =&gt; { const newObj = createObj('attribute', { name: prefix + name, current: '', characterid: characterid, }); newObj.setWithWorker({current: attrs[name]}); createdObjs.push(newObj); }); return createdObjs; } on('chat:message', (msg) =&gt; { if (msg.type !== 'api') return; if (msg.content.indexOf('!custom-spell') !== 0) return; const args = msg.content.splitArgs(); args.shift(); const fromWho = getObj('player', msg.playerid); const fromName = fromWho.get('displayname'); if (args[0] === 'refresh' &amp;&amp; playerIsGM(msg.playerid)) { // spellData is loaded when the sandbox spins up, but `!custom-spell refresh` lets a GM update spellData without restarting the API. initSpellData(); sendChat('System', '/w gm Custom spell data refreshed!', null, {noarchive: true}); return; } if (args[0] === 'debug' &amp;&amp; playerIsGM(msg.playerid)) { const spellNames = Object.keys(spellData).filter((name) =&gt; name.toLowerCase().indexOf((args[1] || '').toLowerCase()) &gt;= 0); const spell = spellData[spellNames[0]]; if (!spell) { sendChat('System', `/w gm No spell found for &amp;lt;${args[1] || ''}&amp;gt;`, null, {noarchive:true}); return; } sendChat('System', `/w gm &amp;{template:default} {{name=${spellNames[0]}}} {{=&lt;pre&gt;&lt;code&gt;${JSON.stringify(spell, null, 2)}&lt;/code&gt;&lt;/pre&gt;}}`, null, {noarchive:true}); return; } if (args[0] === 'search') { // produces a list of custom spells, potentially fileterd by a search term let spellNames = Object.keys(spellData); let output = '&amp;{template:default}'; if (args[1]) { spellNames = spellNames.filter((name) =&gt; name.toLowerCase().indexOf(args[1].toLowerCase()) &gt;= 0); output += `{{name=Custom Spells matching &amp;lt;${args[1]}&amp;gt;}}`; } else { output += '{{name=Custom Spells}}'; } output += spellNames.map((name, i) =&gt; `{{${''.padStart(i, ' ')}=[${name}](!custom-spell add &amp;#34;${name}&amp;#34;)}}`).join(''); sendChat('System', `/w "${fromName}" ${output}`, null, {noarchive:true}); return; } if (args[0] === 'add') { // adds a specified custom spell to the selected character if (!args[1]) { sendChat('System', `/w "${fromName}" No custom spell specified`, null, {noarchive: true}); return; } if (!spellData[args[1]]) { sendChat('System', `/w "${fromName}" No custom spell named &amp;lt;${args[1]}&amp;gt; found. Try using [search](!custom-spell search) to get an exact name.`, null, {noarchive:true}); return; } if (!msg.selected) { sendChat('System', `/w "${fromName}" No character selected. Please select a character and try again.`, null, {noarchive:true}); return; } const selected = getObj(msg.selected[0]._type, msg.selected[0]._id); if (!selected.get('represents')) { sendChat('System', `/w "${fromName}" Selected token has no associated character. Please select a token that is linked to a character sheet.`, null, {noarchive:true}); return; } const character = getObj('character', selected.get('represents')); const spell = spellData[args[1]]; const rowId = generateRowID(); const attrPrefix = `repeating_spell-${spell.spelllevel}_${rowId}_`; const createdAttrs = createAttrs(attrPrefix, character.id, { spellname: args[1], 'options-flag': 'false', 'details-flag': 'false', spellschool: spell.spellschool, spellritual: spell.spellritual ? '{{ritual=1}}' : '0', spellcastingtime: spell.spellcastingtime, spellrange: spell.spellrange, spelltarget: spell.spelltarget, spellcomp_v: spell.components.spellcomp_v ? '{{v=1}}' : '0', spellcomp_s: spell.components.spellcomp_s ? '{{s=1}}' : '0', spellcomp_m: spell.components.spellcomp_m ? '{{m=1}}' : '0', spellcomp_materials: spell.components.spellcomp_materials, spellconcentration: spell.spellconcentration ? '{{concentration=1}}' : '0', spellduration: spell.spellduration, spell_ability: 'spell', innate: '', // spelloutput: spell.output.spelloutput, spelloutput: 'SPELLCARD', spellattack: spell.output.spellattack, spelldamage: spell.output.spelldamage, spelldamagetype: spell.output.spelldamagetype, spelldamage2: spell.output.spelldamage2, spelldamagetype2: spell.output.spelldamagetype2, spellhealing: spell.output.spellhealing, spelldmgmod: spell.output.spelldmgmod ? 'Yes' : '0', spellsave: spell.output.spellsave, spellsavesuccess: spell.output.spellsavesuccess, spellhldie: spell.output.spellhldie, spellhldietype: spell.output.spellhldietype, spellhlbonus: spell.output.spellhlbonus, includedesc: spell.output.includedesc, spelldescription: spell.output.spelldescription, spellathigherlevels: spell.output.spellathigherlevels, }); return; } }); }); Each spell in the "compendium" is specified with its own handout (which can be archived), and the GM notes field is filled with JSON containing all the spell details, like this: { "spelllevel": 3, "spellschool": "abjuration", "spellritual": false, "spellcastingtime": "1 action", "spellrange": "self", "spelltarget": "self", "components": { "spellcomp_v": true, "spellcomp_s": true, "spellcomp_m": false, "spellcomp_materials": "" }, "spellconcentration": false, "spellduration": "1 minute", "output": { "spellattack": "None", "spelldamage": "", "spelldamagetype": "", "spelldamage2": "", "spelldamagetype2": "", "spellhealing": "", "spelldmgmod": false, "spellsave": "", "spellsavesuccess": "", "spellhldie": "", "spellhldietype": "", "spellhlbonus": "", "includedesc": "off", "spelldescription": "This spell creates an invisible magic field that does not stop weapons from moving toward you, but impedes their motion when they are retracted. When you are hit with a melee attack, make a contested ability check using your spellcasting ability against the attacker's ability score used in the attack. If you succeed, your opponent's attacking weapon or body part becomes stuck in the field.\n\nIf your opponent's melee weapon becomes stuck in the field, the opponent may release the weapon and move away from you. If your opponent attacked with an unarmed strike, natural weapon, or refuses to release their weapon, they are grappled by you. You do not require a free hand to grapple the creature, but only one creature can be grappled via *blade snare* at a time; if another creature hits you with a melee attack, you must release the first one in order to attempt to grapple the second.\n\nAs an action, a creature grappled in this way may make a Strength (Athletics) or Dexterity (Acrobatics) check with a DC equal to your spell save DC, ending the grapple and escaping the field on success.\n\nWhile *blade snare* is active, you may not make ranged weapon attacks, but you can make ranged spell attacks and melee attacks freely.", "spellathigherlevels": "" } } The !custom-spell search command lets the users search/filter the list of custom spells, with buttons to add a spell to the character sheet of the currently selected token.
1714544081
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
I once built a spreadsheet to parse information into ChatSetAttr macros that would create class abilities. Then Charactermancer came out, and I dropped it.
1714566618
Finderski
Plus
Sheet Author
Compendium Curator
That type of script is great for a Pro sub (which I am), BUUUUUT does nothing for those who aren't. When all the major competitors allow this out of the box regardless of level...this is becoming a glaring hole. &nbsp;I'm also about to let my sub lapse, because I'm using a different VTT more now...one that allows custom compendiums with drag and drop stuff without the need for a custom script. &nbsp;I really do prefer R20 for its simplicity of use, but they are starting to lag behind.