Thanks, GiGs...I meant to include: setAttrs(sattrs,{silent:true}); Because I'd tried that as well. Silent doesn't seem to be doing anything...or rather it still triggers. Anyway, to get us all on the same page, I modified my code to match the above. To help (I hope), I'm also going to include the HTML portion sheet worker: //Create Trait values for rank and display hidden fields const traits = ['agility','smarts','spirit','strength','vigor','fighting','academicsskill','athletics','battle','boating','climbing','commonknowledge','driving','electronics','faith','focus','gambling','guts','hacking','healing','intimidation','investigation','language','lockpicking','notice','occult','performance','persuasion','piloting','psionics','repair','research','riding','ritual','science','shooting','spellcasting','stealth','streetwise','survival','swimming','taunt','throwing','thievery','tracking','weirdscience','repeating_skills:skillnamerank','pastrength','rastrength']; traits.forEach(trait => { //Should trigger second on(`change:${trait} change:${trait}mod`, function(eventInfo) { console.log(`+++ Change Detected for ${trait} +++`); console.log("Trait triggering update: " + eventInfo.sourceAttribute); //let trait = traits; console.log("trait = "+trait); console.log("Check for repeating: "+trait.slice(0,6)); /*if (trait.slice(0,5) === 'repeating') { traitcheck = `repeating_skills_${traits}`; }*/ trait = trait.replace(':','_'); console.log("trait = "+trait); getAttrs([trait, `${trait}mod`, `${trait}_delta`], v => { console.log("intMod will == "+v[`${trait}mod`]); console.log("Delta will == "+v[`${trait}_delta`]); let tDelta = v[`${trait}_delta`]; tDelta = parseInt(tDelta)||0; let tDie = parseInt(v[trait])||0; console.log(`^^^ Delta: ${tDelta}, tDie: ${tDie} ^^^`); let tRank = '1d'; let tDisplay = (tDelta !== 0 ? "*d" : "d"); let intMod = v[`${trait}mod`]; intMod = parseInt(intMod)||0; tRank += v[trait]+'!'; tDisplay += v[trait]; if(intMod > 0) { console.log("Mod is > 0...use +"); tRank += '+'+intMod+'[trait]'; tDisplay += '+' + intMod; } else if(intMod < 0) { console.log("Mod is < 0...use -"); tRank += intMod+'[trait]'; tDisplay += intMod; } else {console.log("Mod is 0...don't do anything");} const sAttRank = trait + '_rank', sAttDisplay = trait + '_display'; const sattrs = {}; sattrs[sAttRank]=tRank; sattrs[sAttDisplay]=tDisplay; setAttrs(sattrs); }); }); }); //Do the Delta thing here... //Set the actual trait and trait mod fields...this is needed so things like Vigor and Parry update if the appropriate trait is boosted/lowered traits.forEach(trait => { on(`change:${trait}_delta`, function(eventInfo) { //let trait = traits; console.log(`^o^ Delta changed for ${trait} ^o^`); trait = trait.replace(':','_'); console.log("trait = "+trait); getAttrs([trait, `${trait}_old`, `${trait}mod`, `${trait}_delta`], v => { let tRank = parseInt(v[`${trait}`])||0; let tMod = parseInt(v[`${trait}mod`])||0; let tDelta = parseInt(v[`${trait}_delta`])||0; let newtDelta, oldRankVal; (tDelta === 0 ? newtDelta = tDelta - eventInfo.previousValue : newtDelta = tDelta); console.log(`^^^ ${trait} Rank: ${tRank}, Mod: ${tMod}, Delta: ${tDelta}, New Delta: ${newtDelta} ^^^`); //Is the Delta something other than 0 if(newtDelta !== 0) { let counter = Math.abs(newtDelta); if(newtDelta > 0) { //Increase console.log(`--- Increasing ${trait} ---`); if(tRank === 4 && tMod === -2) { //Check if skill is unskilled //unskilled counter = counter - 1; tMod = 0; } for(let i = counter; i > 0; i--) { if(tRank === 12) { tMod += 1; } else { tRank += 2; } } } else { //Decrease console.log(`--- Decreasing ${trait} ---`); if(tRank === 4) { //Rank is already 4 before decreasing...don't allow this tDelta = 0; } else { //Rank was not 4 before decreasing... for(let i = counter; i > 0; i--) { if(tRank === 4) { console.log(`Die Type is already 4, can't go any lower`); tRank = tRank; } else { if(tRank === 12 && tMod > 0) { //Something like Professional or Expert Edge, need to reduce positive modifier first tMod = tMod - 1; } else { //No positive modifier on rank, so reduce the die type tRank = tRank - 2; } } } } } } const sAttRank = trait, sAttMod = trait + 'mod', sAttDelta = `${trait}_delta`; let sattrs = {}; sattrs[sAttDelta]=tDelta; setAttrs(sattrs,{silent:true}); sattrs = {}; sattrs[sAttRank]=tRank; sattrs[sAttMod]=tMod; setAttrs(sattrs); }); }); }); There are actually two sheet workers which use the same const array. I bolded the key lines where each start, just to make it a little clearer to see. The first array (the one that would trigger most frequently) doesn't key off a change to delta, and the second only triggers on a change to delta. In the second, that's also why I do two setAttrs calls, so I can set silently the delta trait before triggering the other changes that would trigger the first sheet worker. And here's the HTML: <div class='sheet-basetrait'>
<input class="sheet-deltabutton" type="hidden" name="attr_agility_delta" value="0" />
<button class="sheet-traitbutton sheet-traitflexattbutton" type='roll' name='roll_tAgilityRoll' title="@{tAgilityRoll}" value='@{agilitybutton}' data-i18n="agility"> Agility</button>
<input type="hidden" name="attr_agilitybutton" value="@{whisperagility} &{template:roll} @{rolltAgility} @{setmodsenc} @{showtraitmods}" />
<span><select name="attr_agility" title="@{agility}" class="sheet-traitdie" value="4">
<option value="4">d4</option>
<option value="6">d6</option>
<option value="8">d8</option>
<option value="10">d10</option>
<option value="12">d12</option>
</select>+</span>
<input class="sheet-otraitmod" type="number" name="attr_agilitymod" title="@{agilitymod}" value="0" />
<input class='sheet-moremods' type='checkbox' name='attr_showAgMod' /><span>y</span>
<div class='sheet-basetrait sheet-traitmodplus'>
<hr class="sheet-modsection">
<input class='sheet-whispercheck' type='checkbox' value='/w gm' name='attr_whisperagility' /> <span class='sheet-ModDesc' data-i18n="whisper-roll-to-gm">Whisper roll to GM</span><br />
<select name="attr_agwilddie" title="@{agwilddie}" class="sheet-traitdie" value="6">
<option value="4">d4</option>
<option value="6">d6</option>
<option value="8">d8</option>
<option value="10">d10</option>
<option value="12">d12</option>
</select>
<input type='text' class='sheet-ModDesc' name='attr_agwilddieinfo' placeholder='Standard Wild Die die type' data-i18n-placeholder="standard-wild-die-type" /><br />
<hr class="sheet-modsection">
<input type='text' class='sheet-ModDesc' name='attr_agModType' placeholder='Modifier due to edges, hindrances, etc' data-i18n-placeholder="att-mod-description" />
+<input class="sheet-otraitmod" type="number" name="attr_agrollMod" title="@{agrollMod}" value="0" /><br />
<label class="sheet-delta" data-i18n="die-step-delta">Increase/Decrease die type by number of steps</label><input class="sheet-otraitmod" style="border-bottom: 1px solid black;" type="number" name="attr_agility_delta" title="@{agility_delta}" value="0" />
</div>
<input type='hidden' name='attr_agility_rank' value='1d4!' />
<input type='hidden' name='attr_agility_display' value='1d4' />
<input type='hidden' name='attr_rolltAgility' value='{{name=@{character_name} }} {{trait=^{agility}}} {{skill_rank=@{agility_display} }} {{skill_roll=[[@{agility_rank} + @{agrollMod}+ @{ModSumEnc}[Wounds, Fatigue, Encumbrance, Distracted] + @{traitmods} ]] }} {{mook=[[1d0+@{wdNum}]]}} {{wild_die=[[ 1d@{agwilddie}! + @{agilitymod} + @{agrollMod} + @{ModSumEnc}[Wounds, Fatigue, Encumbrance] + @{traitmods} ]]}}' />
</div>