Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#4790] Death save bonus and config #4855

Merged
merged 2 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1626,6 +1626,8 @@
"DND5E.Dawn": "Dawn",
"DND5E.Day": "Day",
"DND5E.DeathSave": "Death Saves",
"DND5E.DeathSaveBonus": "Death Save Bonus",
"DND5E.DeathSaveConfigure": "Configure Death Saves",
"DND5E.DeathSaveCriticalSuccess": "{name} critically succeeded on a death saving throw and has regained 1 Hit Point!",
"DND5E.DeathSaveHide": "Hide Death Saves",
"DND5E.DeathSaveShow": "Show Death Saves",
Expand Down
5 changes: 5 additions & 0 deletions less/v2/character.less
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,11 @@
--icon-fill: var(--dnd5e-color-gold);
}

.fas {
color: var(--dnd5e-color-gold);
font-size: 32px;
}

&:hover {
background: none;
box-shadow: none;
Expand Down
13 changes: 13 additions & 0 deletions module/applications/actor/api/base-config-sheet.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,17 @@ export default class BaseConfigSheet extends DocumentSheet5e {
submitOnChange: true
}
};

/* -------------------------------------------- */

/** @inheritDoc */
async _prepareContext(options) {
const context = await super._prepareContext(options);
context.advantageModeOptions = [
{ value: -1, label: game.i18n.localize("DND5E.Disadvantage") },
{ value: 0, label: game.i18n.localize("DND5E.Normal") },
{ value: 1, label: game.i18n.localize("DND5E.Advantage") }
];
return context;
}
}
4 changes: 4 additions & 0 deletions module/applications/actor/base-sheet.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import ActorSheetMixin from "./sheet-mixin.mjs";
import AbilityConfig from "./config/ability-config.mjs";
import ArmorClassConfig from "./config/armor-class-config.mjs";
import ConcentrationConfig from "./config/concentration-config.mjs";
import DeathConfig from "./config/death-config.mjs";
import DamagesConfig from "./config/damages-config.mjs";
import HabitatConfig from "./config/habitat-config.mjs";
import HitDiceConfig from "./config/hit-dice-config.mjs";
Expand Down Expand Up @@ -758,6 +759,9 @@ export default class ActorSheet5e extends ActorSheetMixin(ActorSheet) {
case "armor":
app = new ArmorClassConfig({ document: this.actor });
break;
case "death":
app = new DeathConfig({ document: this.actor });
break;
case "habitat":
app = new HabitatConfig({ document: this.actor });
break;
Expand Down
5 changes: 0 additions & 5 deletions module/applications/actor/config/concentration-config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,6 @@ export default class ConcentrationConfig extends BaseConfigSheet {
{ rule: true },
...Object.entries(CONFIG.DND5E.abilities).map(([value, { label }]) => ({ value, label }))
];
context.advantageModeOptions = [
{ value: -1, label: game.i18n.localize("DND5E.Disadvantage") },
{ value: 0, label: game.i18n.localize("DND5E.Normal") },
{ value: 1, label: game.i18n.localize("DND5E.Advantage") }
];

if ( this.document.system.bonuses?.abilities ) context.global = {
data: source.bonuses?.abilities ?? {},
Expand Down
52 changes: 52 additions & 0 deletions module/applications/actor/config/death-config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import BaseConfigSheet from "../api/base-config-sheet.mjs";

/**
* Configuration application for an actor's concentration checks.
*/
export default class DeathConfig extends BaseConfigSheet {
/** @override */
static DEFAULT_OPTIONS = {
ability: null,
position: {
width: 500
}
};

/* -------------------------------------------- */

/** @override */
static PARTS = {
config: {
template: "systems/dnd5e/templates/actors/config/death-config.hbs"
}
};

/* -------------------------------------------- */
/* Properties */
/* -------------------------------------------- */

/** @override */
get title() {
return game.i18n.localize("DND5E.DeathSaveConfigure");
}

/* -------------------------------------------- */
/* Rendering */
/* -------------------------------------------- */

/** @inheritDoc */
async _preparePartContext(partId, context, options) {
context = await super._preparePartContext(partId, context, options);
const source = this.document.system._source;

context.data = source.attributes?.death ?? {};
context.fields = this.document.system.schema.getField("attributes.death").fields;

if ( this.document.system.bonuses?.abilities ) context.global = {
data: source.bonuses?.abilities ?? {},
fields: this.document.system.schema.getField("bonuses.abilities").fields
};

return context;
}
}
5 changes: 5 additions & 0 deletions module/data/actor/character.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ const {
* @property {string} attributes.hp.bonuses.level Bonus formula applied for each class level.
* @property {string} attributes.hp.bonuses.overall Bonus formula applied to total HP.
* @property {object} attributes.death
* @property {object} attributes.death.bonuses
* @property {string} attributes.death.bonuses.save Numeric or dice bonus to death saving throws.
* @property {number} attributes.death.success Number of successful death saves.
* @property {number} attributes.death.failure Number of failed death saves.
* @property {number} attributes.exhaustion Number of levels of exhaustion.
Expand Down Expand Up @@ -123,6 +125,9 @@ export default class CharacterData extends CreatureTemplate {
}),
failure: new NumberField({
required: true, nullable: false, integer: true, min: 0, initial: 0, label: "DND5E.DeathSaveFailures"
}),
bonuses: new SchemaField({
arbron marked this conversation as resolved.
Show resolved Hide resolved
save: new FormulaField({ required: true, label: "DND5E.DeathSaveBonus" })
})
}, { label: "DND5E.DeathSave" }),
inspiration: new BooleanField({ required: true, label: "DND5E.Inspiration" })
Expand Down
9 changes: 7 additions & 2 deletions module/data/actor/npc.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@ const { ArrayField, BooleanField, NumberField, SchemaField, SetField, StringFiel
* @property {number} attributes.hp.tempmax Temporary change to the maximum HP.
* @property {string} attributes.hp.formula Formula used to determine hit points.
* @property {object} attributes.death
* @property {number} attributes.death.success Number of successful death saves.
* @property {number} attributes.death.failure Number of failed death saves.
* @property {object} attributes.death.bonuses
* @property {string} attributes.death.bonuses.save Numeric or dice bonus to death saving throws.
* @property {number} attributes.death.success Number of successful death saves.
* @property {number} attributes.death.failure Number of failed death saves.
* @property {object} details
* @property {TypeData} details.type Creature type of this NPC.
* @property {string} details.type.value NPC's type as defined in the system configuration.
Expand Down Expand Up @@ -120,6 +122,9 @@ export default class NPCData extends CreatureTemplate {
}),
failure: new NumberField({
required: true, nullable: false, integer: true, min: 0, initial: 0, label: "DND5E.DeathSaveFailures"
}),
bonuses: new SchemaField({
save: new FormulaField({ required: true, label: "DND5E.DeathSaveBonus" })
})
}, {label: "DND5E.DeathSave"})
}, {label: "DND5E.Attributes"}),
Expand Down
5 changes: 4 additions & 1 deletion module/documents/actor/actor.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1759,7 +1759,7 @@ export default class Actor5e extends SystemDocumentMixin(Actor) {
*/
async rollDeathSave(config={}, dialog={}, message={}) {
let oldFormat = false;
const death = this.system.attributes.death;
const death = this.system.attributes?.death;
if ( !death ) throw new Error(`Actors of the type '${this.type}' don't support death saves.`);

// Handle deprecated config object
Expand Down Expand Up @@ -1796,6 +1796,9 @@ export default class Actor5e extends SystemDocumentMixin(Actor) {
data.prof = new Proficiency(this.system.attributes.prof, 1).term;
}

// Death save bonus
if ( death.bonuses.save ) parts.push(death.bonuses.save);

const initialRoll = config.rolls?.pop();
if ( initialRoll?.data ) data = { ...data, ...initialRoll.data };
if ( initialRoll?.parts ) parts.unshift(...initialRoll.parts);
Expand Down
8 changes: 8 additions & 0 deletions templates/actors/character-sheet-2.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -310,11 +310,19 @@
</div>

{{!-- Roll Death Save --}}
{{#if editable}}
<button type="button" data-action="death" data-tooltip="DND5E.DeathSaveConfigure"
aria-label="{{ localize "DND5E.DeathSaveConfigure" }}"
class="death-save config-button unbutton">
<i class="fas fa-cog" inert></i>
</button>
{{else}}
<button type="button" data-action="rollDeathSave" data-tooltip="DND5E.DeathSaveRoll"
aria-label="{{ localize "DND5E.DeathSaveRoll" }}"
class="{{ rollableClass }} death-save unbutton">
<dnd5e-icon src="systems/dnd5e/icons/svg/statuses/dead.svg"></dnd5e-icon>
</button>
{{/if}}

{{!-- Failures --}}
<div class="pips" data-prop="system.attributes.death.failure">
Expand Down
34 changes: 34 additions & 0 deletions templates/actors/config/death-config.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<section class="flexcol">
{{!-- Death --}}
<fieldset class="card">
<legend>{{ localize "DND5E.DeathSave" }}</legend>
{{ formField fields.bonuses.fields.save value=data.bonuses.save localize=true rootId=partId }}
</fieldset>

{{!-- Rolls --}}
<fieldset class="card">
<legend>{{ localize "DND5E.ROLL.Section" label=(localize "DND5E.DeathSave") }}</legend>
{{ formField fields.roll.fields.mode value=data.roll.mode options=advantageModeOptions localize=true
rootId=partId }}
<div class="form-group split-group">
<label>{{ localize "DND5E.ROLL.Range.Label" }}</label>
<div class="form-fields">
{{ formField fields.roll.fields.min value=data.roll.min placeholder="1" type="number" localize=true
label=(localize "DND5E.Minimum") hint=false classes="label-top" rootId=partId }}
{{ formField fields.roll.fields.max value=data.roll.max placeholder="20" type="number" localize=true
label=(localize "DND5E.Maximum") hint=false classes="label-top" rootId=partId }}
</div>
</div>
</fieldset>

{{!-- Global Bonus --}}
{{#if global}}
<fieldset class="card">
<legend>{{ localize "DND5E.ABILITY.SECTIONS.Global.Label" }}</legend>
<div class="form-group">
<p class="hint">{{ localize "DND5E.ABILITY.SECTIONS.Global.Hint" }}</p>
</div>
{{ formField global.fields.save value=global.data.save localize=true rootId=partId }}
</fieldset>
{{/if}}
</section>