[Feature] Allow Dice Reroll in ChatMessage (#395)

* Troubleshooting

Created test button in 'duality-roll.hbs' and functionality in chatLog.mjs

* Updated functionality

dialog recheck.

* Update duality-roll.hbs

testing toggle on specific areas so only tooltip is triggered.

Rest of CSS functionality not affected.

* Redoing Dice Reroll functionality

Attempting something new

* The rise of NaNs

Resolved Dice Parsing Errors, now dealing with parsing errors from system values.

* Forcing string evaluation for testing

* Fixed rerolling of duality dice

* Fixed message.rolls not being updated

* Added support for d20 rolls

* PR fixes

---------

Co-authored-by: Nikhil Nagarajan <potter.nikhil@gmail.com>
This commit is contained in:
WBHarry 2025-07-23 01:01:21 +02:00 committed by GitHub
parent 2721dfe417
commit 6301e575e3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 593 additions and 490 deletions

View file

@ -2,14 +2,14 @@ import { SYSTEM } from './module/config/system.mjs';
import * as applications from './module/applications/_module.mjs'; import * as applications from './module/applications/_module.mjs';
import * as models from './module/data/_module.mjs'; import * as models from './module/data/_module.mjs';
import * as documents from './module/documents/_module.mjs'; import * as documents from './module/documents/_module.mjs';
import * as dice from './module/dice/_module.mjs';
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs'; import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs'; import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs';
import { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs'; import { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs';
import { NarrativeCountdowns } from './module/applications/ui/countdowns.mjs'; import { NarrativeCountdowns } from './module/applications/ui/countdowns.mjs';
import { DualityRollColor } from './module/data/settings/Appearance.mjs'; import { DualityRollColor } from './module/data/settings/Appearance.mjs';
import { DHRoll, DualityRoll, D20Roll, DamageRoll, DualityDie } from './module/dice/_module.mjs'; import { DHRoll, DualityRoll, D20Roll, DamageRoll, DualityDie } from './module/dice/_module.mjs';
import { enrichedDualityRoll, renderDualityButton } from './module/enrichers/DualityRollEnricher.mjs'; import { enrichedDualityRoll } from './module/enrichers/DualityRollEnricher.mjs';
import { renderMeasuredTemplate } from './module/enrichers/TemplateEnricher.mjs';
import { registerCountdownHooks } from './module/data/countdowns.mjs'; import { registerCountdownHooks } from './module/data/countdowns.mjs';
import { import {
handlebarsRegistration, handlebarsRegistration,
@ -20,14 +20,14 @@ import { placeables } from './module/canvas/_module.mjs';
import { registerRollDiceHooks } from './module/dice/dhRoll.mjs'; import { registerRollDiceHooks } from './module/dice/dhRoll.mjs';
import { registerDHActorHooks } from './module/documents/actor.mjs'; import { registerDHActorHooks } from './module/documents/actor.mjs';
import './node_modules/@yaireo/tagify/dist/tagify.css'; import './node_modules/@yaireo/tagify/dist/tagify.css';
import { renderDamageButton } from './module/enrichers/DamageEnricher.mjs';
Hooks.once('init', () => { Hooks.once('init', () => {
CONFIG.DH = SYSTEM; CONFIG.DH = SYSTEM;
game.system.api = { game.system.api = {
applications, applications,
models, models,
documents documents,
dice
}; };
CONFIG.TextEditor.enrichers.push(...enricherConfig); CONFIG.TextEditor.enrichers.push(...enricherConfig);
@ -49,7 +49,12 @@ Hooks.once('init', () => {
DamageRoll: DamageRoll DamageRoll: DamageRoll
}; };
CONFIG.Dice.rolls = [...CONFIG.Dice.rolls, ...[DHRoll, DualityRoll, D20Roll, DamageRoll]]; CONFIG.Dice.terms = {
...CONFIG.Dice.terms,
DualityDie
};
CONFIG.Dice.rolls = [...CONFIG.Dice.rolls, DHRoll, DualityRoll, D20Roll, DamageRoll];
CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate; CONFIG.MeasuredTemplate.objectClass = placeables.DhMeasuredTemplate;
const { DocumentSheetConfig } = foundry.applications.apps; const { DocumentSheetConfig } = foundry.applications.apps;

View file

@ -1339,6 +1339,8 @@
"quantity": "Quantity", "quantity": "Quantity",
"range": "Range", "range": "Range",
"recovery": "Recovery", "recovery": "Recovery",
"reroll": "Reroll",
"rerollThing": "Reroll {thing}",
"resource": "Resource", "resource": "Resource",
"roll": "Roll", "roll": "Roll",
"rollAll": "Roll All", "rollAll": "Roll All",
@ -1615,6 +1617,10 @@
"title": "Heal - {healing}", "title": "Heal - {healing}",
"heal": "Heal" "heal": "Heal"
}, },
"reroll": {
"confirmTitle": "Reroll Dice",
"confirmText": "Are you sure you want to reroll?"
},
"resourceRoll": { "resourceRoll": {
"playerMessage": "{user} rerolled their {name}" "playerMessage": "{user} rerolled their {name}"
} }

View file

@ -46,17 +46,14 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
html.querySelectorAll('.target-indicator').forEach(element => html.querySelectorAll('.target-indicator').forEach(element =>
element.addEventListener('click', this.onToggleTargets) element.addEventListener('click', this.onToggleTargets)
); );
html.querySelectorAll('.advantage').forEach(element =>
element.addEventListener('mouseenter', this.hoverAdvantage)
);
html.querySelectorAll('.advantage').forEach(element =>
element.addEventListener('click', event => this.selectAdvantage.call(this, event, data.message))
);
html.querySelectorAll('.ability-use-button').forEach(element => html.querySelectorAll('.ability-use-button').forEach(element =>
element.addEventListener('click', event => this.abilityUseButton.call(this, event, data.message)) element.addEventListener('click', event => this.abilityUseButton(this, event, data.message))
); );
html.querySelectorAll('.action-use-button').forEach(element => html.querySelectorAll('.action-use-button').forEach(element =>
element.addEventListener('click', event => this.actionUseButton.call(this, event, data.message)) element.addEventListener('click', event => this.actionUseButton(this, event, data.message))
);
html.querySelectorAll('.reroll-button').forEach(element =>
element.addEventListener('click', event => this.rerollEvent(this, event, data.message))
); );
}; };
@ -70,7 +67,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
} }
async getActor(id) { async getActor(id) {
// return game.actors.get(id);
return await fromUuid(id); return await fromUuid(id);
} }
@ -85,7 +81,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
return action; return action;
} }
onRollDamage = async (event, message) => { async onRollDamage(event, message) {
event.stopPropagation(); event.stopPropagation();
const actor = await this.getActor(message.system.source.actor); const actor = await this.getActor(message.system.source.actor);
if (game.user.character?.id !== actor.id && !game.user.isGM) return true; if (game.user.character?.id !== actor.id && !game.user.isGM) return true;
@ -94,9 +90,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
if (!action || !action?.rollDamage) return; if (!action || !action?.rollDamage) return;
await action.rollDamage(event, message); await action.rollDamage(event, message);
} }
}; }
onRollHealing = async (event, message) => { async onRollHealing(event, message) {
event.stopPropagation(); event.stopPropagation();
const actor = await this.getActor(message.system.source.actor); const actor = await this.getActor(message.system.source.actor);
if (!actor || !game.user.isGM) return true; if (!actor || !game.user.isGM) return true;
@ -105,9 +101,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
if (!action || !action?.rollHealing) return; if (!action || !action?.rollHealing) return;
await action.rollHealing(event, message); await action.rollHealing(event, message);
} }
}; }
onRollSave = async (event, message) => { async onRollSave(event, message) {
event.stopPropagation(); event.stopPropagation();
const actor = await this.getActor(message.system.source.actor), const actor = await this.getActor(message.system.source.actor),
tokenId = event.target.closest('[data-token]')?.dataset.token, tokenId = event.target.closest('[data-token]')?.dataset.token,
@ -118,9 +114,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
if (!action || !action?.hasSave) return; if (!action || !action?.hasSave) return;
action.rollSave(token, event, message); action.rollSave(token, event, message);
} }
}; }
onRollAllSave = async (event, message) => { onRollAllSave(event, _message) {
event.stopPropagation(); event.stopPropagation();
const targets = event.target.parentElement.querySelectorAll( const targets = event.target.parentElement.querySelectorAll(
'.target-section > [data-token] .target-save-container' '.target-section > [data-token] .target-save-container'
@ -128,9 +124,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
targets.forEach(el => { targets.forEach(el => {
el.dispatchEvent(new PointerEvent('click', { shiftKey: true })); el.dispatchEvent(new PointerEvent('click', { shiftKey: true }));
}); });
}; }
onApplyEffect = async (event, message) => { async onApplyEffect(event, message) {
event.stopPropagation(); event.stopPropagation();
const actor = await this.getActor(message.system.source.actor); const actor = await this.getActor(message.system.source.actor);
if (!actor || !game.user.isGM) return true; if (!actor || !game.user.isGM) return true;
@ -142,9 +138,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected')); ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
await action.applyEffects(event, message, targets); await action.applyEffects(event, message, targets);
} }
}; }
onTargetSelection = async (event, message) => { onTargetSelection(event, message) {
event.stopPropagation(); event.stopPropagation();
const targetSelection = Boolean(event.target.dataset.targetHit), const targetSelection = Boolean(event.target.dataset.targetHit),
msg = ui.chat.collection.get(message._id); msg = ui.chat.collection.get(message._id);
@ -154,9 +150,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
msg.system.targetSelection = targetSelection; msg.system.targetSelection = targetSelection;
msg.system.prepareDerivedData(); msg.system.prepareDerivedData();
ui.chat.updateMessage(msg); ui.chat.updateMessage(msg);
}; }
getTargetList = (event, message) => { getTargetList(event, message) {
const targetSelection = event.target const targetSelection = event.target
.closest('.message-content') .closest('.message-content')
.querySelector('.button-target-selection.target-selected'), .querySelector('.button-target-selection.target-selected'),
@ -167,20 +163,20 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
? message.system.targets.filter(t => t.hit === true).map(target => game.canvas.tokens.get(target.id)) ? message.system.targets.filter(t => t.hit === true).map(target => game.canvas.tokens.get(target.id))
: Array.from(game.user.targets) : Array.from(game.user.targets)
}; };
}; }
hoverTarget = event => { hoverTarget(event) {
event.stopPropagation(); event.stopPropagation();
const token = canvas.tokens.get(event.currentTarget.dataset.token); 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 => { unhoverTarget(event) {
const token = canvas.tokens.get(event.currentTarget.dataset.token); const token = canvas.tokens.get(event.currentTarget.dataset.token);
if (!token?.controlled) token._onHoverOut(event); if (!token?.controlled) token._onHoverOut(event);
}; }
clickTarget = event => { clickTarget(event) {
event.stopPropagation(); event.stopPropagation();
const token = canvas.tokens.get(event.currentTarget.dataset.token); const token = canvas.tokens.get(event.currentTarget.dataset.token);
if (!token) { if (!token) {
@ -188,9 +184,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
return; return;
} }
game.canvas.pan(token); game.canvas.pan(token);
}; }
onDamage = async (event, message) => { async onDamage(event, message) {
event.stopPropagation(); event.stopPropagation();
const { isHit, targets } = this.getTargetList(event, message); const { isHit, targets } = this.getTargetList(event, message);
@ -228,9 +224,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
target.actor.takeDamage(damages); target.actor.takeDamage(damages);
} }
}; }
onHealing = async (event, message) => { async onHealing(event, message) {
event.stopPropagation(); event.stopPropagation();
const targets = Array.from(game.user.targets); const targets = Array.from(game.user.targets);
@ -240,7 +236,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
for (var target of targets) { for (var target of targets) {
target.actor.takeHealing(message.system.roll); target.actor.takeHealing(message.system.roll);
} }
}; }
/** /**
* Toggle visibility of target containers. * Toggle visibility of target containers.
@ -253,51 +249,15 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
.forEach(el => el.classList.toggle('hidden')); .forEach(el => el.classList.toggle('hidden'));
} }
/** async abilityUseButton(event, message) {
* Highlight advantage icons on hover.
* @param {MouseEvent} event
*/
hoverAdvantage(event) {
const parent = event.currentTarget.parentElement;
if (!parent) return;
parent.querySelectorAll('.advantage').forEach(el => {
if (el !== event.currentTarget) {
el.classList.toggle('unused');
}
});
}
/**
* Handle selecting an advantage and disable further selection.
* @param {MouseEvent} event
* @param {object} message
*/
async selectAdvantage(event, message) {
event.stopPropagation();
const updateMessage = game.messages.get(message._id);
await updateMessage?.update({
system: { advantageSelected: event.currentTarget.id === 'hope' ? 1 : 2 }
});
const parent = event.currentTarget.parentElement;
if (!parent) return;
parent.querySelectorAll('.advantage').forEach(el => {
el.replaceWith(el.cloneNode(true));
});
}
abilityUseButton = async (event, message) => {
event.stopPropagation(); event.stopPropagation();
const action = message.system.actions[Number.parseInt(event.currentTarget.dataset.index)]; const action = message.system.actions[Number.parseInt(event.currentTarget.dataset.index)];
const actor = game.actors.get(message.system.source.actor); const actor = game.actors.get(message.system.source.actor);
await actor.useAction(action); await actor.useAction(action);
}; }
actionUseButton = async (event, message) => { async actionUseButton(event, message) {
const { moveIndex, actionIndex } = event.currentTarget.dataset; const { moveIndex, actionIndex } = event.currentTarget.dataset;
const parent = await foundry.utils.fromUuid(message.system.actor); const parent = await foundry.utils.fromUuid(message.system.actor);
const actionType = message.system.moves[moveIndex].actions[actionIndex]; const actionType = message.system.moves[moveIndex].actions[actionIndex];
@ -308,5 +268,30 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
); );
action.use(event); action.use(event);
}; }
async rerollEvent(event, message) {
if (!event.shiftKey) {
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: {
title: game.i18n.localize('DAGGERHEART.UI.Chat.reroll.confirmTitle')
},
content: game.i18n.localize('DAGGERHEART.UI.Chat.reroll.confirmText')
});
if (!confirmed) return;
}
const target = event.target.closest('button[data-die-index]');
let originalRoll_parsed = message.rolls.map(roll => JSON.parse(roll))[0];
const rollClass =
game.system.api.dice[
message.type === 'dualityRoll' ? 'DualityRoll' : target.dataset.type === 'damage' ? 'DHRoll' : 'D20Roll'
];
const { newRoll, parsedRoll } = await rollClass.reroll(originalRoll_parsed, target, message);
await game.messages.get(message._id).update({
'system.roll': newRoll,
'rolls': [parsedRoll]
});
}
} }

