Hello Thanks for the answer. I am not using chatsetattr, I am writing my own api for all that stuff since it's not done through chat (well, it reacts to a command, but then everything happens in .js). You mentioned that simply modifying "spelloutput" would do the trick, but that does not seem to change anything. It does with roll20-compendium-imported spells, yes, but not ones I put in by hand. So I presume I might've done something incorrectly. Generally I've somehow managed to add attacks, but they seem "unlinked" from the character sheet - as in no preview values within update whenever character's stats change (like spell save DC isn't adjusted). So I take there's something amiss. That being said, you asked for my code, so here it is. It's rather rudimentary, as it's just for personal use. Generally the command is irrelevant, as I have special buttons for that, but basically, to add a spell with my script, I put a command like so: !sadd Fireball I have a JSON of all the d&d spells I translated to Polish (as initially I used this as a translation tool, but wanted to expand it to be able to add homebrew stuff easily) and this function reads the JSON, finds the spell, injects it into the character sheet. Since I couldn't find info about how to generate a neat repeating section ID like roll20 does it, I simply added arbitraty IDs to all spells I have (basically their ID on the array of spells I have in my translation project) and it seems to work neatly (but maybe this is the issue?). Initially I just did $0, $1, etc. but this seemed, well, prettier (as I didn't have to iterate through existing spells to find which ID is not taken). In any case, after filling the spell data I add the attack via the function at the bottom. It's not ready yet, as I am still toying with it, but it seems that if I did something wrong when importing the spell and the attack should be added automatically, the function to create attack is not needed. In any case, I added it on the bottom. on("chat:message", function(msg) { if (msg.who === "Magic") return; if (!msg.content.startsWith("!sadd")) return; var searchData = msg.content.replace("!sadd ", ""); if (msg.selected == null || msg.selected.length === 0) { sendChat("Magic", "Próbuję dodać czar o ID=" + searchData + " ale nie wykryłem zaznaczonej postaci"); return; } var obj = getObj(msg.selected[0]._type, msg.selected[0]._id); var character = getObj('character', obj.get('represents')); getTranslationHandout().get('notes', function (contents) { var data = JSON.parse(contents); for (var Di = 0; Di < data.spells.length; ++Di){ var spell = data.spells[Di]; if (spell.spellID != searchData) continue; var current = findSpellOfCharacter(character, searchData); if (!current) current = findSpellOfCharacter(character, spell.spellname); var attackID = ""; var prefix = ""; if (current) { prefix = current.get('name').replace('spellname', ''); var spellAttackAttribute = findObjs({type:'attribute', characterid: character.id}) .filter((o) => o.get('name') == current.get('name').replace('_spellname', '_spellattackid'))[0]; attackID = spellAttackAttribute.get('current'); } else { prefix = 'repeating_spell-' + spell.spellLevel + '_' + spell.uniqueID + '_'; attackID = spell.uniqueAttackID; } addAttribute(character, prefix, 'rollcontent', ''); addAttribute(character, prefix, 'spellhldietype', ''); addAttribute(character, prefix, 'spellhldie', ''); addAttribute(character, prefix, 'spellhlbonus', ''); addAttribute(character, prefix, 'spell_damage_progression', ''); addAttribute(character, prefix, 'spellcomp_s', ''); addAttribute(character, prefix, 'spellcomp_v', ''); addAttribute(character, prefix, 'spellcomp_m', ''); addAttribute(character, prefix, 'spellcomp_materials', ''); addAttribute(character, prefix, 'spellritual', ''); addAttribute(character, prefix, 'spellconcentration', ''); addAttribute(character, prefix, 'spellattack', ''); addAttribute(character, prefix, 'spellsave', ''); addAttribute(character, prefix, 'innate', ''); addAttribute(character, prefix, 'spellsource', ''); addAttribute(character, prefix, 'spellattackid', ''); addAttribute(character, prefix, 'spellclass', ''); addAttribute(character, prefix, 'roll_output_dc', 10); addAttribute(character, prefix, 'options-flag', 0); addAttribute(character, prefix, 'details-flag', 0); if (spell.spellcastingtime.includes('Koncentracja')) addAttribute(character, prefix, 'spellconcentration', '{concentration=1}'); addAttribute(character, prefix, 'spellname', spell.spellname); addAttribute(character, prefix, 'spellrange', spell.spellrange); addAttribute(character, prefix, 'spellcastingtime', spell.spellcastingtime); addAttribute(character, prefix, 'spelltarget', spell.spelltarget); addAttribute(character, prefix, 'spellduration', spell.spellduration); addAttribute(character, prefix, 'spelldescription', spell.spelldescription); addAttribute(character, prefix, 'spellathigherlevels', spell.spellathigherlevels); addAttribute(character, prefix, 'spellschool', spell.school); if (spell.spellLevel == 0) addAttribute(character, prefix, 'spelllevel', 'cantrip'); else addAttribute(character, prefix, 'spelllevel', spell.spellLevel); addAttribute(character, prefix, 'spelldamage', spell.damage1); addAttribute(character, prefix, 'spelldamagetype', spell.damage1Type); addAttribute(character, prefix, 'spelldamage2', spell.damage2); addAttribute(character, prefix, 'spelldamagetype2', spell.damage2Type); addAttribute(character, prefix, 'spellhealing', spell.healing); addAttribute(character, prefix, 'spell_ability', 'spell'); addAttribute(character, prefix, 'spellsavesuccess', spell.saveEffect); if (spell.attackType == 'Melee' || spell.attackType == 'Ranged') { addAttribute(character, prefix, 'spellattack', spell.attackType); addAttribute(character, prefix, 'spellattackid', attackID); } else if (spell.attackType == 'MeleeUnlinked' || spell.attackType == 'RangedUnlinked') { addAttribute(character, prefix, 'spellattack', spell.attackType.replace('Unlinked', '')); } else { addAttribute(character, prefix, 'spellsave', spell.attackType); } if (spell.noModToDamageHeal) addAttribute(character, prefix, 'spelldmgmod', ''); else addAttribute(character, prefix, 'spelldmgmod', 'Yes'); if (spell.isRitual) addAttribute(character, prefix, 'spellritual', 'Yes'); if (spell.verbal) addAttribute(character, prefix, 'spellcomp_s', '{{s=1}}'); if (spell.somatic) addAttribute(character, prefix, 'spellcomp_v', '{{v=1}}'); if (spell.spellcomp_materials.length > 0) { addAttribute(character, prefix, 'spellcomp_m', '{{m=1}}'); addAttribute(character, prefix, 'spellcomp_materials', spell.spellcomp_materials); } if (spell.damage1.length > 0 && !spell.damage1.startsWith('0') || spell.healing.length > 0) addAttribute(character, prefix, 'spelloutput', 'ATTACK'); else addAttribute(character, prefix, 'spelloutput', 'SPELLCARD'); if (spell.upcast.includes('Cantrip')) { addAttribute(character, prefix, 'spell_damage_progression', spell.upcast); } else { var data = spell.upcast; var constPart = ""; var die = ""; var dieCount = ""; if (data.includes('+')) { var parts = data.split('+'); data = parts[0]; constParts = parts[1]; } if (data.includes('d')) { dieCount = data.substring(0, 1); die = data.substring(1); } else constPart = data; addAttribute(character, prefix, 'spellhldietype', die); addAttribute(character, prefix, 'spellhldie', dieCount); addAttribute(character, prefix, 'spellhlbonus', constPart); } fillSpellAttack(character, spell, attackID); addAttribute(character, prefix, '_spellattackid', attackID); return; } }); }); var fillSpellAttack = function(character, spell, attackID) { var prefix = 'repeating_attack_' + attackID + '_'; addAttribute(character, prefix, 'atkattr_base', 'spell'); addAttribute(character, prefix, 'options-flag', '0'); var spellLevel = spell.spellLevel == 0 ? 'cantrip' : spell.spellLevel; addAttribute(character, prefix, 'spelllevel', spellLevel); if (!spell.attackType.includes('Unlinked')) { addAttribute(character, prefix, 'spellid', spell.uniqueID); } else { addAttribute(character, prefix, 'spellid', '0'); } if (spell.attackType == 'Melee' || spell.attackType == 'Ranged' || spell.attackType == 'MeleeUnlinked' || spell.attackType == 'RangedUnlinked') { addAttribute(character, prefix, 'atkflag', '{{attack=1}}'); addAttribute(character, prefix, 'saveflag', '0'); addAttribute(character, prefix, 'saveeffect', ''); addAttribute(character, prefix, 'saveattr', ''); addAttribute(character, prefix, 'atkbonus', '+' + getAttr(character, 'spell_attack_bonus')); } else { addAttribute(character, prefix, 'atkflag', '0'); addAttribute(character, prefix, 'saveflag', '{{save=1}} {{saveattr=@{saveattr}}} {{savedesc=@{saveeffect}}} {{savedc=[[[[@{savedc}]][SAVE]]]}}'); addAttribute(character, prefix, 'saveeffect', spell.saveEffect); addAttribute(character, prefix, 'saveattr', spell.attackType); addAttribute(character, prefix, 'atkbonus', 'ST ' + getAttr(character, 'spell_save_dc')); } var hldmg = ''; if (spell.spellLevel > 0 && spell.upcast.length > 0) { var upcastDie = spell.upcast.substring(1); hldmg = '[[(1*?{Na którym poziomie?'; for (var Li = spell.spellLevel; Li < 10; ++Li) { hldmg += '|Level ' + Li + ',' + (Li - spell.spellLevel); } hldmg += '})' + upcastDie + ']]'; addAttribute(character, prefix, 'hldmg', '{{hldmg=' + hldmg + '}}'); } addAttribute(character, prefix, 'savedc', '@{spell_save_dc}'); addAttribute(character, prefix, 'atkname', spell.spellname); var cantripDamage = '[[round((@{level} + 1) / 6 + 0.5)]]dX'; var cantripDamage0 = '[[(round((@{level} + 1) / 6 + 0.5))-1]]dX'; var dmg1 = '0'; var dmg2 = '0'; var dmg1Type = spell.damage1Type; var dmg2Type = spell.damage2Type; var atkdmgtype = ''; if (spell.damage1.length > 0) { var dmg = spell.damage1.replace('mod+', ''); if (spell.spellLevel == 0) { var die = dmg.substring(1); if (dmg.startsWith('0')) dmg = cantrimDamage0; else dmg = cantripDamage; dmg = dmg.replace('dX', die); } addAttribute(character, prefix, 'dmgbase', dmg); dmg1 = dmg; if (!spell.noModToDamageHeal) { dmg1 = '@{spellcasting_ability}' + dmg; } addAttribute(character, prefix, 'dmgflag', '{{damage=1}} {{dmg1flag=1}}'); if (!spell.noModToDamageHeal) addAttribute(character, prefix, 'dmgattr', 'spell'); else addAttribute(character, prefix, 'dmgattr', ''); addAttribute(character, prefix, 'dmgtype', spell.damage1Type); var atkdmgtype = dmg + ' ' + spell.damage1Type; } if (spell.damage2.length > 0) { var dmg = spell.damage2.replace('mod+', ''); if (spell.spellLevel == 0) { var die = dmg.substring(1); if (dmg.startsWith('0')) dmg = cantrimDamage0; else dmg = cantripDamage; dmg = dmg.replace('dX', die); } addAttribute(character, prefix, 'dmg2base', dmg); dmg2 = dmg; addAttribute(character, prefix, 'dmg2flag', '{{damage=1}} {{dmg2flag=1}}'); addAttribute(character, prefix, 'dmg2attr', ''); addAttribute(character, prefix, 'dmg2type', spell.damage2Type); atkdmgtype += ' ' + dmg2 + ' ' + spell.damage2Type; } var rollBaseDamage = '@{wtype}&{template:dmg} {{rname=@{atkname}}} @{atkflag} {{range=@{atkrange}}} @{dmgflag} {{dmg1=[[' + dmg1 + ']]}} {{dmg1type=' + dmg1Type +'}} @{dmg2flag} {{dmg2=[['+dmg2+']]}} {{dmg2type='+dmg2Type+'}} @{saveflag} {{desc=@{atk_desc}}} @{hldmg} {{spelllevel=@{spelllevel}}} {{innate=@{spell_innate}}} {{globaldamage=[[0]]}} {{globaldamagetype=@{global_damage_mod_type}}} {{spelldesc_link=[Show Spell Description](~repeating_attack_spelldesc_link)}} @{charname_output}'; var rollBase = 'rollbase @{wtype}&{template:dmg} {{rname=@{atkname}}} @{atkflag} {{range=@{atkrange}}} @{dmgflag} {{dmg1=[['+dmg1+']]}} {{dmg1type='+dmg1Type+'}} @{dmg2flag} {{dmg2=[['+dmg2+']]}} {{dmg2type='+dmg2Type+'}} @{saveflag} {{desc=@{atk_desc}}} @{hldmg} {{spelllevel=@{spelllevel}}} {{innate=@{spell_innate}}} {{globaldamage=[[0]]}} {{globaldamagetype=@{global_damage_mod_type}}} ammo=@{ammo} {{spelldesc_link=[Show Spell Description](~repeating_attack_spelldesc_link)}} @{charname_output}'; var rollBase_crit = '@{wtype}&{template:dmg} {{crit=1}} {{rname=@{atkname}}} @{atkflag} {{range=@{atkrange}}} @{dmgflag} {{dmg1=[['+dmg1+']]}} {{dmg1type='+dmg1Type+'}} @{dmg2flag} {{dmg2=[['+dmg2+']]}} {{dmg2type='+dmg2Type+'}} {{crit1=[['+dmg1+']]}} {{crit2=[['+dmg2+']]}} @{saveflag} {{desc=@{atk_desc}}} @{hldmg} {{hldmgcrit='+hldmg+'}} {{spelllevel=@{spelllevel}}} {{innate=@{spell_innate}}} {{globaldamage=[[0]]}} {{globaldamagecrit=[[0]]}} {{globaldamagetype=@{global_damage_mod_type}}} {{spelldesc_link=[Show Spell Description](~repeating_attack_spelldesc_link)}} @{charname_output}'; addAttribute(character, prefix, 'rollBaseDamage', rollBaseDamage); addAttribute(character, prefix, 'rollBase', rollBase); addAttribute(character, prefix, 'rollBase_crit', rollBase_crit); addAttribute(character, prefix, 'spelldesc_link', '%{-O6CVDVDqd58ha1x8X-K|repeating_spell-'+spellLevel+'_'+spell.uniqueID+'_output}'); addAttribute(character, prefix, 'atkrange', spell.spellrange); addAttribute(character, prefix, 'atkdmgtype', atkdmgtype); } var addAttribute = function(character, prefix, attributeName, value) { var name = prefix + attributeName; var attribute = findObjs({type:'attribute', characterid: character.id}) .filter((o) => o.get('name') == name)[0]; if (attribute) attribute.set('current', value); else { attribute = createObj('attribute', { characterid: character.id, name: prefix + attributeName, current: value }); } }