Fixed UseItem and ToChat

This commit is contained in:
WBHarry 2025-07-01 12:49:00 +02:00
parent 8ea596544b
commit 6018763d47
3 changed files with 150 additions and 74 deletions

View file

@ -10,7 +10,8 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) {
position: { width: 660, height: 766 }, position: { width: 660, height: 766 },
actions: { actions: {
reactionRoll: this.reactionRoll, reactionRoll: this.reactionRoll,
attackRoll: this.attackRoll, useItem: this.useItem,
toChat: this.toChat,
attackConfigure: this.attackConfigure, attackConfigure: this.attackConfigure,
addExperience: this.addExperience, addExperience: this.addExperience,
removeExperience: this.removeExperience, removeExperience: this.removeExperience,
@ -70,6 +71,12 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) {
return context; return context;
} }
getAction(element) {
const itemId = (element.target ?? element).closest('[data-item-id]').dataset.itemId,
item = this.document.system.actions.find(x => x.id === itemId);
return item;
}
static async updateForm(event, _, formData) { static async updateForm(event, _, formData) {
await this.document.update(formData.object); await this.document.update(formData.object);
this.render(); this.render();
@ -100,8 +107,36 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) {
await new DHAdversarySettings(this.document).render(true); await new DHAdversarySettings(this.document).render(true);
} }
static async attackRoll(event) { static async useItem(event) {
this.actor.system.attack.use(event); const action = this.getAction(event) ?? this.actor.system.attack;
action.use(event);
}
static async toChat(event, button) {
if (button?.dataset?.type === 'experience') {
const experience = this.document.system.experiences[button.dataset.uuid];
const cls = getDocumentClass('ChatMessage');
const systemData = {
name: game.i18n.localize('DAGGERHEART.General.Experience.Single'),
description: `${experience.name} ${
experience.modifier < 0 ? experience.modifier : `+${experience.modifier}`
}`
};
const msg = new cls({
type: 'abilityUse',
user: game.user.id,
system: systemData,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/chat/ability-use.hbs',
systemData
)
});
cls.create(msg.toObject());
} else {
const item = this.getAction(event) ?? this.document.system.attack;
item.toChat(this.document.id);
}
} }
static async attackConfigure(event) { static async attackConfigure(event) {

View file

@ -815,9 +815,7 @@ export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
const cls = getDocumentClass('ChatMessage'); const cls = getDocumentClass('ChatMessage');
const systemData = { const systemData = {
name: game.i18n.localize('DAGGERHEART.General.Experience.Single'), name: game.i18n.localize('DAGGERHEART.General.Experience.Single'),
description: `${experience.description} ${ description: `${experience.name} ${experience.total < 0 ? experience.total : `+${experience.total}`}`
experience.total < 0 ? experience.total : `+${experience.total}`
}`
}; };
const msg = new cls({ const msg = new cls({
type: 'abilityUse', type: 'abilityUse',

View file

@ -73,7 +73,10 @@ export class DHBaseAction extends foundry.abstract.DataModel {
save: new fields.SchemaField({ save: new fields.SchemaField({
trait: new fields.StringField({ nullable: true, initial: null, choices: SYSTEM.ACTOR.abilities }), trait: new fields.StringField({ nullable: true, initial: null, choices: SYSTEM.ACTOR.abilities }),
difficulty: new fields.NumberField({ nullable: true, initial: 10, integer: true, min: 0 }), difficulty: new fields.NumberField({ nullable: true, initial: 10, integer: true, min: 0 }),
damageMod: new fields.StringField({ initial: SYSTEM.ACTIONS.damageOnSave.none.id, choices: SYSTEM.ACTIONS.damageOnSave }) damageMod: new fields.StringField({
initial: SYSTEM.ACTIONS.damageOnSave.none.id,
choices: SYSTEM.ACTIONS.damageOnSave
})
}), }),
target: new fields.SchemaField({ target: new fields.SchemaField({
type: new fields.StringField({ type: new fields.StringField({
@ -98,9 +101,12 @@ export class DHBaseAction extends foundry.abstract.DataModel {
initial: SYSTEM.GENERAL.healingTypes.hitPoints.id, initial: SYSTEM.GENERAL.healingTypes.hitPoints.id,
label: 'Healing' label: 'Healing'
}), }),
resultBased: new fields.BooleanField({ initial: false, label: "DAGGERHEART.Actions.Settings.ResultBased.label" }), resultBased: new fields.BooleanField({
initial: false,
label: 'DAGGERHEART.Actions.Settings.ResultBased.label'
}),
value: new fields.EmbeddedDataField(DHActionDiceData), value: new fields.EmbeddedDataField(DHActionDiceData),
valueAlt: new fields.EmbeddedDataField(DHActionDiceData), valueAlt: new fields.EmbeddedDataField(DHActionDiceData)
}) })
}, },
extraSchemas = {}; extraSchemas = {};
@ -153,7 +159,7 @@ export class DHBaseAction extends foundry.abstract.DataModel {
return updateSource; return updateSource;
} }
getRollData(data={}) { getRollData(data = {}) {
const actorData = this.actor.getRollData(false); const actorData = this.actor.getRollData(false);
// Remove when included directly in Actor getRollData // Remove when included directly in Actor getRollData
@ -166,9 +172,9 @@ export class DHBaseAction extends foundry.abstract.DataModel {
return a; return a;
}, {}) }, {})
: 1; */ : 1; */
actorData.scale = data.costs?.length // Right now only return the first scalable cost. actorData.scale = data.costs?.length // Right now only return the first scalable cost.
? (data.costs.find(c => c.scalable)?.total ?? 1) ? (data.costs.find(c => c.scalable)?.total ?? 1)
: 1; : 1;
actorData.roll = {}; actorData.roll = {};
return actorData; return actorData;
@ -191,12 +197,14 @@ export class DHBaseAction extends foundry.abstract.DataModel {
// Prepare Costs // Prepare Costs
const costsConfig = this.prepareCost(); const costsConfig = this.prepareCost();
if(isFastForward && !this.hasCost(costsConfig)) return ui.notifications.warn("You don't have the resources to use that action."); if (isFastForward && !this.hasCost(costsConfig))
return ui.notifications.warn("You don't have the resources to use that action.");
// config = this.prepareUseCost(config) // config = this.prepareUseCost(config)
// Prepare Uses // Prepare Uses
const usesConfig = this.prepareUse(); const usesConfig = this.prepareUse();
if(isFastForward && !this.hasUses(usesConfig)) return ui.notifications.warn("That action doesn't have remaining uses."); if (isFastForward && !this.hasUses(usesConfig))
return ui.notifications.warn("That action doesn't have remaining uses.");
// config = this.prepareUseCost(config) // config = this.prepareUseCost(config)
// Prepare Roll Data // Prepare Roll Data
@ -209,24 +217,24 @@ export class DHBaseAction extends foundry.abstract.DataModel {
costs: costsConfig, costs: costsConfig,
uses: usesConfig, uses: usesConfig,
data: actorData data: actorData
} };
if ( Hooks.call(`${SYSTEM.id}.preUseAction`, this, config) === false ) return; if (Hooks.call(`${SYSTEM.id}.preUseAction`, this, config) === false) return;
// Display configuration window if necessary // Display configuration window if necessary
if ( config.dialog?.configure && this.requireConfigurationDialog(config) ) { if (config.dialog?.configure && this.requireConfigurationDialog(config)) {
config = await D20RollDialog.configure(config); config = await D20RollDialog.configure(config);
if (!config) return; if (!config) return;
} }
if ( this.hasRoll ) { if (this.hasRoll) {
const rollConfig = this.prepareRoll(config); const rollConfig = this.prepareRoll(config);
config.roll = rollConfig; config.roll = rollConfig;
config = await this.actor.diceRoll(config); config = await this.actor.diceRoll(config);
if (!config) return; if (!config) return;
} }
if( this.hasSave ) { if (this.hasSave) {
/* config.targets.forEach((t) => { /* config.targets.forEach((t) => {
if(t.hit) { if(t.hit) {
const target = game.canvas.tokens.get(t.id), const target = game.canvas.tokens.get(t.id),
@ -258,16 +266,16 @@ export class DHBaseAction extends foundry.abstract.DataModel {
}) */ }) */
} }
if ( this.doFollowUp() ) { if (this.doFollowUp()) {
if(this.rollDamage) await this.rollDamage(event, config); if (this.rollDamage) await this.rollDamage(event, config);
if(this.rollHealing) await this.rollHealing(event, config); if (this.rollHealing) await this.rollHealing(event, config);
if(this.trigger) await this.trigger(event, config); if (this.trigger) await this.trigger(event, config);
} }
// Consume resources // Consume resources
await this.consume(config); await this.consume(config);
if ( Hooks.call(`${SYSTEM.id}.postUseAction`, this, config) === false ) return; if (Hooks.call(`${SYSTEM.id}.postUseAction`, this, config) === false) return;
return config; return config;
} }
@ -287,7 +295,7 @@ export class DHBaseAction extends foundry.abstract.DataModel {
hasHealing: !!this.healing, hasHealing: !!this.healing,
hasEffect: !!this.effects?.length, hasEffect: !!this.effects?.length,
hasSave: this.hasSave hasSave: this.hasSave
} };
} }
requireConfigurationDialog(config) { requireConfigurationDialog(config) {
@ -317,7 +325,6 @@ export class DHBaseAction extends foundry.abstract.DataModel {
} }
targets = targets.map(t => this.formatTarget(t)); targets = targets.map(t => this.formatTarget(t));
return targets; return targets;
} }
prepareRange() { prepareRange() {
@ -326,7 +333,7 @@ export class DHBaseAction extends foundry.abstract.DataModel {
} }
prepareRoll() { prepareRoll() {
const roll = { const roll = {
modifiers: [], modifiers: [],
trait: this.roll?.trait, trait: this.roll?.trait,
label: 'Attack', label: 'Attack',
@ -334,7 +341,7 @@ export class DHBaseAction extends foundry.abstract.DataModel {
difficulty: this.roll?.difficulty, difficulty: this.roll?.difficulty,
formula: this.roll.getFormula() formula: this.roll.getFormula()
}; };
if(this.roll?.type === 'diceSet') roll.lite = true; if (this.roll?.type === 'diceSet') roll.lite = true;
return roll; return roll;
} }
@ -344,12 +351,14 @@ export class DHBaseAction extends foundry.abstract.DataModel {
} }
async consume(config) { async consume(config) {
const resources = config.costs.filter(c => c.enabled !== false).map(c => { const resources = config.costs
return { type: c.type, value: (c.total ?? c.value) * -1 }; .filter(c => c.enabled !== false)
}); .map(c => {
return { type: c.type, value: (c.total ?? c.value) * -1 };
});
await this.actor.modifyResource(resources); await this.actor.modifyResource(resources);
if(config.uses?.enabled) { if (config.uses?.enabled) {
const newActions = foundry.utils.getProperty(this.item.system, this.systemPath).map(x => x.toObject()); const newActions = foundry.utils.getProperty(this.item.system, this.systemPath).map(x => x.toObject());
newActions[this.index].uses.value++; newActions[this.index].uses.value++;
await this.item.update({ [`system.${this.systemPath}`]: newActions }); await this.item.update({ [`system.${this.systemPath}`]: newActions });
@ -388,13 +397,16 @@ export class DHBaseAction extends foundry.abstract.DataModel {
hasCost(costs) { hasCost(costs) {
const realCosts = this.getRealCosts(costs); const realCosts = this.getRealCosts(costs);
return realCosts.reduce((a, c) => a && this.actor.system.resources[c.type]?.value >= (c.total ?? c.value), true); return realCosts.reduce(
(a, c) => a && this.actor.system.resources[c.type]?.value >= (c.total ?? c.value),
true
);
} }
/* COST */ /* COST */
/* USES */ /* USES */
calcUses(uses) { calcUses(uses) {
if(!uses) return null; if (!uses) return null;
return { return {
...uses, ...uses,
enabled: uses.hasOwnProperty('enabled') ? uses.enabled : true enabled: uses.hasOwnProperty('enabled') ? uses.enabled : true
@ -402,7 +414,7 @@ export class DHBaseAction extends foundry.abstract.DataModel {
} }
hasUses(uses) { hasUses(uses) {
if(!uses) return true; if (!uses) return true;
return (uses.hasOwnProperty('enabled') && !uses.enabled) || uses.value + 1 <= uses.max; return (uses.hasOwnProperty('enabled') && !uses.enabled) || uses.value + 1 <= uses.max;
} }
/* USES */ /* USES */
@ -441,10 +453,10 @@ export class DHBaseAction extends foundry.abstract.DataModel {
let effects = this.effects; let effects = this.effects;
data.system.targets.forEach(async token => { data.system.targets.forEach(async token => {
if (!token.hit && !force) return; if (!token.hit && !force) return;
if(this.hasSave && token.saved.success === true) { if (this.hasSave && token.saved.success === true) {
effects = this.effects.filter(e => e.onSave === true) effects = this.effects.filter(e => e.onSave === true);
} }
if(!effects.length) return; if (!effects.length) return;
effects.forEach(async e => { effects.forEach(async e => {
const actor = canvas.tokens.get(token.id)?.actor, const actor = canvas.tokens.get(token.id)?.actor,
effect = this.item.effects.get(e._id); effect = this.item.effects.get(e._id);
@ -479,38 +491,68 @@ export class DHBaseAction extends foundry.abstract.DataModel {
/* SAVE */ /* SAVE */
async rollSave(target, event, message) { async rollSave(target, event, message) {
if(!target?.actor) return; if (!target?.actor) return;
target.actor.diceRoll({ target.actor
event, .diceRoll({
title: 'Roll Save', event,
roll: { title: 'Roll Save',
trait: this.save.trait, roll: {
difficulty: this.save.difficulty, trait: this.save.trait,
type: "reaction" difficulty: this.save.difficulty,
}, type: 'reaction'
data: target.actor.getRollData() },
}).then(async (result) => { data: target.actor.getRollData()
if(result) this.updateChatMessage(message, target.id, {result: result.roll.total, success: result.roll.success}); })
}) .then(async result => {
if (result)
this.updateChatMessage(message, target.id, {
result: result.roll.total,
success: result.roll.success
});
});
} }
async updateChatMessage(message, targetId, changes, chain=true) { async updateChatMessage(message, targetId, changes, chain = true) {
setTimeout(async () => { setTimeout(async () => {
const chatMessage = ui.chat.collection.get(message._id), const chatMessage = ui.chat.collection.get(message._id),
msgTargets = chatMessage.system.targets, msgTargets = chatMessage.system.targets,
msgTarget = msgTargets.find(mt => mt.id === targetId); msgTarget = msgTargets.find(mt => mt.id === targetId);
msgTarget.saved = changes; msgTarget.saved = changes;
await chatMessage.update({'system.targets': msgTargets}); await chatMessage.update({ 'system.targets': msgTargets });
},100); }, 100);
if(chain) { if (chain) {
if(message.system.source.message) this.updateChatMessage(ui.chat.collection.get(message.system.source.message), targetId, changes, false); 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); const relatedChatMessages = ui.chat.collection.filter(c => c.system.source.message === message._id);
relatedChatMessages.forEach(c => { relatedChatMessages.forEach(c => {
this.updateChatMessage(c, targetId, changes, false); this.updateChatMessage(c, targetId, changes, false);
}) });
} }
} }
/* SAVE */ /* SAVE */
async toChat(origin) {
const cls = getDocumentClass('ChatMessage');
const systemData = {
title: game.i18n.localize('DAGGERHEART.ActionType.action'),
origin: origin,
img: this.img,
name: this.name,
description: this.description,
actions: []
};
const msg = new cls({
type: 'abilityUse',
user: game.user.id,
system: systemData,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/chat/ability-use.hbs',
systemData
)
});
cls.create(msg.toObject());
}
} }
export class DHDamageAction extends DHBaseAction { export class DHDamageAction extends DHBaseAction {
@ -525,7 +567,7 @@ export class DHDamageAction extends DHBaseAction {
getFormulaValue(part, data) { getFormulaValue(part, data) {
let formulaValue = part.value; let formulaValue = part.value;
if(this.hasRoll && part.resultBased && data.system.roll.result.duality === -1) return part.valueAlt; if (this.hasRoll && part.resultBased && data.system.roll.result.duality === -1) return part.valueAlt;
return formulaValue; return formulaValue;
} }
@ -536,18 +578,18 @@ export class DHDamageAction extends DHBaseAction {
let roll = { formula: formula, total: formula }, let roll = { formula: formula, total: formula },
bonusDamage = []; bonusDamage = [];
if(isNaN(formula)) formula = Roll.replaceFormulaData(formula, this.getRollData(data.system ?? data)); if (isNaN(formula)) formula = Roll.replaceFormulaData(formula, this.getRollData(data.system ?? data));
const config = { const config = {
title: game.i18n.format('DAGGERHEART.Chat.DamageRoll.Title', { damage: this.name }), title: game.i18n.format('DAGGERHEART.Chat.DamageRoll.Title', { damage: this.name }),
roll: {formula}, roll: { formula },
targets: (data.system?.targets.filter(t => t.hit) ?? data.targets), targets: data.system?.targets.filter(t => t.hit) ?? data.targets,
hasSave: this.hasSave, hasSave: this.hasSave,
source: data.system?.source, source: data.system?.source,
event event
}; };
if(this.hasSave) config.onSave = this.save.damageMod; if (this.hasSave) config.onSave = this.save.damageMod;
if(data.system) { if (data.system) {
config.source.message = data._id; config.source.message = data._id;
config.directDamage = false; config.directDamage = false;
} }
@ -597,7 +639,8 @@ export class DHHealingAction extends DHBaseAction {
getFormulaValue(data) { getFormulaValue(data) {
let formulaValue = this.healing.value; let formulaValue = this.healing.value;
if(this.hasRoll && this.healing.resultBased && data.system.roll.result.duality === -1) return this.healing.valueAlt; if (this.hasRoll && this.healing.resultBased && data.system.roll.result.duality === -1)
return this.healing.valueAlt;
return formulaValue; return formulaValue;
} }
@ -613,7 +656,7 @@ export class DHHealingAction extends DHBaseAction {
title: game.i18n.format('DAGGERHEART.Chat.HealingRoll.Title', { title: game.i18n.format('DAGGERHEART.Chat.HealingRoll.Title', {
healing: game.i18n.localize(SYSTEM.GENERAL.healingTypes[this.healing.type].label) healing: game.i18n.localize(SYSTEM.GENERAL.healingTypes[this.healing.type].label)
}), }),
roll: {formula}, roll: { formula },
targets: (data.system?.targets ?? data.targets).filter(t => t.hit), targets: (data.system?.targets ?? data.targets).filter(t => t.hit),
messageType: 'healing', messageType: 'healing',
type: this.healing.type, type: this.healing.type,