From 8c29b20010654a7320a4bab0cd135efe46ad5a72 Mon Sep 17 00:00:00 2001 From: Alex Smith Date: Tue, 25 Nov 2025 22:42:38 +0000 Subject: [PATCH] Accurately track which items have been discovered, even if not #named This fixes a couple of bugs: a long-standing bug in which writing a scroll by label could fail even if you've already seen a scroll with that label (due to the game not tracking whether or not you've seen a scroll if it doesn't have a name); and a somewhat newer bug in which spellbooks auto-identified by Wizard knowledge were marked as having been encountered (rather than as known but not encountered). Breaks save file compatibility, but not bones files. --- doc/fixes3-7-0.txt | 3 +++ include/extern.h | 3 ++- include/hack.h | 2 +- include/obj.h | 4 +++- include/objclass.h | 4 ++-- include/patchlevel.h | 2 +- src/apply.c | 2 +- src/artifact.c | 8 +++---- src/detect.c | 20 ++++++++--------- src/display.c | 4 ++-- src/do_name.c | 2 +- src/do_wear.c | 4 ++-- src/eat.c | 18 ++++++++++----- src/end.c | 17 ++++++++------ src/files.c | 2 +- src/fountain.c | 3 ++- src/invent.c | 9 ++++---- src/mhitu.c | 2 +- src/mkobj.c | 3 +++ src/mthrowu.c | 4 ++-- src/muse.c | 12 +++++----- src/nhlobj.c | 2 +- src/o_init.c | 53 ++++++++++++++++++++++++++------------------ src/objnam.c | 7 ++++-- src/pager.c | 4 ++-- src/pickup.c | 2 +- src/polyself.c | 4 ++-- src/potion.c | 10 ++++----- src/pray.c | 11 ++++----- src/quest.c | 2 +- src/shk.c | 2 +- src/sit.c | 3 ++- src/spell.c | 7 +++--- src/trap.c | 8 +++---- src/u_init.c | 9 ++++---- src/uhitm.c | 2 +- src/write.c | 13 ++++------- src/zap.c | 10 ++++----- 38 files changed, 155 insertions(+), 122 deletions(-) diff --git a/doc/fixes3-7-0.txt b/doc/fixes3-7-0.txt index 76b5eac0d..4dd9c189a 100644 --- a/doc/fixes3-7-0.txt +++ b/doc/fixes3-7-0.txt @@ -1542,6 +1542,9 @@ on arboreal levels (Ranger quest) where STONE terrain is treated as TREE, an an item at an ordinary tree location, whether the level is arboreal or not, would be described as itself with no mention of the tree some types of shopkeeper now start with a scroll of charging +objects are now accurately tracked as discovered even if not type-named nor + formally identified (fixing some bugs in scroll writing, and making + the discoveries list more accurate) Fixes to 3.7.0-x General Problems Exposed Via git Repository diff --git a/include/extern.h b/include/extern.h index f86732ab4..49b83f784 100644 --- a/include/extern.h +++ b/include/extern.h @@ -2155,7 +2155,8 @@ extern boolean objdescr_is(struct obj *, const char *) NONNULLARG2; extern void oinit(void); extern void savenames(NHFILE *) NONNULLARG1; extern void restnames(NHFILE *) NONNULLARG1; -extern void discover_object(int, boolean, boolean); +extern void observe_object(struct obj *) NONNULLARG1; +extern void discover_object(int, boolean, boolean, boolean); extern void undiscover_object(int); extern boolean interesting_to_discover(int); extern int choose_disco_sort(int); diff --git a/include/hack.h b/include/hack.h index 361b95cd3..e91df8365 100644 --- a/include/hack.h +++ b/include/hack.h @@ -1527,7 +1527,7 @@ typedef uint32_t mmflags_nht; /* makemon MM_ flags */ (objects[(obj)->otyp].a_ac + (obj)->spe \ - min((int) greatest_erosion(obj), objects[(obj)->otyp].a_ac)) -#define makeknown(x) discover_object((x), TRUE, TRUE) +#define makeknown(x) discover_object((x), TRUE, TRUE, TRUE) #define distu(xx, yy) dist2((coordxy) (xx), (coordxy) (yy), u.ux, u.uy) #define mdistu(mon) distu((mon)->mx, (mon)->my) #define onlineu(xx, yy) online2((coordxy)(xx), (coordxy)(yy), u.ux, u.uy) diff --git a/include/obj.h b/include/obj.h index e23c5f457..431094918 100644 --- a/include/obj.h +++ b/include/obj.h @@ -107,7 +107,9 @@ struct obj { * or enchantment); many items have this preset if * they lack anything interesting to discover */ Bitfield(dknown, 1); /* description known (item seen "up close"); - * some types of items always have dknown set */ + * some types of items always have dknown set; + * use observe_object to set to TRUE so that the + * discoveries list is still correct */ Bitfield(bknown, 1); /* BUC (blessed/uncursed/cursed) known */ Bitfield(rknown, 1); /* rustproofing status known */ Bitfield(cknown, 1); /* for containers (including statues): the contents diff --git a/include/objclass.h b/include/objclass.h index 3faf84a16..b3da30b49 100644 --- a/include/objclass.h +++ b/include/objclass.h @@ -54,8 +54,8 @@ struct objclass { * otherwise, obj->dknown and obj->bknown * tell all, and obj->known should always * be set for proper merging behavior. */ - Bitfield(oc_pre_discovered, 1); /* already known at start of game; flagged - * as such when discoveries are listed */ + Bitfield(oc_encountered, 1); /* hero has observed such an item at least + once (perhaps without naming it) */ Bitfield(oc_magic, 1); /* inherently magical object */ Bitfield(oc_charged, 1); /* may have +n or (n) charges */ Bitfield(oc_unique, 1); /* special one-of-a-kind object */ diff --git a/include/patchlevel.h b/include/patchlevel.h index ff02f26bc..42485fef6 100644 --- a/include/patchlevel.h +++ b/include/patchlevel.h @@ -17,7 +17,7 @@ * Incrementing EDITLEVEL can be used to force invalidation of old bones * and save files. */ -#define EDITLEVEL 130 +#define EDITLEVEL 131 /* * Development status possibilities. diff --git a/src/apply.c b/src/apply.c index 4e3788dd4..e8f1a339b 100644 --- a/src/apply.c +++ b/src/apply.c @@ -2683,7 +2683,7 @@ use_stone(struct obj *tstone) /* in case it was acquired while blinded */ if (!Blind) - tstone->dknown = 1; + observe_object(tstone); known = (tstone->otyp == TOUCHSTONE && tstone->dknown && objects[TOUCHSTONE].oc_name_known); Sprintf(stonebuf, "rub on the stone%s", plur(tstone->quan)); diff --git a/src/artifact.c b/src/artifact.c index e4f4c89bf..11db684f0 100644 --- a/src/artifact.c +++ b/src/artifact.c @@ -1570,7 +1570,7 @@ artifact_hit( } *dmgptr = 2 * mdef->mhp + FATAL_DAMAGE_MODIFIER; pline("%s cuts %s in half!", wepdesc, mon_nam(mdef)); - otmp->dknown = TRUE; + observe_object(otmp); return TRUE; } else { if (bigmonst(gy.youmonst.data)) { @@ -1587,7 +1587,7 @@ artifact_hit( */ *dmgptr = 2 * (Upolyd ? u.mh : u.uhp) + FATAL_DAMAGE_MODIFIER; pline("%s cuts you in half!", wepdesc); - otmp->dknown = TRUE; + observe_object(otmp); return TRUE; } } else if (is_art(otmp, ART_VORPAL_BLADE) @@ -1617,7 +1617,7 @@ artifact_hit( mon_nam(mdef)); if (Hallucination && !flags.female) pline("Good job Henry, but that wasn't Anne."); - otmp->dknown = TRUE; + observe_object(otmp); return TRUE; } else { if (!has_head(gy.youmonst.data)) { @@ -1634,7 +1634,7 @@ artifact_hit( } *dmgptr = 2 * (Upolyd ? u.mh : u.uhp) + FATAL_DAMAGE_MODIFIER; pline(ROLL_FROM(behead_msg), wepdesc, "you"); - otmp->dknown = TRUE; + observe_object(otmp); /* Should amulets fall off? */ return TRUE; } diff --git a/src/detect.c b/src/detect.c index a74cdfea4..bf536e127 100644 --- a/src/detect.c +++ b/src/detect.c @@ -26,7 +26,7 @@ staticfn void reconstrain_map(void); staticfn void map_redisplay(void); staticfn void browse_map(unsigned, const char *); staticfn void map_monst(struct monst *, boolean); -staticfn void do_dknown_of(struct obj *); +staticfn void observe_recursively(struct obj *); staticfn boolean check_map_spot(coordxy, coordxy, char, unsigned); staticfn boolean clear_stale_map(char, unsigned); staticfn void sense_trap(struct trap *, coordxy, coordxy, int); @@ -246,14 +246,14 @@ o_material(struct obj *obj, unsigned material) } staticfn void -do_dknown_of(struct obj *obj) +observe_recursively(struct obj *obj) { struct obj *otmp; - obj->dknown = 1; + observe_object(obj); if (Has_contents(obj)) { for (otmp = obj->cobj; otmp; otmp = otmp->nobj) - do_dknown_of(otmp); + observe_recursively(otmp); } } @@ -638,7 +638,7 @@ object_detect(struct obj *detector, /* object doing the detecting */ if (do_dknown) for (obj = gi.invent; obj; obj = obj->nobj) - do_dknown_of(obj); + observe_recursively(obj); for (obj = fobj; obj; obj = obj->nobj) { if ((!class && !boulder) || o_in(obj, class) || o_in(obj, boulder)) { @@ -648,7 +648,7 @@ object_detect(struct obj *detector, /* object doing the detecting */ ct++; } if (do_dknown) - do_dknown_of(obj); + observe_recursively(obj); } for (obj = svl.level.buriedobjlist; obj; obj = obj->nobj) { @@ -659,7 +659,7 @@ object_detect(struct obj *detector, /* object doing the detecting */ ct++; } if (do_dknown) - do_dknown_of(obj); + observe_recursively(obj); } if (u.usteed) @@ -673,7 +673,7 @@ object_detect(struct obj *detector, /* object doing the detecting */ || o_in(obj, boulder)) ct++; if (do_dknown) - do_dknown_of(obj); + observe_recursively(obj); } if ((is_cursed && M_AP_TYPE(mtmp) == M_AP_OBJECT && (!class || class == objects[mtmp->mappearance].oc_class)) @@ -932,7 +932,7 @@ detect_obj_traps( } if (Is_box(otmp) && otmp->otrapped) { otmp->tknown = 1; - otmp->dknown = 1; + observe_object(otmp); result |= u_at(x, y) ? OTRAP_HERE : OTRAP_THERE; if (ft) { flash_glyph_at(x, y, trapglyph, FOUND_FLASH_COUNT); @@ -1508,7 +1508,7 @@ do_vicinity_map( unlike object detection, we don't notice buried items */ otmp = svl.level.objects[zx][zy]; if (extended) - otmp->dknown = 1; + observe_object(otmp); map_object(otmp, TRUE); newglyph = glyph_at(zx, zy); /* if otmp is underwater, we'll need to redisplay the water */ diff --git a/src/display.c b/src/display.c index ba1841cca..e3f8225f8 100644 --- a/src/display.c +++ b/src/display.c @@ -346,7 +346,7 @@ map_object(struct obj *obj, int show) neardist = (r * r) * 2 - r; /* same as r*r + r*(r-1) */ if (distu(x, y) <= neardist) { - obj->dknown = 1; + observe_object(obj); glyph = obj_to_glyph(obj, newsym_rn2); } } @@ -1585,7 +1585,7 @@ see_nearby_objects(void) if (!cansee(ix, iy) || distu(ix, iy) > neardist) continue; - obj->dknown = 1; /* near enough to see it */ + observe_object(obj); /* operate on remembered glyph rather than current one */ glyph = levl[ix][iy].glyph; if (glyph_is_generic_object(glyph)) diff --git a/src/do_name.c b/src/do_name.c index 9a2d2f54c..946fbdd4d 100644 --- a/src/do_name.c +++ b/src/do_name.c @@ -669,7 +669,7 @@ docall(struct obj *obj) undiscover_object(obj->otyp); } else { *uname_p = dupstr(buf); - discover_object(obj->otyp, FALSE, TRUE); /* possibly add to disco[] */ + discover_object(obj->otyp, FALSE, TRUE, TRUE); /* possibly add to disco[] */ } } diff --git a/src/do_wear.c b/src/do_wear.c index 2ee5b0785..bbbf5fbfd 100644 --- a/src/do_wear.c +++ b/src/do_wear.c @@ -1196,12 +1196,12 @@ learnring(struct obj *ring, boolean observed) mark this ring as having been seen (no need for makeknown); otherwise if we have seen this ring, discover its type */ if (objects[ringtype].oc_name_known) - ring->dknown = 1; + observe_object(ring); else if (ring->dknown) makeknown(ringtype); #if 0 /* see learnwand() */ else - ring->eknown = 1; + observe_object(ring); #endif } diff --git a/src/eat.c b/src/eat.c index 3074b9e10..5060b7b36 100644 --- a/src/eat.c +++ b/src/eat.c @@ -1547,7 +1547,8 @@ consume_tin(const char *mesg) mnum = tin->corpsenm; if (mnum == NON_PM) { pline("It turns out to be empty."); - tin->dknown = tin->known = 1; + observe_object(tin); + tin->known = 1; tin = costly_tin(COST_OPEN); use_up_tin(tin); if (always_eat) @@ -1579,8 +1580,10 @@ consume_tin(const char *mesg) if (y_n("Eat it?") == 'n') { if (flags.verbose) You("discard the open tin."); - if (!Hallucination) - tin->dknown = tin->known = 1; + if (!Hallucination) { + observe_object(tin); + tin->known = 1; + } tin = costly_tin(COST_OPEN); use_up_tin(tin); return; @@ -1594,7 +1597,8 @@ consume_tin(const char *mesg) eating_conducts(&mons[mnum]); - tin->dknown = tin->known = 1; + observe_object(tin); + tin->known = 1; /* charge for one at pre-eating cost */ tin = svc.context.tin.tin = costly_tin(COST_OPEN); @@ -1641,7 +1645,8 @@ consume_tin(const char *mesg) Blind ? "" : " ", Blind ? "" : hcolor(NH_GREEN)); } else { pline("It contains spinach."); - tin->dknown = tin->known = 1; + observe_object(tin); + tin->known = 1; } if (!always_eat && y_n("Eat it?") == 'n') { @@ -2265,7 +2270,8 @@ eataccessory(struct obj *otmp) if (u.uhp <= 0) return; /* died from sink fall */ } - otmp->known = otmp->dknown = 1; /* by taste */ + observe_object(otmp); + otmp->known = 1; /* by taste */ if (!rn2(otmp->oclass == RING_CLASS ? 3 : 5)) { switch (otmp->otyp) { default: diff --git a/src/end.c b/src/end.c index ba2ea4f19..30ba0e91d 100644 --- a/src/end.c +++ b/src/end.c @@ -921,7 +921,8 @@ artifact_score( if (counting) { u.urexp = nowrap_add(u.urexp, points); } else { - discover_object(otmp->otyp, TRUE, FALSE); + discover_object(otmp->otyp, TRUE, TRUE, FALSE); + /* not observe_object; dead characters don't observe */ otmp->known = otmp->dknown = otmp->bknown = otmp->rknown = 1; /* assumes artifacts don't have quan > 1 */ Sprintf(pbuf, "%s%s (worth %ld %s and %ld points)", @@ -1254,7 +1255,8 @@ really_done(int how) */ for (obj = gi.invent; obj; obj = nextobj) { nextobj = obj->nobj; - discover_object(obj->otyp, TRUE, FALSE); + discover_object(obj->otyp, TRUE, TRUE, FALSE); + /* observe_object not necessary after discover_object */ obj->known = obj->bknown = obj->dknown = obj->rknown = 1; set_cknown_lknown(obj); /* set flags when applicable */ /* we resolve Schroedinger's cat now in case of both @@ -1496,9 +1498,10 @@ really_done(int how) if (objects[typ].oc_class != GEM_CLASS || typ <= LAST_REAL_GEM) { otmp = mksobj(typ, FALSE, FALSE); - discover_object(otmp->otyp, TRUE, FALSE); - otmp->known = 1; /* for fake amulets */ + discover_object(otmp->otyp, TRUE, TRUE, FALSE); otmp->dknown = 1; /* seen it (blindness fix) */ + /* observe_object not necessary after discover_object */ + otmp->known = 1; /* for fake amulets */ if (has_oname(otmp)) free_oname(otmp); otmp->quan = count; @@ -1634,9 +1637,9 @@ container_contents( (boolean (*)(OBJ_P)) 0); for (srtc = sortedcobj; (obj = srtc->obj) != 0; ++srtc) { if (identified) { - discover_object(obj->otyp, TRUE, FALSE); - obj->known = obj->bknown = obj->dknown - = obj->rknown = 1; + discover_object(obj->otyp, TRUE, TRUE, FALSE); + obj->dknown = 1; /* observe_object unnecessary */ + obj->known = obj->bknown = obj->rknown = 1; if (Is_container(obj) || obj->otyp == STATUE) obj->cknown = obj->lknown = 1; } diff --git a/src/files.c b/src/files.c index c984c673e..3215449e7 100644 --- a/src/files.c +++ b/src/files.c @@ -2515,7 +2515,7 @@ wizkit_addinv(struct obj *obj) return; /* subset of starting inventory pre-ID */ - obj->dknown = 1; + observe_object(obj); if (Role_if(PM_CLERIC)) obj->bknown = 1; /* ok to bypass set_bknown() */ /* same criteria as lift_object()'s check for available inventory slot */ diff --git a/src/fountain.c b/src/fountain.c index 979bb0518..78925b04c 100644 --- a/src/fountain.c +++ b/src/fountain.c @@ -641,7 +641,8 @@ drinksink(void) otmp->cursed = otmp->blessed = 0; pline("Some %s liquid flows from the faucet.", Blind ? "odd" : hcolor(OBJ_DESCR(objects[otmp->otyp]))); - otmp->dknown = !(Blind || Hallucination); + if(!(Blind || Hallucination)) + observe_object(otmp); otmp->quan++; /* Avoid panic upon useup() */ otmp->fromsink = 1; /* kludge for docall() */ (void) dopotion(otmp); diff --git a/src/invent.c b/src/invent.c index 869c97c3e..abb7d7c39 100644 --- a/src/invent.c +++ b/src/invent.c @@ -178,7 +178,7 @@ loot_classify(Loot *sort_item, struct obj *obj) * will put lower valued ones before higher valued ones. */ if (!Blind) - obj->dknown = 1; /* xname(obj) does this; we want it sooner */ + observe_object(obj); /* xname(obj) does this; we want it sooner */ seen = obj->dknown ? TRUE : FALSE, /* class order */ classorder = flags.sortpack ? flags.inv_order : def_srt_order; @@ -1196,7 +1196,7 @@ hold_another_object( char buf[BUFSZ]; if (!Blind) - obj->dknown = 1; /* maximize mergeability */ + observe_object(obj); /* maximize mergeability */ if (obj->oartifact) { /* place_object may change these */ boolean crysknife = (obj->otyp == CRYSKNIFE); @@ -2546,7 +2546,8 @@ fully_identify_obj(struct obj *otmp) makeknown(otmp->otyp); if (otmp->oartifact) discover_artifact((xint16) otmp->oartifact); - otmp->known = otmp->dknown = otmp->bknown = otmp->rknown = 1; + observe_object(otmp); + otmp->known = otmp->bknown = otmp->rknown = 1; set_cknown_lknown(otmp); /* set otmp->{cknown,lknown} if applicable */ if (otmp->otyp == EGG && otmp->corpsenm != NON_PM) learn_egg_type(otmp->corpsenm); @@ -6123,7 +6124,7 @@ display_binventory(coordxy x, coordxy y, boolean as_if_seen) for (n = 0, obj = svl.level.buriedobjlist; obj; obj = obj->nobj) if (obj->ox == x && obj->oy == y) { if (as_if_seen) - obj->dknown = 1; + observe_object(obj); n++; } diff --git a/src/mhitu.c b/src/mhitu.c index 21622c012..8599136ed 100644 --- a/src/mhitu.c +++ b/src/mhitu.c @@ -2128,7 +2128,7 @@ doseduce(struct monst *mon) /* have her call your gloves by their correct name, possibly revealing them to you */ if (yourgloves) - yourgloves->dknown = 1; + observe_object(yourgloves); verbalize("Well, then you owe me %s%s!", yourgloves ? yname(yourgloves) : "twelve pairs of gloves", diff --git a/src/mkobj.c b/src/mkobj.c index 7434981be..31fe3c89a 100644 --- a/src/mkobj.c +++ b/src/mkobj.c @@ -834,6 +834,8 @@ static const char dknowns[] = { WAND_CLASS, RING_CLASS, POTION_CLASS, void clear_dknown(struct obj *obj) { + /* note: this is an unobserving not an observing, so don't call + observe_object even if dknown is being set to 1 */ obj->dknown = strchr(dknowns, obj->oclass) ? 0 : 1; if ((obj->otyp >= ELVEN_SHIELD && obj->otyp <= ORCISH_SHIELD) || obj->otyp == SHIELD_OF_REFLECTION @@ -960,6 +962,7 @@ mksobj_init(struct obj **obj, boolean artif) we initialize glob->owt explicitly so weight() doesn't need to perform any fix up and returns glob->owt as-is */ otmp->owt = objects[otmp->otyp].oc_weight; + /* dknown, but not observed */ otmp->known = otmp->dknown = 1; otmp->corpsenm = PM_GRAY_OOZE + (otmp->otyp - GLOB_OF_GRAY_OOZE); start_glob_timeout(otmp, 0L); diff --git a/src/mthrowu.c b/src/mthrowu.c index 7b2a23ca7..e19ca4ce9 100644 --- a/src/mthrowu.c +++ b/src/mthrowu.c @@ -334,7 +334,7 @@ ohitmon( ismimic = M_AP_TYPE(mtmp) && M_AP_TYPE(mtmp) != M_AP_MONSTER; vis = cansee(gb.bhitpos.x, gb.bhitpos.y); if (vis) - otmp->dknown = 1; + observe_object(otmp); tmp = 5 + find_mac(mtmp) + omon_adj(mtmp, otmp, FALSE); /* High level monsters will be more likely to hit */ @@ -652,7 +652,7 @@ m_throw( singleobj->ox = gb.bhitpos.x += dx; singleobj->oy = gb.bhitpos.y += dy; if (cansee(gb.bhitpos.x, gb.bhitpos.y)) - singleobj->dknown = 1; + observe_object(singleobj); mtmp = m_at(gb.bhitpos.x, gb.bhitpos.y); if (mtmp && shade_miss(mon, mtmp, singleobj, TRUE, TRUE)) { diff --git a/src/muse.c b/src/muse.c index 42243c832..f4d270e48 100644 --- a/src/muse.c +++ b/src/muse.c @@ -207,7 +207,7 @@ mplayhorn( ? "nearby" : "in the distance"); unknow_object(otmp); /* hero loses info when unseen obj is used */ } else if (self) { - otmp->dknown = 1; + observe_object(otmp); objnamp = xname(otmp); if (strlen(objnamp) >= QBUFSZ) objnamp = simpleonames(otmp); @@ -216,7 +216,7 @@ mplayhorn( pline("%s!", monverbself(mtmp, Monnam(mtmp), "play", objbuf)); makeknown(otmp->otyp); /* (wands handle this slightly differently) */ } else { - otmp->dknown = 1; + observe_object(otmp); objnamp = xname(otmp); if (strlen(objnamp) >= QBUFSZ) objnamp = simpleonames(otmp); @@ -242,7 +242,7 @@ mreadmsg(struct monst *mtmp, struct obj *otmp) if (!vismon && Deaf) return; /* no feedback */ - otmp->dknown = 1; /* seeing or hearing scroll read reveals its label */ + observe_object(otmp); /* seeing/hearing scroll read reveals its label */ Strcpy(onambuf, singular(otmp, vismon ? doname : ansimpleoname)); if (vismon) { @@ -291,7 +291,7 @@ staticfn void mquaffmsg(struct monst *mtmp, struct obj *otmp) { if (canseemon(mtmp)) { - otmp->dknown = 1; + observe_object(otmp); pline_mon(mtmp, "%s drinks %s!", Monnam(mtmp), singular(otmp, doname)); } else if (!Deaf) { Soundeffect(se_mon_chugging_potion, 25); @@ -1967,7 +1967,7 @@ use_offensive(struct monst *mtmp) * are not objects. Also set dknown in mthrowu.c. */ if (cansee(mtmp->mx, mtmp->my)) { - otmp->dknown = 1; + observe_object(otmp); pline_mon(mtmp, "%s hurls %s!", Monnam(mtmp), singular(otmp, doname)); } @@ -3129,7 +3129,7 @@ muse_unslime( vis |= canseemon(mon); /* burning potion may improve visibility */ if (vis) { if (!Unaware) - obj->dknown = 1; /* hero is watching mon drink obj */ + observe_object(obj); /* hero is watching mon drink obj */ pline("%s quaffs a burning %s", saw_lit ? upstart(strcpy(Pronoun, mhe(mon))) : Monnam(mon), simpleonames(obj)); diff --git a/src/nhlobj.c b/src/nhlobj.c index e06fc4b59..0579ddf47 100644 --- a/src/nhlobj.c +++ b/src/nhlobj.c @@ -210,7 +210,7 @@ l_obj_objects_to_table(lua_State *L) nhl_add_table_entry_int(L, "name_known", o->oc_name_known); nhl_add_table_entry_int(L, "merge", o->oc_merge); nhl_add_table_entry_int(L, "uses_known", o->oc_uses_known); - nhl_add_table_entry_int(L, "pre_discovered", o->oc_pre_discovered); + nhl_add_table_entry_int(L, "encountered", o->oc_encountered); nhl_add_table_entry_int(L, "magic", o->oc_magic); nhl_add_table_entry_int(L, "charged", o->oc_charged); nhl_add_table_entry_int(L, "unique", o->oc_unique); diff --git a/src/o_init.c b/src/o_init.c index 1185c177f..06b88e57e 100644 --- a/src/o_init.c +++ b/src/o_init.c @@ -377,7 +377,7 @@ savenames(NHFILE *nhfp) unsigned int len; if (update_file(nhfp)) { - for (i = 0; i < (MAXOCLASSES + 2); ++i) { + for (i = 0; i < (MAXOCLASSES + 2); ++i) { Sfo_int(nhfp, &svb.bases[i], "names-bases"); } for (i = 0; i < NUM_OBJECTS; ++i) { @@ -436,41 +436,50 @@ restnames(NHFILE *nhfp) } #ifndef SFCTOOL +/* make the object dknown and mark it as encountered */ +void +observe_object(struct obj *obj) +{ + obj->dknown = 1; + discover_object(obj->otyp, FALSE, TRUE, FALSE); +} + void discover_object( int oindx, boolean mark_as_known, + boolean mark_as_encountered, boolean credit_hero) { - if (!objects[oindx].oc_name_known + if ((!objects[oindx].oc_name_known && mark_as_known) + || (!objects[oindx].oc_encountered && mark_as_encountered) || (Role_if(PM_SAMURAI) && Japanese_item_name(oindx, (const char *) 0))) { int dindx, acls = objects[oindx].oc_class; /* Loop thru disco[] 'til we find the target (which may have been uname'd) or the next open slot; one or the other will be found - before we reach the next class... - */ + before we reach the next class... */ for (dindx = svb.bases[acls]; svd.disco[dindx] != 0; dindx++) if (svd.disco[dindx] == oindx) break; svd.disco[dindx] = oindx; - /* if already known, we forced an item with a Japanese name into - disco[] but don't want to exercise wisdom or update perminv */ - if (objects[oindx].oc_name_known) - return; + if (mark_as_encountered) + objects[oindx].oc_encountered = 1; - if (mark_as_known) { + if (!objects[oindx].oc_name_known && mark_as_known) { objects[oindx].oc_name_known = 1; if (credit_hero) exercise(A_WIS, TRUE); - } - /* !in_moveloop => initial inventory, gameover => final disclosure */ - if (program_state.in_moveloop && !program_state.gameover) { - if (objects[oindx].oc_class == GEM_CLASS) - gem_learned(oindx); /* could affect price of unpaid gems */ - update_inventory(); + + /* !in_moveloop => initial inventory, + gameover => final disclosure */ + if (program_state.in_moveloop && !program_state.gameover) { + if (objects[oindx].oc_class == GEM_CLASS) + gem_learned(oindx); /* could affect price of unpaid gems */ + update_inventory(); + } } } } @@ -514,9 +523,11 @@ interesting_to_discover(int i) if (Role_if(PM_SAMURAI) && Japanese_item_name(i, (const char *) 0)) return TRUE; - /* Pre-discovered objects are now printed with a '*' */ + /* Objects that were discovered without encountering them are now printed + with a '*' */ return (boolean) (objects[i].oc_uname != (char *) 0 - || (objects[i].oc_name_known + || ((objects[i].oc_name_known + || objects[i].oc_encountered) && OBJ_DESCR(objects[i]) != (char *) 0)); } @@ -550,7 +561,7 @@ sortloot_descr(int otyp, char *outbuf) o = cg.zeroobj; o.otyp = otyp; o.oclass = objects[otyp].oc_class; - o.dknown = 1; + o.dknown = 1; /* not observe_object, this isn't a real object */ o.known = (objects[otyp].oc_name_known || !objects[otyp].oc_uses_known) ? 1 : 0; o.corpsenm = NON_PM; /* suppress statue and figurine details */ @@ -784,7 +795,7 @@ dodiscovered(void) /* free after Robert Viduya */ prev_class = oclass; } } - Strcpy(buf, objects[dis].oc_pre_discovered ? "* " : " "); + Strcpy(buf, objects[dis].oc_encountered ? " " : "* "); if (lootsort) (void) sortloot_descr(dis, &buf[2]); disco_append_typename(buf, dis); @@ -1011,7 +1022,7 @@ doclassdisco(void) ++i) { if ((dis = svd.disco[i]) != 0 && interesting_to_discover(dis)) { ++ct; - Strcpy(buf, objects[dis].oc_pre_discovered ? "* " : " "); + Strcpy(buf, objects[dis].oc_encountered ? " " : "* "); if (lootsort) (void) sortloot_descr(dis, &buf[2]); disco_append_typename(buf, dis); @@ -1113,7 +1124,7 @@ rename_disco(void) odummy.oclass = objects[dis].oc_class; odummy.quan = 1L; odummy.known = !objects[dis].oc_uses_known; - odummy.dknown = 1; + odummy.dknown = 1; /* not observe_object: it isn't real */ docall(&odummy); } } diff --git a/src/objnam.c b/src/objnam.c index e05ea1c12..ee9a29836 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -625,7 +625,7 @@ xname_flags( if (!nn && ocl->oc_uses_known && ocl->oc_unique) obj->known = 0; if (!Blind && !gd.distantname) - obj->dknown = 1; + observe_object(obj); if (Role_if(PM_CLERIC)) obj->bknown = 1; /* avoid set_bknown() to bypass update_inventory() */ @@ -1057,6 +1057,8 @@ minimal_xname(struct obj *obj) bareobj = cg.zeroobj; bareobj.otyp = otyp; bareobj.oclass = obj->oclass; + /* not observe_object, either the hero observed the object already or this + is overriding ID and shouldn't discover the object */ bareobj.dknown = (obj->dknown || iflags.override_ID) ? 1 : 0; /* suppress known except for amulets (needed for fakes and real A-of-Y) */ bareobj.known = (obj->oclass == AMULET_CLASS) @@ -1945,7 +1947,8 @@ killer_xname(struct obj *obj) save_oname = ONAME(obj); /* killer name should be more specific than general xname; however, exact - info like blessed/cursed and rustproof makes things be too verbose */ + info like blessed/cursed and rustproof makes things be too verbose; set + dknown (not observe_object) because dead characters don't observe */ obj->known = obj->dknown = 1; obj->bknown = obj->rknown = obj->greased = 0; /* if character is a priest[ess], bknown will get toggled back on */ diff --git a/src/pager.c b/src/pager.c index 70eb1fe80..9f9ba087b 100644 --- a/src/pager.c +++ b/src/pager.c @@ -366,11 +366,11 @@ object_from_map( && (fakeobj || otmp->where == OBJ_FLOOR) /* not buried */ /* terrain mode views what's already known, doesn't learn new stuff */ && !iflags.terrainmode) /* so don't set dknown when in terrain mode */ - otmp->dknown = 1; /* if a pile, clearly see the top item only */ + observe_object(otmp); /* if a pile, clearly see the top item only */ if (fakeobj && mtmp && mimic_obj && (otmp->dknown || (M_AP_FLAG(mtmp) & M_AP_F_DKNOWN))) { mtmp->m_ap_type |= M_AP_F_DKNOWN; - otmp->dknown = 1; + observe_object(otmp); } *obj_p = otmp; return fakeobj; /* when True, caller needs to dealloc *obj_p */ diff --git a/src/pickup.c b/src/pickup.c index 4a366f757..26655a6c6 100644 --- a/src/pickup.c +++ b/src/pickup.c @@ -1816,7 +1816,7 @@ pickup_object( /* In case of auto-pickup, where we haven't had a chance to look at it yet; affects docall(SCR_SCARE_MONSTER). */ if (!Blind) - obj->dknown = 1; + observe_object(obj); if (obj == uchain) { /* do not pick up attached chain */ return 0; diff --git a/src/polyself.c b/src/polyself.c index 669f32da4..14fe3f1cc 100644 --- a/src/polyself.c +++ b/src/polyself.c @@ -646,7 +646,7 @@ polyself(int psflags) re-converting scales to mail poses risk of evaporation due to over enchanting */ uarm->otyp += GRAY_DRAGON_SCALES - GRAY_DRAGON_SCALE_MAIL; - uarm->dknown = 1; + observe_object(uarm); disp.botl = TRUE; /* AC is changing */ } uskin = uarm; @@ -1371,7 +1371,7 @@ rehumanize(void) return; /* don't rehumanize after all */ } else if (uamul && uamul->otyp == AMULET_OF_UNCHANGING) { Your("%s %s!", simpleonames(uamul), otense(uamul, "fail")); - uamul->dknown = 1; + observe_object(uamul); makeknown(AMULET_OF_UNCHANGING); } } diff --git a/src/potion.c b/src/potion.c index d537daf5c..ca7a2eaa5 100644 --- a/src/potion.c +++ b/src/potion.c @@ -2733,10 +2733,10 @@ potion_dip(struct obj *obj, struct obj *potion) else singlepotion->cursed = obj->cursed; /* odiluted left as-is */ singlepotion->bknown = FALSE; - if (Blind) { - singlepotion->dknown = FALSE; - } else { - singlepotion->dknown = !Hallucination; + singlepotion->dknown = FALSE; /* provisionally */ + if (!Blind) { + if (!Hallucination) + observe_object(singlepotion); *newbuf = '\0'; if (mixture == POT_WATER && singlepotion->dknown) Sprintf(newbuf, "clears"); @@ -2756,7 +2756,7 @@ potion_dip(struct obj *obj, struct obj *potion) struct obj fakeobj; fakeobj = cg.zeroobj; - fakeobj.dknown = 1; + fakeobj.dknown = 1; /* no need to observe_object */ fakeobj.otyp = old_otyp; fakeobj.oclass = POTION_CLASS; docall(&fakeobj); diff --git a/src/pray.c b/src/pray.c index 0564f3672..b515374d7 100644 --- a/src/pray.c +++ b/src/pray.c @@ -879,7 +879,7 @@ gcrownu(void) * even if hero doesn't know book */ bless(obj); obj->bknown = 1; /* ok to skip set_bknown() */ - obj->dknown = 1; + observe_object(obj); at_your_feet(upstart(ansimpleoname(obj))); dropy(obj); u.ugifts++; @@ -923,7 +923,7 @@ gcrownu(void) ; /* already got bonus above */ } else if (obj && in_hand) { Your("%s goes snicker-snack!", xname(obj)); - obj->dknown = 1; + observe_object(obj); } else if (!already_exists) { obj = mksobj(LONG_SWORD, FALSE, FALSE); obj = oname(obj, artiname(ART_VORPAL_BLADE), @@ -949,7 +949,7 @@ gcrownu(void) ; /* already got bonus above */ } else if (obj && in_hand) { Your("%s hums ominously!", swordbuf); - obj->dknown = 1; + observe_object(obj); } else if (!already_exists) { obj = mksobj(RUNESWORD, FALSE, FALSE); obj = oname(obj, artiname(ART_STORMBRINGER), @@ -1052,7 +1052,8 @@ give_spell(void) } obfree(otmp, (struct obj *) 0); /* discard the book */ } else { - otmp->dknown = 1; /* not bknown */ + observe_object(otmp); + /* don't set bknown */ /* discovering blank paper will make it less likely to be given again; small chance to arbitrarily discover some other book type without having to read it first */ @@ -1824,7 +1825,7 @@ bestow_artifact(uchar max_giftvalue) /* make sure we can use this weapon */ unrestrict_weapon_skill(weapon_type(otmp)); if (!Hallucination && !Blind) { - otmp->dknown = 1; + observe_object(otmp); makeknown(otmp->otyp); discover_artifact(otmp->oartifact); } diff --git a/src/quest.c b/src/quest.c index b554a664c..a436e2787 100644 --- a/src/quest.c +++ b/src/quest.c @@ -127,7 +127,7 @@ artitouch(struct obj *obj) if (!Qstat(touched_artifact)) { /* in case we haven't seen the item yet (ie, currently blinded), this quest message describes it by name so mark it as seen */ - obj->dknown = 1; + observe_object(obj); /* only give this message once */ Qstat(touched_artifact) = TRUE; qt_pager("gotit"); diff --git a/src/shk.c b/src/shk.c index d493caefb..4b1aedc1e 100644 --- a/src/shk.c +++ b/src/shk.c @@ -3360,7 +3360,7 @@ shk_names_obj( char *obj_name, fmtbuf[BUFSZ]; boolean was_unknown = !obj->dknown; - obj->dknown = TRUE; + observe_object(obj); /* Use real name for ordinary weapons/armor, and spell-less * scrolls/books (that is, blank and mail), but only if the * object is within the shk's area of interest/expertise. diff --git a/src/sit.c b/src/sit.c index 40628c1b0..62fc1f017 100644 --- a/src/sit.c +++ b/src/sit.c @@ -371,7 +371,8 @@ lay_an_egg(void) uegg->owt = weight(uegg); /* this sets hatch timers if appropriate */ set_corpsenm(uegg, egg_type_from_parent(u.umonnum, FALSE)); - uegg->known = uegg->dknown = 1; + uegg->known = 1; + observe_object(uegg); You("%s an egg.", eggs_in_water(gy.youmonst.data) ? "spawn" : "lay"); dropy(uegg); stackobj(uegg); diff --git a/src/spell.c b/src/spell.c index 03f5ee788..1f2c6a823 100644 --- a/src/spell.c +++ b/src/spell.c @@ -234,7 +234,7 @@ deadbook(struct obj *book2) You("turn the pages of the Book of the Dead..."); makeknown(SPE_BOOK_OF_THE_DEAD); - book2->dknown = 1; /* in case blind now and hasn't been seen yet */ + observe_object(book2); /* in case blind now and hasn't been seen yet */ /* KMH -- Need ->known to avoid "_a_ Book of the Dead" */ book2->known = 1; if (invocation_pos(u.ux, u.uy) && !On_stairs(u.ux, u.uy)) { @@ -896,8 +896,9 @@ skill_based_spellbook_id(void) } if (objects[booktype].oc_level <= known_up_to_level) - /* makeknown(booktype) but don't exercise Wisdom */ - discover_object(booktype, TRUE, FALSE); + /* makeknown(booktype) but don't exercise Wisdom or mark as + encountered */ + discover_object(booktype, TRUE, FALSE, FALSE); } } diff --git a/src/trap.c b/src/trap.c index cfeec5ace..a5602cd5a 100644 --- a/src/trap.c +++ b/src/trap.c @@ -1206,7 +1206,7 @@ trapeffect_arrow_trap( } else { place_object(otmp, u.ux, u.uy); if (!Blind) - otmp->dknown = 1; + observe_object(otmp); stackobj(otmp); newsym(u.ux, u.uy); } @@ -1276,7 +1276,7 @@ trapeffect_dart_trap( } else { place_object(otmp, u.ux, u.uy); if (!Blind) - otmp->dknown = 1; + observe_object(otmp); stackobj(otmp); newsym(u.ux, u.uy); } @@ -1352,7 +1352,7 @@ trapeffect_rocktrap( harmless = TRUE; } if (!Blind) - otmp->dknown = 1; + observe_object(otmp); stackobj(otmp); newsym(u.ux, u.uy); /* map the rock */ @@ -5738,7 +5738,7 @@ untrap_box( else pline("There's a trap on %s.", the(xname(box))); box->tknown = 1; - box->dknown = 1; + observe_object(box); if (!confused) exercise(A_WIS, TRUE); diff --git a/src/u_init.c b/src/u_init.c index e4d60dd62..72bf3043c 100644 --- a/src/u_init.c +++ b/src/u_init.c @@ -562,8 +562,8 @@ knows_object(int obj, boolean override_pauper) { if (u.uroleplay.pauper && !override_pauper) return; - discover_object(obj, TRUE, FALSE); - objects[obj].oc_pre_discovered = 1; /* not a "discovery" */ + /* mark as known, but not yet encountered */ + discover_object(obj, TRUE, FALSE, FALSE); } /* Know ordinary (non-magical) objects of a certain class, @@ -1207,6 +1207,7 @@ ini_inv_adjust_obj(struct trobj *trop, struct obj *obj) } else { if (objects[obj->otyp].oc_uses_known) obj->known = 1; + /* not observe_object during startup, that's handled later */ obj->dknown = obj->bknown = obj->rknown = 1; if (Is_container(obj) || obj->otyp == STATUE) { obj->cknown = obj->lknown = 1; @@ -1246,9 +1247,9 @@ ini_inv_use_obj(struct obj *obj) { /* Make the type known if necessary */ if (OBJ_DESCR(objects[obj->otyp]) && obj->known) - discover_object(obj->otyp, TRUE, FALSE); + discover_object(obj->otyp, TRUE, TRUE, FALSE); if (obj->otyp == OIL_LAMP) - discover_object(POT_OIL, TRUE, FALSE); + discover_object(POT_OIL, TRUE, TRUE, FALSE); if (obj->oclass == ARMOR_CLASS) { if (is_shield(obj) && !uarms && !(uwep && bimanual(uwep))) { diff --git a/src/uhitm.c b/src/uhitm.c index 160d2e0a2..316858896 100644 --- a/src/uhitm.c +++ b/src/uhitm.c @@ -1133,7 +1133,7 @@ hmon_hitmon_misc_obj( corpse_xname(obj, (const char *) 0, obj->dknown ? CXN_PFX_THE : CXN_ARTICLE)); - obj->dknown = 1; + observe_object(obj); if (!munstone(mon, TRUE)) minstapetrify(mon, TRUE); if (resists_ston(mon)) diff --git a/src/write.c b/src/write.c index fd967685e..312072a58 100644 --- a/src/write.c +++ b/src/write.c @@ -143,7 +143,7 @@ dowrite(struct obj *pen) return ECMD_OK; } } - paper->dknown = 1; + observe_object(paper); if (paper->otyp != SCR_BLANK_PAPER && paper->otyp != SPE_BLANK_PAPER) { pline("That %s is not blank!", typeword); exercise(A_WIS, FALSE); @@ -326,13 +326,8 @@ dowrite(struct obj *pen) * * Writing by description requires that the hero knows the * description (a scroll's label, that is, since books by_descr - * are rejected above). BUG: We can only do this for known - * scrolls and for the case where the player has assigned a - * name to put it onto the discoveries list; we lack a way to - * track other scrolls which have been seen closely enough to - * read the label without then being ID'd or named. The only - * exception is for currently carried inventory, where we can - * check for one [with its dknown bit set] of the same type. + * are rejected above). This is done by checking to see if a + * scroll with the same description has been encountered. * * Normal requirements can be overridden if hero is Lucky. */ @@ -345,7 +340,7 @@ dowrite(struct obj *pen) /* if known, then either by-name or by-descr works */ if (!objects[new_obj->otyp].oc_name_known /* else if named, then only by-descr works */ - && !(by_descr && label_known(new_obj->otyp, gi.invent)) + && !(by_descr && objects[new_obj->otyp].oc_encountered) /* else fresh knowledge of the spell works */ && spell_knowledge != spe_Fresh /* and Luck might override after previous checks have failed */ diff --git a/src/zap.c b/src/zap.c index 16e18db34..8849fb75a 100644 --- a/src/zap.c +++ b/src/zap.c @@ -132,14 +132,14 @@ learnwand(struct obj *obj) /* if type already discovered, treat this item has having been seen even if hero is currently blinded (skips redundant makeknown) */ if (objects[obj->otyp].oc_name_known) { - obj->dknown = 1; /* will usually be set already */ + observe_object(obj); /* will usually be dknown already */ /* otherwise discover it if item itself has been or can be seen */ } else { /* in case it was picked up while blind and then zapped without examining inventory after regaining sight (bypassing xname) */ if (!Blind) - obj->dknown = 1; + observe_object(obj); /* make the discovery iff we know what we're manipulating */ if (obj->dknown) makeknown(obj->otyp); @@ -610,7 +610,7 @@ staticfn void probe_objchain(struct obj *otmp) { for (; otmp; otmp = otmp->nobj) { - otmp->dknown = 1; /* treat as "seen" */ + observe_object(otmp); /* treat as "seen" */ if (Is_container(otmp) || otmp->otyp == STATUE) { otmp->lknown = 1; if (!SchroedingersBox(otmp)) @@ -2220,7 +2220,7 @@ bhito(struct obj *obj, struct obj *otmp) case WAN_PROBING: res = !obj->dknown; /* target object has now been "seen (up close)" */ - obj->dknown = 1; + observe_object(obj); if (Is_container(obj) || obj->otyp == STATUE) { obj->cknown = obj->lknown = 1; if (Is_box(obj) && !obj->tknown) { @@ -2249,7 +2249,7 @@ bhito(struct obj *obj, struct obj *otmp) /* view contents (not recursively) */ for (o = obj->cobj; o; o = o->nobj) - o->dknown = 1; /* "seen", even if blind */ + observe_object(o); /* "seen", even if blind */ (void) display_cinventory(obj); } res = 1;