mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-16 05:31:07 +01:00
[Feature] 460 - Reaction Rolls (#481)
* Added a toggle in D20RollDialog for ReactionRolls * DualityRollEnrichment can now use reaction * Added flavor for DualityRollEnrichment
This commit is contained in:
parent
243630878b
commit
e168e3e7ec
10 changed files with 103 additions and 31 deletions
|
|
@ -187,12 +187,15 @@ Hooks.on('renderHandlebarsApplication', (_, element) => {
|
||||||
|
|
||||||
Hooks.on('chatMessage', (_, message) => {
|
Hooks.on('chatMessage', (_, message) => {
|
||||||
if (message.startsWith('/dr')) {
|
if (message.startsWith('/dr')) {
|
||||||
const rollCommand = rollCommandToJSON(message.replace(/\/dr\s?/, ''));
|
const result = rollCommandToJSON(message.replace(/\/dr\s?/, ''));
|
||||||
if (!rollCommand) {
|
if (!result) {
|
||||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.dualityParsing'));
|
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.dualityParsing'));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { result: rollCommand, flavor } = result;
|
||||||
|
|
||||||
|
const reaction = rollCommand.reaction;
|
||||||
const traitValue = rollCommand.trait?.toLowerCase();
|
const traitValue = rollCommand.trait?.toLowerCase();
|
||||||
const advantage = rollCommand.advantage
|
const advantage = rollCommand.advantage
|
||||||
? CONFIG.DH.ACTIONS.advantageState.advantage.value
|
? CONFIG.DH.ACTIONS.advantageState.advantage.value
|
||||||
|
|
@ -208,7 +211,16 @@ Hooks.on('chatMessage', (_, message) => {
|
||||||
})
|
})
|
||||||
: game.i18n.localize('DAGGERHEART.GENERAL.duality');
|
: 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;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1821,7 +1821,6 @@
|
||||||
"basics": "Basics",
|
"basics": "Basics",
|
||||||
"bonus": "Bonus",
|
"bonus": "Bonus",
|
||||||
"burden": "Burden",
|
"burden": "Burden",
|
||||||
"check": "{check} Check",
|
|
||||||
"continue": "Continue",
|
"continue": "Continue",
|
||||||
"criticalSuccess": "Critical Success",
|
"criticalSuccess": "Critical Success",
|
||||||
"damage": "Damage",
|
"damage": "Damage",
|
||||||
|
|
@ -1866,6 +1865,7 @@
|
||||||
"proficiency": "Proficiency",
|
"proficiency": "Proficiency",
|
||||||
"quantity": "Quantity",
|
"quantity": "Quantity",
|
||||||
"range": "Range",
|
"range": "Range",
|
||||||
|
"reactionRoll": "Reaction Roll",
|
||||||
"recovery": "Recovery",
|
"recovery": "Recovery",
|
||||||
"reroll": "Reroll",
|
"reroll": "Reroll",
|
||||||
"rerollThing": "Reroll {thing}",
|
"rerollThing": "Reroll {thing}",
|
||||||
|
|
@ -1873,6 +1873,7 @@
|
||||||
"roll": "Roll",
|
"roll": "Roll",
|
||||||
"rollAll": "Roll All",
|
"rollAll": "Roll All",
|
||||||
"rollDamage": "Roll Damage",
|
"rollDamage": "Roll Damage",
|
||||||
|
"rollWith": "{roll} Roll",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"scalable": "Scalable",
|
"scalable": "Scalable",
|
||||||
"situationalBonus": "Situational Bonus",
|
"situationalBonus": "Situational Bonus",
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
this.roll = roll;
|
this.roll = roll;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.config.experiences = [];
|
this.config.experiences = [];
|
||||||
|
this.reactionOverride = config.roll.type === 'reaction';
|
||||||
|
|
||||||
if (config.source?.action) {
|
if (config.source?.action) {
|
||||||
this.item = config.data.parent.items.get(config.source.item) ?? config.data.parent;
|
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: {
|
actions: {
|
||||||
updateIsAdvantage: this.updateIsAdvantage,
|
updateIsAdvantage: this.updateIsAdvantage,
|
||||||
selectExperience: this.selectExperience,
|
selectExperience: this.selectExperience,
|
||||||
|
toggleReaction: this.toggleReaction,
|
||||||
submitRoll: this.submitRoll
|
submitRoll: this.submitRoll
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
|
|
@ -103,6 +105,9 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
context.isLite = this.config.roll?.lite;
|
context.isLite = this.config.roll?.lite;
|
||||||
context.extraFormula = this.config.extraFormula;
|
context.extraFormula = this.config.extraFormula;
|
||||||
context.formula = this.roll.constructFormula(this.config);
|
context.formula = this.roll.constructFormula(this.config);
|
||||||
|
|
||||||
|
context.showReaction = !context.rollConfig.type && context.rollType === 'DualityRoll';
|
||||||
|
context.reactionOverride = this.reactionOverride;
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
@ -141,7 +146,19 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static toggleReaction() {
|
||||||
|
if (this.config.roll) {
|
||||||
|
this.reactionOverride = !this.reactionOverride;
|
||||||
|
this.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static async submitRoll() {
|
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 });
|
await this.close({ submitted: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ export default class DHRoll extends Roll {
|
||||||
|
|
||||||
static postEvaluate(roll, config = {}) {
|
static postEvaluate(roll, config = {}) {
|
||||||
return {
|
return {
|
||||||
|
type: config.roll.type,
|
||||||
total: roll.total,
|
total: roll.total,
|
||||||
formula: roll.formula,
|
formula: roll.formula,
|
||||||
dice: roll.dice.map(d => ({
|
dice: roll.dice.map(d => ({
|
||||||
|
|
|
||||||
|
|
@ -2,22 +2,23 @@ import { abilities } from '../config/actorConfig.mjs';
|
||||||
import { getCommandTarget, rollCommandToJSON } from '../helpers/utils.mjs';
|
import { getCommandTarget, rollCommandToJSON } from '../helpers/utils.mjs';
|
||||||
|
|
||||||
export default function DhDualityRollEnricher(match, _options) {
|
export default function DhDualityRollEnricher(match, _options) {
|
||||||
const roll = rollCommandToJSON(match[1]);
|
const roll = rollCommandToJSON(match[1], match[0]);
|
||||||
if (!roll) return match[0];
|
if (!roll) return match[0];
|
||||||
|
|
||||||
return getDualityMessage(roll);
|
return getDualityMessage(roll.result, roll.flavor);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDualityMessage(roll) {
|
function getDualityMessage(roll, flavor) {
|
||||||
const traitLabel =
|
const trait = roll.trait && abilities[roll.trait] ? game.i18n.localize(abilities[roll.trait].label) : null;
|
||||||
roll.trait && abilities[roll.trait]
|
const label =
|
||||||
? game.i18n.format('DAGGERHEART.GENERAL.check', {
|
flavor ??
|
||||||
check: game.i18n.localize(abilities[roll.trait].label)
|
(roll.trait
|
||||||
})
|
? game.i18n.format('DAGGERHEART.GENERAL.rollWith', { roll: trait })
|
||||||
: null;
|
: 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 = trait
|
||||||
const dataLabel = traitLabel
|
|
||||||
? game.i18n.localize(abilities[roll.trait].label)
|
? game.i18n.localize(abilities[roll.trait].label)
|
||||||
: game.i18n.localize('DAGGERHEART.GENERAL.duality');
|
: game.i18n.localize('DAGGERHEART.GENERAL.duality');
|
||||||
|
|
||||||
|
|
@ -38,6 +39,7 @@ function getDualityMessage(roll) {
|
||||||
<button class="duality-roll-button"
|
<button class="duality-roll-button"
|
||||||
data-title="${label}"
|
data-title="${label}"
|
||||||
data-label="${dataLabel}"
|
data-label="${dataLabel}"
|
||||||
|
data-reaction="${roll.reaction ? 'true' : 'false'}"
|
||||||
data-hope="${roll.hope ?? 'd12'}"
|
data-hope="${roll.hope ?? 'd12'}"
|
||||||
data-fear="${roll.fear ?? 'd12'}"
|
data-fear="${roll.fear ?? 'd12'}"
|
||||||
${advantage ? `data-advantage="${advantage}"` : ''}
|
${advantage ? `data-advantage="${advantage}"` : ''}
|
||||||
|
|
@ -46,9 +48,9 @@ function getDualityMessage(roll) {
|
||||||
${roll.advantage ? 'data-advantage="true"' : ''}
|
${roll.advantage ? 'data-advantage="true"' : ''}
|
||||||
${roll.disadvantage ? 'data-disadvantage="true"' : ''}
|
${roll.disadvantage ? 'data-disadvantage="true"' : ''}
|
||||||
>
|
>
|
||||||
<i class="fa-solid fa-circle-half-stroke"></i>
|
${roll.reaction ? '<i class="fa-solid fa-reply"></i>' : '<i class="fa-solid fa-circle-half-stroke"></i>'}
|
||||||
${label}
|
${label}
|
||||||
${roll.difficulty || advantageLabel ? `(${[roll.difficulty, advantageLabel ? game.i18n.localize(`DAGGERHEART.GENERAL.${advantageLabel}.short`) : null].filter(x => x).join(' ')})` : ''}
|
${!flavor && (roll.difficulty || advantageLabel) ? `(${[roll.difficulty, advantageLabel ? game.i18n.localize(`DAGGERHEART.GENERAL.${advantageLabel}.short`) : null].filter(x => x).join(' ')})` : ''}
|
||||||
</button>
|
</button>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
@ -57,6 +59,7 @@ function getDualityMessage(roll) {
|
||||||
|
|
||||||
export const renderDualityButton = async event => {
|
export const renderDualityButton = async event => {
|
||||||
const button = event.currentTarget,
|
const button = event.currentTarget,
|
||||||
|
reaction = button.dataset.reaction === 'true',
|
||||||
traitValue = button.dataset.trait?.toLowerCase(),
|
traitValue = button.dataset.trait?.toLowerCase(),
|
||||||
target = getCommandTarget({ allowNull: true }),
|
target = getCommandTarget({ allowNull: true }),
|
||||||
difficulty = button.dataset.difficulty,
|
difficulty = button.dataset.difficulty,
|
||||||
|
|
@ -64,12 +67,12 @@ export const renderDualityButton = async event => {
|
||||||
|
|
||||||
await enrichedDualityRoll(
|
await enrichedDualityRoll(
|
||||||
{
|
{
|
||||||
|
reaction,
|
||||||
traitValue,
|
traitValue,
|
||||||
target,
|
target,
|
||||||
difficulty,
|
difficulty,
|
||||||
title: button.dataset.title,
|
title: button.dataset.title,
|
||||||
label: button.dataset.label,
|
label: button.dataset.label,
|
||||||
actionType: button.dataset.actionType,
|
|
||||||
advantage
|
advantage
|
||||||
},
|
},
|
||||||
event
|
event
|
||||||
|
|
@ -77,7 +80,7 @@ export const renderDualityButton = async event => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const enrichedDualityRoll = async (
|
export const enrichedDualityRoll = async (
|
||||||
{ traitValue, target, difficulty, title, label, actionType, advantage },
|
{ reaction, traitValue, target, difficulty, title, label, advantage },
|
||||||
event
|
event
|
||||||
) => {
|
) => {
|
||||||
const config = {
|
const config = {
|
||||||
|
|
@ -88,7 +91,7 @@ export const enrichedDualityRoll = async (
|
||||||
label: label,
|
label: label,
|
||||||
difficulty: difficulty,
|
difficulty: difficulty,
|
||||||
advantage,
|
advantage,
|
||||||
type: actionType ?? null // Need check,
|
type: reaction ? 'reaction' : null
|
||||||
},
|
},
|
||||||
chatMessage: {
|
chatMessage: {
|
||||||
template: 'systems/daggerheart/templates/ui/chat/duality-roll.hbs'
|
template: 'systems/daggerheart/templates/ui/chat/duality-roll.hbs'
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,19 @@ export { DhDamageEnricher, DhDualityRollEnricher, DhEffectEnricher, DhTemplateEn
|
||||||
|
|
||||||
export const enricherConfig = [
|
export const enricherConfig = [
|
||||||
{
|
{
|
||||||
pattern: /^@Damage\[(.*)\]$/g,
|
pattern: /^@Damage\[(.*)\]({.*})?$/g,
|
||||||
enricher: DhDamageEnricher
|
enricher: DhDamageEnricher
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: /\[\[\/dr\s?(.*?)\]\]/g,
|
pattern: /\[\[\/dr\s?(.*?)\]\]({.*})?/g,
|
||||||
enricher: DhDualityRollEnricher
|
enricher: DhDualityRollEnricher
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: /^@Effect\[(.*)\]$/g,
|
pattern: /^@Effect\[(.*)\]({.*})?$/g,
|
||||||
enricher: DhEffectEnricher
|
enricher: DhEffectEnricher
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: /^@Template\[(.*)\]$/g,
|
pattern: /^@Template\[(.*)\]({.*})?$/g,
|
||||||
enricher: DhTemplateEnricher
|
enricher: DhTemplateEnricher
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,12 @@ export const capitalize = string => {
|
||||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
export function rollCommandToJSON(text) {
|
export function rollCommandToJSON(text, raw) {
|
||||||
if (!text) return {};
|
if (!text) return {};
|
||||||
|
|
||||||
|
const flavorMatch = raw?.match(/{(.*)}$/);
|
||||||
|
const flavor = flavorMatch ? flavorMatch[1] : null;
|
||||||
|
|
||||||
// Match key="quoted string" OR key=unquotedValue
|
// Match key="quoted string" OR key=unquotedValue
|
||||||
const PAIR_RE = /(\w+)=("(?:[^"\\]|\\.)*"|\S+)/g;
|
const PAIR_RE = /(\w+)=("(?:[^"\\]|\\.)*"|\S+)/g;
|
||||||
const result = {};
|
const result = {};
|
||||||
|
|
@ -28,7 +31,7 @@ export function rollCommandToJSON(text) {
|
||||||
}
|
}
|
||||||
result[key] = value;
|
result[key] = value;
|
||||||
}
|
}
|
||||||
return Object.keys(result).length > 0 ? result : null;
|
return Object.keys(result).length > 0 ? { result, flavor } : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getCommandTarget = (options = {}) => {
|
export const getCommandTarget = (options = {}) => {
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,36 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.application.daggerheart.dialog.dh-style.views.roll-selection {
|
.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 {
|
.roll-dialog-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -16,6 +46,7 @@
|
||||||
max-width: 550px;
|
max-width: 550px;
|
||||||
|
|
||||||
.dices-section {
|
.dices-section {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 60px;
|
gap: 60px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
<header class="dialog-header">
|
<header class="dialog-header">
|
||||||
{{#if rollConfig.headerTitle}}
|
<h1>
|
||||||
<h1>{{rollConfig.headerTitle}}</h1>
|
{{ifThen rollConfig.headerTitle rollConfig.headerTitle rollConfig.title}}
|
||||||
{{else}}
|
{{#if showReaction}}
|
||||||
<h1>{{rollConfig.title}}</h1>
|
<button class="reaction-roll-controller {{#if reactionOverride}}active{{/if}}" data-action="toggleReaction" data-tooltip-text="{{localize "DAGGERHEART.GENERAL.reactionRoll"}}">
|
||||||
{{/if}}
|
<i class="fa-solid fa-reply"></i>
|
||||||
|
</button>
|
||||||
|
{{/if}}
|
||||||
|
</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|
@ -40,6 +40,7 @@
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dice-option">
|
<div class="dice-option">
|
||||||
<img class="dice-icon" src="{{concat 'systems/daggerheart/assets/icons/dice/fear/' @root.roll.dFear.denomination '.svg'}}" alt="">
|
<img class="dice-icon" src="{{concat 'systems/daggerheart/assets/icons/dice/fear/' @root.roll.dFear.denomination '.svg'}}" alt="">
|
||||||
<div class="dice-select">
|
<div class="dice-select">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue