Merge branch 'main' into feature/336-damage-targeted-resources

This commit is contained in:
Dapoolp 2025-07-15 17:10:28 +02:00
commit 31647d71ee
52 changed files with 701 additions and 290 deletions

View file

@ -38,6 +38,15 @@ export default class DHAttackAction extends DHDamageAction {
};
}
async use(event, ...args) {
const result = await super.use(event, args);
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
await updateCountdowns(CONFIG.DH.GENERAL.countdownTypes.characterAttack.id);
return result;
}
// get modifiers() {
// return [];
// }

View file

@ -254,7 +254,8 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
hasDamage: !!this.damage?.parts?.length,
hasHealing: !!this.healing,
hasEffect: !!this.effects?.length,
hasSave: this.hasSave
hasSave: this.hasSave,
selectedRollMode: game.settings.get('core', 'rollMode')
};
}
@ -350,7 +351,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
}
get modifiers() {
if(!this.actor) return [];
if (!this.actor) return [];
const modifiers = [];
/** Placeholder for specific bonuses **/
return modifiers;

View file

@ -10,6 +10,7 @@ export default class DHDamageAction extends DHBaseAction {
}
async rollDamage(event, data) {
const systemData = data.system ?? data;
let formula = this.damage.parts.map(p => this.getFormulaValue(p, data).getFormula(this.actor)).join(' + '),
damageTypes = [...new Set(this.damage.parts.reduce((a, c) => a.concat([...c.type]), []))];
@ -19,15 +20,15 @@ export default class DHDamageAction extends DHBaseAction {
let roll = { formula: formula, total: formula },
bonusDamage = [];
if (isNaN(formula)) formula = Roll.replaceFormulaData(formula, this.getRollData(data.system ?? data));
if (isNaN(formula)) formula = Roll.replaceFormulaData(formula, this.getRollData(systemData));
const config = {
title: game.i18n.format('DAGGERHEART.UI.Chat.damageRoll.title', { damage: this.name }),
roll: { formula },
targets: data.system?.targets.filter(t => t.hit) ?? data.targets,
targets: systemData.targets.filter(t => t.hit) ?? data.targets,
hasSave: this.hasSave,
isCritical: data.system?.roll?.isCritical ?? false,
source: data.system?.source,
isCritical: systemData.roll?.isCritical ?? false,
source: systemData.source,
data: this.getRollData(),
damageTypes,
event
@ -36,6 +37,8 @@ export default class DHDamageAction extends DHBaseAction {
if (data.system) {
config.source.message = data._id;
config.directDamage = false;
} else {
config.directDamage = true;
}
roll = CONFIG.Dice.daggerheart.DamageRoll.build(config);

View file

@ -31,14 +31,29 @@ export default class DhpAdversary extends BaseDataActor {
motivesAndTactics: new fields.StringField(),
notes: new fields.HTMLField(),
difficulty: new fields.NumberField({ required: true, initial: 1, integer: true }),
hordeHp: new fields.NumberField({ required: true, initial: 1, integer: true }),
hordeHp: new fields.NumberField({
required: true,
initial: 1,
integer: true,
label: 'DAGGERHEART.GENERAL.hordeHp'
}),
damageThresholds: new fields.SchemaField({
major: new fields.NumberField({ required: true, initial: 0, integer: true }),
severe: new fields.NumberField({ required: true, initial: 0, integer: true })
major: new fields.NumberField({
required: true,
initial: 0,
integer: true,
label: 'DAGGERHEART.GENERAL.DamageThresholds.majorThreshold'
}),
severe: new fields.NumberField({
required: true,
initial: 0,
integer: true,
label: 'DAGGERHEART.GENERAL.DamageThresholds.severeThreshold'
})
}),
resources: new fields.SchemaField({
hitPoints: resourceField(0, true),
stress: resourceField(0, true)
hitPoints: resourceField(0, 'DAGGERHEART.GENERAL.hitPoints', true),
stress: resourceField(0, 'DAGGERHEART.GENERAL.stress', true)
}),
attack: new ActionField({
initial: {
@ -75,13 +90,13 @@ export default class DhpAdversary extends BaseDataActor {
),
bonuses: new fields.SchemaField({
roll: new fields.SchemaField({
attack: bonusField(),
action: bonusField(),
reaction: bonusField()
attack: bonusField('DAGGERHEART.GENERAL.Roll.attack'),
action: bonusField('DAGGERHEART.GENERAL.Roll.action'),
reaction: bonusField('DAGGERHEART.GENERAL.Roll.reaction')
}),
damage: new fields.SchemaField({
physical: bonusField(),
magical: bonusField()
physical: bonusField('DAGGERHEART.GENERAL.Damage.physicalDamage'),
magical: bonusField('DAGGERHEART.GENERAL.Damage.magicalDamage')
})
})
};

View file

@ -1,10 +1,10 @@
import DHBaseActorSettings from '../../applications/sheets/api/actor-setting.mjs';
const resistanceField = () =>
const resistanceField = reductionLabel =>
new foundry.data.fields.SchemaField({
resistance: new foundry.data.fields.BooleanField({ initial: false }),
immunity: new foundry.data.fields.BooleanField({ initial: false }),
reduction: new foundry.data.fields.NumberField({ integer: true, initial: 0 })
reduction: new foundry.data.fields.NumberField({ integer: true, initial: 0, label: reductionLabel })
});
/**
@ -40,8 +40,8 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
if (this.metadata.isNPC) schema.description = new fields.HTMLField({ required: true, nullable: true });
if (this.metadata.hasResistances)
schema.resistance = new fields.SchemaField({
physical: resistanceField(),
magical: resistanceField()
physical: resistanceField('DAGGERHEART.GENERAL.DamageResistance.physicalReduction'),
magical: resistanceField('DAGGERHEART.GENERAL.DamageResistance.magicalReduction')
});
return schema;
}

View file

@ -5,6 +5,8 @@ import BaseDataActor from './base.mjs';
import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs';
export default class DhCharacter extends BaseDataActor {
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Character'];
static get metadata() {
return foundry.utils.mergeObject(super.metadata, {
label: 'TYPES.Actor.character',
@ -19,24 +21,36 @@ export default class DhCharacter extends BaseDataActor {
return {
...super.defineSchema(),
resources: new fields.SchemaField({
hitPoints: resourceField(0, true),
stress: resourceField(6, true),
hope: resourceField(6)
hitPoints: resourceField(0, 'DAGGERHEART.GENERAL.hitPoints', true),
stress: resourceField(6, 'DAGGERHEART.GENERAL.stress', true),
hope: resourceField(6, 'DAGGERHEART.GENERAL.hope')
}),
traits: new fields.SchemaField({
agility: attributeField(),
strength: attributeField(),
finesse: attributeField(),
instinct: attributeField(),
presence: attributeField(),
knowledge: attributeField()
agility: attributeField('DAGGERHEART.CONFIG.Traits.agility.name'),
strength: attributeField('DAGGERHEART.CONFIG.Traits.strength.name'),
finesse: attributeField('DAGGERHEART.CONFIG.Traits.finesse.name'),
instinct: attributeField('DAGGERHEART.CONFIG.Traits.instinct.name'),
presence: attributeField('DAGGERHEART.CONFIG.Traits.presence.name'),
knowledge: attributeField('DAGGERHEART.CONFIG.Traits.knowledge.name')
}),
proficiency: new fields.NumberField({ initial: 1, integer: true }),
evasion: new fields.NumberField({ initial: 0, integer: true }),
armorScore: new fields.NumberField({ integer: true, initial: 0 }),
proficiency: new fields.NumberField({
initial: 1,
integer: true,
label: 'DAGGERHEART.GENERAL.proficiency'
}),
evasion: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.evasion' }),
armorScore: new fields.NumberField({ integer: true, initial: 0, label: 'DAGGERHEART.GENERAL.armorScore' }),
damageThresholds: new fields.SchemaField({
severe: new fields.NumberField({ integer: true, initial: 0 }),
major: new fields.NumberField({ integer: true, initial: 0 })
severe: new fields.NumberField({
integer: true,
initial: 0,
label: 'DAGGERHEART.GENERAL.DamageThresholds.majorThreshold'
}),
major: new fields.NumberField({
integer: true,
initial: 0,
label: 'DAGGERHEART.GENERAL.DamageThresholds.severeThreshold'
})
}),
experiences: new fields.TypedObjectField(
new fields.SchemaField({
@ -76,25 +90,37 @@ export default class DhCharacter extends BaseDataActor {
levelData: new fields.EmbeddedDataField(DhLevelData),
bonuses: new fields.SchemaField({
roll: new fields.SchemaField({
attack: bonusField(),
spellcast: bonusField(),
trait: bonusField(),
action: bonusField(),
reaction: bonusField(),
primaryWeapon: bonusField(),
secondaryWeapon: bonusField()
attack: bonusField('DAGGERHEART.GENERAL.Roll.attack'),
spellcast: bonusField('DAGGERHEART.GENERAL.Roll.spellcast'),
trait: bonusField('DAGGERHEART.GENERAL.Roll.trait'),
action: bonusField('DAGGERHEART.GENERAL.Roll.action'),
reaction: bonusField('DAGGERHEART.GENERAL.Roll.reaction'),
primaryWeapon: bonusField('DAGGERHEART.GENERAL.Roll.primaryWeaponAttack'),
secondaryWeapon: bonusField('DAGGERHEART.GENERAL.Roll.secondaryWeaponAttack')
}),
damage: new fields.SchemaField({
physical: bonusField(),
magical: bonusField(),
primaryWeapon: bonusField(),
secondaryWeapon: bonusField()
physical: bonusField('DAGGERHEART.GENERAL.Damage.physicalDamage'),
magical: bonusField('DAGGERHEART.GENERAL.Damage.magicalDamage'),
primaryWeapon: bonusField('DAGGERHEART.GENERAL.Damage.primaryWeapon'),
secondaryWeapon: bonusField('DAGGERHEART.GENERAL.Damage.primaryWeapon')
}),
healing: bonusField(),
healing: bonusField('DAGGERHEART.GENERAL.Healing.healingAmount'),
range: new fields.SchemaField({
weapon: new fields.NumberField({ integer: true, initial: 0 }),
spell: new fields.NumberField({ integer: true, initial: 0 }),
other: new fields.NumberField({ integer: true, initial: 0 })
weapon: new fields.NumberField({
integer: true,
initial: 0,
label: 'DAGGERHEART.GENERAL.Range.weapon'
}),
spell: new fields.NumberField({
integer: true,
initial: 0,
label: 'DAGGERHEART.GENERAL.Range.spell'
}),
other: new fields.NumberField({
integer: true,
initial: 0,
label: 'DAGGERHEART.GENERAL.Range.other'
})
})
}),
companion: new ForeignDocumentUUIDField({ type: 'Actor', nullable: true, initial: null }),
@ -102,25 +128,34 @@ export default class DhCharacter extends BaseDataActor {
damageReduction: new fields.SchemaField({
maxArmorMarked: new fields.SchemaField({
value: new fields.NumberField({ required: true, integer: true, initial: 1 }),
bonus: new fields.NumberField({ required: true, integer: true, initial: 0 }),
stressExtra: new fields.NumberField({ required: true, integer: true, initial: 0 })
bonus: new fields.NumberField({
required: true,
integer: true,
initial: 0,
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedBonus'
}),
stressExtra: new fields.NumberField({
required: true,
integer: true,
initial: 0,
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.label',
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.maxArmorMarkedStress.hint'
})
}),
stressDamageReduction: new fields.SchemaField({
severe: stressDamageReductionRule(),
major: stressDamageReductionRule(),
minor: stressDamageReductionRule()
severe: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.severe'),
major: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.major'),
minor: stressDamageReductionRule('DAGGERHEART.GENERAL.Rules.damageReduction.stress.minor')
}),
increasePerArmorMark: new fields.NumberField({
integer: true,
initial: 1,
label: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.label',
hint: 'DAGGERHEART.GENERAL.Rules.damageReduction.increasePerArmorMark.hint'
}),
increasePerArmorMark: new fields.NumberField({ integer: true, initial: 1 }),
magical: new fields.BooleanField({ initial: false }),
physical: new fields.BooleanField({ initial: false })
}),
strangePatterns: new fields.NumberField({
integer: true,
min: 1,
max: 12,
nullable: true,
initial: null
}),
weapon: new fields.SchemaField({
/* Unimplemented
-> Should remove the lowest damage dice from weapon damage

View file

@ -24,10 +24,16 @@ export default class DhCompanion extends BaseDataActor {
...super.defineSchema(),
partner: new ForeignDocumentUUIDField({ type: 'Actor' }),
resources: new fields.SchemaField({
stress: resourceField(3, true),
hope: new fields.NumberField({ initial: 0, integer: true })
stress: resourceField(3, 'DAGGERHEART.GENERAL.stress', true),
hope: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.hope' })
}),
evasion: new fields.NumberField({
required: true,
min: 1,
initial: 10,
integer: true,
label: 'DAGGERHEART.GENERAL.evasion'
}),
evasion: new fields.NumberField({ required: true, min: 1, initial: 10, integer: true }),
experiences: new fields.TypedObjectField(
new fields.SchemaField({
name: new fields.StringField({}),
@ -74,8 +80,8 @@ export default class DhCompanion extends BaseDataActor {
levelData: new fields.EmbeddedDataField(DhLevelData),
bonuses: new fields.SchemaField({
damage: new fields.SchemaField({
physical: bonusField(),
magical: bonusField()
physical: bonusField('DAGGERHEART.GENERAL.Damage.physicalDamage'),
magical: bonusField('DAGGERHEART.GENERAL.Damage.magicalDamage')
})
})
};

View file

@ -102,7 +102,7 @@ class DhCountdown extends foundry.abstract.DataModel {
value: new fields.StringField({
required: true,
choices: CONFIG.DH.GENERAL.countdownTypes,
initial: CONFIG.DH.GENERAL.countdownTypes.spotlight.id,
initial: CONFIG.DH.GENERAL.countdownTypes.custom.id,
label: 'DAGGERHEART.APPLICATIONS.Countdown.FIELDS.countdowns.element.progress.type.value.label'
}),
label: new fields.StringField({
@ -132,7 +132,13 @@ class DhCountdown extends foundry.abstract.DataModel {
export const registerCountdownHooks = () => {
Hooks.on(socketEvent.Refresh, ({ refreshType, application }) => {
if (refreshType === RefreshType.Countdown) {
foundry.applications.instances.get(application)?.render();
if (application) {
foundry.applications.instances.get(application)?.render();
} else {
foundry.applications.instances.get('narrative-countdowns').render();
foundry.applications.instances.get('encounter-countdowns').render();
}
return false;
}
});

View file

@ -1,28 +1,32 @@
const fields = foundry.data.fields;
const attributeField = () =>
const attributeField = label =>
new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
value: new fields.NumberField({ initial: 0, integer: true, label }),
tierMarked: new fields.BooleanField({ initial: false })
});
const resourceField = (max = 0, reverse = false) =>
const resourceField = (max = 0, label, reverse = false) =>
new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
value: new fields.NumberField({ initial: 0, integer: true, label }),
max: new fields.NumberField({ initial: max, integer: true }),
isReversed: new fields.BooleanField({ initial: reverse })
});
const stressDamageReductionRule = () =>
const stressDamageReductionRule = localizationPath =>
new fields.SchemaField({
enabled: new fields.BooleanField({ required: true, initial: false }),
cost: new fields.NumberField({ integer: true })
cost: new fields.NumberField({
integer: true,
label: `${localizationPath}.label`,
hint: `${localizationPath}.hint`
})
});
const bonusField = () =>
const bonusField = label =>
new fields.SchemaField({
bonus: new fields.NumberField({ integer: true, initial: 0 }),
bonus: new fields.NumberField({ integer: true, initial: 0, label }),
dice: new fields.ArrayField(new fields.StringField())
})
});
export { attributeField, resourceField, stressDamageReductionRule, bonusField };
export { attributeField, resourceField, stressDamageReductionRule, bonusField };

View file

@ -4,20 +4,22 @@ export default class DhAutomation extends foundry.abstract.DataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
hope: new fields.BooleanField({
required: true,
initial: false,
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hope.label'
}), // Label need to be updated into something like "Duality Roll Auto Gain" + a hint
hopeFear: new fields.SchemaField({
gm: new fields.BooleanField({
required: true,
initial: false,
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hopeFear.gm.label'
}),
players: new fields.BooleanField({
required: true,
initial: false,
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.hopeFear.players.label'
})
}),
actionPoints: new fields.BooleanField({
required: true,
initial: false,
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.actionPoints.label'
}),
countdowns: new fields.BooleanField({
requireD: true,
initial: false,
label: 'DAGGERHEART.SETTINGS.Automation.FIELDS.countdowns.label'
})
};
}