ChatMessage & takeDamage updates

This commit is contained in:
Dapoolp 2025-07-19 12:52:14 +02:00
parent 7e382d2e08
commit 92feddce1b
13 changed files with 158 additions and 90 deletions

View file

@ -10,12 +10,12 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
this.reject = reject; this.reject = reject;
this.actor = actor; this.actor = actor;
this.damage = damage; this.damage = damage;
console.log(damageType)
const canApplyArmor = damageType.every(t => actor.system.armorApplicableDamageTypes[t] === true); const canApplyArmor = damageType.every(t => actor.system.armorApplicableDamageTypes[t] === true);
const maxArmorMarks = canApplyArmor const maxArmorMarks = canApplyArmor
? Math.min( ? Math.min(
actor.system.armorScore - actor.system.armor.system.marks.value, actor.system.armorScore - actor.system.armor.system.marks.value,
actor.system.rules.damageReduction.maxArmorMarked.total actor.system.rules.damageReduction.maxArmorMarked.value
) )
: 0; : 0;
@ -100,7 +100,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
context.armorScore = this.actor.system.armorScore; context.armorScore = this.actor.system.armorScore;
context.armorMarks = currentMarks; context.armorMarks = currentMarks;
context.basicMarksUsed = context.basicMarksUsed =
selectedArmorMarks.length === this.actor.system.rules.damageReduction.maxArmorMarked.total; selectedArmorMarks.length === this.actor.system.rules.damageReduction.maxArmorMarked.value;
const stressReductionStress = this.availableStressReductions const stressReductionStress = this.availableStressReductions
? stressReductions.reduce((acc, red) => acc + red.cost, 0) ? stressReductions.reduce((acc, red) => acc + red.cost, 0)

View file

@ -187,7 +187,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.attackTargetDoesNotExist')); ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.attackTargetDoesNotExist'));
return; return;
} }
game.canvas.pan(token); game.canvas.pan(token);
}; };
@ -210,12 +209,22 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
if (targets.length === 0) if (targets.length === 0)
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected')); ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noTargetsSelected'));
for (let target of targets) {
let damage = message.system.roll.total;
if (message.system.onSave && message.system.targets.find(t => t.id === target.id)?.saved?.success === true)
damage = Math.ceil(damage * (CONFIG.DH.ACTIONS.damageOnSave[message.system.onSave]?.mod ?? 1));
target.actor.takeDamage(damage, message.system.damage.damageType); for (let target of targets) {
let damages = message.system.damage;
if (message.system.onSave && message.system.targets.find(t => t.id === target.id)?.saved?.success === true) {
const mod = CONFIG.DH.ACTIONS.damageOnSave[message.system.onSave]?.mod ?? 1;
Object.entries(damages).forEach((k,v) => {
let newTotal = 0;
v.forEach(part => {
v.total = Math.ceil(v.total * mod);
newTotal += v.total;
})
})
}
// damage = Math.ceil(damage * (CONFIG.DH.ACTIONS.damageOnSave[message.system.onSave]?.mod ?? 1));
target.actor.takeDamage(damages.roll);
} }
}; };

View file

