'${MODULE_TITLE}' depends on the 'libWrapper' module, which is not present.
86 |A fallback implementation will be used, which increases the chance of compatibility issues with other modules.
87 |'libWrapper' is a library which provides module developers with a simple way to modify core Foundry VTT code, while reducing the likelihood of conflict with other modules.
88 |You can install it from the "Add-on Modules" tab in the Foundry VTT Setup, from the Foundry VTT package repository, or from libWrapper's Github page.
89 | `; 90 | 91 | // Settings key used for the "Don't remind me again" setting 92 | const DONT_REMIND_AGAIN_KEY = "libwrapper-dont-remind-again"; 93 | 94 | // Dialog code 95 | console.warn(`${MODULE_TITLE}: libWrapper not present, using fallback implementation.`); 96 | game.settings.register(MODULE_ID, DONT_REMIND_AGAIN_KEY, { name: '', default: false, type: Boolean, scope: 'world', config: false }); 97 | if(game.user.isGM && !game.settings.get(MODULE_ID, DONT_REMIND_AGAIN_KEY)) { 98 | new Dialog({ 99 | title: FALLBACK_MESSAGE_TITLE, 100 | content: FALLBACK_MESSAGE, buttons: { 101 | ok: { icon: '', label: 'Understood' }, 102 | dont_remind: { icon: '', label: "Don't remind me again", callback: () => game.settings.set(MODULE_ID, DONT_REMIND_AGAIN_KEY, true) } 103 | } 104 | }).render(true); 105 | } 106 | }); 107 | } 108 | }); 109 | -------------------------------------------------------------------------------- /betterrolls5e/scripts/renderer.js: -------------------------------------------------------------------------------- 1 | import { CustomItemRoll } from "./custom-roll.js"; 2 | import { BRSettings, getSettings } from "./settings.js"; 3 | import { i18n, Utils } from "./utils/index.js"; 4 | 5 | /** 6 | * Model data for rendering the header template. 7 | * Not part of the entry list 8 | * @typedef HeaderDataProps 9 | * @type {object} 10 | * @property {number} id 11 | * @property {"header"} type 12 | * @property {string} img image path to show in the box 13 | * @property {string} title header title text 14 | */ 15 | 16 | /** 17 | * Model data for rendering description or flavor text 18 | * @typedef DescriptionDataProps 19 | * @type {object} 20 | * @property {number} id 21 | * @property {"description"} type 22 | * @property {boolean} isFlavor 23 | * @property {string} content 24 | */ 25 | 26 | /** 27 | * Model data for rendering a multi roll. 28 | * @typedef MultiRollDataProps 29 | * @type {object} 30 | * @property {number} id 31 | * @property {"multiroll"} type 32 | * @property {string} title 33 | * @property {"highest" | "lowest" | "first" | null} rollState advantage/disadvantage/normal/none 34 | * @property {string?} group Damage group used to identify what damage entries would be affected by a crit 35 | * @property {number} critThreshold 36 | * @property {boolean?} elvenAccuracy whether elven accuracy applies to this attack 37 | * @property {string} rollType If its an attack or custom 38 | * @property {string} formula The full roll formula to show. This is the entries + bonus 39 | * @property {boolean?} forceCrit was the crit status forced 40 | * @property {boolean} isCrit 41 | * @property {Array<{roll: Roll}>} entries Main d20 roll. Bonuses are added to this 42 | * @property {Roll} bonus Any bonuses to add to the roll (that only get rolled once) 43 | */ 44 | 45 | /** 46 | * Model data for rendering damage information. 47 | * @typedef DamageDataProps 48 | * @type {object} 49 | * @property {number} id 50 | * @property {"damage"} type 51 | * @property {number | "versatile" | "other"} damageIndex 52 | * @property {number?} group DamageGroup id used to identify damage entries as related 53 | * @property {string} title 54 | * @property {string} damageType If its something like bludgeoning or piercing 55 | * @property {string} context 56 | * @property {number?} extraCritDice Used for things like savage 57 | * @property {Roll} baseRoll 58 | * @property {Roll?} critRoll 59 | */ 60 | 61 | /** 62 | * Model data for rendering bonus crit information. 63 | * @typedef CritDataProps 64 | * @type {object} 65 | * @property {number} id 66 | * @property {"crit"} type 67 | * @property {number?} group DamageGroup id used to identify damage entries as related 68 | * @property {string} title 69 | * @property {string} damageType If its something like bludgeoning or piercing 70 | * @property {string} context 71 | * @property {Roll?} critRoll 72 | * @property {boolean} revealed Has the crit entry been revealed 73 | */ 74 | 75 | /** 76 | * Model data for save buttons 77 | * @typedef ButtonSaveProps 78 | * @property {number} id 79 | * @property {"button-save"} type 80 | * @property {string} ability 81 | * @property {boolean} hideDC 82 | * @property {number} dc 83 | */ 84 | 85 | /** 86 | * @typedef DamageGroup 87 | * @property {number} id 88 | * @property {"damage-group"} type 89 | * @property {number} attackId id of the associated attack roll 90 | * @property {boolean} prompt 91 | * @property {boolean} isCrit 92 | * @property {boolean?} forceCrit whether crit can be unset or not 93 | * @property {DamageEntry[]} entries 94 | */ 95 | 96 | /** 97 | * Union type of all possible render model types, separatable by the type property. 98 | * @typedef { HeaderDataProps | DescriptionDataProps | MultiRollDataProps | 99 | * ButtonSaveProps | DamageGroup | DamageEntry 100 | * } RenderModelEntry 101 | */ 102 | 103 | /** 104 | * Union type of all fields that relate to damage 105 | * @typedef {DamageDataProps | CritDataProps} DamageEntry 106 | */ 107 | 108 | /** 109 | * Shortcut function to render a templates in the better rolls template folder. 110 | * @param {string} path sub path of the template in the templates folder 111 | * @param {Object} props the props to render with 112 | * @returns {PromiseYour animated objects attack ${targetName}!
29 |Enter the details for the Animated Objects...
30 | 54 | `; 55 | 56 | const attackAction = async (html) => { 57 | let size = html.find("#size")[0].value; 58 | let count = html.find("#count")[0].value; 59 | let rollState = html.find("#roll")[0].value; 60 | const { atk, dmg } = details[size]; 61 | 62 | const rollTypes = { 63 | "highest": "Advantage", 64 | "lowest": "Disadvantage" 65 | }; 66 | 67 | let rollExpr = `1d20 + ${atk}`; 68 | const card = BetterRolls.rollItem(actorData); 69 | card.addField(["header", {img: this.data.img, title: "Animated Objects"}]); 70 | for (let i = 0; i < count; i++) { 71 | const rollTypeStr = rollState in rollTypes ? `(${rollTypes[rollState]})` : ''; 72 | const title = `Object Attacks #${i+1} (+${atk}) ${rollTypeStr}`; 73 | card.addField(["attack", { formula: rollExpr, rollState, title}]); 74 | card.addField(["damage", { formula: dmg }]); 75 | } 76 | await card.toMessage(); 77 | 78 | // Remember setting for next run 79 | game.brMacro = game.brMacro ?? {}; 80 | game.brMacro.animate_objects_count = count; 81 | }; 82 | 83 | new Dialog({ 84 | title: "Animated Object Attacks", 85 | content: content, 86 | buttons: { 87 | attack: { 88 | icon: '', 89 | label: "Attack!", 90 | callback: attackAction 91 | }, 92 | cancel: { label: "Cancel" } 93 | }, 94 | default: "attack", 95 | }).render(true); -------------------------------------------------------------------------------- /samples/defensive-flourish-ItemMacro.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Version of Defensive Flourish Macro meant to be used via the ItemMacro module. 3 | * Attach to any item with a damage roll. 4 | */ 5 | 6 | const effectImagePath = "icons/svg/combat.svg"; 7 | const actor = item?.actor; 8 | (async () => { 9 | if (!item || !actor) { 10 | return ui.notifications.error("This macro needs to be attached to an item on an actor using ItemMacro"); 11 | } 12 | 13 | const roll = BetterRolls.rollItem(item); 14 | await roll.toMessage(); 15 | 16 | if (roll.error) return; 17 | 18 | const value = roll.entriesFlattened().find(m => m.type === 'damage')?.baseRoll.total; 19 | const label = "Defensive Flourish"; 20 | const key = "data.attributes.ac.value"; 21 | 22 | const existing = actor.effects.entries.find(e => e.data.label === label); 23 | if (existing) { 24 | existing.update({ 25 | changes: [ 26 | { key, mode: 2, value, priority: 20 } 27 | ], 28 | disabled: false 29 | }); 30 | } else { 31 | actor.createEmbeddedEntity('ActiveEffect', { 32 | label, 33 | icon: effectImagePath ?? item.img, 34 | duration: { rounds: 1 }, 35 | changes: [ 36 | { key, mode: 2, value, priority: 20 } 37 | ] 38 | }); 39 | } 40 | })(); 41 | -------------------------------------------------------------------------------- /samples/defensive-flourish.js: -------------------------------------------------------------------------------- 1 | const effectImagePath = "icons/svg/combat.svg"; 2 | const selected = [actor] || canvas.tokens.controlled || [game.user.character]; 3 | 4 | // Requires an item called Defensive Flourish that in turn consumes Bardic Inspiration uses 5 | (async () => { 6 | const actors = selected.filter(a => a); 7 | if (actors.length === 0) { 8 | return ui.notifications.error("No actors selected"); 9 | } 10 | 11 | let handled = false; 12 | for (const actor of actors) { 13 | const itemId = actor.items.find(i => i.name === 'Defensive Flourish')?.id; 14 | const item = actor.items.get(itemId); 15 | if (!item) continue; 16 | 17 | const roll = BetterRolls.rollItem(item); 18 | await roll.toMessage(); 19 | 20 | if (roll.error) continue; 21 | 22 | const value = roll.entriesFlattened().find(m => m.type === 'damage')?.baseRoll.total; 23 | const label = "Defensive Flourish"; 24 | const key = "data.attributes.ac.value"; 25 | 26 | const existing = actor.effects.entries.find(e => e.data.label === label); 27 | if (existing) { 28 | existing.update({ 29 | changes: [ 30 | { key, mode: 2, value, priority: 20 } 31 | ], 32 | disabled: false 33 | }); 34 | console.log(existing); 35 | } else { 36 | actor.createEmbeddedEntity('ActiveEffect', { 37 | label, 38 | icon: effectImagePath ?? item.img, 39 | duration: { rounds: 1 }, 40 | changes: [ 41 | { key, mode: 2, value, priority: 20 } 42 | ] 43 | }); 44 | } 45 | 46 | handled = true; 47 | } 48 | 49 | if (!handled) { 50 | ui.notifications.warn("No actors with a Defensive Flourish item was selected"); 51 | } 52 | })(); 53 | -------------------------------------------------------------------------------- /samples/life-cycle-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Basic Integration test that tests that a roll can be created, edited, updated, or what have you. 3 | * NOTE: Incomplete, was accidentally pushed. Will be updated later anyways. 4 | * As of the current release, when a roll is created, that's it. This will be used 5 | * to test that a roll can be edited using the same object for more advanced macros. 6 | */ 7 | 8 | (async () => { 9 | const settings = { 10 | damagePromptEnabled: true 11 | }; 12 | 13 | const card = BetterRolls.rollItem(item, { settings }); 14 | card.addField("header"); 15 | card.addField("flavor"); 16 | card.addField("description"); 17 | card.addField("attack"); 18 | card.addField("damage", { index: "all", versatile: true }); 19 | card.toMessage(); 20 | })(); 21 | --------------------------------------------------------------------------------