diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b6f4097e..80c01bce 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,6 +17,10 @@ We welcome contributions of all kinds: Please be respectful and collaborative — we’re all here to build something great together. +### Community Translations + +Please note that we are not accepting community translations in the main project. Instead, community translations should be published as a module. + --- ## 🧭 General Guidelines diff --git a/lang/en.json b/lang/en.json index adaaf63f..2db4c709 100755 --- a/lang/en.json +++ b/lang/en.json @@ -96,6 +96,7 @@ "attackName": "Attack Name", "includeBase": { "label": "Include Item Damage" }, "multiplier": "Multiplier", + "saveHint": "Set a default Trait to enable Reaction Roll. It can be changed later in Reaction Roll Dialog.", "resultBased": { "label": "Formula based on Hope/Fear result." }, @@ -310,7 +311,8 @@ "toLoadout": "Send to Loadout", "toVault": "Send to Vault", "unequip": "Unequip", - "useItem": "Use Item" + "useItem": "Use Item", + "cancelBeastform": "Cancel Beastform" }, "Countdown": { "addCountdown": "Add Countdown", @@ -1908,6 +1910,7 @@ "tier4": "tier 4", "domains": "Domains", "downtime": "Downtime", + "roll": "Roll", "rules": "Rules", "types": "Types" }, @@ -1961,6 +1964,7 @@ "fear": "Fear", "features": "Features", "formula": "Formula", + "gm": "GM", "healing": "Healing", "healingRoll": "Healing Roll", "hit": { @@ -1997,6 +2001,10 @@ "none": "None", "noTarget": "No current target", "partner": "Partner", + "player": { + "single": "Player", + "plurial": "Players" + }, "proficiency": "Proficiency", "quantity": "Quantity", "range": "Range", @@ -2022,6 +2030,7 @@ }, "title": "Title", "total": "Total", + "traitModifier": "Trait Modifier", "true": "True", "type": "Type", "unarmed": "Unarmed", @@ -2204,10 +2213,35 @@ "playerCanEditSheet": { "label": "Players Can Manually Edit Character Settings", "hint": "Players are allowed to access the manual Character Settings and change their statistics beyond the rules." + }, + "roll": { + "roll": { + "label": "Roll", + "hint": "Auto behavior for rolls like Attack, Spellcast, etc." + }, + "damage": { + "label": "Damage/Healing Roll", + "hint": "Auto behavior for Damage & Healing rolls after the Attack/Spellcast." + }, + "save": { + "label": "Reaction Roll", + "hint": "Auto behavior if a Reaction Roll is needed. Targets must be selected before the action is made" + }, + "damageApply": { + "label": "Apply Damage/Healing", + "hint": "Automatically apply damages & healings. Targets must be selected before the action is made and Reaction Roll Automation must be different than Never. Bypass users permissions." + }, + "effect": { + "label": "Apply Effects", + "hint": "Automatically apply effects. Targets must be selected before the action is made and Reaction Roll Automation must be different than Never. Bypass users permissions." + } } }, "defeated": { "title": "Defeated Handling" + }, + "roll": { + "title": "Actions" } }, "Homebrew": { @@ -2411,6 +2445,7 @@ "beastformInapplicable": "A beastform can only be applied to a Character.", "beastformAlreadyApplied": "The character already has a beastform applied!", "noTargetsSelected": "No targets are selected.", + "noTargetsSelectedOrPerm": "No targets are selected or with the update permission.", "attackTargetDoesNotExist": "The target token no longer exists", "insufficentAdvancements": "You don't have enough advancements left.", "noAssignedPlayerCharacter": "You have no assigned character.", diff --git a/module/applications/dialogs/d20RollDialog.mjs b/module/applications/dialogs/d20RollDialog.mjs index d445f24c..ed8fd6a1 100644 --- a/module/applications/dialogs/d20RollDialog.mjs +++ b/module/applications/dialogs/d20RollDialog.mjs @@ -1,3 +1,5 @@ +import { abilities } from "../../config/actorConfig.mjs"; + const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api; export default class D20RollDialog extends HandlebarsApplicationMixin(ApplicationV2) { @@ -7,7 +9,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio this.roll = roll; this.config = config; this.config.experiences = []; - this.reactionOverride = config.roll?.type === 'reaction'; + this.reactionOverride = config.actionType === 'reaction'; if (config.source?.action) { this.item = config.data.parent.items.get(config.source.item) ?? config.data.parent; @@ -20,7 +22,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio static DEFAULT_OPTIONS = { tag: 'form', - id: 'roll-selection', + // id: 'roll-selection', classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'roll-selection'], position: { width: 'auto' @@ -42,7 +44,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio }; get title() { - return this.config.title; + return `${this.config.title}${this.actor ? `: ${this.actor.name}` : ''}`; } get actor() { @@ -113,15 +115,21 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio context.isLite = this.config.roll?.lite; context.extraFormula = this.config.extraFormula; context.formula = this.roll.constructFormula(this.config); + if(this.actor.system.traits) context.abilities = this.getTraitModifiers(); - context.showReaction = !context.rollConfig.type && context.rollType === 'DualityRoll'; + context.showReaction = !this.config.roll?.type && context.rollType === 'DualityRoll'; context.reactionOverride = this.reactionOverride; } return context; } + getTraitModifiers() { + return Object.values(abilities).map(a => ({ id: a.id, label: `${game.i18n.localize(a.label)} (${this.actor.system.traits[a.id]?.value.signedString() ?? 0})` })) + } + static updateRollConfiguration(event, _, formData) { const { ...rest } = foundry.utils.expandObject(formData.object); + this.config.selectedRollMode = rest.selectedRollMode; if (this.config.costs) { @@ -133,6 +141,12 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio this.roll[key] = value; }); } + if(rest.hasOwnProperty("trait")) { + this.config.roll.trait = rest.trait; + this.config.title = game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { + ability: game.i18n.localize(abilities[this.config.roll.trait]?.label) + }); + } this.config.extraFormula = rest.extraFormula; this.render(); } @@ -151,31 +165,29 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio this.config.experiences.indexOf(button.dataset.key) > -1 ? this.config.experiences.filter(x => x !== button.dataset.key) : [...this.config.experiences, button.dataset.key]; - if (this.config?.data?.parent?.type === 'character' || this.config?.data?.parent?.type === 'companion') { - this.config.costs = - this.config.costs.indexOf(this.config.costs.find(c => c.extKey === button.dataset.key)) > -1 - ? this.config.costs.filter(x => x.extKey !== button.dataset.key) - : [ - ...this.config.costs, - { - extKey: button.dataset.key, - key: 'hope', - value: 1, - name: this.config.data?.experiences?.[button.dataset.key]?.name - } - ]; - } + this.config.costs = + this.config.costs.indexOf(this.config.costs.find(c => c.extKey === button.dataset.key)) > -1 + ? this.config.costs.filter(x => x.extKey !== button.dataset.key) + : [ + ...this.config.costs, + { + extKey: button.dataset.key, + key: this.config?.data?.parent?.isNPC ? 'fear' : 'hope', + value: 1, + name: this.config.data?.experiences?.[button.dataset.key]?.name + } + ]; this.render(); } static toggleReaction() { if (this.config.roll) { this.reactionOverride = !this.reactionOverride; - this.config.roll.type = this.reactionOverride + this.config.actionType = this.reactionOverride ? CONFIG.DH.ITEM.actionTypes.reaction.id - : this.config.roll.type === CONFIG.DH.ITEM.actionTypes.reaction.id + : this.config.actionType === CONFIG.DH.ITEM.actionTypes.reaction.id ? null - : this.config.roll.type; + : this.config.actionType; this.render(); } } diff --git a/module/applications/settings/automationSettings.mjs b/module/applications/settings/automationSettings.mjs index 0157e016..4407897d 100644 --- a/module/applications/settings/automationSettings.mjs +++ b/module/applications/settings/automationSettings.mjs @@ -35,13 +35,14 @@ export default class DhAutomationSettings extends HandlebarsApplicationMixin(App header: { template: 'systems/daggerheart/templates/settings/automation-settings/header.hbs' }, general: { template: 'systems/daggerheart/templates/settings/automation-settings/general.hbs' }, rules: { template: 'systems/daggerheart/templates/settings/automation-settings/rules.hbs' }, + roll: { template: 'systems/daggerheart/templates/settings/automation-settings/roll.hbs' }, footer: { template: 'systems/daggerheart/templates/settings/automation-settings/footer.hbs' } }; /** @inheritdoc */ static TABS = { main: { - tabs: [{ id: 'general' }, { id: 'rules' }], + tabs: [{ id: 'general' }, { id: 'rules' }, { id: 'roll' }], initial: 'general', labelPrefix: 'DAGGERHEART.GENERAL.Tabs' } diff --git a/module/applications/sheets/actors/adversary.mjs b/module/applications/sheets/actors/adversary.mjs index f575a2f2..64f48d02 100644 --- a/module/applications/sheets/actors/adversary.mjs +++ b/module/applications/sheets/actors/adversary.mjs @@ -146,9 +146,9 @@ export default class AdversarySheet extends DHBaseActorSheet { title: `Reaction Roll: ${this.actor.name}`, headerTitle: 'Adversary Reaction Roll', roll: { - type: 'reaction' + type: 'trait' }, - type: 'trait', + actionType: 'reaction', hasRoll: true, data: this.actor.getRollData() }; diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 465da28d..308faee7 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -637,6 +637,15 @@ export default class CharacterSheet extends DHBaseActorSheet { const presets = { compendium: 'daggerheart', folder: key, + filter: + key === 'subclasses' + ? { + 'system.linkedClass.uuid': { + key: 'system.linkedClass.uuid', + value: this.document.system.class.value._stats.compendiumSource + } + } + : undefined, render: { noFolder: true } @@ -670,31 +679,7 @@ export default class CharacterSheet extends DHBaseActorSheet { }) }); - this.consumeResource(result?.costs); - } - - // Remove when Action Refactor part #2 done - async consumeResource(costs) { - if (!costs?.length) return; - const usefulResources = { - ...foundry.utils.deepClone(this.actor.system.resources), - fear: { - value: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear), - max: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear, - reversed: false - } - }; - const resources = game.system.api.fields.ActionFields.CostField.getRealCosts(costs).map(c => { - const resource = usefulResources[c.key]; - return { - key: c.key, - value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1), - target: resource.target, - keyIsID: resource.keyIsID - }; - }); - - await this.actor.modifyResource(resources); + if(result) game.system.api.fields.ActionFields.CostField.execute.call(this, result); } //TODO: redo toggleEquipItem method diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index b9135b46..b1dcfe50 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -412,6 +412,16 @@ export default function DHApplicationMixin(Base) { ]; if (usable) { + options.unshift({ + name: 'DAGGERHEART.APPLICATIONS.ContextMenu.cancelBeastform', + icon: 'fa-solid fa-ban', + condition: target => { + const doc = getDocFromElementSync(target); + return doc && doc.system?.actions?.some(a => a.type === "beastform"); + }, + callback: async target => game.system.api.fields.ActionFields.BeastformField.handleActiveTransformations.call(await getDocFromElement(target)) + }); + options.unshift({ name: 'DAGGERHEART.GENERAL.damage', icon: 'fa-solid fa-explosion', @@ -422,7 +432,9 @@ export default function DHApplicationMixin(Base) { callback: async (target, event) => { const doc = await getDocFromElement(target), action = doc?.system?.attack ?? doc; - return action && action.use(event, { byPassRoll: true }); + const config = action.prepareConfig(event); + config.hasRoll = false; + return action && action.workflow.get("damage").execute(config, null, true); } }); diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs index a80974ed..21762818 100644 --- a/module/applications/ui/chatLog.mjs +++ b/module/applications/ui/chatLog.mjs @@ -1,5 +1,3 @@ -import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs'; - export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog { constructor(options) { super(options); @@ -55,21 +53,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo } addChatListeners = async (app, html, data) => { - html.querySelectorAll('.duality-action-damage').forEach(element => - element.addEventListener('click', event => this.onRollDamage(event, data.message)) - ); - html.querySelectorAll('.target-save').forEach(element => - element.addEventListener('click', event => this.onRollSave(event, data.message)) - ); - html.querySelectorAll('.roll-all-save-button').forEach(element => - element.addEventListener('click', event => this.onRollAllSave(event, data.message)) - ); html.querySelectorAll('.simple-roll-button').forEach(element => element.addEventListener('click', event => this.onRollSimple(event, data.message)) ); - html.querySelectorAll('.healing-button').forEach(element => - element.addEventListener('click', event => this.onHealing(event, data.message)) - ); html.querySelectorAll('.ability-use-button').forEach(element => element.addEventListener('click', event => this.abilityUseButton(event, data.message)) ); @@ -90,80 +76,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo super.close(options); } - async getActor(uuid) { - return await foundry.utils.fromUuid(uuid); - } - - getAction(actor, itemId, actionId) { - const item = actor.items.get(itemId), - action = - actor.system.attack?._id === actionId - ? actor.system.attack - : item.system.attack?._id === actionId - ? item.system.attack - : item?.system?.actions?.get(actionId); - return action; - } - - async onRollDamage(event, message) { - event.stopPropagation(); - const actor = await this.getActor(message.system.source.actor); - if(!actor.isOwner) return true; - if (message.system.source.item && message.system.source.action) { - const action = this.getAction(actor, message.system.source.item, message.system.source.action); - if (!action || !action?.rollDamage) return; - await action.rollDamage(event, message); - } - } - - async onRollSave(event, message) { - event.stopPropagation(); - const actor = await this.getActor(message.system.source.actor), - tokenId = event.target.closest('[data-token]')?.dataset.token, - token = game.canvas.tokens.get(tokenId); - if (!token?.actor || !token.isOwner) return true; - if (message.system.source.item && message.system.source.action) { - const action = this.getAction(actor, message.system.source.item, message.system.source.action); - if (!action || !action?.hasSave) return; - action.rollSave(token.actor, event, message).then(result => - emitAsGM( - GMUpdateEvent.UpdateSaveMessage, - action.updateSaveMessage.bind(action, result, message, token.id), - { - action: action.uuid, - message: message._id, - token: token.id, - result - } - ) - ); - } - } - - async onRollAllSave(event, message) { - event.stopPropagation(); - if (!game.user.isGM) return; - const targets = event.target.parentElement.querySelectorAll('[data-token] .target-save'); - const actor = await this.getActor(message.system.source.actor), - action = this.getAction(actor, message.system.source.item, message.system.source.action); - targets.forEach(async el => { - const tokenId = el.closest('[data-token]')?.dataset.token, - token = game.canvas.tokens.get(tokenId); - if (!token.actor) return; - if (game.user === token.actor.owner) el.dispatchEvent(new PointerEvent('click', { shiftKey: true })); - else { - token.actor.owner - .query('reactionRoll', { - actionId: action.uuid, - actorId: token.actor.uuid, - event, - message - }) - .then(result => action.updateSaveMessage(result, message, token.id)); - } - }); - } - async onRollSimple(event, message) { const buttonType = event.target.dataset.type ?? 'damage', total = message.rolls.reduce((a, c) => a + Roll.fromJSON(c).total, 0), @@ -197,8 +109,11 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo item.system.attack?.id === event.currentTarget.id ? item.system.attack : item.system.actions.get(event.currentTarget.id); - if (event.currentTarget.dataset.directDamage) action.use(event, { byPassRoll: true }); - else action.use(event); + if (event.currentTarget.dataset.directDamage) { + const config = action.prepareConfig(event); + config.hasRoll = false; + action.workflow.get("damage").execute(config, null, true); + } else action.use(event); } async actionUseButton(event, message) { diff --git a/module/applications/ui/fearTracker.mjs b/module/applications/ui/fearTracker.mjs index ace2bbb2..a346aa66 100644 --- a/module/applications/ui/fearTracker.mjs +++ b/module/applications/ui/fearTracker.mjs @@ -78,7 +78,7 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV /** @override */ async _preRender(context, options) { - if (this.currentFear > this.maxFear) + if (this.currentFear > this.maxFear && game.user.isGM) await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, this.maxFear); } @@ -107,18 +107,5 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV async updateFear(value) { return emitAsGM(GMUpdateEvent.UpdateFear, game.settings.set.bind(game.settings, CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear), value); - /* if(!game.user.isGM) - await game.socket.emit(`system.${CONFIG.DH.id}`, { - action: socketEvent.GMUpdate, - data: { - action: GMUpdateEvent.UpdateFear, - update: value - } - }); - else - game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, value); */ - /* if (!game.user.isGM) return; - value = Math.max(0, Math.min(this.maxFear, value)); - await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, value); */ } } diff --git a/module/config/itemConfig.mjs b/module/config/itemConfig.mjs index cee4bc52..7e9f75ea 100644 --- a/module/config/itemConfig.mjs +++ b/module/config/itemConfig.mjs @@ -865,6 +865,9 @@ export const weaponFeatures = { name: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.name', description: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.description', img: 'icons/commodities/currency/coins-crown-stack-gold.webp', + target: { + type: 'self' + }, // Should cost handful of gold, effects: [ { diff --git a/module/config/settingsConfig.mjs b/module/config/settingsConfig.mjs index 76234a97..0f148aeb 100644 --- a/module/config/settingsConfig.mjs +++ b/module/config/settingsConfig.mjs @@ -29,3 +29,22 @@ export const gameSettings = { Countdowns: 'Countdowns', LastMigrationVersion: 'LastMigrationVersion' }; + +export const actionAutomationChoices = { + never: { + id: "never", + label: "Never" + }, + showDialog: { + id: "showDialog", + label: "Show Dialog only" + }, + // npcOnly: { + // id: "npcOnly", + // label: "Always for non-characters" + // }, + always: { + id: "always", + label: "Always" + } +} diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index ea619bcf..6e522ceb 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -1,14 +1,9 @@ import DhpActor from '../../documents/actor.mjs'; import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs'; import { ActionMixin } from '../fields/actionField.mjs'; -import { abilities } from '../../config/actorConfig.mjs'; const fields = foundry.data.fields; -/* - !!! I'm currently refactoring the whole Action thing, it's a WIP !!! -*/ - /* ToDo - Target Check / Target Picker @@ -20,6 +15,7 @@ const fields = foundry.data.fields; export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel) { static extraSchemas = ['cost', 'uses', 'range']; + /** @inheritDoc */ static defineSchema() { const schemaFields = { _id: new fields.DocumentIdField({ initial: () => foundry.utils.randomID() }), @@ -37,31 +33,76 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel }; this.extraSchemas.forEach(s => { - let clsField; - if ((clsField = this.getActionField(s))) schemaFields[s] = new clsField(); + let clsField = this.getActionField(s); + if (clsField) + schemaFields[s] = new clsField(); }); return schemaFields; } + /** + * Create a Map containing each Action step based on fields define in schema. Ordered by Fields order property. + * + * Each step can be called individually as long as needed config is provided. + * Ex: .workflow.get("damage").execute(config) + * @returns {Map} + */ + defineWorkflow() { + const workflow = new Map(); + this.constructor.extraSchemas.forEach(s => { + let clsField = this.constructor.getActionField(s); + if (clsField?.execute) { + workflow.set(s, { order: clsField.order, execute: clsField.execute.bind(this) } ); + if( s === "damage" ) workflow.set("applyDamage", { order: 75, execute: clsField.applyDamage.bind(this) } ); + } + }); + return new Map([...workflow.entries()].sort(([aKey, aValue], [bKey, bValue]) => aValue.order - bValue.order)); + } + + /** + * Getter returning the workflow property or creating it the first time the property is called + */ + get workflow() { + if ( this.hasOwnProperty("_workflow") ) return this._workflow; + const workflow = Object.freeze(this.defineWorkflow()); + Object.defineProperty(this, "_workflow", {value: workflow, writable: false}); + return workflow; + } + + /** + * Get the Field class from ActionFields global config + * @param {string} name Field short name, equal to Action property + * @returns Action Field + */ static getActionField(name) { const field = game.system.api.fields.ActionFields[`${name.capitalize()}Field`]; return fields.DataField.isPrototypeOf(field) && field; } + /** @inheritDoc */ prepareData() { this.name = this.name || game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[this.type].name); this.img = this.img ?? this.parent?.parent?.img; } + /** + * Get Action ID + */ get id() { return this._id; } + /** + * Return Item the action is attached too. + */ get item() { return this.parent.parent; } + /** + * Return the first Actor parent found. + */ get actor() { return this.item instanceof DhpActor ? this.item @@ -74,6 +115,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel return 'trait'; } + /** + * Prepare base data based on Action Type & Parent Type + * @param {object} parent + * @returns {object} + */ static getSourceConfig(parent) { const updateSource = {}; if (parent?.parent?.type === 'weapon' && this === game.system.api.models.actions.actionsTypes.attack) { @@ -96,6 +142,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel return updateSource; } + /** + * Obtain a data object used to evaluate any dice rolls associated with this particular Action + * @param {object} [data ={}] Optional data object from previous configuration/rolls + * @returns {object} + */ getRollData(data = {}) { if (!this.actor) return null; const actorData = this.actor.getRollData(false); @@ -111,19 +162,30 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel return actorData; } - async use(event, options = {}) { + /** + * Execute each part of the Action workflow in order, calling a specific event before and after each part. + * @param {object} config Config object usually created from prepareConfig method + */ + async executeWorkflow(config) { + for(const [key, part] of this.workflow) { + if (Hooks.call(`${CONFIG.DH.id}.pre${key.capitalize()}Action`, this, config) === false) return; + if(await part.execute(config) === false) return; + if (Hooks.call(`${CONFIG.DH.id}.post${key.capitalize()}Action`, this, config) === false) return; + } + } + + /** + * Main method to use the Action + * @param {Event} event Event from the button used to trigger the Action + * @returns {object} + */ + async use(event) { if (!this.actor) throw new Error("An Action can't be used outside of an Actor context."); if (this.chatDisplay) await this.toChat(); - let { byPassRoll } = options, - config = this.prepareConfig(event, byPassRoll); - for (let i = 0; i < this.constructor.extraSchemas.length; i++) { - let clsField = this.constructor.getActionField(this.constructor.extraSchemas[i]); - if (clsField?.prepareConfig) { - const keep = clsField.prepareConfig.call(this, config); - if (config.isFastForward && !keep) return; - } - } + + let config = this.prepareConfig(event); + if(!config) return; if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return; @@ -132,36 +194,22 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel config = await D20RollDialog.configure(null, config); if (!config) return; } - - if (config.hasRoll) { - const rollConfig = this.prepareRoll(config); - config.roll = rollConfig; - config = await this.actor.diceRoll(config); - if (!config) return; - } - - if (this.doFollowUp(config)) { - if (this.rollDamage && this.damage.parts.length) await this.rollDamage(event, config); - else if (this.trigger) await this.trigger(event, config); - else if (this.hasSave || this.hasEffect) { - const roll = new CONFIG.Dice.daggerheart.DHRoll(''); - roll._evaluated = true; - await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config); - } - } - - // Consume resources - await this.consume(config); + + // Execute the Action Worflow in order based of schema fields + await this.executeWorkflow(config); if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return; return config; } - /* */ - prepareConfig(event, byPass = false) { - const hasRoll = this.getUseHasRoll(byPass); - return { + /** + * Create the basic config common to every action type + * @param {Event} event Event from the button used to trigger the Action + * @returns {object} + */ + prepareBaseConfig(event) { + const config = { event, title: `${this.item instanceof CONFIG.Actor.documentClass ? '' : `${this.item.name}: `}${game.i18n.localize(this.name)}`, source: { @@ -169,238 +217,95 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel action: this._id, actor: this.actor.uuid }, - dialog: { - configure: hasRoll - }, - type: this.roll?.type ?? this.type, - hasRoll: hasRoll, - hasDamage: this.damage?.parts?.length && this.type !== 'healing', - hasHealing: this.damage?.parts?.length && this.type === 'healing', - hasEffect: !!this.effects?.length, - isDirect: !!this.damage?.direct, + dialog: {}, + actionType: this.actionType, + hasRoll: this.hasRoll, + hasDamage: this.hasDamage, + hasHealing: this.hasHealing, + hasEffect: this.hasEffect, hasSave: this.hasSave, + isDirect: !!this.damage?.direct, selectedRollMode: game.settings.get('core', 'rollMode'), - isFastForward: event.shiftKey, data: this.getRollData(), - evaluate: hasRoll + evaluate: this.hasRoll }; + DHBaseAction.applyKeybindings(config); + return config; } + /** + * Create the config for that action used for its workflow + * @param {Event} event Event from the button used to trigger the Action + * @returns {object} + */ + prepareConfig(event) { + const config = this.prepareBaseConfig(event); + for(const clsField of Object.values(this.schema.fields)) { + if (clsField?.prepareConfig) + if(clsField.prepareConfig.call(this, config) === false) return false; + } + return config; + } + + /** + * Method used to know if a configuration dialog must be shown or not when there is no roll. + * @param {*} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. + * @returns {boolean} + */ requireConfigurationDialog(config) { return !config.event.shiftKey && !config.hasRoll && (config.costs?.length || config.uses); } - prepareRoll() { - const roll = { - baseModifiers: this.roll.getModifier(), - label: 'Attack', - type: this.actionType, - difficulty: this.roll?.difficulty, - formula: this.roll.getFormula(), - advantage: CONFIG.DH.ACTIONS.advantageState[this.roll.advState].value - }; - if (this.roll?.type === 'diceSet' || !this.hasRoll) roll.lite = true; - - return roll; - } - - doFollowUp(config) { - return !config.hasRoll; - } - + /** + * Consume Action configured resources & uses. + * That method is only used when we want those resources to be consumed outside of the use method workflow. + * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. + * @param {boolean} successCost + */ async consume(config, successCost = false) { - const actor = this.actor.system.partner ?? this.actor, - usefulResources = { - ...foundry.utils.deepClone(actor.system.resources), - fear: { - value: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear), - max: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear, - reversed: false - } - }; + await this.workflow.get("cost")?.execute(config, successCost); + await this.workflow.get("uses")?.execute(config, successCost); - for (var cost of config.costs) { - if (cost.keyIsID) { - usefulResources[cost.key] = { - value: cost.value, - target: this.parent.parent, - keyIsID: true - }; - } - } - - const resources = game.system.api.fields.ActionFields.CostField.getRealCosts(config.costs) - .filter( - c => - (!successCost && (!c.consumeOnSuccess || config.roll?.success)) || - (successCost && c.consumeOnSuccess) - ) - .reduce((a, c) => { - const resource = usefulResources[c.key]; - if (resource) { - a.push({ - key: c.key, - value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1), - target: resource.target, - keyIsID: resource.keyIsID - }); - return a; - } - }, []); - - await actor.modifyResource(resources); - if ( - config.uses?.enabled && - ((!successCost && (!config.uses?.consumeOnSuccess || config.roll?.success)) || - (successCost && config.uses?.consumeOnSuccess)) - ) - this.update({ 'uses.value': this.uses.value + 1 }); - - if (config.roll?.success || successCost) { + if (config.roll && !config.roll.success && successCost) { setTimeout(() => { (config.message ?? config.parent).update({ 'system.successConsumed': true }); }, 50); } } - /* */ - /* ROLL */ - getUseHasRoll(byPass = false) { - return this.hasRoll && !byPass; + /** + * Set if a configuration dialog must be shown or not if a special keyboard key is pressed. + * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. + */ + static applyKeybindings(config) { + config.dialog.configure ??= !(config.event.shiftKey || config.event.altKey || config.event.ctrlKey); } + /** + * Getters to know which parts the action is composed of. A field can exist but configured to not be used. + * @returns {boolean} If that part is in the action. + */ + get hasRoll() { return !!this.roll?.type; } - get modifiers() { - if (!this.actor) return []; - const modifiers = []; - /** Placeholder for specific bonuses **/ - return modifiers; + get hasDamage() { + return this.damage?.parts?.length && this.type !== 'healing' } - /* ROLL */ - /* SAVE */ + get hasHealing() { + return this.damage?.parts?.length && this.type === 'healing' + } + get hasSave() { return !!this.save?.trait; } - /* SAVE */ - /* EFFECTS */ get hasEffect() { return this.effects?.length > 0; } - async applyEffects(event, data, targets) { - targets ??= data.system.targets; - const force = true; /* Where should this come from? */ - if (!this.effects?.length || !targets.length) return; - let effects = this.effects; - targets.forEach(async token => { - if (!token.hit && !force) return; - if (this.hasSave && token.saved.success === true) { - effects = this.effects.filter(e => e.onSave === true); - } - if (!effects.length) return; - effects.forEach(async e => { - const actor = canvas.tokens.get(token.id)?.actor, - effect = this.item.effects.get(e._id); - if (!actor || !effect) return; - await this.applyEffect(effect, actor); - }); - }); - } - - async applyEffect(effect, actor) { - const existingEffect = actor.effects.find(e => e.origin === effect.uuid); - if (existingEffect) { - return effect.update( - foundry.utils.mergeObject({ - ...effect.constructor.getInitialDuration(), - disabled: false - }) - ); - } - - // Otherwise, create a new effect on the target - const effectData = foundry.utils.mergeObject({ - ...effect.toObject(), - disabled: false, - transfer: false, - origin: effect.uuid - }); - await ActiveEffect.implementation.create(effectData, { parent: actor }); - } - /* EFFECTS */ - - /* SAVE */ - async rollSave(actor, event, message) { - if (!actor) return; - const title = actor.isNPC - ? game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll') - : game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { - ability: game.i18n.localize(abilities[this.save.trait]?.label) - }); - return actor.diceRoll({ - event, - title, - roll: { - trait: this.save.trait, - difficulty: this.save.difficulty ?? this.actor?.baseSaveDifficulty, - type: 'reaction' - }, - type: 'trait', - hasRoll: true, - data: actor.getRollData() - }); - } - - updateSaveMessage(result, message, targetId) { - if (!result) return; - const updateMsg = this.updateChatMessage.bind(this, message, targetId, { - result: result.roll.total, - success: result.roll.success - }); - if (game.modules.get('dice-so-nice')?.active) - game.dice3d.waitFor3DAnimationByMessageID(result.message.id ?? result.message._id).then(() => updateMsg()); - else updateMsg(); - } - - static rollSaveQuery({ actionId, actorId, event, message }) { - return new Promise(async (resolve, reject) => { - const actor = await fromUuid(actorId), - action = await fromUuid(actionId); - if (!actor || !actor?.isOwner) reject(); - action.rollSave(actor, event, message).then(result => resolve(result)); - }); - } - /* SAVE */ - - async updateChatMessage(message, targetId, changes, chain = true) { - setTimeout(async () => { - const chatMessage = ui.chat.collection.get(message._id); - - await chatMessage.update({ - flags: { - [game.system.id]: { - reactionRolls: { - [targetId]: changes - } - } - } - }); - }, 100); - if (chain) { - if (message.system.source.message) - this.updateChatMessage(ui.chat.collection.get(message.system.source.message), targetId, changes, false); - const relatedChatMessages = ui.chat.collection.filter(c => c.system.source?.message === message._id); - relatedChatMessages.forEach(c => { - this.updateChatMessage(c, targetId, changes, false); - }); - } - } - /** * Generates a list of localized tags for this action. * @returns {string[]} An array of localized tag strings. diff --git a/module/data/action/beastformAction.mjs b/module/data/action/beastformAction.mjs index 8c2dd31e..657cfde2 100644 --- a/module/data/action/beastformAction.mjs +++ b/module/data/action/beastformAction.mjs @@ -1,10 +1,9 @@ -import BeastformDialog from '../../applications/dialogs/beastformDialog.mjs'; import DHBaseAction from './baseAction.mjs'; export default class DhBeastformAction extends DHBaseAction { static extraSchemas = [...super.extraSchemas, 'beastform']; - async use(event, options) { + /* async use(event, options) { const beastformConfig = this.prepareBeastformConfig(); const abort = await this.handleActiveTransformations(); @@ -82,5 +81,5 @@ export default class DhBeastformAction extends DHBaseAction { beastformEffects.map(x => x.id) ); return existingEffects; - } + } */ } diff --git a/module/data/action/damageAction.mjs b/module/data/action/damageAction.mjs index 7deeb006..b4b3e17c 100644 --- a/module/data/action/damageAction.mjs +++ b/module/data/action/damageAction.mjs @@ -1,65 +1,5 @@ -import { setsEqual } from '../../helpers/utils.mjs'; import DHBaseAction from './baseAction.mjs'; export default class DHDamageAction extends DHBaseAction { static extraSchemas = [...super.extraSchemas, 'damage', 'target', 'effects']; - - getFormulaValue(part, data) { - let formulaValue = part.value; - - if (data.hasRoll && part.resultBased && data.roll.result.duality === -1) return part.valueAlt; - - const isAdversary = this.actor.type === 'adversary'; - if (isAdversary && this.actor.system.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id) { - const hasHordeDamage = this.actor.effects.find(x => x.type === 'horde'); - if (hasHordeDamage && !hasHordeDamage.disabled) return part.valueAlt; - } - - return formulaValue; - } - - formatFormulas(formulas, systemData) { - const formattedFormulas = []; - formulas.forEach(formula => { - if (isNaN(formula.formula)) - formula.formula = Roll.replaceFormulaData(formula.formula, this.getRollData(systemData)); - const same = formattedFormulas.find( - f => setsEqual(f.damageTypes, formula.damageTypes) && f.applyTo === formula.applyTo - ); - if (same) same.formula += ` + ${formula.formula}`; - else formattedFormulas.push(formula); - }); - return formattedFormulas; - } - - async rollDamage(event, data) { - const systemData = data.system ?? data; - - let formulas = this.damage.parts.map(p => ({ - formula: this.getFormulaValue(p, systemData).getFormula(this.actor), - damageTypes: p.applyTo === 'hitPoints' && !p.type.size ? new Set(['physical']) : p.type, - applyTo: p.applyTo - })); - - if (!formulas.length) return; - - formulas = this.formatFormulas(formulas, systemData); - - delete systemData.evaluate; - const config = { - ...systemData, - roll: formulas, - dialog: {}, - data: this.getRollData() - }; - if (this.hasSave) config.onSave = this.save.damageMod; - if (data.system) { - config.source.message = data._id; - config.directDamage = false; - } else { - config.directDamage = true; - } - - return CONFIG.Dice.daggerheart.DamageRoll.build(config); - } } diff --git a/module/data/action/macroAction.mjs b/module/data/action/macroAction.mjs index 58b8eba5..b8338cd8 100644 --- a/module/data/action/macroAction.mjs +++ b/module/data/action/macroAction.mjs @@ -2,15 +2,4 @@ import DHBaseAction from './baseAction.mjs'; export default class DHMacroAction extends DHBaseAction { static extraSchemas = [...super.extraSchemas, 'macro']; - - async trigger(event, ...args) { - const fixUUID = !this.macro.includes('Macro.') ? `Macro.${this.macro}` : this.macro, - macro = await fromUuid(fixUUID); - try { - if (!macro) throw new Error(`No macro found for the UUID: ${this.macro}.`); - macro.execute(); - } catch (error) { - ui.notifications.error(error); - } - } } diff --git a/module/data/chat-message/_modules.mjs b/module/data/chat-message/_modules.mjs index a4e2c1fd..36c6ee9d 100644 --- a/module/data/chat-message/_modules.mjs +++ b/module/data/chat-message/_modules.mjs @@ -1,5 +1,5 @@ import DHAbilityUse from "./abilityUse.mjs"; -import DHActorRoll from "./adversaryRoll.mjs"; +import DHActorRoll from "./actorRoll.mjs"; export const config = { abilityUse: DHAbilityUse, diff --git a/module/data/chat-message/adversaryRoll.mjs b/module/data/chat-message/actorRoll.mjs similarity index 98% rename from module/data/chat-message/adversaryRoll.mjs rename to module/data/chat-message/actorRoll.mjs index d87bab6a..b6512fbd 100644 --- a/module/data/chat-message/adversaryRoll.mjs +++ b/module/data/chat-message/actorRoll.mjs @@ -95,7 +95,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { } registerTargetHook() { - if (!this.parent.isAuthor) return; + if (!this.parent.isAuthor || !this.hasTarget) return; if (this.targetMode && this.parent.targetHook !== null) { Hooks.off('targetToken', this.parent.targetHook); return (this.parent.targetHook = null); diff --git a/module/data/fields/action/_module.mjs b/module/data/fields/action/_module.mjs index e6caa963..7a33e147 100644 --- a/module/data/fields/action/_module.mjs +++ b/module/data/fields/action/_module.mjs @@ -6,6 +6,5 @@ export { default as EffectsField } from './effectsField.mjs'; export { default as SaveField } from './saveField.mjs'; export { default as BeastformField } from './beastformField.mjs'; export { default as DamageField } from './damageField.mjs'; -export { default as HealingField } from './healingField.mjs'; export { default as RollField } from './rollField.mjs'; export { default as MacroField } from './macroField.mjs'; diff --git a/module/data/fields/action/beastformField.mjs b/module/data/fields/action/beastformField.mjs index 832bd9f6..62c735d0 100644 --- a/module/data/fields/action/beastformField.mjs +++ b/module/data/fields/action/beastformField.mjs @@ -1,6 +1,13 @@ +import BeastformDialog from "../../../applications/dialogs/beastformDialog.mjs"; + const fields = foundry.data.fields; export default class BeastformField extends fields.SchemaField { + /** + * Action Workflow order + */ + static order = 90; + constructor(options = {}, context = {}) { const beastformFields = { tierAccess: new fields.SchemaField({ @@ -27,4 +34,96 @@ export default class BeastformField extends fields.SchemaField { }; super(beastformFields, options, context); } + + /** + * Beastform Transformation Action Workflow part. + * Must be called within Action context or similar. + * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. + */ + static async execute(config) { + // Should not be useful anymore here + await BeastformField.handleActiveTransformations.call(this); + + const { selected, evolved, hybrid } = await BeastformDialog.configure(config, this.item); + if (!selected) return false; + + return await BeastformField.transform.call(this, selected, evolved, hybrid); + } + + /** + * Update Action Workflow config object. + * Must be called within Action context. + * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. + */ + prepareConfig(config) { + if(this.actor.effects.find(x => x.type === 'beastform')) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.beastformAlreadyApplied')); + return false; + } + + const settingsTiers = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers; + const actorLevel = this.actor.system.levelData.level.current; + const actorTier = + Object.values(settingsTiers).find( + tier => actorLevel >= tier.levels.start && actorLevel <= tier.levels.end + ) ?? 1; + + config.tierLimit = this.beastform.tierAccess.exact ?? actorTier; + } + + /** + * TODO by Harry + * @param {*} selectedForm + * @param {*} evolvedData + * @param {*} hybridData + * @returns + */ + static async transform(selectedForm, evolvedData, hybridData) { + const formData = evolvedData?.form ? evolvedData.form.toObject() : selectedForm.toObject(); + const beastformEffect = formData.effects.find(x => x.type === 'beastform'); + if (!beastformEffect) { + ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect'); + return false; + } + + if (evolvedData?.form) { + const evolvedForm = selectedForm.effects.find(x => x.type === 'beastform'); + if (!evolvedForm) { + ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect'); + return false; + } + + beastformEffect.changes = [...beastformEffect.changes, ...evolvedForm.changes]; + formData.system.features = [...formData.system.features, ...selectedForm.system.features.map(x => x.uuid)]; + } + + if (selectedForm.system.beastformType === CONFIG.DH.ITEM.beastformTypes.hybrid.id) { + formData.system.advantageOn = Object.values(hybridData.advantages).reduce((advantages, formCategory) => { + Object.keys(formCategory).forEach(advantageKey => { + advantages[advantageKey] = formCategory[advantageKey]; + }); + return advantages; + }, {}); + formData.system.features = [ + ...formData.system.features, + ...Object.values(hybridData.features).flatMap(x => Object.keys(x)) + ]; + } + + this.actor.createEmbeddedDocuments('Item', [formData]); + } + + /** + * Remove existing beastform effect and return true if there was one + * @returns {boolean} + */ + static async handleActiveTransformations() { + const beastformEffects = this.actor.effects.filter(x => x.type === 'beastform'); + const existingEffects = beastformEffects.length > 0; + await this.actor.deleteEmbeddedDocuments( + 'ActiveEffect', + beastformEffects.map(x => x.id) + ); + return existingEffects; + } } diff --git a/module/data/fields/action/costField.mjs b/module/data/fields/action/costField.mjs index c224fff0..ca942909 100644 --- a/module/data/fields/action/costField.mjs +++ b/module/data/fields/action/costField.mjs @@ -1,6 +1,12 @@ const fields = foundry.data.fields; export default class CostField extends fields.ArrayField { + /** + * Action Workflow order + */ + static order = 150; + + /** @inheritDoc */ constructor(options = {}, context = {}) { const element = new fields.SchemaField({ key: new fields.StringField({ @@ -20,15 +26,80 @@ export default class CostField extends fields.ArrayField { super(element, options, context); } - static prepareConfig(config) { + /** + * Cost Consumption Action Workflow part. + * Consume configured action resources. + * Must be called within Action context or similar. + * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. + * @param {boolean} [successCost=false] Consume only resources configured as "On Success only" if not already consumed. + */ + static async execute(config, successCost = false) { + const actor= this.actor.system.partner ?? this.actor, + usefulResources = { + ...foundry.utils.deepClone(actor.system.resources), + fear: { + value: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear), + max: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear, + reversed: false + } + }; + + if(this.parent?.parent) { + for (var cost of config.costs) { + if (cost.keyIsID) { + usefulResources[cost.key] = { + value: cost.value, + target: this.parent.parent, + keyIsID: true + }; + } + } + } + + const resources = CostField.getRealCosts(config.costs) + .filter( + c => + (!successCost && (!c.consumeOnSuccess || config.roll?.success)) || + (successCost && c.consumeOnSuccess) + ) + .reduce((a, c) => { + const resource = usefulResources[c.key]; + if (resource) { + a.push({ + key: c.key, + value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1), + target: resource.target, + keyIsID: resource.keyIsID + }); + return a; + } + }, []); + + await actor.modifyResource(resources); + } + + /** + * Update Action Workflow config object. + * Must be called within Action context or similar. + * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. + * @returns {boolean} Return false if fast-forwarded and no more uses. + */ + prepareConfig(config) { const costs = this.cost?.length ? foundry.utils.deepClone(this.cost) : []; config.costs = CostField.calcCosts.call(this, costs); const hasCost = CostField.hasCost.call(this, config.costs); - if (config.isFastForward && !hasCost) - return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.insufficientResources')); - return hasCost; + if (config.dialog.configure === false && !hasCost) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.insufficientResources')); + return hasCost; + } } + /** + * + * Must be called within Action context. + * @param {*} costs + * @returns + */ static calcCosts(costs) { const resources = CostField.getResources.call(this, costs); return costs.map(c => { @@ -40,13 +111,19 @@ export default class CostField extends fields.ArrayField { c.key === 'fear' ? game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear) : resources[c.key].isReversed - ? resources[c.key].max + ? resources[c.key].max - resources[c.key].value : resources[c.key].value; if (c.scalable) c.maxStep = Math.floor((c.max - c.value) / c.step); return c; }); } + /** + * Check if the current Actor currently has all needed resources. + * Must be called within Action context. + * @param {*} costs + * @returns {boolean} + */ static hasCost(costs) { const realCosts = CostField.getRealCosts.call(this, costs), hasFearCost = realCosts.findIndex(c => c.key === 'fear'); @@ -73,6 +150,12 @@ export default class CostField extends fields.ArrayField { ); } + /** + * Get all Actor resources + parent Item potential one. + * Must be called within Action context. + * @param {*} costs + * @returns + */ static getResources(costs) { const actorResources = foundry.utils.deepClone(this.actor.system.resources); if (this.actor.system.partner) @@ -93,6 +176,11 @@ export default class CostField extends fields.ArrayField { }; } + /** + * + * @param {*} costs + * @returns + */ static getRealCosts(costs) { const realCosts = costs?.length ? costs.filter(c => c.enabled) : []; let mergedCosts = []; @@ -104,6 +192,12 @@ export default class CostField extends fields.ArrayField { return mergedCosts; } + /** + * Format scalable max cost, inject Action datas if it's a formula. + * Must be called within Action context. + * @param {number|string} max Configured maximum for that resource. + * @returns {number} The max cost value. + */ static formatMax(max) { max ??= 0; if (isNaN(max)) { diff --git a/module/data/fields/action/damageField.mjs b/module/data/fields/action/damageField.mjs index 4b4dee7d..db43cfb0 100644 --- a/module/data/fields/action/damageField.mjs +++ b/module/data/fields/action/damageField.mjs @@ -1,8 +1,15 @@ import FormulaField from '../formulaField.mjs'; +import { setsEqual } from '../../../helpers/utils.mjs'; const fields = foundry.data.fields; export default class DamageField extends fields.SchemaField { + /** + * Action Workflow order + */ + static order = 20; + + /** @inheritDoc */ constructor(options, context = {}) { const damageFields = { parts: new fields.ArrayField(new fields.EmbeddedDataField(DHDamageData)), @@ -14,6 +21,141 @@ export default class DamageField extends fields.SchemaField { }; super(damageFields, options, context); } + + /** + * Roll Damage/Healing Action Workflow part. + * Must be called within Action context or similar. + * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. + * @param {string} [messageId=null] ChatMessage Id where the clicked button belong. + * @param {boolean} [force=false] If the method should be executed outside of Action workflow, for ChatMessage button for example. + */ + static async execute(config, messageId = null, force = false) { + if(!this.hasDamage && !this.hasHealing) return; + if((this.hasRoll && DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.never.id) && !force) return; + + let formulas = this.damage.parts.map(p => ({ + formula: DamageField.getFormulaValue.call(this, p, config).getFormula(this.actor), + damageTypes: p.applyTo === 'hitPoints' && !p.type.size ? new Set(['physical']) : p.type, + applyTo: p.applyTo + })); + + if (!formulas.length) return false; + + formulas = DamageField.formatFormulas.call(this, formulas, config); + + const damageConfig = { + ...config, + roll: formulas, + dialog: {}, + data: this.getRollData() + }; + delete damageConfig.evaluate; + + if(DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.always.id) damageConfig.dialog.configure = false; + if (config.hasSave) config.onSave = damageConfig.onSave = this.save.damageMod; + + damageConfig.source.message = config.message?._id ?? messageId; + damageConfig.directDamage = !!damageConfig.source?.message; + + if(damageConfig.source?.message && game.modules.get('dice-so-nice')?.active) + await game.dice3d.waitFor3DAnimationByMessageID(damageConfig.source.message); + + const damageResult = await CONFIG.Dice.daggerheart.DamageRoll.build(damageConfig); + if(!damageResult) return false; + config.damage = damageResult.damage; + config.message ??= damageConfig.message; + } + + /** + * Apply Damage/Healing Action Worflow part. + * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. + * @param {*[]} targets Arrays of targets to bypass pre-selected ones. + * @param {boolean} force If the method should be executed outside of Action workflow, for ChatMessage button for example. + */ + static async applyDamage(config, targets = null, force = false) { + targets ??= config.targets.filter(target => target.hit); + if(!config.damage || !targets?.length || (!DamageField.getApplyAutomation() && !force)) return; + for (let target of targets) { + const actor = fromUuidSync(target.actorId); + if(!actor) continue; + if ( + !config.hasHealing && + config.onSave && + target.saved?.success === true + ) { + const mod = CONFIG.DH.ACTIONS.damageOnSave[config.onSave]?.mod ?? 1; + Object.entries(config.damage).forEach(([k, v]) => { + v.total = 0; + v.parts.forEach(part => { + part.total = Math.ceil(part.total * mod); + v.total += part.total; + }); + }); + } + + if (config.hasHealing) actor.takeHealing(config.damage); + else actor.takeDamage(config.damage, config.isDirect); + } + } + + /** + * Return value or valueAlt from damage part + * Must be called within Action context or similar. + * @param {object} part Damage Part + * @param {object} data Action getRollData + * @returns Formula value object + */ + static getFormulaValue(part, data) { + let formulaValue = part.value; + + if (data.hasRoll && part.resultBased && data.roll.result.duality === -1) return part.valueAlt; + + const isAdversary = this.actor.type === 'adversary'; + if (isAdversary && this.actor.system.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id) { + const hasHordeDamage = this.actor.effects.find(x => x.type === 'horde'); + if (hasHordeDamage && !hasHordeDamage.disabled) return part.valueAlt; + } + + return formulaValue; + } + + /** + * Prepare formulas for Damage Roll + * Must be called within Action context or similar. + * @param {object[]} formulas Array of formatted formulas object + * @param {object} data Action getRollData + * @returns + */ + static formatFormulas(formulas, data) { + const formattedFormulas = []; + formulas.forEach(formula => { + if (isNaN(formula.formula)) + formula.formula = Roll.replaceFormulaData(formula.formula, this.getRollData(data)); + const same = formattedFormulas.find( + f => setsEqual(f.damageTypes, formula.damageTypes) && f.applyTo === formula.applyTo + ); + if (same) same.formula += ` + ${formula.formula}`; + else formattedFormulas.push(formula); + }); + return formattedFormulas; + } + + /** + * Return the automation setting for execute method for current user role + * @returns {string} Id from settingsConfig.mjs actionAutomationChoices + */ + static getAutomation() { + return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damage.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damage.players) + } + + + /** + * Return the automation setting for applyDamage method for current user role + * @returns {boolean} If applyDamage should be triggered automatically + */ + static getApplyAutomation() { + return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damageApply.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damageApply.players) + } } export class DHActionDiceData extends foundry.abstract.DataModel { diff --git a/module/data/fields/action/effectsField.mjs b/module/data/fields/action/effectsField.mjs index ddc69d2d..3b8c5e43 100644 --- a/module/data/fields/action/effectsField.mjs +++ b/module/data/fields/action/effectsField.mjs @@ -1,6 +1,14 @@ +import { emitAsGM, GMUpdateEvent } from "../../../systemRegistration/socket.mjs"; + const fields = foundry.data.fields; export default class EffectsField extends fields.ArrayField { + /** + * Action Workflow order + */ + static order = 100; + + /** @inheritDoc */ constructor(options = {}, context = {}) { const element = new fields.SchemaField({ _id: new fields.DocumentIdField(), @@ -8,4 +16,86 @@ export default class EffectsField extends fields.ArrayField { }); super(element, options, context); } + + /** + * Apply Effects Action Workflow part. + * Must be called within Action context or similar. + * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. + * @param {object[]} [targets=null] Array of targets to override pre-selected ones. + * @param {boolean} [force=false] If the method should be executed outside of Action workflow, for ChatMessage button for example. + */ + static async execute(config, targets = null, force = false) { + if(!config.hasEffect) return; + let message = config.message ?? ui.chat.collection.get(config.parent?._id); + if(!message) { + const roll = new CONFIG.Dice.daggerheart.DHRoll(''); + roll._evaluated = true; + message = config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config); + } + if(EffectsField.getAutomation() || force) { + targets ??= (message.system?.targets ?? config.targets).filter(t => !config.hasRoll || t.hit); + await emitAsGM( + GMUpdateEvent.UpdateEffect, + EffectsField.applyEffects.bind(this), + targets, + this.uuid + ); + // EffectsField.applyEffects.call(this, config.targets.filter(t => !config.hasRoll || t.hit)); + } + } + + /** + * Apply Action Effects to a list of Targets + * Must be called within Action context or similar. + * @param {object[]} targets Array of formatted targets + */ + static async applyEffects(targets) { + if (!this.effects?.length || !targets?.length) return; + let effects = this.effects; + targets.forEach(async token => { + if (this.hasSave && token.saved.success === true) + effects = this.effects.filter(e => e.onSave === true); + if (!effects.length) return; + effects.forEach(async e => { + const actor = canvas.tokens.get(token.id)?.actor, + effect = this.item.effects.get(e._id); + if (!actor || !effect) return; + await EffectsField.applyEffect(effect, actor); + }); + }); + } + + /** + * Apply an Effect to a target or enable it if already on it + * @param {object} effect Effect object containing ActiveEffect UUID + * @param {object} actor Actor Document + */ + static async applyEffect(effect, actor) { + const existingEffect = actor.effects.find(e => e.origin === effect.uuid); + if (existingEffect) { + return effect.update( + foundry.utils.mergeObject({ + ...effect.constructor.getInitialDuration(), + disabled: false + }) + ); + } + + // Otherwise, create a new effect on the target + const effectData = foundry.utils.mergeObject({ + ...effect.toObject(), + disabled: false, + transfer: false, + origin: effect.uuid + }); + await ActiveEffect.implementation.create(effectData, { parent: actor }); + } + + /** + * Return the automation setting for execute method for current user role + * @returns {boolean} If execute should be triggered automatically + */ + static getAutomation() { + return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.effect.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.effect.players) + } } diff --git a/module/data/fields/action/healingField.mjs b/module/data/fields/action/healingField.mjs deleted file mode 100644 index 98f4f5ea..00000000 --- a/module/data/fields/action/healingField.mjs +++ /dev/null @@ -1,12 +0,0 @@ -import { DHDamageData } from './damageField.mjs'; - -const fields = foundry.data.fields; - -export default class HealingField extends fields.SchemaField { - constructor(options, context = {}) { - const healingFields = { - parts: new fields.ArrayField(new fields.EmbeddedDataField(DHDamageData)) - }; - super(healingFields, options, context); - } -} diff --git a/module/data/fields/action/macroField.mjs b/module/data/fields/action/macroField.mjs index 62da0da0..222feb2a 100644 --- a/module/data/fields/action/macroField.mjs +++ b/module/data/fields/action/macroField.mjs @@ -1,7 +1,29 @@ const fields = foundry.data.fields; export default class MacroField extends fields.DocumentUUIDField { + /** + * Action Workflow order + */ + static order = 70; + + /** @inheritDoc */ constructor(context = {}) { super({ type: "Macro" }, context); } + + /** + * Macro Action Workflow part. + * Must be called within Action context or similar or similar. + * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. Currently not used. + */ + static async execute(config) { + const fixUUID = !this.macro.includes('Macro.') ? `Macro.${this.macro}` : this.macro, + macro = await fromUuid(fixUUID); + try { + if (!macro) throw new Error(`No macro found for the UUID: ${this.macro}.`); + macro.execute(); + } catch (error) { + ui.notifications.error(error); + } + } } diff --git a/module/data/fields/action/rangeField.mjs b/module/data/fields/action/rangeField.mjs index 221f00af..1237e507 100644 --- a/module/data/fields/action/rangeField.mjs +++ b/module/data/fields/action/rangeField.mjs @@ -1,6 +1,8 @@ const fields = foundry.data.fields; export default class RangeField extends fields.StringField { + + /** @inheritDoc */ constructor(context = {}) { const options = { choices: CONFIG.DH.GENERAL.range, @@ -11,7 +13,12 @@ export default class RangeField extends fields.StringField { super(options, context); } - static prepareConfig(config) { - return true; + /** + * Update Action Workflow config object. + * NOT YET IMPLEMENTED. + * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. + */ + prepareConfig(config) { + return; } } diff --git a/module/data/fields/action/rollField.mjs b/module/data/fields/action/rollField.mjs index a4df2a9e..cfcfb56b 100644 --- a/module/data/fields/action/rollField.mjs +++ b/module/data/fields/action/rollField.mjs @@ -71,29 +71,6 @@ export class DHActionRollData extends foundry.abstract.DataModel { const modifiers = []; if (!this.parent?.actor) return modifiers; switch (this.parent.actor.type) { - case 'character': - const spellcastingTrait = - this.type === 'spellcast' - ? (this.parent.actor?.system?.spellcastModifierTrait?.key ?? 'agility') - : null; - const trait = - this.useDefault || !this.trait - ? (spellcastingTrait ?? this.parent.item.system.attack?.roll?.trait ?? 'agility') - : this.trait; - if ( - this.type === CONFIG.DH.GENERAL.rollTypes.attack.id || - this.type === CONFIG.DH.GENERAL.rollTypes.trait.id - ) - modifiers.push({ - label: `DAGGERHEART.CONFIG.Traits.${trait}.name`, - value: this.parent.actor.system.traits[trait].value - }); - else if (this.type === CONFIG.DH.GENERAL.rollTypes.spellcast.id) - modifiers.push({ - label: `DAGGERHEART.CONFIG.RollTypes.spellcast.name`, - value: this.parent.actor.system.spellcastModifier - }); - break; case 'companion': case 'adversary': if (this.type === CONFIG.DH.GENERAL.rollTypes.attack.id) @@ -107,10 +84,74 @@ export class DHActionRollData extends foundry.abstract.DataModel { } return modifiers; } + + get rollTrait() { + if(this.parent?.actor?.type !== "character") return null; + switch (this.type) { + case CONFIG.DH.GENERAL.rollTypes.spellcast.id: + return this.parent.actor?.system?.spellcastModifierTrait?.key ?? 'agility'; + case CONFIG.DH.GENERAL.rollTypes.attack.id: + case CONFIG.DH.GENERAL.rollTypes.trait.id: + return this.useDefault || !this.trait + ? this.parent.item.system.attack?.roll?.trait ?? 'agility' + : this.trait; + default: + return null; + } + } } export default class RollField extends fields.EmbeddedDataField { + /** + * Action Workflow order + */ + static order = 10; + + /** @inheritDoc */ constructor(options, context = {}) { super(DHActionRollData, options, context); } + + /** + * Roll Action Workflow part. + * Must be called within Action context or similar. + * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. + */ + static async execute(config) { + if(!config.hasRoll) return; + config = await this.actor.diceRoll(config); + if(!config) return false; + } + + /** + * Update Action Workflow config object. + * Must be called within Action context. + * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. + */ + prepareConfig(config) { + if(!config.hasRoll) return; + + config.dialog.configure = RollField.getAutomation() ? !config.dialog.configure : config.dialog.configure; + + const roll = { + baseModifiers: this.roll.getModifier(), + label: 'Attack', + type: this.roll?.type, + trait: this.roll?.rollTrait, + difficulty: this.roll?.difficulty, + formula: this.roll.getFormula(), + advantage: CONFIG.DH.ACTIONS.advantageState[this.roll.advState].value + }; + if (this.roll.type === 'diceSet' || !this.hasRoll) roll.lite = true; + + config.roll = roll; + } + + /** + * Return the automation setting for execute method for current user role + * @returns {boolean} If execute should be triggered automatically + */ + static getAutomation() { + return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.roll.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.roll.players) + } } diff --git a/module/data/fields/action/saveField.mjs b/module/data/fields/action/saveField.mjs index e93a82a9..0003a4d5 100644 --- a/module/data/fields/action/saveField.mjs +++ b/module/data/fields/action/saveField.mjs @@ -1,6 +1,14 @@ +import { abilities } from "../../../config/actorConfig.mjs"; + const fields = foundry.data.fields; export default class SaveField extends fields.SchemaField { + /** + * Action Workflow order + */ + static order = 50; + + /** @inheritDoc */ constructor(options = {}, context = {}) { const saveFields = { trait: new fields.StringField({ @@ -16,4 +24,151 @@ export default class SaveField extends fields.SchemaField { }; super(saveFields, options, context); } + + /** + * Reaction Roll Action Workflow part. + * Must be called within Action context or similar. + * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. + * @param {object[]} [targets=null] Array of targets to override pre-selected ones. + * @param {boolean} [force=false] If the method should be executed outside of Action workflow, for ChatMessage button for example. + */ + static async execute(config, targets = null, force = false) { + if(!config.hasSave) return; + let message = config.message ?? ui.chat.collection.get(config.parent?._id); + + if(!message) { + const roll = new CONFIG.Dice.daggerheart.DHRoll(''); + roll._evaluated = true; + message = config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config); + } + if(SaveField.getAutomation() !== CONFIG.DH.SETTINGS.actionAutomationChoices.never.id || force) { + targets ??= config.targets.filter(t => !config.hasRoll || t.hit); + await SaveField.rollAllSave.call(this, targets, config.event, message); + } else return false; + } + + /** + * Roll a Reaction Roll for all targets. Send a query to the owner if the User is not. + * Must be called within Action context. + * @param {object[]} targets Array of formatted targets. + * @param {Event} event Triggering event + * @param {ChatMessage} message The ChatMessage the triggered button comes from. + */ + static async rollAllSave(targets, event, message) { + if(!targets) return; + return new Promise(resolve => { + const aPromise = []; + targets.forEach(target => { + aPromise.push( + new Promise(async subResolve => { + const actor = fromUuidSync(target.actorId); + if(actor) { + const rollSave = game.user === actor.owner ? + SaveField.rollSave.call(this, actor, event) + : actor.owner + .query('reactionRoll', { + actionId: this.uuid, + actorId: actor.uuid, + event, + message + }); + const result = await rollSave; + await SaveField.updateSaveMessage.call(this, result, message, target.id); + subResolve(); + } else subResolve(); + }) + ) + }); + Promise.all(aPromise).then(result => resolve()); + }) + } + + /** + * Roll a Reaction Roll for the specified Actor against the Action difficulty. + * Must be called within Action context. + * @param {*} actor Actor document + * @param {Event} event Triggering event + * @returns {object} Actor diceRoll config result. + */ + static async rollSave(actor, event) { + if (!actor) return; + const title = actor.isNPC + ? game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll') + : game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { + ability: game.i18n.localize(abilities[this.save.trait]?.label) + }), + rollConfig = { + event, + title, + roll: { + trait: this.save.trait, + difficulty: this.save.difficulty ?? this.actor?.baseSaveDifficulty, + type: 'trait' + }, + actionType: 'reaction', + hasRoll: true, + data: actor.getRollData() + }; + if(SaveField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.always.id) rollConfig.dialog = { configure: false }; + return actor.diceRoll(rollConfig); + } + + /** + * Update a Roll ChatMessage for a token according to his Reaction Roll result. + * @param {object} result Result from the Reaction Roll + * @param {object} message ChatMessage to update + * @param {string} targetId Token ID + */ + static async updateSaveMessage(result, message, targetId) { + if (!result) return; + const updateMsg = async function(message, targetId, result) { + // setTimeout(async () => { + const chatMessage = ui.chat.collection.get(message._id), + changes = { + flags: { + [game.system.id]: { + reactionRolls: { + [targetId]: + { + result: result.roll.total, + success: result.roll.success + } + } + } + } + }; + await chatMessage.update(changes); + // }, 100); + }; + if (game.modules.get('dice-so-nice')?.active) + game.dice3d.waitFor3DAnimationByMessageID(result.message.id ?? result.message._id).then(async () => await updateMsg(message, targetId, result)); + else await updateMsg(message, targetId, result); + } + + /** + * Return the automation setting for execute method for current user role + * @returns {string} Id from settingsConfig.mjs actionAutomationChoices + */ + static getAutomation() { + return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.save.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.save.players) + } + + /** + * Send a query to an Actor owner to roll a Reaction Roll then send back the result. + * @param {object} param0 + * @param {string} param0.actionId Action ID + * @param {string} param0.actorId Actor ID + * @param {Event} param0.event Triggering event + * @param {ChatMessage} param0.message Chat Message to update + * @returns + */ + static rollSaveQuery({ actionId, actorId, event, message }) { + return new Promise(async (resolve, reject) => { + const actor = await fromUuid(actorId), + action = await fromUuid(actionId); + if (!actor || !actor?.isOwner) reject(); + SaveField.rollSave.call(action, actor, event, message) + .then(result => resolve(result)); + }); + } } diff --git a/module/data/fields/action/targetField.mjs b/module/data/fields/action/targetField.mjs index bfb01db9..4499dcc8 100644 --- a/module/data/fields/action/targetField.mjs +++ b/module/data/fields/action/targetField.mjs @@ -1,6 +1,8 @@ const fields = foundry.data.fields; export default class TargetField extends fields.SchemaField { + + /** @inheritDoc */ constructor(options = {}, context = {}) { const targetFields = { type: new fields.StringField({ @@ -13,44 +15,70 @@ export default class TargetField extends fields.SchemaField { super(targetFields, options, context); } - static prepareConfig(config) { - if (!this.target?.type) return []; + /** + * Update Action Workflow config object. + * Must be called within Action context. + * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. + */ + prepareConfig(config) { + if (!this.target?.type) return config.targets = []; config.hasTarget = true; let targets; + // If the Action is configured as self-targeted, set targets as the owner. if (this.target?.type === CONFIG.DH.GENERAL.targetTypes.self.id) targets = [this.actor.token ?? this.actor.prototypeToken]; else { targets = Array.from(game.user.targets); if (this.target.type !== CONFIG.DH.GENERAL.targetTypes.any.id) { - targets = targets.filter(t => TargetField.isTargetFriendly.call(this, t)); + targets = targets.filter(target => TargetField.isTargetFriendly(this.actor, target, this.target.type)); if (this.target.amount && targets.length > this.target.amount) targets = []; } } config.targets = targets.map(t => TargetField.formatTarget.call(this, t)); const hasTargets = TargetField.checkTargets.call(this, this.target.amount, config.targets); - if (config.isFastForward && !hasTargets) - return ui.notifications.warn('Too many targets selected for that actions.'); - return hasTargets; + if (config.dialog.configure === false && !hasTargets) { + ui.notifications.warn('Too many targets selected for that actions.'); + return hasTargets; + } } + /** + * Check if the number of selected targets respect the amount set in the Action. + * NOT YET IMPLEMENTED. Will be with Target Picker. + * @param {number} amount Max amount of targets configured in the action. + * @param {*[]} targets Array of targeted tokens. + * @returns {boolean} If the amount of targeted tokens does not exceed action configured one. + */ static checkTargets(amount, targets) { return true; // return !amount || (targets.length > amount); } - static isTargetFriendly(target) { - const actorDisposition = this.actor.token - ? this.actor.token.disposition - : this.actor.prototypeToken.disposition, + /** + * Compare 2 Actors disposition between each other + * @param {*} actor First actor document. + * @param {*} target Second actor document. + * @param {string} type Disposition id to compare (friendly/hostile). + * @returns {boolean} If both actors respect the provided type. + */ + static isTargetFriendly(actor, target, type) { + const actorDisposition = actor.token + ? actor.token.disposition + : actor.prototypeToken.disposition, targetDisposition = target.document.disposition; return ( - (this.target.type === CONFIG.DH.GENERAL.targetTypes.friendly.id && + (type === CONFIG.DH.GENERAL.targetTypes.friendly.id && actorDisposition === targetDisposition) || - (this.target.type === CONFIG.DH.GENERAL.targetTypes.hostile.id && + (type === CONFIG.DH.GENERAL.targetTypes.hostile.id && actorDisposition + targetDisposition === 0) ); } + /** + * Format actor to useful datas for Action roll workflow. + * @param {*} actor Actor object to format. + * @returns {*} Formatted Actor. + */ static formatTarget(actor) { return { id: actor.id, diff --git a/module/data/fields/action/usesField.mjs b/module/data/fields/action/usesField.mjs index 5c2bfb61..d180ddf8 100644 --- a/module/data/fields/action/usesField.mjs +++ b/module/data/fields/action/usesField.mjs @@ -3,6 +3,12 @@ import FormulaField from '../formulaField.mjs'; const fields = foundry.data.fields; export default class UsesField extends fields.SchemaField { + /** + * Action Workflow order + */ + static order = 160; + + /** @inheritDoc */ constructor(options = {}, context = {}) { const usesFields = { value: new fields.NumberField({ nullable: true, initial: null }), @@ -20,15 +26,45 @@ export default class UsesField extends fields.SchemaField { super(usesFields, options, context); } - static prepareConfig(config) { + /** + * Uses Consumption Action Workflow part. + * Increment Action spent uses by 1. + * Must be called within Action context or similar or similar. + * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. + * @param {boolean} [successCost=false] Consume only resources configured as "On Success only" if not already consumed. + */ + static async execute(config, successCost = false) { + if ( + config.uses?.enabled && + ((!successCost && (!config.uses?.consumeOnSuccess || config.roll?.success)) || + (successCost && config.uses?.consumeOnSuccess)) + ) + this.update({ 'uses.value': this.uses.value + 1 }); + } + + /** + * Update Action Workflow config object. + * Must be called within Action context. + * @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. + * @returns {boolean} Return false if fast-forwarded and no more uses. + */ + prepareConfig(config) { const uses = this.uses?.max ? foundry.utils.deepClone(this.uses) : null; if (uses && !uses.value) uses.value = 0; config.uses = uses; const hasUses = UsesField.hasUses.call(this, config.uses); - if (config.isFastForward && !hasUses) return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.actionNoUsesRemaining')); - return hasUses; + if (config.dialog.configure === false && !hasUses) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.actionNoUsesRemaining')); + return hasUses; + } } + /** + * Prepare Uses object for Action Workflow + * Must be called within Action context. + * @param {object} uses + * @returns {object} + */ static calcUses(uses) { if (!uses) return null; return { @@ -38,6 +74,12 @@ export default class UsesField extends fields.SchemaField { }; } + /** + * Check if the Action still get atleast one unspent uses. + * Must be called within Action context. + * @param {*} uses + * @returns {boolean} + */ static hasUses(uses) { if (!uses) return true; let max = uses.max ?? 0; diff --git a/module/data/item/subclass.mjs b/module/data/item/subclass.mjs index e16a15f7..375588fb 100644 --- a/module/data/item/subclass.mjs +++ b/module/data/item/subclass.mjs @@ -52,8 +52,7 @@ export default class DHSubclass extends BaseDataItem { async _preCreate(data, options, user) { if (this.actor?.type === 'character') { - const dataUuid = - data.uuid ?? (data.folder ? `Compendium.daggerheart.subclasses.Item.${data._id}` : `Item.${data._id}`); + const dataUuid = data.uuid ?? data._stats.compendiumSource ?? `Item.${data._id}`; if (this.actor.system.class.subclass) { if (this.actor.system.multiclass.subclass) { ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassesAlreadyPresent')); diff --git a/module/data/settings/Automation.mjs b/module/data/settings/Automation.mjs index e1d63669..da71e899 100644 --- a/module/data/settings/Automation.mjs +++ b/module/data/settings/Automation.mjs @@ -80,6 +80,72 @@ export default class DhAutomation extends foundry.abstract.DataModel { initial: CONFIG.DH.GENERAL.defeatedConditions.defeated.id, label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.defeated.companionDefault.label' }) + }), + roll: new fields.SchemaField({ + roll: new fields.SchemaField({ + gm: new fields.BooleanField({ + required: true, + initial: false, + label: 'DAGGERHEART.GENERAL.gm' + }), + players: new fields.BooleanField({ + required: true, + initial: false, + label: 'DAGGERHEART.GENERAL.player.plurial' + }) + }), + damage: new fields.SchemaField({ + gm: new fields.StringField({ + required: true, + initial: "never", + choices: CONFIG.DH.SETTINGS.actionAutomationChoices, + label: 'DAGGERHEART.GENERAL.gm' + }), + players: new fields.StringField({ + required: true, + initial: "never", + choices: CONFIG.DH.SETTINGS.actionAutomationChoices, + label: 'DAGGERHEART.GENERAL.player.plurial' + }) + }), + save: new fields.SchemaField({ + gm: new fields.StringField({ + required: true, + initial: "never", + choices: CONFIG.DH.SETTINGS.actionAutomationChoices, + label: 'DAGGERHEART.GENERAL.gm' + }), + players: new fields.StringField({ + required: true, + initial: "never", + choices: CONFIG.DH.SETTINGS.actionAutomationChoices, + label: 'DAGGERHEART.GENERAL.player.plurial' + }) + }), + damageApply: new fields.SchemaField({ + gm: new fields.BooleanField({ + required: true, + initial: false, + label: 'DAGGERHEART.GENERAL.gm' + }), + players: new fields.BooleanField({ + required: true, + initial: false, + label: 'DAGGERHEART.GENERAL.player.plurial' + }) + }), + effect: new fields.SchemaField({ + gm: new fields.BooleanField({ + required: true, + initial: false, + label: 'DAGGERHEART.GENERAL.gm' + }), + players: new fields.BooleanField({ + required: true, + initial: false, + label: 'DAGGERHEART.GENERAL.player.plurial' + }) + }) }) }; } diff --git a/module/dice/d20Roll.mjs b/module/dice/d20Roll.mjs index 63d84744..f0660106 100644 --- a/module/dice/d20Roll.mjs +++ b/module/dice/d20Roll.mjs @@ -128,7 +128,9 @@ export default class D20Roll extends DHRoll { applyBaseBonus() { const modifiers = foundry.utils.deepClone(this.options.roll.baseModifiers) ?? []; - modifiers.push(...this.getBonus(`roll.${this.options.type}`, `${this.options.type?.capitalize()} Bonus`)); + modifiers.push( + ...this.getBonus(`roll.${this.options.actionType}`, `${this.options.actionType?.capitalize()} Bonus`) + ); modifiers.push( ...this.getBonus(`roll.${this.options.roll.type}`, `${this.options.roll.type?.capitalize()} Bonus`) ); @@ -138,7 +140,7 @@ export default class D20Roll extends DHRoll { static postEvaluate(roll, config = {}) { const data = super.postEvaluate(roll, config); - data.type = config.roll?.type; + data.type = config.actionType; data.difficulty = config.roll.difficulty; if (config.targets?.length) { config.targets.forEach(target => { @@ -147,6 +149,7 @@ export default class D20Roll extends DHRoll { }); data.success = config.targets.some(target => target.hit); } else if (config.roll.difficulty) data.success = roll.isCritical || roll.total >= config.roll.difficulty; + config.successConsumed = data.success; data.advantage = { type: config.roll.advantage, diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs index aa9e1d94..4d293d9d 100644 --- a/module/dice/damageRoll.mjs +++ b/module/dice/damageRoll.mjs @@ -9,6 +9,7 @@ export default class DamageRoll extends DHRoll { static DefaultDialog = DamageDialog; static async buildEvaluate(roll, config = {}, message = {}) { + if (config.dialog.configure === false) roll.constructFormula(config); if (config.evaluate !== false) for (const roll of config.roll) await roll.roll.evaluate(); roll._evaluated = true; @@ -46,9 +47,8 @@ export default class DamageRoll extends DHRoll { ); } await super.buildPost(roll, config, message); - if (config.source?.message) { + if (config.source?.message) chatMessage.update({ 'system.damage': config.damage }); - } } static unifyDamageRoll(rolls) { diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index ac340c64..0dcdd316 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -28,6 +28,7 @@ export default class DHRoll extends Roll { static async buildConfigure(config = {}, message = {}) { config.hooks = [...this.getHooks(), '']; config.dialog ??= {}; + for (const hook of config.hooks) { if (Hooks.call(`${CONFIG.DH.id}.preRoll${hook.capitalize()}`, config, message) === false) return null; } @@ -45,10 +46,7 @@ export default class DHRoll extends Roll { } for (const hook of config.hooks) { - if ( - Hooks.call(`${CONFIG.DH.id}.post${hook.capitalize()}RollConfiguration`, roll, config, message) === false - ) - return []; + if (Hooks.call(`${CONFIG.DH.id}.post${hook.capitalize()}RollConfiguration`, roll, config, message) === false) return []; } return roll; } @@ -88,11 +86,12 @@ export default class DHRoll extends Roll { type: this.messageType, user: game.user.id, title: roll.title, - speaker: cls.getSpeaker(), + speaker: cls.getSpeaker({ actor: roll.data?.parent }), sound: config.mute ? null : CONFIG.sounds.dice, system: config, rolls: [roll] }; + config.selectedRollMode ??= game.settings.get('core', 'rollMode'); if (roll._evaluated) { @@ -226,7 +225,7 @@ export const registerRollDiceHooks = () => { if ( !config.source?.actor || (game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) || - config.roll.type === 'reaction' + config.actionType === 'reaction' ) return; diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index ac1047ab..93ac231e 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -19,7 +19,7 @@ export default class DualityRoll extends D20Roll { get title() { return game.i18n.localize( - `DAGGERHEART.GENERAL.${this.options?.roll?.type === CONFIG.DH.ITEM.actionTypes.reaction.id ? 'reactionRoll' : 'dualityRoll'}` + `DAGGERHEART.GENERAL.${this.options?.actionType === CONFIG.DH.ITEM.actionTypes.reaction.id ? 'reactionRoll' : 'dualityRoll'}` ); } @@ -153,10 +153,10 @@ export default class DualityRoll extends D20Roll { applyBaseBonus() { const modifiers = super.applyBaseBonus(); - - if (this.options.roll.trait && this.data.traits[this.options.roll.trait]) + + if (this.options.roll.trait && this.data.traits?.[this.options.roll.trait]) modifiers.unshift({ - label: `DAGGERHEART.CONFIG.Traits.${this.options.roll.trait}.name`, + label: this.options.roll.type === CONFIG.DH.GENERAL.rollTypes.spellcast.id ? "DAGGERHEART.CONFIG.RollTypes.spellcast.name" : `DAGGERHEART.CONFIG.Traits.${this.options.roll.trait}.name`, value: this.data.traits[this.options.roll.trait].value }); diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 08735a45..950ed621 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -14,7 +14,7 @@ export default class DhpActor extends Actor { get owner() { const user = this.hasPlayerOwner && game.users.players.find(u => this.testUserPermission(u, 'OWNER') && u.active); - if (!user) return game.user.isGM ? game.user : null; + if (!user) return game.users.activeGM; return user; } diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs index c7f30e48..1a619a9c 100644 --- a/module/documents/chatMessage.mjs +++ b/module/documents/chatMessage.mjs @@ -1,3 +1,5 @@ +import { emitAsGM, GMUpdateEvent } from "../systemRegistration/socket.mjs"; + export default class DhpChatMessage extends foundry.documents.ChatMessage { targetHook = null; @@ -102,20 +104,30 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { }); if (itemDesc && autoExpandRoll.desc) itemDesc.setAttribute('open', ''); } - - if (!game.user.isGM) { - const applyButtons = html.querySelector('.apply-buttons'); + + if(!this.isAuthor && !this.speakerActor?.isOwner) { + const applyButtons = html.querySelector(".apply-buttons"); applyButtons?.remove(); - if (!this.isAuthor && !this.speakerActor?.isOwner) { - const buttons = html.querySelectorAll('.ability-card-footer > .ability-use-button'); - buttons.forEach(b => b.remove()); - } + const buttons = html.querySelectorAll(".ability-card-footer > .ability-use-button"); + buttons.forEach(b => b.remove()); } } addChatListeners(html) { + html.querySelectorAll('.duality-action-damage').forEach(element => + element.addEventListener('click', this.onRollDamage.bind(this)) + ); + html.querySelectorAll('.damage-button').forEach(element => - element.addEventListener('click', this.onDamage.bind(this)) + element.addEventListener('click', this.onApplyDamage.bind(this)) + ); + + html.querySelectorAll('.target-save').forEach(element => + element.addEventListener('click', this.onRollSave.bind(this)) + ); + + html.querySelectorAll('.roll-all-save-button').forEach(element => + element.addEventListener('click', this.onRollAllSave.bind(this)) ); html.querySelectorAll('.duality-action-effect').forEach(element => @@ -133,17 +145,21 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { }); } - getTargetList() { - const targets = this.system.hitTargets ?? []; - return targets.map(target => game.canvas.tokens.documentCollection.find(t => t.actor?.uuid === target.actorId)); + async onRollDamage(event) { + event.stopPropagation(); + const config = foundry.utils.deepClone(this.system); + config.event = event; + this.system.action?.workflow.get("damage")?.execute(config, this._id, true); } - async onDamage(event) { + async onApplyDamage(event) { event.stopPropagation(); - const targets = this.getTargetList(); + const targets = this.filterPermTargets(this.system.hitTargets), + config = foundry.utils.deepClone(this.system); + config.event = event; if (this.system.onSave) { - const pendingingSaves = this.system.hitTargets.filter(t => t.saved.success === null); + const pendingingSaves = targets.filter(t => t.saved.success === null); if (pendingingSaves.length) { const confirm = await foundry.applications.api.DialogV2.confirm({ window: { title: 'Pending Reaction Rolls found' }, @@ -154,62 +170,62 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { } if (targets.length === 0) - return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected')); + return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelectedOrPerm')); + + this.consumeOnSuccess(); + this.system.action?.workflow.get("applyDamage")?.execute(config, targets, true); + } - for (let target of targets) { - let damages = foundry.utils.deepClone(this.system.damage); - if ( - !this.system.hasHealing && - this.system.onSave && - this.system.hitTargets.find(t => t.id === target.id)?.saved?.success === true - ) { - const mod = CONFIG.DH.ACTIONS.damageOnSave[this.system.onSave]?.mod ?? 1; - Object.entries(damages).forEach(([k, v]) => { - v.total = 0; - v.parts.forEach(part => { - part.total = Math.ceil(part.total * mod); - v.total += part.total; - }); - }); - } - - this.consumeOnSuccess(); - if (this.system.hasHealing) target.actor.takeHealing(damages); - else target.actor.takeDamage(damages, this.system.isDirect); + async onRollSave(event) { + event.stopPropagation(); + const tokenId = event.target.closest('[data-token]')?.dataset.token, + token = game.canvas.tokens.get(tokenId); + if (!token?.actor || !token.isOwner) return true; + if (this.system.source.item && this.system.source.action) { + const action = this.system.action; + if (!action || !action?.hasSave) return; + game.system.api.fields.ActionFields.SaveField.rollSave.call(action, token.actor, event).then(result => + emitAsGM( + GMUpdateEvent.UpdateSaveMessage, + game.system.api.fields.ActionFields.SaveField.updateSaveMessage.bind(action, result, this, token.id), + { + action: action.uuid, + message: this._id, + token: token.id, + result + } + ) + ); } } - getAction(actor, itemId, actionId) { - const item = actor.items.get(itemId), - action = - actor.system.attack?._id === actionId - ? actor.system.attack - : item.system.attack?._id === actionId - ? item.system.attack - : item?.system?.actions?.get(actionId); - return action; + async onRollAllSave(event) { + event.stopPropagation(); + if (!game.user.isGM) return; + const targets = this.system.hitTargets, + config = foundry.utils.deepClone(this.system); + config.event = event; + this.system.action?.workflow.get("save")?.execute(config, targets, true); } async onApplyEffect(event) { event.stopPropagation(); - const actor = await foundry.utils.fromUuid(this.system.source.actor); - if (!actor || !game.user.isGM) return true; - if (this.system.source.item && this.system.source.action) { - const action = this.getAction(actor, this.system.source.item, this.system.source.action); - if (!action || !action?.applyEffects) return; - const targets = this.getTargetList(); - if (targets.length === 0) - ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected')); - this.consumeOnSuccess(); - await action.applyEffects(event, this, targets); - } + const targets = this.filterPermTargets(this.system.hitTargets), + config = foundry.utils.deepClone(this.system); + config.event = event; + if (targets.length === 0) + ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelectedOrPerm')); + this.consumeOnSuccess(); + this.system.action?.workflow.get("effects")?.execute(config, targets, true); + } + + filterPermTargets(targets) { + return targets.filter(t => fromUuidSync(t.actorId)?.canUserModify(game.user, "update")) } consumeOnSuccess() { - if (!this.system.successConsumed && !this.targetSelection) { - const action = this.system.action; - if (action) action.consume(this.system, true); - } + if (!this.system.successConsumed && !this.targetSelection) + this.system.action?.consume(this.system, true); } hoverTarget(event) { diff --git a/module/enrichers/DamageEnricher.mjs b/module/enrichers/DamageEnricher.mjs index a859be9f..a52c4b31 100644 --- a/module/enrichers/DamageEnricher.mjs +++ b/module/enrichers/DamageEnricher.mjs @@ -44,7 +44,7 @@ function getDamageMessage(damage, type, inline, defaultElement) { const dualityElement = document.createElement('span'); dualityElement.innerHTML = ` - `; diff --git a/module/systemRegistration/socket.mjs b/module/systemRegistration/socket.mjs index b47fee85..f3f9629b 100644 --- a/module/systemRegistration/socket.mjs +++ b/module/systemRegistration/socket.mjs @@ -22,6 +22,7 @@ export const socketEvent = { export const GMUpdateEvent = { UpdateDocument: 'DhGMUpdateDocument', + UpdateEffect: 'DhGMUpdateEffect', UpdateSetting: 'DhGMUpdateSetting', UpdateFear: 'DhGMUpdateFear', UpdateSaveMessage: 'DhGMUpdateSaveMessage' @@ -37,9 +38,12 @@ export const registerSocketHooks = () => { const document = data.uuid ? await fromUuid(data.uuid) : null; switch (data.action) { case GMUpdateEvent.UpdateDocument: - if (document && data.update) { + if (document && data.update) await document.update(data.update); - } + break; + case GMUpdateEvent.UpdateEffect: + if (document && data.update) + await game.system.api.fields.ActionFields.EffectsField.applyEffects.call(document, data.update); break; case GMUpdateEvent.UpdateSetting: await game.settings.set(CONFIG.DH.id, data.uuid, data.update); @@ -78,7 +82,7 @@ export const registerSocketHooks = () => { export const registerUserQueries = () => { CONFIG.queries.armorSlot = DamageReductionDialog.armorSlotQuery; - CONFIG.queries.reactionRoll = game.system.api.models.actions.actionsTypes.base.rollSaveQuery; + CONFIG.queries.reactionRoll = game.system.api.fields.ActionFields.SaveField.rollSaveQuery; }; export const emitAsGM = async (eventName, callback, update, uuid = null) => { diff --git a/src/packs/ancestries/feature_Retract_UFR67BUOhNGLFyg9.json b/src/packs/ancestries/feature_Retract_UFR67BUOhNGLFyg9.json index 59e33ce4..0e3e6555 100644 --- a/src/packs/ancestries/feature_Retract_UFR67BUOhNGLFyg9.json +++ b/src/packs/ancestries/feature_Retract_UFR67BUOhNGLFyg9.json @@ -23,12 +23,12 @@ }, "effects": [], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Mark Stress", "img": "icons/magic/defensive/shield-barrier-flaming-diamond-teal.webp", - "range": "" + "range": "self" } }, "originItemType": null, @@ -111,10 +111,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753996513763, - "modifiedTime": 1755394440308, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756040910699, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!UFR67BUOhNGLFyg9" } diff --git a/src/packs/ancestries/feature_Wings_WquAjoOcso8lwySW.json b/src/packs/ancestries/feature_Wings_WquAjoOcso8lwySW.json index 7bc73dc6..87aa36a9 100644 --- a/src/packs/ancestries/feature_Wings_WquAjoOcso8lwySW.json +++ b/src/packs/ancestries/feature_Wings_WquAjoOcso8lwySW.json @@ -31,12 +31,12 @@ }, "effects": [], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Mark Stress", "img": "icons/creatures/abilities/wing-batlike-white-blue.webp", - "range": "" + "range": "self" } }, "originItemType": null, @@ -113,10 +113,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753994723305, - "modifiedTime": 1755394368487, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756040981649, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!WquAjoOcso8lwySW" } diff --git a/src/packs/beastforms/feature_Armored_Shell_nDQZdIF2epKlhauX.json b/src/packs/beastforms/feature_Armored_Shell_nDQZdIF2epKlhauX.json index b4d711a5..26534190 100644 --- a/src/packs/beastforms/feature_Armored_Shell_nDQZdIF2epKlhauX.json +++ b/src/packs/beastforms/feature_Armored_Shell_nDQZdIF2epKlhauX.json @@ -24,7 +24,7 @@ ], "uses": { "value": null, - "max": null, + "max": "", "recovery": null }, "effects": [ @@ -39,7 +39,7 @@ }, "name": "Retract", "img": "icons/creatures/reptiles/turtle-shell-glowing-green.webp", - "range": "" + "range": "self" } }, "originItemType": null, @@ -149,10 +149,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753580983699, - "modifiedTime": 1755395623887, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756041050697, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "nDQZdIF2epKlhauX", "sort": 2200000, diff --git a/src/packs/beastforms/feature_Rampage_8upqfcZvi7b5hRLE.json b/src/packs/beastforms/feature_Rampage_8upqfcZvi7b5hRLE.json index 3ac9731d..e9b157ba 100644 --- a/src/packs/beastforms/feature_Rampage_8upqfcZvi7b5hRLE.json +++ b/src/packs/beastforms/feature_Rampage_8upqfcZvi7b5hRLE.json @@ -24,22 +24,22 @@ ], "uses": { "value": null, - "max": null, + "max": "", "recovery": null }, "effects": [ { - "_id": "5TX0hHFKZHvBeWne", + "_id": "A7l4JEBC1FFQajsN", "onSave": false } ], "target": { - "type": null, + "type": "self", "amount": null }, - "name": "Gain Proficiency", + "name": "Mark Stress", "img": "icons/creatures/abilities/bear-roar-bite-brown.webp", - "range": "" + "range": "self" } }, "originItemType": null, @@ -51,7 +51,59 @@ "artist": "" } }, - "effects": [], + "effects": [ + { + "name": "Rampage", + "img": "icons/creatures/claws/claw-bear-paw-swipe-red.webp", + "origin": "Compendium.daggerheart.beastforms.Item.8upqfcZvi7b5hRLE", + "transfer": false, + "_id": "A7l4JEBC1FFQajsN", + "type": "base", + "system": { + "rangeDependence": { + "enabled": false, + "type": "withinRange", + "target": "hostile", + "range": "melee" + } + }, + "changes": [ + { + "key": "system.proficiency", + "mode": 2, + "value": "1", + "priority": null + } + ], + "disabled": false, + "duration": { + "startTime": null, + "combat": null, + "seconds": null, + "rounds": null, + "turns": null, + "startRound": null, + "startTurn": null + }, + "description": "