View file

@ -6,7 +6,7 @@ export default class DHAdversaryRoll extends foundry.abstract.TypeDataModel {
static defineSchema() { static defineSchema() {
return { return {
title: new fields.StringField(), title: new fields.StringField(),
roll: new fields.DataField(), roll: new fields.ObjectField(),
targets: new fields.ArrayField( targets: new fields.ArrayField(
new fields.SchemaField({ new fields.SchemaField({
id: new fields.StringField({}), id: new fields.StringField({}),

View file

@ -26,10 +26,7 @@ const stressDamageReductionRule = localizationPath =>
const bonusField = label => const bonusField = label =>
new fields.SchemaField({ new fields.SchemaField({
bonus: new fields.NumberField({ integer: true, initial: 0, label: `${game.i18n.localize(label)} Value` }), bonus: new fields.NumberField({ integer: true, initial: 0, label: `${game.i18n.localize(label)} Value` }),
dice: new fields.ArrayField( dice: new fields.ArrayField(new fields.StringField(), { label: `${game.i18n.localize(label)} Dice` })
new fields.StringField(),
{ label: `${game.i18n.localize(label)} Dice` }
)
}); });
export { attributeField, resourceField, stressDamageReductionRule, bonusField }; export { attributeField, resourceField, stressDamageReductionRule, bonusField };

View file

@ -147,7 +147,10 @@ export default class D20Roll extends DHRoll {
const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion; const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion;
target.hit = this.isCritical || roll.total >= difficulty; target.hit = this.isCritical || roll.total >= difficulty;
}); });
} else if (config.roll.difficulty) data.success = roll.isCritical || roll.total >= config.roll.difficulty; } else if (config.roll.difficulty) {
data.difficulty = config.roll.difficulty;
data.success = roll.isCritical || roll.total >= config.roll.difficulty;
}
data.advantage = { data.advantage = {
type: config.roll.advantage, type: config.roll.advantage,
dice: roll.dAdvantage?.denomination, dice: roll.dAdvantage?.denomination,
@ -169,4 +172,22 @@ export default class D20Roll extends DHRoll {
resetFormula() { resetFormula() {
return (this._formula = this.constructor.getFormula(this.terms)); return (this._formula = this.constructor.getFormula(this.terms));
} }
static async reroll(rollString, _target, message) {
let parsedRoll = game.system.api.dice.D20Roll.fromData(rollString);
parsedRoll = await parsedRoll.reroll();
const newRoll = game.system.api.dice.D20Roll.postEvaluate(parsedRoll, {
targets: message.system.targets,
roll: {
advantage: message.system.roll.advantage?.type,
difficulty: message.system.roll.difficulty ? Number(message.system.roll.difficulty) : null
}
});
if (game.modules.get('dice-so-nice')?.active) {
await game.dice3d.showForRoll(parsedRoll, game.user, true);
}
return { newRoll, parsedRoll };
}
} }

View file

@ -1,6 +1,7 @@
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs'; import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
import D20Roll from './d20Roll.mjs'; import D20Roll from './d20Roll.mjs';
import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs'; import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
import { getDiceSoNicePresets } from '../config/generalConfig.mjs';
export default class DualityRoll extends D20Roll { export default class DualityRoll extends D20Roll {
_advantageFaces = 6; _advantageFaces = 6;
@ -110,6 +111,13 @@ export default class DualityRoll extends D20Roll {
return [...(hooks ?? []), 'Duality']; return [...(hooks ?? []), 'Duality'];
} }
/** @inheritDoc */
static fromData(data) {
data.terms[0].class = game.system.api.dice.DualityDie.name;
data.terms[2].class = game.system.api.dice.DualityDie.name;
return super.fromData(data);
}
createBaseDice() { createBaseDice() {
if ( if (
this.dice[0] instanceof CONFIG.Dice.daggerheart.DualityDie && this.dice[0] instanceof CONFIG.Dice.daggerheart.DualityDie &&
@ -186,4 +194,44 @@ export default class DualityRoll extends D20Roll {
return data; return data;
} }
static async reroll(rollString, target, message) {
let parsedRoll = game.system.api.dice.DualityRoll.fromData({ ...rollString, evaluated: false });
const term = parsedRoll.terms[target.dataset.dieIndex];
await term.reroll(`/r1=${term.total}`);
if (game.modules.get('dice-so-nice')?.active) {
const diceSoNiceRoll = {
_evaluated: true,
dice: [
new foundry.dice.terms.Die({
...term,
faces: term._faces,
results: term.results.filter(x => !x.rerolled)
})
],
options: { appearance: {} }
};
const diceSoNicePresets = getDiceSoNicePresets();
const type = target.dataset.type;
if (diceSoNicePresets[type]) {
diceSoNiceRoll.dice[0].options = { appearance: diceSoNicePresets[type] };
}
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true);
}
await parsedRoll.evaluate();
const newRoll = game.system.api.dice.DualityRoll.postEvaluate(parsedRoll, {
targets: message.system.targets,
roll: {
advantage: message.system.roll.advantage?.type,
difficulty: message.system.roll.difficulty ? Number(message.system.roll.difficulty) : null
}
});
newRoll.extra = newRoll.extra.slice(2);
return { newRoll, parsedRoll };
}
} }

View file

@ -48,7 +48,7 @@ function getDualityMessage(roll) {
> >
<i class="fa-solid fa-circle-half-stroke"></i> <i class="fa-solid fa-circle-half-stroke"></i>
${label} ${label}
${roll.difficulty || advantageLabel ? `(${[roll.difficulty, game.i18n.localize(`DAGGERHEART.GENERAL.${advantageLabel}.short`)].filter(x => x).join(' ')})` : ''} ${roll.difficulty || advantageLabel ? `(${[roll.difficulty, advantageLabel ? game.i18n.localize(`DAGGERHEART.GENERAL.${advantageLabel}.short`) : null].filter(x => x).join(' ')})` : ''}
</button> </button>
`; `;

View file

@ -33,7 +33,8 @@
font-style: normal; font-style: normal;
font-weight: 700; font-weight: 700;
font-display: swap; font-display: swap;
src: url(https://fonts.gstatic.com/s/cinzeldecorative/v18/daaHSScvJGqLYhG8nNt8KPPswUAPniZoaelD.ttf) format('truetype'); src: url(https://fonts.gstatic.com/s/cinzeldecorative/v18/daaHSScvJGqLYhG8nNt8KPPswUAPniZoaelD.ttf)
format('truetype');
} }
@font-face { @font-face {
font-family: 'Montserrat'; font-family: 'Montserrat';
@ -384,7 +385,9 @@
opacity: 1; opacity: 1;
transform: translateY(0); transform: translateY(0);
grid-column: 1/-1; grid-column: 1/-1;
transition: opacity 0.3s ease-out, transform 0.3s ease-out; transition:
opacity 0.3s ease-out,
transform 0.3s ease-out;
} }
.application.dh-style .item-description.invisible { .application.dh-style .item-description.invisible {
height: 0; height: 0;
@ -578,10 +581,18 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
} }
.application .component.dh-style.card-preview-container .preview-empty-container .preview-empty-inner-container .preview-add-icon { .application
.component.dh-style.card-preview-container
.preview-empty-container
.preview-empty-inner-container
.preview-add-icon {
font-size: 48px; font-size: 48px;
} }
.application .component.dh-style.card-preview-container .preview-empty-container .preview-empty-inner-container .preview-empty-subtext { .application
.component.dh-style.card-preview-container
.preview-empty-container
.preview-empty-inner-container
.preview-empty-subtext {
position: absolute; position: absolute;
top: 10%; top: 10%;
font-size: 18px; font-size: 18px;

View file

@ -44,7 +44,35 @@
&.duality { &.duality {
display: flex; display: flex;
gap: 0.25rem; 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 {
.reroll-button {
border: none;
background: initial;
&:hover {
background: var(--button-background-color);
border: 1px solid var(--button-border-color);
}
}
}
// margin: 0; // margin: 0;
> .roll { > .roll {
display: flex; display: flex;
@ -52,6 +80,7 @@
justify-content: center; justify-content: center;
gap: 4px; gap: 4px;
margin-bottom: 4px; margin-bottom: 4px;
.dice-container { .dice-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View file

@ -12,16 +12,13 @@
<span class="part-total">{{total}}</span> <span class="part-total">{{total}}</span>
</header> </header>
<div class="flexrow"> <div class="flexrow">
<ol class="dice-rolls"> <ol class="dice-rolls rerollable">
{{#each results}} <button type="checkbox" class="reroll-button" data-die-index="0" data-tooltip="{{localize "DAGGERHEART.GENERAL.reroll"}}">
<li class="roll die {{../dice}}{{#if discarded}} discarded{{/if}} min">{{result}}</li> {{#each results as |result index|}}
<li class="roll die {{../dice}}{{#if discarded}} discarded{{/if}} min">{{result.result}}</li>
{{/each}} {{/each}}
</button>
</ol> </ol>
{{#if (eq index 0)}}
<div class="attack-roll-advantage-container">
{{#if (eq ../roll.advantage.type 1)}}{{localize "DAGGERHEART.GENERAL.Advantage.full"}}{{/if}}{{#if (eq ../roll.advantage.type -1)}}{{localize "DAGGERHEART.GENERAL.Disadvantage.full"}}{{/if}}
</div>
{{/if}}
</div> </div>
{{/each}} {{/each}}
</div> </div>

View file

@ -1,6 +1,6 @@
<div class="dice-roll daggerheart chat roll" data-action="expandRoll"> <div class="dice-roll daggerheart chat roll" data-action="expandRoll">
<div class="dice-flavor">{{title}}</div> <div class="dice-flavor" data-action="expandRoll">{{title}}</div>
<div class="duality-modifiers"> <div class="duality-modifiers" data-action="expandRoll">
{{#each roll.modifiers}} {{#each roll.modifiers}}
<div class="duality-modifier"> <div class="duality-modifier">
{{localize label}} {{#if (gte value 0)}}+{{/if}}{{value}} {{localize label}} {{#if (gte value 0)}}+{{/if}}{{value}}
@ -23,11 +23,11 @@
{{/if}} {{/if}}
</div> </div>
<div class="dice-result"> <div class="dice-result">
<div class="dice-formula">{{roll.formula}}</div> <div class="dice-formula" data-action="expandRoll">{{roll.formula}}</div>
<div class="dice-tooltip"> <div class="dice-tooltip">
<div class="wrapper"> <div class="wrapper">
<section class="tooltip-part"> <section class="tooltip-part">
<div class="dice"> <div class="dice" data-action="expandRoll">
<header class="part-header flexrow"> <header class="part-header flexrow">
<span class="part-formula"> <span class="part-formula">
<span>1{{roll.hope.dice}}</span> <span>1{{roll.hope.dice}}</span>
@ -38,25 +38,29 @@
</header> </header>
<div class="flexrow"> <div class="flexrow">
<ol class="dice-rolls duality"> <ol class="dice-rolls duality">
<li class="roll die {{roll.hope.dice}}" title="{{localize "DAGGERHEART.GENERAL.hope"}}"> <li class="roll die {{roll.hope.dice}}">
<div class="dice-container"> <div class="dice-container">
<div class="dice-title">{{localize "DAGGERHEART.GENERAL.hope"}}</div> <div class="dice-title">{{localize "DAGGERHEART.GENERAL.hope"}}</div>
<div class="dice-inner-container hope" title="{{localize "DAGGERHEART.GENERAL.hope"}}"> <div class="dice-inner-container hope" data-tooltip="{{localize "DAGGERHEART.GENERAL.rerollThing" thing=(localize "DAGGERHEART.GENERAL.hope")}}">
<button type="checkbox" class="reroll-button" data-die-index="0" data-type="hope">
<div class="dice-wrapper"> <div class="dice-wrapper">
<img class="dice" src="../icons/svg/{{roll.hope.dice}}-grey.svg"/> <img class="dice" src="../icons/svg/{{roll.hope.dice}}-grey.svg"/>
</div> </div>
<div class="dice-value">{{roll.hope.value}}</div> <div class="dice-value">{{roll.hope.value}}</div>
</button>
</div> </div>
</div> </div>
</li> </li>
<li class="roll die {{roll.fear.dice}}" title="{{localize "DAGGERHEART.GENERAL.fear"}}"> <li class="roll die {{roll.fear.dice}}">
<div class="dice-container"> <div class="dice-container">
<div class="dice-title">{{localize "DAGGERHEART.GENERAL.fear"}}</div> <div class="dice-title">{{localize "DAGGERHEART.GENERAL.fear"}}</div>
<div class="dice-inner-container fear" title="{{localize "DAGGERHEART.GENERAL.fear"}}"> <div class="dice-inner-container fear" data-tooltip="{{localize "DAGGERHEART.GENERAL.rerollThing" thing=(localize "DAGGERHEART.GENERAL.fear")}}">
<button type="checkbox" class="reroll-button" data-die-index="2" data-type="fear">
<div class="dice-wrapper"> <div class="dice-wrapper">
<img class="dice" src="../icons/svg/{{roll.fear.dice}}-grey.svg"/> <img class="dice" src="../icons/svg/{{roll.fear.dice}}-grey.svg"/>
</div> </div>
<div class="dice-value">{{roll.fear.value}}</div> <div class="dice-value">{{roll.fear.value}}</div>
</button>
</div> </div>
</div> </div>
</li> </li>
@ -64,7 +68,7 @@
</div> </div>
</div> </div>
{{#if roll.advantage.type}} {{#if roll.advantage.type}}
<div class="dice"> <div class="dice" data-action="expandRoll">
<header class="part-header flexrow"> <header class="part-header flexrow">
<span class="part-formula"> <span class="part-formula">
<span>1{{roll.advantage.dice}}</span> <span>1{{roll.advantage.dice}}</span>
@ -112,7 +116,7 @@
</div> </div>
{{/if}} {{/if}}
{{#each roll.extra as | extra | }} {{#each roll.extra as | extra | }}
<div class="dice"> <div class="dice" data-action="expandRoll">
<header class="part-header flexrow"> <header class="part-header flexrow">
<span class="part-formula"> <span class="part-formula">
<span>1{{extra.dice}}</span> <span>1{{extra.dice}}</span>