From faab60b45bd9f35374fea0808de300c1f3edaeaf Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 24 May 2025 10:25:40 +0200 Subject: [PATCH 1/5] Updated attack rolls and damage rolls for adversaries --- daggerheart.mjs | 1 + lang/en.json | 18 ++- module/applications/chatMessage.mjs | 7 +- module/applications/damageSelectionDialog.mjs | 2 +- .../applications/npcRollSelectionDialog.mjs | 24 ++-- module/applications/sheets/adversary.mjs | 14 ++- module/data/_module.mjs | 1 + module/data/adversary.mjs | 8 +- module/data/adversaryRoll.mjs | 39 +++++-- module/data/damageRoll.mjs | 42 +++++++ module/documents/actor.mjs | 42 ++++--- module/ui/chatLog.mjs | 49 +++++--- styles/application.less | 52 ++++----- styles/chat.less | 23 +++- styles/daggerheart.css | 105 +++++++++++------- template.json | 3 +- templates/chat/adversary-attack-roll.hbs | 37 +++--- templates/chat/damage-roll.hbs | 37 ++++-- templates/chat/duality-roll.hbs | 6 +- templates/sheets/adversary.hbs | 27 ++--- templates/sheets/environment.hbs | 4 +- templates/views/npcRollSelection.hbs | 19 ++-- 22 files changed, 356 insertions(+), 204 deletions(-) create mode 100644 module/data/damageRoll.mjs diff --git a/daggerheart.mjs b/daggerheart.mjs index de8cbe4f..c17117ac 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -74,6 +74,7 @@ Hooks.once('init', () => { CONFIG.ChatMessage.dataModels = { dualityRoll: models.DhpDualityRoll, adversaryRoll: models.DhpAdversaryRoll, + damageRoll: models.DhpDamageRoll, abilityUse: models.DhpAbilityUse }; CONFIG.ChatMessage.documentClass = applications.DhpChatMessage; diff --git a/lang/en.json b/lang/en.json index 691afd13..a5b81d59 100644 --- a/lang/en.json +++ b/lang/en.json @@ -86,7 +86,8 @@ "SecondaryEquipWhileTwohanded": "A secondary weapon can't be equipped together with a Two-Handed weapon.", "TwohandedEquipWhileSecondary": "Can't equip a Two-Handed weapon together with a secondary weapon.", "SelectClassBeforeSubclass": "Select a Class before selecting a Subclass.", - "SubclassNotOfClass": "This Subclass doesn't belong to your current Class." + "SubclassNotOfClass": "This Subclass doesn't belong to your current Class.", + "AttackTargetDoesNotExist": "The target token no longer exists" }, "Error": { "NoClassSelected": "Your character has no class selected!", @@ -101,8 +102,14 @@ "Hope": "Hope", "Fear": "Fear", "CriticalSuccess": "Critical Success", - "Advantage": "Advantage", - "Disadvantage": "Disadvantage", + "Advantage": { + "Full": "Advantage", + "Short": "Adv" + }, + "Disadvantage": { + "Full": "Disadvantage", + "Short": "Dis" + }, "OK": "OK", "Cancel": "Cancel", "Or": "Or", @@ -748,6 +755,7 @@ "AdvantageChooseTitle": "Select Hope Dice" }, "DamageRoll": { + "DealDamageToTargets": "Damage Hit Targets", "DealDamage": "Deal Damage" }, "HealingRoll": { @@ -899,8 +907,8 @@ "Stress": "Stress", "Experience": "Experience", "Experiences": "Experiences", - "Moves": "Moves", - "NewMove": "New Move" + "Features": "Features", + "NewFeature": "New Feature" }, "Environment": { "ToneAndFeel": "Tone And feel", diff --git a/module/applications/chatMessage.mjs b/module/applications/chatMessage.mjs index e83e174f..004e7dd5 100644 --- a/module/applications/chatMessage.mjs +++ b/module/applications/chatMessage.mjs @@ -1,6 +1,11 @@ export default class DhpChatMesssage extends ChatMessage { async renderHTML() { - if (this.type === 'dualityRoll' || this.type === 'adversaryRoll' || this.type === 'abilityUse') { + if ( + this.type === 'dualityRoll' || + this.type === 'adversaryRoll' || + this.type === 'damageRoll' || + this.type === 'abilityUse' + ) { this.content = await foundry.applications.handlebars.renderTemplate(this.content, this.system); } diff --git a/module/applications/damageSelectionDialog.mjs b/module/applications/damageSelectionDialog.mjs index 4540ae6d..5553c98f 100644 --- a/module/applications/damageSelectionDialog.mjs +++ b/module/applications/damageSelectionDialog.mjs @@ -1,7 +1,7 @@ const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api; export default class DamageSelectionDialog extends HandlebarsApplicationMixin(ApplicationV2) { - constructor(rollString, bonusDamage, hope, resolve) { + constructor(rollString, bonusDamage, resolve, hope = 0) { super({}); this.data = { diff --git a/module/applications/npcRollSelectionDialog.mjs b/module/applications/npcRollSelectionDialog.mjs index bee3dd16..b7c62c76 100644 --- a/module/applications/npcRollSelectionDialog.mjs +++ b/module/applications/npcRollSelectionDialog.mjs @@ -6,7 +6,6 @@ export default class NpcRollSelectionDialog extends FormApplication { this.resolve = resolve; this.selectedExperiences = []; this.data = { - nrDice: 1, advantage: null }; } @@ -19,7 +18,7 @@ export default class NpcRollSelectionDialog extends FormApplication { const defaults = super.defaultOptions; const overrides = { height: 'auto', - width: 400, + width: 500, id: 'roll-selection', template: 'systems/daggerheart/templates/views/npcRollSelection.hbs', closeOnSubmit: false, @@ -34,11 +33,11 @@ export default class NpcRollSelectionDialog extends FormApplication { async getData() { const context = super.getData(); - context.nrDice = this.data.nrDice; context.advantage = this.data.advantage; - context.experiences = this.experiences.map(x => ({ + context.experiences = Object.values(this.experiences).map(x => ({ ...x, - selected: this.selectedExperiences.find(selected => selected.id === x.id) + selected: this.selectedExperiences.find(selected => selected.id === x.id), + value: `${x.value >= 0 ? '+' : '-'}${x.value}` })); return context; @@ -47,17 +46,10 @@ export default class NpcRollSelectionDialog extends FormApplication { activateListeners(html) { super.activateListeners(html); - html.find('.increase').click(_ => this.updateNrDice(1)); - html.find('.decrease').click(_ => this.updateNrDice(-1)); html.find('.advantage').click(_ => this.updateIsAdvantage(true)); html.find('.disadvantage').click(_ => this.updateIsAdvantage(false)); html.find('.roll-button').click(this.finish.bind(this)); - html.find('.roll-dialog-chip').click(this.selectExperience.bind(this)); - } - - updateNrDice(value) { - this.data.nrDice += value; - this.render(); + html.find('.experience-chip').click(this.selectExperience.bind(this)); } updateIsAdvantage(advantage) { @@ -66,9 +58,9 @@ export default class NpcRollSelectionDialog extends FormApplication { } selectExperience(event) { - const experience = this.experiences[event.currentTarget.dataset.key]; - this.selectedExperiences = this.selectedExperiences.find(x => x.name === experience.name) - ? this.selectedExperiences.filter(x => x.name !== experience.name) + const experience = Object.values(this.experiences).find(experience => experience.id === event.currentTarget.id); + this.selectedExperiences = this.selectedExperiences.find(x => x.id === experience.id) + ? this.selectedExperiences.filter(x => x.id !== experience.id) : [...this.selectedExperiences, experience]; this.render(); diff --git a/module/applications/sheets/adversary.mjs b/module/applications/sheets/adversary.mjs index 25afe393..c1637dc9 100644 --- a/module/applications/sheets/adversary.mjs +++ b/module/applications/sheets/adversary.mjs @@ -349,7 +349,7 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) { static async attackRoll(event, button) { const modifier = Number.parseInt(button.dataset.value); - const { roll, diceResults, modifiers } = await this.actor.diceRoll( + const { roll, dice, advantageState, modifiers } = await this.actor.diceRoll( { title: `${this.actor.name} - Attack Roll`, value: modifier }, event.shiftKey ); @@ -365,11 +365,14 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) { const cls = getDocumentClass('ChatMessage'); const msg = new cls({ type: 'adversaryRoll', + sound: CONFIG.sounds.dice, system: { + origin: this.document.id, roll: roll._formula, + advantageState, total: roll._total, modifiers: modifiers, - diceResults: diceResults, + dice: dice, targets: targets, damage: { value: button.dataset.damage, type: button.dataset.damageType } }, @@ -381,16 +384,15 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) { } static async addExperience() { + const experienceId = foundry.utils.randomID(); await this.document.update({ - 'system.experiences': [...this.document.system.experiences, { name: 'Experience', value: 1 }] + [`system.experiences.${experienceId}`]: { id: experienceId, name: 'Experience', value: 1 } }); } static async removeExperience(_, button) { await this.document.update({ - 'system.experiences': this.document.system.experiences.filter( - (_, index) => index !== Number.parseInt(button.dataset.experience) - ) + [`system.experiences.-=${button.dataset.experience}`]: null }); } diff --git a/module/data/_module.mjs b/module/data/_module.mjs index bc3839d7..4822229f 100644 --- a/module/data/_module.mjs +++ b/module/data/_module.mjs @@ -14,5 +14,6 @@ export { default as DhpWeapon } from './weapon.mjs'; export { default as DhpArmor } from './armor.mjs'; export { default as DhpDualityRoll } from './dualityRoll.mjs'; export { default as DhpAdversaryRoll } from './adversaryRoll.mjs'; +export { default as DhpDamageRoll } from './damageRoll.mjs'; export { default as DhpAbilityUse } from './abilityUse.mjs'; export { default as DhpEnvironment } from './environment.mjs'; diff --git a/module/data/adversary.mjs b/module/data/adversary.mjs index 6d15cf3c..f625c28f 100644 --- a/module/data/adversary.mjs +++ b/module/data/adversary.mjs @@ -35,20 +35,20 @@ export default class DhpAdversary extends foundry.abstract.TypeDataModel { }), difficulty: new fields.NumberField({ initial: 1, integer: true }), damageThresholds: new fields.SchemaField({ - minor: new fields.NumberField({ initial: 0, integer: true }), major: new fields.NumberField({ initial: 0, integer: true }), severe: new fields.NumberField({ initial: 0, integer: true }) }), - experiences: new fields.ArrayField( + experiences: new fields.TypedObjectField( new fields.SchemaField({ - name: new fields.StringField({}), + id: new fields.StringField({ required: true }), + name: new fields.StringField(), value: new fields.NumberField({ integer: true, nullable: true, initial: null }) }) ) }; } - get moves() { + get features() { return this.parent.items.filter(x => x.type === 'feature'); } } diff --git a/module/data/adversaryRoll.mjs b/module/data/adversaryRoll.mjs index a218f65c..3619b143 100644 --- a/module/data/adversaryRoll.mjs +++ b/module/data/adversaryRoll.mjs @@ -3,6 +3,7 @@ export default class DhpAdversaryRoll extends foundry.abstract.TypeDataModel { const fields = foundry.data.fields; return { + origin: new fields.StringField({ required: true }), roll: new fields.StringField({}), total: new fields.NumberField({ integer: true }), modifiers: new fields.ArrayField( @@ -12,12 +13,8 @@ export default class DhpAdversaryRoll extends foundry.abstract.TypeDataModel { title: new fields.StringField({}) }) ), - diceResults: new fields.ArrayField( - new fields.SchemaField({ - value: new fields.NumberField({ integer: true }), - discarded: new fields.BooleanField({ initial: false }) - }) - ), + advantageState: new fields.NumberField({ required: true, choices: [0, 1, 2], initial: 0 }), + dice: new fields.EmbeddedDataField(DhpAdversaryRollDice), targets: new fields.ArrayField( new fields.SchemaField({ id: new fields.StringField({}), @@ -39,17 +36,17 @@ export default class DhpAdversaryRoll extends foundry.abstract.TypeDataModel { } prepareDerivedData() { - const diceKeys = Object.keys(this.diceResults); + const diceKeys = Object.keys(this.dice.rolls); const highestIndex = 0; for (var index in diceKeys) { const resultIndex = Number.parseInt(index); if (highestIndex === resultIndex) continue; - const current = this.diceResults[resultIndex]; - const highest = this.diceResults[highestIndex]; + const current = this.dice.rolls[resultIndex]; + const highest = this.dice.rolls[highestIndex]; - if (current.value > highest.value) this.diceResults[highestIndex].discarded = true; - else this.diceResults[resultIndex].discarded = true; + if (current.value > highest.value) this.dice.rolls[highestIndex].discarded = true; + else this.dice.rolls[resultIndex].discarded = true; } this.targets.forEach(target => { @@ -57,3 +54,23 @@ export default class DhpAdversaryRoll extends foundry.abstract.TypeDataModel { }); } } + +class DhpAdversaryRollDice extends foundry.abstract.DataModel { + static defineSchema() { + const fields = foundry.data.fields; + + return { + type: new fields.StringField({ required: true }), + rolls: new fields.ArrayField( + new fields.SchemaField({ + value: new fields.NumberField({ required: true, integer: true }), + discarded: new fields.BooleanField({ initial: false }) + }) + ) + }; + } + + get rollTotal() { + return this.rolls.reduce((acc, roll) => acc + roll.value, 0); + } +} diff --git a/module/data/damageRoll.mjs b/module/data/damageRoll.mjs new file mode 100644 index 00000000..c44819c5 --- /dev/null +++ b/module/data/damageRoll.mjs @@ -0,0 +1,42 @@ +export default class DhpDamageRoll extends foundry.abstract.TypeDataModel { + static defineSchema() { + const fields = foundry.data.fields; + + return { + roll: new fields.StringField({ required: true }), + damage: new fields.SchemaField({ + total: new fields.NumberField({ required: true, integer: true }), + type: new fields.StringField({ choices: Object.keys(SYSTEM.GENERAL.damageTypes), integer: false }) + }), + dice: new fields.ArrayField(new fields.EmbeddedDataField(DhpDamageDice)), + modifiers: new fields.ArrayField( + new fields.SchemaField({ + value: new fields.NumberField({ required: true, integer: true }), + operator: new fields.StringField({ required: true, choices: ['+', '-', '*', '/'] }) + }) + ), + targets: new fields.ArrayField( + new fields.SchemaField({ + id: new fields.StringField({ required: true }), + name: new fields.StringField(), + img: new fields.StringField() + }) + ) + }; + } +} + +class DhpDamageDice extends foundry.abstract.DataModel { + static defineSchema() { + const fields = foundry.data.fields; + + return { + type: new fields.StringField({ required: true }), + rolls: new fields.ArrayField(new fields.NumberField({ required: true, integer: true })) + }; + } + + get rollTotal() { + return this.rolls.reduce((acc, roll) => acc + roll, 0); + } +} diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index cbcf0ed5..5c9429f9 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -68,7 +68,6 @@ export default class DhpActor extends Actor { } async npcRoll(modifier, shiftKey) { - let nrDice = 1; let advantage = null; const modifiers = [ @@ -84,7 +83,6 @@ export default class DhpActor extends Actor { }); const result = await dialogClosed; - nrDice = result.nrDice; advantage = result.advantage; result.experiences.forEach(x => modifiers.push({ @@ -95,13 +93,20 @@ export default class DhpActor extends Actor { ); } - const roll = new Roll( - `${nrDice}d20${advantage === true ? 'kh' : advantage === false ? 'kl' : ''} ${modifiers.map(x => `+ ${x.value}`).join(' ')}` + const roll = Roll.create( + `${advantage === true || advantage === false ? 2 : 1}d20${advantage === true ? 'kh' : advantage === false ? 'kl' : ''} ${modifiers.map(x => `+ ${x.value}`).join(' ')}` ); let rollResult = await roll.evaluate(); - const diceResults = rollResult.dice.flatMap(x => x.results.flatMap(result => ({ value: result.result }))); + const dice = []; + for (var i = 0; i < rollResult.terms.length; i++) { + const term = rollResult.terms[i]; + if (term.faces) { + dice.push({ type: `d${term.faces}`, rolls: term.results.map(x => ({ value: x.result })) }); + } + } - return { roll, diceResults: diceResults, modifiers: modifiers }; + // There is Only ever one dice term here + return { roll, dice: dice[0], modifiers, advantageState: advantage === true ? 1 : advantage === false ? 2 : 0 }; } async dualityRoll(modifier, shiftKey, bonusDamage = []) { @@ -245,14 +250,12 @@ export default class DhpActor extends Actor { }; } - async damageRoll(damage, shiftKey) { + async damageRoll(damage, targets, shiftKey) { let rollString = damage.value; let bonusDamage = damage.bonusDamage?.filter(x => x.initiallySelected) ?? []; if (!shiftKey) { const dialogClosed = new Promise((resolve, _) => { - new DamageSelectionDialog(rollString, bonusDamage, this.system.resources.hope.value, resolve).render( - true - ); + new DamageSelectionDialog(rollString, bonusDamage, resolve).render(true); }); const result = await dialogClosed; bonusDamage = result.bonusDamage; @@ -274,23 +277,30 @@ export default class DhpActor extends Actor { for (var i = 0; i < rollResult.terms.length; i++) { const term = rollResult.terms[i]; if (term.faces) { - dice.push({ type: `d${term.faces}`, value: term.total }); + dice.push({ type: `d${term.faces}`, rolls: term.results.map(x => x.result) }); } else if (term.operator) { } else if (term.number) { const operator = i === 0 ? '' : rollResult.terms[i - 1].operator; - modifiers.push(`${operator}${term.number}`); + modifiers.push({ value: term.number, operator: operator }); } } const cls = getDocumentClass('ChatMessage'); const msg = new cls({ + type: 'damageRoll', user: game.user.id, - content: await renderTemplate('systems/daggerheart/templates/chat/damage-roll.hbs', { + sound: CONFIG.sounds.dice, + system: { roll: rollString, - total: rollResult.total, + damage: { + total: rollResult.total, + type: damage.type + }, dice: dice, - modifiers: modifiers - }), + modifiers: modifiers, + targets: targets + }, + content: 'systems/daggerheart/templates/chat/damage-roll.hbs', rolls: [roll] }); diff --git a/module/ui/chatLog.mjs b/module/ui/chatLog.mjs index 49972386..e6553e3c 100644 --- a/module/ui/chatLog.mjs +++ b/module/ui/chatLog.mjs @@ -17,16 +17,21 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo html.querySelectorAll('.roll-damage-button').forEach(element => element.addEventListener('click', event => this.onRollDamage(event, data.message)) ); - html.querySelectorAll('.target-container').forEach(element => - element.addEventListener('hover', hover(this.hoverTarget, this.unhoverTarget)) - ); // ???? - // html.find('.target-container').mouseout(this.unhoverTarget); - html.querySelectorAll('.damage-button').forEach(element => element.addEventListener('click', this.onDamage)); + html.querySelectorAll('.target-container').forEach(element => { + element.addEventListener('mouseenter', this.hoverTarget); + element.addEventListener('mouseleave', this.unhoverTarget); + element.addEventListener('click', this.clickTarget); + }); + html.querySelectorAll('.damage-button').forEach(element => + element.addEventListener('click', event => this.onDamage(event, data.message)) + ); html.querySelectorAll('.healing-button').forEach(element => element.addEventListener('click', this.onHealing)); html.querySelectorAll('.target-indicator').forEach(element => element.addEventListener('click', this.onToggleTargets) ); - html.querySelectorAll('.advantage').forEach(element => element.hover(this.hoverAdvantage)); // ?? + html.querySelectorAll('.advantage').forEach(element => + element.addEventListener('mouseenter', this.hoverAdvantage) + ); html.querySelectorAll('.advantage').forEach(element => element.addEventListener('click', event => this.selectAdvantage.bind(this)(event, data.message)) ); @@ -46,31 +51,49 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo onRollDamage = async (event, message) => { event.stopPropagation(); + const actor = game.actors.get(message.system.origin); + if (!actor || !game.user.isGM) return true; - await game.user.character.damageRoll(message.system.damage, event.shiftKey); + await actor.damageRoll( + message.system.damage, + message.system.targets.filter(x => x.hit).map(x => ({ id: x.id, name: x.name, img: x.img })), + event.shiftKey + ); }; hoverTarget = event => { event.stopPropagation(); const token = canvas.tokens.get(event.currentTarget.dataset.token); - if (!token.controlled) token._onHoverIn(event, { hoverOutOthers: true }); + 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); + if (!token?.controlled) token._onHoverOut(event); }; - onDamage = async event => { + clickTarget = event => { event.stopPropagation(); - const damage = Number.parseInt(event.currentTarget.dataset.value); - const targets = Array.from(game.user.targets); + const token = canvas.tokens.get(event.currentTarget.dataset.token); + if (!token) { + ui.notifications.info(game.i18n.localize('DAGGERHEART.Notification.Info.AttackTargetDoesNotExist')); + return; + } + + game.canvas.pan(token); + }; + + onDamage = async (event, message) => { + event.stopPropagation(); + const targets = event.currentTarget.dataset.targetHit + ? message.system.targets.map(target => game.canvas.tokens.get(target.id)) + : Array.from(game.user.targets); if (targets.length === 0) ui.notifications.info(game.i18n.localize('DAGGERHEART.Notification.Info.NoTargetsSelected')); for (var target of targets) { - await target.actor.takeDamage(damage, event.currentTarget.dataset.type); + await target.actor.takeDamage(message.system.damage.total, message.system.damage.type); } }; diff --git a/styles/application.less b/styles/application.less index a0f61883..82da864b 100644 --- a/styles/application.less +++ b/styles/application.less @@ -296,6 +296,7 @@ div.daggerheart.views.multiclass { display: flex; align-items: center; margin-bottom: @fullMargin; + gap: 16px; .dice-container { display: flex; @@ -306,6 +307,7 @@ div.daggerheart.views.multiclass { position: relative; display: flex; align-items: center; + justify-content: center; i { font-size: 18px; @@ -319,12 +321,24 @@ div.daggerheart.views.multiclass { .dice-number { position: absolute; - top: calc(50% - 14px); - left: calc(50% - 7px); font-size: 24px; font-weight: bold; } } + + .advantage-container { + display: flex; + flex-direction: column; + gap: 2px; + flex: 1; + + .advantage-button { + &.active, + &:hover { + background: var(--button-hover-background-color); + } + } + } } } @@ -333,33 +347,19 @@ div.daggerheart.views.multiclass { align-items: flex-start; flex-wrap: wrap; gap: @halfMargin; - flex: 2; + flex: 1; height: 100%; - .roll-dialog-chip { - border: @thinBorder solid black; - border-radius: 6px; - flex-basis: calc(50% - 2px); - display: flex; - align-items: center; - justify-content: space-between; - cursor: pointer; - padding: @fullPadding; - background: grey; - overflow: hidden; + .experience-chip { + opacity: 0.6; + border-radius: 16px; + width: calc(50% - 4px); + white-space: nowrap; - &.hover { - filter: drop-shadow(0 0 3px @mainShadow); - } - - span { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - - &.selected i { - color: green; + &.active, + &:hover { + opacity: 1; + background: var(--button-hover-background-color); } } } diff --git a/styles/chat.less b/styles/chat.less index fe1483e3..e39fbc2e 100644 --- a/styles/chat.less +++ b/styles/chat.less @@ -30,7 +30,7 @@ &.roll { .dice-tooltip { - .dice-rolls { + .dice-rolls.duality { display: flex; align-items: center; justify-content: space-around; @@ -93,6 +93,11 @@ font-size: 16px; } } + + .attack-roll-advantage-container { + text-align: end; + font-weight: bold; + } } .dice-total { @@ -159,8 +164,20 @@ } } - .roll-damage-button { - margin-top: 5px; + .dice-actions { + display: flex; + gap: 4px; + + button { + flex: 1; + } + } + + .dice-result { + .roll-damage-button, + .damage-button { + margin-top: 5px; + } } } diff --git a/styles/daggerheart.css b/styles/daggerheart.css index a1b1f200..745fee57 100644 --- a/styles/daggerheart.css +++ b/styles/daggerheart.css @@ -1760,55 +1760,59 @@ .daggerheart.chat.downtime .downtime-refresh-container .refresh-title { font-weight: bold; } -.daggerheart.chat.roll .dice-tooltip .dice-rolls { +.daggerheart.chat.roll .dice-tooltip .dice-rolls.duality { display: flex; align-items: center; justify-content: space-around; } -.daggerheart.chat.roll .dice-tooltip .dice-rolls .dice-hope-container { +.daggerheart.chat.roll .dice-tooltip .dice-rolls.duality .dice-hope-container { display: flex; } -.daggerheart.chat.roll .dice-tooltip .dice-rolls .dice-hope-container .roll.die:not(:last-of-type) { +.daggerheart.chat.roll .dice-tooltip .dice-rolls.duality .dice-hope-container .roll.die:not(:last-of-type) { margin-right: 8px; } -.daggerheart.chat.roll .dice-tooltip .dice-rolls .modifiers-container { +.daggerheart.chat.roll .dice-tooltip .dice-rolls.duality .modifiers-container { display: flex; } -.daggerheart.chat.roll .dice-tooltip .dice-rolls .modifiers-container .modifier-value:not(:last-of-type) { +.daggerheart.chat.roll .dice-tooltip .dice-rolls.duality .modifiers-container .modifier-value:not(:last-of-type) { margin-right: 8px; } -.daggerheart.chat.roll .dice-tooltip .dice-rolls .roll.die.hope { +.daggerheart.chat.roll .dice-tooltip .dice-rolls.duality .roll.die.hope { color: white; -webkit-text-stroke-color: #008080; -webkit-text-stroke-width: 1.5px; font-weight: 400; } -.daggerheart.chat.roll .dice-tooltip .dice-rolls .roll.die.fear { +.daggerheart.chat.roll .dice-tooltip .dice-rolls.duality .roll.die.fear { color: white; -webkit-text-stroke-color: #430070; -webkit-text-stroke-width: 1.5px; font-weight: 400; } -.daggerheart.chat.roll .dice-tooltip .dice-rolls .roll.die.disadvantage { +.daggerheart.chat.roll .dice-tooltip .dice-rolls.duality .roll.die.disadvantage { color: white; -webkit-text-stroke-color: #b30000; -webkit-text-stroke-width: 1.5px; font-weight: 400; } -.daggerheart.chat.roll .dice-tooltip .dice-rolls .roll.die.advantage { +.daggerheart.chat.roll .dice-tooltip .dice-rolls.duality .roll.die.advantage { color: white; -webkit-text-stroke-color: green; -webkit-text-stroke-width: 1.5px; font-weight: 400; } -.daggerheart.chat.roll .dice-tooltip .dice-rolls .roll.die.unused { +.daggerheart.chat.roll .dice-tooltip .dice-rolls.duality .roll.die.unused { opacity: 0.3; } -.daggerheart.chat.roll .dice-tooltip .dice-rolls .modifier-value { +.daggerheart.chat.roll .dice-tooltip .dice-rolls.duality .modifier-value { text-align: center; font-weight: bold; font-size: 16px; } +.daggerheart.chat.roll .dice-tooltip .attack-roll-advantage-container { + text-align: end; + font-weight: bold; +} .daggerheart.chat.roll .dice-total .dice-total-value .hope { color: #008080; } @@ -1859,7 +1863,15 @@ justify-content: center; margin-right: 32px; } -.daggerheart.chat.roll .roll-damage-button { +.daggerheart.chat.roll .dice-actions { + display: flex; + gap: 4px; +} +.daggerheart.chat.roll .dice-actions button { + flex: 1; +} +.daggerheart.chat.roll .dice-result .roll-damage-button, +.daggerheart.chat.roll .dice-result .damage-button { margin-top: 5px; } .daggerheart.chat.domain-card { @@ -2227,6 +2239,7 @@ div.daggerheart.views.multiclass { display: flex; align-items: center; margin-bottom: 8px; + gap: 16px; } .daggerheart.views.npc-roll-selection .npc-roll-dialog-container .selection-container .dice-container { display: flex; @@ -2241,6 +2254,7 @@ div.daggerheart.views.multiclass { position: relative; display: flex; align-items: center; + justify-content: center; } .daggerheart.views.npc-roll-selection .npc-roll-dialog-container @@ -2267,52 +2281,57 @@ div.daggerheart.views.multiclass { .dice-inner-container .dice-number { position: absolute; - top: calc(50% - 14px); - left: calc(50% - 7px); font-size: 24px; font-weight: bold; } +.daggerheart.views.npc-roll-selection + .npc-roll-dialog-container + .selection-container + .dice-container + .advantage-container { + display: flex; + flex-direction: column; + gap: 2px; + flex: 1; +} +.daggerheart.views.npc-roll-selection + .npc-roll-dialog-container + .selection-container + .dice-container + .advantage-container + .advantage-button.active, +.daggerheart.views.npc-roll-selection + .npc-roll-dialog-container + .selection-container + .dice-container + .advantage-container + .advantage-button:hover { + background: var(--button-hover-background-color); +} .daggerheart.views.npc-roll-selection .npc-roll-dialog-container .roll-dialog-experience-container { display: flex; align-items: flex-start; flex-wrap: wrap; gap: 4px; - flex: 2; + flex: 1; height: 100%; } -.daggerheart.views.npc-roll-selection .npc-roll-dialog-container .roll-dialog-experience-container .roll-dialog-chip { - border: 1px solid black; - border-radius: 6px; - flex-basis: calc(50% - 2px); - display: flex; - align-items: center; - justify-content: space-between; - cursor: pointer; - padding: 4px; - background: grey; - overflow: hidden; -} -.daggerheart.views.npc-roll-selection - .npc-roll-dialog-container - .roll-dialog-experience-container - .roll-dialog-chip.hover { - filter: drop-shadow(0 0 3px red); -} -.daggerheart.views.npc-roll-selection - .npc-roll-dialog-container - .roll-dialog-experience-container - .roll-dialog-chip - span { - overflow: hidden; +.daggerheart.views.npc-roll-selection .npc-roll-dialog-container .roll-dialog-experience-container .experience-chip { + opacity: 0.6; + border-radius: 16px; + width: calc(50% - 4px); white-space: nowrap; - text-overflow: ellipsis; } .daggerheart.views.npc-roll-selection .npc-roll-dialog-container .roll-dialog-experience-container - .roll-dialog-chip.selected - i { - color: green; + .experience-chip.active, +.daggerheart.views.npc-roll-selection + .npc-roll-dialog-container + .roll-dialog-experience-container + .experience-chip:hover { + opacity: 1; + background: var(--button-hover-background-color); } .daggerheart.views.multiclass .multiclass-container { margin-bottom: 16px; diff --git a/template.json b/template.json index e66dba4a..7e6c3c67 100644 --- a/template.json +++ b/template.json @@ -37,9 +37,10 @@ "combat": {} }, "ChatMessage": { - "types": ["dualityRoll", "adversaryRoll", "abilityUse"], + "types": ["dualityRoll", "adversaryRoll", "damageRoll", "abilityUse"], "dualityRoll": {}, "adversaryRoll": {}, + "damageRoll": {}, "abilityUse": {} } } diff --git a/templates/chat/adversary-attack-roll.hbs b/templates/chat/adversary-attack-roll.hbs index 8ed8f094..2951c803 100644 --- a/templates/chat/adversary-attack-roll.hbs +++ b/templates/chat/adversary-attack-roll.hbs @@ -1,20 +1,27 @@ -
+
{{roll}}
-
    -
    - {{#each diceResults}} -
  1. {{this.value}}
  2. - {{/each}} -
    -
    - {{#each modifiers}} -
  3. {{this.label}}
  4. - {{/each}} -
    -
+
+
+
+
+ {{this.dice.rolls.length}}{{this.dice.type}} + {{this.total}} +
+
+
    + {{#each this.dice.rolls}} +
  1. {{this.value}}
  2. + {{/each}} +
+
{{#if (eq this.advantageState 1)}}{{localize "DAGGERHEART.General.Advantage.Full"}}{{/if}}{{#if (eq this.advantageState 2)}}{{localize "DAGGERHEART.General.Disadvantage.Full"}}{{/if}}
+
+
+
+
+
{{total}}
@@ -30,6 +37,8 @@ {{/each}}
{{/if}} - +
+ +
\ No newline at end of file diff --git a/templates/chat/damage-roll.hbs b/templates/chat/damage-roll.hbs index b67f3e7f..81ea7c7a 100644 --- a/templates/chat/damage-roll.hbs +++ b/templates/chat/damage-roll.hbs @@ -1,19 +1,32 @@ -
+
{{this.roll}}
+
-
    - {{#each dice}} -
  1. {{this.value}}
  2. - {{/each}} - {{#each modifiers}} -
  3. {{this}}
  4. - {{/each}} -
+
+
+ {{#each dice}} +
+
+ {{this.rolls.length}}{{this.type}} + + {{this.rollTotal}} +
+
    + {{#each this.rolls}} +
  1. {{this}}
  2. + {{/each}} +
+
+ {{/each}} +
+
-
{{this.total}}
-
- + +
{{this.damage.total}}
+
+ +
\ No newline at end of file diff --git a/templates/chat/duality-roll.hbs b/templates/chat/duality-roll.hbs index 5f975821..8a6f6809 100644 --- a/templates/chat/duality-roll.hbs +++ b/templates/chat/duality-roll.hbs @@ -2,15 +2,15 @@
{{roll}}
-
    +
    1. {{hope.value}}
    2. {{fear.value}}
    3. {{#if advantage.value}} -
    4. {{ advantage.value}}
    5. +
    6. {{ advantage.value}}
    7. {{/if}} {{#if disadvantage.value}} -
    8. {{disadvantage.value}}
    9. +
    10. {{disadvantage.value}}
    11. {{/if}}
      diff --git a/templates/sheets/adversary.hbs b/templates/sheets/adversary.hbs index 7bb017c0..0f6fd81c 100644 --- a/templates/sheets/adversary.hbs +++ b/templates/sheets/adversary.hbs @@ -94,8 +94,6 @@
      - - @@ -124,21 +122,21 @@
      - +
      {{/each}}
      -

      {{localize "DAGGERHEART.Sheets.Adversary.Moves"}}

      - {{#each this.data.moves as |move key|}} +

      {{localize "DAGGERHEART.Sheets.Adversary.Features"}}

      + {{#each this.data.features as |feature key|}}
      - -
      {{move.name}}
      + +
      {{feature.name}}
      - - + +
      {{/each}} @@ -186,9 +184,6 @@
      - - {{this.data.damageThresholds.minor}} -
      {{this.data.damageThresholds.major}}
      @@ -228,11 +223,11 @@
      --}}
      -
      {{localize "DAGGERHEART.Sheets.Adversary.Moves"}}
      - {{#each this.data.moves as |move index|}} +
      {{localize "DAGGERHEART.Sheets.Adversary.Features"}}
      + {{#each this.data.features as |feature index|}}
      - -
      {{{move.system.description}}}
      + +
      {{{feature.system.description}}}
      {{/each}}
      diff --git a/templates/sheets/environment.hbs b/templates/sheets/environment.hbs index d5274926..5a0f3bbb 100644 --- a/templates/sheets/environment.hbs +++ b/templates/sheets/environment.hbs @@ -48,7 +48,7 @@
      -

      {{localize "DAGGERHEART.Sheets.Adversary.Moves"}}

      +

      {{localize "DAGGERHEART.Sheets.Adversary.Features"}}

      {{#each data.features as |feature key|}}
      @@ -97,7 +97,7 @@
      -
      {{localize "DAGGERHEART.Sheets.Adversary.Moves"}}
      +
      {{localize "DAGGERHEART.Sheets.Adversary.Features"}}
      {{#each data.features as |feature index|}}
      diff --git a/templates/views/npcRollSelection.hbs b/templates/views/npcRollSelection.hbs index 7dd8a90a..5bd86d98 100644 --- a/templates/views/npcRollSelection.hbs +++ b/templates/views/npcRollSelection.hbs @@ -4,23 +4,20 @@
      -
      {{this.nrDice}}
      +
      d20
      -
      - - -
      -
      -
      Adv
      -
      Dis
      +
      + +
      - {{#each this.experiences as |experience key|}} -
      + {{#each this.experiences as |experience|}} + + {{!--
      {{experience.name}} -
      +
      --}} {{/each}}
      From 707e47bc1b345fd4154066da6cf49619a7679a2e Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 24 May 2025 12:00:52 +0200 Subject: [PATCH 2/5] Updated NpcRollSelectionDialog.mjs to ApplicationV2 --- lang/en.json | 3 + module/applications/damageSelectionDialog.mjs | 61 --------------- .../applications/npcRollSelectionDialog.mjs | 76 ++++++++++--------- templates/views/npcRollSelection.hbs | 12 +-- 4 files changed, 48 insertions(+), 104 deletions(-) diff --git a/lang/en.json b/lang/en.json index 158c32f5..497a8003 100644 --- a/lang/en.json +++ b/lang/en.json @@ -711,6 +711,9 @@ "Title": "{actor} - Death Move", "TakeMove": "Take Death Move" }, + "RollSelection": { + "Title": "Roll Options" + }, "Settings": { "Title": "Daggerheart Settings" }, diff --git a/module/applications/damageSelectionDialog.mjs b/module/applications/damageSelectionDialog.mjs index 5553c98f..06d700f8 100644 --- a/module/applications/damageSelectionDialog.mjs +++ b/module/applications/damageSelectionDialog.mjs @@ -122,64 +122,3 @@ export default class DamageSelectionDialog extends HandlebarsApplicationMixin(Ap this.close(); } } - -// export default class DamageSelectionDialog extends FormApplication { -// constructor(rollString, bonusDamage, resolve){ -// super({}, {}); - -// this.data = { -// rollString, -// bonusDamage: bonusDamage.map(x => ({ -// ...x, -// hopeUses: 0 -// })), -// } -// this.resolve = resolve; -// } - -// get title (){ -// return 'Damage Options'; -// } - -// static get defaultOptions() { -// const defaults = super.defaultOptions; -// const overrides = { -// height: 'auto', -// width: 400, -// id: 'damage-selection', -// template: 'systems/daggerheart/templates/views/damageSelection.hbs', -// closeOnSubmit: false, -// classes: ["daggerheart", "views", "damage-selection"], -// }; - -// const mergedOptions = foundry.utils.mergeObject(defaults, overrides); - -// return mergedOptions; -// } - -// async getData(){ -// const context = super.getData(); -// context.rollString = this.data.rollString; -// context.bonusDamage = this.data.bonusDamage; - -// return context; -// } - -// activateListeners(html) { -// super.activateListeners(html); - -// html.find('.roll-button').click(this.finish.bind(this)); -// html.find('.').change(); -// } - -// // async _updateObject(_, formData) { -// // const data = foundry.utils.expandObject(formData); -// // this.data = foundry.utils.mergeObject(this.data, data); -// // this.render(true); -// // } - -// finish(){ -// this.resolve(this.data); -// this.close(); -// } -// } diff --git a/module/applications/npcRollSelectionDialog.mjs b/module/applications/npcRollSelectionDialog.mjs index b7c62c76..ad263fdf 100644 --- a/module/applications/npcRollSelectionDialog.mjs +++ b/module/applications/npcRollSelectionDialog.mjs @@ -1,9 +1,12 @@ -export default class NpcRollSelectionDialog extends FormApplication { - constructor(experiences, resolve, isNpc) { - super({}, {}); +const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; + +export default class NpcRollSelectionDialog extends HandlebarsApplicationMixin(ApplicationV2) { + constructor(experiences, resolve, reject) { + super({}); this.experiences = experiences; this.resolve = resolve; + this.reject = reject; this.selectedExperiences = []; this.data = { advantage: null @@ -11,28 +14,30 @@ export default class NpcRollSelectionDialog extends FormApplication { } get title() { - return 'Roll Options'; + return game.i18n.localize('DAGGERHEART.Application.Settings.Title'); } - static get defaultOptions() { - const defaults = super.defaultOptions; - const overrides = { - height: 'auto', - width: 500, - id: 'roll-selection', - template: 'systems/daggerheart/templates/views/npcRollSelection.hbs', - closeOnSubmit: false, - submitOnChange: true, - classes: ['daggerheart', 'views', 'npc-roll-selection'] - }; + static DEFAULT_OPTIONS = { + tag: 'form', + id: 'roll-selection', + classes: ['daggerheart', 'views', 'npc-roll-selection'], + position: { width: '500', height: 'auto' }, + actions: { + updateIsAdvantage: this.updateIsAdvantage, + selectExperience: this.selectExperience + }, + form: { handler: this.updateData, submitOnChange: false } + }; - const mergedOptions = foundry.utils.mergeObject(defaults, overrides); + static PARTS = { + main: { + id: 'main', + template: 'systems/daggerheart/templates/views/npcRollSelection.hbs' + } + }; - return mergedOptions; - } - - async getData() { - const context = super.getData(); + async _prepareContext(_options) { + const context = await super._prepareContext(_options); context.advantage = this.data.advantage; context.experiences = Object.values(this.experiences).map(x => ({ ...x, @@ -43,22 +48,14 @@ export default class NpcRollSelectionDialog extends FormApplication { return context; } - activateListeners(html) { - super.activateListeners(html); - - html.find('.advantage').click(_ => this.updateIsAdvantage(true)); - html.find('.disadvantage').click(_ => this.updateIsAdvantage(false)); - html.find('.roll-button').click(this.finish.bind(this)); - html.find('.experience-chip').click(this.selectExperience.bind(this)); - } - - updateIsAdvantage(advantage) { + static updateIsAdvantage(_, button) { + const advantage = Boolean(button.dataset.advantage); this.data.advantage = this.data.advantage === advantage ? null : advantage; this.render(); } - selectExperience(event) { - const experience = Object.values(this.experiences).find(experience => experience.id === event.currentTarget.id); + static selectExperience(_, button) { + const experience = Object.values(this.experiences).find(experience => experience.id === button.id); this.selectedExperiences = this.selectedExperiences.find(x => x.id === experience.id) ? this.selectedExperiences.filter(x => x.id !== experience.id) : [...this.selectedExperiences, experience]; @@ -66,8 +63,17 @@ export default class NpcRollSelectionDialog extends FormApplication { this.render(); } - finish() { + static async updateData() { this.resolve({ ...this.data, experiences: this.selectedExperiences }); - this.close(); + this.close({ updateClose: true }); + } + + async close(options = {}) { + const { updateClose, ...baseOptions } = options; + if (!updateClose) { + this.reject(); + } + + await super.close(baseOptions); } } diff --git a/templates/views/npcRollSelection.hbs b/templates/views/npcRollSelection.hbs index 5bd86d98..02f5238b 100644 --- a/templates/views/npcRollSelection.hbs +++ b/templates/views/npcRollSelection.hbs @@ -7,21 +7,17 @@
      d20
      - - + +
      {{#each this.experiences as |experience|}} - {{!--
      - {{experience.name}} - -
      --}} {{/each}}
      -
      - +
      +
      \ No newline at end of file From 5a501d67695b2396c980141ea4594fd06e77b874 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 24 May 2025 16:56:01 +0200 Subject: [PATCH 3/5] Fixed Check Rolls, Attack Rolls and Damage Rolls for PCs --- lang/en.json | 6 +- module/applications/sheets/adversary.mjs | 1 + module/applications/sheets/pc.mjs | 16 +++- module/data/adversaryRoll.mjs | 36 +++++--- module/data/damageRoll.mjs | 1 + module/data/dualityRoll.mjs | 18 +++- module/documents/actor.mjs | 10 +-- module/ui/chatLog.mjs | 1 + styles/chat.less | 11 ++- styles/daggerheart.css | 11 ++- templates/chat/adversary-attack-roll.hbs | 5 +- templates/chat/attack-roll.hbs | 77 +++++++++++------ templates/chat/damage-roll.hbs | 1 + templates/chat/duality-roll.hbs | 101 +++++++++++++---------- templates/sheets/adversary.hbs | 2 +- templates/sheets/parts/attributes.hbs | 2 +- 16 files changed, 204 insertions(+), 95 deletions(-) diff --git a/lang/en.json b/lang/en.json index 497a8003..a99266ed 100644 --- a/lang/en.json +++ b/lang/en.json @@ -735,9 +735,13 @@ }, "Chat": { "DualityRoll": { - "AdvantageChooseTitle": "Select Hope Dice" + "AbilityCheckTitle": "{ability} Check" + }, + "AttackRoll": { + "Title": "Attack - {attack}" }, "DamageRoll": { + "Title": "Damage - {damage}", "DealDamageToTargets": "Damage Hit Targets", "DealDamage": "Deal Damage" }, diff --git a/module/applications/sheets/adversary.mjs b/module/applications/sheets/adversary.mjs index c1637dc9..9066e194 100644 --- a/module/applications/sheets/adversary.mjs +++ b/module/applications/sheets/adversary.mjs @@ -367,6 +367,7 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) { type: 'adversaryRoll', sound: CONFIG.sounds.dice, system: { + title: button.dataset.name, origin: this.document.id, roll: roll._formula, advantageState, diff --git a/module/applications/sheets/pc.mjs b/module/applications/sheets/pc.mjs index 8112a697..36d8b49b 100644 --- a/module/applications/sheets/pc.mjs +++ b/module/applications/sheets/pc.mjs @@ -4,6 +4,7 @@ import DhpDowntime from '../downtime.mjs'; import DhpLevelup from '../levelup.mjs'; import AncestrySelectionDialog from '../ancestrySelectionDialog.mjs'; import DaggerheartSheet from './daggerheart-sheet.mjs'; +import { abilities } from '../../config/actorConfig.mjs'; const { ActorSheetV2 } = foundry.applications.sheets; export default class PCSheet extends DaggerheartSheet(ActorSheetV2) { @@ -480,9 +481,9 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) { this.render(); } - static async rollAttribute(event, target) { + static async rollAttribute(event, button) { const { roll, hope, fear, advantage, disadvantage, modifiers } = await this.document.dualityRoll( - { title: 'Attribute Bonus', value: event.target.dataset.value }, + { title: 'Attribute Bonus', value: button.dataset.value }, event.shiftKey ); @@ -490,6 +491,10 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) { const msgData = { type: 'dualityRoll', system: { + title: game.i18n.format('DAGGERHEART.Chat.DualityRoll.AbilityCheckTitle', { + ability: game.i18n.localize(abilities[button.dataset.attribute].label) + }), + origin: this.document.id, roll: roll._formula, modifiers: modifiers, hope: hope, @@ -550,8 +555,8 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) { await this.document.update({ [update]: newValue }); } - static async attackRoll(_, event) { - const weapon = await fromUuid(event.currentTarget.dataset.weapon); + static async attackRoll(event, button) { + const weapon = await fromUuid(button.dataset.weapon); const damage = { value: `${this.document.system.proficiency.value}${weapon.system.damage.value}`, type: weapon.system.damage.type, @@ -579,7 +584,10 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) { const cls = getDocumentClass('ChatMessage'); const msg = new cls({ type: 'dualityRoll', + sound: CONFIG.sounds.dice, system: { + title: weapon.name, + origin: this.document.id, roll: roll._formula, modifiers: modifiers, hope: hope, diff --git a/module/data/adversaryRoll.mjs b/module/data/adversaryRoll.mjs index 3619b143..e7086c55 100644 --- a/module/data/adversaryRoll.mjs +++ b/module/data/adversaryRoll.mjs @@ -3,6 +3,7 @@ export default class DhpAdversaryRoll extends foundry.abstract.TypeDataModel { const fields = foundry.data.fields; return { + title: new fields.StringField(), origin: new fields.StringField({ required: true }), roll: new fields.StringField({}), total: new fields.NumberField({ integer: true }), @@ -37,18 +38,31 @@ export default class DhpAdversaryRoll extends foundry.abstract.TypeDataModel { prepareDerivedData() { const diceKeys = Object.keys(this.dice.rolls); - const highestIndex = 0; - for (var index in diceKeys) { - const resultIndex = Number.parseInt(index); - if (highestIndex === resultIndex) continue; - - const current = this.dice.rolls[resultIndex]; - const highest = this.dice.rolls[highestIndex]; - - if (current.value > highest.value) this.dice.rolls[highestIndex].discarded = true; - else this.dice.rolls[resultIndex].discarded = true; + const highestDiceIndex = + diceKeys.length < 2 + ? null + : this.dice.rolls[diceKeys[0]].value > this.dice.rolls[diceKeys[1]].value + ? 0 + : 1; + if (highestDiceIndex !== null) { + this.dice.rolls = this.dice.rolls.map((roll, index) => ({ + ...roll, + discarded: this.advantageState === 1 ? index !== highestDiceIndex : index === highestDiceIndex + })); } + // const highestIndex = 0; + // for (var index in diceKeys) { + // const resultIndex = Number.parseInt(index); + // if (highestIndex === resultIndex) continue; + + // const current = this.dice.rolls[resultIndex]; + // const highest = this.dice.rolls[highestIndex]; + + // if (current.value > highest.value) this.dice.rolls[highestIndex].discarded = true; + // else this.dice.rolls[resultIndex].discarded = true; + // } + this.targets.forEach(target => { target.hit = target.difficulty ? this.total >= target.difficulty : this.total >= target.evasion; }); @@ -71,6 +85,6 @@ class DhpAdversaryRollDice extends foundry.abstract.DataModel { } get rollTotal() { - return this.rolls.reduce((acc, roll) => acc + roll.value, 0); + return this.rolls.reduce((acc, roll) => acc + (!roll.discarded ? roll.value : 0), 0); } } diff --git a/module/data/damageRoll.mjs b/module/data/damageRoll.mjs index c44819c5..20392b35 100644 --- a/module/data/damageRoll.mjs +++ b/module/data/damageRoll.mjs @@ -3,6 +3,7 @@ export default class DhpDamageRoll extends foundry.abstract.TypeDataModel { const fields = foundry.data.fields; return { + title: new fields.StringField(), roll: new fields.StringField({ required: true }), damage: new fields.SchemaField({ total: new fields.NumberField({ required: true, integer: true }), diff --git a/module/data/dualityRoll.mjs b/module/data/dualityRoll.mjs index d6c4471c..d071f84f 100644 --- a/module/data/dualityRoll.mjs +++ b/module/data/dualityRoll.mjs @@ -8,6 +8,8 @@ const diceField = () => export default class DhpDualityRoll extends foundry.abstract.TypeDataModel { static defineSchema() { return { + title: new fields.StringField(), + origin: new fields.StringField({ required: true }), roll: new fields.StringField({}), modifiers: new fields.ArrayField( new fields.SchemaField({ @@ -20,7 +22,6 @@ export default class DhpDualityRoll extends foundry.abstract.TypeDataModel { fear: diceField(), advantage: diceField(), disadvantage: diceField(), - advantageSelected: new fields.NumberField({ initial: 0 }), targets: new fields.ArrayField( new fields.SchemaField({ id: new fields.StringField({}), @@ -57,8 +58,16 @@ export default class DhpDualityRoll extends foundry.abstract.TypeDataModel { get total() { const modifiers = this.modifiers.reduce((acc, x) => acc + x.value, 0); - const advantage = (this.advantage.value ?? this.disadvantage.value) ? -this.disadvantage.value : 0; - return this.hope.value + this.fear.value + advantage + modifiers; + const advantage = this.advantage.value + ? this.advantage.value + : this.disadvantage.value + ? -this.disadvantage.value + : 0; + return this.highestRoll + advantage + modifiers; + } + + get highestRoll() { + return Math.max(this.hope.value, this.fear.value); } get totalLabel() { @@ -75,6 +84,9 @@ export default class DhpDualityRoll extends foundry.abstract.TypeDataModel { prepareDerivedData() { const total = this.total; + this.hope.discarded = this.hope.value < this.fear.value; + this.fear.discarded = this.fear.value < this.hope.value; + this.targets.forEach(target => { target.hit = target.difficulty ? total >= target.difficulty : total >= target.evasion; }); diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs index 5c9429f9..2da03417 100644 --- a/module/documents/actor.mjs +++ b/module/documents/actor.mjs @@ -207,10 +207,9 @@ export default class DhpActor extends Actor { } const hope = rollResult.dice[0].results[0].result; - const advantage = advantageDice ? rollResult.dice[1].results[0].result : null; - const disadvantage = disadvantageDice ? rollResult.dice[1].results[0].result : null; - const fear = - advantage || disadvantage ? rollResult.dice[2].results[0].result : rollResult.dice[1].results[0].result; + const fear = rollResult.dice[1].results[0].result; + const advantage = advantageDice ? rollResult.dice[2].results[0].result : null; + const disadvantage = disadvantageDice ? rollResult.dice[2].results[0].result : null; if (disadvantage) { rollResult = { ...rollResult, total: rollResult.total - Math.max(hope, disadvantage) }; @@ -250,7 +249,7 @@ export default class DhpActor extends Actor { }; } - async damageRoll(damage, targets, shiftKey) { + async damageRoll(title, damage, targets, shiftKey) { let rollString = damage.value; let bonusDamage = damage.bonusDamage?.filter(x => x.initiallySelected) ?? []; if (!shiftKey) { @@ -291,6 +290,7 @@ export default class DhpActor extends Actor { user: game.user.id, sound: CONFIG.sounds.dice, system: { + title: game.i18n.format('DAGGERHEART.Chat.DamageRoll.Title', { damage: title }), roll: rollString, damage: { total: rollResult.total, diff --git a/module/ui/chatLog.mjs b/module/ui/chatLog.mjs index e6553e3c..ec5e257f 100644 --- a/module/ui/chatLog.mjs +++ b/module/ui/chatLog.mjs @@ -55,6 +55,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo if (!actor || !game.user.isGM) return true; await actor.damageRoll( + message.system.title, message.system.damage, message.system.targets.filter(x => x.hit).map(x => ({ id: x.id, name: x.name, img: x.img })), event.shiftKey diff --git a/styles/chat.less b/styles/chat.less index e39fbc2e..1494966c 100644 --- a/styles/chat.less +++ b/styles/chat.less @@ -29,11 +29,14 @@ } &.roll { + .dice-flavor { + text-align: center; + font-weight: bold; + } .dice-tooltip { .dice-rolls.duality { display: flex; align-items: center; - justify-content: space-around; .dice-hope-container { display: flex; @@ -57,12 +60,18 @@ -webkit-text-stroke-color: @hope; -webkit-text-stroke-width: 1.5px; font-weight: 400; + &:not(.discarded) { + filter: none; + } } &.fear { color: white; -webkit-text-stroke-color: @fear; -webkit-text-stroke-width: 1.5px; font-weight: 400; + &:not(.discarded) { + filter: none; + } } &.disadvantage { color: white; diff --git a/styles/daggerheart.css b/styles/daggerheart.css index 745fee57..83bc6ee9 100644 --- a/styles/daggerheart.css +++ b/styles/daggerheart.css @@ -1760,10 +1760,13 @@ .daggerheart.chat.downtime .downtime-refresh-container .refresh-title { font-weight: bold; } +.daggerheart.chat.roll .dice-flavor { + text-align: center; + font-weight: bold; +} .daggerheart.chat.roll .dice-tooltip .dice-rolls.duality { display: flex; align-items: center; - justify-content: space-around; } .daggerheart.chat.roll .dice-tooltip .dice-rolls.duality .dice-hope-container { display: flex; @@ -1783,12 +1786,18 @@ -webkit-text-stroke-width: 1.5px; font-weight: 400; } +.daggerheart.chat.roll .dice-tooltip .dice-rolls.duality .roll.die.hope:not(.discarded) { + filter: none; +} .daggerheart.chat.roll .dice-tooltip .dice-rolls.duality .roll.die.fear { color: white; -webkit-text-stroke-color: #430070; -webkit-text-stroke-width: 1.5px; font-weight: 400; } +.daggerheart.chat.roll .dice-tooltip .dice-rolls.duality .roll.die.fear:not(.discarded) { + filter: none; +} .daggerheart.chat.roll .dice-tooltip .dice-rolls.duality .roll.die.disadvantage { color: white; -webkit-text-stroke-color: #b30000; diff --git a/templates/chat/adversary-attack-roll.hbs b/templates/chat/adversary-attack-roll.hbs index 2951c803..3e805de9 100644 --- a/templates/chat/adversary-attack-roll.hbs +++ b/templates/chat/adversary-attack-roll.hbs @@ -1,4 +1,5 @@
      +
      {{localize "DAGGERHEART.Chat.AttackRoll.Title" attack=this.title}}
      {{roll}}
      @@ -7,7 +8,7 @@
      {{this.dice.rolls.length}}{{this.dice.type}} - {{this.total}} + {{this.dice.rollTotal}}
        @@ -23,7 +24,7 @@
      -
      {{total}}
      +
      {{this.total}}
      {{#if (gt targets.length 0)}}
      diff --git a/templates/chat/attack-roll.hbs b/templates/chat/attack-roll.hbs index a32a111f..8a80b103 100644 --- a/templates/chat/attack-roll.hbs +++ b/templates/chat/attack-roll.hbs @@ -1,35 +1,64 @@ -
      +
      +
      {{localize "DAGGERHEART.Chat.AttackRoll.Title" attack=this.title}}
      {{roll}}
      -
      -
        -
        -
      1. {{hope.value}}
      2. + +
        +
        +
        +
        +
        + + 1{{hope.dice}} + | + 1{{fear.dice}} + + {{this.highestRoll}} +
        +
        +
          +
        1. {{hope.value}}
        2. +
        3. {{fear.value}}
        4. +
        +
        +
        {{#if advantage.value}} -
      3. {{ advantage.value}}
      4. +
        +
        + + 1{{advantage.dice}} + + {{advantage.value}} +
        +
        +
          +
        1. {{advantage.value}}
        2. +
        +
        +
        {{/if}} {{#if disadvantage.value}} -
      5. {{disadvantage.value}}
      6. +
        +
        + + 1{{disadvantage.dice}} + + {{disadvantage.value}} +
        +
        +
          +
        1. {{disadvantage.value}}
        2. +
        +
        +
        {{/if}} -
        -
      7. {{fear.value}}
      8. -
        - {{#each modifiers}} -
      9. {{this.label}}
      10. - {{/each}} -
        -
      + +
      {{totalLabel}}
      - {{#if total.alternate}} - {{#with dualityDiceStates}} - {{../total.normal}} - {{../total.alternate}} - {{/with}} - {{else}} - {{total.normal}} - {{/if}} + {{this.total}}
      {{#if (gt targets.length 0)}} @@ -44,6 +73,8 @@ {{/each}}
      {{/if}} - +
      + +
      \ No newline at end of file diff --git a/templates/chat/damage-roll.hbs b/templates/chat/damage-roll.hbs index 81ea7c7a..9097331d 100644 --- a/templates/chat/damage-roll.hbs +++ b/templates/chat/damage-roll.hbs @@ -1,4 +1,5 @@
      +
      {{this.title}}
      {{this.roll}}
      diff --git a/templates/chat/duality-roll.hbs b/templates/chat/duality-roll.hbs index 8a6f6809..eed124ff 100644 --- a/templates/chat/duality-roll.hbs +++ b/templates/chat/duality-roll.hbs @@ -1,7 +1,62 @@ -
      +
      +
      {{this.title}}
      {{roll}}
      -
      + +
      +
      +
      +
      +
      + + 1{{hope.dice}} + | + 1{{fear.dice}} + + {{this.highestRoll}} +
      +
      +
        +
      1. {{hope.value}}
      2. +
      3. {{fear.value}}
      4. +
      +
      +
      + {{#if advantage.value}} +
      +
      + + 1{{advantage.dice}} + + {{advantage.value}} +
      +
      +
        +
      1. {{advantage.value}}
      2. +
      +
      +
      + {{/if}} + {{#if disadvantage.value}} +
      +
      + + 1{{disadvantage.dice}} + + {{disadvantage.value}} +
      +
      +
        +
      1. {{disadvantage.value}}
      2. +
      +
      +
      + {{/if}} +
      +
      +
      + + {{!--
      1. {{hope.value}}
      2. @@ -19,7 +74,7 @@ {{/each}}
      -
      +
      --}}
      {{totalLabel}}
      @@ -27,42 +82,4 @@
      -
      - -{{!-- V1.3 --}} -{{!--
      -
      -
      {{roll}}
      -
      -
        -
        -
      1. {{hope.value}}
      2. - {{#if advantage.value}} -
      3. {{ advantage.value}}
      4. - {{/if}} - {{#if disadvantage.value}} -
      5. {{disadvantage.value}}
      6. - {{/if}} -
        -
      7. {{fear.value}}
      8. -
        - {{#each modifiers}} -
      9. {{this.label}}
      10. - {{/each}} -
        -
      -
      -
      -
      {{totalLabel}}
      -
      - {{#if total.alternate}} - {{#with dualityDiceStates}} - {{../total.normal}} - {{../total.alternate}} - {{/with}} - {{else}} - {{total.normal}} - {{/if}} -
      -
      -
      -
      --}} \ No newline at end of file +
      \ No newline at end of file diff --git a/templates/sheets/adversary.hbs b/templates/sheets/adversary.hbs index 0f6fd81c..a9c5619f 100644 --- a/templates/sheets/adversary.hbs +++ b/templates/sheets/adversary.hbs @@ -175,7 +175,7 @@
      +{{this.data.attack.attackModifier}} - +
      diff --git a/templates/sheets/parts/attributes.hbs b/templates/sheets/parts/attributes.hbs index 77d0248a..46a15317 100644 --- a/templates/sheets/parts/attributes.hbs +++ b/templates/sheets/parts/attributes.hbs @@ -7,7 +7,7 @@ {{#each this.attributes as |attribute key|}}
      - +
      {{key}}
      From 76674f0bf55196dae7bd95eb35cecd903224a0f4 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 24 May 2025 17:08:27 +0200 Subject: [PATCH 4/5] Cleaned up rollSelectionDialog.mjs --- module/applications/rollSelectionDialog.mjs | 133 +------------------- 1 file changed, 1 insertion(+), 132 deletions(-) diff --git a/module/applications/rollSelectionDialog.mjs b/module/applications/rollSelectionDialog.mjs index f8e6f218..ed35d710 100644 --- a/module/applications/rollSelectionDialog.mjs +++ b/module/applications/rollSelectionDialog.mjs @@ -62,7 +62,7 @@ export default class RollSelectionDialog extends HandlebarsApplicationMixin(Appl }; get title() { - return `Roll Options`; + return game.i18n.localize('DAGGERHEART.Application.RollSelection.Title'); } async _prepareContext(_options) { @@ -157,134 +157,3 @@ export default class RollSelectionDialog extends HandlebarsApplicationMixin(Appl this.close(); } } - -// V1.3 -// const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api; - -// export default class RollSelectionDialog extends HandlebarsApplicationMixin(ApplicationV2) { -// constructor(experiences, bonusDamage, hopeResource, resolve, isNpc){ -// super({}, {}); - -// this.experiences = experiences; -// this.resolve = resolve; -// this.isNpc; -// this.selectedExperiences = []; -// this.data = { -// diceOptions: [{ name: 'd12', value: 'd12' }, { name: 'd20', value: 'd20' }], -// hope: ['d12'], -// fear: ['d12'], -// advantage: null, -// disadvantage: null, -// bonusDamage: bonusDamage.reduce((acc, x) => { -// if(x.appliesOn === SYSTEM.EFFECTS.applyLocations.attackRoll.id){ -// acc.push(({ -// ...x, -// hopeUses: 0 -// })); -// } - -// return acc; -// }, []), -// hopeResource: hopeResource, -// }; -// } - -// static DEFAULT_OPTIONS = { -// tag: 'form', -// classes: ["daggerheart", "views", "roll-selection"], -// position: { -// width: 400, -// height: "auto" -// }, -// actions: { -// selectExperience: this.selectExperience, -// decreaseHopeUse: this.decreaseHopeUse, -// increaseHopeUse: this.increaseHopeUse, -// finish: this.finish, -// }, -// form: { -// handler: this.updateSelection, -// submitOnChange: true, -// closeOnSubmit: false, -// } -// }; - -// /** @override */ -// static PARTS = { -// damageSelection: { -// id: "damageSelection", -// template: "systems/daggerheart/templates/views/rollSelection.hbs" -// } -// } - -// get title() { -// return `Roll Options`; -// } - -// async _prepareContext(_options) { -// const context = await super._prepareContext(_options); -// context.isNpc = this.isNpc; -// context.diceOptions = this.data.diceOptions; -// context.hope = this.data.hope; -// context.fear = this.data.fear; -// context.advantage = this.data.advantage; -// context.disadvantage = this.data.disadvantage; -// context.experiences = this.experiences.map(x => ({ ...x, selected: this.selectedExperiences.find(selected => selected.id === x.id) })); -// context.bonusDamage = this.data.bonusDamage; -// context.hopeResource = this.data.hopeResource+1; -// context.hopeUsed = this.getHopeUsed(); - -// return context; -// } - -// static updateSelection(event, _, formData){ -// const { bonusDamage, ...rest } = foundry.utils.expandObject(formData.object); - -// for(var index in bonusDamage){ -// this.data.bonusDamage[index].initiallySelected = bonusDamage[index].initiallySelected; -// if(bonusDamage[index].hopeUses){ -// const value = Number.parseInt(bonusDamage[index].hopeUses); -// if(!Number.isNaN(value)) this.data.bonusDamage[index].hopeUses = value; -// } -// } - -// this.data = foundry.utils.mergeObject(this.data, rest); -// this.render(true); -// } - -// static selectExperience(_, button){ -// if(this.selectedExperiences.find(x => x.id === button.dataset.key)){ -// this.selectedExperiences = this.selectedExperiences.filter(x => x.id !== button.dataset.key); -// } else { -// this.selectedExperiences = [...this.selectedExperiences, this.experiences.find(x => x.id === button.dataset.key)]; -// } - -// this.render(); -// } - -// getHopeUsed(){ -// return this.data.bonusDamage.reduce((acc, x) => acc+x.hopeUses, 0); -// } - -// static decreaseHopeUse(_, button){ -// const index = Number.parseInt(button.dataset.index); -// if(this.data.bonusDamage[index].hopeUses - 1 >= 0) { -// this.data.bonusDamage[index].hopeUses -= 1; -// this.render(true); -// } -// } - -// static increaseHopeUse(_, button){ -// const index = Number.parseInt(button.dataset.index); -// if(this.data.bonusDamage[index].hopeUses <= this.data.hopeResource+1) { -// this.data.bonusDamage[index].hopeUses += 1; -// this.render(true); -// } -// } - -// static finish(){ -// const { diceOptions, ...rest } = this.data; -// this.resolve({ ...rest, experiences: this.selectedExperiences, hopeUsed: this.getHopeUsed(), bonusDamage: this.data.bonusDamage.reduce((acc, x) => acc.concat(` + ${1+x.hopeUses}${x.value}`), "") }); -// this.close(); -// } -// } From b696ba2ebc85595709a1cb12291a208691e3a5f6 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 24 May 2025 22:25:54 +0200 Subject: [PATCH 5/5] PR-Fixes --- .prettierignore | 3 ++- module/data/adversaryRoll.mjs | 12 ------------ templates/chat/duality-roll.hbs | 21 --------------------- 3 files changed, 2 insertions(+), 34 deletions(-) diff --git a/.prettierignore b/.prettierignore index a71f9f91..e792a467 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,4 +3,5 @@ package-lock.json package.json .github *.hbs -styles/daggerheart.css \ No newline at end of file +styles/daggerheart.css +src/packs \ No newline at end of file diff --git a/module/data/adversaryRoll.mjs b/module/data/adversaryRoll.mjs index e7086c55..bdb09c68 100644 --- a/module/data/adversaryRoll.mjs +++ b/module/data/adversaryRoll.mjs @@ -51,18 +51,6 @@ export default class DhpAdversaryRoll extends foundry.abstract.TypeDataModel { })); } - // const highestIndex = 0; - // for (var index in diceKeys) { - // const resultIndex = Number.parseInt(index); - // if (highestIndex === resultIndex) continue; - - // const current = this.dice.rolls[resultIndex]; - // const highest = this.dice.rolls[highestIndex]; - - // if (current.value > highest.value) this.dice.rolls[highestIndex].discarded = true; - // else this.dice.rolls[resultIndex].discarded = true; - // } - this.targets.forEach(target => { target.hit = target.difficulty ? this.total >= target.difficulty : this.total >= target.evasion; }); diff --git a/templates/chat/duality-roll.hbs b/templates/chat/duality-roll.hbs index eed124ff..8645e7a3 100644 --- a/templates/chat/duality-roll.hbs +++ b/templates/chat/duality-roll.hbs @@ -2,7 +2,6 @@
      {{this.title}}
      {{roll}}
      -
      @@ -55,26 +54,6 @@
      - - {{!--
      -
        -
        -
      1. {{hope.value}}
      2. -
      3. {{fear.value}}
      4. - {{#if advantage.value}} -
      5. {{ advantage.value}}
      6. - {{/if}} - {{#if disadvantage.value}} -
      7. {{disadvantage.value}}
      8. - {{/if}} -
        -
        - {{#each modifiers}} -
      9. {{this.label}}
      10. - {{/each}} -
        -
      -
      --}}
      {{totalLabel}}