Feature/635 allow weapon direct damage (#657)

* #635 & #637

* #653
This commit is contained in:
Dapoulp 2025-08-06 20:28:53 +02:00 committed by GitHub
parent cf571aa2a5
commit bcedc74bf3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 57 additions and 156 deletions

View file

@ -2,7 +2,6 @@ export { default as BeastformDialog } from './beastformDialog.mjs';
export { default as d20RollDialog } from './d20RollDialog.mjs'; export { default as d20RollDialog } from './d20RollDialog.mjs';
export { default as DamageDialog } from './damageDialog.mjs'; export { default as DamageDialog } from './damageDialog.mjs';
export { default as DamageReductionDialog } from './damageReductionDialog.mjs'; export { default as DamageReductionDialog } from './damageReductionDialog.mjs';
export { default as DamageSelectionDialog } from './damageSelectionDialog.mjs';
export { default as DeathMove } from './deathMove.mjs'; export { default as DeathMove } from './deathMove.mjs';
export { default as Downtime } from './downtime.mjs'; export { default as Downtime } from './downtime.mjs';
export { default as MulticlassChoiceDialog } from './multiclassChoiceDialog.mjs'; export { default as MulticlassChoiceDialog } from './multiclassChoiceDialog.mjs';

View file

@ -1,128 +0,0 @@
// TO DELETE ?
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
export default class DamageSelectionDialog extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(rollString, bonusDamage, resolve, hope = 0) {
super({});
this.data = {
rollString,
bonusDamage: bonusDamage.reduce((acc, x) => {
if (x.appliesOn === CONFIG.DH.EFFECTS.applyLocations.damageRoll.id) {
acc.push({
...x,
hopeUses: 0
});
}
return acc;
}, []),
hope
};
this.resolve = resolve;
}
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'damage-selection'],
position: {
width: 400,
height: 'auto'
},
actions: {
decreaseHopeUse: this.decreaseHopeUse,
increaseHopeUse: this.increaseHopeUse,
rollDamage: this.rollDamage
},
form: {
handler: this.updateSelection,
submitOnChange: true,
closeOnSubmit: false
}
};
/** @override */
static PARTS = {
damageSelection: {
id: 'damageSelection',
template: 'systems/daggerheart/templates/dialogs/dice-roll/damageSelection.hbs'
}
};
/* -------------------------------------------- */
/** @inheritDoc */
get title() {
return `Damage Options`;
}
async _prepareContext(_options) {
return {
rollString: this.getRollString(),
bonusDamage: this.data.bonusDamage,
hope: this.data.hope + 1,
hopeUsed: this.getHopeUsed()
};
}
static updateSelection(event, _, formData) {
const { bonusDamage, ...rest } = foundry.utils.expandObject(formData.object);
for (var index in bonusDamage) {
this.data.bonusDamage[index].initiallySelected = bonusDamage[index].initiallySelected;
if (bonusDamage[index].hopeUses) {
const value = Number.parseInt(bonusDamage[index].hopeUses);
if (!Number.isNaN(value)) this.data.bonusDamage[index].hopeUses = value;
}
}
this.data = foundry.utils.mergeObject(this.data, rest);
this.render(true);
}
getRollString() {
return this.data.rollString.concat(
this.data.bonusDamage.reduce((acc, x) => {
if (x.initiallySelected) {
const nr = 1 + x.hopeUses;
const baseDamage = x.value;
return acc.concat(` + ${nr}${baseDamage}`);
}
return acc;
}, '')
);
}
getHopeUsed() {
return this.data.bonusDamage.reduce((acc, x) => acc + x.hopeUses, 0);
}
static decreaseHopeUse(_, button) {
const index = Number.parseInt(button.dataset.index);
if (this.data.bonusDamage[index].hopeUses - 1 >= 0) {
this.data.bonusDamage[index].hopeUses -= 1;
this.render(true);
}
}
static increaseHopeUse(_, button) {
const index = Number.parseInt(button.dataset.index);
if (this.data.bonusDamage[index].hopeUses <= this.data.hope + 1) {
this.data.bonusDamage[index].hopeUses += 1;
this.render(true);
}
}
static rollDamage(event) {
event.preventDefault();
this.resolve({
rollString: this.getRollString(),
bonusDamage: this.data.bonusDamage,
hopeUsed: this.getHopeUsed()
});
this.close();
}
}

View file

