From 6ebc3a7291ad8ceb3a363e8a927837a81c5c0a7f Mon Sep 17 00:00:00 2001 From: PatR Date: Tue, 30 Apr 2024 13:27:10 -0700 Subject: [PATCH] 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. --- src/apply.c | 4 ++-- src/mkobj.c | 37 ++++++++++++++++++++++++++++++++++++- src/mon.c | 18 ++++++++++++++++++ src/objnam.c | 13 +++++++++---- src/zap.c | 10 ++++++++++ 5 files changed, 75 insertions(+), 7 deletions(-) diff --git a/src/apply.c b/src/apply.c index 63fcdc964..12e8567ca 100644 --- a/src/apply.c +++ b/src/apply.c @@ -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 */ diff --git a/src/mkobj.c b/src/mkobj.c index a78e0f05f..6e2954ade 100644 --- a/src/mkobj.c +++ b/src/mkobj.c @@ -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" */ diff --git a/src/mon.c b/src/mon.c index d1cdfdcd1..993cb32a6 100644 --- a/src/mon.c +++ b/src/mon.c @@ -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 } } diff --git a/src/objnam.c b/src/objnam.c index eeaefa4ef..7713a5dad 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -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; } diff --git a/src/zap.c b/src/zap.c index 2bec3f75c..127395788 100644 --- a/src/zap.c +++ b/src/zap.c @@ -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))