Impproved Adversary Sheet Data Display

Fixes #604
This commit is contained in:
Joaquin Pereyra 2025-08-10 19:40:20 -03:00
commit 9d0cfdd927
130 changed files with 2460 additions and 1451 deletions

3
.gitignore vendored
View file

@ -2,5 +2,6 @@
node_modules
/packs
Build
build
foundry
styles/daggerheart.css
styles/daggerheart.css

View file

@ -1,21 +1,26 @@
# Daggerheart
# Foundryborne Daggerheart
## Table of Contents
- [Overview](#overview)
- [User Install Guide](#user-install)
- [Documentation](#documentation)
- [Developer Setup](#development-setup)
- [Contribution Info](#contributing)
## 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
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.
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.
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.
1. **Recommended** Searching for _Daggerheart_ or _Foundryborne_ in the System Installation dialogue of the FoundryVTT admin settings.
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. 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View file

@ -272,7 +272,8 @@
"combatStarted": "Active",
"giveSpotlight": "Give The Spotlight",
"requestingSpotlight": "Requesting The Spotlight",
"requestSpotlight": "Request The Spotlight"
"requestSpotlight": "Request The Spotlight",
"openCountdowns": "Countdowns"
},
"ContextMenu": {
"disableEffect": "Disable Effect",
@ -498,6 +499,11 @@
"ReactionRoll": {
"title": "Reaction Roll: {trait}"
},
"RerollDialog": {
"title": "Reroll",
"deselectDiceNotification": "Deselect one of the selected dice first",
"acceptCurrentRolls": "Accept Current Rolls"
},
"ResourceDice": {
"title": "{name} Resource",
"rerollDice": "Reroll Dice"
@ -505,7 +511,8 @@
},
"CLASS": {
"Feature": {
"rallyDice": "Bardic Rally Dice"
"rallyDice": "Bardic Rally Dice",
"short": "Rally"
}
},
"CONFIG": {
@ -817,8 +824,8 @@
"name": "Restrained",
"description": "When an effect makes a creature Restrained, it means they cannot move until this condition is cleared.\nThey can still take actions from their current position."
},
"unconcious": {
"name": "Unconcious",
"unconscious": {
"name": "Unconscious",
"description": "Your character cant move or act while unconscious, they cant be targeted by an attack."
},
"vulnerable": {
@ -1757,6 +1764,7 @@
"weapon": "Range Increase: Weapon"
},
"RefreshType": {
"scene": "Scene",
"session": "Session",
"shortrest": "Short Rest",
"longrest": "Long Rest"
@ -1892,6 +1900,7 @@
"difficulty": "Difficulty",
"downtime": "Downtime",
"dropActorsHere": "Drop Actors here",
"dropFeaturesHere": "Drop Features here",
"duality": "Duality",
"dualityRoll": "Duality Roll",
"enabled": "Enabled",
@ -2259,11 +2268,6 @@
"abilityCheckTitle": "{ability} Check"
},
"featureTitle": "Class Feature",
"foundationCard": {
"ancestryTitle": "Ancestry Card",
"communityTitle": "Community Card",
"subclassFeatureTitle": "Subclass Feature"
},
"healingRoll": {
"title": "Heal - {damage}",
"heal": "Heal",

View file

@ -359,6 +359,11 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
context.community = { ...this.setup.community, compendium: 'communities' };
context.class = { ...this.setup.class, compendium: 'classes' };
context.subclass = { ...this.setup.subclass, compendium: 'subclasses' };
const allDomainData = CONFIG.DH.DOMAIN.allDomains();
context.classDomains = context.class.uuid
? context.class.system.domains.map(key => game.i18n.localize(allDomainData[key].label))
: [];
context.domainCards = Object.keys(this.setup.domainCards).reduce((acc, x) => {
acc[x] = { ...this.setup.domainCards[x], compendium: 'domains' };
return acc;

View file

@ -6,5 +6,6 @@ export { default as DeathMove } from './deathMove.mjs';
export { default as Downtime } from './downtime.mjs';
export { default as MulticlassChoiceDialog } from './multiclassChoiceDialog.mjs';
export { default as OwnershipSelection } from './ownershipSelection.mjs';
export { default as RerollDamageDialog } from './rerollDamageDialog.mjs';
export { default as ResourceDiceDialog } from './resourceDiceDialog.mjs';
export { default as ActionSelectionDialog } from './actionSelectionDialog.mjs';

View file

@ -45,6 +45,10 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
return this.config.title;
}
get actor() {
return this.config?.data?.parent;
}
/** @override */
static PARTS = {
header: {
@ -69,9 +73,10 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
icon
}));
this.config.costs ??= [];
if (this.config.costs?.length) {
const updatedCosts = game.system.api.fields.ActionFields.CostField.calcCosts.call(
this.action,
this.action ?? { actor: this.actor },
this.config.costs
);
context.costs = updatedCosts.map(x => ({
@ -80,7 +85,10 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
? this.action.parent.parent.name
: game.i18n.localize(CONFIG.DH.GENERAL.abilityCosts[x.key].label)
}));
context.canRoll = game.system.api.fields.ActionFields.CostField.hasCost.call(this.action, updatedCosts);
context.canRoll = game.system.api.fields.ActionFields.CostField.hasCost.call(
this.action ?? { actor: this.actor },
updatedCosts
);
this.config.data.scale = this.config.costs[0].total;
}
if (this.config.uses?.max) {
@ -143,6 +151,20 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
this.config.experiences.indexOf(button.dataset.key) > -1
? this.config.experiences.filter(x => x !== button.dataset.key)
: [...this.config.experiences, button.dataset.key];
if (this.config?.data?.parent?.type === 'character' || this.config?.data?.parent?.type === 'companion') {
this.config.costs =
this.config.costs.indexOf(this.config.costs.find(c => c.extKey === button.dataset.key)) > -1
? this.config.costs.filter(x => x.extKey !== button.dataset.key)
: [
...this.config.costs,
{
extKey: button.dataset.key,
key: 'hope',
value: 1,
name: this.config.data?.experiences?.[button.dataset.key]?.name
}
];
}
this.render();
}
@ -152,8 +174,8 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
this.config.roll.type = this.reactionOverride
? CONFIG.DH.ITEM.actionTypes.reaction.id
: this.config.roll.type === CONFIG.DH.ITEM.actionTypes.reaction.id
? null
: this.config.roll.type;
? null
: this.config.roll.type;
this.render();
}
}

View file

@ -56,12 +56,14 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
label,
icon
}));
context.modifiers = this.config.modifiers;
return context;
}
static updateRollConfiguration(_event, _, formData) {
const { ...rest } = foundry.utils.expandObject(formData.object);
foundry.utils.mergeObject(this.config.roll, rest.roll);
foundry.utils.mergeObject(this.config.modifiers, rest.modifiers);
this.config.selectedRollMode = rest.selectedRollMode;
this.render();

View 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();
}
}

View 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();
}
}

View file

@ -51,7 +51,7 @@ export default class DhCharacterLevelUp extends LevelUpBase {
.filter(exp => exp.data.length > 0)
.flatMap(exp =>
exp.data.map(data => {
const experience = Object.keys(this.actor.system.experiences).find(x => x === data);
const experience = Object.keys(this.actor.system.experiences)[data];
return this.actor.system.experiences[experience].name;
})
);

View file

@ -39,7 +39,7 @@ export default class DhCompanionLevelUp extends BaseLevelUp {
.filter(exp => exp.data.length > 0)
.flatMap(exp =>
exp.data.map(data => {
const experience = Object.keys(this.actor.system.experiences).find(x => x === data);
const experience = Object.keys(this.actor.system.experiences)[data];
return this.actor.system.experiences[experience].name;
})
);

View file

@ -1,4 +1,5 @@
import DhAppearance from '../../data/settings/Appearance.mjs';
import { getDiceSoNicePreset } from '../../config/generalConfig.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -25,7 +26,8 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
},
actions: {
reset: this.reset,
save: this.save
save: this.save,
preview: this.preview
},
form: { handler: this.updateData, submitOnChange: true }
};
@ -89,6 +91,22 @@ export default class DHAppearanceSettings extends HandlebarsApplicationMixin(App
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() {
this.settings = new DhAppearance();
this.render();

View file

@ -98,11 +98,17 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
async _onDrop(event) {
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
if (data.fromInternal) return;
const item = await fromUuid(data.uuid);
if (item.type === 'feature') {
await this.actor.createEmbeddedDocuments('Item', [item]);
if (item?.type === 'feature') {
if (data.fromInternal && item.parent?.uuid === this.actor.uuid) {
return;
}
const itemData = item.toObject();
delete itemData._id;
await this.actor.createEmbeddedDocuments('Item', [itemData]);
}
}
}

View file

@ -631,13 +631,33 @@ export default class CharacterSheet extends DHBaseActorSheet {
},
hasRoll: true
};
this.document.diceRoll({
const result = await this.document.diceRoll({
...config,
headerTitle: `${game.i18n.localize('DAGGERHEART.GENERAL.dualityRoll')}: ${this.actor.name}`,
title: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
ability: abilityLabel
})
});
setTimeout(() => {
this.consumeResource(result?.costs);
}, 50);
}
async consumeResource(costs) {
if (!costs?.length) return;
const usefulResources = foundry.utils.deepClone(this.actor.system.resources);
const resources = game.system.api.fields.ActionFields.CostField.getRealCosts(costs).map(c => {
const resource = usefulResources[c.key];
return {
key: c.key,
value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1),
target: resource.target,
keyIsID: resource.keyIsID
};
});
await this.actor.modifyResource(resources);
}
//TODO: redo toggleEquipItem method

View file