@ -7,7 +7,11 @@ export default class DhpEnvironment extends DHBaseActorSheet {
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
classes: ['environment'], classes: ['environment'],
position: { position: {
width: 500 width: 500,
height: 725
},
window: {
resizable: true
}, },
actions: {}, actions: {},
dragDrop: [{ dragSelector: '.action-section .inventory-item', dropSelector: null }] dragDrop: [{ dragSelector: '.action-section .inventory-item', dropSelector: null }]

View file

@ -323,6 +323,20 @@ export default function DHApplicationMixin(Base) {
]; ];
if (usable) if (usable)
options.unshift({
name: 'DAGGERHEART.GENERAL.damage',
icon: 'fa-solid fa-explosion',
condition: target => {
const doc = getDocFromElementSync(target);
return doc?.system?.attack?.damage.parts.length || doc?.damage?.parts.length;
},
callback: async (target, event) => {
const doc = await getDocFromElement(target),
action = doc?.system?.attack ?? doc;
return action && action.use(event, { byPassRoll: true })
}
});
options.unshift({ options.unshift({
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem', name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
icon: 'fa-solid fa-burst', icon: 'fa-solid fa-burst',
@ -334,7 +348,7 @@ export default function DHApplicationMixin(Base) {
}); });
if (toChat) if (toChat)
options.unshift({ options.push({
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat', name: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
icon: 'fa-solid fa-message', icon: 'fa-solid fa-message',
callback: async target => (await getDocFromElement(target)).toChat(this.document.id) callback: async target => (await getDocFromElement(target)).toChat(this.document.id)

View file

@ -194,8 +194,11 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
event.stopPropagation(); event.stopPropagation();
const item = await foundry.utils.fromUuid(message.system.origin); const item = await foundry.utils.fromUuid(message.system.origin);
const action = item.system.actions.get(event.currentTarget.id); const action = item.system.attack?.id === event.currentTarget.id ? item.system.attack : item.system.actions.get(event.currentTarget.id);
await item.use(action); if(event.currentTarget.dataset.directDamage)
action.use(event, { byPassRoll: true })
else
action.use(event);
} }
async actionUseButton(event, message) { async actionUseButton(event, message) {

View file

@ -34,8 +34,8 @@ export default class DHAttackAction extends DHDamageAction {
}; };
} }
async use(event, ...args) { async use(event, options) {
const result = await super.use(event, args); const result = await super.use(event, options);
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns; const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
await updateCountdowns(CONFIG.DH.GENERAL.countdownTypes.characterAttack.id); await updateCountdowns(CONFIG.DH.GENERAL.countdownTypes.characterAttack.id);

View file

@ -111,12 +111,13 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
return actorData; return actorData;
} }
async use(event, ...args) { async use(event, options = {}) {
if (!this.actor) throw new Error("An Action can't be used outside of an Actor context."); if (!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
if (this.chatDisplay) await this.toChat(); if (this.chatDisplay) await this.toChat();
let config = this.prepareConfig(event); let { byPassRoll } = options,
config = this.prepareConfig(event, byPassRoll);
for (let i = 0; i < this.constructor.extraSchemas.length; i++) { for (let i = 0; i < this.constructor.extraSchemas.length; i++) {
let clsField = this.constructor.getActionField(this.constructor.extraSchemas[i]); let clsField = this.constructor.getActionField(this.constructor.extraSchemas[i]);
if (clsField?.prepareConfig) { if (clsField?.prepareConfig) {
@ -133,14 +134,14 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
if (!config) return; if (!config) return;
} }
if (this.hasRoll) { if (config.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.doFollowUp()) { if (this.doFollowUp(config)) {
if (this.rollDamage && this.damage.parts.length) await this.rollDamage(event, 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.trigger) await this.trigger(event, config);
else if (this.hasSave || this.hasEffect) { else if (this.hasSave || this.hasEffect) {
@ -160,7 +161,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
} }
/* */ /* */
prepareConfig(event) { prepareConfig(event, byPass = false) {
const hasRoll = this.getUseHasRoll(byPass);
return { return {
event, event,
title: `${this.item.name}: ${this.name}`, title: `${this.item.name}: ${this.name}`,
@ -170,10 +172,10 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
actor: this.actor.uuid actor: this.actor.uuid
}, },
dialog: { dialog: {
configure: this.hasRoll configure: hasRoll
}, },
type: this.type, type: this.type,
hasRoll: this.hasRoll, hasRoll: hasRoll,
hasDamage: this.damage?.parts?.length && this.type !== 'healing', hasDamage: this.damage?.parts?.length && this.type !== 'healing',
hasHealing: this.damage?.parts?.length && this.type === 'healing', hasHealing: this.damage?.parts?.length && this.type === 'healing',
hasEffect: !!this.effects?.length, hasEffect: !!this.effects?.length,
@ -182,12 +184,12 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
selectedRollMode: game.settings.get('core', 'rollMode'), selectedRollMode: game.settings.get('core', 'rollMode'),
isFastForward: event.shiftKey, isFastForward: event.shiftKey,
data: this.getRollData(), data: this.getRollData(),
evaluate: this.hasRoll evaluate: hasRoll
}; };
} }
requireConfigurationDialog(config) { requireConfigurationDialog(config) {
return !config.event.shiftKey && !this.hasRoll && (config.costs?.length || config.uses); return !config.event.shiftKey && !config.hasRoll && (config.costs?.length || config.uses);
} }
prepareRoll() { prepareRoll() {
@ -205,7 +207,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
} }
doFollowUp(config) { doFollowUp(config) {
return !this.hasRoll; return !config.hasRoll;
} }
async consume(config, successCost = false) { async consume(config, successCost = false) {
@ -257,6 +259,10 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
/* */ /* */
/* ROLL */ /* ROLL */
getUseHasRoll(byPass = false) {
return this.hasRoll && !byPass;
}
get hasRoll() { get hasRoll() {
return !!this.roll?.type; return !!this.roll?.type;
} }

View file

@ -4,7 +4,7 @@ import DHBaseAction from './baseAction.mjs';
export default class DhBeastformAction extends DHBaseAction { export default class DhBeastformAction extends DHBaseAction {
static extraSchemas = [...super.extraSchemas, 'beastform']; static extraSchemas = [...super.extraSchemas, 'beastform'];
async use(event, ...args) { async use(event, options) {
const beastformConfig = this.prepareBeastformConfig(); const beastformConfig = this.prepareBeastformConfig();
const abort = await this.handleActiveTransformations(); const abort = await this.handleActiveTransformations();
@ -20,7 +20,7 @@ export default class DhBeastformAction extends DHBaseAction {
const { selected, evolved, hybrid } = await BeastformDialog.configure(beastformConfig, this.item); const { selected, evolved, hybrid } = await BeastformDialog.configure(beastformConfig, this.item);
if (!selected) return; if (!selected) return;
const result = await super.use(event, args); const result = await super.use(event, options);
if (!result) return; if (!result) return;
await this.transform(selected, evolved, hybrid); await this.transform(selected, evolved, hybrid);

View file

@ -6,7 +6,7 @@ export default 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 (data.hasRoll && part.resultBased && data.system.roll.result.duality === -1) return part.valueAlt;
const isAdversary = this.actor.type === 'adversary'; const isAdversary = this.actor.type === 'adversary';
if (isAdversary && this.actor.system.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id) { if (isAdversary && this.actor.system.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id) {

View file

@ -10,8 +10,6 @@ export default class DHMacroAction extends DHBaseAction {
} }
async trigger(event, ...args) { async trigger(event, ...args) {
// const config = await super.use(event, args);
// if (['error', 'warning'].includes(config.type)) return;
const fixUUID = !this.documentUUID.includes('Macro.') ? `Macro.${this.documentUUID}` : this.documentUUID, const fixUUID = !this.documentUUID.includes('Macro.') ? `Macro.${this.documentUUID}` : this.documentUUID,
macro = await fromUuid(fixUUID); macro = await fromUuid(fixUUID);
try { try {

View file

@ -11,7 +11,6 @@ export default class DHSummonAction extends DHBaseAction {
async trigger(event, ...args) { async trigger(event, ...args) {
if (!this.canSummon || !canvas.scene) return; if (!this.canSummon || !canvas.scene) return;
// const config = await super.use(event, args);
} }
get canSummon() { get canSummon() {

View file

@ -132,7 +132,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
if(this.targetSelection === true) { if(this.targetSelection === true) {
this.targetShort = this.targets.reduce((a,c) => { this.targetShort = this.targets.reduce((a,c) => {
if(c.hit) a.hit += 1; if(c.hit) a.hit += 1;
else c.miss += 1; else a.miss += 1;
return a; return a;
}, {hit: 0, miss: 0}) }, {hit: 0, miss: 0})
} }

View file

@ -145,7 +145,7 @@ export default class D20Roll extends DHRoll {
config.targetSelection = true; config.targetSelection = true;
config.targets.forEach(target => { config.targets.forEach(target => {
const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion; const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion;
target.hit = this.isCritical || roll.total >= difficulty; target.hit = roll.isCritical || roll.total >= difficulty;
}); });
data.success = config.targets.some(target => target.hit) data.success = config.targets.some(target => target.hit)
} else if (config.roll.difficulty) { } else if (config.roll.difficulty) {

View file

@ -163,7 +163,7 @@ export default class DHItem extends foundry.documents.Item {
img: this.img, img: this.img,
tags: this._getTags() tags: this._getTags()
}, },
actions: item.system.actions, actions: item.system.actionsList,
description: this.system.description description: this.system.description
}; };

View file

@ -17,6 +17,7 @@
.feature-tab { .feature-tab {
border: none; border: none;
white-space: nowrap;
a { a {
color: light-dark(@dark-blue, @golden); color: light-dark(@dark-blue, @golden);

View file

@ -19,7 +19,12 @@
<button class="ability-use-button" id="{{action.id}}"> <button class="ability-use-button" id="{{action.id}}">
{{action.name}} {{action.name}}
</button> </button>
{{#if action.cost.value}}<div class="ability-card-action-cost">{{action.cost.value}} {{action.cost.type}}</div>{{/if}} {{#if (eq action.systemPath "attack")}}
<button class="ability-use-button" id="{{action.id}}" data-direct-damage="true">
{{localize "DAGGERHEART.GENERAL.damage"}}
</button>
{{/if}}
{{!-- {{#if action.cost.value}}<div class="ability-card-action-cost">{{action.cost.value}} {{action.cost.type}}</div>{{/if}} --}}
{{/each}} {{/each}}
</footer> </footer>
</div> </div>