diff --git a/src/end.c b/src/end.c index 4dfe6e629..53a4dc077 100644 --- a/src/end.c +++ b/src/end.c @@ -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) diff --git a/src/trap.c b/src/trap.c index 46cf32718..1dc9c55ac 100644 --- a/src/trap.c +++ b/src/trap.c @@ -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 " * but surface() could return a lot of things where that ends up