@ -51,9 +51,8 @@ import { ItemBrowser } from '../../ui/itemBrowser.mjs';
*/
/**
* @template {Constructor<foundry.applications.api.DocumentSheet>} BaseDocumentSheet
* @param {BaseDocumentSheet} Base - The base class to extend.
* @returns {BaseDocumentSheet}
* @template {new (...args: any[]) => {}} T
* @arg Base {T}
*/
export default function DHApplicationMixin(Base) {
class DHSheetV2 extends HandlebarsApplicationMixin(Base) {
@ -123,12 +122,14 @@ export default function DHApplicationMixin(Base) {
super._attachPartListeners(partId, htmlElement, options);
this._dragDrop.forEach(d => d.bind(htmlElement));
}
/**@inheritdoc */
async _onFirstRender(context, options) {
await super._onFirstRender(context, options);
const docs = [];
for (var docData of this.relatedDocs) {
for (const docData of this.relatedDocs) {
if (!docData) continue;
const doc = await foundry.utils.fromUuid(docData.uuid);
docs.push(doc);
}
@ -247,6 +248,9 @@ export default function DHApplicationMixin(Base) {
/* Context Menu */
/* -------------------------------------------- */
/**
* Create all configured context menus for this application ins tance.
*/
_createContextMenus() {
for (const config of this.options.contextMenus) {
const { handler, selector, options } = config;
@ -257,9 +261,9 @@ export default function DHApplicationMixin(Base) {
/* -------------------------------------------- */
/**
* Get the set of ContextMenu options for DomainCards.
* Get the set of ContextMenu options for ActiveEffects.
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
* @this {CharacterSheet}
* @this {DHSheetV2}
* @protected
*/
static #getEffectContextOptions() {
@ -305,8 +309,13 @@ export default function DHApplicationMixin(Base) {
}
/**
* Get the set of ContextMenu options.
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
* Get the common ContextMenu options for an element.
* @param {Object} options
* @param {boolean} [options.usable=false] - Whether to include an option to use the item or apply damage.
* @param {boolean} [options.toChat=false] - Whether to include an option to send the item to chat.
* @param {boolean} [options.deletable=true] - Whether to include an option to delete the item.
*
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]}
*/
_getContextMenuCommonOptions({ usable = false, toChat = false, deletable = true }) {
const options = [
@ -325,7 +334,7 @@ export default function DHApplicationMixin(Base) {
}
];
if (usable)
if (usable) {
options.unshift({
name: 'DAGGERHEART.GENERAL.damage',
icon: 'fa-solid fa-explosion',
@ -340,15 +349,16 @@ export default function DHApplicationMixin(Base) {
}
});
options.unshift({
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
icon: 'fa-solid fa-burst',
condition: target => {
const doc = getDocFromElementSync(target);
return doc && !(doc.type === 'domainCard' && doc.system.inVault);
},
callback: async (target, event) => (await getDocFromElement(target)).use(event)
});
options.unshift({
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
icon: 'fa-solid fa-burst',
condition: target => {
const doc = getDocFromElementSync(target);
return doc && !(doc.type === 'domainCard' && doc.system.inVault);
},
callback: async (target, event) => (await getDocFromElement(target)).use(event)
});
}
if (toChat)
options.push({
@ -410,7 +420,7 @@ export default function DHApplicationMixin(Base) {
: this.document.system.actions?.get(actionId);
if (!doc) return;
const description = doc.system?.description ?? doc.description;
const description = game.i18n.localize(doc.system?.description ?? doc.description);
const isAction = !!actionId;
descriptionElement.innerHTML = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
description,

View file

@ -7,8 +7,6 @@ const { ActorSheetV2 } = foundry.applications.sheets;
/**
* A base actor sheet extending {@link ActorSheetV2} via {@link DHApplicationMixin}
* @extends ActorSheetV2
* @mixes DHSheetV2
*/
export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
/** @inheritDoc */
@ -106,7 +104,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
/**
* Get the set of ContextMenu options for Features.
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
* @this {DHSheetV2}
* @this {DHBaseActorSheet}
* @protected
*/
static #getFeatureContextOptions() {

View file

@ -7,8 +7,6 @@ const { ItemSheetV2 } = foundry.applications.sheets;
/**
* A base item sheet extending {@link ItemSheetV2} via {@link DHApplicationMixin}
* @extends ItemSheetV2
* @mixes DHSheetV2
*/
export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
/** @inheritDoc */
@ -108,7 +106,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
/**
* Get the set of ContextMenu options for Features.
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance
* @this {DHSheetV2}
* @this {DHBaseItemSheet}
* @protected
*/
static #getFeatureContextOptions() {
@ -183,12 +181,18 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
static async #deleteFeature(_, element) {
const target = element.closest('[data-item-uuid]');
const feature = await getDocFromElement(target);
if (!feature) return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
await this.document.update({
'system.features': this.document.system.features
.filter(x => target.dataset.type !== x.type || x.item.uuid !== feature.uuid)
.map(x => ({ ...x, item: x.item.uuid }))
});
if (!feature) {
await this.document.update({
'system.features': this.document.system.features
.filter(x => x.item)
.map(x => ({ ...x, item: x.item.uuid }))
});
} else
await this.document.update({
'system.features': this.document.system.features
.filter(x => target.dataset.type !== x.type || x.item.uuid !== feature.uuid)
.map(x => ({ ...x, item: x.item.uuid }))
});
}
/**
@ -261,21 +265,45 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
if (data.fromInternal) return;
const target = event.target.closest('fieldset.drop-section');
const item = await fromUuid(data.uuid);
let item = await fromUuid(data.uuid);
if (item?.type === 'feature') {
const cls = foundry.documents.Item.implementation;
if (this.document.parent?.type === 'character') {
const itemData = item.toObject();
item = await cls.create(
{
...itemData,
system: {
...itemData.system,
originItemType: this.document.type,
originId: this.document.id,
identifier: this.document.system.isMulticlass ? 'multiclass' : null
}
},
{ parent: this.document.parent }
);
}
if (target.dataset.type) {
await this.document.update({
'system.features': [...this.document.system.features, { type: target.dataset.type, item }].map(
x => ({
...x,
item: x.item?.uuid
})
)
});
await this.document.update(
{
'system.features': [...this.document.system.features, { type: target.dataset.type, item }].map(
x => ({
...x,
item: x.item?.uuid
})
)
},
{ parent: this.document.parent?.type === 'character' ? this.document.parent : undefined }
);
} else {
await this.document.update({
'system.features': [...this.document.system.features, item].map(x => x.uuid)
});
await this.document.update(
{
'system.features': [...this.document.system.features, item].map(x => x.uuid)
},
{ parent: this.document.parent?.type === 'character' ? this.document.parent : undefined }
);
}
}
}

View file

@ -20,6 +20,40 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
classes: ['daggerheart']
};
_getEntryContextOptions() {
return [
...super._getEntryContextOptions(),
// {
// name: 'Reroll',
// icon: '<i class="fa-solid fa-dice"></i>',
// condition: li => {
// const message = game.messages.get(li.dataset.messageId);
// return (game.user.isGM || message.isAuthor) && message.rolls.length > 0;
// },
// callback: li => {
// const message = game.messages.get(li.dataset.messageId);
// new game.system.api.applications.dialogs.RerollDialog(message).render({ force: true });
// }
// },
{
name: 'Reroll Damage',
icon: '<i class="fa-solid fa-dice"></i>',
condition: li => {
const message = game.messages.get(li.dataset.messageId);
const hasRolledDamage = message.system.hasDamage
? Object.keys(message.system.damage).length > 0
: false;
return (game.user.isGM || message.isAuthor) && hasRolledDamage;
},
callback: li => {
const message = game.messages.get(li.dataset.messageId);
new game.system.api.applications.dialogs.RerollDamageDialog(message).render({ force: true });
}
}
];
}
addChatListeners = async (app, html, data) => {
html.querySelectorAll('.duality-action-damage').forEach(element =>
element.addEventListener('click', event => this.onRollDamage(event, data.message))
@ -33,14 +67,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
html.querySelectorAll('.simple-roll-button').forEach(element =>
element.addEventListener('click', event => this.onRollSimple(event, data.message))
);
html.querySelectorAll('.target-container').forEach(element => {
element.addEventListener('mouseenter', this.hoverTarget);
element.addEventListener('mouseleave', this.unhoverTarget);
element.addEventListener('click', this.clickTarget);
});
html.querySelectorAll('.button-target-selection').forEach(element => {
element.addEventListener('click', event => this.onTargetSelection(event, data.message));
});
html.querySelectorAll('.healing-button').forEach(element =>
element.addEventListener('click', event => this.onHealing(event, data.message))
);
@ -138,33 +164,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
});
}
onTargetSelection(event, message) {
event.stopPropagation();
const msg = ui.chat.collection.get(message._id);
msg.system.targetMode = Boolean(event.target.dataset.targetHit);
}
hoverTarget(event) {
event.stopPropagation();
const token = canvas.tokens.get(event.currentTarget.dataset.token);
if (!token?.controlled) token._onHoverIn(event, { hoverOutOthers: true });
}
unhoverTarget(event) {
const token = canvas.tokens.get(event.currentTarget.dataset.token);
if (!token?.controlled) token._onHoverOut(event);
}
clickTarget(event) {
event.stopPropagation();
const token = canvas.tokens.get(event.currentTarget.dataset.token);
if (!token) {
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.attackTargetDoesNotExist'));
return;
}
game.canvas.pan(token);
}
async onRollSimple(event, message) {
const buttonType = event.target.dataset.type ?? 'damage',
total = message.rolls.reduce((a, c) => a + Roll.fromJSON(c).total, 0),
@ -228,19 +227,28 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
}
const target = event.target.closest('[data-die-index]');
let originalRoll_parsed = message.rolls.map(roll => JSON.parse(roll))[0];
const rollClass =
game.system.api.dice[
message.type === 'dualityRoll' ? 'DualityRoll' : target.dataset.type === 'damage' ? 'DHRoll' : 'D20Roll'
];
if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
if (target.dataset.type === 'damage') {
game.system.api.dice.DamageRoll.reroll(target, message);
} else {
let originalRoll_parsed = message.rolls.map(roll => JSON.parse(roll))[0];
const rollClass =
game.system.api.dice[
message.type === 'dualityRoll'
? 'DualityRoll'
: target.dataset.type === 'damage'
? 'DHRoll'
: 'D20Roll'
];
const { newRoll, parsedRoll } = await rollClass.reroll(originalRoll_parsed, target, message);
if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
await game.messages.get(message._id).update({
'system.roll': newRoll,
'rolls': [parsedRoll]
});
const { newRoll, parsedRoll } = await rollClass.reroll(originalRoll_parsed, target, message);
await game.messages.get(message._id).update({
'system.roll': newRoll,
'rolls': [parsedRoll]
});
}
}
}

View file

@ -17,7 +17,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
this.config = CONFIG.DH.ITEMBROWSER.compendiumConfig;
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);
}
@ -26,7 +26,6 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
id: 'itemBrowser',
classes: ['daggerheart', 'dh-style', 'dialog', 'compendium-browser'],
tag: 'div',
// title: 'Item Browser',
window: {
frame: true,
title: 'Compendium Browser',
@ -41,9 +40,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
sortList: this.sortList
},
position: {
top: 330,
left: 120,
width: 800,
left: 100,
width: 850,
height: 600
}
};
@ -88,16 +86,14 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
/** @inheritDoc */
async _preFirstRender(context, options) {
if(context.presets?.render?.noFolder || context.presets?.render?.lite)
options.position.width = 600;
if (context.presets?.render?.noFolder || context.presets?.render?.lite) options.position.width = 600;
await super._preFirstRender(context, options);
}
/** @inheritDoc */
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);
await super._preRender(context, options);
@ -110,18 +106,17 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
this._createSearchFilter();
this._createFilterInputs();
this._createDragProcess();
if(context.presets?.render?.lite)
this.element.classList.add('lite');
if(context.presets?.render?.noFolder)
this.element.classList.add('no-folder');
if(context.presets?.render?.noFilter)
this.element.classList.add('no-filter');
if(this.presets?.filter) {
Object.entries(this.presets.filter).forEach(([k,v]) => this.fieldFilter.find(c => c.name === k).value = v.value);
if (context.presets?.render?.lite) this.element.classList.add('lite');
if (context.presets?.render?.noFolder) this.element.classList.add('no-folder');
if (context.presets?.render?.noFilter) this.element.classList.add('no-filter');
if (this.presets?.filter) {
Object.entries(this.presets.filter).forEach(
([k, v]) => (this.fieldFilter.find(c => c.name === k).value = v.value)
);
await this._onInputFilterBrowser();
}
}
@ -198,6 +193,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
formatLabel(item, field) {
const property = foundry.utils.getProperty(item, field.key);
if (Array.isArray(property)) property.join(', ');
if (typeof field.format !== 'function') return property ?? '-';
return field.format(property);
}
@ -315,19 +311,18 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
async _onInputFilterBrowser(event) {
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')) {
const itemUUID = li.dataset.itemUuid,
item = this.items.find(i => i.uuid === itemUUID);
if(!item) continue;
if (!item) continue;
const matchesMenu =
this.fieldFilter.length === 0 ||
this.fieldFilter.every(f => (
!f.value && f.value !== false) ||
ItemBrowser.evaluateFilter(item, this.createFilterData(f))
this.fieldFilter.every(
f => (!f.value && f.value !== false) || ItemBrowser.evaluateFilter(item, this.createFilterData(f))
);
if (matchesMenu) this.#filteredItems.browser.input.add(item.id);
@ -335,21 +330,21 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
li.hidden = !(search.has(item.id) && matchesMenu);
}
}
/**
* Foundry evaluateFilter doesn't allow you to match if filter values are included into item data
* @param {*} obj
* @param {*} filter
* @param {*} obj
* @param {*} filter
*/
static evaluateFilter(obj, filter) {
let docValue = foundry.utils.getProperty(obj, filter.field);
let filterValue = filter.value;
switch (filter.operator) {
case "contains2":
case 'contains2':
filterValue = Array.isArray(filterValue) ? filterValue : [filterValue];
docValue = Array.isArray(docValue) ? docValue : [docValue];
return docValue.some(dv => filterValue.includes(dv));
case "contains3":
case 'contains3':
return docValue.some(f => f.value === filterValue);
default:
return foundry.applications.ux.SearchFilter.evaluateFilter(obj, filter);
@ -373,30 +368,33 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
this.render({ force: true });
}
static getFolderConfig(folder, property = "columns") {
if(!folder) return [];
static getFolderConfig(folder, property = 'columns') {
if (!folder) return [];
return folder[property] ?? CONFIG.DH.ITEMBROWSER.typeConfig[folder.listType]?.[property] ?? [];
}
static sortList(_, target) {
const key = target.dataset.sortKey,
type = !target.dataset.sortType || target.dataset.sortType === "DESC" ? "ASC" : "DESC",
itemListContainer = target.closest(".compendium-results").querySelector(".item-list"),
itemList = itemListContainer.querySelectorAll(".item-container");
type = !target.dataset.sortType || target.dataset.sortType === 'DESC' ? 'ASC' : 'DESC',
itemListContainer = target.closest('.compendium-results').querySelector('.item-list'),
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;
const newOrder = [...itemList].reverse().sort((a, b) => {
const aProp = a.querySelector(`[data-item-key="${key}"]`),
bProp = b.querySelector(`[data-item-key="${key}"]`)
if(type === "DESC") {
bProp = b.querySelector(`[data-item-key="${key}"]`);
if (type === 'DESC') {
return aProp.innerText < bProp.innerText ? 1 : -1;
} else {
return aProp.innerText > bProp.innerText ? 1 : -1;
}
});
itemListContainer.replaceChildren(...newOrder);
}

View file

@ -183,11 +183,11 @@ export const conditions = {
icon: 'icons/magic/control/debuff-chains-shackle-movement-red.webp',
description: 'DAGGERHEART.CONFIG.Condition.restrained.description'
},
unconcious: {
id: 'unconcious',
name: 'DAGGERHEART.CONFIG.Condition.unconcious.name',
unconscious: {
id: 'unconscious',
name: 'DAGGERHEART.CONFIG.Condition.unconscious.name',
icon: 'icons/magic/control/sleep-bubble-purple.webp',
description: 'DAGGERHEART.CONFIG.Condition.unconcious.description'
description: 'DAGGERHEART.CONFIG.Condition.unconscious.description'
},
dead: {
id: 'dead',
@ -494,44 +494,49 @@ export const diceSetNumbers = {
flat: 'Flat'
};
export const getDiceSoNicePresets = async (hopeFaces, fearFaces, advantageFaces = 'd6', disadvantageFaces = 'd6') => {
const { diceSoNice } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance);
const getPreset = async (type, faces) => {
const system = game.dice3d.DiceFactory.systems.get(type.system).dice.get(faces);
if (!system) {
ui.notifications.error(
game.i18n.format('DAGGERHEART.UI.Notifications.noDiceSystem', {
system: game.dice3d.DiceFactory.systems.get(type.system).name,
faces: faces
})
);
return;
}
export const getDiceSoNicePreset = async (type, faces) => {
const system = game.dice3d.DiceFactory.systems.get(type.system).dice.get(faces);
if (!system) {
ui.notifications.error(
game.i18n.format('DAGGERHEART.UI.Notifications.noDiceSystem', {
system: game.dice3d.DiceFactory.systems.get(type.system).name,
faces: faces
})
);
return;
}
if (system.modelFile && !system.modelLoaded) {
await system.loadModel(game.dice3d.DiceFactory.loaderGLTF);
} else {
await system.loadTextures();
}
return {
modelFile: system.modelFile,
appearance: {
...system.appearance,
...type
}
};
};
if (system.modelFile && !system.modelLoaded) {
await system.loadModel(game.dice3d.DiceFactory.loaderGLTF);
} else {
await system.loadTextures();
}
return {
hope: await getPreset(diceSoNice.hope, hopeFaces),
fear: await getPreset(diceSoNice.fear, fearFaces),
advantage: await getPreset(diceSoNice.advantage, advantageFaces),
disadvantage: await getPreset(diceSoNice.disadvantage, disadvantageFaces)
modelFile: system.modelFile,
appearance: {
...system.appearance,
...type
}
};
};
export const getDiceSoNicePresets = async (hopeFaces, fearFaces, advantageFaces = 'd6', disadvantageFaces = 'd6') => {
const { diceSoNice } = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance);
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)
};
};
export const refreshTypes = {
scene: {
id: 'session',
label: 'DAGGERHEART.GENERAL.RefreshType.scene'
},
session: {
id: 'session',
label: 'DAGGERHEART.GENERAL.RefreshType.session'

View file

@ -780,7 +780,15 @@ export const weaponFeatures = {
mode: 2,
value: '1'
}
]
],
system: {
rangeDependence: {
enabled: true,
range: 'melee',
target: 'hostile',
type: 'withinRange'
}
}
}
]
},
@ -1040,16 +1048,6 @@ export const weaponFeatures = {
key: 'system.evasion',
mode: 2,
value: '-1'
},
{
key: 'system.bonuses.damage.primaryWeapon.extraDice',
mode: 2,
value: '1'
},
{
key: 'system.rules.weapon.dropLowestDamageDice',
mode: 5,
value: '1'
}
]
}
@ -1089,7 +1087,15 @@ export const weaponFeatures = {
mode: 2,
value: 'ITEM.@system.tier + 1'
}
]
],
system: {
rangeDependence: {
enabled: true,
range: 'melee',
target: 'hostile',
type: 'withinRange'
}
}
}
]
},
@ -1166,18 +1172,7 @@ export const weaponFeatures = {
name: 'DAGGERHEART.CONFIG.WeaponFeature.powerful.effects.powerful.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.powerful.effects.powerful.description',
img: 'icons/magic/control/buff-flight-wings-runes-red-yellow.webp',
changes: [
{
key: 'system.bonuses.damage.primaryWeapon.extraDice',
mode: 2,
value: '1'
},
{
key: 'system.rules.weapon.dropLowestDamageDice',
mode: 5,
value: '1'
}
]
changes: []
}
]
},
@ -1229,7 +1224,7 @@ export const weaponFeatures = {
img: 'icons/skills/melee/strike-sword-slashing-red.webp',
changes: [
{
key: 'system.bonuses.roll.primaryWeapon.attack',
key: 'system.bonuses.roll.primaryWeapon.bonus',
mode: 2,
value: 1
}
@ -1301,13 +1296,7 @@ export const weaponFeatures = {
name: 'DAGGERHEART.CONFIG.WeaponFeature.selfCorrecting.effects.selfCorrecting.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.selfCorrecting.effects.selfCorrecting.description',
img: 'icons/weapons/ammunition/arrow-broadhead-glowing-orange.webp',
changes: [
{
key: 'system.rules.damage.flipMinDiceValue',
mode: 5,
value: 1
}
]
changes: []
}
]
},
@ -1319,13 +1308,7 @@ export const weaponFeatures = {
name: 'DAGGERHEART.CONFIG.WeaponFeature.serrated.effects.serrated.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.serrated.effects.serrated.description',
img: 'icons/weapons/ammunition/arrow-broadhead-glowing-orange.webp',
changes: [
{
key: 'system.rules.damage.flipMinDiceValue',
mode: 5,
value: 1
}
]
changes: []
}
]
},

