From 747cdd20e51acee05f77238da29ba3f57296613a Mon Sep 17 00:00:00 2001 From: Kenneth B Date: Thu, 8 Jun 2023 17:32:08 +0200 Subject: [PATCH] v11 updates --- classes/bard/eloquence.js | 10 +- classes/cleric/eyes_of_night.js | 2 +- classes/cleric/harness_divine_power.js | 14 +-- classes/cleric/steps_of_night.js | 12 +- classes/cleric/twilight_sanctuary.js | 22 ++-- .../channel_divinity_burning_weapon.js | 86 +++++++-------- .../channel_divinity_harness_divine_power.js | 4 +- .../channel_divinity_warming_respite.js | 20 ++-- classes/paladin/divine_smite.js | 6 +- classes/paladin/lay_on_hands.js | 6 +- classes/rogue/sneak_attack.js | 19 ---- classes/sorcerer/font_of_magic.js | 14 +-- classes/warlock/healing_light.js | 16 +-- classes/warlock/summon_tentacle.js | 28 ----- classes/wizard/arcane_recovery.js | 18 +-- classes/wizard/bladesong.js | 37 +++---- classes/wizard/scroll_to_spell.js | 22 ++-- classes/wizard/song_of_defense.js | 16 +-- dnd5e_features/hook_dual_wielder.js | 10 +- dnd5e_spells/flaming_sphere.js | 31 ------ dnd5e_spells/magic_missile.js | 4 +- dnd5e_spells/rainbow_recurve.js | 104 ------------------ dnd5e_spells/shield_spell.js | 24 ---- dnd5e_spells/toll_the_dead.js | 10 -- dnd5e_utils/adjust_resources_dialog.js | 19 ++-- dnd5e_utils/grant_temphp_to_selected.js | 27 ++--- dnd5e_utils/loot_creator.js | 34 ++---- dnd5e_utils/packAttack.js | 20 ++-- ...roll_d20roll_with_custom_floor_and_ceil.js | 10 +- dnd5e_utils/token_save_and_apply_damage.js | 8 +- misc/combat_dice_for_2d20_system.js | 57 +++++----- misc/cyberpunk_red_damage_rolls.js | 12 +- misc/cyberpunk_red_grant_currency.js | 11 +- misc/tor2e_request_skill.js | 69 ++++++------ tools/image_migration_tool.js | 6 +- tools/image_migration_tool_PREPEND.js | 2 +- tools/itemUpdater.js | 8 +- tools/scene/pan_to_object.js | 7 +- tools/scene/scene_picker.js | 9 +- tools/scene/scene_picker_player.js | 15 +-- tools/scene/toggle_aura_template.js | 12 +- tools/show_image_from_url.js | 21 ++-- .../add_table_results_to_selected_token.js | 4 +- .../table/create_rolltable_from_compendium.js | 2 +- tools/token/configure_all_selected_tokens.js | 4 +- tools/token/cycle_token_wildcards.js | 2 +- tools/token/pan_to_current_combatant.js | 2 +- tools/update_all_scene_lights.js | 4 +- tools/walls/wall_toggler.js | 8 +- tools/walls/walls_to_terrain_walls.js | 6 +- tools/whisper_players.js | 40 +++---- 51 files changed, 360 insertions(+), 594 deletions(-) delete mode 100644 classes/rogue/sneak_attack.js delete mode 100644 classes/warlock/summon_tentacle.js delete mode 100644 dnd5e_spells/flaming_sphere.js delete mode 100644 dnd5e_spells/rainbow_recurve.js delete mode 100644 dnd5e_spells/shield_spell.js delete mode 100644 dnd5e_spells/toll_the_dead.js diff --git a/classes/bard/eloquence.js b/classes/bard/eloquence.js index 8adbb7b..e5e4948 100644 --- a/classes/bard/eloquence.js +++ b/classes/bard/eloquence.js @@ -1,9 +1,7 @@ // Eloquence // required modules: none -const a = token?.actor ?? game.user.character; -if(a) return ui.notifications.warn("You must have a selected token or assigned actor."); - +if (!actor) return ui.notifications.warn("You must have a selected token or assigned actor."); return Dialog.prompt({ title: "Eloquence", content: ` @@ -12,8 +10,8 @@ return Dialog.prompt({
@@ -22,6 +20,6 @@ return Dialog.prompt({ label: "Roll", callback: async (html) => { const skill = html[0].querySelector("select").value; - return a.rollSkill(skill, {reliableTalent: true, event}); + return actor.rollSkill(skill, {reliableTalent: true, event}); } }); diff --git a/classes/cleric/eyes_of_night.js b/classes/cleric/eyes_of_night.js index c8bb170..d03430e 100644 --- a/classes/cleric/eyes_of_night.js +++ b/classes/cleric/eyes_of_night.js @@ -23,6 +23,6 @@ const options = { const use = await item.use(); if (!use) return; -for(const target of targets) { +for (const target of targets) { await warpgate.mutate(target.document, updates, {}, options); } diff --git a/classes/cleric/harness_divine_power.js b/classes/cleric/harness_divine_power.js index addaaa6..b61a7ef 100644 --- a/classes/cleric/harness_divine_power.js +++ b/classes/cleric/harness_divine_power.js @@ -2,24 +2,24 @@ // required modules: itemacro const data = actor.getRollData(); -const maxLevel = Math.ceil(data.attributes.prof/2); +const maxLevel = Math.ceil(data.attributes.prof / 2); const validLevels = []; -for(let i = 1; i <= maxLevel; i++){ +for (let i = 1; i <= maxLevel; i++) { const key = `spell${i}`; - if( (data.spells[key].value < data.spells[key].max) && (data.spells[key].max > 0) ){ + if ((data.spells[key].value < data.spells[key].max) && (data.spells[key].max > 0)) { validLevels.push(key); } } const pact = data.spells.pact; -if( (pact.max > 0) && (pact.level <= maxLevel) && (pact.value < pact.max) ) validLevels.push("pact"); +if ((pact.max > 0) && (pact.level <= maxLevel) && (pact.value < pact.max)) validLevels.push("pact"); // bail out if you are not missing any spell slots or if the item has no remaining uses (limited uses and resources). -if(!validLevels.length) return ui.notifications.warn("You are not missing any valid spell slots."); -if(!item.system.uses.value) return ui.notifications.warn("You have no uses left of Harness Divine Power."); +if (!validLevels.length) return ui.notifications.warn("You are not missing any valid spell slots."); +if (!item.system.uses.value) return ui.notifications.warn("You have no uses left of Harness Divine Power."); const resourceItem = actor.items.get(item.system.consume.target); const uses = resourceItem.system.uses; -if(!uses.value) return ui.notifications.warn("You have no uses of Channel Divinity left."); +if (!uses.value) return ui.notifications.warn("You have no uses of Channel Divinity left."); // define dialog contents const options = validLevels.reduce((acc, e) => { diff --git a/classes/cleric/steps_of_night.js b/classes/cleric/steps_of_night.js index cac9249..256d136 100644 --- a/classes/cleric/steps_of_night.js +++ b/classes/cleric/steps_of_night.js @@ -2,13 +2,13 @@ // required modules: itemacro const id = item.name.slugify({strict: true}); -const effect = actor.effects.find(e => e.flags.core?.statusId === id); +const effect = actor.effects.find(e => e.statuses.has(id)); if (effect) return effect.delete(); const use = await item.use(); if (!use) return; return actor.createEmbeddedDocuments("ActiveEffect", [{ - label: item.name, + name: item.name, changes: [{ key: "system.attributes.movement.fly", mode: CONST.ACTIVE_EFFECT_MODES.UPGRADE, @@ -16,9 +16,7 @@ return actor.createEmbeddedDocuments("ActiveEffect", [{ }], duration: {seconds: 60}, icon: item.img, - "flags.core.statusId": id, - "flags.visual-active-effects.data": { - intro: "

You have a flying speed equal to your walking speed.

", - content: item.system.description.value - } + statuses: [id], + description: "

You have a flying speed equal to your walking speed.

", + "flags.visual-active-effects.data.content": item.system.description.value }]); diff --git a/classes/cleric/twilight_sanctuary.js b/classes/cleric/twilight_sanctuary.js index 608f03d..22339f2 100644 --- a/classes/cleric/twilight_sanctuary.js +++ b/classes/cleric/twilight_sanctuary.js @@ -9,20 +9,20 @@ const target = game.user.targets.first(); // find Sequencer effect const [effect] = Sequencer.EffectManager.getEffects({name: item.name}); -if(!effect){ +if (!effect) { const use = await item.use(); if (!use) return; return new Sequence() .effect() - .attachTo(token) - .persist() - .name(item.name) - .file(file) - .size(canvas.grid.size * 8) - .scaleIn(0, 800, {ease: "easeOutCubic"}) - .rotateIn(180, 1200, {ease: "easeOutCubic"}) - .scaleOut(0, 500, {ease: "easeOutCubic"}) - .fadeOut(500, {ease: "easeOutCubic"}); + .attachTo(token) + .persist() + .name(item.name) + .file(file) + .size(canvas.grid.size * 8) + .scaleIn(0, 800, {ease: "easeOutCubic"}) + .rotateIn(180, 1200, {ease: "easeOutCubic"}) + .scaleOut(0, 500, {ease: "easeOutCubic"}) + .fadeOut(500, {ease: "easeOutCubic"}) .play(); } @@ -40,7 +40,7 @@ new Dialog({ const temp = target.actor.system.attributes.hp.temp ?? 0; const updates = {actor: {"system.attributes.hp.temp": total}}; const config = {permanent: true, name: item.name, description: `You are being granted ${total} temporary hit points.`}; - if(total > temp) return warpgate.mutate(target.document, updates, {}, config); + if (total > temp) return warpgate.mutate(target.document, updates, {}, config); } }, effect: { diff --git a/classes/paladin/channel_divinity_burning_weapon.js b/classes/paladin/channel_divinity_burning_weapon.js index 8027eb7..949bcdd 100644 --- a/classes/paladin/channel_divinity_burning_weapon.js +++ b/classes/paladin/channel_divinity_burning_weapon.js @@ -11,50 +11,50 @@ const name = "Burning Weapon"; const hasMutation = warpgate.mutationStack(token.document).getName(name); -if ( hasMutation ) return warpgate.revert(token.document, name); +if (hasMutation) return warpgate.revert(token.document, name); const weapons = actor.itemTypes.weapon.filter(i => i.system.equipped); -if ( !weapons.length ) return ui.notifications.warn("You have no equipped weapons."); +if (!weapons.length) return ui.notifications.warn("You have no equipped weapons."); const use = await item.use(); -if ( !use ) return; +if (!use) return; // mutation function: -async function mutate(weaponId){ - const weapon = actor.items.get(weaponId); - if ( !weapon ) return; - const mod = Math.max(1, actor.system.abilities.cha.mod); - const damageParts = weapon.system.damage.parts; - damageParts[0][0] = `${damageParts[0][0]} + ${mod}[fire]`; - - await warpgate.mutate(token.document, { - token: { - light: { - bright: 20, - dim: 40, - color: "#e05d06", - "animation.type": "torch" - } - }, - embedded: { - Item: { - [weaponId]: { - system: { - "properties.mgc": true, - "damage.parts": damageParts - } - } - } +async function mutate(weaponId) { + const weapon = actor.items.get(weaponId); + if (!weapon) return; + const mod = Math.max(1, actor.system.abilities.cha.mod); + const damageParts = weapon.system.damage.parts; + damageParts[0][0] = `${damageParts[0][0]} + ${mod}[fire]`; + + await warpgate.mutate(token.document, { + token: { + light: { + bright: 20, + dim: 40, + color: "#e05d06", + "animation.type": "torch" + } + }, + embedded: { + Item: { + [weaponId]: { + system: { + "properties.mgc": true, + "damage.parts": damageParts + } } - }, {}, { name, comparisonKeys: { Item: "_id" } }); + } + } + }, {}, {name, comparisonKeys: {Item: "_id"}}); } /* exactly one weapon */ -if ( weapons.length === 1 ) return mutate(weapons[0].id); +if (weapons.length === 1) return mutate(weapons[0].id); /* multiple weapons */ -const weaponSelect = weapons.reduce((acc, { id, name }) => { - return acc + ``; +const weaponSelect = weapons.reduce((acc, {id, name}) => { + return acc + ``; }, ""); const content = `

Pick your weapon for ${name}.

@@ -68,16 +68,16 @@ const content = ` `; new Dialog({ - title: name, - content, - buttons: { - go: { - icon: "", - label: "Flame On!", - callback: (html) => { - const id = html[0].querySelector("#wpn").value; - return mutate(id); - } - } + title: name, + content, + buttons: { + go: { + icon: "", + label: "Flame On!", + callback: (html) => { + const id = html[0].querySelector("#wpn").value; + return mutate(id); + } } + } }).render(true); diff --git a/classes/paladin/channel_divinity_harness_divine_power.js b/classes/paladin/channel_divinity_harness_divine_power.js index 1b1df2b..f97561c 100644 --- a/classes/paladin/channel_divinity_harness_divine_power.js +++ b/classes/paladin/channel_divinity_harness_divine_power.js @@ -1,12 +1,12 @@ // HARNESS DIVINE POWER // required modules: itemacro -// setup: +// setup: // (1) Place macro in Item Macro on the Harness Divine Power feature. // (2) Have Harness Divine Power set up with Limited Uses. // (3) Have Harness Divine Power consume Limited Uses of a Channel Divinity item. const {spells, attributes: {prof}} = foundry.utils.duplicate(actor.system); -const maxLevel = Math.ceil(prof/2); +const maxLevel = Math.ceil(prof / 2); const validLevels = []; for (let i = 1; i <= maxLevel; i++) { const key = `spell${i}`; diff --git a/classes/paladin/channel_divinity_warming_respite.js b/classes/paladin/channel_divinity_warming_respite.js index acdf7a4..467cd15 100644 --- a/classes/paladin/channel_divinity_warming_respite.js +++ b/classes/paladin/channel_divinity_warming_respite.js @@ -2,16 +2,16 @@ // required modules: itemacro, warpgate const use = await item.use(); -if ( !use ) return; +if (!use) return; -const { levels } = actor.getRollData().classes.paladin; -const updates = { actor: { "system.attributes.hp.temp": levels } } +const levels = actor.classes.paladin.system.levels; +const updates = {actor: {"system.attributes.hp.temp": levels}}; const options = { - permanent: true, - description: `${actor.name} is granting you ${levels} temporary hit points.` + permanent: true, + description: `${actor.name} is granting you ${levels} temporary hit points.` +}; +for (const target of game.user.targets) { + if (target.actor.system.attributes.hp.temp < levels) { + await warpgate.mutate(target.document, updates, {}, options); + } } -Array.from(game.user.targets).filter(target => { - return target.actor.system.attributes.hp.temp < levels; -}).map(target => { - return warpgate.mutate(target.document, updates, {}, options); -}); diff --git a/classes/paladin/divine_smite.js b/classes/paladin/divine_smite.js index b88677a..4de9927 100644 --- a/classes/paladin/divine_smite.js +++ b/classes/paladin/divine_smite.js @@ -8,7 +8,7 @@ const inputs = Object.entries(data.spells).filter(([key, values]) => { const crd = key === "pact" ? "Pact Slot" : CONFIG.DND5E.spellLevels[key.at(-1)]; return [key, crd, values]; }); -if(!inputs.length) return ui.notifications.warn("You have no spell slots remaining."); +if (!inputs.length) return ui.notifications.warn("You have no spell slots remaining."); const options = inputs.reduce((acc, [key, crd, values]) => { return acc + ``; @@ -38,7 +38,7 @@ return new Dialog({ } }).render(true); -async function rollDamage(html){ +async function rollDamage(html) { const slot = html[0].querySelector("select").value; const extra = html[0].querySelector("#smite-extra-die").checked; const level = slot === "pact" ? data.spells["pact"].level : Number(slot.at(-1)); @@ -52,7 +52,7 @@ async function rollDamage(html){ } }, {parent: actor}); const roll = await feature.rollDamage({event}); - if(!roll) return; + if (!roll) return; const value = data.spells[slot].value - 1; return actor.update({[`system.spells.${slot}.value`]: value}); } diff --git a/classes/paladin/lay_on_hands.js b/classes/paladin/lay_on_hands.js index 0b61243..0c7d359 100644 --- a/classes/paladin/lay_on_hands.js +++ b/classes/paladin/lay_on_hands.js @@ -2,7 +2,7 @@ // required modules: itemacro const uses = item.system.uses; -if(!uses.value) return ui.notifications.warn(`${item.name} has no uses left.`); +if (!uses.value) return ui.notifications.warn(`${item.name} has no uses left.`); const content = `

Lay on Hands has ${uses.value} uses left.

@@ -21,13 +21,13 @@ const buttons = { label: "Heal!", callback: async (html) => { const number = Number(html[0].querySelector("#num").value); - if(!number.between(1, uses.value)) return ui.notifications.warn("Invalid number."); + if (!number.between(1, uses.value)) return ui.notifications.warn("Invalid number."); await new Roll(`${number}`).toMessage({speaker, flavor: item.name}); return item.update({"system.uses.value": uses.value - number}); } } }; -if(uses.value >= 5){ +if (uses.value >= 5) { buttons.cure = { icon: "", label: "Cure!", diff --git a/classes/rogue/sneak_attack.js b/classes/rogue/sneak_attack.js deleted file mode 100644 index eabffd5..0000000 --- a/classes/rogue/sneak_attack.js +++ /dev/null @@ -1,19 +0,0 @@ -// Sneak Attack -// Required modules: itemacro - -const id = "sneak-attack"; -const effect = actor.effects.find(e => e.flags.core?.statusId === id); -if(effect) return effect.delete(); - -const mode = CONST.ACTIVE_EFFECT_MODES.ADD; -const value = `@scale.rogue.${id}`; -const changes = ["mwak", "rwak"].map(wak => { - return {key: `system.bonuses.${wak}.damage`, mode, value}; -}); - -return actor.createEmbeddedDocuments("ActiveEffect", [{ - changes, - icon: item.img, - label: item.name, - "flags.core.statusId": id -}]); diff --git a/classes/sorcerer/font_of_magic.js b/classes/sorcerer/font_of_magic.js index ca109e5..9af516a 100644 --- a/classes/sorcerer/font_of_magic.js +++ b/classes/sorcerer/font_of_magic.js @@ -3,7 +3,7 @@ // setup: embed macro in item with limited uses acting as sorcery points. // Map of slot level to point cost. -const conversionMap = { 1: 2, 2: 3, 3: 5, 4: 6, 5: 7 }; +const conversionMap = {1: 2, 2: 3, 3: 5, 4: 6, 5: 7}; const style = ` `; const spellPoints = item.system.uses; -const spellSlots = { ...actor.system.spells }; +const spellSlots = {...actor.system.spells}; // array of spell levels for converting points to slots. const validLevelsWithSpentSpellSlots = Object.entries(spellSlots).filter(([key, entry]) => { @@ -53,7 +53,7 @@ if (canConvertPointsToSlot) buttons["pointToSlot"] = { label: "Convert sorcery points to a spell slot", callback: pointsToSlot } -new Dialog({ title: item.name, buttons }).render(true); +new Dialog({title: item.name, buttons}).render(true); // Convert spell slot to sorcery points. async function slotToPoints() { @@ -81,10 +81,10 @@ async function slotToPoints() { }); if (!retKey) return null; - await actor.update({ [`system.spells.${retKey}.value`]: spellSlots[retKey].value - 1 }); + await actor.update({[`system.spells.${retKey}.value`]: spellSlots[retKey].value - 1}); const level = retKey === "pact" ? spellSlots["pact"].level : retKey.at(-1); const newPointsValue = Math.clamped(spellPoints.value + Number(level), 0, spellPoints.max); - await item.update({ "system.uses.value": newPointsValue }); + await item.update({"system.uses.value": newPointsValue}); return ChatMessage.create({ speaker, content: `${actor.name} regained ${newPointsValue - spellPoints.value} sorcery points.` @@ -115,9 +115,9 @@ async function pointsToSlot() { }); if (!retKey) return null; - await actor.update({ [`system.spells.${retKey}.value`]: spellSlots[retKey].value + 1 }); + await actor.update({[`system.spells.${retKey}.value`]: spellSlots[retKey].value + 1}); const level = retKey === "pact" ? spellSlots["pact"].level : retKey.at(-1); - await item.update({ "system.uses.value": Math.clamped(spellPoints.value - conversionMap[level], 0, spellPoints.max) }); + await item.update({"system.uses.value": Math.clamped(spellPoints.value - conversionMap[level], 0, spellPoints.max)}); const str = retKey === "pact" ? "Pact Slot" : `${CONFIG.DND5E.spellLevels[level]} spell slot`; return ChatMessage.create({ speaker, diff --git a/classes/warlock/healing_light.js b/classes/warlock/healing_light.js index aa55ae9..0613da5 100644 --- a/classes/warlock/healing_light.js +++ b/classes/warlock/healing_light.js @@ -1,18 +1,18 @@ // Healing Light // required modules: itemacro -const { value, max } = item.system.uses; -const { mod } = actor.system.abilities.cha; // maximum you can spend at once +const {value, max} = item.system.uses; +const {mod} = actor.system.abilities.cha; // maximum you can spend at once const die = "d6"; // die size const target = game.user.targets.first(); -if ( value < 1 ) return item.use({}, { configureDialog: false }); +if (value < 1) return item.use({}, {configureDialog: false}); -const options = Array.fromRange(Math.min(mod,value)).reduce((acc, e) => { - return acc += ``; +const options = Array.fromRange(Math.min(mod, value), 1).reduce((acc, e) => { + return acc += ``; }, ""); const content = ` -
+
@@ -33,9 +33,9 @@ new Dialog({ const spending = html[0].querySelector("#spend").value; await new Roll(`${spending}${die}`).toMessage({ flavor: `${actor.name} uses ${item.name} ${targetAppend}`, - speaker: ChatMessage.getSpeaker({ actor }) + speaker }); - await item.update({ "system.uses.value": value - spending }); + return item.update({"system.uses.value": value - spending}); } } } diff --git a/classes/warlock/summon_tentacle.js b/classes/warlock/summon_tentacle.js deleted file mode 100644 index 836601e..0000000 --- a/classes/warlock/summon_tentacle.js +++ /dev/null @@ -1,28 +0,0 @@ -// Fathomless Warlock, spawn Fathomless Tentacle. -// if spawned actor already exists, show this item's chat card, otherwise summon. -// required modules: warpgate, itemacro. - -// check if spawn already exists, and if it does, use this item. -const spawn = canvas.scene.tokens.find(t => t.actor.getFlag("world", "fathomless-tentacle") === actor.id); -if ( spawn ) return item.displayCard(); - -// if spawn does not exist, cast the spell / use the feature: -const use = await item.use(); -if ( !use ) return; - -// updates to the spawn's token, actor, and items: -const updates = { - token: { - name: `${game.user.charname}'s Fathomless Tentacle`, - displayName: CONST.TOKEN_DISPLAY_MODES.HOVER, - displayBars: CONST.TOKEN_DISPLAY_MODES.NONE - }, - actor: { - "flags.world.fathomless-tentacle": actor.id - } -} - -// now spawn the actor: -await actor.sheet.minimize(); -await warpgate.spawn("Fathomless Tentacle", updates); -await actor.sheet.maximize(); diff --git a/classes/wizard/arcane_recovery.js b/classes/wizard/arcane_recovery.js index f213ecb..9734fce 100644 --- a/classes/wizard/arcane_recovery.js +++ b/classes/wizard/arcane_recovery.js @@ -2,8 +2,8 @@ // required modules: itemacro // get spell object and wizard levels. -const levels = actor.getRollData().classes.wizard.levels; -const spells = foundry.utils.duplicate(actor.system.spells); +const levels = actor.classes.wizard.system.levels; +const spells = foundry.utils.deepClone(actor.system.spells); // bail out if you can't use this item again. const available = item.system.uses.value > 0; @@ -13,9 +13,9 @@ if (!available) { } // attained spell levels, then mapping to value and max. -const level_maps = Array.fromRange(6).reduce((acc, i) => { - const s = spells[`spell${i+1}`]; - if(s.max > 0) acc.push(s); +const level_maps = Array.fromRange(6, 1).reduce((acc, i) => { + const s = spells[`spell${i}`]; + if (s.max > 0) acc.push(s); return acc; }, []); @@ -60,17 +60,17 @@ const dialog = new Dialog({ return dialog.render(true); } for (let i = 0; i < 9; i++) { - const selector = `input[name=level${i+1}]:checked`; + const selector = `input[name=level${i + 1}]:checked`; const val = html[0].querySelectorAll(selector).length; - spells[`spell${i+1}`].value = val; - } + spells[`spell${i + 1}`].value = val; + } await actor.update({"system.spells": spells}); ui.notifications.info("Spell slots recovered!"); } } }, render: (html) => { - html[0].addEventListener("change", function () { + html[0].addEventListener("change", function() { const selector = "input:checked:not(:disabled)"; const inputs = html[0].querySelectorAll(selector); spent = Array.from(inputs).reduce((acc, node) => { diff --git a/classes/wizard/bladesong.js b/classes/wizard/bladesong.js index 672a6f8..094d538 100644 --- a/classes/wizard/bladesong.js +++ b/classes/wizard/bladesong.js @@ -2,36 +2,29 @@ // required modules: itemacro. // added benefit with concentrationnotifier and visual-active-effects. -const effect = actor.effects.find(e => e.getFlag("world", "bladesong") === actor.id); -if ( effect ) return effect.delete(); +const effect = actor.effects.find(e => e.statuses.has("bladesong")); +if (effect) return effect.delete(); -const { - abilities: { int: { mod } }, - classes: { wizard: { levels } } -} = actor.getRollData(); -const {ADD} = CONST.ACTIVE_EFFECT_MODES; +const mode = CONST.ACTIVE_EFFECT_MODES.ADD; +const value = actor.system.abilities.int.mod; const changes = [ - { key: "system.attributes.ac.bonus", mode: ADD, value: mod }, - { key: "system.attributes.movement.walk", mode: ADD, value: 10 }, - { key: "flags.dnd5e.concentrationBonus", mode: ADD, value: `+${mod}` } + {key: "system.attributes.ac.bonus", mode, value}, + {key: "system.attributes.movement.walk", mode, value: 10}, + {key: "flags.dnd5e.concentrationBonus", mode, value: `+${value}`} ]; -let mwakBonus = ""; -if ( levels >= 14 ) { - changes.push({ key: "system.bonuses.mwak.damage", mode: ADD, value: `+${mod}` }); - mwakBonus = ` you add +${mod} to melee weapon damage,`; +if (actor.classes.wizard.system.levels >= 14) { + changes.push({key: "system.bonuses.mwak.damage", mode, value: `+${value}`}); } const use = await item.use(); -if ( !use ) return; +if (!use) return; return actor.createEmbeddedDocuments("ActiveEffect", [{ changes, icon: item.img, - label: item.name, - duration: { seconds: 60 }, - "flags.world.bladesong": actor.id, - "flags.visual-active-effects.data": { - intro: "

You are under the effects of Bladesong.

", - content: `

You have +${mod} to your AC, +10ft of movement speed, advantage on Acrobatics checks,${mwakBonus} and you have a +${mod} bonus to saving throws to maintain concentration.

` - } + name: item.name, + duration: {seconds: 60}, + statuses: ["bladesong"], + description: "

You are under the effects of Bladesong.

", + "flags.visual-active-effects.data.content": `

... text goes here ...

` }]); diff --git a/classes/wizard/scroll_to_spell.js b/classes/wizard/scroll_to_spell.js index 98e2108..06921be 100644 --- a/classes/wizard/scroll_to_spell.js +++ b/classes/wizard/scroll_to_spell.js @@ -5,22 +5,22 @@ const key = "..."; // key for the pack where you find the spell. const wizard = game.actors.getName("Name of the wizard"); const scrolls = wizard.itemTypes.consumable.filter(item => { - if ( item.system.consumableType !== "scroll" ) return false; + if (item.system.consumableType !== "scroll") return false; return item.name.startsWith("Spell Scroll:"); }); -if ( !scrolls.length ) { +if (!scrolls.length) { ui.notifications.info("Actor has no scrolls."); return; } -const options = scrolls.reduce((acc, { id, name }) => { +const options = scrolls.reduce((acc, {id, name}) => { return acc += ``; }, ""); const content = ` - +
- +
- +
`; @@ -30,12 +30,10 @@ return Dialog.prompt({ rejectClose: false, label: "Create Spell", callback: async (html) => { - const scrollId = html[0].querySelector("#item-select").value; - const scroll = wizard.items.get(scrollId); + const scroll = wizard.items.get(html[0].querySelector("select").value); const pack = game.packs.get(key); - const spellId = pack.index.getName(scroll.name.replace("Spell Scroll: ", ""))._id; - const spell = pack.getDocument(spellId); - const createdSpell = await wizard.createEmbeddedDocuments("Item", [spell.toObject()]); - if ( createdSpell.length > 0 ) return scroll.delete(); + const spell = await pack.getDocument(pack.index.getName(scroll.name.replace("Spell Scroll: ", ""))._id); + const [createdSpell] = await wizard.createEmbeddedDocuments("Item", [spell.toObject()]); + if (createdSpell) return scroll.delete(); } }); diff --git a/classes/wizard/song_of_defense.js b/classes/wizard/song_of_defense.js index 95f1b81..29b8dc2 100644 --- a/classes/wizard/song_of_defense.js +++ b/classes/wizard/song_of_defense.js @@ -9,11 +9,11 @@ const style = ` } `; -const spells = foundry.utils.duplicate(actor.system.spells); +const spells = foundry.utils.deepClone(actor.system.spells); // levels with unspent spell slots. const availableSlots = Object.entries(spells).filter(([key, level]) => { - return (level.value > 0 && level.max > 0); + return (level.value > 0) && (level.max > 0); }); if (!availableSlots.length) { ui.notifications.warn("You have no spell slots remaining."); @@ -21,10 +21,10 @@ if (!availableSlots.length) { } const buttons = availableSlots.reduce((acc, [key]) => { - const level = key === "pact" ? spells[key].level : key.at(-1); - const label = key === "pact" ? "Pact Slot" : CONFIG.DND5E.spellLevels[level]; + const level = (key === "pact") ? spells[key].level : key.at(-1); + const label = (key === "pact") ? "Pact Slot" : CONFIG.DND5E.spellLevels[level]; const callback = async () => spendSlot(key, level); - acc[key] = { label, callback }; + acc[key] = {label, callback}; return acc; }, {}); new Dialog({ @@ -34,11 +34,13 @@ new Dialog({ }).render(true); // spend spell slot to reduce by 5 * level -async function spendSlot(key, level) { +async function spendSlot(html, event) { + const key = event.currentTarget.dataset.button; + const level = (key === "pact") ? spells[key].level : key.at(-1); spells[key].value--; await actor.update({"system.spells": spells}); return ChatMessage.create({ content: `${actor.name} reduced the incoming damage by up to ${Number(level) * 5}.`, - speaker: ChatMessage.getSpeaker({actor}) + speaker }); } diff --git a/dnd5e_features/hook_dual_wielder.js b/dnd5e_features/hook_dual_wielder.js index 886ca36..365a166 100644 --- a/dnd5e_features/hook_dual_wielder.js +++ b/dnd5e_features/hook_dual_wielder.js @@ -16,21 +16,21 @@ Hooks.once("ready", () => { const update_feature = async (item, ...rest) => { // Only do this for one user; the one doing the update. if (game.user.id !== rest.at(-1)) return; - + // Must be an owned item and owner must have 'Dual-Wielder' special trait. const dualWielder = item.actor?.getFlag("dnd5e", "dualWielder"); if (!dualWielder) return; - + // Get equipped weapons and Dual Wielder effect. const equipped = item.actor.itemTypes.weapon.filter(weapon => weapon.system.equipped); const effect = item.actor.effects.find(e => e.getFlag("world", "dual-wielder")); - + // If not exactly two weapons equipped, delete the effect if it exists. if (equipped.length !== 2) return effect?.delete(); - + // If effect already active, do nothing. if (effect) return; - + // If exactly two weapons equipped, create the effect. return item.actor.createEmbeddedDocuments("ActiveEffect", [{ icon: "icons/skills/melee/weapons-crossed-swords-white-blue.webp", diff --git a/dnd5e_spells/flaming_sphere.js b/dnd5e_spells/flaming_sphere.js deleted file mode 100644 index 462ded8..0000000 --- a/dnd5e_spells/flaming_sphere.js +++ /dev/null @@ -1,31 +0,0 @@ -// FLAMING SPHERE -// Spawns a flaming sphere, and allows each subsequent use while concentrating -// on the spell to redisplay the card at the correct level. Uses Effect Macro -// to dismiss the sphere when concentration ends. -// Required modules: itemacro, warpgate, concentrationnotifier, effectmacro. - -// if concentrating on this spell, redisplay it. -const isConc = CN.isActorConcentratingOnItem(actor, item); -if (isConc) return CN.redisplayCard(actor); - -// if not concentrating on this spell, cast and summon. -const use = await item.use(); -if (!use) return; - -// then set up updates to token and actor: -const updates = { token: { - name: `${actor.name.split(" ")[0]}'s Sphere`, - displayName: CONST.TOKEN_DISPLAY_MODES.HOVER, - displayBars: CONST.TOKEN_DISPLAY_MODES.NONE, - light: { dim: 40, bright: 20 } -} } -const options = { crosshairs: { - drawIcon: false, icon: "icons/svg/dice-target.svg" -} } - -// then spawn the actor: -await actor.sheet.minimize(); -const [spawn] = await warpgate.spawn("Flaming Sphere", updates, {}, options); -await actor.sheet.maximize(); -const effect = CN.isActorConcentratingOnItem(actor, item); -return effect?.setFlag("effectmacro", "onDelete.script", `await warpgate.dismiss("${spawn}");`); diff --git a/dnd5e_spells/magic_missile.js b/dnd5e_spells/magic_missile.js index e06ffb8..7c71d42 100644 --- a/dnd5e_spells/magic_missile.js +++ b/dnd5e_spells/magic_missile.js @@ -27,13 +27,13 @@ const dialog = new Dialog({ callback: async (html) => { const csv = html[0].querySelector("#csv").value; const values = csv.split(","); - + // check if the sum is correct. const sum = values.reduce((acc, e) => acc += Number(e), 0); if (sum !== level + 2) return dialog.render(); // create the rolls. const rolls = await Promise.all(values.map(v => { - return new Roll(`${v}d4 + ${v}`).evaluate({ async: true }); + return new Roll(`${v}d4 + ${v}`).evaluate({async: true}); })); return ChatMessage.create({ flavor: "Magic Missile - Damage Roll (Force)", diff --git a/dnd5e_spells/rainbow_recurve.js b/dnd5e_spells/rainbow_recurve.js deleted file mode 100644 index c8632e6..0000000 --- a/dnd5e_spells/rainbow_recurve.js +++ /dev/null @@ -1,104 +0,0 @@ -// RAINBOW RECURVE -// required modules: concentrationnotifier, itemacro, rollgroups - -// get whether we are concentrating on the spell. -let effect = CN.isActorConcentratingOnItem(actor, item); - -const colors = [ - "red", "orange", "yellow", "green", - "blue", "indigo", "violet" -]; - -// if not concentrating, cast the spell. -if (!effect) { - const use = await item.use(); - if (!use) return; - effect = await CN.waitForConcentrationStart(actor, { - item, max_wait: 1000 - }); - if (!effect) return; -} -return chooseArrow(); - -// dialog to choose arrow. -async function chooseArrow() { - // get all arros that have NOT been fired. - const arrows = effect.getFlag("world", "arrow-fired") ?? {}; - const available = colors.filter(c => { - return !(c in arrows); - }); - - // no arrows available, end concentration. - if (available.length < 1) return effect.delete(); - - const options = available.reduce((acc, color) => { - return acc + ``; - }, ""); - - new Dialog({ - title: "Rainbow Recurve", - content: ` -
-
- -
- -
-
-
`, - buttons: { - shoot: { - icon: "", - label: "Shoot!", - callback: async (html) => { - const arrow = html[0].querySelector("#arrow").value; - return shootArrow(arrow); - } - } - } - }).render(true); -} - -// create item clone. -async function shootArrow(arrow) { - // set up rollgroups and damage parts. - const groups = [{ label: "Force", parts: [0] }]; - if (arrow === "red") { - groups.push({ label: "Fire", parts: [1] }); - } else if (arrow === "orange") { - groups.push({ label: "Acid", parts: [2] }); - } else if (arrow === "yellow") { - groups.push({ label: "Lightning", parts: [3] }); - } else if (arrow === "green") { - groups.push({ label: "Poison", parts: [4] }); - } else if (arrow === "blue") { - groups.push({ label: "Cold", parts: [5] }); - } - - let addSave; - if (arrow === "indigo") addSave = "con"; - else if (arrow === "violet") addSave = "wis"; - - await effect.setFlag("concentrationnotifier", "data", { - "itemData.flags.rollgroups.config.groups": groups - }); - - const card = await CN.redisplayCard(actor); - - // add new saving throw button. - if (addSave) { - const div = document.createElement("DIV"); - div.innerHTML = card.content; - const oldSave = div.querySelector("button[data-action=save]"); - const dc = actor.system.attributes.spelldc; - - const ability = CONFIG.DND5E.abilities[addSave]; - const newSaveButton = document.createElement("button"); - newSaveButton.setAttribute("data-action", "save"); - newSaveButton.setAttribute("data-ability", addSave); - newSaveButton.innerHTML = `Saving Throw DC ${dc} ${ability}`; - oldSave.after(newSaveButton); - await card.update({ content: div.innerHTML }); - } - await effect.setFlag("world", `arrow-fired.${arrow}`, true); -} diff --git a/dnd5e_spells/shield_spell.js b/dnd5e_spells/shield_spell.js deleted file mode 100644 index c8ee9c1..0000000 --- a/dnd5e_spells/shield_spell.js +++ /dev/null @@ -1,24 +0,0 @@ -// SHIELD -// required modules: itemacro. -// supported: visual-active-effects - -const effect = actor.effects.find(i => i.getFlag("world", "shield-spell") === actor.id); -if (effect) return effect.delete(); - -const use = await item.use(); -if (!use) return; -await actor.createEmbeddedDocuments("ActiveEffect", [{ - changes: [{ - key: "system.attributes.ac.bonus", - mode: CONST.ACTIVE_EFFECT_MODES.ADD, - value: 5 - }], - icon: item.img, - label: item.name, - duration: { rounds: 1 }, - "flags.world.shield-spell": actor.id, - "flags.visual-active-effects.data": { - intro: "You have +5 to AC and immunity to damage from Magic Missile.", - content: item.system.description.value - } -}]); diff --git a/dnd5e_spells/toll_the_dead.js b/dnd5e_spells/toll_the_dead.js deleted file mode 100644 index 82baf76..0000000 --- a/dnd5e_spells/toll_the_dead.js +++ /dev/null @@ -1,10 +0,0 @@ -// Toll the Dead -// required modules: itemacro. - -if (game.user.targets.size !== 1) return ui.notifications.warn("Please target exactly one token."); - -const hp = game.user.targets.first().actor.system.attributes.hp; -const formula = (hp.value ?? 0) < (hp.max + (hp.tempmax ?? 0)) ? "1d12" : "1d8"; -const clone = item.clone({"system.damage.parts": [[formula, "necrotic"]]}, {keepId: true}); -clone.prepareFinalAttributes(); -const use = await clone.use({}, {"flags.dnd5e.itemData": clone.toObject()}); diff --git a/dnd5e_utils/adjust_resources_dialog.js b/dnd5e_utils/adjust_resources_dialog.js index 10ec485..e08bc4b 100644 --- a/dnd5e_utils/adjust_resources_dialog.js +++ b/dnd5e_utils/adjust_resources_dialog.js @@ -1,12 +1,11 @@ // Dialog to adjust resources. // Optional compatibility with 'Add a Resource'. -const a = token?.actor ?? game.user.character; -if(!a) return ui.notifications.warn("You need a token selected or an actor assigned."); +if (!actor) return ui.notifications.warn("You need a token selected or an actor assigned."); -const data = a.toObject(); +const data = actor.toObject(); const names = ["primary", "secondary", "tertiary"].map(r => `system.resources.${r}`); -if(game.modules.get("addar")?.active){ +if (game.modules.get("addar")?.active) { const ids = Object.keys(data.flags.addar?.resource ?? {}); names.push(...ids.map(id => `flags.addar.resource.${id}`)); } @@ -17,9 +16,9 @@ const content = names.reduce((acc, name) => {
- + / - +
`; }, ""); @@ -30,11 +29,7 @@ const form = await Dialog.prompt({ rejectClose: false, label: "All Good", callback: (html) => { - const formData = {}; - html[0].querySelectorAll("input[type=number]").forEach(input => { - formData[input.name] = input.valueAsNumber; - }); - return formData; + const update = new FormDataExtended(html[0].querySelector("form")).object; + return actor.update(update); } }); -if(form) return a.update(form); diff --git a/dnd5e_utils/grant_temphp_to_selected.js b/dnd5e_utils/grant_temphp_to_selected.js index c59e86f..ba668a9 100644 --- a/dnd5e_utils/grant_temphp_to_selected.js +++ b/dnd5e_utils/grant_temphp_to_selected.js @@ -1,22 +1,23 @@ /** - Click to set temporary hit points of selected tokens. - Shift-click to remove temporary hit points of selected tokens. -**/ + * Click to set temporary hit points of selected tokens. + * Shift-click to remove temporary hit points of selected tokens. + */ if (event.shiftKey) { - return canvas.tokens.controlled.map(token => { - return token.actor?.update({ "system.attributes.hp.temp": null }); - }); + for (const token of canvas.tokens.controlled) { + token.actor?.update({"system.attributes.hp.temp": null}); + } + return; } new Dialog({ title: "Add temporary hit points", content: ` -
+
- +
- +
`, @@ -25,10 +26,10 @@ new Dialog({ icon: "", label: "Apply", callback: async (html) => { - const { value } = html[0].querySelector("#temphp"); - return canvas.tokens.controlled.map(token => { - return token.actor?.applyTempHP(value); - }); + const update = new FormDataExtended(html[0].querySelector("form")).object; + for (const token of canvas.tokens.controlled) { + await token.actor?.applyTempHP(update.temphp); + } } } } diff --git a/dnd5e_utils/loot_creator.js b/dnd5e_utils/loot_creator.js index 7a72198..c103263 100644 --- a/dnd5e_utils/loot_creator.js +++ b/dnd5e_utils/loot_creator.js @@ -11,17 +11,17 @@ const img = files[Math.floor(Math.random() * files.length)]; // set up input fields. const nameField = ``; -const priceField = ``; -const weightField = ``; -const quantityField = ``; -const descriptionField = ``; +const priceField = ``; +const weightField = ``; +const quantityField = ``; +const descriptionField = ``; const sidebarCheck = ``; // set up html. const content = `


-
+
${nameField}
@@ -58,19 +58,8 @@ new Dialog({ label: "Create Loot!", callback: async (html) => { // construct item data. - const form = html[0].querySelector("form"); - const itemData = { - name: form.name.value, - img, - type: "loot", - system: { - description: {value: form.desc.value}, - price: {value: form.price.value}, - quantity: form.quantity.value, - rarity: "common", - weight: form.weight.value - } - }; + const data = new FormDataExtended(html[0].querySelector("form")).object; + const itemData = foundry.utils.mergeObject({img, type: "loot", "system.rarity": "common"}, data); // pick the target or location. const crosshairs = await warpgate.crosshairs.show({ label: "Select recipient or location", @@ -79,13 +68,12 @@ new Dialog({ }); const tokenDocs = !crosshairs.cancelled ? warpgate.crosshairs.collect(crosshairs) : []; // pop it in the sidebar - const sidebar = form.sidebar.checked; - if (sidebar) await Item.createDocuments([itemData]); - + if (data.sidebar) await Item.createDocuments([itemData]); + // if no token was targeted, add the item to a new item pile, initially hidden. if (!tokenDocs.length) { const updates = { - embedded: {Item: {[form.name]: itemData}}, + embedded: {Item: {[data.name]: itemData}}, token: {hidden: true} } return warpgate.spawnAt(crosshairs, "Default Item Pile", updates); @@ -94,7 +82,7 @@ new Dialog({ await tokenDocs[0].actor.createEmbeddedDocuments("Item", [itemData]); // if single item pile it's probably named as that item, so fix that const isItemPile = tokenDocs[0].getFlag("item-piles", "data.enabled"); - if (!!isItemPile) await tokenDocs[0].update({ name: "Pile of Loot" }); + if (isItemPile) return tokenDocs[0].update({name: "Pile of Loot"}); } } } diff --git a/dnd5e_utils/packAttack.js b/dnd5e_utils/packAttack.js index 71cf277..055060c 100644 --- a/dnd5e_utils/packAttack.js +++ b/dnd5e_utils/packAttack.js @@ -3,7 +3,7 @@ const pack = canvas.tokens.controlled.map(i => i.actor); const actorIds = pack.map(i => i.id); -if ( !new Set(actorIds).size ) { +if (!new Set(actorIds).size) { ui.notifications.warn("No selected tokens!"); return; } @@ -17,7 +17,7 @@ const itemsWithAttacks = actorIds.map(id => { const names = new Set(itemsWithAttacks); const options = names.reduce((acc, name) => { - return acc + ``; + return acc + ``; }, ""); const content = `

Choose pack attack:

@@ -39,21 +39,21 @@ return Dialog.prompt({ const name = html[0].querySelector("#attack").value; let content = ""; - for ( const member of pack ) { + for (const member of pack) { const item = await member.items.getName(name); - if(!item) continue; + if (!item) continue; leader = member; - const attack = await item.rollAttack({ event: ev, chatMessage: false }); - if(!attack) continue; + const attack = await item.rollAttack({event: ev, chatMessage: false}); + if (!attack) continue; game.dice3d?.showForRoll(attack, game.user, true); - if(attack.isCritical) content += `

Attack Roll: ${attack.total} (Critical!)

`; - else if(attack.isFumble) content += `

Attack Roll: ${attack.total} (Fumbled)

`; + if (attack.isCritical) content += `

Attack Roll: ${attack.total} (Critical!)

`; + else if (attack.isFumble) content += `

Attack Roll: ${attack.total} (Fumbled)

`; else content += `

Attack Roll: ${attack.total}

`; } const item = leader.items.getName(name); await item.displayCard(); - await ChatMessage.create({ content, "flags.core.canPopout": true }); - if ( item.hasDamage ) for ( const member of pack ) { + await ChatMessage.create({content, "flags.core.canPopout": true}); + if (item.hasDamage) for (const member of pack) { await member.items.getName(name)?.rollDamage(); } } diff --git a/dnd5e_utils/roll_d20roll_with_custom_floor_and_ceil.js b/dnd5e_utils/roll_d20roll_with_custom_floor_and_ceil.js index cadbe33..7ab9c31 100644 --- a/dnd5e_utils/roll_d20roll_with_custom_floor_and_ceil.js +++ b/dnd5e_utils/roll_d20roll_with_custom_floor_and_ceil.js @@ -9,15 +9,15 @@ const skill = "ins"; const msg = await token.actor.rollSkill(skill, {chatMessage: false, event}); const useFloor = 20 >= floor && floor > 1; const useCeil = 20 > ceil && ceil > 0; -if(useFloor) msg.dice[0].modifiers.push(`min${floor}`); -if(useCeil) msg.dice[0].modifiers.push(`max${ceil}`); +if (useFloor) msg.dice[0].modifiers.push(`min${floor}`); +if (useCeil) msg.dice[0].modifiers.push(`max${ceil}`); msg._formula = msg._formula.replace("d20", "d20" + (useFloor ? `min${floor}` : "") + (useCeil > 0 ? `max${ceil}` : "")); -for(let d20 of msg.dice[0].results){ - if(useFloor && d20.result < floor){ +for (let d20 of msg.dice[0].results) { + if (useFloor && d20.result < floor) { d20.rerolled = true; d20.count = floor; } - if(useCeil && d20.result > ceil){ + if (useCeil && d20.result > ceil) { d20.rerolled = true; d20.count = ceil; } diff --git a/dnd5e_utils/token_save_and_apply_damage.js b/dnd5e_utils/token_save_and_apply_damage.js index e96663c..d74c867 100644 --- a/dnd5e_utils/token_save_and_apply_damage.js +++ b/dnd5e_utils/token_save_and_apply_damage.js @@ -4,13 +4,13 @@ * and each selected NON-PLAYER-OWNED token will roll a save and apply * full damage if they fail, or half if they succeed. * (toggle for no damage on success too) - * (Does not take damage resistances into account) + * (Does not take damage resistances into account) **/ const tokens = canvas.tokens.controlled.filter(t => !t.actor.hasPlayerOwner); if (!tokens.length) return ui.notifications.warn("No valid tokens."); const names = tokens.map(i => i.name).join(", "); -const options = Object.entries(CONFIG.DND5E.abilities).reduce((acc, [a,b]) => { +const options = Object.entries(CONFIG.DND5E.abilities).reduce((acc, [a, b]) => { return acc + ``; }, ""); const content = ` @@ -54,10 +54,10 @@ new Dialog({ if (!dc) return ui.notifications.warn("Invalid DC."); if (dmg < 1) return ui.notifications.warn("Invalid damage."); for (const t of tokens) { - const { total } = await t.actor.rollAbilitySave(type, { event: ev }); + const {total} = await t.actor.rollAbilitySave(type, {event: ev}); if (total < dc) await t.actor.applyDamage(dmg); else if (no) continue; - else await t.actor.applyDamage(Math.floor(dmg/2)); + else await t.actor.applyDamage(Math.floor(dmg / 2)); } } } diff --git a/misc/combat_dice_for_2d20_system.js b/misc/combat_dice_for_2d20_system.js index 308c2c6..e8efcd5 100644 --- a/misc/combat_dice_for_2d20_system.js +++ b/misc/combat_dice_for_2d20_system.js @@ -2,36 +2,37 @@ // roll variable number of d6; result: [1, 2, 0, 0, 1, 1], and number of effects: [0, 0, 0, 0, 1, 1] const content = ` - -
- -
- -
+ +
+ +
+
- `; +
+`; new Dialog({ title: "Combat Dice", content, - buttons: {go: { - icon: ``, - label: "Roll", - callback: async (html) => { - const diceCount = html[0].querySelector("input[id='combat-dice-num']").value; - const roll = await new Roll(`${diceCount}d6`).evaluate({async: true}); - const sum = roll.dice[0].results.reduce((acc, res) => { - if([1, 5, 6].includes(res.result)) return acc + 1; - if([3,4].includes(res.result)){ - res.active = false; - res.discarded = true; - return acc; - } - if(res.result === 2) return acc + 2; - }, 0); - const effects = roll.dice[0].results.reduce((acc, {result}) => acc += (result > 4 ? 1 : 0), 0); - roll._total = sum; - await roll.toMessage({flavor: `You rolled a total of ${sum} and got ${effects} effects.`}); + buttons: { + go: { + icon: ``, + label: "Roll", + callback: async (html) => { + const diceCount = html[0].querySelector("input").value; + const roll = await new Roll(`${diceCount}d6`).evaluate(); + const sum = roll.dice[0].results.reduce((acc, res) => { + if ([1, 5, 6].includes(res.result)) return acc + 1; + if ([3, 4].includes(res.result)) { + res.active = false; + res.discarded = true; + return acc; + } + if (res.result === 2) return acc + 2; + }, 0); + const effects = roll.dice[0].results.reduce((acc, {result}) => acc + (result > 4 ? 1 : 0), 0); + roll._total = sum; + return roll.toMessage({flavor: `You rolled a total of ${sum} and got ${effects} effects.`}); + } } - }}, - default: "go" -}).render(true); \ No newline at end of file + } +}).render(true); diff --git a/misc/cyberpunk_red_damage_rolls.js b/misc/cyberpunk_red_damage_rolls.js index 077b799..f635b91 100644 --- a/misc/cyberpunk_red_damage_rolls.js +++ b/misc/cyberpunk_red_damage_rolls.js @@ -19,14 +19,14 @@ new Dialog({ label: "Roll it.", callback: async (html) => { const modifier = html[0].querySelector("input").value; - const roll = await new Roll(`1d10xo + ${modifier}`).evaluate({async: true}); - if(roll.dice[0].results[0].result !== 1){ + const roll = await new Roll(`1d10xo + ${modifier}`).evaluate(); + if (roll.dice[0].results[0].result !== 1) { return roll.toMessage({ flavor: "your flavor text here", speaker: ChatMessage.getSpeaker() }); } - const roll2 = await new Roll(`1d10xo + ${modifier} - 1d10`).evaluate({async: true}); + const roll2 = await new Roll(`1d10xo + ${modifier} - 1d10`).evaluate(); roll2.dice[0].results[0] = roll.dice[0].results[0]; roll2._total = Number(roll2.result); await roll2.toMessage({ @@ -61,10 +61,10 @@ new Dialog({ label: "Roll it", callback: async (html) => { const modifier = Number(html[0].querySelector("input").value); - const roll = await new Roll(`${modifier}d6`).evaluate({async: true}); - if((modifier === 1) && (roll.total === 6)){ + const roll = await new Roll(`${modifier}d6`).evaluate(); + if ((modifier === 1) && (roll.total === 6)) { const roll2 = await new Roll("1d6").evaluate({async: true}); - if(roll2.total !== 6){ + if (roll2.total !== 6) { roll2.dice[0].results[0].active = false; roll2.dice[0].results[0].discarded = true; roll.dice[0].results.push(roll2.dice[0].results[0]); diff --git a/misc/cyberpunk_red_grant_currency.js b/misc/cyberpunk_red_grant_currency.js index 681d4fa..d6dd6ef 100644 --- a/misc/cyberpunk_red_grant_currency.js +++ b/misc/cyberpunk_red_grant_currency.js @@ -1,7 +1,8 @@ // Grant currency to all selected tokens, with appended notes. +// unknown system. const tokens = canvas.tokens.controlled.reduce((acc, token) => { - if(!token.actor) return acc; + if (!token.actor) return acc; return acc + `
@@ -34,12 +35,12 @@ return Dialog.prompt({ label: "Apply" }); -async function callback(html){ +async function callback(html) { const data = new FormDataExtended(html[0].querySelector("form")).object; console.log(data); - if(!data.reason || !data.amount) return ui.notifications.warn("One or both fields were not filled."); - for(const id of data.tokenId){ - if(!id) continue; // if unchecked. + if (!data.reason || !data.amount) return ui.notifications.warn("One or both fields were not filled."); + for (const id of data.tokenId) { + if (!id) continue; // if unchecked. const token = canvas.scene.tokens.get(id); const wealth = foundry.utils.deepClone(token.actor.system.wealth); wealth.value += data.amount; diff --git a/misc/tor2e_request_skill.js b/misc/tor2e_request_skill.js index 578a14b..045ae2e 100644 --- a/misc/tor2e_request_skill.js +++ b/misc/tor2e_request_skill.js @@ -3,52 +3,53 @@ // fake actor to the rescue to get all the common skills. All hail Steve. -const skill_labels = Object.values(new Actor({name: "Steve", type: "character"}).data.data.commonSkills).map(({label}) => game.i18n.localize(label)); +const skill_labels = Object.values(new Actor({name: "Steve", type: "character"}).system.commonSkills).map(({label}) => game.i18n.localize(label)); // construct dialog so the GM can pick which skills. const choices = await new Promise(resolve => { - const checkboxes = skill_labels.reduce((acc, e) => acc += ` + const checkboxes = skill_labels.reduce((acc, e) => acc += `
- +
-
`, ``); - new Dialog({ - title: "Select skills available", - content: `
${checkboxes}
`, - buttons: {go: { - icon: ``, - label: "All Good", - callback: (html) => { - const boxes = html[0].querySelectorAll("input[type='checkbox']:checked"); - const ticked = []; - for(let box of boxes) ticked.push(box.value); - resolve(ticked); - } - }}, - default: "go", - close: () => resolve([]) - }).render(true); +
`, ""); + new Dialog({ + title: "Select skills available", + content: `
${checkboxes}
`, + buttons: { + go: { + icon: ``, + label: "All Good", + callback: (html) => { + const boxes = html[0].querySelectorAll("input[type='checkbox']:checked"); + const ticked = []; + for (let box of boxes) ticked.push(box.value); + resolve(ticked); + } + } + }, + default: "go", + close: () => resolve([]) + }).render(true); }); -if(choices.length < 1) return; +if (!choices.length) return; // construct Requestor. const buttonData = []; -for(let i = 0; i < choices.length; i++){ - buttonData.push({ - action: async () => { - await game.tor2e.macro.utility.rollSkillMacro(args.skill, false, {actorId: game.user.character.id, event}); - }, - label: choices[i], - skill: choices[i] - }); +for (const c of choices) { + buttonData.push({ + command: async () => { + return game.tor2e.macro.utility.rollSkillMacro(skill, false, {actorId: game.user.character.id, event}); + }, + label: c, + skill: c + }); } await Requestor.request({ - buttonData, - title: "Roll Skill", - description: "Please roll one of these skills.", - context: {popout: true, autoClose: true}, - img: "insert your image here" + buttonData, + title: "Roll Skill", + description: "Please roll one of these skills.", + img: "insert your image here" }); diff --git a/tools/image_migration_tool.js b/tools/image_migration_tool.js index 35ab9cb..16149a6 100644 --- a/tools/image_migration_tool.js +++ b/tools/image_migration_tool.js @@ -63,12 +63,12 @@ async function swapEmbedded(collection, embeddedCollection, property) { function createMap(docs, property) { return docs.map(doc => { const prop = foundry.utils.getProperty(doc, property); - if (!prop) return { _id: doc.id }; - return { _id: doc.id, [property]: prop.replace(toReplace, replacement) }; + if (!prop) return {_id: doc.id}; + return {_id: doc.id, [property]: prop.replace(toReplace, replacement)}; }) } -function error(string, err){ +function error(string, err) { console.error(string); console.warn(err); errors++; diff --git a/tools/image_migration_tool_PREPEND.js b/tools/image_migration_tool_PREPEND.js index 24c14ac..7ede776 100644 --- a/tools/image_migration_tool_PREPEND.js +++ b/tools/image_migration_tool_PREPEND.js @@ -50,7 +50,7 @@ for (const m of embedded) await swapEmbedded(...m); ui.notifications.info(`MIGRATION COMPLETED! (errors: ${errors})`); console.log("MISSING FILE PATHS:"); -for(const [doc, prop] of missingFilePaths) console.log(doc, prop); +for (const [doc, prop] of missingFilePaths) console.log(doc, prop); async function swap(collection, property) { const coll = collection.metadata.collection; diff --git a/tools/itemUpdater.js b/tools/itemUpdater.js index 03bd03c..2e96a45 100644 --- a/tools/itemUpdater.js +++ b/tools/itemUpdater.js @@ -24,7 +24,7 @@ function _render(html) { const uuid = target.dataset.uuid; const del = await _replaceItemOnActor(uuid); if (del) target.closest("tr").remove(); - d.setPosition({ height: "auto" }); + d.setPosition({height: "auto"}); }); html[0].querySelector(".drop-location").addEventListener("drop", _onDrop); } @@ -75,9 +75,9 @@ async function _generateContent() { `; const DIV = document.createElement("DIV"); - DIV.innerHTML = style + await TextEditor.enrichHTML(_content, { async: true }); + DIV.innerHTML = style + await TextEditor.enrichHTML(_content, {async: true}); d.element[0].querySelector(".drop-location").replaceWith(...DIV.children); - d.setPosition({ height: "auto" }); + d.setPosition({height: "auto"}); } const d = new Dialog({ @@ -92,5 +92,5 @@ const d = new Dialog({ content: "" }, { classes: ["dialog", "find-and-replace"], - dragDrop: [{ dragSelector: null, dropSelector: ".drop-location" }] + dragDrop: [{dragSelector: null, dropSelector: ".drop-location"}] }).render(true); diff --git a/tools/scene/pan_to_object.js b/tools/scene/pan_to_object.js index 94a4fe9..bdd814e 100644 --- a/tools/scene/pan_to_object.js +++ b/tools/scene/pan_to_object.js @@ -9,19 +9,22 @@ const options = canvas.scene.notes.reduce((acc, e) => { if (!e.entryId) label = e.text; else if (e.pageId) label = game.journal.get(e.entryId).pages.get(e.pageId).name; else label = game.journal.get(e.entryId).name; - return acc + ``; + return acc + ``; }, ""); const content = `
-
+
+ +
`; await Dialog.prompt({ title: "Pan to City", content, rejectClose: false, + label: "Pan to City", callback: (html) => { const id = html[0].querySelector("select").value; const note = canvas.scene.notes.get(id); diff --git a/tools/scene/scene_picker.js b/tools/scene/scene_picker.js index 6b39b9f..3fd51f6 100644 --- a/tools/scene/scene_picker.js +++ b/tools/scene/scene_picker.js @@ -1,5 +1,5 @@ // pop a dialog to pick a scene to activate. -const options = game.scenes.reduce((acc, { id, name }) => { +const options = game.scenes.reduce((acc, {id, name}) => { return acc + ``; }, ""); const content = ` @@ -7,7 +7,7 @@ const content = `
- +
`; @@ -19,9 +19,8 @@ new Dialog({ icon: "", label: "Activate!", callback: async (html) => { - const sceneId = html[0].querySelector("#scene-select").value; - const scene = game.scenes.get(sceneId); - await scene.activate(); + const sceneId = html[0].querySelector("select").value; + return game.scenes.get(sceneId).activate(); } } } diff --git a/tools/scene/scene_picker_player.js b/tools/scene/scene_picker_player.js index 2ddc2cf..f5c7dfa 100644 --- a/tools/scene/scene_picker_player.js +++ b/tools/scene/scene_picker_player.js @@ -1,9 +1,9 @@ // pop a dialog to pick a scene to view. // choosing from all scenes set to 'All Players' const options = game.scenes.filter(scene => { - const { active, ownership } = scene; - return active || ownership.default === 2; -}).reduce((acc, { id, name }) => { + const {active, ownership} = scene; + return active || (ownership.default === 2); +}).reduce((acc, {id, name}) => { return acc + ``; }, ""); const content = ` @@ -11,9 +11,7 @@ const content = `
- +
`; @@ -24,9 +22,8 @@ new Dialog({ view: { icon: "", label: "View!", - callback: async (html) => { - const { value } = html[0].querySelector("#select"); - return game.scenes.get(value)?.view(); + callback: (html) => { + return game.scenes.get(html[0].querySelector("select").value).view(); } } } diff --git a/tools/scene/toggle_aura_template.js b/tools/scene/toggle_aura_template.js index 9eefecc..57ec19f 100644 --- a/tools/scene/toggle_aura_template.js +++ b/tools/scene/toggle_aura_template.js @@ -6,13 +6,13 @@ const base = 10; const template = canvas.scene.templates.find(t => { return t.getFlag("world", "aura"); }); -if(template) return template.delete(); +if (template) return template.delete(); const {x, y, width} = token.document; const {size, distance} = canvas.scene.grid; -const center = {x: x + width/2 * size, y: y + width/2 * size}; -const radius = base + (width/2 * distance); +const center = {x: x + width / 2 * size, y: y + width / 2 * size}; +const radius = base + (width / 2 * distance); await canvas.scene.createEmbeddedDocuments("MeasuredTemplate", [{ - t: "circle", x: center.x, y: center.y, - distance: radius, user: game.user.id, - flags: {world: {aura: true}} + t: "circle", x: center.x, y: center.y, + distance: radius, user: game.user.id, + flags: {world: {aura: true}} }]); diff --git a/tools/show_image_from_url.js b/tools/show_image_from_url.js index 0d3d738..4a004ac 100644 --- a/tools/show_image_from_url.js +++ b/tools/show_image_from_url.js @@ -1,16 +1,21 @@ // shows the provided media to all players. await Dialog.prompt({ - title: "Share Image via URL", - content: "", + title: "Share Image", + content: ` +
+
+ +
+ +
+
+
`, + label: "Show", rejectClose: false, callback: (html) => { const imageUrl = html[0].querySelector("input").value; - if (!imageUrl) { - return ui.notifications.info("You did not provide a valid image."); - } - const ip = new ImagePopout(imageUrl); - ip.render(true); - ip.shareImage(); + if (!imageUrl) return ui.notifications.info("You did not provide a valid image."); + return new ImagePopout(imageUrl).render(true).shareImage(); } }); diff --git a/tools/table/add_table_results_to_selected_token.js b/tools/table/add_table_results_to_selected_token.js index cc4bb59..d8f42ff 100644 --- a/tools/table/add_table_results_to_selected_token.js +++ b/tools/table/add_table_results_to_selected_token.js @@ -5,7 +5,7 @@ const table = await fromUuid("uuid of table goes here"); // Draw items from compendium or sidebar. const receiver = token?.actor; -if(!table || !receiver){ +if (!table || !receiver) { ui.notifications.warn("Missing table or selected token."); return null; } @@ -13,7 +13,7 @@ const draw = await table.draw(); const promises = draw.results.map(i => { const key = i.documentCollection; const id = i.documentId; - const uuid = `Compendium.${key}.${id}`; + const uuid = `Compendium.${key}.Item.${id}`; return key === "Item" ? game.items.get(id) : fromUuid(uuid); }); const items = await Promise.all(promises); diff --git a/tools/table/create_rolltable_from_compendium.js b/tools/table/create_rolltable_from_compendium.js index d217380..a4dac26 100644 --- a/tools/table/create_rolltable_from_compendium.js +++ b/tools/table/create_rolltable_from_compendium.js @@ -7,7 +7,7 @@ const tableDescription = "description of table goes here"; /* --------------------- */ const pack = game.packs.get(collection); -if(!pack) return ui.notifications.warn("Your key is invalid."); +if (!pack) return ui.notifications.warn("Your key is invalid."); const tableResults = pack.index.map((item, i) => ({ img: item.img, diff --git a/tools/token/configure_all_selected_tokens.js b/tools/token/configure_all_selected_tokens.js index ced2900..87f73bb 100644 --- a/tools/token/configure_all_selected_tokens.js +++ b/tools/token/configure_all_selected_tokens.js @@ -1,6 +1,6 @@ // single dialog to configure all selected tokens' disposition, displayBars, and displayName configuration. -const { disposition: defdisp, displayBars: defbars, displayName: defname } = token.document; +const {disposition: defdisp, displayBars: defbars, displayName: defname} = token.document; const options_disposition = Object.entries(CONST.TOKEN_DISPOSITIONS).reduce((acc, [key, value]) => { const selected = value === defdisp && "selected"; return acc + `` @@ -40,7 +40,7 @@ new Dialog({ const displayBars = Number(html[0].querySelector("#display-bars").value); const displayName = Number(html[0].querySelector("#display-name").value); const updates = canvas.tokens.controlled.map(token => { - return { _id: token.id, disposition, displayBars, displayName}; + return {_id: token.id, disposition, displayBars, displayName}; }); await canvas.scene.updateEmbeddedDocuments("Token", updates); } diff --git a/tools/token/cycle_token_wildcards.js b/tools/token/cycle_token_wildcards.js index 2c041e1..32050a6 100644 --- a/tools/token/cycle_token_wildcards.js +++ b/tools/token/cycle_token_wildcards.js @@ -5,4 +5,4 @@ const actorId = token.actor.id; const wildcardImages = await game.actors.get(actorId).getTokenImages(); const currentIndex = wildcardImages.indexOf(currentImg); const next = (currentIndex + 1) === wildcardImages.length ? 0 : currentIndex + 1; -await token.document.update({ "texture.src": wildcardImages[next] }); +await token.document.update({"texture.src": wildcardImages[next]}); diff --git a/tools/token/pan_to_current_combatant.js b/tools/token/pan_to_current_combatant.js index 66654c9..81299f7 100644 --- a/tools/token/pan_to_current_combatant.js +++ b/tools/token/pan_to_current_combatant.js @@ -14,4 +14,4 @@ if (sheet.rendered) sheet.maximize(); else sheet.render(true); current.object.control(); -canvas.animatePan({ x: current.x, y: current.y, duration: 1000 }); +canvas.animatePan({x: current.x, y: current.y, duration: 1000}); diff --git a/tools/update_all_scene_lights.js b/tools/update_all_scene_lights.js index b373f70..37154a5 100644 --- a/tools/update_all_scene_lights.js +++ b/tools/update_all_scene_lights.js @@ -3,11 +3,11 @@ const updates = canvas.scene.lights.map(i => ({ _id: i.id, hidden: false, config: { - animation: { type: "torch", speed: 1, intensity: 5 }, + animation: {type: "torch", speed: 1, intensity: 5}, color: "#FF00FF", dim: 15, bright: 7.5, - darkness: { min: 0.35, max: 1 } + darkness: {min: 0.35, max: 1} } })); await canvas.scene.updateEmbeddedDocuments("AmbientLight", updates); diff --git a/tools/walls/wall_toggler.js b/tools/walls/wall_toggler.js index f0deffe..70e8f33 100644 --- a/tools/walls/wall_toggler.js +++ b/tools/walls/wall_toggler.js @@ -2,18 +2,18 @@ // hold shift to instead cycle between movement types (none, normal). if (!event.shiftKey) { - const { BOTH, LEFT, RIGHT } = CONST.WALL_DIRECTIONS; + const {BOTH, LEFT, RIGHT} = CONST.WALL_DIRECTIONS; const updates = canvas.walls.controlled.map(wall => { let dir = BOTH; if (wall.document.dir === BOTH) dir = LEFT; else if (wall.document.dir === LEFT) dir = RIGHT; - return { _id: wall.id, dir }; + return {_id: wall.id, dir}; }); await canvas.scene.updateEmbeddedDocuments("Wall", updates); } else { - const { NONE, NORMAL } = CONST.WALL_MOVEMENT_TYPES; + const {NONE, NORMAL} = CONST.WALL_MOVEMENT_TYPES; const updates = canvas.walls.controlled.map(wall => { - return { _id: wall.id, move: wall.document.move === NONE ? NORMAL : NONE }; + return {_id: wall.id, move: wall.document.move === NONE ? NORMAL : NONE}; }); await canvas.scene.updateEmbeddedDocuments("Wall", updates); } diff --git a/tools/walls/walls_to_terrain_walls.js b/tools/walls/walls_to_terrain_walls.js index 69cab81..2c6ec6f 100644 --- a/tools/walls/walls_to_terrain_walls.js +++ b/tools/walls/walls_to_terrain_walls.js @@ -1,12 +1,12 @@ // converts all normal walls on the current scene to terrain walls. -const { NORMAL, LIMITED } = CONST.WALL_SENSE_TYPES; +const {NORMAL, LIMITED} = CONST.WALL_SENSE_TYPES; const updates = canvas.scene.walls.filter(wallDoc => { // only walls that are set up exactly as regular walls (blocking everything). - const { light, move, sight, sound } = wallDoc; + const {light, move, sight, sound} = wallDoc; return new Set([light, move, sight, sound, NORMAL]).size === 1; }).map(wallDoc => { // set blocking of light, sight, and sound to terrain defaults. - return { _id: wallDoc.id, light: LIMITED, sight: LIMITED, sound: LIMITED }; + return {_id: wallDoc.id, light: LIMITED, sight: LIMITED, sound: LIMITED}; }); await canvas.scene.updateEmbeddedDocuments("Wall", updates); diff --git a/tools/whisper_players.js b/tools/whisper_players.js index 556e75c..3cd0750 100644 --- a/tools/whisper_players.js +++ b/tools/whisper_players.js @@ -13,7 +13,7 @@ const selectedPlayerIds = canvas.tokens.controlled.map(i => i.actor.id).filter(i const options = users.reduce((acc, {id, name, character}) => { // should be checked by default. const checked = (!!character && selectedPlayerIds.includes(character.id)) ? "selected" : ""; - + return acc + `${name}`; }, `
`) + `
`; @@ -47,28 +47,30 @@ new Dialog({
`, - buttons: {go: { - icon: ``, - label: "Whisper", - callback: async (html) => { - const whisperIds = new Set(); - for(let {id} of users){ - if(!!html[0].querySelector(`span[id="${id}"].selected`)){ - whisperIds.add(id); + buttons: { + go: { + icon: ``, + label: "Whisper", + callback: async (html) => { + const whisperIds = new Set(); + for (let {id} of users) { + if (!!html[0].querySelector(`span[id="${id}"].selected`)) { + whisperIds.add(id); + } } + const content = html[0].querySelector("textarea[id=message]").value; + if (!whisperIds.size) return; + const whisper = Array.from(whisperIds); + await ChatMessage.create({content, whisper}); } - const content = html[0].querySelector("textarea[id=message]").value; - if(!whisperIds.size) return; - const whisper = Array.from(whisperIds); - await ChatMessage.create({content, whisper}); } - }}, + }, render: (html) => { html.css("height", "auto"); - for(let playerName of html[0].querySelectorAll(".whisper-dialog-player-name")){ - playerName.addEventListener("click", () => { - playerName.classList.toggle("selected"); - }); - } + for (let playerName of html[0].querySelectorAll(".whisper-dialog-player-name")) { + playerName.addEventListener("click", () => { + playerName.classList.toggle("selected"); + }); + } } }).render(true);