Before you make an attack roll, you can mark a Stress to gain a +1 bonus to your Proficiency for that attack.

", + "tint": "#ffffff", + "statuses": [], + "sort": 0, + "flags": {}, + "_stats": { + "compendiumSource": null, + "duplicateSource": null, + "exportSource": null, + "coreVersion": "13.347", + "systemId": "daggerheart", + "systemVersion": "1.1.0", + "createdTime": 1756041194412, + "modifiedTime": 1756041218646, + "lastModifiedBy": "vUIbuan0U50nfKBE" + }, + "_key": "!items.effects!8upqfcZvi7b5hRLE.A7l4JEBC1FFQajsN" + } + ], "folder": "uU8bIoZvXge0rLaU", "ownership": { "default": 0, @@ -64,10 +116,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753582591417, - "modifiedTime": 1755395649724, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756041228102, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "8upqfcZvi7b5hRLE", "sort": 2000000, diff --git a/src/packs/beastforms/feature_Takedown_0ey4kM9ssj2otHvb.json b/src/packs/beastforms/feature_Takedown_0ey4kM9ssj2otHvb.json index a1a14198..f31ed781 100644 --- a/src/packs/beastforms/feature_Takedown_0ey4kM9ssj2otHvb.json +++ b/src/packs/beastforms/feature_Takedown_0ey4kM9ssj2otHvb.json @@ -24,7 +24,7 @@ ], "uses": { "value": null, - "max": null, + "max": "", "recovery": null }, "damage": { @@ -58,7 +58,7 @@ "includeBase": false }, "target": { - "type": "any", + "type": "self", "amount": null }, "effects": [], @@ -84,7 +84,7 @@ }, "name": "Attack", "img": "icons/creatures/abilities/paw-print-orange.webp", - "range": "" + "range": "self" } }, "originItemType": null, @@ -101,7 +101,7 @@ "name": "Takedown", "img": "icons/creatures/abilities/paw-print-orange.webp", "origin": "Item.okjlLUwEdNOKeUk3", - "transfer": false, + "transfer": true, "_id": "hE6ciIusvKEtUQ8U", "type": "base", "system": {}, @@ -113,7 +113,7 @@ "priority": null } ], - "disabled": false, + "disabled": true, "duration": { "startTime": null, "combat": null, @@ -123,7 +123,7 @@ "startRound": null, "startTurn": null }, - "description": "", + "description": "

