add two unused fields for hardfought save compatability

There are two hardfought code additions that render save and bones files incompatible
with the upstream NetHack-3.7, and that makes testing with hardfought
save and bones files more challenging than it needs to be, when
investigating and troubleshooting bug reports.

Add some unused fields to advance towards achieving save file parity with
hardfought, which is a significant source of play-testing for NetHack-3.7.

1) the elbereth field addition to u_conduct

This adds an unused placeholder field named 'hf_reserved1', at the appropriate
place in u_conduct to achieve struct field parity with the one in use on
hardfought.

2) hardfought adds a field to struct monst:
    char former_rank[25]; /* for bones' ghost rank in their former life */

Instead of adding that to every monst, this adds a new mextra struct
named 'former', which currently contains the equivalent 25-character
field called 'rank' which can hold the content that was in the
former_rank[25] field. That way, the field will only be added when it
is needed.

A pull request https://github.com/k21971/NetHack37/pull/2 has been
done on hardfought to do it the same way (untested there as of yet).

Even though NetHack-3.7 does not utilize that information presently,
this will be a further step toward allowing hardfought-generated save
and bones files to be used for troubleshooting, without modification,
on a similar architecture running stock NetHack-3.7 code.

That savefile parity won't be achieved until the after the
hardfought pull-request mentioned above (or equivalent) is merged.

As this change will not be compatible with existing save and bones
files, it will be accompanied with an EDITLEVEL increment.
This commit is contained in:
nhmall
2025-02-02 09:00:05 -05:00
parent d74624abbd
commit d785f7a649
11 changed files with 91 additions and 4 deletions

View File

@@ -257,6 +257,7 @@ extern char *do_statusline2(void);
extern void bot(void);
extern void timebot(void);
extern int xlev_to_rank(int);
extern const char *rank(void);
extern int rank_to_xlev(int);
extern const char *rank_of(int, short, boolean);
extern int title_to_mon(const char *, int *, int *);
@@ -1442,6 +1443,8 @@ extern void mkmonmoney(struct monst *, long) NONNULLARG1;
extern int bagotricks(struct obj *, boolean, int *);
extern boolean propagate(int, boolean, boolean);
extern void summon_furies(int);
extern void newformer(struct monst *) NONNULLARG1;
extern void free_former(struct monst *) NONNULLARG1;
/* ### mcastu.c ### */

View File

@@ -182,6 +182,10 @@ struct edog {
Bitfield(killed_by_u, 1); /* you attempted to kill him */
};
struct former_incarnation {
char rank[25]; /* for bones' ghost rank in their former life */
};
/***
** mextra.h -- collection of all monster extensions
*/
@@ -192,6 +196,7 @@ struct mextra {
struct eshk *eshk;
struct emin *emin;
struct edog *edog;
struct former_incarnation *former;
int mcorpsenm; /* obj->corpsenm for mimic posing as statue or corpse,
* obj->spe (fruit index) for one posing as a slime mold,
* or an alignment mask for one posing as an altar */
@@ -203,6 +208,7 @@ struct mextra {
#define ESHK(mon) ((mon)->mextra->eshk)
#define EMIN(mon) ((mon)->mextra->emin)
#define EDOG(mon) ((mon)->mextra->edog)
#define FORMER(mon) ((mon)->mextra->former)
#define MCORPSENM(mon) ((mon)->mextra->mcorpsenm)
#define has_mgivenname(mon) ((mon)->mextra && MGIVENNAME(mon))
@@ -211,6 +217,7 @@ struct mextra {
#define has_eshk(mon) ((mon)->mextra && ESHK(mon))
#define has_emin(mon) ((mon)->mextra && EMIN(mon))
#define has_edog(mon) ((mon)->mextra && EDOG(mon))
#define has_former(mon) ((mon)->mextra && FORMER(mon))
#define has_mcorpsenm(mon) ((mon)->mextra && MCORPSENM(mon) != NON_PM)
#endif /* MEXTRA_H */

View File

@@ -260,6 +260,14 @@
|| objects[(obj)->otyp].oc_material == VEGGY \
|| ((obj)->otyp == CORPSE && (obj)->corpsenm == PM_LICHEN))))
/* is_bones_monster() is currently only used by hardfought livelog,
* but is included as part of savefile field compatibility adjustments.
*/
#define is_bones_monster(ptr) \
((ptr) == &mons[PM_GHOST] || (ptr) == &mons[PM_GHOUL] \
|| (ptr) == &mons[PM_VAMPIRE] || (ptr) == &mons[PM_WRAITH] \
|| (ptr) == &mons[PM_GREEN_SLIME] || (ptr)->mlet == S_MUMMY)
#ifdef PMNAME_MACROS
#define pmname(ptr,g) ((((g) == MALE || (g) == FEMALE) && (ptr)->pmnames[g]) \
? (ptr)->pmnames[g] : (ptr)->pmnames[NEUTRAL])

View File

