]> granicus.if.org Git - nethack/commitdiff
fix github issue #531 - genderless corpses
authorPatR <rankin@nethack.org>
Tue, 8 Jun 2021 10:43:46 +0000 (03:43 -0700)
committerPatR <rankin@nethack.org>
Tue, 8 Jun 2021 10:43:46 +0000 (03:43 -0700)
Dead monsters that had traits saved with the corpse would revive as
the same gender, but ordinary corpses revived with random gender so
could be different from before they got killed.

Since corpses of monsters lacked gender, those for monsters with
gender-specific names were described by the neuter name.

This is a fairly big change for a fairly minor problem and needs a
lot more testing.

Fixes #531

12 files changed:
doc/fixes37.0
include/hack.h
include/obj.h
src/display.c
src/mkobj.c
src/mon.c
src/mondata.c
src/nhlobj.c
src/objnam.c
src/sp_lev.c
src/trap.c
src/zap.c

index fb02de6189f2dbac9a67bf10789653850cca8d56..3158478817825b62e9043af8e1a150f211de7075 100644 (file)
@@ -541,6 +541,7 @@ change valkyrie and warrior (valk quest) monsters from chaotic to lawful
 change attendant (healer quest) monster from lawful to neutral
 quit is not longer bound to M-q
 change default value of autopickup to off and color to on
+resurrected corpse of mon could end up with different gender from original mon
 
 
 Fixes to 3.7.0-x Problems that Were Exposed Via git Repository
@@ -710,6 +711,8 @@ stair revamp unintentionally resulted in cursed potion of levitation no longer
        causing hero to hit head on ceiling
 enlightenment/disclosure when wielding two weapons with two-weap skill higher
        than secondary weapon's skill had "with" duplicated in the feedback
+corpse of monster with gender specific names (king vs queen and so forth)
+       was always described by the neutral name (ruler and such)
 
 curses: 'msg_window' option wasn't functional for curses unless the binary
        also included tty support
index 42b1282e21eb34478c684f6d7b375f6ccc443a90..17aaa1243fecf7ec01733ac8dd266c3bdc994129 100644 (file)
@@ -285,10 +285,19 @@ typedef struct sortloot_item Loot;
                                * to make an extra call to goodpos()]        */
 #define GP_ALLOW_U  0x080000L /* don't reject hero's location */
 
-/* flags for make_corpse() and mkcorpstat() */
-#define CORPSTAT_NONE 0x00
-#define CORPSTAT_INIT 0x01   /* pass init flag to mkcorpstat */
-#define CORPSTAT_BURIED 0x02 /* bury the corpse or statue */
+/* flags for make_corpse() and mkcorpstat(); 0..7 are recorded in obj->spe */
+#define CORPSTAT_NONE     0x00
+#define CORPSTAT_GENDER   0x03 /* 0x01 | 0x02 */
+#define CORPSTAT_HISTORIC 0x04 /* historic statue; not used for corpse */
+#define CORPSTAT_SPE_VAL  0x07 /* 0x03 | 0x04 */
+#define CORPSTAT_INIT     0x08 /* pass init flag to mkcorpstat */
+#define CORPSTAT_BURIED   0x10 /* bury the corpse or statue */
+/* note: gender flags have different values from those used for monsters
+   so that 0 can be unspecified/random instead of male */
+#define CORPSTAT_RANDOM 0
+#define CORPSTAT_FEMALE 1
+#define CORPSTAT_MALE   2
+#define CORPSTAT_NEUTER 3
 
 /* flags for decide_to_shift() */
 #define SHIFT_SEENMSG 0x01 /* put out a message if in sight */