@ -163,6 +163,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
} }
getRollData(data = {}) { getRollData(data = {}) {
if(!this.actor) return null;
const actorData = this.actor.getRollData(false); const actorData = this.actor.getRollData(false);
// Add Roll results to RollDatas // Add Roll results to RollDatas
@ -177,6 +178,8 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
} }
async use(event, ...args) { async use(event, ...args) {
if(!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
const isFastForward = event.shiftKey || (!this.hasRoll && !this.hasSave); const isFastForward = event.shiftKey || (!this.hasRoll && !this.hasSave);
// Prepare base Config // Prepare base Config
const initConfig = this.initActionConfig(event); const initConfig = this.initActionConfig(event);

View file

@ -170,11 +170,10 @@ export default class DhCharacter extends BaseDataActor {
rules: new fields.SchemaField({ rules: new fields.SchemaField({
damageReduction: new fields.SchemaField({ damageReduction: new fields.SchemaField({
maxArmorMarked: new fields.SchemaField({ maxArmorMarked: new fields.SchemaField({
value: new fields.NumberField({ required: true, integer: true, initial: 1 }), value: new fields.NumberField({
bonus: new fields.NumberField({
required: true, required: true,
integer: true, integer: true,
initial: 0, initial: 1,
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedBonus' label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedBonus'
}), }),
stressExtra: new fields.NumberField({ stressExtra: new fields.NumberField({

View file

@ -140,28 +140,22 @@ export default class D20Roll extends DHRoll {
return modifiers; return modifiers;
} }
static async buildEvaluate(roll, config = {}, message = {}) {
if (config.evaluate !== false) await roll.evaluate();
this.postEvaluate(roll, config);
}
static postEvaluate(roll, config = {}) { static postEvaluate(roll, config = {}) {
super.postEvaluate(roll, config); const data = super.postEvaluate(roll, config);
if (config.targets?.length) { if (config.targets?.length) {
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 = this.isCritical || roll.total >= difficulty;
}); });
} else if (config.roll.difficulty) } else if (config.roll.difficulty)
config.roll.success = roll.isCritical || roll.total >= config.roll.difficulty; data.success = roll.isCritical || roll.total >= config.roll.difficulty;
config.roll.advantage = { data.advantage = {
type: config.roll.advantage, type: config.roll.advantage,
dice: roll.dAdvantage?.denomination, dice: roll.dAdvantage?.denomination,
value: roll.dAdvantage?.total value: roll.dAdvantage?.total
}; };
config.roll.isCritical = roll.isCritical; data.isCritical = roll.isCritical;
config.roll.extra = roll.dice data.extra = roll.dice
.filter(d => !roll.baseTerms.includes(d)) .filter(d => !roll.baseTerms.includes(d))
.map(d => { .map(d => {
return { return {
@ -169,7 +163,8 @@ export default class D20Roll extends DHRoll {
value: d.total value: d.total
}; };
}); });
config.roll.modifierTotal = this.calculateTotalModifiers(roll); data.modifierTotal = this.calculateTotalModifiers(roll);
return data;
} }
resetFormula() { resetFormula() {

View file

@ -14,14 +14,18 @@ export default class DamageRoll extends DHRoll {
if ( config.evaluate !== false ) { if ( config.evaluate !== false ) {
for ( const roll of config.roll ) await roll.roll.evaluate(); for ( const roll of config.roll ) await roll.roll.evaluate();
} }
config.roll = config.roll.map(r => this.postEvaluate(r.roll)); const parts = config.roll.map(r => this.postEvaluate(r));
config.roll = this.unifyDamageRoll(parts);
} }
static postEvaluate(roll, config = {}) { static postEvaluate(roll, config = {}) {
return { return {
...super.postEvaluate(roll, config), ...roll,
...super.postEvaluate(roll.roll, config),
damageTypes: [...roll.damageTypes],
roll: roll.roll,
type: config.type, type: config.type,
modifierTotal: this.calculateTotalModifiers(roll) modifierTotal: this.calculateTotalModifiers(roll.roll)
} }
} }
@ -33,6 +37,43 @@ export default class DamageRoll extends DHRoll {
} }
} }
static unifyDamageRoll(rolls) {
const unified = {};
rolls.forEach(r => {
const resource = unified[r.applyTo] ?? { formula: '', total: 0, parts: [] };
resource.formula += `${resource.formula !== '' ? ' + ' : ''}${r.formula}`;
resource.total += r.total;
resource.parts.push(r);
unified[r.applyTo] = resource;
})
return unified;
}
static formatGlobal(rolls) {
let formula, total;
const applyTo = new Set(rolls.flatMap(r => r.applyTo));
if(applyTo.size > 1) {
const data = {};
rolls.forEach(r => {
if(data[r.applyTo]) {
data[r.applyTo].formula += ` + ${r.formula}` ;
data[r.applyTo].total += r.total ;
} else {
data[r.applyTo] = {
formula: r.formula,
total: r.total
}
}
});
formula = Object.entries(data).reduce((a, [k,v]) => a + ` ${k}: ${v.formula}`, '');
total = Object.entries(data).reduce((a, [k,v]) => a + ` ${k}: ${v.total}`, '');
} else {
formula = rolls.map(r => r.formula).join(' + ');
total = rolls.reduce((a,c) => a + c.total, 0)
}
return {formula, total}
}
applyBaseBonus(part) { applyBaseBonus(part) {
const modifiers = [], const modifiers = [],
type = this.options.messageType ?? 'damage', type = this.options.messageType ?? 'damage',

View file

@ -47,7 +47,7 @@ export default class DHRoll extends Roll {
static async buildEvaluate(roll, config = {}, message = {}) { static async buildEvaluate(roll, config = {}, message = {}) {
if (config.evaluate !== false) await roll.evaluate(); if (config.evaluate !== false) await roll.evaluate();
config.roll = this.postEvaluate(roll); config.roll = this.postEvaluate(roll, config);
} }
static async buildPost(roll, config, message) { static async buildPost(roll, config, message) {
@ -57,8 +57,9 @@ export default class DHRoll extends Roll {
// Create Chat Message // Create Chat Message
if (config.source?.message) { if (config.source?.message) {
if(Array.isArray(config.roll)) { console.log(config)
const pool = foundry.dice.terms.PoolTerm.fromRolls(config.roll); if(Array.isArray(config.roll?.parts)) {
const pool = foundry.dice.terms.PoolTerm.fromRolls(config.roll.parts.map(r => r.roll));
roll = Roll.fromTerms([pool]); roll = Roll.fromTerms([pool]);
} }
if (game.modules.get('dice-so-nice')?.active) await game.dice3d.showForRoll(roll, game.user, true); if (game.modules.get('dice-so-nice')?.active) await game.dice3d.showForRoll(roll, game.user, true);
@ -67,7 +68,7 @@ export default class DHRoll extends Roll {
} }
} }
static postEvaluate(roll) { static postEvaluate(roll, config = {}) {
return { return {
total: roll.total, total: roll.total,
formula: roll.formula, formula: roll.formula,

View file

@ -161,21 +161,21 @@ export default class DualityRoll extends D20Roll {
} }
static postEvaluate(roll, config = {}) { static postEvaluate(roll, config = {}) {
super.postEvaluate(roll, config); const data = super.postEvaluate(roll, config);
config.roll.hope = { data.hope = {
dice: roll.dHope.denomination, dice: roll.dHope.denomination,
value: roll.dHope.total value: roll.dHope.total
}; };
config.roll.fear = { data.fear = {
dice: roll.dFear.denomination, dice: roll.dFear.denomination,
value: roll.dFear.total value: roll.dFear.total
}; };
config.roll.rally = { data.rally = {
dice: roll.dRally?.denomination, dice: roll.dRally?.denomination,
value: roll.dRally?.total value: roll.dRally?.total
}; };
config.roll.result = { data.result = {
duality: roll.withHope ? 1 : roll.withFear ? -1 : 0, duality: roll.withHope ? 1 : roll.withFear ? -1 : 0,
total: roll.dHope.total + roll.dFear.total, total: roll.dHope.total + roll.dFear.total,
label: roll.totalLabel label: roll.totalLabel
@ -184,6 +184,8 @@ export default class DualityRoll extends D20Roll {
if(roll._rallyIndex && roll.data?.parent) if(roll._rallyIndex && roll.data?.parent)
roll.data.parent.deleteEmbeddedDocuments('ActiveEffect', [roll._rallyIndex]); roll.data.parent.deleteEmbeddedDocuments('ActiveEffect', [roll._rallyIndex]);
setDiceSoNiceForDualityRoll(roll, config.roll.advantage.type); setDiceSoNiceForDualityRoll(roll, data.advantage.type);
return data;
} }
} }

View file

@ -391,27 +391,40 @@ export default class DhpActor extends Actor {
return canUseArmor || canUseStress; return canUseArmor || canUseStress;
} }
async takeDamage(baseDamage, type) { async takeDamage(damages) {
if (Hooks.call(`${CONFIG.DH.id}.preTakeDamage`, this, baseDamage, type) === false) return null; if (Hooks.call(`${CONFIG.DH.id}.preTakeDamage`, this, damages) === false) return null;
if (this.type === 'companion') { if (this.type === 'companion') {
await this.modifyResource([{ value: 1, key: 'stress' }]); await this.modifyResource([{ value: 1, key: 'stress' }]);
return; return;
} }
type = !Array.isArray(type) ? [type] : type; const updates = [];
const hpDamage = this.calculateDamage(baseDamage, type); Object.entries(damages).forEach(([type, damage]) => {
damage.parts.forEach(part => {
if(part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id)
part.total = this.calculateDamage(part.total, part.damageTypes);
const update = updates.find(u => u.type === type);
if(update) {
update.value += part.total;
update.damageTypes.add(...new Set(part.damageTypes));
} else updates.push({ value: part.total, type, damageTypes: new Set(part.damageTypes) })
})
});
if (!hpDamage) return; if (Hooks.call(`${CONFIG.DH.id}.postCalculateDamage`, this, damages) === false) return null;
const updates = [{ value: hpDamage, type: 'hitPoints' }]; if(!updates.length) return;
if (this.type === 'character' && this.system.armor && this.#canReduceDamage(hpDamage, type)) { const hpDamage = updates.find(u => u.type === CONFIG.DH.GENERAL.healingTypes.hitPoints.id);
if(hpDamage) {
hpDamage.value = this.convertDamageToThreshold(hpDamage.value);
if (this.type === 'character' && this.system.armor && this.#canReduceDamage(hpDamage.value, hpDamage.damageTypes)) {
const armorStackResult = await this.owner.query('armorStack', { const armorStackResult = await this.owner.query('armorStack', {
actorId: this.uuid, actorId: this.uuid,
damage: hpDamage, damage: hpDamage.value,
type: type type: [...hpDamage.damageTypes]
}); });
if (armorStackResult) { if (armorStackResult) {
const { modifiedDamage, armorSpent, stressSpent } = armorStackResult; const { modifiedDamage, armorSpent, stressSpent } = armorStackResult;
@ -422,28 +435,23 @@ export default class DhpActor extends Actor {
); );
} }
} }
}
await this.modifyResource(updates); await this.modifyResource(updates);
if (Hooks.call(`${CONFIG.DH.id}.postTakeDamage`, this, damage, type) === false) return null; if (Hooks.call(`${CONFIG.DH.id}.postTakeDamage`, this, damages) === false) return null;
} }
calculateDamage(baseDamage, type) { calculateDamage(baseDamage, type) {
if (Hooks.call(`${CONFIG.DH.id}.preCalculateDamage`, this, baseDamage, type) === false) return null;
/* if(this.system.resistance[type]?.immunity) return 0;
if(this.system.resistance[type]?.resistance) baseDamage = Math.ceil(baseDamage / 2); */
if (this.canResist(type, 'immunity')) return 0; if (this.canResist(type, 'immunity')) return 0;
if (this.canResist(type, 'resistance')) baseDamage = Math.ceil(baseDamage / 2); if (this.canResist(type, 'resistance')) baseDamage = Math.ceil(baseDamage / 2);
// const flatReduction = this.system.resistance[type].reduction;
const flatReduction = this.getDamageTypeReduction(type); const flatReduction = this.getDamageTypeReduction(type);
const damage = Math.max(baseDamage - (flatReduction ?? 0), 0); const damage = Math.max(baseDamage - (flatReduction ?? 0), 0);
const hpDamage = this.convertDamageToThreshold(damage); // const hpDamage = this.convertDamageToThreshold(damage);
if (Hooks.call(`${CONFIG.DH.id}.postCalculateDamage`, this, baseDamage, type) === false) return null; return damage;
return hpDamage;
} }
canResist(type, resistance) { canResist(type, resistance) {

View file

@ -16,7 +16,7 @@ export default class DHToken extends TokenDocument {
}); });
bars.sort((a, b) => a.label.compare(b.label)); bars.sort((a, b) => a.label.compare(b.label));
const invalidAttributes = ['gold', 'levelData', 'actions', 'rules.damageReduction.maxArmorMarked.value']; const invalidAttributes = ['gold', 'levelData', 'actions'];
const values = attributes.value.reduce((acc, v) => { const values = attributes.value.reduce((acc, v) => {
const a = v.join('.'); const a = v.join('.');
if (invalidAttributes.some(x => a.startsWith(x))) return acc; if (invalidAttributes.some(x => a.startsWith(x))) return acc;

View file

@ -164,6 +164,11 @@
} }
} }
} }
.damage-resource {
font-weight: 600;
margin-top: 5px;
}
} }
.dice-total { .dice-total {

View file

@ -4,7 +4,7 @@
</header> </header>
{{#each @root.formula}} {{#each @root.formula}}
<div class="damage-formula"> <div class="damage-formula">
<span class="formula-label"><b>Formula:</b> {{roll.formula}}</span> <span class="damage-resource"><b>Formula:</b> {{roll.formula}}</span>
{{!-- <span>{{localize (concat 'CONFIG.DH.GENERAL.healingTypes.' applyTo '.label')}}</span> --}} {{!-- <span>{{localize (concat 'CONFIG.DH.GENERAL.healingTypes.' applyTo '.label')}}</span> --}}
<span class="damage-details"> <span class="damage-details">
{{#with (lookup @root.config.GENERAL.healingTypes applyTo)}} {{#with (lookup @root.config.GENERAL.healingTypes applyTo)}}

View file

@ -2,28 +2,33 @@
{{log damage}} {{log damage}}
{{#unless noTitle}}<div class="dice-flavor">{{damage.title}}</div>{{/unless}} {{#unless noTitle}}<div class="dice-flavor">{{damage.title}}</div>{{/unless}}
<div class="dice-result"> <div class="dice-result">
<div class="dice-formula">{{damage.roll.formula}}</div> {{#each damage.roll as | roll index | }}
<div class="dice-flavor">{{localize (concat 'DAGGERHEART.CONFIG.HealingType.' index '.name')}}</div>
<div class="dice-formula">{{roll.formula}}</div>
<div class="dice-tooltip"> <div class="dice-tooltip">
<div class="wrapper"> <div class="wrapper">
{{#each roll.parts}}
<section class="tooltip-part"> <section class="tooltip-part">
{{#each damage.roll.dice}}
<div class="dice"> <div class="dice">
<header class="part-header flexrow"> <header class="part-header flexrow">
<span class="part-formula">{{formula}}</span> <span class="part-formula">{{formula}}</span>
<span class="part-total">{{total}}</span> <span class="part-total">{{total}}</span>
</header> </header>
<ol class="dice-rolls"> <ol class="dice-rolls">
{{#each dice}}
{{#each results}} {{#each results}}
<li class="roll die {{../dice}} min">{{result}}</li> <li class="roll die {{../dice}} min">{{result}}</li>
{{/each}} {{/each}}
{{/each}}
</ol> </ol>
</div> </div>
{{/each}} {{#if modifierTotal}}<div class="duality-modifier">{{#if (gt modifierTotal 0)}}+{{/if}}{{modifierTotal}}</div>{{/if}}
{{#if damage.roll.modifierTotal}}<div class="duality-modifier">{{#if (gt damage.roll.modifierTotal 0)}}+{{/if}}{{damage.roll.modifierTotal}}</div>{{/if}} <div class="duality-result">Total: {{total}}</div>
<div class="duality-result">Total: {{damage.roll.total}}</div>
</section> </section>
{{/each}}
</div> </div>
</div> </div>
<div class="dice-total">{{damage.roll.total}}</div> <div class="dice-total">{{roll.total}}</div>
{{/each}}
</div> </div>
</div> </div>