mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-11 19:25:21 +01:00
* Updated the background image for the system * Fixed so Weapon/Armor features are added again * Fixed so fear is available as a resource to be deducted by actions (#757) * Changed to use the config labels and src * Updated Weapons * Fixed so the decrease button of simple fear tracker is not visible when not hovered * Fixed so armor preUpdate doesn't fail if no system changes are made * Updated .gitignore and author details (#777) * Add author details and name mapping for chrisryan10 (#773) Co-authored-by: Chris Ryan <chrisr@blackhole> * Add build to ignore for my linux dev (#775) Co-authored-by: Chris Ryan <chrisr@blackhole> --------- Co-authored-by: Chris Ryan <chrisr@blackhole> * Corrected sneak attack active effect (#780) * Fixed a spelling error (#779) * Fix bardic rally showing in damage dialog when it should not (#783) * update spelling (#786) * Translating inventory descriptions (#782) * updated credits for 1.0.1 release (#797) * updated credits for 1.0.1 release * further updated artwork credits * Chagned handlebarhelper rollparsed to be more defensive (#794) * Added missing scene refreshType (#790) * Remove ability use buttons for not owned abilities (#795) * [Fix] PrayerDice Fixed (#799) * Fixed prayer dice, and wheelchair images * Fixed -settings data sources * Dragging features from one adversary to another (#788) * [Fix] Levelup Fixes (#787) * Fixed crash on experience selection. Fixed subclass error on multiclassing * Fixed so multiclasses do not gain the hope feature for the class * Fixed so Class/Subclass features are properly deleted on delevel * Removed automatic deletion of features on delevel when not using levelup auto * Fixed so custom domains can be selected in levelup when multiclassing * Changed so encounter countdowns is a button (#804) * Fixed so that dropping on class/subclass...creates the item on the character (#803) * [BUG] - Importing All Adversaries/Environments (#814) Fixes #774 Co-authored-by: Joaquin Pereyra <joaquinpereyra98@users.noreply.github.com> * Bug/671 reaction roll chat title (#809) * Update Reaction Roll Chat Message Title * Removed console log --------- Co-authored-by: WBHarry <williambjrklund@gmail.com> * Improve Trait tooltip display (#817) Fixes #806 Co-authored-by: Joaquin Pereyra <joaquinpereyra98@users.noreply.github.com> * [BUG] - Combat Tracker d12 logo not found (#812) Fixes #764 Co-authored-by: Joaquin Pereyra <joaquinpereyra98@users.noreply.github.com> * Compendium Browser (#821) * Corrected timbending description localization (#816) * [Fix] Compendium Item (#810) * Corrected Emberwoven Armor * Fixed subclass regression * Fixed so character's with wildcard images don't break beastform (#815) * Fix roll result based duality damage (#822) --------- Co-authored-by: Chris Ryan <73275196+chrisryan10@users.noreply.github.com> Co-authored-by: Chris Ryan <chrisr@blackhole> Co-authored-by: Dapoulp <74197441+Dapoulp@users.noreply.github.com> Co-authored-by: IrkTheImp <41175833+IrkTheImp@users.noreply.github.com> Co-authored-by: CPTN_Cosmo <cptncosmo@gmail.com> Co-authored-by: Josh Q. <jshqntnr13@gmail.com> Co-authored-by: joaquinpereyra98 <24190917+joaquinpereyra98@users.noreply.github.com> Co-authored-by: Joaquin Pereyra <joaquinpereyra98@users.noreply.github.com>
258 lines
8.8 KiB
JavaScript
258 lines
8.8 KiB
JavaScript
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
|
|
|
|
export default class DHRoll extends Roll {
|
|
baseTerms = [];
|
|
constructor(formula, data = {}, options = {}) {
|
|
super(formula, data, options);
|
|
if (!this.data || !Object.keys(this.data).length) this.data = options.data;
|
|
}
|
|
|
|
get title() {
|
|
return game.i18n.localize('DAGGERHEART.GENERAL.Roll.basic');
|
|
}
|
|
|
|
static messageType = 'adversaryRoll';
|
|
|
|
static CHAT_TEMPLATE = 'systems/daggerheart/templates/ui/chat/roll.hbs';
|
|
|
|
static DefaultDialog = D20RollDialog;
|
|
|
|
static async build(config = {}, message = {}) {
|
|
const roll = await this.buildConfigure(config, message);
|
|
if (!roll) return;
|
|
await this.buildEvaluate(roll, config, (message = {}));
|
|
await this.buildPost(roll, config, (message = {}));
|
|
return config;
|
|
}
|
|
|
|
static async buildConfigure(config = {}, message = {}) {
|
|
config.hooks = [...this.getHooks(), ''];
|
|
config.dialog ??= {};
|
|
for (const hook of config.hooks) {
|
|
if (Hooks.call(`${CONFIG.DH.id}.preRoll${hook.capitalize()}`, config, message) === false) return null;
|
|
}
|
|
|
|
this.applyKeybindings(config);
|
|
|
|
this.temporaryModifierBuilder(config);
|
|
|
|
let roll = new this(config.roll.formula, config.data, config);
|
|
if (config.dialog.configure !== false) {
|
|
// Open Roll Dialog
|
|
const DialogClass = config.dialog?.class ?? this.DefaultDialog;
|
|
const configDialog = await DialogClass.configure(roll, config, message);
|
|
if (!configDialog) return;
|
|
}
|
|
|
|
for (const hook of config.hooks) {
|
|
if (
|
|
Hooks.call(`${CONFIG.DH.id}.post${hook.capitalize()}RollConfiguration`, roll, config, message) === false
|
|
)
|
|
return [];
|
|
}
|
|
return roll;
|
|
}
|
|
|
|
static async buildEvaluate(roll, config = {}, message = {}) {
|
|
if (config.evaluate !== false) {
|
|
await roll.evaluate();
|
|
config.roll = this.postEvaluate(roll, config);
|
|
}
|
|
}
|
|
|
|
static async buildPost(roll, config, message) {
|
|
for (const hook of config.hooks) {
|
|
if (Hooks.call(`${CONFIG.DH.id}.postRoll${hook.capitalize()}`, config, message) === false) return null;
|
|
}
|
|
|
|
// Create Chat Message
|
|
if (!config.source?.message) config.message = await this.toMessage(roll, config);
|
|
}
|
|
|
|
static postEvaluate(roll, config = {}) {
|
|
return {
|
|
total: roll.total,
|
|
formula: roll.formula,
|
|
dice: roll.dice.map(d => ({
|
|
dice: d.denomination,
|
|
total: d.total,
|
|
formula: d.formula,
|
|
results: d.results
|
|
}))
|
|
};
|
|
}
|
|
|
|
static async toMessage(roll, config) {
|
|
const cls = getDocumentClass('ChatMessage'),
|
|
msg = {
|
|
type: this.messageType,
|
|
user: game.user.id,
|
|
title: roll.title,
|
|
speaker: cls.getSpeaker(),
|
|
sound: config.mute ? null : CONFIG.sounds.dice,
|
|
system: config,
|
|
rolls: [roll]
|
|
};
|
|
config.selectedRollMode ??= game.settings.get('core', 'rollMode');
|
|
if (roll._evaluated) return await cls.create(msg, { rollMode: config.selectedRollMode });
|
|
return msg;
|
|
}
|
|
|
|
/** @inheritDoc */
|
|
async render({ flavor, template = this.constructor.CHAT_TEMPLATE, isPrivate = false, ...options } = {}) {
|
|
if (!this._evaluated) return;
|
|
const chatData = await this._prepareChatRenderContext({ flavor, isPrivate, ...options });
|
|
return foundry.applications.handlebars.renderTemplate(template, chatData);
|
|
}
|
|
|
|
/** @inheritDoc */
|
|
async _prepareChatRenderContext({ flavor, isPrivate = false, ...options } = {}) {
|
|
if (isPrivate) {
|
|
return {
|
|
user: game.user.id,
|
|
flavor: null,
|
|
title: '???',
|
|
roll: {
|
|
total: '??'
|
|
},
|
|
hasRoll: true,
|
|
isPrivate
|
|
};
|
|
} else {
|
|
options.message.system.user = game.user.id;
|
|
return options.message.system;
|
|
}
|
|
}
|
|
|
|
static applyKeybindings(config) {
|
|
if (config.event)
|
|
config.dialog.configure ??= !(config.event.shiftKey || config.event.altKey || config.event.ctrlKey);
|
|
}
|
|
|
|
static getHooks(hooks) {
|
|
return hooks ?? [];
|
|
}
|
|
|
|
formatModifier(modifier) {
|
|
if (Array.isArray(modifier)) {
|
|
return [
|
|
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
|
...this.constructor.parse(modifier.join(' + '), this.options.data)
|
|
];
|
|
} else {
|
|
const numTerm = modifier < 0 ? '-' : '+';
|
|
return [
|
|
new foundry.dice.terms.OperatorTerm({ operator: numTerm }),
|
|
new foundry.dice.terms.NumericTerm({ number: Math.abs(modifier) })
|
|
];
|
|
}
|
|
}
|
|
|
|
applyBaseBonus() {
|
|
return [];
|
|
}
|
|
|
|
addModifiers(roll) {
|
|
roll = roll ?? this.options.roll;
|
|
roll.modifiers?.forEach(m => {
|
|
this.terms.push(...this.formatModifier(m.value));
|
|
});
|
|
}
|
|
|
|
getBonus(path, label) {
|
|
const bonus = foundry.utils.getProperty(this.data.bonuses, path),
|
|
modifiers = [];
|
|
if (bonus?.bonus)
|
|
modifiers.push({
|
|
label: label,
|
|
value: bonus?.bonus
|
|
});
|
|
if (bonus?.dice?.length)
|
|
modifiers.push({
|
|
label: label,
|
|
value: bonus?.dice
|
|
});
|
|
return modifiers;
|
|
}
|
|
|
|
getFaces(faces) {
|
|
return Number(faces.startsWith('d') ? faces.replace('d', '') : faces);
|
|
}
|
|
|
|
constructFormula(config) {
|
|
this.terms = Roll.parse(this.options.roll.formula, config.data);
|
|
|
|
this.options.roll.modifiers = this.applyBaseBonus();
|
|
this.addModifiers();
|
|
|
|
if (this.options.extraFormula) {
|
|
this.terms.push(
|
|
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
|
...this.constructor.parse(this.options.extraFormula, this.options.data)
|
|
);
|
|
}
|
|
return (this._formula = this.constructor.getFormula(this.terms));
|
|
}
|
|
|
|
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}`);
|
|
}
|
|
return modifierTotal;
|
|
}
|
|
|
|
static temporaryModifierBuilder(config) {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
export const registerRollDiceHooks = () => {
|
|
Hooks.on(`${CONFIG.DH.id}.postRollDuality`, async (config, message) => {
|
|
const hopeFearAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).hopeFear;
|
|
if (
|
|
!config.source?.actor ||
|
|
(game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) ||
|
|
config.roll.type === 'reaction'
|
|
)
|
|
return;
|
|
|
|
const actor = await fromUuid(config.source.actor),
|
|
updates = [];
|
|
if (!actor) return;
|
|
if (config.roll.isCritical || config.roll.result.duality === 1) updates.push({ key: 'hope', value: 1 });
|
|
if (config.roll.isCritical) updates.push({ key: 'stress', value: -1 });
|
|
if (config.roll.result.duality === -1) updates.push({ key: 'fear', value: 1 });
|
|
|
|
if (config.rerolledRoll) {
|
|
if (config.rerolledRoll.isCritical || config.rerolledRoll.result.duality === 1)
|
|
updates.push({ key: 'hope', value: -1 });
|
|
if (config.rerolledRoll.isCritical) updates.push({ key: 'stress', value: 1 });
|
|
if (config.rerolledRoll.result.duality === -1) updates.push({ key: 'fear', value: -1 });
|
|
}
|
|
|
|
if (updates.length) {
|
|
const target = actor.system.partner ?? actor;
|
|
if (!['dead', 'unconscious'].some(x => actor.statuses.has(x))) {
|
|
setTimeout(() => {
|
|
target.modifyResource(updates);
|
|
}, 50);
|
|
}
|
|
}
|
|
|
|
if (!config.roll.hasOwnProperty('success') && !config.targets?.length) return;
|
|
|
|
const rollResult = config.roll.success || config.targets.some(t => t.hit),
|
|
looseSpotlight = !rollResult || config.roll.result.duality === -1;
|
|
|
|
if (looseSpotlight && game.combat?.active) {
|
|
const currentCombatant = game.combat.combatants.get(game.combat.current?.combatantId);
|
|
if (currentCombatant?.actorId == actor.id) ui.combat.setCombatantSpotlight(currentCombatant.id);
|
|
}
|
|
});
|
|
};
|