diff --git a/lang/en.json b/lang/en.json index c21c1d5b..f4e70966 100755 --- a/lang/en.json +++ b/lang/en.json @@ -2307,6 +2307,9 @@ "label": "Apply Effects", "hint": "Automatically apply effects. Targets must be selected before the action is made and Reaction Roll Automation must be different than Never. Bypass users permissions." } + }, + "summaryMessages": { + "label": "Summary Messages" } }, "defeated": { @@ -2428,6 +2431,7 @@ "action": { "title": "Action" }, + "appliedTo": "Applied To", "applyEffect": { "title": "Apply Effects - {name}" }, @@ -2437,6 +2441,11 @@ "rollHealing": "Roll Healing", "applyEffect": "Apply Effects" }, + "clearResource": "Clear {quantity} {resource}", + "damageSummary": { + "title": "Damage Applied", + "healingTitle": "Healing Applied" + }, "damageRoll": { "title": "Damage - {damage}", "dealDamageToTargets": "Damage Hit Targets", @@ -2458,12 +2467,16 @@ "dualityRoll": { "abilityCheckTitle": "{ability} Check" }, + "effectSummary": { + "title": "Effects Applied" + }, "featureTitle": "Class Feature", "healingRoll": { "title": "Heal - {damage}", "heal": "Heal", "applyHealing": "Apply Healing" }, + "markResource": "Mark {quantity} {resource}", "refreshMessage": { "title": "Feature Refresh", "header": "Refreshed" diff --git a/module/data/chat-message/_modules.mjs b/module/data/chat-message/_modules.mjs index 7e301906..67046248 100644 --- a/module/data/chat-message/_modules.mjs +++ b/module/data/chat-message/_modules.mjs @@ -1,9 +1,11 @@ import DHAbilityUse from './abilityUse.mjs'; import DHActorRoll from './actorRoll.mjs'; +import DHSystemMessage from './systemMessage.mjs'; export const config = { abilityUse: DHAbilityUse, adversaryRoll: DHActorRoll, damageRoll: DHActorRoll, - dualityRoll: DHActorRoll + dualityRoll: DHActorRoll, + systemMessage: DHSystemMessage }; diff --git a/module/data/chat-message/systemMessage.mjs b/module/data/chat-message/systemMessage.mjs new file mode 100644 index 00000000..cd2a06a1 --- /dev/null +++ b/module/data/chat-message/systemMessage.mjs @@ -0,0 +1,9 @@ +export default class DHSystemMessage extends foundry.abstract.TypeDataModel { + static defineSchema() { + const fields = foundry.data.fields; + + return { + useTitle: new fields.BooleanField({ initial: true }) + }; + } +} diff --git a/module/data/fields/action/damageField.mjs b/module/data/fields/action/damageField.mjs index 26d720c0..43623c94 100644 --- a/module/data/fields/action/damageField.mjs +++ b/module/data/fields/action/damageField.mjs @@ -81,6 +81,9 @@ export default class DamageField extends fields.SchemaField { static async applyDamage(config, targets = null, force = false) { targets ??= config.targets.filter(target => target.hit); if (!config.damage || !targets?.length || (!DamageField.getApplyAutomation() && !force)) return; + + const targetDamage = []; + const damagePromises = []; for (let target of targets) { const actor = fromUuidSync(target.actorId); if (!actor) continue; @@ -95,9 +98,45 @@ export default class DamageField extends fields.SchemaField { }); } - if (config.hasHealing) actor.takeHealing(config.damage); - else actor.takeDamage(config.damage, config.isDirect); + if (config.hasHealing) + damagePromises.push( + actor + .takeHealing(config.damage) + .then(updates => targetDamage.push({ token: actor.token ?? actor.prototypeToken, updates })) + ); + else + damagePromises.push( + actor + .takeDamage(config.damage, config.isDirect) + .then(updates => targetDamage.push({ token: actor.token ?? actor.prototypeToken, updates })) + ); } + + Promise.all(damagePromises).then(async _ => { + const summaryMessageSettings = game.settings.get( + CONFIG.DH.id, + CONFIG.DH.SETTINGS.gameSettings.Automation + ).summaryMessages; + if (!summaryMessageSettings.damage) return; + + const cls = getDocumentClass('ChatMessage'); + const msg = { + type: 'systemMessage', + user: game.user.id, + speaker: cls.getSpeaker(), + title: game.i18n.localize( + `DAGGERHEART.UI.Chat.damageSummary.${config.hasHealing ? 'healingTitle' : 'title'}` + ), + content: await foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/ui/chat/damageSummary.hbs', + { + targets: targetDamage + } + ) + }; + + cls.create(msg); + }); } /** diff --git a/module/data/fields/action/effectsField.mjs b/module/data/fields/action/effectsField.mjs index 0f205d72..887607ba 100644 --- a/module/data/fields/action/effectsField.mjs +++ b/module/data/fields/action/effectsField.mjs @@ -46,17 +46,48 @@ export default class EffectsField extends fields.ArrayField { */ static async applyEffects(targets) { if (!this.effects?.length || !targets?.length) return; + let effects = this.effects; - targets.forEach(async token => { + const messageTargets = []; + targets.forEach(async baseToken => { if (this.hasSave && token.saved.success === true) effects = this.effects.filter(e => e.onSave === true); if (!effects.length) return; + + const token = canvas.tokens.get(baseToken.id); + if (!token) return; + messageTargets.push(token.document); + effects.forEach(async e => { - const actor = canvas.tokens.get(token.id)?.actor, - effect = this.item.effects.get(e._id); - if (!actor || !effect) return; - await EffectsField.applyEffect(effect, actor); + const effect = this.item.effects.get(e._id); + if (!token.actor || !effect) return; + await EffectsField.applyEffect(effect, token.actor); }); }); + + if (messageTargets.length === 0) return; + + const summaryMessageSettings = game.settings.get( + CONFIG.DH.id, + CONFIG.DH.SETTINGS.gameSettings.Automation + ).summaryMessages; + if (!summaryMessageSettings.effects) return; + + const cls = getDocumentClass('ChatMessage'); + const msg = { + type: 'systemMessage', + user: game.user.id, + speaker: cls.getSpeaker(), + title: game.i18n.localize('DAGGERHEART.UI.Chat.effectSummary.title'), + content: await foundry.applications.handlebars.renderTemplate( + 'systems/daggerheart/templates/ui/chat/effectSummary.hbs', + { + effects: this.effects.map(e => this.item.effects.get(e._id)), + targets: messageTargets + } + ) + }; + + cls.create(msg); } /** diff --git a/module/data/settings/Automation.mjs b/module/data/settings/Automation.mjs index beefac0b..c54a1c31 100644 --- a/module/data/settings/Automation.mjs +++ b/module/data/settings/Automation.mjs @@ -2,6 +2,10 @@ export default class DhAutomation extends foundry.abstract.DataModel { static defineSchema() { const fields = foundry.data.fields; return { + summaryMessages: new fields.SchemaField({ + damage: new fields.BooleanField({ initial: true, label: 'DAGGERHEART.GENERAL.damage' }), + effects: new fields.BooleanField({ initial: true, label: 'DAGGERHEART.GENERAL.Effect.plural' }) + }), hopeFear: new fields.SchemaField({ gm: new fields.BooleanField({ required: true, diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 3601e09d..a4c1ade8 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -599,6 +599,8 @@ export default class DhpActor extends Actor { await this.modifyResource(updates); if (Hooks.call(`${CONFIG.DH.id}.postTakeDamage`, this, updates) === false) return null; + + return updates; } calculateDamage(baseDamage, type) { @@ -647,6 +649,8 @@ export default class DhpActor extends Actor { await this.modifyResource(updates); if (Hooks.call(`${CONFIG.DH.id}.postTakeHealing`, this, updates) === false) return null; + + return updates; } async modifyResource(resources) { diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs index d7476395..bb535c6d 100644 --- a/module/documents/chatMessage.mjs +++ b/module/documents/chatMessage.mjs @@ -143,6 +143,12 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { html.querySelectorAll('.button-target-selection').forEach(element => { element.addEventListener('click', this.onTargetSelection.bind(this)); }); + + html.querySelectorAll('.token-target-container').forEach(element => { + element.addEventListener('pointerover', this.hoverTarget); + element.addEventListener('pointerout', this.unhoverTarget); + element.addEventListener('click', this.clickTarget); + }); } async onRollDamage(event) { diff --git a/module/helpers/handlebarsHelper.mjs b/module/helpers/handlebarsHelper.mjs index e6c1a2f0..847b04ce 100644 --- a/module/helpers/handlebarsHelper.mjs +++ b/module/helpers/handlebarsHelper.mjs @@ -14,7 +14,8 @@ export default class RegisterHandlebarsHelpers { getProperty: foundry.utils.getProperty, setVar: this.setVar, empty: this.empty, - pluralize: this.pluralize + pluralize: this.pluralize, + positive: this.positive }); } static add(a, b) { @@ -89,4 +90,8 @@ export default class RegisterHandlebarsHelpers { const key = isSingular ? `${baseKey}.single` : `${baseKey}.plural`; return game.i18n.localize(key); } + + static positive(a) { + return Math.abs(Number(a)); + } } diff --git a/module/systemRegistration/handlebars.mjs b/module/systemRegistration/handlebars.mjs index 24047827..dd19e429 100644 --- a/module/systemRegistration/handlebars.mjs +++ b/module/systemRegistration/handlebars.mjs @@ -29,7 +29,6 @@ export const preloadHandlebarsTemplates = async function () { 'systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs', 'systems/daggerheart/templates/dialogs/downtime/activities.hbs', 'systems/daggerheart/templates/dialogs/dice-roll/costSelection.hbs', - 'systems/daggerheart/templates/ui/chat/parts/roll-part.hbs', 'systems/daggerheart/templates/ui/chat/parts/damage-part.hbs', 'systems/daggerheart/templates/ui/chat/parts/target-part.hbs', diff --git a/styles/less/ui/chat/damage-summary.less b/styles/less/ui/chat/damage-summary.less new file mode 100644 index 00000000..02fdbadf --- /dev/null +++ b/styles/less/ui/chat/damage-summary.less @@ -0,0 +1,87 @@ +@import '../../utils/colors.less'; + +#interface.theme-light { + .daggerheart.chat.damage-summary .token-target-container { + &:hover { + background: @dark-blue-10; + } + + header { + .actor-name { + color: @dark; + } + + &::after { + background: @dark-blue; + } + } + } +} + +.daggerheart.chat.damage-summary { + display: flex; + flex-direction: column; + gap: 5px; + padding: 0; + + .token-target-container { + display: flex; + flex-direction: column; + gap: 2px; + cursor: pointer; + transition: all 0.3s ease; + border-radius: 6px; + + &:hover { + background: @golden-10; + } + + header { + display: flex; + align-items: center; + gap: 5px; + pointer-events: none; + position: relative; + margin-bottom: 10px; + + img { + width: 40px; + height: 40px; + padding: 0; + border-radius: 50%; + } + + .actor-name { + margin: 0; + color: @beige; + font-size: var(--font-size-20); + padding: 8px; + } + + &::after { + content: ''; + position: absolute; + bottom: -10px; + background: @golden; + mask-image: linear-gradient(270deg, transparent 0%, black 50%, transparent 100%); + height: 2px; + width: 100%; + } + } + + .damage-container { + display: flex; + flex-direction: column; + justify-content: center; + gap: 5px; + pointer-events: none; + margin-top: 5px; + list-style: disc; + + .damage-row { + padding: 0 2px; + gap: 4px; + } + } + } +} diff --git a/styles/less/ui/chat/effect-summary.less b/styles/less/ui/chat/effect-summary.less new file mode 100644 index 00000000..9bea1fd9 --- /dev/null +++ b/styles/less/ui/chat/effect-summary.less @@ -0,0 +1,166 @@ +@import '../../utils/colors.less'; + +#interface.theme-light { + .daggerheart.chat.effect-summary { + .effect-header, + .actor-header { + &::before, + &::after { + height: 2px; + background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, @dark-blue 100%); + } + + &::after { + background: linear-gradient(90deg, @dark-blue 0%, rgba(0, 0, 0, 0) 100%); + } + + span { + color: @dark; + } + } + + .token-target-container, + .effect-target-container { + .effect-label .title, + .title { + color: @dark-blue; + } + + .effect-label { + border-color: @dark-blue; + } + + &:hover { + background: @dark-blue-10; + } + } + } +} + +.daggerheart.chat.effect-summary { + display: flex; + flex-direction: column; + + .effect-header, + .actor-header { + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 5px; + + &::before, + &::after { + content: ''; + flex: 1; + height: 2px; + background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, @golden 100%); + } + + &::after { + background: linear-gradient(90deg, @golden 0%, rgba(0, 0, 0, 0) 100%); + } + + span { + color: @beige; + padding: 0 10px; + white-space: nowrap; + } + } + + .effects-container { + display: flex; + flex-wrap: wrap; + gap: 5px; + margin-bottom: 8px; + } + + .targets-container { + display: flex; + flex-direction: column; + gap: 5px; + } + + .token-target-container { + display: flex; + align-items: center; + gap: 13px; + border-radius: 6px; + padding: 0 2px; + border-radius: 6px; + background: transparent; + transition: all 0.3s ease; + padding: 5px; + cursor: pointer; + transition: all 0.3s ease; + + &:hover { + background: @golden-10; + } + + img { + width: 40px; + height: 40px; + border-radius: 50%; + pointer-events: none; + } + + .title { + font-size: var(--font-size-20); + color: @golden; + font-weight: 700; + margin: 0; + pointer-events: none; + } + } + + details[open] { + .fa-chevron-down { + transform: rotate(180deg); + transition: all 0.3s ease; + } + } + + .effect-target-container { + width: 100%; + transition: all 0.3s ease; + cursor: pointer; + + &:hover { + background: @golden-10; + } + + .fa-chevron-down { + transition: all 0.3s ease; + margin-left: auto; + } + + .effect-label { + display: flex; + flex-direction: row; + align-items: center; + margin: 8px 8px 0; + padding-bottom: 5px; + width: -webkit-fill-available; + gap: 13px; + border-bottom: 1px solid @golden; + + .effect-img { + width: 40px; + height: 40px; + border-radius: 3px; + object-fit: cover; + } + + .title { + font-size: var(--font-size-20); + color: @golden; + font-weight: 700; + margin: 0; + } + } + + .description { + padding: 8px; + } + } +} diff --git a/styles/less/ui/index.less b/styles/less/ui/index.less index 0a89afc3..296ef325 100644 --- a/styles/less/ui/index.less +++ b/styles/less/ui/index.less @@ -1,7 +1,9 @@ @import './chat/ability-use.less'; @import './chat/action.less'; @import './chat/chat.less'; +@import './chat/damage-summary.less'; @import './chat/downtime.less'; +@import './chat/effect-summary.less'; @import './chat/refresh-message.less'; @import './chat/sheet.less'; diff --git a/system.json b/system.json index 4eeeff1a..2caeca2b 100644 --- a/system.json +++ b/system.json @@ -266,7 +266,8 @@ "dualityRoll": {}, "adversaryRoll": {}, "damageRoll": {}, - "abilityUse": {} + "abilityUse": {}, + "systemMessage": {} } }, "background": "systems/daggerheart/assets/logos/FoundrybornBackgroundLogo.png", diff --git a/templates/settings/automation-settings/general.hbs b/templates/settings/automation-settings/general.hbs index 211ee68e..8921ab6a 100644 --- a/templates/settings/automation-settings/general.hbs +++ b/templates/settings/automation-settings/general.hbs @@ -7,7 +7,11 @@ {{formGroup settingFields.schema.fields.hopeFear.fields.gm value=settingFields._source.hopeFear.gm localize=true}} {{formGroup settingFields.schema.fields.hopeFear.fields.players value=settingFields._source.hopeFear.players localize=true}} - + +