From cc5bb44a9a757fe00800c90cb3a648b009af155d Mon Sep 17 00:00:00 2001 From: PatR Date: Sat, 24 Nov 2018 01:45:09 -0800 Subject: [PATCH] fix #H7591 - migrating monsters vs full levels During level change, when a monster from mydogs (monsters accompaying hero, usually pets) couldn't be placed because the level was full, it was set to migrate to that level (in order to get another chance to arrive if hero left and returned). The code sequence mon_arrive()-> mnexto()-> m_into_limbo()-> migrate_to_level()-> relmon() tried to remove the monster from the map, but it wasn't necessarily on the map (depending upon whether it couldn't arrive at all, or arrived at the hero's spot and couldn't be moved out of the hero's way). The EXTRA_SANITY_CHECKS for remove_monster() issued impossible "no monster to remove". relmon() now checks whether monster is already off the map. While investigating that, I discovered that pets set to re-migrate to the same level to try again on hero's next visit didn't work at all. migrating_mons gets processed after mydogs so moving something from the latter to the former after arrival failure just resulted in immediate second failure when the more general list was handled during the hero's current arrival. And failure to arrive from migrating_mons would kill the monster instead of scheduling another attempt. The sanest fix for that turned out to be to have all monsters who can't arrive be put back on the migrating_mons list to try again upon hero's next visit. Pets still fail twice but are no longer discarded during the second time, and now do arrive when hero leaves and comes back provided he or she has opened up some space before leaving. If there's still no space on the next visit, monsters who can't arrive then are scheduled to try again on the visit after that. Recent fix for invalid corpses becomes moot. Monsters aren't killed during arrival failure so there are no resulting corpses to deal with. --- doc/fixes36.2 | 11 +++++++ include/rm.h | 18 ++++++---- src/do.c | 7 ++-- src/dog.c | 91 +++++++++++++++++++-------------------------------- src/mon.c | 20 ++++++----- 5 files changed, 70 insertions(+), 77 deletions(-) diff --git a/doc/fixes36.2 b/doc/fixes36.2 index 2ad652f36..c83806f8d 100644 --- a/doc/fixes36.2 +++ b/doc/fixes36.2 @@ -205,6 +205,17 @@ successfully paying for shop damage with shop credit would be followed by if a migrating monster was killed off because there was no room on the destination level, it would leave a corpse even if it was a type which should never leave one (demon, golem, blob, &c) +monsters accompanying hero during level change (usually pets) who failed to + arrive and tried to re-migrate were being removed from the map after + already having been removed [impossible "no monster to remove" if + compiled with EXTRA_SANITY_CHECKS enabled] during migration handling +monsters accompanying hero during level change (usually pets) who failed to + arrive and tried to re-migrate (for hero's next visit to the level) + ended up being killed because the migration attempt happened right + away (same visit by hero, so level still full) and they weren't + accompanying hero on the second attempt +fix for above (all failed arrivals will re-migrate) makes the earlier fix (for + invalid corpse being left by monst killed upon migration failure) moot end of game while carrying Schroedinger's Box would reveal cat-or-corpse for inventory disclosure or put that info into dumplog, but not both attempting to untrap an adjacent trap while on the edge of--not in--a pit diff --git a/include/rm.h b/include/rm.h index b6cf36d54..080677e15 100644 --- a/include/rm.h +++ b/include/rm.h @@ -1,4 +1,4 @@ -/* NetHack 3.6 rm.h $NHDT-Date: 1432512776 2015/05/25 00:12:56 $ $NHDT-Branch: master $:$NHDT-Revision: 1.41 $ */ +/* NetHack 3.6 rm.h $NHDT-Date: 1543052680 2018/11/24 09:44:40 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.59 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Pasi Kallinen, 2017. */ /* NetHack may be freely redistributed. See license for details. */ @@ -631,13 +631,17 @@ extern dlevel_t level; /* structure describing the current level */ (level.monsters[x][y] != (struct monst *) 0 \ && (level.monsters[x][y])->mburied) #ifdef EXTRA_SANITY_CHECKS -#define place_worm_seg(m, x, y) do { \ - if (level.monsters[x][y] && level.monsters[x][y] != m) impossible("place_worm_seg over mon"); \ - level.monsters[x][y] = m; \ +#define place_worm_seg(m, x, y) \ + do { \ + if (level.monsters[x][y] && level.monsters[x][y] != m) \ + impossible("place_worm_seg over mon"); \ + level.monsters[x][y] = m; \ } while(0) -#define remove_monster(x, y) do { \ - if (!level.monsters[x][y]) impossible("no monster to remove"); \ - level.monsters[x][y] = (struct monst *) 0; \ +#define remove_monster(x, y) \ + do { \ + if (!level.monsters[x][y]) \ + impossible("no monster to remove"); \ + level.monsters[x][y] = (struct monst *) 0; \ } while(0) #else #define place_worm_seg(m, x, y) level.monsters[x][y] = m diff --git a/src/do.c b/src/do.c index 4bb739254..4817d150a 100644 --- a/src/do.c +++ b/src/do.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 do.c $NHDT-Date: 1542765356 2018/11/21 01:55:56 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.174 $ */ +/* NetHack 3.6 do.c $NHDT-Date: 1543052696 2018/11/24 09:44:56 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.175 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Derek S. Ray, 2015. */ /* NetHack may be freely redistributed. See license for details. */ @@ -1446,10 +1446,9 @@ boolean at_stairs, falling, portal; with the situation, so only say something when debugging */ if (wizard) pline("(monster in hero's way)"); - if (!rloc(mtmp, TRUE) || m_at(u.ux, u.uy)) + if (!rloc(mtmp, TRUE) || (mtmp = m_at(u.ux, u.uy)) != 0) /* no room to move it; send it away, to return later */ - migrate_to_level(mtmp, ledger_no(&u.uz), MIGR_RANDOM, - (coord *) 0); + m_into_limbo(mtmp); } } diff --git a/src/dog.c b/src/dog.c index 0d967195e..93a33716e 100644 --- a/src/dog.c +++ b/src/dog.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 dog.c $NHDT-Date: 1502753406 2017/08/14 23:30:06 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.60 $ */ +/* NetHack 3.6 dog.c $NHDT-Date: 1543052701 2018/11/24 09:45:01 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.84 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2011. */ /* NetHack may be freely redistributed. See license for details. */ @@ -224,7 +224,7 @@ update_mlstmv() void losedogs() { - register struct monst *mtmp, *mtmp0 = 0, *mtmp2; + register struct monst *mtmp, *mtmp0, *mtmp2; int dismissKops = 0; /* @@ -279,17 +279,26 @@ losedogs() mon_arrive(mtmp, TRUE); } - /* time for migrating monsters to arrive */ + /* time for migrating monsters to arrive; + monsters who belong on this level but fail to arrive get put + back onto the list (at head), so traversing it is tricky */ for (mtmp = migrating_mons; mtmp; mtmp = mtmp2) { mtmp2 = mtmp->nmon; if (mtmp->mux == u.uz.dnum && mtmp->muy == u.uz.dlevel) { - if (mtmp == migrating_mons) + /* remove mtmp from migrating_mons list */ + if (mtmp == migrating_mons) { migrating_mons = mtmp->nmon; - else - mtmp0->nmon = mtmp->nmon; + } else { + for (mtmp0 = migrating_mons; mtmp0; mtmp0 = mtmp0->nmon) + if (mtmp0->nmon == mtmp) { + mtmp0->nmon = mtmp->nmon; + break; + } + if (!mtmp0) + panic("losedogs: can't find migrating mon"); + } mon_arrive(mtmp, FALSE); - } else - mtmp0 = mtmp; + } } } @@ -300,9 +309,9 @@ struct monst *mtmp; boolean with_you; { struct trap *t; - struct obj *obj; xchar xlocale, ylocale, xyloc, xyflags, wander; int num_segs; + boolean failed_to_place = FALSE; mtmp->nmon = fmon; fmon = mtmp; @@ -328,7 +337,7 @@ boolean with_you; xyflags = mtmp->mtrack[0].y; xlocale = mtmp->mtrack[1].x; ylocale = mtmp->mtrack[1].y; - memset(mtmp->mtrack, 0, sizeof(mtmp->mtrack)); + memset(mtmp->mtrack, 0, sizeof mtmp->mtrack); if (mtmp == u.usteed) return; /* don't place steed on the map */ @@ -447,47 +456,13 @@ boolean with_you; mtmp->mx = 0; /*(already is 0)*/ mtmp->my = xyflags; - if (xlocale) { - if (!mnearto(mtmp, xlocale, ylocale, FALSE)) - goto fail_mon_placement; - } else { - if (!rloc(mtmp, TRUE)) { - /* - * Failed to place migrating monster, - * probably because the level is full. - * Dump the monster's cargo and leave the monster dead. - * - * TODO? Put back on migrating_mons list instead so - * that if hero leaves this level and then returns, - * monster will have another chance to arrive. - */ -fail_mon_placement: - while ((obj = mtmp->minvent) != 0) { - obj_extract_self(obj); - obj_no_longer_held(obj); - if (obj->owornmask & W_WEP) - setmnotwielded(mtmp, obj); - obj->owornmask = 0L; - if (xlocale && ylocale) - place_object(obj, xlocale, ylocale); - else if (rloco(obj)) { - if (!get_obj_location(obj, &xlocale, &ylocale, 0)) - impossible("Can't find relocated object."); - } - } - /* - * TODO? Maybe switch to make_corpse() [won't be needed if - * we re-migrate as suggested above], probably with new - * CORPSTAT_NOOBJS flag to suppress dragon scales and such. - */ - if (!(mvitals[monsndx(mtmp->data)].mvflags & G_NOCORPSE) - && !LEVEL_SPECIFIC_NOCORPSE(mtmp->data)) - (void) mkcorpstat(CORPSE, mtmp, mtmp->data, - xlocale, ylocale, CORPSTAT_NONE); - mtmp->mx = mtmp->my = 0; /* for mongone, mon is not anywhere */ - mongone(mtmp); - } - } + if (xlocale) + failed_to_place = !mnearto(mtmp, xlocale, ylocale, FALSE); + else + failed_to_place = !rloc(mtmp, TRUE); + + if (failed_to_place) + m_into_limbo(mtmp); /* try again next time hero comes to this level */ } /* heal monster for time spent elsewhere */ @@ -708,7 +683,7 @@ xchar tolev; /* destination level */ xchar xyloc; /* MIGR_xxx destination xy location: */ coord *cc; /* optional destination coordinates */ { - register struct obj *obj; + struct obj *obj; d_level new_lev; xchar xyflags; int num_segs = 0; /* count of worm segments */ @@ -717,12 +692,12 @@ coord *cc; /* optional destination coordinates */ set_residency(mtmp, TRUE); if (mtmp->wormno) { - register int cnt; + int cnt = count_wsegs(mtmp); + /* **** NOTE: worm is truncated to # segs = max wormno size **** */ - cnt = count_wsegs(mtmp); - num_segs = min(cnt, MAX_NUM_WORMS - 1); - wormgone(mtmp); - place_monster(mtmp, mtmp->mx, mtmp->my); + num_segs = min(cnt, MAX_NUM_WORMS - 1); /* used below */ + wormgone(mtmp); /* destroys tail and takes head off map */ + place_monster(mtmp, mtmp->mx, mtmp->my); /* put head back for relmon */ } /* set minvent's obj->no_charge to 0 */ @@ -755,7 +730,7 @@ coord *cc; /* optional destination coordinates */ mtmp->muy = new_lev.dlevel; mtmp->mx = mtmp->my = 0; /* this implies migration */ if (mtmp == context.polearm.hitmon) - context.polearm.hitmon = NULL; + context.polearm.hitmon = (struct monst *) 0; } /* return quality of food; the lower the better */ diff --git a/src/mon.c b/src/mon.c index 043b22e89..03cd1eb9c 100644 --- a/src/mon.c +++ b/src/mon.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 mon.c $NHDT-Date: 1539479657 2018/10/14 01:14:17 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.260 $ */ +/* NetHack 3.6 mon.c $NHDT-Date: 1543052701 2018/11/24 09:45:01 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.270 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Derek S. Ray, 2015. */ /* NetHack may be freely redistributed. See license for details. */ @@ -1618,8 +1618,9 @@ struct monst *mon; struct monst **monst_list; /* &migrating_mons or &mydogs or null */ { struct monst *mtmp; - boolean unhide = (monst_list != 0); int mx = mon->mx, my = mon->my; + boolean on_map = (m_at(mx, my) == mon), + unhide = (monst_list != 0); if (!fmon) panic("relmon: no fmon available."); @@ -1633,10 +1634,12 @@ struct monst **monst_list; /* &migrating_mons or &mydogs or null */ seemimic(mon); } - if (mon->wormno) - remove_worm(mon); - else - remove_monster(mx, my); + if (on_map) { + if (mon->wormno) + remove_worm(mon); + else + remove_monster(mx, my); + } if (mon == fmon) { fmon = fmon->nmon; @@ -1652,7 +1655,8 @@ struct monst **monst_list; /* &migrating_mons or &mydogs or null */ } if (unhide) { - newsym(mx, my); + if (on_map) + newsym(mx, my); /* insert into mydogs or migrating_mons */ mon->nmon = *monst_list; *monst_list = mon; @@ -2515,7 +2519,7 @@ struct monst *mtmp; { unstuck(mtmp); mdrop_special_objs(mtmp); - migrate_to_level(mtmp, ledger_no(&u.uz), MIGR_APPROX_XY, NULL); + migrate_to_level(mtmp, ledger_no(&u.uz), MIGR_APPROX_XY, (coord *) 0); } /* make monster mtmp next to you (if possible);