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.
This commit is contained in:
Alex Smith
2026-05-02 10:25:39 +01:00
parent b8c1a5dae9
commit ec7781b996
10 changed files with 1 additions and 97 deletions
-8
View File
@@ -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 <space>), 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,
-6
View File
@@ -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;
-1
View File
@@ -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);
+1 -1
View File
@@ -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.
-1
View File
@@ -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. */
-5
View File
@@ -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);
-4
View File
@@ -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 */
-55
View File
@@ -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)
-15
View File
@@ -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;
-1
View File
@@ -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() */