mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-17 07:36:26 +01:00
Merge branch 'development' into compendium-browser-per-type
This commit is contained in:
commit
a391470ad0
116 changed files with 2062 additions and 1065 deletions
|
|
@ -17,6 +17,10 @@ We welcome contributions of all kinds:
|
|||
|
||||
Please be respectful and collaborative — we’re all here to build something great together.
|
||||
|
||||
### Community Translations
|
||||
|
||||
Please note that we are not accepting community translations in the main project. Instead, community translations should be published as a module.
|
||||
|
||||
---
|
||||
|
||||
## 🧭 General Guidelines
|
||||
|
|
|
|||
37
lang/en.json
37
lang/en.json
|
|
@ -96,6 +96,7 @@
|
|||
"attackName": "Attack Name",
|
||||
"includeBase": { "label": "Include Item Damage" },
|
||||
"multiplier": "Multiplier",
|
||||
"saveHint": "Set a default Trait to enable Reaction Roll. It can be changed later in Reaction Roll Dialog.",
|
||||
"resultBased": {
|
||||
"label": "Formula based on Hope/Fear result."
|
||||
},
|
||||
|
|
@ -310,7 +311,8 @@
|
|||
"toLoadout": "Send to Loadout",
|
||||
"toVault": "Send to Vault",
|
||||
"unequip": "Unequip",
|
||||
"useItem": "Use Item"
|
||||
"useItem": "Use Item",
|
||||
"cancelBeastform": "Cancel Beastform"
|
||||
},
|
||||
"Countdown": {
|
||||
"addCountdown": "Add Countdown",
|
||||
|
|
@ -1908,6 +1910,7 @@
|
|||
"tier4": "tier 4",
|
||||
"domains": "Domains",
|
||||
"downtime": "Downtime",
|
||||
"roll": "Roll",
|
||||
"rules": "Rules",
|
||||
"types": "Types"
|
||||
},
|
||||
|
|
@ -1961,6 +1964,7 @@
|
|||
"fear": "Fear",
|
||||
"features": "Features",
|
||||
"formula": "Formula",
|
||||
"gm": "GM",
|
||||
"healing": "Healing",
|
||||
"healingRoll": "Healing Roll",
|
||||
"hit": {
|
||||
|
|
@ -1997,6 +2001,10 @@
|
|||
"none": "None",
|
||||
"noTarget": "No current target",
|
||||
"partner": "Partner",
|
||||
"player": {
|
||||
"single": "Player",
|
||||
"plurial": "Players"
|
||||
},
|
||||
"proficiency": "Proficiency",
|
||||
"quantity": "Quantity",
|
||||
"range": "Range",
|
||||
|
|
@ -2022,6 +2030,7 @@
|
|||
},
|
||||
"title": "Title",
|
||||
"total": "Total",
|
||||
"traitModifier": "Trait Modifier",
|
||||
"true": "True",
|
||||
"type": "Type",
|
||||
"unarmed": "Unarmed",
|
||||
|
|
@ -2204,10 +2213,35 @@
|
|||
"playerCanEditSheet": {
|
||||
"label": "Players Can Manually Edit Character Settings",
|
||||
"hint": "Players are allowed to access the manual Character Settings and change their statistics beyond the rules."
|
||||
},
|
||||
"roll": {
|
||||
"roll": {
|
||||
"label": "Roll",
|
||||
"hint": "Auto behavior for rolls like Attack, Spellcast, etc."
|
||||
},
|
||||
"damage": {
|
||||
"label": "Damage/Healing Roll",
|
||||
"hint": "Auto behavior for Damage & Healing rolls after the Attack/Spellcast."
|
||||
},
|
||||
"save": {
|
||||
"label": "Reaction Roll",
|
||||
"hint": "Auto behavior if a Reaction Roll is needed. Targets must be selected before the action is made"
|
||||
},
|
||||
"damageApply": {
|
||||
"label": "Apply Damage/Healing",
|
||||
"hint": "Automatically apply damages & healings. Targets must be selected before the action is made and Reaction Roll Automation must be different than Never. Bypass users permissions."
|
||||
},
|
||||
"effect": {
|
||||
"label": "Apply Effects",
|
||||
"hint": "Automatically apply effects. Targets must be selected before the action is made and Reaction Roll Automation must be different than Never. Bypass users permissions."
|
||||
}
|
||||
}
|
||||
},
|
||||
"defeated": {
|
||||
"title": "Defeated Handling"
|
||||
},
|
||||
"roll": {
|
||||
"title": "Actions"
|
||||
}
|
||||
},
|
||||
"Homebrew": {
|
||||
|
|
@ -2411,6 +2445,7 @@
|
|||
"beastformInapplicable": "A beastform can only be applied to a Character.",
|
||||
"beastformAlreadyApplied": "The character already has a beastform applied!",
|
||||
"noTargetsSelected": "No targets are selected.",
|
||||
"noTargetsSelectedOrPerm": "No targets are selected or with the update permission.",
|
||||
"attackTargetDoesNotExist": "The target token no longer exists",
|
||||
"insufficentAdvancements": "You don't have enough advancements left.",
|
||||
"noAssignedPlayerCharacter": "You have no assigned character.",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { abilities } from "../../config/actorConfig.mjs";
|
||||
|
||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
export default class D20RollDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
|
|
@ -7,7 +9,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
this.roll = roll;
|
||||
this.config = config;
|
||||
this.config.experiences = [];
|
||||
this.reactionOverride = config.roll?.type === 'reaction';
|
||||
this.reactionOverride = config.actionType === 'reaction';
|
||||
|
||||
if (config.source?.action) {
|
||||
this.item = config.data.parent.items.get(config.source.item) ?? config.data.parent;
|
||||
|
|
@ -20,7 +22,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
|
||||
static DEFAULT_OPTIONS = {
|
||||
tag: 'form',
|
||||
id: 'roll-selection',
|
||||
// id: 'roll-selection',
|
||||
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'roll-selection'],
|
||||
position: {
|
||||
width: 'auto'
|
||||
|
|
@ -42,7 +44,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
};
|
||||
|
||||
get title() {
|
||||
return this.config.title;
|
||||
return `${this.config.title}${this.actor ? `: ${this.actor.name}` : ''}`;
|
||||
}
|
||||
|
||||
get actor() {
|
||||
|
|
@ -113,15 +115,21 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
context.isLite = this.config.roll?.lite;
|
||||
context.extraFormula = this.config.extraFormula;
|
||||
context.formula = this.roll.constructFormula(this.config);
|
||||
if(this.actor.system.traits) context.abilities = this.getTraitModifiers();
|
||||
|
||||
context.showReaction = !context.rollConfig.type && context.rollType === 'DualityRoll';
|
||||
context.showReaction = !this.config.roll?.type && context.rollType === 'DualityRoll';
|
||||
context.reactionOverride = this.reactionOverride;
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
getTraitModifiers() {
|
||||
return Object.values(abilities).map(a => ({ id: a.id, label: `${game.i18n.localize(a.label)} (${this.actor.system.traits[a.id]?.value.signedString() ?? 0})` }))
|
||||
}
|
||||
|
||||
static updateRollConfiguration(event, _, formData) {
|
||||
const { ...rest } = foundry.utils.expandObject(formData.object);
|
||||
|
||||
this.config.selectedRollMode = rest.selectedRollMode;
|
||||
|
||||
if (this.config.costs) {
|
||||
|
|
@ -133,6 +141,12 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
|||
this.roll[key] = value;
|
||||
});
|
||||
}
|
||||
if(rest.hasOwnProperty("trait")) {
|
||||
this.config.roll.trait = rest.trait;
|
||||
this.config.title = game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||
ability: game.i18n.localize(abilities[this.config.roll.trait]?.label)
|
||||
});
|
||||
}
|
||||
this.config.extraFormula = rest.extraFormula;
|
||||
this.render();
|
||||
}
|
||||
|
|
@ -151,31 +165,29 @@ 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.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: this.config?.data?.parent?.isNPC ? 'fear' : 'hope',
|
||||
value: 1,
|
||||
name: this.config.data?.experiences?.[button.dataset.key]?.name
|
||||
}
|
||||
];
|
||||
this.render();
|
||||
}
|
||||
|
||||
static toggleReaction() {
|
||||
if (this.config.roll) {
|
||||
this.reactionOverride = !this.reactionOverride;
|
||||
this.config.roll.type = this.reactionOverride
|
||||
this.config.actionType = this.reactionOverride
|
||||
? CONFIG.DH.ITEM.actionTypes.reaction.id
|
||||
: this.config.roll.type === CONFIG.DH.ITEM.actionTypes.reaction.id
|
||||
: this.config.actionType === CONFIG.DH.ITEM.actionTypes.reaction.id
|
||||
? null
|
||||
: this.config.roll.type;
|
||||
: this.config.actionType;
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,13 +35,14 @@ export default class DhAutomationSettings extends HandlebarsApplicationMixin(App
|
|||
header: { template: 'systems/daggerheart/templates/settings/automation-settings/header.hbs' },
|
||||
general: { template: 'systems/daggerheart/templates/settings/automation-settings/general.hbs' },
|
||||
rules: { template: 'systems/daggerheart/templates/settings/automation-settings/rules.hbs' },
|
||||
roll: { template: 'systems/daggerheart/templates/settings/automation-settings/roll.hbs' },
|
||||
footer: { template: 'systems/daggerheart/templates/settings/automation-settings/footer.hbs' }
|
||||
};
|
||||
|
||||
/** @inheritdoc */
|
||||
static TABS = {
|
||||
main: {
|
||||
tabs: [{ id: 'general' }, { id: 'rules' }],
|
||||
tabs: [{ id: 'general' }, { id: 'rules' }, { id: 'roll' }],
|
||||
initial: 'general',
|
||||
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,9 +146,9 @@ export default class AdversarySheet extends DHBaseActorSheet {
|
|||
title: `Reaction Roll: ${this.actor.name}`,
|
||||
headerTitle: 'Adversary Reaction Roll',
|
||||
roll: {
|
||||
type: 'reaction'
|
||||
type: 'trait'
|
||||
},
|
||||
type: 'trait',
|
||||
actionType: 'reaction',
|
||||
hasRoll: true,
|
||||
data: this.actor.getRollData()
|
||||
};
|
||||
|
|
|
|||
|
|
@ -637,6 +637,15 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
const presets = {
|
||||
compendium: 'daggerheart',
|
||||
folder: key,
|
||||
filter:
|
||||
key === 'subclasses'
|
||||
? {
|
||||
'system.linkedClass.uuid': {
|
||||
key: 'system.linkedClass.uuid',
|
||||
value: this.document.system.class.value._stats.compendiumSource
|
||||
}
|
||||
}
|
||||
: undefined,
|
||||
render: {
|
||||
noFolder: true
|
||||
}
|
||||
|
|
@ -670,31 +679,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
})
|
||||
});
|
||||
|
||||
this.consumeResource(result?.costs);
|
||||
}
|
||||
|
||||
// Remove when Action Refactor part #2 done
|
||||
async consumeResource(costs) {
|
||||
if (!costs?.length) return;
|
||||
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
|
||||
}
|
||||
};
|
||||
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);
|
||||
if(result) game.system.api.fields.ActionFields.CostField.execute.call(this, result);
|
||||
}
|
||||
|
||||
//TODO: redo toggleEquipItem method
|
||||
|
|
|
|||
|
|
@ -412,6 +412,16 @@ export default function DHApplicationMixin(Base) {
|
|||
];
|
||||
|
||||
if (usable) {
|
||||
options.unshift({
|
||||
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.cancelBeastform',
|
||||
icon: 'fa-solid fa-ban',
|
||||
condition: target => {
|
||||
const doc = getDocFromElementSync(target);
|
||||
return doc && doc.system?.actions?.some(a => a.type === "beastform");
|
||||
},
|
||||
callback: async target => game.system.api.fields.ActionFields.BeastformField.handleActiveTransformations.call(await getDocFromElement(target))
|
||||
});
|
||||
|
||||
options.unshift({
|
||||
name: 'DAGGERHEART.GENERAL.damage',
|
||||
icon: 'fa-solid fa-explosion',
|
||||
|
|
@ -422,7 +432,9 @@ export default function DHApplicationMixin(Base) {
|
|||
callback: async (target, event) => {
|
||||
const doc = await getDocFromElement(target),
|
||||
action = doc?.system?.attack ?? doc;
|
||||
return action && action.use(event, { byPassRoll: true });
|
||||
const config = action.prepareConfig(event);
|
||||
config.hasRoll = false;
|
||||
return action && action.workflow.get("damage").execute(config, null, true);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { emitAsGM, GMUpdateEvent } from '../../systemRegistration/socket.mjs';
|
||||
|
||||
export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLog {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
|
@ -55,21 +53,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
}
|
||||
|
||||
addChatListeners = async (app, html, data) => {
|
||||
html.querySelectorAll('.duality-action-damage').forEach(element =>
|
||||
element.addEventListener('click', event => this.onRollDamage(event, data.message))
|
||||
);
|
||||
html.querySelectorAll('.target-save').forEach(element =>
|
||||
element.addEventListener('click', event => this.onRollSave(event, data.message))
|
||||
);
|
||||
html.querySelectorAll('.roll-all-save-button').forEach(element =>
|
||||
element.addEventListener('click', event => this.onRollAllSave(event, data.message))
|
||||
);
|
||||
html.querySelectorAll('.simple-roll-button').forEach(element =>
|
||||
element.addEventListener('click', event => this.onRollSimple(event, data.message))
|
||||
);
|
||||
html.querySelectorAll('.healing-button').forEach(element =>
|
||||
element.addEventListener('click', event => this.onHealing(event, data.message))
|
||||
);
|
||||
html.querySelectorAll('.ability-use-button').forEach(element =>
|
||||
element.addEventListener('click', event => this.abilityUseButton(event, data.message))
|
||||
);
|
||||
|
|
@ -90,80 +76,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
super.close(options);
|
||||
}
|
||||
|
||||
async getActor(uuid) {
|
||||
return await foundry.utils.fromUuid(uuid);
|
||||
}
|
||||
|
||||
getAction(actor, itemId, actionId) {
|
||||
const item = actor.items.get(itemId),
|
||||
action =
|
||||
actor.system.attack?._id === actionId
|
||||
? actor.system.attack
|
||||
: item.system.attack?._id === actionId
|
||||
? item.system.attack
|
||||
: item?.system?.actions?.get(actionId);
|
||||
return action;
|
||||
}
|
||||
|
||||
async onRollDamage(event, message) {
|
||||
event.stopPropagation();
|
||||
const actor = await this.getActor(message.system.source.actor);
|
||||
if(!actor.isOwner) return true;
|
||||
if (message.system.source.item && message.system.source.action) {
|
||||
const action = this.getAction(actor, message.system.source.item, message.system.source.action);
|
||||
if (!action || !action?.rollDamage) return;
|
||||
await action.rollDamage(event, message);
|
||||
}
|
||||
}
|
||||
|
||||
async onRollSave(event, message) {
|
||||
event.stopPropagation();
|
||||
const actor = await this.getActor(message.system.source.actor),
|
||||
tokenId = event.target.closest('[data-token]')?.dataset.token,
|
||||
token = game.canvas.tokens.get(tokenId);
|
||||
if (!token?.actor || !token.isOwner) return true;
|
||||
if (message.system.source.item && message.system.source.action) {
|
||||
const action = this.getAction(actor, message.system.source.item, message.system.source.action);
|
||||
if (!action || !action?.hasSave) return;
|
||||
action.rollSave(token.actor, event, message).then(result =>
|
||||
emitAsGM(
|
||||
GMUpdateEvent.UpdateSaveMessage,
|
||||
action.updateSaveMessage.bind(action, result, message, token.id),
|
||||
{
|
||||
action: action.uuid,
|
||||
message: message._id,
|
||||
token: token.id,
|
||||
result
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async onRollAllSave(event, message) {
|
||||
event.stopPropagation();
|
||||
if (!game.user.isGM) return;
|
||||
const targets = event.target.parentElement.querySelectorAll('[data-token] .target-save');
|
||||
const actor = await this.getActor(message.system.source.actor),
|
||||
action = this.getAction(actor, message.system.source.item, message.system.source.action);
|
||||
targets.forEach(async el => {
|
||||
const tokenId = el.closest('[data-token]')?.dataset.token,
|
||||
token = game.canvas.tokens.get(tokenId);
|
||||
if (!token.actor) return;
|
||||
if (game.user === token.actor.owner) el.dispatchEvent(new PointerEvent('click', { shiftKey: true }));
|
||||
else {
|
||||
token.actor.owner
|
||||
.query('reactionRoll', {
|
||||
actionId: action.uuid,
|
||||
actorId: token.actor.uuid,
|
||||
event,
|
||||
message
|
||||
})
|
||||
.then(result => action.updateSaveMessage(result, message, token.id));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async onRollSimple(event, message) {
|
||||
const buttonType = event.target.dataset.type ?? 'damage',
|
||||
total = message.rolls.reduce((a, c) => a + Roll.fromJSON(c).total, 0),
|
||||
|
|
@ -197,8 +109,11 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
|||
item.system.attack?.id === event.currentTarget.id
|
||||
? item.system.attack
|
||||
: item.system.actions.get(event.currentTarget.id);
|
||||
if (event.currentTarget.dataset.directDamage) action.use(event, { byPassRoll: true });
|
||||
else action.use(event);
|
||||
if (event.currentTarget.dataset.directDamage) {
|
||||
const config = action.prepareConfig(event);
|
||||
config.hasRoll = false;
|
||||
action.workflow.get("damage").execute(config, null, true);
|
||||
} else action.use(event);
|
||||
}
|
||||
|
||||
async actionUseButton(event, message) {
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
|
|||
|
||||
/** @override */
|
||||
async _preRender(context, options) {
|
||||
if (this.currentFear > this.maxFear)
|
||||
if (this.currentFear > this.maxFear && game.user.isGM)
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, this.maxFear);
|
||||
}
|
||||
|
||||
|
|
@ -107,18 +107,5 @@ export default class FearTracker extends HandlebarsApplicationMixin(ApplicationV
|
|||
|
||||
async updateFear(value) {
|
||||
return emitAsGM(GMUpdateEvent.UpdateFear, game.settings.set.bind(game.settings, CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear), value);
|
||||
/* if(!game.user.isGM)
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, {
|
||||
action: socketEvent.GMUpdate,
|
||||
data: {
|
||||
action: GMUpdateEvent.UpdateFear,
|
||||
update: value
|
||||
}
|
||||
});
|
||||
else
|
||||
game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, value); */
|
||||
/* if (!game.user.isGM) return;
|
||||
value = Math.max(0, Math.min(this.maxFear, value));
|
||||
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear, value); */
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -865,6 +865,9 @@ export const weaponFeatures = {
|
|||
name: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.name',
|
||||
description: 'DAGGERHEART.CONFIG.WeaponFeature.greedy.description',
|
||||
img: 'icons/commodities/currency/coins-crown-stack-gold.webp',
|
||||
target: {
|
||||
type: 'self'
|
||||
},
|
||||
// Should cost handful of gold,
|
||||
effects: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -29,3 +29,22 @@ export const gameSettings = {
|
|||
Countdowns: 'Countdowns',
|
||||
LastMigrationVersion: 'LastMigrationVersion'
|
||||
};
|
||||
|
||||
export const actionAutomationChoices = {
|
||||
never: {
|
||||
id: "never",
|
||||
label: "Never"
|
||||
},
|
||||
showDialog: {
|
||||
id: "showDialog",
|
||||
label: "Show Dialog only"
|
||||
},
|
||||
// npcOnly: {
|
||||
// id: "npcOnly",
|
||||
// label: "Always for non-characters"
|
||||
// },
|
||||
always: {
|
||||
id: "always",
|
||||
label: "Always"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,9 @@
|
|||
import DhpActor from '../../documents/actor.mjs';
|
||||
import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs';
|
||||
import { ActionMixin } from '../fields/actionField.mjs';
|
||||
import { abilities } from '../../config/actorConfig.mjs';
|
||||
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
/*
|
||||
!!! I'm currently refactoring the whole Action thing, it's a WIP !!!
|
||||
*/
|
||||
|
||||
/*
|
||||
ToDo
|
||||
- Target Check / Target Picker
|
||||
|
|
@ -20,6 +15,7 @@ const fields = foundry.data.fields;
|
|||
export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel) {
|
||||
static extraSchemas = ['cost', 'uses', 'range'];
|
||||
|
||||
/** @inheritDoc */
|
||||
static defineSchema() {
|
||||
const schemaFields = {
|
||||
_id: new fields.DocumentIdField({ initial: () => foundry.utils.randomID() }),
|
||||
|
|
@ -37,31 +33,76 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
};
|
||||
|
||||
this.extraSchemas.forEach(s => {
|
||||
let clsField;
|
||||
if ((clsField = this.getActionField(s))) schemaFields[s] = new clsField();
|
||||
let clsField = this.getActionField(s);
|
||||
if (clsField)
|
||||
schemaFields[s] = new clsField();
|
||||
});
|
||||
|
||||
return schemaFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Map containing each Action step based on fields define in schema. Ordered by Fields order property.
|
||||
*
|
||||
* Each step can be called individually as long as needed config is provided.
|
||||
* Ex: <action>.workflow.get("damage").execute(config)
|
||||
* @returns {Map}
|
||||
*/
|
||||
defineWorkflow() {
|
||||
const workflow = new Map();
|
||||
this.constructor.extraSchemas.forEach(s => {
|
||||
let clsField = this.constructor.getActionField(s);
|
||||
if (clsField?.execute) {
|
||||
workflow.set(s, { order: clsField.order, execute: clsField.execute.bind(this) } );
|
||||
if( s === "damage" ) workflow.set("applyDamage", { order: 75, execute: clsField.applyDamage.bind(this) } );
|
||||
}
|
||||
});
|
||||
return new Map([...workflow.entries()].sort(([aKey, aValue], [bKey, bValue]) => aValue.order - bValue.order));
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter returning the workflow property or creating it the first time the property is called
|
||||
*/
|
||||
get workflow() {
|
||||
if ( this.hasOwnProperty("_workflow") ) return this._workflow;
|
||||
const workflow = Object.freeze(this.defineWorkflow());
|
||||
Object.defineProperty(this, "_workflow", {value: workflow, writable: false});
|
||||
return workflow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Field class from ActionFields global config
|
||||
* @param {string} name Field short name, equal to Action property
|
||||
* @returns Action Field
|
||||
*/
|
||||
static getActionField(name) {
|
||||
const field = game.system.api.fields.ActionFields[`${name.capitalize()}Field`];
|
||||
return fields.DataField.isPrototypeOf(field) && field;
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
prepareData() {
|
||||
this.name = this.name || game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[this.type].name);
|
||||
this.img = this.img ?? this.parent?.parent?.img;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Action ID
|
||||
*/
|
||||
get id() {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Item the action is attached too.
|
||||
*/
|
||||
get item() {
|
||||
return this.parent.parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the first Actor parent found.
|
||||
*/
|
||||
get actor() {
|
||||
return this.item instanceof DhpActor
|
||||
? this.item
|
||||
|
|
@ -74,6 +115,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
return 'trait';
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare base data based on Action Type & Parent Type
|
||||
* @param {object} parent
|
||||
* @returns {object}
|
||||
*/
|
||||
static getSourceConfig(parent) {
|
||||
const updateSource = {};
|
||||
if (parent?.parent?.type === 'weapon' && this === game.system.api.models.actions.actionsTypes.attack) {
|
||||
|
|
@ -96,6 +142,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
return updateSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a data object used to evaluate any dice rolls associated with this particular Action
|
||||
* @param {object} [data ={}] Optional data object from previous configuration/rolls
|
||||
* @returns {object}
|
||||
*/
|
||||
getRollData(data = {}) {
|
||||
if (!this.actor) return null;
|
||||
const actorData = this.actor.getRollData(false);
|
||||
|
|
@ -111,19 +162,30 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
return actorData;
|
||||
}
|
||||
|
||||
async use(event, options = {}) {
|
||||
/**
|
||||
* Execute each part of the Action workflow in order, calling a specific event before and after each part.
|
||||
* @param {object} config Config object usually created from prepareConfig method
|
||||
*/
|
||||
async executeWorkflow(config) {
|
||||
for(const [key, part] of this.workflow) {
|
||||
if (Hooks.call(`${CONFIG.DH.id}.pre${key.capitalize()}Action`, this, config) === false) return;
|
||||
if(await part.execute(config) === false) return;
|
||||
if (Hooks.call(`${CONFIG.DH.id}.post${key.capitalize()}Action`, this, config) === false) return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main method to use the Action
|
||||
* @param {Event} event Event from the button used to trigger the Action
|
||||
* @returns {object}
|
||||
*/
|
||||
async use(event) {
|
||||
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++) {
|
||||
let clsField = this.constructor.getActionField(this.constructor.extraSchemas[i]);
|
||||
if (clsField?.prepareConfig) {
|
||||
const keep = clsField.prepareConfig.call(this, config);
|
||||
if (config.isFastForward && !keep) return;
|
||||
}
|
||||
}
|
||||
|
||||
let config = this.prepareConfig(event);
|
||||
if(!config) return;
|
||||
|
||||
if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return;
|
||||
|
||||
|
|
@ -132,36 +194,22 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
config = await D20RollDialog.configure(null, config);
|
||||
if (!config) return;
|
||||
}
|
||||
|
||||
if (config.hasRoll) {
|
||||
const rollConfig = this.prepareRoll(config);
|
||||
config.roll = rollConfig;
|
||||
config = await this.actor.diceRoll(config);
|
||||
if (!config) return;
|
||||
}
|
||||
|
||||
if (this.doFollowUp(config)) {
|
||||
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 CONFIG.Dice.daggerheart.DHRoll('');
|
||||
roll._evaluated = true;
|
||||
await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config);
|
||||
}
|
||||
}
|
||||
|
||||
// Consume resources
|
||||
await this.consume(config);
|
||||
|
||||
// Execute the Action Worflow in order based of schema fields
|
||||
await this.executeWorkflow(config);
|
||||
|
||||
if (Hooks.call(`${CONFIG.DH.id}.postUseAction`, this, config) === false) return;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/* */
|
||||
prepareConfig(event, byPass = false) {
|
||||
const hasRoll = this.getUseHasRoll(byPass);
|
||||
return {
|
||||
/**
|
||||
* Create the basic config common to every action type
|
||||
* @param {Event} event Event from the button used to trigger the Action
|
||||
* @returns {object}
|
||||
*/
|
||||
prepareBaseConfig(event) {
|
||||
const config = {
|
||||
event,
|
||||
title: `${this.item instanceof CONFIG.Actor.documentClass ? '' : `${this.item.name}: `}${game.i18n.localize(this.name)}`,
|
||||
source: {
|
||||
|
|
@ -169,238 +217,95 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
|||
action: this._id,
|
||||
actor: this.actor.uuid
|
||||
},
|
||||
dialog: {
|
||||
configure: hasRoll
|
||||
},
|
||||
type: this.roll?.type ?? this.type,
|
||||
hasRoll: hasRoll,
|
||||
hasDamage: this.damage?.parts?.length && this.type !== 'healing',
|
||||
hasHealing: this.damage?.parts?.length && this.type === 'healing',
|
||||
hasEffect: !!this.effects?.length,
|
||||
isDirect: !!this.damage?.direct,
|
||||
dialog: {},
|
||||
actionType: this.actionType,
|
||||
hasRoll: this.hasRoll,
|
||||
hasDamage: this.hasDamage,
|
||||
hasHealing: this.hasHealing,
|
||||
hasEffect: this.hasEffect,
|
||||
hasSave: this.hasSave,
|
||||
isDirect: !!this.damage?.direct,
|
||||
selectedRollMode: game.settings.get('core', 'rollMode'),
|
||||
isFastForward: event.shiftKey,
|
||||
data: this.getRollData(),
|
||||
evaluate: hasRoll
|
||||
evaluate: this.hasRoll
|
||||
};
|
||||
DHBaseAction.applyKeybindings(config);
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the config for that action used for its workflow
|
||||
* @param {Event} event Event from the button used to trigger the Action
|
||||
* @returns {object}
|
||||
*/
|
||||
prepareConfig(event) {
|
||||
const config = this.prepareBaseConfig(event);
|
||||
for(const clsField of Object.values(this.schema.fields)) {
|
||||
if (clsField?.prepareConfig)
|
||||
if(clsField.prepareConfig.call(this, config) === false) return false;
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to know if a configuration dialog must be shown or not when there is no roll.
|
||||
* @param {*} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
requireConfigurationDialog(config) {
|
||||
return !config.event.shiftKey && !config.hasRoll && (config.costs?.length || config.uses);
|
||||
}
|
||||
|
||||
prepareRoll() {
|
||||
const roll = {
|
||||
baseModifiers: this.roll.getModifier(),
|
||||
label: 'Attack',
|
||||
type: this.actionType,
|
||||
difficulty: this.roll?.difficulty,
|
||||
formula: this.roll.getFormula(),
|
||||
advantage: CONFIG.DH.ACTIONS.advantageState[this.roll.advState].value
|
||||
};
|
||||
if (this.roll?.type === 'diceSet' || !this.hasRoll) roll.lite = true;
|
||||
|
||||
return roll;
|
||||
}
|
||||
|
||||
doFollowUp(config) {
|
||||
return !config.hasRoll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume Action configured resources & uses.
|
||||
* That method is only used when we want those resources to be consumed outside of the use method workflow.
|
||||
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
||||
* @param {boolean} successCost
|
||||
*/
|
||||
async consume(config, successCost = false) {
|
||||
const actor = this.actor.system.partner ?? this.actor,
|
||||
usefulResources = {
|
||||
...foundry.utils.deepClone(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
|
||||
}
|
||||
};
|
||||
await this.workflow.get("cost")?.execute(config, successCost);
|
||||
await this.workflow.get("uses")?.execute(config, successCost);
|
||||
|
||||
for (var cost of config.costs) {
|
||||
if (cost.keyIsID) {
|
||||
usefulResources[cost.key] = {
|
||||
value: cost.value,
|
||||
target: this.parent.parent,
|
||||
keyIsID: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const resources = game.system.api.fields.ActionFields.CostField.getRealCosts(config.costs)
|
||||
.filter(
|
||||
c =>
|
||||
(!successCost && (!c.consumeOnSuccess || config.roll?.success)) ||
|
||||
(successCost && c.consumeOnSuccess)
|
||||
)
|
||||
.reduce((a, c) => {
|
||||
const resource = usefulResources[c.key];
|
||||
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 actor.modifyResource(resources);
|
||||
if (
|
||||
config.uses?.enabled &&
|
||||
((!successCost && (!config.uses?.consumeOnSuccess || config.roll?.success)) ||
|
||||
(successCost && config.uses?.consumeOnSuccess))
|
||||
)
|
||||
this.update({ 'uses.value': this.uses.value + 1 });
|
||||
|
||||
if (config.roll?.success || successCost) {
|
||||
if (config.roll && !config.roll.success && successCost) {
|
||||
setTimeout(() => {
|
||||
(config.message ?? config.parent).update({ 'system.successConsumed': true });
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
/* */
|
||||
|
||||
/* ROLL */
|
||||
getUseHasRoll(byPass = false) {
|
||||
return this.hasRoll && !byPass;
|
||||
/**
|
||||
* Set if a configuration dialog must be shown or not if a special keyboard key is pressed.
|
||||
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
||||
*/
|
||||
static applyKeybindings(config) {
|
||||
config.dialog.configure ??= !(config.event.shiftKey || config.event.altKey || config.event.ctrlKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Getters to know which parts the action is composed of. A field can exist but configured to not be used.
|
||||
* @returns {boolean} If that part is in the action.
|
||||
*/
|
||||
|
||||
get hasRoll() {
|
||||
return !!this.roll?.type;
|
||||
}
|
||||
|
||||
get modifiers() {
|
||||
if (!this.actor) return [];
|
||||
const modifiers = [];
|
||||
/** Placeholder for specific bonuses **/
|
||||
return modifiers;
|
||||
get hasDamage() {
|
||||
return this.damage?.parts?.length && this.type !== 'healing'
|
||||
}
|
||||
/* ROLL */
|
||||
|
||||
/* SAVE */
|
||||
get hasHealing() {
|
||||
return this.damage?.parts?.length && this.type === 'healing'
|
||||
}
|
||||
|
||||
get hasSave() {
|
||||
return !!this.save?.trait;
|
||||
}
|
||||
/* SAVE */
|
||||
|
||||
/* EFFECTS */
|
||||
get hasEffect() {
|
||||
return this.effects?.length > 0;
|
||||
}
|
||||
|
||||
async applyEffects(event, data, targets) {
|
||||
targets ??= data.system.targets;
|
||||
const force = true; /* Where should this come from? */
|
||||
if (!this.effects?.length || !targets.length) return;
|
||||
let effects = this.effects;
|
||||
targets.forEach(async token => {
|
||||
if (!token.hit && !force) return;
|
||||
if (this.hasSave && token.saved.success === true) {
|
||||
effects = this.effects.filter(e => e.onSave === true);
|
||||
}
|
||||
if (!effects.length) return;
|
||||
effects.forEach(async e => {
|
||||
const actor = canvas.tokens.get(token.id)?.actor,
|
||||
effect = this.item.effects.get(e._id);
|
||||
if (!actor || !effect) return;
|
||||
await this.applyEffect(effect, actor);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async applyEffect(effect, actor) {
|
||||
const existingEffect = actor.effects.find(e => e.origin === effect.uuid);
|
||||
if (existingEffect) {
|
||||
return effect.update(
|
||||
foundry.utils.mergeObject({
|
||||
...effect.constructor.getInitialDuration(),
|
||||
disabled: false
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Otherwise, create a new effect on the target
|
||||
const effectData = foundry.utils.mergeObject({
|
||||
...effect.toObject(),
|
||||
disabled: false,
|
||||
transfer: false,
|
||||
origin: effect.uuid
|
||||
});
|
||||
await ActiveEffect.implementation.create(effectData, { parent: actor });
|
||||
}
|
||||
/* EFFECTS */
|
||||
|
||||
/* SAVE */
|
||||
async rollSave(actor, event, message) {
|
||||
if (!actor) return;
|
||||
const title = actor.isNPC
|
||||
? game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll')
|
||||
: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||
ability: game.i18n.localize(abilities[this.save.trait]?.label)
|
||||
});
|
||||
return actor.diceRoll({
|
||||
event,
|
||||
title,
|
||||
roll: {
|
||||
trait: this.save.trait,
|
||||
difficulty: this.save.difficulty ?? this.actor?.baseSaveDifficulty,
|
||||
type: 'reaction'
|
||||
},
|
||||
type: 'trait',
|
||||
hasRoll: true,
|
||||
data: actor.getRollData()
|
||||
});
|
||||
}
|
||||
|
||||
updateSaveMessage(result, message, targetId) {
|
||||
if (!result) return;
|
||||
const updateMsg = this.updateChatMessage.bind(this, message, targetId, {
|
||||
result: result.roll.total,
|
||||
success: result.roll.success
|
||||
});
|
||||
if (game.modules.get('dice-so-nice')?.active)
|
||||
game.dice3d.waitFor3DAnimationByMessageID(result.message.id ?? result.message._id).then(() => updateMsg());
|
||||
else updateMsg();
|
||||
}
|
||||
|
||||
static rollSaveQuery({ actionId, actorId, event, message }) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const actor = await fromUuid(actorId),
|
||||
action = await fromUuid(actionId);
|
||||
if (!actor || !actor?.isOwner) reject();
|
||||
action.rollSave(actor, event, message).then(result => resolve(result));
|
||||
});
|
||||
}
|
||||
/* SAVE */
|
||||
|
||||
async updateChatMessage(message, targetId, changes, chain = true) {
|
||||
setTimeout(async () => {
|
||||
const chatMessage = ui.chat.collection.get(message._id);
|
||||
|
||||
await chatMessage.update({
|
||||
flags: {
|
||||
[game.system.id]: {
|
||||
reactionRolls: {
|
||||
[targetId]: changes
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}, 100);
|
||||
if (chain) {
|
||||
if (message.system.source.message)
|
||||
this.updateChatMessage(ui.chat.collection.get(message.system.source.message), targetId, changes, false);
|
||||
const relatedChatMessages = ui.chat.collection.filter(c => c.system.source?.message === message._id);
|
||||
relatedChatMessages.forEach(c => {
|
||||
this.updateChatMessage(c, targetId, changes, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a list of localized tags for this action.
|
||||
* @returns {string[]} An array of localized tag strings.
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
import BeastformDialog from '../../applications/dialogs/beastformDialog.mjs';
|
||||
import DHBaseAction from './baseAction.mjs';
|
||||
|
||||
export default class DhBeastformAction extends DHBaseAction {
|
||||
static extraSchemas = [...super.extraSchemas, 'beastform'];
|
||||
|
||||
async use(event, options) {
|
||||
/* async use(event, options) {
|
||||
const beastformConfig = this.prepareBeastformConfig();
|
||||
|
||||
const abort = await this.handleActiveTransformations();
|
||||
|
|
@ -82,5 +81,5 @@ export default class DhBeastformAction extends DHBaseAction {
|
|||
beastformEffects.map(x => x.id)
|
||||
);
|
||||
return existingEffects;
|
||||
}
|
||||
} */
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,65 +1,5 @@
|
|||
import { setsEqual } from '../../helpers/utils.mjs';
|
||||
import DHBaseAction from './baseAction.mjs';
|
||||
|
||||
export default class DHDamageAction extends DHBaseAction {
|
||||
static extraSchemas = [...super.extraSchemas, 'damage', 'target', 'effects'];
|
||||
|
||||
getFormulaValue(part, data) {
|
||||
let formulaValue = part.value;
|
||||
|
||||
if (data.hasRoll && part.resultBased && data.roll.result.duality === -1) return part.valueAlt;
|
||||
|
||||
const isAdversary = this.actor.type === 'adversary';
|
||||
if (isAdversary && this.actor.system.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id) {
|
||||
const hasHordeDamage = this.actor.effects.find(x => x.type === 'horde');
|
||||
if (hasHordeDamage && !hasHordeDamage.disabled) return part.valueAlt;
|
||||
}
|
||||
|
||||
return formulaValue;
|
||||
}
|
||||
|
||||
formatFormulas(formulas, systemData) {
|
||||
const formattedFormulas = [];
|
||||
formulas.forEach(formula => {
|
||||
if (isNaN(formula.formula))
|
||||
formula.formula = Roll.replaceFormulaData(formula.formula, this.getRollData(systemData));
|
||||
const same = formattedFormulas.find(
|
||||
f => setsEqual(f.damageTypes, formula.damageTypes) && f.applyTo === formula.applyTo
|
||||
);
|
||||
if (same) same.formula += ` + ${formula.formula}`;
|
||||
else formattedFormulas.push(formula);
|
||||
});
|
||||
return formattedFormulas;
|
||||
}
|
||||
|
||||
async rollDamage(event, data) {
|
||||
const systemData = data.system ?? data;
|
||||
|
||||
let formulas = this.damage.parts.map(p => ({
|
||||
formula: this.getFormulaValue(p, systemData).getFormula(this.actor),
|
||||
damageTypes: p.applyTo === 'hitPoints' && !p.type.size ? new Set(['physical']) : p.type,
|
||||
applyTo: p.applyTo
|
||||
}));
|
||||
|
||||
if (!formulas.length) return;
|
||||
|
||||
formulas = this.formatFormulas(formulas, systemData);
|
||||
|
||||
delete systemData.evaluate;
|
||||
const config = {
|
||||
...systemData,
|
||||
roll: formulas,
|
||||
dialog: {},
|
||||
data: this.getRollData()
|
||||
};
|
||||
if (this.hasSave) config.onSave = this.save.damageMod;
|
||||
if (data.system) {
|
||||
config.source.message = data._id;
|
||||
config.directDamage = false;
|
||||
} else {
|
||||
config.directDamage = true;
|
||||
}
|
||||
|
||||
return CONFIG.Dice.daggerheart.DamageRoll.build(config);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,15 +2,4 @@ import DHBaseAction from './baseAction.mjs';
|
|||
|
||||
export default class DHMacroAction extends DHBaseAction {
|
||||
static extraSchemas = [...super.extraSchemas, 'macro'];
|
||||
|
||||
async trigger(event, ...args) {
|
||||
const fixUUID = !this.macro.includes('Macro.') ? `Macro.${this.macro}` : this.macro,
|
||||
macro = await fromUuid(fixUUID);
|
||||
try {
|
||||
if (!macro) throw new Error(`No macro found for the UUID: ${this.macro}.`);
|
||||
macro.execute();
|
||||
} catch (error) {
|
||||
ui.notifications.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import DHAbilityUse from "./abilityUse.mjs";
|
||||
import DHActorRoll from "./adversaryRoll.mjs";
|
||||
import DHActorRoll from "./actorRoll.mjs";
|
||||
|
||||
export const config = {
|
||||
abilityUse: DHAbilityUse,
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
|
|||
}
|
||||
|
||||
registerTargetHook() {
|
||||
if (!this.parent.isAuthor) return;
|
||||
if (!this.parent.isAuthor || !this.hasTarget) return;
|
||||
if (this.targetMode && this.parent.targetHook !== null) {
|
||||
Hooks.off('targetToken', this.parent.targetHook);
|
||||
return (this.parent.targetHook = null);
|
||||
|
|
@ -6,6 +6,5 @@ export { default as EffectsField } from './effectsField.mjs';
|
|||
export { default as SaveField } from './saveField.mjs';
|
||||
export { default as BeastformField } from './beastformField.mjs';
|
||||
export { default as DamageField } from './damageField.mjs';
|
||||
export { default as HealingField } from './healingField.mjs';
|
||||
export { default as RollField } from './rollField.mjs';
|
||||
export { default as MacroField } from './macroField.mjs';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,13 @@
|
|||
import BeastformDialog from "../../../applications/dialogs/beastformDialog.mjs";
|
||||
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
export default class BeastformField extends fields.SchemaField {
|
||||
/**
|
||||
* Action Workflow order
|
||||
*/
|
||||
static order = 90;
|
||||
|
||||
constructor(options = {}, context = {}) {
|
||||
const beastformFields = {
|
||||
tierAccess: new fields.SchemaField({
|
||||
|
|
@ -27,4 +34,96 @@ export default class BeastformField extends fields.SchemaField {
|
|||
};
|
||||
super(beastformFields, options, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Beastform Transformation Action Workflow part.
|
||||
* Must be called within Action context or similar.
|
||||
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
||||
*/
|
||||
static async execute(config) {
|
||||
// Should not be useful anymore here
|
||||
await BeastformField.handleActiveTransformations.call(this);
|
||||
|
||||
const { selected, evolved, hybrid } = await BeastformDialog.configure(config, this.item);
|
||||
if (!selected) return false;
|
||||
|
||||
return await BeastformField.transform.call(this, selected, evolved, hybrid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Action Workflow config object.
|
||||
* Must be called within Action context.
|
||||
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
||||
*/
|
||||
prepareConfig(config) {
|
||||
if(this.actor.effects.find(x => x.type === 'beastform')) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.beastformAlreadyApplied'));
|
||||
return false;
|
||||
}
|
||||
|
||||
const settingsTiers = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers;
|
||||
const actorLevel = this.actor.system.levelData.level.current;
|
||||
const actorTier =
|
||||
Object.values(settingsTiers).find(
|
||||
tier => actorLevel >= tier.levels.start && actorLevel <= tier.levels.end
|
||||
) ?? 1;
|
||||
|
||||
config.tierLimit = this.beastform.tierAccess.exact ?? actorTier;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO by Harry
|
||||
* @param {*} selectedForm
|
||||
* @param {*} evolvedData
|
||||
* @param {*} hybridData
|
||||
* @returns
|
||||
*/
|
||||
static async transform(selectedForm, evolvedData, hybridData) {
|
||||
const formData = evolvedData?.form ? evolvedData.form.toObject() : selectedForm.toObject();
|
||||
const beastformEffect = formData.effects.find(x => x.type === 'beastform');
|
||||
if (!beastformEffect) {
|
||||
ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (evolvedData?.form) {
|
||||
const evolvedForm = selectedForm.effects.find(x => x.type === 'beastform');
|
||||
if (!evolvedForm) {
|
||||
ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect');
|
||||
return false;
|
||||
}
|
||||
|
||||
beastformEffect.changes = [...beastformEffect.changes, ...evolvedForm.changes];
|
||||
formData.system.features = [...formData.system.features, ...selectedForm.system.features.map(x => x.uuid)];
|
||||
}
|
||||
|
||||
if (selectedForm.system.beastformType === CONFIG.DH.ITEM.beastformTypes.hybrid.id) {
|
||||
formData.system.advantageOn = Object.values(hybridData.advantages).reduce((advantages, formCategory) => {
|
||||
Object.keys(formCategory).forEach(advantageKey => {
|
||||
advantages[advantageKey] = formCategory[advantageKey];
|
||||
});
|
||||
return advantages;
|
||||
}, {});
|
||||
formData.system.features = [
|
||||
...formData.system.features,
|
||||
...Object.values(hybridData.features).flatMap(x => Object.keys(x))
|
||||
];
|
||||
}
|
||||
|
||||
this.actor.createEmbeddedDocuments('Item', [formData]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove existing beastform effect and return true if there was one
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static async handleActiveTransformations() {
|
||||
const beastformEffects = this.actor.effects.filter(x => x.type === 'beastform');
|
||||
const existingEffects = beastformEffects.length > 0;
|
||||
await this.actor.deleteEmbeddedDocuments(
|
||||
'ActiveEffect',
|
||||
beastformEffects.map(x => x.id)
|
||||
);
|
||||
return existingEffects;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
const fields = foundry.data.fields;
|
||||
|
||||
export default class CostField extends fields.ArrayField {
|
||||
/**
|
||||
* Action Workflow order
|
||||
*/
|
||||
static order = 150;
|
||||
|
||||
/** @inheritDoc */
|
||||
constructor(options = {}, context = {}) {
|
||||
const element = new fields.SchemaField({
|
||||
key: new fields.StringField({
|
||||
|
|
@ -20,15 +26,80 @@ export default class CostField extends fields.ArrayField {
|
|||
super(element, options, context);
|
||||
}
|
||||
|
||||
static prepareConfig(config) {
|
||||
/**
|
||||
* Cost Consumption Action Workflow part.
|
||||
* Consume configured action resources.
|
||||
* Must be called within Action context or similar.
|
||||
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
||||
* @param {boolean} [successCost=false] Consume only resources configured as "On Success only" if not already consumed.
|
||||
*/
|
||||
static async execute(config, successCost = false) {
|
||||
const actor= this.actor.system.partner ?? this.actor,
|
||||
usefulResources = {
|
||||
...foundry.utils.deepClone(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
|
||||
}
|
||||
};
|
||||
|
||||
if(this.parent?.parent) {
|
||||
for (var cost of config.costs) {
|
||||
if (cost.keyIsID) {
|
||||
usefulResources[cost.key] = {
|
||||
value: cost.value,
|
||||
target: this.parent.parent,
|
||||
keyIsID: true
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const resources = CostField.getRealCosts(config.costs)
|
||||
.filter(
|
||||
c =>
|
||||
(!successCost && (!c.consumeOnSuccess || config.roll?.success)) ||
|
||||
(successCost && c.consumeOnSuccess)
|
||||
)
|
||||
.reduce((a, c) => {
|
||||
const resource = usefulResources[c.key];
|
||||
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 actor.modifyResource(resources);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Action Workflow config object.
|
||||
* Must be called within Action context or similar.
|
||||
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
||||
* @returns {boolean} Return false if fast-forwarded and no more uses.
|
||||
*/
|
||||
prepareConfig(config) {
|
||||
const costs = this.cost?.length ? foundry.utils.deepClone(this.cost) : [];
|
||||
config.costs = CostField.calcCosts.call(this, costs);
|
||||
const hasCost = CostField.hasCost.call(this, config.costs);
|
||||
if (config.isFastForward && !hasCost)
|
||||
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.insufficientResources'));
|
||||
return hasCost;
|
||||
if (config.dialog.configure === false && !hasCost) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.insufficientResources'));
|
||||
return hasCost;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Must be called within Action context.
|
||||
* @param {*} costs
|
||||
* @returns
|
||||
*/
|
||||
static calcCosts(costs) {
|
||||
const resources = CostField.getResources.call(this, costs);
|
||||
return costs.map(c => {
|
||||
|
|
@ -40,13 +111,19 @@ export default class CostField extends fields.ArrayField {
|
|||
c.key === 'fear'
|
||||
? game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear)
|
||||
: resources[c.key].isReversed
|
||||
? resources[c.key].max
|
||||
? resources[c.key].max - resources[c.key].value
|
||||
: resources[c.key].value;
|
||||
if (c.scalable) c.maxStep = Math.floor((c.max - c.value) / c.step);
|
||||
return c;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current Actor currently has all needed resources.
|
||||
* Must be called within Action context.
|
||||
* @param {*} costs
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static hasCost(costs) {
|
||||
const realCosts = CostField.getRealCosts.call(this, costs),
|
||||
hasFearCost = realCosts.findIndex(c => c.key === 'fear');
|
||||
|
|
@ -73,6 +150,12 @@ export default class CostField extends fields.ArrayField {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all Actor resources + parent Item potential one.
|
||||
* Must be called within Action context.
|
||||
* @param {*} costs
|
||||
* @returns
|
||||
*/
|
||||
static getResources(costs) {
|
||||
const actorResources = foundry.utils.deepClone(this.actor.system.resources);
|
||||
if (this.actor.system.partner)
|
||||
|
|
@ -93,6 +176,11 @@ export default class CostField extends fields.ArrayField {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {*} costs
|
||||
* @returns
|
||||
*/
|
||||
static getRealCosts(costs) {
|
||||
const realCosts = costs?.length ? costs.filter(c => c.enabled) : [];
|
||||
let mergedCosts = [];
|
||||
|
|
@ -104,6 +192,12 @@ export default class CostField extends fields.ArrayField {
|
|||
return mergedCosts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format scalable max cost, inject Action datas if it's a formula.
|
||||
* Must be called within Action context.
|
||||
* @param {number|string} max Configured maximum for that resource.
|
||||
* @returns {number} The max cost value.
|
||||
*/
|
||||
static formatMax(max) {
|
||||
max ??= 0;
|
||||
if (isNaN(max)) {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
import FormulaField from '../formulaField.mjs';
|
||||
import { setsEqual } from '../../../helpers/utils.mjs';
|
||||
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
export default class DamageField extends fields.SchemaField {
|
||||
/**
|
||||
* Action Workflow order
|
||||
*/
|
||||
static order = 20;
|
||||
|
||||
/** @inheritDoc */
|
||||
constructor(options, context = {}) {
|
||||
const damageFields = {
|
||||
parts: new fields.ArrayField(new fields.EmbeddedDataField(DHDamageData)),
|
||||
|
|
@ -14,6 +21,141 @@ export default class DamageField extends fields.SchemaField {
|
|||
};
|
||||
super(damageFields, options, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Roll Damage/Healing Action Workflow part.
|
||||
* Must be called within Action context or similar.
|
||||
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
||||
* @param {string} [messageId=null] ChatMessage Id where the clicked button belong.
|
||||
* @param {boolean} [force=false] If the method should be executed outside of Action workflow, for ChatMessage button for example.
|
||||
*/
|
||||
static async execute(config, messageId = null, force = false) {
|
||||
if(!this.hasDamage && !this.hasHealing) return;
|
||||
if((this.hasRoll && DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.never.id) && !force) return;
|
||||
|
||||
let formulas = this.damage.parts.map(p => ({
|
||||
formula: DamageField.getFormulaValue.call(this, p, config).getFormula(this.actor),
|
||||
damageTypes: p.applyTo === 'hitPoints' && !p.type.size ? new Set(['physical']) : p.type,
|
||||
applyTo: p.applyTo
|
||||
}));
|
||||
|
||||
if (!formulas.length) return false;
|
||||
|
||||
formulas = DamageField.formatFormulas.call(this, formulas, config);
|
||||
|
||||
const damageConfig = {
|
||||
...config,
|
||||
roll: formulas,
|
||||
dialog: {},
|
||||
data: this.getRollData()
|
||||
};
|
||||
delete damageConfig.evaluate;
|
||||
|
||||
if(DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.always.id) damageConfig.dialog.configure = false;
|
||||
if (config.hasSave) config.onSave = damageConfig.onSave = this.save.damageMod;
|
||||
|
||||
damageConfig.source.message = config.message?._id ?? messageId;
|
||||
damageConfig.directDamage = !!damageConfig.source?.message;
|
||||
|
||||
if(damageConfig.source?.message && game.modules.get('dice-so-nice')?.active)
|
||||
await game.dice3d.waitFor3DAnimationByMessageID(damageConfig.source.message);
|
||||
|
||||
const damageResult = await CONFIG.Dice.daggerheart.DamageRoll.build(damageConfig);
|
||||
if(!damageResult) return false;
|
||||
config.damage = damageResult.damage;
|
||||
config.message ??= damageConfig.message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply Damage/Healing Action Worflow part.
|
||||
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
||||
* @param {*[]} targets Arrays of targets to bypass pre-selected ones.
|
||||
* @param {boolean} force If the method should be executed outside of Action workflow, for ChatMessage button for example.
|
||||
*/
|
||||
static async applyDamage(config, targets = null, force = false) {
|
||||
targets ??= config.targets.filter(target => target.hit);
|
||||
if(!config.damage || !targets?.length || (!DamageField.getApplyAutomation() && !force)) return;
|
||||
for (let target of targets) {
|
||||
const actor = fromUuidSync(target.actorId);
|
||||
if(!actor) continue;
|
||||
if (
|
||||
!config.hasHealing &&
|
||||
config.onSave &&
|
||||
target.saved?.success === true
|
||||
) {
|
||||
const mod = CONFIG.DH.ACTIONS.damageOnSave[config.onSave]?.mod ?? 1;
|
||||
Object.entries(config.damage).forEach(([k, v]) => {
|
||||
v.total = 0;
|
||||
v.parts.forEach(part => {
|
||||
part.total = Math.ceil(part.total * mod);
|
||||
v.total += part.total;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (config.hasHealing) actor.takeHealing(config.damage);
|
||||
else actor.takeDamage(config.damage, config.isDirect);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return value or valueAlt from damage part
|
||||
* Must be called within Action context or similar.
|
||||
* @param {object} part Damage Part
|
||||
* @param {object} data Action getRollData
|
||||
* @returns Formula value object
|
||||
*/
|
||||
static getFormulaValue(part, data) {
|
||||
let formulaValue = part.value;
|
||||
|
||||
if (data.hasRoll && part.resultBased && data.roll.result.duality === -1) return part.valueAlt;
|
||||
|
||||
const isAdversary = this.actor.type === 'adversary';
|
||||
if (isAdversary && this.actor.system.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id) {
|
||||
const hasHordeDamage = this.actor.effects.find(x => x.type === 'horde');
|
||||
if (hasHordeDamage && !hasHordeDamage.disabled) return part.valueAlt;
|
||||
}
|
||||
|
||||
return formulaValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare formulas for Damage Roll
|
||||
* Must be called within Action context or similar.
|
||||
* @param {object[]} formulas Array of formatted formulas object
|
||||
* @param {object} data Action getRollData
|
||||
* @returns
|
||||
*/
|
||||
static formatFormulas(formulas, data) {
|
||||
const formattedFormulas = [];
|
||||
formulas.forEach(formula => {
|
||||
if (isNaN(formula.formula))
|
||||
formula.formula = Roll.replaceFormulaData(formula.formula, this.getRollData(data));
|
||||
const same = formattedFormulas.find(
|
||||
f => setsEqual(f.damageTypes, formula.damageTypes) && f.applyTo === formula.applyTo
|
||||
);
|
||||
if (same) same.formula += ` + ${formula.formula}`;
|
||||
else formattedFormulas.push(formula);
|
||||
});
|
||||
return formattedFormulas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the automation setting for execute method for current user role
|
||||
* @returns {string} Id from settingsConfig.mjs actionAutomationChoices
|
||||
*/
|
||||
static getAutomation() {
|
||||
return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damage.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damage.players)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the automation setting for applyDamage method for current user role
|
||||
* @returns {boolean} If applyDamage should be triggered automatically
|
||||
*/
|
||||
static getApplyAutomation() {
|
||||
return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damageApply.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.damageApply.players)
|
||||
}
|
||||
}
|
||||
|
||||
export class DHActionDiceData extends foundry.abstract.DataModel {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
import { emitAsGM, GMUpdateEvent } from "../../../systemRegistration/socket.mjs";
|
||||
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
export default class EffectsField extends fields.ArrayField {
|
||||
/**
|
||||
* Action Workflow order
|
||||
*/
|
||||
static order = 100;
|
||||
|
||||
/** @inheritDoc */
|
||||
constructor(options = {}, context = {}) {
|
||||
const element = new fields.SchemaField({
|
||||
_id: new fields.DocumentIdField(),
|
||||
|
|
@ -8,4 +16,86 @@ export default class EffectsField extends fields.ArrayField {
|
|||
});
|
||||
super(element, options, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply Effects Action Workflow part.
|
||||
* Must be called within Action context or similar.
|
||||
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
||||
* @param {object[]} [targets=null] Array of targets to override pre-selected ones.
|
||||
* @param {boolean} [force=false] If the method should be executed outside of Action workflow, for ChatMessage button for example.
|
||||
*/
|
||||
static async execute(config, targets = null, force = false) {
|
||||
if(!config.hasEffect) return;
|
||||
let message = config.message ?? ui.chat.collection.get(config.parent?._id);
|
||||
if(!message) {
|
||||
const roll = new CONFIG.Dice.daggerheart.DHRoll('');
|
||||
roll._evaluated = true;
|
||||
message = config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config);
|
||||
}
|
||||
if(EffectsField.getAutomation() || force) {
|
||||
targets ??= (message.system?.targets ?? config.targets).filter(t => !config.hasRoll || t.hit);
|
||||
await emitAsGM(
|
||||
GMUpdateEvent.UpdateEffect,
|
||||
EffectsField.applyEffects.bind(this),
|
||||
targets,
|
||||
this.uuid
|
||||
);
|
||||
// EffectsField.applyEffects.call(this, config.targets.filter(t => !config.hasRoll || t.hit));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply Action Effects to a list of Targets
|
||||
* Must be called within Action context or similar.
|
||||
* @param {object[]} targets Array of formatted targets
|
||||
*/
|
||||
static async applyEffects(targets) {
|
||||
if (!this.effects?.length || !targets?.length) return;
|
||||
let effects = this.effects;
|
||||
targets.forEach(async token => {
|
||||
if (this.hasSave && token.saved.success === true)
|
||||
effects = this.effects.filter(e => e.onSave === true);
|
||||
if (!effects.length) return;
|
||||
effects.forEach(async e => {
|
||||
const actor = canvas.tokens.get(token.id)?.actor,
|
||||
effect = this.item.effects.get(e._id);
|
||||
if (!actor || !effect) return;
|
||||
await EffectsField.applyEffect(effect, actor);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply an Effect to a target or enable it if already on it
|
||||
* @param {object} effect Effect object containing ActiveEffect UUID
|
||||
* @param {object} actor Actor Document
|
||||
*/
|
||||
static async applyEffect(effect, actor) {
|
||||
const existingEffect = actor.effects.find(e => e.origin === effect.uuid);
|
||||
if (existingEffect) {
|
||||
return effect.update(
|
||||
foundry.utils.mergeObject({
|
||||
...effect.constructor.getInitialDuration(),
|
||||
disabled: false
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Otherwise, create a new effect on the target
|
||||
const effectData = foundry.utils.mergeObject({
|
||||
...effect.toObject(),
|
||||
disabled: false,
|
||||
transfer: false,
|
||||
origin: effect.uuid
|
||||
});
|
||||
await ActiveEffect.implementation.create(effectData, { parent: actor });
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the automation setting for execute method for current user role
|
||||
* @returns {boolean} If execute should be triggered automatically
|
||||
*/
|
||||
static getAutomation() {
|
||||
return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.effect.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.effect.players)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
import { DHDamageData } from './damageField.mjs';
|
||||
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
export default class HealingField extends fields.SchemaField {
|
||||
constructor(options, context = {}) {
|
||||
const healingFields = {
|
||||
parts: new fields.ArrayField(new fields.EmbeddedDataField(DHDamageData))
|
||||
};
|
||||
super(healingFields, options, context);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,29 @@
|
|||
const fields = foundry.data.fields;
|
||||
|
||||
export default class MacroField extends fields.DocumentUUIDField {
|
||||
/**
|
||||
* Action Workflow order
|
||||
*/
|
||||
static order = 70;
|
||||
|
||||
/** @inheritDoc */
|
||||
constructor(context = {}) {
|
||||
super({ type: "Macro" }, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Macro Action Workflow part.
|
||||
* Must be called within Action context or similar or similar.
|
||||
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. Currently not used.
|
||||
*/
|
||||
static async execute(config) {
|
||||
const fixUUID = !this.macro.includes('Macro.') ? `Macro.${this.macro}` : this.macro,
|
||||
macro = await fromUuid(fixUUID);
|
||||
try {
|
||||
if (!macro) throw new Error(`No macro found for the UUID: ${this.macro}.`);
|
||||
macro.execute();
|
||||
} catch (error) {
|
||||
ui.notifications.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
const fields = foundry.data.fields;
|
||||
|
||||
export default class RangeField extends fields.StringField {
|
||||
|
||||
/** @inheritDoc */
|
||||
constructor(context = {}) {
|
||||
const options = {
|
||||
choices: CONFIG.DH.GENERAL.range,
|
||||
|
|
@ -11,7 +13,12 @@ export default class RangeField extends fields.StringField {
|
|||
super(options, context);
|
||||
}
|
||||
|
||||
static prepareConfig(config) {
|
||||
return true;
|
||||
/**
|
||||
* Update Action Workflow config object.
|
||||
* NOT YET IMPLEMENTED.
|
||||
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
||||
*/
|
||||
prepareConfig(config) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,29 +71,6 @@ export class DHActionRollData extends foundry.abstract.DataModel {
|
|||
const modifiers = [];
|
||||
if (!this.parent?.actor) return modifiers;
|
||||
switch (this.parent.actor.type) {
|
||||
case 'character':
|
||||
const spellcastingTrait =
|
||||
this.type === 'spellcast'
|
||||
? (this.parent.actor?.system?.spellcastModifierTrait?.key ?? 'agility')
|
||||
: null;
|
||||
const trait =
|
||||
this.useDefault || !this.trait
|
||||
? (spellcastingTrait ?? this.parent.item.system.attack?.roll?.trait ?? 'agility')
|
||||
: this.trait;
|
||||
if (
|
||||
this.type === CONFIG.DH.GENERAL.rollTypes.attack.id ||
|
||||
this.type === CONFIG.DH.GENERAL.rollTypes.trait.id
|
||||
)
|
||||
modifiers.push({
|
||||
label: `DAGGERHEART.CONFIG.Traits.${trait}.name`,
|
||||
value: this.parent.actor.system.traits[trait].value
|
||||
});
|
||||
else if (this.type === CONFIG.DH.GENERAL.rollTypes.spellcast.id)
|
||||
modifiers.push({
|
||||
label: `DAGGERHEART.CONFIG.RollTypes.spellcast.name`,
|
||||
value: this.parent.actor.system.spellcastModifier
|
||||
});
|
||||
break;
|
||||
case 'companion':
|
||||
case 'adversary':
|
||||
if (this.type === CONFIG.DH.GENERAL.rollTypes.attack.id)
|
||||
|
|
@ -107,10 +84,74 @@ export class DHActionRollData extends foundry.abstract.DataModel {
|
|||
}
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
get rollTrait() {
|
||||
if(this.parent?.actor?.type !== "character") return null;
|
||||
switch (this.type) {
|
||||
case CONFIG.DH.GENERAL.rollTypes.spellcast.id:
|
||||
return this.parent.actor?.system?.spellcastModifierTrait?.key ?? 'agility';
|
||||
case CONFIG.DH.GENERAL.rollTypes.attack.id:
|
||||
case CONFIG.DH.GENERAL.rollTypes.trait.id:
|
||||
return this.useDefault || !this.trait
|
||||
? this.parent.item.system.attack?.roll?.trait ?? 'agility'
|
||||
: this.trait;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default class RollField extends fields.EmbeddedDataField {
|
||||
/**
|
||||
* Action Workflow order
|
||||
*/
|
||||
static order = 10;
|
||||
|
||||
/** @inheritDoc */
|
||||
constructor(options, context = {}) {
|
||||
super(DHActionRollData, options, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Roll Action Workflow part.
|
||||
* Must be called within Action context or similar.
|
||||
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
||||
*/
|
||||
static async execute(config) {
|
||||
if(!config.hasRoll) return;
|
||||
config = await this.actor.diceRoll(config);
|
||||
if(!config) return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Action Workflow config object.
|
||||
* Must be called within Action context.
|
||||
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
||||
*/
|
||||
prepareConfig(config) {
|
||||
if(!config.hasRoll) return;
|
||||
|
||||
config.dialog.configure = RollField.getAutomation() ? !config.dialog.configure : config.dialog.configure;
|
||||
|
||||
const roll = {
|
||||
baseModifiers: this.roll.getModifier(),
|
||||
label: 'Attack',
|
||||
type: this.roll?.type,
|
||||
trait: this.roll?.rollTrait,
|
||||
difficulty: this.roll?.difficulty,
|
||||
formula: this.roll.getFormula(),
|
||||
advantage: CONFIG.DH.ACTIONS.advantageState[this.roll.advState].value
|
||||
};
|
||||
if (this.roll.type === 'diceSet' || !this.hasRoll) roll.lite = true;
|
||||
|
||||
config.roll = roll;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the automation setting for execute method for current user role
|
||||
* @returns {boolean} If execute should be triggered automatically
|
||||
*/
|
||||
static getAutomation() {
|
||||
return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.roll.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.roll.players)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,14 @@
|
|||
import { abilities } from "../../../config/actorConfig.mjs";
|
||||
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
export default class SaveField extends fields.SchemaField {
|
||||
/**
|
||||
* Action Workflow order
|
||||
*/
|
||||
static order = 50;
|
||||
|
||||
/** @inheritDoc */
|
||||
constructor(options = {}, context = {}) {
|
||||
const saveFields = {
|
||||
trait: new fields.StringField({
|
||||
|
|
@ -16,4 +24,151 @@ export default class SaveField extends fields.SchemaField {
|
|||
};
|
||||
super(saveFields, options, context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reaction Roll Action Workflow part.
|
||||
* Must be called within Action context or similar.
|
||||
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
||||
* @param {object[]} [targets=null] Array of targets to override pre-selected ones.
|
||||
* @param {boolean} [force=false] If the method should be executed outside of Action workflow, for ChatMessage button for example.
|
||||
*/
|
||||
static async execute(config, targets = null, force = false) {
|
||||
if(!config.hasSave) return;
|
||||
let message = config.message ?? ui.chat.collection.get(config.parent?._id);
|
||||
|
||||
if(!message) {
|
||||
const roll = new CONFIG.Dice.daggerheart.DHRoll('');
|
||||
roll._evaluated = true;
|
||||
message = config.message = await CONFIG.Dice.daggerheart.DHRoll.toMessage(roll, config);
|
||||
}
|
||||
if(SaveField.getAutomation() !== CONFIG.DH.SETTINGS.actionAutomationChoices.never.id || force) {
|
||||
targets ??= config.targets.filter(t => !config.hasRoll || t.hit);
|
||||
await SaveField.rollAllSave.call(this, targets, config.event, message);
|
||||
} else return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Roll a Reaction Roll for all targets. Send a query to the owner if the User is not.
|
||||
* Must be called within Action context.
|
||||
* @param {object[]} targets Array of formatted targets.
|
||||
* @param {Event} event Triggering event
|
||||
* @param {ChatMessage} message The ChatMessage the triggered button comes from.
|
||||
*/
|
||||
static async rollAllSave(targets, event, message) {
|
||||
if(!targets) return;
|
||||
return new Promise(resolve => {
|
||||
const aPromise = [];
|
||||
targets.forEach(target => {
|
||||
aPromise.push(
|
||||
new Promise(async subResolve => {
|
||||
const actor = fromUuidSync(target.actorId);
|
||||
if(actor) {
|
||||
const rollSave = game.user === actor.owner ?
|
||||
SaveField.rollSave.call(this, actor, event)
|
||||
: actor.owner
|
||||
.query('reactionRoll', {
|
||||
actionId: this.uuid,
|
||||
actorId: actor.uuid,
|
||||
event,
|
||||
message
|
||||
});
|
||||
const result = await rollSave;
|
||||
await SaveField.updateSaveMessage.call(this, result, message, target.id);
|
||||
subResolve();
|
||||
} else subResolve();
|
||||
})
|
||||
)
|
||||
});
|
||||
Promise.all(aPromise).then(result => resolve());
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Roll a Reaction Roll for the specified Actor against the Action difficulty.
|
||||
* Must be called within Action context.
|
||||
* @param {*} actor Actor document
|
||||
* @param {Event} event Triggering event
|
||||
* @returns {object} Actor diceRoll config result.
|
||||
*/
|
||||
static async rollSave(actor, event) {
|
||||
if (!actor) return;
|
||||
const title = actor.isNPC
|
||||
? game.i18n.localize('DAGGERHEART.GENERAL.reactionRoll')
|
||||
: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
|
||||
ability: game.i18n.localize(abilities[this.save.trait]?.label)
|
||||
}),
|
||||
rollConfig = {
|
||||
event,
|
||||
title,
|
||||
roll: {
|
||||
trait: this.save.trait,
|
||||
difficulty: this.save.difficulty ?? this.actor?.baseSaveDifficulty,
|
||||
type: 'trait'
|
||||
},
|
||||
actionType: 'reaction',
|
||||
hasRoll: true,
|
||||
data: actor.getRollData()
|
||||
};
|
||||
if(SaveField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.always.id) rollConfig.dialog = { configure: false };
|
||||
return actor.diceRoll(rollConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a Roll ChatMessage for a token according to his Reaction Roll result.
|
||||
* @param {object} result Result from the Reaction Roll
|
||||
* @param {object} message ChatMessage to update
|
||||
* @param {string} targetId Token ID
|
||||
*/
|
||||
static async updateSaveMessage(result, message, targetId) {
|
||||
if (!result) return;
|
||||
const updateMsg = async function(message, targetId, result) {
|
||||
// setTimeout(async () => {
|
||||
const chatMessage = ui.chat.collection.get(message._id),
|
||||
changes = {
|
||||
flags: {
|
||||
[game.system.id]: {
|
||||
reactionRolls: {
|
||||
[targetId]:
|
||||
{
|
||||
result: result.roll.total,
|
||||
success: result.roll.success
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
await chatMessage.update(changes);
|
||||
// }, 100);
|
||||
};
|
||||
if (game.modules.get('dice-so-nice')?.active)
|
||||
game.dice3d.waitFor3DAnimationByMessageID(result.message.id ?? result.message._id).then(async () => await updateMsg(message, targetId, result));
|
||||
else await updateMsg(message, targetId, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the automation setting for execute method for current user role
|
||||
* @returns {string} Id from settingsConfig.mjs actionAutomationChoices
|
||||
*/
|
||||
static getAutomation() {
|
||||
return (game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.save.gm) || (!game.user.isGM && game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).roll.save.players)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a query to an Actor owner to roll a Reaction Roll then send back the result.
|
||||
* @param {object} param0
|
||||
* @param {string} param0.actionId Action ID
|
||||
* @param {string} param0.actorId Actor ID
|
||||
* @param {Event} param0.event Triggering event
|
||||
* @param {ChatMessage} param0.message Chat Message to update
|
||||
* @returns
|
||||
*/
|
||||
static rollSaveQuery({ actionId, actorId, event, message }) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const actor = await fromUuid(actorId),
|
||||
action = await fromUuid(actionId);
|
||||
if (!actor || !actor?.isOwner) reject();
|
||||
SaveField.rollSave.call(action, actor, event, message)
|
||||
.then(result => resolve(result));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
const fields = foundry.data.fields;
|
||||
|
||||
export default class TargetField extends fields.SchemaField {
|
||||
|
||||
/** @inheritDoc */
|
||||
constructor(options = {}, context = {}) {
|
||||
const targetFields = {
|
||||
type: new fields.StringField({
|
||||
|
|
@ -13,44 +15,70 @@ export default class TargetField extends fields.SchemaField {
|
|||
super(targetFields, options, context);
|
||||
}
|
||||
|
||||
static prepareConfig(config) {
|
||||
if (!this.target?.type) return [];
|
||||
/**
|
||||
* Update Action Workflow config object.
|
||||
* Must be called within Action context.
|
||||
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
||||
*/
|
||||
prepareConfig(config) {
|
||||
if (!this.target?.type) return config.targets = [];
|
||||
config.hasTarget = true;
|
||||
let targets;
|
||||
// If the Action is configured as self-targeted, set targets as the owner.
|
||||
if (this.target?.type === CONFIG.DH.GENERAL.targetTypes.self.id)
|
||||
targets = [this.actor.token ?? this.actor.prototypeToken];
|
||||
else {
|
||||
targets = Array.from(game.user.targets);
|
||||
if (this.target.type !== CONFIG.DH.GENERAL.targetTypes.any.id) {
|
||||
targets = targets.filter(t => TargetField.isTargetFriendly.call(this, t));
|
||||
targets = targets.filter(target => TargetField.isTargetFriendly(this.actor, target, this.target.type));
|
||||
if (this.target.amount && targets.length > this.target.amount) targets = [];
|
||||
}
|
||||
}
|
||||
config.targets = targets.map(t => TargetField.formatTarget.call(this, t));
|
||||
const hasTargets = TargetField.checkTargets.call(this, this.target.amount, config.targets);
|
||||
if (config.isFastForward && !hasTargets)
|
||||
return ui.notifications.warn('Too many targets selected for that actions.');
|
||||
return hasTargets;
|
||||
if (config.dialog.configure === false && !hasTargets) {
|
||||
ui.notifications.warn('Too many targets selected for that actions.');
|
||||
return hasTargets;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the number of selected targets respect the amount set in the Action.
|
||||
* NOT YET IMPLEMENTED. Will be with Target Picker.
|
||||
* @param {number} amount Max amount of targets configured in the action.
|
||||
* @param {*[]} targets Array of targeted tokens.
|
||||
* @returns {boolean} If the amount of targeted tokens does not exceed action configured one.
|
||||
*/
|
||||
static checkTargets(amount, targets) {
|
||||
return true;
|
||||
// return !amount || (targets.length > amount);
|
||||
}
|
||||
|
||||
static isTargetFriendly(target) {
|
||||
const actorDisposition = this.actor.token
|
||||
? this.actor.token.disposition
|
||||
: this.actor.prototypeToken.disposition,
|
||||
/**
|
||||
* Compare 2 Actors disposition between each other
|
||||
* @param {*} actor First actor document.
|
||||
* @param {*} target Second actor document.
|
||||
* @param {string} type Disposition id to compare (friendly/hostile).
|
||||
* @returns {boolean} If both actors respect the provided type.
|
||||
*/
|
||||
static isTargetFriendly(actor, target, type) {
|
||||
const actorDisposition = actor.token
|
||||
? actor.token.disposition
|
||||
: actor.prototypeToken.disposition,
|
||||
targetDisposition = target.document.disposition;
|
||||
return (
|
||||
(this.target.type === CONFIG.DH.GENERAL.targetTypes.friendly.id &&
|
||||
(type === CONFIG.DH.GENERAL.targetTypes.friendly.id &&
|
||||
actorDisposition === targetDisposition) ||
|
||||
(this.target.type === CONFIG.DH.GENERAL.targetTypes.hostile.id &&
|
||||
(type === CONFIG.DH.GENERAL.targetTypes.hostile.id &&
|
||||
actorDisposition + targetDisposition === 0)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format actor to useful datas for Action roll workflow.
|
||||
* @param {*} actor Actor object to format.
|
||||
* @returns {*} Formatted Actor.
|
||||
*/
|
||||
static formatTarget(actor) {
|
||||
return {
|
||||
id: actor.id,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,12 @@ import FormulaField from '../formulaField.mjs';
|
|||
const fields = foundry.data.fields;
|
||||
|
||||
export default class UsesField extends fields.SchemaField {
|
||||
/**
|
||||
* Action Workflow order
|
||||
*/
|
||||
static order = 160;
|
||||
|
||||
/** @inheritDoc */
|
||||
constructor(options = {}, context = {}) {
|
||||
const usesFields = {
|
||||
value: new fields.NumberField({ nullable: true, initial: null }),
|
||||
|
|
@ -20,15 +26,45 @@ export default class UsesField extends fields.SchemaField {
|
|||
super(usesFields, options, context);
|
||||
}
|
||||
|
||||
static prepareConfig(config) {
|
||||
/**
|
||||
* Uses Consumption Action Workflow part.
|
||||
* Increment Action spent uses by 1.
|
||||
* Must be called within Action context or similar or similar.
|
||||
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
||||
* @param {boolean} [successCost=false] Consume only resources configured as "On Success only" if not already consumed.
|
||||
*/
|
||||
static async execute(config, successCost = false) {
|
||||
if (
|
||||
config.uses?.enabled &&
|
||||
((!successCost && (!config.uses?.consumeOnSuccess || config.roll?.success)) ||
|
||||
(successCost && config.uses?.consumeOnSuccess))
|
||||
)
|
||||
this.update({ 'uses.value': this.uses.value + 1 });
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Action Workflow config object.
|
||||
* Must be called within Action context.
|
||||
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
|
||||
* @returns {boolean} Return false if fast-forwarded and no more uses.
|
||||
*/
|
||||
prepareConfig(config) {
|
||||
const uses = this.uses?.max ? foundry.utils.deepClone(this.uses) : null;
|
||||
if (uses && !uses.value) uses.value = 0;
|
||||
config.uses = uses;
|
||||
const hasUses = UsesField.hasUses.call(this, config.uses);
|
||||
if (config.isFastForward && !hasUses) return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.actionNoUsesRemaining'));
|
||||
return hasUses;
|
||||
if (config.dialog.configure === false && !hasUses) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.actionNoUsesRemaining'));
|
||||
return hasUses;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare Uses object for Action Workflow
|
||||
* Must be called within Action context.
|
||||
* @param {object} uses
|
||||
* @returns {object}
|
||||
*/
|
||||
static calcUses(uses) {
|
||||
if (!uses) return null;
|
||||
return {
|
||||
|
|
@ -38,6 +74,12 @@ export default class UsesField extends fields.SchemaField {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the Action still get atleast one unspent uses.
|
||||
* Must be called within Action context.
|
||||
* @param {*} uses
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static hasUses(uses) {
|
||||
if (!uses) return true;
|
||||
let max = uses.max ?? 0;
|
||||
|
|
|
|||
|
|
@ -52,8 +52,7 @@ export default class DHSubclass extends BaseDataItem {
|
|||
|
||||
async _preCreate(data, options, user) {
|
||||
if (this.actor?.type === 'character') {
|
||||
const dataUuid =
|
||||
data.uuid ?? (data.folder ? `Compendium.daggerheart.subclasses.Item.${data._id}` : `Item.${data._id}`);
|
||||
const dataUuid = data.uuid ?? data._stats.compendiumSource ?? `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'));
|
||||
|
|
|
|||
|
|
@ -80,6 +80,72 @@ export default class DhAutomation extends foundry.abstract.DataModel {
|
|||
initial: CONFIG.DH.GENERAL.defeatedConditions.defeated.id,
|
||||
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.defeated.companionDefault.label'
|
||||
})
|
||||
}),
|
||||
roll: new fields.SchemaField({
|
||||
roll: new fields.SchemaField({
|
||||
gm: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.GENERAL.gm'
|
||||
}),
|
||||
players: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.GENERAL.player.plurial'
|
||||
})
|
||||
}),
|
||||
damage: new fields.SchemaField({
|
||||
gm: new fields.StringField({
|
||||
required: true,
|
||||
initial: "never",
|
||||
choices: CONFIG.DH.SETTINGS.actionAutomationChoices,
|
||||
label: 'DAGGERHEART.GENERAL.gm'
|
||||
}),
|
||||
players: new fields.StringField({
|
||||
required: true,
|
||||
initial: "never",
|
||||
choices: CONFIG.DH.SETTINGS.actionAutomationChoices,
|
||||
label: 'DAGGERHEART.GENERAL.player.plurial'
|
||||
})
|
||||
}),
|
||||
save: new fields.SchemaField({
|
||||
gm: new fields.StringField({
|
||||
required: true,
|
||||
initial: "never",
|
||||
choices: CONFIG.DH.SETTINGS.actionAutomationChoices,
|
||||
label: 'DAGGERHEART.GENERAL.gm'
|
||||
}),
|
||||
players: new fields.StringField({
|
||||
required: true,
|
||||
initial: "never",
|
||||
choices: CONFIG.DH.SETTINGS.actionAutomationChoices,
|
||||
label: 'DAGGERHEART.GENERAL.player.plurial'
|
||||
})
|
||||
}),
|
||||
damageApply: new fields.SchemaField({
|
||||
gm: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.GENERAL.gm'
|
||||
}),
|
||||
players: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.GENERAL.player.plurial'
|
||||
})
|
||||
}),
|
||||
effect: new fields.SchemaField({
|
||||
gm: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.GENERAL.gm'
|
||||
}),
|
||||
players: new fields.BooleanField({
|
||||
required: true,
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.GENERAL.player.plurial'
|
||||
})
|
||||
})
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,7 +128,9 @@ export default class D20Roll extends DHRoll {
|
|||
applyBaseBonus() {
|
||||
const modifiers = foundry.utils.deepClone(this.options.roll.baseModifiers) ?? [];
|
||||
|
||||
modifiers.push(...this.getBonus(`roll.${this.options.type}`, `${this.options.type?.capitalize()} Bonus`));
|
||||
modifiers.push(
|
||||
...this.getBonus(`roll.${this.options.actionType}`, `${this.options.actionType?.capitalize()} Bonus`)
|
||||
);
|
||||
modifiers.push(
|
||||
...this.getBonus(`roll.${this.options.roll.type}`, `${this.options.roll.type?.capitalize()} Bonus`)
|
||||
);
|
||||
|
|
@ -138,7 +140,7 @@ export default class D20Roll extends DHRoll {
|
|||
|
||||
static postEvaluate(roll, config = {}) {
|
||||
const data = super.postEvaluate(roll, config);
|
||||
data.type = config.roll?.type;
|
||||
data.type = config.actionType;
|
||||
data.difficulty = config.roll.difficulty;
|
||||
if (config.targets?.length) {
|
||||
config.targets.forEach(target => {
|
||||
|
|
@ -147,6 +149,7 @@ export default class D20Roll extends DHRoll {
|
|||
});
|
||||
data.success = config.targets.some(target => target.hit);
|
||||
} else if (config.roll.difficulty) data.success = roll.isCritical || roll.total >= config.roll.difficulty;
|
||||
config.successConsumed = data.success;
|
||||
|
||||
data.advantage = {
|
||||
type: config.roll.advantage,
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ export default class DamageRoll extends DHRoll {
|
|||
static DefaultDialog = DamageDialog;
|
||||
|
||||
static async buildEvaluate(roll, config = {}, message = {}) {
|
||||
if (config.dialog.configure === false) roll.constructFormula(config);
|
||||
if (config.evaluate !== false) for (const roll of config.roll) await roll.roll.evaluate();
|
||||
|
||||
roll._evaluated = true;
|
||||
|
|
@ -46,9 +47,8 @@ export default class DamageRoll extends DHRoll {
|
|||
);
|
||||
}
|
||||
await super.buildPost(roll, config, message);
|
||||
if (config.source?.message) {
|
||||
if (config.source?.message)
|
||||
chatMessage.update({ 'system.damage': config.damage });
|
||||
}
|
||||
}
|
||||
|
||||
static unifyDamageRoll(rolls) {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ export default class DHRoll extends Roll {
|
|||
static async buildConfigure(config = {}, message = {}) {
|
||||
config.hooks = [...this.getHooks(), ''];
|
||||
config.dialog ??= {};
|
||||
|
||||
for (const hook of config.hooks) {
|
||||
if (Hooks.call(`${CONFIG.DH.id}.preRoll${hook.capitalize()}`, config, message) === false) return null;
|
||||
}
|
||||
|
|
@ -45,10 +46,7 @@ export default class DHRoll extends Roll {
|
|||
}
|
||||
|
||||
for (const hook of config.hooks) {
|
||||
if (
|
||||
Hooks.call(`${CONFIG.DH.id}.post${hook.capitalize()}RollConfiguration`, roll, config, message) === false
|
||||
)
|
||||
return [];
|
||||
if (Hooks.call(`${CONFIG.DH.id}.post${hook.capitalize()}RollConfiguration`, roll, config, message) === false) return [];
|
||||
}
|
||||
return roll;
|
||||
}
|
||||
|
|
@ -88,11 +86,12 @@ export default class DHRoll extends Roll {
|
|||
type: this.messageType,
|
||||
user: game.user.id,
|
||||
title: roll.title,
|
||||
speaker: cls.getSpeaker(),
|
||||
speaker: cls.getSpeaker({ actor: roll.data?.parent }),
|
||||
sound: config.mute ? null : CONFIG.sounds.dice,
|
||||
system: config,
|
||||
rolls: [roll]
|
||||
};
|
||||
|
||||
config.selectedRollMode ??= game.settings.get('core', 'rollMode');
|
||||
|
||||
if (roll._evaluated) {
|
||||
|
|
@ -226,7 +225,7 @@ export const registerRollDiceHooks = () => {
|
|||
if (
|
||||
!config.source?.actor ||
|
||||
(game.user.isGM ? !hopeFearAutomation.gm : !hopeFearAutomation.players) ||
|
||||
config.roll.type === 'reaction'
|
||||
config.actionType === 'reaction'
|
||||
)
|
||||
return;
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export default class DualityRoll extends D20Roll {
|
|||
|
||||
get title() {
|
||||
return game.i18n.localize(
|
||||
`DAGGERHEART.GENERAL.${this.options?.roll?.type === CONFIG.DH.ITEM.actionTypes.reaction.id ? 'reactionRoll' : 'dualityRoll'}`
|
||||
`DAGGERHEART.GENERAL.${this.options?.actionType === CONFIG.DH.ITEM.actionTypes.reaction.id ? 'reactionRoll' : 'dualityRoll'}`
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -153,10 +153,10 @@ export default class DualityRoll extends D20Roll {
|
|||
|
||||
applyBaseBonus() {
|
||||
const modifiers = super.applyBaseBonus();
|
||||
|
||||
if (this.options.roll.trait && this.data.traits[this.options.roll.trait])
|
||||
|
||||
if (this.options.roll.trait && this.data.traits?.[this.options.roll.trait])
|
||||
modifiers.unshift({
|
||||
label: `DAGGERHEART.CONFIG.Traits.${this.options.roll.trait}.name`,
|
||||
label: this.options.roll.type === CONFIG.DH.GENERAL.rollTypes.spellcast.id ? "DAGGERHEART.CONFIG.RollTypes.spellcast.name" : `DAGGERHEART.CONFIG.Traits.${this.options.roll.trait}.name`,
|
||||
value: this.data.traits[this.options.roll.trait].value
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export default class DhpActor extends Actor {
|
|||
get owner() {
|
||||
const user =
|
||||
this.hasPlayerOwner && game.users.players.find(u => this.testUserPermission(u, 'OWNER') && u.active);
|
||||
if (!user) return game.user.isGM ? game.user : null;
|
||||
if (!user) return game.users.activeGM;
|
||||
return user;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { emitAsGM, GMUpdateEvent } from "../systemRegistration/socket.mjs";
|
||||
|
||||
export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
||||
targetHook = null;
|
||||
|
||||
|
|
@ -102,20 +104,30 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
|||
});
|
||||
if (itemDesc && autoExpandRoll.desc) itemDesc.setAttribute('open', '');
|
||||
}
|
||||
|
||||
if (!game.user.isGM) {
|
||||
const applyButtons = html.querySelector('.apply-buttons');
|
||||
|
||||
if(!this.isAuthor && !this.speakerActor?.isOwner) {
|
||||
const applyButtons = html.querySelector(".apply-buttons");
|
||||
applyButtons?.remove();
|
||||
if (!this.isAuthor && !this.speakerActor?.isOwner) {
|
||||
const buttons = html.querySelectorAll('.ability-card-footer > .ability-use-button');
|
||||
buttons.forEach(b => b.remove());
|
||||
}
|
||||
const buttons = html.querySelectorAll(".ability-card-footer > .ability-use-button");
|
||||
buttons.forEach(b => b.remove());
|
||||
}
|
||||
}
|
||||
|
||||
addChatListeners(html) {
|
||||
html.querySelectorAll('.duality-action-damage').forEach(element =>
|
||||
element.addEventListener('click', this.onRollDamage.bind(this))
|
||||
);
|
||||
|
||||
html.querySelectorAll('.damage-button').forEach(element =>
|
||||
element.addEventListener('click', this.onDamage.bind(this))
|
||||
element.addEventListener('click', this.onApplyDamage.bind(this))
|
||||
);
|
||||
|
||||
html.querySelectorAll('.target-save').forEach(element =>
|
||||
element.addEventListener('click', this.onRollSave.bind(this))
|
||||
);
|
||||
|
||||
html.querySelectorAll('.roll-all-save-button').forEach(element =>
|
||||
element.addEventListener('click', this.onRollAllSave.bind(this))
|
||||
);
|
||||
|
||||
html.querySelectorAll('.duality-action-effect').forEach(element =>
|
||||
|
|
@ -133,17 +145,21 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
|||
});
|
||||
}
|
||||
|
||||
getTargetList() {
|
||||
const targets = this.system.hitTargets ?? [];
|
||||
return targets.map(target => game.canvas.tokens.documentCollection.find(t => t.actor?.uuid === target.actorId));
|
||||
async onRollDamage(event) {
|
||||
event.stopPropagation();
|
||||
const config = foundry.utils.deepClone(this.system);
|
||||
config.event = event;
|
||||
this.system.action?.workflow.get("damage")?.execute(config, this._id, true);
|
||||
}
|
||||
|
||||
async onDamage(event) {
|
||||
async onApplyDamage(event) {
|
||||
event.stopPropagation();
|
||||
const targets = this.getTargetList();
|
||||
const targets = this.filterPermTargets(this.system.hitTargets),
|
||||
config = foundry.utils.deepClone(this.system);
|
||||
config.event = event;
|
||||
|
||||
if (this.system.onSave) {
|
||||
const pendingingSaves = this.system.hitTargets.filter(t => t.saved.success === null);
|
||||
const pendingingSaves = targets.filter(t => t.saved.success === null);
|
||||
if (pendingingSaves.length) {
|
||||
const confirm = await foundry.applications.api.DialogV2.confirm({
|
||||
window: { title: 'Pending Reaction Rolls found' },
|
||||
|
|
@ -154,62 +170,62 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
|
|||
}
|
||||
|
||||
if (targets.length === 0)
|
||||
return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
|
||||
return ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelectedOrPerm'));
|
||||
|
||||
this.consumeOnSuccess();
|
||||
this.system.action?.workflow.get("applyDamage")?.execute(config, targets, true);
|
||||
}
|
||||
|
||||
for (let target of targets) {
|
||||
let damages = foundry.utils.deepClone(this.system.damage);
|
||||
if (
|
||||
!this.system.hasHealing &&
|
||||
this.system.onSave &&
|
||||
this.system.hitTargets.find(t => t.id === target.id)?.saved?.success === true
|
||||
) {
|
||||
const mod = CONFIG.DH.ACTIONS.damageOnSave[this.system.onSave]?.mod ?? 1;
|
||||
Object.entries(damages).forEach(([k, v]) => {
|
||||
v.total = 0;
|
||||
v.parts.forEach(part => {
|
||||
part.total = Math.ceil(part.total * mod);
|
||||
v.total += part.total;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this.consumeOnSuccess();
|
||||
if (this.system.hasHealing) target.actor.takeHealing(damages);
|
||||
else target.actor.takeDamage(damages, this.system.isDirect);
|
||||
async onRollSave(event) {
|
||||
event.stopPropagation();
|
||||
const tokenId = event.target.closest('[data-token]')?.dataset.token,
|
||||
token = game.canvas.tokens.get(tokenId);
|
||||
if (!token?.actor || !token.isOwner) return true;
|
||||
if (this.system.source.item && this.system.source.action) {
|
||||
const action = this.system.action;
|
||||
if (!action || !action?.hasSave) return;
|
||||
game.system.api.fields.ActionFields.SaveField.rollSave.call(action, token.actor, event).then(result =>
|
||||
emitAsGM(
|
||||
GMUpdateEvent.UpdateSaveMessage,
|
||||
game.system.api.fields.ActionFields.SaveField.updateSaveMessage.bind(action, result, this, token.id),
|
||||
{
|
||||
action: action.uuid,
|
||||
message: this._id,
|
||||
token: token.id,
|
||||
result
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getAction(actor, itemId, actionId) {
|
||||
const item = actor.items.get(itemId),
|
||||
action =
|
||||
actor.system.attack?._id === actionId
|
||||
? actor.system.attack
|
||||
: item.system.attack?._id === actionId
|
||||
? item.system.attack
|
||||
: item?.system?.actions?.get(actionId);
|
||||
return action;
|
||||
async onRollAllSave(event) {
|
||||
event.stopPropagation();
|
||||
if (!game.user.isGM) return;
|
||||
const targets = this.system.hitTargets,
|
||||
config = foundry.utils.deepClone(this.system);
|
||||
config.event = event;
|
||||
this.system.action?.workflow.get("save")?.execute(config, targets, true);
|
||||
}
|
||||
|
||||
async onApplyEffect(event) {
|
||||
event.stopPropagation();
|
||||
const actor = await foundry.utils.fromUuid(this.system.source.actor);
|
||||
if (!actor || !game.user.isGM) return true;
|
||||
if (this.system.source.item && this.system.source.action) {
|
||||
const action = this.getAction(actor, this.system.source.item, this.system.source.action);
|
||||
if (!action || !action?.applyEffects) return;
|
||||
const targets = this.getTargetList();
|
||||
if (targets.length === 0)
|
||||
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
|
||||
this.consumeOnSuccess();
|
||||
await action.applyEffects(event, this, targets);
|
||||
}
|
||||
const targets = this.filterPermTargets(this.system.hitTargets),
|
||||
config = foundry.utils.deepClone(this.system);
|
||||
config.event = event;
|
||||
if (targets.length === 0)
|
||||
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelectedOrPerm'));
|
||||
this.consumeOnSuccess();
|
||||
this.system.action?.workflow.get("effects")?.execute(config, targets, true);
|
||||
}
|
||||
|
||||
filterPermTargets(targets) {
|
||||
return targets.filter(t => fromUuidSync(t.actorId)?.canUserModify(game.user, "update"))
|
||||
}
|
||||
|
||||
consumeOnSuccess() {
|
||||
if (!this.system.successConsumed && !this.targetSelection) {
|
||||
const action = this.system.action;
|
||||
if (action) action.consume(this.system, true);
|
||||
}
|
||||
if (!this.system.successConsumed && !this.targetSelection)
|
||||
this.system.action?.consume(this.system, true);
|
||||
}
|
||||
|
||||
hoverTarget(event) {
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ function getDamageMessage(damage, type, inline, defaultElement) {
|
|||
|
||||
const dualityElement = document.createElement('span');
|
||||
dualityElement.innerHTML = `
|
||||
<button class="enriched-damage-button${inline ? ' inline' : ''}"
|
||||
<button type="button" class="enriched-damage-button${inline ? ' inline' : ''}"
|
||||
data-value="${damage}"
|
||||
data-type="${type}"
|
||||
data-tooltip="${game.i18n.localize('DAGGERHEART.GENERAL.damage')}"
|
||||
|
|
@ -71,6 +71,11 @@ export const renderDamageButton = async event => {
|
|||
title: game.i18n.localize('Damage Roll'),
|
||||
data: { bonuses: [] },
|
||||
source: {},
|
||||
hasDamage: true,
|
||||
hasTarget: true,
|
||||
targets: Array.from(game.user.targets).map(t =>
|
||||
game.system.api.fields.ActionFields.TargetField.formatTarget(t)
|
||||
),
|
||||
roll: [
|
||||
{
|
||||
formula: value,
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ function getDualityMessage(roll, flavor) {
|
|||
|
||||
const dualityElement = document.createElement('span');
|
||||
dualityElement.innerHTML = `
|
||||
<button class="duality-roll-button${roll.inline ? ' inline' : ''}"
|
||||
<button type="button" class="duality-roll-button${roll.inline ? ' inline' : ''}"
|
||||
data-title="${label}"
|
||||
data-label="${dataLabel}"
|
||||
data-reaction="${roll.reaction ? 'true' : 'false'}"
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export default function DhTemplateEnricher(match, _options) {
|
|||
|
||||
const templateElement = document.createElement('span');
|
||||
templateElement.innerHTML = `
|
||||
<button class="measured-template-button${inline ? ' inline' : ''}" data-type="${type}" data-range="${range}">
|
||||
<button type="button" class="measured-template-button${inline ? ' inline' : ''}" data-type="${type}" data-range="${range}">
|
||||
${label} - ${game.i18n.localize(`DAGGERHEART.CONFIG.Range.${range}.name`)}
|
||||
</button>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export const socketEvent = {
|
|||
|
||||
export const GMUpdateEvent = {
|
||||
UpdateDocument: 'DhGMUpdateDocument',
|
||||
UpdateEffect: 'DhGMUpdateEffect',
|
||||
UpdateSetting: 'DhGMUpdateSetting',
|
||||
UpdateFear: 'DhGMUpdateFear',
|
||||
UpdateSaveMessage: 'DhGMUpdateSaveMessage'
|
||||
|
|
@ -37,9 +38,12 @@ export const registerSocketHooks = () => {
|
|||
const document = data.uuid ? await fromUuid(data.uuid) : null;
|
||||
switch (data.action) {
|
||||
case GMUpdateEvent.UpdateDocument:
|
||||
if (document && data.update) {
|
||||
if (document && data.update)
|
||||
await document.update(data.update);
|
||||
}
|
||||
break;
|
||||
case GMUpdateEvent.UpdateEffect:
|
||||
if (document && data.update)
|
||||
await game.system.api.fields.ActionFields.EffectsField.applyEffects.call(document, data.update);
|
||||
break;
|
||||
case GMUpdateEvent.UpdateSetting:
|
||||
await game.settings.set(CONFIG.DH.id, data.uuid, data.update);
|
||||
|
|
@ -78,7 +82,7 @@ export const registerSocketHooks = () => {
|
|||
|
||||
export const registerUserQueries = () => {
|
||||
CONFIG.queries.armorSlot = DamageReductionDialog.armorSlotQuery;
|
||||
CONFIG.queries.reactionRoll = game.system.api.models.actions.actionsTypes.base.rollSaveQuery;
|
||||
CONFIG.queries.reactionRoll = game.system.api.fields.ActionFields.SaveField.rollSaveQuery;
|
||||
};
|
||||
|
||||
export const emitAsGM = async (eventName, callback, update, uuid = null) => {
|
||||
|
|
|
|||
|
|
@ -23,12 +23,12 @@
|
|||
},
|
||||
"effects": [],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Mark Stress",
|
||||
"img": "icons/magic/defensive/shield-barrier-flaming-diamond-teal.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"originItemType": null,
|
||||
|
|
@ -111,10 +111,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753996513763,
|
||||
"modifiedTime": 1755394440308,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756040910699,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items!UFR67BUOhNGLFyg9"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,12 +31,12 @@
|
|||
},
|
||||
"effects": [],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Mark Stress",
|
||||
"img": "icons/creatures/abilities/wing-batlike-white-blue.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"originItemType": null,
|
||||
|
|
@ -113,10 +113,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753994723305,
|
||||
"modifiedTime": 1755394368487,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756040981649,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items!WquAjoOcso8lwySW"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
],
|
||||
"uses": {
|
||||
"value": null,
|
||||
"max": null,
|
||||
"max": "",
|
||||
"recovery": null
|
||||
},
|
||||
"effects": [
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
},
|
||||
"name": "Retract",
|
||||
"img": "icons/creatures/reptiles/turtle-shell-glowing-green.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"originItemType": null,
|
||||
|
|
@ -149,10 +149,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753580983699,
|
||||
"modifiedTime": 1755395623887,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756041050697,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "nDQZdIF2epKlhauX",
|
||||
"sort": 2200000,
|
||||
|
|
|
|||
|
|
@ -24,22 +24,22 @@
|
|||
],
|
||||
"uses": {
|
||||
"value": null,
|
||||
"max": null,
|
||||
"max": "",
|
||||
"recovery": null
|
||||
},
|
||||
"effects": [
|
||||
{
|
||||
"_id": "5TX0hHFKZHvBeWne",
|
||||
"_id": "A7l4JEBC1FFQajsN",
|
||||
"onSave": false
|
||||
}
|
||||
],
|
||||
"target": {
|
||||
"type": null,
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Gain Proficiency",
|
||||
"name": "Mark Stress",
|
||||
"img": "icons/creatures/abilities/bear-roar-bite-brown.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"originItemType": null,
|
||||
|
|
@ -51,7 +51,59 @@
|
|||
"artist": ""
|
||||
}
|
||||
},
|
||||
"effects": [],
|
||||
"effects": [
|
||||
{
|
||||
"name": "Rampage",
|
||||
"img": "icons/creatures/claws/claw-bear-paw-swipe-red.webp",
|
||||
"origin": "Compendium.daggerheart.beastforms.Item.8upqfcZvi7b5hRLE",
|
||||
"transfer": false,
|
||||
"_id": "A7l4JEBC1FFQajsN",
|
||||
"type": "base",
|
||||
"system": {
|
||||
"rangeDependence": {
|
||||
"enabled": false,
|
||||
"type": "withinRange",
|
||||
"target": "hostile",
|
||||
"range": "melee"
|
||||
}
|
||||
},
|
||||
"changes": [
|
||||
{
|
||||
"key": "system.proficiency",
|
||||
"mode": 2,
|
||||
"value": "1",
|
||||
"priority": null
|
||||
}
|
||||
],
|
||||
"disabled": false,
|
||||
"duration": {
|
||||
"startTime": null,
|
||||
"combat": null,
|
||||
"seconds": null,
|
||||
"rounds": null,
|
||||
"turns": null,
|
||||
"startRound": null,
|
||||
"startTurn": null
|
||||
},
|
||||
"description": "<p>Before you make an attack roll, you can mark a Stress to gain a +1 bonus to your Proficiency for that attack.</p>",
|
||||
"tint": "#ffffff",
|
||||
"statuses": [],
|
||||
"sort": 0,
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1756041194412,
|
||||
"modifiedTime": 1756041218646,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items.effects!8upqfcZvi7b5hRLE.A7l4JEBC1FFQajsN"
|
||||
}
|
||||
],
|
||||
"folder": "uU8bIoZvXge0rLaU",
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
|
|
@ -64,10 +116,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753582591417,
|
||||
"modifiedTime": 1755395649724,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756041228102,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "8upqfcZvi7b5hRLE",
|
||||
"sort": 2000000,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
],
|
||||
"uses": {
|
||||
"value": null,
|
||||
"max": null,
|
||||
"max": "",
|
||||
"recovery": null
|
||||
},
|
||||
"damage": {
|
||||
|
|
@ -58,7 +58,7 @@
|
|||
"includeBase": false
|
||||
},
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"effects": [],
|
||||
|
|
@ -84,7 +84,7 @@
|
|||
},
|
||||
"name": "Attack",
|
||||
"img": "icons/creatures/abilities/paw-print-orange.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"originItemType": null,
|
||||
|
|
@ -101,7 +101,7 @@
|
|||
"name": "Takedown",
|
||||
"img": "icons/creatures/abilities/paw-print-orange.webp",
|
||||
"origin": "Item.okjlLUwEdNOKeUk3",
|
||||
"transfer": false,
|
||||
"transfer": true,
|
||||
"_id": "hE6ciIusvKEtUQ8U",
|
||||
"type": "base",
|
||||
"system": {},
|
||||
|
|
@ -113,7 +113,7 @@
|
|||
"priority": null
|
||||
}
|
||||
],
|
||||
"disabled": false,
|
||||
"disabled": true,
|
||||
"duration": {
|
||||
"startTime": null,
|
||||
"combat": null,
|
||||
|
|
@ -123,7 +123,7 @@
|
|||
"startRound": null,
|
||||
"startTurn": null
|
||||
},
|
||||
"description": "",
|
||||
"description": "<p>You gain a +2 bonus to your Proficiency for this attack and the target must mark a Stress.</p>",
|
||||
"tint": "#ffffff",
|
||||
"statuses": [],
|
||||
"sort": 0,
|
||||
|
|
@ -132,10 +132,11 @@
|
|||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.346",
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"lastModifiedBy": null
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE",
|
||||
"modifiedTime": 1756041368153
|
||||
},
|
||||
"_key": "!items.effects!0ey4kM9ssj2otHvb.hE6ciIusvKEtUQ8U"
|
||||
}
|
||||
|
|
@ -152,10 +153,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753621786000,
|
||||
"modifiedTime": 1755395696040,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756041242273,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "0ey4kM9ssj2otHvb",
|
||||
"sort": 600000,
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@
|
|||
"includeBase": false
|
||||
},
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"effects": [],
|
||||
|
|
@ -81,7 +81,7 @@
|
|||
},
|
||||
"name": "Spend Hope",
|
||||
"img": "icons/magic/defensive/shield-barrier-flaming-pentagon-orange.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"originItemType": null,
|
||||
|
|
@ -105,10 +105,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1754246687097,
|
||||
"modifiedTime": 1755391259920,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756034159296,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items!YS1g7YdWwOaS629x"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,14 +5,14 @@
|
|||
"_id": "2Cyb9ZeuAesf5Sb3",
|
||||
"img": "icons/magic/defensive/barrier-shield-dome-deflect-teal.webp",
|
||||
"system": {
|
||||
"description": "<p>Spend 3 Hope when you succeed on an attack with a weapon to use that same roll against two additional adversaries within range of the attack.</p>",
|
||||
"description": "<p><strong>Spend 3 Hope</strong> when you succeed on an attack with a weapon to use that same roll against two additional adversaries within range of the attack.</p>",
|
||||
"resource": null,
|
||||
"actions": {
|
||||
"yhVUna5biFAN0o2Y": {
|
||||
"type": "effect",
|
||||
"_id": "yhVUna5biFAN0o2Y",
|
||||
"systemPath": "actions",
|
||||
"description": "<p>Spend 3 Hope when you succeed on an attack with a weapon to use that same roll against two additional adversaries within range of the attack.</p>",
|
||||
"description": "<p><strong>Spend 3 Hope</strong> when you succeed on an attack with a weapon to use that same roll against two additional adversaries within range of the attack.</p>",
|
||||
"chatDisplay": true,
|
||||
"actionType": "action",
|
||||
"cost": [
|
||||
|
|
@ -60,10 +60,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1754268431889,
|
||||
"modifiedTime": 1755391281371,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756034191317,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items!2Cyb9ZeuAesf5Sb3"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,12 +38,12 @@
|
|||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Spend Hope",
|
||||
"img": "icons/magic/holy/barrier-shield-winged-blue.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"originItemType": null,
|
||||
|
|
@ -119,10 +119,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1754257270096,
|
||||
"modifiedTime": 1755391439746,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756034406042,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items!njj2C3tMDeCHHOoh"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,12 @@
|
|||
"max": "",
|
||||
"recovery": null
|
||||
},
|
||||
"effects": [],
|
||||
"effects": [
|
||||
{
|
||||
"_id": "SXi2dQWqpwY9fap4",
|
||||
"onSave": false
|
||||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"amount": null
|
||||
|
|
@ -47,7 +52,52 @@
|
|||
"artist": ""
|
||||
}
|
||||
},
|
||||
"effects": [],
|
||||
"effects": [
|
||||
{
|
||||
"name": "Ranger's Focus",
|
||||
"img": "icons/magic/perception/eye-ringed-green.webp",
|
||||
"origin": "Compendium.daggerheart.classes.Item.ncLx2P8BOUtrAD38",
|
||||
"transfer": false,
|
||||
"_id": "SXi2dQWqpwY9fap4",
|
||||
"type": "base",
|
||||
"system": {
|
||||
"rangeDependence": {
|
||||
"enabled": false,
|
||||
"type": "withinRange",
|
||||
"target": "hostile",
|
||||
"range": "melee"
|
||||
}
|
||||
},
|
||||
"changes": [],
|
||||
"disabled": false,
|
||||
"duration": {
|
||||
"startTime": null,
|
||||
"combat": null,
|
||||
"seconds": null,
|
||||
"rounds": null,
|
||||
"turns": null,
|
||||
"startRound": null,
|
||||
"startTurn": null
|
||||
},
|
||||
"description": "<p>Until this feature ends or the ranger makes a different creature their Focus, they gain the following benefits against this adversary:</p><ul><li><p>They know precisely what direction they are in.</p></li><li><p>When they deal damage to them, the adverasry must mark a Stress.</p></li><li><p>When they fail an attack against them, they can end their Ranger’s Focus feature to reroll their Duality Dice.</p></li></ul>",
|
||||
"tint": "#ffffff",
|
||||
"statuses": [],
|
||||
"sort": 0,
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1756034238996,
|
||||
"modifiedTime": 1756034332923,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items.effects!ncLx2P8BOUtrAD38.SXi2dQWqpwY9fap4"
|
||||
}
|
||||
],
|
||||
"sort": 0,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
|
|
@ -60,10 +110,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1754268505051,
|
||||
"modifiedTime": 1755391289388,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756034239047,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items!ncLx2P8BOUtrAD38"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Spend Hope",
|
||||
|
|
@ -76,7 +76,7 @@
|
|||
"priority": null
|
||||
}
|
||||
],
|
||||
"disabled": true,
|
||||
"disabled": false,
|
||||
"duration": {
|
||||
"startTime": null,
|
||||
"combat": null,
|
||||
|
|
@ -95,12 +95,12 @@
|
|||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.346",
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1754323997794,
|
||||
"modifiedTime": 1754351805065,
|
||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
||||
"modifiedTime": 1756034385415,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items.effects!hVaaPIjxoextIgSL.hhVjBro2osGDTT5g"
|
||||
}
|
||||
|
|
@ -117,10 +117,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1754323951411,
|
||||
"modifiedTime": 1755391319440,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756034357629,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items!hVaaPIjxoextIgSL"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,35 +7,7 @@
|
|||
"system": {
|
||||
"description": "<p>When you succeed on an attack while Cloaked or while an ally is within Melee range of your target, add a number of d6s equal to your tier to your damage roll. </p><ul><li><p>Level 1 -> Tier 1</p></li><li><p>Levels 2–4 -> Tier 2 </p></li><li><p>Levels 5–7 -> Tier 3 </p></li><li><p>Levels 8–10 -> Tier 4</p></li></ul>",
|
||||
"resource": null,
|
||||
"actions": {
|
||||
"SfctrIW5KjH4nq5G": {
|
||||
"type": "effect",
|
||||
"_id": "SfctrIW5KjH4nq5G",
|
||||
"systemPath": "actions",
|
||||
"description": "<p>When you succeed on an attack while Cloaked or while an ally is within Melee range of your target, add a number of d6s equal to your tier to your damage roll.</p><ul><li><p>Level 1 -> Tier 1</p></li><li><p>Levels 2–4 -> Tier 2</p></li><li><p>Levels 5–7 -> Tier 3</p></li><li><p>Levels 8–10 -> Tier 4</p></li></ul>",
|
||||
"chatDisplay": true,
|
||||
"actionType": "action",
|
||||
"cost": [],
|
||||
"uses": {
|
||||
"value": null,
|
||||
"max": "",
|
||||
"recovery": null
|
||||
},
|
||||
"effects": [
|
||||
{
|
||||
"_id": "380jFzw756qSy5ae",
|
||||
"onSave": false
|
||||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Use Sneak Attack",
|
||||
"img": "icons/skills/melee/strike-dagger-skull-white.webp",
|
||||
"range": ""
|
||||
}
|
||||
},
|
||||
"actions": {},
|
||||
"originItemType": null,
|
||||
"originId": null,
|
||||
"attribution": {
|
||||
|
|
@ -115,10 +87,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1754324216454,
|
||||
"modifiedTime": 1755391313725,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756034698108,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items!5QqpEwmwkPfZHpMW"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"folder": "1gQpPaxdgvCxYlLs",
|
||||
"name": "Strange Dirty Penant",
|
||||
"name": "Strange Dirty Pendant",
|
||||
"type": "loot",
|
||||
"_id": "LriTeh9hkwzEaCr1",
|
||||
"img": "icons/equipment/neck/necklace-carved-stone-spiral.webp",
|
||||
|
|
@ -20,12 +20,12 @@
|
|||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.346",
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1754222150005,
|
||||
"modifiedTime": 1754246254311,
|
||||
"lastModifiedBy": "LgnbNMLaxandgMQq"
|
||||
"modifiedTime": 1756136989085,
|
||||
"lastModifiedBy": "HKcDBUU22bYKtQmH"
|
||||
},
|
||||
"_key": "!items!LriTeh9hkwzEaCr1"
|
||||
}
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
},
|
||||
"name": "Spend Hope",
|
||||
"img": "icons/magic/holy/barrier-shield-winged-blue.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
},
|
||||
"OdRTXMOXhZFiZHES": {
|
||||
"type": "effect",
|
||||
|
|
@ -81,10 +81,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753922784418,
|
||||
"modifiedTime": 1755429846358,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756038980509,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "tdsL00yTSLNgZWs6",
|
||||
"sort": 3400000,
|
||||
|
|
@ -93,7 +93,7 @@
|
|||
"name": "Bold Presence",
|
||||
"img": "icons/magic/holy/barrier-shield-winged-blue.webp",
|
||||
"origin": "Compendium.daggerheart.domains.Item.tdsL00yTSLNgZWs6",
|
||||
"transfer": true,
|
||||
"transfer": false,
|
||||
"_id": "2XEYhuAcRGTtqvED",
|
||||
"type": "base",
|
||||
"system": {
|
||||
|
|
@ -112,7 +112,7 @@
|
|||
"priority": null
|
||||
}
|
||||
],
|
||||
"disabled": true,
|
||||
"disabled": false,
|
||||
"duration": {
|
||||
"startTime": null,
|
||||
"combat": null,
|
||||
|
|
@ -131,12 +131,12 @@
|
|||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.346",
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1754241531511,
|
||||
"modifiedTime": 1754241605959,
|
||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
||||
"modifiedTime": 1756038989398,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items.effects!tdsL00yTSLNgZWs6.2XEYhuAcRGTtqvED"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
"includeBase": false
|
||||
},
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"effects": [
|
||||
|
|
@ -59,7 +59,7 @@
|
|||
},
|
||||
"name": "Transform",
|
||||
"img": "icons/containers/barrels/barrel-reinforced-cherry-brown.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
},
|
||||
"h7i4ZuDYuYLnjze6": {
|
||||
"type": "attack",
|
||||
|
|
@ -126,10 +126,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753922784424,
|
||||
"modifiedTime": 1755429102985,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756038121293,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "SZMNR3uGNinJcN4N",
|
||||
"sort": 3400000,
|
||||
|
|
|
|||
|
|
@ -30,12 +30,12 @@
|
|||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Adjust Appearance",
|
||||
"img": "icons/magic/defensive/shield-barrier-blades-teal.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
},
|
||||
"wBQkw3P4Esj6kOx2": {
|
||||
"type": "effect",
|
||||
|
|
@ -132,10 +132,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753922784424,
|
||||
"modifiedTime": 1755428993189,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756037990822,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "eq8VNqYMRHhF9xw9",
|
||||
"sort": 3400000,
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@
|
|||
},
|
||||
"name": "Magic Immunity",
|
||||
"img": "icons/magic/defensive/barrier-shield-dome-deflect-teal.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"attribution": {
|
||||
|
|
@ -105,10 +105,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753922784429,
|
||||
"modifiedTime": 1755429115570,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756038143042,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "J1ovx2FpNDvPq1o6",
|
||||
"sort": 3400000,
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"amount": null
|
||||
"amount": 1
|
||||
},
|
||||
"name": "Mark Stress",
|
||||
"img": "icons/skills/wounds/bone-broken-knee-beam.webp",
|
||||
|
|
@ -61,10 +61,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753922784431,
|
||||
"modifiedTime": 1755428345979,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756037935304,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "8UANBgSdhMZ0sqfO",
|
||||
"sort": 3400000,
|
||||
|
|
|
|||
|
|
@ -40,12 +40,12 @@
|
|||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Become Cloaked",
|
||||
"img": "icons/magic/perception/shadow-stealth-eyes-purple.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"attribution": {
|
||||
|
|
@ -61,10 +61,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753922784435,
|
||||
"modifiedTime": 1755428020354,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756037373705,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "Zhw7PtK8nMPlsOqD",
|
||||
"sort": 3400000,
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
"includeBase": false
|
||||
},
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"effects": [
|
||||
|
|
@ -67,7 +67,7 @@
|
|||
},
|
||||
"name": "Tekaira's Armored Beetles: Stress",
|
||||
"img": "icons/creatures/invertebrates/wasp-swarm-attack.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
},
|
||||
"533qzPIjcccpiMey": {
|
||||
"type": "effect",
|
||||
|
|
@ -195,10 +195,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753922784438,
|
||||
"modifiedTime": 1755429481288,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756038512929,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "rZPH0BY8Sznc9sFG",
|
||||
"sort": 3400000,
|
||||
|
|
|
|||
|
|
@ -26,17 +26,17 @@
|
|||
},
|
||||
"effects": [
|
||||
{
|
||||
"_id": "6sR46Hd554DiLHy4",
|
||||
"_id": "TFWiAUDCfGaax5MU",
|
||||
"onSave": false
|
||||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"amount": null
|
||||
"type": "self",
|
||||
"amount": 1
|
||||
},
|
||||
"name": "Focus",
|
||||
"img": "icons/skills/targeting/crosshair-pointed-orange.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"attribution": {
|
||||
|
|
@ -52,20 +52,20 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753922784443,
|
||||
"modifiedTime": 1755428127375,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756037604544,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "xxZOXC4tiZQ6kg1e",
|
||||
"sort": 3400000,
|
||||
"effects": [
|
||||
{
|
||||
"name": "Deadly Focus",
|
||||
"img": "systems/daggerheart/assets/icons/domains/domain-card/blade.png",
|
||||
"img": "icons/skills/targeting/crosshair-pointed-orange.webp",
|
||||
"origin": "Compendium.daggerheart.domains.Item.xxZOXC4tiZQ6kg1e",
|
||||
"transfer": true,
|
||||
"_id": "6sR46Hd554DiLHy4",
|
||||
"transfer": false,
|
||||
"_id": "TFWiAUDCfGaax5MU",
|
||||
"type": "base",
|
||||
"system": {
|
||||
"rangeDependence": {
|
||||
|
|
@ -83,7 +83,7 @@
|
|||
"priority": null
|
||||
}
|
||||
],
|
||||
"disabled": true,
|
||||
"disabled": false,
|
||||
"duration": {
|
||||
"startTime": null,
|
||||
"combat": null,
|
||||
|
|
@ -93,7 +93,7 @@
|
|||
"startRound": null,
|
||||
"startTurn": null
|
||||
},
|
||||
"description": "",
|
||||
"description": "<p class=\"Body-Foundation\">Until you attack another creature, you defeat the target, or the battle ends, gain a +1 bonus to your Proficiency.</p>",
|
||||
"tint": "#ffffff",
|
||||
"statuses": [],
|
||||
"sort": 0,
|
||||
|
|
@ -102,14 +102,14 @@
|
|||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.346",
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1754244951545,
|
||||
"modifiedTime": 1754304242570,
|
||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1756037548255,
|
||||
"modifiedTime": 1756037588317,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items.effects!xxZOXC4tiZQ6kg1e.6sR46Hd554DiLHy4"
|
||||
"_key": "!items.effects!xxZOXC4tiZQ6kg1e.TFWiAUDCfGaax5MU"
|
||||
}
|
||||
],
|
||||
"ownership": {
|
||||
|
|
|
|||
|
|
@ -38,12 +38,12 @@
|
|||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Transform",
|
||||
"img": "icons/magic/nature/elemental-plant-humanoid.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
},
|
||||
"1rLoYS90AZizJujS": {
|
||||
"type": "effect",
|
||||
|
|
@ -89,10 +89,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753922784456,
|
||||
"modifiedTime": 1755429633230,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756038760003,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "LzVpMkD5I4QeaIHf",
|
||||
"sort": 3400000,
|
||||
|
|
|
|||
|
|
@ -31,12 +31,12 @@
|
|||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Frenzy",
|
||||
"img": "icons/magic/fire/projectile-wave-yellow.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"attribution": {
|
||||
|
|
@ -52,10 +52,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753922784459,
|
||||
"modifiedTime": 1755428186983,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756037770198,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "MMl7abdGRLl7TJLO",
|
||||
"sort": 3400000,
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
},
|
||||
"name": "Mark Stress",
|
||||
"img": "icons/magic/control/buff-flight-wings-runes-purple.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"attribution": {
|
||||
|
|
@ -59,10 +59,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753922784460,
|
||||
"modifiedTime": 1755429931329,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756039042961,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "SgvjJfMyubZowPxS",
|
||||
"sort": 3400000,
|
||||
|
|
@ -71,7 +71,7 @@
|
|||
"name": "Full Surge",
|
||||
"img": "icons/magic/symbols/ring-circle-smoke-blue.webp",
|
||||
"origin": "Compendium.daggerheart.domains.Item.SgvjJfMyubZowPxS",
|
||||
"transfer": true,
|
||||
"transfer": false,
|
||||
"_id": "H5q5iYImr69TfZcp",
|
||||
"type": "base",
|
||||
"system": {
|
||||
|
|
@ -120,7 +120,7 @@
|
|||
"priority": null
|
||||
}
|
||||
],
|
||||
"disabled": true,
|
||||
"disabled": false,
|
||||
"duration": {
|
||||
"startTime": null,
|
||||
"combat": null,
|
||||
|
|
@ -139,12 +139,12 @@
|
|||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.346",
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1754242182511,
|
||||
"modifiedTime": 1754242229676,
|
||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
||||
"modifiedTime": 1756039048089,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items.effects!SgvjJfMyubZowPxS.H5q5iYImr69TfZcp"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@
|
|||
},
|
||||
"name": "Spend Hope",
|
||||
"img": "icons/magic/holy/barrier-shield-winged-blue.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
},
|
||||
"SKG6Gu0uJZxtYTnz": {
|
||||
"type": "effect",
|
||||
|
|
@ -87,10 +87,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753922784468,
|
||||
"modifiedTime": 1755429945423,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756039072992,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "kdFoLo3KXwn4LqTG",
|
||||
"sort": 3400000,
|
||||
|
|
@ -99,7 +99,7 @@
|
|||
"name": "Hold the Line",
|
||||
"img": "icons/magic/defensive/shield-barrier-blue.webp",
|
||||
"origin": "Compendium.daggerheart.domains.Item.kdFoLo3KXwn4LqTG",
|
||||
"transfer": true,
|
||||
"transfer": false,
|
||||
"_id": "y82y3nUStyL8DIJL",
|
||||
"type": "base",
|
||||
"system": {
|
||||
|
|
@ -111,7 +111,7 @@
|
|||
}
|
||||
},
|
||||
"changes": [],
|
||||
"disabled": true,
|
||||
"disabled": false,
|
||||
"duration": {
|
||||
"startTime": null,
|
||||
"combat": null,
|
||||
|
|
@ -130,18 +130,18 @@
|
|||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.346",
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1754242295342,
|
||||
"modifiedTime": 1754242434181,
|
||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
||||
"modifiedTime": 1756039077113,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items.effects!kdFoLo3KXwn4LqTG.y82y3nUStyL8DIJL"
|
||||
},
|
||||
{
|
||||
"name": "Hold the Line",
|
||||
"img": "systems/daggerheart/assets/icons/domains/domain-card/valor.png",
|
||||
"img": "icons/skills/melee/shield-block-bash-blue.webp",
|
||||
"origin": "Compendium.daggerheart.domains.Item.kdFoLo3KXwn4LqTG",
|
||||
"transfer": false,
|
||||
"_id": "WTYg0b8nE1XbnMiA",
|
||||
|
|
@ -176,12 +176,12 @@
|
|||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.346",
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1754242400483,
|
||||
"modifiedTime": 1754242409060,
|
||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
||||
"modifiedTime": 1756039151398,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items.effects!kdFoLo3KXwn4LqTG.WTYg0b8nE1XbnMiA"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
"type": "attack",
|
||||
"_id": "kLMAuyZktmohOSXa",
|
||||
"systemPath": "actions",
|
||||
"description": "<p>Make a <strong>Spellcast Roll</strong> against all adversaries in front of you within Close range. Once per rest on a success, create an illusion of flashing colors and lights that temporarily <em>Stuns</em> targets you succeed against and forces them to mark a Stress. While <em>Stunned</em>, they can’t use reactions and can’t take any other actions until they clear this condition.</p>",
|
||||
"description": "<p>Make a <strong>Spellcast Roll</strong> against all adversaries in front of you within Close range. Once per rest on a success, create an illusion of flashing colors and lights that temporarily <em>Stuns</em> targets you succeed against and forces them to mark a Stress. While <em>Stunned</em>, they can’t use reactions and can’t take any other actions until they clear this condition.</p><p>@Template[type:infront|range:c]</p>",
|
||||
"chatDisplay": true,
|
||||
"actionType": "action",
|
||||
"cost": [],
|
||||
|
|
@ -102,10 +102,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753922784471,
|
||||
"modifiedTime": 1755429167606,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756038232979,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "2ZeuCGVatQdPOVC6",
|
||||
"sort": 3400000,
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"amount": null
|
||||
"amount": 1
|
||||
},
|
||||
"name": "Mark Stress",
|
||||
"img": "icons/magic/control/buff-flight-wings-runes-purple.webp",
|
||||
|
|
@ -59,10 +59,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753922784475,
|
||||
"modifiedTime": 1755429951881,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756039181179,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "YWCRplmtwpCjpq5i",
|
||||
"sort": 3400000,
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
"includeBase": false
|
||||
},
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"effects": [
|
||||
|
|
@ -69,7 +69,7 @@
|
|||
},
|
||||
"name": "Cast",
|
||||
"img": "icons/magic/holy/angel-winged-humanoid-blue.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"attribution": {
|
||||
|
|
@ -85,10 +85,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753922784491,
|
||||
"modifiedTime": 1755429784343,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756038903709,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "iEBLySZD9z8CLdz7",
|
||||
"sort": 3400000,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
"type": "effect",
|
||||
"_id": "LmjwPg03xLnoGTHm",
|
||||
"systemPath": "actions",
|
||||
"description": "",
|
||||
"description": "<p>You can <strong>mark a Stress</strong> to gain a bonus to your damage roll equal to twice your Strength.</p>",
|
||||
"chatDisplay": true,
|
||||
"actionType": "action",
|
||||
"cost": [
|
||||
|
|
@ -22,8 +22,8 @@
|
|||
"scalable": false,
|
||||
"key": "stress",
|
||||
"value": 1,
|
||||
"step": 1,
|
||||
"keyIsID": false,
|
||||
"step": null,
|
||||
"consumeOnSuccess": false
|
||||
}
|
||||
],
|
||||
|
|
@ -40,18 +40,18 @@
|
|||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Mark 1 Stress",
|
||||
"img": "icons/magic/control/silhouette-aura-energy.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
},
|
||||
"fKY9NcYBwCFwMsgV": {
|
||||
"type": "effect",
|
||||
"_id": "fKY9NcYBwCFwMsgV",
|
||||
"systemPath": "actions",
|
||||
"description": "",
|
||||
"description": "<p>You can <strong>mark a Stress</strong> to gain a bonus to your damage roll equal to twice your Strength.</p>",
|
||||
"chatDisplay": true,
|
||||
"actionType": "action",
|
||||
"cost": [
|
||||
|
|
@ -77,12 +77,12 @@
|
|||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Mark 2 Stress",
|
||||
"img": "icons/magic/control/silhouette-aura-energy.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"attribution": {
|
||||
|
|
@ -98,10 +98,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753922784496,
|
||||
"modifiedTime": 1755428155116,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756037744719,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "GRL0cvs96vrTDckZ",
|
||||
"sort": 3400000,
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -14,7 +14,7 @@
|
|||
"type": "healing",
|
||||
"_id": "dmnB4ZMSk8lsB8Lg",
|
||||
"systemPath": "actions",
|
||||
"description": "<p>Make a <strong>Spellcast Roll (16)</strong>. On a success, <strong>mark any number of Stress</strong> to target a line of allies within Far range. You can clear Hit Points on the targets equal to the number of Stress marked, divided among them however you’d like.</p>",
|
||||
"description": "<p>Make a <strong>Spellcast Roll (16)</strong>. On a success, <strong>mark any number of Stress</strong> to target a line of allies within Far range. You can clear Hit Points on the targets equal to the number of Stress marked, divided among them however you’d like.</p><p>@Template[type:ray|range:f]</p>",
|
||||
"chatDisplay": true,
|
||||
"actionType": "action",
|
||||
"cost": [
|
||||
|
|
@ -101,10 +101,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753922784508,
|
||||
"modifiedTime": 1755429792958,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756038941872,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "4uAFGp3LxiC07woC",
|
||||
"sort": 3400000,
|
||||
|
|
|
|||
|
|
@ -40,12 +40,12 @@
|
|||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Charge Smite",
|
||||
"img": "icons/skills/melee/sword-winged-holy-orange.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"attribution": {
|
||||
|
|
@ -61,10 +61,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753922784519,
|
||||
"modifiedTime": 1755429734196,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756038851464,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "U1uWJE94HZVudujz",
|
||||
"sort": 3400000,
|
||||
|
|
|
|||
|
|
@ -38,12 +38,12 @@
|
|||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Become Spectral",
|
||||
"img": "icons/magic/unholy/silhouette-light-fire-blue.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"attribution": {
|
||||
|
|
@ -59,10 +59,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753922784520,
|
||||
"modifiedTime": 1755429431607,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756038467712,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "iQhgqmLwhcSTYnvr",
|
||||
"sort": 3400000,
|
||||
|
|
|
|||
|
|
@ -33,12 +33,12 @@
|
|||
},
|
||||
"effects": [],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Spend Hope",
|
||||
"img": "icons/magic/nature/thorns-hand-glow-green.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
},
|
||||
"IVAyyVf8gqFWB7bP": {
|
||||
"type": "effect",
|
||||
|
|
@ -92,10 +92,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753922784531,
|
||||
"modifiedTime": 1755429528391,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756038664007,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "oUipGK84E2KjoKqh",
|
||||
"sort": 3400000,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
"includeBase": false
|
||||
},
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"effects": [
|
||||
|
|
@ -39,7 +39,7 @@
|
|||
],
|
||||
"name": "Don Facade",
|
||||
"img": "icons/magic/control/debuff-energy-hold-pink.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
},
|
||||
"OoNND7VcWoBQdtFK": {
|
||||
"type": "effect",
|
||||
|
|
@ -91,10 +91,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753922784537,
|
||||
"modifiedTime": 1755429298602,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756038348939,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "TV56wSysbU5xAlOa",
|
||||
"sort": 3400000,
|
||||
|
|
|
|||
|
|
@ -126,12 +126,12 @@
|
|||
},
|
||||
"effects": [],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Replenish Tokens",
|
||||
"img": "icons/commodities/gems/gem-faceted-diamond-blue.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"attribution": {
|
||||
|
|
@ -147,10 +147,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753922784537,
|
||||
"modifiedTime": 1755427920648,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756037237479,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "o62i0QdbUDIiAhSq",
|
||||
"sort": 3400000,
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Vanish",
|
||||
|
|
@ -59,10 +59,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753922784539,
|
||||
"modifiedTime": 1755429382850,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756038425727,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "GBMIElIpk4cvk1Bd",
|
||||
"sort": 3400000,
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
"includeBase": false
|
||||
},
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"effects": [
|
||||
|
|
@ -59,7 +59,7 @@
|
|||
},
|
||||
"name": "Cast",
|
||||
"img": "icons/magic/unholy/barrier-shield-glowing-pink.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"attribution": {
|
||||
|
|
@ -75,10 +75,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753922784541,
|
||||
"modifiedTime": 1755429328234,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756038378309,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_id": "gV4L5ZZmfPrEbIDh",
|
||||
"sort": 3400000,
|
||||
|
|
|
|||
|
|
@ -6,18 +6,18 @@
|
|||
"sorting": "m",
|
||||
"_id": "TL1TutmbeCVJ06nR",
|
||||
"description": "",
|
||||
"sort": 800000,
|
||||
"sort": 900000,
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.346",
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1748717133557,
|
||||
"modifiedTime": 1752681441081,
|
||||
"lastModifiedBy": "YNJ4HgHtFrTI89mx"
|
||||
"modifiedTime": 1756038952388,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!folders!TL1TutmbeCVJ06nR"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,18 +6,18 @@
|
|||
"sorting": "m",
|
||||
"_id": "yPVeShe47ETIqs9q",
|
||||
"description": "",
|
||||
"sort": 900000,
|
||||
"sort": 800000,
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.346",
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1748717138700,
|
||||
"modifiedTime": 1752681443619,
|
||||
"lastModifiedBy": "YNJ4HgHtFrTI89mx"
|
||||
"modifiedTime": 1756038952388,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!folders!yPVeShe47ETIqs9q"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,12 +30,12 @@
|
|||
},
|
||||
"effects": [],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Stitch",
|
||||
"img": "icons/skills/trades/textiles-stitching-leather-brown.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"consumeOnUse": true,
|
||||
|
|
@ -59,10 +59,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753588973384,
|
||||
"modifiedTime": 1755432710896,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756041701211,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items!VlbsCjvvLNfTzNXb"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
},
|
||||
"name": "Drink",
|
||||
"img": "icons/consumables/potions/bottle-conical-corked-purple.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"consumeOnUse": true,
|
||||
|
|
@ -101,10 +101,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753587033468,
|
||||
"modifiedTime": 1755432536588,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756041711386,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items!JGD3M9hBHtVAA8XP"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,12 +22,12 @@
|
|||
},
|
||||
"effects": [],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Drink",
|
||||
"img": "icons/consumables/potions/potion-tube-corked-bat-gold-red.webp",
|
||||
"range": ""
|
||||
"range": "veryFar"
|
||||
}
|
||||
},
|
||||
"consumeOnUse": true,
|
||||
|
|
@ -51,10 +51,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753589954973,
|
||||
"modifiedTime": 1755432804800,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756041739719,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items!pDGzmczoTlKGmKgd"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
},
|
||||
"name": "Drink",
|
||||
"img": "icons/consumables/potions/potion-vial-tube-yellow.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"consumeOnUse": true,
|
||||
|
|
@ -101,10 +101,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753586850134,
|
||||
"modifiedTime": 1755432522101,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756041748451,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items!FOPQNqXbiVO0ilYL"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,12 +27,12 @@
|
|||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Drink",
|
||||
"img": "icons/consumables/drinks/wine-amphora-clay-gray.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"consumeOnUse": true,
|
||||
|
|
@ -101,10 +101,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753592717630,
|
||||
"modifiedTime": 1755433085832,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756041829971,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items!xDnJeF1grkmKck8Q"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,13 +11,13 @@
|
|||
"type": "attack",
|
||||
"_id": "rcHEz3ImUDwo6lPC",
|
||||
"systemPath": "actions",
|
||||
"description": "",
|
||||
"description": "<p>You can drink this tea to unleash a fiery breath attack. Make an Instinct Roll against all adversaries in front of you within Close range. Targets you succeed against take d20 physical damage using your Proficiency.</p><p>@Template[type:emanation|range:c]</p>",
|
||||
"chatDisplay": true,
|
||||
"actionType": "action",
|
||||
"cost": [],
|
||||
"uses": {
|
||||
"value": null,
|
||||
"max": null,
|
||||
"max": "",
|
||||
"recovery": null
|
||||
},
|
||||
"damage": {
|
||||
|
|
@ -100,10 +100,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753591525829,
|
||||
"modifiedTime": 1755432974525,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756041882255,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items!wM18PWWW2Ami4fBG"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,14 +20,19 @@
|
|||
"max": "",
|
||||
"recovery": null
|
||||
},
|
||||
"effects": [],
|
||||
"effects": [
|
||||
{
|
||||
"_id": "VfJIJBW96e45xQHY",
|
||||
"onSave": false
|
||||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Use",
|
||||
"img": "icons/commodities/bones/bones-stack-worn-brown.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"consumeOnUse": true,
|
||||
|
|
@ -37,7 +42,52 @@
|
|||
"artist": ""
|
||||
}
|
||||
},
|
||||
"effects": [],
|
||||
"effects": [
|
||||
{
|
||||
"name": "Featherbone",
|
||||
"img": "icons/commodities/bones/bones-stack-worn-brown.webp",
|
||||
"origin": "Compendium.daggerheart.consumables.Item.DpxEMpwfasEBpORU",
|
||||
"transfer": false,
|
||||
"_id": "VfJIJBW96e45xQHY",
|
||||
"type": "base",
|
||||
"system": {
|
||||
"rangeDependence": {
|
||||
"enabled": false,
|
||||
"type": "withinRange",
|
||||
"target": "hostile",
|
||||
"range": "melee"
|
||||
}
|
||||
},
|
||||
"changes": [],
|
||||
"disabled": false,
|
||||
"duration": {
|
||||
"startTime": null,
|
||||
"combat": null,
|
||||
"seconds": null,
|
||||
"rounds": null,
|
||||
"turns": null,
|
||||
"startRound": null,
|
||||
"startTurn": null
|
||||
},
|
||||
"description": "<p>You can use this bone to control your falling speed for a number of minutes equal to your level.</p>",
|
||||
"tint": "#ffffff",
|
||||
"statuses": [],
|
||||
"sort": 0,
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1756041997551,
|
||||
"modifiedTime": 1756042000517,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items.effects!DpxEMpwfasEBpORU.VfJIJBW96e45xQHY"
|
||||
}
|
||||
],
|
||||
"folder": null,
|
||||
"sort": 0,
|
||||
"ownership": {
|
||||
|
|
@ -51,10 +101,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753590624634,
|
||||
"modifiedTime": 1755432889504,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756042004101,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items!DpxEMpwfasEBpORU"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,14 +20,19 @@
|
|||
"max": "",
|
||||
"recovery": null
|
||||
},
|
||||
"effects": [],
|
||||
"effects": [
|
||||
{
|
||||
"_id": "5rL9CY5GO9SJcEZq",
|
||||
"onSave": false
|
||||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Apply",
|
||||
"img": "icons/commodities/materials/bowl-powder-blue.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"consumeOnUse": true,
|
||||
|
|
@ -37,7 +42,52 @@
|
|||
"artist": ""
|
||||
}
|
||||
},
|
||||
"effects": [],
|
||||
"effects": [
|
||||
{
|
||||
"name": "Gill Salve",
|
||||
"img": "icons/commodities/materials/bowl-powder-blue.webp",
|
||||
"origin": "Compendium.daggerheart.consumables.Item.Nvbb9mze6o5D0AEg",
|
||||
"transfer": false,
|
||||
"_id": "5rL9CY5GO9SJcEZq",
|
||||
"type": "base",
|
||||
"system": {
|
||||
"rangeDependence": {
|
||||
"enabled": false,
|
||||
"type": "withinRange",
|
||||
"target": "hostile",
|
||||
"range": "melee"
|
||||
}
|
||||
},
|
||||
"changes": [],
|
||||
"disabled": false,
|
||||
"duration": {
|
||||
"startTime": null,
|
||||
"combat": null,
|
||||
"seconds": null,
|
||||
"rounds": null,
|
||||
"turns": null,
|
||||
"startRound": null,
|
||||
"startTurn": null
|
||||
},
|
||||
"description": "<p>You can apply this salve to your neck to breathe underwater for a number of minutes equal to your level.</p>",
|
||||
"tint": "#ffffff",
|
||||
"statuses": [],
|
||||
"sort": 0,
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1756041968799,
|
||||
"modifiedTime": 1756041978849,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items.effects!Nvbb9mze6o5D0AEg.5rL9CY5GO9SJcEZq"
|
||||
}
|
||||
],
|
||||
"folder": null,
|
||||
"sort": 0,
|
||||
"ownership": {
|
||||
|
|
@ -51,10 +101,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753589241094,
|
||||
"modifiedTime": 1755432719146,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756041983578,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items!Nvbb9mze6o5D0AEg"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"type": "effect",
|
||||
"_id": "ATQgH12mufTOQLKs",
|
||||
"systemPath": "actions",
|
||||
"description": "",
|
||||
"description": "<p>Activate this pebble-sized stone to memorize the appearance of someone you can see. Spend a Hope to magically recreate this guise on yourself as an illusion.</p>",
|
||||
"chatDisplay": true,
|
||||
"actionType": "action",
|
||||
"cost": [
|
||||
|
|
@ -25,17 +25,22 @@
|
|||
],
|
||||
"uses": {
|
||||
"value": null,
|
||||
"max": null,
|
||||
"max": "",
|
||||
"recovery": null
|
||||
},
|
||||
"effects": [],
|
||||
"effects": [
|
||||
{
|
||||
"_id": "K5SB6tfuDkdVaQYe",
|
||||
"onSave": false
|
||||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Create Illusion",
|
||||
"img": "icons/commodities/treasure/token-engraved-purple-glowing.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"attribution": {
|
||||
|
|
@ -44,7 +49,52 @@
|
|||
"artist": ""
|
||||
}
|
||||
},
|
||||
"effects": [],
|
||||
"effects": [
|
||||
{
|
||||
"name": "Glamour Stone",
|
||||
"img": "icons/commodities/treasure/token-engraved-purple-glowing.webp",
|
||||
"origin": "Compendium.daggerheart.loot.Item.Pj17cvdJ1XG1jv6I",
|
||||
"transfer": false,
|
||||
"_id": "K5SB6tfuDkdVaQYe",
|
||||
"type": "base",
|
||||
"system": {
|
||||
"rangeDependence": {
|
||||
"enabled": false,
|
||||
"type": "withinRange",
|
||||
"target": "hostile",
|
||||
"range": "melee"
|
||||
}
|
||||
},
|
||||
"changes": [],
|
||||
"disabled": false,
|
||||
"duration": {
|
||||
"startTime": null,
|
||||
"combat": null,
|
||||
"seconds": null,
|
||||
"rounds": null,
|
||||
"turns": null,
|
||||
"startRound": null,
|
||||
"startTurn": null
|
||||
},
|
||||
"description": "<p>Activate this pebble-sized stone to memorize the appearance of someone you can see. Spend a Hope to magically recreate this guise on yourself as an illusion.</p>",
|
||||
"tint": "#ffffff",
|
||||
"statuses": [],
|
||||
"sort": 0,
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1756042876194,
|
||||
"modifiedTime": 1756042884422,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items.effects!Pj17cvdJ1XG1jv6I.K5SB6tfuDkdVaQYe"
|
||||
}
|
||||
],
|
||||
"folder": null,
|
||||
"sort": 0,
|
||||
"ownership": {
|
||||
|
|
@ -58,10 +108,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753625773657,
|
||||
"modifiedTime": 1755431995057,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756042876216,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items!Pj17cvdJ1XG1jv6I"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"type": "effect",
|
||||
"_id": "DW5AqEM0F8XaUqpn",
|
||||
"systemPath": "actions",
|
||||
"description": "",
|
||||
"description": "<p>Three times per rest when you succeed on an attack with one of these arrows, you can add your Proficiency to the damage roll.</p>",
|
||||
"chatDisplay": true,
|
||||
"actionType": "action",
|
||||
"cost": [],
|
||||
|
|
@ -108,10 +108,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753625947079,
|
||||
"modifiedTime": 1755432020809,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756042964027,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items!I63LTFD6GXHgyGpR"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,11 +17,11 @@
|
|||
"cost": [],
|
||||
"uses": {
|
||||
"value": null,
|
||||
"max": null,
|
||||
"max": "",
|
||||
"recovery": null
|
||||
},
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"effects": [],
|
||||
|
|
@ -89,10 +89,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753624827945,
|
||||
"modifiedTime": 1755431895118,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756042976231,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items!QGYPNBIufpBguwjC"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"type": "effect",
|
||||
"_id": "4swwe8SZeh2KyPtl",
|
||||
"systemPath": "actions",
|
||||
"description": "",
|
||||
"description": "<p>Once per long rest, you can activate this ring after a successful attack against you to halve the damage.</p>",
|
||||
"chatDisplay": true,
|
||||
"actionType": "action",
|
||||
"cost": [],
|
||||
|
|
@ -20,14 +20,19 @@
|
|||
"max": 1,
|
||||
"recovery": "longRest"
|
||||
},
|
||||
"effects": [],
|
||||
"effects": [
|
||||
{
|
||||
"_id": "Sj7uGf560T7u4rlX",
|
||||
"onSave": false
|
||||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Activate",
|
||||
"img": "icons/equipment/finger/ring-shield-silver.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"attribution": {
|
||||
|
|
@ -36,7 +41,65 @@
|
|||
"artist": ""
|
||||
}
|
||||
},
|
||||
"effects": [],
|
||||
"effects": [
|
||||
{
|
||||
"name": "Ring of Resistance",
|
||||
"img": "icons/equipment/finger/ring-shield-silver.webp",
|
||||
"origin": "Compendium.daggerheart.loot.Item.aUqRifqR5JXXa1dN",
|
||||
"transfer": false,
|
||||
"_id": "Sj7uGf560T7u4rlX",
|
||||
"type": "base",
|
||||
"system": {
|
||||
"rangeDependence": {
|
||||
"enabled": false,
|
||||
"type": "withinRange",
|
||||
"target": "hostile",
|
||||
"range": "melee"
|
||||
}
|
||||
},
|
||||
"changes": [
|
||||
{
|
||||
"key": "system.resistance.magical.resistance",
|
||||
"mode": 5,
|
||||
"value": "1",
|
||||
"priority": null
|
||||
},
|
||||
{
|
||||
"key": "system.resistance.physical.resistance",
|
||||
"mode": 5,
|
||||
"value": "1",
|
||||
"priority": null
|
||||
}
|
||||
],
|
||||
"disabled": false,
|
||||
"duration": {
|
||||
"startTime": null,
|
||||
"combat": null,
|
||||
"seconds": null,
|
||||
"rounds": null,
|
||||
"turns": null,
|
||||
"startRound": null,
|
||||
"startTurn": null
|
||||
},
|
||||
"description": "<p>Once per long rest, you can activate this ring after a successful attack against you to halve the damage.</p>",
|
||||
"tint": "#ffffff",
|
||||
"statuses": [],
|
||||
"sort": 0,
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1756043003002,
|
||||
"modifiedTime": 1756043032046,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items.effects!aUqRifqR5JXXa1dN.Sj7uGf560T7u4rlX"
|
||||
}
|
||||
],
|
||||
"folder": null,
|
||||
"sort": 0,
|
||||
"ownership": {
|
||||
|
|
@ -50,10 +113,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753638222884,
|
||||
"modifiedTime": 1755432185583,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756043010729,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items!aUqRifqR5JXXa1dN"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"type": "effect",
|
||||
"_id": "Y4mvuJ4tncrKhyjY",
|
||||
"systemPath": "actions",
|
||||
"description": "",
|
||||
"description": "<p><strong>Spend a Hope</strong> to activate this ring. Your footsteps are silent until your next rest.</p>",
|
||||
"chatDisplay": true,
|
||||
"actionType": "action",
|
||||
"cost": [
|
||||
|
|
@ -28,14 +28,19 @@
|
|||
"max": 1,
|
||||
"recovery": "shortRest"
|
||||
},
|
||||
"effects": [],
|
||||
"effects": [
|
||||
{
|
||||
"_id": "9JSLRu7amQ4iuIO7",
|
||||
"onSave": false
|
||||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Activate",
|
||||
"img": "icons/equipment/finger/ring-ball-purple.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"attribution": {
|
||||
|
|
@ -47,9 +52,11 @@
|
|||
"effects": [
|
||||
{
|
||||
"name": "Ring of Silence",
|
||||
"type": "base",
|
||||
"_id": "aCt3QjdeTREZAlEa",
|
||||
"img": "icons/equipment/finger/ring-ball-purple.webp",
|
||||
"origin": "Compendium.daggerheart.loot.Item.K1ysGnTpNyxPu5Au",
|
||||
"transfer": false,
|
||||
"_id": "9JSLRu7amQ4iuIO7",
|
||||
"type": "base",
|
||||
"system": {
|
||||
"rangeDependence": {
|
||||
"enabled": false,
|
||||
|
|
@ -59,7 +66,7 @@
|
|||
}
|
||||
},
|
||||
"changes": [],
|
||||
"disabled": true,
|
||||
"disabled": false,
|
||||
"duration": {
|
||||
"startTime": null,
|
||||
"combat": null,
|
||||
|
|
@ -69,10 +76,8 @@
|
|||
"startRound": null,
|
||||
"startTurn": null
|
||||
},
|
||||
"description": "<p><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">Your footsteps are silent until your next rest.</span></p>",
|
||||
"origin": null,
|
||||
"description": "<p>Your footsteps are silent until your next rest.</p>",
|
||||
"tint": "#ffffff",
|
||||
"transfer": true,
|
||||
"statuses": [],
|
||||
"sort": 0,
|
||||
"flags": {},
|
||||
|
|
@ -80,14 +85,14 @@
|
|||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.346",
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"createdTime": 1753990311274,
|
||||
"modifiedTime": 1753990345889,
|
||||
"lastModifiedBy": "MQSznptE5yLT7kj8"
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1756043091753,
|
||||
"modifiedTime": 1756043101742,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items.effects!K1ysGnTpNyxPu5Au.aCt3QjdeTREZAlEa"
|
||||
"_key": "!items.effects!K1ysGnTpNyxPu5Au.9JSLRu7amQ4iuIO7"
|
||||
}
|
||||
],
|
||||
"folder": null,
|
||||
|
|
@ -103,10 +108,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1753637775628,
|
||||
"modifiedTime": 1755432151648,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756043091776,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items!K1ysGnTpNyxPu5Au"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Mark Adversary",
|
||||
|
|
@ -98,10 +98,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1754243143650,
|
||||
"modifiedTime": 1755391854504,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756035614382,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items!k7vvMJtEcxMWUUrW"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,36 +7,7 @@
|
|||
"system": {
|
||||
"description": "<p>While you're Vulnerable, add your level to your damage rolls.</p>",
|
||||
"resource": null,
|
||||
"actions": {
|
||||
"UgffABhuobRDP7MQ": {
|
||||
"type": "effect",
|
||||
"_id": "UgffABhuobRDP7MQ",
|
||||
"systemPath": "actions",
|
||||
"description": "<p>While you're Vulnerable, add your level to your damage rolls.</p>",
|
||||
"chatDisplay": true,
|
||||
"actionType": "action",
|
||||
"cost": [],
|
||||
"uses": {
|
||||
"value": null,
|
||||
"max": "",
|
||||
"recovery": null,
|
||||
"consumeOnSuccess": false
|
||||
},
|
||||
"effects": [
|
||||
{
|
||||
"_id": "HMx9uZ54mvMiH95x",
|
||||
"onSave": false
|
||||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Apply",
|
||||
"img": "icons/magic/unholy/hand-marked-pink.webp",
|
||||
"range": ""
|
||||
}
|
||||
},
|
||||
"actions": {},
|
||||
"originItemType": null,
|
||||
"originId": null,
|
||||
"attribution": {
|
||||
|
|
@ -116,10 +87,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1754319984350,
|
||||
"modifiedTime": 1755392070263,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756035779655,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items!uByM34yQlw38yf1V"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,59 @@
|
|||
"artist": ""
|
||||
}
|
||||
},
|
||||
"effects": [],
|
||||
"effects": [
|
||||
{
|
||||
"name": "Battle Bonded",
|
||||
"type": "base",
|
||||
"system": {
|
||||
"rangeDependence": {
|
||||
"enabled": false,
|
||||
"type": "withinRange",
|
||||
"target": "hostile",
|
||||
"range": "melee"
|
||||
}
|
||||
},
|
||||
"_id": "IZhakv6EuG8DO4a3",
|
||||
"img": "icons/creatures/mammals/humanoid-wolf-dog-blue.webp",
|
||||
"changes": [
|
||||
{
|
||||
"key": "system.evasion",
|
||||
"mode": 2,
|
||||
"value": "2",
|
||||
"priority": null
|
||||
}
|
||||
],
|
||||
"disabled": true,
|
||||
"duration": {
|
||||
"startTime": null,
|
||||
"combat": null,
|
||||
"seconds": null,
|
||||
"rounds": null,
|
||||
"turns": null,
|
||||
"startRound": null,
|
||||
"startTurn": null
|
||||
},
|
||||
"description": "<p>When an adversary attacks you while they’re within your companion’s Melee range, you gain a +2 bonus to your Evasion against the attack.</p>",
|
||||
"origin": null,
|
||||
"tint": "#ffffff",
|
||||
"transfer": true,
|
||||
"statuses": [],
|
||||
"sort": 0,
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1756035661033,
|
||||
"modifiedTime": 1756035696385,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items.effects!hWsKyed1vfILg0I8.IZhakv6EuG8DO4a3"
|
||||
}
|
||||
],
|
||||
"sort": 0,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
|
|
|
|||
|
|
@ -38,12 +38,12 @@
|
|||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Bonus to Roll",
|
||||
"img": "icons/sundries/gaming/dice-runed-tan.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
},
|
||||
"S7HvFD3qIR3ifJRL": {
|
||||
"type": "effect",
|
||||
|
|
@ -75,12 +75,12 @@
|
|||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Bonus to Damage",
|
||||
"img": "icons/sundries/gaming/dice-runed-tan.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"originItemType": null,
|
||||
|
|
@ -115,7 +115,7 @@
|
|||
"priority": null
|
||||
}
|
||||
],
|
||||
"disabled": true,
|
||||
"disabled": false,
|
||||
"duration": {
|
||||
"startTime": null,
|
||||
"combat": null,
|
||||
|
|
@ -134,10 +134,11 @@
|
|||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.346",
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"lastModifiedBy": null
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE",
|
||||
"modifiedTime": 1756035533156
|
||||
},
|
||||
"_key": "!items.effects!dPcqKN5NeDkjB1HW.EY87mY6ULfIt3XC8"
|
||||
},
|
||||
|
|
@ -170,7 +171,7 @@
|
|||
"priority": null
|
||||
}
|
||||
],
|
||||
"disabled": true,
|
||||
"disabled": false,
|
||||
"duration": {
|
||||
"startTime": null,
|
||||
"combat": null,
|
||||
|
|
@ -189,10 +190,11 @@
|
|||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.346",
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "0.0.1",
|
||||
"lastModifiedBy": null
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE",
|
||||
"modifiedTime": 1756035536128
|
||||
},
|
||||
"_key": "!items.effects!dPcqKN5NeDkjB1HW.WwibpgaO6Kkks7aZ"
|
||||
}
|
||||
|
|
@ -209,10 +211,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1754349507020,
|
||||
"modifiedTime": 1755392284524,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756035526712,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items!dPcqKN5NeDkjB1HW"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,59 @@
|
|||
"artist": ""
|
||||
}
|
||||
},
|
||||
"effects": [],
|
||||
"effects": [
|
||||
{
|
||||
"name": "Elusive Predator",
|
||||
"type": "base",
|
||||
"system": {
|
||||
"rangeDependence": {
|
||||
"enabled": false,
|
||||
"type": "withinRange",
|
||||
"target": "hostile",
|
||||
"range": "melee"
|
||||
}
|
||||
},
|
||||
"_id": "X4llFOcAcdJLbear",
|
||||
"img": "icons/creatures/mammals/beast-horned-scaled-glowing-orange.webp",
|
||||
"changes": [
|
||||
{
|
||||
"key": "system.evasion",
|
||||
"mode": 2,
|
||||
"value": "2",
|
||||
"priority": null
|
||||
}
|
||||
],
|
||||
"disabled": true,
|
||||
"duration": {
|
||||
"startTime": null,
|
||||
"combat": null,
|
||||
"seconds": null,
|
||||
"rounds": null,
|
||||
"turns": null,
|
||||
"startRound": null,
|
||||
"startTurn": null
|
||||
},
|
||||
"description": "<p>When your Focus makes an attack against you, you gain a +2 bonus to your Evasion against the attack.</p>",
|
||||
"origin": null,
|
||||
"tint": "#ffffff",
|
||||
"transfer": true,
|
||||
"statuses": [],
|
||||
"sort": 0,
|
||||
"flags": {},
|
||||
"_stats": {
|
||||
"compendiumSource": null,
|
||||
"duplicateSource": null,
|
||||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1756035709943,
|
||||
"modifiedTime": 1756035734622,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items.effects!Cjtc43V3IzAmfIFG.X4llFOcAcdJLbear"
|
||||
}
|
||||
],
|
||||
"sort": 0,
|
||||
"ownership": {
|
||||
"default": 0,
|
||||
|
|
|
|||
|
|
@ -36,12 +36,12 @@
|
|||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Mark Stress",
|
||||
"img": "icons/creatures/mammals/wolf-shadow-black.webp",
|
||||
"range": ""
|
||||
"range": "self"
|
||||
}
|
||||
},
|
||||
"originItemType": null,
|
||||
|
|
@ -117,10 +117,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1754266926055,
|
||||
"modifiedTime": 1755391927605,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756035422344,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items!Qny2J3R35bvC0Cey"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,12 +36,12 @@
|
|||
}
|
||||
],
|
||||
"target": {
|
||||
"type": "any",
|
||||
"type": "self",
|
||||
"amount": null
|
||||
},
|
||||
"name": "Mark Stress",
|
||||
"img": "icons/magic/unholy/projectile-smoke-trail-pink.webp",
|
||||
"range": ""
|
||||
"range": "far"
|
||||
}
|
||||
},
|
||||
"originItemType": null,
|
||||
|
|
@ -110,10 +110,10 @@
|
|||
"exportSource": null,
|
||||
"coreVersion": "13.347",
|
||||
"systemId": "daggerheart",
|
||||
"systemVersion": "1.0.5",
|
||||
"systemVersion": "1.1.0",
|
||||
"createdTime": 1754318976447,
|
||||
"modifiedTime": 1755392035163,
|
||||
"lastModifiedBy": "VZIeX2YDvX338Zvr"
|
||||
"modifiedTime": 1756035450401,
|
||||
"lastModifiedBy": "vUIbuan0U50nfKBE"
|
||||
},
|
||||
"_key": "!items!hAwTXjhyphiE3aeW"
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue