break fuzzer out of life-save loop, take II

When being burned up by lava, die 20 times before giving up the
attempt at life-saving (was unlimited).  Giving up leads to the hero
standing on lava rather than dying.  Normally moveloop() dunks the
hero again on next turn but fuzzer life-saving now has a chance to
confer temporary fire resistance.  So hero might have an opportunity
to level teleport or use ranged attacks that free up spots so have
somewhere available to teleport to safety if/when dunked again.

The recent code to give up on trying to resurrect the dying hero
after 15 deaths on the same move is extended to 20.  They apply to
each of the 20 lava resurrect attempts but still doesn't guarantee
that the hero will eventually get free before done() gives up.
This commit is contained in:
PatR
2023-05-08 01:35:21 -07:00
parent 9ca1c5fb56
commit 210387c176
2 changed files with 97 additions and 31 deletions

103
src/end.c
View File

@@ -29,6 +29,7 @@ static void disclose(int, boolean);
static void get_valuables(struct obj *);
static void sort_valuables(struct valuable_data *, int);
static void artifact_score(struct obj *, boolean, winid);
static boolean fuzzer_savelife(int);
ATTRNORETURN static void really_done(int) NORETURN;
static void savelife(int);
static boolean should_query_disclose_option(int, char *);
@@ -1142,6 +1143,77 @@ artifact_score(
}
}
/* when dying while running the debug fuzzer, [almost] always keep going;
True: forced survival; False: doomed unless wearing life-save amulet */
static boolean
fuzzer_savelife(int how)
{
/*
* Some debugging code pulled out of done() to unclutter it.
* 'done_seq' is maintained in done().
*/
if (!gp.program_state.panicking
&& how != PANICKED && how != TRICKED
/* Guard against getting stuck in a loop if we die in one of
* the few ways where life-saving isn't effective (cited case
* was burning in lava when the level was too full to allow
* teleporting to safety). Skip the life-save attempt if we've
* died on the same move more than 20 times; give up instead.
* (Note: theoretically we could get killed more than that in
* one move if there are multiple fast monsters with multiple
* attacks against a wimply hero, or a ton of ranged attacks.) */
&& (gd.done_seq++ < gh.hero_seq + 20L)) {
savelife(how);
/* periodically restore characteristics plus lost experience
levels or cure lycanthropy or both; those conditions make the
hero vulnerable to repeat deaths (often by becoming surrounded
while being too encumbered to do anything) */
if (!rn2((gd.done_seq > gh.hero_seq + 2L) ? 2 : 10)) {
struct obj *potion;
int propidx, proptim, remedies = 0;
/* get rid of temporary potion with obfree() rather than useup()
because it doesn't get entered into inventory */
if (u.ulycn >= LOW_PM && !rn2(3)) {
potion = mksobj(POT_WATER, TRUE, FALSE);
bless(potion);
(void) peffects(potion);
obfree(potion, (struct obj *) 0);
++remedies;
}
if (!remedies || rn2(3)) {
potion = mksobj(POT_RESTORE_ABILITY, TRUE, FALSE);
bless(potion);
(void) peffects(potion);
obfree(potion, (struct obj *) 0);
++remedies;
}
if (!rn2(3 + 3 * remedies)) {
/* confer temporary resistances for first 8 properities:
fire, cold, sleep, disint, shock, poison, acid, stone */
for (propidx = 1; propidx <= 8; ++propidx) {
if (!u.uprops[propidx].intrinsic
&& !u.uprops[propidx].extrinsic
&& (proptim = rn2(3)) > 0) /* 0..2 */
set_itimeout(&u.uprops[propidx].intrinsic,
(long) (2 * proptim + 1)); /* 3 or 5 */
}
++remedies;
}
if (!rn2(5 + 5 * remedies)) {
; /* might confer temporary Antimagic (magic resistance)
* or even Invulnerable */
}
}
/* clear stale cause of death info after life-saving */
gk.killer.name[0] = '\0';
gk.killer.format = 0;
return TRUE;
}
return FALSE; /* panic or too many consecutive deaths */
}
/* Be careful not to call panic from here! */
void
done(int how)
@@ -1175,38 +1247,14 @@ done(int how)
/* hero_seq is (moves<<3 + n) where n is number of moves made
by the hero on the current turn (since the 'moves' variable
actually counts turns); its details shouldn't matter here */
actually counts turns); its details shouldn't matter here;
used by fuzzer_savelife() and for hangup below */
if (gd.done_seq < gh.hero_seq)
gd.done_seq = gh.hero_seq;
if (iflags.debug_fuzzer) {
if (!gp.program_state.panicking && how != PANICKED
/* Guard against getting stuck in a loop if we die in one of
* the few ways where life-saving isn't effective (cited case
* was burning in lava when the level was too full to allow
* teleporting to safety). Skip the life-save attempt if we've
* died on the same move more than 15 times; give up instead.
* (Note: theoretically we could get killed more than that in
* one move if there are multiple fast monsters with multiple
* attacks against a wimply hero, or a ton of ranged attacks.) */
&& (gd.done_seq++ < gh.hero_seq + 15L)) {
savelife(how);
/* periodically restore characteristics and lost exp levels
or cure lycanthropy */
if (!rn2(10)) {
struct obj *potion = mksobj((u.ulycn > LOW_PM && !rn2(3))
? POT_WATER : POT_RESTORE_ABILITY,
TRUE, FALSE);
bless(potion);
(void) peffects(potion); /* always -1 for restore ability */
/* not useup(); we haven't put this potion into inventory */
obfree(potion, (struct obj *) 0);
}
gk.killer.name[0] = '\0';
gk.killer.format = 0;
if (fuzzer_savelife(how))
return;
}
}
if (how == ASCENDED || (!gk.killer.name[0] && how == GENOCIDED))
@@ -1231,6 +1279,7 @@ done(int how)
}
if (Lifesaved && (how <= GENOCIDED)) {
pline("But wait...");
/* assumes that only one type of item confers LifeSaved property */
makeknown(AMULET_OF_LIFE_SAVING);
Your("medallion %s!", !Blind ? "begins to glow" : "feels warm");
if (how == CHOKING)