]> granicus.if.org Git - nethack/commitdiff
\#wizmakemap vs migrating monsters
authorPatR <rankin@nethack.org>
Fri, 15 Jul 2022 21:36:03 +0000 (14:36 -0700)
committerPatR <rankin@nethack.org>
Fri, 15 Jul 2022 21:36:03 +0000 (14:36 -0700)
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.

src/cmd.c
src/mon.c
src/vault.c

index b7858d4f781fc66aa9da63e520839966e4c69f85..28d06939dbcf625428a77c9661f15372412118c7 100644 (file)
--- 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;
 }
 
index 0613aafe87825805d272a3a26b9d508cc3ba6d80..517054ff4af02894d723532d0aa0352253e54963 100644 (file)
--- 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
index 5c867663a7e70a9d91f49a11d3784c13bda8bacd..9e14fae23040c3cf0cbe3328ec2691fa56d7557c 100644 (file)
@@ -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) {