You gain a +2 bonus to your Proficiency for this attack and the target must mark a Stress.

", "tint": "#ffffff", "statuses": [], "sort": 0, @@ -132,10 +132,11 @@ "compendiumSource": null, "duplicateSource": null, "exportSource": null, - "coreVersion": "13.346", + "coreVersion": "13.347", "systemId": "daggerheart", "systemVersion": "0.0.1", - "lastModifiedBy": null + "lastModifiedBy": "vUIbuan0U50nfKBE", + "modifiedTime": 1756041368153 }, "_key": "!items.effects!0ey4kM9ssj2otHvb.hE6ciIusvKEtUQ8U" } @@ -152,10 +153,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753621786000, - "modifiedTime": 1755395696040, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756041242273, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "0ey4kM9ssj2otHvb", "sort": 600000, diff --git a/src/packs/classes/feature_Frontline_Tank_YS1g7YdWwOaS629x.json b/src/packs/classes/feature_Frontline_Tank_YS1g7YdWwOaS629x.json index a0b56f5f..701f8eb9 100644 --- a/src/packs/classes/feature_Frontline_Tank_YS1g7YdWwOaS629x.json +++ b/src/packs/classes/feature_Frontline_Tank_YS1g7YdWwOaS629x.json @@ -60,7 +60,7 @@ "includeBase": false }, "target": { - "type": "any", + "type": "self", "amount": null }, "effects": [], @@ -81,7 +81,7 @@ }, "name": "Spend Hope", "img": "icons/magic/defensive/shield-barrier-flaming-pentagon-orange.webp", - "range": "" + "range": "self" } }, "originItemType": null, @@ -105,10 +105,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1754246687097, - "modifiedTime": 1755391259920, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756034159296, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!YS1g7YdWwOaS629x" } diff --git a/src/packs/classes/feature_Hold_Them_Off_2Cyb9ZeuAesf5Sb3.json b/src/packs/classes/feature_Hold_Them_Off_2Cyb9ZeuAesf5Sb3.json index f2e99b8f..3a640f48 100644 --- a/src/packs/classes/feature_Hold_Them_Off_2Cyb9ZeuAesf5Sb3.json +++ b/src/packs/classes/feature_Hold_Them_Off_2Cyb9ZeuAesf5Sb3.json @@ -5,14 +5,14 @@ "_id": "2Cyb9ZeuAesf5Sb3", "img": "icons/magic/defensive/barrier-shield-dome-deflect-teal.webp", "system": { - "description": "

