mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-04-21 23:13:39 +02:00
[V14] Message Rolls Rework (#1757)
* Basic rework to the roll data in messages * . * Fixed advantage/disadvantage * . * . * Fixed TagTeamDialog * Reuse getter in faces setter * Simplify fate roll type css class * Add more caution to the dualityRoll fromData function * Apply suggestion from @CarlosFdez * Compute modifiers using deterministic terms (#1758) --------- Co-authored-by: Carlos Fernandez <cfern1990@gmail.com> Co-authored-by: Carlos Fernandez <CarlosFdez@users.noreply.github.com>
This commit is contained in:
parent
dbd5ef8bb0
commit
f156b12d79
20 changed files with 267 additions and 249 deletions
|
|
@ -4,3 +4,4 @@ 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';
|
||||
export { diceTypes } from './die/_module.mjs';
|
||||
|
|
|
|||
|
|
@ -217,49 +217,11 @@ export default class D20Roll extends DHRoll {
|
|||
results: d.results
|
||||
};
|
||||
});
|
||||
data.modifierTotal = this.calculateTotalModifiers(roll);
|
||||
data.modifierTotal = roll.modifierTotal;
|
||||
return data;
|
||||
}
|
||||
|
||||
resetFormula() {
|
||||
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);
|
||||
}
|
||||
|
||||
const rerolled = {
|
||||
any: true,
|
||||
rerolls: [
|
||||
...(message.system.roll.dice[0].rerolled?.rerolls?.length > 0
|
||||
? [message.system.roll.dice[0].rerolled?.rerolls]
|
||||
: []),
|
||||
rollString.terms[0].results
|
||||
]
|
||||
};
|
||||
return {
|
||||
newRoll: {
|
||||
...newRoll,
|
||||
dice: [
|
||||
{
|
||||
...newRoll.dice[0],
|
||||
rerolled: rerolled
|
||||
}
|
||||
]
|
||||
},
|
||||
parsedRoll
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,10 @@ export default class DHRoll extends Roll {
|
|||
return game.i18n.localize('DAGGERHEART.GENERAL.Roll.basic');
|
||||
}
|
||||
|
||||
get modifierTotal() {
|
||||
return this.constructor.calculateTotalModifiers(this);
|
||||
}
|
||||
|
||||
static messageType = 'adversaryRoll';
|
||||
|
||||
static CHAT_TEMPLATE = 'systems/daggerheart/templates/ui/chat/roll.hbs';
|
||||
|
|
@ -138,6 +142,7 @@ export default class DHRoll extends Roll {
|
|||
const chatData = await this._prepareChatRenderContext({ flavor, isPrivate, ...options });
|
||||
return foundry.applications.handlebars.renderTemplate(template, {
|
||||
...chatData,
|
||||
roll: this,
|
||||
parent: chatData.parent,
|
||||
targetMode: chatData.targetMode,
|
||||
metagamingSettings
|
||||
|
|
@ -241,16 +246,21 @@ export default class DHRoll extends Roll {
|
|||
return (this._formula = this.constructor.getFormula(this.terms));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate total modifiers of any rolls, including non-dh rolls.
|
||||
* This exists because damage rolls still may receive base roll classes
|
||||
*/
|
||||
static calculateTotalModifiers(roll) {
|
||||
let modifierTotal = 0;
|
||||
for (let i = 0; i < roll.terms.length; i++) {
|
||||
if (
|
||||
roll.terms[i] instanceof foundry.dice.terms.NumericTerm &&
|
||||
!!roll.terms[i - 1] &&
|
||||
roll.terms[i - 1] instanceof foundry.dice.terms.OperatorTerm
|
||||
)
|
||||
modifierTotal += Number(`${roll.terms[i - 1].operator}${roll.terms[i].total}`);
|
||||
if (!roll.terms[i].isDeterministic) continue;
|
||||
const termTotal = roll.terms[i].total;
|
||||
if (typeof termTotal === 'number') {
|
||||
const multiplier = roll.terms[i - 1]?.operator === " - " ? -1 : 1;
|
||||
modifierTotal += multiplier * termTotal;
|
||||
}
|
||||
}
|
||||
|
||||
return modifierTotal;
|
||||
}
|
||||
|
||||
|
|
|
|||
9
module/dice/die/_module.mjs
Normal file
9
module/dice/die/_module.mjs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import DualityDie from './dualityDie.mjs';
|
||||
import AdvantageDie from './advantageDie.mjs';
|
||||
import DisadvantageDie from './disadvantageDie.mjs';
|
||||
|
||||
export const diceTypes = {
|
||||
DualityDie,
|
||||
AdvantageDie,
|
||||
DisadvantageDie
|
||||
};
|
||||
7
module/dice/die/advantageDie.mjs
Normal file
7
module/dice/die/advantageDie.mjs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export default class AdvantageDie extends foundry.dice.terms.Die {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.modifiers = [];
|
||||
}
|
||||
}
|
||||
7
module/dice/die/disadvantageDie.mjs
Normal file
7
module/dice/die/disadvantageDie.mjs
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export default class DisadvantageDie extends foundry.dice.terms.Die {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.modifiers = [];
|
||||
}
|
||||
}
|
||||
62
module/dice/die/dualityDie.mjs
Normal file
62
module/dice/die/dualityDie.mjs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import { ResourceUpdateMap } from '../../data/action/baseAction.mjs';
|
||||
|
||||
export default class DualityDie extends foundry.dice.terms.Die {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
this.modifiers = [];
|
||||
}
|
||||
|
||||
#getDualityState(roll) {
|
||||
if (!roll) return null;
|
||||
return roll.withHope ? 1 : roll.withFear ? -1 : 0;
|
||||
}
|
||||
|
||||
#updateResources(oldDuality, newDuality, actor) {
|
||||
const { hopeFear } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
|
||||
if (game.user.isGM ? !hopeFear.gm : !hopeFear.players) return;
|
||||
|
||||
const updates = [];
|
||||
const hope = (newDuality >= 0 ? 1 : 0) - (oldDuality >= 0 ? 1 : 0);
|
||||
const stress = (newDuality === 0 ? 1 : 0) - (oldDuality === 0 ? 1 : 0);
|
||||
const fear = (newDuality === -1 ? 1 : 0) - (oldDuality === -1 ? 1 : 0);
|
||||
|
||||
if (hope !== 0) updates.push({ key: 'hope', value: hope, total: -1 * hope, enabled: true });
|
||||
if (stress !== 0) updates.push({ key: 'stress', value: -1 * stress, total: stress, enabled: true });
|
||||
if (fear !== 0) updates.push({ key: 'fear', value: fear, total: -1 * fear, enabled: true });
|
||||
|
||||
const resourceUpdates = new ResourceUpdateMap(actor);
|
||||
resourceUpdates.addResources(updates);
|
||||
resourceUpdates.updateResources();
|
||||
}
|
||||
|
||||
async reroll(modifier, options) {
|
||||
const oldDuality = this.#getDualityState(options.liveRoll.roll);
|
||||
await super.reroll(modifier, options);
|
||||
|
||||
if (options?.liveRoll) {
|
||||
/* Can't currently test since DiceSoNice is not v14. Might need to set the appearance earlier if a roll is triggered by super.reroll */
|
||||
if (game.modules.get('dice-so-nice')?.active) {
|
||||
const diceSoNiceRoll = {
|
||||
_evaluated: true,
|
||||
dice: [this],
|
||||
options: { appearance: {} }
|
||||
};
|
||||
|
||||
const preset = await getDiceSoNicePreset(diceSoNice[key], faces);
|
||||
diceSoNiceRoll.dice[0].options.appearance = preset.appearance;
|
||||
diceSoNiceRoll.dice[0].options.modelFile = preset.modelFile;
|
||||
|
||||
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true);
|
||||
} else {
|
||||
foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
|
||||
}
|
||||
|
||||
await options.liveRoll.roll._evaluate();
|
||||
if (options.liveRoll.isReaction) return;
|
||||
|
||||
const newDuality = this.#getDualityState(options.liveRoll.roll);
|
||||
this.#updateResources(oldDuality, newDuality, options.liveRoll.actor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
|
||||
import D20Roll from './d20Roll.mjs';
|
||||
import { parseRallyDice, setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
|
||||
import { getDiceSoNicePresets } from '../config/generalConfig.mjs';
|
||||
import { ResourceUpdateMap } from '../data/action/baseAction.mjs';
|
||||
|
||||
export default class DualityRoll extends D20Roll {
|
||||
_advantageFaces = 6;
|
||||
|
|
@ -26,27 +24,31 @@ export default class DualityRoll extends D20Roll {
|
|||
}
|
||||
|
||||
get dHope() {
|
||||
if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
if (!(this.dice[0] instanceof game.system.api.dice.diceTypes.DualityDie)) this.createBaseDice();
|
||||
return this.dice[0];
|
||||
}
|
||||
|
||||
set dHope(faces) {
|
||||
if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
this.dice[0].faces = this.getFaces(faces);
|
||||
// TODO this should not be asymmetrical with the getter. updateRollConfiguration() should use dHope.faces
|
||||
this.dHope.faces = this.getFaces(faces);
|
||||
}
|
||||
|
||||
get dFear() {
|
||||
if (!(this.dice[1] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
if (!(this.dice[1] instanceof game.system.api.dice.diceTypes.DualityDie)) this.createBaseDice();
|
||||
return this.dice[1];
|
||||
}
|
||||
|
||||
set dFear(faces) {
|
||||
if (!(this.dice[1] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
this.dice[1].faces = this.getFaces(faces);
|
||||
// TODO this should not be asymmetrical with the getter. updateRollConfiguration() should use dFear.faces
|
||||
this.dFear.faces = this.getFaces(faces);
|
||||
}
|
||||
|
||||
get dAdvantage() {
|
||||
return this.dice[2];
|
||||
return this.dice[2] instanceof game.system.api.dice.diceTypes.AdvantageDie ? this.dice[2] : null;
|
||||
}
|
||||
|
||||
get dDisadvantage() {
|
||||
return this.dice[2] instanceof game.system.api.dice.diceTypes.DisadvantageDie ? this.dice[2] : null;
|
||||
}
|
||||
|
||||
get advantageFaces() {
|
||||
|
|
@ -65,6 +67,11 @@ export default class DualityRoll extends D20Roll {
|
|||
this._advantageNumber = Number(value);
|
||||
}
|
||||
|
||||
get extraDice() {
|
||||
const { DualityDie, AdvantageDie, DisadvantageDie } = game.system.api.dice.diceTypes;
|
||||
return this.dice.filter(x => ![DualityDie, AdvantageDie, DisadvantageDie].some(die => x instanceof die));
|
||||
}
|
||||
|
||||
setRallyChoices() {
|
||||
return this.data?.parent?.appliedEffects.reduce((a, c) => {
|
||||
const change = c.system.changes.find(ch => ch.key === 'system.bonuses.rally');
|
||||
|
|
@ -118,22 +125,28 @@ export default class DualityRoll extends D20Roll {
|
|||
|
||||
/** @inheritDoc */
|
||||
static fromData(data) {
|
||||
data.terms[0].class = foundry.dice.terms.Die.name;
|
||||
data.terms[2].class = foundry.dice.terms.Die.name;
|
||||
data.terms[0].class = 'DualityDie';
|
||||
data.terms[2].class = 'DualityDie';
|
||||
if (data.options.roll.advantage?.type && data.terms[4]?.faces) {
|
||||
data.terms[4].class = data.options.roll.advantage.type === 1 ? 'AdvantageDie' : 'DisadvantageDie';
|
||||
}
|
||||
return super.fromData(data);
|
||||
}
|
||||
|
||||
createBaseDice() {
|
||||
if (this.dice[0] instanceof foundry.dice.terms.Die && this.dice[1] instanceof foundry.dice.terms.Die) {
|
||||
if (
|
||||
this.dice[0] instanceof game.system.api.dice.diceTypes.DualityDie &&
|
||||
this.dice[1] instanceof game.system.api.dice.diceTypes.DualityDie
|
||||
) {
|
||||
this.terms = [this.terms[0], this.terms[1], this.terms[2]];
|
||||
return;
|
||||
}
|
||||
|
||||
this.terms[0] = new foundry.dice.terms.Die({
|
||||
this.terms[0] = new game.system.api.dice.diceTypes.DualityDie({
|
||||
faces: this.data.rules.dualityRoll?.defaultHopeDice ?? 12
|
||||
});
|
||||
this.terms[1] = new foundry.dice.terms.OperatorTerm({ operator: '+' });
|
||||
this.terms[2] = new foundry.dice.terms.Die({
|
||||
this.terms[2] = new game.system.api.dice.diceTypes.DualityDie({
|
||||
faces: this.data.rules.dualityRoll?.defaultFearDice ?? 12
|
||||
});
|
||||
}
|
||||
|
|
@ -371,63 +384,4 @@ export default class DualityRoll extends D20Roll {
|
|||
if (currentCombatant?.actorId == config.data.id) ui.combat.setCombatantSpotlight(currentCombatant.id);
|
||||
}
|
||||
}
|
||||
|
||||
static async reroll(rollBase, dieIndex, diceType, updateResources = true) {
|
||||
let parsedRoll = game.system.api.dice.DualityRoll.fromData({ ...rollBase, evaluated: false });
|
||||
const term = parsedRoll.terms[dieIndex];
|
||||
await term.reroll(`/r1=${term.total}`);
|
||||
const result = await parsedRoll.evaluate();
|
||||
|
||||
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}`);
|
||||
if (diceSoNicePresets[diceType]) {
|
||||
diceSoNiceRoll.dice[0].options = diceSoNicePresets[diceType];
|
||||
}
|
||||
|
||||
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true);
|
||||
} else {
|
||||
foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
|
||||
}
|
||||
|
||||
const newRoll = game.system.api.dice.DualityRoll.postEvaluate(parsedRoll, {
|
||||
targets: parsedRoll.options.targets ?? [],
|
||||
roll: {
|
||||
advantage: parsedRoll.options.roll.advantage?.type,
|
||||
difficulty: parsedRoll.options.roll.difficulty ? Number(parsedRoll.options.roll.difficulty) : null
|
||||
}
|
||||
});
|
||||
|
||||
const extraIndex = newRoll.advantage ? 3 : 2;
|
||||
newRoll.extra = newRoll.extra.slice(extraIndex);
|
||||
|
||||
const actor = parsedRoll.options.source.actor
|
||||
? await foundry.utils.fromUuid(parsedRoll.options.source.actor)
|
||||
: null;
|
||||
const config = {
|
||||
source: { actor: parsedRoll.options.source.actor ?? '' },
|
||||
targets: parsedRoll.targets,
|
||||
roll: newRoll,
|
||||
rerolledRoll: parsedRoll.options.roll,
|
||||
resourceUpdates: new ResourceUpdateMap(actor)
|
||||
};
|
||||
|
||||
if (updateResources) {
|
||||
await DualityRoll.addDualityResourceUpdates(config);
|
||||
await config.resourceUpdates.updateResources();
|
||||
}
|
||||
|
||||
return { newRoll, parsedRoll };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ export default class FateRoll extends D20Roll {
|
|||
}
|
||||
|
||||
set dHope(faces) {
|
||||
if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
this.dice[0].faces = this.getFaces(faces);
|
||||
// TODO this should not be asymmetrical with the getter. updateRollConfiguration() should use dHope.faces
|
||||
this.dHope.faces = this.getFaces(faces);
|
||||
}
|
||||
|
||||
get dFear() {
|
||||
|
|
@ -31,8 +31,8 @@ export default class FateRoll extends D20Roll {
|
|||
}
|
||||
|
||||
set dFear(faces) {
|
||||
if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice();
|
||||
this.dice[0].faces = this.getFaces(faces);
|
||||
// TODO this should not be asymmetrical with the getter. updateRollConfiguration() should use dFear.faces
|
||||
this.dFear.faces = this.getFaces(faces);
|
||||
}
|
||||
|
||||
get isCritical() {
|
||||
|
|
@ -43,6 +43,22 @@ export default class FateRoll extends D20Roll {
|
|||
return this.data.fateType;
|
||||
}
|
||||
|
||||
get withHope() {
|
||||
if (!this._evaluatedl) return;
|
||||
return this.dHope.total >= this.dFear.total;
|
||||
}
|
||||
|
||||
get withFear() {
|
||||
if (!this._evaluated) return;
|
||||
return this.dHope.total < this.dFear.total;
|
||||
}
|
||||
|
||||
get totalLabel() {
|
||||
const label = this.withHope ? 'DAGGERHEART.GENERAL.hope' : 'DAGGERHEART.GENERAL.fear';
|
||||
|
||||
return game.i18n.localize(label);
|
||||
}
|
||||
|
||||
static getHooks(hooks) {
|
||||
return [...(hooks ?? []), 'Fate'];
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue