const UPICON = 'icons/magic/life/cross-embers-glow-yellow-purple.webp' const DOWNICON = 'icons/magic/movement/chevrons-down-yellow.webp' let tokens = []; let targets = Array.from(game.user.targets); if (targets.length > 0) { tokens = targets; } else if (canvas.tokens.controlled.length > 0) { tokens = canvas.tokens.controlled; } if (tokens.length > 0) { main(tokens); } else { ui.notifications.error("Please select or target a token"); } async function main(tokens) { let traitOptions = [ "Agility", "Smarts", "Spirit", "Strength", "Vigor" ]; let allSkills = []; let traits = { "Agility": { type: "attribute", name: "Agility", modkey: "data.attributes.agility.die.modifier", diekey: "data.attributes.agility.die.sides" }, "Smarts": { type: "attribute", name: "Smarts", modkey: "data.attributes.smarts.die.modifier", diekey: "data.attributes.smarts.die.sides" }, "Spirit": { type: "attribute", name: "Spirit", modkey: "data.attributes.spirit.die.modifier", diekey: "data.attributes.spirit.die.sides" }, "Strength": { type: "attribute", name: "Strength", modkey: "data.attributes.strength.die.modifier", diekey: "data.attributes.strength.die.sides" }, "Vigor": { type: "attribute", name: "Vigor", modkey: "data.attributes.vigor.die.modifier", diekey: "data.attributes.vigor.die.sides" } }; let tokenList = tokens.map(t => t.name).join(", "); for (const token of tokens) { let skills = token.actor.items.filter(e => e.type == "skill"); for (const skill of skills) { let name = skill.data.name traits[name] = { type: "skill", name: name, modkey: `@Skill{${name}}[data.die.modifier]`, diekey: `@Skill{${name}}[data.die.sides]` }; if (name != 'Unskilled' && !allSkills.find(v => v == name)) { allSkills.push(name); } } } traitOptions = traitOptions.concat(allSkills.sort()); let menuOptions = { title: 'Boost/Lower Trait', defaultButton: "Cancel", options: {} }; let menuData = { inputs: [ {type: 'header', label: 'Boost/Lower Trait'}, {type: 'info', label: `Affected Tokens: ${tokenList}`}, {type: 'select', label: 'Trait', options: traitOptions}, {type: 'info', label: "Boost or Lower?"}, {type: 'radio', label: 'Boost', options: ['boostlower', true]}, {type: 'radio', label: 'Lower', options: ['boostlower', false]}, ], buttons: [ {label: "Apply", value: "apply"}, {label: "Apply with raise", value: "raise"}, {label: "Cancel", value: "cancel"} ] } let {buttons, inputs} = await warpgate.menu(menuData, menuOptions); if (buttons != "cancel") { let trait = inputs[2]; let direction = inputs[4] || inputs[5]; createEffect(tokens, traits[trait], direction, buttons); } } function getDottedPart(obj, key) { let value = obj; for (const part of key.split(".")) { value = value[part]; } return value; } async function createEffect(tokens, trait, direction, buttons) { const raise = (buttons == 'raise'); const effectName = `${raise ? "Major" : "Minor"} ${direction} ${trait.name}`; const effectId = `${raise ? "Major" : "Minor"}${direction}${trait.name}`; const effectIcon = (direction == 'Boost' ? UPICON : DOWNICON) for (const token of tokens) { let tokenDoc = token.document; let currentDie = 0; let currentMod = 0; if (trait.type == 'attribute') { currentDie = getDottedPart(token.actor.data, trait.diekey); currentMod = getDottedPart(token.actor.data, trait.modkey); } else { let skill = token.actor.items.filter(s => s.type == 'skill').find( s => s.data.name == trait.name) if (skill) { currentDie = skill.data.data.die.sides; currentMod = skill.data.data.die.modifier; } else { currentDie = 4; currentMod = -2; } } if (currentDie == 0) { continue; } if (currentDie == 4 && direction == "Lower") { continue; } let diemod = 2; let modmod = 0; if (direction == "Lower") { diemod = -2; } if (currentDie == 6 && direction == "Lower" && raise) { diemod = -1 } else if (currentDie == 12 && direction == "Boost") { diemod = 0; modmod = 1; } if (raise) { diemod *= 2; modmod *= 2; } if (currentDie == 10 && direction == "Boost" && raise) { diemod = 2; modmod = 1; } if (currentDie == 4 && currentMod == -2 && direction == "Boost") { diemod = (raise ? 2 : 0); modmod = 2; } let effectData = { icon: effectIcon, id: effectId, label: effectName, flags: { swade: { expiration: 3, loseTurnOnHold: true } }, changes: [] }; let mode = foundry.CONST.ACTIVE_EFFECT_MODES.ADD; if (diemod != 0) { effectData.changes.push({ key: trait.diekey, mode: mode, value: diemod, priority: 0 }); } if (modmod != 0) { effectData.changes.push({ key: trait.modkey, mode: mode, value: modmod, priority: 0 }); } if (direction == "Boost") { effectData.duration = { rounds: 5 }; } let mutate = { embedded: { ActiveEffect: { } } }; mutate.embedded.ActiveEffect[effectName] = effectData; let mutateOptions = { comparisonKeys: { 'ActiveEffect': 'label' }, name: effectName, permanent: false, description: effectName, } if (trait.type == 'skill' && direction == 'Boost' && !token.actor.items.getName(trait.name)) { let itemmutate = { embedded: { Item: {} } } itemmutate.embedded.Item[trait.name] = { "name": trait.name, "type": "skill", "img": "systems/swade/assets/icons/skills/uncertainty.svg", "data": { "description": "See SWADE. Auto added as untrained for Boost Trait.", "notes": "", "additionalStats": {}, "attribute": trait.attribute, "isCoreSkill": false, "die": { "sides": 4, "modifier": -2 }, "wild-die": { "sides": 6 } } } await warpgate.mutate(token.document, itemmutate, {}, mutateOptions); } mutateOptions.permanent = true; await warpgate.mutate(token.document, mutate, {}, mutateOptions); } }