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)

View File

@@ -301,7 +301,11 @@ erode_obj(
if (otmp->owornmask) {
/* unwear otmp before deleting it */
if (carried(otmp)) {
/* otmp remains in hero's invent */
/* otmp remains in hero's invent; if we get here because
it is being burned up by lava, we don't need to worry
about unwearing levitation boots and having that
trigger float_down to then fall in again; if such
were being worn, they wouldn't be in the lava now */
remove_worn_item(otmp, TRUE); /* calls Cloak_off(),&c */
} else if (mcarried(otmp)) {
/* results in otmp->where==OBJ_FREE; delobj() doesn't care */
@@ -6113,8 +6117,9 @@ boolean
lava_effects(void)
{
register struct obj *obj, *obj2;
int dmg = d(6, 6); /* only applicable for water walking */
int lifesave_limit;
boolean usurvive, boil_away;
int dmg = d(6, 6); /* only applicable for water walking */
if (iflags.in_lava_effects) {
debugpline0("Skipping recursive lava_effects().");
@@ -6200,7 +6205,8 @@ lava_effects(void)
boil_away = (u.umonnum == PM_WATER_ELEMENTAL
|| u.umonnum == PM_STEAM_VORTEX
|| u.umonnum == PM_FOG_CLOUD);
for (;;) {
lifesave_limit = 20; /* prevent fuzz testing from getting stuck */
do {
u.uhp = -1;
/* killer format and name are reconstructed every iteration
because lifesaving resets them */
@@ -6213,10 +6219,21 @@ lava_effects(void)
break; /* successful life-save */
/* nowhere safe to land; repeat burning loop */
pline("You're still burning.");
}
} while (--lifesave_limit > 0);
iflags.in_lava_effects--;
if (!lifesave_limit) { /* failed to be teleported to safety */
gd.done_seq = 0L; /* reset the life-saves per move limit */
/* when fuzz testing, couldn't be rescued but mustn't stay stuck
in the done(BURNING) loop; if not fuzz testing, player has
answered no to "Die?" over and over (ridiculously persistent
or maybe pasted a bunch of junk into the input buffer) */
goto burn_stuff; /* moveloop() will kill hero again next move;
* fuzzer will eventually pick wizard mode level
* teleport for hero's randomly chosen action */
}
/*
* 3.7: this used to be unconditional "back on solid <surface>"
* but surface() could return a lot of things where that ends up