diff --git a/include/decl.h b/include/decl.h index 18085f6c0..a2baba577 100644 --- a/include/decl.h +++ b/include/decl.h @@ -1085,6 +1085,12 @@ struct instance_globals_x { /* mkmaze.c */ int xmin, xmax; /* level boundaries x */ + /* objnam.c */ + char *xnamep; /* obuf[] returned by xname(), for use in doname() for + * bounds checking; differs from xname() return value + * due to reserving PREFIX bytes at start and possibly + * skipping leading "the " after constructing result */ + /* sp_lev.c */ coordxy xstart, xsize; diff --git a/src/decl.c b/src/decl.c index c7bcd431e..a86c65eff 100644 --- a/src/decl.c +++ b/src/decl.c @@ -882,9 +882,8 @@ const struct instance_globals_w g_init_w = { UNDEFINED_PTR, /* wportal */ /* new */ { wdmode_traditional, NO_COLOR }, /* wsettings */ - TRUE, /* havestate*/ - IVMAGIC /* w_magic used to validate that structure layout has been preserved */ + IVMAGIC /* w_magic to validate that structure layout has been preserved */ }; const struct instance_globals_x g_init_x = { @@ -895,6 +894,8 @@ const struct instance_globals_x g_init_x = { /* mkmaze.c */ UNDEFINED_VALUE, /* xmin */ UNDEFINED_VALUE, /* xmax */ + /* objnam.c */ + NULL, /* xnamep */ /* sp_lev.c */ UNDEFINED_VALUE, /* xstart */ UNDEFINED_VALUE, /* xsize */ diff --git a/src/objnam.c b/src/objnam.c index 2036b6322..5c0d7ae14 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -66,6 +66,32 @@ struct Jitem { #define BSTRNCMPI(base, ptr, str, num) \ ((ptr) < base || strncmpi((ptr), str, num)) #define Strcasecpy(dst, src) (void) strcasecpy(dst, src) +#define Strncat(dst, src, cnt) (void) strncat(dst, src, cnt) + +/* Concat(): append text to base, adjusted by delta, with bounds checking + via a pair of behind-the-scenes variables; delta is either 0 for normal + concatenation or 1 to replace the final character with something */ +#define Concat(base, delta, text) \ + do { \ + Strncat(base ## _eos - delta, text, base ## spaceleft + delta); \ + ConcUpdate(base); \ + } while (0) +#define ConcatF1(base, delta, fmt, arg1) \ + do { \ + Snprintf(base ## _eos - delta, base ## spaceleft + delta, \ + fmt, arg1); \ + ConcUpdate(base); \ + } while (0) +#define ConcatF2(base, delta, fmt, arg1, arg2) \ + do { \ + Snprintf(base ## _eos - delta, base ## spaceleft + delta, \ + fmt, arg1, arg2); \ + ConcUpdate(base); \ + } while (0) +#define ConcUpdate(base) \ + base ## _eos = eos(base ## _eos), \ + /* convert signed ptrdiff_t to unsigned size_t */ \ + base ## spaceleft = (size_t) (base ## _end - base ## _eos) /* true for gems/rocks that should have " stone" appended to their names */ #define GemStone(typ) \ @@ -93,7 +119,7 @@ static const struct Jitem Japanese_items[] = { }; static char * -strprepend(char *s,const char * pref) +strprepend(char *s, const char *pref) { register int i = (int) strlen(pref); @@ -520,7 +546,7 @@ xcalled( } char * -xname(struct obj* obj) +xname(struct obj *obj) { return xname_flags(obj, CXN_NORMAL); } @@ -531,7 +557,8 @@ xname_flags( unsigned cxn_flags) /* bitmask of CXN_xxx values */ { register char *buf; - char *obufp; + char *obufp, *buf_end, *buf_eos; + size_t bufspaceleft; int typ = obj->otyp; struct objclass *ocl = &objects[typ]; int nn = ocl->oc_name_known, omndx = obj->corpsenm; @@ -541,7 +568,14 @@ xname_flags( boolean pluralize = (obj->quan != 1L) && !(cxn_flags & CXN_SINGULAR); boolean known, dknown, bknown; - buf = nextobuf() + PREFIX; /* leave room for "17 -3 " */ + gx.xnamep = nextobuf(); + /* set up primary work buffer; the first 'PREFIX' bytes are set + aside for use by doname() */ + buf = gx.xnamep + PREFIX; /* leave room for "17 -3 " */ + buf_end = gx.xnamep + BUFSZ - 1; /* last byte within the obuf[] */ + buf[0] = '\0', buf_eos = buf; /* eos(buf) for empty buf[] */ + ConcUpdate(buf); /* update buf_eos (redundantly) and bufspaceleft */ + if (Role_if(PM_SAMURAI)) { actualn = Japanese_item_name(typ, actualn); if (typ == WOODEN_HARP || typ == MAGIC_HARP) @@ -556,7 +590,6 @@ xname_flags( if (!dn) dn = actualn; - buf[0] = '\0'; /* * clean up known when it's tied to oc_name_known, eg after AD_DRIN * This is only required for unique objects since the article @@ -603,6 +636,12 @@ xname_flags( if (obj_is_pname(obj)) goto nameit; + + /* Some classes use strcpy(buf, something)+strcat(buf, otherthing). + In those cases, ConcUpdate() is needed in between if Concat() + will be used for the strcat() part. Other classes just use + strcpy(buf, something) and the ConcUpdate() can be deferred + until after the switch. */ switch (obj->oclass) { case AMULET_CLASS: if (!dknown) @@ -623,6 +662,8 @@ xname_flags( /*FALLTHRU*/ case VENOM_CLASS: case TOOL_CLASS: + /* note: lenses or towel prefix would overwrite poisoned weapon + prefix if both were simultaneously posssible, but they aren't */ if (typ == LENSES) Strcpy(buf, "pair of "); else if (is_wet_towel(obj)) @@ -636,15 +677,16 @@ xname_flags( xcalled(buf, BUFSZ - PREFIX, dn, un); else Strcat(buf, dn); + ConcUpdate(buf); if (typ == FIGURINE && omndx != NON_PM) { char anbuf[10]; /* [4] would be enough: 'a','n',' ','\0' */ const char *pm_name = obj_pmname(obj); - Sprintf(eos(buf), " of %s%s", just_an(anbuf, pm_name), pm_name); + ConcatF2(buf, 0, " of %s%s", just_an(anbuf, pm_name), pm_name); } else if (is_wet_towel(obj)) { if (wizard) - Sprintf(eos(buf), " (%d)", obj->spe); + ConcatF1(buf, 0, " (%d)", obj->spe); } break; case ARMOR_CLASS: @@ -652,11 +694,10 @@ xname_flags( if (typ >= GRAY_DRAGON_SCALES && typ <= YELLOW_DRAGON_SCALES) { Sprintf(buf, "set of %s", actualn); break; - } - if (is_boots(obj) || is_gloves(obj)) + } else if (is_boots(obj) || is_gloves(obj)) { Strcpy(buf, "pair of "); - - if (!dknown) { + /*FALLTHRU*/ + } else if (is_shield(obj) && !dknown) { if (obj->otyp >= ELVEN_SHIELD && obj->otyp <= ORCISH_SHIELD) { Strcpy(buf, "shield"); break; @@ -665,13 +706,14 @@ xname_flags( break; } } + ConcUpdate(buf); if (nn) - Strcat(buf, actualn); + Concat(buf, 0, actualn); else if (un) xcalled(buf, BUFSZ - PREFIX, armor_simple_name(obj), un); else - Strcat(buf, dn); + Concat(buf, 0, dn); break; case FOOD_CLASS: /* we could include partly-eaten-hack on fruit but don't need to */ @@ -682,6 +724,9 @@ xname_flags( impossible("Bad fruit #%d?", obj->spe); Strcpy(buf, "fruit"); } else { + /* fruit name is limited in length to PL_FSIZ; converting + to/from singular/plural might increase the length a + little but not enough to pose a risk of overflowing buf */ Strcpy(buf, f->fname); if (pluralize) { /* ick: already pluralized fruit names are allowed--we @@ -706,18 +751,18 @@ xname_flags( appropriate and omitted by xname(); shrink_glob() wants it but uses Yname2() -> yname() -> xname() rather than doname() so we've added an external flag to request it */ - Strcat(buf, "partly eaten "); + Concat(buf, 0, "partly eaten "); } if (obj->globby) { /* 3.7 added "medium" to replace no-prefix */ - Sprintf(eos(buf), "%s %s", (obj->owt <= 100) ? "small" - : (obj->owt <= 300) ? "medium" - : (obj->owt <= 500) ? "large" - : "very large", - actualn); + ConcatF2(buf, 0, "%s %s", (obj->owt <= 100) ? "small" + : (obj->owt <= 300) ? "medium" + : (obj->owt <= 500) ? "large" + : "very large", + actualn); break; } - Strcpy(buf, actualn); + Concat(buf, 0, actualn); if (typ == TIN && known) tin_details(obj, omndx, buf); break; @@ -730,24 +775,24 @@ xname_flags( char anbuf[10]; const char *statue_pmname = obj_pmname(obj); - Sprintf(buf, "%s%s of %s%s", - (Role_if(PM_ARCHEOLOGIST) - && (obj->spe & CORPSTAT_HISTORIC)) ? "historic " : "", - actualn, - type_is_pname(&mons[omndx]) ? "" - : the_unique_pm(&mons[omndx]) ? "the " - : just_an(anbuf, statue_pmname), - statue_pmname); - } else { + Snprintf(buf, bufspaceleft, "%s%s of %s%s", + (Role_if(PM_ARCHEOLOGIST) + && (obj->spe & CORPSTAT_HISTORIC) != 0) ? "historic " + : "", + actualn, + type_is_pname(&mons[omndx]) ? "" + : the_unique_pm(&mons[omndx]) ? "the " + : just_an(anbuf, statue_pmname), + statue_pmname); + } else if (typ == BOULDER && obj->next_boulder == 1) { /* sometimes caller wants "next boulder" rather than just "boulder" (when pushing against a pile of more than one); originally we just tested for non-0 but checking for 1 is more robust because the default value for that overloaded field (obj->corpsenm) is NON_PM (-1) rather than 0 */ - if (typ == BOULDER && obj->next_boulder == 1) - Strcat(strcpy(buf, "next "), actualn); - else - Strcpy(buf, actualn); + Strcat(strcpy(buf, "next "), actualn); /* "next boulder" */ + } else { + Strcpy(buf, actualn); /* "boulder" or "statue" */ } break; case BALL_CLASS: @@ -852,20 +897,38 @@ xname_flags( Strcat(buf, " stone"); } break; - } + } /* gem */ default: Sprintf(buf, "glorkum %d %d %d", obj->oclass, typ, obj->spe); impossible("xname_flags: %s", buf); break; + } /* switch */ + + /* check whether we've already gone out of bounds of the obuf[], prior + to pluralization and end-of-game shirt and apron text */ + buf_eos = eos(buf); + if (buf_eos > buf_end) { + /* PREFIX is bigger than 6 so there will always be room within the + obuf[] in front of buf to insert "buf[]="; strncpy(,,N) doesn't + add '\0' terminator unless fewer than N chars are copied */ + paniclog("xname", strncpy(buf - 6, "buf[]=", 6)); + panic("xname: buffer overflow before appending name."); + /*NOTREACHED*/ } + bufspaceleft = (size_t) (buf_end - buf_eos); + + /* if the name should be plural, do that now, after overflow check; + it could make buf[] become shorter */ if (pluralize) { - /* (see fruit name handling in case FOOD_CLASS above) */ - Strcpy(buf, obufp = makeplural(buf)); + obufp = makeplural(buf); + buf[0] = '\0', buf_eos = buf; /* replace the whole string */ + ConcUpdate(buf); + Concat(buf, 0, obufp); releaseobuf(obufp); } /* maybe give some extra information which isn't shown during play */ - if (gp.program_state.gameover) { + if (gp.program_state.gameover && bufspaceleft > 0) { const char *lbl; char tmpbuf[BUFSZ]; @@ -874,18 +937,18 @@ xname_flags( switch (obj->otyp) { case T_SHIRT: case ALCHEMY_SMOCK: - Sprintf(eos(buf), " with text \"%s\"", - (obj->otyp == T_SHIRT) ? tshirt_text(obj, tmpbuf) - : apron_text(obj, tmpbuf)); + ConcatF1(buf, 0, " with text \"%s\"", + (obj->otyp == T_SHIRT) ? tshirt_text(obj, tmpbuf) + : apron_text(obj, tmpbuf)); break; case CANDY_BAR: lbl = candy_wrapper_text(obj); if (*lbl) - Sprintf(eos(buf), " labeled \"%s\"", lbl); + ConcatF1(buf, 0, " labeled \"%s\"", lbl); break; case HAWAIIAN_SHIRT: - Sprintf(eos(buf), " with %s motif", - an(hawaiian_motif(obj, tmpbuf))); + ConcatF1(buf, 0, " with %s motif", + an(hawaiian_motif(obj, tmpbuf))); break; default: break; @@ -893,18 +956,35 @@ xname_flags( } if (has_oname(obj) && dknown) { - Strcat(buf, " named "); + Concat(buf, 0, " named "); + + /* jump directly here if obj passes the has-personal-name test */ nameit: - obufp = eos(buf); - (void) strncat(buf, ONAME(obj), - BUFSZ - 1 - PREFIX - (unsigned) strlen(buf)); + /*assert(has_oname(obj));*/ + obufp = eos(buf); /* remember where the name will start */ + Concat(buf, 0, ONAME(obj)); /* downcase "The" in " named The ..." */ if (obj->oartifact && !strncmp(obufp, "The ", 4)) - *obufp = lowc(*obufp); /* = 't'; */ + *obufp = lowc(*obufp); /* change 'T' in "The " to 't' */ } if (!strncmpi(buf, "the ", 4)) buf += 4; + + buf_eos = eos(buf); /* pointer to '\0' terminator somewhere in obuf[] */ + if (buf_eos >= buf_end) { /* ('>' shouldn't be possible) */ + static int xname_full = 0; + + /* we want a record of something needing more buffer space than + anticipated; since we aren't panicking here, this could happen + repeatedly and we don't want to spam the paniclog file */ + if (!xname_full++) { + paniclog("xname", strncpy(buf - 6, "buf[]=", 6)); + /* 'PREFIX' ought to be 'PREFIX+4' if we stripped leading "the" */ + paniclog("xname", "used up entire obuf[PREFIX..BUFSX-1]"); + } + } + return buf; } @@ -965,7 +1045,7 @@ minimal_xname(struct obj *obj) /* xname() output augmented for multishot missile feedback */ char * -mshot_xname(struct obj* obj) +mshot_xname(struct obj *obj) { char tmpbuf[BUFSZ]; char *onm = xname(obj); @@ -981,7 +1061,7 @@ mshot_xname(struct obj* obj) /* used for naming "the unique_item" instead of "a unique_item" */ boolean -the_unique_obj(struct obj* obj) +the_unique_obj(struct obj *obj) { boolean known = (obj->known || iflags.override_ID); @@ -996,7 +1076,7 @@ the_unique_obj(struct obj* obj) /* should monster type be prefixed with "the"? (mostly used for corpses) */ boolean -the_unique_pm(struct permonst* ptr) +the_unique_pm(struct permonst *ptr) { boolean uniq; @@ -1018,7 +1098,7 @@ the_unique_pm(struct permonst* ptr) } static void -add_erosion_words(struct obj* obj, char* prefix) +add_erosion_words(struct obj *obj, char *prefix) { boolean iscrys = (obj->otyp == CRYSKNIFE); boolean rknown; @@ -1093,16 +1173,18 @@ erosion_matters(struct obj *obj) #define DONAME_WITH_PRICE 1 #define DONAME_VAGUE_QUAN 2 +#define DONAME_FOR_MENU 4 /* [not used anywhere yet] */ /* core of doname() */ static char * doname_base( - struct obj* obj, /* object to format */ + struct obj *obj, /* object to format */ unsigned doname_flags) /* special case requests */ { boolean ispoisoned = FALSE, with_price = (doname_flags & DONAME_WITH_PRICE) != 0, - vague_quan = (doname_flags & DONAME_VAGUE_QUAN) != 0; + vague_quan = (doname_flags & DONAME_VAGUE_QUAN) != 0, + for_menu = (doname_flags & DONAME_FOR_MENU) != 0; boolean known, dknown, cknown, bknown, lknown, fake_arti, force_the; char prefix[PREFIX]; @@ -1111,7 +1193,20 @@ doname_base( * end (Strcat is used on the end) */ const char *aname = 0; int omndx = obj->corpsenm; - register char *bp = xname(obj); + register char *bp; + char *bp_eos, *bp_end; + size_t bpspaceleft; + + /* 'bp' will be within an obuf[] rather than at the start of one, + usually (but not always) pointing at &obuf[PREFIX]; + gx.xnamep always points to the start of that buffer; + 'bp_eos' and 'bpspaceleft' are used and updated by Concat*() macros */ + bp = xname(obj); + bp_end = gx.xnamep + BUFSZ - 1; + bp_eos = eos(bp); + assert(bp_end >= bp_eos); /* ok provided xname() bounds checking works */ + /* size_t cast: convert signed ptrdiff_t to unsigned size_t */ + bpspaceleft = (size_t) (bp_end - bp_eos); if (iflags.override_ID) { known = dknown = cknown = bknown = lknown = TRUE; @@ -1130,7 +1225,7 @@ doname_base( */ /* must check opoisoned--someone can have a weirdly-named fruit */ if (!strncmp(bp, "poisoned ", 9) && obj->opoisoned) { - bp += 9; + bp += 9; /* doesn't affect bp_eos or bpspaceleft */ ispoisoned = TRUE; } @@ -1153,7 +1248,7 @@ doname_base( ; } else if (force_the || obj_is_pname(obj) || the_unique_obj(obj)) { if (!strncmpi(bp, "the ", 4)) - bp += 4; + bp += 4; /* doesn't affect bp_eos or bpspaceleft */ Strcpy(prefix, "the "); } else if (!fake_arti) { /* default prefix */ @@ -1225,39 +1320,48 @@ doname_base( if (obj->greased) Strcat(prefix, "greased "); - if (cknown && Has_contents(obj)) { + if (cknown && Has_contents(obj) && bpspaceleft > 0) { /* we count the number of separate stacks, which corresponds to the number of inventory slots needed to be able to take everything out if no merges occur */ long itemcount = count_contents(obj, FALSE, FALSE, TRUE, FALSE); - Sprintf(eos(bp), " containing %ld item%s", itemcount, - plur(itemcount)); + ConcatF2(bp, 0, " containing %ld item%s", itemcount, plur(itemcount)); } switch (is_weptool(obj) ? WEAPON_CLASS : obj->oclass) { case AMULET_CLASS: if (obj->owornmask & W_AMUL) - Strcat(bp, " (being worn)"); + Concat(bp, 0, " (being worn)"); break; case ARMOR_CLASS: if (obj->owornmask & W_ARMOR) { - Strcat(bp, (obj == uskin) ? " (embedded in your skin)" - /* in case of perm_invent update while Wear/Takeoff - is in progress; check doffing() before donning() - because donning() returns True for both cases */ - : doffing(obj) ? " (being doffed)" - : donning(obj) ? " (being donned)" - : " (being worn)"); - /* slippery fingers is an intrinsic condition of the hero - rather than extrinsic condition of objects, but gloves - are described as slippery when hero has slippery fingers */ - if (obj == uarmg && Glib) /* just appended "(something)", - * change to "(something; slippery)" */ - Strcpy(strrchr(bp, ')'), "; slippery)"); - else if (!Blind && obj->lamplit && artifact_light(obj)) - Sprintf(strrchr(bp, ')'), ", %s lit)", - arti_light_description(obj)); + Concat(bp, 0, + (obj == uskin) ? " (embedded in your skin)" + /* in case of perm_invent update while Wear/Takeoff + is in progress; check doffing() before donning() + because donning() returns True for both cases */ + : doffing(obj) ? " (being doffed)" + : donning(obj) ? " (being donned)" + : " (being worn)"); + /* we just added a parenthesized pharse, but the right paren + might be absent if the appended string got truncated */ + if (bp_eos[-1] == ')') { + /* slippery fingers is an intrinsic condition of the hero + rather than extrinsic condition of objects, but gloves + are described as slippery when hero has slippery fingers */ + if (obj == uarmg && Glib) /* just appended "(something)", + * replace paren, changing that + * to be "(something; slippery)" */ + Concat(bp, 1, "; slippery)"); + } + if (bp_eos[-1] == ')') { + /* there could be light-emitting artifact gloves someday, + so add 'lit' separately from 'slippery' rather than via + 'else if' after uarmg+Glib */ + if (!Blind && obj->lamplit && artifact_light(obj)) + ConcatF1(bp, 1, ", %s lit)", arti_light_description(obj)); + } } /*FALLTHRU*/ case WEAPON_CLASS: @@ -1265,13 +1369,12 @@ doname_base( Strcat(prefix, "poisoned "); add_erosion_words(obj, prefix); if (known) { - Strcat(prefix, sitoa(obj->spe)); - Strcat(prefix, " "); + Sprintf(eos(prefix), "%+d ", obj->spe); /* sitoa(obj->spe)+" " */ } break; case TOOL_CLASS: if (obj->owornmask & (W_TOOL | W_SADDLE)) { /* blindfold */ - Strcat(bp, " (being worn)"); + Concat(bp, 0, " (being worn)"); break; } if (obj->otyp == LEASH && obj->leashmon != 0) { @@ -1281,15 +1384,17 @@ doname_base( impossible("leashed monster not on this level"); obj->leashmon = 0; } else { - Sprintf(eos(bp), " (attached to %s)", - noit_mon_nam(mlsh)); + ConcatF1(bp, 0, " (attached to %s)", noit_mon_nam(mlsh)); } break; } if (obj->otyp == CANDELABRUM_OF_INVOCATION) { - Sprintf(eos(bp), " (%d of 7 candle%s%s)", - obj->spe, plur(obj->spe), + char suffix[20]; /* longest value is "s attached" */ + + /* separately formatted suffix avoids need for ConcatF3() */ + Sprintf(suffix, "%s%s", plur(obj->spe), !obj->lamplit ? " attached" : ", lit"); + ConcatF2(bp, 0, " (%d of 7 candle%s)", obj->spe, suffix); break; } else if (obj->otyp == OIL_LAMP || obj->otyp == MAGIC_LAMP || obj->otyp == BRASS_LANTERN || Is_candle(obj)) { @@ -1313,7 +1418,7 @@ doname_base( Strcat(prefix, "partly used "); } if (obj->lamplit) - Strcat(bp, " (lit)"); + Concat(bp, 0, " (lit)"); break; } if (objects[obj->otyp].oc_charged) @@ -1322,25 +1427,22 @@ doname_base( case WAND_CLASS: charges: if (known) - Sprintf(eos(bp), " (%d:%d)", (int) obj->recharged, obj->spe); + ConcatF2(bp, 0, " (%d:%d)", (int) obj->recharged, obj->spe); break; case POTION_CLASS: if (obj->otyp == POT_OIL && obj->lamplit) - Strcat(bp, " (lit)"); + Concat(bp, 0, " (lit)"); break; case RING_CLASS: ring: if (obj->owornmask & W_RINGR) - Strcat(bp, " (on right "); + Concat(bp, 0, " (on right "); if (obj->owornmask & W_RINGL) - Strcat(bp, " (on left "); - if (obj->owornmask & W_RING) { - Strcat(bp, body_part(HAND)); - Strcat(bp, ")"); - } + Concat(bp, 0, " (on left "); + if (obj->owornmask & W_RING) /* either left or right */ + ConcatF1(bp, 0,"%s)", body_part(HAND)); if (known && objects[obj->otyp].oc_charged) { - Strcat(prefix, sitoa(obj->spe)); - Strcat(prefix, " "); + Sprintf(prefix, "%+d", obj->spe); /* sitoa(obj->spe)+" " */ } break; case FOOD_CLASS: @@ -1367,7 +1469,7 @@ doname_base( Strcat(prefix, mons[omndx].pmnames[NEUTRAL]); Strcat(prefix, " "); if (obj->spe == 1) - Strcat(bp, " (laid by you)"); + Concat(bp, 0, " (laid by you)"); } } if (obj->otyp == MEAT_RING) @@ -1377,8 +1479,8 @@ doname_base( case CHAIN_CLASS: add_erosion_words(obj, prefix); if (obj->owornmask & (W_BALL | W_CHAIN)) - Sprintf(eos(bp), " (%s to you)", - (obj->owornmask & W_BALL) ? "chained" : "attached"); + ConcatF1(bp, 0, " (%s to you)", + (obj->owornmask & W_BALL) ? "chained" : "attached"); break; } @@ -1388,9 +1490,10 @@ doname_base( mgend = ((cgend == CORPSTAT_MALE) ? MALE : (cgend == CORPSTAT_FEMALE) ? FEMALE : NEUTRAL); - Sprintf(eos(bp), " (%s)", - cgend != CORPSTAT_RANDOM ? genders[mgend].adj - : "unspecified gender"); + + ConcatF1(bp, 0, " (%s)", + (cgend != CORPSTAT_RANDOM) ? genders[mgend].adj + : "unspecified gender"); } if ((obj->owornmask & W_WEP) && !gm.mrg_to_wielded) { @@ -1408,83 +1511,78 @@ doname_base( ? (is_ammo(obj) || is_missile(obj)) : !is_weptool(obj))) && !twoweap_primary) { - Strcat(bp, " (wielded)"); + Concat(bp, 0, " (wielded)"); } else { const char *hand_s = body_part(HAND); char *obufp, handsbuf[40]; - if (bimanual(obj)) { + if (bimanual(obj)) { /* "hands" */ hand_s = strcpy(handsbuf, obufp = makeplural(hand_s)); releaseobuf(obufp); + } else { /* "right hand" or "left hand" */ + Sprintf(handsbuf, "%s %s", + URIGHTY ? "right" : "left", hand_s); + hand_s = handsbuf; } /* note: Sting's glow message, if added, will insert text in front of "(weapon in hand)"'s closing paren */ - Sprintf(eos(bp), " (%s%s in %s%s)", - tethered ? "tethered " : "", /* aklys */ - /* avoid "tethered wielded in right hand" for twoweapon */ - (twoweap_primary && !tethered) ? "wielded" : "weapon", - bimanual(obj) ? "" : URIGHTY ? "right " : "left ", -/* - (twoweap_primary && URIGHTY) - ? "right " - : (twoweap_primary && ULEFTY) - ? "left " - : "", -*/ - hand_s); - if (!Blind) { + ConcatF2(bp, 0, " (%s %s)", + tethered ? "tethered to" + : twoweap_primary ? "wielded in" + : "weapon in", + hand_s); + + /* we just added a parenthesized pharse, but the right paren + might be absent if the appended string got truncated */ + if (!Blind && bpspaceleft && bp_eos[-1] == ')') { if (gw.warn_obj_cnt && obj == uwep && (EWarn_of_mon & W_WEP) != 0L) /* we know bp[] ends with ')'; overwrite that */ - Sprintf(eos(bp) - 1, ", %s %s)", - glow_verb(gw.warn_obj_cnt, TRUE), - glow_color(obj->oartifact)); + ConcatF2(bp, 1, ", %s %s)", + glow_verb(gw.warn_obj_cnt, TRUE), + glow_color(obj->oartifact)); else if (obj->lamplit && artifact_light(obj)) /* as above, overwrite known closing paren */ - Sprintf(eos(bp) - 1, ", %s lit)", - arti_light_description(obj)); + ConcatF1(bp, 1, ", %s lit)", + arti_light_description(obj)); } } } if (obj->owornmask & W_SWAPWEP) { if (u.twoweap) - Sprintf(eos(bp), " (wielded in %s %s)", - URIGHTY ? "left" : "right", body_part(HAND)); + ConcatF2(bp, 0, " (wielded in %s %s)", + URIGHTY ? "left" : "right", body_part(HAND)); else /* TODO: rephrase this when obj isn't a weapon or weptool */ - Sprintf(eos(bp), " (alternate weapon%s; not wielded)", - plur(obj->quan)); + ConcatF1(bp, 0, " (alternate weapon%s; not wielded)", + plur(obj->quan)); } if (obj->owornmask & W_QUIVER) { + int Qtyp; + switch (obj->oclass) { case WEAPON_CLASS: - if (is_ammo(obj)) { - if (objects[obj->otyp].oc_skill == -P_BOW) { - /* Ammo for a bow */ - Strcat(bp, " (in quiver)"); - break; - } else { - /* Ammo not for a bow */ - Strcat(bp, " (in quiver pouch)"); - break; - } - } else { - /* Weapons not considered ammo */ - Strcat(bp, " (at the ready)"); - break; - } - /* Small things and ammo not for a bow */ + Qtyp = !is_ammo(obj) ? 3 /* not ammo: "at the ready" */ + : (objects[obj->otyp].oc_skill != -P_BOW) ? 2 /* non-bow */ + : 1; /* ammo for a bow: "in quiver" */ + break; case RING_CLASS: case AMULET_CLASS: case WAND_CLASS: case COIN_CLASS: case GEM_CLASS: - Strcat(bp, " (in quiver pouch)"); + Qtyp = 2; /* small, non-bow: "in quiver pouch" */ break; default: /* odd things */ - Strcat(bp, " (at the ready)"); + Qtyp = 3; /* "at the ready" */ + break; } + ConcatF1(bp, 0, " (%s)", + (Qtyp == 1) ? "in quiver" + : (Qtyp == 2) ? "in quiver pouch" + : "at the ready"); } + /* treat 'restoring' like suppress_price because shopkeeper and bill might not be available yet while restore is in progress (objects won't normally be formatted during that time, but if @@ -1492,22 +1590,28 @@ doname_base( if (iflags.suppress_price || gp.program_state.restoring) { ; /* don't attempt to obtain any shop pricing, even if 'with_price' */ } else if (is_unpaid(obj)) { /* in inventory or in container in invent */ + char pricebuf[40]; long quotedprice = unpaid_cost(obj, TRUE); - Sprintf(eos(bp), " (%s, %ld %s)", - obj->unpaid ? "unpaid" : "contents", - quotedprice, currency(quotedprice)); + /* separately formatted suffix avoids need for ConcatF3() */ + Sprintf(pricebuf, "%ld %s", quotedprice, currency(quotedprice)); + ConcatF2(bp, 0, " (%s, %s)", + obj->unpaid ? "unpaid" : "contents", pricebuf); } else if (with_price) { /* on floor or in container on floor */ int nochrg = 0; long price = get_cost_of_shop_item(obj, &nochrg); - if (price > 0L) - Sprintf(eos(bp), " (%s, %ld %s)", - nochrg ? "contents" : "for sale", - price, currency(price)); - else if (nochrg > 0) - Sprintf(eos(bp), " (no charge)"); + if (price > 0L) { + char pricebuf[40]; + + Sprintf(pricebuf, "%ld %s", price, currency(price)); + ConcatF2(bp, 0, " (%s, %s)", + nochrg ? "contents" : "for sale", pricebuf); + } else if (nochrg > 0) { + Concat(bp, 0, " (no charge)"); + } } + if (!strncmp(prefix, "a ", 2)) { /* save current prefix, without "a "; might be empty */ Strcpy(tmpbuf, prefix + 2); @@ -1521,12 +1625,33 @@ doname_base( "aum" is stolen from Crawl's "Arbitrary Unit of Measure" */ if (wizard && iflags.wizweight) { /* wizard mode user has asked to see object weights */ - if (with_price && (*(eos(bp)-1) == ')')) - Sprintf(eos(bp)-1, ", %u aum)", obj->owt); + if (with_price && bp_eos[-1] == ')') + ConcatF1(bp, 1, ", %u aum)", obj->owt); else - Sprintf(eos(bp), " (%u aum)", obj->owt); + ConcatF1(bp, 0, " (%u aum)", obj->owt); + + /* ConcatF1(bp) updates bp_eos + bpspaceleft; we're done with them */ + nhUse(bp_eos); + nhUse(bpspaceleft); } + bp = strprepend(bp, prefix); + + /* + * If caller intends this to be for a menu entry, make sure that + * there is some room to combine with menu selector prefix without + * exceeding BUFSZ-1. + * + * 4: width of menu entry selector text: "c - " for tty. For + * curses, that wastes a space since it only needs 3: "c) ". + * + * Reaching full BUFSZ-1 length can't happen unless both doname + * (BUFSZ-PREFIX) and strprepend (PREFIX) use up all available + * space. + */ + if (for_menu && strlen(bp) + 4 > BUFSZ - 1) + bp[BUFSZ - 1 - 4] = '\0'; + return bp; } @@ -1606,8 +1731,7 @@ corpse_xname( const char *adjective, unsigned cxn_flags) /* bitmask of CXN_xxx values */ { - /* some callers [aobjnam()] rely on prefix area that xname() sets aside */ - char *nambuf = nextobuf() + PREFIX; + char *nambuf; int omndx = otmp->corpsenm; boolean ignore_quan = (cxn_flags & CXN_SINGULAR) != 0, /* suppress "the" from "the unique monster corpse" */ @@ -1622,6 +1746,10 @@ corpse_xname( glob = (otmp->otyp != CORPSE && otmp->globby); const char *mnam; + /* some callers [aobjnam()] rely on prefix area that xname() sets aside */ + gx.xnamep = nextobuf(); + nambuf = gx.xnamep + PREFIX; + if (glob) { mnam = OBJ_NAME(objects[otmp->otyp]); /* "glob of " */ } else if (omndx == NON_PM) { /* paranoia */