From: PatR Date: Tue, 8 Jun 2021 10:43:46 +0000 (-0700) Subject: fix github issue #531 - genderless corpses X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=04a8ddcce1f2c679706a0e9413f742677dbb0f68;p=nethack fix github issue #531 - genderless corpses 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 --- diff --git a/doc/fixes37.0 b/doc/fixes37.0 index fb02de618..315847881 100644 --- a/doc/fixes37.0 +++ b/doc/fixes37.0 @@ -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 diff --git a/include/hack.h b/include/hack.h index 42b1282e2..17aaa1243 100644 --- a/include/hack.h +++ b/include/hack.h @@ -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 */ diff --git a/include/obj.h b/include/obj.h index bc00a7388..40e5db202 100644 --- a/include/obj.h +++ b/include/obj.h @@ -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 */ diff --git a/src/display.c b/src/display.c index 3a1a8d111..fa4a59e6e 100644 --- a/src/display.c +++ b/src/display.c @@ -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; diff --git a/src/mkobj.c b/src/mkobj.c index 4873c85e4..bd4e1d973 100644 --- a/src/mkobj.c +++ b/src/mkobj.c @@ -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 diff --git a/src/mon.c b/src/mon.c index 0578921f9..9cd1c28f3 100644 --- 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); diff --git a/src/mondata.c b/src/mondata.c index a85f4dea8..cf367dd49 100644 --- a/src/mondata.c +++ b/src/mondata.c @@ -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 }, diff --git a/src/nhlobj.c b/src/nhlobj.c index 23c053cc8..88ef6d87b 100644 --- a/src/nhlobj.c +++ b/src/nhlobj.c @@ -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", diff --git a/src/objnam.c b/src/objnam.c index d11f5e472..be991fba3 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -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. */ diff --git a/src/sp_lev.c b/src/sp_lev.c index 5f89efa39..ac07aa6bc 100755 --- a/src/sp_lev.c +++ b/src/sp_lev.c @@ -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) diff --git a/src/trap.c b/src/trap.c index a536f0f39..18bd505e2 100644 --- a/src/trap.c +++ b/src/trap.c @@ -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)); diff --git a/src/zap.c b/src/zap.c index f8fbc5e89..c0e71db89 100644 --- 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); }