leash sanity checking

I still haven't found any explanation for the report by a hardfought
player recently that going down some stairs with a pair of leashed
pets got one into a confused state where it was flagged as leashed
but the corresponding leash was no longer in use.

This adds some new object and monster sanity checks regarding leashes,
and it changes o_unleash(obj) to clear obj->leashmon even if/when the
monster can't be found.

It also changes behavior for dipping an attached leash into a potion
of polymorph when that happens to yield another leash--now the new
one will end up being pre-attached.
This commit is contained in:
PatR
2024-04-30 13:27:10 -07:00
parent 37a7e62be5
commit 6ebc3a7291
5 changed files with 75 additions and 7 deletions

View File

@@ -702,10 +702,10 @@ o_unleash(struct obj *otmp)
for (mtmp = fmon; mtmp; mtmp = mtmp->nmon)
if (mtmp->m_id == (unsigned) otmp->leashmon) {
mtmp->mleashed = 0;
otmp->leashmon = 0;
update_inventory();
break;
}
otmp->leashmon = 0;
update_inventory();
}
/* mtmp is about to die, or become untame */

View File

@@ -2907,6 +2907,8 @@ objlist_sanity(struct obj *objlist, int wheretype, const char *mesg)
insane_object(obj, ofmt0, mesg, (struct monst *) 0);
if (obj->where == OBJ_INVENT && obj->how_lost != LOST_NONE) {
char lostbuf[40];
/* %d: bitfield is unsigned but narrow, so promotes to int */
Sprintf(lostbuf, "how_lost=%d obj in inventory!", obj->how_lost);
insane_object(obj, ofmt0, lostbuf, (struct monst *) 0);
}
@@ -2948,6 +2950,37 @@ objlist_sanity(struct obj *objlist, int wheretype, const char *mesg)
break;
}
}
if (obj->otyp == LEASH && obj->leashmon) {
char buf[BUFSZ];
struct monst *mtmp = find_mid(obj->leashmon, FM_FMON);
if (obj->where == OBJ_INVENT) {
if (!mtmp) { /* found leash with phantom mon */
Sprintf(buf, "leashmon=%u no monst,",
(unsigned) obj->leashmon);
insane_object(obj, ofmt0, buf, (struct monst *) 0);
} else if (!mtmp->mleashed) { /* found leashed mon
* not flagged as leashed */
Sprintf(buf, "leashmon=%u %s not leashed,",
(unsigned) obj->leashmon, mon_pmname(mtmp));
insane_object(obj, ofmt0, buf, (struct monst *) 0);
}
} else {
struct monst *mtmp2 = (obj->where == OBJ_MINVENT)
? obj->ocarry : (struct monst *) 0;
if (mtmp) { /* found monst leashed by non-invent leash */
Sprintf(buf, "leashmon:%u %s leashed by %s leash,",
(unsigned) obj->leashmon,
mon_pmname(mtmp), where_name(obj));
insane_object(obj, ofmt0, buf, mtmp2);
} else { /* found non-invent leash with m_id of phantom mon */
Sprintf(buf, "leashmon:%u no monst for %s leash,",
(unsigned) obj->leashmon, where_name(obj));
insane_object(obj, ofmt0, buf, mtmp2);
}
}
}
if (obj->globby)
check_glob(obj, mesg);
/* temporary flags that might have been set but which should
@@ -3165,11 +3198,13 @@ init_dummyobj(struct obj *obj, short otyp, long oquan)
/* obj->dknown = 0; */
/* suppress known except for amulets (needed for fakes & real AoY) */
obj->known = (obj->oclass == AMULET_CLASS)
? obj->known
? obj->known
/* default is "on" for types which don't use it */
: !objects[otyp].oc_uses_known;
obj->quan = oquan ? oquan : 1L;
obj->corpsenm = NON_PM; /* suppress statue and figurine details */
if (obj->otyp == LEASH)
obj->leashmon = 0; /* overloads corpsenm, avoid NON_PM */
if (obj->otyp == BOULDER)
obj->next_boulder = 0; /* overloads corpsenm, avoid NON_PM */
/* but suppressing fruit details leads to "bad fruit #0" */

View File

@@ -214,6 +214,24 @@ sanity_check_single_mon(
impossible("mimic%s concealed in inaccessible location: %s (%s)",
is_mimic ? "" : "ker", typnam, msg);
}
#endif
}
if (mtmp->mleashed) {
if (!get_mleash(mtmp))
impossible("monst %u: leashed but no leash for %s",
mtmp->m_id, mon_pmname(mtmp));
else if (!mtmp->mtame)
impossible("monst %u: leashed but not tame %s",
mtmp->m_id, mon_pmname(mtmp));
#if 0
/* after hero moves, leashed mon won't necessarily pass 'm_next2u()'
test; 90 is farthest observed distance with expert jumping spell
when very slow mon is already several steps away and hero jumps in
opposite direction (if hero teleports, leashed mon moves adjacent
immediately; knockback has shorter range than magical jumping) */
else if (distu(mtmp->mx, mtmp->my) > 90) /*if (!m_next2u(mtmp))*/
impossible("monst %u: leashed but not next to you (%d)",
mtmp->m_id, distu(mtmp->mx, mtmp->my));
#endif
}
}

View File

@@ -1407,11 +1407,16 @@ doname_base(
if (obj->otyp == LEASH && obj->leashmon != 0) {
struct monst *mlsh = find_mid(obj->leashmon, FM_FMON);
if (!mlsh) {
impossible("leashed monster not on this level");
obj->leashmon = 0;
} else {
if (mlsh && !DEADMONSTER(mlsh)) {
ConcatF1(bp, 0, " (attached to %s)", noit_mon_nam(mlsh));
} else {
if (mlsh) /*&& DEADMONSTER(mlsh)*/
impossible("leashed %s #%u is dead",
mon_pmname(mlsh), (unsigned) obj->leashmon);
else
impossible("leashed monster #%u not found",
(unsigned) obj->leashmon);
obj->leashmon = 0;
}
break;
}

View File

@@ -1769,6 +1769,16 @@ poly_obj(struct obj *obj, int id)
otmp->cursed = FALSE;
}
}
if (obj->otyp == LEASH && obj->leashmon != 0) {
if (otmp->otyp == LEASH) {
otmp->leashmon = obj->leashmon;
/* clear m_id before delobj(), to avoid o_unleash() by obfree() */
obj->leashmon = 0;
} else {
/* obfree() would do this if we didn't do it here */
o_unleash(obj);
}
}
/* no box contents --KAA */
if (Has_contents(otmp))