@@ -153,6 +153,7 @@ struct u_conduct { /* number of times... */
long polyselfs; /* transformed yourself */
long wishes; /* used a wish */
long wisharti; /* wished for an artifact */
long hf_reserved1; /* hf uses for elbereth;for hf savefile compatiblity */
long sokocheat; /* violated special 'rules' in Sokoban */
long pets; /* obtained a pet */
/* genocides already listed at end of game */

View File

@@ -506,6 +506,13 @@ savebones(int how, time_t when, struct obj *corpse)
mtmp->mhp = mtmp->mhpmax = u.uhpmax;
mtmp->female = flags.female;
mtmp->msleeping = 1;
if (!has_former(mtmp))
newformer(mtmp);
if (has_former(mtmp)) {
Snprintf(FORMER(mtmp)->rank,
sizeof FORMER(mtmp)->rank,
"%s", rank());
}
}
for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) {
set_ghostly_objlist(mtmp->minvent);

View File

@@ -16,7 +16,6 @@ const char *const enc_stat[] = {
"Strained", "Overtaxed", "Overloaded"
};
staticfn const char *rank(void);
staticfn void bot_via_windowport(void);
staticfn void stat_update_time(void);
staticfn char *get_strength_str(void);
@@ -361,7 +360,7 @@ rank_of(int lev, short monnum, boolean female)
return "Player";
}
staticfn const char *
const char *
rank(void)
{
return rank_of(u.ulevel, Role_switch, flags.female);

View File

@@ -1070,6 +1070,28 @@ newmextra(void)
return mextra;
}
void
newformer(struct monst *mtmp)
{
if (!mtmp->mextra)
mtmp->mextra = newmextra();
if (!FORMER(mtmp)) {
FORMER(mtmp) = (struct former_incarnation *) alloc(
sizeof(struct former_incarnation *));
(void) memset((genericptr_t) FORMER(mtmp), 0,
sizeof(struct former_incarnation *));
}
}
void
free_former(struct monst *mtmp)
{
if (mtmp->mextra && FORMER(mtmp)) {
free((genericptr_t) FORMER(mtmp));
FORMER(mtmp) = (struct former_incarnation *) 0;
}
}
staticfn boolean
makemon_rnd_goodpos(
struct monst *mon,

View File

@@ -2567,6 +2567,12 @@ copy_mextra(struct monst *mtmp2, struct monst *mtmp1)
assert(has_edog(mtmp2));
*EDOG(mtmp2) = *EDOG(mtmp1);
}
if (FORMER(mtmp1)) {
if (!FORMER(mtmp2))
newformer(mtmp2);
assert(has_former(mtmp2));
*FORMER(mtmp2) = *FORMER(mtmp1);
}
if (has_mcorpsenm(mtmp1))
MCORPSENM(mtmp2) = MCORPSENM(mtmp1);
}
@@ -2589,6 +2595,8 @@ dealloc_mextra(struct monst *m)
free((genericptr_t) x->emin), x->emin = 0;
if (x->edog)
free((genericptr_t) x->edog), x->edog = 0;
if (x->former)
free((genericptr_t) x->former), x->former = 0;
x->mcorpsenm = NON_PM; /* no allocation to release */
free((genericptr_t) x);
@@ -2991,8 +2999,6 @@ logdeadmon(struct monst *mtmp, int mndx)
}
}
#undef livelog_mon_nam
/* monster 'mtmp' has died; maybe life-save, otherwise unshapeshift and
update vanquished stats and update map */
void
@@ -3635,10 +3641,26 @@ xkilled(
/* malign was already adjusted for u.ualign.type and randomization */
adjalign(mtmp->malign);
#if 0 /* HARDFOUGHT-only at present */
#ifdef LIVELOG
if (is_bones_monster(mtmp->data)
&& has_former(mtmp) && *FORMER(mtmp)->rank
&& strlen(FORMER(mtmp)->rank) > 0) {
if (mtmp->data == &mons[PM_GHOST])
livelog_printf(LL_UMONST, "destroyed %s, the former %s",
livelog_mon_nam(mtmp), FORMER(mtmp)->rank);
else
livelog_printf(LL_UMONST, "destroyed %s, and former %s",
livelog_mon_nam(mtmp), FORMER(mtmp)->rank);
}
#endif /* LIVELOG */
#endif
return;
}
#undef LEVEL_SPECIFIC_NOCORPSE
#undef livelog_mon_nam
/* changes the monster into a stone monster of the same type
this should only be called when poly_when_stoned() is true */

View File

@@ -370,6 +370,15 @@ restmon(NHFILE *nhfp, struct monst *mtmp)
EDOG(mtmp)->apport = 1;
}
}
/* former - info about former self, primarily for bones files */
if (nhfp->structlevel)
Mread(nhfp->fd, &buflen, sizeof buflen);
if (buflen > 0) {
newformer(mtmp);
if (nhfp->structlevel)
Mread(nhfp->fd, FORMER(mtmp),
sizeof (struct former_incarnation));
}
/* mcorpsenm - obj->corpsenm for mimic posing as corpse or
statue (inline int rather than pointer to something) */
if (nhfp->structlevel)

View File

@@ -943,6 +943,13 @@ savemon(NHFILE *nhfp, struct monst *mtmp)
if (nhfp->structlevel)
bwrite(nhfp->fd, (genericptr_t) EDOG(mtmp), buflen);
}
buflen = FORMER(mtmp) ? (int) sizeof (struct former_incarnation) : 0;
if (nhfp->structlevel)
bwrite(nhfp->fd, (genericptr_t) &buflen, sizeof (int));
if (buflen > 0) {
if (nhfp->structlevel)
bwrite(nhfp->fd, (genericptr_t) FORMER(mtmp), buflen);
}
/* mcorpsenm is inline int rather than pointer to something,
so doesn't need to be preceded by a length field */
if (nhfp->structlevel)

View File

@@ -1246,6 +1246,8 @@ size_monst(struct monst *mtmp, boolean incl_wsegs)
sz += (int) sizeof (struct emin);
if (EDOG(mtmp))
sz += (int) sizeof (struct edog);
if (FORMER(mtmp))
sz += (int) sizeof (struct former_incarnation);
/* mextra->mcorpsenm doesn't point to more memory */
}
return sz;