View file

@ -115,7 +115,6 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
if (!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
if (this.chatDisplay) await this.toChat();
let { byPassRoll } = options,
config = this.prepareConfig(event, byPassRoll);
for (let i = 0; i < this.constructor.extraSchemas.length; i++) {
@ -145,9 +144,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
if (this.rollDamage && this.damage.parts.length) await this.rollDamage(event, config);
else if (this.trigger) await this.trigger(event, config);
else if (this.hasSave || this.hasEffect) {
const roll = new Roll('');
const roll = new CONFIG.Dice.daggerheart.DHRoll('');
roll._evaluated = true;
if (this.hasTarget) config.targetSelection = config.targets.length > 0;
await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config);
}
}
@ -180,7 +178,6 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
hasHealing: this.damage?.parts?.length && this.type === 'healing',
hasEffect: !!this.effects?.length,
hasSave: this.hasSave,
hasTarget: true,
selectedRollMode: game.settings.get('core', 'rollMode'),
isFastForward: event.shiftKey,
data: this.getRollData(),
@ -211,7 +208,14 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
}
async consume(config, successCost = false) {
const usefulResources = foundry.utils.deepClone(this.actor.system.resources);
const usefulResources = {
...foundry.utils.deepClone(this.actor.system.resources),
fear: {
value: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
max: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear,
reversed: false
}
};
for (var cost of config.costs) {
if (cost.keyIsID) {
@ -223,24 +227,26 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
}
}
const resources = config.costs
const resources = game.system.api.fields.ActionFields.CostField.getRealCosts(config.costs)
.filter(
c =>
c.enabled !== false &&
((!successCost && (!c.consumeOnSuccess || config.roll?.success)) ||
(successCost && c.consumeOnSuccess))
(!successCost && (!c.consumeOnSuccess || config.roll?.success)) ||
(successCost && c.consumeOnSuccess)
)
.map(c => {
.reduce((a, c) => {
const resource = usefulResources[c.key];
return {
key: c.key,
value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1),
target: resource.target,
keyIsID: resource.keyIsID
};
});
if (resource) {
a.push({
key: c.key,
value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1),
target: resource.target,
keyIsID: resource.keyIsID
});
return a;
}
}, []);
await this.actor.modifyResource(resources);
await (this.actor.system.partner ?? this.actor).modifyResource(resources);
if (
config.uses?.enabled &&
((!successCost && (!config.uses?.consumeOnSuccess || config.roll?.success)) ||
@ -248,8 +254,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
)
this.update({ 'uses.value': this.uses.value + 1 });
if (config.roll?.success || successCost)
(config.message ?? config.parent).update({ 'system.successConsumed': true });
if (config.roll?.success || successCost) {
setTimeout(() => {
(config.message ?? config.parent).update({ 'system.successConsumed': true });
}, 50);
}
}
/* */
@ -368,15 +377,15 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
async updateChatMessage(message, targetId, changes, chain = true) {
setTimeout(async () => {
const chatMessage = ui.chat.collection.get(message._id),
msgTarget =
chatMessage.system.targets.find(mt => mt.id === targetId) ??
chatMessage.system.oldTargets.find(mt => mt.id === targetId);
msgTarget.saved = changes;
const chatMessage = ui.chat.collection.get(message._id);
await chatMessage.update({
system: {
targets: chatMessage.system.targets,
oldTargets: chatMessage.system.oldTargets
flags: {
[game.system.id]: {
reactionRolls: {
[targetId]: changes
}
}
}
});
}, 100);

View file

@ -49,8 +49,7 @@ export default class DHDamageAction extends DHBaseAction {
...systemData,
roll: formulas,
dialog: {},
data: this.getRollData(),
targetSelection: systemData.targets.length > 0
data: this.getRollData()
};
if (this.hasSave) config.onSave = this.save.damageMod;
if (data.system) {

View file

@ -32,7 +32,7 @@ export default class BeastformEffect extends BaseEffect {
if (this.parent.parent?.type === 'character') {
this.parent.parent.system.primaryWeapon?.update?.({ 'system.equipped': false });
this.parent.parent.system.secondayWeapon?.update?.({ 'system.equipped': false });
this.parent.parent.system.secondaryWeapon?.update?.({ 'system.equipped': false });
}
}

View file

@ -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 }),
burden: new fields.SchemaField({
ignore: new fields.BooleanField()
@ -363,6 +351,17 @@ export default class DhCharacter extends BaseDataActor {
return [...classDomains, ...multiclassDomains];
}
get domainData() {
const allDomainData = CONFIG.DH.DOMAIN.allDomains();
return this.domains.map(key => {
const domain = allDomainData[key];
return {
...domain,
label: game.i18n.localize(domain.label)
};
});
}
get domainCards() {
const domainCards = this.parent.items.filter(x => x.type === 'domainCard');
const loadout = domainCards.filter(x => !x.system.inVault);

View file

@ -3,7 +3,6 @@ export default class DHAbilityUse extends foundry.abstract.TypeDataModel {
const fields = foundry.data.fields;
return {
title: new fields.StringField({}),
origin: new fields.StringField({}),
img: new fields.StringField({}),
name: new fields.StringField({}),

View file

@ -18,15 +18,11 @@ const targetsField = () =>
);
export default class DHActorRoll extends foundry.abstract.TypeDataModel {
targetHook = null;
static defineSchema() {
return {
title: new fields.StringField(),
roll: new fields.ObjectField(),
targets: targetsField(),
oldTargets: targetsField(),
targetSelection: new fields.BooleanField({ initial: false }),
hasRoll: new fields.BooleanField({ initial: false }),
hasDamage: new fields.BooleanField({ initial: false }),
hasHealing: new fields.BooleanField({ initial: false }),
@ -63,66 +59,59 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
return actionItem.system.actionsList?.find(a => a.id === this.source.action);
}
get messageTemplate() {
return 'systems/daggerheart/templates/ui/chat/roll.hbs';
}
get targetMode() {
return this.targetSelection;
return this.parent.targetSelection;
}
set targetMode(mode) {
this.targetSelection = mode;
this.updateTargets();
if (!this.parent.isAuthor) return;
this.parent.targetSelection = mode;
this.registerTargetHook();
this.parent.update({
system: {
targetSelection: this.targetSelection,
oldTargets: this.oldTargets
this.updateTargets();
}
get hitTargets() {
return this.currentTargets.filter(t => t.hit || !this.hasRoll || !this.targetMode);
}
async updateTargets() {
if (!ui.chat.collection.get(this.parent.id)) return;
let targets;
if (this.targetMode) targets = this.targets;
else
targets = Array.from(game.user.targets).map(t =>
game.system.api.fields.ActionFields.TargetField.formatTarget(t)
);
await this.parent.update({
flags: {
[game.system.id]: {
targets: targets,
targetMode: this.targetMode
}
}
});
}
get hitTargets() {
return this.currentTargets.filter(t => t.hit || !this.hasRoll || !this.targetSelection);
}
async updateTargets() {
this.currentTargets = this.getTargetList();
if (!this.targetSelection) {
this.currentTargets.forEach(ct => {
if (this.targets.find(t => t.actorId === ct.actorId)) return;
const indexTarget = this.oldTargets.findIndex(ot => ot.actorId === ct.actorId);
if (indexTarget === -1) this.oldTargets.push(ct);
});
if (this.hasSave) this.setPendingSaves();
if (this.currentTargets.length) {
if (!this.parent._id) return;
const updates = await this.parent.update({
system: {
oldTargets: this.oldTargets
}
});
if (!updates && ui.chat.collection.get(this.parent.id)) ui.chat.updateMessage(this.parent);
}
}
}
registerTargetHook() {
if (this.targetSelection && this.targetHook !== null) {
Hooks.off('targetToken', this.targetHook);
this.targetHook = null;
} else if (!this.targetSelection && this.targetHook === null) {
this.targetHook = Hooks.on('targetToken', foundry.utils.debounce(this.updateTargets.bind(this), 50));
if (!this.parent.isAuthor) return;
if (this.targetMode && this.parent.targetHook !== null) {
Hooks.off('targetToken', this.parent.targetHook);
return (this.parent.targetHook = null);
} else if (!this.targetMode && this.parent.targetHook === null) {
return (this.parent.targetHook = Hooks.on(
'targetToken',
foundry.utils.debounce(this.updateTargets.bind(this), 50)
));
}
}
prepareDerivedData() {
if (this.hasTarget) {
this.hasHitTarget = this.targets.filter(t => t.hit === true).length > 0;
this.updateTargets();
this.registerTargetHook();
if (this.targetSelection === true) {
this.currentTargets = this.getTargetList();
if (this.targetMode === true && this.hasRoll) {
this.targetShort = this.targets.reduce(
(a, c) => {
if (c.hit) a.hit += 1;
@ -136,23 +125,28 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
}
this.canViewSecret = this.parent.speakerActor?.testUserPermission(game.user, 'OBSERVER');
this.canButtonApply = game.user.isGM;
}
getTargetList() {
return this.targetSelection !== true
? Array.from(game.user.targets).map(t => {
const target = game.system.api.fields.ActionFields.TargetField.formatTarget(t),
oldTarget =
this.targets.find(ot => ot.actorId === target.actorId) ??
this.oldTargets.find(ot => ot.actorId === target.actorId);
if (oldTarget) return oldTarget;
return target;
})
: this.targets;
const targets =
this.targetMode && this.parent.isAuthor
? this.targets
: (this.parent.getFlag(game.system.id, 'targets') ?? this.targets),
reactionRolls = this.parent.getFlag(game.system.id, 'reactionRolls');
if (reactionRolls) {
Object.entries(reactionRolls).forEach(([k, r]) => {
const target = targets.find(t => t.id === k);
if (target) target.saved = r;
});
}
return targets;
}
setPendingSaves() {
this.pendingSaves = this.targetSelection
this.pendingSaves = this.targetMode
? this.targets.filter(target => target.hit && target.saved.success === null).length > 0
: this.currentTargets.filter(target => target.saved.success === null).length > 0;
}

View file

@ -12,7 +12,10 @@ export default class CostField extends fields.ArrayField {
value: new fields.NumberField({ nullable: true, initial: 1, min: 0 }),
scalable: new fields.BooleanField({ initial: false }),
step: new fields.NumberField({ nullable: true, initial: null }),
consumeOnSuccess: new fields.BooleanField({ initial: false, label: "DAGGERHEART.ACTIONS.Settings.consumeOnSuccess.label" })
consumeOnSuccess: new fields.BooleanField({
initial: false,
label: 'DAGGERHEART.ACTIONS.Settings.consumeOnSuccess.label'
})
});
super(element, options, context);
}
@ -47,6 +50,7 @@ export default class CostField extends fields.ArrayField {
static hasCost(costs) {
const realCosts = CostField.getRealCosts.call(this, costs),
hasFearCost = realCosts.findIndex(c => c.key === 'fear');
if (hasFearCost > -1) {
const fearCost = realCosts.splice(hasFearCost, 1)[0];
if (
@ -70,7 +74,9 @@ export default class CostField extends fields.ArrayField {
}
static getResources(costs) {
const actorResources = 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);
const itemResources = {};
for (let itemResource of costs) {
if (itemResource.keyIsID) {
@ -89,7 +95,13 @@ export default class CostField extends fields.ArrayField {
static getRealCosts(costs) {
const realCosts = costs?.length ? costs.filter(c => c.enabled) : [];
return realCosts;
let mergedCosts = [];
realCosts.forEach(c => {
const getCost = Object.values(mergedCosts).find(gc => gc.key === c.key);
if (getCost) getCost.total += c.total;
else mergedCosts.push(c);
});
return mergedCosts;
}
static formatMax(max) {

View file

@ -15,6 +15,7 @@ export default class TargetField extends fields.SchemaField {
static prepareConfig(config) {
if (!this.target?.type) return [];
config.hasTarget = true;
let targets;
if (this.target?.type === CONFIG.DH.GENERAL.targetTypes.self.id)
targets = [this.actor.token ?? this.actor.prototypeToken];

View file

@ -229,7 +229,7 @@ export function ActionMixin(Base) {
}
return this.inCollection
? foundry.utils.getProperty(result, basePath).get(this.id)
? foundry.utils.getProperty(result, basePath)?.get(this.id)
: foundry.utils.getProperty(result, basePath);
}
@ -285,6 +285,7 @@ export function ActionMixin(Base) {
}
};
ChatMessage.applyRollMode(msg, game.settings.get('core', 'rollMode'));
cls.create(msg);
}
}

View file

@ -60,7 +60,7 @@ export default class DHArmor extends AttachableItem {
const allowed = await super._preUpdate(changes, options, user);
if (allowed === false) return false;
if (changes.system.armorFeatures) {
if (changes.system?.armorFeatures) {
const removed = this.armorFeatures.filter(x => !changes.system.armorFeatures.includes(x));
const added = changes.system.armorFeatures.filter(x => !this.armorFeatures.includes(x));

View file

@ -33,6 +33,11 @@ export default class DHDomainCard extends BaseDataItem {
};
}
get domainLabel() {
const allDomainData = CONFIG.DH.DOMAIN.allDomains();
return game.i18n.localize(allDomainData[this.domain].label);
}
/* -------------------------------------------- */
/**@override */
@ -71,7 +76,7 @@ export default class DHDomainCard extends BaseDataItem {
_getTags() {
const tags = [
game.i18n.localize(`DAGGERHEART.CONFIG.DomainCardTypes.${this.type}`),
game.i18n.localize(`DAGGERHEART.GENERAL.Domain.${this.domain}.label`),
this.domainLabel,
`${game.i18n.localize('DAGGERHEART.ITEMS.DomainCard.recallCost')}: ${this.recallCost}`
];
@ -85,7 +90,7 @@ export default class DHDomainCard extends BaseDataItem {
_getLabels() {
const labels = [
game.i18n.localize(`DAGGERHEART.CONFIG.DomainCardTypes.${this.type}`),
game.i18n.localize(`DAGGERHEART.GENERAL.Domain.${this.domain}.label`),
this.domainLabel,
{
value: `${this.recallCost}`, //converts the number to a string
icons: ['fa-bolt']

View file

@ -21,7 +21,7 @@ export default class DHSubclass extends BaseDataItem {
integer: false,
nullable: true,
initial: null,
label: "DAGGERHEART.ITEMS.Subclass.spellcastingTrait"
label: 'DAGGERHEART.ITEMS.Subclass.spellcastingTrait'
}),
features: new ItemLinkFields(),
featureState: new fields.NumberField({ required: true, initial: 1, min: 1 }),
@ -50,7 +50,8 @@ export default class DHSubclass extends BaseDataItem {
async _preCreate(data, options, user) {
if (this.actor?.type === 'character') {
const dataUuid = data.uuid ?? data._stats?.compendiumSource ?? `Item.${data._id}`;
const dataUuid =
(data.uuid ?? data.folder) ? `Compendium.daggerheart.subclasses.Item.${data._id}` : `Item.${data._id}`;
if (this.actor.system.class.subclass) {
if (this.actor.system.multiclass.subclass) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassesAlreadyPresent'));

View file

@ -139,17 +139,15 @@ export default class D20Roll extends DHRoll {
static postEvaluate(roll, config = {}) {
const data = super.postEvaluate(roll, config);
data.type = config.roll?.type;
data.difficulty = config.roll.difficulty;
if (config.targets?.length) {
config.targetSelection = true;
config.targets.forEach(target => {
const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion;
target.hit = roll.isCritical || roll.total >= difficulty;
});
data.success = config.targets.some(target => target.hit);
} else if (config.roll.difficulty) {
data.difficulty = config.roll.difficulty;
data.success = roll.isCritical || roll.total >= config.roll.difficulty;
}
} else if (config.roll.difficulty) data.success = roll.isCritical || roll.total >= config.roll.difficulty;
data.advantage = {
type: config.roll.advantage,
dice: roll.dAdvantage?.denomination,

View file

@ -15,7 +15,6 @@ export default class DamageRoll extends DHRoll {
const parts = config.roll.map(r => this.postEvaluate(r));
config.damage = this.unifyDamageRoll(parts);
// config.targetSelection = config.targets?.length
}
static postEvaluate(roll, config = {}) {
@ -30,16 +29,18 @@ export default class DamageRoll extends DHRoll {
}
static async buildPost(roll, config, message) {
const chatMessage = config.source?.message
? ui.chat.collection.get(config.source.message)
: getDocumentClass('ChatMessage').applyRollMode({}, config.rollMode);
if (game.modules.get('dice-so-nice')?.active) {
const pool = foundry.dice.terms.PoolTerm.fromRolls(
Object.values(config.damage).flatMap(r => r.parts.map(p => p.roll))
),
diceRoll = Roll.fromTerms([pool]);
await game.dice3d.showForRoll(diceRoll, game.user, true);
await game.dice3d.showForRoll(diceRoll, game.user, true, chatMessage.whisper, chatMessage.blind);
}
await super.buildPost(roll, config, message);
if (config.source?.message) {
const chatMessage = ui.chat.collection.get(config.source.message);
chatMessage.update({ 'system.damage': config.damage });
}
}
@ -102,14 +103,14 @@ export default class DamageRoll extends DHRoll {
}
constructFormula(config) {
this.options.roll.forEach(part => {
this.options.roll.forEach((part, index) => {
part.roll = new Roll(Roll.replaceFormulaData(part.formula, config.data));
this.constructFormulaPart(config, part);
this.constructFormulaPart(config, part, index);
});
return this.options.roll;
}
constructFormulaPart(config, part) {
constructFormulaPart(config, part, index) {
part.roll.terms = Roll.parse(part.roll.formula, config.data);
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) {
part.roll.terms.push(
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
@ -132,6 +141,192 @@ export default class DamageRoll extends DHRoll {
criticalBonus = tmpRoll.total - this.constructor.calculateTotalModifiers(tmpRoll);
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));
}
/* To Remove When Reaction System */
static temporaryModifierBuilder(config) {
const mods = {};
if (config.data?.parent) {
if (config.data.parent.appliedEffects) {
// Bardic Rally
const rallyChoices = 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;
}, [])
if(rallyChoices.length) {
mods.rally = {
label: 'DAGGERHEART.CLASS.Feature.rallyDice',
values: rallyChoices,
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
}
}
});
}
}

View file

@ -2,19 +2,19 @@ import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
export default class DHRoll extends Roll {
baseTerms = [];
constructor(formula, data, options) {
constructor(formula, data = {}, options = {}) {
super(formula, data, options);
if (!this.data || !Object.keys(this.data).length) this.data = options.data;
}
get title() {
return game.i18n.localize(
"DAGGERHEART.GENERAL.Roll.basic"
);
return game.i18n.localize('DAGGERHEART.GENERAL.Roll.basic');
}
static messageType = 'adversaryRoll';
static CHAT_TEMPLATE = 'systems/daggerheart/templates/ui/chat/roll.hbs';
static DefaultDialog = D20RollDialog;
static async build(config = {}, message = {}) {
@ -34,6 +34,8 @@ export default class DHRoll extends Roll {
this.applyKeybindings(config);
this.temporaryModifierBuilder(config);
let roll = new this(config.roll.formula, config.data, config);
if (config.dialog.configure !== false) {
// Open Roll Dialog
@ -64,8 +66,7 @@ export default class DHRoll extends Roll {
}
// Create Chat Message
if (!config.source?.message)
config.message = await this.toMessage(roll, config);
if (!config.source?.message) config.message = await this.toMessage(roll, config);
}
static postEvaluate(roll, config = {}) {
@ -92,10 +93,37 @@ export default class DHRoll extends Roll {
system: config,
rolls: [roll]
};
if(roll._evaluated) return await cls.create(msg, { rollMode: config.selectedRollMode });
config.selectedRollMode ??= game.settings.get('core', 'rollMode');
if (roll._evaluated) return await cls.create(msg, { rollMode: config.selectedRollMode });
return msg;
}
/** @inheritDoc */
async render({ flavor, template = this.constructor.CHAT_TEMPLATE, isPrivate = false, ...options } = {}) {
if (!this._evaluated) return;
const chatData = await this._prepareChatRenderContext({ flavor, isPrivate, ...options });
return foundry.applications.handlebars.renderTemplate(template, chatData);
}
/** @inheritDoc */
async _prepareChatRenderContext({ flavor, isPrivate = false, ...options } = {}) {
if (isPrivate) {
return {
user: game.user.id,
flavor: null,
title: '???',
roll: {
total: '??'
},
hasRoll: true,
isPrivate
};
} else {
options.message.system.user = game.user.id;
return options.message.system;
}
}
static applyKeybindings(config) {
if (config.event)
config.dialog.configure ??= !(config.event.shiftKey || config.event.altKey || config.event.ctrlKey);
@ -178,11 +206,15 @@ export default class DHRoll extends Roll {
}
return modifierTotal;
}
static temporaryModifierBuilder(config) {
return {};
}
}
export const registerRollDiceHooks = () => {
Hooks.on(`${CONFIG.DH.id}.postRollDuality`, async (config, message) => {
const hopeFearAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).hopeFear;
const hopeFearAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).hopeFear;
if (
!config.source?.actor ||
(game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) ||
@ -206,8 +238,10 @@ export const registerRollDiceHooks = () => {
if (updates.length) {
const target = actor.system.partner ?? actor;
if (!['dead', 'unconcious'].some(x => actor.statuses.has(x))) {
target.modifyResource(updates);
if (!['dead', 'unconscious'].some(x => actor.statuses.has(x))) {
setTimeout(() => {
target.modifyResource(updates);
}, 50);
}
}

View file

@ -124,15 +124,20 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
return tags;
}
/**
* Create a new ChatMessage to display this documents data.
* @param {String} origin - uuid of a document. TODO: This needs to be reviewed.
*/
async toChat(origin) {
/**@type {foundry.documents.ChatMessage} */
const cls = getDocumentClass('ChatMessage');
const actor = game.actors.get(cls.getSpeaker().actor);
const speaker = cls.getSpeaker();
const actor = cls.getSpeakerActor(speaker);
const systemData = {
action: { img: this.img, name: this.name },
actor: { name: actor.name, img: actor.img },
author: this.author,
speaker: cls.getSpeaker(),
origin: origin,
actor: { name: actor?.name, img: actor?.img },
speaker,
origin,
description: this.description,
actions: []
};

View file

@ -84,6 +84,8 @@ export default class DhpActor extends Actor {
await this.update({ 'system.levelData.level.changed': Math.min(newLevel, maxLevel) });
} else {
const levelupAuto = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).levelupAuto;
const usedLevel = Math.max(newLevel, 1);
if (newLevel < 1) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.tooLowLevel'));
@ -95,79 +97,90 @@ export default class DhpActor extends Actor {
return acc;
}, {});
const features = [];
const domainCards = [];
const experiences = [];
const subclassFeatureState = { class: null, multiclass: null };
let multiclass = null;
Object.keys(this.system.levelData.levelups)
.filter(x => x > usedLevel)
.forEach(levelKey => {
const level = this.system.levelData.levelups[levelKey];
const achievementCards = level.achievements.domainCards.map(x => x.itemUuid);
const advancementCards = level.selections.filter(x => x.type === 'domainCard').map(x => x.itemUuid);
domainCards.push(...achievementCards, ...advancementCards);
experiences.push(...Object.keys(level.achievements.experiences));
features.push(...level.selections.flatMap(x => x.features));
if (levelupAuto) {
const features = [];
const domainCards = [];
const experiences = [];
const subclassFeatureState = { class: null, multiclass: null };
let multiclass = null;
Object.keys(this.system.levelData.levelups)
.filter(x => x > usedLevel)
.forEach(levelKey => {
const level = this.system.levelData.levelups[levelKey];
const achievementCards = level.achievements.domainCards.map(x => x.itemUuid);
const advancementCards = level.selections
.filter(x => x.type === 'domainCard')
.map(x => x.itemUuid);
domainCards.push(...achievementCards, ...advancementCards);
experiences.push(...Object.keys(level.achievements.experiences));
features.push(...level.selections.flatMap(x => x.features));
const subclass = level.selections.find(x => x.type === 'subclass');
if (subclass) {
const path = subclass.secondaryData.isMulticlass === 'true' ? 'multiclass' : 'class';
const subclassState = Number(subclass.secondaryData.featureState) - 1;
subclassFeatureState[path] = subclassFeatureState[path]
? Math.min(subclassState, subclassFeatureState[path])
: subclassState;
}
const subclass = level.selections.find(x => x.type === 'subclass');
if (subclass) {
const path = subclass.secondaryData.isMulticlass === 'true' ? 'multiclass' : 'class';
const subclassState = Number(subclass.secondaryData.featureState) - 1;
subclassFeatureState[path] = subclassFeatureState[path]
? Math.min(subclassState, subclassFeatureState[path])
: subclassState;
}
multiclass = level.selections.find(x => x.type === 'multiclass');
});
multiclass = level.selections.find(x => x.type === 'multiclass');
});
for (let feature of features) {
if (feature.onPartner && !this.system.partner) continue;
for (let feature of features) {
if (feature.onPartner && !this.system.partner) continue;
const document = feature.onPartner ? this.system.partner : this;
document.items.get(feature.id)?.delete();
}
if (experiences.length > 0) {
const getUpdate = () => ({
'system.experiences': experiences.reduce((acc, key) => {
acc[`-=${key}`] = null;
return acc;
}, {})
});
this.update(getUpdate());
if (this.system.companion) {
this.system.companion.update(getUpdate());
const document = feature.onPartner ? this.system.partner : this;
document.items.get(feature.id)?.delete();
}
}
if (subclassFeatureState.class) {
this.system.class.subclass.update({ 'system.featureState': subclassFeatureState.class });
}
if (subclassFeatureState.multiclass) {
this.system.multiclass.subclass.update({ 'system.featureState': subclassFeatureState.multiclass });
}
if (multiclass) {
const multiclassSubclass = this.items.find(x => x.type === 'subclass' && x.system.isMulticlass);
const multiclassItem = this.items.find(x => x.uuid === multiclass.itemUuid);
multiclassSubclass.delete();
multiclassItem.delete();
this.update({
'system.multiclass': {
value: null,
subclass: null
if (experiences.length > 0) {
const getUpdate = () => ({
'system.experiences': experiences.reduce((acc, key) => {
acc[`-=${key}`] = null;
return acc;
}, {})
});
this.update(getUpdate());
if (this.system.companion) {
this.system.companion.update(getUpdate());
}
});
}
}
for (let domainCard of domainCards) {
const itemCard = this.items.find(x => x.uuid === domainCard);
itemCard.delete();
if (subclassFeatureState.class) {
this.system.class.subclass.update({ 'system.featureState': subclassFeatureState.class });
}
if (subclassFeatureState.multiclass) {
this.system.multiclass.subclass.update({ 'system.featureState': subclassFeatureState.multiclass });
}
if (multiclass) {
const multiclassItem = this.items.find(x => x.uuid === multiclass.itemUuid);
const multiclassFeatures = this.items.filter(
x => x.system.originItemType === 'class' && x.system.identifier === 'multiclass'
);
const subclassFeatures = this.items.filter(
x => x.system.originItemType === 'subclass' && x.system.identifier === 'multiclass'
);
this.deleteEmbeddedDocuments(
'Item',
[multiclassItem, ...multiclassFeatures, ...subclassFeatures].map(x => x.id)
);
this.update({
'system.multiclass': {
value: null,
subclass: null
}
});
}
for (let domainCard of domainCards) {
const itemCard = this.items.find(x => x.uuid === domainCard);
itemCard.delete();
}
}
await this.update({
@ -315,6 +328,7 @@ export default class DhpActor extends Actor {
...multiclassData,
system: {
...multiclassData.system,
features: multiclassData.system.features.filter(x => x.type !== 'hope'),
domains: [multiclass.secondaryData.domain],
isMulticlass: true
}
@ -644,16 +658,23 @@ export default class DhpActor extends Actor {
);
break;
case 'armor':
updates.armor.resources['system.marks.value'] = Math.max(
Math.min(this.system.armor.system.marks.value + r.value, this.system.armorScore),
0
);
if (this.system.armor?.system?.marks) {
updates.armor.resources['system.marks.value'] = Math.max(
Math.min(this.system.armor.system.marks.value + r.value, this.system.armorScore),
0
);
}
break;
default:
updates.actor.resources[`system.resources.${r.key}.value`] = Math.max(
Math.min(this.system.resources[r.key].value + r.value, this.system.resources[r.key].max),
0
);
if (this.system.resources?.[r.key]) {
updates.actor.resources[`system.resources.${r.key}.value`] = Math.max(
Math.min(
this.system.resources[r.key].value + r.value,
this.system.resources[r.key].max
),
0
);
}
break;
}
}

View file

@ -1,21 +1,61 @@
export default class DhpChatMessage extends foundry.documents.ChatMessage {
async renderHTML() {
if (this.system.messageTemplate)
this.content = await foundry.applications.handlebars.renderTemplate(this.system.messageTemplate, {
...this.system,
_source: this.system._source
});
targetHook = null;
targetSelection = null;
async renderHTML() {
const actor = game.actors.get(this.speaker.actor);
const actorData = actor ?? {
img: this.author.avatar ? this.author.avatar : 'icons/svg/mystery-man.svg',
name: ''
};
const actorData =
actor && this.isContentVisible
? actor
: {
img: this.author.avatar ? this.author.avatar : 'icons/svg/mystery-man.svg',
name: ''
};
/* We can change to fully implementing the renderHTML function if needed, instead of augmenting it. */
const html = await super.renderHTML({ actor: actorData, author: this.author });
this.applyPermission(html);
if (this.type === 'dualityRoll') {
this.enrichChatMessage(html);
this.addChatListeners(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) {
const elements = html.querySelectorAll('[data-perm-id]');
elements.forEach(e => {
const uuid = e.dataset.permId,
document = fromUuidSync(uuid);
if (!document) return;
e.setAttribute('data-view-perm', document.testUserPermission(game.user, 'OBSERVER'));
e.setAttribute('data-use-perm', document.testUserPermission(game.user, 'OWNER'));
});
if (this.isContentVisible && this.type === 'dualityRoll') {
html.classList.add('duality');
switch (this.system.roll?.result?.duality) {
case 1:
@ -29,36 +69,14 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
break;
}
}
this.enrichChatMessage(html);
return html;
}
applyPermission(html) {
const elements = html.querySelectorAll('[data-perm-id]');
elements.forEach(e => {
const uuid = e.dataset.permId,
document = fromUuidSync(uuid);
if (!document) return;
e.setAttribute('data-view-perm', document.testUserPermission(game.user, 'OBSERVER'));
e.setAttribute('data-use-perm', document.testUserPermission(game.user, 'OWNER'));
});
}
async _preCreate(data, options, user) {
options.speaker = ChatMessage.getSpeaker();
const rollActorOwner = data.rolls?.[0]?.data?.parent?.owner;
if (rollActorOwner) {
data.author = rollActorOwner ? rollActorOwner.id : data.author;
await this.updateSource({ author: rollActorOwner ?? user });
if(!game.user.isGM && !this.isAuthor && !this.speakerActor?.isOwner) {
const buttons = html.querySelectorAll(".ability-card-footer > .ability-use-button");
buttons.forEach(b => b.remove());
}
return super._preCreate(data, options, rollActorOwner ?? user);
}
enrichChatMessage(html) {
addChatListeners(html) {
html.querySelectorAll('.damage-button').forEach(element =>
element.addEventListener('click', this.onDamage.bind(this))
);
@ -66,10 +84,20 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
html.querySelectorAll('.duality-action-effect').forEach(element =>
element.addEventListener('click', this.onApplyEffect.bind(this))
);
html.querySelectorAll('.roll-target').forEach(element => {
element.addEventListener('mouseenter', this.hoverTarget);
element.addEventListener('mouseleave', this.unhoverTarget);
element.addEventListener('click', this.clickTarget);
});
html.querySelectorAll('.button-target-selection').forEach(element => {
element.addEventListener('click', this.onTargetSelection.bind(this));
});
}
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));
}
@ -141,9 +169,36 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
}
consumeOnSuccess() {
if (!this.system.successConsumed && !this.system.targetSelection) {
if (!this.system.successConsumed && !this.targetSelection) {
const action = this.system.action;
if (action) action.consume(this.system, true);
}
}
hoverTarget(event) {
event.stopPropagation();
const token = canvas.tokens.get(event.currentTarget.dataset.token);
if (token && !token?.controlled) token._onHoverIn(event, { hoverOutOthers: true });
}
unhoverTarget(event) {
const token = canvas.tokens.get(event.currentTarget.dataset.token);
if (token && !token?.controlled) token._onHoverOut(event);
}
clickTarget(event) {
event.stopPropagation();
const token = canvas.tokens.get(event.currentTarget.dataset.token);
if (!token) {
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.attackTargetDoesNotExist'));
return;
}
game.canvas.pan(token);
}
onTargetSelection(event) {
event.stopPropagation();
if (!event.target.classList.contains('target-selected'))
this.system.targetMode = Boolean(event.target.dataset.targetHit);
}
}

View file

@ -142,19 +142,16 @@ export default class DHItem extends foundry.documents.Item {
}
}
/**
* Create a new ChatMessage to display this documents data
* @param {String} origin - uuid of a document. TODO: This needs to be reviewed.
*/
async toChat(origin) {
/**@type {foundry.documents.ChatMessage} */
const cls = getDocumentClass('ChatMessage');
const item = await foundry.utils.fromUuid(origin);
const systemData = {
title:
this.type === 'ancestry'
? game.i18n.localize('DAGGERHEART.UI.Chat.foundationCard.ancestryTitle')
: this.type === 'community'
? game.i18n.localize('DAGGERHEART.UI.Chat.foundationCard.communityTitle')
: this.type === 'feature'
? game.i18n.localize('TYPES.Item.feature')
: game.i18n.localize('DAGGERHEART.UI.Chat.foundationCard.subclassFeatureTitle'),
origin: origin,
img: this.img,
item: {
@ -170,7 +167,6 @@ export default class DHItem extends foundry.documents.Item {
type: 'abilityUse',
user: game.user.id,
actor: item.parent,
author: this.author,
speaker: cls.getSpeaker(),
system: systemData,
title: game.i18n.localize('DAGGERHEART.ACTIONS.Config.displayInChat'),

View file

@ -60,7 +60,7 @@ export default class RegisterHandlebarsHelpers {
static rollParsed(value, actor, item, numerical) {
const isNumerical = typeof numerical === 'boolean' ? numerical : false;
const result = itemAbleRollParse(value, actor.getRollData(), item);
const result = itemAbleRollParse(value, actor?.getRollData() ?? {}, item);
return isNumerical ? (!result ? 0 : Number(result)) : result;
}
@ -69,7 +69,7 @@ export default class RegisterHandlebarsHelpers {
}
static empty(object) {
if(!(typeof object === 'object')) return true;
if (!(typeof object === 'object')) return true;
return Object.keys(object).length === 0;
}
}

View file

@ -85,7 +85,12 @@ export const chunkify = (array, chunkSize, mappingFunc) => {
export const tagifyElement = (element, baseOptions, onChange, tagifyOptions = {}) => {
const { maxTags } = tagifyOptions;
const options = typeof baseOptions === 'object' ? Object.values(baseOptions) : baseOptions;
const options = Array.isArray(baseOptions)
? baseOptions
: Object.keys(baseOptions).map(optionKey => ({
...baseOptions[optionKey],
id: optionKey
}));
const tagifyElement = new Tagify(element, {
tagTextProp: 'name',
@ -172,6 +177,26 @@ Roll.replaceFormulaData = function (formula, data = {}, { missing, warn = false
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 => {
return ['none', 'minor', 'major', 'severe'][damage];
};

View file

@ -9,7 +9,7 @@
"resource": {
"type": "diceValue",
"value": 0,
"max": "2",
"max": "@system.traits.strength.value",
"icon": "",
"recovery": "session"
},
@ -28,12 +28,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"systemVersion": "1.0.0",
"createdTime": 1754352649696,
"modifiedTime": 1754352712334,
"lastModifiedBy": "Q9NoTaEarn3VMS6Z"
"modifiedTime": 1754845640002,
"lastModifiedBy": "H02dtt2xvVJvYESk"
},
"_key": "!items!Xd7RYhfTxIj9aWI2"
}

View file

@ -14,16 +14,7 @@
"description": "",
"chatDisplay": false,
"actionType": "action",
"cost": [
{
"scalable": false,
"key": "hitPoints",
"value": 1,
"keyIsID": false,
"step": null,
"consumeOnSuccess": false
}
],
"cost": [],
"uses": {
"value": null,
"max": "1",
@ -67,7 +58,7 @@
{
"key": "system.bonuses.rally",
"mode": 2,
"value": "1d6",
"value": "d6",
"priority": null
}
],

View file

@ -14,16 +14,7 @@
"description": "",
"chatDisplay": true,
"actionType": "action",
"cost": [
{
"scalable": false,
"key": "hitPoints",
"value": 1,
"keyIsID": false,
"step": null,
"consumeOnSuccess": false
}
],
"cost": [],
"uses": {
"value": null,
"max": "1",
@ -67,7 +58,7 @@
{
"key": "system.bonuses.rally",
"mode": 2,
"value": "1d8",
"value": "d8",
"priority": null
}
],

View file

@ -59,13 +59,13 @@
{
"key": "system.bonuses.damage.physical.dice",
"mode": 2,
"value": "@system.tierd6",
"value": "@tierd6",
"priority": null
},
{
"key": "system.bonuses.damage.magical.dice",
"mode": 2,
"value": "@system.tierd6",
"value": "@tierd6",
"priority": null
}
],

View file

@ -64,7 +64,7 @@
"effects": [
{
"name": "Gifted Tracker",
"img": "icons/svg/item-bag.svg",
"img": "systems/daggerheart/assets/icons/domains/domain-card/sage.png",
"origin": "Compendium.daggerheart.domains.Item.VZ2b4zfRzV73XTuT",
"transfer": false,
"_id": "47Oh2weCdmuvKHM9",
@ -104,12 +104,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1754114056078,
"modifiedTime": 1754114073478,
"lastModifiedBy": "Q9NoTaEarn3VMS6Z"
"modifiedTime": 1754670410126,
"lastModifiedBy": "49DaecTcBSc5d0DA"
},
"_key": "!items.effects!VZ2b4zfRzV73XTuT.47Oh2weCdmuvKHM9"
}

View file

@ -389,7 +389,7 @@
"effects": [
{
"name": "Healed by Healing Hands",
"img": "icons/svg/item-bag.svg",
"img": "systems/daggerheart/assets/icons/domains/domain-card/splendor.png",
"origin": "Compendium.daggerheart.domains.Item.WTlhnQMajc1r8i50",
"transfer": false,
"_id": "sd5liP4ZcVeTMAoW",
@ -422,12 +422,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1754263407455,
"modifiedTime": 1754263727114,
"lastModifiedBy": "Q9NoTaEarn3VMS6Z"
"modifiedTime": 1754670504951,
"lastModifiedBy": "49DaecTcBSc5d0DA"
},
"_key": "!items.effects!WTlhnQMajc1r8i50.sd5liP4ZcVeTMAoW"
}

View file

@ -66,7 +66,7 @@
"effects": [
{
"name": "Life Ward",
"img": "icons/svg/item-bag.svg",
"img": "systems/daggerheart/assets/icons/domains/domain-card/splendor.png",
"origin": "Compendium.daggerheart.domains.Item.OszbCj0jTqq2ADx9",
"transfer": false,
"_id": "E7Ou4OMEy3TeK1Gf",
@ -99,12 +99,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1754264687962,
"modifiedTime": 1754264717646,
"lastModifiedBy": "Q9NoTaEarn3VMS6Z"
"modifiedTime": 1754670535710,
"lastModifiedBy": "49DaecTcBSc5d0DA"
},
"_key": "!items.effects!OszbCj0jTqq2ADx9.E7Ou4OMEy3TeK1Gf"
}

View file

@ -37,7 +37,7 @@
}
},
"_id": "UJTsJlnhi5Zi0XQ2",
"img": "icons/magic/life/heart-cross-blue.webp",
"img": "systems/daggerheart/assets/icons/domains/domain-card/bone.png",
"changes": [
{
"key": "system.rules.damageReduction.thresholdImmunities.minor",
@ -67,12 +67,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1754303484332,
"modifiedTime": 1754303570504,
"lastModifiedBy": "MQSznptE5yLT7kj8"
"modifiedTime": 1754670012467,
"lastModifiedBy": "49DaecTcBSc5d0DA"
},
"_key": "!items.effects!zbxPl81kbWEegKQN.UJTsJlnhi5Zi0XQ2"
}

View file

@ -16,7 +16,7 @@
{
"value": "reliable",
"effectIds": [
"HrbJ0bI7lMAYUCux"
"nRNnU57i7RMJoklC"
],
"actionIds": []
}
@ -109,14 +109,21 @@
"img": "icons/skills/melee/strike-sword-slashing-red.webp",
"changes": [
{
"key": "system.bonuses.roll.primaryWeapon.attack",
"key": "system.bonuses.roll.primaryWeapon.bonus",
"mode": 2,
"value": "1"
}
],
"_id": "HrbJ0bI7lMAYUCux",
"_id": "nRNnU57i7RMJoklC",
"type": "base",
"system": {},
"system": {
"rangeDependence": {
"enabled": false,
"type": "withinRange",
"target": "hostile",
"range": "melee"
}
},
"disabled": false,
"duration": {
"startTime": null,
@ -132,14 +139,14 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753835307838,
"modifiedTime": 1753835307838,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"systemVersion": "1.0.0",
"createdTime": 1754815224718,
"modifiedTime": 1754815224718,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items.effects!ijodu5yNBoMxpkHV.HrbJ0bI7lMAYUCux"
"_key": "!items.effects!ijodu5yNBoMxpkHV.nRNnU57i7RMJoklC"
}
],
"sort": 0,
@ -152,12 +159,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"systemVersion": "1.0.0",
"createdTime": 1753835285790,
"modifiedTime": 1753835317605,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754815224721,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items!ijodu5yNBoMxpkHV"
}

View file

@ -2,7 +2,7 @@
"folder": "TyqMEXhSkjOUq5SA",
"name": "Advanced Arcane-Frame Wheelchair",
"type": "weapon",
"img": "icons/svg/item-bag.svg",
"img": "systems/daggerheart/assets/icons/documents/items/ArcaneWheelchair.webp",
"system": {
"description": "",
"actions": {},
@ -15,7 +15,7 @@
{
"value": "reliable",
"effectIds": [
"G561ymlNGmaFAYFB"
"VnV5X9MBMabhz47b"
],
"actionIds": []
}
@ -108,14 +108,21 @@
"img": "icons/skills/melee/strike-sword-slashing-red.webp",
"changes": [
{
"key": "system.bonuses.roll.primaryWeapon.attack",
"key": "system.bonuses.roll.primaryWeapon.bonus",
"mode": 2,
"value": "1"
}
],
"_id": "G561ymlNGmaFAYFB",
"_id": "VnV5X9MBMabhz47b",
"type": "base",
"system": {},
"system": {
"rangeDependence": {
"enabled": false,
"type": "withinRange",
"target": "hostile",
"range": "melee"
}
},
"disabled": false,
"duration": {
"startTime": null,
@ -131,12 +138,14 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"lastModifiedBy": null
"systemVersion": "1.0.0",
"createdTime": 1754815300372,
"modifiedTime": 1754815300372,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items.effects!la3sAWgnvadc4NvP.G561ymlNGmaFAYFB"
"_key": "!items.effects!la3sAWgnvadc4NvP.VnV5X9MBMabhz47b"
}
],
"ownership": {
@ -148,12 +157,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"systemVersion": "1.0.0",
"createdTime": 1753836715885,
"modifiedTime": 1753836789197,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754845968271,
"lastModifiedBy": "H02dtt2xvVJvYESk"
},
"_id": "la3sAWgnvadc4NvP",
"sort": 0,

View file

@ -16,7 +16,7 @@
{
"value": "reliable",
"effectIds": [
"fRPKHzbKRz4yTHAF"
"wu2AmDvgeWI3hmRQ"
],
"actionIds": []
}
@ -109,14 +109,21 @@
"img": "icons/skills/melee/strike-sword-slashing-red.webp",
"changes": [
{
"key": "system.bonuses.roll.primaryWeapon.attack",
"key": "system.bonuses.roll.primaryWeapon.bonus",
"mode": 2,
"value": "1"
}
],
"_id": "fRPKHzbKRz4yTHAF",
"_id": "wu2AmDvgeWI3hmRQ",
"type": "base",
"system": {},
"system": {
"rangeDependence": {
"enabled": false,
"type": "withinRange",
"target": "hostile",
"range": "melee"
}
},
"disabled": false,
"duration": {
"startTime": null,
@ -132,14 +139,14 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753831629563,
"modifiedTime": 1753831629563,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"systemVersion": "1.0.0",
"createdTime": 1754814950116,
"modifiedTime": 1754814950116,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items.effects!WtQAGz0TUgz8Xg70.fRPKHzbKRz4yTHAF"
"_key": "!items.effects!WtQAGz0TUgz8Xg70.wu2AmDvgeWI3hmRQ"
}
],
"sort": 0,
@ -152,12 +159,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"systemVersion": "1.0.0",
"createdTime": 1753831599435,
"modifiedTime": 1753831629573,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754814950120,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items!WtQAGz0TUgz8Xg70"
}

View file

@ -2,7 +2,7 @@
"folder": "TyqMEXhSkjOUq5SA",
"name": "Advanced Heavy-Frame Wheelchair",
"type": "weapon",
"img": "icons/svg/item-bag.svg",
"img": "systems/daggerheart/assets/icons/documents/items/HeavyWheelchair.webp",
"system": {
"description": "",
"actions": {},
@ -148,12 +148,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753836675558,
"modifiedTime": 1753836795905,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754845996869,
"lastModifiedBy": "H02dtt2xvVJvYESk"
},
"_id": "eT2Qwb0RdrLX2hH1",
"sort": 0,

View file

@ -2,7 +2,7 @@
"folder": "TyqMEXhSkjOUq5SA",
"name": "Advanced Light-Frame Wheelchair",
"type": "weapon",
"img": "icons/svg/item-bag.svg",
"img": "systems/daggerheart/assets/icons/documents/items/LightWheelchair.webp",
"system": {
"description": "",
"actions": {
@ -141,12 +141,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753836614032,
"modifiedTime": 1753836802197,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754846020904,
"lastModifiedBy": "H02dtt2xvVJvYESk"
},
"_id": "BuMfupnCzHbziQ8o",
"sort": 0,

View file

@ -43,7 +43,7 @@
"parts": [
{
"value": {
"dice": "d8",
"dice": "d10",
"bonus": 9,
"multiplier": "prof",
"flatMultiplier": 1,

View file

@ -16,7 +16,7 @@
{
"value": "paired",
"effectIds": [
"gJ7Ey9CfPZqYgxEO"
"MYgB3v3oQ5lIr3VE"
],
"actionIds": []
}
@ -114,9 +114,16 @@
"value": "ITEM.@system.tier + 1"
}
],
"_id": "gJ7Ey9CfPZqYgxEO",
"system": {
"rangeDependence": {
"enabled": true,
"range": "melee",
"target": "hostile",
"type": "withinRange"
}
},
"_id": "MYgB3v3oQ5lIr3VE",
"type": "base",
"system": {},
"disabled": false,
"duration": {
"startTime": null,
@ -132,14 +139,14 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753794991410,
"modifiedTime": 1753794991410,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"systemVersion": "1.0.0",
"createdTime": 1754814673988,
"modifiedTime": 1754814673988,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items.effects!0thN0BpN05KT8Avx.gJ7Ey9CfPZqYgxEO"
"_key": "!items.effects!0thN0BpN05KT8Avx.MYgB3v3oQ5lIr3VE"
}
],
"sort": 0,
@ -152,12 +159,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"systemVersion": "1.0.0",
"createdTime": 1753794938643,
"modifiedTime": 1753794991413,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754814673991,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items!0thN0BpN05KT8Avx"
}

View file

@ -12,15 +12,7 @@
"equipped": false,
"secondary": false,
"burden": "twoHanded",
"weaponFeatures": [
{
"value": "cumbersome",
"effectIds": [
"hl0S2LrBY5Mg69q6"
],
"actionIds": []
}
],
"weaponFeatures": [],
"attack": {
"name": "Attack",
"img": "icons/skills/melee/blood-slash-foam-red.webp",
@ -51,8 +43,8 @@
"parts": [
{
"value": {
"dice": "d10",
"bonus": 8,
"dice": "d8",
"bonus": 9,
"multiplier": "prof",
"flatMultiplier": 1,
"custom": {

View file

@ -3,7 +3,7 @@
"name": "Arcane-Frame Wheelchair",
"type": "weapon",
"_id": "XRChepscgr75Uug7",
"img": "icons/svg/item-bag.svg",
"img": "systems/daggerheart/assets/icons/documents/items/ArcaneWheelchair.webp",
"system": {
"description": "",
"actions": {},
@ -16,7 +16,7 @@
{
"value": "reliable",
"effectIds": [
"G561ymlNGmaFAYFB"
"dXHsy9qr5FWZqsVq"
],
"actionIds": []
}
@ -109,14 +109,21 @@
"img": "icons/skills/melee/strike-sword-slashing-red.webp",
"changes": [
{
"key": "system.bonuses.roll.primaryWeapon.attack",
"key": "system.bonuses.roll.primaryWeapon.bonus",
"mode": 2,
"value": "1"
}
],
"_id": "G561ymlNGmaFAYFB",
"_id": "dXHsy9qr5FWZqsVq",
"type": "base",
"system": {},
"system": {
"rangeDependence": {
"enabled": false,
"type": "withinRange",
"target": "hostile",
"range": "melee"
}
},
"disabled": false,
"duration": {
"startTime": null,
@ -132,14 +139,14 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753836707582,
"modifiedTime": 1753836707582,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"systemVersion": "1.0.0",
"createdTime": 1754815278217,
"modifiedTime": 1754815278217,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items.effects!XRChepscgr75Uug7.G561ymlNGmaFAYFB"
"_key": "!items.effects!XRChepscgr75Uug7.dXHsy9qr5FWZqsVq"
}
],
"sort": 0,
@ -152,12 +159,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"systemVersion": "1.0.0",
"createdTime": 1753836689082,
"modifiedTime": 1753836707594,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754845945327,
"lastModifiedBy": "H02dtt2xvVJvYESk"
},
"_key": "!items!XRChepscgr75Uug7"
}

View file

@ -16,7 +16,7 @@
{
"value": "reliable",
"effectIds": [
"GNwIa1EAaa0T0RZi"
"mqcpj2cFAprf2AmY"
],
"actionIds": []
}
@ -109,14 +109,21 @@
"img": "icons/skills/melee/strike-sword-slashing-red.webp",
"changes": [
{
"key": "system.bonuses.roll.primaryWeapon.attack",
"key": "system.bonuses.roll.primaryWeapon.bonus",
"mode": 2,
"value": "1"
}
],
"_id": "GNwIa1EAaa0T0RZi",
"_id": "mqcpj2cFAprf2AmY",
"type": "base",
"system": {},
"system": {
"rangeDependence": {
"enabled": false,
"type": "withinRange",
"target": "hostile",
"range": "melee"
}
},
"disabled": false,
"duration": {
"startTime": null,
@ -132,14 +139,14 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753827762112,
"modifiedTime": 1753827762112,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"systemVersion": "1.0.0",
"createdTime": 1754814769818,
"modifiedTime": 1754814769818,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items.effects!1cwWNt4sqlgA8gCT.GNwIa1EAaa0T0RZi"
"_key": "!items.effects!1cwWNt4sqlgA8gCT.mqcpj2cFAprf2AmY"
}
],
"sort": 0,
@ -152,12 +159,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"systemVersion": "1.0.0",
"createdTime": 1753827734892,
"modifiedTime": 1753827762118,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754814769821,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items!1cwWNt4sqlgA8gCT"
}

View file

@ -3,7 +3,7 @@
"name": "Heavy-Frame Wheelchair",
"type": "weapon",
"_id": "XjPQjhRCH08VUIbr",
"img": "icons/svg/item-bag.svg",
"img": "systems/daggerheart/assets/icons/documents/items/HeavyWheelchair.webp",
"system": {
"description": "",
"actions": {},
@ -152,12 +152,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753836652314,
"modifiedTime": 1753836667128,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754845988869,
"lastModifiedBy": "H02dtt2xvVJvYESk"
},
"_key": "!items!XjPQjhRCH08VUIbr"
}

View file

@ -2,7 +2,7 @@
"folder": "fFuMdvpD1F3UshmM",
"name": "Improved Arcane-Frame Wheelchair",
"type": "weapon",
"img": "icons/svg/item-bag.svg",
"img": "systems/daggerheart/assets/icons/documents/items/ArcaneWheelchair.webp",
"system": {
"description": "",
"actions": {},
@ -15,7 +15,7 @@
{
"value": "reliable",
"effectIds": [
"G561ymlNGmaFAYFB"
"1f6fFhOLwZrmA6e5"
],
"actionIds": []
}
@ -108,14 +108,21 @@
"img": "icons/skills/melee/strike-sword-slashing-red.webp",
"changes": [
{
"key": "system.bonuses.roll.primaryWeapon.attack",
"key": "system.bonuses.roll.primaryWeapon.bonus",
"mode": 2,
"value": "1"
}
],
"_id": "G561ymlNGmaFAYFB",
"_id": "1f6fFhOLwZrmA6e5",
"type": "base",
"system": {},
"system": {
"rangeDependence": {
"enabled": false,
"type": "withinRange",
"target": "hostile",
"range": "melee"
}
},
"disabled": false,
"duration": {
"startTime": null,
@ -131,12 +138,14 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"lastModifiedBy": null
"systemVersion": "1.0.0",
"createdTime": 1754815290648,
"modifiedTime": 1754815290648,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items.effects!N9P695V5KKlJbAY5.G561ymlNGmaFAYFB"
"_key": "!items.effects!N9P695V5KKlJbAY5.1f6fFhOLwZrmA6e5"
}
],
"ownership": {
@ -148,12 +157,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"systemVersion": "1.0.0",
"createdTime": 1753836714712,
"modifiedTime": 1753836748404,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754845960700,
"lastModifiedBy": "H02dtt2xvVJvYESk"
},
"_id": "N9P695V5KKlJbAY5",
"sort": 0,

View file

@ -16,7 +16,7 @@
{
"value": "reliable",
"effectIds": [
"xU0DD5ydbwmXMKtF"
"228lcQpohdJ3Bbga"
],
"actionIds": []
}
@ -109,14 +109,21 @@
"img": "icons/skills/melee/strike-sword-slashing-red.webp",
"changes": [
{
"key": "system.bonuses.roll.primaryWeapon.attack",
"key": "system.bonuses.roll.primaryWeapon.bonus",
"mode": 2,
"value": "1"
}
],
"_id": "xU0DD5ydbwmXMKtF",
"_id": "228lcQpohdJ3Bbga",
"type": "base",
"system": {},
"system": {
"rangeDependence": {
"enabled": false,
"type": "withinRange",
"target": "hostile",
"range": "melee"
}
},
"disabled": false,
"duration": {
"startTime": null,
@ -132,14 +139,14 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753829126416,
"modifiedTime": 1753829126416,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"systemVersion": "1.0.0",
"createdTime": 1754814935810,
"modifiedTime": 1754814935810,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items.effects!OcKeLJxvmdT81VBc.xU0DD5ydbwmXMKtF"
"_key": "!items.effects!OcKeLJxvmdT81VBc.228lcQpohdJ3Bbga"
}
],
"sort": 0,
@ -152,12 +159,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"systemVersion": "1.0.0",
"createdTime": 1753829098118,
"modifiedTime": 1753829367508,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754814935815,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items!OcKeLJxvmdT81VBc"
}

View file

@ -2,7 +2,7 @@
"folder": "fFuMdvpD1F3UshmM",
"name": "Improved Heavy-Frame Wheelchair",
"type": "weapon",
"img": "icons/svg/item-bag.svg",
"img": "systems/daggerheart/assets/icons/documents/items/HeavyWheelchair.webp",
"system": {
"description": "",
"actions": {},
@ -148,12 +148,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753836674233,
"modifiedTime": 1753836769685,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754845992757,
"lastModifiedBy": "H02dtt2xvVJvYESk"
},
"_id": "L5KeCtrs768PmYWW",
"sort": 0,

View file

@ -2,7 +2,7 @@
"folder": "fFuMdvpD1F3UshmM",
"name": "Improved Light-Frame Wheelchair",
"type": "weapon",
"img": "icons/svg/item-bag.svg",
"img": "systems/daggerheart/assets/icons/documents/items/LightWheelchair.webp",
"system": {
"description": "",
"actions": {
@ -141,12 +141,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753836612291,
"modifiedTime": 1753836778961,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754846018260,
"lastModifiedBy": "H02dtt2xvVJvYESk"
},
"_id": "ZJsetdHKV77ygtCE",
"sort": 0,

View file

@ -43,7 +43,7 @@
"parts": [
{
"value": {
"dice": "d8",
"dice": "d10",
"bonus": 6,
"multiplier": "prof",
"flatMultiplier": 1,

View file

@ -16,7 +16,7 @@
{
"value": "paired",
"effectIds": [
"cSmiIOXeuw0xpjel"
"9iHHwd9BxkBsV9lY"
],
"actionIds": []
}
@ -114,9 +114,16 @@
"value": "ITEM.@system.tier + 1"
}
],
"_id": "cSmiIOXeuw0xpjel",
"system": {
"rangeDependence": {
"enabled": true,
"range": "melee",
"target": "hostile",
"type": "withinRange"
}
},
"_id": "9iHHwd9BxkBsV9lY",
"type": "base",
"system": {},
"disabled": false,
"duration": {
"startTime": null,
@ -132,14 +139,14 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753794021004,
"modifiedTime": 1753794021004,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"systemVersion": "1.0.0",
"createdTime": 1754814695260,
"modifiedTime": 1754814695260,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items.effects!rSyBNRwemBVuTo3H.cSmiIOXeuw0xpjel"
"_key": "!items.effects!rSyBNRwemBVuTo3H.9iHHwd9BxkBsV9lY"
}
],
"sort": 0,
@ -152,12 +159,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"systemVersion": "1.0.0",
"createdTime": 1753744566951,
"modifiedTime": 1753794021010,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754814695265,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items!rSyBNRwemBVuTo3H"
}

View file

@ -16,7 +16,7 @@
{
"value": "paired",
"effectIds": [
"rOjtLkrnI9EZHJm8"
"JHIUGyZH5q83ODvd"
],
"actionIds": []
}
@ -114,9 +114,16 @@
"value": "ITEM.@system.tier + 1"
}
],
"_id": "rOjtLkrnI9EZHJm8",
"system": {
"rangeDependence": {
"enabled": true,
"range": "melee",
"target": "hostile",
"type": "withinRange"
}
},
"_id": "JHIUGyZH5q83ODvd",
"type": "base",
"system": {},
"disabled": false,
"duration": {
"startTime": null,
@ -132,14 +139,14 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753794340876,
"modifiedTime": 1753794340876,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"systemVersion": "1.0.0",
"createdTime": 1754814703717,
"modifiedTime": 1754814703717,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items.effects!nMuF8ZDZ2aXZVTg6.rOjtLkrnI9EZHJm8"
"_key": "!items.effects!nMuF8ZDZ2aXZVTg6.JHIUGyZH5q83ODvd"
}
],
"sort": 0,
@ -152,12 +159,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"systemVersion": "1.0.0",
"createdTime": 1753794291887,
"modifiedTime": 1753794340879,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754814703719,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items!nMuF8ZDZ2aXZVTg6"
}

View file

@ -12,15 +12,7 @@
"equipped": false,
"secondary": false,
"burden": "twoHanded",
"weaponFeatures": [
{
"value": "cumbersome",
"effectIds": [
"8twXPJELZpvFWA5K"
],
"actionIds": []
}
],
"weaponFeatures": [],
"attack": {
"name": "Attack",
"img": "icons/skills/melee/blood-slash-foam-red.webp",
@ -51,8 +43,8 @@
"parts": [
{
"value": {
"dice": "d10",
"bonus": 5,
"dice": "d8",
"bonus": 6,
"multiplier": "prof",
"flatMultiplier": 1,
"custom": {

View file

@ -16,7 +16,7 @@
{
"value": "reliable",
"effectIds": [
"FlmOrbhYbieIAVJL"
"LvxPAfrKuRfgubGV"
],
"actionIds": []
}
@ -109,14 +109,21 @@
"img": "icons/skills/melee/strike-sword-slashing-red.webp",
"changes": [
{
"key": "system.bonuses.roll.primaryWeapon.attack",
"key": "system.bonuses.roll.primaryWeapon.bonus",
"mode": 2,
"value": "1"
}
],
"_id": "FlmOrbhYbieIAVJL",
"_id": "LvxPAfrKuRfgubGV",
"type": "base",
"system": {},
"system": {
"rangeDependence": {
"enabled": false,
"type": "withinRange",
"target": "hostile",
"range": "melee"
}
},
"disabled": false,
"duration": {
"startTime": null,
@ -132,14 +139,14 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753831441325,
"modifiedTime": 1753831441325,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"systemVersion": "1.0.0",
"createdTime": 1754815023490,
"modifiedTime": 1754815023490,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items.effects!q382JqMkqLaaFLIr.FlmOrbhYbieIAVJL"
"_key": "!items.effects!q382JqMkqLaaFLIr.LvxPAfrKuRfgubGV"
}
],
"sort": 0,
@ -152,12 +159,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"systemVersion": "1.0.0",
"createdTime": 1753831418620,
"modifiedTime": 1753831441332,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754815023493,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items!q382JqMkqLaaFLIr"
}

View file

@ -7,63 +7,22 @@
"system": {
"description": "",
"actions": {
"fRKcEeShWSLkoExh": {
"B0uT1D1WRidiHxyh": {
"type": "effect",
"actionType": "action",
"chatDisplay": true,
"name": "Double Up",
"description": "When you make an attack with your primary weapon, you can deal damage to another target within Melee range.",
"img": "icons/skills/melee/strike-slashes-orange.webp",
"_id": "fRKcEeShWSLkoExh",
"_id": "B0uT1D1WRidiHxyh",
"effects": [],
"systemPath": "actions",
"cost": [],
"uses": {
"value": null,
"max": null,
"recovery": null
},
"target": {
"type": "any",
"amount": null
}
},
"lVsEmJwjYgpYL9l4": {
"type": "effect",
"actionType": "action",
"chatDisplay": true,
"name": "Double Up",
"description": "When you make an attack with your primary weapon, you can deal damage to another target within Melee range.",
"img": "icons/skills/melee/strike-slashes-orange.webp",
"_id": "lVsEmJwjYgpYL9l4",
"effects": [],
"systemPath": "actions",
"cost": [],
"uses": {
"value": null,
"max": null,
"recovery": null
},
"target": {
"type": "any",
"amount": null
}
},
"2ndqofzTHsEUMxsm": {
"type": "effect",
"actionType": "action",
"chatDisplay": true,
"name": "Double Up",
"description": "When you make an attack with your primary weapon, you can deal damage to another target within Melee range.",
"img": "icons/skills/melee/strike-slashes-orange.webp",
"_id": "2ndqofzTHsEUMxsm",
"effects": [],
"systemPath": "actions",
"cost": [],
"uses": {
"value": null,
"max": null,
"recovery": null
"recovery": null,
"consumeOnSuccess": false
},
"target": {
"type": "any",
@ -81,7 +40,7 @@
"value": "doubledUp",
"effectIds": [],
"actionIds": [
"2ndqofzTHsEUMxsm"
"B0uT1D1WRidiHxyh"
]
}
],
@ -177,12 +136,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"systemVersion": "1.0.0",
"createdTime": 1753797258168,
"modifiedTime": 1753798882899,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754814600761,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items!SFqganS8Du4aEKjQ"
}

View file

@ -2,7 +2,7 @@
"folder": "beilKE5ZPAihKg3O",
"name": "Legendary Arcane-Frame Wheelchair",
"type": "weapon",
"img": "icons/svg/item-bag.svg",
"img": "systems/daggerheart/assets/icons/documents/items/ArcaneWheelchair.webp",
"system": {
"description": "",
"actions": {},
@ -15,7 +15,7 @@
{
"value": "reliable",
"effectIds": [
"G561ymlNGmaFAYFB"
"TvsoAiqHCwgtYat1"
],
"actionIds": []
}
@ -108,14 +108,21 @@
"img": "icons/skills/melee/strike-sword-slashing-red.webp",
"changes": [
{
"key": "system.bonuses.roll.primaryWeapon.attack",
"key": "system.bonuses.roll.primaryWeapon.bonus",
"mode": 2,
"value": "1"
}
],
"_id": "G561ymlNGmaFAYFB",
"_id": "TvsoAiqHCwgtYat1",
"type": "base",
"system": {},
"system": {
"rangeDependence": {
"enabled": false,
"type": "withinRange",
"target": "hostile",
"range": "melee"
}
},
"disabled": false,
"duration": {
"startTime": null,
@ -131,12 +138,14 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"lastModifiedBy": null
"systemVersion": "1.0.0",
"createdTime": 1754815308723,
"modifiedTime": 1754815308723,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items.effects!gA2tiET9VHGhwMoO.G561ymlNGmaFAYFB"
"_key": "!items.effects!gA2tiET9VHGhwMoO.TvsoAiqHCwgtYat1"
}
],
"ownership": {
@ -148,12 +157,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"systemVersion": "1.0.0",
"createdTime": 1753836717240,
"modifiedTime": 1753836812453,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754845972571,
"lastModifiedBy": "H02dtt2xvVJvYESk"
},
"_id": "gA2tiET9VHGhwMoO",
"sort": 0,

View file

@ -16,7 +16,7 @@
{
"value": "reliable",
"effectIds": [
"lmUzKw6J6RW3krRT"
"ujb6VAqjyXmfnnjy"
],
"actionIds": []
}
@ -109,14 +109,21 @@
"img": "icons/skills/melee/strike-sword-slashing-red.webp",
"changes": [
{
"key": "system.bonuses.roll.primaryWeapon.attack",
"key": "system.bonuses.roll.primaryWeapon.bonus",
"mode": 2,
"value": "1"
}
],
"_id": "lmUzKw6J6RW3krRT",
"_id": "ujb6VAqjyXmfnnjy",
"type": "base",
"system": {},
"system": {
"rangeDependence": {
"enabled": false,
"type": "withinRange",
"target": "hostile",
"range": "melee"
}
},
"disabled": false,
"duration": {
"startTime": null,
@ -132,14 +139,14 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753834461846,
"modifiedTime": 1753834461846,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"systemVersion": "1.0.0",
"createdTime": 1754814961439,
"modifiedTime": 1754814961439,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items.effects!y3hfTPfZhMognyaJ.lmUzKw6J6RW3krRT"
"_key": "!items.effects!y3hfTPfZhMognyaJ.ujb6VAqjyXmfnnjy"
}
],
"sort": 0,
@ -152,12 +159,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"systemVersion": "1.0.0",
"createdTime": 1753834430378,
"modifiedTime": 1753834465538,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754814961454,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items!y3hfTPfZhMognyaJ"
}

View file

@ -2,7 +2,7 @@
"folder": "beilKE5ZPAihKg3O",
"name": "Legendary Heavy-Frame Wheelchair",
"type": "weapon",
"img": "icons/svg/item-bag.svg",
"img": "systems/daggerheart/assets/icons/documents/items/HeavyWheelchair.webp",
"system": {
"description": "",
"actions": {},
@ -148,12 +148,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753836676831,
"modifiedTime": 1753836820180,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754846000470,
"lastModifiedBy": "H02dtt2xvVJvYESk"
},
"_id": "S6nB0CNlzdU05o5U",
"sort": 0,

View file

@ -2,7 +2,7 @@
"folder": "beilKE5ZPAihKg3O",
"name": "Legendary Light-Frame Wheelchair",
"type": "weapon",
"img": "icons/svg/item-bag.svg",
"img": "systems/daggerheart/assets/icons/documents/items/LightWheelchair.webp",
"system": {
"description": "",
"actions": {
@ -141,12 +141,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753836615437,
"modifiedTime": 1753836826572,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754846023338,
"lastModifiedBy": "H02dtt2xvVJvYESk"
},
"_id": "Xt8tVSn5Fu6ly6LF",
"sort": 0,

View file

@ -43,7 +43,7 @@
"parts": [
{
"value": {
"dice": "d8",
"dice": "d10",
"bonus": 12,
"multiplier": "prof",
"flatMultiplier": 1,

View file

@ -16,7 +16,7 @@
{
"value": "paired",
"effectIds": [
"DgZQSBJx9JmoOngB"
"VFt61c2Apfbli2dG"
],
"actionIds": []
}
@ -114,9 +114,16 @@
"value": "ITEM.@system.tier + 1"
}
],
"_id": "DgZQSBJx9JmoOngB",
"system": {
"rangeDependence": {
"enabled": true,
"range": "melee",
"target": "hostile",
"type": "withinRange"
}
},
"_id": "VFt61c2Apfbli2dG",
"type": "base",
"system": {},
"disabled": false,
"duration": {
"startTime": null,
@ -132,14 +139,14 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753796957432,
"modifiedTime": 1753796957432,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"systemVersion": "1.0.0",
"createdTime": 1754814544486,
"modifiedTime": 1754814544486,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items.effects!dEumq3BIZBk5xYTk.DgZQSBJx9JmoOngB"
"_key": "!items.effects!dEumq3BIZBk5xYTk.VFt61c2Apfbli2dG"
}
],
"sort": 0,
@ -152,12 +159,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"systemVersion": "1.0.0",
"createdTime": 1753796913551,
"modifiedTime": 1753796957439,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754814544510,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items!dEumq3BIZBk5xYTk"
}

View file

@ -16,7 +16,7 @@
{
"value": "paired",
"effectIds": [
"Wjl3MEwNdQPeY63u"
"rnVm0jSEtdWhKGCh"
],
"actionIds": []
}
@ -114,9 +114,16 @@
"value": "ITEM.@system.tier + 1"
}
],
"_id": "Wjl3MEwNdQPeY63u",
"system": {
"rangeDependence": {
"enabled": true,
"range": "melee",
"target": "hostile",
"type": "withinRange"
}
},
"_id": "rnVm0jSEtdWhKGCh",
"type": "base",
"system": {},
"disabled": false,
"duration": {
"startTime": null,
@ -132,14 +139,14 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753797088930,
"modifiedTime": 1753797088930,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"systemVersion": "1.0.0",
"createdTime": 1754814562988,
"modifiedTime": 1754814562988,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items.effects!Px3Rh3kIvAqyISxJ.Wjl3MEwNdQPeY63u"
"_key": "!items.effects!Px3Rh3kIvAqyISxJ.rnVm0jSEtdWhKGCh"
}
],
"sort": 0,
@ -152,12 +159,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"systemVersion": "1.0.0",
"createdTime": 1753797057472,
"modifiedTime": 1753797088933,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754814562995,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items!Px3Rh3kIvAqyISxJ"
}

View file

@ -12,15 +12,7 @@
"equipped": false,
"secondary": false,
"burden": "twoHanded",
"weaponFeatures": [
{
"value": "cumbersome",
"effectIds": [
"f44KWDgCQeKYfccr"
],
"actionIds": []
}
],
"weaponFeatures": [],
"attack": {
"name": "Attack",
"img": "icons/skills/melee/blood-slash-foam-red.webp",
@ -51,8 +43,8 @@
"parts": [
{
"value": {
"dice": "d10",
"bonus": 11,
"dice": "d8",
"bonus": 12,
"multiplier": "prof",
"flatMultiplier": 1,
"custom": {

View file

@ -3,7 +3,7 @@
"name": "Light-Frame Wheelchair",
"type": "weapon",
"_id": "iaGnlUkShBgdeMo0",
"img": "icons/svg/item-bag.svg",
"img": "systems/daggerheart/assets/icons/documents/items/LightWheelchair.webp",
"system": {
"description": "",
"actions": {
@ -143,12 +143,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753836579296,
"modifiedTime": 1753836587147,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754846015528,
"lastModifiedBy": "H02dtt2xvVJvYESk"
},
"_key": "!items!iaGnlUkShBgdeMo0"
}

View file

@ -43,7 +43,7 @@
"parts": [
{
"value": {
"dice": "d8",
"dice": "d10",
"bonus": 3,
"multiplier": "prof",
"flatMultiplier": 1,

View file

@ -7,21 +7,22 @@
"system": {
"description": "",
"actions": {
"KGJJgz0SMdY0f0em": {
"dNN2KOfUxGzQ2yjY": {
"type": "effect",
"actionType": "action",
"chatDisplay": true,
"name": "Lock On",
"description": "On a successful attack, your next attack against the same target with your primary weapon automatically succeeds.",
"img": "icons/skills/targeting/crosshair-arrowhead-blue.webp",
"_id": "KGJJgz0SMdY0f0em",
"_id": "dNN2KOfUxGzQ2yjY",
"effects": [],
"systemPath": "actions",
"cost": [],
"uses": {
"value": null,
"max": null,
"recovery": null
"recovery": null,
"consumeOnSuccess": false
},
"target": {
"type": "any",
@ -36,10 +37,10 @@
"burden": "oneHanded",
"weaponFeatures": [
{
"value": "lockedon",
"value": "lockedOn",
"effectIds": [],
"actionIds": [
"KGJJgz0SMdY0f0em"
"dNN2KOfUxGzQ2yjY"
]
}
],
@ -135,12 +136,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"systemVersion": "1.0.0",
"createdTime": 1753797317938,
"modifiedTime": 1753797376548,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754814518028,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items!SxcblanBvqaest3A"
}

View file

@ -16,7 +16,7 @@
{
"value": "paired",
"effectIds": [
"VII5oRJrQTsSir0E"
"5RpOUFs0kDhzwltM"
],
"actionIds": []
}
@ -114,9 +114,16 @@
"value": "ITEM.@system.tier + 1"
}
],
"_id": "VII5oRJrQTsSir0E",
"system": {
"rangeDependence": {
"enabled": true,
"range": "melee",
"target": "hostile",
"type": "withinRange"
}
},
"_id": "5RpOUFs0kDhzwltM",
"type": "base",
"system": {},
"disabled": false,
"duration": {
"startTime": null,
@ -132,14 +139,14 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753793980974,
"modifiedTime": 1753793980974,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"systemVersion": "1.0.0",
"createdTime": 1754814729443,
"modifiedTime": 1754814729443,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items.effects!cjGZpXCoshEqi1FI.VII5oRJrQTsSir0E"
"_key": "!items.effects!cjGZpXCoshEqi1FI.5RpOUFs0kDhzwltM"
}
],
"sort": 0,
@ -152,12 +159,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"systemVersion": "1.0.0",
"createdTime": 1753741549716,
"modifiedTime": 1753793980983,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754814729447,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items!cjGZpXCoshEqi1FI"
}

View file

@ -16,7 +16,7 @@
{
"value": "paired",
"effectIds": [
"2OLsxbZJqMgk680J"
"wK6ccFAirp9HYK5Q"
],
"actionIds": []
}
@ -114,9 +114,16 @@
"value": "ITEM.@system.tier + 1"
}
],
"_id": "2OLsxbZJqMgk680J",
"system": {
"rangeDependence": {
"enabled": true,
"range": "melee",
"target": "hostile",
"type": "withinRange"
}
},
"_id": "wK6ccFAirp9HYK5Q",
"type": "base",
"system": {},
"disabled": false,
"duration": {
"startTime": null,
@ -132,14 +139,14 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753794165509,
"modifiedTime": 1753794165509,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"systemVersion": "1.0.0",
"createdTime": 1754814738851,
"modifiedTime": 1754814738851,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items.effects!wKklDxs5nkzILNp4.2OLsxbZJqMgk680J"
"_key": "!items.effects!wKklDxs5nkzILNp4.wK6ccFAirp9HYK5Q"
}
],
"sort": 0,
@ -152,12 +159,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"systemVersion": "1.0.0",
"createdTime": 1753744141625,
"modifiedTime": 1753794165511,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754814738854,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items!wKklDxs5nkzILNp4"
}

View file

@ -12,15 +12,7 @@
"equipped": false,
"secondary": false,
"burden": "twoHanded",
"weaponFeatures": [
{
"value": "cumbersome",
"effectIds": [
"Z5MnVI8EOOgzRdXC"
],
"actionIds": []
}
],
"weaponFeatures": [],
"attack": {
"name": "Attack",
"img": "icons/skills/melee/blood-slash-foam-red.webp",
@ -51,8 +43,8 @@
"parts": [
{
"value": {
"dice": "d10",
"bonus": 2,
"dice": "d8",
"bonus": 3,
"multiplier": "prof",
"flatMultiplier": 1,
"custom": {

View file

@ -16,7 +16,7 @@
{
"value": "doubleDuty",
"effectIds": [
"qo4VPSV0VZha1ya2"
"d3TJtlpoHBCztbom"
],
"actionIds": []
}
@ -119,9 +119,16 @@
"value": "1"
}
],
"_id": "qo4VPSV0VZha1ya2",
"system": {
"rangeDependence": {
"enabled": true,
"range": "melee",
"target": "hostile",
"type": "withinRange"
}
},
"_id": "d3TJtlpoHBCztbom",
"type": "base",
"system": {},
"disabled": false,
"duration": {
"startTime": null,
@ -137,14 +144,14 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753794551639,
"modifiedTime": 1753794551639,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"systemVersion": "1.0.0",
"createdTime": 1754814481208,
"modifiedTime": 1754814481208,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items.effects!vzyzFwLUniWZV1rt.qo4VPSV0VZha1ya2"
"_key": "!items.effects!vzyzFwLUniWZV1rt.d3TJtlpoHBCztbom"
}
],
"sort": 0,
@ -157,12 +164,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"systemVersion": "1.0.0",
"createdTime": 1753794535926,
"modifiedTime": 1753794665373,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754814481212,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items!vzyzFwLUniWZV1rt"
}

View file

@ -16,7 +16,7 @@
{
"value": "reliable",
"effectIds": [
"YA5tbB6XBISWsf0p"
"G9mMGxBSexwjWTYV"
],
"actionIds": []
}
@ -109,14 +109,21 @@
"img": "icons/skills/melee/strike-sword-slashing-red.webp",
"changes": [
{
"key": "system.bonuses.roll.primaryWeapon.attack",
"key": "system.bonuses.roll.primaryWeapon.bonus",
"mode": 2,
"value": "1"
}
],
"_id": "YA5tbB6XBISWsf0p",
"_id": "G9mMGxBSexwjWTYV",
"type": "base",
"system": {},
"system": {
"rangeDependence": {
"enabled": false,
"type": "withinRange",
"target": "hostile",
"range": "melee"
}
},
"disabled": false,
"duration": {
"startTime": null,
@ -132,14 +139,14 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753836322206,
"modifiedTime": 1753836322206,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"systemVersion": "1.0.0",
"createdTime": 1754815251133,
"modifiedTime": 1754815251133,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items.effects!I1nDGpulg29GpWOW.YA5tbB6XBISWsf0p"
"_key": "!items.effects!I1nDGpulg29GpWOW.G9mMGxBSexwjWTYV"
}
],
"sort": 0,
@ -152,12 +159,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"systemVersion": "1.0.0",
"createdTime": 1753836302436,
"modifiedTime": 1753836334493,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754815251135,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items!I1nDGpulg29GpWOW"
}

View file

@ -16,7 +16,7 @@
{
"value": "reliable",
"effectIds": [
"Gt0tHtJDQwdSActw"
"cOYeI9TxHXpDwszu"
],
"actionIds": []
}
@ -109,14 +109,21 @@
"img": "icons/skills/melee/strike-sword-slashing-red.webp",
"changes": [
{
"key": "system.bonuses.roll.primaryWeapon.attack",
"key": "system.bonuses.roll.primaryWeapon.bonus",
"mode": 2,
"value": "1"
}
],
"_id": "Gt0tHtJDQwdSActw",
"_id": "cOYeI9TxHXpDwszu",
"type": "base",
"system": {},
"system": {
"rangeDependence": {
"enabled": false,
"type": "withinRange",
"target": "hostile",
"range": "melee"
}
},
"disabled": false,
"duration": {
"startTime": null,
@ -132,14 +139,14 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1753829771677,
"modifiedTime": 1753829771677,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"systemVersion": "1.0.0",
"createdTime": 1754814992368,
"modifiedTime": 1754814992368,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items.effects!z6yEdFYQJ5IzgTX3.Gt0tHtJDQwdSActw"
"_key": "!items.effects!z6yEdFYQJ5IzgTX3.cOYeI9TxHXpDwszu"
}
],
"sort": 0,
@ -152,12 +159,12 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"systemVersion": "1.0.0",
"createdTime": 1753829740082,
"modifiedTime": 1753829771680,
"lastModifiedBy": "FecEtPuoQh6MpjQ0"
"modifiedTime": 1754814992370,
"lastModifiedBy": "MQSznptE5yLT7kj8"
},
"_key": "!items!z6yEdFYQJ5IzgTX3"
}

View file

@ -132,7 +132,7 @@
"image": {},
"text": {
"format": 1,
"content": "<blockquote><p>This product includes materials from the Daggerheart System Reference Document 1.0, © Critical Role, LLC. under the terms of the Darrington Press Community Gaming (DPCGL) License. More information can be found at <a href=\"https://www.daggerheart.com/\" title=\"Daggerheart Official Website\">https://www.daggerheart.com</a>. There are no previous modifications by others.</p><p></p></blockquote><h1>The Foundryborne Team</h1><p></p><p>The Foundryborne Team consists of:</p><p></p><ul><li><p><strong>@harryfuralle</strong></p></li><li><p><strong>@cptn_cosmo</strong></p></li><li><p><strong>@molilo</strong></p></li><li><p><strong>@joaquinp98</strong></p></li><li><p><strong>@dapoulp</strong></p></li><li><p>@ikraik</p></li><li><p>@irktheimp</p></li><li><p>@jacobwojo</p></li><li><p>@vyrth</p></li><li><p>@.ontaro</p></li><li><p>@saatsin</p></li><li><p>@david.xyz</p></li></ul><p></p><p>With Art from:</p><p></p><ul><li><p>UsernameIsInUse</p></li></ul><p></p><p>We would also like to thank the FoundryVTT team for their support in publishing this system.</p><p>And, of course, special thanks to the teams at Critical Role and Darrington Press for making such a wonderful game and updating the license to allow a FoundryVTT version of the system.</p><p></p><h1>The Foundryborne Community</h1><p></p><p>Without our amazing community this project would not have been possible.</p><p>You kept us going with both direct contributions and just endless support!</p><p>We thank you with all our hearts.</p><p><a href=\"https://foundryborne.online/\" title=\"Foundryborne official website\">Come join us!</a></p>"
"content": "<blockquote><p>This product includes materials from the Daggerheart System Reference Document 1.0, © Critical Role, LLC. under the terms of the Darrington Press Community Gaming (DPCGL) License. More information can be found at <a href=\"https://www.daggerheart.com/\" title=\"Daggerheart Official Website\">https://www.daggerheart.com</a>. There are no previous modifications by others.</p><p></p></blockquote><h1>The Foundryborne Team</h1><p></p><p>The Foundryborne Team consists of:</p><p></p><ul><li><p>@harryfuralle</p></li><li><p>@cptn_cosmo</p></li><li><p>@molilo</p></li><li><p>@joaquinp98</p></li><li><p>@dapoulp</p></li><li><p>@ikraik</p></li><li><p>@irktheimp</p></li><li><p>@jacobwojo</p></li><li><p>@vyrth</p></li><li><p>@.ontaro</p></li><li><p>@saatsin</p></li><li><p>@david.xyz</p></li><li><p>@lazjen</p></li></ul><p></p><p>With Art from:</p><p></p><ul><li><p>@molilo (Foundryborne User Interface &amp; Domain Card Placeholder Artwork)</p></li><li><p>@UsernameIsInUse (Foundryborne Logo, Website Design, Duality Roll Icon</p></li><li><p>@CyrensMaps (Combat Wheelchair Icons)</p></li></ul><p></p><p>And special thanks to our hard working community testers:<br /></p><ul><li><p>@lazjen</p></li></ul><p></p><p>We would also like to thank the FoundryVTT team for their support in publishing this system, as well as providing countless Icons to be used in many places.</p><p>And, of course, special thanks to the teams at Critical Role and Darrington Press for making such a wonderful game and updating the license to allow a FoundryVTT version of the system.</p><p></p><h1>The Foundryborne Community</h1><p></p><p>Without our amazing community this project would not have been possible.</p><p>You kept us going with both direct contributions and just endless support!</p><p>We thank you with all our hearts.</p><p><a href=\"https://foundryborne.online/\" title=\"Foundryborne official website\">Come join us!</a></p>"
},
"video": {
"controls": true,
@ -153,8 +153,8 @@
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1754225939902,
"modifiedTime": 1754226994508,
"lastModifiedBy": "l5jB3XmcVXOTQpRZ"
"modifiedTime": 1754847043391,
"lastModifiedBy": "9GFCfEY8m5Co2mHo"
},
"_key": "!journal.pages!g7NhKvwltwafmMyR.dP6xSKEld4TSqHhK"
}

View file

@ -23,7 +23,7 @@
}
},
"_id": "RSmscgGyuHJucF6C",
"img": "icons/magic/life/heart-cross-blue.webp",
"img": "icons/sundries/documents/document-letter-blue.webp",
"changes": [
{
"key": "system.bonuses.rally",
@ -53,10 +53,11 @@
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"coreVersion": "13.347",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"lastModifiedBy": null
"lastModifiedBy": "49DaecTcBSc5d0DA",
"modifiedTime": 1754669077252
},
"_key": "!items.effects!eCoEWkWuZPMZ9C6a.RSmscgGyuHJucF6C"
}

View file

@ -27,3 +27,5 @@
@import './damage-reduction/sheets.less';
@import './multiclass-choice/sheet.less';
@import './reroll-dialog/sheet.less';

View 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;
}
}
}

View file

@ -32,6 +32,7 @@
.message-header-metadata {
flex: none;
display: flex;
flex-direction: column;
.message-metadata {
font-family: @font-body;
@ -73,6 +74,13 @@
.message-content {
padding-bottom: 8px;
.flavor-text {
font-size: var(--font-size-12);
line-height: 20px;
color: var(--color-dark-4);
text-align: center;
display: block;
}
}
}
}

View file

@ -53,7 +53,7 @@
font-weight: 500;
font-size: 14px;
line-height: 17px;
white-space: nowrap;
color: light-dark(@dark, @beige);
}

View file

@ -571,6 +571,16 @@
white-space: nowrap;
}
}
.button-container {
display: grid;
grid-template-columns: 1fr;
gap: 10px;
text-align: center;
display: flex;
justify-content: center;
width: 100%;
}
}
footer {

View file

@ -14,8 +14,14 @@
margin-bottom: 12px;
}
.feature-list {
.feature-list,
.features-dragger {
display: flex;
width: 100%;
font-family: @font-body;
}
.feature-list {
flex-direction: column;
gap: 10px;
@ -45,5 +51,16 @@
}
}
}
.features-dragger {
align-items: center;
justify-content: center;
box-sizing: border-box;
height: 40px;
margin-top: 10px;
border: 1px dashed light-dark(@dark-blue-50, @beige-50);
border-radius: 3px;
color: light-dark(@dark-blue-50, @beige-50);
}
}
}

Some files were not shown because too many files have changed in this diff Show more