index bc00a738808398c57bdbcd32caf42b6c223fcf68..40e5db202c4b988269c604f445740d7f34ceb3a4 100644 (file)
@@ -55,10 +55,8 @@ struct obj {
                 * candy bar wrapper index;
                 * scroll of mail (normal==0, bones or wishing==1, written==2);
                 * splash of venom (normal==0, wishing==1);
-                * historic flag and gender for statues */
-#define STATUE_HISTORIC 0x01
-#define STATUE_MALE 0x02
-#define STATUE_FEMALE 0x04
+                * gender for corpses and statues (0..3, CORPSTAT_GENDER),
+                * historic flag (4, CORPSTAT_HISTORIC) for statues */
     char oclass;    /* object class */
     char invlet;    /* designation in inventory */
     char oartifact; /* artifact array index */
index 3a1a8d1117a72fbc206fd45a976e1e189b5d1865..fa4a59e6e5b0ebbd4d619fad5fc6565ec9140edd 100644 (file)
@@ -2169,7 +2169,8 @@ map_glyphinfo(xchar x, xchar y, int glyph,
         special |= MG_STATUE;
         if (is_objpile(x,y))
             special |= MG_OBJPILE;
-        if ((obj = sobj_at(STATUE, x, y)) && (obj->spe & STATUE_FEMALE))
+        if ((obj = sobj_at(STATUE, x, y)) != 0
+            && (obj->spe & CORPSTAT_GENDER) == CORPSTAT_FEMALE)
             special |= MG_FEMALE;
     } else if ((offset = (glyph - GLYPH_WARNING_OFF)) >= 0) { /* warn flash */
         idx = offset + SYM_OFF_W;
index 4873c85e410ebe1b3d3808215f8b92b9a7feaac6..bd4e1d97314352a890e88482677c3d276afe677e 100644 (file)
@@ -1520,7 +1520,9 @@ mkcorpstat(
     } else {
         otmp = mksobj_at(objtype, x, y, init, FALSE);
     }
-    otmp->norevive = g.mkcorpstat_norevive;
+    /* record gender and 'historic statue' in overloaded enchantment field */
+    otmp->spe = (corpstatflags & CORPSTAT_SPE_VAL);
+    otmp->norevive = g.mkcorpstat_norevive; /* via envrmt rather than flags */
 
     /* when 'mtmp' is non-null save the monster's details with the
        corpse or statue; it will also force the 'ptr' override below */
@@ -1685,10 +1687,10 @@ mk_tt_object(
    never returns Null */
 struct obj *
 mk_named_object(
-int objtype, /* CORPSE or STATUE */
-struct permonst *ptr,
-int x, int y,
-const char *nm)
+    int objtype, /* CORPSE or STATUE */
+    struct permonst *ptr,
+    int x, int y,
+    const char *nm)
 {
     struct obj *otmp;
     unsigned corpstatflags = (objtype != STATUE) ? CORPSTAT_INIT
index 0578921f97b8d1e0bf33d5310496b387f7b54ad6..9cd1c28f303c8a8e0f8c29f595b14aa8f6b3fe1f 100644 (file)
--- a/src/mon.c
+++ b/src/mon.c
@@ -480,7 +480,7 @@ pm_to_cham(int mndx)
  * etc....
  */
 static struct obj *
-make_corpse(register struct monst* mtmp, unsigned int corpseflags)
+make_corpse(struct monst *mtmp, unsigned int corpseflags)
 {
     register struct permonst *mdat = mtmp->data;
     int num;
@@ -491,6 +491,11 @@ make_corpse(register struct monst* mtmp, unsigned int corpseflags)
     unsigned corpstatflags = corpseflags;
     boolean burythem = ((corpstatflags & CORPSTAT_BURIED) != 0);
 
+    if (mtmp->female)
+        corpstatflags |= CORPSTAT_FEMALE;
+    else if (!is_neuter(mtmp->data))
+        corpstatflags |= CORPSTAT_MALE;
+
     switch (mndx) {
     case PM_GRAY_DRAGON:
     case PM_SILVER_DRAGON:
@@ -578,8 +583,8 @@ make_corpse(register struct monst* mtmp, unsigned int corpseflags)
         break;
     case PM_STONE_GOLEM:
         corpstatflags &= ~CORPSTAT_INIT;
-        obj =
-            mkcorpstat(STATUE, (struct monst *) 0, mdat, x, y, corpstatflags);
+        obj = mkcorpstat(STATUE, (struct monst *) 0, mdat, x, y,
+                         corpstatflags);
         break;
     case PM_WOOD_GOLEM:
         num = d(2, 4);
index a85f4dea82966d42bddd1fab10f0f00d3c9ad19b..cf367dd493df417fddade0f6234c50812089d143 100644 (file)
@@ -655,7 +655,7 @@ struct alt_spl {
 /* figure out what type of monster a user-supplied string is specifying;
    ingore anything past the monster name */
 int
-name_to_mon(const char *in_str, int * gender_name_var)
+name_to_mon(const char *in_str, int *gender_name_var)
 {
     return name_to_monplus(in_str, (const char **) 0, gender_name_var);
 }
@@ -691,6 +691,8 @@ name_to_monplus(
 
     if (remainder_p)
         *remainder_p = (const char *) 0;
+    if (gender_name_var)
+        *gender_name_var = 0;
 
     str = strcpy(buf, in_str);
 
@@ -740,13 +742,16 @@ name_to_monplus(
             { "master of assassin", PM_MASTER_ASSASSIN, NEUTRAL },
             /* Outdated names */
             { "invisible stalker", PM_STALKER, NEUTRAL },
-            { "high-elf", PM_ELVEN_MONARCH, NEUTRAL }, /* PM_HIGH_ELF is obsolete */
+            { "high-elf", PM_ELVEN_MONARCH, NEUTRAL }, /* PM_HIGH_ELF is
+                                                        * obsolete */
             /* other misspellings or incorrect words */
             { "wood-elf", PM_WOODLAND_ELF, NEUTRAL },
             { "wood elf", PM_WOODLAND_ELF, NEUTRAL },
             { "woodland nymph", PM_WOOD_NYMPH, NEUTRAL },
-            { "halfling", PM_HOBBIT, NEUTRAL },    /* potential guess for polyself */
-            { "genie", PM_DJINNI, NEUTRAL }, /* potential guess for ^G/#wizgenesis */
+            { "halfling", PM_HOBBIT, NEUTRAL },    /* potential guess for
+                                                    * polyself */
+            { "genie", PM_DJINNI, NEUTRAL }, /* potential guess for
+                                              * ^G/#wizgenesis */
             /* prefix used to workaround duplicate monster names for
                monsters with alternate forms */
             { "human wererat", PM_HUMAN_WERERAT, NEUTRAL },
index 23c053cc820000658ddc0867b18cf6931d38feee..88ef6d87bb57222f3aef1366ca87a6c54eecfb5f 100644 (file)
@@ -257,10 +257,14 @@ l_obj_to_table(lua_State *L)
     nhl_add_table_entry_int(L, "quan", obj->quan);
     nhl_add_table_entry_int(L, "spe", obj->spe);
 
-    if (obj->otyp == STATUE) {
-        nhl_add_table_entry_int(L, "historic", (obj->spe & STATUE_HISTORIC));
-        nhl_add_table_entry_int(L, "statue_male", (obj->spe & STATUE_MALE));
-        nhl_add_table_entry_int(L, "statue_female", (obj->spe & STATUE_FEMALE));
+    if (obj->otyp == STATUE)
+        nhl_add_table_entry_int(L, "historic",
+                                (obj->spe & CORPSTAT_HISTORIC) != 0);
+    if (obj->otyp == CORPSE || obj->otyp == STATUE) {
+        nhl_add_table_entry_int(L, "male",
+                                (obj->spe & CORPSTAT_MALE) != 0);
+        nhl_add_table_entry_int(L, "female",
+                                (obj->spe & CORPSTAT_FEMALE) != 0);
     }
 
     nhl_add_table_entry_char(L, "oclass",
index d11f5e47269bff7eabb676e928128e3b6bb69ece..be991fba32651bea321fc53f47a693a8f4abf02f 100644 (file)
@@ -606,18 +606,16 @@ xname_flags(
     case ROCK_CLASS:
         if (typ == STATUE && omndx != NON_PM) {
             char anbuf[10];
-            int mgend = (obj->spe & STATUE_FEMALE) ? FEMALE : MALE;
+            int mgend = (((obj->spe & CORPSTAT_GENDER) == CORPSTAT_FEMALE)
+                         ? FEMALE : MALE);
 
             Sprintf(buf, "%s%s of %s%s",
-                    (Role_if(PM_ARCHEOLOGIST) && (obj->spe & STATUE_HISTORIC))
-                       ? "historic "
-                       : "",
+                    (Role_if(PM_ARCHEOLOGIST)
+                     && (obj->spe & CORPSTAT_HISTORIC)) ? "historic " : "",
                     actualn,
-                    type_is_pname(&mons[omndx])
-                       ? ""
-                       : the_unique_pm(&mons[omndx])
-                          ? "the "
-                          : just_an(anbuf, pmname(&mons[omndx], mgend)),
+                    type_is_pname(&mons[omndx]) ? ""
+                      : the_unique_pm(&mons[omndx]) ? "the "
+                        : just_an(anbuf, pmname(&mons[omndx], mgend)),
                     pmname(&mons[omndx], mgend));
         } else
             Strcpy(buf, actualn);
@@ -1423,7 +1421,12 @@ corpse_xname(
         /* avoid "aligned priest"; it just exposes internal details */
         mnam = "priest";
     } else {
-        mnam = mons[omndx].pmnames[NEUTRAL];
+        int cspe = (otmp->spe & CORPSTAT_GENDER),
+            mgend = (cspe == CORPSTAT_FEMALE) ? FEMALE
+                    : (cspe == CORPSTAT_MALE) ? MALE
+                      : NEUTRAL;
+
+        mnam = pmname(&mons[omndx], mgend);
         if (the_unique_pm(&mons[omndx]) || type_is_pname(&mons[omndx])) {
             mnam = s_suffix(mnam);
             possessive = TRUE;
@@ -3264,7 +3267,7 @@ readobjnam_init(char* bp, struct _readobjnam_data* d)
         = d->looted /* wizard mode fountain/sink/throne/tree and grave */
         = d->real = d->fake = 0; /* Amulet */
     d->tvariety = RANDOM_TIN;
-    d->mgend = MALE;
+    d->mgend = NEUTRAL;
     d->mntmp = NON_PM;
     d->contents = UNDEFINED;
     d->oclass = 0;
@@ -3650,8 +3653,8 @@ readobjnam_postparse1(struct _readobjnam_data* d)
                 d->typ = TIN;
                 return 2; /*goto typfnd;*/
             } else if ((d->p = strstri(d->bp, " of ")) != 0
-                       && (d->mntmp = name_to_mon(d->p + 4,
-                                                  &d->mgend)) >= LOW_PM)
+                       && ((d->mntmp = name_to_mon(d->p + 4, &d->mgend))
+                           >= LOW_PM))
                 *d->p = 0;
         }
     }
@@ -3665,8 +3668,8 @@ readobjnam_postparse1(struct _readobjnam_data* d)
         const char *rest = 0;
 
         if (d->mntmp < LOW_PM && strlen(d->bp) > 2
-            && (d->mntmp = name_to_monplus(d->bp, &rest,
-                                           &d->mgend)) >= LOW_PM) {
+            && ((d->mntmp = name_to_monplus(d->bp, &rest, &d->mgend))
+                >= LOW_PM)) {
             char *obp = d->bp;
 
             /* 'rest' is a pointer past the matching portion; if that was
@@ -4302,11 +4305,20 @@ readobjnam(char* bp, struct obj* no_wish)
     case HEAVY_IRON_BALL:
     case IRON_CHAIN:
         break;
-    case STATUE:
-        /* otmp->cobj already done in mksobj() */
-        if (d.mgend)
-            d.otmp->spe |= STATUE_FEMALE;
+    case STATUE: /* otmp->cobj already done in mksobj() */
+    case CORPSE: {
+        struct permonst *P = (d.mntmp >= LOW_PM) ? &mons[d.mntmp] : 0;
+
+        d.otmp->spe = !P ? CORPSTAT_RANDOM
+                      /* if neuter, force neuter regardless of wish request */
+                      : is_neuter(P) ? CORPSTAT_NEUTER
+                        /* not neuter, honor wish unless it conflicts */
+                        : (d.mgend == FEMALE && !is_male(P)) ? CORPSTAT_FEMALE
+                          : (d.mgend == MALE && !is_female(P)) ? CORPSTAT_MALE
+                            /* unspecified or wish conflicts */
+                            : CORPSTAT_RANDOM;
         break;
+    };
 #ifdef MAIL_STRUCTURES
     /* scroll of mail:  0: delivered in-game via external event (or randomly
        for fake mail); 1: from bones or wishing; 2: written with marker */
@@ -4379,7 +4391,7 @@ readobjnam(char* bp, struct obj* no_wish)
             d.otmp->corpsenm = d.mntmp;
             if (Has_contents(d.otmp) && verysmall(&mons[d.mntmp]))
                 delete_contents(d.otmp); /* no spellbook */
-            d.otmp->spe |= d.ishistoric ? STATUE_HISTORIC : 0;
+            d.otmp->spe |= d.ishistoric ? CORPSTAT_HISTORIC : 0;
             break;
         case SCALE_MAIL:
             /* Dragon mail - depends on the order of objects & dragons. */
index 5f89efa39dbced453a6329a5ba7f24bc7a98d3d9..ac07aa6bcd3655f4779c2f7cce33dc84cba9ff66 100755 (executable)
@@ -2002,6 +2002,7 @@ create_monster(monster* m, struct mkroom* croom)
                 block_point(x, y);
         }
 
+        mtmp->female = m->female;
         if (m->peaceful >= 0) {
             mtmp->mpeaceful = m->peaceful;
             /* changed mpeaceful again; have to reset malign */
@@ -2020,8 +2021,6 @@ create_monster(monster* m, struct mkroom* croom)
         }
         if (m->seentraps)
             mtmp->mtrapseen = m->seentraps;
-        if (m->female)
-            mtmp->female = 1;
         if (m->cancelled)
             mtmp->mcan = 1;
         if (m->revived)
@@ -3379,20 +3378,21 @@ lspo_object(lua_State *L)
         || tmpobj.id == CORPSE || tmpobj.id == TIN
         || tmpobj.id == FIGURINE) {
         struct permonst *pm = NULL;
-        int i, lflags = 0;
+        int i;
         char *montype = get_table_str_opt(L, "montype", NULL);
 
         if (montype) {
             if (strlen(montype) == 1
                 && def_char_to_monclass(*montype) != MAXMCLASSES) {
-                pm = mkclass(def_char_to_monclass(*montype), G_NOGEN|G_IGNORE);
+                pm = mkclass(def_char_to_monclass(*montype),
+                             G_NOGEN | G_IGNORE);
             } else {
                 for (i = LOW_PM; i < NUMMONS; i++)
                     if (!strcmpi(mons[i].pmnames[NEUTRAL], montype)
                         || (mons[i].pmnames[MALE] != 0
-                               && !strcmpi(mons[i].pmnames[MALE], montype))
+                            && !strcmpi(mons[i].pmnames[MALE], montype))
                         || (mons[i].pmnames[FEMALE] != 0
-                               && !strcmpi(mons[i].pmnames[FEMALE], montype))) {
+                            && !strcmpi(mons[i].pmnames[FEMALE], montype))) {
                         pm = &mons[i];
                         break;
                     }
@@ -3403,16 +3403,20 @@ lspo_object(lua_State *L)
             else
                 nhl_error(L, "Unknown montype");
         }
-        if (tmpobj.id == STATUE) {
+        if (tmpobj.id == STATUE || tmpobj.id == CORPSE) {
+            int lflags = 0;
+
             if (get_table_boolean_opt(L, "historic", 0))
-                lflags |= STATUE_HISTORIC;
+                lflags |= CORPSTAT_HISTORIC;
             if (get_table_boolean_opt(L, "male", 0))
-                lflags |= STATUE_MALE;
+                lflags |= CORPSTAT_MALE;
             if (get_table_boolean_opt(L, "female", 0))
-                lflags |= STATUE_FEMALE;
+                lflags |= CORPSTAT_FEMALE;
             tmpobj.spe = lflags;
         } else if (tmpobj.id == EGG) {
             tmpobj.spe = get_table_boolean_opt(L, "laid_by_you", 0) ? 1 : 0;
+        } else {
+            tmpobj.spe = 0;
         }
     }
 
@@ -3719,7 +3723,8 @@ lspo_room(lua_State *L)
         tmproom.chance = get_table_int_opt(L, "chance", 100);
         tmproom.rlit = get_table_int_opt(L, "lit", -1);
         /* theme rooms default to unfilled */
-        tmproom.needfill = get_table_int_opt(L, "filled", g.in_mk_themerooms ? 0 : 1);
+        tmproom.needfill = get_table_int_opt(L, "filled",
+                                             g.in_mk_themerooms ? 0 : 1);
         tmproom.joined = get_table_boolean_opt(L, "joined", TRUE);
 
         if (!g.coder->failed_room[g.coder->n_subroom - 1]) {
@@ -4055,8 +4060,7 @@ lspo_trap(lua_State *L)
 
         get_table_xy_or_coord(L, &x, &y);
         tmptrap.type = get_table_traptype_opt(L, "type", -1);
-        tmptrap.spider_on_web
-            = get_table_boolean_opt(L, "spider_on_web", 1);
+        tmptrap.spider_on_web = get_table_boolean_opt(L, "spider_on_web", 1);
     }
 
     if (tmptrap.type == NO_TRAP)
index a536f0f39bbcdffc851c7cc72ce40d17bfaad04d..18bd505e272837bdf6dedb379b4a6f4c08636e60 100644 (file)
@@ -603,7 +603,7 @@ animate_statue(
     struct obj *item;
     coord cc;
     boolean historic = (Role_if(PM_ARCHEOLOGIST)
-                        && (statue->spe & STATUE_HISTORIC) != 0),
+                        && (statue->spe & CORPSTAT_HISTORIC) != 0),
             golem_xform = FALSE, use_saved_traits;
     const char *comes_to_life;
     char statuename[BUFSZ], tmpbuf[BUFSZ];
@@ -645,10 +645,16 @@ animate_statue(
                be NON_PM; otherwise, set form to match the statue */
             if (mon && mon->cham >= LOW_PM)
                 (void) newcham(mon, mptr, FALSE, FALSE);
-        } else
+        } else {
             mon = makemon(mptr, x, y, (cause == ANIMATE_SPELL)
                                           ? (NO_MINVENT | MM_ADJACENTOK)
                                           : NO_MINVENT);
+        }
+        /* a non-montraits() statue might specify gender */
+        if ((statue->spe & CORPSTAT_MALE) != 0)
+            mon->female = 0;
+        else if ((statue->spe & CORPSTAT_FEMALE) != 0)
+            mon->female = 1;
     }
 
     if (!mon) {
@@ -659,11 +665,6 @@ animate_statue(
         return (struct monst *) 0;
     }
 
-    /* a non-montraits() statue might specify gender */
-    if (statue->spe & STATUE_MALE)
-        mon->female = FALSE;
-    else if (statue->spe & STATUE_FEMALE)
-        mon->female = TRUE;
     /* if statue has been named, give same name to the monster */
     if (has_oname(statue) && !unique_corpstat(mon->data))
         mon = christen_monst(mon, ONAME(statue));
index f8fbc5e8992de0519cc8168417c643487f386447..c0e71db892be8f3ac55be5b38526984967123cad 100644 (file)
--- a/src/zap.c
+++ b/src/zap.c
@@ -856,6 +856,16 @@ revive(struct obj *corpse, boolean by_hero)
     if (!mtmp)
         return (struct monst *) 0;
 
+    /* if we didn't use montraits, corpse might specify mon's gender */
+    if (!has_omonst(corpse)) {
+        int cspe = (corpse->spe & CORPSTAT_GENDER);
+
+        if (cspe == CORPSTAT_MALE)
+            mtmp->female = 0;
+        else if (cspe == CORPSTAT_FEMALE)
+            mtmp->female = 1;
+    }
+
     /* hiders shouldn't already be re-hidden when they revive */
     if (mtmp->mundetected) {
         mtmp->mundetected = 0;
@@ -4896,7 +4906,8 @@ break_statue(struct obj *obj)
         obj_extract_self(item);
         place_object(item, obj->ox, obj->oy);
     }
-    if (by_you && Role_if(PM_ARCHEOLOGIST) && (obj->spe & STATUE_HISTORIC)) {
+    if (by_you && Role_if(PM_ARCHEOLOGIST)
+        && (obj->spe & CORPSTAT_HISTORIC)) {
         You_feel("guilty about damaging such a historic statue.");
         adjalign(-1);
     }