mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-18 07:59:03 +01:00
Merge branch 'main' into fix/filter-subclasses-creation
This commit is contained in:
commit
419dd70833
50 changed files with 1392 additions and 333 deletions
15
README.md
15
README.md
|
|
@ -1,21 +1,26 @@
|
||||||
# Daggerheart
|
# Foundryborne Daggerheart
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
- [Overview](#overview)
|
- [Overview](#overview)
|
||||||
- [User Install Guide](#user-install)
|
- [User Install Guide](#user-install)
|
||||||
|
- [Documentation](#documentation)
|
||||||
- [Developer Setup](#development-setup)
|
- [Developer Setup](#development-setup)
|
||||||
- [Contribution Info](#contributing)
|
- [Contribution Info](#contributing)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
This is a community repo for a Foundry VTT implementation of Daggerheart. It is not associated with Critical Role or Darrington Press.
|
This is the community repo for the Foundry VTT system _Foundryborne_ Daggerheart. It is not associated with Critical Role or Darrington Press.
|
||||||
|
|
||||||
## User Install
|
## User Install
|
||||||
|
|
||||||
1. **(Not Yet Supported - No Releases Yet)** Pasting `https://raw.githubusercontent.com/Foundryborne/daggerheart/refs/heads/main/system.json` into the Install System dialog on the Setup menu of the application.
|
1. **recommended** Searching for _Daggerheart_ or _Foundryborne_ in the System Instalaltion dialgoe of the FoundryVTT admin settings.
|
||||||
2. **(Not Yet Supported - No Releases Yet)** Browsing the repository's Releases page, where you can copy any system.json link for use in the Install System dialog.
|
2. Pasting `https://raw.githubusercontent.com/Foundryborne/daggerheart/refs/heads/main/system.json` into the Install System dialog on the Setup menu of the application.
|
||||||
3. **(Not Yet Supported - No Releases Yet)** Downloading one of the .zip archives from the Releases page and extracting it into your foundry Data folder, under Data/systems/daggerheart.
|
3. Downloading one of the .zip archives from the Releases page and extracting it into your foundry Data folder, under Data/systems/daggerheart.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
You can find the documentation here: https://github.com/Foundryborne/daggerheart/wiki
|
||||||
|
|
||||||
## Development Setup
|
## Development Setup
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -498,6 +498,11 @@
|
||||||
"ReactionRoll": {
|
"ReactionRoll": {
|
||||||
"title": "Reaction Roll: {trait}"
|
"title": "Reaction Roll: {trait}"
|
||||||
},
|
},
|
||||||
|
"RerollDialog": {
|
||||||
|
"title": "Reroll",
|
||||||
|
"deselectDiceNotification": "Deselect one of the selected dice first",
|
||||||
|
"acceptCurrentRolls": "Accept Current Rolls"
|
||||||
|
},
|
||||||
"ResourceDice": {
|
"ResourceDice": {
|
||||||
"title": "{name} Resource",
|
"title": "{name} Resource",
|
||||||
"rerollDice": "Reroll Dice"
|
"rerollDice": "Reroll Dice"
|
||||||
|
|
@ -505,7 +510,8 @@
|
||||||
},
|
},
|
||||||
"CLASS": {
|
"CLASS": {
|
||||||
"Feature": {
|
"Feature": {
|
||||||
"rallyDice": "Bardic Rally Dice"
|
"rallyDice": "Bardic Rally Dice",
|
||||||
|
"short": "Rally"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CONFIG": {
|
"CONFIG": {
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,6 @@ export { default as DeathMove } from './deathMove.mjs';
|
||||||
export { default as Downtime } from './downtime.mjs';
|
export { default as Downtime } from './downtime.mjs';
|
||||||
export { default as MulticlassChoiceDialog } from './multiclassChoiceDialog.mjs';
|
export { default as MulticlassChoiceDialog } from './multiclassChoiceDialog.mjs';
|
||||||
export { default as OwnershipSelection } from './ownershipSelection.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 ResourceDiceDialog } from './resourceDiceDialog.mjs';
|
||||||
export { default as ActionSelectionDialog } from './actionSelectionDialog.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.indexOf(button.dataset.key) > -1
|
||||||
? this.config.experiences.filter(x => x !== button.dataset.key)
|
? this.config.experiences.filter(x => x !== button.dataset.key)
|
||||||
: [...this.config.experiences, 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 =
|
||||||
this.config.costs.indexOf(this.config.costs.find(c => c.extKey === button.dataset.key)) > -1
|
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.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();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
@ -166,8 +174,8 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
this.config.roll.type = this.reactionOverride
|
this.config.roll.type = this.reactionOverride
|
||||||
? CONFIG.DH.ITEM.actionTypes.reaction.id
|
? CONFIG.DH.ITEM.actionTypes.reaction.id
|
||||||
: this.config.roll.type === CONFIG.DH.ITEM.actionTypes.reaction.id
|
: this.config.roll.type === CONFIG.DH.ITEM.actionTypes.reaction.id
|
||||||
? null
|
? null
|
||||||
: this.config.roll.type;
|
: this.config.roll.type;
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,12 +56,14 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
|
||||||
label,
|
label,
|
||||||
icon
|
icon
|
||||||
}));
|
}));
|
||||||
|
context.modifiers = this.config.modifiers;
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
static updateRollConfiguration(_event, _, formData) {
|
static updateRollConfiguration(_event, _, formData) {
|
||||||
const { ...rest } = foundry.utils.expandObject(formData.object);
|
const { ...rest } = foundry.utils.expandObject(formData.object);
|
||||||
foundry.utils.mergeObject(this.config.roll, rest.roll);
|
foundry.utils.mergeObject(this.config.roll, rest.roll);
|
||||||
|
foundry.utils.mergeObject(this.config.modifiers, rest.modifiers);
|
||||||
this.config.selectedRollMode = rest.selectedRollMode;
|
this.config.selectedRollMode = rest.selectedRollMode;
|
||||||
|
|
||||||
this.render();
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import DhAppearance from '../../data/settings/Appearance.mjs';
|
import DhAppearance from '../../data/settings/Appearance.mjs';
|
||||||
|
import { getDiceSoNicePreset } from '../../config/generalConfig.mjs';
|
||||||
|
|
||||||
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
|
@ -25,7 +26,8 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
reset: this.reset,
|
reset: this.reset,
|
||||||
save: this.save
|
save: this.save,
|
||||||
|
preview: this.preview
|
||||||
},
|
},
|
||||||
form: { handler: this.updateData, submitOnChange: true }
|
form: { handler: this.updateData, submitOnChange: true }
|
||||||
};
|
};
|
||||||
|
|
@ -89,6 +91,22 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async preview() {
|
||||||
|
const source = this.settings._source.diceSoNice[this.tabGroups.diceSoNice];
|
||||||
|
let faces = 'd12';
|
||||||
|
switch (this.tabGroups.diceSoNice) {
|
||||||
|
case 'advantage':
|
||||||
|
case 'disadvantage':
|
||||||
|
faces = 'd6';
|
||||||
|
}
|
||||||
|
const preset = await getDiceSoNicePreset(source, faces);
|
||||||
|
const diceSoNiceRoll = await new Roll(`1${faces}`).evaluate();
|
||||||
|
diceSoNiceRoll.dice[0].options.appearance = preset.appearance;
|
||||||
|
diceSoNiceRoll.dice[0].options.modelFile = preset.modelFile;
|
||||||
|
|
||||||
|
await game.dice3d.showForRoll(diceSoNiceRoll, game.user, false);
|
||||||
|
}
|
||||||
|
|
||||||
static async reset() {
|
static async reset() {
|
||||||
this.settings = new DhAppearance();
|
this.settings = new DhAppearance();
|
||||||
this.render();
|
this.render();
|
||||||
|
|
|
||||||
|
|
@ -645,18 +645,17 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
||||||
}
|
}
|
||||||
|
|
||||||
async consumeResource(costs) {
|
async consumeResource(costs) {
|
||||||
if(!costs?.length) return;
|
if (!costs?.length) return;
|
||||||
const usefulResources = foundry.utils.deepClone(this.actor.system.resources);
|
const usefulResources = foundry.utils.deepClone(this.actor.system.resources);
|
||||||
const resources = game.system.api.fields.ActionFields.CostField.getRealCosts(costs)
|
const resources = game.system.api.fields.ActionFields.CostField.getRealCosts(costs).map(c => {
|
||||||
.map(c => {
|
const resource = usefulResources[c.key];
|
||||||
const resource = usefulResources[c.key];
|
return {
|
||||||
return {
|
key: c.key,
|
||||||
key: c.key,
|
value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1),
|
||||||
value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1),
|
target: resource.target,
|
||||||
target: resource.target,
|
keyIsID: resource.keyIsID
|
||||||
keyIsID: resource.keyIsID
|
};
|
||||||
};
|
});
|
||||||
});
|
|
||||||
|
|
||||||
await this.actor.modifyResource(resources);
|
await this.actor.modifyResource(resources);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -333,7 +333,7 @@ export default function DHApplicationMixin(Base) {
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
if (usable)
|
if (usable) {
|
||||||
options.unshift({
|
options.unshift({
|
||||||
name: 'DAGGERHEART.GENERAL.damage',
|
name: 'DAGGERHEART.GENERAL.damage',
|
||||||
icon: 'fa-solid fa-explosion',
|
icon: 'fa-solid fa-explosion',
|
||||||
|
|
@ -341,20 +341,6 @@ export default function DHApplicationMixin(Base) {
|
||||||
const doc = getDocFromElementSync(target);
|
const doc = getDocFromElementSync(target);
|
||||||
return doc?.system?.attack?.damage.parts.length || doc?.damage?.parts.length;
|
return doc?.system?.attack?.damage.parts.length || doc?.damage?.parts.length;
|
||||||
},
|
},
|
||||||
callback: async (target, event) => {
|
|
||||||
const doc = await getDocFromElement(target),
|
|
||||||
action = doc?.system?.attack ?? doc;
|
|
||||||
return action && action.use(event, { byPassRoll: true })
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
options.unshift({
|
|
||||||
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
|
|
||||||
icon: 'fa-solid fa-burst',
|
|
||||||
condition: target => {
|
|
||||||
const doc = getDocFromElementSync(target);
|
|
||||||
return doc?.system?.attack?.damage.parts.length || doc?.damage?.parts.length;
|
|
||||||
},
|
|
||||||
callback: async (target, event) => {
|
callback: async (target, event) => {
|
||||||
const doc = await getDocFromElement(target),
|
const doc = await getDocFromElement(target),
|
||||||
action = doc?.system?.attack ?? doc;
|
action = doc?.system?.attack ?? doc;
|
||||||
|
|
@ -362,15 +348,16 @@ export default function DHApplicationMixin(Base) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
options.unshift({
|
options.unshift({
|
||||||
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
|
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
|
||||||
icon: 'fa-solid fa-burst',
|
icon: 'fa-solid fa-burst',
|
||||||
condition: target => {
|
condition: target => {
|
||||||
const doc = getDocFromElementSync(target);
|
const doc = getDocFromElementSync(target);
|
||||||
return doc && !(doc.type === 'domainCard' && doc.system.inVault);
|
return doc && !(doc.type === 'domainCard' && doc.system.inVault);
|
||||||
},
|
},
|
||||||
callback: async (target, event) => (await getDocFromElement(target)).use(event)
|
callback: async (target, event) => (await getDocFromElement(target)).use(event)
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (toChat)
|
if (toChat)
|
||||||
options.push({
|
options.push({
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,40 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
classes: ['daggerheart']
|
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) => {
|
addChatListeners = async (app, html, data) => {
|
||||||
html.querySelectorAll('.duality-action-damage').forEach(element =>
|
html.querySelectorAll('.duality-action-damage').forEach(element =>
|
||||||
element.addEventListener('click', event => this.onRollDamage(event, data.message))
|
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]');
|
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({
|
const { newRoll, parsedRoll } = await rollClass.reroll(originalRoll_parsed, target, message);
|
||||||
'system.roll': newRoll,
|
|
||||||
'rolls': [parsedRoll]
|
await game.messages.get(message._id).update({
|
||||||
});
|
'system.roll': newRoll,
|
||||||
|
'rolls': [parsedRoll]
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
this.config = CONFIG.DH.ITEMBROWSER.compendiumConfig;
|
this.config = CONFIG.DH.ITEMBROWSER.compendiumConfig;
|
||||||
this.presets = options.presets;
|
this.presets = options.presets;
|
||||||
|
|
||||||
if(this.presets?.compendium && this.presets?.folder)
|
if (this.presets?.compendium && this.presets?.folder)
|
||||||
ItemBrowser.selectFolder.call(this, null, null, this.presets.compendium, this.presets.folder);
|
ItemBrowser.selectFolder.call(this, null, null, this.presets.compendium, this.presets.folder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,7 +26,6 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
id: 'itemBrowser',
|
id: 'itemBrowser',
|
||||||
classes: ['daggerheart', 'dh-style', 'dialog', 'compendium-browser'],
|
classes: ['daggerheart', 'dh-style', 'dialog', 'compendium-browser'],
|
||||||
tag: 'div',
|
tag: 'div',
|
||||||
// title: 'Item Browser',
|
|
||||||
window: {
|
window: {
|
||||||
frame: true,
|
frame: true,
|
||||||
title: 'Compendium Browser',
|
title: 'Compendium Browser',
|
||||||
|
|
@ -41,9 +40,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
sortList: this.sortList
|
sortList: this.sortList
|
||||||
},
|
},
|
||||||
position: {
|
position: {
|
||||||
top: 330,
|
left: 100,
|
||||||
left: 120,
|
width: 850,
|
||||||
width: 800,
|
|
||||||
height: 600
|
height: 600
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -88,16 +86,14 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
async _preFirstRender(context, options) {
|
async _preFirstRender(context, options) {
|
||||||
if(context.presets?.render?.noFolder || context.presets?.render?.lite)
|
if (context.presets?.render?.noFolder || context.presets?.render?.lite) options.position.width = 600;
|
||||||
options.position.width = 600;
|
|
||||||
|
|
||||||
await super._preFirstRender(context, options);
|
await super._preFirstRender(context, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
async _preRender(context, options) {
|
async _preRender(context, options) {
|
||||||
|
if (context.presets?.render?.noFolder || context.presets?.render?.lite)
|
||||||
if(context.presets?.render?.noFolder || context.presets?.render?.lite)
|
|
||||||
options.parts.splice(options.parts.indexOf('sidebar'), 1);
|
options.parts.splice(options.parts.indexOf('sidebar'), 1);
|
||||||
|
|
||||||
await super._preRender(context, options);
|
await super._preRender(context, options);
|
||||||
|
|
@ -111,17 +107,16 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
this._createFilterInputs();
|
this._createFilterInputs();
|
||||||
this._createDragProcess();
|
this._createDragProcess();
|
||||||
|
|
||||||
if(context.presets?.render?.lite)
|
if (context.presets?.render?.lite) this.element.classList.add('lite');
|
||||||
this.element.classList.add('lite');
|
|
||||||
|
|
||||||
if(context.presets?.render?.noFolder)
|
if (context.presets?.render?.noFolder) this.element.classList.add('no-folder');
|
||||||
this.element.classList.add('no-folder');
|
|
||||||
|
|
||||||
if(context.presets?.render?.noFilter)
|
if (context.presets?.render?.noFilter) this.element.classList.add('no-filter');
|
||||||
this.element.classList.add('no-filter');
|
|
||||||
|
|
||||||
if(this.presets?.filter) {
|
if (this.presets?.filter) {
|
||||||
Object.entries(this.presets.filter).forEach(([k,v]) => this.fieldFilter.find(c => c.name === k).value = v.value);
|
Object.entries(this.presets.filter).forEach(
|
||||||
|
([k, v]) => (this.fieldFilter.find(c => c.name === k).value = v.value)
|
||||||
|
);
|
||||||
await this._onInputFilterBrowser();
|
await this._onInputFilterBrowser();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -203,6 +198,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
|
||||||
formatLabel(item, field) {
|
formatLabel(item, field) {
|
||||||
const property = foundry.utils.getProperty(item, field.key);
|
const property = foundry.utils.getProperty(item, field.key);
|
||||||
|
if (Array.isArray(property)) property.join(', ');
|
||||||
if (typeof field.format !== 'function') return property ?? '-';
|
if (typeof field.format !== 'function') return property ?? '-';
|
||||||
return field.format(property);
|
return field.format(property);
|
||||||
}
|
}
|
||||||
|
|
@ -320,19 +316,18 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
async _onInputFilterBrowser(event) {
|
async _onInputFilterBrowser(event) {
|
||||||
this.#filteredItems.browser.input.clear();
|
this.#filteredItems.browser.input.clear();
|
||||||
|
|
||||||
if(event) this.fieldFilter.find(f => f.name === event.target.name).value = event.target.value;
|
if (event) this.fieldFilter.find(f => f.name === event.target.name).value = event.target.value;
|
||||||
|
|
||||||
for (const li of this.element.querySelectorAll('.item-container')) {
|
for (const li of this.element.querySelectorAll('.item-container')) {
|
||||||
const itemUUID = li.dataset.itemUuid,
|
const itemUUID = li.dataset.itemUuid,
|
||||||
item = this.items.find(i => i.uuid === itemUUID);
|
item = this.items.find(i => i.uuid === itemUUID);
|
||||||
|
|
||||||
if(!item) continue;
|
if (!item) continue;
|
||||||
|
|
||||||
const matchesMenu =
|
const matchesMenu =
|
||||||
this.fieldFilter.length === 0 ||
|
this.fieldFilter.length === 0 ||
|
||||||
this.fieldFilter.every(f => (
|
this.fieldFilter.every(
|
||||||
!f.value && f.value !== false) ||
|
f => (!f.value && f.value !== false) || ItemBrowser.evaluateFilter(item, this.createFilterData(f))
|
||||||
ItemBrowser.evaluateFilter(item, this.createFilterData(f))
|
|
||||||
);
|
);
|
||||||
if (matchesMenu) this.#filteredItems.browser.input.add(item.id);
|
if (matchesMenu) this.#filteredItems.browser.input.add(item.id);
|
||||||
|
|
||||||
|
|
@ -350,11 +345,11 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
let docValue = foundry.utils.getProperty(obj, filter.field);
|
let docValue = foundry.utils.getProperty(obj, filter.field);
|
||||||
let filterValue = filter.value;
|
let filterValue = filter.value;
|
||||||
switch (filter.operator) {
|
switch (filter.operator) {
|
||||||
case "contains2":
|
case 'contains2':
|
||||||
filterValue = Array.isArray(filterValue) ? filterValue : [filterValue];
|
filterValue = Array.isArray(filterValue) ? filterValue : [filterValue];
|
||||||
docValue = Array.isArray(docValue) ? docValue : [docValue];
|
docValue = Array.isArray(docValue) ? docValue : [docValue];
|
||||||
return docValue.some(dv => filterValue.includes(dv));
|
return docValue.some(dv => filterValue.includes(dv));
|
||||||
case "contains3":
|
case 'contains3':
|
||||||
return docValue.some(f => f.value === filterValue);
|
return docValue.some(f => f.value === filterValue);
|
||||||
default:
|
default:
|
||||||
return foundry.applications.ux.SearchFilter.evaluateFilter(obj, filter);
|
return foundry.applications.ux.SearchFilter.evaluateFilter(obj, filter);
|
||||||
|
|
@ -378,24 +373,27 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
this.render({ force: true });
|
this.render({ force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
static getFolderConfig(folder, property = "columns") {
|
static getFolderConfig(folder, property = 'columns') {
|
||||||
if(!folder) return [];
|
if (!folder) return [];
|
||||||
return folder[property] ?? CONFIG.DH.ITEMBROWSER.typeConfig[folder.listType]?.[property] ?? [];
|
return folder[property] ?? CONFIG.DH.ITEMBROWSER.typeConfig[folder.listType]?.[property] ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
static sortList(_, target) {
|
static sortList(_, target) {
|
||||||
const key = target.dataset.sortKey,
|
const key = target.dataset.sortKey,
|
||||||
type = !target.dataset.sortType || target.dataset.sortType === "DESC" ? "ASC" : "DESC",
|
type = !target.dataset.sortType || target.dataset.sortType === 'DESC' ? 'ASC' : 'DESC',
|
||||||
itemListContainer = target.closest(".compendium-results").querySelector(".item-list"),
|
itemListContainer = target.closest('.compendium-results').querySelector('.item-list'),
|
||||||
itemList = itemListContainer.querySelectorAll(".item-container");
|
itemList = itemListContainer.querySelectorAll('.item-container');
|
||||||
|
|
||||||
target.closest(".item-list-header").querySelectorAll('[data-sort-key]').forEach(b => b.dataset.sortType = "");
|
target
|
||||||
|
.closest('.item-list-header')
|
||||||
|
.querySelectorAll('[data-sort-key]')
|
||||||
|
.forEach(b => (b.dataset.sortType = ''));
|
||||||
target.dataset.sortType = type;
|
target.dataset.sortType = type;
|
||||||
|
|
||||||
const newOrder = [...itemList].reverse().sort((a, b) => {
|
const newOrder = [...itemList].reverse().sort((a, b) => {
|
||||||
const aProp = a.querySelector(`[data-item-key="${key}"]`),
|
const aProp = a.querySelector(`[data-item-key="${key}"]`),
|
||||||
bProp = b.querySelector(`[data-item-key="${key}"]`)
|
bProp = b.querySelector(`[data-item-key="${key}"]`);
|
||||||
if(type === "DESC") {
|
if (type === 'DESC') {
|
||||||
return aProp.innerText < bProp.innerText ? 1 : -1;
|
return aProp.innerText < bProp.innerText ? 1 : -1;
|
||||||
} else {
|
} else {
|
||||||
return aProp.innerText > bProp.innerText ? 1 : -1;
|
return aProp.innerText > bProp.innerText ? 1 : -1;
|
||||||
|
|
|
||||||
|
|
@ -494,40 +494,41 @@ export const diceSetNumbers = {
|
||||||
flat: 'Flat'
|
flat: 'Flat'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getDiceSoNicePresets = async (hopeFaces, fearFaces, advantageFaces = 'd6', disadvantageFaces = 'd6') => {
|
export const getDiceSoNicePreset = async (type, faces) => {
|
||||||
const { diceSoNice } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance);
|
const system = game.dice3d.DiceFactory.systems.get(type.system).dice.get(faces);
|
||||||
const getPreset = async (type, faces) => {
|
if (!system) {
|
||||||
const system = game.dice3d.DiceFactory.systems.get(type.system).dice.get(faces);
|
ui.notifications.error(
|
||||||
if (!system) {
|
game.i18n.format('DAGGERHEART.UI.Notifications.noDiceSystem', {
|
||||||
ui.notifications.error(
|
system: game.dice3d.DiceFactory.systems.get(type.system).name,
|
||||||
game.i18n.format('DAGGERHEART.UI.Notifications.noDiceSystem', {
|
faces: faces
|
||||||
system: game.dice3d.DiceFactory.systems.get(type.system).name,
|
})
|
||||||
faces: faces
|
);
|
||||||
})
|
return;
|
||||||
);
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (system.modelFile && !system.modelLoaded) {
|
if (system.modelFile && !system.modelLoaded) {
|
||||||
await system.loadModel(game.dice3d.DiceFactory.loaderGLTF);
|
await system.loadModel(game.dice3d.DiceFactory.loaderGLTF);
|
||||||
} else {
|
} else {
|
||||||
await system.loadTextures();
|
await system.loadTextures();
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
|
||||||
modelFile: system.modelFile,
|
|
||||||
appearance: {
|
|
||||||
...system.appearance,
|
|
||||||
...type
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hope: await getPreset(diceSoNice.hope, hopeFaces),
|
modelFile: system.modelFile,
|
||||||
fear: await getPreset(diceSoNice.fear, fearFaces),
|
appearance: {
|
||||||
advantage: await getPreset(diceSoNice.advantage, advantageFaces),
|
...system.appearance,
|
||||||
disadvantage: await getPreset(diceSoNice.disadvantage, disadvantageFaces)
|
...type
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDiceSoNicePresets = async (hopeFaces, fearFaces, advantageFaces = 'd6', disadvantageFaces = 'd6') => {
|
||||||
|
const { diceSoNice } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance);
|
||||||
|
|
||||||
|
return {
|
||||||
|
hope: await getDiceSoNicePreset(diceSoNice.hope, hopeFaces),
|
||||||
|
fear: await getDiceSoNicePreset(diceSoNice.fear, fearFaces),
|
||||||
|
advantage: await getDiceSoNicePreset(diceSoNice.advantage, advantageFaces),
|
||||||
|
disadvantage: await getDiceSoNicePreset(diceSoNice.disadvantage, disadvantageFaces)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1040,16 +1040,6 @@ export const weaponFeatures = {
|
||||||
key: 'system.evasion',
|
key: 'system.evasion',
|
||||||
mode: 2,
|
mode: 2,
|
||||||
value: '-1'
|
value: '-1'
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'system.bonuses.damage.primaryWeapon.extraDice',
|
|
||||||
mode: 2,
|
|
||||||
value: '1'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'system.rules.weapon.dropLowestDamageDice',
|
|
||||||
mode: 5,
|
|
||||||
value: '1'
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -1166,18 +1156,7 @@ export const weaponFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.powerful.effects.powerful.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.powerful.effects.powerful.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.powerful.effects.powerful.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.powerful.effects.powerful.description',
|
||||||
img: 'icons/magic/control/buff-flight-wings-runes-red-yellow.webp',
|
img: 'icons/magic/control/buff-flight-wings-runes-red-yellow.webp',
|
||||||
changes: [
|
changes: []
|
||||||
{
|
|
||||||
key: 'system.bonuses.damage.primaryWeapon.extraDice',
|
|
||||||
mode: 2,
|
|
||||||
value: '1'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'system.rules.weapon.dropLowestDamageDice',
|
|
||||||
mode: 5,
|
|
||||||
value: '1'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -1301,13 +1280,7 @@ export const weaponFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.selfCorrecting.effects.selfCorrecting.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.selfCorrecting.effects.selfCorrecting.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.selfCorrecting.effects.selfCorrecting.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.selfCorrecting.effects.selfCorrecting.description',
|
||||||
img: 'icons/weapons/ammunition/arrow-broadhead-glowing-orange.webp',
|
img: 'icons/weapons/ammunition/arrow-broadhead-glowing-orange.webp',
|
||||||
changes: [
|
changes: []
|
||||||
{
|
|
||||||
key: 'system.rules.damage.flipMinDiceValue',
|
|
||||||
mode: 5,
|
|
||||||
value: 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -1319,13 +1292,7 @@ export const weaponFeatures = {
|
||||||
name: 'DAGGERHEART.CONFIG.WeaponFeature.serrated.effects.serrated.name',
|
name: 'DAGGERHEART.CONFIG.WeaponFeature.serrated.effects.serrated.name',
|
||||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.serrated.effects.serrated.description',
|
description: 'DAGGERHEART.CONFIG.WeaponFeature.serrated.effects.serrated.description',
|
||||||
img: 'icons/weapons/ammunition/arrow-broadhead-glowing-orange.webp',
|
img: 'icons/weapons/ammunition/arrow-broadhead-glowing-orange.webp',
|
||||||
changes: [
|
changes: []
|
||||||
{
|
|
||||||
key: 'system.rules.damage.flipMinDiceValue',
|
|
||||||
mode: 5,
|
|
||||||
value: 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,6 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
else if (this.hasSave || this.hasEffect) {
|
else if (this.hasSave || this.hasEffect) {
|
||||||
const roll = new CONFIG.Dice.daggerheart.DHRoll('');
|
const roll = new CONFIG.Dice.daggerheart.DHRoll('');
|
||||||
roll._evaluated = true;
|
roll._evaluated = true;
|
||||||
if(config.hasTarget) config.targetSelection = config.targets.length > 0;
|
|
||||||
await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config);
|
await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -225,17 +224,20 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
.filter(
|
.filter(
|
||||||
c =>
|
c =>
|
||||||
(!successCost && (!c.consumeOnSuccess || config.roll?.success)) ||
|
(!successCost && (!c.consumeOnSuccess || config.roll?.success)) ||
|
||||||
(successCost && c.consumeOnSuccess)
|
(successCost && c.consumeOnSuccess)
|
||||||
)
|
)
|
||||||
.map(c => {
|
.reduce((a, c) => {
|
||||||
const resource = usefulResources[c.key];
|
const resource = usefulResources[c.key];
|
||||||
return {
|
if (resource) {
|
||||||
key: c.key,
|
a.push({
|
||||||
value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1),
|
key: c.key,
|
||||||
target: resource.target,
|
value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1),
|
||||||
keyIsID: resource.keyIsID
|
target: resource.target,
|
||||||
};
|
keyIsID: resource.keyIsID
|
||||||
});
|
});
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
await (this.actor.system.partner ?? this.actor).modifyResource(resources);
|
await (this.actor.system.partner ?? this.actor).modifyResource(resources);
|
||||||
if (
|
if (
|
||||||
|
|
@ -245,9 +247,9 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
)
|
)
|
||||||
this.update({ 'uses.value': this.uses.value + 1 });
|
this.update({ 'uses.value': this.uses.value + 1 });
|
||||||
|
|
||||||
if(config.roll?.success || successCost) {
|
if (config.roll?.success || successCost) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
(config.message ?? config.parent).update({'system.successConsumed': true})
|
(config.message ?? config.parent).update({ 'system.successConsumed': true });
|
||||||
}, 50);
|
}, 50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -369,11 +371,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
async updateChatMessage(message, targetId, changes, chain = true) {
|
async updateChatMessage(message, targetId, changes, chain = true) {
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
const chatMessage = ui.chat.collection.get(message._id);
|
const chatMessage = ui.chat.collection.get(message._id);
|
||||||
|
|
||||||
await chatMessage.update({
|
await chatMessage.update({
|
||||||
flags: {
|
flags: {
|
||||||
[game.system.id]: {
|
[game.system.id]: {
|
||||||
"reactionRolls": {
|
reactionRolls: {
|
||||||
[targetId]: changes
|
[targetId]: changes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,8 +49,7 @@ export default class DHDamageAction extends DHBaseAction {
|
||||||
...systemData,
|
...systemData,
|
||||||
roll: formulas,
|
roll: formulas,
|
||||||
dialog: {},
|
dialog: {},
|
||||||
data: this.getRollData(),
|
data: this.getRollData()
|
||||||
targetSelection: systemData.targets.length > 0
|
|
||||||
};
|
};
|
||||||
if (this.hasSave) config.onSave = this.save.damageMod;
|
if (this.hasSave) config.onSave = this.save.damageMod;
|
||||||
if (data.system) {
|
if (data.system) {
|
||||||
|
|
|
||||||
|
|
@ -287,18 +287,6 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
weapon: new fields.SchemaField({
|
|
||||||
/* Unimplemented
|
|
||||||
-> Should remove the lowest damage dice from weapon damage
|
|
||||||
-> Reflect this in the chat message somehow so players get feedback that their choice is helping them.
|
|
||||||
*/
|
|
||||||
dropLowestDamageDice: new fields.BooleanField({ initial: false }),
|
|
||||||
/* Unimplemented
|
|
||||||
-> Should flip any lowest possible dice rolls for weapon damage to highest
|
|
||||||
-> Reflect this in the chat message somehow so players get feedback that their choice is helping them.
|
|
||||||
*/
|
|
||||||
flipMinDiceValue: new fields.BooleanField({ intial: false })
|
|
||||||
}),
|
|
||||||
runeWard: new fields.BooleanField({ initial: false }),
|
runeWard: new fields.BooleanField({ initial: false }),
|
||||||
burden: new fields.SchemaField({
|
burden: new fields.SchemaField({
|
||||||
ignore: new fields.BooleanField()
|
ignore: new fields.BooleanField()
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,11 @@ const targetsField = () =>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
targetHook = null;
|
|
||||||
|
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
return {
|
return {
|
||||||
title: new fields.StringField(),
|
title: new fields.StringField(),
|
||||||
roll: new fields.ObjectField(),
|
roll: new fields.ObjectField(),
|
||||||
targets: targetsField(),
|
targets: targetsField(),
|
||||||
targetSelection: new fields.BooleanField({ initial: false }),
|
|
||||||
hasRoll: new fields.BooleanField({ initial: false }),
|
hasRoll: new fields.BooleanField({ initial: false }),
|
||||||
hasDamage: new fields.BooleanField({ initial: false }),
|
hasDamage: new fields.BooleanField({ initial: false }),
|
||||||
hasHealing: new fields.BooleanField({ initial: false }),
|
hasHealing: new fields.BooleanField({ initial: false }),
|
||||||
|
|
@ -63,42 +60,49 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
get targetMode() {
|
get targetMode() {
|
||||||
return this.targetSelection;
|
return this.parent.targetSelection;
|
||||||
}
|
}
|
||||||
|
|
||||||
set targetMode(mode) {
|
set targetMode(mode) {
|
||||||
this.targetSelection = mode;
|
if (!this.parent.isAuthor) return;
|
||||||
|
this.parent.targetSelection = mode;
|
||||||
this.registerTargetHook();
|
this.registerTargetHook();
|
||||||
this.updateTargets();
|
this.updateTargets();
|
||||||
}
|
}
|
||||||
|
|
||||||
get hitTargets() {
|
get hitTargets() {
|
||||||
return this.currentTargets.filter(t => t.hit || !this.hasRoll || !this.targetSelection);
|
return this.currentTargets.filter(t => t.hit || !this.hasRoll || !this.targetMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateTargets() {
|
async updateTargets() {
|
||||||
if(!ui.chat.collection.get(this.parent.id)) return;
|
if (!ui.chat.collection.get(this.parent.id)) return;
|
||||||
let targets;
|
let targets;
|
||||||
if(this.targetSelection)
|
if (this.targetMode) targets = this.targets;
|
||||||
targets = this.targets;
|
|
||||||
else
|
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)
|
||||||
this.parent.setFlag(game.system.id, "targets", targets);
|
);
|
||||||
await this.parent.updateSource({
|
|
||||||
system: {
|
await this.parent.update({
|
||||||
targetSelection: this.targetSelection
|
flags: {
|
||||||
|
[game.system.id]: {
|
||||||
|
targets: targets,
|
||||||
|
targetMode: this.targetMode
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
registerTargetHook() {
|
registerTargetHook() {
|
||||||
if(!this.parent.isAuthor) return;
|
if (!this.parent.isAuthor) return;
|
||||||
if(this.targetSelection && this.targetHook !== null) {
|
if (this.targetMode && this.parent.targetHook !== null) {
|
||||||
Hooks.off("targetToken", this.targetHook);
|
Hooks.off('targetToken', this.parent.targetHook);
|
||||||
this.targetHook = null;
|
return (this.parent.targetHook = null);
|
||||||
} else if (!this.targetSelection && this.targetHook === null) {
|
} else if (!this.targetMode && this.parent.targetHook === null) {
|
||||||
this.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)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,14 +110,16 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
if (this.hasTarget) {
|
if (this.hasTarget) {
|
||||||
this.hasHitTarget = this.targets.filter(t => t.hit === true).length > 0;
|
this.hasHitTarget = this.targets.filter(t => t.hit === true).length > 0;
|
||||||
this.currentTargets = this.getTargetList();
|
this.currentTargets = this.getTargetList();
|
||||||
this. registerTargetHook();
|
|
||||||
|
if (this.targetMode === true && this.hasRoll) {
|
||||||
if(this.targetSelection === true && this.hasRoll) {
|
this.targetShort = this.targets.reduce(
|
||||||
this.targetShort = this.targets.reduce((a,c) => {
|
(a, c) => {
|
||||||
if(c.hit) a.hit += 1;
|
if (c.hit) a.hit += 1;
|
||||||
else a.miss += 1;
|
else a.miss += 1;
|
||||||
return a;
|
return a;
|
||||||
}, {hit: 0, miss: 0})
|
},
|
||||||
|
{ hit: 0, miss: 0 }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (this.hasSave) this.setPendingSaves();
|
if (this.hasSave) this.setPendingSaves();
|
||||||
}
|
}
|
||||||
|
|
@ -123,13 +129,16 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
getTargetList() {
|
getTargetList() {
|
||||||
const targets = this.targetSelection && this.parent.isAuthor ? this.targets : (this.parent.getFlag(game.system.id, "targets") ?? this.targets),
|
const targets =
|
||||||
reactionRolls = this.parent.getFlag(game.system.id, "reactionRolls");
|
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]) => {
|
Object.entries(reactionRolls).forEach(([k, r]) => {
|
||||||
const target = targets.find(t => t.id === k);
|
const target = targets.find(t => t.id === k);
|
||||||
if(target) target.saved = r;
|
if (target) target.saved = r;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -137,7 +146,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
setPendingSaves() {
|
setPendingSaves() {
|
||||||
this.pendingSaves = this.targetSelection
|
this.pendingSaves = this.targetMode
|
||||||
? this.targets.filter(target => target.hit && target.saved.success === null).length > 0
|
? this.targets.filter(target => target.hit && target.saved.success === null).length > 0
|
||||||
: this.currentTargets.filter(target => target.saved.success === null).length > 0;
|
: this.currentTargets.filter(target => target.saved.success === null).length > 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,10 @@ export default class CostField extends fields.ArrayField {
|
||||||
value: new fields.NumberField({ nullable: true, initial: 1, min: 0 }),
|
value: new fields.NumberField({ nullable: true, initial: 1, min: 0 }),
|
||||||
scalable: new fields.BooleanField({ initial: false }),
|
scalable: new fields.BooleanField({ initial: false }),
|
||||||
step: new fields.NumberField({ nullable: true, initial: null }),
|
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);
|
super(element, options, context);
|
||||||
}
|
}
|
||||||
|
|
@ -47,7 +50,7 @@ export default class CostField extends fields.ArrayField {
|
||||||
static hasCost(costs) {
|
static hasCost(costs) {
|
||||||
const realCosts = CostField.getRealCosts.call(this, costs),
|
const realCosts = CostField.getRealCosts.call(this, costs),
|
||||||
hasFearCost = realCosts.findIndex(c => c.key === 'fear');
|
hasFearCost = realCosts.findIndex(c => c.key === 'fear');
|
||||||
|
|
||||||
if (hasFearCost > -1) {
|
if (hasFearCost > -1) {
|
||||||
const fearCost = realCosts.splice(hasFearCost, 1)[0];
|
const fearCost = realCosts.splice(hasFearCost, 1)[0];
|
||||||
if (
|
if (
|
||||||
|
|
@ -72,7 +75,8 @@ export default class CostField extends fields.ArrayField {
|
||||||
|
|
||||||
static getResources(costs) {
|
static getResources(costs) {
|
||||||
const actorResources = foundry.utils.deepClone(this.actor.system.resources);
|
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 = {};
|
const itemResources = {};
|
||||||
for (let itemResource of costs) {
|
for (let itemResource of costs) {
|
||||||
if (itemResource.keyIsID) {
|
if (itemResource.keyIsID) {
|
||||||
|
|
@ -92,9 +96,9 @@ export default class CostField extends fields.ArrayField {
|
||||||
static getRealCosts(costs) {
|
static getRealCosts(costs) {
|
||||||
const realCosts = costs?.length ? costs.filter(c => c.enabled) : [];
|
const realCosts = costs?.length ? costs.filter(c => c.enabled) : [];
|
||||||
let mergedCosts = [];
|
let mergedCosts = [];
|
||||||
realCosts.forEach(c => {
|
realCosts.forEach(c => {
|
||||||
const getCost = Object.values(mergedCosts).find(gc => gc.key === c.key);
|
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);
|
else mergedCosts.push(c);
|
||||||
});
|
});
|
||||||
return mergedCosts;
|
return mergedCosts;
|
||||||
|
|
|
||||||
|
|
@ -229,7 +229,7 @@ export function ActionMixin(Base) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.inCollection
|
return this.inCollection
|
||||||
? foundry.utils.getProperty(result, basePath).get(this.id)
|
? foundry.utils.getProperty(result, basePath)?.get(this.id)
|
||||||
: foundry.utils.getProperty(result, basePath);
|
: foundry.utils.getProperty(result, basePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -141,15 +141,13 @@ export default class D20Roll extends DHRoll {
|
||||||
data.type = config.roll?.type;
|
data.type = config.roll?.type;
|
||||||
data.difficulty = config.roll.difficulty;
|
data.difficulty = config.roll.difficulty;
|
||||||
if (config.targets?.length) {
|
if (config.targets?.length) {
|
||||||
config.targetSelection = true;
|
|
||||||
config.targets.forEach(target => {
|
config.targets.forEach(target => {
|
||||||
const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion;
|
const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion;
|
||||||
target.hit = roll.isCritical || roll.total >= difficulty;
|
target.hit = roll.isCritical || roll.total >= difficulty;
|
||||||
});
|
});
|
||||||
data.success = config.targets.some(target => target.hit)
|
data.success = config.targets.some(target => target.hit);
|
||||||
} else if (config.roll.difficulty)
|
} else if (config.roll.difficulty) data.success = roll.isCritical || roll.total >= config.roll.difficulty;
|
||||||
data.success = roll.isCritical || roll.total >= config.roll.difficulty;
|
|
||||||
|
|
||||||
data.advantage = {
|
data.advantage = {
|
||||||
type: config.roll.advantage,
|
type: config.roll.advantage,
|
||||||
dice: roll.dAdvantage?.denomination,
|
dice: roll.dAdvantage?.denomination,
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ export default class DamageRoll extends DHRoll {
|
||||||
const parts = config.roll.map(r => this.postEvaluate(r));
|
const parts = config.roll.map(r => this.postEvaluate(r));
|
||||||
|
|
||||||
config.damage = this.unifyDamageRoll(parts);
|
config.damage = this.unifyDamageRoll(parts);
|
||||||
// config.targetSelection = config.targets?.length
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static postEvaluate(roll, config = {}) {
|
static postEvaluate(roll, config = {}) {
|
||||||
|
|
@ -30,7 +29,9 @@ export default class DamageRoll extends DHRoll {
|
||||||
}
|
}
|
||||||
|
|
||||||
static async buildPost(roll, config, message) {
|
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) {
|
if (game.modules.get('dice-so-nice')?.active) {
|
||||||
const pool = foundry.dice.terms.PoolTerm.fromRolls(
|
const pool = foundry.dice.terms.PoolTerm.fromRolls(
|
||||||
Object.values(config.damage).flatMap(r => r.parts.map(p => p.roll))
|
Object.values(config.damage).flatMap(r => r.parts.map(p => p.roll))
|
||||||
|
|
@ -102,14 +103,14 @@ export default class DamageRoll extends DHRoll {
|
||||||
}
|
}
|
||||||
|
|
||||||
constructFormula(config) {
|
constructFormula(config) {
|
||||||
this.options.roll.forEach(part => {
|
this.options.roll.forEach((part, index) => {
|
||||||
part.roll = new Roll(Roll.replaceFormulaData(part.formula, config.data));
|
part.roll = new Roll(Roll.replaceFormulaData(part.formula, config.data));
|
||||||
this.constructFormulaPart(config, part);
|
this.constructFormulaPart(config, part, index);
|
||||||
});
|
});
|
||||||
return this.options.roll;
|
return this.options.roll;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructFormulaPart(config, part) {
|
constructFormulaPart(config, part, index) {
|
||||||
part.roll.terms = Roll.parse(part.roll.formula, config.data);
|
part.roll.terms = Roll.parse(part.roll.formula, config.data);
|
||||||
|
|
||||||
if (part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
if (part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
|
||||||
|
|
@ -120,6 +121,14 @@ 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) {
|
||||||
|
const modifier = config.modifiers[mod];
|
||||||
|
if (modifier.beforeCrit === true && (modifier.enabled || modifier.value)) modifier.callback(part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (part.extraFormula) {
|
if (part.extraFormula) {
|
||||||
part.roll.terms.push(
|
part.roll.terms.push(
|
||||||
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
||||||
|
|
@ -132,6 +141,189 @@ export default class DamageRoll extends DHRoll {
|
||||||
criticalBonus = tmpRoll.total - this.constructor.calculateTotalModifiers(tmpRoll);
|
criticalBonus = tmpRoll.total - this.constructor.calculateTotalModifiers(tmpRoll);
|
||||||
part.roll.terms.push(...this.formatModifier(criticalBonus));
|
part.roll.terms.push(...this.formatModifier(criticalBonus));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* To Remove When Reaction System */
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (part.roll._formula = this.constructor.getFormula(part.roll.terms));
|
return (part.roll._formula = this.constructor.getFormula(part.roll.terms));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* To Remove When Reaction System */
|
||||||
|
static temporaryModifierBuilder(config) {
|
||||||
|
const mods = {};
|
||||||
|
if (config.data?.parent) {
|
||||||
|
if (config.data.parent.appliedEffects) {
|
||||||
|
// Bardic Rally
|
||||||
|
mods.rally = {
|
||||||
|
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 });
|
||||||
|
return a;
|
||||||
|
}, []),
|
||||||
|
value: null,
|
||||||
|
beforeCrit: true,
|
||||||
|
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}`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = config.data.parent.items?.get(config.source.item);
|
||||||
|
if (item) {
|
||||||
|
// Massive (Weapon Feature)
|
||||||
|
if (item.system.itemFeatures.find(f => f.value === 'massive'))
|
||||||
|
mods.massive = {
|
||||||
|
label: CONFIG.DH.ITEM.weaponFeatures.massive.label,
|
||||||
|
enabled: true,
|
||||||
|
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'))
|
||||||
|
mods.powerful = {
|
||||||
|
label: CONFIG.DH.ITEM.weaponFeatures.powerful.label,
|
||||||
|
enabled: true,
|
||||||
|
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'))
|
||||||
|
mods.brutal = {
|
||||||
|
label: CONFIG.DH.ITEM.weaponFeatures.brutal.label,
|
||||||
|
enabled: true,
|
||||||
|
beforeCrit: true,
|
||||||
|
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'))
|
||||||
|
mods.serrated = {
|
||||||
|
label: CONFIG.DH.ITEM.weaponFeatures.serrated.label,
|
||||||
|
enabled: true,
|
||||||
|
callback: part => {
|
||||||
|
part.roll.terms[0].modifiers.push(`sc8`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Self-Correcting (Weapon Feature)
|
||||||
|
if (item.system.itemFeatures.find(f => f.value === 'selfCorrecting'))
|
||||||
|
mods.selfCorrecting = {
|
||||||
|
label: CONFIG.DH.ITEM.weaponFeatures.selfCorrecting.label,
|
||||||
|
enabled: true,
|
||||||
|
callback: part => {
|
||||||
|
part.roll.terms[0].modifiers.push(`sc6`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
get title() {
|
||||||
return game.i18n.localize(
|
return game.i18n.localize('DAGGERHEART.GENERAL.Roll.basic');
|
||||||
"DAGGERHEART.GENERAL.Roll.basic"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static messageType = 'adversaryRoll';
|
static messageType = 'adversaryRoll';
|
||||||
|
|
@ -36,6 +34,8 @@ export default class DHRoll extends Roll {
|
||||||
|
|
||||||
this.applyKeybindings(config);
|
this.applyKeybindings(config);
|
||||||
|
|
||||||
|
this.temporaryModifierBuilder(config);
|
||||||
|
|
||||||
let roll = new this(config.roll.formula, config.data, config);
|
let roll = new this(config.roll.formula, config.data, config);
|
||||||
if (config.dialog.configure !== false) {
|
if (config.dialog.configure !== false) {
|
||||||
// Open Roll Dialog
|
// Open Roll Dialog
|
||||||
|
|
@ -66,8 +66,7 @@ export default class DHRoll extends Roll {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create Chat Message
|
// Create Chat Message
|
||||||
if (!config.source?.message)
|
if (!config.source?.message) config.message = await this.toMessage(roll, config);
|
||||||
config.message = await this.toMessage(roll, config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static postEvaluate(roll, config = {}) {
|
static postEvaluate(roll, config = {}) {
|
||||||
|
|
@ -95,30 +94,30 @@ export default class DHRoll extends Roll {
|
||||||
rolls: [roll]
|
rolls: [roll]
|
||||||
};
|
};
|
||||||
config.selectedRollMode ??= game.settings.get('core', 'rollMode');
|
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;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
async render({flavor, template=this.constructor.CHAT_TEMPLATE, isPrivate=false, ...options}={}) {
|
async render({ flavor, template = this.constructor.CHAT_TEMPLATE, isPrivate = false, ...options } = {}) {
|
||||||
if ( !this._evaluated ) return;
|
if (!this._evaluated) return;
|
||||||
const chatData = await this._prepareChatRenderContext({flavor, isPrivate, ...options});
|
const chatData = await this._prepareChatRenderContext({ flavor, isPrivate, ...options });
|
||||||
return foundry.applications.handlebars.renderTemplate(template, chatData);
|
return foundry.applications.handlebars.renderTemplate(template, chatData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
async _prepareChatRenderContext({flavor, isPrivate=false, ...options}={}) {
|
async _prepareChatRenderContext({ flavor, isPrivate = false, ...options } = {}) {
|
||||||
if(isPrivate) {
|
if (isPrivate) {
|
||||||
return {
|
return {
|
||||||
user: game.user.id,
|
user: game.user.id,
|
||||||
flavor: null,
|
flavor: null,
|
||||||
title: "???",
|
title: '???',
|
||||||
roll: {
|
roll: {
|
||||||
total: "??"
|
total: '??'
|
||||||
},
|
},
|
||||||
hasRoll: true,
|
hasRoll: true,
|
||||||
isPrivate
|
isPrivate
|
||||||
}
|
};
|
||||||
} else {
|
} else {
|
||||||
options.message.system.user = game.user.id;
|
options.message.system.user = game.user.id;
|
||||||
return options.message.system;
|
return options.message.system;
|
||||||
|
|
@ -207,11 +206,15 @@ export default class DHRoll extends Roll {
|
||||||
}
|
}
|
||||||
return modifierTotal;
|
return modifierTotal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static temporaryModifierBuilder(config) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const registerRollDiceHooks = () => {
|
export const registerRollDiceHooks = () => {
|
||||||
Hooks.on(`${CONFIG.DH.id}.postRollDuality`, async (config, message) => {
|
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 (
|
if (
|
||||||
!config.source?.actor ||
|
!config.source?.actor ||
|
||||||
(game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) ||
|
(game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) ||
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@ export default class DualityRoll extends D20Roll {
|
||||||
}
|
}
|
||||||
if (this.rallyFaces)
|
if (this.rallyFaces)
|
||||||
this.terms.push(
|
this.terms.push(
|
||||||
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
|
new foundry.dice.terms.OperatorTerm({ operator: this.hasDisadvantage ? '-' : '+' }),
|
||||||
new foundry.dice.terms.Die({ faces: this.rallyFaces })
|
new foundry.dice.terms.Die({ faces: this.rallyFaces })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -644,16 +644,23 @@ export default class DhpActor extends Actor {
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case 'armor':
|
case 'armor':
|
||||||
updates.armor.resources['system.marks.value'] = Math.max(
|
if (this.system.armor?.system?.marks) {
|
||||||
Math.min(this.system.armor.system.marks.value + r.value, this.system.armorScore),
|
updates.armor.resources['system.marks.value'] = Math.max(
|
||||||
0
|
Math.min(this.system.armor.system.marks.value + r.value, this.system.armorScore),
|
||||||
);
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
updates.actor.resources[`system.resources.${r.key}.value`] = Math.max(
|
if (this.system.resources?.[r.key]) {
|
||||||
Math.min(this.system.resources[r.key].value + r.value, this.system.resources[r.key].max),
|
updates.actor.resources[`system.resources.${r.key}.value`] = Math.max(
|
||||||
0
|
Math.min(
|
||||||
);
|
this.system.resources[r.key].value + r.value,
|
||||||
|
this.system.resources[r.key].max
|
||||||
|
),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,16 @@
|
||||||
export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
|
targetHook = null;
|
||||||
|
targetSelection = null;
|
||||||
|
|
||||||
async renderHTML() {
|
async renderHTML() {
|
||||||
const actor = game.actors.get(this.speaker.actor);
|
const actor = game.actors.get(this.speaker.actor);
|
||||||
const actorData = actor && this.isContentVisible ? actor : {
|
const actorData =
|
||||||
img: this.author.avatar ? this.author.avatar : 'icons/svg/mystery-man.svg',
|
actor && this.isContentVisible
|
||||||
name: ''
|
? 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. */
|
/* 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 });
|
const html = await super.renderHTML({ actor: actorData, author: this.author });
|
||||||
|
|
||||||
|
|
@ -14,6 +20,30 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
prepareData() {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
async _preDelete(options, user) {
|
||||||
|
if (this.targetHook !== null) Hooks.off('targetToken', this.targetHook);
|
||||||
|
return super._preDelete(options, user);
|
||||||
|
}
|
||||||
|
|
||||||
enrichChatMessage(html) {
|
enrichChatMessage(html) {
|
||||||
const elements = html.querySelectorAll('[data-perm-id]');
|
const elements = html.querySelectorAll('[data-perm-id]');
|
||||||
elements.forEach(e => {
|
elements.forEach(e => {
|
||||||
|
|
@ -55,14 +85,14 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
element.addEventListener('mouseleave', this.unhoverTarget);
|
element.addEventListener('mouseleave', this.unhoverTarget);
|
||||||
element.addEventListener('click', this.clickTarget);
|
element.addEventListener('click', this.clickTarget);
|
||||||
});
|
});
|
||||||
|
|
||||||
html.querySelectorAll('.button-target-selection').forEach(element => {
|
html.querySelectorAll('.button-target-selection').forEach(element => {
|
||||||
element.addEventListener('click', this.onTargetSelection.bind(this));
|
element.addEventListener('click', this.onTargetSelection.bind(this));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getTargetList() {
|
getTargetList() {
|
||||||
const targets = this.system.hitTargets;
|
const targets = this.system.hitTargets ?? [];
|
||||||
return targets.map(target => game.canvas.tokens.documentCollection.find(t => t.actor?.uuid === target.actorId));
|
return targets.map(target => game.canvas.tokens.documentCollection.find(t => t.actor?.uuid === target.actorId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,7 +164,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
consumeOnSuccess() {
|
consumeOnSuccess() {
|
||||||
if (!this.system.successConsumed && !this.system.targetSelection) {
|
if (!this.system.successConsumed && !this.targetSelection) {
|
||||||
const action = this.system.action;
|
const action = this.system.action;
|
||||||
if (action) action.consume(this.system, true);
|
if (action) action.consume(this.system, true);
|
||||||
}
|
}
|
||||||
|
|
@ -143,12 +173,12 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
hoverTarget(event) {
|
hoverTarget(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const token = canvas.tokens.get(event.currentTarget.dataset.token);
|
const token = canvas.tokens.get(event.currentTarget.dataset.token);
|
||||||
if (!token?.controlled) token._onHoverIn(event, { hoverOutOthers: true });
|
if (token && !token?.controlled) token._onHoverIn(event, { hoverOutOthers: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
unhoverTarget(event) {
|
unhoverTarget(event) {
|
||||||
const token = canvas.tokens.get(event.currentTarget.dataset.token);
|
const token = canvas.tokens.get(event.currentTarget.dataset.token);
|
||||||
if (!token?.controlled) token._onHoverOut(event);
|
if (token && !token?.controlled) token._onHoverOut(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
clickTarget(event) {
|
clickTarget(event) {
|
||||||
|
|
@ -163,6 +193,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||||
|
|
||||||
onTargetSelection(event) {
|
onTargetSelection(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
this.system.targetMode = Boolean(event.target.dataset.targetHit);
|
if (!event.target.classList.contains('target-selected'))
|
||||||
|
this.system.targetMode = Boolean(event.target.dataset.targetHit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -172,6 +172,26 @@ Roll.replaceFormulaData = function (formula, data = {}, { missing, warn = false
|
||||||
return nativeReplaceFormulaData(formula, data, { missing, warn });
|
return nativeReplaceFormulaData(formula, data, { missing, warn });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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) {
|
||||||
|
const rgx = /(?:sc)([0-9]+)/i;
|
||||||
|
const match = modifier.match(rgx);
|
||||||
|
if (!match) return false;
|
||||||
|
let [target] = match.slice(1);
|
||||||
|
target = parseInt(target);
|
||||||
|
for (const r of this.results) {
|
||||||
|
if (r.result === 1) {
|
||||||
|
r.result = target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const getDamageKey = damage => {
|
export const getDamageKey = damage => {
|
||||||
return ['none', 'minor', 'major', 'severe'][damage];
|
return ['none', 'minor', 'major', 'severe'][damage];
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@
|
||||||
"parts": [
|
"parts": [
|
||||||
{
|
{
|
||||||
"value": {
|
"value": {
|
||||||
"dice": "d8",
|
"dice": "d10",
|
||||||
"bonus": 9,
|
"bonus": 9,
|
||||||
"multiplier": "prof",
|
"multiplier": "prof",
|
||||||
"flatMultiplier": 1,
|
"flatMultiplier": 1,
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,7 @@
|
||||||
"equipped": false,
|
"equipped": false,
|
||||||
"secondary": false,
|
"secondary": false,
|
||||||
"burden": "twoHanded",
|
"burden": "twoHanded",
|
||||||
"weaponFeatures": [
|
"weaponFeatures": [],
|
||||||
{
|
|
||||||
"value": "cumbersome",
|
|
||||||
"effectIds": [
|
|
||||||
"hl0S2LrBY5Mg69q6"
|
|
||||||
],
|
|
||||||
"actionIds": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"attack": {
|
"attack": {
|
||||||
"name": "Attack",
|
"name": "Attack",
|
||||||
"img": "icons/skills/melee/blood-slash-foam-red.webp",
|
"img": "icons/skills/melee/blood-slash-foam-red.webp",
|
||||||
|
|
@ -51,8 +43,8 @@
|
||||||
"parts": [
|
"parts": [
|
||||||
{
|
{
|
||||||
"value": {
|
"value": {
|
||||||
"dice": "d10",
|
"dice": "d8",
|
||||||
"bonus": 8,
|
"bonus": 9,
|
||||||
"multiplier": "prof",
|
"multiplier": "prof",
|
||||||
"flatMultiplier": 1,
|
"flatMultiplier": 1,
|
||||||
"custom": {
|
"custom": {
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@
|
||||||
"parts": [
|
"parts": [
|
||||||
{
|
{
|
||||||
"value": {
|
"value": {
|
||||||
"dice": "d8",
|
"dice": "d10",
|
||||||
"bonus": 6,
|
"bonus": 6,
|
||||||
"multiplier": "prof",
|
"multiplier": "prof",
|
||||||
"flatMultiplier": 1,
|
"flatMultiplier": 1,
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,7 @@
|
||||||
"equipped": false,
|
"equipped": false,
|
||||||
"secondary": false,
|
"secondary": false,
|
||||||
"burden": "twoHanded",
|
"burden": "twoHanded",
|
||||||
"weaponFeatures": [
|
"weaponFeatures": [],
|
||||||
{
|
|
||||||
"value": "cumbersome",
|
|
||||||
"effectIds": [
|
|
||||||
"8twXPJELZpvFWA5K"
|
|
||||||
],
|
|
||||||
"actionIds": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"attack": {
|
"attack": {
|
||||||
"name": "Attack",
|
"name": "Attack",
|
||||||
"img": "icons/skills/melee/blood-slash-foam-red.webp",
|
"img": "icons/skills/melee/blood-slash-foam-red.webp",
|
||||||
|
|
@ -51,8 +43,8 @@
|
||||||
"parts": [
|
"parts": [
|
||||||
{
|
{
|
||||||
"value": {
|
"value": {
|
||||||
"dice": "d10",
|
"dice": "d8",
|
||||||
"bonus": 5,
|
"bonus": 6,
|
||||||
"multiplier": "prof",
|
"multiplier": "prof",
|
||||||
"flatMultiplier": 1,
|
"flatMultiplier": 1,
|
||||||
"custom": {
|
"custom": {
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@
|
||||||
"parts": [
|
"parts": [
|
||||||
{
|
{
|
||||||
"value": {
|
"value": {
|
||||||
"dice": "d8",
|
"dice": "d10",
|
||||||
"bonus": 12,
|
"bonus": 12,
|
||||||
"multiplier": "prof",
|
"multiplier": "prof",
|
||||||
"flatMultiplier": 1,
|
"flatMultiplier": 1,
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,7 @@
|
||||||
"equipped": false,
|
"equipped": false,
|
||||||
"secondary": false,
|
"secondary": false,
|
||||||
"burden": "twoHanded",
|
"burden": "twoHanded",
|
||||||
"weaponFeatures": [
|
"weaponFeatures": [],
|
||||||
{
|
|
||||||
"value": "cumbersome",
|
|
||||||
"effectIds": [
|
|
||||||
"f44KWDgCQeKYfccr"
|
|
||||||
],
|
|
||||||
"actionIds": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"attack": {
|
"attack": {
|
||||||
"name": "Attack",
|
"name": "Attack",
|
||||||
"img": "icons/skills/melee/blood-slash-foam-red.webp",
|
"img": "icons/skills/melee/blood-slash-foam-red.webp",
|
||||||
|
|
@ -51,8 +43,8 @@
|
||||||
"parts": [
|
"parts": [
|
||||||
{
|
{
|
||||||
"value": {
|
"value": {
|
||||||
"dice": "d10",
|
"dice": "d8",
|
||||||
"bonus": 11,
|
"bonus": 12,
|
||||||
"multiplier": "prof",
|
"multiplier": "prof",
|
||||||
"flatMultiplier": 1,
|
"flatMultiplier": 1,
|
||||||
"custom": {
|
"custom": {
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@
|
||||||
"parts": [
|
"parts": [
|
||||||
{
|
{
|
||||||
"value": {
|
"value": {
|
||||||
"dice": "d8",
|
"dice": "d10",
|
||||||
"bonus": 3,
|
"bonus": 3,
|
||||||
"multiplier": "prof",
|
"multiplier": "prof",
|
||||||
"flatMultiplier": 1,
|
"flatMultiplier": 1,
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,7 @@
|
||||||
"equipped": false,
|
"equipped": false,
|
||||||
"secondary": false,
|
"secondary": false,
|
||||||
"burden": "twoHanded",
|
"burden": "twoHanded",
|
||||||
"weaponFeatures": [
|
"weaponFeatures": [],
|
||||||
{
|
|
||||||
"value": "cumbersome",
|
|
||||||
"effectIds": [
|
|
||||||
"Z5MnVI8EOOgzRdXC"
|
|
||||||
],
|
|
||||||
"actionIds": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"attack": {
|
"attack": {
|
||||||
"name": "Attack",
|
"name": "Attack",
|
||||||
"img": "icons/skills/melee/blood-slash-foam-red.webp",
|
"img": "icons/skills/melee/blood-slash-foam-red.webp",
|
||||||
|
|
@ -51,8 +43,8 @@
|
||||||
"parts": [
|
"parts": [
|
||||||
{
|
{
|
||||||
"value": {
|
"value": {
|
||||||
"dice": "d10",
|
"dice": "d8",
|
||||||
"bonus": 2,
|
"bonus": 3,
|
||||||
"multiplier": "prof",
|
"multiplier": "prof",
|
||||||
"flatMultiplier": 1,
|
"flatMultiplier": 1,
|
||||||
"custom": {
|
"custom": {
|
||||||
|
|
|
||||||
|
|
@ -27,3 +27,5 @@
|
||||||
@import './damage-reduction/sheets.less';
|
@import './damage-reduction/sheets.less';
|
||||||
|
|
||||||
@import './multiclass-choice/sheet.less';
|
@import './multiclass-choice/sheet.less';
|
||||||
|
|
||||||
|
@import './reroll-dialog/sheet.less';
|
||||||
|
|
|
||||||
125
styles/less/dialog/reroll-dialog/sheet.less
Normal file
125
styles/less/dialog/reroll-dialog/sheet.less
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
.daggerheart.dialog.dh-style.views.reroll-dialog {
|
||||||
|
.window-content {
|
||||||
|
max-width: 648px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reroll-outer-container {
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dices-container {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dice-outer-container {
|
||||||
|
width: 300px;
|
||||||
|
|
||||||
|
legend {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dice-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||||
|
|
||||||
|
.result-container {
|
||||||
|
position: relative;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 22px;
|
||||||
|
opacity: 0.8;
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
opacity: 1;
|
||||||
|
border: 1px solid;
|
||||||
|
border-radius: 6px;
|
||||||
|
border-color: light-dark(@dark-blue, @golden);
|
||||||
|
filter: drop-shadow(0 0 3px @golden);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: ' ';
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: -1;
|
||||||
|
mask: var(--svg-die) no-repeat center;
|
||||||
|
mask-size: contain;
|
||||||
|
background: linear-gradient(139.01deg, #efe6d8 3.51%, #372e1f 96.49%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.d4:before {
|
||||||
|
--svg-die: url(../assets/icons/dice/default/d4.svg);
|
||||||
|
}
|
||||||
|
&.d6:before {
|
||||||
|
--svg-die: url(../assets/icons/dice/default/d6.svg);
|
||||||
|
}
|
||||||
|
&.d8:before {
|
||||||
|
--svg-die: url(../assets/icons/dice/default/d8.svg);
|
||||||
|
}
|
||||||
|
&.d10:before {
|
||||||
|
--svg-die: url(../assets/icons/dice/default/d10.svg);
|
||||||
|
}
|
||||||
|
&.d12:before {
|
||||||
|
--svg-die: url('../assets/icons/dice/default/d12.svg');
|
||||||
|
}
|
||||||
|
&.d20:before {
|
||||||
|
--svg-die: url(../assets/icons/dice/default/d20.svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.to-reroll-result {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -7px;
|
||||||
|
gap: 2px;
|
||||||
|
border: 1px solid;
|
||||||
|
border-radius: 6px;
|
||||||
|
background-image: url(../assets/parchments/dh-parchment-dark.png);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2px 6px;
|
||||||
|
|
||||||
|
input {
|
||||||
|
margin: 0;
|
||||||
|
height: 12px;
|
||||||
|
line-height: 0px;
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
|
||||||
|
&:before,
|
||||||
|
&:after {
|
||||||
|
line-height: 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin-top: 8px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -53,7 +53,7 @@
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 17px;
|
line-height: 17px;
|
||||||
|
white-space: nowrap;
|
||||||
color: light-dark(@dark, @beige);
|
color: light-dark(@dark, @beige);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -571,6 +571,16 @@
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 10px;
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,8 @@
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
[data-view-perm='false'] {
|
[data-view-perm='false'] {
|
||||||
&[data-perm-hidden='true'], > * {
|
&[data-perm-hidden='true'],
|
||||||
|
> * {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
&::after {
|
&::after {
|
||||||
|
|
@ -161,7 +162,7 @@
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-family: 'Cinzel', sans-serif;
|
font-family: 'Cinzel', sans-serif;
|
||||||
line-height: .75;
|
line-height: 0.75;
|
||||||
|
|
||||||
.roll-result-value {
|
.roll-result-value {
|
||||||
font-size: var(--font-size-24);
|
font-size: var(--font-size-24);
|
||||||
|
|
@ -191,10 +192,14 @@
|
||||||
.roll-die {
|
.roll-die {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
". a a"
|
'. a a'
|
||||||
"c b b";
|
'c b b';
|
||||||
gap: 3px;
|
gap: 3px;
|
||||||
|
|
||||||
|
.reroll-button:hover {
|
||||||
|
filter: drop-shadow(0 0 3px @golden);
|
||||||
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
height: var(--font-size-12);
|
height: var(--font-size-12);
|
||||||
|
|
@ -272,7 +277,7 @@
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba(255,255,255,.1);
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.target-img {
|
.target-img {
|
||||||
|
|
|
||||||
|
|
@ -227,14 +227,16 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
flex: 1;
|
flex: 2.5;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
.item-list-img {
|
.item-list-img {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
flex: unset;
|
flex: unset;
|
||||||
}
|
}
|
||||||
.item-list-name {
|
.item-list-name {
|
||||||
flex-grow: 3 !important;
|
flex-grow: 3;
|
||||||
|
text-align: start;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,22 @@
|
||||||
<input type="text" value="{{extraFormula}}" name="roll.{{ @index }}.extraFormula" placeholder="Situational Bonus">
|
<input type="text" value="{{extraFormula}}" name="roll.{{ @index }}.extraFormula" placeholder="Situational Bonus">
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
{{#if @root.modifiers}}
|
||||||
|
<fieldset class="modifier-container two-columns">
|
||||||
|
<legend>{{localize "DAGGERHEART.GENERAL.Modifier.plural"}}</legend>
|
||||||
|
{{#each @root.modifiers}}
|
||||||
|
<span class="formula-label">{{ localize label }}</span>
|
||||||
|
{{#if (hasProperty this "values")}}
|
||||||
|
<select name="modifiers.{{@key}}.value">
|
||||||
|
{{selectOptions values blank="" selected=value}}
|
||||||
|
</select>
|
||||||
|
{{/if}}
|
||||||
|
{{#if (hasProperty this "enabled")}}
|
||||||
|
<input type="checkbox" name="modifiers.{{@key}}.enabled" {{ checked enabled }}>
|
||||||
|
{{/if}}
|
||||||
|
{{/each}}
|
||||||
|
</fieldset>
|
||||||
|
{{/if}}
|
||||||
<div class="damage-section-controls">
|
<div class="damage-section-controls">
|
||||||
{{#if directDamage}}
|
{{#if directDamage}}
|
||||||
<select class="roll-mode-select" name="selectedRollMode">
|
<select class="roll-mode-select" name="selectedRollMode">
|
||||||
|
|
|
||||||
35
templates/dialogs/rerollDialog/damage/main.hbs
Normal file
35
templates/dialogs/rerollDialog/damage/main.hbs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
<div class="reroll-outer-container">
|
||||||
|
{{#each damage}}
|
||||||
|
<h2>{{localize (concat 'DAGGERHEART.CONFIG.HealingType.' @key '.name')}}</h2>
|
||||||
|
{{#each this}}
|
||||||
|
<div class="dices-container">
|
||||||
|
{{#each this}}
|
||||||
|
<fieldset class="dice-outer-container">
|
||||||
|
<legend>
|
||||||
|
<input class="to-reroll-input" type="checkbox" data-type="{{@../../key}}" data-part="{{@../key}}" data-dice="{{@key}}" {{checked this.toReroll}} />
|
||||||
|
<i class="fa-solid fa-dice"></i>
|
||||||
|
<span>{{this.selectedResults}}/{{this.maxSelected}} Selected</span>
|
||||||
|
</legend>
|
||||||
|
|
||||||
|
<div class="dice-container">
|
||||||
|
{{#each this.results}}
|
||||||
|
<div
|
||||||
|
class="result-container {{../dice}} {{#if this.active}}selected{{/if}}"
|
||||||
|
data-action="selectRoll" data-type="{{@../../../key}}" data-part="{{@../../key}}" data-dice="{{@../key}}" data-result="{{@key}}"
|
||||||
|
>
|
||||||
|
{{this.result}}
|
||||||
|
{{#if this.active}}
|
||||||
|
<a class="to-reroll-result" data-action="toggleResult" data-type="{{@../../../key}}" data-part="{{@../../key}}" data-dice="{{@../key}}" data-result="{{@key}}">
|
||||||
|
<input class="to-reroll-result-input" type="checkbox" {{checked this.toReroll}} />
|
||||||
|
<i class="fa-solid fa-dice"></i>
|
||||||
|
</a>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
4
templates/dialogs/rerollDialog/footer.hbs
Normal file
4
templates/dialogs/rerollDialog/footer.hbs
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<footer>
|
||||||
|
<button type="button" data-action="doReroll" {{disabled disabledReroll}}>{{localize "Reroll"}} <i class="fa-solid fa-dice"></i></button>
|
||||||
|
<button type="button" data-action="save" {{disabled saveDisabled}}>{{localize "DAGGERHEART.APPLICATIONS.RerollDialog.acceptCurrentRolls"}}</button>
|
||||||
|
</footer>
|
||||||
35
templates/dialogs/rerollDialog/main.hbs
Normal file
35
templates/dialogs/rerollDialog/main.hbs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
<div class="reroll-outer-container">
|
||||||
|
{{#each damage}}
|
||||||
|
<h2>{{localize (concat 'DAGGERHEART.CONFIG.HealingType.' @key '.name')}}</h2>
|
||||||
|
{{#each this}}
|
||||||
|
<div class="dices-container">
|
||||||
|
{{#each this}}
|
||||||
|
<fieldset class="dice-outer-container">
|
||||||
|
<legend>
|
||||||
|
<input class="to-reroll-input" type="checkbox" data-type="{{@../../key}}" data-part="{{@../key}}" data-dice="{{@key}}" {{checked this.toReroll}} />
|
||||||
|
<i class="fa-solid fa-dice"></i>
|
||||||
|
<span>{{this.selectedResults}}/{{this.results.length}} Selected</span>
|
||||||
|
</legend>
|
||||||
|
|
||||||
|
<div class="dice-container">
|
||||||
|
{{#each this.results}}
|
||||||
|
<div
|
||||||
|
class="result-container {{../dice}} {{#if this.active}}selected{{/if}}"
|
||||||
|
data-action="selectRoll" data-type="{{@../../../key}}" data-part="{{@../../key}}" data-dice="{{@../key}}" data-result="{{@key}}"
|
||||||
|
>
|
||||||
|
{{this.result}}
|
||||||
|
{{#if this.active}}
|
||||||
|
<a class="to-reroll-result" data-action="toggleResult" data-type="{{@../../../key}}" data-part="{{@../../key}}" data-dice="{{@../key}}" data-result="{{@key}}">
|
||||||
|
<input class="to-reroll-result-input" type="checkbox" {{checked this.toReroll}} />
|
||||||
|
<i class="fa-solid fa-dice"></i>
|
||||||
|
</a>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
{{/each}}
|
||||||
|
{{/each}}
|
||||||
|
</div>
|
||||||
|
|
@ -69,7 +69,13 @@
|
||||||
{{selectOptions diceSoNiceMaterials selected=diceTab.source.material valueAttr="key" labelAttr="name" localize=true}}
|
{{selectOptions diceSoNiceMaterials selected=diceTab.source.material valueAttr="key" labelAttr="name" localize=true}}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="button-container">
|
||||||
|
<button data-action="preview">
|
||||||
|
<i class="fa-solid fa-dice"></i>
|
||||||
|
<span>{{localize "Preview"}}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@
|
||||||
{{formGroup settingFields.schema.fields.rangeMeasurement.fields.veryClose value=settingFields._source.rangeMeasurement.veryClose localize=true}}
|
{{formGroup settingFields.schema.fields.rangeMeasurement.fields.veryClose value=settingFields._source.rangeMeasurement.veryClose localize=true}}
|
||||||
{{formGroup settingFields.schema.fields.rangeMeasurement.fields.close value=settingFields._source.rangeMeasurement.close localize=true}}
|
{{formGroup settingFields.schema.fields.rangeMeasurement.fields.close value=settingFields._source.rangeMeasurement.close localize=true}}
|
||||||
{{formGroup settingFields.schema.fields.rangeMeasurement.fields.far value=settingFields._source.rangeMeasurement.far localize=true}}
|
{{formGroup settingFields.schema.fields.rangeMeasurement.fields.far value=settingFields._source.rangeMeasurement.far localize=true}}
|
||||||
{{formGroup settingFields.schema.fields.rangeMeasurement.fields.veryFar value=settingFields._source.rangeMeasurement.veryFar localize=true}}
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,13 @@
|
||||||
{{#each results}}
|
{{#each results}}
|
||||||
{{#unless discarded}}
|
{{#unless discarded}}
|
||||||
<div class="roll-die{{#unless @../first}} has-plus{{/unless}}">
|
<div class="roll-die{{#unless @../first}} has-plus{{/unless}}">
|
||||||
<div class="dice {{../dice}}">{{result}}</div>
|
<div
|
||||||
|
class="dice reroll-button {{../dice}}"
|
||||||
|
data-die-index="0" data-type="damage" data-damage-type="{{@../../../key}}" data-part="{{@../../key}}" data-dice="{{@../key}}" data-result="{{@key}}"
|
||||||
|
>
|
||||||
|
{{#if hasRerolls}}<i class="fa-solid fa-dice dice-rerolled" data-tooltip="{{localize "Rerolled"}}"></i>{{/if}}
|
||||||
|
{{result}}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if roll.rally.dice}}
|
{{#if roll.rally.dice}}
|
||||||
<div class="roll-die has-plus">
|
<div class="roll-die has-plus">
|
||||||
<label>{{localize "DAGGERHEART.GENERAL.fear"}}</label>
|
<label>{{localize "DAGGERHEART.CLASS.Feature.short"}}</label>
|
||||||
<div class="dice {{roll.rally.dice}}">{{roll.rally.value}}</div>
|
<div class="dice {{roll.rally.dice}}">{{roll.rally.value}}</div>
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@
|
||||||
<div class="target-selector">
|
<div class="target-selector">
|
||||||
<div class="roll-part-header"><div></div></div>
|
<div class="roll-part-header"><div></div></div>
|
||||||
<div class="target-choice">
|
<div class="target-choice">
|
||||||
<div class="button-target-selection{{#if targetSelection}} target-selected{{/if}}" data-target-hit="true">{{localize "DAGGERHEART.UI.Chat.damageRoll.hitTarget"}}</div>
|
<div class="button-target-selection{{#if targetMode}} target-selected{{/if}}" data-target-hit="true">{{localize "DAGGERHEART.UI.Chat.damageRoll.hitTarget"}}</div>
|
||||||
<div class="button-target-selection{{#unless targetSelection}} target-selected{{/unless}}">{{localize "DAGGERHEART.UI.Chat.damageRoll.currentTarget"}}</div>
|
<div class="button-target-selection{{#unless targetMode}} target-selected{{/unless}}">{{localize "DAGGERHEART.UI.Chat.damageRoll.currentTarget"}}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="roll-part-header"><div></div></div>
|
<div class="roll-part-header"><div></div></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
<img class="target-img" src="{{img}}">
|
<img class="target-img" src="{{img}}">
|
||||||
<div class="target-data">
|
<div class="target-data">
|
||||||
<div class="target-name" data-perm-id="{{actorId}}"><span>{{name}}</span></div>
|
<div class="target-name" data-perm-id="{{actorId}}"><span>{{name}}</span></div>
|
||||||
{{#if (and ../targetSelection ../hasRoll)}}
|
{{#if (and ../targetMode ../hasRoll)}}
|
||||||
<div class="target-hit-status {{#if hit}}is-hit{{else}}is-miss{{/if}}">
|
<div class="target-hit-status {{#if hit}}is-hit{{else}}is-miss{{/if}}">
|
||||||
{{#if hit}}
|
{{#if hit}}
|
||||||
{{localize "DAGGERHEART.GENERAL.hit.single"}}
|
{{localize "DAGGERHEART.GENERAL.hit.single"}}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue