diff --git a/lang/en.json b/lang/en.json index d03ce369..fd278e89 100755 --- a/lang/en.json +++ b/lang/en.json @@ -2259,11 +2259,6 @@ "abilityCheckTitle": "{ability} Check" }, "featureTitle": "Class Feature", - "foundationCard": { - "ancestryTitle": "Ancestry Card", - "communityTitle": "Community Card", - "subclassFeatureTitle": "Subclass Feature" - }, "healingRoll": { "title": "Heal - {damage}", "heal": "Heal", diff --git a/module/applications/dialogs/d20RollDialog.mjs b/module/applications/dialogs/d20RollDialog.mjs index 4b8749a3..a06708e6 100644 --- a/module/applications/dialogs/d20RollDialog.mjs +++ b/module/applications/dialogs/d20RollDialog.mjs @@ -45,6 +45,10 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio return this.config.title; } + get actor() { + return this.config?.data?.parent; + } + /** @override */ static PARTS = { header: { @@ -69,9 +73,10 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio icon })); + this.config.costs ??= []; if (this.config.costs?.length) { const updatedCosts = game.system.api.fields.ActionFields.CostField.calcCosts.call( - this.action, + this.action ?? { actor: this.actor }, this.config.costs ); context.costs = updatedCosts.map(x => ({ @@ -80,7 +85,10 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio ? this.action.parent.parent.name : game.i18n.localize(CONFIG.DH.GENERAL.abilityCosts[x.key].label) })); - context.canRoll = game.system.api.fields.ActionFields.CostField.hasCost.call(this.action, updatedCosts); + context.canRoll = game.system.api.fields.ActionFields.CostField.hasCost.call( + this.action ?? { actor: this.actor }, + updatedCosts + ); this.config.data.scale = this.config.costs[0].total; } if (this.config.uses?.max) { @@ -143,6 +151,12 @@ 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.render(); } diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs index 55662f90..e93de7ed 100644 --- a/module/applications/sheets/actors/character.mjs +++ b/module/applications/sheets/actors/character.mjs @@ -631,13 +631,34 @@ export default class CharacterSheet extends DHBaseActorSheet { }, hasRoll: true }; - this.document.diceRoll({ + const result = await this.document.diceRoll({ ...config, headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`, title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', { ability: abilityLabel }) }); + + setTimeout(() => { + this.consumeResource(result?.costs); + }, 50); + } + + async consumeResource(costs) { + if(!costs?.length) return; + const usefulResources = foundry.utils.deepClone(this.actor.system.resources); + 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); } //TODO: redo toggleEquipItem method diff --git a/module/applications/sheets/api/application-mixin.mjs b/module/applications/sheets/api/application-mixin.mjs index 2a02ba01..7f646460 100644 --- a/module/applications/sheets/api/application-mixin.mjs +++ b/module/applications/sheets/api/application-mixin.mjs @@ -51,9 +51,8 @@ import { ItemBrowser } from '../../ui/itemBrowser.mjs'; */ /** - * @template {Constructor} BaseDocumentSheet - * @param {BaseDocumentSheet} Base - The base class to extend. - * @returns {BaseDocumentSheet} + * @template {new (...args: any[]) => {}} T + * @arg Base {T} */ export default function DHApplicationMixin(Base) { class DHSheetV2 extends HandlebarsApplicationMixin(Base) { @@ -123,12 +122,13 @@ export default function DHApplicationMixin(Base) { super._attachPartListeners(partId, htmlElement, options); this._dragDrop.forEach(d => d.bind(htmlElement)); } + /**@inheritdoc */ async _onFirstRender(context, options) { await super._onFirstRender(context, options); const docs = []; - for (var docData of this.relatedDocs) { + for (const docData of this.relatedDocs) { const doc = await foundry.utils.fromUuid(docData.uuid); docs.push(doc); } @@ -247,6 +247,9 @@ export default function DHApplicationMixin(Base) { /* Context Menu */ /* -------------------------------------------- */ + /** + * Create all configured context menus for this application ins tance. + */ _createContextMenus() { for (const config of this.options.contextMenus) { const { handler, selector, options } = config; @@ -257,9 +260,9 @@ export default function DHApplicationMixin(Base) { /* -------------------------------------------- */ /** - * Get the set of ContextMenu options for DomainCards. + * Get the set of ContextMenu options for ActiveEffects. * @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance - * @this {CharacterSheet} + * @this {DHSheetV2} * @protected */ static #getEffectContextOptions() { @@ -305,8 +308,13 @@ export default function DHApplicationMixin(Base) { } /** - * Get the set of ContextMenu options. - * @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance + * Get the common ContextMenu options for an element. + * @param {Object} options + * @param {boolean} [options.usable=false] - Whether to include an option to use the item or apply damage. + * @param {boolean} [options.toChat=false] - Whether to include an option to send the item to chat. + * @param {boolean} [options.deletable=true] - Whether to include an option to delete the item. + * + * @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */ _getContextMenuCommonOptions({ usable = false, toChat = false, deletable = true }) { const options = [ @@ -333,6 +341,20 @@ export default function DHApplicationMixin(Base) { const doc = getDocFromElementSync(target); return doc?.system?.attack?.damage.parts.length || doc?.damage?.parts.length; }, + callback: async (target, event) => { + const doc = await getDocFromElement(target), + action = doc?.system?.attack ?? doc; + return action && action.use(event, { byPassRoll: true }) + } + }); + + options.unshift({ + name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem', + icon: 'fa-solid fa-burst', + condition: target => { + const doc = getDocFromElementSync(target); + return doc?.system?.attack?.damage.parts.length || doc?.damage?.parts.length; + }, callback: async (target, event) => { const doc = await getDocFromElement(target), action = doc?.system?.attack ?? doc; diff --git a/module/applications/sheets/api/base-actor.mjs b/module/applications/sheets/api/base-actor.mjs index b664929c..67cec44f 100644 --- a/module/applications/sheets/api/base-actor.mjs +++ b/module/applications/sheets/api/base-actor.mjs @@ -7,8 +7,6 @@ const { ActorSheetV2 } = foundry.applications.sheets; /** * A base actor sheet extending {@link ActorSheetV2} via {@link DHApplicationMixin} - * @extends ActorSheetV2 - * @mixes DHSheetV2 */ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { /** @inheritDoc */ @@ -106,7 +104,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) { /** * Get the set of ContextMenu options for Features. * @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance - * @this {DHSheetV2} + * @this {DHBaseActorSheet} * @protected */ static #getFeatureContextOptions() { diff --git a/module/applications/sheets/api/base-item.mjs b/module/applications/sheets/api/base-item.mjs index b5573a0c..6bd91ae8 100644 --- a/module/applications/sheets/api/base-item.mjs +++ b/module/applications/sheets/api/base-item.mjs @@ -7,8 +7,6 @@ const { ItemSheetV2 } = foundry.applications.sheets; /** * A base item sheet extending {@link ItemSheetV2} via {@link DHApplicationMixin} - * @extends ItemSheetV2 - * @mixes DHSheetV2 */ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { /** @inheritDoc */ @@ -108,7 +106,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) { /** * Get the set of ContextMenu options for Features. * @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance - * @this {DHSheetV2} + * @this {DHBaseItemSheet} * @protected */ static #getFeatureContextOptions() { diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs index fd9ab096..79d5e468 100644 --- a/module/applications/ui/chatLog.mjs +++ b/module/applications/ui/chatLog.mjs @@ -33,14 +33,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo html.querySelectorAll('.simple-roll-button').forEach(element => element.addEventListener('click', event => this.onRollSimple(event, data.message)) ); - html.querySelectorAll('.target-container').forEach(element => { - element.addEventListener('mouseenter', this.hoverTarget); - element.addEventListener('mouseleave', this.unhoverTarget); - element.addEventListener('click', this.clickTarget); - }); - html.querySelectorAll('.button-target-selection').forEach(element => { - element.addEventListener('click', event => this.onTargetSelection(event, data.message)); - }); html.querySelectorAll('.healing-button').forEach(element => element.addEventListener('click', event => this.onHealing(event, data.message)) ); @@ -138,33 +130,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo }); } - onTargetSelection(event, message) { - event.stopPropagation(); - const msg = ui.chat.collection.get(message._id); - msg.system.targetMode = Boolean(event.target.dataset.targetHit); - } - - hoverTarget(event) { - event.stopPropagation(); - const token = canvas.tokens.get(event.currentTarget.dataset.token); - if (!token?.controlled) token._onHoverIn(event, { hoverOutOthers: true }); - } - - unhoverTarget(event) { - const token = canvas.tokens.get(event.currentTarget.dataset.token); - if (!token?.controlled) token._onHoverOut(event); - } - - clickTarget(event) { - event.stopPropagation(); - const token = canvas.tokens.get(event.currentTarget.dataset.token); - if (!token) { - ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.attackTargetDoesNotExist')); - return; - } - game.canvas.pan(token); - } - async onRollSimple(event, message) { const buttonType = event.target.dataset.type ?? 'damage', total = message.rolls.reduce((a, c) => a + Roll.fromJSON(c).total, 0), diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index 070864da..954e5328 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -115,7 +115,6 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel 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++) { @@ -145,9 +144,9 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel 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 Roll(''); + const roll = new CONFIG.Dice.daggerheart.DHRoll(''); roll._evaluated = true; - if (this.hasTarget) config.targetSelection = config.targets.length > 0; + if(config.hasTarget) config.targetSelection = config.targets.length > 0; await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config); } } @@ -180,7 +179,6 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel hasHealing: this.damage?.parts?.length && this.type === 'healing', hasEffect: !!this.effects?.length, hasSave: this.hasSave, - hasTarget: true, selectedRollMode: game.settings.get('core', 'rollMode'), isFastForward: event.shiftKey, data: this.getRollData(), @@ -223,12 +221,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel } } - const resources = config.costs + const resources = game.system.api.fields.ActionFields.CostField.getRealCosts(config.costs) .filter( c => - c.enabled !== false && - ((!successCost && (!c.consumeOnSuccess || config.roll?.success)) || - (successCost && c.consumeOnSuccess)) + (!successCost && (!c.consumeOnSuccess || config.roll?.success)) || + (successCost && c.consumeOnSuccess) ) .map(c => { const resource = usefulResources[c.key]; @@ -240,7 +237,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel }; }); - await this.actor.modifyResource(resources); + await (this.actor.system.partner ?? this.actor).modifyResource(resources); if ( config.uses?.enabled && ((!successCost && (!config.uses?.consumeOnSuccess || config.roll?.success)) || @@ -248,8 +245,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel ) this.update({ 'uses.value': this.uses.value + 1 }); - if (config.roll?.success || successCost) - (config.message ?? config.parent).update({ 'system.successConsumed': true }); + if(config.roll?.success || successCost) { + setTimeout(() => { + (config.message ?? config.parent).update({'system.successConsumed': true}) + }, 50); + } } /* */ @@ -368,15 +368,15 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel async updateChatMessage(message, targetId, changes, chain = true) { setTimeout(async () => { - const chatMessage = ui.chat.collection.get(message._id), - msgTarget = - chatMessage.system.targets.find(mt => mt.id === targetId) ?? - chatMessage.system.oldTargets.find(mt => mt.id === targetId); - msgTarget.saved = changes; + const chatMessage = ui.chat.collection.get(message._id); + await chatMessage.update({ - system: { - targets: chatMessage.system.targets, - oldTargets: chatMessage.system.oldTargets + flags: { + [game.system.id]: { + "reactionRolls": { + [targetId]: changes + } + } } }); }, 100); diff --git a/module/data/chat-message/abilityUse.mjs b/module/data/chat-message/abilityUse.mjs index c431f14a..07209fe2 100644 --- a/module/data/chat-message/abilityUse.mjs +++ b/module/data/chat-message/abilityUse.mjs @@ -3,7 +3,6 @@ export default class DHAbilityUse extends foundry.abstract.TypeDataModel { const fields = foundry.data.fields; return { - title: new fields.StringField({}), origin: new fields.StringField({}), img: new fields.StringField({}), name: new fields.StringField({}), diff --git a/module/data/chat-message/adversaryRoll.mjs b/module/data/chat-message/adversaryRoll.mjs index fa6e48a6..5e1835c0 100644 --- a/module/data/chat-message/adversaryRoll.mjs +++ b/module/data/chat-message/adversaryRoll.mjs @@ -25,7 +25,6 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { title: new fields.StringField(), roll: new fields.ObjectField(), targets: targetsField(), - oldTargets: targetsField(), targetSelection: new fields.BooleanField({ initial: false }), hasRoll: new fields.BooleanField({ initial: false }), hasDamage: new fields.BooleanField({ initial: false }), @@ -63,24 +62,14 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { return actionItem.system.actionsList?.find(a => a.id === this.source.action); } - get messageTemplate() { - return 'systems/daggerheart/templates/ui/chat/roll.hbs'; - } - get targetMode() { return this.targetSelection; } set targetMode(mode) { this.targetSelection = mode; - this.updateTargets(); this.registerTargetHook(); - this.parent.update({ - system: { - targetSelection: this.targetSelection, - oldTargets: this.oldTargets - } - }); + this.updateTargets(); } get hitTargets() { @@ -88,29 +77,25 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { } async updateTargets() { - this.currentTargets = this.getTargetList(); - if (!this.targetSelection) { - this.currentTargets.forEach(ct => { - if (this.targets.find(t => t.actorId === ct.actorId)) return; - const indexTarget = this.oldTargets.findIndex(ot => ot.actorId === ct.actorId); - if (indexTarget === -1) this.oldTargets.push(ct); - }); - if (this.hasSave) this.setPendingSaves(); - if (this.currentTargets.length) { - if (!this.parent._id) return; - const updates = await this.parent.update({ - system: { - oldTargets: this.oldTargets - } - }); - if (!updates && ui.chat.collection.get(this.parent.id)) ui.chat.updateMessage(this.parent); + if(!ui.chat.collection.get(this.parent.id)) return; + let targets; + if(this.targetSelection) + targets = this.targets; + else + targets = Array.from(game.user.targets).map(t => game.system.api.fields.ActionFields.TargetField.formatTarget(t)); + + this.parent.setFlag(game.system.id, "targets", targets); + await this.parent.updateSource({ + system: { + targetSelection: this.targetSelection } - } + }); } registerTargetHook() { - if (this.targetSelection && this.targetHook !== null) { - Hooks.off('targetToken', this.targetHook); + if(!this.parent.isAuthor) return; + if(this.targetSelection && this.targetHook !== null) { + Hooks.off("targetToken", this.targetHook); this.targetHook = null; } else if (!this.targetSelection && this.targetHook === null) { this.targetHook = Hooks.on('targetToken', foundry.utils.debounce(this.updateTargets.bind(this), 50)); @@ -120,35 +105,35 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel { prepareDerivedData() { if (this.hasTarget) { this.hasHitTarget = this.targets.filter(t => t.hit === true).length > 0; - this.updateTargets(); - this.registerTargetHook(); - if (this.targetSelection === true) { - this.targetShort = this.targets.reduce( - (a, c) => { - if (c.hit) a.hit += 1; - else a.miss += 1; - return a; - }, - { hit: 0, miss: 0 } - ); + this.currentTargets = this.getTargetList(); + this. registerTargetHook(); + + if(this.targetSelection === true && this.hasRoll) { + this.targetShort = this.targets.reduce((a,c) => { + if(c.hit) a.hit += 1; + else a.miss += 1; + return a; + }, {hit: 0, miss: 0}) } if (this.hasSave) this.setPendingSaves(); } this.canViewSecret = this.parent.speakerActor?.testUserPermission(game.user, 'OBSERVER'); + this.canButtonApply = game.user.isGM; } getTargetList() { - return this.targetSelection !== true - ? Array.from(game.user.targets).map(t => { - const target = game.system.api.fields.ActionFields.TargetField.formatTarget(t), - oldTarget = - this.targets.find(ot => ot.actorId === target.actorId) ?? - this.oldTargets.find(ot => ot.actorId === target.actorId); - if (oldTarget) return oldTarget; - return target; - }) - : this.targets; + const targets = this.targetSelection && this.parent.isAuthor ? this.targets : (this.parent.getFlag(game.system.id, "targets") ?? this.targets), + reactionRolls = this.parent.getFlag(game.system.id, "reactionRolls"); + + if(reactionRolls) { + Object.entries(reactionRolls).forEach(([k, r]) => { + const target = targets.find(t => t.id === k); + if(target) target.saved = r; + }); + } + + return targets; } setPendingSaves() { diff --git a/module/data/fields/action/costField.mjs b/module/data/fields/action/costField.mjs index b9495e8b..4972a9a0 100644 --- a/module/data/fields/action/costField.mjs +++ b/module/data/fields/action/costField.mjs @@ -47,6 +47,7 @@ export default class CostField extends fields.ArrayField { static hasCost(costs) { const realCosts = CostField.getRealCosts.call(this, costs), hasFearCost = realCosts.findIndex(c => c.key === 'fear'); + if (hasFearCost > -1) { const fearCost = realCosts.splice(hasFearCost, 1)[0]; if ( @@ -70,7 +71,8 @@ export default class CostField extends fields.ArrayField { } static getResources(costs) { - const actorResources = this.actor.system.resources; + const actorResources = foundry.utils.deepClone(this.actor.system.resources); + if(this.actor.system.partner) actorResources.hope = foundry.utils.deepClone(this.actor.system.partner.system.resources.hope); const itemResources = {}; for (let itemResource of costs) { if (itemResource.keyIsID) { @@ -89,7 +91,13 @@ export default class CostField extends fields.ArrayField { static getRealCosts(costs) { const realCosts = costs?.length ? costs.filter(c => c.enabled) : []; - return realCosts; + let mergedCosts = []; + realCosts.forEach(c => { + const getCost = Object.values(mergedCosts).find(gc => gc.key === c.key); + if(getCost) getCost.total += c.total; + else mergedCosts.push(c); + }); + return mergedCosts; } static formatMax(max) { diff --git a/module/data/fields/action/targetField.mjs b/module/data/fields/action/targetField.mjs index 681f8353..bfb01db9 100644 --- a/module/data/fields/action/targetField.mjs +++ b/module/data/fields/action/targetField.mjs @@ -15,6 +15,7 @@ export default class TargetField extends fields.SchemaField { static prepareConfig(config) { if (!this.target?.type) return []; + config.hasTarget = true; let targets; if (this.target?.type === CONFIG.DH.GENERAL.targetTypes.self.id) targets = [this.actor.token ?? this.actor.prototypeToken]; diff --git a/module/data/fields/actionField.mjs b/module/data/fields/actionField.mjs index c3bdcaaa..9300a02e 100644 --- a/module/data/fields/actionField.mjs +++ b/module/data/fields/actionField.mjs @@ -285,6 +285,7 @@ export function ActionMixin(Base) { } }; + ChatMessage.applyRollMode(msg, game.settings.get('core', 'rollMode')); cls.create(msg); } } diff --git a/module/dice/d20Roll.mjs b/module/dice/d20Roll.mjs index 45471532..d636fd27 100644 --- a/module/dice/d20Roll.mjs +++ b/module/dice/d20Roll.mjs @@ -139,17 +139,17 @@ export default class D20Roll extends DHRoll { static postEvaluate(roll, config = {}) { const data = super.postEvaluate(roll, config); data.type = config.roll?.type; + data.difficulty = config.roll.difficulty; if (config.targets?.length) { config.targetSelection = true; config.targets.forEach(target => { const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion; target.hit = roll.isCritical || roll.total >= difficulty; }); - data.success = config.targets.some(target => target.hit); - } else if (config.roll.difficulty) { - data.difficulty = config.roll.difficulty; + data.success = config.targets.some(target => target.hit) + } else if (config.roll.difficulty) data.success = roll.isCritical || roll.total >= config.roll.difficulty; - } + data.advantage = { type: config.roll.advantage, dice: roll.dAdvantage?.denomination, diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs index 999a4bb8..31458516 100644 --- a/module/dice/damageRoll.mjs +++ b/module/dice/damageRoll.mjs @@ -30,16 +30,16 @@ 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); 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)) ), diceRoll = Roll.fromTerms([pool]); - await game.dice3d.showForRoll(diceRoll, game.user, true); + await game.dice3d.showForRoll(diceRoll, game.user, true, chatMessage.whisper, chatMessage.blind); } await super.buildPost(roll, config, message); if (config.source?.message) { - const chatMessage = ui.chat.collection.get(config.source.message); chatMessage.update({ 'system.damage': config.damage }); } } diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index 1b490921..889b24b3 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -2,7 +2,7 @@ import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs'; export default class DHRoll extends Roll { baseTerms = []; - constructor(formula, data, options) { + constructor(formula, data = {}, options = {}) { super(formula, data, options); if (!this.data || !Object.keys(this.data).length) this.data = options.data; } @@ -15,6 +15,8 @@ export default class DHRoll extends Roll { static messageType = 'adversaryRoll'; + static CHAT_TEMPLATE = 'systems/daggerheart/templates/ui/chat/roll.hbs'; + static DefaultDialog = D20RollDialog; static async build(config = {}, message = {}) { @@ -92,9 +94,36 @@ export default class DHRoll extends Roll { system: config, rolls: [roll] }; + config.selectedRollMode ??= game.settings.get('core', 'rollMode'); if(roll._evaluated) return await cls.create(msg, { rollMode: config.selectedRollMode }); return msg; } + + /** @inheritDoc */ + async render({flavor, template=this.constructor.CHAT_TEMPLATE, isPrivate=false, ...options}={}) { + if ( !this._evaluated ) return; + const chatData = await this._prepareChatRenderContext({flavor, isPrivate, ...options}); + return foundry.applications.handlebars.renderTemplate(template, chatData); + } + + /** @inheritDoc */ + async _prepareChatRenderContext({flavor, isPrivate=false, ...options}={}) { + if(isPrivate) { + return { + user: game.user.id, + flavor: null, + title: "???", + roll: { + total: "??" + }, + hasRoll: true, + isPrivate + } + } else { + options.message.system.user = game.user.id; + return options.message.system; + } + } static applyKeybindings(config) { if (config.event) @@ -207,7 +236,9 @@ export const registerRollDiceHooks = () => { if (updates.length) { const target = actor.system.partner ?? actor; if (!['dead', 'unconcious'].some(x => actor.statuses.has(x))) { - target.modifyResource(updates); + setTimeout(() => { + target.modifyResource(updates); + }, 50); } } diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index 35bae725..030b4df2 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -149,7 +149,7 @@ export default class DualityRoll extends D20Roll { } if (this.rallyFaces) this.terms.push( - new foundry.dice.terms.OperatorTerm({ operator: this.hasDisadvantage ? '-' : '+' }), + new foundry.dice.terms.OperatorTerm({ operator: '+' }), new foundry.dice.terms.Die({ faces: this.rallyFaces }) ); } diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index 3aced0bf..bf535b78 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -124,15 +124,20 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { return tags; } + /** + * Create a new ChatMessage to display this document’s data. + * @param {String} origin - uuid of a document. TODO: This needs to be reviewed. + */ async toChat(origin) { + /**@type {foundry.documents.ChatMessage} */ const cls = getDocumentClass('ChatMessage'); - const actor = game.actors.get(cls.getSpeaker().actor); + const speaker = cls.getSpeaker(); + const actor = cls.getSpeakerActor(speaker); const systemData = { action: { img: this.img, name: this.name }, - actor: { name: actor.name, img: actor.img }, - author: this.author, - speaker: cls.getSpeaker(), - origin: origin, + actor: { name: actor?.name, img: actor?.img }, + speaker, + origin, description: this.description, actions: [] }; diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs index 302ba1d8..06a9f147 100644 --- a/module/documents/chatMessage.mjs +++ b/module/documents/chatMessage.mjs @@ -1,21 +1,31 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { async renderHTML() { - if (this.system.messageTemplate) - this.content = await foundry.applications.handlebars.renderTemplate(this.system.messageTemplate, { - ...this.system, - _source: this.system._source - }); - const actor = game.actors.get(this.speaker.actor); - const actorData = actor ?? { + const actorData = actor && this.isContentVisible ? actor : { img: this.author.avatar ? this.author.avatar : 'icons/svg/mystery-man.svg', name: '' }; /* We can change to fully implementing the renderHTML function if needed, instead of augmenting it. */ const html = await super.renderHTML({ actor: actorData, author: this.author }); - this.applyPermission(html); - if (this.type === 'dualityRoll') { + this.enrichChatMessage(html); + this.addChatListeners(html); + + return html; + } + + enrichChatMessage(html) { + const elements = html.querySelectorAll('[data-perm-id]'); + elements.forEach(e => { + const uuid = e.dataset.permId, + document = fromUuidSync(uuid); + if (!document) return; + + e.setAttribute('data-view-perm', document.testUserPermission(game.user, 'OBSERVER')); + e.setAttribute('data-use-perm', document.testUserPermission(game.user, 'OWNER')); + }); + + if (this.isContentVisible && this.type === 'dualityRoll') { html.classList.add('duality'); switch (this.system.roll?.result?.duality) { case 1: @@ -29,36 +39,9 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { break; } } - - this.enrichChatMessage(html); - - return html; } - applyPermission(html) { - const elements = html.querySelectorAll('[data-perm-id]'); - elements.forEach(e => { - const uuid = e.dataset.permId, - document = fromUuidSync(uuid); - if (!document) return; - - e.setAttribute('data-view-perm', document.testUserPermission(game.user, 'OBSERVER')); - e.setAttribute('data-use-perm', document.testUserPermission(game.user, 'OWNER')); - }); - } - - async _preCreate(data, options, user) { - options.speaker = ChatMessage.getSpeaker(); - const rollActorOwner = data.rolls?.[0]?.data?.parent?.owner; - if (rollActorOwner) { - data.author = rollActorOwner ? rollActorOwner.id : data.author; - await this.updateSource({ author: rollActorOwner ?? user }); - } - - return super._preCreate(data, options, rollActorOwner ?? user); - } - - enrichChatMessage(html) { + addChatListeners(html) { html.querySelectorAll('.damage-button').forEach(element => element.addEventListener('click', this.onDamage.bind(this)) ); @@ -66,6 +49,16 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { html.querySelectorAll('.duality-action-effect').forEach(element => element.addEventListener('click', this.onApplyEffect.bind(this)) ); + + html.querySelectorAll('.roll-target').forEach(element => { + element.addEventListener('mouseenter', this.hoverTarget); + element.addEventListener('mouseleave', this.unhoverTarget); + element.addEventListener('click', this.clickTarget); + }); + + html.querySelectorAll('.button-target-selection').forEach(element => { + element.addEventListener('click', this.onTargetSelection.bind(this)); + }); } getTargetList() { @@ -146,4 +139,30 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { if (action) action.consume(this.system, true); } } + + hoverTarget(event) { + event.stopPropagation(); + const token = canvas.tokens.get(event.currentTarget.dataset.token); + if (!token?.controlled) token._onHoverIn(event, { hoverOutOthers: true }); + } + + unhoverTarget(event) { + const token = canvas.tokens.get(event.currentTarget.dataset.token); + if (!token?.controlled) token._onHoverOut(event); + } + + clickTarget(event) { + event.stopPropagation(); + const token = canvas.tokens.get(event.currentTarget.dataset.token); + if (!token) { + ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.attackTargetDoesNotExist')); + return; + } + game.canvas.pan(token); + } + + onTargetSelection(event) { + event.stopPropagation(); + this.system.targetMode = Boolean(event.target.dataset.targetHit); + } } diff --git a/module/documents/item.mjs b/module/documents/item.mjs index a261677a..fb558e8c 100644 --- a/module/documents/item.mjs +++ b/module/documents/item.mjs @@ -142,19 +142,16 @@ export default class DHItem extends foundry.documents.Item { } } + /** + * Create a new ChatMessage to display this document’s data + * @param {String} origin - uuid of a document. TODO: This needs to be reviewed. + */ async toChat(origin) { + /**@type {foundry.documents.ChatMessage} */ const cls = getDocumentClass('ChatMessage'); const item = await foundry.utils.fromUuid(origin); const systemData = { - title: - this.type === 'ancestry' - ? game.i18n.localize('DAGGERHEART.UI.Chat.foundationCard.ancestryTitle') - : this.type === 'community' - ? game.i18n.localize('DAGGERHEART.UI.Chat.foundationCard.communityTitle') - : this.type === 'feature' - ? game.i18n.localize('TYPES.Item.feature') - : game.i18n.localize('DAGGERHEART.UI.Chat.foundationCard.subclassFeatureTitle'), origin: origin, img: this.img, item: { @@ -170,7 +167,6 @@ export default class DHItem extends foundry.documents.Item { type: 'abilityUse', user: game.user.id, actor: item.parent, - author: this.author, speaker: cls.getSpeaker(), system: systemData, title: game.i18n.localize('DAGGERHEART.ACTIONS.Config.displayInChat'), diff --git a/src/packs/classes/feature_Rally_PydiMnNCKpd44SGS.json b/src/packs/classes/feature_Rally_PydiMnNCKpd44SGS.json index 4b509228..c5933ca8 100644 --- a/src/packs/classes/feature_Rally_PydiMnNCKpd44SGS.json +++ b/src/packs/classes/feature_Rally_PydiMnNCKpd44SGS.json @@ -14,16 +14,7 @@ "description": "", "chatDisplay": false, "actionType": "action", - "cost": [ - { - "scalable": false, - "key": "hitPoints", - "value": 1, - "keyIsID": false, - "step": null, - "consumeOnSuccess": false - } - ], + "cost": [], "uses": { "value": null, "max": "1", @@ -67,7 +58,7 @@ { "key": "system.bonuses.rally", "mode": 2, - "value": "1d6", + "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 index bba20558..da124244 100644 --- a/src/packs/classes/feature_Rally__Level_5__TVeEyqmPPiRa2r3i.json +++ b/src/packs/classes/feature_Rally__Level_5__TVeEyqmPPiRa2r3i.json @@ -14,16 +14,7 @@ "description": "", "chatDisplay": true, "actionType": "action", - "cost": [ - { - "scalable": false, - "key": "hitPoints", - "value": 1, - "keyIsID": false, - "step": null, - "consumeOnSuccess": false - } - ], + "cost": [], "uses": { "value": null, "max": "1", @@ -67,7 +58,7 @@ { "key": "system.bonuses.rally", "mode": 2, - "value": "1d8", + "value": "d8", "priority": null } ], diff --git a/src/packs/domains/domainCard_Gifted_Tracker_VZ2b4zfRzV73XTuT.json b/src/packs/domains/domainCard_Gifted_Tracker_VZ2b4zfRzV73XTuT.json index c274b9ac..a9e66110 100644 --- a/src/packs/domains/domainCard_Gifted_Tracker_VZ2b4zfRzV73XTuT.json +++ b/src/packs/domains/domainCard_Gifted_Tracker_VZ2b4zfRzV73XTuT.json @@ -64,7 +64,7 @@ "effects": [ { "name": "Gifted Tracker", - "img": "icons/svg/item-bag.svg", + "img": "systems/daggerheart/assets/icons/domains/domain-card/sage.png", "origin": "Compendium.daggerheart.domains.Item.VZ2b4zfRzV73XTuT", "transfer": false, "_id": "47Oh2weCdmuvKHM9", @@ -104,12 +104,12 @@ "compendiumSource": null, "duplicateSource": null, "exportSource": null, - "coreVersion": "13.346", + "coreVersion": "13.347", "systemId": "daggerheart", "systemVersion": "0.0.1", "createdTime": 1754114056078, - "modifiedTime": 1754114073478, - "lastModifiedBy": "Q9NoTaEarn3VMS6Z" + "modifiedTime": 1754670410126, + "lastModifiedBy": "49DaecTcBSc5d0DA" }, "_key": "!items.effects!VZ2b4zfRzV73XTuT.47Oh2weCdmuvKHM9" } diff --git a/src/packs/domains/domainCard_Healing_Hands_WTlhnQMajc1r8i50.json b/src/packs/domains/domainCard_Healing_Hands_WTlhnQMajc1r8i50.json index 622e2266..a7aeb6b2 100644 --- a/src/packs/domains/domainCard_Healing_Hands_WTlhnQMajc1r8i50.json +++ b/src/packs/domains/domainCard_Healing_Hands_WTlhnQMajc1r8i50.json @@ -389,7 +389,7 @@ "effects": [ { "name": "Healed by Healing Hands", - "img": "icons/svg/item-bag.svg", + "img": "systems/daggerheart/assets/icons/domains/domain-card/splendor.png", "origin": "Compendium.daggerheart.domains.Item.WTlhnQMajc1r8i50", "transfer": false, "_id": "sd5liP4ZcVeTMAoW", @@ -422,12 +422,12 @@ "compendiumSource": null, "duplicateSource": null, "exportSource": null, - "coreVersion": "13.346", + "coreVersion": "13.347", "systemId": "daggerheart", "systemVersion": "0.0.1", "createdTime": 1754263407455, - "modifiedTime": 1754263727114, - "lastModifiedBy": "Q9NoTaEarn3VMS6Z" + "modifiedTime": 1754670504951, + "lastModifiedBy": "49DaecTcBSc5d0DA" }, "_key": "!items.effects!WTlhnQMajc1r8i50.sd5liP4ZcVeTMAoW" } diff --git a/src/packs/domains/domainCard_Life_Ward_OszbCj0jTqq2ADx9.json b/src/packs/domains/domainCard_Life_Ward_OszbCj0jTqq2ADx9.json index efdec150..78b2ec5e 100644 --- a/src/packs/domains/domainCard_Life_Ward_OszbCj0jTqq2ADx9.json +++ b/src/packs/domains/domainCard_Life_Ward_OszbCj0jTqq2ADx9.json @@ -66,7 +66,7 @@ "effects": [ { "name": "Life Ward", - "img": "icons/svg/item-bag.svg", + "img": "systems/daggerheart/assets/icons/domains/domain-card/splendor.png", "origin": "Compendium.daggerheart.domains.Item.OszbCj0jTqq2ADx9", "transfer": false, "_id": "E7Ou4OMEy3TeK1Gf", @@ -99,12 +99,12 @@ "compendiumSource": null, "duplicateSource": null, "exportSource": null, - "coreVersion": "13.346", + "coreVersion": "13.347", "systemId": "daggerheart", "systemVersion": "0.0.1", "createdTime": 1754264687962, - "modifiedTime": 1754264717646, - "lastModifiedBy": "Q9NoTaEarn3VMS6Z" + "modifiedTime": 1754670535710, + "lastModifiedBy": "49DaecTcBSc5d0DA" }, "_key": "!items.effects!OszbCj0jTqq2ADx9.E7Ou4OMEy3TeK1Gf" } diff --git a/src/packs/domains/domainCard_On_the_Brink_zbxPl81kbWEegKQN.json b/src/packs/domains/domainCard_On_the_Brink_zbxPl81kbWEegKQN.json index a7276bba..b6f6548f 100644 --- a/src/packs/domains/domainCard_On_the_Brink_zbxPl81kbWEegKQN.json +++ b/src/packs/domains/domainCard_On_the_Brink_zbxPl81kbWEegKQN.json @@ -37,7 +37,7 @@ } }, "_id": "UJTsJlnhi5Zi0XQ2", - "img": "icons/magic/life/heart-cross-blue.webp", + "img": "systems/daggerheart/assets/icons/domains/domain-card/bone.png", "changes": [ { "key": "system.rules.damageReduction.thresholdImmunities.minor", @@ -67,12 +67,12 @@ "compendiumSource": null, "duplicateSource": null, "exportSource": null, - "coreVersion": "13.346", + "coreVersion": "13.347", "systemId": "daggerheart", "systemVersion": "0.0.1", "createdTime": 1754303484332, - "modifiedTime": 1754303570504, - "lastModifiedBy": "MQSznptE5yLT7kj8" + "modifiedTime": 1754670012467, + "lastModifiedBy": "49DaecTcBSc5d0DA" }, "_key": "!items.effects!zbxPl81kbWEegKQN.UJTsJlnhi5Zi0XQ2" } diff --git a/src/packs/journals/journal_Welcome___Information_g7NhKvwltwafmMyR.json b/src/packs/journals/journal_Welcome___Information_g7NhKvwltwafmMyR.json index b08e4a1d..6620d62c 100644 --- a/src/packs/journals/journal_Welcome___Information_g7NhKvwltwafmMyR.json +++ b/src/packs/journals/journal_Welcome___Information_g7NhKvwltwafmMyR.json @@ -132,7 +132,7 @@ "image": {}, "text": { "format": 1, - "content": "

This product includes materials from the Daggerheart System Reference Document 1.0, © Critical Role, LLC. under the terms of the Darrington Press Community Gaming (DPCGL) License. More information can be found at https://www.daggerheart.com. There are no previous modifications by others.

The Foundryborne Team

The Foundryborne Team consists of:

With Art from:

We would also like to thank the FoundryVTT team for their support in publishing this system.

And, of course, special thanks to the teams at Critical Role and Darrington Press for making such a wonderful game and updating the license to allow a FoundryVTT version of the system.

The Foundryborne Community

Without our amazing community this project would not have been possible.

You kept us going with both direct contributions and just endless support!

We thank you with all our hearts.

Come join us!

" + "content": "

This product includes materials from the Daggerheart System Reference Document 1.0, © Critical Role, LLC. under the terms of the Darrington Press Community Gaming (DPCGL) License. More information can be found at https://www.daggerheart.com. There are no previous modifications by others.

The Foundryborne Team

The Foundryborne Team consists of:

With Art from:

And special thanks to our hard working community testers:

We would also like to thank the FoundryVTT team for their support in publishing this system.

And, of course, special thanks to the teams at Critical Role and Darrington Press for making such a wonderful game and updating the license to allow a FoundryVTT version of the system.

The Foundryborne Community

Without our amazing community this project would not have been possible.

You kept us going with both direct contributions and just endless support!

We thank you with all our hearts.

Come join us!

" }, "video": { "controls": true, @@ -153,8 +153,8 @@ "systemId": "daggerheart", "systemVersion": "0.0.1", "createdTime": 1754225939902, - "modifiedTime": 1754226994508, - "lastModifiedBy": "l5jB3XmcVXOTQpRZ" + "modifiedTime": 1754668980876, + "lastModifiedBy": "Cf0YKwnZ1OHBZWl8" }, "_key": "!journal.pages!g7NhKvwltwafmMyR.dP6xSKEld4TSqHhK" } diff --git a/src/packs/subclasses/feature_Epic_Poetry_eCoEWkWuZPMZ9C6a.json b/src/packs/subclasses/feature_Epic_Poetry_eCoEWkWuZPMZ9C6a.json index 3429f585..f80c05e3 100644 --- a/src/packs/subclasses/feature_Epic_Poetry_eCoEWkWuZPMZ9C6a.json +++ b/src/packs/subclasses/feature_Epic_Poetry_eCoEWkWuZPMZ9C6a.json @@ -23,7 +23,7 @@ } }, "_id": "RSmscgGyuHJucF6C", - "img": "icons/magic/life/heart-cross-blue.webp", + "img": "icons/sundries/documents/document-letter-blue.webp", "changes": [ { "key": "system.bonuses.rally", @@ -53,10 +53,11 @@ "compendiumSource": null, "duplicateSource": null, "exportSource": null, - "coreVersion": "13.346", + "coreVersion": "13.347", "systemId": "daggerheart", "systemVersion": "0.0.1", - "lastModifiedBy": null + "lastModifiedBy": "49DaecTcBSc5d0DA", + "modifiedTime": 1754669077252 }, "_key": "!items.effects!eCoEWkWuZPMZ9C6a.RSmscgGyuHJucF6C" } diff --git a/styles/less/global/chat.less b/styles/less/global/chat.less index 1e37309d..37ec993d 100644 --- a/styles/less/global/chat.less +++ b/styles/less/global/chat.less @@ -32,6 +32,7 @@ .message-header-metadata { flex: none; display: flex; + flex-direction: column; .message-metadata { font-family: @font-body; @@ -73,6 +74,13 @@ .message-content { padding-bottom: 8px; + .flavor-text { + font-size: var(--font-size-12); + line-height: 20px; + color: var(--color-dark-4); + text-align: center; + display: block; + } } } } diff --git a/styles/less/ui/chat/chat.less b/styles/less/ui/chat/chat.less index 9afa32d3..6ffd00cf 100644 --- a/styles/less/ui/chat/chat.less +++ b/styles/less/ui/chat/chat.less @@ -11,333 +11,6 @@ } } - /* &.roll { - .dice-flavor { - text-align: center; - font-weight: bold; - } - .dice-tooltip { - .dice-rolls { - &.duality { - display: flex; - gap: 0.25rem; - - > .roll { - background-image: none; - - .reroll-button { - border: none; - background: initial; - width: 42px; - - &:hover { - background: var(--button-background-color); - border: 1px solid var(--button-border-color); - } - } - } - } - - &.rerollable { - position: relative; - flex: none; - - .dice-rerolled { - z-index: 2; - position: absolute; - right: 0; - font-size: 12px; - cursor: help; - } - - .reroll-button { - border: none; - background: initial; - - &:hover { - background: var(--button-background-color); - border: 1px solid var(--button-border-color); - } - } - } - - // margin: 0; - > .roll { - display: flex; - align-items: center; - justify-content: center; - gap: 4px; - margin-bottom: 4px; - - .dice-container { - display: flex; - flex-direction: column; - gap: 2px; - position: relative; - - .dice-title { - color: var(--color-light-1); - text-shadow: 0 0 1px black; - } - - .dice-rerolled { - z-index: 2; - position: absolute; - right: -2px; - font-size: 12px; - cursor: help; - } - - .dice-inner-container { - display: flex; - align-items: center; - justify-content: center; - position: relative; - &.hope, - &.fear { - .dice-wrapper { - clip-path: polygon( - 50% 0%, - 80% 10%, - 100% 35%, - 100% 70%, - 80% 90%, - 50% 100%, - 20% 90%, - 0% 70%, - 0% 35%, - 20% 10% - ); - } - } - .dice-wrapper { - height: 24px; - width: 24px; - position: relative; - display: flex; - align-items: center; - justify-content: center; - - .dice { - height: 26px; - width: 26px; - max-width: unset; - position: absolute; - } - } - - .dice-value { - position: absolute; - font-weight: bold; - font-size: 16px; - } - - &.hope { - .dice-wrapper { - background: black; - - .dice { - filter: brightness(0) saturate(100%) invert(79%) sepia(79%) saturate(333%) - hue-rotate(352deg) brightness(102%) contrast(103%); - } - } - - .dice-value { - color: var(--color-dark-1); - text-shadow: 0 0 4px white; - } - } - - &.fear { - .dice-wrapper { - background: white; - - .dice { - filter: brightness(0) saturate(100%) invert(12%) sepia(88%) saturate(4321%) - hue-rotate(221deg) brightness(92%) contrast(110%); - } - } - - .dice-value { - color: var(--color-light-1); - text-shadow: 0 0 4px black; - } - } - - &.advantage { - .dice-wrapper { - .dice { - filter: brightness(0) saturate(100%) invert(18%) sepia(92%) saturate(4133%) - hue-rotate(96deg) brightness(104%) contrast(107%); - } - } - } - - &.disadvantage { - .dice-wrapper { - .dice { - filter: brightness(0) saturate(100%) invert(9%) sepia(78%) saturate(6903%) - hue-rotate(11deg) brightness(93%) contrast(117%); - } - } - } - } - } - } - } - - .damage-resource { - font-weight: 600; - margin-top: 5px; - } - } - - .dice-total { - &.duality { - &.hope { - border-color: @hope; - border-width: 3px; - background: rgba(@hope, 0.5); - } - &.fear { - border-color: @fear; - border-width: 3px; - background: rgba(@fear, 0.5); - } - &.critical { - border-color: @critical; - border-width: 3px; - background: rgba(@critical, 0.5); - } - } - - .dice-total-value { - .hope { - color: @hope; - } - .fear { - color: @fear; - } - .critical { - color: @critical; - } - } - } - - .dice-total-label { - font-size: 12px; - font-weight: bold; - font-variant: all-small-caps; - margin: -@fullMargin 0; - } - - .target-selection { - display: flex; - justify-content: space-around; - input[type='radio'] { - display: none; - &:checked + label { - text-shadow: 0px 0px 4px #ce5937; - } - &:not(:checked) + label { - opacity: 0.75; - } - } - label { - cursor: pointer; - opacity: 0.75; - &.target-selected { - text-shadow: 0px 0px 4px #ce5937; - opacity: 1; - } - } - } - - .target-section { - margin-top: 5px; - - .target-container { - display: flex; - transition: all 0.2s ease-in-out; - - &:hover { - filter: drop-shadow(0 0 3px @secondaryShadow); - border-color: gold; - } - - &.hidden { - display: none; - border: 0; - } - - &.hit { - background: @hit; - } - - &.miss { - background: @miss; - } - - img, - .target-save-container { - width: 22px; - height: 22px; - align-self: center; - border-color: transparent; - } - - img { - flex: 0; - margin-left: 8px; - } - - .target-save-container { - margin-right: 8px; - justify-content: center; - display: flex; - align-items: center; - min-height: unset; - border: 1px solid black; - } - - .target-inner-container { - flex: 1; - display: flex; - justify-content: center; - font-size: var(--font-size-16); - } - - &:not(:has(.target-save-container)) .target-inner-container { - margin-right: @hugeMargin; - } - } - } - - .dice-actions { - display: flex; - gap: 4px; - - button { - flex: 1; - height: 40px; - font-family: @font-body; - font-weight: 600; - } - } - - .dice-result { - .roll-damage-button, - .damage-button, - .duality-action { - margin-top: 5px; - } - } - - &:not(.expanded) .dice-tooltip { - grid-template-rows: 0fr; - } - } */ - button { &.inner-button { --button-size: 1.25rem; @@ -349,19 +22,6 @@ } } } - - [data-use-perm='false'] { - pointer-events: none; - border-color: transparent; - } - [data-view-perm='false'] { - > * { - display: none; - } - &::after { - content: '??'; - } - } } .daggerheart, @@ -370,6 +30,19 @@ --text-color: light-dark(@dark-blue, @golden); --bg-color: light-dark(@dark-blue-40, @golden-40); + [data-use-perm='false'] { + pointer-events: none; + border-color: transparent; + } + [data-view-perm='false'] { + &[data-perm-hidden='true'], > * { + display: none; + } + &::after { + content: '??'; + } + } + &.duality { &.hope { --text-color: @golden; @@ -412,7 +85,7 @@ grid-template-columns: 1fr auto 1fr; align-items: center; color: light-dark(@dark, @beige); - margin: 5px 0; + margin: 10px 0; span { display: flex; @@ -450,7 +123,6 @@ flex-direction: column; align-items: center; gap: 5px; - padding: 5px 0; .dice-tooltip { width: 100%; @@ -489,6 +161,7 @@ color: var(--text-color); font-weight: 700; font-family: 'Cinzel', sans-serif; + line-height: .75; .roll-result-value { font-size: var(--font-size-24); @@ -503,10 +176,6 @@ } } } - - .roll-difficulty { - margin-top: -5px; - } } } @@ -580,7 +249,7 @@ .button-target-selection { flex: 1; text-align: center; - padding: 5px 0; + margin: -5px 0; } .button-target-selection:hover, @@ -600,6 +269,11 @@ width: 100%; gap: 10px; align-items: center; + border-radius: 3px; + + &:hover { + background-color: rgba(255,255,255,.1); + } .target-img { border-radius: 50%; @@ -640,6 +314,7 @@ padding: 3px 5px; width: fit-content; margin: auto; + white-space: nowrap; } .roll-difficulty, @@ -706,6 +381,19 @@ margin-top: 0; } + .damage-section[data-action='expandRoll'] { + .on-reduced { + .wrapper { + flex-wrap: wrap; + gap: 10px 5px; + } + + .roll-formula { + font-size: var(--font-size-16); + } + } + } + .target-section { .roll-part-content { gap: 10px; diff --git a/templates/dialogs/dice-roll/costSelection.hbs b/templates/dialogs/dice-roll/costSelection.hbs index 765fbaf9..775e681c 100644 --- a/templates/dialogs/dice-roll/costSelection.hbs +++ b/templates/dialogs/dice-roll/costSelection.hbs @@ -5,25 +5,29 @@
  • - - + +
    - +
  • {{/if}} {{#each costs as | cost index |}}
  • - - + +
    {{#if (and scalable maxStep)}} {{/if}} - +
  • {{/each}} diff --git a/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs b/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs index 8fbd5800..1ef065d5 100644 --- a/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs +++ b/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs @@ -26,7 +26,7 @@ Parameters: {{localize title}} {{#if canCreate}} -
    - {{#if (eq message.type 'base')}} -
    -

    {{actor.name}}

    -
    {{author.name}}
    -
    - {{else}} -
    -

    {{ifThen message.title message.title alias}}

    -
    {{actor.name}} {{#if author.isGM}}(GM){{/if}}
    -
    - {{/if}} +
    + {{#unless actor.name}} +

    {{author.name}}

    + {{else}} + {{#if (eq message.type 'base')}} +

    {{actor.name}}

    +
    {{author.name}}
    + {{else}} +

    {{ifThen message.title message.title alias}}

    +
    {{actor.name}} {{#if author.isGM}}(GM){{/if}}
    + {{/if}} + {{/unless}} +
    {{{message.content}}} + {{#if message.flavor}} + {{{message.flavor}}} + {{/if}}
    \ No newline at end of file diff --git a/templates/ui/chat/parts/button-part.hbs b/templates/ui/chat/parts/button-part.hbs index f28f1a7b..f83972f7 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 hasEffect}}{{/if}} + {{#if (and hasEffect canButtonApply)}}{{/if}}
    \ No newline at end of file diff --git a/templates/ui/chat/parts/roll-part.hbs b/templates/ui/chat/parts/roll-part.hbs index d4e46b1f..1d7f3d2b 100644 --- a/templates/ui/chat/parts/roll-part.hbs +++ b/templates/ui/chat/parts/roll-part.hbs @@ -14,14 +14,15 @@ {{#if roll.difficulty}} - {{#if canViewSecret}} + {{!-- {{#if canViewSecret}} --}} difficulty {{roll.difficulty}} - {{else}} + {{!-- {{else}} {{localize (ifThen roll.success "DAGGERHEART.GENERAL.success" "DAGGERHEART.GENERAL.failure")}} - {{/if}} + {{/if}} --}} {{/if}} + {{#unless isPrivate}}
    {{localize "DAGGERHEART.GENERAL.formula"}}
    @@ -87,4 +88,5 @@
    + {{/unless}} \ No newline at end of file diff --git a/templates/ui/chat/parts/target-part.hbs b/templates/ui/chat/parts/target-part.hbs index 94810a03..51b9e79c 100644 --- a/templates/ui/chat/parts/target-part.hbs +++ b/templates/ui/chat/parts/target-part.hbs @@ -14,7 +14,7 @@
    - {{#if targets.length}} + {{#if (and parent.isAuthor targets.length)}}
    @@ -29,7 +29,7 @@
    -
    {{name}}
    +
    {{name}}
    {{#if (and ../targetSelection ../hasRoll)}}
    {{#if hit}} @@ -40,7 +40,7 @@
    {{/if}}
    - {{#if (and ../hasSave (or hit (not @root.targetSelection)))}} + {{#if (and ../hasSave (or hit (not @root.hasRoll)))}}
    diff --git a/templates/ui/chat/roll.hbs b/templates/ui/chat/roll.hbs index 37e1aef2..2e6f5d24 100644 --- a/templates/ui/chat/roll.hbs +++ b/templates/ui/chat/roll.hbs @@ -5,4 +5,4 @@ {{#if hasTarget}}{{> 'systems/daggerheart/templates/ui/chat/parts/target-part.hbs'}}{{/if}}
    -{{> 'systems/daggerheart/templates/ui/chat/parts/button-part.hbs'}} \ No newline at end of file +{{#if (or parent.isAuthor canButtonApply)}}{{> 'systems/daggerheart/templates/ui/chat/parts/button-part.hbs'}}{{/if}} \ No newline at end of file