Partial Fate Roll creation and Fate Roll Enricher (/fr)

This commit is contained in:
Chris Ryan 2025-11-26 23:06:53 +10:00
parent 04f8793f20
commit 54996e7e12
12 changed files with 257 additions and 7 deletions

View file

@ -116,14 +116,14 @@ 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);
if (this.actor.system.traits) context.abilities = this.getTraitModifiers();
if (this.actor?.system?.traits) context.abilities = this.getTraitModifiers();
context.showReaction = !this.config.roll?.type && context.rollType === 'DualityRoll';
context.reactionOverride = this.reactionOverride;
}
const tagTeamSetting = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
if (tagTeamSetting.members[this.actor.id] && !this.config.skips?.createMessage) {
if (this.actor?.id && tagTeamSetting.members[this.actor.id] && !this.config.skips?.createMessage) {
context.activeTagTeamRoll = true;
context.tagTeamSelected = this.config.tagTeamSelected;
}

View file

@ -8,6 +8,7 @@ export const config = {
adversaryRoll: DHActorRoll,
damageRoll: DHActorRoll,
dualityRoll: DHActorRoll,
fateRoll: DHActorRoll,
groupRoll: DHGroupRoll,
systemMessage: DHSystemMessage
};

View file

@ -3,3 +3,4 @@ export { default as D20Roll } from './d20Roll.mjs';
export { default as DamageRoll } from './damageRoll.mjs';
export { default as DHRoll } from './dhRoll.mjs';
export { default as DualityRoll } from './dualityRoll.mjs';
export { default as FateRoll } from './fateRoll.mjs';

127
module/dice/fateRoll.mjs Normal file
View file

@ -0,0 +1,127 @@
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
import D20Roll from './d20Roll.mjs';
import { setDiceSoNiceForFateRoll } from '../helpers/utils.mjs';
export default class FateRoll extends D20Roll {
constructor(formula, data = {}, options = {}) {
super(formula, data, options);
}
static messageType = 'fateRoll';
static DefaultDialog = D20RollDialog;
get title() {
return game.i18n.localize(
`DAGGERHEART.GENERAL.fateRoll`
);
}
get dHope() {
// if ( !(this.terms[0] instanceof foundry.dice.terms.Die) ) return;
if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice();
return this.dice[0];
// return this.#hopeDice;
}
set dHope(faces) {
if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice();
this.terms[0].faces = this.getFaces(faces);
// this.#hopeDice = `d${face}`;
}
get isCritical() {
return false;
}
static getHooks(hooks) {
return [...(hooks ?? []), 'Fate'];
}
/** @inheritDoc */
static fromData(data) {
data.terms[0].class = foundry.dice.terms.Die.name;
return super.fromData(data);
}
createBaseDice() {
if (this.dice[0] instanceof foundry.dice.terms.Die) {
this.terms = [this.terms[0]];
return;
}
this.terms[0] = new foundry.dice.terms.Die({ faces: 12 });
}
static async buildEvaluate(roll, config = {}, message = {}) {
await super.buildEvaluate(roll, config, message);
await setDiceSoNiceForFateRoll(
roll,
config.roll.hope.dice
);
}
static postEvaluate(roll, config = {}) {
const data = super.postEvaluate(roll, config);
data.hope = {
dice: roll.dHope.denomination,
value: roll.dHope.total,
rerolled: {
any: roll.dHope.results.some(x => x.rerolled),
rerolls: roll.dHope.results.filter(x => x.rerolled)
}
};
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 = await getDiceSoNicePresets(`d${term._faces}`, `d${term._faces}`);
// const type = target.dataset.type;
// if (diceSoNicePresets[type]) {
// diceSoNiceRoll.dice[0].options = 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);
// const tagTeamSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.TagTeamRoll);
// Hooks.call(`${CONFIG.DH.id}.postRollDuality`, {
// source: { actor: message.system.source.actor ?? '' },
// targets: message.system.targets,
// tagTeamSelected: Object.values(tagTeamSettings.members).some(x => x.messageId === message._id),
// roll: newRoll,
// rerolledRoll:
// newRoll.result.duality !== message.system.roll.result.duality ? message.system.roll : undefined
// });
// return { newRoll, parsedRoll };
// }
}

View file

@ -87,6 +87,10 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
break;
}
}
if (this.type === 'fateRoll') {
html.classList.add('fate');
html.classList.add('hope');
}
const autoExpandRoll = game.settings.get(
CONFIG.DH.id,

View file

@ -0,0 +1,64 @@
import { abilities } from '../config/actorConfig.mjs';
import { getCommandTarget, rollCommandToJSON } from '../helpers/utils.mjs';
export default function DhFateRollEnricher(match, _options) {
const roll = rollCommandToJSON(match[1], match[0]);
if (!roll) return match[0];
return getFateMessage(roll.result, roll.flavor ?? 'FLAVOR');
}
function getFateMessage(roll, flavor) {
const label = flavor ?? 'fate';
const dataLabel = game.i18n.localize('DAGGERHEART.GENERAL.fate');
const fateElement = document.createElement('span');
fateElement.innerHTML = `
<button type="button" class="fate-roll-button${roll?.inline ? ' inline' : ''}"
data-title="${label}"
data-label="${dataLabel}"
data-hope="${roll?.hope ?? 'd12'}"
${label}
</button>
`;
return fateElement;
}
export const renderFateButton = async event => {
const button = event.currentTarget,
target = getCommandTarget({ allowNull: true });
await enrichedFateRoll(
{
target,
title: button.dataset.title,
label: button.dataset.label
},
event
);
};
export const enrichedFateRoll = async (
{ target, title, label },
event
) => {
const config = {
event: event ?? {},
title: title,
roll: {
label: label,
},
hasRoll: true
};
if (target) {
await target.diceRoll(config);
} else {
// For no target, call FateRoll directly with basic data
config.data = { experiences: {}, traits: {} };
config.source = { actor: null };
await CONFIG.Dice.daggerheart.FateRoll.build(config);
}
};

View file

@ -1,10 +1,11 @@
import { default as DhDamageEnricher, renderDamageButton } from './DamageEnricher.mjs';
import { default as DhDualityRollEnricher, renderDualityButton } from './DualityRollEnricher.mjs';
import { default as DhFateRollEnricher, renderFateButton } from './FateRollEnricher.mjs';
import { default as DhEffectEnricher } from './EffectEnricher.mjs';
import { default as DhTemplateEnricher, renderMeasuredTemplate } from './TemplateEnricher.mjs';
import { default as DhLookupEnricher } from './LookupEnricher.mjs';
export { DhDamageEnricher, DhDualityRollEnricher, DhEffectEnricher, DhTemplateEnricher };
export { DhDamageEnricher, DhDualityRollEnricher, DhEffectEnricher, DhTemplateEnricher, DhFateRollEnricher };
export const enricherConfig = [
{
@ -15,6 +16,10 @@ export const enricherConfig = [
pattern: /\[\[\/dr\s?(.*?)\]\]({[^}]*})?/g,
enricher: DhDualityRollEnricher
},
{
pattern: /\[\[\/fr\s?(.*?)\]\]({[^}]*})?/g,
enricher: DhFateRollEnricher
},
{
pattern: /@Effect\[([^\[\]]*)\]({[^}]*})?/g,
enricher: DhEffectEnricher
@ -38,6 +43,10 @@ export const enricherRenderSetup = element => {
.querySelectorAll('.duality-roll-button')
.forEach(element => element.addEventListener('click', renderDualityButton));
element
.querySelectorAll('.fate-roll-button')
.forEach(element => element.addEventListener('click', renderFateButton));
element
.querySelectorAll('.measured-template-button')
.forEach(element => element.addEventListener('click', renderMeasuredTemplate));

View file

@ -1,4 +1,4 @@
import { diceTypes, getDiceSoNicePresets, range } from '../config/generalConfig.mjs';
import { diceTypes, getDiceSoNicePresets, getDiceSoNicePreset, range } from '../config/generalConfig.mjs';
import Tagify from '@yaireo/tagify';
export const capitalize = string => {
@ -69,6 +69,13 @@ export const setDiceSoNiceForDualityRoll = async (rollResult, advantageState, ho
}
};
export const setDiceSoNiceForFateRoll = async (rollResult, hopeFaces) => {
if (!game.modules.get('dice-so-nice')?.active) return;
const { diceSoNice } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance);
const diceSoNicePresets = await getDiceSoNicePreset(diceSoNice.hope, hopeFaces);
rollResult.dice[0].options = diceSoNicePresets.hope;
};
export const chunkify = (array, chunkSize, mappingFunc) => {
var chunkifiedArray = [];
for (let i = 0; i < array.length; i += chunkSize) {