diff --git a/module/data/actor/character.mjs b/module/data/actor/character.mjs index ff6af4dc99..1d3d222a17 100644 --- a/module/data/actor/character.mjs +++ b/module/data/actor/character.mjs @@ -113,6 +113,7 @@ export default class CharacterData extends CreatureTemplate { }) }, { label: "DND5E.HitPoints" }), death: new RollConfigField({ + ability: false, success: new NumberField({ required: true, nullable: false, integer: true, min: 0, initial: 0, label: "DND5E.DeathSaveSuccesses" }), diff --git a/module/data/actor/npc.mjs b/module/data/actor/npc.mjs index 901d621312..93f36bc28d 100644 --- a/module/data/actor/npc.mjs +++ b/module/data/actor/npc.mjs @@ -104,6 +104,7 @@ export default class NPCData extends CreatureTemplate { formula: new FormulaField({required: true, label: "DND5E.HPFormula"}) }, {label: "DND5E.HitPoints"}), death: new RollConfigField({ + ability: false, success: new NumberField({ required: true, nullable: false, integer: true, min: 0, initial: 0, label: "DND5E.DeathSaveSuccesses" }), diff --git a/module/data/actor/templates/attributes.mjs b/module/data/actor/templates/attributes.mjs index 87a407ef76..ff38e81be4 100644 --- a/module/data/actor/templates/attributes.mjs +++ b/module/data/actor/templates/attributes.mjs @@ -144,7 +144,7 @@ export default class AttributesFields { const abilityId = concentration.ability || CONFIG.DND5E.defaultAbilities.concentration; const ability = this.abilities?.[abilityId] || {}; const bonus = simplifyBonus(concentration.bonuses.save, rollData); - concentration.save = (ability.save ?? 0) + bonus; + concentration.save = (ability.save?.value ?? 0) + bonus; } /* -------------------------------------------- */ diff --git a/module/data/actor/templates/common.mjs b/module/data/actor/templates/common.mjs index aadf7fb745..bdf85f4add 100644 --- a/module/data/actor/templates/common.mjs +++ b/module/data/actor/templates/common.mjs @@ -4,6 +4,7 @@ import { ActorDataModel } from "../../abstract.mjs"; import FormulaField from "../../fields/formula-field.mjs"; import MappingField from "../../fields/mapping-field.mjs"; import CurrencyTemplate from "../../shared/currency.mjs"; +import RollConfigField from "../../shared/roll-config-field.mjs"; const { NumberField, SchemaField } = foundry.data.fields; @@ -15,6 +16,16 @@ const { NumberField, SchemaField } = foundry.data.fields; * @property {object} bonuses Bonuses that modify ability checks and saves. * @property {string} bonuses.check Numeric or dice bonus to ability checks. * @property {string} bonuses.save Numeric or dice bonus to ability saving throws. + * @property {object} check Properties related to ability checks. + * @property {object} check.roll + * @property {number} check.roll.mode The advantage mode of ability checks. + * @property {number} check.roll.min The minimum that can be rolled on the d20. + * @property {number} check.roll.max The maximum that can be rolled on the d20. + * @property {object} save Properties related to ability saving throws. + * @property {object} save.roll + * @property {number} save.roll.mode The advantage mode of ability saving throws. + * @property {number} save.roll.min The minimum that can be rolled on the d20. + * @property {number} save.roll.max The maximum that can be rolled on the d20. */ /** @@ -41,7 +52,10 @@ export default class CommonTemplate extends ActorDataModel.mixin(CurrencyTemplat bonuses: new SchemaField({ check: new FormulaField({ required: true, label: "DND5E.AbilityCheckBonus" }), save: new FormulaField({ required: true, label: "DND5E.SaveBonus" }) - }, { label: "DND5E.AbilityBonuses" }) + }, { label: "DND5E.AbilityBonuses" }), + save: new RollConfigField({ + ability: false + }) }), { initialKeys: CONFIG.DND5E.abilities, initialValue: this._initialAbilityValue.bind(this), initialKeysOnly: true, label: "DND5E.Abilities" @@ -147,14 +161,22 @@ export default class CommonTemplate extends ActorDataModel.mixin(CurrencyTemplat const checkBonusAbl = simplifyBonus(abl.bonuses?.check, rollData); abl.checkBonus = checkBonusAbl + checkBonus; - abl.save = abl.mod + abl.saveBonus; - if ( Number.isNumeric(abl.saveProf.term) ) abl.save += abl.saveProf.flat; + abl.save.value = abl.mod + abl.saveBonus; + if ( Number.isNumeric(abl.saveProf.term) ) abl.save.value += abl.saveProf.flat; abl.dc = 8 + abl.mod + prof + dcBonus; if ( !Number.isFinite(abl.max) ) abl.max = CONFIG.DND5E.maxAbilityScore; // If we merged saves when transforming, take the highest bonus here. - if ( originalSaves && abl.proficient ) abl.save = Math.max(abl.save, originalSaves[id].save); + if ( originalSaves && abl.proficient ) abl.save.value = Math.max(abl.save, originalSaves[id].save.value); + + // Deprecations. + abl.save.toString = function() { + foundry.utils.logCompatibilityWarning("The 'abilities..save' property is now stored in 'abilities..save.value'.", { + since: "4.2", until: "4.5" + }); + return abl.save.value; + }; } } diff --git a/module/data/shared/roll-config-field.mjs b/module/data/shared/roll-config-field.mjs index 6d9a699c99..0c6db7c399 100644 --- a/module/data/shared/roll-config-field.mjs +++ b/module/data/shared/roll-config-field.mjs @@ -4,7 +4,7 @@ const { StringField, NumberField, SchemaField } = foundry.data.fields; /** * @typedef {object} RollConfigData - * @property {string} ability Default ability associated with this roll. + * @property {string} [ability] Default ability associated with this roll. * @property {object} roll * @property {number} roll.min Minimum number on the die rolled. * @property {number} roll.max Maximum number on the die rolled. @@ -18,7 +18,6 @@ export default class RollConfigField extends foundry.data.fields.SchemaField { constructor({roll={}, ability="", ...fields}={}, options={}) { const opts = { initial: null, nullable: true, min: 1, max: 20, integer: true }; fields = { - ability: new StringField({required: true, initial: ability, label: "DND5E.AbilityModifier"}), roll: new SchemaField({ min: new NumberField({...opts, label: "DND5E.ROLL.Range.Minimum"}), max: new NumberField({...opts, label: "DND5E.ROLL.Range.Maximum"}), @@ -27,6 +26,13 @@ export default class RollConfigField extends foundry.data.fields.SchemaField { }), ...fields }; + if ( ability !== false ) { + fields.ability = new StringField({ + required: true, + initial: ability, + label: "DND5E.AbilityModifier" + }); + } super(fields, options); } } diff --git a/templates/actors/character-sheet.hbs b/templates/actors/character-sheet.hbs index d62df29939..c1d807ed2a 100644 --- a/templates/actors/character-sheet.hbs +++ b/templates/actors/character-sheet.hbs @@ -171,7 +171,7 @@ {{{ability.icon}}} - {{numberFormat ability.save decimals=0 sign=true}} + {{numberFormat ability.save.value decimals=0 sign=true}} - {{ dnd5e-formatModifier save }} + {{ dnd5e-formatModifier save.value }} diff --git a/templates/actors/npc-sheet.hbs b/templates/actors/npc-sheet.hbs index 6e4435380b..8987ecb762 100644 --- a/templates/actors/npc-sheet.hbs +++ b/templates/actors/npc-sheet.hbs @@ -143,7 +143,7 @@ {{{ability.icon}}} - {{numberFormat ability.save decimals=0 sign=true}} + {{numberFormat ability.save.value decimals=0 sign=true}} {{ label }} {{ abbr }} -
{{ dnd5e-formatModifier save }}
+
{{ dnd5e-formatModifier save.value }}
{{#if @root.editable}}