From ec7781b996c66a9c600ed7a7f40dad88d1bc0c4d Mon Sep 17 00:00:00 2001 From: Alex Smith Date: Sat, 2 May 2026 10:25:39 +0100 Subject: [PATCH] Remove saving grace This feature was no longer fulfilling its intended purpose: the issue is that when players are aware of it, instead of creating interesting stories, it becomes a resource the player can rely on. Alternative mitigation for some "unfair" early-game deaths has been added (such as the warning shot for early-game attack wands and the ability of iron shoes to protect against certain early traps), and the warning shot in particular can create interesting gameplay moments and stories of its own, especially when saving grace is *not* present (discovering that a monster has a wand of fire and can oneshot you is an interesting emergency situation, but less scary if you know that its first attempt to oneshot you will fail). The feature was also proving quite hard to code correctly, because there are numerous cases that it "obviously" shouldn't affect (most notably beheading due to Vorpal Blade, but also things like purple worm digestion) which would need to be special-cased, and because there are grey areas like wand zap bounces (which might or might not be an intentional attempt by the player to hit themself with the bounce) and damage from traps. Removing it saves the need to work out, for every new damage source, whether and when saving grace should interact with it. Breaks save compatibility. --- doc/fixes3-7-0.txt | 8 ------- include/decl.h | 6 ----- include/extern.h | 1 - include/patchlevel.h | 2 +- include/you.h | 1 - src/allmain.c | 5 ---- src/decl.c | 4 ---- src/hack.c | 55 -------------------------------------------- src/insight.c | 15 ------------ src/mhitu.c | 1 - 10 files changed, 1 insertion(+), 97 deletions(-) diff --git a/doc/fixes3-7-0.txt b/doc/fixes3-7-0.txt index 65da5b0cf..916ee7177 100644 --- a/doc/fixes3-7-0.txt +++ b/doc/fixes3-7-0.txt @@ -2219,9 +2219,6 @@ uncursed genocide while hallucinating deliberately mis-reports hero's role as target but was also inappropriately showing it to livelog/#chronicle livelog/#chronicle for bribing a demon lord reported random monster and currency if hero was hallucinating -livelog/#chronicle for saving-grace: if saving-grace prevents hero's death, - report it [at present, it uses the livelog classification for breaking - a conduct] naming a type of item, unnaming it (with name of ), naming it again (new name or re-use of old one), then unnaming it again would issue impossible: "named object not in disco" @@ -2904,11 +2901,6 @@ pyrolisk eggs explode when broken pauper-option to start the character with no possessions wand of secret door detection, spell of detect unseen, and wizard mode ^E now flash the cursor at each location where detection finds something -saving-grace: once per game if receiving a killing blow from full or nearly - full HP, survive with 1 HP -enlightenment/attribute disclosure for saving-grace: include a line for have - or haven't been saved (game in progress) or did or didn't get saved - (game over) via saving-grace 'selectsaved' lists "name-role-race-gend-algn" instead of just "name" in the menu of save files available to be restored 'selectsaved' prefixes "name-role-race-gend-algn" with "- " for normal play, diff --git a/include/decl.h b/include/decl.h index 9f9478052..d1b4cafe6 100644 --- a/include/decl.h +++ b/include/decl.h @@ -840,9 +840,6 @@ struct instance_globals_r { struct instance_globals_s { - /* allmain.c */ - boolean saving_grace_turn; /* saving grace was triggered this turn */ - /* artifact.c */ int spec_dbon_applies; /* coordinate effects from spec_dbon() with messages in artifact_hit() */ @@ -974,9 +971,6 @@ struct instance_globals_t { struct instance_globals_u { - /* allmain.c */ - int uhp_at_start_of_monster_turn; - /* botl.c */ boolean update_all; diff --git a/include/extern.h b/include/extern.h index 4843b3731..3ad80a5de 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1236,7 +1236,6 @@ extern int monster_nearby(void); extern void end_running(boolean); extern void nomul(int); extern void unmul(const char *); -extern int saving_grace(int); extern void showdamage(int); extern void losehp(int, const char *, schar) ; extern int weight_cap(void); diff --git a/include/patchlevel.h b/include/patchlevel.h index fd3dafc09..c2b43469d 100644 --- a/include/patchlevel.h +++ b/include/patchlevel.h @@ -17,7 +17,7 @@ * Incrementing EDITLEVEL can be used to force invalidation of old bones * and save files. */ -#define EDITLEVEL 142 +#define EDITLEVEL 143 /* * Development status possibilities. diff --git a/include/you.h b/include/you.h index a073e5c57..d71cb6e63 100644 --- a/include/you.h +++ b/include/you.h @@ -428,7 +428,6 @@ struct you { Bitfield(uinvulnerable, 1); /* you're invulnerable (praying) */ Bitfield(uburied, 1); /* you're buried */ Bitfield(uedibility, 1); /* blessed food detect; sense unsafe food */ - Bitfield(usaving_grace, 1); /* prevents death once */ Bitfield(uhandedness, 1); /* There is no advantage for either handedness. The distinction is only for flavor variation and for use in messages. */ diff --git a/src/allmain.c b/src/allmain.c index 48dc040ef..7b82d86a8 100644 --- a/src/allmain.c +++ b/src/allmain.c @@ -208,7 +208,6 @@ moveloop_core(void) encumber_msg(); svc.context.mon_moving = TRUE; - gu.uhp_at_start_of_monster_turn = u.uhp; do { monscanmove = movemon(); if (u.umovement >= NORMAL_SPEED) @@ -277,8 +276,6 @@ moveloop_core(void) if (u.ublesscnt) u.ublesscnt--; - gs.saving_grace_turn = FALSE; - /* One possible result of prayer is healing. Whether or * not you get healed depends on your current hit points. * If you are allowed to regenerate during the prayer, @@ -430,8 +427,6 @@ moveloop_core(void) else if (!u.umoved) (void) pooleffects(FALSE); - gs.saving_grace_turn = FALSE; - /* vision while buried or underwater is updated here */ if (Underwater) under_water(0); diff --git a/src/decl.c b/src/decl.c index b8852795f..558ad66ab 100644 --- a/src/decl.c +++ b/src/decl.c @@ -675,8 +675,6 @@ static const struct instance_globals_r g_init_r = { }; static const struct instance_globals_s g_init_s = { - /* allmain.c */ - FALSE, /* saving_grace_turn */ /* artifact.c */ 0, /* spec_dbon_applies */ /* decl.c */ @@ -767,8 +765,6 @@ static const struct instance_globals_t g_init_t = { }; static const struct instance_globals_u g_init_u = { - /* allmain.c */ - 0, /* uhp_at_start_of_monster_turn */ /* botl.c */ FALSE, /* update_all */ /* decl.c */ diff --git a/src/hack.c b/src/hack.c index 367591eff..f4d108d95 100644 --- a/src/hack.c +++ b/src/hack.c @@ -4242,60 +4242,6 @@ maybe_wail(void) } } -/* once per game, if receiving a killing blow from above 90% HP, - allow the hero to survive with 1 HP */ -int -saving_grace(int dmg) -{ - if (dmg < 0) { - impossible("saving_grace check for negative damage? (%d)", dmg); - return 0; - } -#if 0 /* saving grace _does_ protect hero during own actions */ - if (!svc.context.mon_moving) { - /* saving grace doesn't protect you from your own actions */ - return dmg; - } -#endif - if (dmg < u.uhp || u.uhp <= 0) { - /* no need for saving grace */ - return dmg; - } - - if (gs.saving_grace_turn) { - /* saving grace already triggered and prevents HP reducing below 1 - this turn (specifically: until the next player action or turn - boundary), don't print further messages or livelog entries */ - return u.uhp - 1; - } - - if (!u.usaving_grace && - (gu.uhp_at_start_of_monster_turn * 100 / u.uhpmax) >= 90) { - /* saving_grace doesn't have it's own livelog classification; - we might invent one, or perhaps use LL_LIFESAVE, but surviving - certain death (or preserving worn amulet of life saving) via - saving-grace feels like breaking a conduct; not sure how best - to phrase this though; classifying it as a spoiler will hide it - from #chronicle during play but show it to livelog observers */ - livelog_printf(LL_CONDUCT | LL_SPOILER, "%s (%d damage, %d/%d HP)", - "survived one-shot death via saving-grace", - /* include damage that happened earlier this turn */ - gu.uhp_at_start_of_monster_turn - u.uhp + dmg, - gu.uhp_at_start_of_monster_turn, u.uhpmax); - - /* note: this could reduce dmg to 0 if u.uhpmax==1 */ - dmg = u.uhp - 1; - u.usaving_grace = 1; /* used up */ - gs.saving_grace_turn = TRUE; - end_running(TRUE); - if (u.usleep) - unmul("Suddenly you wake up!"); - if (is_fainted()) - reset_faint(); - } - return dmg; -} - /* show a message how much damage you received */ void showdamage(int dmg) @@ -4330,7 +4276,6 @@ losehp(int n, const char *knam, schar k_format) return; } - n = saving_grace(n); u.uhp -= n; showdamage(n); if (u.uhp > u.uhpmax) diff --git a/src/insight.c b/src/insight.c index ab2bcd1a1..35351a5f3 100644 --- a/src/insight.c +++ b/src/insight.c @@ -1974,21 +1974,6 @@ attributes_enlightenment( } #endif - /* saving-grace: show during final disclosure, hide during normal play */ - if (final || wizard || discover) { - static const char *verbchoices[2][2] = { - { "might avoid", "have avoided" }, - { "could have avoided", "avoided" }, - }; - /* u.usaving_grace will always be 0 or 1; final is 0 (game in - progress), 1 (game over, survived), or 2 (game over, died) */ - const char *verb = verbchoices[!!final][u.usaving_grace]; - - /* 'verb' has already been set for present or past but enl_msg() - needs it twice, one for in progress, the other for game over */ - enl_msg(You_, verb, verb, " a one-shot death via saving-grace", ""); - } - { const char *p; diff --git a/src/mhitu.c b/src/mhitu.c index 71d90a8ec..feb29ffbd 100644 --- a/src/mhitu.c +++ b/src/mhitu.c @@ -1916,7 +1916,6 @@ mdamageu(struct monst *mtmp, int n) if (u.mh < 1) rehumanize(); } else { - n = saving_grace(n); u.uhp -= n; showdamage(n); /* caller might have reduced uhpmax before calling mdamageu() */