Spend 3 Hope when you succeed on an attack with a weapon to use that same roll against two additional adversaries within range of the attack.

", + "description": "

Spend 3 Hope when you succeed on an attack with a weapon to use that same roll against two additional adversaries within range of the attack.

", "resource": null, "actions": { "yhVUna5biFAN0o2Y": { "type": "effect", "_id": "yhVUna5biFAN0o2Y", "systemPath": "actions", - "description": "

Spend 3 Hope when you succeed on an attack with a weapon to use that same roll against two additional adversaries within range of the attack.

", + "description": "

Spend 3 Hope when you succeed on an attack with a weapon to use that same roll against two additional adversaries within range of the attack.

", "chatDisplay": true, "actionType": "action", "cost": [ @@ -60,10 +60,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1754268431889, - "modifiedTime": 1755391281371, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756034191317, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!2Cyb9ZeuAesf5Sb3" } diff --git a/src/packs/classes/feature_No_Mercy_njj2C3tMDeCHHOoh.json b/src/packs/classes/feature_No_Mercy_njj2C3tMDeCHHOoh.json index b131fcdf..7d689fe3 100644 --- a/src/packs/classes/feature_No_Mercy_njj2C3tMDeCHHOoh.json +++ b/src/packs/classes/feature_No_Mercy_njj2C3tMDeCHHOoh.json @@ -38,12 +38,12 @@ } ], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Spend Hope", "img": "icons/magic/holy/barrier-shield-winged-blue.webp", - "range": "" + "range": "self" } }, "originItemType": null, @@ -119,10 +119,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1754257270096, - "modifiedTime": 1755391439746, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756034406042, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!njj2C3tMDeCHHOoh" } diff --git a/src/packs/classes/feature_Ranger_s_Focus_ncLx2P8BOUtrAD38.json b/src/packs/classes/feature_Ranger_s_Focus_ncLx2P8BOUtrAD38.json index 4f798663..d3d10bc0 100644 --- a/src/packs/classes/feature_Ranger_s_Focus_ncLx2P8BOUtrAD38.json +++ b/src/packs/classes/feature_Ranger_s_Focus_ncLx2P8BOUtrAD38.json @@ -29,7 +29,12 @@ "max": "", "recovery": null }, - "effects": [], + "effects": [ + { + "_id": "SXi2dQWqpwY9fap4", + "onSave": false + } + ], "target": { "type": "any", "amount": null @@ -47,7 +52,52 @@ "artist": "" } }, - "effects": [], + "effects": [ + { + "name": "Ranger's Focus", + "img": "icons/magic/perception/eye-ringed-green.webp", + "origin": "Compendium.daggerheart.classes.Item.ncLx2P8BOUtrAD38", + "transfer": false, + "_id": "SXi2dQWqpwY9fap4", + "type": "base", + "system": { + "rangeDependence": { + "enabled": false, + "type": "withinRange", + "target": "hostile", + "range": "melee" + } + }, + "changes": [], + "disabled": false, + "duration": { + "startTime": null, + "combat": null, + "seconds": null, + "rounds": null, + "turns": null, + "startRound": null, + "startTurn": null + }, + "description": "

Until this feature ends or the ranger makes a different creature their Focus, they gain the following benefits against this adversary:

", + "tint": "#ffffff", + "statuses": [], + "sort": 0, + "flags": {}, + "_stats": { + "compendiumSource": null, + "duplicateSource": null, + "exportSource": null, + "coreVersion": "13.347", + "systemId": "daggerheart", + "systemVersion": "1.1.0", + "createdTime": 1756034238996, + "modifiedTime": 1756034332923, + "lastModifiedBy": "vUIbuan0U50nfKBE" + }, + "_key": "!items.effects!ncLx2P8BOUtrAD38.SXi2dQWqpwY9fap4" + } + ], "sort": 0, "ownership": { "default": 0, @@ -60,10 +110,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1754268505051, - "modifiedTime": 1755391289388, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756034239047, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!ncLx2P8BOUtrAD38" } diff --git a/src/packs/classes/feature_Rogue_s_Dodge_hVaaPIjxoextIgSL.json b/src/packs/classes/feature_Rogue_s_Dodge_hVaaPIjxoextIgSL.json index 244f46c5..fcc178d0 100644 --- a/src/packs/classes/feature_Rogue_s_Dodge_hVaaPIjxoextIgSL.json +++ b/src/packs/classes/feature_Rogue_s_Dodge_hVaaPIjxoextIgSL.json @@ -36,7 +36,7 @@ } ], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Spend Hope", @@ -76,7 +76,7 @@ "priority": null } ], - "disabled": true, + "disabled": false, "duration": { "startTime": null, "combat": null, @@ -95,12 +95,12 @@ "compendiumSource": null, "duplicateSource": null, "exportSource": null, - "coreVersion": "13.346", + "coreVersion": "13.347", "systemId": "daggerheart", "systemVersion": "0.0.1", "createdTime": 1754323997794, - "modifiedTime": 1754351805065, - "lastModifiedBy": "MQSznptE5yLT7kj8" + "modifiedTime": 1756034385415, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items.effects!hVaaPIjxoextIgSL.hhVjBro2osGDTT5g" } @@ -117,10 +117,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1754323951411, - "modifiedTime": 1755391319440, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756034357629, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!hVaaPIjxoextIgSL" } diff --git a/src/packs/classes/feature_Sneak_Attack_5QqpEwmwkPfZHpMW.json b/src/packs/classes/feature_Sneak_Attack_5QqpEwmwkPfZHpMW.json index eb637edd..8984606c 100644 --- a/src/packs/classes/feature_Sneak_Attack_5QqpEwmwkPfZHpMW.json +++ b/src/packs/classes/feature_Sneak_Attack_5QqpEwmwkPfZHpMW.json @@ -7,35 +7,7 @@ "system": { "description": "

When you succeed on an attack while Cloaked or while an ally is within Melee range of your target, add a number of d6s equal to your tier to your damage roll.

", "resource": null, - "actions": { - "SfctrIW5KjH4nq5G": { - "type": "effect", - "_id": "SfctrIW5KjH4nq5G", - "systemPath": "actions", - "description": "

When you succeed on an attack while Cloaked or while an ally is within Melee range of your target, add a number of d6s equal to your tier to your damage roll.

", - "chatDisplay": true, - "actionType": "action", - "cost": [], - "uses": { - "value": null, - "max": "", - "recovery": null - }, - "effects": [ - { - "_id": "380jFzw756qSy5ae", - "onSave": false - } - ], - "target": { - "type": "any", - "amount": null - }, - "name": "Use Sneak Attack", - "img": "icons/skills/melee/strike-dagger-skull-white.webp", - "range": "" - } - }, + "actions": {}, "originItemType": null, "originId": null, "attribution": { @@ -115,10 +87,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1754324216454, - "modifiedTime": 1755391313725, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756034698108, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!5QqpEwmwkPfZHpMW" } diff --git a/src/packs/classes/loot_Strange_Dirty_Penant_LriTeh9hkwzEaCr1.json b/src/packs/classes/loot_Strange_Dirty_Pendant_LriTeh9hkwzEaCr1.json similarity index 80% rename from src/packs/classes/loot_Strange_Dirty_Penant_LriTeh9hkwzEaCr1.json rename to src/packs/classes/loot_Strange_Dirty_Pendant_LriTeh9hkwzEaCr1.json index ba307e05..3ca7b938 100644 --- a/src/packs/classes/loot_Strange_Dirty_Penant_LriTeh9hkwzEaCr1.json +++ b/src/packs/classes/loot_Strange_Dirty_Pendant_LriTeh9hkwzEaCr1.json @@ -1,6 +1,6 @@ { "folder": "1gQpPaxdgvCxYlLs", - "name": "Strange Dirty Penant", + "name": "Strange Dirty Pendant", "type": "loot", "_id": "LriTeh9hkwzEaCr1", "img": "icons/equipment/neck/necklace-carved-stone-spiral.webp", @@ -20,12 +20,12 @@ "compendiumSource": null, "duplicateSource": null, "exportSource": null, - "coreVersion": "13.346", + "coreVersion": "13.347", "systemId": "daggerheart", "systemVersion": "0.0.1", "createdTime": 1754222150005, - "modifiedTime": 1754246254311, - "lastModifiedBy": "LgnbNMLaxandgMQq" + "modifiedTime": 1756136989085, + "lastModifiedBy": "HKcDBUU22bYKtQmH" }, "_key": "!items!LriTeh9hkwzEaCr1" } diff --git a/src/packs/domains/domainCard_Bold_Presence_tdsL00yTSLNgZWs6.json b/src/packs/domains/domainCard_Bold_Presence_tdsL00yTSLNgZWs6.json index 164ffb48..c83c1fde 100644 --- a/src/packs/domains/domainCard_Bold_Presence_tdsL00yTSLNgZWs6.json +++ b/src/packs/domains/domainCard_Bold_Presence_tdsL00yTSLNgZWs6.json @@ -43,7 +43,7 @@ }, "name": "Spend Hope", "img": "icons/magic/holy/barrier-shield-winged-blue.webp", - "range": "" + "range": "self" }, "OdRTXMOXhZFiZHES": { "type": "effect", @@ -81,10 +81,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753922784418, - "modifiedTime": 1755429846358, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756038980509, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "tdsL00yTSLNgZWs6", "sort": 3400000, @@ -93,7 +93,7 @@ "name": "Bold Presence", "img": "icons/magic/holy/barrier-shield-winged-blue.webp", "origin": "Compendium.daggerheart.domains.Item.tdsL00yTSLNgZWs6", - "transfer": true, + "transfer": false, "_id": "2XEYhuAcRGTtqvED", "type": "base", "system": { @@ -112,7 +112,7 @@ "priority": null } ], - "disabled": true, + "disabled": false, "duration": { "startTime": null, "combat": null, @@ -131,12 +131,12 @@ "compendiumSource": null, "duplicateSource": null, "exportSource": null, - "coreVersion": "13.346", + "coreVersion": "13.347", "systemId": "daggerheart", "systemVersion": "0.0.1", "createdTime": 1754241531511, - "modifiedTime": 1754241605959, - "lastModifiedBy": "MQSznptE5yLT7kj8" + "modifiedTime": 1756038989398, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items.effects!tdsL00yTSLNgZWs6.2XEYhuAcRGTtqvED" } diff --git a/src/packs/domains/domainCard_Book_of_Ronin_SZMNR3uGNinJcN4N.json b/src/packs/domains/domainCard_Book_of_Ronin_SZMNR3uGNinJcN4N.json index b6f8bfcc..862d5da9 100644 --- a/src/packs/domains/domainCard_Book_of_Ronin_SZMNR3uGNinJcN4N.json +++ b/src/packs/domains/domainCard_Book_of_Ronin_SZMNR3uGNinJcN4N.json @@ -28,7 +28,7 @@ "includeBase": false }, "target": { - "type": "any", + "type": "self", "amount": null }, "effects": [ @@ -59,7 +59,7 @@ }, "name": "Transform", "img": "icons/containers/barrels/barrel-reinforced-cherry-brown.webp", - "range": "" + "range": "self" }, "h7i4ZuDYuYLnjze6": { "type": "attack", @@ -126,10 +126,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753922784424, - "modifiedTime": 1755429102985, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756038121293, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "SZMNR3uGNinJcN4N", "sort": 3400000, diff --git a/src/packs/domains/domainCard_Book_of_Sitil_eq8VNqYMRHhF9xw9.json b/src/packs/domains/domainCard_Book_of_Sitil_eq8VNqYMRHhF9xw9.json index 4ee7c45c..3c60a08d 100644 --- a/src/packs/domains/domainCard_Book_of_Sitil_eq8VNqYMRHhF9xw9.json +++ b/src/packs/domains/domainCard_Book_of_Sitil_eq8VNqYMRHhF9xw9.json @@ -30,12 +30,12 @@ } ], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Adjust Appearance", "img": "icons/magic/defensive/shield-barrier-blades-teal.webp", - "range": "" + "range": "self" }, "wBQkw3P4Esj6kOx2": { "type": "effect", @@ -132,10 +132,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753922784424, - "modifiedTime": 1755428993189, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756037990822, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "eq8VNqYMRHhF9xw9", "sort": 3400000, diff --git a/src/packs/domains/domainCard_Book_of_Yarrow_J1ovx2FpNDvPq1o6.json b/src/packs/domains/domainCard_Book_of_Yarrow_J1ovx2FpNDvPq1o6.json index 8cef1c1e..8d91c1ee 100644 --- a/src/packs/domains/domainCard_Book_of_Yarrow_J1ovx2FpNDvPq1o6.json +++ b/src/packs/domains/domainCard_Book_of_Yarrow_J1ovx2FpNDvPq1o6.json @@ -89,7 +89,7 @@ }, "name": "Magic Immunity", "img": "icons/magic/defensive/barrier-shield-dome-deflect-teal.webp", - "range": "" + "range": "self" } }, "attribution": { @@ -105,10 +105,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753922784429, - "modifiedTime": 1755429115570, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756038143042, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "J1ovx2FpNDvPq1o6", "sort": 3400000, diff --git a/src/packs/domains/domainCard_Breaking_Blow_8UANBgSdhMZ0sqfO.json b/src/packs/domains/domainCard_Breaking_Blow_8UANBgSdhMZ0sqfO.json index 567d7d57..3781989d 100644 --- a/src/packs/domains/domainCard_Breaking_Blow_8UANBgSdhMZ0sqfO.json +++ b/src/packs/domains/domainCard_Breaking_Blow_8UANBgSdhMZ0sqfO.json @@ -41,7 +41,7 @@ ], "target": { "type": "any", - "amount": null + "amount": 1 }, "name": "Mark Stress", "img": "icons/skills/wounds/bone-broken-knee-beam.webp", @@ -61,10 +61,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753922784431, - "modifiedTime": 1755428345979, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756037935304, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "8UANBgSdhMZ0sqfO", "sort": 3400000, diff --git a/src/packs/domains/domainCard_Cloaking_Blast_Zhw7PtK8nMPlsOqD.json b/src/packs/domains/domainCard_Cloaking_Blast_Zhw7PtK8nMPlsOqD.json index f19833c5..5314de9e 100644 --- a/src/packs/domains/domainCard_Cloaking_Blast_Zhw7PtK8nMPlsOqD.json +++ b/src/packs/domains/domainCard_Cloaking_Blast_Zhw7PtK8nMPlsOqD.json @@ -40,12 +40,12 @@ } ], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Become Cloaked", "img": "icons/magic/perception/shadow-stealth-eyes-purple.webp", - "range": "" + "range": "self" } }, "attribution": { @@ -61,10 +61,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753922784435, - "modifiedTime": 1755428020354, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756037373705, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "Zhw7PtK8nMPlsOqD", "sort": 3400000, diff --git a/src/packs/domains/domainCard_Conjure_Swarm_rZPH0BY8Sznc9sFG.json b/src/packs/domains/domainCard_Conjure_Swarm_rZPH0BY8Sznc9sFG.json index 9d243ade..82d68f9c 100644 --- a/src/packs/domains/domainCard_Conjure_Swarm_rZPH0BY8Sznc9sFG.json +++ b/src/packs/domains/domainCard_Conjure_Swarm_rZPH0BY8Sznc9sFG.json @@ -36,7 +36,7 @@ "includeBase": false }, "target": { - "type": "any", + "type": "self", "amount": null }, "effects": [ @@ -67,7 +67,7 @@ }, "name": "Tekaira's Armored Beetles: Stress", "img": "icons/creatures/invertebrates/wasp-swarm-attack.webp", - "range": "" + "range": "self" }, "533qzPIjcccpiMey": { "type": "effect", @@ -195,10 +195,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753922784438, - "modifiedTime": 1755429481288, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756038512929, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "rZPH0BY8Sznc9sFG", "sort": 3400000, diff --git a/src/packs/domains/domainCard_Deadly_Focus_xxZOXC4tiZQ6kg1e.json b/src/packs/domains/domainCard_Deadly_Focus_xxZOXC4tiZQ6kg1e.json index df82f1aa..9246896d 100644 --- a/src/packs/domains/domainCard_Deadly_Focus_xxZOXC4tiZQ6kg1e.json +++ b/src/packs/domains/domainCard_Deadly_Focus_xxZOXC4tiZQ6kg1e.json @@ -26,17 +26,17 @@ }, "effects": [ { - "_id": "6sR46Hd554DiLHy4", + "_id": "TFWiAUDCfGaax5MU", "onSave": false } ], "target": { - "type": "any", - "amount": null + "type": "self", + "amount": 1 }, "name": "Focus", "img": "icons/skills/targeting/crosshair-pointed-orange.webp", - "range": "" + "range": "self" } }, "attribution": { @@ -52,20 +52,20 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753922784443, - "modifiedTime": 1755428127375, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756037604544, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "xxZOXC4tiZQ6kg1e", "sort": 3400000, "effects": [ { "name": "Deadly Focus", - "img": "systems/daggerheart/assets/icons/domains/domain-card/blade.png", + "img": "icons/skills/targeting/crosshair-pointed-orange.webp", "origin": "Compendium.daggerheart.domains.Item.xxZOXC4tiZQ6kg1e", - "transfer": true, - "_id": "6sR46Hd554DiLHy4", + "transfer": false, + "_id": "TFWiAUDCfGaax5MU", "type": "base", "system": { "rangeDependence": { @@ -83,7 +83,7 @@ "priority": null } ], - "disabled": true, + "disabled": false, "duration": { "startTime": null, "combat": null, @@ -93,7 +93,7 @@ "startRound": null, "startTurn": null }, - "description": "", + "description": "

