Merge branch 'main' into bug/103-enrich-htmlfield-content-before-its-used-in-applications

This commit is contained in:
Joaquin Pereyra 2025-07-18 15:24:25 -03:00
commit f56c2482cb
551 changed files with 2831 additions and 14203 deletions

View file

@ -1,13 +1,7 @@
import DHAdversarySettings from '../../applications/sheets-configs/adversary-settings.mjs';
import ActionField from '../fields/actionField.mjs';
import BaseDataActor from './base.mjs';
const resourceField = () =>
new foundry.data.fields.SchemaField({
value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
max: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
isReversed: new foundry.data.fields.BooleanField({ initial: true })
});
import { resourceField, bonusField } from '../fields/actorField.mjs';
export default class DhpAdversary extends BaseDataActor {
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Adversary'];
@ -37,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(),
stress: resourceField()
hitPoints: resourceField(0, 'DAGGERHEART.GENERAL.hitPoints.plural', true),
stress: resourceField(0, 'DAGGERHEART.GENERAL.stress', true)
}),
attack: new ActionField({
initial: {
@ -59,7 +68,7 @@ export default class DhpAdversary extends BaseDataActor {
amount: 1
},
roll: {
type: 'weapon'
type: 'attack'
},
damage: {
parts: [
@ -80,9 +89,14 @@ export default class DhpAdversary extends BaseDataActor {
})
),
bonuses: new fields.SchemaField({
difficulty: new fields.SchemaField({
all: new fields.NumberField({ integer: true, initial: 0 }),
reaction: new fields.NumberField({ integer: true, initial: 0 })
roll: new fields.SchemaField({
attack: bonusField('DAGGERHEART.GENERAL.Roll.attack'),
action: bonusField('DAGGERHEART.GENERAL.Roll.action'),
reaction: bonusField('DAGGERHEART.GENERAL.Roll.reaction')
}),
damage: new fields.SchemaField({
physical: bonusField('DAGGERHEART.GENERAL.Damage.physicalDamage'),
magical: bonusField('DAGGERHEART.GENERAL.Damage.magicalDamage')
})
})
};
@ -95,4 +109,37 @@ export default class DhpAdversary extends BaseDataActor {
get features() {
return this.parent.items.filter(x => x.type === 'feature');
}
async _preUpdate(changes, options, user) {
const allowed = await super._preUpdate(changes, options, user);
if (allowed === false) return false;
if (this.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id) {
if (changes.system?.resources?.hitPoints?.value) {
const halfHP = Math.ceil(this.resources.hitPoints.max / 2);
const newHitPoints = changes.system.resources.hitPoints.value;
const previouslyAboveHalf = this.resources.hitPoints.value < halfHP;
const loweredBelowHalf = previouslyAboveHalf && newHitPoints >= halfHP;
const raisedAboveHalf = !previouslyAboveHalf && newHitPoints < halfHP;
if (loweredBelowHalf) {
await this.parent.createEmbeddedDocuments('ActiveEffect', [
{
name: game.i18n.localize('DAGGERHEART.CONFIG.AdversaryType.horde.label'),
img: 'icons/magic/movement/chevrons-down-yellow.webp',
disabled: !game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation)
.hordeDamage
}
]);
} else if (raisedAboveHalf) {
const hordeEffects = this.parent.effects.filter(
x => x.name === game.i18n.localize('DAGGERHEART.CONFIG.AdversaryType.horde.label')
);
await this.parent.deleteEmbeddedDocuments(
'ActiveEffect',
hordeEffects.map(x => x.id)
);
}
}
}
}
}

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

@ -2,27 +2,11 @@ import { burden } from '../../config/generalConfig.mjs';
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
import DhLevelData from '../levelData.mjs';
import BaseDataActor from './base.mjs';
const attributeField = () =>
new foundry.data.fields.SchemaField({
value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
tierMarked: new foundry.data.fields.BooleanField({ initial: false })
});
const resourceField = (max, reverse = false) =>
new foundry.data.fields.SchemaField({
value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
max: new foundry.data.fields.NumberField({ initial: max, integer: true }),
isReversed: new foundry.data.fields.BooleanField({ initial: reverse })
});
const stressDamageReductionRule = () =>
new foundry.data.fields.SchemaField({
enabled: new foundry.data.fields.BooleanField({ required: true, initial: false }),
cost: new foundry.data.fields.NumberField({ integer: true })
});
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',
@ -37,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.plural', 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({
@ -91,24 +87,82 @@ export default class DhCharacter extends BaseDataActor {
value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }),
subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true })
}),
advantageSources: new fields.ArrayField(new fields.StringField()),
disadvantageSources: new fields.ArrayField(new fields.StringField()),
levelData: new fields.EmbeddedDataField(DhLevelData),
bonuses: new fields.SchemaField({
roll: new fields.SchemaField({
attack: new fields.NumberField({ integer: true, initial: 0 }),
primaryWeapon: new fields.SchemaField({
attack: new fields.NumberField({ integer: true, initial: 0 })
}),
spellcast: new fields.NumberField({ integer: true, initial: 0 }),
action: new fields.NumberField({ integer: true, initial: 0 }),
hopeOrFear: new fields.NumberField({ integer: true, initial: 0 })
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({
all: new fields.NumberField({ integer: true, initial: 0 }),
physical: new fields.NumberField({ integer: true, initial: 0 }),
magic: new fields.NumberField({ integer: true, initial: 0 }),
primaryWeapon: new fields.SchemaField({
bonus: new fields.NumberField({ integer: true }),
extraDice: new fields.NumberField({ integer: true })
physical: bonusField('DAGGERHEART.GENERAL.Damage.physicalDamage'),
magical: bonusField('DAGGERHEART.GENERAL.Damage.magicalDamage'),
primaryWeapon: bonusField('DAGGERHEART.GENERAL.Damage.primaryWeapon'),
secondaryWeapon: bonusField('DAGGERHEART.GENERAL.Damage.secondaryWeapon')
}),
healing: bonusField('DAGGERHEART.GENERAL.Healing.healingAmount'),
range: new fields.SchemaField({
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'
})
}),
rally: new fields.ArrayField(new fields.StringField(), {
label: 'DAGGERHEART.CLASS.Feature.rallyDice'
}),
rest: new fields.SchemaField({
shortRest: new fields.SchemaField({
shortMoves: new fields.NumberField({
required: true,
integer: true,
min: 0,
initial: 0,
label: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.shortRestMoves.label',
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.shortRestMoves.hint'
}),
longMoves: new fields.NumberField({
required: true,
integer: true,
min: 0,
initial: 0,
label: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.longRestMoves.label',
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.longRestMoves.hint'
})
}),
longRest: new fields.SchemaField({
shortMoves: new fields.NumberField({
required: true,
integer: true,
min: 0,
initial: 0,
label: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.shortRestMoves.label',
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.shortRestMoves.hint'
}),
longMoves: new fields.NumberField({
required: true,
integer: true,
min: 0,
initial: 0,
label: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.longRestMoves.label',
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.longRestMoves.hint'
})
})
})
}),
@ -117,25 +171,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
@ -181,6 +244,11 @@ export default class DhCharacter extends BaseDataActor {
return !this.class.value || !this.class.subclass;
}
get spellcastModifier() {
const subClasses = this.parent.items.filter(x => x.type === 'subclass') ?? [];
return Math.max(subClasses?.map(sc => this.traits[sc.system.spellcastingTrait]?.value));
}
get spellcastingModifiers() {
return {
main: this.class.subclass?.system?.spellcastingTrait,
@ -219,23 +287,23 @@ export default class DhCharacter extends BaseDataActor {
features = [];
for (let item of this.parent.items) {
if (item.system.type === CONFIG.DH.ITEM.featureTypes.ancestry.id) {
if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.ancestry.id) {
ancestryFeatures.push(item);
} else if (item.system.type === CONFIG.DH.ITEM.featureTypes.community.id) {
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.community.id) {
communityFeatures.push(item);
} else if (item.system.type === CONFIG.DH.ITEM.featureTypes.class.id) {
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.class.id) {
classFeatures.push(item);
} else if (item.system.type === CONFIG.DH.ITEM.featureTypes.subclass.id) {
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.subclass.id) {
const subclassState = this.class.subclass.system.featureState;
const identifier = item.system.identifier;
const subType = item.system.subType;
if (
identifier === 'foundationFeature' ||
(identifier === 'specializationFeature' && subclassState >= 2) ||
(identifier === 'masterFeature' && subclassState >= 3)
subType === CONFIG.DH.ITEM.featureSubTypes.foundation ||
(subType === CONFIG.DH.ITEM.featureSubTypes.specialization && subclassState >= 2) ||
(subType === CONFIG.DH.ITEM.featureSubTypes.mastery && subclassState >= 3)
) {
subclassFeatures.push(item);
}
} else if (item.system.type === CONFIG.DH.ITEM.featureTypes.companion.id) {
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.companion.id) {
companionFeatures.push(item);
} else if (item.type === 'feature' && !item.system.type) {
features.push(item);
@ -323,6 +391,8 @@ export default class DhCharacter extends BaseDataActor {
}
prepareBaseData() {
this.evasion = this.class.value?.system?.evasion ?? 0;
const currentLevel = this.levelData.level.current;
const currentTier =
currentLevel === 1
@ -380,7 +450,8 @@ export default class DhCharacter extends BaseDataActor {
}
prepareDerivedData() {
this.resources.hope.value = Math.min(this.resources.hope.value, this.resources.hope.max);
const baseHope = this.resources.hope.value + (this.companion?.system?.resources?.hope ?? 0);
this.resources.hope.value = Math.min(baseHope, this.resources.hope.max);
}
getRollData() {

View file

@ -4,6 +4,7 @@ import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
import ActionField from '../fields/actionField.mjs';
import { adjustDice, adjustRange } from '../../helpers/utils.mjs';
import DHCompanionSettings from '../../applications/sheets-configs/companion-settings.mjs';
import { resourceField, bonusField } from '../fields/actorField.mjs';
export default class DhCompanion extends BaseDataActor {
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Companion'];
@ -23,14 +24,16 @@ export default class DhCompanion extends BaseDataActor {
...super.defineSchema(),
partner: new ForeignDocumentUUIDField({ type: 'Actor' }),
resources: new fields.SchemaField({
stress: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 3, integer: true }),
isReversed: new foundry.data.fields.BooleanField({ initial: 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({}),
@ -56,9 +59,8 @@ export default class DhCompanion extends BaseDataActor {
amount: 1
},
roll: {
type: 'weapon',
bonus: 0,
trait: 'instinct'
type: 'attack',
bonus: 0
},
damage: {
parts: [
@ -74,13 +76,13 @@ export default class DhCompanion extends BaseDataActor {
}
}),
actions: new fields.ArrayField(new ActionField()),
levelData: new fields.EmbeddedDataField(DhLevelData)
};
}
get traits() {
return {
instinct: { value: this.attack.roll.bonus }
levelData: new fields.EmbeddedDataField(DhLevelData),
bonuses: new fields.SchemaField({
damage: new fields.SchemaField({
physical: bonusField('DAGGERHEART.GENERAL.Damage.physicalDamage'),
magical: bonusField('DAGGERHEART.GENERAL.Damage.magicalDamage')
})
})
};
}
@ -89,9 +91,7 @@ export default class DhCompanion extends BaseDataActor {
}
prepareBaseData() {
const partnerSpellcastingModifier = this.partner?.system?.spellcastingModifiers?.main;
const spellcastingModifier = this.partner?.system?.traits?.[partnerSpellcastingModifier]?.value;
this.attack.roll.bonus = spellcastingModifier ?? 0; // Needs to expand on which modifier it is that should be used because of multiclassing;
this.attack.roll.bonus = this.partner?.system?.spellcastModifier ?? 0;
for (let levelKey in this.levelData.levelups) {
const level = this.levelData.levelups[levelKey];
@ -124,12 +124,6 @@ export default class DhCompanion extends BaseDataActor {
}
}
prepareDerivedData() {
if (this.partner) {
this.partner.system.resources.hope.max += this.resources.hope;
}
}
async _preDelete() {
if (this.partner) {
await this.partner.update({ 'system.companion': null });