diff --git a/daggerheart.mjs b/daggerheart.mjs index f75ff1da..49ed7049 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -242,6 +242,38 @@ Hooks.on('setup', () => { systemEffect: true })) ]; + + const actorCommon = { + bar: ['resources.stress'], + value: [] + }; + const damageThresholds = ['damageThresholds.major', 'damageThresholds.severe']; + const traits = Object.keys(game.system.api.data.actors.DhCharacter.schema.fields.traits.fields).map( + trait => `traits.${trait}.value` + ); + CONFIG.Actor.trackableAttributes = { + character: { + bar: [...actorCommon.bar, 'resources.hitPoints', 'resources.hope'], + value: [ + ...actorCommon.value, + ...traits, + ...damageThresholds, + 'proficiency', + 'evasion', + 'armorScore', + 'scars', + 'levelData.level.current' + ] + }, + adversary: { + bar: [...actorCommon.bar, 'resources.hitPoints'], + value: [...actorCommon.value, ...damageThresholds, 'criticalThreshold'] + }, + companion: { + bar: [...actorCommon.bar], + value: [...actorCommon.value, 'evasion', 'levelData.level.current'] + } + }; }); Hooks.on('ready', async () => { diff --git a/lang/en.json b/lang/en.json index 0186ae3e..86b4323c 100755 --- a/lang/en.json +++ b/lang/en.json @@ -2152,6 +2152,7 @@ "continue": "Continue", "criticalSuccess": "Critical Success", "criticalShort": "Critical", + "currentLevel": "Current Level", "custom": "Custom", "d20Roll": "D20 Roll", "damage": "Damage", diff --git a/module/applications/sheets-configs/activeEffectConfig.mjs b/module/applications/sheets-configs/activeEffectConfig.mjs index e807a94a..9b8edd8a 100644 --- a/module/applications/sheets-configs/activeEffectConfig.mjs +++ b/module/applications/sheets-configs/activeEffectConfig.mjs @@ -4,20 +4,34 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac constructor(options) { super(options); - const ignoredActorKeys = ['config', 'DhEnvironment']; + const ignoredActorKeys = ['config', 'DhEnvironment', 'DhParty']; this.changeChoices = Object.keys(game.system.api.models.actors).reduce((acc, key) => { - if (!ignoredActorKeys.includes(key)) { - const model = game.system.api.models.actors[key]; - const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model); - // As per DHToken._getTrackedAttributesFromSchema, attributes.bar have a max version as well. - const maxAttributes = attributes.bar.map(x => [...x, 'max']); - attributes.value.push(...maxAttributes); - const group = game.i18n.localize(model.metadata.label); - const choices = CONFIG.Token.documentClass - .getTrackedAttributeChoices(attributes, model) - .map(x => ({ ...x, group: group })); - acc.push(...choices); - } + if (ignoredActorKeys.includes(key)) return acc; + + const model = game.system.api.models.actors[key]; + const group = game.i18n.localize(model.metadata.label); + const attributes = CONFIG.Token.documentClass.getTrackedAttributes(model.metadata.type); + + const getLabel = path => { + const label = model.schema.getField(path)?.label; + return label ? game.i18n.localize(label) : path; + }; + + const bars = attributes.bar.flatMap(x => { + const joined = `${x.join('.')}.max`; + const label = + joined === 'resources.hope.max' + ? 'DAGGERHEART.SETTINGS.Homebrew.FIELDS.maxHope.label' + : getLabel(joined); + return { value: joined, label, group }; + }); + const values = attributes.value.flatMap(x => { + const joined = x.join('.'); + return { value: joined, label: getLabel(joined), group }; + }); + + acc.push(...bars, ...values); + return acc; }, []); } diff --git a/module/data/actor/adversary.mjs b/module/data/actor/adversary.mjs index 16e7e37a..f2c38090 100644 --- a/module/data/actor/adversary.mjs +++ b/module/data/actor/adversary.mjs @@ -40,7 +40,14 @@ export default class DhpAdversary extends BaseDataActor { integer: true, label: 'DAGGERHEART.GENERAL.hordeHp' }), - criticalThreshold: new fields.NumberField({ required: true, integer: true, min: 1, max: 20, initial: 20 }), + criticalThreshold: new fields.NumberField({ + required: true, + integer: true, + min: 1, + max: 20, + initial: 20, + label: 'DAGGERHEART.ACTIONS.Settings.criticalThreshold' + }), damageThresholds: new fields.SchemaField({ major: new fields.NumberField({ required: true, diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index 8af4c74c..3913d426 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -35,15 +35,18 @@ export default class DhCharacter extends BaseDataActor { 'DAGGERHEART.ACTORS.Character.maxHPBonus' ), stress: resourceField(6, 0, 'DAGGERHEART.GENERAL.stress', true), - hope: new fields.SchemaField({ - value: new fields.NumberField({ - initial: 2, - min: 0, - integer: true, - label: 'DAGGERHEART.GENERAL.hope' - }), - isReversed: new fields.BooleanField({ initial: false }) - }) + hope: new fields.SchemaField( + { + value: new fields.NumberField({ + initial: 2, + min: 0, + integer: true, + label: 'DAGGERHEART.GENERAL.hope' + }), + isReversed: new fields.BooleanField({ initial: false }) + }, + { label: 'DAGGERHEART.GENERAL.hope' } + ) }), traits: new fields.SchemaField({ agility: attributeField('DAGGERHEART.CONFIG.Traits.agility.name'), diff --git a/module/data/fields/actorField.mjs b/module/data/fields/actorField.mjs index f9eeeb90..db1faad4 100644 --- a/module/data/fields/actorField.mjs +++ b/module/data/fields/actorField.mjs @@ -7,16 +7,20 @@ const attributeField = label => }); const resourceField = (max = 0, initial = 0, label, reverse = false, maxLabel) => - new fields.SchemaField({ - value: new fields.NumberField({ initial: initial, min: 0, integer: true, label }), - max: new fields.NumberField({ - initial: max, - integer: true, - label: - maxLabel ?? game.i18n.format('DAGGERHEART.GENERAL.maxWithThing', { thing: game.i18n.localize(label) }) - }), - isReversed: new fields.BooleanField({ initial: reverse }) - }); + new fields.SchemaField( + { + value: new fields.NumberField({ initial: initial, min: 0, integer: true, label }), + max: new fields.NumberField({ + initial: max, + integer: true, + label: + maxLabel ?? + game.i18n.format('DAGGERHEART.GENERAL.maxWithThing', { thing: game.i18n.localize(label) }) + }), + isReversed: new fields.BooleanField({ initial: reverse }) + }, + { label } + ); const stressDamageReductionRule = localizationPath => new fields.SchemaField({ diff --git a/module/data/levelData.mjs b/module/data/levelData.mjs index 669077ee..4f55d9ee 100644 --- a/module/data/levelData.mjs +++ b/module/data/levelData.mjs @@ -6,7 +6,12 @@ export default class DhLevelData extends foundry.abstract.DataModel { return { level: new fields.SchemaField({ - current: new fields.NumberField({ required: true, integer: true, initial: 1 }), + current: new fields.NumberField({ + required: true, + integer: true, + initial: 1, + label: 'DAGGERHEART.GENERAL.currentLevel' + }), changed: new fields.NumberField({ required: true, integer: true, initial: 1 }), bonuses: new fields.TypedObjectField(new fields.NumberField({ integer: true, nullable: false })) }), diff --git a/module/documents/token.mjs b/module/documents/token.mjs index b9507c2f..6fd931d6 100644 --- a/module/documents/token.mjs +++ b/module/documents/token.mjs @@ -1,78 +1,30 @@ export default class DHToken extends CONFIG.Token.documentClass { - /** - * Inspect the Actor data model and identify the set of attributes which could be used for a Token Bar. - * @param {object} attributes The tracked attributes which can be chosen from - * @returns {object} A nested object of attribute choices to display - */ - static getTrackedAttributeChoices(attributes, model) { + /**@inheritdoc */ + static getTrackedAttributeChoices(attributes, typeKey) { attributes = attributes || this.getTrackedAttributes(); const barGroup = game.i18n.localize('TOKEN.BarAttributes'); const valueGroup = game.i18n.localize('TOKEN.BarValues'); + const actorModel = typeKey ? game.system.api.data.actors[`Dh${typeKey.capitalize()}`] : null; + const getLabel = path => { + const label = actorModel.schema.getField(path)?.label; + return label ? game.i18n.localize(label) : path; + }; const bars = attributes.bar.map(v => { const a = v.join('.'); - const modelLabel = model ? game.i18n.localize(model.schema.getField(`${a}.value`).label) : null; - return { group: barGroup, value: a, label: modelLabel ? modelLabel : a }; + return { group: barGroup, value: a, label: getLabel(a) }; }); - bars.sort((a, b) => a.label.compare(b.label)); + bars.sort((a, b) => a.value.compare(b.value)); - const invalidAttributes = [ - 'gold', - 'levelData', - 'actions', - 'biography', - 'class', - 'multiclass', - 'companion', - 'notes', - 'partner', - 'description', - 'impulses', - 'tier', - 'type' - ]; - const values = attributes.value.reduce((acc, v) => { + const values = attributes.value.map(v => { const a = v.join('.'); - if (invalidAttributes.some(x => a.startsWith(x))) return acc; - - const field = model ? model.schema.getField(a) : null; - const modelLabel = field ? game.i18n.localize(field.label) : null; - const hint = field ? game.i18n.localize(field.hint) : null; - acc.push({ group: valueGroup, value: a, label: modelLabel ? modelLabel : a, hint: hint }); - - return acc; - }, []); - values.sort((a, b) => a.label.compare(b.label)); + return { group: valueGroup, value: a, label: getLabel(a) }; + }); + values.sort((a, b) => a.value.compare(b.value)); return bars.concat(values); } - static _getTrackedAttributesFromSchema(schema, _path = []) { - const attributes = { bar: [], value: [] }; - for (const [name, field] of Object.entries(schema.fields)) { - const p = _path.concat([name]); - if (field instanceof foundry.data.fields.NumberField) attributes.value.push(p); - if (field instanceof foundry.data.fields.BooleanField && field.options.isAttributeChoice) - attributes.value.push(p); - if (field instanceof foundry.data.fields.StringField) attributes.value.push(p); - if (field instanceof foundry.data.fields.ArrayField) attributes.value.push(p); - const isSchema = field instanceof foundry.data.fields.SchemaField; - const isModel = field instanceof foundry.data.fields.EmbeddedDataField; - - if (isSchema || isModel) { - const schema = isModel ? field.model.schema : field; - const isBar = schema.has && schema.has('value') && schema.has('max'); - if (isBar) attributes.bar.push(p); - else { - const inner = this.getTrackedAttributes(schema, p); - attributes.bar.push(...inner.bar); - attributes.value.push(...inner.value); - } - } - } - return attributes; - } - _shouldRecordMovementHistory() { return false; }