Until you attack another creature, you defeat the target, or the battle ends, gain a +1 bonus to your Proficiency.

", "tint": "#ffffff", "statuses": [], "sort": 0, @@ -102,14 +102,14 @@ "compendiumSource": null, "duplicateSource": null, "exportSource": null, - "coreVersion": "13.346", + "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "0.0.1", - "createdTime": 1754244951545, - "modifiedTime": 1754304242570, - "lastModifiedBy": "MQSznptE5yLT7kj8" + "systemVersion": "1.1.0", + "createdTime": 1756037548255, + "modifiedTime": 1756037588317, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, - "_key": "!items.effects!xxZOXC4tiZQ6kg1e.6sR46Hd554DiLHy4" + "_key": "!items.effects!xxZOXC4tiZQ6kg1e.TFWiAUDCfGaax5MU" } ], "ownership": { diff --git a/src/packs/domains/domainCard_Force_of_Nature_LzVpMkD5I4QeaIHf.json b/src/packs/domains/domainCard_Force_of_Nature_LzVpMkD5I4QeaIHf.json index ff9b1ebf..9aefa1bf 100644 --- a/src/packs/domains/domainCard_Force_of_Nature_LzVpMkD5I4QeaIHf.json +++ b/src/packs/domains/domainCard_Force_of_Nature_LzVpMkD5I4QeaIHf.json @@ -38,12 +38,12 @@ } ], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Transform", "img": "icons/magic/nature/elemental-plant-humanoid.webp", - "range": "" + "range": "self" }, "1rLoYS90AZizJujS": { "type": "effect", @@ -89,10 +89,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753922784456, - "modifiedTime": 1755429633230, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756038760003, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "LzVpMkD5I4QeaIHf", "sort": 3400000, diff --git a/src/packs/domains/domainCard_Frenzy_MMl7abdGRLl7TJLO.json b/src/packs/domains/domainCard_Frenzy_MMl7abdGRLl7TJLO.json index 87dddb15..c6323551 100644 --- a/src/packs/domains/domainCard_Frenzy_MMl7abdGRLl7TJLO.json +++ b/src/packs/domains/domainCard_Frenzy_MMl7abdGRLl7TJLO.json @@ -31,12 +31,12 @@ } ], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Frenzy", "img": "icons/magic/fire/projectile-wave-yellow.webp", - "range": "" + "range": "self" } }, "attribution": { @@ -52,10 +52,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753922784459, - "modifiedTime": 1755428186983, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756037770198, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "MMl7abdGRLl7TJLO", "sort": 3400000, diff --git a/src/packs/domains/domainCard_Full_Surge_SgvjJfMyubZowPxS.json b/src/packs/domains/domainCard_Full_Surge_SgvjJfMyubZowPxS.json index 15ffb49e..b8ac3547 100644 --- a/src/packs/domains/domainCard_Full_Surge_SgvjJfMyubZowPxS.json +++ b/src/packs/domains/domainCard_Full_Surge_SgvjJfMyubZowPxS.json @@ -43,7 +43,7 @@ }, "name": "Mark Stress", "img": "icons/magic/control/buff-flight-wings-runes-purple.webp", - "range": "" + "range": "self" } }, "attribution": { @@ -59,10 +59,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753922784460, - "modifiedTime": 1755429931329, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756039042961, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "SgvjJfMyubZowPxS", "sort": 3400000, @@ -71,7 +71,7 @@ "name": "Full Surge", "img": "icons/magic/symbols/ring-circle-smoke-blue.webp", "origin": "Compendium.daggerheart.domains.Item.SgvjJfMyubZowPxS", - "transfer": true, + "transfer": false, "_id": "H5q5iYImr69TfZcp", "type": "base", "system": { @@ -120,7 +120,7 @@ "priority": null } ], - "disabled": true, + "disabled": false, "duration": { "startTime": null, "combat": null, @@ -139,12 +139,12 @@ "compendiumSource": null, "duplicateSource": null, "exportSource": null, - "coreVersion": "13.346", + "coreVersion": "13.347", "systemId": "daggerheart", "systemVersion": "0.0.1", "createdTime": 1754242182511, - "modifiedTime": 1754242229676, - "lastModifiedBy": "MQSznptE5yLT7kj8" + "modifiedTime": 1756039048089, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items.effects!SgvjJfMyubZowPxS.H5q5iYImr69TfZcp" } diff --git a/src/packs/domains/domainCard_Hold_the_Line_kdFoLo3KXwn4LqTG.json b/src/packs/domains/domainCard_Hold_the_Line_kdFoLo3KXwn4LqTG.json index b572243f..6ea5b5c2 100644 --- a/src/packs/domains/domainCard_Hold_the_Line_kdFoLo3KXwn4LqTG.json +++ b/src/packs/domains/domainCard_Hold_the_Line_kdFoLo3KXwn4LqTG.json @@ -43,7 +43,7 @@ }, "name": "Spend Hope", "img": "icons/magic/holy/barrier-shield-winged-blue.webp", - "range": "" + "range": "self" }, "SKG6Gu0uJZxtYTnz": { "type": "effect", @@ -87,10 +87,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753922784468, - "modifiedTime": 1755429945423, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756039072992, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "kdFoLo3KXwn4LqTG", "sort": 3400000, @@ -99,7 +99,7 @@ "name": "Hold the Line", "img": "icons/magic/defensive/shield-barrier-blue.webp", "origin": "Compendium.daggerheart.domains.Item.kdFoLo3KXwn4LqTG", - "transfer": true, + "transfer": false, "_id": "y82y3nUStyL8DIJL", "type": "base", "system": { @@ -111,7 +111,7 @@ } }, "changes": [], - "disabled": true, + "disabled": false, "duration": { "startTime": null, "combat": null, @@ -130,18 +130,18 @@ "compendiumSource": null, "duplicateSource": null, "exportSource": null, - "coreVersion": "13.346", + "coreVersion": "13.347", "systemId": "daggerheart", "systemVersion": "0.0.1", "createdTime": 1754242295342, - "modifiedTime": 1754242434181, - "lastModifiedBy": "MQSznptE5yLT7kj8" + "modifiedTime": 1756039077113, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items.effects!kdFoLo3KXwn4LqTG.y82y3nUStyL8DIJL" }, { "name": "Hold the Line", - "img": "systems/daggerheart/assets/icons/domains/domain-card/valor.png", + "img": "icons/skills/melee/shield-block-bash-blue.webp", "origin": "Compendium.daggerheart.domains.Item.kdFoLo3KXwn4LqTG", "transfer": false, "_id": "WTYg0b8nE1XbnMiA", @@ -176,12 +176,12 @@ "compendiumSource": null, "duplicateSource": null, "exportSource": null, - "coreVersion": "13.346", + "coreVersion": "13.347", "systemId": "daggerheart", "systemVersion": "0.0.1", "createdTime": 1754242400483, - "modifiedTime": 1754242409060, - "lastModifiedBy": "MQSznptE5yLT7kj8" + "modifiedTime": 1756039151398, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items.effects!kdFoLo3KXwn4LqTG.WTYg0b8nE1XbnMiA" } diff --git a/src/packs/domains/domainCard_Hypnotic_Shimmer_2ZeuCGVatQdPOVC6.json b/src/packs/domains/domainCard_Hypnotic_Shimmer_2ZeuCGVatQdPOVC6.json index 7579ac02..d7ad18c6 100644 --- a/src/packs/domains/domainCard_Hypnotic_Shimmer_2ZeuCGVatQdPOVC6.json +++ b/src/packs/domains/domainCard_Hypnotic_Shimmer_2ZeuCGVatQdPOVC6.json @@ -14,7 +14,7 @@ "type": "attack", "_id": "kLMAuyZktmohOSXa", "systemPath": "actions", - "description": "

