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
This commit is contained in:
PatR
2021-06-08 03:43:46 -07:00
parent 14b2330fd6
commit 04a8ddcce1
12 changed files with 121 additions and 66 deletions

View 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

View 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 */

View 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 */

View 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;

View 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

View File

@@ -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);

View 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 },

View 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",

View 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. */

View File

@@ -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)

View 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));

View File

@@ -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);
}