diff --git a/daggerheart.mjs b/daggerheart.mjs
index 1366dc60..f939743c 100644
--- a/daggerheart.mjs
+++ b/daggerheart.mjs
@@ -187,12 +187,15 @@ Hooks.on('renderHandlebarsApplication', (_, element) => {
Hooks.on('chatMessage', (_, message) => {
if (message.startsWith('/dr')) {
- const rollCommand = rollCommandToJSON(message.replace(/\/dr\s?/, ''));
- if (!rollCommand) {
+ const result = rollCommandToJSON(message.replace(/\/dr\s?/, ''));
+ if (!result) {
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.dualityParsing'));
return false;
}
+ const { result: rollCommand, flavor } = result;
+
+ const reaction = rollCommand.reaction;
const traitValue = rollCommand.trait?.toLowerCase();
const advantage = rollCommand.advantage
? CONFIG.DH.ACTIONS.advantageState.advantage.value
@@ -208,7 +211,16 @@ Hooks.on('chatMessage', (_, message) => {
})
: game.i18n.localize('DAGGERHEART.GENERAL.duality');
- enrichedDualityRoll({ traitValue, target, difficulty, title, label: 'test', actionType: null, advantage });
+ enrichedDualityRoll({
+ reaction,
+ traitValue,
+ target,
+ difficulty,
+ title,
+ label: 'test',
+ actionType: null,
+ advantage
+ });
return false;
}
});
diff --git a/lang/en.json b/lang/en.json
index 8846f995..1ccfeac8 100755
--- a/lang/en.json
+++ b/lang/en.json
@@ -1821,7 +1821,6 @@
"basics": "Basics",
"bonus": "Bonus",
"burden": "Burden",
- "check": "{check} Check",
"continue": "Continue",
"criticalSuccess": "Critical Success",
"damage": "Damage",
@@ -1866,6 +1865,7 @@
"proficiency": "Proficiency",
"quantity": "Quantity",
"range": "Range",
+ "reactionRoll": "Reaction Roll",
"recovery": "Recovery",
"reroll": "Reroll",
"rerollThing": "Reroll {thing}",
@@ -1873,6 +1873,7 @@
"roll": "Roll",
"rollAll": "Roll All",
"rollDamage": "Roll Damage",
+ "rollWith": "{roll} Roll",
"save": "Save",
"scalable": "Scalable",
"situationalBonus": "Situational Bonus",
diff --git a/module/applications/dialogs/d20RollDialog.mjs b/module/applications/dialogs/d20RollDialog.mjs
index 76871b6a..9075c454 100644
--- a/module/applications/dialogs/d20RollDialog.mjs
+++ b/module/applications/dialogs/d20RollDialog.mjs
@@ -7,6 +7,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
this.roll = roll;
this.config = config;
this.config.experiences = [];
+ this.reactionOverride = config.roll.type === 'reaction';
if (config.source?.action) {
this.item = config.data.parent.items.get(config.source.item) ?? config.data.parent;
@@ -30,6 +31,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
actions: {
updateIsAdvantage: this.updateIsAdvantage,
selectExperience: this.selectExperience,
+ toggleReaction: this.toggleReaction,
submitRoll: this.submitRoll
},
form: {
@@ -103,6 +105,9 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
context.isLite = this.config.roll?.lite;
context.extraFormula = this.config.extraFormula;
context.formula = this.roll.constructFormula(this.config);
+
+ context.showReaction = !context.rollConfig.type && context.rollType === 'DualityRoll';
+ context.reactionOverride = this.reactionOverride;
}
return context;
}
@@ -141,7 +146,19 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
this.render();
}
+ static toggleReaction() {
+ if (this.config.roll) {
+ this.reactionOverride = !this.reactionOverride;
+ this.render();
+ }
+ }
+
static async submitRoll() {
+ this.config.roll.type = this.reactionOverride
+ ? CONFIG.DH.ITEM.actionTypes.reaction.id
+ : this.config.roll.type === CONFIG.DH.ITEM.actionTypes.reaction.id
+ ? null
+ : this.config.roll.type;
await this.close({ submitted: true });
}
diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs
index bacaf47b..fcf30be5 100644
--- a/module/dice/dhRoll.mjs
+++ b/module/dice/dhRoll.mjs
@@ -69,6 +69,7 @@ export default class DHRoll extends Roll {
static postEvaluate(roll, config = {}) {
return {
+ type: config.roll.type,
total: roll.total,
formula: roll.formula,
dice: roll.dice.map(d => ({
diff --git a/module/enrichers/DualityRollEnricher.mjs b/module/enrichers/DualityRollEnricher.mjs
index 24dd0602..4a6fac23 100644
--- a/module/enrichers/DualityRollEnricher.mjs
+++ b/module/enrichers/DualityRollEnricher.mjs
@@ -2,22 +2,23 @@ import { abilities } from '../config/actorConfig.mjs';
import { getCommandTarget, rollCommandToJSON } from '../helpers/utils.mjs';
export default function DhDualityRollEnricher(match, _options) {
- const roll = rollCommandToJSON(match[1]);
+ const roll = rollCommandToJSON(match[1], match[0]);
if (!roll) return match[0];
- return getDualityMessage(roll);
+ return getDualityMessage(roll.result, roll.flavor);
}
-function getDualityMessage(roll) {
- const traitLabel =
- roll.trait && abilities[roll.trait]
- ? game.i18n.format('DAGGERHEART.GENERAL.check', {
- check: game.i18n.localize(abilities[roll.trait].label)
- })
- : null;
+function getDualityMessage(roll, flavor) {
+ const trait = roll.trait && abilities[roll.trait] ? game.i18n.localize(abilities[roll.trait].label) : null;
+ const label =
+ flavor ??
+ (roll.trait
+ ? game.i18n.format('DAGGERHEART.GENERAL.rollWith', { roll: trait })
+ : roll.reaction
+ ? game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll')
+ : game.i18n.localize('DAGGERHEART.GENERAL.duality'));
- const label = traitLabel ?? game.i18n.localize('DAGGERHEART.GENERAL.duality');
- const dataLabel = traitLabel
+ const dataLabel = trait
? game.i18n.localize(abilities[roll.trait].label)
: game.i18n.localize('DAGGERHEART.GENERAL.duality');
@@ -38,6 +39,7 @@ function getDualityMessage(roll) {
`;
@@ -57,6 +59,7 @@ function getDualityMessage(roll) {
export const renderDualityButton = async event => {
const button = event.currentTarget,
+ reaction = button.dataset.reaction === 'true',
traitValue = button.dataset.trait?.toLowerCase(),
target = getCommandTarget({ allowNull: true }),
difficulty = button.dataset.difficulty,
@@ -64,12 +67,12 @@ export const renderDualityButton = async event => {
await enrichedDualityRoll(
{
+ reaction,
traitValue,
target,
difficulty,
title: button.dataset.title,
label: button.dataset.label,
- actionType: button.dataset.actionType,
advantage
},
event
@@ -77,7 +80,7 @@ export const renderDualityButton = async event => {
};
export const enrichedDualityRoll = async (
- { traitValue, target, difficulty, title, label, actionType, advantage },
+ { reaction, traitValue, target, difficulty, title, label, advantage },
event
) => {
const config = {
@@ -88,7 +91,7 @@ export const enrichedDualityRoll = async (
label: label,
difficulty: difficulty,
advantage,
- type: actionType ?? null // Need check,
+ type: reaction ? 'reaction' : null
},
chatMessage: {
template: 'systems/daggerheart/templates/ui/chat/duality-roll.hbs'
diff --git a/module/enrichers/_module.mjs b/module/enrichers/_module.mjs
index abfd8158..3b597dd5 100644
--- a/module/enrichers/_module.mjs
+++ b/module/enrichers/_module.mjs
@@ -7,19 +7,19 @@ export { DhDamageEnricher, DhDualityRollEnricher, DhEffectEnricher, DhTemplateEn
export const enricherConfig = [
{
- pattern: /^@Damage\[(.*)\]$/g,
+ pattern: /^@Damage\[(.*)\]({.*})?$/g,
enricher: DhDamageEnricher
},
{
- pattern: /\[\[\/dr\s?(.*?)\]\]/g,
+ pattern: /\[\[\/dr\s?(.*?)\]\]({.*})?/g,
enricher: DhDualityRollEnricher
},
{
- pattern: /^@Effect\[(.*)\]$/g,
+ pattern: /^@Effect\[(.*)\]({.*})?$/g,
enricher: DhEffectEnricher
},
{
- pattern: /^@Template\[(.*)\]$/g,
+ pattern: /^@Template\[(.*)\]({.*})?$/g,
enricher: DhTemplateEnricher
}
];
diff --git a/module/helpers/utils.mjs b/module/helpers/utils.mjs
index 66e743a3..9d22906c 100644
--- a/module/helpers/utils.mjs
+++ b/module/helpers/utils.mjs
@@ -5,9 +5,12 @@ export const capitalize = string => {
return string.charAt(0).toUpperCase() + string.slice(1);
};
-export function rollCommandToJSON(text) {
+export function rollCommandToJSON(text, raw) {
if (!text) return {};
+ const flavorMatch = raw?.match(/{(.*)}$/);
+ const flavor = flavorMatch ? flavorMatch[1] : null;
+
// Match key="quoted string" OR key=unquotedValue
const PAIR_RE = /(\w+)=("(?:[^"\\]|\\.)*"|\S+)/g;
const result = {};
@@ -28,7 +31,7 @@ export function rollCommandToJSON(text) {
}
result[key] = value;
}
- return Object.keys(result).length > 0 ? result : null;
+ return Object.keys(result).length > 0 ? { result, flavor } : null;
}
export const getCommandTarget = (options = {}) => {
diff --git a/styles/less/dialog/dice-roll/roll-selection.less b/styles/less/dialog/dice-roll/roll-selection.less
index 4ee2ee1f..d536ee04 100644
--- a/styles/less/dialog/dice-roll/roll-selection.less
+++ b/styles/less/dialog/dice-roll/roll-selection.less
@@ -9,6 +9,36 @@
}
.application.daggerheart.dialog.dh-style.views.roll-selection {
+ .dialog-header {
+ display: flex;
+ justify-content: center;
+
+ h1 {
+ width: auto;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+
+ .reaction-roll-controller {
+ width: auto;
+ opacity: 0.3;
+ border-radius: 50%;
+ font-size: 18px;
+ font-weight: bold;
+
+ &:hover {
+ opacity: 0.5;
+ background: light-dark(transparent, @golden);
+ color: light-dark(@dark-blue, @dark-blue);
+ }
+
+ &.active {
+ opacity: 1;
+ }
+ }
+ }
+ }
+
.roll-dialog-container {
display: flex;
flex-direction: column;
@@ -16,6 +46,7 @@
max-width: 550px;
.dices-section {
+ position: relative;
display: flex;
gap: 60px;
justify-content: center;
diff --git a/templates/dialogs/dice-roll/header.hbs b/templates/dialogs/dice-roll/header.hbs
index 462408f6..cea07209 100644
--- a/templates/dialogs/dice-roll/header.hbs
+++ b/templates/dialogs/dice-roll/header.hbs
@@ -1,7 +1,10 @@
{{rollConfig.headerTitle}}
- {{else}}
- {{rollConfig.title}}
- {{/if}}
+
+ {{ifThen rollConfig.headerTitle rollConfig.headerTitle rollConfig.title}}
+ {{#if showReaction}}
+
+ {{/if}}
+