Make a Spellcast Roll against all adversaries in front of you within Close range. Once per rest on a success, create an illusion of flashing colors and lights that temporarily Stuns targets you succeed against and forces them to mark a Stress. While Stunned, they can’t use reactions and can’t take any other actions until they clear this condition.

", + "description": "

Make a Spellcast Roll against all adversaries in front of you within Close range. Once per rest on a success, create an illusion of flashing colors and lights that temporarily Stuns targets you succeed against and forces them to mark a Stress. While Stunned, they can’t use reactions and can’t take any other actions until they clear this condition.

@Template[type:infront|range:c]

", "chatDisplay": true, "actionType": "action", "cost": [], @@ -102,10 +102,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753922784471, - "modifiedTime": 1755429167606, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756038232979, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "2ZeuCGVatQdPOVC6", "sort": 3400000, diff --git a/src/packs/domains/domainCard_Lead_by_Example_YWCRplmtwpCjpq5i.json b/src/packs/domains/domainCard_Lead_by_Example_YWCRplmtwpCjpq5i.json index fa6f3c10..2566febc 100644 --- a/src/packs/domains/domainCard_Lead_by_Example_YWCRplmtwpCjpq5i.json +++ b/src/packs/domains/domainCard_Lead_by_Example_YWCRplmtwpCjpq5i.json @@ -39,7 +39,7 @@ ], "target": { "type": "any", - "amount": null + "amount": 1 }, "name": "Mark Stress", "img": "icons/magic/control/buff-flight-wings-runes-purple.webp", @@ -59,10 +59,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753922784475, - "modifiedTime": 1755429951881, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756039181179, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "YWCRplmtwpCjpq5i", "sort": 3400000, diff --git a/src/packs/domains/domainCard_Overwhelming_Aura_iEBLySZD9z8CLdz7.json b/src/packs/domains/domainCard_Overwhelming_Aura_iEBLySZD9z8CLdz7.json index 72a79735..e3dee02a 100644 --- a/src/packs/domains/domainCard_Overwhelming_Aura_iEBLySZD9z8CLdz7.json +++ b/src/packs/domains/domainCard_Overwhelming_Aura_iEBLySZD9z8CLdz7.json @@ -38,7 +38,7 @@ "includeBase": false }, "target": { - "type": "any", + "type": "self", "amount": null }, "effects": [ @@ -69,7 +69,7 @@ }, "name": "Cast", "img": "icons/magic/holy/angel-winged-humanoid-blue.webp", - "range": "" + "range": "self" } }, "attribution": { @@ -85,10 +85,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753922784491, - "modifiedTime": 1755429784343, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756038903709, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "iEBLySZD9z8CLdz7", "sort": 3400000, diff --git a/src/packs/domains/domainCard_Rage_Up_GRL0cvs96vrTDckZ.json b/src/packs/domains/domainCard_Rage_Up_GRL0cvs96vrTDckZ.json index d9d67737..140e570d 100644 --- a/src/packs/domains/domainCard_Rage_Up_GRL0cvs96vrTDckZ.json +++ b/src/packs/domains/domainCard_Rage_Up_GRL0cvs96vrTDckZ.json @@ -14,7 +14,7 @@ "type": "effect", "_id": "LmjwPg03xLnoGTHm", "systemPath": "actions", - "description": "", + "description": "

You can mark a Stress to gain a bonus to your damage roll equal to twice your Strength.

", "chatDisplay": true, "actionType": "action", "cost": [ @@ -22,8 +22,8 @@ "scalable": false, "key": "stress", "value": 1, - "step": 1, "keyIsID": false, + "step": null, "consumeOnSuccess": false } ], @@ -40,18 +40,18 @@ } ], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Mark 1 Stress", "img": "icons/magic/control/silhouette-aura-energy.webp", - "range": "" + "range": "self" }, "fKY9NcYBwCFwMsgV": { "type": "effect", "_id": "fKY9NcYBwCFwMsgV", "systemPath": "actions", - "description": "", + "description": "

You can mark a Stress to gain a bonus to your damage roll equal to twice your Strength.

", "chatDisplay": true, "actionType": "action", "cost": [ @@ -77,12 +77,12 @@ } ], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Mark 2 Stress", "img": "icons/magic/control/silhouette-aura-energy.webp", - "range": "" + "range": "self" } }, "attribution": { @@ -98,10 +98,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753922784496, - "modifiedTime": 1755428155116, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756037744719, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "GRL0cvs96vrTDckZ", "sort": 3400000, diff --git a/src/packs/domains/domainCard_Rain_of_Blades_Ucenef6JpjQxwXni.json b/src/packs/domains/domainCard_Rain_of_Blades_Ucenef6JpjQxwXni.json index 630960f4..03337fa2 100644 --- a/src/packs/domains/domainCard_Rain_of_Blades_Ucenef6JpjQxwXni.json +++ b/src/packs/domains/domainCard_Rain_of_Blades_Ucenef6JpjQxwXni.json @@ -14,7 +14,7 @@ "type": "attack", "_id": "WTnjKQs2uI1TuF9r", "systemPath": "actions", - "description": "

Spend a Hope to make a Spellcast Roll and conjure throwing blades that strike out at all targets within Very Close range. Targets you succeed against take d8+2 magic damage using your Proficiency.

", + "description": "

Spend a Hope to make a Spellcast Roll and conjure throwing blades that strike out at all targets within Very Close range. Targets you succeed against take d8+2 magic damage using your Proficiency.

@Template[type:emanation|range:vc]

", "chatDisplay": true, "actionType": "action", "cost": [ @@ -154,10 +154,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753922784497, - "modifiedTime": 1755429293162, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756038335767, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "Ucenef6JpjQxwXni", "sort": 3400000, diff --git a/src/packs/domains/domainCard_Salvation_Beam_4uAFGp3LxiC07woC.json b/src/packs/domains/domainCard_Salvation_Beam_4uAFGp3LxiC07woC.json index baaba0a6..c8efc258 100644 --- a/src/packs/domains/domainCard_Salvation_Beam_4uAFGp3LxiC07woC.json +++ b/src/packs/domains/domainCard_Salvation_Beam_4uAFGp3LxiC07woC.json @@ -14,7 +14,7 @@ "type": "healing", "_id": "dmnB4ZMSk8lsB8Lg", "systemPath": "actions", - "description": "

Make a Spellcast Roll (16). On a success, mark any number of Stress to target a line of allies within Far range. You can clear Hit Points on the targets equal to the number of Stress marked, divided among them however you’d like.

", + "description": "

Make a Spellcast Roll (16). On a success, mark any number of Stress to target a line of allies within Far range. You can clear Hit Points on the targets equal to the number of Stress marked, divided among them however you’d like.

@Template[type:ray|range:f]

", "chatDisplay": true, "actionType": "action", "cost": [ @@ -101,10 +101,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753922784508, - "modifiedTime": 1755429792958, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756038941872, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "4uAFGp3LxiC07woC", "sort": 3400000, diff --git a/src/packs/domains/domainCard_Smite_U1uWJE94HZVudujz.json b/src/packs/domains/domainCard_Smite_U1uWJE94HZVudujz.json index 245db4a2..7b966db2 100644 --- a/src/packs/domains/domainCard_Smite_U1uWJE94HZVudujz.json +++ b/src/packs/domains/domainCard_Smite_U1uWJE94HZVudujz.json @@ -40,12 +40,12 @@ } ], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Charge Smite", "img": "icons/skills/melee/sword-winged-holy-orange.webp", - "range": "" + "range": "self" } }, "attribution": { @@ -61,10 +61,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753922784519, - "modifiedTime": 1755429734196, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756038851464, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "U1uWJE94HZVudujz", "sort": 3400000, diff --git a/src/packs/domains/domainCard_Specter_of_the_Dark_iQhgqmLwhcSTYnvr.json b/src/packs/domains/domainCard_Specter_of_the_Dark_iQhgqmLwhcSTYnvr.json index 02d43c56..a7af630e 100644 --- a/src/packs/domains/domainCard_Specter_of_the_Dark_iQhgqmLwhcSTYnvr.json +++ b/src/packs/domains/domainCard_Specter_of_the_Dark_iQhgqmLwhcSTYnvr.json @@ -38,12 +38,12 @@ } ], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Become Spectral", "img": "icons/magic/unholy/silhouette-light-fire-blue.webp", - "range": "" + "range": "self" } }, "attribution": { @@ -59,10 +59,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753922784520, - "modifiedTime": 1755429431607, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756038467712, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "iQhgqmLwhcSTYnvr", "sort": 3400000, diff --git a/src/packs/domains/domainCard_Thorn_Skin_oUipGK84E2KjoKqh.json b/src/packs/domains/domainCard_Thorn_Skin_oUipGK84E2KjoKqh.json index 6c56de38..41103c18 100644 --- a/src/packs/domains/domainCard_Thorn_Skin_oUipGK84E2KjoKqh.json +++ b/src/packs/domains/domainCard_Thorn_Skin_oUipGK84E2KjoKqh.json @@ -33,12 +33,12 @@ }, "effects": [], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Spend Hope", "img": "icons/magic/nature/thorns-hand-glow-green.webp", - "range": "" + "range": "self" }, "IVAyyVf8gqFWB7bP": { "type": "effect", @@ -92,10 +92,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753922784531, - "modifiedTime": 1755429528391, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756038664007, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "oUipGK84E2KjoKqh", "sort": 3400000, diff --git a/src/packs/domains/domainCard_Uncanny_Disguise_TV56wSysbU5xAlOa.json b/src/packs/domains/domainCard_Uncanny_Disguise_TV56wSysbU5xAlOa.json index 1d0456ce..be34704d 100644 --- a/src/packs/domains/domainCard_Uncanny_Disguise_TV56wSysbU5xAlOa.json +++ b/src/packs/domains/domainCard_Uncanny_Disguise_TV56wSysbU5xAlOa.json @@ -28,7 +28,7 @@ "includeBase": false }, "target": { - "type": "any", + "type": "self", "amount": null }, "effects": [ @@ -39,7 +39,7 @@ ], "name": "Don Facade", "img": "icons/magic/control/debuff-energy-hold-pink.webp", - "range": "" + "range": "self" }, "OoNND7VcWoBQdtFK": { "type": "effect", @@ -91,10 +91,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753922784537, - "modifiedTime": 1755429298602, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756038348939, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "TV56wSysbU5xAlOa", "sort": 3400000, diff --git a/src/packs/domains/domainCard_Unleash_Chaos_o62i0QdbUDIiAhSq.json b/src/packs/domains/domainCard_Unleash_Chaos_o62i0QdbUDIiAhSq.json index 8137b243..757aca62 100644 --- a/src/packs/domains/domainCard_Unleash_Chaos_o62i0QdbUDIiAhSq.json +++ b/src/packs/domains/domainCard_Unleash_Chaos_o62i0QdbUDIiAhSq.json @@ -126,12 +126,12 @@ }, "effects": [], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Replenish Tokens", "img": "icons/commodities/gems/gem-faceted-diamond-blue.webp", - "range": "" + "range": "self" } }, "attribution": { @@ -147,10 +147,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753922784537, - "modifiedTime": 1755427920648, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756037237479, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "o62i0QdbUDIiAhSq", "sort": 3400000, diff --git a/src/packs/domains/domainCard_Vanishing_Dodge_GBMIElIpk4cvk1Bd.json b/src/packs/domains/domainCard_Vanishing_Dodge_GBMIElIpk4cvk1Bd.json index 29ba6f90..d5284f5a 100644 --- a/src/packs/domains/domainCard_Vanishing_Dodge_GBMIElIpk4cvk1Bd.json +++ b/src/packs/domains/domainCard_Vanishing_Dodge_GBMIElIpk4cvk1Bd.json @@ -38,7 +38,7 @@ } ], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Vanish", @@ -59,10 +59,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753922784539, - "modifiedTime": 1755429382850, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756038425727, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "GBMIElIpk4cvk1Bd", "sort": 3400000, diff --git a/src/packs/domains/domainCard_Veil_of_Night_gV4L5ZZmfPrEbIDh.json b/src/packs/domains/domainCard_Veil_of_Night_gV4L5ZZmfPrEbIDh.json index 3e0a4623..ad78023c 100644 --- a/src/packs/domains/domainCard_Veil_of_Night_gV4L5ZZmfPrEbIDh.json +++ b/src/packs/domains/domainCard_Veil_of_Night_gV4L5ZZmfPrEbIDh.json @@ -28,7 +28,7 @@ "includeBase": false }, "target": { - "type": "any", + "type": "self", "amount": null }, "effects": [ @@ -59,7 +59,7 @@ }, "name": "Cast", "img": "icons/magic/unholy/barrier-shield-glowing-pink.webp", - "range": "" + "range": "self" } }, "attribution": { @@ -75,10 +75,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753922784541, - "modifiedTime": 1755429328234, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756038378309, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_id": "gV4L5ZZmfPrEbIDh", "sort": 3400000, diff --git a/src/packs/domains/folders_Splendor_TL1TutmbeCVJ06nR.json b/src/packs/domains/folders_Splendor_TL1TutmbeCVJ06nR.json index c0870ecf..87cc7469 100644 --- a/src/packs/domains/folders_Splendor_TL1TutmbeCVJ06nR.json +++ b/src/packs/domains/folders_Splendor_TL1TutmbeCVJ06nR.json @@ -6,18 +6,18 @@ "sorting": "m", "_id": "TL1TutmbeCVJ06nR", "description": "", - "sort": 800000, + "sort": 900000, "flags": {}, "_stats": { "compendiumSource": null, "duplicateSource": null, "exportSource": null, - "coreVersion": "13.346", + "coreVersion": "13.347", "systemId": "daggerheart", "systemVersion": "0.0.1", "createdTime": 1748717133557, - "modifiedTime": 1752681441081, - "lastModifiedBy": "YNJ4HgHtFrTI89mx" + "modifiedTime": 1756038952388, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!folders!TL1TutmbeCVJ06nR" } diff --git a/src/packs/domains/folders_Valor_yPVeShe47ETIqs9q.json b/src/packs/domains/folders_Valor_yPVeShe47ETIqs9q.json index 0a65d51f..f6361e50 100644 --- a/src/packs/domains/folders_Valor_yPVeShe47ETIqs9q.json +++ b/src/packs/domains/folders_Valor_yPVeShe47ETIqs9q.json @@ -6,18 +6,18 @@ "sorting": "m", "_id": "yPVeShe47ETIqs9q", "description": "", - "sort": 900000, + "sort": 800000, "flags": {}, "_stats": { "compendiumSource": null, "duplicateSource": null, "exportSource": null, - "coreVersion": "13.346", + "coreVersion": "13.347", "systemId": "daggerheart", "systemVersion": "0.0.1", "createdTime": 1748717138700, - "modifiedTime": 1752681443619, - "lastModifiedBy": "YNJ4HgHtFrTI89mx" + "modifiedTime": 1756038952388, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!folders!yPVeShe47ETIqs9q" } diff --git a/src/packs/items/consumables/consumable_Armor_Stitcher_VlbsCjvvLNfTzNXb.json b/src/packs/items/consumables/consumable_Armor_Stitcher_VlbsCjvvLNfTzNXb.json index 62049319..4f68e0d7 100644 --- a/src/packs/items/consumables/consumable_Armor_Stitcher_VlbsCjvvLNfTzNXb.json +++ b/src/packs/items/consumables/consumable_Armor_Stitcher_VlbsCjvvLNfTzNXb.json @@ -30,12 +30,12 @@ }, "effects": [], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Stitch", "img": "icons/skills/trades/textiles-stitching-leather-brown.webp", - "range": "" + "range": "self" } }, "consumeOnUse": true, @@ -59,10 +59,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753588973384, - "modifiedTime": 1755432710896, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756041701211, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!VlbsCjvvLNfTzNXb" } diff --git a/src/packs/items/consumables/consumable_Attune_Potion_JGD3M9hBHtVAA8XP.json b/src/packs/items/consumables/consumable_Attune_Potion_JGD3M9hBHtVAA8XP.json index 9e316540..64db5be0 100644 --- a/src/packs/items/consumables/consumable_Attune_Potion_JGD3M9hBHtVAA8XP.json +++ b/src/packs/items/consumables/consumable_Attune_Potion_JGD3M9hBHtVAA8XP.json @@ -32,7 +32,7 @@ }, "name": "Drink", "img": "icons/consumables/potions/bottle-conical-corked-purple.webp", - "range": "" + "range": "self" } }, "consumeOnUse": true, @@ -101,10 +101,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753587033468, - "modifiedTime": 1755432536588, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756041711386, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!JGD3M9hBHtVAA8XP" } diff --git a/src/packs/items/consumables/consumable_Blood_of_the_Yorgi_pDGzmczoTlKGmKgd.json b/src/packs/items/consumables/consumable_Blood_of_the_Yorgi_pDGzmczoTlKGmKgd.json index a1b70dc6..7d7596eb 100644 --- a/src/packs/items/consumables/consumable_Blood_of_the_Yorgi_pDGzmczoTlKGmKgd.json +++ b/src/packs/items/consumables/consumable_Blood_of_the_Yorgi_pDGzmczoTlKGmKgd.json @@ -22,12 +22,12 @@ }, "effects": [], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Drink", "img": "icons/consumables/potions/potion-tube-corked-bat-gold-red.webp", - "range": "" + "range": "veryFar" } }, "consumeOnUse": true, @@ -51,10 +51,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753589954973, - "modifiedTime": 1755432804800, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756041739719, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!pDGzmczoTlKGmKgd" } diff --git a/src/packs/items/consumables/consumable_Bolster_Potion_FOPQNqXbiVO0ilYL.json b/src/packs/items/consumables/consumable_Bolster_Potion_FOPQNqXbiVO0ilYL.json index 7519c7ab..70d93553 100644 --- a/src/packs/items/consumables/consumable_Bolster_Potion_FOPQNqXbiVO0ilYL.json +++ b/src/packs/items/consumables/consumable_Bolster_Potion_FOPQNqXbiVO0ilYL.json @@ -32,7 +32,7 @@ }, "name": "Drink", "img": "icons/consumables/potions/potion-vial-tube-yellow.webp", - "range": "" + "range": "self" } }, "consumeOnUse": true, @@ -101,10 +101,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753586850134, - "modifiedTime": 1755432522101, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756041748451, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!FOPQNqXbiVO0ilYL" } diff --git a/src/packs/items/consumables/consumable_Death_Tea_xDnJeF1grkmKck8Q.json b/src/packs/items/consumables/consumable_Death_Tea_xDnJeF1grkmKck8Q.json index e0b8a28c..25f7a45d 100644 --- a/src/packs/items/consumables/consumable_Death_Tea_xDnJeF1grkmKck8Q.json +++ b/src/packs/items/consumables/consumable_Death_Tea_xDnJeF1grkmKck8Q.json @@ -27,12 +27,12 @@ } ], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Drink", "img": "icons/consumables/drinks/wine-amphora-clay-gray.webp", - "range": "" + "range": "self" } }, "consumeOnUse": true, @@ -101,10 +101,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753592717630, - "modifiedTime": 1755433085832, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756041829971, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!xDnJeF1grkmKck8Q" } diff --git a/src/packs/items/consumables/consumable_Dragonbloom_Tea_wM18PWWW2Ami4fBG.json b/src/packs/items/consumables/consumable_Dragonbloom_Tea_wM18PWWW2Ami4fBG.json index 9389ca85..ee696b5d 100644 --- a/src/packs/items/consumables/consumable_Dragonbloom_Tea_wM18PWWW2Ami4fBG.json +++ b/src/packs/items/consumables/consumable_Dragonbloom_Tea_wM18PWWW2Ami4fBG.json @@ -11,13 +11,13 @@ "type": "attack", "_id": "rcHEz3ImUDwo6lPC", "systemPath": "actions", - "description": "", + "description": "

