mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-11 19:25:21 +01:00
[Feature] Damage-Reroll (#753)
* Added rerolls for damage dice in chat * Fixed multiple dice * Added reroll icon * Fixed new style of dialog
This commit is contained in:
parent
2aaab73699
commit
300719c116
28 changed files with 1094 additions and 167 deletions
|
|
@ -6,5 +6,6 @@ export { default as DeathMove } from './deathMove.mjs';
|
|||
export { default as Downtime } from './downtime.mjs';
|
||||
export { default as MulticlassChoiceDialog } from './multiclassChoiceDialog.mjs';
|
||||
export { default as OwnershipSelection } from './ownershipSelection.mjs';
|
||||
export { default as RerollDamageDialog } from './rerollDamageDialog.mjs';
|
||||
export { default as ResourceDiceDialog } from './resourceDiceDialog.mjs';
|
||||
export { default as ActionSelectionDialog } from './actionSelectionDialog.mjs';
|
||||
|
|
|
|||
|
|
@ -151,11 +151,19 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
this.config.experiences.indexOf(button.dataset.key) > -1
|
||||
? this.config.experiences.filter(x => x !== button.dataset.key)
|
||||
: [...this.config.experiences, button.dataset.key];
|
||||
if(this.config?.data?.parent?.type === 'character' || this.config?.data?.parent?.type === 'companion') {
|
||||
if (this.config?.data?.parent?.type === 'character' || this.config?.data?.parent?.type === 'companion') {
|
||||
this.config.costs =
|
||||
this.config.costs.indexOf(this.config.costs.find(c => c.extKey === button.dataset.key)) > -1
|
||||
? this.config.costs.filter(x => x.extKey !== button.dataset.key)
|
||||
: [...this.config.costs, { extKey: button.dataset.key, key: 'hope', value: 1, name: this.config.data?.experiences?.[button.dataset.key]?.name }];
|
||||
: [
|
||||
...this.config.costs,
|
||||
{
|
||||
extKey: button.dataset.key,
|
||||
key: 'hope',
|
||||
value: 1,
|
||||
name: this.config.data?.experiences?.[button.dataset.key]?.name
|
||||
}
|
||||
];
|
||||
}
|
||||
this.render();
|
||||
}
|
||||
|
|
@ -166,8 +174,8 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
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;
|
||||
? null
|
||||
: this.config.roll.type;
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
279
module/applications/dialogs/rerollDamageDialog.mjs
Normal file
279
module/applications/dialogs/rerollDamageDialog.mjs
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
export default class RerollDamageDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(message, options = {}) {
|
||||
super(options);
|
||||
|
||||
this.message = message;
|
||||
this.damage = Object.keys(message.system.damage).reduce((acc, typeKey) => {
|
||||
const type = message.system.damage[typeKey];
|
||||
acc[typeKey] = Object.keys(type.parts).reduce((acc, partKey) => {
|
||||
const part = type.parts[partKey];
|
||||
acc[partKey] = Object.keys(part.dice).reduce((acc, diceKey) => {
|
||||
const dice = part.dice[diceKey];
|
||||
const activeResults = dice.results.filter(x => x.active);
|
||||
acc[diceKey] = {
|
||||
dice: dice.dice,
|
||||
selectedResults: activeResults.length,
|
||||
maxSelected: activeResults.length,
|
||||
results: activeResults.map(x => ({ ...x, selected: true }))
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
id: 'reroll-dialog',
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'reroll-dialog'],
|
||||
window: {
|
||||
icon: 'fa-solid fa-dice'
|
||||
},
|
||||
actions: {
|
||||
toggleResult: RerollDamageDialog.#toggleResult,
|
||||
selectRoll: RerollDamageDialog.#selectRoll,
|
||||
doReroll: RerollDamageDialog.#doReroll,
|
||||
save: RerollDamageDialog.#save
|
||||
}
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
id: 'main',
|
||||
template: 'systems/daggerheart/templates/dialogs/rerollDialog/damage/main.hbs'
|
||||
},
|
||||
footer: {
|
||||
id: 'footer',
|
||||
template: 'systems/daggerheart/templates/dialogs/rerollDialog/footer.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.damageTitle');
|
||||
}
|
||||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
|
||||
htmlElement.querySelectorAll('.to-reroll-input').forEach(element => {
|
||||
element.addEventListener('change', this.toggleDice.bind(this));
|
||||
});
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.damage = this.damage;
|
||||
context.disabledReroll = !this.getRerollDice().length;
|
||||
context.saveDisabled = !this.isSelectionDone();
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static async #save() {
|
||||
const update = {
|
||||
'system.damage': Object.keys(this.damage).reduce((acc, typeKey) => {
|
||||
const type = this.damage[typeKey];
|
||||
let typeTotal = 0;
|
||||
const messageType = this.message.system.damage[typeKey];
|
||||
const parts = Object.keys(type).map(partKey => {
|
||||
const part = type[partKey];
|
||||
const messagePart = messageType.parts[partKey];
|
||||
let partTotal = messagePart.modifierTotal;
|
||||
const dice = Object.keys(part).map(diceKey => {
|
||||
const dice = part[diceKey];
|
||||
const total = dice.results.reduce((acc, result) => {
|
||||
if (result.active) acc += result.result;
|
||||
return acc;
|
||||
}, 0);
|
||||
partTotal += total;
|
||||
const messageDice = messagePart.dice[diceKey];
|
||||
return {
|
||||
...messageDice,
|
||||
total: total,
|
||||
results: dice.results.map(x => ({
|
||||
...x,
|
||||
hasRerolls: dice.results.length > 1
|
||||
}))
|
||||
};
|
||||
});
|
||||
|
||||
typeTotal += partTotal;
|
||||
return {
|
||||
...messagePart,
|
||||
total: partTotal,
|
||||
dice: dice
|
||||
};
|
||||
});
|
||||
|
||||
acc[typeKey] = {
|
||||
...messageType,
|
||||
total: typeTotal,
|
||||
parts: parts
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
};
|
||||
await this.message.update(update);
|
||||
await this.close();
|
||||
}
|
||||
|
||||
getRerollDice() {
|
||||
const rerollDice = [];
|
||||
Object.keys(this.damage).forEach(typeKey => {
|
||||
const type = this.damage[typeKey];
|
||||
Object.keys(type).forEach(partKey => {
|
||||
const part = type[partKey];
|
||||
Object.keys(part).forEach(diceKey => {
|
||||
const dice = part[diceKey];
|
||||
Object.keys(dice.results).forEach(resultKey => {
|
||||
const result = dice.results[resultKey];
|
||||
if (result.toReroll) {
|
||||
rerollDice.push({
|
||||
...result,
|
||||
dice: dice.dice,
|
||||
type: typeKey,
|
||||
part: partKey,
|
||||
dice: diceKey,
|
||||
result: resultKey
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return rerollDice;
|
||||
}
|
||||
|
||||
isSelectionDone() {
|
||||
const diceFinishedData = [];
|
||||
Object.keys(this.damage).forEach(typeKey => {
|
||||
const type = this.damage[typeKey];
|
||||
Object.keys(type).forEach(partKey => {
|
||||
const part = type[partKey];
|
||||
Object.keys(part).forEach(diceKey => {
|
||||
const dice = part[diceKey];
|
||||
const selected = dice.results.reduce((acc, result) => acc + (result.active ? 1 : 0), 0);
|
||||
diceFinishedData.push(selected === dice.maxSelected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return diceFinishedData.every(x => x);
|
||||
}
|
||||
|
||||
toggleDice(event) {
|
||||
const target = event.target;
|
||||
const { type, part, dice } = target.dataset;
|
||||
const toggleDice = this.damage[type][part][dice];
|
||||
|
||||
const existingDiceRerolls = this.getRerollDice().filter(
|
||||
x => x.type === type && x.part === part && x.dice === dice
|
||||
);
|
||||
|
||||
const allRerolled = existingDiceRerolls.length === toggleDice.results.filter(x => x.active).length;
|
||||
|
||||
toggleDice.toReroll = !allRerolled;
|
||||
toggleDice.results.forEach(result => {
|
||||
if (result.active) {
|
||||
result.toReroll = !allRerolled;
|
||||
}
|
||||
});
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static #toggleResult(event) {
|
||||
event.stopPropagation();
|
||||
|
||||
const target = event.target.closest('.to-reroll-result');
|
||||
const { type, part, dice, result } = target.dataset;
|
||||
const toggleDice = this.damage[type][part][dice];
|
||||
const toggleResult = toggleDice.results[result];
|
||||
toggleResult.toReroll = !toggleResult.toReroll;
|
||||
|
||||
const existingDiceRerolls = this.getRerollDice().filter(
|
||||
x => x.type === type && x.part === part && x.dice === dice
|
||||
);
|
||||
|
||||
const allToReroll = existingDiceRerolls.length === toggleDice.results.filter(x => x.active).length;
|
||||
toggleDice.toReroll = allToReroll;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #selectRoll(_, button) {
|
||||
const { type, part, dice, result } = button.dataset;
|
||||
|
||||
const diceVal = this.damage[type][part][dice];
|
||||
const diceResult = diceVal.results[result];
|
||||
if (!diceResult.active && diceVal.results.filter(x => x.active).length === diceVal.maxSelected) {
|
||||
return ui.notifications.warn(
|
||||
game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.deselectDiceNotification')
|
||||
);
|
||||
}
|
||||
|
||||
if (diceResult.active) {
|
||||
diceVal.toReroll = false;
|
||||
diceResult.toReroll = false;
|
||||
}
|
||||
|
||||
diceVal.selectedResults += diceResult.active ? -1 : 1;
|
||||
diceResult.active = !diceResult.active;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #doReroll() {
|
||||
const toReroll = this.getRerollDice().map(x => {
|
||||
const { type, part, dice, result } = x;
|
||||
const diceData = this.damage[type][part][dice].results[result];
|
||||
return {
|
||||
...diceData,
|
||||
dice: this.damage[type][part][dice].dice,
|
||||
typeKey: type,
|
||||
partKey: part,
|
||||
diceKey: dice,
|
||||
resultsIndex: result
|
||||
};
|
||||
});
|
||||
|
||||
const roll = await new Roll(toReroll.map(x => `1${x.dice}`).join(' + ')).evaluate();
|
||||
|
||||
if (game.modules.get('dice-so-nice')?.active) {
|
||||
const diceSoNiceRoll = {
|
||||
_evaluated: true,
|
||||
dice: roll.dice,
|
||||
options: { appearance: {} }
|
||||
};
|
||||
|
||||
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true);
|
||||
}
|
||||
|
||||
toReroll.forEach((data, index) => {
|
||||
const { typeKey, partKey, diceKey, resultsIndex } = data;
|
||||
const rerolledDice = roll.dice[index];
|
||||
|
||||
const dice = this.damage[typeKey][partKey][diceKey];
|
||||
dice.toReroll = false;
|
||||
dice.results[resultsIndex].active = false;
|
||||
dice.results[resultsIndex].discarded = true;
|
||||
dice.results[resultsIndex].toReroll = false;
|
||||
dice.results.splice(dice.results.length, 0, {
|
||||
...rerolledDice.results[0],
|
||||
toReroll: false,
|
||||
selected: true
|
||||
});
|
||||
});
|
||||
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
279
module/applications/dialogs/rerollDialog.mjs
Normal file
279
module/applications/dialogs/rerollDialog.mjs
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
export default class RerollDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(message, options = {}) {
|
||||
super(options);
|
||||
|
||||
this.message = message;
|
||||
this.damage = Object.keys(message.system.damage).reduce((acc, typeKey) => {
|
||||
const type = message.system.damage[typeKey];
|
||||
acc[typeKey] = Object.keys(type.parts).reduce((acc, partKey) => {
|
||||
const part = type.parts[partKey];
|
||||
acc[partKey] = Object.keys(part.dice).reduce((acc, diceKey) => {
|
||||
const dice = part.dice[diceKey];
|
||||
const activeResults = dice.results.filter(x => x.active);
|
||||
acc[diceKey] = {
|
||||
dice: dice.dice,
|
||||
selectedResults: activeResults.length,
|
||||
maxSelected: activeResults.length,
|
||||
results: activeResults.map(x => ({ ...x, selected: true }))
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
static DEFAULT_OPTIONS = {
|
||||
id: 'reroll-dialog',
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'reroll-dialog'],
|
||||
window: {
|
||||
icon: 'fa-solid fa-dice'
|
||||
},
|
||||
actions: {
|
||||
toggleResult: RerollDialog.#toggleResult,
|
||||
selectRoll: RerollDialog.#selectRoll,
|
||||
doReroll: RerollDialog.#doReroll,
|
||||
save: RerollDialog.#save
|
||||
}
|
||||
};
|
||||
|
||||
/** @override */
|
||||
static PARTS = {
|
||||
main: {
|
||||
id: 'main',
|
||||
template: 'systems/daggerheart/templates/dialogs/rerollDialog/main.hbs'
|
||||
},
|
||||
footer: {
|
||||
id: 'footer',
|
||||
template: 'systems/daggerheart/templates/dialogs/rerollDialog/footer.hbs'
|
||||
}
|
||||
};
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.title');
|
||||
}
|
||||
|
||||
_attachPartListeners(partId, htmlElement, options) {
|
||||
super._attachPartListeners(partId, htmlElement, options);
|
||||
|
||||
htmlElement.querySelectorAll('.to-reroll-input').forEach(element => {
|
||||
element.addEventListener('change', this.toggleDice.bind(this));
|
||||
});
|
||||
}
|
||||
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
context.damage = this.damage;
|
||||
context.disabledReroll = !this.getRerollDice().length;
|
||||
context.saveDisabled = !this.isSelectionDone();
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
static async #save() {
|
||||
const update = {
|
||||
'system.damage': Object.keys(this.damage).reduce((acc, typeKey) => {
|
||||
const type = this.damage[typeKey];
|
||||
let typeTotal = 0;
|
||||
const messageType = this.message.system.damage[typeKey];
|
||||
const parts = Object.keys(type).map(partKey => {
|
||||
const part = type[partKey];
|
||||
const messagePart = messageType.parts[partKey];
|
||||
let partTotal = messagePart.modifierTotal;
|
||||
const dice = Object.keys(part).map(diceKey => {
|
||||
const dice = part[diceKey];
|
||||
const total = dice.results.reduce((acc, result) => {
|
||||
if (result.active) acc += result.result;
|
||||
return acc;
|
||||
}, 0);
|
||||
partTotal += total;
|
||||
const messageDice = messagePart.dice[diceKey];
|
||||
return {
|
||||
...messageDice,
|
||||
total: total,
|
||||
results: dice.results.map(x => ({
|
||||
...x,
|
||||
hasRerolls: dice.results.length > 1
|
||||
}))
|
||||
};
|
||||
});
|
||||
|
||||
typeTotal += partTotal;
|
||||
return {
|
||||
...messagePart,
|
||||
total: partTotal,
|
||||
dice: dice
|
||||
};
|
||||
});
|
||||
|
||||
acc[typeKey] = {
|
||||
...messageType,
|
||||
total: typeTotal,
|
||||
parts: parts
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {})
|
||||
};
|
||||
await this.message.update(update);
|
||||
await this.close();
|
||||
}
|
||||
|
||||
getRerollDice() {
|
||||
const rerollDice = [];
|
||||
Object.keys(this.damage).forEach(typeKey => {
|
||||
const type = this.damage[typeKey];
|
||||
Object.keys(type).forEach(partKey => {
|
||||
const part = type[partKey];
|
||||
Object.keys(part).forEach(diceKey => {
|
||||
const dice = part[diceKey];
|
||||
Object.keys(dice.results).forEach(resultKey => {
|
||||
const result = dice.results[resultKey];
|
||||
if (result.toReroll) {
|
||||
rerollDice.push({
|
||||
...result,
|
||||
dice: dice.dice,
|
||||
type: typeKey,
|
||||
part: partKey,
|
||||
dice: diceKey,
|
||||
result: resultKey
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return rerollDice;
|
||||
}
|
||||
|
||||
isSelectionDone() {
|
||||
const diceFinishedData = [];
|
||||
Object.keys(this.damage).forEach(typeKey => {
|
||||
const type = this.damage[typeKey];
|
||||
Object.keys(type).forEach(partKey => {
|
||||
const part = type[partKey];
|
||||
Object.keys(part).forEach(diceKey => {
|
||||
const dice = part[diceKey];
|
||||
const selected = dice.results.reduce((acc, result) => acc + (result.active ? 1 : 0), 0);
|
||||
diceFinishedData.push(selected === dice.maxSelected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return diceFinishedData.every(x => x);
|
||||
}
|
||||
|
||||
toggleDice(event) {
|
||||
const target = event.target;
|
||||
const { type, part, dice } = target.dataset;
|
||||
const toggleDice = this.damage[type][part][dice];
|
||||
|
||||
const existingDiceRerolls = this.getRerollDice().filter(
|
||||
x => x.type === type && x.part === part && x.dice === dice
|
||||
);
|
||||
|
||||
const allRerolled = existingDiceRerolls.length === toggleDice.results.filter(x => x.active).length;
|
||||
|
||||
toggleDice.toReroll = !allRerolled;
|
||||
toggleDice.results.forEach(result => {
|
||||
if (result.active) {
|
||||
result.toReroll = !allRerolled;
|
||||
}
|
||||
});
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static #toggleResult(event) {
|
||||
event.stopPropagation();
|
||||
|
||||
const target = event.target.closest('.to-reroll-result');
|
||||
const { type, part, dice, result } = target.dataset;
|
||||
const toggleDice = this.damage[type][part][dice];
|
||||
const toggleResult = toggleDice.results[result];
|
||||
toggleResult.toReroll = !toggleResult.toReroll;
|
||||
|
||||
const existingDiceRerolls = this.getRerollDice().filter(
|
||||
x => x.type === type && x.part === part && x.dice === dice
|
||||
);
|
||||
|
||||
const allToReroll = existingDiceRerolls.length === toggleDice.results.length;
|
||||
toggleDice.toReroll = allToReroll;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #selectRoll(_, button) {
|
||||
const { type, part, dice, result } = button.dataset;
|
||||
|
||||
const diceVal = this.damage[type][part][dice];
|
||||
const diceResult = diceVal.results[result];
|
||||
if (!diceResult.active && diceVal.results.filter(x => x.active).length === diceVal.maxSelected) {
|
||||
return ui.notifications.warn(
|
||||
game.i18n.localize('DAGGERHEART.APPLICATIONS.RerollDialog.deselectDiceNotification')
|
||||
);
|
||||
}
|
||||
|
||||
if (diceResult.active) {
|
||||
diceVal.toReroll = false;
|
||||
diceResult.toReroll = false;
|
||||
}
|
||||
|
||||
diceVal.selectedResults += diceResult.active ? -1 : 1;
|
||||
diceResult.active = !diceResult.active;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
static async #doReroll() {
|
||||
const toReroll = this.getRerollDice().map(x => {
|
||||
const { type, part, dice, result } = x;
|
||||
const diceData = this.damage[type][part][dice].results[result];
|
||||
return {
|
||||
...diceData,
|
||||
dice: this.damage[type][part][dice].dice,
|
||||
typeKey: type,
|
||||
partKey: part,
|
||||
diceKey: dice,
|
||||
resultsIndex: result
|
||||
};
|
||||
});
|
||||
|
||||
const roll = await new Roll(toReroll.map(x => `1${x.dice}`).join(' + ')).evaluate();
|
||||
|
||||
if (game.modules.get('dice-so-nice')?.active) {
|
||||
const diceSoNiceRoll = {
|
||||
_evaluated: true,
|
||||
dice: roll.dice,
|
||||
options: { appearance: {} }
|
||||
};
|
||||
|
||||
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true);
|
||||
}
|
||||
|
||||
toReroll.forEach((data, index) => {
|
||||
const { typeKey, partKey, diceKey, resultsIndex } = data;
|
||||
const rerolledDice = roll.dice[index];
|
||||
|
||||
const dice = this.damage[typeKey][partKey][diceKey];
|
||||
dice.toReroll = false;
|
||||
dice.results[resultsIndex].active = false;
|
||||
dice.results[resultsIndex].discarded = true;
|
||||
dice.results[resultsIndex].toReroll = false;
|
||||
dice.results.splice(dice.results.length, 0, {
|
||||
...rerolledDice.results[0],
|
||||
toReroll: false,
|
||||
selected: true
|
||||
});
|
||||
});
|
||||
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
|
@ -645,18 +645,17 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
}
|
||||
|
||||
async consumeResource(costs) {
|
||||
if(!costs?.length) return;
|
||||
if (!costs?.length) return;
|
||||
const usefulResources = foundry.utils.deepClone(this.actor.system.resources);
|
||||
const resources = game.system.api.fields.ActionFields.CostField.getRealCosts(costs)
|
||||
.map(c => {
|
||||
const resource = usefulResources[c.key];
|
||||
return {
|
||||
key: c.key,
|
||||
value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1),
|
||||
target: resource.target,
|
||||
keyIsID: resource.keyIsID
|
||||
};
|
||||
});
|
||||
const resources = game.system.api.fields.ActionFields.CostField.getRealCosts(costs).map(c => {
|
||||
const resource = usefulResources[c.key];
|
||||
return {
|
||||
key: c.key,
|
||||
value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1),
|
||||
target: resource.target,
|
||||
keyIsID: resource.keyIsID
|
||||
};
|
||||
});
|
||||
|
||||
await this.actor.modifyResource(resources);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -344,7 +344,7 @@ export default function DHApplicationMixin(Base) {
|
|||
callback: async (target, event) => {
|
||||
const doc = await getDocFromElement(target),
|
||||
action = doc?.system?.attack ?? doc;
|
||||
return action && action.use(event, { byPassRoll: true })
|
||||
return action && action.use(event, { byPassRoll: true });
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,40 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
classes: ['daggerheart']
|
||||
};
|
||||
|
||||
_getEntryContextOptions() {
|
||||
return [
|
||||
...super._getEntryContextOptions(),
|
||||
// {
|
||||
// name: 'Reroll',
|
||||
// icon: '<i class="fa-solid fa-dice"></i>',
|
||||
// condition: li => {
|
||||
// const message = game.messages.get(li.dataset.messageId);
|
||||
|
||||
// return (game.user.isGM || message.isAuthor) && message.rolls.length > 0;
|
||||
// },
|
||||
// callback: li => {
|
||||
// const message = game.messages.get(li.dataset.messageId);
|
||||
// new game.system.api.applications.dialogs.RerollDialog(message).render({ force: true });
|
||||
// }
|
||||
// },
|
||||
{
|
||||
name: 'Reroll Damage',
|
||||
icon: '<i class="fa-solid fa-dice"></i>',
|
||||
condition: li => {
|
||||
const message = game.messages.get(li.dataset.messageId);
|
||||
const hasRolledDamage = message.system.hasDamage
|
||||
? Object.keys(message.system.damage).length > 0
|
||||
: false;
|
||||
return (game.user.isGM || message.isAuthor) && hasRolledDamage;
|
||||
},
|
||||
callback: li => {
|
||||
const message = game.messages.get(li.dataset.messageId);
|
||||
new game.system.api.applications.dialogs.RerollDamageDialog(message).render({ force: true });
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
addChatListeners = async (app, html, data) => {
|
||||
html.querySelectorAll('.duality-action-damage').forEach(element =>
|
||||
element.addEventListener('click', event => this.onRollDamage(event, data.message))
|
||||
|
|
@ -193,19 +227,28 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
}
|
||||
|
||||
const target = event.target.closest('[data-die-index]');
|
||||
let originalRoll_parsed = message.rolls.map(roll => JSON.parse(roll))[0];
|
||||
const rollClass =
|
||||
game.system.api.dice[
|
||||
message.type === 'dualityRoll' ? 'DualityRoll' : target.dataset.type === 'damage' ? 'DHRoll' : 'D20Roll'
|
||||
];
|
||||
|
||||
if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
|
||||
if (target.dataset.type === 'damage') {
|
||||
game.system.api.dice.DamageRoll.reroll(target, message);
|
||||
} else {
|
||||
let originalRoll_parsed = message.rolls.map(roll => JSON.parse(roll))[0];
|
||||
const rollClass =
|
||||
game.system.api.dice[
|
||||
message.type === 'dualityRoll'
|
||||
? 'DualityRoll'
|
||||
: target.dataset.type === 'damage'
|
||||
? 'DHRoll'
|
||||
: 'D20Roll'
|
||||
];
|
||||
|
||||
const { newRoll, parsedRoll } = await rollClass.reroll(originalRoll_parsed, target, message);
|
||||
if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
|
||||
|
||||
await game.messages.get(message._id).update({
|
||||
'system.roll': newRoll,
|
||||
'rolls': [parsedRoll]
|
||||
});
|
||||
const { newRoll, parsedRoll } = await rollClass.reroll(originalRoll_parsed, target, message);
|
||||
|
||||
await game.messages.get(message._id).update({
|
||||
'system.roll': newRoll,
|
||||
'rolls': [parsedRoll]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -495,31 +495,31 @@ export const diceSetNumbers = {
|
|||
};
|
||||
|
||||
export const getDiceSoNicePreset = async (type, faces) => {
|
||||
const system = game.dice3d.DiceFactory.systems.get(type.system).dice.get(faces);
|
||||
if (!system) {
|
||||
ui.notifications.error(
|
||||
game.i18n.format('DAGGERHEART.UI.Notifications.noDiceSystem', {
|
||||
system: game.dice3d.DiceFactory.systems.get(type.system).name,
|
||||
faces: faces
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
const system = game.dice3d.DiceFactory.systems.get(type.system).dice.get(faces);
|
||||
if (!system) {
|
||||
ui.notifications.error(
|
||||
game.i18n.format('DAGGERHEART.UI.Notifications.noDiceSystem', {
|
||||
system: game.dice3d.DiceFactory.systems.get(type.system).name,
|
||||
faces: faces
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (system.modelFile && !system.modelLoaded) {
|
||||
await system.loadModel(game.dice3d.DiceFactory.loaderGLTF);
|
||||
} else {
|
||||
await system.loadTextures();
|
||||
}
|
||||
if (system.modelFile && !system.modelLoaded) {
|
||||
await system.loadModel(game.dice3d.DiceFactory.loaderGLTF);
|
||||
} else {
|
||||
await system.loadTextures();
|
||||
}
|
||||
|
||||
return {
|
||||
modelFile: system.modelFile,
|
||||
appearance: {
|
||||
...system.appearance,
|
||||
...type
|
||||
}
|
||||
};
|
||||
return {
|
||||
modelFile: system.modelFile,
|
||||
appearance: {
|
||||
...system.appearance,
|
||||
...type
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const getDiceSoNicePresets = async (hopeFaces, fearFaces, advantageFaces = 'd6', disadvantageFaces = 'd6') => {
|
||||
const { diceSoNice } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance);
|
||||
|
|
|
|||
|
|
@ -224,11 +224,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
.filter(
|
||||
c =>
|
||||
(!successCost && (!c.consumeOnSuccess || config.roll?.success)) ||
|
||||
(successCost && c.consumeOnSuccess)
|
||||
(successCost && c.consumeOnSuccess)
|
||||
)
|
||||
.reduce((a, c) => {
|
||||
const resource = usefulResources[c.key];
|
||||
if( resource ) {
|
||||
if (resource) {
|
||||
a.push({
|
||||
key: c.key,
|
||||
value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1),
|
||||
|
|
@ -247,9 +247,9 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
)
|
||||
this.update({ 'uses.value': this.uses.value + 1 });
|
||||
|
||||
if(config.roll?.success || successCost) {
|
||||
if (config.roll?.success || successCost) {
|
||||
setTimeout(() => {
|
||||
(config.message ?? config.parent).update({'system.successConsumed': true})
|
||||
(config.message ?? config.parent).update({ 'system.successConsumed': true });
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
|
|
@ -371,11 +371,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
async updateChatMessage(message, targetId, changes, chain = true) {
|
||||
setTimeout(async () => {
|
||||
const chatMessage = ui.chat.collection.get(message._id);
|
||||
|
||||
|
||||
await chatMessage.update({
|
||||
flags: {
|
||||
[game.system.id]: {
|
||||
"reactionRolls": {
|
||||
reactionRolls: {
|
||||
[targetId]: changes
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ const targetsField = () =>
|
|||
);
|
||||
|
||||
export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||
|
||||
static defineSchema() {
|
||||
return {
|
||||
title: new fields.StringField(),
|
||||
|
|
@ -65,7 +64,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
|||
}
|
||||
|
||||
set targetMode(mode) {
|
||||
if(!this.parent.isAuthor) return;
|
||||
if (!this.parent.isAuthor) return;
|
||||
this.parent.targetSelection = mode;
|
||||
this.registerTargetHook();
|
||||
this.updateTargets();
|
||||
|
|
@ -76,13 +75,14 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
|||
}
|
||||
|
||||
async updateTargets() {
|
||||
if(!ui.chat.collection.get(this.parent.id)) return;
|
||||
if (!ui.chat.collection.get(this.parent.id)) return;
|
||||
let targets;
|
||||
if(this.targetMode)
|
||||
targets = this.targets;
|
||||
if (this.targetMode) targets = this.targets;
|
||||
else
|
||||
targets = Array.from(game.user.targets).map(t => game.system.api.fields.ActionFields.TargetField.formatTarget(t));
|
||||
|
||||
targets = Array.from(game.user.targets).map(t =>
|
||||
game.system.api.fields.ActionFields.TargetField.formatTarget(t)
|
||||
);
|
||||
|
||||
await this.parent.update({
|
||||
flags: {
|
||||
[game.system.id]: {
|
||||
|
|
@ -90,16 +90,19 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
|||
targetMode: this.targetMode
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
registerTargetHook() {
|
||||
if(!this.parent.isAuthor) return;
|
||||
if(this.targetMode && this.parent.targetHook !== null) {
|
||||
Hooks.off("targetToken", this.parent.targetHook);
|
||||
return this.parent.targetHook = null;
|
||||
if (!this.parent.isAuthor) return;
|
||||
if (this.targetMode && this.parent.targetHook !== null) {
|
||||
Hooks.off('targetToken', this.parent.targetHook);
|
||||
return (this.parent.targetHook = null);
|
||||
} else if (!this.targetMode && this.parent.targetHook === null) {
|
||||
return this.parent.targetHook = Hooks.on('targetToken', foundry.utils.debounce(this.updateTargets.bind(this), 50));
|
||||
return (this.parent.targetHook = Hooks.on(
|
||||
'targetToken',
|
||||
foundry.utils.debounce(this.updateTargets.bind(this), 50)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -107,13 +110,16 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
|||
if (this.hasTarget) {
|
||||
this.hasHitTarget = this.targets.filter(t => t.hit === true).length > 0;
|
||||
this.currentTargets = this.getTargetList();
|
||||
|
||||
if(this.targetMode === true && this.hasRoll) {
|
||||
this.targetShort = this.targets.reduce((a,c) => {
|
||||
if(c.hit) a.hit += 1;
|
||||
else a.miss += 1;
|
||||
return a;
|
||||
}, {hit: 0, miss: 0})
|
||||
|
||||
if (this.targetMode === true && this.hasRoll) {
|
||||
this.targetShort = this.targets.reduce(
|
||||
(a, c) => {
|
||||
if (c.hit) a.hit += 1;
|
||||
else a.miss += 1;
|
||||
return a;
|
||||
},
|
||||
{ hit: 0, miss: 0 }
|
||||
);
|
||||
}
|
||||
if (this.hasSave) this.setPendingSaves();
|
||||
}
|
||||
|
|
@ -123,13 +129,16 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
|||
}
|
||||
|
||||
getTargetList() {
|
||||
const targets = this.targetMode && this.parent.isAuthor ? this.targets : (this.parent.getFlag(game.system.id, "targets") ?? this.targets),
|
||||
reactionRolls = this.parent.getFlag(game.system.id, "reactionRolls");
|
||||
const targets =
|
||||
this.targetMode && this.parent.isAuthor
|
||||
? this.targets
|
||||
: (this.parent.getFlag(game.system.id, 'targets') ?? this.targets),
|
||||
reactionRolls = this.parent.getFlag(game.system.id, 'reactionRolls');
|
||||
|
||||
if(reactionRolls) {
|
||||
if (reactionRolls) {
|
||||
Object.entries(reactionRolls).forEach(([k, r]) => {
|
||||
const target = targets.find(t => t.id === k);
|
||||
if(target) target.saved = r;
|
||||
if (target) target.saved = r;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,10 @@ export default class CostField extends fields.ArrayField {
|
|||
value: new fields.NumberField({ nullable: true, initial: 1, min: 0 }),
|
||||
scalable: new fields.BooleanField({ initial: false }),
|
||||
step: new fields.NumberField({ nullable: true, initial: null }),
|
||||
consumeOnSuccess: new fields.BooleanField({ initial: false, label: "DAGGERHEART.ACTIONS.Settings.consumeOnSuccess.label" })
|
||||
consumeOnSuccess: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.ACTIONS.Settings.consumeOnSuccess.label'
|
||||
})
|
||||
});
|
||||
super(element, options, context);
|
||||
}
|
||||
|
|
@ -47,7 +50,7 @@ export default class CostField extends fields.ArrayField {
|
|||
static hasCost(costs) {
|
||||
const realCosts = CostField.getRealCosts.call(this, costs),
|
||||
hasFearCost = realCosts.findIndex(c => c.key === 'fear');
|
||||
|
||||
|
||||
if (hasFearCost > -1) {
|
||||
const fearCost = realCosts.splice(hasFearCost, 1)[0];
|
||||
if (
|
||||
|
|
@ -72,7 +75,8 @@ export default class CostField extends fields.ArrayField {
|
|||
|
||||
static getResources(costs) {
|
||||
const actorResources = foundry.utils.deepClone(this.actor.system.resources);
|
||||
if(this.actor.system.partner) actorResources.hope = foundry.utils.deepClone(this.actor.system.partner.system.resources.hope);
|
||||
if (this.actor.system.partner)
|
||||
actorResources.hope = foundry.utils.deepClone(this.actor.system.partner.system.resources.hope);
|
||||
const itemResources = {};
|
||||
for (let itemResource of costs) {
|
||||
if (itemResource.keyIsID) {
|
||||
|
|
@ -92,9 +96,9 @@ export default class CostField extends fields.ArrayField {
|
|||
static getRealCosts(costs) {
|
||||
const realCosts = costs?.length ? costs.filter(c => c.enabled) : [];
|
||||
let mergedCosts = [];
|
||||
realCosts.forEach(c => {
|
||||
realCosts.forEach(c => {
|
||||
const getCost = Object.values(mergedCosts).find(gc => gc.key === c.key);
|
||||
if(getCost) getCost.total += c.total;
|
||||
if (getCost) getCost.total += c.total;
|
||||
else mergedCosts.push(c);
|
||||
});
|
||||
return mergedCosts;
|
||||
|
|
|
|||
|
|
@ -227,7 +227,7 @@ export function ActionMixin(Base) {
|
|||
} else {
|
||||
result = await this.item.update({ [path]: updates }, options);
|
||||
}
|
||||
|
||||
|
||||
return this.inCollection
|
||||
? foundry.utils.getProperty(result, basePath)?.get(this.id)
|
||||
: foundry.utils.getProperty(result, basePath);
|
||||
|
|
|
|||
|
|
@ -145,10 +145,9 @@ export default class D20Roll extends DHRoll {
|
|||
const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion;
|
||||
target.hit = roll.isCritical || roll.total >= difficulty;
|
||||
});
|
||||
data.success = config.targets.some(target => target.hit)
|
||||
} else if (config.roll.difficulty)
|
||||
data.success = roll.isCritical || roll.total >= config.roll.difficulty;
|
||||
|
||||
data.success = config.targets.some(target => target.hit);
|
||||
} else if (config.roll.difficulty) data.success = roll.isCritical || roll.total >= config.roll.difficulty;
|
||||
|
||||
data.advantage = {
|
||||
type: config.roll.advantage,
|
||||
dice: roll.dAdvantage?.denomination,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,9 @@ export default class DamageRoll extends DHRoll {
|
|||
}
|
||||
|
||||
static async buildPost(roll, config, message) {
|
||||
const chatMessage = config.source?.message ? ui.chat.collection.get(config.source.message) : getDocumentClass('ChatMessage').applyRollMode({}, config.rollMode);
|
||||
const chatMessage = config.source?.message
|
||||
? ui.chat.collection.get(config.source.message)
|
||||
: getDocumentClass('ChatMessage').applyRollMode({}, config.rollMode);
|
||||
if (game.modules.get('dice-so-nice')?.active) {
|
||||
const pool = foundry.dice.terms.PoolTerm.fromRolls(
|
||||
Object.values(config.damage).flatMap(r => r.parts.map(p => p.roll))
|
||||
|
|
@ -120,11 +122,10 @@ export default class DamageRoll extends DHRoll {
|
|||
}
|
||||
|
||||
/* To Remove When Reaction System */
|
||||
if(index === 0 && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
||||
for(const mod in config.modifiers) {
|
||||
if (index === 0 && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
||||
for (const mod in config.modifiers) {
|
||||
const modifier = config.modifiers[mod];
|
||||
if(modifier.beforeCrit === true && (modifier.enabled || modifier.value))
|
||||
modifier.callback(part);
|
||||
if (modifier.beforeCrit === true && (modifier.enabled || modifier.value)) modifier.callback(part);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -142,11 +143,10 @@ export default class DamageRoll extends DHRoll {
|
|||
}
|
||||
|
||||
/* To Remove When Reaction System */
|
||||
if(index === 0 && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
||||
for(const mod in config.modifiers) {
|
||||
if (index === 0 && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
||||
for (const mod in config.modifiers) {
|
||||
const modifier = config.modifiers[mod];
|
||||
if(!modifier.beforeCrit && (modifier.enabled || modifier.value))
|
||||
modifier.callback(part);
|
||||
if (!modifier.beforeCrit && (modifier.enabled || modifier.value)) modifier.callback(part);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -156,11 +156,11 @@ export default class DamageRoll extends DHRoll {
|
|||
/* To Remove When Reaction System */
|
||||
static temporaryModifierBuilder(config) {
|
||||
const mods = {};
|
||||
if(config.data?.parent) {
|
||||
if(config.data.parent.appliedEffects) {
|
||||
if (config.data?.parent) {
|
||||
if (config.data.parent.appliedEffects) {
|
||||
// Bardic Rally
|
||||
mods.rally = {
|
||||
label: "DAGGERHEART.CLASS.Feature.rallyDice",
|
||||
label: 'DAGGERHEART.CLASS.Feature.rallyDice',
|
||||
values: config.data?.parent?.appliedEffects.reduce((a, c) => {
|
||||
const change = c.changes.find(ch => ch.key === 'system.bonuses.rally');
|
||||
if (change) a.push({ value: c.id, label: change.value });
|
||||
|
|
@ -168,8 +168,10 @@ export default class DamageRoll extends DHRoll {
|
|||
}, []),
|
||||
value: null,
|
||||
beforeCrit: true,
|
||||
callback: (part) => {
|
||||
const rallyFaces = config.modifiers.rally.values.find(r => r.value === config.modifiers.rally.value)?.label;
|
||||
callback: part => {
|
||||
const rallyFaces = config.modifiers.rally.values.find(
|
||||
r => r.value === config.modifiers.rally.value
|
||||
)?.label;
|
||||
part.roll.terms.push(
|
||||
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
||||
...this.parse(`1${rallyFaces}`)
|
||||
|
|
@ -177,58 +179,58 @@ export default class DamageRoll extends DHRoll {
|
|||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
const item = config.data.parent.items?.get(config.source.item);
|
||||
if(item) {
|
||||
if (item) {
|
||||
// Massive (Weapon Feature)
|
||||
if(item.system.itemFeatures.find(f => f.value === "massive"))
|
||||
if (item.system.itemFeatures.find(f => f.value === 'massive'))
|
||||
mods.massive = {
|
||||
label: CONFIG.DH.ITEM.weaponFeatures.massive.label,
|
||||
enabled: true,
|
||||
callback: (part) => {
|
||||
callback: part => {
|
||||
part.roll.terms[0].modifiers.push(`kh${part.roll.terms[0].number}`);
|
||||
part.roll.terms[0].number += 1;
|
||||
}
|
||||
};
|
||||
|
||||
// Powerful (Weapon Feature)
|
||||
if(item.system.itemFeatures.find(f => f.value === "powerful"))
|
||||
if (item.system.itemFeatures.find(f => f.value === 'powerful'))
|
||||
mods.powerful = {
|
||||
label: CONFIG.DH.ITEM.weaponFeatures.powerful.label,
|
||||
enabled: true,
|
||||
callback: (part) => {
|
||||
callback: part => {
|
||||
part.roll.terms[0].modifiers.push(`kh${part.roll.terms[0].number}`);
|
||||
part.roll.terms[0].number += 1;
|
||||
}
|
||||
};
|
||||
|
||||
// Brutal (Weapon Feature)
|
||||
if(item.system.itemFeatures.find(f => f.value === "brutal"))
|
||||
if (item.system.itemFeatures.find(f => f.value === 'brutal'))
|
||||
mods.brutal = {
|
||||
label: CONFIG.DH.ITEM.weaponFeatures.brutal.label,
|
||||
enabled: true,
|
||||
beforeCrit: true,
|
||||
callback: (part) => {
|
||||
callback: part => {
|
||||
part.roll.terms[0].modifiers.push(`x${part.roll.terms[0].faces}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Serrated (Weapon Feature)
|
||||
if(item.system.itemFeatures.find(f => f.value === "serrated"))
|
||||
if (item.system.itemFeatures.find(f => f.value === 'serrated'))
|
||||
mods.serrated = {
|
||||
label: CONFIG.DH.ITEM.weaponFeatures.serrated.label,
|
||||
enabled: true,
|
||||
callback: (part) => {
|
||||
callback: part => {
|
||||
part.roll.terms[0].modifiers.push(`sc8`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Self-Correcting (Weapon Feature)
|
||||
if(item.system.itemFeatures.find(f => f.value === "selfCorrecting"))
|
||||
if (item.system.itemFeatures.find(f => f.value === 'selfCorrecting'))
|
||||
mods.selfCorrecting = {
|
||||
label: CONFIG.DH.ITEM.weaponFeatures.selfCorrecting.label,
|
||||
enabled: true,
|
||||
callback: (part) => {
|
||||
callback: part => {
|
||||
part.roll.terms[0].modifiers.push(`sc6`);
|
||||
}
|
||||
};
|
||||
|
|
@ -238,4 +240,90 @@ export default class DamageRoll extends DHRoll {
|
|||
config.modifiers = mods;
|
||||
return mods;
|
||||
}
|
||||
|
||||
static async reroll(target, message) {
|
||||
const { damageType, part, dice, result } = target.dataset;
|
||||
const rollPart = message.system.damage[damageType].parts[part];
|
||||
|
||||
let diceIndex = 0;
|
||||
let parsedRoll = game.system.api.dice.DamageRoll.fromData({
|
||||
...rollPart.roll,
|
||||
terms: rollPart.roll.terms.map(term => {
|
||||
const isDie = term.class === 'Die';
|
||||
const fixedTerm = {
|
||||
...term,
|
||||
...(isDie ? { results: rollPart.dice[diceIndex].results } : {})
|
||||
};
|
||||
|
||||
if (isDie) diceIndex++;
|
||||
return fixedTerm;
|
||||
}),
|
||||
class: 'DamageRoll',
|
||||
evaluated: false
|
||||
});
|
||||
|
||||
const parsedDiceTerms = Object.keys(parsedRoll.terms).reduce((acc, key) => {
|
||||
const term = parsedRoll.terms[key];
|
||||
if (term instanceof CONFIG.Dice.termTypes.DiceTerm) acc[Object.keys(acc).length] = term;
|
||||
return acc;
|
||||
}, {});
|
||||
const term = parsedDiceTerms[dice];
|
||||
const termResult = parsedDiceTerms[dice].results[result];
|
||||
|
||||
const newIndex = parsedDiceTerms[dice].results.length;
|
||||
await term.reroll(`/r1=${termResult.result}`);
|
||||
|
||||
if (game.modules.get('dice-so-nice')?.active) {
|
||||
const newResult = parsedDiceTerms[dice].results[newIndex];
|
||||
const diceSoNiceRoll = {
|
||||
_evaluated: true,
|
||||
dice: [
|
||||
new foundry.dice.terms.Die({
|
||||
...term,
|
||||
total: newResult.result,
|
||||
faces: term._faces,
|
||||
results: [newResult]
|
||||
})
|
||||
],
|
||||
options: { appearance: {} }
|
||||
};
|
||||
|
||||
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, true);
|
||||
}
|
||||
|
||||
await parsedRoll.evaluate();
|
||||
|
||||
const results = parsedRoll.dice[dice].results.map(result => ({
|
||||
...result,
|
||||
discarded: !result.active
|
||||
}));
|
||||
const newResult = results.splice(results.length - 1, 1);
|
||||
results.splice(Number(result) + 1, 0, newResult[0]);
|
||||
|
||||
const rerolledDice = parsedRoll.dice.map((x, index) => {
|
||||
const isRerollDice = index === Number(dice);
|
||||
if (!isRerollDice) return { ...x, dice: x.denomination };
|
||||
return {
|
||||
dice: parsedRoll.dice[dice].denomination,
|
||||
total: parsedRoll.dice[dice].total,
|
||||
results: results.map(result => ({
|
||||
...result,
|
||||
hasRerolls: result.hasRerolls || isRerollDice
|
||||
}))
|
||||
};
|
||||
});
|
||||
|
||||
const updateMessage = game.messages.get(message._id);
|
||||
await updateMessage.update({
|
||||
[`system.damage.${damageType}`]: {
|
||||
...updateMessage,
|
||||
total: parsedRoll.total,
|
||||
[`parts.${part}`]: {
|
||||
...rollPart,
|
||||
total: parsedRoll.total,
|
||||
dice: rerolledDice
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,7 @@ export default class DHRoll extends Roll {
|
|||
}
|
||||
|
||||
get title() {
|
||||
return game.i18n.localize(
|
||||
"DAGGERHEART.GENERAL.Roll.basic"
|
||||
);
|
||||
return game.i18n.localize('DAGGERHEART.GENERAL.Roll.basic');
|
||||
}
|
||||
|
||||
static messageType = 'adversaryRoll';
|
||||
|
|
@ -68,8 +66,7 @@ export default class DHRoll extends Roll {
|
|||
}
|
||||
|
||||
// Create Chat Message
|
||||
if (!config.source?.message)
|
||||
config.message = await this.toMessage(roll, config);
|
||||
if (!config.source?.message) config.message = await this.toMessage(roll, config);
|
||||
}
|
||||
|
||||
static postEvaluate(roll, config = {}) {
|
||||
|
|
@ -97,30 +94,30 @@ export default class DHRoll extends Roll {
|
|||
rolls: [roll]
|
||||
};
|
||||
config.selectedRollMode ??= game.settings.get('core', 'rollMode');
|
||||
if(roll._evaluated) return await cls.create(msg, { rollMode: config.selectedRollMode });
|
||||
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});
|
||||
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) {
|
||||
async _prepareChatRenderContext({ flavor, isPrivate = false, ...options } = {}) {
|
||||
if (isPrivate) {
|
||||
return {
|
||||
user: game.user.id,
|
||||
flavor: null,
|
||||
title: "???",
|
||||
title: '???',
|
||||
roll: {
|
||||
total: "??"
|
||||
total: '??'
|
||||
},
|
||||
hasRoll: true,
|
||||
isPrivate
|
||||
}
|
||||
};
|
||||
} else {
|
||||
options.message.system.user = game.user.id;
|
||||
return options.message.system;
|
||||
|
|
@ -217,7 +214,7 @@ export default class DHRoll extends Roll {
|
|||
|
||||
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;
|
||||
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) ||
|
||||
|
|
|
|||
|
|
@ -644,7 +644,7 @@ export default class DhpActor extends Actor {
|
|||
);
|
||||
break;
|
||||
case 'armor':
|
||||
if(this.system.armor?.system?.marks) {
|
||||
if (this.system.armor?.system?.marks) {
|
||||
updates.armor.resources['system.marks.value'] = Math.max(
|
||||
Math.min(this.system.armor.system.marks.value + r.value, this.system.armorScore),
|
||||
0
|
||||
|
|
@ -652,9 +652,12 @@ export default class DhpActor extends Actor {
|
|||
}
|
||||
break;
|
||||
default:
|
||||
if(this.system.resources?.[r.key]) {
|
||||
if (this.system.resources?.[r.key]) {
|
||||
updates.actor.resources[`system.resources.${r.key}.value`] = Math.max(
|
||||
Math.min(this.system.resources[r.key].value + r.value, this.system.resources[r.key].max),
|
||||
Math.min(
|
||||
this.system.resources[r.key].value + r.value,
|
||||
this.system.resources[r.key].max
|
||||
),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,13 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
|||
|
||||
async renderHTML() {
|
||||
const actor = game.actors.get(this.speaker.actor);
|
||||
const actorData = actor && this.isContentVisible ? actor : {
|
||||
img: this.author.avatar ? this.author.avatar : 'icons/svg/mystery-man.svg',
|
||||
name: ''
|
||||
};
|
||||
const actorData =
|
||||
actor && this.isContentVisible
|
||||
? actor
|
||||
: {
|
||||
img: this.author.avatar ? this.author.avatar : 'icons/svg/mystery-man.svg',
|
||||
name: ''
|
||||
};
|
||||
/* We can change to fully implementing the renderHTML function if needed, instead of augmenting it. */
|
||||
const html = await super.renderHTML({ actor: actorData, author: this.author });
|
||||
|
||||
|
|
@ -18,28 +21,26 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
|||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
||||
/** @inheritDoc */
|
||||
prepareData() {
|
||||
if(this.isAuthor && this.targetSelection === null)
|
||||
this.targetSelection = this.system.targets?.length > 0;
|
||||
if (this.isAuthor && this.targetSelection === null) this.targetSelection = this.system.targets?.length > 0;
|
||||
super.prepareData();
|
||||
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
|
||||
/** @inheritDoc */
|
||||
_onCreate(data, options, userId) {
|
||||
super._onCreate(data, options, userId);
|
||||
if(this.system.registerTargetHook) this.system.registerTargetHook();
|
||||
if (this.system.registerTargetHook) this.system.registerTargetHook();
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
|
||||
/** @inheritDoc */
|
||||
async _preDelete(options, user) {
|
||||
if(this.targetHook !== null) Hooks.off("targetToken", this.targetHook);
|
||||
if (this.targetHook !== null) Hooks.off('targetToken', this.targetHook);
|
||||
return super._preDelete(options, user);
|
||||
}
|
||||
|
||||
|
|
@ -84,7 +85,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
|||
element.addEventListener('mouseleave', this.unhoverTarget);
|
||||
element.addEventListener('click', this.clickTarget);
|
||||
});
|
||||
|
||||
|
||||
html.querySelectorAll('.button-target-selection').forEach(element => {
|
||||
element.addEventListener('click', this.onTargetSelection.bind(this));
|
||||
});
|
||||
|
|
@ -192,7 +193,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
|||
|
||||
onTargetSelection(event) {
|
||||
event.stopPropagation();
|
||||
if(!event.target.classList.contains("target-selected"))
|
||||
if (!event.target.classList.contains('target-selected'))
|
||||
this.system.targetMode = Boolean(event.target.dataset.targetHit);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -172,25 +172,25 @@ Roll.replaceFormulaData = function (formula, data = {}, { missing, warn = false
|
|||
return nativeReplaceFormulaData(formula, data, { missing, warn });
|
||||
};
|
||||
|
||||
foundry.dice.terms.Die.MODIFIERS.sc = "selfCorrecting";
|
||||
foundry.dice.terms.Die.MODIFIERS.sc = 'selfCorrecting';
|
||||
|
||||
/**
|
||||
* Return the configured value as result if 1 is rolled
|
||||
* Example: 6d6sc6 Roll 6d6, each result of 1 will be changed into 6
|
||||
* @param {string} modifier The matched modifier query
|
||||
*/
|
||||
foundry.dice.terms.Die.prototype.selfCorrecting = function(modifier) {
|
||||
foundry.dice.terms.Die.prototype.selfCorrecting = function (modifier) {
|
||||
const rgx = /(?:sc)([0-9]+)/i;
|
||||
const match = modifier.match(rgx);
|
||||
if ( !match ) return false;
|
||||
if (!match) return false;
|
||||
let [target] = match.slice(1);
|
||||
target = parseInt(target);
|
||||
for ( const r of this.results ) {
|
||||
if ( r.result === 1 ) {
|
||||
for (const r of this.results) {
|
||||
if (r.result === 1) {
|
||||
r.result = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const getDamageKey = damage => {
|
||||
return ['none', 'minor', 'major', 'severe'][damage];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue