diff --git a/lang/en.json b/lang/en.json index 9553ab9f..b70e9b1d 100755 --- a/lang/en.json +++ b/lang/en.json @@ -1055,8 +1055,7 @@ }, "vulnerable": { "name": "Vulnerable", - "description": "While a creature is Vulnerable, all rolls targeting them have advantage.\nA creature who is already Vulnerable can’t be made to take the condition again.", - "autoAppliedByLabel": "Max Stress" + "description": "While a creature is Vulnerable, all rolls targeting them have advantage.\nA creature who is already Vulnerable can’t be made to take the condition again." } }, "CountdownType": { @@ -1191,12 +1190,12 @@ }, "far": { "name": "Far", - "description": "means a distance where one can see the appearance of a person or object, but probably not in great detail-- across a small battlefield or down a large corridor. This is usually about 30-100 feet away. While under danger, a PC will likely have to make an Agility roll to get here safely. Anything on a battle map that is within the length of a standard piece of paper (~10-11 inches) can usually be considered far.", + "description": "means a distance where one can see the appearance of a person or object, but probably not in great detail-- across a small battlefield or down a large corridor. This is usually about 30-100 feet away. While under danger, a PC will likely have to make an Agility check to get here safely. Anything on a battle map that is within the length of a standard piece of paper (~10-11 inches) can usually be considered far.", "short": "Far" }, "veryFar": { "name": "Very Far", - "description": "means a distance where you can see the shape of a person or object, but probably not make outany details-- across a large battlefield or down a long street, generally about 100-300 feet away. While under danger, a PC likely has to make an Agility roll to get here safely. Anything on a battle map that is beyond far distance, but still within sight of the characters can usually be considered very far.", + "description": "means a distance where you can see the shape of a person or object, but probably not make outany details-- across a large battlefield or down a long street, generally about 100-300 feet away. While under danger, a PC likely has to make an Agility check to get here safely. Anything on a battle map that is beyond far distance, but still within sight of the characters can usually be considered very far.", "short": "V. Far" } }, @@ -1319,7 +1318,6 @@ "triggerTexts": { "strangePatternsContentTitle": "Matched {nr} times.", "strangePatternsContentSubTitle": "Increase hope and stress to a total of {nr}.", - "strangePatternsActionExplanation": "Left click to increase, right click to decrease", "ferocityContent": "Spend 2 Hope to gain {bonus} bonus Evasion until after the next attack against you?", "ferocityEffectDescription": "Your evasion is increased by {bonus}. This bonus lasts until after the next attack made against you." }, @@ -2587,10 +2585,6 @@ "gm": { "label": "GM" }, "players": { "label": "Players" } }, - "vulnerableAutomation": { - "label": "Vulnerable Automation", - "hint": "Automatically apply the Vulnerable condition when a actor reaches max stress" - }, "countdownAutomation": { "label": "Countdown Automation", "hint": "Automatically progress countdowns based on their progression settings" @@ -2841,7 +2835,7 @@ "title": "Domain Card" }, "dualityRoll": { - "abilityCheckTitle": "{ability} Roll" + "abilityCheckTitle": "{ability} Check" }, "effectSummary": { "title": "Effects Applied", @@ -2856,7 +2850,7 @@ "selectLeader": "Select a Leader", "selectMember": "Select a Member", "rerollTitle": "Reroll Group Roll", - "rerollContent": "Are you sure you want to reroll your {trait} roll?", + "rerollContent": "Are you sure you want to reroll your {trait} check?", "rerollTooltip": "Reroll", "wholePartySelected": "The whole party is selected" }, @@ -3022,8 +3016,7 @@ "tokenActorMissing": "{name} is missing an Actor", "tokenActorsMissing": "[{names}] missing Actors", "domainTouchRequirement": "This domain card requires {nr} {domain} cards in the loadout to be used", - "knowTheTide": "Know The Tide gained a token", - "lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}" + "knowTheTide": "Know The Tide gained a token" }, "Sidebar": { "actorDirectory": { diff --git a/module/applications/settings/homebrewSettings.mjs b/module/applications/settings/homebrewSettings.mjs index 0779bf58..bfad899b 100644 --- a/module/applications/settings/homebrewSettings.mjs +++ b/module/applications/settings/homebrewSettings.mjs @@ -187,7 +187,6 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli }); } - game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject()); this.render(); } @@ -228,7 +227,6 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli } }); - game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject()); this.render(); } @@ -248,8 +246,6 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli await this.settings.updateSource({ [`${path}.${id}`]: _del }); - - game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject()); this.render(); } diff --git a/module/applications/sheets-configs/activeEffectConfig.mjs b/module/applications/sheets-configs/activeEffectConfig.mjs index 062883b6..09ef12d2 100644 --- a/module/applications/sheets-configs/activeEffectConfig.mjs +++ b/module/applications/sheets-configs/activeEffectConfig.mjs @@ -4,7 +4,62 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac constructor(options) { super(options); - this.changeChoices = DhActiveEffectConfig.getChangeChoices(); + const ignoredActorKeys = ['config', 'DhEnvironment', 'DhParty']; + + const getAllLeaves = (root, group, parentPath = '') => { + const leaves = []; + const rootKey = `${parentPath ? `${parentPath}.` : ''}${root.name}`; + for (const field of Object.values(root.fields)) { + if (field instanceof foundry.data.fields.SchemaField) + leaves.push(...getAllLeaves(field, group, rootKey)); + else + leaves.push({ + value: `${rootKey}.${field.name}`, + label: game.i18n.localize(field.label), + hint: game.i18n.localize(field.hint), + group + }); + } + + return leaves; + }; + this.changeChoices = Object.keys(game.system.api.models.actors).reduce((acc, key) => { + if (ignoredActorKeys.includes(key)) return acc; + + const model = game.system.api.models.actors[key]; + const group = game.i18n.localize(model.metadata.label); + const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model.metadata.type); + + const getTranslations = path => { + if (path === 'resources.hope.max') + return { + label: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.FIELDS.maxHope.label'), + hint: '' + }; + + const field = model.schema.getField(path); + return { + label: field ? game.i18n.localize(field.label) : path, + hint: field ? game.i18n.localize(field.hint) : '' + }; + }; + + const bars = attributes.bar.flatMap(x => { + const joined = `${x.join('.')}.max`; + return { value: joined, ...getTranslations(joined), group }; + }); + const values = attributes.value.flatMap(x => { + const joined = x.join('.'); + return { value: joined, ...getTranslations(joined), group }; + }); + + const bonuses = getAllLeaves(model.schema.fields.bonuses, group); + const rules = getAllLeaves(model.schema.fields.rules, group); + + acc.push(...bars, ...values, ...rules, ...bonuses); + + return acc; + }, []); } static DEFAULT_OPTIONS = { @@ -46,69 +101,6 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac } }; - /** - * Get ChangeChoices for the changes autocomplete. Static for use in this class aswell as in settings-active-effect-config.mjs - * @returns {ChangeChoice { value: string, label: string, hint: string, group: string }[]} - */ - static getChangeChoices() { - const ignoredActorKeys = ['config', 'DhEnvironment', 'DhParty']; - - const getAllLeaves = (root, group, parentPath = '') => { - const leaves = []; - const rootKey = `${parentPath ? `${parentPath}.` : ''}${root.name}`; - for (const field of Object.values(root.fields)) { - if (field instanceof foundry.data.fields.SchemaField) - leaves.push(...getAllLeaves(field, group, rootKey)); - else - leaves.push({ - value: `${rootKey}.${field.name}`, - label: game.i18n.localize(field.label), - hint: game.i18n.localize(field.hint), - group - }); - } - - return leaves; - }; - return Object.keys(game.system.api.models.actors).reduce((acc, key) => { - if (ignoredActorKeys.includes(key)) return acc; - - const model = game.system.api.models.actors[key]; - const group = game.i18n.localize(model.metadata.label); - const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model.metadata.type); - - const getTranslations = path => { - if (path === 'resources.hope.max') - return { - label: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.FIELDS.maxHope.label'), - hint: '' - }; - - const field = model.schema.getField(path); - return { - label: field ? game.i18n.localize(field.label) : path, - hint: field ? game.i18n.localize(field.hint) : '' - }; - }; - - const bars = attributes.bar.flatMap(x => { - const joined = `${x.join('.')}.max`; - return { value: joined, ...getTranslations(joined), group }; - }); - const values = attributes.value.flatMap(x => { - const joined = x.join('.'); - return { value: joined, ...getTranslations(joined), group }; - }); - - const bonuses = getAllLeaves(model.schema.fields.bonuses, group); - const rules = getAllLeaves(model.schema.fields.rules, group); - - acc.push(...bars, ...values, ...rules, ...bonuses); - - return acc; - }, []); - } - _attachPartListeners(partId, htmlElement, options) { super._attachPartListeners(partId, htmlElement, options); const changeChoices = this.changeChoices; diff --git a/module/applications/sheets-configs/setting-active-effect-config.mjs b/module/applications/sheets-configs/setting-active-effect-config.mjs index 12ac90d1..fe36e37f 100644 --- a/module/applications/sheets-configs/setting-active-effect-config.mjs +++ b/module/applications/sheets-configs/setting-active-effect-config.mjs @@ -7,7 +7,19 @@ export default class SettingActiveEffectConfig extends HandlebarsApplicationMixi super({}); this.effect = foundry.utils.deepClone(effect); - this.changeChoices = game.system.api.applications.sheetConfigs.ActiveEffectConfig.getChangeChoices(); + const ignoredActorKeys = ['config', 'DhEnvironment']; + this.changeChoices = Object.keys(game.system.api.models.actors).reduce((acc, key) => { + if (!ignoredActorKeys.includes(key)) { + const model = game.system.api.models.actors[key]; + const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model); + const group = game.i18n.localize(model.metadata.label); + const choices = CONFIG.Token.documentClass + .getTrackedAttributeChoices(attributes, model) + .map(x => ({ ...x, group: group })); + acc.push(...choices); + } + return acc; + }, []); } static DEFAULT_OPTIONS = { diff --git a/module/applications/sheets-configs/setting-feature-config.mjs b/module/applications/sheets-configs/setting-feature-config.mjs index fb790f7f..e8ff7818 100644 --- a/module/applications/sheets-configs/setting-feature-config.mjs +++ b/module/applications/sheets-configs/setting-feature-config.mjs @@ -73,11 +73,9 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App return context; } - static async updateData(_event, _element, formData) { + static async updateData(event, element, formData) { const data = foundry.utils.expandObject(formData.object); - await this.updateMove({ - [`${this.movePath}`]: data - }); + foundry.utils.mergeObject(this.move, data); this.render(); } @@ -137,7 +135,9 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App } ); - await this.updateMove({ [`${this.actionsPath}.${action.id}`]: action }); + await this.settings.updateSource({ [`${this.actionsPath}.${action.id}`]: action }); + this.move = foundry.utils.getProperty(this.settings, this.movePath); + this.render(); } @@ -150,12 +150,13 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App await game.system.api.applications.sheetConfigs.SettingActiveEffectConfig.configure(effect); if (!updatedEffect) return; - await this.updateMove({ + await this.settings.updateSource({ [`${this.movePath}.effects`]: this.move.effects.reduce((acc, effect, index) => { acc.push(index === effectIndex ? { ...updatedEffect, id: effect.id } : effect); return acc; }, []) }); + this.move = foundry.utils.getProperty(this.settings, this.movePath); this.render(); } else { const action = this.move.actions.get(id); @@ -170,13 +171,13 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App : existingEffectIndex === -1 ? [...currentEffects, effectData] : currentEffects.with(existingEffectIndex, effectData); - await this.updateMove({ + await this.settings.updateSource({ [`${this.movePath}.effects`]: updatedEffects }); } - await this.updateMove({ [`${this.actionsPath}.${id}`]: updatedMove }); - + await this.settings.updateSource({ [`${this.actionsPath}.${id}`]: updatedMove }); + this.move = foundry.utils.getProperty(this.settings, this.movePath); this.render(); return updatedEffects; }).render(true); @@ -198,34 +199,31 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App }); } } - await this.updateMove({ + await this.settings.updateSource({ [this.movePath]: { effects: move.effects.filter(x => x.id !== id), actions: move.actions } }); } else { - await this.updateMove({ [`${this.actionsPath}.${target.dataset.id}`]: _del }); + await this.settings.updateSource({ [`${this.actionsPath}.${target.dataset.id}`]: _del }); } + this.move = foundry.utils.getProperty(this.settings, this.movePath); this.render(); } - static async addEffect() { + static async addEffect(_, target) { const currentEffects = foundry.utils.getProperty(this.settings, `${this.movePath}.effects`); - - await this.updateMove({ + await this.settings.updateSource({ [`${this.movePath}.effects`]: [ ...currentEffects, game.system.api.data.activeEffects.BaseEffect.getDefaultObject() ] }); - this.render(); - } - async updateMove(update) { - await this.settings.updateSource(update); this.move = foundry.utils.getProperty(this.settings, this.movePath); + this.render(); } static resetMoves() {} diff --git a/module/applications/sheets/actors/party.mjs b/module/applications/sheets/actors/party.mjs index 1b1722db..d78519cb 100644 --- a/module/applications/sheets/actors/party.mjs +++ b/module/applications/sheets/actors/party.mjs @@ -6,6 +6,7 @@ import DaggerheartMenu from '../../sidebar/tabs/daggerheartMenu.mjs'; import { socketEvent } from '../../../systemRegistration/socket.mjs'; import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs'; import DhpActor from '../../../documents/actor.mjs'; +import DHItem from '../../../documents/item.mjs'; export default class Party extends DHBaseActorSheet { constructor(options) { @@ -268,6 +269,15 @@ export default class Party extends DHBaseActorSheet { ).render({ force: true }); } + /** + * Get the set of ContextMenu options for Consumable and Loot. + * @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance + * @this {CharacterSheet} + * @protected + */ + static #getItemContextOptions() { + return this._getContextMenuCommonOptions.call(this, { usable: true, toChat: true }); + } /* -------------------------------------------- */ /* Filter Tracking */ /* -------------------------------------------- */ diff --git a/module/applications/sheets/api/base-actor.mjs b/module/applications/sheets/api/base-actor.mjs index 6f994faf..85ecd616 100644 --- a/module/applications/sheets/api/base-actor.mjs +++ b/module/applications/sheets/api/base-actor.mjs @@ -36,7 +36,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { ], dragDrop: [ { dragSelector: '.inventory-item[data-type="attack"]', dropSelector: null }, - { dragSelector: '.currency[data-currency] .drag-handle', dropSelector: null } + { dragSelector: ".currency[data-currency] .drag-handle", dropSelector: null } ] }; @@ -92,7 +92,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { value: context.source.system.gold[key] }; } - context.inventory.hasCurrency = Object.values(context.inventory.currencies).some(c => c.enabled); + context.inventory.hasCurrency = Object.values(context.inventory.currencies).some((c) => c.enabled); } return context; @@ -270,9 +270,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { currency }); if (quantity) { - originActor.update({ - [`system.gold.${currency}`]: Math.max(0, originActor.system.gold[currency] - quantity) - }); + originActor.update({ [`system.gold.${currency}`]: Math.max(0, originActor.system.gold[currency] - quantity) }); this.document.update({ [`system.gold.${currency}`]: this.document.system.gold[currency] + quantity }); } return; @@ -294,15 +292,6 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { /* Handling transfer of inventoryItems */ if (item.system.metadata.isInventoryItem) { - if (!this.document.testUserPermission(game.user, 'OWNER', { exact: true })) { - return ui.notifications.error( - game.i18n.format('DAGGERHEART.UI.Notifications.lackingItemTransferPermission', { - user: game.user.name, - target: this.document.name - }) - ); - } - if (item.system.metadata.isQuantifiable) { const actorItem = originActor.items.get(data.originId); const quantityTransfered = await game.system.api.applications.dialogs.ItemTransferDialog.configure({ @@ -311,6 +300,14 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { }); if (quantityTransfered) { + if (quantityTransfered === actorItem.system.quantity) { + await originActor.deleteEmbeddedDocuments('Item', [data.originId]); + } else { + await actorItem.update({ + 'system.quantity': actorItem.system.quantity - quantityTransfered + }); + } + const existingItem = this.document.items.find(x => itemIsIdentical(x, item)); if (existingItem) { await existingItem.update({ @@ -328,18 +325,10 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { } ]); } - - if (quantityTransfered === actorItem.system.quantity) { - await originActor.deleteEmbeddedDocuments('Item', [data.originId]); - } else { - await actorItem.update({ - 'system.quantity': actorItem.system.quantity - quantityTransfered - }); - } } } else { - await this.document.createEmbeddedDocuments('Item', [item.toObject()]); await originActor.deleteEmbeddedDocuments('Item', [data.originId]); + await this.document.createEmbeddedDocuments('Item', [item.toObject()]); } } } @@ -350,7 +339,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { */ async _onDragStart(event) { // Handle drag/dropping currencies - const currencyEl = event.currentTarget.closest('.currency[data-currency]'); + const currencyEl = event.currentTarget.closest(".currency[data-currency]"); if (currencyEl) { const currency = currencyEl.dataset.currency; const data = { type: 'Currency', currency, originActor: this.document.uuid }; @@ -370,8 +359,8 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { event.dataTransfer.setData('text/plain', JSON.stringify(attackData)); event.dataTransfer.setDragImage(attackItem.querySelector('img'), 60, 0); return; - } - + } + const item = await getDocFromElement(event.target); if (item) { const dragData = { diff --git a/module/applications/sidebar/tabs/daggerheartMenu.mjs b/module/applications/sidebar/tabs/daggerheartMenu.mjs index 26ae484b..a16a1490 100644 --- a/module/applications/sidebar/tabs/daggerheartMenu.mjs +++ b/module/applications/sidebar/tabs/daggerheartMenu.mjs @@ -1,4 +1,4 @@ -import { RefreshFeatures } from '../../../helpers/utils.mjs'; +import { expireActiveEffects, refreshIsAllowed } from '../../../helpers/utils.mjs'; const { HandlebarsApplicationMixin } = foundry.applications.api; const { AbstractSidebarTab } = foundry.applications.sidebar; @@ -54,6 +54,75 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract return context; } + async getRefreshables(types) { + const refreshedActors = {}; + for (let actor of game.actors) { + if (['character', 'adversary'].includes(actor.type) && actor.prototypeToken.actorLink) { + expireActiveEffects(actor, types); + + const updates = {}; + for (let item of actor.items) { + if (item.system.metadata?.hasResource && refreshIsAllowed(types, item.system.resource?.recovery)) { + if (!refreshedActors[actor.id]) + refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() }; + refreshedActors[actor.id].refreshed.add( + game.i18n.localize(CONFIG.DH.GENERAL.refreshTypes[item.system.resource.recovery].label) + ); + + if (!updates[item.id]?.system) updates[item.id] = { system: {} }; + + const increasing = + item.system.resource.progression === CONFIG.DH.ITEM.itemResourceProgression.increasing.id; + updates[item.id].system = { + ...updates[item.id].system, + 'resource.value': increasing + ? 0 + : Roll.replaceFormulaData(item.system.resource.max, actor.getRollData()) + }; + } + if (item.system.metadata?.hasActions) { + const refreshTypes = new Set(); + const actions = item.system.actions.filter(action => { + if (refreshIsAllowed(types, action.uses.recovery)) { + refreshTypes.add(action.uses.recovery); + return true; + } + + return false; + }); + if (actions.length === 0) continue; + + if (!refreshedActors[actor.id]) + refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() }; + refreshedActors[actor.id].refreshed.add( + ...refreshTypes.map(type => game.i18n.localize(CONFIG.DH.GENERAL.refreshTypes[type].label)) + ); + + if (!updates[item.id]?.system) updates[item.id] = { system: {} }; + + updates[item.id].system = { + ...updates[item.id].system, + ...actions.reduce( + (acc, action) => { + acc.actions[action.id] = { 'uses.value': 0 }; + return acc; + }, + { actions: updates[item.id].system.actions ?? {} } + ) + }; + } + } + + for (let key in updates) { + const update = updates[key]; + await actor.items.get(key).update(update); + } + } + } + + return refreshedActors; + } + /* -------------------------------------------- */ /* Application Clicks Actions */ /* -------------------------------------------- */ @@ -66,9 +135,30 @@ export default class DaggerheartMenu extends HandlebarsApplicationMixin(Abstract static async #refreshActors() { const refreshKeys = Object.keys(this.refreshSelections).filter(key => this.refreshSelections[key].selected); - await RefreshFeatures(refreshKeys); - + await this.getRefreshables(refreshKeys); + const types = refreshKeys.map(x => this.refreshSelections[x].label).join(', '); + ui.notifications.info( + game.i18n.format('DAGGERHEART.UI.Notifications.gmMenuRefresh', { + types: `[${types}]` + }) + ); this.refreshSelections = DaggerheartMenu.defaultRefreshSelections(); + + const cls = getDocumentClass('ChatMessage'); + const msg = { + user: game.user.id, + content: await foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/ui/chat/refreshMessage.hbs', + { + types: types + } + ), + title: game.i18n.localize('DAGGERHEART.UI.Chat.refreshMessage.title'), + speaker: cls.getSpeaker() + }; + + cls.create(msg); + this.render(); } } diff --git a/module/config/generalConfig.mjs b/module/config/generalConfig.mjs index da7b96ac..872c956e 100644 --- a/module/config/generalConfig.mjs +++ b/module/config/generalConfig.mjs @@ -206,8 +206,7 @@ export const conditions = () => ({ id: 'vulnerable', name: 'DAGGERHEART.CONFIG.Condition.vulnerable.name', img: 'icons/magic/control/silhouette-fall-slip-prone.webp', - description: 'DAGGERHEART.CONFIG.Condition.vulnerable.description', - autoApplyFlagId: 'auto-vulnerable' + description: 'DAGGERHEART.CONFIG.Condition.vulnerable.description' }, hidden: { id: 'hidden', diff --git a/module/config/itemConfig.mjs b/module/config/itemConfig.mjs index 77328987..7d80e597 100644 --- a/module/config/itemConfig.mjs +++ b/module/config/itemConfig.mjs @@ -467,7 +467,9 @@ export const allArmorFeatures = () => { }; export const orderedArmorFeatures = () => { - const allFeatures = allArmorFeatures(); + const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures + .armorFeatures; + const allFeatures = { ...armorFeatures, ...homebrewFeatures }; const all = Object.keys(allFeatures).map(key => { const feature = allFeatures[key]; return { @@ -1402,7 +1404,9 @@ export const allWeaponFeatures = () => { }; export const orderedWeaponFeatures = () => { - const allFeatures = allWeaponFeatures(); + const homebrewFeatures = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).itemFeatures + .weaponFeatures; + const allFeatures = { ...weaponFeatures, ...homebrewFeatures }; const all = Object.keys(allFeatures).map(key => { const feature = allFeatures[key]; return { diff --git a/module/data/actor/creature.mjs b/module/data/actor/creature.mjs index c8bf8448..4b927aed 100644 --- a/module/data/actor/creature.mjs +++ b/module/data/actor/creature.mjs @@ -17,45 +17,4 @@ export default class DhCreature extends BaseDataActor { }) }; } - - get isAutoVulnerableActive() { - const vulnerableAppliedByOther = this.parent.effects.some( - x => x.statuses.has('vulnerable') && !x.flags.daggerheart?.autoApplyFlagId - ); - return !vulnerableAppliedByOther; - } - - async _preUpdate(changes, options, userId) { - const allowed = await super._preUpdate(changes, options, userId); - if (allowed === false) return; - - const automationSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); - if ( - automationSettings.vulnerableAutomation && - this.parent.type !== 'companion' && - changes.system?.resources?.stress?.value - ) { - const { name, description, img, autoApplyFlagId } = CONFIG.DH.GENERAL.conditions().vulnerable; - const autoEffects = this.parent.effects.filter( - x => x.flags.daggerheart?.autoApplyFlagId === autoApplyFlagId - ); - if (changes.system.resources.stress.value >= this.resources.stress.max) { - if (!autoEffects.length) - this.parent.createEmbeddedDocuments('ActiveEffect', [ - { - name: game.i18n.localize(name), - description: game.i18n.localize(description), - img: img, - statuses: ['vulnerable'], - flags: { daggerheart: { autoApplyFlagId } } - } - ]); - } else if (this.resources.stress.value >= this.resources.stress.max) { - this.parent.deleteEmbeddedDocuments( - 'ActiveEffect', - autoEffects.map(x => x.id) - ); - } - } - } } diff --git a/module/data/item/armor.mjs b/module/data/item/armor.mjs index 264d7f93..f6b67ff2 100644 --- a/module/data/item/armor.mjs +++ b/module/data/item/armor.mjs @@ -23,7 +23,9 @@ export default class DHArmor extends AttachableItem { armorFeatures: new fields.ArrayField( new fields.SchemaField({ value: new fields.StringField({ - required: true + required: true, + choices: CONFIG.DH.ITEM.allArmorFeatures, + blank: true }), effectIds: new fields.ArrayField(new fields.StringField({ required: true })), actionIds: new fields.ArrayField(new fields.StringField({ required: true })) @@ -56,7 +58,7 @@ export default class DHArmor extends AttachableItem { async getDescriptionData() { const baseDescription = this.description; const allFeatures = CONFIG.DH.ITEM.allArmorFeatures(); - const features = this.armorFeatures.map(x => allFeatures[x.value]).filter(x => x); + const features = this.armorFeatures.map(x => allFeatures[x.value]); const prefix = await foundry.applications.handlebars.renderTemplate( 'systems/daggerheart/templates/sheets/items/armor/description.hbs', diff --git a/module/data/item/weapon.mjs b/module/data/item/weapon.mjs index bb2e10d5..389dc1c5 100644 --- a/module/data/item/weapon.mjs +++ b/module/data/item/weapon.mjs @@ -38,7 +38,9 @@ export default class DHWeapon extends AttachableItem { weaponFeatures: new fields.ArrayField( new fields.SchemaField({ value: new fields.StringField({ - required: true + required: true, + choices: CONFIG.DH.ITEM.allWeaponFeatures, + blank: true }), effectIds: new fields.ArrayField(new fields.StringField({ required: true })), actionIds: new fields.ArrayField(new fields.StringField({ required: true })) @@ -119,7 +121,7 @@ export default class DHWeapon extends AttachableItem { const burden = game.i18n.localize(CONFIG.DH.GENERAL.burden[this.burden].label); const allFeatures = CONFIG.DH.ITEM.allWeaponFeatures(); - const features = this.weaponFeatures.map(x => allFeatures[x.value]).filter(x => x); + const features = this.weaponFeatures.map(x => allFeatures[x.value]); const prefix = await foundry.applications.handlebars.renderTemplate( 'systems/daggerheart/templates/sheets/items/weapon/description.hbs', diff --git a/module/data/registeredTriggers.mjs b/module/data/registeredTriggers.mjs index ab86351c..ee4f3b49 100644 --- a/module/data/registeredTriggers.mjs +++ b/module/data/registeredTriggers.mjs @@ -75,7 +75,7 @@ export default class RegisteredTriggers extends Map { unregisterSceneEnvironmentTriggers(flagSystemData) { const sceneData = new game.system.api.data.scenes.DHScene(flagSystemData); for (const environment of sceneData.sceneEnvironments) { - if (!environment || environment.pack) continue; + if (environment.pack) continue; this.unregisterItemTriggers(environment.system.features); } } diff --git a/module/data/settings/Automation.mjs b/module/data/settings/Automation.mjs index 35e87327..edc4eb28 100644 --- a/module/data/settings/Automation.mjs +++ b/module/data/settings/Automation.mjs @@ -18,10 +18,6 @@ export default class DhAutomation extends foundry.abstract.DataModel { label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hopeFear.players.label' }) }), - vulnerableAutomation: new fields.BooleanField({ - initial: true, - label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.vulnerableAutomation.label' - }), countdownAutomation: new fields.BooleanField({ required: true, initial: true, diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs index b322aae7..e1acaf40 100644 --- a/module/dice/damageRoll.mjs +++ b/module/dice/damageRoll.mjs @@ -1,5 +1,4 @@ import DamageDialog from '../applications/dialogs/damageDialog.mjs'; -import { parseRallyDice } from '../helpers/utils.mjs'; import { RefreshType, socketEvent } from '../systemRegistration/socket.mjs'; import DHRoll from './dhRoll.mjs'; @@ -34,7 +33,7 @@ export default class DamageRoll extends DHRoll { static async buildPost(roll, config, message) { const chatMessage = config.source?.message ? ui.chat.collection.get(config.source.message) - : getDocumentClass('ChatMessage').applyRollMode({}, config.rollMode ?? CONST.DICE_ROLL_MODES.PUBLIC); + : getDocumentClass('ChatMessage').applyRollMode({}, config.rollMode); if (game.modules.get('dice-so-nice')?.active) { const pool = foundry.dice.terms.PoolTerm.fromRolls( Object.values(config.damage).flatMap(r => r.parts.map(p => p.roll)) @@ -47,14 +46,9 @@ export default class DamageRoll extends DHRoll { chatMessage.whisper?.length > 0 ? chatMessage.whisper : null, chatMessage.blind ); - config.mute = true; } await super.buildPost(roll, config, message); - if (config.source?.message) { - chatMessage.update({ 'system.damage': config.damage }); - - if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice }); - } + if (config.source?.message) chatMessage.update({ 'system.damage': config.damage }); } static unifyDamageRoll(rolls) { @@ -198,7 +192,7 @@ export default class DamageRoll extends DHRoll { // Bardic Rally const rallyChoices = config.data?.parent?.appliedEffects.reduce((a, c) => { const change = c.system.changes.find(ch => ch.key === 'system.bonuses.rally'); - if (change) a.push({ value: c.id, label: parseRallyDice(change.value, c) }); + if (change) a.push({ value: c.id, label: change.value }); return a; }, []); if (rallyChoices.length) { diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index 01452512..83b5559f 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -1,6 +1,6 @@ import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs'; import D20Roll from './d20Roll.mjs'; -import { parseRallyDice, setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs'; +import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs'; import { getDiceSoNicePresets } from '../config/generalConfig.mjs'; import { ResourceUpdateMap } from '../data/action/baseAction.mjs'; @@ -68,7 +68,7 @@ export default class DualityRoll extends D20Roll { setRallyChoices() { return this.data?.parent?.appliedEffects.reduce((a, c) => { const change = c.system.changes.find(ch => ch.key === 'system.bonuses.rally'); - if (change) a.push({ value: c.id, label: parseRallyDice(change.value, c) }); + if (change) a.push({ value: c.id, label: change.value }); return a; }, []); } diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index f0538bff..cb51a255 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -934,23 +934,10 @@ export default class DhpActor extends Actor { /** Get active effects */ getActiveEffects() { - const conditions = CONFIG.DH.GENERAL.conditions(); const statusMap = new Map(foundry.CONFIG.statusEffects.map(status => [status.id, status])); - const autoVulnerableActive = this.system.isAutoVulnerableActive; return this.effects .filter(x => !x.disabled) .reduce((acc, effect) => { - /* Could be generalized if needed. Currently just related to Vulnerable */ - const isAutoVulnerableEffect = - effect.flags.daggerheart?.autoApplyFlagId === conditions.vulnerable.autoApplyFlagId; - if (isAutoVulnerableEffect) { - if (!autoVulnerableActive) return acc; - - effect.appliedBy = game.i18n.localize('DAGGERHEART.CONFIG.Condition.vulnerable.autoAppliedByLabel'); - effect.isLockedCondition = true; - effect.condition = 'vulnerable'; - } - acc.push(effect); const currentStatusActiveEffects = acc.filter( diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs index 0043f183..f9075b8e 100644 --- a/module/helpers/utils.mjs +++ b/module/helpers/utils.mjs @@ -119,8 +119,8 @@ export const tagifyElement = (element, baseOptions, onChange, tagifyOptions = {} }), maxTags: typeof maxTags === 'function' ? maxTags() : maxTags, dropdown: { - searchKeys: ['value', 'name'], mapValueTo: 'name', + searchKeys: ['value'], enabled: 0, maxItems: 100, closeOnSelect: true, @@ -479,7 +479,7 @@ export function refreshIsAllowed(allowedTypes, typeToCheck) { case CONFIG.DH.GENERAL.refreshTypes.scene.id: case CONFIG.DH.GENERAL.refreshTypes.session.id: case CONFIG.DH.GENERAL.refreshTypes.longRest.id: - return allowedTypes.includes?.(typeToCheck) ?? allowedTypes.has(typeToCheck); + return allowedTypes.includes(typeToCheck); case CONFIG.DH.GENERAL.refreshTypes.shortRest.id: return allowedTypes.some( x => @@ -624,123 +624,3 @@ export function compareValues(valA, valB, comparator) { return valA <= valB; } } - -export function parseRallyDice(value, effect) { - const legacyStartsWithPrefix = value.toLowerCase().startsWith('d'); - const workingValue = legacyStartsWithPrefix ? value.slice(1) : value; - const dataParsedValue = itemAbleRollParse(workingValue, effect.parent); - - return `d${game.system.api.documents.DhActiveEffect.effectSafeEval(dataParsedValue)}`; -} -/** - * Refreshes character and/or adversary resources. - * @param { string[] } refreshTypes Which type of features to refresh using IDs from CONFIG.DH.GENERAL.refreshTypes - * @param { string[] = ['character', 'adversary'] } actorTypes Which actor types should refresh their features. Defaults to character and adversary. - * @param { boolean = true } sendRefreshMessage If a chat message should be created detailing the refresh - * @return { Actor[] } The actors that had their features refreshed - */ -export async function RefreshFeatures( - refreshTypes = [], - actorTypes = ['character', 'adversary'], - sendNotificationMessage = true, - sendRefreshMessage = true -) { - const refreshedActors = {}; - for (let actor of game.actors) { - if (actorTypes.includes(actor.type) && actor.prototypeToken.actorLink) { - expireActiveEffects(actor, refreshTypes); - - const updates = {}; - for (let item of actor.items) { - if ( - item.system.metadata?.hasResource && - refreshIsAllowed(refreshTypes, item.system.resource?.recovery) - ) { - if (!refreshedActors[actor.id]) - refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() }; - refreshedActors[actor.id].refreshed.add( - game.i18n.localize(CONFIG.DH.GENERAL.refreshTypes[item.system.resource.recovery].label) - ); - - if (!updates[item.id]?.system) updates[item.id] = { system: {} }; - - const increasing = - item.system.resource.progression === CONFIG.DH.ITEM.itemResourceProgression.increasing.id; - updates[item.id].system = { - ...updates[item.id].system, - 'resource.value': increasing - ? 0 - : game.system.api.documents.DhActiveEffect.effectSafeEval( - Roll.replaceFormulaData(item.system.resource.max, actor.getRollData()) - ) - }; - } - if (item.system.metadata?.hasActions) { - const usedTypes = new Set(); - const actions = item.system.actions.filter(action => { - if (refreshIsAllowed(refreshTypes, action.uses.recovery)) { - usedTypes.add(action.uses.recovery); - return true; - } - - return false; - }); - if (actions.length === 0) continue; - - if (!refreshedActors[actor.id]) - refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() }; - refreshedActors[actor.id].refreshed.add( - ...usedTypes.map(type => game.i18n.localize(CONFIG.DH.GENERAL.refreshTypes[type].label)) - ); - - if (!updates[item.id]?.system) updates[item.id] = { system: {} }; - - updates[item.id].system = { - ...updates[item.id].system, - ...actions.reduce( - (acc, action) => { - acc.actions[action.id] = { 'uses.value': 0 }; - return acc; - }, - { actions: updates[item.id].system.actions ?? {} } - ) - }; - } - } - - for (let key in updates) { - const update = updates[key]; - await actor.items.get(key).update(update); - } - } - } - - const types = refreshTypes.map(x => game.i18n.localize(CONFIG.DH.GENERAL.refreshTypes[x].label)).join(', '); - - if (sendNotificationMessage) { - ui.notifications.info( - game.i18n.format('DAGGERHEART.UI.Notifications.gmMenuRefresh', { - types: `[${types}]` - }) - ); - } - - if (sendRefreshMessage) { - const cls = getDocumentClass('ChatMessage'); - const msg = { - user: game.user.id, - content: await foundry.applications.handlebars.renderTemplate( - 'systems/daggerheart/templates/ui/chat/refreshMessage.hbs', - { - types: types - } - ), - title: game.i18n.localize('DAGGERHEART.UI.Chat.refreshMessage.title'), - speaker: cls.getSpeaker() - }; - - cls.create(msg); - } - - return refreshedActors; -} diff --git a/src/packs/classes/class_Bard_vegl3bFOq3pcFTWT.json b/src/packs/classes/class_Bard_vegl3bFOq3pcFTWT.json index c4dd83a7..b7830722 100644 --- a/src/packs/classes/class_Bard_vegl3bFOq3pcFTWT.json +++ b/src/packs/classes/class_Bard_vegl3bFOq3pcFTWT.json @@ -20,6 +20,10 @@ { "type": "class", "item": "Compendium.daggerheart.classes.Item.PydiMnNCKpd44SGS" + }, + { + "type": "class", + "item": "Compendium.daggerheart.classes.Item.TVeEyqmPPiRa2r3i" } ], "subclasses": [ diff --git a/src/packs/classes/feature_Rally_PydiMnNCKpd44SGS.json b/src/packs/classes/feature_Rally_PydiMnNCKpd44SGS.json index e2a0b5bb..e8d4c3c9 100644 --- a/src/packs/classes/feature_Rally_PydiMnNCKpd44SGS.json +++ b/src/packs/classes/feature_Rally_PydiMnNCKpd44SGS.json @@ -63,7 +63,7 @@ { "key": "system.bonuses.rally", "mode": 2, - "value": "6 + min((floor(@system.levelData.level.current / 5)*2), 2)", + "value": "d6", "priority": null } ], diff --git a/src/packs/classes/feature_Rally__Level_5__TVeEyqmPPiRa2r3i.json b/src/packs/classes/feature_Rally__Level_5__TVeEyqmPPiRa2r3i.json new file mode 100644 index 00000000..46717fcb --- /dev/null +++ b/src/packs/classes/feature_Rally__Level_5__TVeEyqmPPiRa2r3i.json @@ -0,0 +1,99 @@ +{ + "folder": "C9y59fIkq50d3SyD", + "name": "Rally (Level 5)", + "type": "feature", + "img": "icons/tools/instruments/drum-hand-tan.webp", + "system": { + "description": "

Once per session, describe how you rally the party and give yourself and each of your allies a Rally Die. At level 1, your Rally Die is a d6. A PC can spend their Rally Die to roll it, adding the result to their action roll, reaction roll, damage roll, or to clear a number of Stress equal to the result. At the end of each session, clear all unspent Rally Dice. At level 5, your Rally Die increases to a d8.

", + "resource": null, + "actions": { + "Z1KWFrpXOqZWuZD1": { + "type": "effect", + "_id": "Z1KWFrpXOqZWuZD1", + "systemPath": "actions", + "description": "", + "chatDisplay": true, + "actionType": "action", + "cost": [], + "uses": { + "value": null, + "max": "1", + "recovery": "session" + }, + "effects": [ + { + "_id": "8CFxYJV8zE6Wabwj", + "onSave": false + } + ], + "target": { + "type": "any", + "amount": null + }, + "name": "Rally your Allies", + "img": "icons/tools/instruments/drum-hand-tan.webp", + "range": "" + } + }, + "originItemType": null, + "originId": null, + "attribution": { + "source": "Daggerheart SRD", + "page": 9, + "artist": "" + } + }, + "effects": [ + { + "name": "Rally (Level 5)", + "img": "icons/tools/instruments/drum-hand-tan.webp", + "origin": "Compendium.daggerheart.classes.Item.oxv0m8AFUQVFKtZ4", + "transfer": false, + "_id": "8CFxYJV8zE6Wabwj", + "type": "base", + "system": { + "rangeDependence": { + "enabled": false, + "type": "withinRange", + "target": "hostile", + "range": "melee" + } + }, + "changes": [ + { + "key": "system.bonuses.rally", + "mode": 2, + "value": "d8", + "priority": null + } + ], + "disabled": false, + "duration": { + "startTime": null, + "combat": null, + "seconds": null, + "rounds": null, + "turns": null, + "startRound": null, + "startTurn": null + }, + "description": "", + "tint": "#ffffff", + "statuses": [], + "sort": 0, + "flags": {}, + "_stats": { + "compendiumSource": null + }, + "_key": "!items.effects!TVeEyqmPPiRa2r3i.8CFxYJV8zE6Wabwj" + } + ], + "flags": {}, + "ownership": { + "default": 0, + "LgnbNMLaxandgMQq": 3 + }, + "_id": "TVeEyqmPPiRa2r3i", + "sort": 300000, + "_key": "!items!TVeEyqmPPiRa2r3i" +} diff --git a/src/packs/classes/feature_Strange_Patterns_6YsfFjmCGuFYVhT4.json b/src/packs/classes/feature_Strange_Patterns_6YsfFjmCGuFYVhT4.json index 953b3a2c..95f42c06 100644 --- a/src/packs/classes/feature_Strange_Patterns_6YsfFjmCGuFYVhT4.json +++ b/src/packs/classes/feature_Strange_Patterns_6YsfFjmCGuFYVhT4.json @@ -85,7 +85,7 @@ { "trigger": "dualityRoll", "triggeringActorType": "self", - "command": "/* Ignore if it's a TagTeam roll */\nconst tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);\nif (tagTeam.members[actor.id]) return;\n\n/* Check if there's a Strange Pattern match */\nconst dice = [roll.dFear.total, roll.dHope.total];\nconst resource = this.parent.resource?.diceStates ? Object.values(this.parent.resource.diceStates).map(x => x.value)[0] : null;\nconst nrMatches = dice.filter(x => x === resource).length;\n\nif (!nrMatches) return;\n\n/* Create a dialog to choose Hope or Stress - or to cancel*/\nconst content = `\n
${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentTitle', { nr: nrMatches })}
\n
${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentSubTitle', { nr: nrMatches })}
\n
${game.i18n.localize('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsActionExplanation')}
\n
\n \n \n
\n
`;\n\nconst result = await foundry.applications.api.DialogV2.input({\n classes: ['dh-style', 'two-big-buttons'],\n window: { title: this.item.name },\n content: content,\n render: (_, dialog) => {\n const hopeButton = dialog.element.querySelector('#hopeButton');\n const stressButton = dialog.element.querySelector('#stressButton');\ndialog.element.querySelector('button[type=\"submit\"]').disabled = true;\n \n const updateFunc = (event, selector, adding, clamp) => {\n const button = event.target.closest(`#${selector}Button`);\n const parent = event.target.closest('.flexrow');\n const hope = Number.parseInt(parent.querySelector('#hopeButton label').innerHTML);\n const stress = Number.parseInt(parent.querySelector('#stressButton label').innerHTML);\n const currentTotal = (Number.isNumeric(hope) ? hope : 0) + (Number.isNumeric(stress) ? stress : 0);\n if (adding && currentTotal === nrMatches) return;\n \n const current = Number.parseInt(button.querySelector('label').innerHTML);\n if (!adding && current === 0) return;\n \n const value = Number.isNumeric(current) ? adding ? current+1 : current-1 : 1;\n if (!dialog.data) dialog.data = {};\n dialog.data[selector] = clamp(value);\n button.querySelector('label').innerHTML = dialog.data[selector];\n\n event.target.closest('.dialog-form').querySelector('button[type=\"submit\"]').disabled = !adding || currentTotal < (nrMatches-1);\n \n };\n hopeButton.addEventListener('click', event => updateFunc(event, 'hope', true, x => Math.min(x, nrMatches)));\n hopeButton.addEventListener('contextmenu', event => updateFunc(event, 'hope', false, x => Math.max(x, 0)));\n stressButton.addEventListener('click', event => updateFunc(event, 'stress', true, x => Math.min(x, nrMatches)));\n stressButton.addEventListener('contextmenu', event => updateFunc(event, 'stress', false, x => Math.max(x, 0)));\n },\n ok: { callback: (_event, _result, dialog) => {\n const hope = dialog.data.hope ?? 0;\n const stress = dialog.data.stress ?? 0;\n if (!hope && !stress) return;\n\n /* Return resource update according to choices */\n const hopeUpdate = hope ? { key: 'hope', value: hope, total: -hope, enabled: true } : null;\n const stressUpdate = stress ? { key: 'stress', value: -stress, total: stress, enabled: true } : null;\n return { updates: [hopeUpdate, stressUpdate].filter(x => x) };\n }}\n});\n\nreturn result;" + "command": "/* Ignore if it's a TagTeam roll */\nconst tagTeam = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);\nif (tagTeam.members[actor.id]) return;\n\n/* Check if there's a Strange Pattern match */\nconst dice = [roll.dFear.total, roll.dHope.total];\nconst resource = this.parent.resource?.diceStates ? Object.values(this.parent.resource.diceStates).map(x => x.value)[0] : null;\nconst nrMatches = dice.filter(x => x === resource).length;\n\nif (!nrMatches) return;\n\n/* Create a dialog to choose Hope or Stress - or to cancel*/\nconst content = `\n
${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentTitle', { nr: nrMatches })}
\n
${game.i18n.format('DAGGERHEART.CONFIG.Triggers.triggerTexts.strangePatternsContentSubTitle', { nr: nrMatches })}
\n
\n \n \n
\n
`;\n\nconst result = await foundry.applications.api.DialogV2.input({\n classes: ['dh-style', 'two-big-buttons'],\n window: { title: this.item.name },\n content: content,\n render: (_, dialog) => {\n const hopeButton = dialog.element.querySelector('#hopeButton');\n const stressButton = dialog.element.querySelector('#stressButton');\ndialog.element.querySelector('button[type=\"submit\"]').disabled = true;\n \n const updateFunc = (event, selector, adding, clamp) => {\n const button = event.target.closest(`#${selector}Button`);\n const parent = event.target.closest('.flexrow');\n const hope = Number.parseInt(parent.querySelector('#hopeButton label').innerHTML);\n const stress = Number.parseInt(parent.querySelector('#stressButton label').innerHTML);\n const currentTotal = (Number.isNumeric(hope) ? hope : 0) + (Number.isNumeric(stress) ? stress : 0);\n if (adding && currentTotal === nrMatches) return;\n \n const current = Number.parseInt(button.querySelector('label').innerHTML);\n if (!adding && current === 0) return;\n \n const value = Number.isNumeric(current) ? adding ? current+1 : current-1 : 1;\n if (!dialog.data) dialog.data = {};\n dialog.data[selector] = clamp(value);\n button.querySelector('label').innerHTML = dialog.data[selector];\n\n event.target.closest('.dialog-form').querySelector('button[type=\"submit\"]').disabled = !adding || currentTotal < (nrMatches-1);\n \n };\n hopeButton.addEventListener('click', event => updateFunc(event, 'hope', true, x => Math.min(x, nrMatches)));\n hopeButton.addEventListener('contextmenu', event => updateFunc(event, 'hope', false, x => Math.max(x, 0)));\n stressButton.addEventListener('click', event => updateFunc(event, 'stress', true, x => Math.min(x, nrMatches)));\n stressButton.addEventListener('contextmenu', event => updateFunc(event, 'stress', false, x => Math.max(x, 0)));\n },\n ok: { callback: (_event, _result, dialog) => {\n const hope = dialog.data.hope ?? 0;\n const stress = dialog.data.stress ?? 0;\n if (!hope && !stress) return;\n\n /* Return resource update according to choices */\n const hopeUpdate = hope ? { key: 'hope', value: hope, total: -hope, enabled: true } : null;\n const stressUpdate = stress ? { key: 'stress', value: -stress, total: stress, enabled: true } : null;\n return { updates: [hopeUpdate, stressUpdate].filter(x => x) };\n }}\n});\n\nreturn result;" } ] } diff --git a/src/packs/domains/domainCard_Wrangle_9DwSxHoUwl8Kxj3n.json b/src/packs/domains/domainCard_Wrangle_9DwSxHoUwl8Kxj3n.json index 16753e1e..8cdb62b0 100644 --- a/src/packs/domains/domainCard_Wrangle_9DwSxHoUwl8Kxj3n.json +++ b/src/packs/domains/domainCard_Wrangle_9DwSxHoUwl8Kxj3n.json @@ -53,7 +53,7 @@ "difficulty": null, "damageMod": "none" }, - "name": "Agility Roll", + "name": "Agility Check", "img": "icons/skills/melee/sword-engraved-glow-purple.webp", "range": "close" } diff --git a/styles/less/global/inventory-item.less b/styles/less/global/inventory-item.less index d703d189..9045baf5 100644 --- a/styles/less/global/inventory-item.less +++ b/styles/less/global/inventory-item.less @@ -103,9 +103,10 @@ display: flex; align-items: center; justify-content: end; + gap: 8px; a { - width: 20px; + width: 15px; text-align: center; } @@ -274,10 +275,8 @@ grid-area: controls; align-self: start; padding-top: 0.3125rem; + gap: 4px; margin-bottom: -1px; - a { - width: 18px; - } } > .item-labels { align-self: start; @@ -335,27 +334,6 @@ border-radius: 6px; } - .recall-cost { - position: absolute; - right: 4px; - top: 4px; - width: 1.75em; - height: 1.75em; - - align-items: center; - background: @dark-blue; - border-radius: 50%; - border: 1px solid @golden; - color: @golden; - display: flex; - justify-content: center; - padding-top: 0.1em; // compensate for font - - i { - font-size: 0.68em; - } - } - .card-label { display: flex; flex-direction: column; diff --git a/styles/less/sheets/actors/actor-sheet-shared.less b/styles/less/sheets/actors/actor-sheet-shared.less index 23db088a..bf6393f4 100644 --- a/styles/less/sheets/actors/actor-sheet-shared.less +++ b/styles/less/sheets/actors/actor-sheet-shared.less @@ -183,11 +183,6 @@ } } - .domain-details { - display: flex; - flex-direction: column; - } - .level-details { align-self: center; } diff --git a/styles/less/ui/chat/chat.less b/styles/less/ui/chat/chat.less index 3591fc65..1e723ed7 100644 --- a/styles/less/ui/chat/chat.less +++ b/styles/less/ui/chat/chat.less @@ -158,7 +158,7 @@ .daggerheart, #chat-notifications { .chat-message { - --text-color: light-dark(@dark-blue, @golden); + --text-color: @golden; --bg-color: @golden-40; [data-use-perm='false'] { @@ -233,7 +233,7 @@ font-family: @font-subtitle; font-size: var(--font-size-18); font-weight: bold; - color: var(--text-color); + color: light-dark(@dark-blue, var(--text-color)); margin-bottom: -2px; } diff --git a/templates/settings/automation-settings/general.hbs b/templates/settings/automation-settings/general.hbs index bbf73bd9..a10e50c6 100644 --- a/templates/settings/automation-settings/general.hbs +++ b/templates/settings/automation-settings/general.hbs @@ -14,7 +14,6 @@ {{formGroup settingFields.schema.fields.summaryMessages.fields.effects value=settingFields._source.summaryMessages.effects localize=true}} - {{formGroup settingFields.schema.fields.vulnerableAutomation value=settingFields._source.vulnerableAutomation localize=true}} {{formGroup settingFields.schema.fields.autoExpireActiveEffects value=settingFields._source.autoExpireActiveEffects localize=true}} {{formGroup settingFields.schema.fields.countdownAutomation value=settingFields._source.countdownAutomation localize=true}} {{formGroup settingFields.schema.fields.actionPoints value=settingFields._source.actionPoints localize=true}} diff --git a/templates/sheets/global/partials/domain-card-item.hbs b/templates/sheets/global/partials/domain-card-item.hbs index 54e44e64..ae95b7af 100644 --- a/templates/sheets/global/partials/domain-card-item.hbs +++ b/templates/sheets/global/partials/domain-card-item.hbs @@ -1,9 +1,5 @@
  • - - {{item.system.recallCost}} - -