From: PatR Date: Fri, 15 Jul 2022 21:36:03 +0000 (-0700) Subject: \#wizmakemap vs migrating monsters X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=e5977a3a2d3ff966a1762dd49627a523cdfc8076;p=nethack \#wizmakemap vs migrating monsters The bookkeeping for number of dead or removed monsters got out of sync if a shopkeeper, temple priest, or vault guard on the migrating_mons list who was scheduled to return to the current level got passed to mongone() when #wizmakemap destroyed the current level in order to replace it. When getting rid of such monsters, first put them on fmon. Once 'gone' they'll still be on that list and dmonsfree() will include them in the tally of dead or removed monsters and hopefully the count will match the number it thinks were pending. This works for a vault guard; I didn't try for shopkeeper or priest. Ditch pet(s) so that they won't kill stuff and open up map spots while you're waiting for guard activity; enter vault away from the wall nearest to 'civilization'; fill the level with lichens or other junk; wait for guard to arrive; don't drop gold. After a short while the guard will try to teleport next to you but with the level full will end up in limbo instead (migrating, scheduled to come back to current level). Then perform #wizmakemap. Prior to this patch, there will be an impossible about N removed not matching N+1 pending; after it, no such impossible. --- diff --git a/src/cmd.c b/src/cmd.c index b7858d4f7..28d06939d 100644 --- a/src/cmd.c +++ b/src/cmd.c @@ -130,7 +130,7 @@ static int wiz_rumor_check(void); static int wiz_migrate_mons(void); #endif -static void makemap_unmakemon(struct monst *); +static void makemap_unmakemon(struct monst *, boolean); static void makemap_remove_mons(void); static void wiz_map_levltyp(void); static void wiz_levltyp_legend(void); @@ -992,7 +992,7 @@ wiz_identify(void) /* used when wiz_makemap() gets rid of monsters for the old incarnation of a level before creating a new incarnation of it */ static void -makemap_unmakemon(struct monst *mtmp) +makemap_unmakemon(struct monst *mtmp, boolean migratory) { int ndx = monsndx(mtmp->data); @@ -1013,6 +1013,14 @@ makemap_unmakemon(struct monst *mtmp) } else if (mtmp->isshk && on_level(&u.uz, &ESHK(mtmp)->shoplevel)) { setpaid(mtmp); } + if (migratory) { + /* caller has removed 'mtmp' from migrating_mons; put it onto fmon + so that dmonsfree() bookkeeping for number of dead or removed + monsters won't get out of sync; it is not on the map but + mongone() -> m_detach() -> mon_leaving_level() copes with that */ + mtmp->nmon = fmon; + fmon = mtmp; + } mongone(mtmp); } @@ -1028,7 +1036,7 @@ makemap_remove_mons(void) keepdogs(TRUE); /* (pets-only; normally we'd be using 'FALSE') */ /* get rid of all the monsters that didn't make it to 'mydogs' */ for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) - makemap_unmakemon(mtmp); + makemap_unmakemon(mtmp, FALSE); /* some monsters retain details of this level in mon->mextra; that data becomes invalid when the level is replaced by a new one; get rid of them now if migrating or already arrived elsewhere; @@ -1044,13 +1052,16 @@ makemap_remove_mons(void) || (mtmp->ispriest && on_level(&u.uz, &EPRI(mtmp)->shrlevel)) || (mtmp->isgd && on_level(&u.uz, &EGD(mtmp)->gdlevel)))) { *mprev = mtmp->nmon; - makemap_unmakemon(mtmp); + makemap_unmakemon(mtmp, TRUE); } else { mprev = &mtmp->nmon; } } /* release dead and 'unmade' monsters */ dmonsfree(); + if (fmon) { + impossible("makemap_remove_mons: 'fmon' did not get emptied?"); + } return; } diff --git a/src/mon.c b/src/mon.c index 0613aafe8..517054ff4 100644 --- a/src/mon.c +++ b/src/mon.c @@ -93,7 +93,7 @@ sanity_check_single_mon( #endif if (DEADMONSTER(mtmp)) { #if 0 - /* bad if not fmons list or if not vault guard */ + /* bad if not fmon list or if not vault guard */ if (strcmp(msg, "fmon") || !mtmp->isgd) impossible("dead monster on %s; %s at <%d,%d>", msg, mons[mndx].pmnames[NEUTRAL], @@ -2129,7 +2129,7 @@ dmonsfree(void) char buf[QBUFSZ]; buf[0] = '\0'; - for (mtmp = &fmon; *mtmp;) { + for (mtmp = &fmon; *mtmp; ) { freetmp = *mtmp; if (DEADMONSTER(freetmp) && !freetmp->isgd) { *mtmp = freetmp->nmon; @@ -2401,8 +2401,13 @@ m_detach( if (In_endgame(&u.uz)) mtmp->mstate |= MON_ENDGAME_FREE; - mtmp->mstate |= MON_DETACH; - iflags.purge_monsters++; + if ((mtmp->mstate & MON_DETACH) != 0) { + impossible("m_detach: %s is already detached?", + minimal_monnam(mtmp, FALSE)); + } else { + mtmp->mstate |= MON_DETACH; + iflags.purge_monsters++; + } } /* give a life-saved monster a reasonable mhpmax value in case it has diff --git a/src/vault.c b/src/vault.c index 5c867663a..9e14fae23 100644 --- a/src/vault.c +++ b/src/vault.c @@ -55,7 +55,7 @@ clear_fcorr(struct monst *grd, boolean forceshow) if (!on_level(&egrd->gdlevel, &u.uz)) return TRUE; - /* note: guard remains on 'fmons' list (alive or dead, at off-map + /* note: guard remains on 'fmon' list (alive or dead, at off-map coordinate <0,0>), until temporary corridor from vault back to civilization has been removed */ while ((fcbeg = egrd->fcbeg) < egrd->fcend) {