You can drink this tea to unleash a fiery breath attack. Make an Instinct Roll against all adversaries in front of you within Close range. Targets you succeed against take d20 physical damage using your Proficiency.

@Template[type:emanation|range:c]

", "chatDisplay": true, "actionType": "action", "cost": [], "uses": { "value": null, - "max": null, + "max": "", "recovery": null }, "damage": { @@ -100,10 +100,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753591525829, - "modifiedTime": 1755432974525, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756041882255, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!wM18PWWW2Ami4fBG" } diff --git a/src/packs/items/consumables/consumable_Featherbone_DpxEMpwfasEBpORU.json b/src/packs/items/consumables/consumable_Featherbone_DpxEMpwfasEBpORU.json index 31a5b7fd..fbdb9c17 100644 --- a/src/packs/items/consumables/consumable_Featherbone_DpxEMpwfasEBpORU.json +++ b/src/packs/items/consumables/consumable_Featherbone_DpxEMpwfasEBpORU.json @@ -20,14 +20,19 @@ "max": "", "recovery": null }, - "effects": [], + "effects": [ + { + "_id": "VfJIJBW96e45xQHY", + "onSave": false + } + ], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Use", "img": "icons/commodities/bones/bones-stack-worn-brown.webp", - "range": "" + "range": "self" } }, "consumeOnUse": true, @@ -37,7 +42,52 @@ "artist": "" } }, - "effects": [], + "effects": [ + { + "name": "Featherbone", + "img": "icons/commodities/bones/bones-stack-worn-brown.webp", + "origin": "Compendium.daggerheart.consumables.Item.DpxEMpwfasEBpORU", + "transfer": false, + "_id": "VfJIJBW96e45xQHY", + "type": "base", + "system": { + "rangeDependence": { + "enabled": false, + "type": "withinRange", + "target": "hostile", + "range": "melee" + } + }, + "changes": [], + "disabled": false, + "duration": { + "startTime": null, + "combat": null, + "seconds": null, + "rounds": null, + "turns": null, + "startRound": null, + "startTurn": null + }, + "description": "

You can use this bone to control your falling speed for a number of minutes equal to your level.

", + "tint": "#ffffff", + "statuses": [], + "sort": 0, + "flags": {}, + "_stats": { + "compendiumSource": null, + "duplicateSource": null, + "exportSource": null, + "coreVersion": "13.347", + "systemId": "daggerheart", + "systemVersion": "1.1.0", + "createdTime": 1756041997551, + "modifiedTime": 1756042000517, + "lastModifiedBy": "vUIbuan0U50nfKBE" + }, + "_key": "!items.effects!DpxEMpwfasEBpORU.VfJIJBW96e45xQHY" + } + ], "folder": null, "sort": 0, "ownership": { @@ -51,10 +101,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753590624634, - "modifiedTime": 1755432889504, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756042004101, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!DpxEMpwfasEBpORU" } diff --git a/src/packs/items/consumables/consumable_Gill_Salve_Nvbb9mze6o5D0AEg.json b/src/packs/items/consumables/consumable_Gill_Salve_Nvbb9mze6o5D0AEg.json index 5e770de0..dd7ce092 100644 --- a/src/packs/items/consumables/consumable_Gill_Salve_Nvbb9mze6o5D0AEg.json +++ b/src/packs/items/consumables/consumable_Gill_Salve_Nvbb9mze6o5D0AEg.json @@ -20,14 +20,19 @@ "max": "", "recovery": null }, - "effects": [], + "effects": [ + { + "_id": "5rL9CY5GO9SJcEZq", + "onSave": false + } + ], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Apply", "img": "icons/commodities/materials/bowl-powder-blue.webp", - "range": "" + "range": "self" } }, "consumeOnUse": true, @@ -37,7 +42,52 @@ "artist": "" } }, - "effects": [], + "effects": [ + { + "name": "Gill Salve", + "img": "icons/commodities/materials/bowl-powder-blue.webp", + "origin": "Compendium.daggerheart.consumables.Item.Nvbb9mze6o5D0AEg", + "transfer": false, + "_id": "5rL9CY5GO9SJcEZq", + "type": "base", + "system": { + "rangeDependence": { + "enabled": false, + "type": "withinRange", + "target": "hostile", + "range": "melee" + } + }, + "changes": [], + "disabled": false, + "duration": { + "startTime": null, + "combat": null, + "seconds": null, + "rounds": null, + "turns": null, + "startRound": null, + "startTurn": null + }, + "description": "

You can apply this salve to your neck to breathe underwater for a number of minutes equal to your level.

", + "tint": "#ffffff", + "statuses": [], + "sort": 0, + "flags": {}, + "_stats": { + "compendiumSource": null, + "duplicateSource": null, + "exportSource": null, + "coreVersion": "13.347", + "systemId": "daggerheart", + "systemVersion": "1.1.0", + "createdTime": 1756041968799, + "modifiedTime": 1756041978849, + "lastModifiedBy": "vUIbuan0U50nfKBE" + }, + "_key": "!items.effects!Nvbb9mze6o5D0AEg.5rL9CY5GO9SJcEZq" + } + ], "folder": null, "sort": 0, "ownership": { @@ -51,10 +101,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753589241094, - "modifiedTime": 1755432719146, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756041983578, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!Nvbb9mze6o5D0AEg" } diff --git a/src/packs/items/loot/loot_Glamour_Stone_Pj17cvdJ1XG1jv6I.json b/src/packs/items/loot/loot_Glamour_Stone_Pj17cvdJ1XG1jv6I.json index 6b233757..fa76826b 100644 --- a/src/packs/items/loot/loot_Glamour_Stone_Pj17cvdJ1XG1jv6I.json +++ b/src/packs/items/loot/loot_Glamour_Stone_Pj17cvdJ1XG1jv6I.json @@ -11,7 +11,7 @@ "type": "effect", "_id": "ATQgH12mufTOQLKs", "systemPath": "actions", - "description": "", + "description": "

Activate this pebble-sized stone to memorize the appearance of someone you can see. Spend a Hope to magically recreate this guise on yourself as an illusion.

", "chatDisplay": true, "actionType": "action", "cost": [ @@ -25,17 +25,22 @@ ], "uses": { "value": null, - "max": null, + "max": "", "recovery": null }, - "effects": [], + "effects": [ + { + "_id": "K5SB6tfuDkdVaQYe", + "onSave": false + } + ], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Create Illusion", "img": "icons/commodities/treasure/token-engraved-purple-glowing.webp", - "range": "" + "range": "self" } }, "attribution": { @@ -44,7 +49,52 @@ "artist": "" } }, - "effects": [], + "effects": [ + { + "name": "Glamour Stone", + "img": "icons/commodities/treasure/token-engraved-purple-glowing.webp", + "origin": "Compendium.daggerheart.loot.Item.Pj17cvdJ1XG1jv6I", + "transfer": false, + "_id": "K5SB6tfuDkdVaQYe", + "type": "base", + "system": { + "rangeDependence": { + "enabled": false, + "type": "withinRange", + "target": "hostile", + "range": "melee" + } + }, + "changes": [], + "disabled": false, + "duration": { + "startTime": null, + "combat": null, + "seconds": null, + "rounds": null, + "turns": null, + "startRound": null, + "startTurn": null + }, + "description": "

Activate this pebble-sized stone to memorize the appearance of someone you can see. Spend a Hope to magically recreate this guise on yourself as an illusion.

", + "tint": "#ffffff", + "statuses": [], + "sort": 0, + "flags": {}, + "_stats": { + "compendiumSource": null, + "duplicateSource": null, + "exportSource": null, + "coreVersion": "13.347", + "systemId": "daggerheart", + "systemVersion": "1.1.0", + "createdTime": 1756042876194, + "modifiedTime": 1756042884422, + "lastModifiedBy": "vUIbuan0U50nfKBE" + }, + "_key": "!items.effects!Pj17cvdJ1XG1jv6I.K5SB6tfuDkdVaQYe" + } + ], "folder": null, "sort": 0, "ownership": { @@ -58,10 +108,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753625773657, - "modifiedTime": 1755431995057, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756042876216, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!Pj17cvdJ1XG1jv6I" } diff --git a/src/packs/items/loot/loot_Piercing_Arrows_I63LTFD6GXHgyGpR.json b/src/packs/items/loot/loot_Piercing_Arrows_I63LTFD6GXHgyGpR.json index 9a056431..26845282 100644 --- a/src/packs/items/loot/loot_Piercing_Arrows_I63LTFD6GXHgyGpR.json +++ b/src/packs/items/loot/loot_Piercing_Arrows_I63LTFD6GXHgyGpR.json @@ -11,7 +11,7 @@ "type": "effect", "_id": "DW5AqEM0F8XaUqpn", "systemPath": "actions", - "description": "", + "description": "

Three times per rest when you succeed on an attack with one of these arrows, you can add your Proficiency to the damage roll.

", "chatDisplay": true, "actionType": "action", "cost": [], @@ -108,10 +108,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753625947079, - "modifiedTime": 1755432020809, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756042964027, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!I63LTFD6GXHgyGpR" } diff --git a/src/packs/items/loot/loot_Premium_Bedroll_QGYPNBIufpBguwjC.json b/src/packs/items/loot/loot_Premium_Bedroll_QGYPNBIufpBguwjC.json index f000d904..1a2b7f62 100644 --- a/src/packs/items/loot/loot_Premium_Bedroll_QGYPNBIufpBguwjC.json +++ b/src/packs/items/loot/loot_Premium_Bedroll_QGYPNBIufpBguwjC.json @@ -17,11 +17,11 @@ "cost": [], "uses": { "value": null, - "max": null, + "max": "", "recovery": null }, "target": { - "type": "any", + "type": "self", "amount": null }, "effects": [], @@ -89,10 +89,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753624827945, - "modifiedTime": 1755431895118, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756042976231, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!QGYPNBIufpBguwjC" } diff --git a/src/packs/items/loot/loot_Ring_of_Resistance_aUqRifqR5JXXa1dN.json b/src/packs/items/loot/loot_Ring_of_Resistance_aUqRifqR5JXXa1dN.json index 628fe395..1fa60198 100644 --- a/src/packs/items/loot/loot_Ring_of_Resistance_aUqRifqR5JXXa1dN.json +++ b/src/packs/items/loot/loot_Ring_of_Resistance_aUqRifqR5JXXa1dN.json @@ -11,7 +11,7 @@ "type": "effect", "_id": "4swwe8SZeh2KyPtl", "systemPath": "actions", - "description": "", + "description": "

Once per long rest, you can activate this ring after a successful attack against you to halve the damage.

", "chatDisplay": true, "actionType": "action", "cost": [], @@ -20,14 +20,19 @@ "max": 1, "recovery": "longRest" }, - "effects": [], + "effects": [ + { + "_id": "Sj7uGf560T7u4rlX", + "onSave": false + } + ], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Activate", "img": "icons/equipment/finger/ring-shield-silver.webp", - "range": "" + "range": "self" } }, "attribution": { @@ -36,7 +41,65 @@ "artist": "" } }, - "effects": [], + "effects": [ + { + "name": "Ring of Resistance", + "img": "icons/equipment/finger/ring-shield-silver.webp", + "origin": "Compendium.daggerheart.loot.Item.aUqRifqR5JXXa1dN", + "transfer": false, + "_id": "Sj7uGf560T7u4rlX", + "type": "base", + "system": { + "rangeDependence": { + "enabled": false, + "type": "withinRange", + "target": "hostile", + "range": "melee" + } + }, + "changes": [ + { + "key": "system.resistance.magical.resistance", + "mode": 5, + "value": "1", + "priority": null + }, + { + "key": "system.resistance.physical.resistance", + "mode": 5, + "value": "1", + "priority": null + } + ], + "disabled": false, + "duration": { + "startTime": null, + "combat": null, + "seconds": null, + "rounds": null, + "turns": null, + "startRound": null, + "startTurn": null + }, + "description": "

Once per long rest, you can activate this ring after a successful attack against you to halve the damage.

", + "tint": "#ffffff", + "statuses": [], + "sort": 0, + "flags": {}, + "_stats": { + "compendiumSource": null, + "duplicateSource": null, + "exportSource": null, + "coreVersion": "13.347", + "systemId": "daggerheart", + "systemVersion": "1.1.0", + "createdTime": 1756043003002, + "modifiedTime": 1756043032046, + "lastModifiedBy": "vUIbuan0U50nfKBE" + }, + "_key": "!items.effects!aUqRifqR5JXXa1dN.Sj7uGf560T7u4rlX" + } + ], "folder": null, "sort": 0, "ownership": { @@ -50,10 +113,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753638222884, - "modifiedTime": 1755432185583, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756043010729, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!aUqRifqR5JXXa1dN" } diff --git a/src/packs/items/loot/loot_Ring_of_Silence_K1ysGnTpNyxPu5Au.json b/src/packs/items/loot/loot_Ring_of_Silence_K1ysGnTpNyxPu5Au.json index b6de3da5..7d38f5a2 100644 --- a/src/packs/items/loot/loot_Ring_of_Silence_K1ysGnTpNyxPu5Au.json +++ b/src/packs/items/loot/loot_Ring_of_Silence_K1ysGnTpNyxPu5Au.json @@ -11,7 +11,7 @@ "type": "effect", "_id": "Y4mvuJ4tncrKhyjY", "systemPath": "actions", - "description": "", + "description": "

Spend a Hope to activate this ring. Your footsteps are silent until your next rest.

", "chatDisplay": true, "actionType": "action", "cost": [ @@ -28,14 +28,19 @@ "max": 1, "recovery": "shortRest" }, - "effects": [], + "effects": [ + { + "_id": "9JSLRu7amQ4iuIO7", + "onSave": false + } + ], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Activate", "img": "icons/equipment/finger/ring-ball-purple.webp", - "range": "" + "range": "self" } }, "attribution": { @@ -47,9 +52,11 @@ "effects": [ { "name": "Ring of Silence", - "type": "base", - "_id": "aCt3QjdeTREZAlEa", "img": "icons/equipment/finger/ring-ball-purple.webp", + "origin": "Compendium.daggerheart.loot.Item.K1ysGnTpNyxPu5Au", + "transfer": false, + "_id": "9JSLRu7amQ4iuIO7", + "type": "base", "system": { "rangeDependence": { "enabled": false, @@ -59,7 +66,7 @@ } }, "changes": [], - "disabled": true, + "disabled": false, "duration": { "startTime": null, "combat": null, @@ -69,10 +76,8 @@ "startRound": null, "startTurn": null }, - "description": "

Your footsteps are silent until your next rest.

", - "origin": null, + "description": "

Your footsteps are silent until your next rest.

", "tint": "#ffffff", - "transfer": true, "statuses": [], "sort": 0, "flags": {}, @@ -80,14 +85,14 @@ "compendiumSource": null, "duplicateSource": null, "exportSource": null, - "coreVersion": "13.346", + "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "0.0.1", - "createdTime": 1753990311274, - "modifiedTime": 1753990345889, - "lastModifiedBy": "MQSznptE5yLT7kj8" + "systemVersion": "1.1.0", + "createdTime": 1756043091753, + "modifiedTime": 1756043101742, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, - "_key": "!items.effects!K1ysGnTpNyxPu5Au.aCt3QjdeTREZAlEa" + "_key": "!items.effects!K1ysGnTpNyxPu5Au.9JSLRu7amQ4iuIO7" } ], "folder": null, @@ -103,10 +108,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1753637775628, - "modifiedTime": 1755432151648, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756043091776, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!K1ysGnTpNyxPu5Au" } diff --git a/src/packs/subclasses/feature_Act_of_Reprisal_k7vvMJtEcxMWUUrW.json b/src/packs/subclasses/feature_Act_of_Reprisal_k7vvMJtEcxMWUUrW.json index 441bf12a..a73eb3d7 100644 --- a/src/packs/subclasses/feature_Act_of_Reprisal_k7vvMJtEcxMWUUrW.json +++ b/src/packs/subclasses/feature_Act_of_Reprisal_k7vvMJtEcxMWUUrW.json @@ -29,7 +29,7 @@ } ], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Mark Adversary", @@ -98,10 +98,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1754243143650, - "modifiedTime": 1755391854504, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756035614382, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!k7vvMJtEcxMWUUrW" } diff --git a/src/packs/subclasses/feature_Adrenaline_uByM34yQlw38yf1V.json b/src/packs/subclasses/feature_Adrenaline_uByM34yQlw38yf1V.json index c1b11484..e15129b0 100644 --- a/src/packs/subclasses/feature_Adrenaline_uByM34yQlw38yf1V.json +++ b/src/packs/subclasses/feature_Adrenaline_uByM34yQlw38yf1V.json @@ -7,36 +7,7 @@ "system": { "description": "

While you're Vulnerable, add your level to your damage rolls.

", "resource": null, - "actions": { - "UgffABhuobRDP7MQ": { - "type": "effect", - "_id": "UgffABhuobRDP7MQ", - "systemPath": "actions", - "description": "

While you're Vulnerable, add your level to your damage rolls.

", - "chatDisplay": true, - "actionType": "action", - "cost": [], - "uses": { - "value": null, - "max": "", - "recovery": null, - "consumeOnSuccess": false - }, - "effects": [ - { - "_id": "HMx9uZ54mvMiH95x", - "onSave": false - } - ], - "target": { - "type": "self", - "amount": null - }, - "name": "Apply", - "img": "icons/magic/unholy/hand-marked-pink.webp", - "range": "" - } - }, + "actions": {}, "originItemType": null, "originId": null, "attribution": { @@ -116,10 +87,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1754319984350, - "modifiedTime": 1755392070263, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756035779655, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!uByM34yQlw38yf1V" } diff --git a/src/packs/subclasses/feature_Battle_Bonded_hWsKyed1vfILg0I8.json b/src/packs/subclasses/feature_Battle_Bonded_hWsKyed1vfILg0I8.json index 259a25da..1efb24e2 100644 --- a/src/packs/subclasses/feature_Battle_Bonded_hWsKyed1vfILg0I8.json +++ b/src/packs/subclasses/feature_Battle_Bonded_hWsKyed1vfILg0I8.json @@ -16,7 +16,59 @@ "artist": "" } }, - "effects": [], + "effects": [ + { + "name": "Battle Bonded", + "type": "base", + "system": { + "rangeDependence": { + "enabled": false, + "type": "withinRange", + "target": "hostile", + "range": "melee" + } + }, + "_id": "IZhakv6EuG8DO4a3", + "img": "icons/creatures/mammals/humanoid-wolf-dog-blue.webp", + "changes": [ + { + "key": "system.evasion", + "mode": 2, + "value": "2", + "priority": null + } + ], + "disabled": true, + "duration": { + "startTime": null, + "combat": null, + "seconds": null, + "rounds": null, + "turns": null, + "startRound": null, + "startTurn": null + }, + "description": "

When an adversary attacks you while they’re within your companion’s Melee range, you gain a +2 bonus to your Evasion against the attack.

", + "origin": null, + "tint": "#ffffff", + "transfer": true, + "statuses": [], + "sort": 0, + "flags": {}, + "_stats": { + "compendiumSource": null, + "duplicateSource": null, + "exportSource": null, + "coreVersion": "13.347", + "systemId": "daggerheart", + "systemVersion": "1.1.0", + "createdTime": 1756035661033, + "modifiedTime": 1756035696385, + "lastModifiedBy": "vUIbuan0U50nfKBE" + }, + "_key": "!items.effects!hWsKyed1vfILg0I8.IZhakv6EuG8DO4a3" + } + ], "sort": 0, "ownership": { "default": 0, diff --git a/src/packs/subclasses/feature_Elementalist_dPcqKN5NeDkjB1HW.json b/src/packs/subclasses/feature_Elementalist_dPcqKN5NeDkjB1HW.json index dd2d8597..ac661a74 100644 --- a/src/packs/subclasses/feature_Elementalist_dPcqKN5NeDkjB1HW.json +++ b/src/packs/subclasses/feature_Elementalist_dPcqKN5NeDkjB1HW.json @@ -38,12 +38,12 @@ } ], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Bonus to Roll", "img": "icons/sundries/gaming/dice-runed-tan.webp", - "range": "" + "range": "self" }, "S7HvFD3qIR3ifJRL": { "type": "effect", @@ -75,12 +75,12 @@ } ], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Bonus to Damage", "img": "icons/sundries/gaming/dice-runed-tan.webp", - "range": "" + "range": "self" } }, "originItemType": null, @@ -115,7 +115,7 @@ "priority": null } ], - "disabled": true, + "disabled": false, "duration": { "startTime": null, "combat": null, @@ -134,10 +134,11 @@ "compendiumSource": null, "duplicateSource": null, "exportSource": null, - "coreVersion": "13.346", + "coreVersion": "13.347", "systemId": "daggerheart", "systemVersion": "0.0.1", - "lastModifiedBy": null + "lastModifiedBy": "vUIbuan0U50nfKBE", + "modifiedTime": 1756035533156 }, "_key": "!items.effects!dPcqKN5NeDkjB1HW.EY87mY6ULfIt3XC8" }, @@ -170,7 +171,7 @@ "priority": null } ], - "disabled": true, + "disabled": false, "duration": { "startTime": null, "combat": null, @@ -189,10 +190,11 @@ "compendiumSource": null, "duplicateSource": null, "exportSource": null, - "coreVersion": "13.346", + "coreVersion": "13.347", "systemId": "daggerheart", "systemVersion": "0.0.1", - "lastModifiedBy": null + "lastModifiedBy": "vUIbuan0U50nfKBE", + "modifiedTime": 1756035536128 }, "_key": "!items.effects!dPcqKN5NeDkjB1HW.WwibpgaO6Kkks7aZ" } @@ -209,10 +211,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1754349507020, - "modifiedTime": 1755392284524, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756035526712, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!dPcqKN5NeDkjB1HW" } diff --git a/src/packs/subclasses/feature_Elusive_Predator_Cjtc43V3IzAmfIFG.json b/src/packs/subclasses/feature_Elusive_Predator_Cjtc43V3IzAmfIFG.json index fed36288..d0787ec3 100644 --- a/src/packs/subclasses/feature_Elusive_Predator_Cjtc43V3IzAmfIFG.json +++ b/src/packs/subclasses/feature_Elusive_Predator_Cjtc43V3IzAmfIFG.json @@ -16,7 +16,59 @@ "artist": "" } }, - "effects": [], + "effects": [ + { + "name": "Elusive Predator", + "type": "base", + "system": { + "rangeDependence": { + "enabled": false, + "type": "withinRange", + "target": "hostile", + "range": "melee" + } + }, + "_id": "X4llFOcAcdJLbear", + "img": "icons/creatures/mammals/beast-horned-scaled-glowing-orange.webp", + "changes": [ + { + "key": "system.evasion", + "mode": 2, + "value": "2", + "priority": null + } + ], + "disabled": true, + "duration": { + "startTime": null, + "combat": null, + "seconds": null, + "rounds": null, + "turns": null, + "startRound": null, + "startTurn": null + }, + "description": "

When your Focus makes an attack against you, you gain a +2 bonus to your Evasion against the attack.

", + "origin": null, + "tint": "#ffffff", + "transfer": true, + "statuses": [], + "sort": 0, + "flags": {}, + "_stats": { + "compendiumSource": null, + "duplicateSource": null, + "exportSource": null, + "coreVersion": "13.347", + "systemId": "daggerheart", + "systemVersion": "1.1.0", + "createdTime": 1756035709943, + "modifiedTime": 1756035734622, + "lastModifiedBy": "vUIbuan0U50nfKBE" + }, + "_key": "!items.effects!Cjtc43V3IzAmfIFG.X4llFOcAcdJLbear" + } + ], "sort": 0, "ownership": { "default": 0, diff --git a/src/packs/subclasses/feature_Ruthless_Predator_Qny2J3R35bvC0Cey.json b/src/packs/subclasses/feature_Ruthless_Predator_Qny2J3R35bvC0Cey.json index 8ac4675a..c58ad931 100644 --- a/src/packs/subclasses/feature_Ruthless_Predator_Qny2J3R35bvC0Cey.json +++ b/src/packs/subclasses/feature_Ruthless_Predator_Qny2J3R35bvC0Cey.json @@ -36,12 +36,12 @@ } ], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Mark Stress", "img": "icons/creatures/mammals/wolf-shadow-black.webp", - "range": "" + "range": "self" } }, "originItemType": null, @@ -117,10 +117,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1754266926055, - "modifiedTime": 1755391927605, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756035422344, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!Qny2J3R35bvC0Cey" } diff --git a/src/packs/subclasses/feature_Shadow_Stepper_hAwTXjhyphiE3aeW.json b/src/packs/subclasses/feature_Shadow_Stepper_hAwTXjhyphiE3aeW.json index c46446d0..de0f54b7 100644 --- a/src/packs/subclasses/feature_Shadow_Stepper_hAwTXjhyphiE3aeW.json +++ b/src/packs/subclasses/feature_Shadow_Stepper_hAwTXjhyphiE3aeW.json @@ -36,12 +36,12 @@ } ], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Mark Stress", "img": "icons/magic/unholy/projectile-smoke-trail-pink.webp", - "range": "" + "range": "far" } }, "originItemType": null, @@ -110,10 +110,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1754318976447, - "modifiedTime": 1755392035163, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756035450401, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!hAwTXjhyphiE3aeW" } diff --git a/src/packs/subclasses/feature_Vanishing_Act_iyIg1VLwO8C6jvFZ.json b/src/packs/subclasses/feature_Vanishing_Act_iyIg1VLwO8C6jvFZ.json index d56182e9..57ae1815 100644 --- a/src/packs/subclasses/feature_Vanishing_Act_iyIg1VLwO8C6jvFZ.json +++ b/src/packs/subclasses/feature_Vanishing_Act_iyIg1VLwO8C6jvFZ.json @@ -36,12 +36,12 @@ } ], "target": { - "type": "any", + "type": "self", "amount": null }, "name": "Mark Stress", "img": "icons/magic/unholy/strike-hand-glow-pink.webp", - "range": "" + "range": "self" } }, "originItemType": null, @@ -57,7 +57,7 @@ "name": "Vanishing Act: Cloaked", "img": "icons/magic/unholy/strike-hand-glow-pink.webp", "origin": "Compendium.daggerheart.subclasses.Item.iyIg1VLwO8C6jvFZ", - "transfer": true, + "transfer": false, "_id": "czrwqq44sEr0uJ8O", "type": "base", "system": { @@ -88,12 +88,12 @@ "compendiumSource": null, "duplicateSource": null, "exportSource": null, - "coreVersion": "13.346", + "coreVersion": "13.347", "systemId": "daggerheart", "systemVersion": "0.0.1", "createdTime": 1754322584884, - "modifiedTime": 1754352062188, - "lastModifiedBy": "MQSznptE5yLT7kj8" + "modifiedTime": 1756035920880, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items.effects!iyIg1VLwO8C6jvFZ.czrwqq44sEr0uJ8O" } @@ -110,10 +110,10 @@ "exportSource": null, "coreVersion": "13.347", "systemId": "daggerheart", - "systemVersion": "1.0.5", + "systemVersion": "1.1.0", "createdTime": 1754321406972, - "modifiedTime": 1755392101482, - "lastModifiedBy": "VZIeX2YDvX338Zvr" + "modifiedTime": 1756035929726, + "lastModifiedBy": "vUIbuan0U50nfKBE" }, "_key": "!items!iyIg1VLwO8C6jvFZ" } diff --git a/styles/less/global/elements.less b/styles/less/global/elements.less index 399715ca..e4a2128c 100755 --- a/styles/less/global/elements.less +++ b/styles/less/global/elements.less @@ -624,6 +624,7 @@ .form-group { justify-content: end; + flex-wrap: nowrap; } .hint { diff --git a/styles/less/global/global.less b/styles/less/global/global.less index bf084751..383cdc6f 100644 --- a/styles/less/global/global.less +++ b/styles/less/global/global.less @@ -18,7 +18,7 @@ color: var(--color-form-hint); } - &:hover { + .form-group:hover { .hint { color: var(--color-form-hint-hover); } diff --git a/styles/less/ui/settings/homebrew-settings/domains.less b/styles/less/ui/settings/homebrew-settings/domains.less index 84e013c2..da258fcd 100644 --- a/styles/less/ui/settings/homebrew-settings/domains.less +++ b/styles/less/ui/settings/homebrew-settings/domains.less @@ -53,6 +53,10 @@ display: grid; grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr; gap: 8px; + max-height: 184px; + overflow: auto; + scrollbar-width: thin; + scrollbar-color: light-dark(#18162e, #f3c267) transparent; .domain-container { position: relative; diff --git a/styles/less/ui/settings/settings.less b/styles/less/ui/settings/settings.less index 2ac8bfb0..67fe7718 100644 --- a/styles/less/ui/settings/settings.less +++ b/styles/less/ui/settings/settings.less @@ -127,4 +127,4 @@ text-align: center; } } -} +} \ No newline at end of file diff --git a/system.json b/system.json index 31130bb8..8e5a7610 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "daggerheart", "title": "Daggerheart", "description": "An unofficial implementation of the Daggerheart system", - "version": "1.1.1", + "version": "1.1.2", "compatibility": { "minimum": "13", "verified": "13.347", diff --git a/templates/actionTypes/beastform.hbs b/templates/actionTypes/beastform.hbs index b2710208..b9bea445 100644 --- a/templates/actionTypes/beastform.hbs +++ b/templates/actionTypes/beastform.hbs @@ -1,9 +1,4 @@ -
- -
{{localize "DAGGERHEART.ACTIONS.Config.beastform.label"}}
-
- -
- {{formGroup fields.tierAccess.fields.exact value=beastform.tierAccess.exact labelAttr="label" valueAttr="key" localize=true blank=""}} -
+
+ {{localize "DAGGERHEART.ACTIONS.Config.beastform.label"}} + {{formGroup fields.tierAccess.fields.exact value=source.tierAccess.exact name="beastform.tierAccess.exact" labelAttr="label" valueAttr="key" localize=true blank=""}}
\ No newline at end of file diff --git a/templates/actionTypes/damage.hbs b/templates/actionTypes/damage.hbs index 6a94752b..96bb361c 100644 --- a/templates/actionTypes/damage.hbs +++ b/templates/actionTypes/damage.hbs @@ -56,7 +56,7 @@
{{formField ../fields.valueAlt.fields.flatMultiplier value=dmg.valueAlt.flatMultiplier name=(concat ../path "damage.parts." realIndex ".valueAlt.flatMultiplier") label="DAGGERHEART.ACTIONS.Settings.multiplier" classes="inline-child" localize=true }} - {{formField ../fields.valueAlt.fields.dice value=dmg.valueAlt.dice name=(concat ../path "damage.parts." realIndex ".valueAlt.dice") classes="inline-child"}} + {{formField ../fields.valueAlt.fields.dice value=dmg.valueAlt.dice name=(concat ../path "damage.parts." realIndex ".valueAlt.dice") classes="inline-child" localize=true}} {{formField ../fields.valueAlt.fields.bonus value=dmg.valueAlt.bonus name=(concat ../path "damage.parts." realIndex ".valueAlt.bonus") localize=true classes="inline-child"}}
@@ -70,7 +70,7 @@ {{#*inline "formula"}} {{#unless dmg.base}} - {{formField fields.custom.fields.enabled value=source.custom.enabled name=(concat path "damage.parts." realIndex "." target ".custom.enabled") classes="checkbox"}} + {{formField fields.custom.fields.enabled value=source.custom.enabled name=(concat path "damage.parts." realIndex "." target ".custom.enabled") classes="checkbox" localize=true}} {{/unless}} {{#if source.custom.enabled}} {{formField fields.custom.fields.formula value=source.custom.formula name=(concat path "damage.parts." realIndex "." target ".custom.formula") localize=true}} @@ -79,8 +79,8 @@ {{#unless @root.isNPC}} {{formField fields.multiplier value=source.multiplier name=(concat path "damage.parts." realIndex "." target ".multiplier") localize=true}} {{/unless}} - {{#if (eq source.multiplier 'flat')}}{{formField fields.flatMultiplier value=source.flatMultiplier name=(concat path "damage.parts." realIndex "." target ".flatMultiplier") }}{{/if}} - {{formField fields.dice value=source.dice name=(concat path "damage.parts." realIndex "." target ".dice")}} + {{#if (eq source.multiplier 'flat')}}{{formField fields.flatMultiplier value=source.flatMultiplier name=(concat path "damage.parts." realIndex "." target ".flatMultiplier") localize=true }}{{/if}} + {{formField fields.dice value=source.dice name=(concat path "damage.parts." realIndex "." target ".dice") localize=true}} {{formField fields.bonus value=source.bonus name=(concat path "damage.parts." realIndex "." target ".bonus") localize=true}} {{/if}} diff --git a/templates/actionTypes/save.hbs b/templates/actionTypes/save.hbs index 90bc0483..85536c87 100644 --- a/templates/actionTypes/save.hbs +++ b/templates/actionTypes/save.hbs @@ -1,6 +1,9 @@ -
- {{localize "DAGGERHEART.GENERAL.save"}} - {{formField fields.trait label="Trait" name="save.trait" value=source.trait localize=true}} - {{formField fields.difficulty label="Difficulty" name="save.difficulty" value=source.difficulty disabled=(not source.trait) placeholder=@root.baseSaveDifficulty}} - {{formField fields.damageMod label="Damage on Save" name="save.damageMod" value=source.damageMod localize=true disabled=(not source.trait)}} +
+ {{localize "DAGGERHEART.GENERAL.Roll.reaction"}} +

{{localize "DAGGERHEART.ACTIONS.Settings.saveHint"}}

+
+ {{formField fields.trait label="Trait" name="save.trait" value=source.trait localize=true}} + {{formField fields.difficulty label="Difficulty" name="save.difficulty" value=source.difficulty disabled=(not source.trait) placeholder=@root.baseSaveDifficulty}} + {{formField fields.damageMod label="Damage on Save" name="save.damageMod" value=source.damageMod localize=true disabled=(not source.trait)}} +
\ No newline at end of file diff --git a/templates/dialogs/dice-roll/rollSelection.hbs b/templates/dialogs/dice-roll/rollSelection.hbs index b2feaa0b..6a4655d6 100644 --- a/templates/dialogs/dice-roll/rollSelection.hbs +++ b/templates/dialogs/dice-roll/rollSelection.hbs @@ -75,16 +75,8 @@ {{#each experiences}} {{#if name}}
- {{#if (includes ../selectedExperiences id)}} - - {{else}} - - {{/if}} - {{#if (eq @root.rollType 'D20Roll')}} - {{name}} +{{modifier}} - {{else}} - {{name}} +{{value}} - {{/if}} + + {{name}} +{{value}}
{{/if}} {{/each}} @@ -126,6 +118,12 @@ {{selectOptions diceOptions selected=@root.roll.dAdvantage.denomination}} + {{#if abilities}} + {{localize "DAGGERHEART.GENERAL.traitModifier"}} + + {{/if}} {{/unless}} {{#if @root.rallyDie.length}} {{localize "DAGGERHEART.CLASS.Feature.rallyDice"}} diff --git a/templates/settings/automation-settings/roll.hbs b/templates/settings/automation-settings/roll.hbs new file mode 100644 index 00000000..5769bf61 --- /dev/null +++ b/templates/settings/automation-settings/roll.hbs @@ -0,0 +1,22 @@ +
+
+ + {{localize "DAGGERHEART.SETTINGS.Automation.roll.title"}} + + {{#each settingFields.schema.fields.roll.fields as | field |}} + {{!-- {{formGroup field value=(lookup @root.settingFields.roll field.name) localize=true rootId="automation-roll"}} --}} +
+ + {{#with (lookup @root.settingFields.roll field.name) as | values |}} + {{formGroup field.fields.gm value=values.gm rootId=(concat "automation-roll-" field.name "-gm") localize=true}} + {{formGroup field.fields.players value=values.players rootId=(concat "automation-roll-" field.name "-players") localize=true}} + {{/with}} +

{{localize (concat "DAGGERHEART.SETTINGS.Automation.FIELDS.roll." field.name ".hint")}}

+
+ {{/each}} +
+
\ No newline at end of file diff --git a/templates/sheets-settings/action-settings/effect.hbs b/templates/sheets-settings/action-settings/effect.hbs index 26a097af..51c15aae 100644 --- a/templates/sheets-settings/action-settings/effect.hbs +++ b/templates/sheets-settings/action-settings/effect.hbs @@ -6,7 +6,6 @@ {{#if fields.roll}}{{> 'systems/daggerheart/templates/actionTypes/roll.hbs' fields=fields.roll.fields source=source.roll}}{{/if}} {{#if fields.save}}{{> 'systems/daggerheart/templates/actionTypes/save.hbs' fields=fields.save.fields source=source.save}}{{/if}} {{#if fields.damage}}{{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=fields.damage.fields.parts.element.fields source=source.damage directField=fields.damage.fields.direct }}{{/if}} - {{#if fields.resource}}{{> 'systems/daggerheart/templates/actionTypes/resource.hbs' fields=fields.resource.fields source=source.resource}}{{/if}} {{#if fields.macro}}{{> 'systems/daggerheart/templates/actionTypes/macro.hbs' fields=fields.macro source=source.macro}}{{/if}} {{#if fields.effects}}{{> 'systems/daggerheart/templates/actionTypes/effect.hbs' fields=fields.effects.element.fields source=source.effects}}{{/if}} {{#if fields.beastform}}{{> 'systems/daggerheart/templates/actionTypes/beastform.hbs' fields=fields.beastform.fields source=source.beastform}}{{/if}} diff --git a/templates/ui/chat/parts/button-part.hbs b/templates/ui/chat/parts/button-part.hbs index f83972f7..21317939 100644 --- a/templates/ui/chat/parts/button-part.hbs +++ b/templates/ui/chat/parts/button-part.hbs @@ -1,17 +1,17 @@
{{#if hasDamage}} {{#unless (empty damage)}} - {{#if canButtonApply}}{{/if}} + {{else}} {{/unless}} {{/if}} {{#if hasHealing}} {{#unless (empty damage)}} - {{#if canButtonApply}}{{/if}} + {{else}} {{/unless}} {{/if}} - {{#if (and hasEffect canButtonApply)}}{{/if}} + {{#if (and hasEffect)}}{{/if}}
\ No newline at end of file diff --git a/templates/ui/chat/parts/damage-part.hbs b/templates/ui/chat/parts/damage-part.hbs index 05b6b825..97828bd2 100644 --- a/templates/ui/chat/parts/damage-part.hbs +++ b/templates/ui/chat/parts/damage-part.hbs @@ -13,10 +13,10 @@ {{#each damage as | roll index | }}
- {{localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.inChatRoll')}}
{{localize "DAGGERHEART.GENERAL.total"}}: {{roll.total}}
{{#if (and (eq index "hitPoints")../isDirect)}}
{{localize "DAGGERHEART.CONFIG.DamageType.direct.short"}}
{{/if}} + {{#if ../hasHealing}}{{localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.name')}}{{else}}{{localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.inChatRoll')}}{{/if}}
{{localize "DAGGERHEART.GENERAL.total"}}: {{roll.total}}
{{#if (and (eq index "hitPoints") ../isDirect)}}
{{localize "DAGGERHEART.CONFIG.DamageType.direct.short"}}
{{/if}}
{{#each roll.parts}} - {{#if damageTypes.length}} + {{#if (and (not @root.hasHealing) damageTypes.length)}}