From 152d9e77051f90e5aa9accdae39bc0c94e53f55d Mon Sep 17 00:00:00 2001 From: PatR Date: Sun, 11 Mar 2018 12:39:01 -0700 Subject: [PATCH 01/31] fix #H6955 - wielded potion 'object lost' panic Report classified this as 'segfault' but it's actually a controlled panic(). When hero has lycanthropy and is wielding a potion of unholy water while in human form, if that potion is boiled then it triggers a transformation to beast form which in turn causes wielded weapon to be dropped. When the code unwinds back up through potionbreathe() to destroy_item(), the boiled potion won't be found in inventory any more and useup() -> useupall() -> freeinv() -> extract_nobj() panics. --- doc/fixes36.1 | 3 +++ src/polyself.c | 19 ++++++++++++++----- src/potion.c | 9 +++++++-- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index bb8c17505..53cb563da 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -521,6 +521,9 @@ prayer boon of 'fix all troubles' could get stuck in an infinite loop for TROUBLE_STUCK_IN_WALL if there was no spot to teleport into available It shouldn't be considered hypocrisy if you speed up your pet while standing on Elbereth +fix 'object lost' panic if hero with lycanthropy but in human form is wielding + a potion of unholy water which gets boiled/exploded by fire, causing + were-transformation and drop of wielded weapon Fixes to Post-3.6.0 Problems that Were Exposed Via git Repository diff --git a/src/polyself.c b/src/polyself.c index d79f01f44..227b7d7f7 100644 --- a/src/polyself.c +++ b/src/polyself.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 polyself.c $NHDT-Date: 1513298347 2017/12/15 00:39:07 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.116 $ */ +/* NetHack 3.6 polyself.c $NHDT-Date: 1520797126 2018/03/11 19:38:46 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.117 $ */ /* Copyright (C) 1987, 1988, 1989 by Ken Arromdee */ /* NetHack may be freely redistributed. See license for details. */ @@ -964,7 +964,7 @@ int alone; { struct obj *otmp; const char *what, *which, *whichtoo; - boolean candropwep, candropswapwep; + boolean candropwep, candropswapwep, updateinv = TRUE; if (uwep) { /* !alone check below is currently superfluous but in the @@ -989,17 +989,26 @@ int alone; You("find you must %s %s %s!", what, the_your[!!strncmp(which, "corpse", 6)], which); } + /* if either uwep or wielded uswapwep is flagged as 'in_use' + then don't drop it or explicitly update inventory; leave + those actions to caller (or caller's caller, &c) */ if (u.twoweap) { otmp = uswapwep; uswapwepgone(); - if (candropswapwep) + if (otmp->in_use) + updateinv = FALSE; + else if (candropswapwep) dropx(otmp); } otmp = uwep; uwepgone(); - if (candropwep) + if (otmp->in_use) + updateinv = FALSE; + else if (candropwep) dropx(otmp); - update_inventory(); + + if (updateinv) + update_inventory(); } else if (!could_twoweap(youmonst.data)) { untwoweapon(); } diff --git a/src/potion.c b/src/potion.c index 5daa9fbcc..fc9de1d76 100644 --- a/src/potion.c +++ b/src/potion.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 potion.c $NHDT-Date: 1502753790 2017/08/14 23:36:30 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.138 $ */ +/* NetHack 3.6 potion.c $NHDT-Date: 1520797133 2018/03/11 19:38:53 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.144 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -1563,6 +1563,11 @@ register struct obj *obj; int i, ii, isdone, kn = 0; boolean cureblind = FALSE; + /* potion of unholy water might be wielded; prevent + you_were() -> drop_weapon() from dropping it so that it + remains in inventory where our caller expects it to be */ + obj->in_use = 1; + switch (obj->otyp) { case POT_RESTORE_ABILITY: case POT_GAIN_ABILITY: @@ -1714,7 +1719,7 @@ register struct obj *obj; break; */ } - /* note: no obfree() */ + /* note: no obfree() -- that's our caller's responsibility */ if (obj->dknown) { if (kn) makeknown(obj->otyp); From 4e086caceb9c4d7b714ec0c41f4a0c4e4d67d1a2 Mon Sep 17 00:00:00 2001 From: nhmall Date: Sun, 11 Mar 2018 23:29:06 -0400 Subject: [PATCH 02/31] ensure tty_curs() behaves the same whether DEBUG defined or not ensure tty_curs() behaves the same whether DEBUG defined or not when it comes to positioning the cursor. The return statement, in the debug case only, was preventing the cursor from being moved following a range check, so the next output was written whereever the cursor happened to be previously. The messaging that detects the failed range check will get written in the DEBUG defined case hopefully allowing resolution to the range check failure, but now the cmov will still be attempted just as it is in the case where DEBUG is not defined. --- win/tty/wintty.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/win/tty/wintty.c b/win/tty/wintty.c index 32dc6b3c5..0ad6d2aa6 100644 --- a/win/tty/wintty.c +++ b/win/tty/wintty.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 wintty.c $NHDT-Date: 1506908980 2017/10/02 01:49:40 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.140 $ */ +/* NetHack 3.6 wintty.c $NHDT-Date: 1520825319 2018/03/12 03:28:39 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.142 $ */ /* Copyright (c) David Cohrs, 1991 */ /* NetHack may be freely redistributed. See license for details. */ @@ -2365,7 +2365,13 @@ register int x, y; /* not xchar: perhaps xchar is unsigned and } debugpline4("bad curs positioning win %d %s (%d,%d)", window, s, x, y); - return; + /* This return statement caused a functional difference between DEBUG and + non-DEBUG operation, so it is being commented out. It caused tty_curs() + to fail to move the cursor to the location it needed to be if the x,y + range checks failed, leaving the next piece of output to be displayed + at whatever random location the cursor happened to be at prior. */ + + /* return; */ } #endif x += cw->offx; From ae6a391997149f2892d54c8a5b04f24573ce130f Mon Sep 17 00:00:00 2001 From: nhmall Date: Sun, 11 Mar 2018 23:38:00 -0400 Subject: [PATCH 03/31] fix an out of range issue on WIN32 tty --- sys/winnt/nttty.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/sys/winnt/nttty.c b/sys/winnt/nttty.c index 3ac469ea8..1d2d8c89f 100644 --- a/sys/winnt/nttty.c +++ b/sys/winnt/nttty.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 nttty.c $NHDT-Date: 1454381842 2016/02/02 02:57:22 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.67 $ */ +/* NetHack 3.6 nttty.c $NHDT-Date: 1520825872 2018/03/12 03:37:52 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.70 $ */ /* Copyright (c) NetHack PC Development Team 1993 */ /* NetHack may be freely redistributed. See license for details. */ @@ -366,11 +366,16 @@ nttty_kbhit() void get_scr_size() { + int lines, cols; + GetConsoleScreenBufferInfo(hConOut, &csbi); + + lines = csbi.srWindow.Bottom - (csbi.srWindow.Top + 1); + cols = csbi.srWindow.Right - (csbi.srWindow.Left + 1); - LI = csbi.srWindow.Bottom - (csbi.srWindow.Top + 1); - CO = csbi.srWindow.Right - (csbi.srWindow.Left + 1); - + LI = lines; + CO = min(cols, 80); + if ((LI < 25) || (CO < 80)) { COORD newcoord; From 0b6e26d9e0a85da01addd82ac93e2b760326f356 Mon Sep 17 00:00:00 2001 From: nhmall Date: Mon, 12 Mar 2018 08:47:37 -0400 Subject: [PATCH 04/31] windows spotless bit --- sys/winnt/Makefile.msc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sys/winnt/Makefile.msc b/sys/winnt/Makefile.msc index 585f8c29a..4abb7a6c5 100644 --- a/sys/winnt/Makefile.msc +++ b/sys/winnt/Makefile.msc @@ -1,4 +1,4 @@ -# NetHack 3.6 Makefile.msc $NHDT-Date: 1520177858 2018/03/04 15:37:38 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.111 $ */ +# NetHack 3.6 Makefile.msc $NHDT-Date: 1520858848 2018/03/12 12:47:28 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.112 $ */ # Copyright (c) NetHack PC Development Team 1993-2018 # #============================================================================== @@ -1239,6 +1239,7 @@ spotless: clean if exist $(O)gamedir.tag del $(O)gamedir.tag if exist $(O)nh*key.lib del $(O)nh*key.lib if exist $(O)nh*key.exp del $(O)nh*key.exp + if exist $(INCL)\win32api.h del $(INCL)\win32api.h if exist $(MSWIN)\mnsel.bmp del $(MSWIN)\mnsel.bmp if exist $(MSWIN)\mnselcnt.bmp del $(MSWIN)\mnselcnt.bmp if exist $(MSWIN)\mnunsel.bmp del $(MSWIN)\mnunsel.bmp From c28b44c27c7adb2f48bab495f12e81229f57a062 Mon Sep 17 00:00:00 2001 From: PatR Date: Tue, 13 Mar 2018 11:27:04 -0700 Subject: [PATCH 05/31] prevent pline() segfault The use of debugpline() in tty_curs() got me wondering what would happen if debugpline() was called while pline() is in progress. I don't know how to trigger the bad coordinate situation, so I put an unconditional debugpline() in the NHW_MESSAGE case of tty_putstr() and used DEBUGFILES=wintty.c to enable it. Instant segfault, and the backtrace was short and not useful so the stack might have been clobbered. I didn't spend any time trying to figure where or why the segfault occurred. Change pline() so that if it is called while the previous pline() hasn't finished yet (ie, recursively), use raw_print() and return early. The raw_print message isn't very useful--it pops up wherever the cursor happens to be, just like the cursor position bug that has been an issue recently--but does get delivered without any segualt and isn't completely useless if DUMPLOG is enabled and you save or quit before the message buffer gets recycled. Message readability situation could be improved but avoiding the segfault was my goal. Putting any debugpline() into *_raw_print() would be inadvisable.... --- doc/fixes36.1 | 2 ++ src/pline.c | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index 53cb563da..4e616831e 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -524,6 +524,8 @@ It shouldn't be considered hypocrisy if you speed up your pet while standing fix 'object lost' panic if hero with lycanthropy but in human form is wielding a potion of unholy water which gets boiled/exploded by fire, causing were-transformation and drop of wielded weapon +prevent segfault if pline() is called recursively (which could happen if the + interface code issues a debugpline() while processing putstr()) Fixes to Post-3.6.0 Problems that Were Exposed Via git Repository diff --git a/src/pline.c b/src/pline.c index 319f13946..745bd7d22 100644 --- a/src/pline.c +++ b/src/pline.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 pline.c $NHDT-Date: 1519183957 2018/02/21 03:32:37 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.65 $ */ +/* NetHack 3.6 pline.c $NHDT-Date: 1520964541 2018/03/13 18:09:01 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.66 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -96,6 +96,7 @@ void pline VA_DECL(const char *, line) #endif /* USE_STDARG | USE_VARARG */ { /* start of vpline() or of nested block in USE_OLDARG's pline() */ + static int in_pline = 0; char pbuf[3 * BUFSZ]; int ln; int msgtyp; @@ -138,11 +139,14 @@ VA_DECL(const char *, line) if ((pline_flags & SUPPRESS_HISTORY) == 0) dumplogmsg(line); #endif - - if (!iflags.window_inited) { + /* use raw_print() if we're called too early (or perhaps too late + during shutdown) or if we're being called recursively (probably + via debugpline() in the interface code) */ + if (in_pline++ || !iflags.window_inited) { + /* [we should probably be using raw_printf("\n%s", line) here] */ raw_print(line); iflags.last_msg = PLNMSG_UNKNOWN; - return; + goto pline_done; } msgtyp = MSGTYP_NORMAL; @@ -158,7 +162,7 @@ VA_DECL(const char *, line) * doing so out of context and probably end up seeming silly. * (Not an issue for no-repeat but matters for no-show.) */ - return; + goto pline_done; } if (vision_full_recalc) @@ -178,6 +182,10 @@ VA_DECL(const char *, line) if (msgtyp == MSGTYP_STOP) display_nhwindow(WIN_MESSAGE, TRUE); /* --more-- */ + pline_done: + --in_pline; + return; + #if !(defined(USE_STDARG) || defined(USE_VARARGS)) /* provide closing brace for the nested block which immediately follows USE_OLDARGS's VA_DECL() */ From b331d4643b7715c2c2df9eb8b9f24d6b4c27a082 Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Wed, 14 Mar 2018 18:44:24 +0200 Subject: [PATCH 06/31] Update Files I don't know how the automatic Files update should work, so adding the Qt4 files manually. --- Files | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Files b/Files index f2bb266aa..0e6674eec 100644 --- a/Files +++ b/Files @@ -226,11 +226,24 @@ mdgrep.pl panic.c recover.c dgn_comp.l dgn_comp.y lev_comp.l lev_comp.y win/Qt: -(files for the Qt widget library - X11, Windows, Mac OS X, or Qtopia) +(files for the Qt 3 widget library - X11, Windows, Mac OS X, or Qtopia) Info.plist Install.Qt knethack.lnk knh-mini.xpm knh.xpm nhicns.uu nhsplash.xpm qt_clust.cpp qt_win.cpp qttableview.cpp tileedit.cpp tileedit.h qpe-nethack.control +win/Qt4: +(files for the Qt 4 widget library - X11, Windows, Mac OS X) +qt4bind.cpp qt4bind.h qt4click.cpp qt4click.h qt4clust.cpp +qt4clust.h qt4delay.cpp qt4delay.h qt4glyph.cpp qt4glyph.h +qt4icon.cpp qt4icon.h qt4inv.cpp qt4inv.h qt4kde0.h +qt4key.cpp qt4key.h qt4line.cpp qt4line.h qt4main.cpp +qt4main.h qt4map.cpp qt4map.h qt4menu.cpp qt4menu.h +qt4msg.cpp qt4msg.h qt4plsel.cpp qt4plsel.h qt4rip.cpp +qt4rip.h qt4set.cpp qt4set.h qt4stat.cpp qt4stat.h +qt4str.cpp qt4streq.cpp qt4streq.h qt4str.h qt4svsel.cpp +qt4svsel.h qt4win.cpp qt4win.h qt4xcmd.cpp qt4xcmd.h +qt4yndlg.cpp qt4yndlg.h + win/X11: (files for X versions) Install.X11 NetHack.ad Window.c dialogs.c ibm.bdf @@ -287,7 +300,7 @@ tiles.mak winMS.h winhack.c winhack.rc win/win32/vs2015: (files for Visual Studio 2015 Express Edition builds) -afterdgncomp.proj afterdlb.proj afterlevcomp.proj aftermakedefs.proj +afterdgncomp.proj afterdlb.proj afterlevcomp.proj aftermakedefs.proj afternethack.proj afterrecover.proj aftertile2bmp.proj aftertilemap.proj afteruudecode.proj build.bat common.props config.props console.props default.props default_dll.props dgncomp.vcxproj @@ -389,7 +402,7 @@ uudecode.exe DEVEL: (files for people developing changes to NetHack) -code_features.txt code_style.txt Developer.txt git_recipes.txt +code_features.txt code_style.txt Developer.txt git_recipes.txt nhgitset.pl DEVEL/DOTGIT: From cbf6f83a8b04b4c1575e6616772ed65475c2785a Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Wed, 14 Mar 2018 19:02:07 +0200 Subject: [PATCH 07/31] Fix the filename in qt4 hints file --- sys/unix/hints/linux-qt4 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sys/unix/hints/linux-qt4 b/sys/unix/hints/linux-qt4 index ae3911939..34c8863d5 100644 --- a/sys/unix/hints/linux-qt4 +++ b/sys/unix/hints/linux-qt4 @@ -1,5 +1,5 @@ # -# NetHack 3.6 linux-x11 $NHDT-Date: 1432512814 2015/05/25 00:13:34 $ $NHDT-Branch: master $:$NHDT-Revision: 1.12 $ +# NetHack 3.6 linux-qt4 $NHDT-Date: 1432512814 2015/05/25 00:13:34 $ $NHDT-Branch: master $:$NHDT-Revision: 1.12 $ # Copyright (c) Kenneth Lorber, Kensington, Maryland, 2007. # NetHack may be freely redistributed. See license for details. # From a785663c58038820f63551d8e4a792969d04d311 Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Wed, 14 Mar 2018 20:06:35 +0200 Subject: [PATCH 08/31] Attempting to open down or at yourself is same as #loot Trying to open at the same location as you did nothing, make it loot instead. Apparently #looting is also annoying when using vi-keys. Based on code by aosdict --- doc/fixes36.1 | 1 + src/lock.c | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index 4e616831e..c01208ca7 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -526,6 +526,7 @@ fix 'object lost' panic if hero with lycanthropy but in human form is wielding were-transformation and drop of wielded weapon prevent segfault if pline() is called recursively (which could happen if the interface code issues a debugpline() while processing putstr()) +open at yourself is the same as #loot Fixes to Post-3.6.0 Problems that Were Exposed Via git Repository diff --git a/src/lock.c b/src/lock.c index 426e8a35f..594351124 100644 --- a/src/lock.c +++ b/src/lock.c @@ -630,8 +630,9 @@ int x, y; } else if (!get_adjacent_loc((char *) 0, (char *) 0, u.ux, u.uy, &cc)) return 0; + /* open at yourself/up/down */ if ((cc.x == u.ux) && (cc.y == u.uy)) - return 0; + return doloot(); if (stumble_on_door_mimic(cc.x, cc.y)) return 1; From aac0a21a7ebee4fb22eccdd99e81d0933cf14f30 Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Thu, 15 Mar 2018 18:42:22 +0200 Subject: [PATCH 09/31] Make graves white Making them easy to distinguish from walls. --- doc/fixes36.1 | 2 +- src/drawing.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index c01208ca7..caf40ec87 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -741,7 +741,7 @@ swallowers can't re-engulf hero immediately after spitting him/her out werejackals can summon foxes and coyotes; werewolves can summon wargs allow taming monkeys and apes with bananas GENERICUSERS is now a sysconf statement instead of compile-time option -fountains are bright blue +fountains are bright blue, graves are white ray bounceback chance depends on the wall type undead #turning takes less time at higher experience level peacefuls may react when you attack other peacefuls diff --git a/src/drawing.c b/src/drawing.c index 88bc26750..d81ffa2c9 100644 --- a/src/drawing.c +++ b/src/drawing.c @@ -166,7 +166,7 @@ const struct symdef defsyms[MAXPCHARS] = { { '<', "ladder up", C(CLR_BROWN) }, /* upladder */ { '>', "ladder down", C(CLR_BROWN) }, /* dnladder */ { '_', "altar", C(CLR_GRAY) }, /* altar */ - { '|', "grave", C(CLR_GRAY) }, /* grave */ + { '|', "grave", C(CLR_WHITE) }, /* grave */ { '\\', "opulent throne", C(HI_GOLD) }, /* throne */ /*30*/ { '#', "sink", C(CLR_GRAY) }, /* sink */ { '{', "fountain", C(CLR_BRIGHT_BLUE) }, /* fountain */ From 877f403734fdfba7f0c3447d8b85146dc7568491 Mon Sep 17 00:00:00 2001 From: PatR Date: Thu, 15 Mar 2018 13:05:08 -0700 Subject: [PATCH 10/31] fix plural of box "boxen" may be hacker slang for plural of "box", but "foxen" is definitely not the plural of "fox". Restrict the "ox"->"oxen" entry to full word "ox", not "*ox" suffix. --- doc/fixes36.1 | 1 + src/objnam.c | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index caf40ec87..73c66e65d 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -608,6 +608,7 @@ when clairvoyance lets you move the cursor to examine the map (if it occurs monster), the "for instructions type '?'" prompt could be confusing prevent Mjollnir from being auto-quivered if it's been thrown without return and then picked back up while quiver slot is empty +plural of "fox" is not "foxen" Platform- and/or Interface-Specific Fixes diff --git a/src/objnam.c b/src/objnam.c index 90c96e156..590d6a90b 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 objnam.c $NHDT-Date: 1471112245 2016/08/13 18:17:25 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.178 $ */ +/* NetHack 3.6 objnam.c $NHDT-Date: 1521144299 2018/03/15 20:04:59 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.189 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -2043,9 +2043,9 @@ static struct sing_plur one_off[] = { { "ovum", "ova" }, { "ox", "oxen" }, { "rtex", "rtices" }, /* vortex */ - { "tooth", "teeth" }, { "serum", "sera" }, { "staff", "staves" }, + { "tooth", "teeth" }, { 0, 0 } }; @@ -2065,8 +2065,7 @@ static const char *const as_is[] = { variant instead of attempting to support both. */ }; -/* singularize/pluralize decisions common to both makesingular & makeplural - */ +/* singularize/pluralize decisions common to both makesingular & makeplural */ STATIC_OVL boolean singplur_lookup(basestr, endstring, to_plural, alt_as_is) char *basestr, *endstring; /* base string, pointer to eos(string) */ @@ -2090,14 +2089,20 @@ const char *const *alt_as_is; /* another set like as_is[] */ } } - /* avoid false hit on one_off[].plur == "lice"; + /* avoid false hit on one_off[].plur == "lice" or .sing == "goose"; if more of these turn up, one_off[] entries will need to flagged as to which are whole words and which are matchable as suffices then matching in the loop below will end up becoming more complex */ if (!strcmpi(basestr, "slice") || !strcmpi(basestr, "mongoose")) { if (to_plural) - (void) strkitten(basestr, 's'); + Strcasecpy(endstring, "s"); + return TRUE; + } + /* skip "ox" -> "oxen" entry when pluralizing "ox" */ + if (to_plural && strlen(basestr) > 2 && !strcmpi(endstring - 2, "ox")) { + /* "fox" -> "foxes" */ + Strcasecpy(endstring, "es"); return TRUE; } for (sp = one_off; sp->sing; sp++) { @@ -2209,7 +2214,7 @@ const char *oldstr; spot--; while (spot > str && *spot == ' ') spot--; /* Strip blanks from end */ - *(spot + 1) = 0; + *(spot + 1) = '\0'; /* Now spot is the last character of the string */ len = strlen(str); From 467e3bfdab890b8a965fffea740c7475f993733f Mon Sep 17 00:00:00 2001 From: nhmall Date: Thu, 15 Mar 2018 22:02:41 -0400 Subject: [PATCH 11/31] more pluralization bits Handle more *man and *men cases. Some plural usage of completely made up fruit names that should be entered in singular form but have what appears to be a valid plural name it will end up singularized. Not much we can do about that for ficticious words. For instance, if you try to name your fruit bigmen or snakemen and you intended that to be the singular name, NetHack will likely singularize it to bigman or snakeman. Many real dictionary words that end in "men", however, should be handled a wee bit better now. A real word such as stamen, for example. --- src/objnam.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/src/objnam.c b/src/objnam.c index 590d6a90b..cb11464e9 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -22,6 +22,7 @@ STATIC_DCL boolean FDECL(singplur_lookup, (char *, char *, BOOLEAN_P, const char *const *)); STATIC_DCL char *FDECL(singplur_compound, (char *)); STATIC_DCL char *FDECL(xname_flags, (struct obj *, unsigned)); +STATIC_DCL boolean FDECL(badman, (const char *, BOOLEAN_P)); struct Jitem { int item; @@ -2105,6 +2106,17 @@ const char *const *alt_as_is; /* another set like as_is[] */ Strcasecpy(endstring, "es"); return TRUE; } + if (to_plural) { + if (!strcmpi(endstring - 3, "man") + && badman(basestr, to_plural)) { + Strcasecpy(endstring, "s"); + return TRUE; + } + } else { + if (!strcmpi(endstring - 3, "men") + && badman(basestr, to_plural)) + return TRUE; + } for (sp = one_off; sp->sing; sp++) { /* check whether endstring already matches */ same = to_plural ? sp->plur : sp->sing; @@ -2245,9 +2257,8 @@ const char *oldstr; /* man/men ("Wiped out all cavemen.") */ if (len >= 3 && !strcmpi(spot - 2, "man") - /* exclude shamans and humans */ - && (len < 6 || strcmpi(spot - 5, "shaman")) - && (len < 5 || strcmpi(spot - 4, "human"))) { + /* exclude shamans and humans etc */ + && !badman(str, TRUE)) { Strcasecpy(spot - 1, "en"); goto bottom; } @@ -2422,7 +2433,8 @@ const char *oldstr; } else { /* input doesn't end in 's' */ - if (!BSTRCMPI(bp, p - 3, "men")) { + if (!BSTRCMPI(bp, p - 3, "men") + && !badman(bp, FALSE)) { Strcasecpy(p - 2, "an"); goto bottom; } @@ -2449,6 +2461,50 @@ bottom: return bp; } +boolean +badman(basestr, to_plural) +const char *basestr; +boolean to_plural; /* true => makeplural, false => makesingular */ +{ + int i, al; + char *endstr; + /* these are all the prefixes for *man that don't have a *men plural */ + const char *no_men[] = { + "albu", "antihu", "anti", "ata", "auto", "cai", + "cay", "ceru", "corner", "decu", "des", "dura", "fir", + "glass", "hanu", "het", "infrahu", "inhu", "land", + "meat", "nonhu", "otto", "out", "prehu", "protohu", + "subhu", "superhu", "talis", "unhu", "sha", + "hu", "un", "le", "re", "so", "to", "at", "a", + }; + /* these are all the prefixes for *men that don't have a *man singular */ + const char *no_man[] = { + "acu", "ceru", "cogno", "cycla", "fleh", "hegu", "preno", "sonar", + "dai", "exa", "fla", "sta", "teg", "tegu", "vela", + "da", "hy", "lu", "no", "nu", "ra", "ru", "se", "vi", "ya", "o" + }; + + if (!basestr || strlen(basestr) < 4) + return FALSE; + + endstr = eos((char *)basestr); + + if (to_plural) { + for (i = 0; i < SIZE(no_men); i++) { + al = (int) strlen(no_men[i]); + if (!BSTRNCMPI(basestr, endstr - (al + 3), no_men[i], al)) + return TRUE; + } + } else { + for (i = 0; i < SIZE(no_man); i++) { + al = (int) strlen(no_man[i]); + if (!BSTRNCMPI(basestr, endstr - (al + 3), no_man[i], al)) + return TRUE; + } + } + return FALSE; +} + /* compare user string against object name string using fuzzy matching */ STATIC_OVL boolean wishymatch(u_str, o_str, retry_inverted) From c42dc273305ab126110e8613ebe7711ca7d114da Mon Sep 17 00:00:00 2001 From: nhmall Date: Thu, 15 Mar 2018 22:19:28 -0400 Subject: [PATCH 12/31] ox tweak --- src/objnam.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/objnam.c b/src/objnam.c index cb11464e9..23a520233 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -2100,8 +2100,10 @@ const char *const *alt_as_is; /* another set like as_is[] */ Strcasecpy(endstring, "s"); return TRUE; } - /* skip "ox" -> "oxen" entry when pluralizing "ox" */ - if (to_plural && strlen(basestr) > 2 && !strcmpi(endstring - 2, "ox")) { + /* skip "ox" -> "oxen" entry when pluralizing "ox" + unless it is muskox */ + if (to_plural && strlen(basestr) > 2 && !strcmpi(endstring - 2, "ox") + && strcmpi(endstring - 6, "muskox")) { /* "fox" -> "foxes" */ Strcasecpy(endstring, "es"); return TRUE; From c43b19a6d2c9b391dbd6fdf44d84bbaacc5aa058 Mon Sep 17 00:00:00 2001 From: PatR Date: Fri, 16 Mar 2018 00:39:05 -0700 Subject: [PATCH 13/31] sys/unix/gitinfo.sh Avoid '[[ ... ]]' to cater to a more rudimentary shell. --- sys/unix/gitinfo.sh | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/sys/unix/gitinfo.sh b/sys/unix/gitinfo.sh index 5cdb34715..770943e25 100755 --- a/sys/unix/gitinfo.sh +++ b/sys/unix/gitinfo.sh @@ -1,5 +1,5 @@ #!/bin/sh -# NetHack 3.6 gitinfo.sh $NHDT-Date: 1520201830 2018/03/04 22:17:10 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.0 $ +# NetHack 3.6 gitinfo.sh $NHDT-Date: 1521185933 2018/03/16 07:38:53 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.1 $ # bring dat/gitinfo.txt up to date; called from Makefile.src @@ -10,16 +10,18 @@ # this script to be skipped or to run but fail to generate dat/gitinfo.txt. # -always=0 -if [[ $1 -eq 1 || $1 == "force" || $1 == "always" ]]; then always=1; fi - # try to figure out where we are: top, one level down (expected), or sys/unix prefix=. if [ -f ../sys/unix/gitinfo.sh ]; then prefix=..; fi if [ -f ../../sys/unix/gitinfo.sh ]; then prefix=../..; fi +rungit=0 +if [ $1 -eq 1 ]; then rungit=1; fi +if [ $1 = "force" ]; then rungit=1; fi +if [ ! -f $prefix/dat/gitinfo.txt ]; then rungit=1; fi + # try to run a perl script which is part of nethack's git repository -if [[ $always -eq 1 || ! -f $prefix/dat/gitinfo.txt ]]; then +if [ $rungit -eq 1 ]; then ( cd $prefix; \ perl -IDEVEL/hooksdir -MNHgithook -e '&NHgithook::nhversioning' \ 2> /dev/null ) \ From f2d92426cc397c55a56e16a1b7bd95f11bf58285 Mon Sep 17 00:00:00 2001 From: nhmall Date: Fri, 16 Mar 2018 22:34:13 -0400 Subject: [PATCH 14/31] more plural refinement --- src/objnam.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/objnam.c b/src/objnam.c index 23a520233..aea4ec568 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 objnam.c $NHDT-Date: 1521144299 2018/03/15 20:04:59 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.189 $ */ +/* NetHack 3.6 objnam.c $NHDT-Date: 1521254016 2018/03/17 02:33:36 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.192 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -2243,7 +2243,6 @@ const char *oldstr; { static const char *const already_plural[] = { "ae", /* algae, larvae, &c */ - "men", /* also catches women, watchmen */ "matzot", 0, }; @@ -2469,7 +2468,7 @@ const char *basestr; boolean to_plural; /* true => makeplural, false => makesingular */ { int i, al; - char *endstr; + char *endstr, *spot; /* these are all the prefixes for *man that don't have a *men plural */ const char *no_men[] = { "albu", "antihu", "anti", "ata", "auto", "cai", @@ -2494,13 +2493,17 @@ boolean to_plural; /* true => makeplural, false => makesingular */ if (to_plural) { for (i = 0; i < SIZE(no_men); i++) { al = (int) strlen(no_men[i]); - if (!BSTRNCMPI(basestr, endstr - (al + 3), no_men[i], al)) + spot = endstr - (al + 3); + if (!BSTRNCMPI(basestr, spot, no_men[i], al) + && (spot == basestr || *(spot - 1) == ' ')) return TRUE; } } else { for (i = 0; i < SIZE(no_man); i++) { al = (int) strlen(no_man[i]); - if (!BSTRNCMPI(basestr, endstr - (al + 3), no_man[i], al)) + spot = endstr - (al + 3); + if (!BSTRNCMPI(basestr, spot, no_man[i], al) + && (spot == basestr || *(spot - 1) == ' ')) return TRUE; } } From e07c6b5b77c94f7c55ee93bd7912c0a950c57abc Mon Sep 17 00:00:00 2001 From: nhmall Date: Sat, 17 Mar 2018 23:05:52 -0400 Subject: [PATCH 15/31] broken large box wording change > When you try to #force a large box or chest whose lock is already broken from a > previous #force, the game tells you "There is a broken large box here, but its > lock is already broken." It's minor, but this implies that the box being broken > is separate from the lock being broken (as well as that the box itself *can* be > broken). change the wording to "lock-damaged box" and suppress ", but its lock is aleady broken" when "lock-damaged box" has already been displayed. (Nobody particularly likes the wording "lock-damaged box" either, but at least it seems less misleading) --- doc/fixes36.1 | 2 ++ src/lock.c | 15 ++++++++++++--- src/objnam.c | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index 73c66e65d..ce3ebc86e 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -609,6 +609,8 @@ when clairvoyance lets you move the cursor to examine the map (if it occurs prevent Mjollnir from being auto-quivered if it's been thrown without return and then picked back up while quiver slot is empty plural of "fox" is not "foxen" +change wording from "broken chest" to "lock-damaged chest" and suppress #force + ", but its lock is already broken" when lock-damaged was already shown Platform- and/or Interface-Specific Fixes diff --git a/src/lock.c b/src/lock.c index 594351124..99f6c75c9 100644 --- a/src/lock.c +++ b/src/lock.c @@ -548,9 +548,18 @@ doforce() xlock.box = (struct obj *) 0; for (otmp = level.objects[u.ux][u.uy]; otmp; otmp = otmp->nexthere) if (Is_box(otmp)) { - if (otmp->obroken || !otmp->olocked) { - There("is %s here, but its lock is already %s.", doname(otmp), - otmp->obroken ? "broken" : "unlocked"); + if (otmp->obroken) { + There("is %s here%s.", doname(otmp), + /* The displayed name will have already been prefixed + * with "lock-damaged" if otmp->lknown is already set + * so suppress the additional notification about the + * lock in that case. */ + !otmp->lknown ? ", but its lock is already broken" : ""); + otmp->lknown = 1; + continue; + } else if (!otmp->olocked) { + There("is %s here, but its lock is already unlocked.", + doname(otmp)); otmp->lknown = 1; continue; } diff --git a/src/objnam.c b/src/objnam.c index aea4ec568..88d105d41 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -998,7 +998,7 @@ unsigned doname_flags; /* 3.6.0 used "unlockable" here but that could be misunderstood to mean "capable of being unlocked" rather than the intended "not capable of being locked" */ - Strcat(prefix, "broken "); + Strcat(prefix, "lock-damaged "); else if (obj->olocked) Strcat(prefix, "locked "); else From e7ed6508cde1daf2c5985012c0f06fe815ae7342 Mon Sep 17 00:00:00 2001 From: nhmall Date: Sun, 18 Mar 2018 08:49:25 -0400 Subject: [PATCH 16/31] more message adjustments to chests with broken locks --- doc/fixes36.1 | 4 ++-- src/lock.c | 6 +++--- src/objnam.c | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index ce3ebc86e..51f5abcd5 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -609,8 +609,8 @@ when clairvoyance lets you move the cursor to examine the map (if it occurs prevent Mjollnir from being auto-quivered if it's been thrown without return and then picked back up while quiver slot is empty plural of "fox" is not "foxen" -change wording from "broken chest" to "lock-damaged chest" and suppress #force - ", but its lock is already broken" when lock-damaged was already shown +change wording from "broken chest" to "chest with a broken lock" and during + #force" suppress redundant info when locks state is unknown Platform- and/or Interface-Specific Fixes diff --git a/src/lock.c b/src/lock.c index 99f6c75c9..1e3e43d06 100644 --- a/src/lock.c +++ b/src/lock.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 lock.c $NHDT-Date: 1446955300 2015/11/08 04:01:40 $ $NHDT-Branch: master $:$NHDT-Revision: 1.67 $ */ +/* NetHack 3.6 lock.c $NHDT-Date: 1521377334 2018/03/18 12:48:54 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.78 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -550,8 +550,8 @@ doforce() if (Is_box(otmp)) { if (otmp->obroken) { There("is %s here%s.", doname(otmp), - /* The displayed name will have already been prefixed - * with "lock-damaged" if otmp->lknown is already set + /* The displayed name will have already stated + * "with a broken lock" if otmp->lknown is already set * so suppress the additional notification about the * lock in that case. */ !otmp->lknown ? ", but its lock is already broken" : ""); diff --git a/src/objnam.c b/src/objnam.c index 88d105d41..28c211f69 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 objnam.c $NHDT-Date: 1521254016 2018/03/17 02:33:36 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.192 $ */ +/* NetHack 3.6 objnam.c $NHDT-Date: 1521377345 2018/03/18 12:49:05 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.194 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -995,10 +995,10 @@ unsigned doname_flags; if (lknown && Is_box(obj)) { if (obj->obroken) - /* 3.6.0 used "unlockable" here but that could be misunderstood - to mean "capable of being unlocked" rather than the intended - "not capable of being locked" */ - Strcat(prefix, "lock-damaged "); + /* 3.6.0 used an "unlockable" prefix here but that could be + misunderstood to mean "capable of being unlocked" rather + than the intended "not capable of being locked" */ + Strcat(bp, " with a broken lock"); else if (obj->olocked) Strcat(prefix, "locked "); else From 168d5171b772e48fe3e49ba08b8864af547cc3fa Mon Sep 17 00:00:00 2001 From: nhmall Date: Sun, 18 Mar 2018 08:52:34 -0400 Subject: [PATCH 17/31] typo in fixes36.1 locks -> lock --- doc/fixes36.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index 51f5abcd5..8c206c9b6 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -610,7 +610,7 @@ prevent Mjollnir from being auto-quivered if it's been thrown without return and then picked back up while quiver slot is empty plural of "fox" is not "foxen" change wording from "broken chest" to "chest with a broken lock" and during - #force" suppress redundant info when locks state is unknown + #force" suppress redundant info when lock state is unknown Platform- and/or Interface-Specific Fixes From 8ce08d3cf90640b7567d2b9e7e0a215f5d9ff505 Mon Sep 17 00:00:00 2001 From: nhmall Date: Sun, 18 Mar 2018 09:09:08 -0400 Subject: [PATCH 18/31] 3rd time fixes36.1 --- doc/fixes36.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index 8c206c9b6..fc7fbaa95 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -610,7 +610,7 @@ prevent Mjollnir from being auto-quivered if it's been thrown without return and then picked back up while quiver slot is empty plural of "fox" is not "foxen" change wording from "broken chest" to "chest with a broken lock" and during - #force" suppress redundant info when lock state is unknown + #force" suppress redundant info when lock state is already known Platform- and/or Interface-Specific Fixes From d8b12eefe7873000d8eb61e55b447cbf23ba2ee3 Mon Sep 17 00:00:00 2001 From: nhmall Date: Sun, 18 Mar 2018 09:24:38 -0400 Subject: [PATCH 19/31] just a couple of additional pluralization bits --- src/objnam.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/objnam.c b/src/objnam.c index 28c211f69..01adc2943 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -2043,6 +2043,7 @@ static struct sing_plur one_off[] = { { "nemesis", "nemeses" }, { "ovum", "ova" }, { "ox", "oxen" }, + { "passerby", "passersby" }, { "rtex", "rtices" }, /* vortex */ { "serum", "sera" }, { "staff", "staves" }, @@ -2055,11 +2056,11 @@ static const char *const as_is[] = { "boots", "shoes", "gloves", "lenses", "scales", "eyes", "gauntlets", "iron bars", /* both singular and plural are spelled the same */ - "deer", "elk", "fish", "tuna", "yaki", - "-hai", "krill", "manes", "moose", "ninja", - "sheep", "ronin", "roshi", "shito", "tengu", - "ki-rin", "Nazgul", "gunyoki", "piranha", "samurai", - "shuriken", 0, + "bison", "deer", "elk", "fish", "fowl", + "tuna", "yaki", "-hai", "krill", "manes", + "moose", "ninja", "sheep", "ronin", "roshi", + "shito", "tengu", "ki-rin", "Nazgul", "gunyoki", + "piranha", "samurai", "shuriken", 0, /* Note: "fish" and "piranha" are collective plurals, suitable for "wiped out all ". For "3 ", they should be "fishes" and "piranhas" instead. We settle for collective From 1ab2c25c9a0d7cc58e1c49636096bb66bdc3851f Mon Sep 17 00:00:00 2001 From: nhmall Date: Sun, 18 Mar 2018 09:54:18 -0400 Subject: [PATCH 20/31] another dictionary consulted, another couple of prefixes --- objnam.c | 4062 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 4062 insertions(+) create mode 100644 objnam.c diff --git a/objnam.c b/objnam.c new file mode 100644 index 000000000..ecb5de377 --- /dev/null +++ b/objnam.c @@ -0,0 +1,4062 @@ +/* NetHack 3.6 objnam.c $NHDT-Date: 1521377345 2018/03/18 12:49:05 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.194 $ */ +/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ +/* NetHack may be freely redistributed. See license for details. */ + +#include "hack.h" + +/* "an uncursed greased partly eaten guardian naga hatchling [corpse]" */ +#define PREFIX 80 /* (56) */ +#define SCHAR_LIM 127 +#define NUMOBUF 12 + +STATIC_DCL char *FDECL(strprepend, (char *, const char *)); +STATIC_DCL short FDECL(rnd_otyp_by_wpnskill, (SCHAR_P)); +STATIC_DCL short FDECL(rnd_otyp_by_namedesc, (char *, CHAR_P)); +STATIC_DCL boolean FDECL(wishymatch, (const char *, const char *, BOOLEAN_P)); +STATIC_DCL char *NDECL(nextobuf); +STATIC_DCL void FDECL(releaseobuf, (char *)); +STATIC_DCL char *FDECL(minimal_xname, (struct obj *)); +STATIC_DCL void FDECL(add_erosion_words, (struct obj *, char *)); +STATIC_DCL char *FDECL(doname_base, (struct obj *obj, unsigned)); +STATIC_DCL boolean FDECL(singplur_lookup, (char *, char *, BOOLEAN_P, + const char *const *)); +STATIC_DCL char *FDECL(singplur_compound, (char *)); +STATIC_DCL char *FDECL(xname_flags, (struct obj *, unsigned)); +STATIC_DCL boolean FDECL(badman, (const char *, BOOLEAN_P)); + +struct Jitem { + int item; + const char *name; +}; + +#define BSTRCMPI(base, ptr, str) ((ptr) < base || strcmpi((ptr), str)) +#define BSTRNCMPI(base, ptr, str, num) \ + ((ptr) < base || strncmpi((ptr), str, num)) +#define Strcasecpy(dst, src) (void) strcasecpy(dst, src) + +/* true for gems/rocks that should have " stone" appended to their names */ +#define GemStone(typ) \ + (typ == FLINT \ + || (objects[typ].oc_material == GEMSTONE \ + && (typ != DILITHIUM_CRYSTAL && typ != RUBY && typ != DIAMOND \ + && typ != SAPPHIRE && typ != BLACK_OPAL && typ != EMERALD \ + && typ != OPAL))) + +STATIC_OVL struct Jitem Japanese_items[] = { { SHORT_SWORD, "wakizashi" }, + { BROADSWORD, "ninja-to" }, + { FLAIL, "nunchaku" }, + { GLAIVE, "naginata" }, + { LOCK_PICK, "osaku" }, + { WOODEN_HARP, "koto" }, + { KNIFE, "shito" }, + { PLATE_MAIL, "tanko" }, + { HELMET, "kabuto" }, + { LEATHER_GLOVES, "yugake" }, + { FOOD_RATION, "gunyoki" }, + { POT_BOOZE, "sake" }, + { 0, "" } }; + +STATIC_DCL const char *FDECL(Japanese_item_name, (int i)); + +STATIC_OVL char * +strprepend(s, pref) +register char *s; +register const char *pref; +{ + register int i = (int) strlen(pref); + + if (i > PREFIX) { + impossible("PREFIX too short (for %d).", i); + return s; + } + s -= i; + (void) strncpy(s, pref, i); /* do not copy trailing 0 */ + return s; +} + +/* manage a pool of BUFSZ buffers, so callers don't have to */ +static char NEARDATA obufs[NUMOBUF][BUFSZ]; +static int obufidx = 0; + +STATIC_OVL char * +nextobuf() +{ + obufidx = (obufidx + 1) % NUMOBUF; + return obufs[obufidx]; +} + +/* put the most recently allocated buffer back if possible */ +STATIC_OVL void +releaseobuf(bufp) +char *bufp; +{ + /* caller may not know whether bufp is the most recently allocated + buffer; if it isn't, do nothing; note that because of the somewhat + obscure PREFIX handling for object name formatting by xname(), + the pointer our caller has and is passing to us might be into the + middle of an obuf rather than the address returned by nextobuf() */ + if (bufp >= obufs[obufidx] + && bufp < obufs[obufidx] + sizeof obufs[obufidx]) /* obufs[][BUFSZ] */ + obufidx = (obufidx - 1 + NUMOBUF) % NUMOBUF; +} + +char * +obj_typename(otyp) +register int otyp; +{ + char *buf = nextobuf(); + struct objclass *ocl = &objects[otyp]; + const char *actualn = OBJ_NAME(*ocl); + const char *dn = OBJ_DESCR(*ocl); + const char *un = ocl->oc_uname; + int nn = ocl->oc_name_known; + + if (Role_if(PM_SAMURAI) && Japanese_item_name(otyp)) + actualn = Japanese_item_name(otyp); + switch (ocl->oc_class) { + case COIN_CLASS: + Strcpy(buf, "coin"); + break; + case POTION_CLASS: + Strcpy(buf, "potion"); + break; + case SCROLL_CLASS: + Strcpy(buf, "scroll"); + break; + case WAND_CLASS: + Strcpy(buf, "wand"); + break; + case SPBOOK_CLASS: + if (otyp != SPE_NOVEL) { + Strcpy(buf, "spellbook"); + } else { + Strcpy(buf, !nn ? "book" : "novel"); + nn = 0; + } + break; + case RING_CLASS: + Strcpy(buf, "ring"); + break; + case AMULET_CLASS: + if (nn) + Strcpy(buf, actualn); + else + Strcpy(buf, "amulet"); + if (un) + Sprintf(eos(buf), " called %s", un); + if (dn) + Sprintf(eos(buf), " (%s)", dn); + return buf; + default: + if (nn) { + Strcpy(buf, actualn); + if (GemStone(otyp)) + Strcat(buf, " stone"); + if (un) + Sprintf(eos(buf), " called %s", un); + if (dn) + Sprintf(eos(buf), " (%s)", dn); + } else { + Strcpy(buf, dn ? dn : actualn); + if (ocl->oc_class == GEM_CLASS) + Strcat(buf, + (ocl->oc_material == MINERAL) ? " stone" : " gem"); + if (un) + Sprintf(eos(buf), " called %s", un); + } + return buf; + } + /* here for ring/scroll/potion/wand */ + if (nn) { + if (ocl->oc_unique) + Strcpy(buf, actualn); /* avoid spellbook of Book of the Dead */ + else + Sprintf(eos(buf), " of %s", actualn); + } + if (un) + Sprintf(eos(buf), " called %s", un); + if (dn) + Sprintf(eos(buf), " (%s)", dn); + return buf; +} + +/* less verbose result than obj_typename(); either the actual name + or the description (but not both); user-assigned name is ignored */ +char * +simple_typename(otyp) +int otyp; +{ + char *bufp, *pp, *save_uname = objects[otyp].oc_uname; + + objects[otyp].oc_uname = 0; /* suppress any name given by user */ + bufp = obj_typename(otyp); + objects[otyp].oc_uname = save_uname; + if ((pp = strstri(bufp, " (")) != 0) + *pp = '\0'; /* strip the appended description */ + return bufp; +} + +boolean +obj_is_pname(obj) +struct obj *obj; +{ + if (!obj->oartifact || !has_oname(obj)) + return FALSE; + if (!program_state.gameover && !iflags.override_ID) { + if (not_fully_identified(obj)) + return FALSE; + } + return TRUE; +} + +/* used by distant_name() to pass extra information to xname_flags(); + it would be much cleaner if this were a parameter, but that would + require all of the xname() and doname() calls to be modified */ +static int distantname = 0; + +/* Give the name of an object seen at a distance. Unlike xname/doname, + * we don't want to set dknown if it's not set already. + */ +char * +distant_name(obj, func) +struct obj *obj; +char *FDECL((*func), (OBJ_P)); +{ + char *str; + + /* 3.6.1: this used to save Blind, set it, make the call, then restore + * the saved value; but the Eyes of the Overworld override blindness + * and let characters wearing them get dknown set for distant items. + * + * TODO? if the hero is wearing those Eyes, figure out whether the + * object is within X-ray radius and only treat it as distant when + * beyond that radius. Logic is iffy but result might be interesting. + */ + ++distantname; + str = (*func)(obj); + --distantname; + return str; +} + +/* convert player specified fruit name into corresponding fruit juice name + ("slice of pizza" -> "pizza juice" rather than "slice of pizza juice") */ +char * +fruitname(juice) +boolean juice; /* whether or not to append " juice" to the name */ +{ + char *buf = nextobuf(); + const char *fruit_nam = strstri(pl_fruit, " of "); + + if (fruit_nam) + fruit_nam += 4; /* skip past " of " */ + else + fruit_nam = pl_fruit; /* use it as is */ + + Sprintf(buf, "%s%s", makesingular(fruit_nam), juice ? " juice" : ""); + return buf; +} + +/* look up a named fruit by index (1..127) */ +struct fruit * +fruit_from_indx(indx) +int indx; +{ + struct fruit *f; + + for (f = ffruit; f; f = f->nextf) + if (f->fid == indx) + break; + return f; +} + +/* look up a named fruit by name */ +struct fruit * +fruit_from_name(fname, exact, highest_fid) +const char *fname; +boolean exact; /* False => prefix or exact match, True = exact match only */ +int *highest_fid; /* optional output; only valid if 'fname' isn't found */ +{ + struct fruit *f, *tentativef; + char *altfname; + unsigned k; + /* + * note: named fruits are case-senstive... + */ + + if (highest_fid) + *highest_fid = 0; + /* first try for an exact match */ + for (f = ffruit; f; f = f->nextf) + if (!strcmp(f->fname, fname)) + return f; + else if (highest_fid && f->fid > *highest_fid) + *highest_fid = f->fid; + + /* didn't match as-is; if caller is willing to accept a prefix + match, try to find one; we want to find the longest prefix that + matches, not the first */ + if (!exact) { + tentativef = 0; + for (f = ffruit; f; f = f->nextf) { + k = strlen(f->fname); + if (!strncmp(f->fname, fname, k) + && (!fname[k] || fname[k] == ' ') + && (!tentativef || k > strlen(tentativef->fname))) + tentativef = f; + } + f = tentativef; + } + /* if we still don't have a match, try singularizing the target; + for exact match, that's trivial, but for prefix, it's hard */ + if (!f) { + altfname = makesingular(fname); + for (f = ffruit; f; f = f->nextf) { + if (!strcmp(f->fname, altfname)) + break; + } + releaseobuf(altfname); + } + if (!f && !exact) { + char fnamebuf[BUFSZ], *p; + unsigned fname_k = strlen(fname); /* length of assumed plural fname */ + + tentativef = 0; + for (f = ffruit; f; f = f->nextf) { + k = strlen(f->fname); + /* reload fnamebuf[] each iteration in case it gets modified; + there's no need to recalculate fname_k */ + Strcpy(fnamebuf, fname); + /* bug? if singular of fname is longer than plural, + failing the 'fname_k > k' test could skip a viable + candidate; unfortunately, we can't singularize until + after stripping off trailing stuff and we can't get + accurate fname_k until fname has been singularized; + compromise and use 'fname_k >= k' instead of '>', + accepting 1 char length discrepancy without risking + false match (I hope...) */ + if (fname_k >= k && (p = index(&fnamebuf[k], ' ')) != 0) { + *p = '\0'; /* truncate at 1st space past length of f->fname */ + altfname = makesingular(fnamebuf); + k = strlen(altfname); /* actually revised 'fname_k' */ + if (!strcmp(f->fname, altfname) + && (!tentativef || k > strlen(tentativef->fname))) + tentativef = f; + releaseobuf(altfname); /* avoid churning through all obufs */ + } + } + f = tentativef; + } + return f; +} + +/* sort the named-fruit linked list by fruit index number */ +void +reorder_fruit(forward) +boolean forward; +{ + struct fruit *f, *allfr[1 + 127]; + int i, j, k = SIZE(allfr); + + for (i = 0; i < k; ++i) + allfr[i] = (struct fruit *) 0; + for (f = ffruit; f; f = f->nextf) { + /* without sanity checking, this would reduce to 'allfr[f->fid]=f' */ + j = f->fid; + if (j < 1 || j >= k) { + impossible("reorder_fruit: fruit index (%d) out of range", j); + return; /* don't sort after all; should never happen... */ + } else if (allfr[j]) { + impossible("reorder_fruit: duplicate fruit index (%d)", j); + return; + } + allfr[j] = f; + } + ffruit = 0; /* reset linked list; we're rebuilding it from scratch */ + /* slot [0] will always be empty; must start 'i' at 1 to avoid + [k - i] being out of bounds during first iteration */ + for (i = 1; i < k; ++i) { + /* for forward ordering, go through indices from high to low; + for backward ordering, go from low to high */ + j = forward ? (k - i) : i; + if (allfr[j]) { + allfr[j]->nextf = ffruit; + ffruit = allfr[j]; + } + } +} + +char * +xname(obj) +struct obj *obj; +{ + return xname_flags(obj, CXN_NORMAL); +} + +char * +xname_flags(obj, cxn_flags) +register struct obj *obj; +unsigned cxn_flags; /* bitmask of CXN_xxx values */ +{ + register char *buf; + register int typ = obj->otyp; + register struct objclass *ocl = &objects[typ]; + int nn = ocl->oc_name_known, omndx = obj->corpsenm; + const char *actualn = OBJ_NAME(*ocl); + const char *dn = OBJ_DESCR(*ocl) ? OBJ_DESCR(*ocl) : actualn; + const char *un = ocl->oc_uname; + boolean pluralize = (obj->quan != 1L) && !(cxn_flags & CXN_SINGULAR); + boolean known, dknown, bknown; + + buf = nextobuf() + PREFIX; /* leave room for "17 -3 " */ + if (Role_if(PM_SAMURAI) && Japanese_item_name(typ)) + actualn = Japanese_item_name(typ); + + 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 + * printed for the object is tied to the combination of the two + * and printing the wrong article gives away information. + */ + if (!nn && ocl->oc_uses_known && ocl->oc_unique) + obj->known = 0; + if (!Blind && !distantname) + obj->dknown = TRUE; + if (Role_if(PM_PRIEST)) + obj->bknown = TRUE; + + if (iflags.override_ID) { + known = dknown = bknown = TRUE; + nn = 1; + } else { + known = obj->known; + dknown = obj->dknown; + bknown = obj->bknown; + } + + if (obj_is_pname(obj)) + goto nameit; + switch (obj->oclass) { + case AMULET_CLASS: + if (!dknown) + Strcpy(buf, "amulet"); + else if (typ == AMULET_OF_YENDOR || typ == FAKE_AMULET_OF_YENDOR) + /* each must be identified individually */ + Strcpy(buf, known ? actualn : dn); + else if (nn) + Strcpy(buf, actualn); + else if (un) + Sprintf(buf, "amulet called %s", un); + else + Sprintf(buf, "%s amulet", dn); + break; + case WEAPON_CLASS: + if (is_poisonable(obj) && obj->opoisoned) + Strcpy(buf, "poisoned "); + case VENOM_CLASS: + case TOOL_CLASS: + if (typ == LENSES) + Strcpy(buf, "pair of "); + else if (is_wet_towel(obj)) + Strcpy(buf, (obj->spe < 3) ? "moist " : "wet "); + + if (!dknown) + Strcat(buf, dn); + else if (nn) + Strcat(buf, actualn); + else if (un) { + Strcat(buf, dn); + Strcat(buf, " called "); + Strcat(buf, un); + } else + Strcat(buf, dn); + /* If we use an() here we'd have to remember never to use */ + /* it whenever calling doname() or xname(). */ + if (typ == FIGURINE && omndx != NON_PM) { + Sprintf(eos(buf), " of a%s %s", + index(vowels, *mons[omndx].mname) ? "n" : "", + mons[omndx].mname); + } else if (is_wet_towel(obj)) { + if (wizard) + Sprintf(eos(buf), " (%d)", obj->spe); + } + break; + case ARMOR_CLASS: + /* depends on order of the dragon scales objects */ + if (typ >= GRAY_DRAGON_SCALES && typ <= YELLOW_DRAGON_SCALES) { + Sprintf(buf, "set of %s", actualn); + break; + } + if (is_boots(obj) || is_gloves(obj)) + Strcpy(buf, "pair of "); + + if (obj->otyp >= ELVEN_SHIELD && obj->otyp <= ORCISH_SHIELD + && !dknown) { + Strcpy(buf, "shield"); + break; + } + if (obj->otyp == SHIELD_OF_REFLECTION && !dknown) { + Strcpy(buf, "smooth shield"); + break; + } + + if (nn) + Strcat(buf, actualn); + else if (un) { + if (is_boots(obj)) + Strcat(buf, "boots"); + else if (is_gloves(obj)) + Strcat(buf, "gloves"); + else if (is_cloak(obj)) + Strcpy(buf, "cloak"); + else if (is_helmet(obj)) + Strcpy(buf, "helmet"); + else if (is_shield(obj)) + Strcpy(buf, "shield"); + else + Strcpy(buf, "armor"); + Strcat(buf, " called "); + Strcat(buf, un); + } else + Strcat(buf, dn); + break; + case FOOD_CLASS: + if (typ == SLIME_MOLD) { + struct fruit *f = fruit_from_indx(obj->spe); + + if (!f) { + impossible("Bad fruit #%d?", obj->spe); + Strcpy(buf, "fruit"); + } else { + Strcpy(buf, f->fname); + if (pluralize) { + /* ick; already pluralized fruit names + are allowed--we want to try to avoid + adding a redundant plural suffix */ + Strcpy(buf, makeplural(makesingular(buf))); + pluralize = FALSE; + } + } + break; + } + if (obj->globby) { + Sprintf(buf, "%s%s", + (obj->owt <= 100) + ? "small " + : (obj->owt > 500) + ? "very large " + : (obj->owt > 300) + ? "large " + : "", + actualn); + break; + } + + Strcpy(buf, actualn); + if (typ == TIN && known) + tin_details(obj, omndx, buf); + break; + case COIN_CLASS: + case CHAIN_CLASS: + Strcpy(buf, actualn); + break; + case ROCK_CLASS: + if (typ == STATUE && omndx != NON_PM) + Sprintf(buf, "%s%s of %s%s", + (Role_if(PM_ARCHEOLOGIST) && (obj->spe & STATUE_HISTORIC)) + ? "historic " + : "", + actualn, + type_is_pname(&mons[omndx]) + ? "" + : the_unique_pm(&mons[omndx]) + ? "the " + : index(vowels, *mons[omndx].mname) + ? "an " + : "a ", + mons[omndx].mname); + else + Strcpy(buf, actualn); + break; + case BALL_CLASS: + Sprintf(buf, "%sheavy iron ball", + (obj->owt > ocl->oc_weight) ? "very " : ""); + break; + case POTION_CLASS: + if (dknown && obj->odiluted) + Strcpy(buf, "diluted "); + if (nn || un || !dknown) { + Strcat(buf, "potion"); + if (!dknown) + break; + if (nn) { + Strcat(buf, " of "); + if (typ == POT_WATER && bknown + && (obj->blessed || obj->cursed)) { + Strcat(buf, obj->blessed ? "holy " : "unholy "); + } + Strcat(buf, actualn); + } else { + Strcat(buf, " called "); + Strcat(buf, un); + } + } else { + Strcat(buf, dn); + Strcat(buf, " potion"); + } + break; + case SCROLL_CLASS: + Strcpy(buf, "scroll"); + if (!dknown) + break; + if (nn) { + Strcat(buf, " of "); + Strcat(buf, actualn); + } else if (un) { + Strcat(buf, " called "); + Strcat(buf, un); + } else if (ocl->oc_magic) { + Strcat(buf, " labeled "); + Strcat(buf, dn); + } else { + Strcpy(buf, dn); + Strcat(buf, " scroll"); + } + break; + case WAND_CLASS: + if (!dknown) + Strcpy(buf, "wand"); + else if (nn) + Sprintf(buf, "wand of %s", actualn); + else if (un) + Sprintf(buf, "wand called %s", un); + else + Sprintf(buf, "%s wand", dn); + break; + case SPBOOK_CLASS: + if (typ == SPE_NOVEL) { /* 3.6 tribute */ + if (!dknown) + Strcpy(buf, "book"); + else if (nn) + Strcpy(buf, actualn); + else if (un) + Sprintf(buf, "novel called %s", un); + else + Sprintf(buf, "%s book", dn); + break; + /* end of tribute */ + } else if (!dknown) { + Strcpy(buf, "spellbook"); + } else if (nn) { + if (typ != SPE_BOOK_OF_THE_DEAD) + Strcpy(buf, "spellbook of "); + Strcat(buf, actualn); + } else if (un) { + Sprintf(buf, "spellbook called %s", un); + } else + Sprintf(buf, "%s spellbook", dn); + break; + case RING_CLASS: + if (!dknown) + Strcpy(buf, "ring"); + else if (nn) + Sprintf(buf, "ring of %s", actualn); + else if (un) + Sprintf(buf, "ring called %s", un); + else + Sprintf(buf, "%s ring", dn); + break; + case GEM_CLASS: { + const char *rock = (ocl->oc_material == MINERAL) ? "stone" : "gem"; + + if (!dknown) { + Strcpy(buf, rock); + } else if (!nn) { + if (un) + Sprintf(buf, "%s called %s", rock, un); + else + Sprintf(buf, "%s %s", dn, rock); + } else { + Strcpy(buf, actualn); + if (GemStone(typ)) + Strcat(buf, " stone"); + } + break; + } + default: + Sprintf(buf, "glorkum %d %d %d", obj->oclass, typ, obj->spe); + } + if (pluralize) + Strcpy(buf, makeplural(buf)); + + if (obj->otyp == T_SHIRT && program_state.gameover) { + char tmpbuf[BUFSZ]; + + Sprintf(eos(buf), " with text \"%s\"", tshirt_text(obj, tmpbuf)); + } + + if (has_oname(obj) && dknown) { + Strcat(buf, " named "); + nameit: + Strcat(buf, ONAME(obj)); + } + + if (!strncmpi(buf, "the ", 4)) + buf += 4; + return buf; +} + +/* similar to simple_typename but minimal_xname operates on a particular + object rather than its general type; it formats the most basic info: + potion -- if description not known + brown potion -- if oc_name_known not set + potion of object detection -- if discovered + */ +STATIC_OVL char * +minimal_xname(obj) +struct obj *obj; +{ + char *bufp; + struct obj bareobj; + struct objclass saveobcls; + int otyp = obj->otyp; + + /* suppress user-supplied name */ + saveobcls.oc_uname = objects[otyp].oc_uname; + objects[otyp].oc_uname = 0; + /* suppress actual name if object's description is unknown */ + saveobcls.oc_name_known = objects[otyp].oc_name_known; + if (!obj->dknown) + objects[otyp].oc_name_known = 0; + + /* caveat: this makes a lot of assumptions about which fields + are required in order for xname() to yield a sensible result */ + bareobj = zeroobj; + bareobj.otyp = otyp; + bareobj.oclass = obj->oclass; + bareobj.dknown = obj->dknown; + /* suppress known except for amulets (needed for fakes and real A-of-Y) */ + bareobj.known = (obj->oclass == AMULET_CLASS) + ? obj->known + /* default is "on" for types which don't use it */ + : !objects[otyp].oc_uses_known; + bareobj.quan = 1L; /* don't want plural */ + bareobj.corpsenm = NON_PM; /* suppress statue and figurine details */ + /* but suppressing fruit details leads to "bad fruit #0" + [perhaps we should force "slime mold" rather than use xname?] */ + if (obj->otyp == SLIME_MOLD) + bareobj.spe = obj->spe; + + bufp = distant_name(&bareobj, xname); /* xname(&bareobj) */ + if (!strncmp(bufp, "uncursed ", 9)) + bufp += 9; /* Role_if(PM_PRIEST) */ + + objects[otyp].oc_uname = saveobcls.oc_uname; + objects[otyp].oc_name_known = saveobcls.oc_name_known; + return bufp; +} + +/* xname() output augmented for multishot missile feedback */ +char * +mshot_xname(obj) +struct obj *obj; +{ + char tmpbuf[BUFSZ]; + char *onm = xname(obj); + + if (m_shot.n > 1 && m_shot.o == obj->otyp) { + /* "the Nth arrow"; value will eventually be passed to an() or + The(), both of which correctly handle this "the " prefix */ + Sprintf(tmpbuf, "the %d%s ", m_shot.i, ordin(m_shot.i)); + onm = strprepend(onm, tmpbuf); + } + return onm; +} + +/* used for naming "the unique_item" instead of "a unique_item" */ +boolean +the_unique_obj(obj) +struct obj *obj; +{ + boolean known = (obj->known || iflags.override_ID); + + if (!obj->dknown && !iflags.override_ID) + return FALSE; + else if (obj->otyp == FAKE_AMULET_OF_YENDOR && !known) + return TRUE; /* lie */ + else + return (boolean) (objects[obj->otyp].oc_unique + && (known || obj->otyp == AMULET_OF_YENDOR)); +} + +/* should monster type be prefixed with "the"? (mostly used for corpses) */ +boolean +the_unique_pm(ptr) +struct permonst *ptr; +{ + boolean uniq; + + /* even though monsters with personal names are unique, we want to + describe them as "Name" rather than "the Name" */ + if (type_is_pname(ptr)) + return FALSE; + + uniq = (ptr->geno & G_UNIQ) ? TRUE : FALSE; + /* high priest is unique if it includes "of ", otherwise not + (caller needs to handle the 1st possibility; we assume the 2nd); + worm tail should be irrelevant but is included for completeness */ + if (ptr == &mons[PM_HIGH_PRIEST] || ptr == &mons[PM_LONG_WORM_TAIL]) + uniq = FALSE; + /* Wizard no longer needs this; he's flagged as unique these days */ + if (ptr == &mons[PM_WIZARD_OF_YENDOR]) + uniq = TRUE; + return uniq; +} + +STATIC_OVL void +add_erosion_words(obj, prefix) +struct obj *obj; +char *prefix; +{ + boolean iscrys = (obj->otyp == CRYSKNIFE); + boolean rknown; + + rknown = (iflags.override_ID == 0) ? obj->rknown : TRUE; + + if (!is_damageable(obj) && !iscrys) + return; + + /* The only cases where any of these bits do double duty are for + * rotted food and diluted potions, which are all not is_damageable(). + */ + if (obj->oeroded && !iscrys) { + switch (obj->oeroded) { + case 2: + Strcat(prefix, "very "); + break; + case 3: + Strcat(prefix, "thoroughly "); + break; + } + Strcat(prefix, is_rustprone(obj) ? "rusty " : "burnt "); + } + if (obj->oeroded2 && !iscrys) { + switch (obj->oeroded2) { + case 2: + Strcat(prefix, "very "); + break; + case 3: + Strcat(prefix, "thoroughly "); + break; + } + Strcat(prefix, is_corrodeable(obj) ? "corroded " : "rotted "); + } + if (rknown && obj->oerodeproof) + Strcat(prefix, iscrys + ? "fixed " + : is_rustprone(obj) + ? "rustproof " + : is_corrodeable(obj) + ? "corrodeproof " /* "stainless"? */ + : is_flammable(obj) + ? "fireproof " + : ""); +} + +/* used to prevent rust on items where rust makes no difference */ +boolean +erosion_matters(obj) +struct obj *obj; +{ + switch (obj->oclass) { + case TOOL_CLASS: + /* it's possible for a rusty weptool to be polymorphed into some + non-weptool iron tool, in which case the rust implicitly goes + away, but it's also possible for it to be polymorphed into a + non-iron tool, in which case rust also implicitly goes away, + so there's no particular reason to try to handle the first + instance differently [this comment belongs in poly_obj()...] */ + return is_weptool(obj) ? TRUE : FALSE; + case WEAPON_CLASS: + case ARMOR_CLASS: + case BALL_CLASS: + case CHAIN_CLASS: + return TRUE; + default: + break; + } + return FALSE; +} + +#define DONAME_WITH_PRICE 1 +#define DONAME_VAGUE_QUAN 2 + +STATIC_OVL char * +doname_base(obj, doname_flags) +struct obj *obj; +unsigned doname_flags; +{ + boolean ispoisoned = FALSE, + with_price = (doname_flags & DONAME_WITH_PRICE) != 0, + vague_quan = (doname_flags & DONAME_VAGUE_QUAN) != 0; + boolean known, dknown, cknown, bknown, lknown; + int omndx = obj->corpsenm; + char prefix[PREFIX]; + char tmpbuf[PREFIX + 1]; /* for when we have to add something at + the start of prefix instead of the + end (Strcat is used on the end) */ + register char *bp = xname(obj); + + if (iflags.override_ID) { + known = dknown = cknown = bknown = lknown = TRUE; + } else { + known = obj->known; + dknown = obj->dknown; + cknown = obj->cknown; + bknown = obj->bknown; + lknown = obj->lknown; + } + + /* When using xname, we want "poisoned arrow", and when using + * doname, we want "poisoned +0 arrow". This kludge is about the only + * way to do it, at least until someone overhauls xname() and doname(), + * combining both into one function taking a parameter. + */ + /* must check opoisoned--someone can have a weirdly-named fruit */ + if (!strncmp(bp, "poisoned ", 9) && obj->opoisoned) { + bp += 9; + ispoisoned = TRUE; + } + + if (obj->quan != 1L) { + if (dknown || !vague_quan) + Sprintf(prefix, "%ld ", obj->quan); + else + Strcpy(prefix, "some "); + } else if (obj->otyp == CORPSE) { + /* skip article prefix for corpses [else corpse_xname() + would have to be taught how to strip it off again] */ + *prefix = '\0'; + } else if (obj_is_pname(obj) || the_unique_obj(obj)) { + if (!strncmpi(bp, "the ", 4)) + bp += 4; + Strcpy(prefix, "the "); + } else { + Strcpy(prefix, "a "); + } + + /* "empty" goes at the beginning, but item count goes at the end */ + if (cknown + /* bag of tricks: include "empty" prefix if it's known to + be empty but its precise number of charges isn't known + (when that is known, suffix of "(n:0)" will be appended, + making the prefix be redundant; note that 'known' flag + isn't set when emptiness gets discovered because then + charging magic would yield known number of new charges) */ + && ((obj->otyp == BAG_OF_TRICKS) + ? (obj->spe == 0 && !obj->known) + /* not bag of tricks: empty if container which has no contents */ + : ((Is_container(obj) || obj->otyp == STATUE) + && !Has_contents(obj)))) + Strcat(prefix, "empty "); + + if (bknown && obj->oclass != COIN_CLASS + && (obj->otyp != POT_WATER || !objects[POT_WATER].oc_name_known + || (!obj->cursed && !obj->blessed))) { + /* allow 'blessed clear potion' if we don't know it's holy water; + * always allow "uncursed potion of water" + */ + if (obj->cursed) + Strcat(prefix, "cursed "); + else if (obj->blessed) + Strcat(prefix, "blessed "); + else if (!iflags.implicit_uncursed + /* For most items with charges or +/-, if you know how many + * charges are left or what the +/- is, then you must have + * totally identified the item, so "uncursed" is unnecessary, + * because an identified object not described as "blessed" or + * "cursed" must be uncursed. + * + * If the charges or +/- is not known, "uncursed" must be + * printed to avoid ambiguity between an item whose curse + * status is unknown, and an item known to be uncursed. + */ + || ((!known || !objects[obj->otyp].oc_charged + || obj->oclass == ARMOR_CLASS + || obj->oclass == RING_CLASS) +#ifdef MAIL + && obj->otyp != SCR_MAIL +#endif + && obj->otyp != FAKE_AMULET_OF_YENDOR + && obj->otyp != AMULET_OF_YENDOR + && !Role_if(PM_PRIEST))) + Strcat(prefix, "uncursed "); + } + + if (lknown && Is_box(obj)) { + if (obj->obroken) + /* 3.6.0 used an "unlockable" prefix here but that could be + misunderstood to mean "capable of being unlocked" rather + than the intended "not capable of being locked" */ + Strcat(bp, " with a broken lock"); + else if (obj->olocked) + Strcat(prefix, "locked "); + else + Strcat(prefix, "unlocked "); + } + + if (obj->greased) + Strcat(prefix, "greased "); + + if (cknown && Has_contents(obj)) { + /* 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); + + Sprintf(eos(bp), " 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)"); + break; + case ARMOR_CLASS: + if (obj->owornmask & W_ARMOR) + Strcat(bp, (obj == uskin) ? " (embedded in your skin)" + : " (being worn)"); + /*FALLTHRU*/ + case WEAPON_CLASS: + if (ispoisoned) + Strcat(prefix, "poisoned "); + add_erosion_words(obj, prefix); + if (known) { + Strcat(prefix, sitoa(obj->spe)); + Strcat(prefix, " "); + } + break; + case TOOL_CLASS: + if (obj->owornmask & (W_TOOL | W_SADDLE)) { /* blindfold */ + Strcat(bp, " (being worn)"); + break; + } + 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 { + Sprintf(eos(bp), " (attached to %s)", + a_monnam(mlsh)); + } + break; + } + if (obj->otyp == CANDELABRUM_OF_INVOCATION) { + if (!obj->spe) + Strcpy(tmpbuf, "no"); + else + Sprintf(tmpbuf, "%d", obj->spe); + Sprintf(eos(bp), " (%s candle%s%s)", tmpbuf, plur(obj->spe), + !obj->lamplit ? " attached" : ", lit"); + break; + } else if (obj->otyp == OIL_LAMP || obj->otyp == MAGIC_LAMP + || obj->otyp == BRASS_LANTERN || Is_candle(obj)) { + if (Is_candle(obj) + && obj->age < 20L * (long) objects[obj->otyp].oc_cost) + Strcat(prefix, "partly used "); + if (obj->lamplit) + Strcat(bp, " (lit)"); + break; + } + if (objects[obj->otyp].oc_charged) + goto charges; + break; + case WAND_CLASS: + charges: + if (known) + Sprintf(eos(bp), " (%d:%d)", (int) obj->recharged, obj->spe); + break; + case POTION_CLASS: + if (obj->otyp == POT_OIL && obj->lamplit) + Strcat(bp, " (lit)"); + break; + case RING_CLASS: + ring: + if (obj->owornmask & W_RINGR) + Strcat(bp, " (on right "); + if (obj->owornmask & W_RINGL) + Strcat(bp, " (on left "); + if (obj->owornmask & W_RING) { + Strcat(bp, body_part(HAND)); + Strcat(bp, ")"); + } + if (known && objects[obj->otyp].oc_charged) { + Strcat(prefix, sitoa(obj->spe)); + Strcat(prefix, " "); + } + break; + case FOOD_CLASS: + if (obj->oeaten) + Strcat(prefix, "partly eaten "); + if (obj->otyp == CORPSE) { + /* (quan == 1) => want corpse_xname() to supply article, + (quan != 1) => already have count or "some" as prefix; + "corpse" is already in the buffer returned by xname() */ + unsigned cxarg = (((obj->quan != 1L) ? 0 : CXN_ARTICLE) + | CXN_NOCORPSE); + char *cxstr = corpse_xname(obj, prefix, cxarg); + + Sprintf(prefix, "%s ", cxstr); + /* avoid having doname(corpse) consume an extra obuf */ + releaseobuf(cxstr); + } else if (obj->otyp == EGG) { +#if 0 /* corpses don't tell if they're stale either */ + if (known && stale_egg(obj)) + Strcat(prefix, "stale "); +#endif + if (omndx >= LOW_PM + && (known || (mvitals[omndx].mvflags & MV_KNOWS_EGG))) { + Strcat(prefix, mons[omndx].mname); + Strcat(prefix, " "); + if (obj->spe) + Strcat(bp, " (laid by you)"); + } + } + if (obj->otyp == MEAT_RING) + goto ring; + break; + case BALL_CLASS: + case CHAIN_CLASS: + add_erosion_words(obj, prefix); + if (obj->owornmask & W_BALL) + Strcat(bp, " (chained to you)"); + break; + } + + if ((obj->owornmask & W_WEP) && !mrg_to_wielded) { + if (obj->quan != 1L) { + Strcat(bp, " (wielded)"); + } else { + const char *hand_s = body_part(HAND); + + if (bimanual(obj)) + hand_s = makeplural(hand_s); + Sprintf(eos(bp), " (weapon in %s)", hand_s); + + if (warn_obj_cnt && obj == uwep && (EWarn_of_mon & W_WEP) != 0L) { + /* presumably can be felt when blind */ + Strcat(bp, " (glowing"); + if (!Blind) + Sprintf(eos(bp), " %s", glow_color(obj->oartifact)); + Strcat(bp, ")"); + } + } + } + if (obj->owornmask & W_SWAPWEP) { + if (u.twoweap) + Sprintf(eos(bp), " (wielded in other %s)", body_part(HAND)); + else + Strcat(bp, " (alternate weapon; not wielded)"); + } + if (obj->owornmask & W_QUIVER) { + 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 */ + case RING_CLASS: + case AMULET_CLASS: + case WAND_CLASS: + case COIN_CLASS: + case GEM_CLASS: + Strcat(bp, " (in quiver pouch)"); + break; + default: /* odd things */ + Strcat(bp, " (at the ready)"); + } + } + if (!iflags.suppress_price && is_unpaid(obj)) { + long quotedprice = unpaid_cost(obj, TRUE); + + Sprintf(eos(bp), " (%s, %ld %s)", + obj->unpaid ? "unpaid" : "contents", + quotedprice, currency(quotedprice)); + } else if (with_price) { + long price = get_cost_of_shop_item(obj); + + if (price > 0) + Sprintf(eos(bp), " (%ld %s)", price, currency(price)); + } + if (!strncmp(prefix, "a ", 2) + && index(vowels, *(prefix + 2) ? *(prefix + 2) : *bp) + && (*(prefix + 2) + || (strncmp(bp, "uranium", 7) && strncmp(bp, "unicorn", 7) + && strncmp(bp, "eucalyptus", 10)))) { + Strcpy(tmpbuf, prefix); + Strcpy(prefix, "an "); + Strcpy(prefix + 3, tmpbuf + 2); + } + + /* show weight for items (debug tourist info) + * aum is stolen from Crawl's "Arbitrary Unit of Measure" */ + if (wizard && iflags.wizweight) { + Sprintf(eos(bp), " (%d aum)", obj->owt); + } + bp = strprepend(bp, prefix); + return bp; +} + +char * +doname(obj) +struct obj *obj; +{ + return doname_base(obj, (unsigned) 0); +} + +/* Name of object including price. */ +char * +doname_with_price(obj) +struct obj *obj; +{ + return doname_base(obj, DONAME_WITH_PRICE); +} + +/* "some" instead of precise quantity if obj->dknown not set */ +char * +doname_vague_quan(obj) +struct obj *obj; +{ + /* Used by farlook. + * If it hasn't been seen up close and quantity is more than one, + * use "some" instead of the quantity: "some gold pieces" rather + * than "25 gold pieces". This is suboptimal, to put it mildly, + * because lookhere and pickup report the precise amount. + * Picking the item up while blind also shows the precise amount + * for inventory display, then dropping it while still blind leaves + * obj->dknown unset so the count reverts to "some" for farlook. + * + * TODO: add obj->qknown flag for 'quantity known' on stackable + * items; it could overlay obj->cknown since no containers stack. + */ + return doname_base(obj, DONAME_VAGUE_QUAN); +} + +/* used from invent.c */ +boolean +not_fully_identified(otmp) +struct obj *otmp; +{ + /* gold doesn't have any interesting attributes [yet?] */ + if (otmp->oclass == COIN_CLASS) + return FALSE; /* always fully ID'd */ + /* check fundamental ID hallmarks first */ + if (!otmp->known || !otmp->dknown +#ifdef MAIL + || (!otmp->bknown && otmp->otyp != SCR_MAIL) +#else + || !otmp->bknown +#endif + || !objects[otmp->otyp].oc_name_known) + return TRUE; + if ((!otmp->cknown && (Is_container(otmp) || otmp->otyp == STATUE)) + || (!otmp->lknown && Is_box(otmp))) + return TRUE; + if (otmp->oartifact && undiscovered_artifact(otmp->oartifact)) + return TRUE; + /* otmp->rknown is the only item of interest if we reach here */ + /* + * Note: if a revision ever allows scrolls to become fireproof or + * rings to become shockproof, this checking will need to be revised. + * `rknown' ID only matters if xname() will provide the info about it. + */ + if (otmp->rknown + || (otmp->oclass != ARMOR_CLASS && otmp->oclass != WEAPON_CLASS + && !is_weptool(otmp) /* (redundant) */ + && otmp->oclass != BALL_CLASS)) /* (useless) */ + return FALSE; + else /* lack of `rknown' only matters for vulnerable objects */ + return (boolean) (is_rustprone(otmp) || is_corrodeable(otmp) + || is_flammable(otmp)); +} + +/* format a corpse name (xname() omits monster type; doname() calls us); + eatcorpse() also uses us for death reason when eating tainted glob */ +char * +corpse_xname(otmp, adjective, cxn_flags) +struct obj *otmp; +const char *adjective; +unsigned cxn_flags; /* bitmask of CXN_xxx values */ +{ + char *nambuf = nextobuf(); + int omndx = otmp->corpsenm; + boolean ignore_quan = (cxn_flags & CXN_SINGULAR) != 0, + /* suppress "the" from "the unique monster corpse" */ + no_prefix = (cxn_flags & CXN_NO_PFX) != 0, + /* include "the" for "the woodchuck corpse */ + the_prefix = (cxn_flags & CXN_PFX_THE) != 0, + /* include "an" for "an ogre corpse */ + any_prefix = (cxn_flags & CXN_ARTICLE) != 0, + /* leave off suffix (do_name() appends "corpse" itself) */ + omit_corpse = (cxn_flags & CXN_NOCORPSE) != 0, + possessive = FALSE, + glob = (otmp->otyp != CORPSE && otmp->globby); + const char *mname; + + if (glob) { + mname = OBJ_NAME(objects[otmp->otyp]); /* "glob of " */ + } else if (omndx == NON_PM) { /* paranoia */ + mname = "thing"; + /* [Possible enhancement: check whether corpse has monster traits + attached in order to use priestname() for priests and minions.] */ + } else if (omndx == PM_ALIGNED_PRIEST) { + /* avoid "aligned priest"; it just exposes internal details */ + mname = "priest"; + } else { + mname = mons[omndx].mname; + if (the_unique_pm(&mons[omndx]) || type_is_pname(&mons[omndx])) { + mname = s_suffix(mname); + possessive = TRUE; + /* don't precede personal name like "Medusa" with an article */ + if (type_is_pname(&mons[omndx])) + no_prefix = TRUE; + /* always precede non-personal unique monster name like + "Oracle" with "the" unless explicitly overridden */ + else if (the_unique_pm(&mons[omndx]) && !no_prefix) + the_prefix = TRUE; + } + } + if (no_prefix) + the_prefix = any_prefix = FALSE; + else if (the_prefix) + any_prefix = FALSE; /* mutually exclusive */ + + *nambuf = '\0'; + /* can't use the() the way we use an() below because any capitalized + Name causes it to assume a personal name and return Name as-is; + that's usually the behavior wanted, but here we need to force "the" + to precede capitalized unique monsters (pnames are handled above) */ + if (the_prefix) + Strcat(nambuf, "the "); + + if (!adjective || !*adjective) { + /* normal case: newt corpse */ + Strcat(nambuf, mname); + } else { + /* adjective positioning depends upon format of monster name */ + if (possessive) /* Medusa's cursed partly eaten corpse */ + Sprintf(eos(nambuf), "%s %s", mname, adjective); + else /* cursed partly eaten troll corpse */ + Sprintf(eos(nambuf), "%s %s", adjective, mname); + /* in case adjective has a trailing space, squeeze it out */ + mungspaces(nambuf); + /* doname() might include a count in the adjective argument; + if so, don't prepend an article */ + if (digit(*adjective)) + any_prefix = FALSE; + } + + if (glob) { + ; /* omit_corpse doesn't apply; quantity is always 1 */ + } else if (!omit_corpse) { + Strcat(nambuf, " corpse"); + /* makeplural(nambuf) => append "s" to "corpse" */ + if (otmp->quan > 1L && !ignore_quan) { + Strcat(nambuf, "s"); + any_prefix = FALSE; /* avoid "a newt corpses" */ + } + } + + /* it's safe to overwrite our nambuf after an() has copied + its old value into another buffer */ + if (any_prefix) + Strcpy(nambuf, an(nambuf)); + + return nambuf; +} + +/* xname doesn't include monster type for "corpse"; cxname does */ +char * +cxname(obj) +struct obj *obj; +{ + if (obj->otyp == CORPSE) + return corpse_xname(obj, (const char *) 0, CXN_NORMAL); + return xname(obj); +} + +/* like cxname, but ignores quantity */ +char * +cxname_singular(obj) +struct obj *obj; +{ + if (obj->otyp == CORPSE) + return corpse_xname(obj, (const char *) 0, CXN_SINGULAR); + return xname_flags(obj, CXN_SINGULAR); +} + +/* treat an object as fully ID'd when it might be used as reason for death */ +char * +killer_xname(obj) +struct obj *obj; +{ + struct obj save_obj; + unsigned save_ocknown; + char *buf, *save_ocuname, *save_oname = (char *) 0; + + /* bypass object twiddling for artifacts */ + if (obj->oartifact) + return bare_artifactname(obj); + + /* remember original settings for core of the object; + oextra structs other than oname don't matter here--since they + aren't modified they don't need to be saved and restored */ + save_obj = *obj; + if (has_oname(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 */ + obj->known = obj->dknown = 1; + obj->bknown = obj->rknown = obj->greased = 0; + /* if character is a priest[ess], bknown will get toggled back on */ + if (obj->otyp != POT_WATER) + obj->blessed = obj->cursed = 0; + else + obj->bknown = 1; /* describe holy/unholy water as such */ + /* "killed by poisoned " would be misleading when poison is + not the cause of death and "poisoned by poisoned " would + be redundant when it is, so suppress "poisoned" prefix */ + obj->opoisoned = 0; + /* strip user-supplied name; artifacts keep theirs */ + if (!obj->oartifact && save_oname) + ONAME(obj) = (char *) 0; + /* temporarily identify the type of object */ + save_ocknown = objects[obj->otyp].oc_name_known; + objects[obj->otyp].oc_name_known = 1; + save_ocuname = objects[obj->otyp].oc_uname; + objects[obj->otyp].oc_uname = 0; /* avoid "foo called bar" */ + + /* format the object */ + if (obj->otyp == CORPSE) { + buf = nextobuf(); + Strcpy(buf, corpse_xname(obj, (const char *) 0, CXN_NORMAL)); + } else if (obj->otyp == SLIME_MOLD) { + /* concession to "most unique deaths competition" in the annual + devnull tournament, suppress player supplied fruit names because + those can be used to fake other objects and dungeon features */ + buf = nextobuf(); + Sprintf(buf, "deadly slime mold%s", plur(obj->quan)); + } else { + buf = xname(obj); + } + /* apply an article if appropriate; caller should always use KILLED_BY */ + if (obj->quan == 1L && !strstri(buf, "'s ") && !strstri(buf, "s' ")) + buf = (obj_is_pname(obj) || the_unique_obj(obj)) ? the(buf) : an(buf); + + objects[obj->otyp].oc_name_known = save_ocknown; + objects[obj->otyp].oc_uname = save_ocuname; + *obj = save_obj; /* restore object's core settings */ + if (!obj->oartifact && save_oname) + ONAME(obj) = save_oname; + + return buf; +} + +/* xname,doname,&c with long results reformatted to omit some stuff */ +char * +short_oname(obj, func, altfunc, lenlimit) +struct obj *obj; +char *FDECL((*func), (OBJ_P)), /* main formatting routine */ + *FDECL((*altfunc), (OBJ_P)); /* alternate for shortest result */ +unsigned lenlimit; +{ + struct obj save_obj; + char unamebuf[12], onamebuf[12], *save_oname, *save_uname, *outbuf; + + outbuf = (*func)(obj); + if ((unsigned) strlen(outbuf) <= lenlimit) + return outbuf; + + /* shorten called string to fairly small amount */ + save_uname = objects[obj->otyp].oc_uname; + if (save_uname && strlen(save_uname) >= sizeof unamebuf) { + (void) strncpy(unamebuf, save_uname, sizeof unamebuf - 4); + Strcpy(unamebuf + sizeof unamebuf - 4, "..."); + objects[obj->otyp].oc_uname = unamebuf; + releaseobuf(outbuf); + outbuf = (*func)(obj); + objects[obj->otyp].oc_uname = save_uname; /* restore called string */ + if ((unsigned) strlen(outbuf) <= lenlimit) + return outbuf; + } + + /* shorten named string to fairly small amount */ + save_oname = has_oname(obj) ? ONAME(obj) : 0; + if (save_oname && strlen(save_oname) >= sizeof onamebuf) { + (void) strncpy(onamebuf, save_oname, sizeof onamebuf - 4); + Strcpy(onamebuf + sizeof onamebuf - 4, "..."); + ONAME(obj) = onamebuf; + releaseobuf(outbuf); + outbuf = (*func)(obj); + ONAME(obj) = save_oname; /* restore named string */ + if ((unsigned) strlen(outbuf) <= lenlimit) + return outbuf; + } + + /* shorten both called and named strings; + unamebuf and onamebuf have both already been populated */ + if (save_uname && strlen(save_uname) >= sizeof unamebuf && save_oname + && strlen(save_oname) >= sizeof onamebuf) { + objects[obj->otyp].oc_uname = unamebuf; + ONAME(obj) = onamebuf; + releaseobuf(outbuf); + outbuf = (*func)(obj); + if ((unsigned) strlen(outbuf) <= lenlimit) { + objects[obj->otyp].oc_uname = save_uname; + ONAME(obj) = save_oname; + return outbuf; + } + } + + /* still long; strip several name-lengthening attributes; + called and named strings are still in truncated form */ + save_obj = *obj; + obj->bknown = obj->rknown = obj->greased = 0; + obj->oeroded = obj->oeroded2 = 0; + releaseobuf(outbuf); + outbuf = (*func)(obj); + if (altfunc && (unsigned) strlen(outbuf) > lenlimit) { + /* still long; use the alternate function (usually one of + the jackets around minimal_xname()) */ + releaseobuf(outbuf); + outbuf = (*altfunc)(obj); + } + /* restore the object */ + *obj = save_obj; + if (save_oname) + ONAME(obj) = save_oname; + if (save_uname) + objects[obj->otyp].oc_uname = save_uname; + + /* use whatever we've got, whether it's too long or not */ + return outbuf; +} + +/* + * Used if only one of a collection of objects is named (e.g. in eat.c). + */ +const char * +singular(otmp, func) +register struct obj *otmp; +char *FDECL((*func), (OBJ_P)); +{ + long savequan; + char *nam; + + /* using xname for corpses does not give the monster type */ + if (otmp->otyp == CORPSE && func == xname) + func = cxname; + + savequan = otmp->quan; + otmp->quan = 1L; + nam = (*func)(otmp); + otmp->quan = savequan; + return nam; +} + +char * +an(str) +register const char *str; +{ + char *buf = nextobuf(); + + buf[0] = '\0'; + + if (strncmpi(str, "the ", 4) && strcmp(str, "molten lava") + && strcmp(str, "iron bars") && strcmp(str, "ice")) { + if (index(vowels, *str) && strncmp(str, "one-", 4) + && strncmp(str, "useful", 6) && strncmp(str, "unicorn", 7) + && strncmp(str, "uranium", 7) && strncmp(str, "eucalyptus", 10)) + Strcpy(buf, "an "); + else + Strcpy(buf, "a "); + } + + Strcat(buf, str); + return buf; +} + +char * +An(str) +const char *str; +{ + char *tmp = an(str); + + *tmp = highc(*tmp); + return tmp; +} + +/* + * Prepend "the" if necessary; assumes str is a subject derived from xname. + * Use type_is_pname() for monster names, not the(). the() is idempotent. + */ +char * +the(str) +const char *str; +{ + char *buf = nextobuf(); + boolean insert_the = FALSE; + + if (!strncmpi(str, "the ", 4)) { + buf[0] = lowc(*str); + Strcpy(&buf[1], str + 1); + return buf; + } else if (*str < 'A' || *str > 'Z' + /* treat named fruit as not a proper name, even if player + has assigned a capitalized proper name as his/her fruit */ + || fruit_from_name(str, TRUE, (int *) 0)) { + /* not a proper name, needs an article */ + insert_the = TRUE; + } else { + /* Probably a proper name, might not need an article */ + register char *tmp, *named, *called; + int l; + + /* some objects have capitalized adjectives in their names */ + if (((tmp = rindex(str, ' ')) != 0 || (tmp = rindex(str, '-')) != 0) + && (tmp[1] < 'A' || tmp[1] > 'Z')) { + insert_the = TRUE; + } else if (tmp && index(str, ' ') < tmp) { /* has spaces */ + /* it needs an article if the name contains "of" */ + tmp = strstri(str, " of "); + named = strstri(str, " named "); + called = strstri(str, " called "); + if (called && (!named || called < named)) + named = called; + + if (tmp && (!named || tmp < named)) /* found an "of" */ + insert_the = TRUE; + /* stupid special case: lacks "of" but needs "the" */ + else if (!named && (l = strlen(str)) >= 31 + && !strcmp(&str[l - 31], + "Platinum Yendorian Express Card")) + insert_the = TRUE; + } + } + if (insert_the) + Strcpy(buf, "the "); + else + buf[0] = '\0'; + Strcat(buf, str); + + return buf; +} + +char * +The(str) +const char *str; +{ + char *tmp = the(str); + + *tmp = highc(*tmp); + return tmp; +} + +/* returns "count cxname(otmp)" or just cxname(otmp) if count == 1 */ +char * +aobjnam(otmp, verb) +struct obj *otmp; +const char *verb; +{ + char prefix[PREFIX]; + char *bp = cxname(otmp); + + if (otmp->quan != 1L) { + Sprintf(prefix, "%ld ", otmp->quan); + bp = strprepend(bp, prefix); + } + if (verb) { + Strcat(bp, " "); + Strcat(bp, otense(otmp, verb)); + } + return bp; +} + +/* combine yname and aobjnam eg "your count cxname(otmp)" */ +char * +yobjnam(obj, verb) +struct obj *obj; +const char *verb; +{ + char *s = aobjnam(obj, verb); + + /* leave off "your" for most of your artifacts, but prepend + * "your" for unique objects and "foo of bar" quest artifacts */ + if (!carried(obj) || !obj_is_pname(obj) + || obj->oartifact >= ART_ORB_OF_DETECTION) { + char *outbuf = shk_your(nextobuf(), obj); + int space_left = BUFSZ - 1 - strlen(outbuf); + + s = strncat(outbuf, s, space_left); + } + return s; +} + +/* combine Yname2 and aobjnam eg "Your count cxname(otmp)" */ +char * +Yobjnam2(obj, verb) +struct obj *obj; +const char *verb; +{ + register char *s = yobjnam(obj, verb); + + *s = highc(*s); + return s; +} + +/* like aobjnam, but prepend "The", not count, and use xname */ +char * +Tobjnam(otmp, verb) +struct obj *otmp; +const char *verb; +{ + char *bp = The(xname(otmp)); + + if (verb) { + Strcat(bp, " "); + Strcat(bp, otense(otmp, verb)); + } + return bp; +} + +/* capitalized variant of doname() */ +char * +Doname2(obj) +struct obj *obj; +{ + char *s = doname(obj); + + *s = highc(*s); + return s; +} + +/* returns "[your ]xname(obj)" or "Foobar's xname(obj)" or "the xname(obj)" */ +char * +yname(obj) +struct obj *obj; +{ + char *s = cxname(obj); + + /* leave off "your" for most of your artifacts, but prepend + * "your" for unique objects and "foo of bar" quest artifacts */ + if (!carried(obj) || !obj_is_pname(obj) + || obj->oartifact >= ART_ORB_OF_DETECTION) { + char *outbuf = shk_your(nextobuf(), obj); + int space_left = BUFSZ - 1 - strlen(outbuf); + + s = strncat(outbuf, s, space_left); + } + + return s; +} + +/* capitalized variant of yname() */ +char * +Yname2(obj) +struct obj *obj; +{ + char *s = yname(obj); + + *s = highc(*s); + return s; +} + +/* returns "your minimal_xname(obj)" + * or "Foobar's minimal_xname(obj)" + * or "the minimal_xname(obj)" + */ +char * +ysimple_name(obj) +struct obj *obj; +{ + char *outbuf = nextobuf(); + char *s = shk_your(outbuf, obj); /* assert( s == outbuf ); */ + int space_left = BUFSZ - 1 - strlen(s); + + return strncat(s, minimal_xname(obj), space_left); +} + +/* capitalized variant of ysimple_name() */ +char * +Ysimple_name2(obj) +struct obj *obj; +{ + char *s = ysimple_name(obj); + + *s = highc(*s); + return s; +} + +/* "scroll" or "scrolls" */ +char * +simpleonames(obj) +struct obj *obj; +{ + char *simpleoname = minimal_xname(obj); + + if (obj->quan != 1L) + simpleoname = makeplural(simpleoname); + return simpleoname; +} + +/* "a scroll" or "scrolls"; "a silver bell" or "the Bell of Opening" */ +char * +ansimpleoname(obj) +struct obj *obj; +{ + char *simpleoname = simpleonames(obj); + int otyp = obj->otyp; + + /* prefix with "the" if a unique item, or a fake one imitating same, + has been formatted with its actual name (we let typename() handle + any `known' and `dknown' checking necessary) */ + if (otyp == FAKE_AMULET_OF_YENDOR) + otyp = AMULET_OF_YENDOR; + if (objects[otyp].oc_unique + && !strcmp(simpleoname, OBJ_NAME(objects[otyp]))) + return the(simpleoname); + + /* simpleoname is singular if quan==1, plural otherwise */ + if (obj->quan == 1L) + simpleoname = an(simpleoname); + return simpleoname; +} + +/* "the scroll" or "the scrolls" */ +char * +thesimpleoname(obj) +struct obj *obj; +{ + char *simpleoname = simpleonames(obj); + + return the(simpleoname); +} + +/* artifact's name without any object type or known/dknown/&c feedback */ +char * +bare_artifactname(obj) +struct obj *obj; +{ + char *outbuf; + + if (obj->oartifact) { + outbuf = nextobuf(); + Strcpy(outbuf, artiname(obj->oartifact)); + if (!strncmp(outbuf, "The ", 4)) + outbuf[0] = lowc(outbuf[0]); + } else { + outbuf = xname(obj); + } + return outbuf; +} + +static const char *wrp[] = { + "wand", "ring", "potion", "scroll", "gem", + "amulet", "spellbook", "spell book", + /* for non-specific wishes */ + "weapon", "armor", "tool", "food", "comestible", +}; +static const char wrpsym[] = { WAND_CLASS, RING_CLASS, POTION_CLASS, + SCROLL_CLASS, GEM_CLASS, AMULET_CLASS, + SPBOOK_CLASS, SPBOOK_CLASS, WEAPON_CLASS, + ARMOR_CLASS, TOOL_CLASS, FOOD_CLASS, + FOOD_CLASS }; + +/* return form of the verb (input plural) if xname(otmp) were the subject */ +char * +otense(otmp, verb) +struct obj *otmp; +const char *verb; +{ + char *buf; + + /* + * verb is given in plural (without trailing s). Return as input + * if the result of xname(otmp) would be plural. Don't bother + * recomputing xname(otmp) at this time. + */ + if (!is_plural(otmp)) + return vtense((char *) 0, verb); + + buf = nextobuf(); + Strcpy(buf, verb); + return buf; +} + +/* various singular words that vtense would otherwise categorize as plural; + also used by makesingular() to catch some special cases */ +static const char *const special_subjs[] = { + "erinys", "manes", /* this one is ambiguous */ + "Cyclops", "Hippocrates", "Pelias", "aklys", + "amnesia", "detect monsters", "paralysis", "shape changers", + "nemesis", 0 + /* note: "detect monsters" and "shape changers" are normally + caught via "(s) of ", but they can be + wished for using the shorter form, so we include them here + to accommodate usage by makesingular during wishing */ +}; + +/* return form of the verb (input plural) for present tense 3rd person subj */ +char * +vtense(subj, verb) +register const char *subj; +register const char *verb; +{ + char *buf = nextobuf(), *bspot; + int len, ltmp; + const char *sp, *spot; + const char *const *spec; + + /* + * verb is given in plural (without trailing s). Return as input + * if subj appears to be plural. Add special cases as necessary. + * Many hard cases can already be handled by using otense() instead. + * If this gets much bigger, consider decomposing makeplural. + * Note: monster names are not expected here (except before corpse). + * + * Special case: allow null sobj to get the singular 3rd person + * present tense form so we don't duplicate this code elsewhere. + */ + if (subj) { + if (!strncmpi(subj, "a ", 2) || !strncmpi(subj, "an ", 3)) + goto sing; + spot = (const char *) 0; + for (sp = subj; (sp = index(sp, ' ')) != 0; ++sp) { + if (!strncmpi(sp, " of ", 4) || !strncmpi(sp, " from ", 6) + || !strncmpi(sp, " called ", 8) || !strncmpi(sp, " named ", 7) + || !strncmpi(sp, " labeled ", 9)) { + if (sp != subj) + spot = sp - 1; + break; + } + } + len = (int) strlen(subj); + if (!spot) + spot = subj + len - 1; + + /* + * plural: anything that ends in 's', but not '*us' or '*ss'. + * Guess at a few other special cases that makeplural creates. + */ + if ((lowc(*spot) == 's' && spot != subj + && !index("us", lowc(*(spot - 1)))) + || !BSTRNCMPI(subj, spot - 3, "eeth", 4) + || !BSTRNCMPI(subj, spot - 3, "feet", 4) + || !BSTRNCMPI(subj, spot - 1, "ia", 2) + || !BSTRNCMPI(subj, spot - 1, "ae", 2)) { + /* check for special cases to avoid false matches */ + len = (int) (spot - subj) + 1; + for (spec = special_subjs; *spec; spec++) { + ltmp = strlen(*spec); + if (len == ltmp && !strncmpi(*spec, subj, len)) + goto sing; + /* also check for + to catch things like "the invisible erinys" */ + if (len > ltmp && *(spot - ltmp) == ' ' + && !strncmpi(*spec, spot - ltmp + 1, ltmp)) + goto sing; + } + + return strcpy(buf, verb); + } + /* + * 3rd person plural doesn't end in telltale 's'; + * 2nd person singular behaves as if plural. + */ + if (!strcmpi(subj, "they") || !strcmpi(subj, "you")) + return strcpy(buf, verb); + } + +sing: + Strcpy(buf, verb); + len = (int) strlen(buf); + bspot = buf + len - 1; + + if (!strcmpi(buf, "are")) { + Strcasecpy(buf, "is"); + } else if (!strcmpi(buf, "have")) { + Strcasecpy(bspot - 1, "s"); + } else if (index("zxs", lowc(*bspot)) + || (len >= 2 && lowc(*bspot) == 'h' + && index("cs", lowc(*(bspot - 1)))) + || (len == 2 && lowc(*bspot) == 'o')) { + /* Ends in z, x, s, ch, sh; add an "es" */ + Strcasecpy(bspot + 1, "es"); + } else if (lowc(*bspot) == 'y' && !index(vowels, lowc(*(bspot - 1)))) { + /* like "y" case in makeplural */ + Strcasecpy(bspot, "ies"); + } else { + Strcasecpy(bspot + 1, "s"); + } + + return buf; +} + +struct sing_plur { + const char *sing, *plur; +}; + +/* word pairs that don't fit into formula-based transformations; + also some suffices which have very few--often one--matches or + which aren't systematically reversible (knives, staves) */ +static struct sing_plur one_off[] = { + { "child", + "children" }, /* (for wise guys who give their food funny names) */ + { "cubus", "cubi" }, /* in-/suc-cubus */ + { "culus", "culi" }, /* homunculus */ + { "djinni", "djinn" }, + { "erinys", "erinyes" }, + { "foot", "feet" }, + { "fungus", "fungi" }, + { "goose", "geese" }, + { "knife", "knives" }, + { "labrum", "labra" }, /* candelabrum */ + { "louse", "lice" }, + { "mouse", "mice" }, + { "mumak", "mumakil" }, + { "nemesis", "nemeses" }, + { "ovum", "ova" }, + { "ox", "oxen" }, + { "passerby", "passersby" }, + { "rtex", "rtices" }, /* vortex */ + { "serum", "sera" }, + { "staff", "staves" }, + { "tooth", "teeth" }, + { 0, 0 } +}; + +static const char *const as_is[] = { + /* makesingular() leaves these plural due to how they're used */ + "boots", "shoes", "gloves", "lenses", "scales", + "eyes", "gauntlets", "iron bars", + /* both singular and plural are spelled the same */ + "bison", "deer", "elk", "fish", "fowl", + "tuna", "yaki", "-hai", "krill", "manes", + "moose", "ninja", "sheep", "ronin", "roshi", + "shito", "tengu", "ki-rin", "Nazgul", "gunyoki", + "piranha", "samurai", "shuriken", 0, + /* Note: "fish" and "piranha" are collective plurals, suitable + for "wiped out all ". For "3 ", they should be + "fishes" and "piranhas" instead. We settle for collective + variant instead of attempting to support both. */ +}; + +/* singularize/pluralize decisions common to both makesingular & makeplural */ +STATIC_OVL boolean +singplur_lookup(basestr, endstring, to_plural, alt_as_is) +char *basestr, *endstring; /* base string, pointer to eos(string) */ +boolean to_plural; /* true => makeplural, false => makesingular */ +const char *const *alt_as_is; /* another set like as_is[] */ +{ + const struct sing_plur *sp; + const char *same, *other, *const *as; + int al; + + for (as = as_is; *as; ++as) { + al = (int) strlen(*as); + if (!BSTRCMPI(basestr, endstring - al, *as)) + return TRUE; + } + if (alt_as_is) { + for (as = alt_as_is; *as; ++as) { + al = (int) strlen(*as); + if (!BSTRCMPI(basestr, endstring - al, *as)) + return TRUE; + } + } + + /* avoid false hit on one_off[].plur == "lice" or .sing == "goose"; + if more of these turn up, one_off[] entries will need to flagged + as to which are whole words and which are matchable as suffices + then matching in the loop below will end up becoming more complex */ + if (!strcmpi(basestr, "slice") + || !strcmpi(basestr, "mongoose")) { + if (to_plural) + Strcasecpy(endstring, "s"); + return TRUE; + } + /* skip "ox" -> "oxen" entry when pluralizing "ox" + unless it is muskox */ + if (to_plural && strlen(basestr) > 2 && !strcmpi(endstring - 2, "ox") + && strcmpi(endstring - 6, "muskox")) { + /* "fox" -> "foxes" */ + Strcasecpy(endstring, "es"); + return TRUE; + } + if (to_plural) { + if (!strcmpi(endstring - 3, "man") + && badman(basestr, to_plural)) { + Strcasecpy(endstring, "s"); + return TRUE; + } + } else { + if (!strcmpi(endstring - 3, "men") + && badman(basestr, to_plural)) + return TRUE; + } + for (sp = one_off; sp->sing; sp++) { + /* check whether endstring already matches */ + same = to_plural ? sp->plur : sp->sing; + al = (int) strlen(same); + if (!BSTRCMPI(basestr, endstring - al, same)) + return TRUE; /* use as-is */ + /* check whether it matches the inverse; if so, transform it */ + other = to_plural ? sp->sing : sp->plur; + al = (int) strlen(other); + if (!BSTRCMPI(basestr, endstring - al, other)) { + Strcasecpy(endstring - al, same); + return TRUE; /* one_off[] transformation */ + } + } + return FALSE; +} + +/* searches for common compounds, ex. lump of royal jelly */ +STATIC_OVL char * +singplur_compound(str) +char *str; +{ + /* if new entries are added, be sure to keep compound_start[] in sync */ + static const char *const compounds[] = + { + " of ", " labeled ", " called ", + " named ", " above", /* lurkers above */ + " versus ", " from ", " in ", + " on ", " a la ", " with", /* " with "? */ + " de ", " d'", " du ", + "-in-", "-at-", 0 + }, /* list of first characters for all compounds[] entries */ + compound_start[] = " -"; + + const char *const *cmpd; + char *p; + + for (p = str; *p; ++p) { + /* substring starting at p can only match if *p is found + within compound_start[] */ + if (!index(compound_start, *p)) + continue; + + /* check current substring against all words in the compound[] list */ + for (cmpd = compounds; *cmpd; ++cmpd) + if (!strncmpi(p, *cmpd, (int) strlen(*cmpd))) + return p; + } + /* wasn't recognized as a compound phrase */ + return 0; +} + +/* Plural routine; once upon a time it may have been chiefly used for + * user-defined fruits, but it is now used extensively throughout the + * program. + * + * For fruit, we have to try to account for everything reasonable the + * player has; something unreasonable can still break the code. + * However, it's still a lot more accurate than "just add an 's' at the + * end", which Rogue uses... + * + * Also used for plural monster names ("Wiped out all homunculi." or the + * vanquished monsters list) and body parts. A lot of unique monsters have + * names which get mangled by makeplural and/or makesingular. They're not + * genocidable, and vanquished-mon handling does its own special casing + * (for uniques who've been revived and re-killed), so we don't bother + * trying to get those right here. + * + * Also misused by muse.c to convert 1st person present verbs to 2nd person. + * 3.6.0: made case-insensitive. + */ +char * +makeplural(oldstr) +const char *oldstr; +{ + register char *spot; + char lo_c, *str = nextobuf(); + const char *excess = (char *) 0; + int len; + + if (oldstr) + while (*oldstr == ' ') + oldstr++; + if (!oldstr || !*oldstr) { + impossible("plural of null?"); + Strcpy(str, "s"); + return str; + } + Strcpy(str, oldstr); + + /* + * Skip changing "pair of" to "pairs of". According to Webster, usual + * English usage is use pairs for humans, e.g. 3 pairs of dancers, + * and pair for objects and non-humans, e.g. 3 pair of boots. We don't + * refer to pairs of humans in this game so just skip to the bottom. + */ + if (!strncmpi(str, "pair of ", 8)) + goto bottom; + + /* look for "foo of bar" so that we can focus on "foo" */ + if ((spot = singplur_compound(str)) != 0) { + excess = oldstr + (int) (spot - str); + *spot = '\0'; + } else + spot = eos(str); + + spot--; + while (spot > str && *spot == ' ') + spot--; /* Strip blanks from end */ + *(spot + 1) = '\0'; + /* Now spot is the last character of the string */ + + len = strlen(str); + + /* Single letters */ + if (len == 1 || !letter(*spot)) { + Strcpy(spot + 1, "'s"); + goto bottom; + } + + /* dispense with some words which don't need pluralization */ + { + static const char *const already_plural[] = { + "ae", /* algae, larvae, &c */ + "matzot", 0, + }; + + /* spot+1: synch up with makesingular's usage */ + if (singplur_lookup(str, spot + 1, TRUE, already_plural)) + goto bottom; + + /* more of same, but not suitable for blanket loop checking */ + if ((len == 2 && !strcmpi(str, "ya")) + || (len >= 3 && !strcmpi(spot - 2, " ya"))) + goto bottom; + } + + /* man/men ("Wiped out all cavemen.") */ + if (len >= 3 && !strcmpi(spot - 2, "man") + /* exclude shamans and humans etc */ + && !badman(str, TRUE)) { + Strcasecpy(spot - 1, "en"); + goto bottom; + } + if (lowc(*spot) == 'f') { /* (staff handled via one_off[]) */ + lo_c = lowc(*(spot - 1)); + if (len >= 3 && !strcmpi(spot - 2, "erf")) { + /* avoid "nerf" -> "nerves", "serf" -> "serves" */ + ; /* fall through to default (append 's') */ + } else if (index("lr", lo_c) || index(vowels, lo_c)) { + /* [aeioulr]f to [aeioulr]ves */ + Strcasecpy(spot, "ves"); + goto bottom; + } + } + /* ium/ia (mycelia, baluchitheria) */ + if (len >= 3 && !strcmpi(spot - 2, "ium")) { + Strcasecpy(spot - 2, "ia"); + goto bottom; + } + /* algae, larvae, hyphae (another fungus part) */ + if ((len >= 4 && !strcmpi(spot - 3, "alga")) + || (len >= 5 + && (!strcmpi(spot - 4, "hypha") || !strcmpi(spot - 4, "larva"))) + || (len >= 6 && !strcmpi(spot - 5, "amoeba")) + || (len >= 8 && (!strcmpi(spot - 7, "vertebra")))) { + /* a to ae */ + Strcasecpy(spot + 1, "e"); + goto bottom; + } + /* fungus/fungi, homunculus/homunculi, but buses, lotuses, wumpuses */ + if (len > 3 && !strcmpi(spot - 1, "us") + && !((len >= 5 && !strcmpi(spot - 4, "lotus")) + || (len >= 6 && !strcmpi(spot - 5, "wumpus")))) { + Strcasecpy(spot - 1, "i"); + goto bottom; + } + /* sis/ses (nemesis) */ + if (len >= 3 && !strcmpi(spot - 2, "sis")) { + Strcasecpy(spot - 1, "es"); + goto bottom; + } + /* matzoh/matzot, possible food name */ + if (len >= 6 + && (!strcmpi(spot - 5, "matzoh") || !strcmpi(spot - 5, "matzah"))) { + Strcasecpy(spot - 1, "ot"); /* oh/ah -> ot */ + goto bottom; + } + if (len >= 5 + && (!strcmpi(spot - 4, "matzo") || !strcmpi(spot - 4, "matza"))) { + Strcasecpy(spot, "ot"); /* o/a -> ot */ + goto bottom; + } + + /* note: -eau/-eaux (gateau, bordeau...) */ + /* note: ox/oxen, VAX/VAXen, goose/geese */ + + lo_c = lowc(*spot); + + /* Ends in z, x, s, ch, sh; add an "es" */ + if (index("zxs", lo_c) + || (len >= 2 && lo_c == 'h' && index("cs", lowc(*(spot - 1)))) + /* Kludge to get "tomatoes" and "potatoes" right */ + || (len >= 4 && !strcmpi(spot - 2, "ato")) + || (len >= 5 && !strcmpi(spot - 4, "dingo"))) { + Strcasecpy(spot + 1, "es"); /* append es */ + goto bottom; + } + /* Ends in y preceded by consonant (note: also "qu") change to "ies" */ + if (lo_c == 'y' && !index(vowels, lowc(*(spot - 1)))) { + Strcasecpy(spot, "ies"); /* y -> ies */ + goto bottom; + } + /* Default: append an 's' */ + Strcasecpy(spot + 1, "s"); + +bottom: + if (excess) + Strcat(str, excess); + return str; +} + +/* + * Singularize a string the user typed in; this helps reduce the complexity + * of readobjnam, and is also used in pager.c to singularize the string + * for which help is sought. + * + * "Manes" is ambiguous: monster type (keep s), or horse body part (drop s)? + * Its inclusion in as_is[]/special_subj[] makes it get treated as the former. + * + * A lot of unique monsters have names ending in s; plural, or singular + * from plural, doesn't make much sense for them so we don't bother trying. + * 3.6.0: made case-insensitive. + */ +char * +makesingular(oldstr) +const char *oldstr; +{ + register char *p, *bp; + const char *excess = 0; + char *str = nextobuf(); + + if (oldstr) + while (*oldstr == ' ') + oldstr++; + if (!oldstr || !*oldstr) { + impossible("singular of null?"); + str[0] = '\0'; + return str; + } + + bp = strcpy(str, oldstr); + + /* check for "foo of bar" so that we can focus on "foo" */ + if ((p = singplur_compound(bp)) != 0) { + excess = oldstr + (int) (p - bp); + *p = '\0'; + } else + p = eos(bp); + + /* dispense with some words which don't need singularization */ + if (singplur_lookup(bp, p, FALSE, special_subjs)) + goto bottom; + + /* remove -s or -es (boxes) or -ies (rubies) */ + if (p >= bp + 1 && lowc(p[-1]) == 's') { + if (p >= bp + 2 && lowc(p[-2]) == 'e') { + if (p >= bp + 3 && lowc(p[-3]) == 'i') { /* "ies" */ + if (!BSTRCMPI(bp, p - 7, "cookies") + || !BSTRCMPI(bp, p - 4, "pies") + || !BSTRCMPI(bp, p - 5, "mbies") /* zombie */ + || !BSTRCMPI(bp, p - 5, "yries")) /* valkyrie */ + goto mins; + Strcasecpy(p - 3, "y"); /* ies -> y */ + goto bottom; + } + /* wolves, but f to ves isn't fully reversible */ + if (p - 4 >= bp && (index("lr", lowc(*(p - 4))) + || index(vowels, lowc(*(p - 4)))) + && !BSTRCMPI(bp, p - 3, "ves")) { + if (!BSTRCMPI(bp, p - 6, "cloves") + || !BSTRCMPI(bp, p - 6, "nerves")) + goto mins; + Strcasecpy(p - 3, "f"); /* ves -> f */ + goto bottom; + } + /* note: nurses, axes but boxes, wumpuses */ + if (!BSTRCMPI(bp, p - 4, "eses") + || !BSTRCMPI(bp, p - 4, "oxes") /* boxes, foxes */ + || !BSTRCMPI(bp, p - 4, "nxes") /* lynxes */ + || !BSTRCMPI(bp, p - 4, "ches") + || !BSTRCMPI(bp, p - 4, "uses") /* lotuses */ + || !BSTRCMPI(bp, p - 4, "sses") /* priestesses */ + || !BSTRCMPI(bp, p - 5, "atoes") /* tomatoes */ + || !BSTRCMPI(bp, p - 7, "dingoes") + || !BSTRCMPI(bp, p - 7, "Aleaxes")) { + *(p - 2) = '\0'; /* drop es */ + goto bottom; + } /* else fall through to mins */ + + /* ends in 's' but not 'es' */ + } else if (!BSTRCMPI(bp, p - 2, "us")) { /* lotus, fungus... */ + if (BSTRCMPI(bp, p - 6, "tengus") /* but not these... */ + && BSTRCMPI(bp, p - 7, "hezrous")) + goto bottom; + } else if (!BSTRCMPI(bp, p - 2, "ss") + || !BSTRCMPI(bp, p - 5, " lens") + || (p - 4 == bp && !strcmpi(p - 4, "lens"))) { + goto bottom; + } + mins: + *(p - 1) = '\0'; /* drop s */ + + } else { /* input doesn't end in 's' */ + + if (!BSTRCMPI(bp, p - 3, "men") + && !badman(bp, FALSE)) { + Strcasecpy(p - 2, "an"); + goto bottom; + } + /* matzot -> matzo, algae -> alga */ + if (!BSTRCMPI(bp, p - 6, "matzot") || !BSTRCMPI(bp, p - 2, "ae")) { + *(p - 1) = '\0'; /* drop t/e */ + goto bottom; + } + /* balactheria -> balactherium */ + if (p - 4 >= bp && !strcmpi(p - 2, "ia") + && index("lr", lowc(*(p - 3))) && lowc(*(p - 4)) == 'e') { + Strcasecpy(p - 1, "um"); /* a -> um */ + } + + /* here we cannot find the plural suffix */ + } + +bottom: + /* if we stripped off a suffix (" of bar" from "foo of bar"), + put it back now [strcat() isn't actually 100% safe here...] */ + if (excess) + Strcat(bp, excess); + + return bp; +} + +boolean +badman(basestr, to_plural) +const char *basestr; +boolean to_plural; /* true => makeplural, false => makesingular */ +{ + int i, al; + char *endstr, *spot; + /* these are all the prefixes for *man that don't have a *men plural */ + const char *no_men[] = { + "albu", "antihu", "anti", "ata", "auto", "bildungsro", "cai", + "cay", "ceru", "corner", "decu", "des", "dura", "fir", + "glass", "hanu", "het", "infrahu", "inhu", "land", + "meat", "nonhu", "otto", "out", "prehu", "protohu", + "subhu", "superhu", "talis", "unhu", "sha", + "hu", "un", "le", "re", "so", "to", "at", "a", + }; + /* these are all the prefixes for *men that don't have a *man singular */ + const char *no_man[] = { + "abdo", "acu", "agno", "ceru", "cogno", "cycla", "fleh", "grava", + "hegu", "preno", "sonar", "dai", "exa", "fla", "sta", "teg", "tegu", + "vela", "da", "hy", "lu", "no", "nu", "ra", "ru", "se", "vi", "ya", + "o", "a", + }; + + if (!basestr || strlen(basestr) < 4) + return FALSE; + + endstr = eos((char *)basestr); + + if (to_plural) { + for (i = 0; i < SIZE(no_men); i++) { + al = (int) strlen(no_men[i]); + spot = endstr - (al + 3); + if (!BSTRNCMPI(basestr, spot, no_men[i], al) + && (spot == basestr || *(spot - 1) == ' ')) + return TRUE; + } + } else { + for (i = 0; i < SIZE(no_man); i++) { + al = (int) strlen(no_man[i]); + spot = endstr - (al + 3); + if (!BSTRNCMPI(basestr, spot, no_man[i], al) + && (spot == basestr || *(spot - 1) == ' ')) + return TRUE; + } + } + return FALSE; +} + +/* compare user string against object name string using fuzzy matching */ +STATIC_OVL boolean +wishymatch(u_str, o_str, retry_inverted) +const char *u_str; /* from user, so might be variant spelling */ +const char *o_str; /* from objects[], so is in canonical form */ +boolean retry_inverted; /* optional extra "of" handling */ +{ + static NEARDATA const char detect_SP[] = "detect ", + SP_detection[] = " detection"; + char *p, buf[BUFSZ]; + + /* ignore spaces & hyphens and upper/lower case when comparing */ + if (fuzzymatch(u_str, o_str, " -", TRUE)) + return TRUE; + + if (retry_inverted) { + const char *u_of, *o_of; + + /* when just one of the strings is in the form "foo of bar", + convert it into "bar foo" and perform another comparison */ + u_of = strstri(u_str, " of "); + o_of = strstri(o_str, " of "); + if (u_of && !o_of) { + Strcpy(buf, u_of + 4); + p = eos(strcat(buf, " ")); + while (u_str < u_of) + *p++ = *u_str++; + *p = '\0'; + return fuzzymatch(buf, o_str, " -", TRUE); + } else if (o_of && !u_of) { + Strcpy(buf, o_of + 4); + p = eos(strcat(buf, " ")); + while (o_str < o_of) + *p++ = *o_str++; + *p = '\0'; + return fuzzymatch(u_str, buf, " -", TRUE); + } + } + + /* [note: if something like "elven speed boots" ever gets added, these + special cases should be changed to call wishymatch() recursively in + order to get the "of" inversion handling] */ + if (!strncmp(o_str, "dwarvish ", 9)) { + if (!strncmpi(u_str, "dwarven ", 8)) + return fuzzymatch(u_str + 8, o_str + 9, " -", TRUE); + } else if (!strncmp(o_str, "elven ", 6)) { + if (!strncmpi(u_str, "elvish ", 7)) + return fuzzymatch(u_str + 7, o_str + 6, " -", TRUE); + else if (!strncmpi(u_str, "elfin ", 6)) + return fuzzymatch(u_str + 6, o_str + 6, " -", TRUE); + } else if (!strncmp(o_str, detect_SP, sizeof detect_SP - 1)) { + /* check for "detect " vs " detection" */ + if ((p = strstri(u_str, SP_detection)) != 0 + && !*(p + sizeof SP_detection - 1)) { + /* convert " detection" into "detect " */ + *p = '\0'; + Strcat(strcpy(buf, detect_SP), u_str); + /* "detect monster" -> "detect monsters" */ + if (!strcmpi(u_str, "monster")) + Strcat(buf, "s"); + *p = ' '; + return fuzzymatch(buf, o_str, " -", TRUE); + } + } else if (strstri(o_str, SP_detection)) { + /* and the inverse, " detection" vs "detect " */ + if (!strncmpi(u_str, detect_SP, sizeof detect_SP - 1)) { + /* convert "detect s" into " detection" */ + p = makesingular(u_str + sizeof detect_SP - 1); + Strcat(strcpy(buf, p), SP_detection); + /* caller may be looping through objects[], so avoid + churning through all the obufs */ + releaseobuf(p); + return fuzzymatch(buf, o_str, " -", TRUE); + } + } else if (strstri(o_str, "ability")) { + /* when presented with "foo of bar", makesingular() used to + singularize both foo & bar, but now only does so for foo */ + /* catch "{potion(s),ring} of {gain,restore,sustain} abilities" */ + if ((p = strstri(u_str, "abilities")) != 0 + && !*(p + sizeof "abilities" - 1)) { + (void) strncpy(buf, u_str, (unsigned) (p - u_str)); + Strcpy(buf + (p - u_str), "ability"); + return fuzzymatch(buf, o_str, " -", TRUE); + } + } else if (!strcmp(o_str, "aluminum")) { + /* this special case doesn't really fit anywhere else... */ + /* (note that " wand" will have been stripped off by now) */ + if (!strcmpi(u_str, "aluminium")) + return fuzzymatch(u_str + 9, o_str + 8, " -", TRUE); + } + + return FALSE; +} + +struct o_range { + const char *name, oclass; + int f_o_range, l_o_range; +}; + +/* wishable subranges of objects */ +STATIC_OVL NEARDATA const struct o_range o_ranges[] = { + { "bag", TOOL_CLASS, SACK, BAG_OF_TRICKS }, + { "lamp", TOOL_CLASS, OIL_LAMP, MAGIC_LAMP }, + { "candle", TOOL_CLASS, TALLOW_CANDLE, WAX_CANDLE }, + { "horn", TOOL_CLASS, TOOLED_HORN, HORN_OF_PLENTY }, + { "shield", ARMOR_CLASS, SMALL_SHIELD, SHIELD_OF_REFLECTION }, + { "hat", ARMOR_CLASS, FEDORA, DUNCE_CAP }, + { "helm", ARMOR_CLASS, ELVEN_LEATHER_HELM, HELM_OF_TELEPATHY }, + { "gloves", ARMOR_CLASS, LEATHER_GLOVES, GAUNTLETS_OF_DEXTERITY }, + { "gauntlets", ARMOR_CLASS, LEATHER_GLOVES, GAUNTLETS_OF_DEXTERITY }, + { "boots", ARMOR_CLASS, LOW_BOOTS, LEVITATION_BOOTS }, + { "shoes", ARMOR_CLASS, LOW_BOOTS, IRON_SHOES }, + { "cloak", ARMOR_CLASS, MUMMY_WRAPPING, CLOAK_OF_DISPLACEMENT }, + { "shirt", ARMOR_CLASS, HAWAIIAN_SHIRT, T_SHIRT }, + { "dragon scales", ARMOR_CLASS, GRAY_DRAGON_SCALES, + YELLOW_DRAGON_SCALES }, + { "dragon scale mail", ARMOR_CLASS, GRAY_DRAGON_SCALE_MAIL, + YELLOW_DRAGON_SCALE_MAIL }, + { "sword", WEAPON_CLASS, SHORT_SWORD, KATANA }, + { "venom", VENOM_CLASS, BLINDING_VENOM, ACID_VENOM }, + { "gray stone", GEM_CLASS, LUCKSTONE, FLINT }, + { "grey stone", GEM_CLASS, LUCKSTONE, FLINT }, +}; + +/* alternate spellings; if the difference is only the presence or + absence of spaces and/or hyphens (such as "pickaxe" vs "pick axe" + vs "pick-axe") then there is no need for inclusion in this list; + likewise for ``"of" inversions'' ("boots of speed" vs "speed boots") */ +struct alt_spellings { + const char *sp; + int ob; +} spellings[] = { + { "pickax", PICK_AXE }, + { "whip", BULLWHIP }, + { "saber", SILVER_SABER }, + { "silver sabre", SILVER_SABER }, + { "smooth shield", SHIELD_OF_REFLECTION }, + { "grey dragon scale mail", GRAY_DRAGON_SCALE_MAIL }, + { "grey dragon scales", GRAY_DRAGON_SCALES }, + { "iron ball", HEAVY_IRON_BALL }, + { "lantern", BRASS_LANTERN }, + { "mattock", DWARVISH_MATTOCK }, + { "amulet of poison resistance", AMULET_VERSUS_POISON }, + { "potion of sleep", POT_SLEEPING }, + { "stone", ROCK }, + { "camera", EXPENSIVE_CAMERA }, + { "tee shirt", T_SHIRT }, + { "can", TIN }, + { "can opener", TIN_OPENER }, + { "kelp", KELP_FROND }, + { "eucalyptus", EUCALYPTUS_LEAF }, + { "royal jelly", LUMP_OF_ROYAL_JELLY }, + { "lembas", LEMBAS_WAFER }, + { "marker", MAGIC_MARKER }, + { "hook", GRAPPLING_HOOK }, + { "grappling iron", GRAPPLING_HOOK }, + { "grapnel", GRAPPLING_HOOK }, + { "grapple", GRAPPLING_HOOK }, + { "protection from shape shifters", RIN_PROTECTION_FROM_SHAPE_CHAN }, + /* normally we wouldn't have to worry about unnecessary , but + " stone" will get stripped off, preventing a wishymatch; that actually + lets "flint stone" be a match, so we also accept bogus "flintstone" */ + { "luck stone", LUCKSTONE }, + { "load stone", LOADSTONE }, + { "touch stone", TOUCHSTONE }, + { "flintstone", FLINT }, + { (const char *) 0, 0 }, +}; + +STATIC_OVL short +rnd_otyp_by_wpnskill(skill) +schar skill; +{ + int i, n = 0; + short otyp = STRANGE_OBJECT; + for (i = bases[WEAPON_CLASS]; + i < NUM_OBJECTS && objects[i].oc_class == WEAPON_CLASS; i++) + if (objects[i].oc_skill == skill) { + n++; + otyp = i; + } + if (n > 0) { + n = rn2(n); + for (i = bases[WEAPON_CLASS]; + i < NUM_OBJECTS && objects[i].oc_class == WEAPON_CLASS; i++) + if (objects[i].oc_skill == skill) + if (--n < 0) + return i; + } + return otyp; +} + +STATIC_OVL short +rnd_otyp_by_namedesc(name, oclass) +char *name; +char oclass; +{ + int i, n = 0; + short validobjs[NUM_OBJECTS]; + register const char *zn; + long maxprob = 0; + + if (!name) + return STRANGE_OBJECT; + + memset((genericptr_t) validobjs, 0, sizeof(validobjs)); + + for (i = oclass ? bases[(int)oclass] : STRANGE_OBJECT + 1; + i < NUM_OBJECTS && (!oclass || objects[i].oc_class == oclass); + ++i) { + /* don't match extra descriptions (w/o real name) */ + if ((zn = OBJ_NAME(objects[i])) == 0) + continue; + if (wishymatch(name, zn, TRUE) + || ((zn = OBJ_DESCR(objects[i])) != 0 + && wishymatch(name, zn, FALSE)) + || ((zn = objects[i].oc_uname) != 0 + && wishymatch(name, zn, FALSE))) { + validobjs[n++] = (short) i; + maxprob += (objects[i].oc_prob + 1); + } + } + + if (n > 0 && maxprob) { + long prob = rn2(maxprob); + + i = 0; + while (i < n - 1 + && (prob -= (objects[validobjs[i]].oc_prob + 1)) >= 0) + i++; + return validobjs[i]; + } + return STRANGE_OBJECT; +} + +/* + * Return something wished for. Specifying a null pointer for + * the user request string results in a random object. Otherwise, + * if asking explicitly for "nothing" (or "nil") return no_wish; + * if not an object return &zeroobj; if an error (no matching object), + * return null. + */ +struct obj * +readobjnam(bp, no_wish) +register char *bp; +struct obj *no_wish; +{ + register char *p; + register int i; + register struct obj *otmp; + int cnt, spe, spesgn, typ, very, rechrg; + int blessed, uncursed, iscursed, ispoisoned, isgreased; + int eroded, eroded2, erodeproof; + int halfeaten, mntmp, contents; + int islit, unlabeled, ishistoric, isdiluted, trapped; + int tmp, tinv, tvariety; + int wetness, gsize = 0; + struct fruit *f; + int ftype = context.current_fruit; + char fruitbuf[BUFSZ]; + /* Fruits may not mess up the ability to wish for real objects (since + * you can leave a fruit in a bones file and it will be added to + * another person's game), so they must be checked for last, after + * stripping all the possible prefixes and seeing if there's a real + * name in there. So we have to save the full original name. However, + * it's still possible to do things like "uncursed burnt Alaska", + * or worse yet, "2 burned 5 course meals", so we need to loop to + * strip off the prefixes again, this time stripping only the ones + * possible on food. + * We could get even more detailed so as to allow food names with + * prefixes that _are_ possible on food, so you could wish for + * "2 3 alarm chilis". Currently this isn't allowed; options.c + * automatically sticks 'candied' in front of such names. + */ + char oclass; + char *un, *dn, *actualn, *origbp = bp; + const char *name = 0; + + cnt = spe = spesgn = typ = very = rechrg = blessed = uncursed = iscursed = + ispoisoned = isgreased = eroded = eroded2 = erodeproof = halfeaten = + islit = unlabeled = ishistoric = isdiluted = trapped = 0; + tvariety = RANDOM_TIN; + mntmp = NON_PM; +#define UNDEFINED 0 +#define EMPTY 1 +#define SPINACH 2 + contents = UNDEFINED; + oclass = 0; + actualn = dn = un = 0; + wetness = 0; + + if (!bp) + goto any; + /* first, remove extra whitespace they may have typed */ + (void) mungspaces(bp); + /* allow wishing for "nothing" to preserve wishless conduct... + [now requires "wand of nothing" if that's what was really wanted] */ + if (!strcmpi(bp, "nothing") || !strcmpi(bp, "nil") + || !strcmpi(bp, "none")) + return no_wish; + /* save the [nearly] unmodified choice string */ + Strcpy(fruitbuf, bp); + + for (;;) { + register int l; + + if (!bp || !*bp) + goto any; + if (!strncmpi(bp, "an ", l = 3) || !strncmpi(bp, "a ", l = 2)) { + cnt = 1; + } else if (!strncmpi(bp, "the ", l = 4)) { + ; /* just increment `bp' by `l' below */ + } else if (!cnt && digit(*bp) && strcmp(bp, "0")) { + cnt = atoi(bp); + while (digit(*bp)) + bp++; + while (*bp == ' ') + bp++; + l = 0; + } else if (*bp == '+' || *bp == '-') { + spesgn = (*bp++ == '+') ? 1 : -1; + spe = atoi(bp); + while (digit(*bp)) + bp++; + while (*bp == ' ') + bp++; + l = 0; + } else if (!strncmpi(bp, "blessed ", l = 8) + || !strncmpi(bp, "holy ", l = 5)) { + blessed = 1; + } else if (!strncmpi(bp, "moist ", l = 6) + || !strncmpi(bp, "wet ", l = 4)) { + if (!strncmpi(bp, "wet ", 4)) + wetness = rn2(3) + 3; + else + wetness = rnd(2); + } else if (!strncmpi(bp, "cursed ", l = 7) + || !strncmpi(bp, "unholy ", l = 7)) { + iscursed = 1; + } else if (!strncmpi(bp, "uncursed ", l = 9)) { + uncursed = 1; + } else if (!strncmpi(bp, "rustproof ", l = 10) + || !strncmpi(bp, "erodeproof ", l = 11) + || !strncmpi(bp, "corrodeproof ", l = 13) + || !strncmpi(bp, "fixed ", l = 6) + || !strncmpi(bp, "fireproof ", l = 10) + || !strncmpi(bp, "rotproof ", l = 9)) { + erodeproof = 1; + } else if (!strncmpi(bp, "lit ", l = 4) + || !strncmpi(bp, "burning ", l = 8)) { + islit = 1; + } else if (!strncmpi(bp, "unlit ", l = 6) + || !strncmpi(bp, "extinguished ", l = 13)) { + islit = 0; + /* "unlabeled" and "blank" are synonymous */ + } else if (!strncmpi(bp, "unlabeled ", l = 10) + || !strncmpi(bp, "unlabelled ", l = 11) + || !strncmpi(bp, "blank ", l = 6)) { + unlabeled = 1; + } else if (!strncmpi(bp, "poisoned ", l = 9)) { + ispoisoned = 1; + /* "trapped" recognized but not honored outside wizard mode */ + } else if (!strncmpi(bp, "trapped ", l = 8)) { + trapped = 0; /* undo any previous "untrapped" */ + if (wizard) + trapped = 1; + } else if (!strncmpi(bp, "untrapped ", l = 10)) { + trapped = 2; /* not trapped */ + } else if (!strncmpi(bp, "greased ", l = 8)) { + isgreased = 1; + } else if (!strncmpi(bp, "very ", l = 5)) { + /* very rusted very heavy iron ball */ + very = 1; + } else if (!strncmpi(bp, "thoroughly ", l = 11)) { + very = 2; + } else if (!strncmpi(bp, "rusty ", l = 6) + || !strncmpi(bp, "rusted ", l = 7) + || !strncmpi(bp, "burnt ", l = 6) + || !strncmpi(bp, "burned ", l = 7)) { + eroded = 1 + very; + very = 0; + } else if (!strncmpi(bp, "corroded ", l = 9) + || !strncmpi(bp, "rotted ", l = 7)) { + eroded2 = 1 + very; + very = 0; + } else if (!strncmpi(bp, "partly eaten ", l = 13) + || !strncmpi(bp, "partially eaten ", l = 16)) { + halfeaten = 1; + } else if (!strncmpi(bp, "historic ", l = 9)) { + ishistoric = 1; + } else if (!strncmpi(bp, "diluted ", l = 8)) { + isdiluted = 1; + } else if (!strncmpi(bp, "empty ", l = 6)) { + contents = EMPTY; + } else if (!strncmpi(bp, "small ", l = 6)) { /* glob sizes */ + gsize = 1; + } else if (!strncmpi(bp, "medium ", l = 7)) { + /* xname() doesn't display "medium" but without this + there'd be no way to ask for the intermediate size */ + gsize = 2; + } else if (!strncmpi(bp, "large ", l = 6)) { + /* "very large " had "very " peeled off on previous iteration */ + gsize = (very != 1) ? 3 : 4; + } else + break; + bp += l; + } + if (!cnt) + cnt = 1; /* %% what with "gems" etc. ? */ + if (strlen(bp) > 1 && (p = rindex(bp, '(')) != 0) { + boolean keeptrailingchars = TRUE; + + p[(p > bp && p[-1] == ' ') ? -1 : 0] = '\0'; /*terminate bp */ + ++p; /* advance past '(' */ + if (!strncmpi(p, "lit)", 4)) { + islit = 1; + p += 4 - 1; /* point at ')' */ + } else { + spe = atoi(p); + while (digit(*p)) + p++; + if (*p == ':') { + p++; + rechrg = spe; + spe = atoi(p); + while (digit(*p)) + p++; + } + if (*p != ')') { + spe = rechrg = 0; + /* mis-matched parentheses; rest of string will be ignored + * [probably we should restore everything back to '(' + * instead since it might be part of "named ..."] + */ + keeptrailingchars = FALSE; + } else { + spesgn = 1; + } + } + if (keeptrailingchars) { + char *pp = eos(bp); + + /* 'pp' points at 'pb's terminating '\0', + 'p' points at ')' and will be incremented past it */ + do { + *pp++ = *++p; + } while (*p); + } + } + /* + * otmp->spe is type schar, so we don't want spe to be any bigger or + * smaller. Also, spe should always be positive --some cheaters may + * try to confuse atoi(). + */ + if (spe < 0) { + spesgn = -1; /* cheaters get what they deserve */ + spe = abs(spe); + } + if (spe > SCHAR_LIM) + spe = SCHAR_LIM; + if (rechrg < 0 || rechrg > 7) + rechrg = 7; /* recharge_limit */ + + /* now we have the actual name, as delivered by xname, say + * green potions called whisky + * scrolls labeled "QWERTY" + * egg + * fortune cookies + * very heavy iron ball named hoei + * wand of wishing + * elven cloak + */ + if ((p = strstri(bp, " named ")) != 0) { + *p = 0; + name = p + 7; + } + if ((p = strstri(bp, " called ")) != 0) { + *p = 0; + un = p + 8; + /* "helmet called telepathy" is not "helmet" (a specific type) + * "shield called reflection" is not "shield" (a general type) + */ + for (i = 0; i < SIZE(o_ranges); i++) + if (!strcmpi(bp, o_ranges[i].name)) { + oclass = o_ranges[i].oclass; + goto srch; + } + } + if ((p = strstri(bp, " labeled ")) != 0) { + *p = 0; + dn = p + 9; + } else if ((p = strstri(bp, " labelled ")) != 0) { + *p = 0; + dn = p + 10; + } + if ((p = strstri(bp, " of spinach")) != 0) { + *p = 0; + contents = SPINACH; + } + + /* + * Skip over "pair of ", "pairs of", "set of" and "sets of". + * + * Accept "3 pair of boots" as well as "3 pairs of boots". It is + * valid English either way. See makeplural() for more on pair/pairs. + * + * We should only double count if the object in question is not + * referred to as a "pair of". E.g. We should double if the player + * types "pair of spears", but not if the player types "pair of + * lenses". Luckily (?) all objects that are referred to as pairs + * -- boots, gloves, and lenses -- are also not mergable, so cnt is + * ignored anyway. + */ + if (!strncmpi(bp, "pair of ", 8)) { + bp += 8; + cnt *= 2; + } else if (!strncmpi(bp, "pairs of ", 9)) { + bp += 9; + if (cnt > 1) + cnt *= 2; + } else if (!strncmpi(bp, "set of ", 7)) { + bp += 7; + } else if (!strncmpi(bp, "sets of ", 8)) { + bp += 8; + } + + /* intercept pudding globs here; they're a valid wish target, + * but we need them to not get treated like a corpse. + * + * also don't let player wish for multiple globs. + */ + if ((p = strstri(bp, "glob of ")) != 0 + || (p = strstri(bp, "globs of ")) != 0) { + int globoffset = (*(p + 4) == 's') ? 9 : 8; + + if ((mntmp = name_to_mon(p + globoffset)) >= PM_GRAY_OOZE + && mntmp <= PM_BLACK_PUDDING) { + mntmp = NON_PM; /* lie to ourselves */ + cnt = 0; /* force only one */ + } + } else { + /* + * Find corpse type using "of" (figurine of an orc, tin of orc meat) + * Don't check if it's a wand or spellbook. + * (avoid "wand/finger of death" confusion). + */ + if (!strstri(bp, "wand ") && !strstri(bp, "spellbook ") + && !strstri(bp, "finger ")) { + if ((p = strstri(bp, "tin of ")) != 0) { + if (!strcmpi(p + 7, "spinach")) { + contents = SPINACH; + mntmp = NON_PM; + } else { + tmp = tin_variety_txt(p + 7, &tinv); + tvariety = tinv; + mntmp = name_to_mon(p + 7 + tmp); + } + typ = TIN; + goto typfnd; + } else if ((p = strstri(bp, " of ")) != 0 + && (mntmp = name_to_mon(p + 4)) >= LOW_PM) + *p = 0; + } + } + /* Find corpse type w/o "of" (red dragon scale mail, yeti corpse) */ + if (strncmpi(bp, "samurai sword", 13) /* not the "samurai" monster! */ + && strncmpi(bp, "wizard lock", 11) /* not the "wizard" monster! */ + && strncmpi(bp, "ninja-to", 8) /* not the "ninja" rank */ + && strncmpi(bp, "master key", 10) /* not the "master" rank */ + && strncmpi(bp, "magenta", 7)) { /* not the "mage" rank */ + if (mntmp < LOW_PM && strlen(bp) > 2 + && (mntmp = name_to_mon(bp)) >= LOW_PM) { + int mntmptoo, mntmplen; /* double check for rank title */ + char *obp = bp; + + mntmptoo = title_to_mon(bp, (int *) 0, &mntmplen); + bp += (mntmp != mntmptoo) ? (int) strlen(mons[mntmp].mname) + : mntmplen; + if (*bp == ' ') { + bp++; + } else if (!strncmpi(bp, "s ", 2)) { + bp += 2; + } else if (!strncmpi(bp, "es ", 3)) { + bp += 3; + } else if (!*bp && !actualn && !dn && !un && !oclass) { + /* no referent; they don't really mean a monster type */ + bp = obp; + mntmp = NON_PM; + } + } + } + + /* first change to singular if necessary */ + if (*bp) { + char *sng = makesingular(bp); + if (strcmp(bp, sng)) { + if (cnt == 1) + cnt = 2; + Strcpy(bp, sng); + } + } + + /* Alternate spellings (pick-ax, silver sabre, &c) */ + { + struct alt_spellings *as = spellings; + + while (as->sp) { + if (fuzzymatch(bp, as->sp, " -", TRUE)) { + typ = as->ob; + goto typfnd; + } + as++; + } + /* can't use spellings list for this one due to shuffling */ + if (!strncmpi(bp, "grey spell", 10)) + *(bp + 2) = 'a'; + + if ((p = strstri(bp, "armour")) != 0) { + /* skip past "armo", then copy remainder beyond "u" */ + p += 4; + while ((*p = *(p + 1)) != '\0') + ++p; /* self terminating */ + } + } + + /* dragon scales - assumes order of dragons */ + if (!strcmpi(bp, "scales") && mntmp >= PM_GRAY_DRAGON + && mntmp <= PM_YELLOW_DRAGON) { + typ = GRAY_DRAGON_SCALES + mntmp - PM_GRAY_DRAGON; + mntmp = NON_PM; /* no monster */ + goto typfnd; + } + + p = eos(bp); + if (!BSTRCMPI(bp, p - 10, "holy water")) { + typ = POT_WATER; + if ((p - bp) >= 12 && *(p - 12) == 'u') + iscursed = 1; /* unholy water */ + else + blessed = 1; + goto typfnd; + } + if (unlabeled && !BSTRCMPI(bp, p - 6, "scroll")) { + typ = SCR_BLANK_PAPER; + goto typfnd; + } + if (unlabeled && !BSTRCMPI(bp, p - 9, "spellbook")) { + typ = SPE_BLANK_PAPER; + goto typfnd; + } + /* + * NOTE: Gold pieces are handled as objects nowadays, and therefore + * this section should probably be reconsidered as well as the entire + * gold/money concept. Maybe we want to add other monetary units as + * well in the future. (TH) + */ + if (!BSTRCMPI(bp, p - 10, "gold piece") + || !BSTRCMPI(bp, p - 7, "zorkmid") + || !strcmpi(bp, "gold") || !strcmpi(bp, "money") + || !strcmpi(bp, "coin") || *bp == GOLD_SYM) { + if (cnt > 5000 && !wizard) + cnt = 5000; + else if (cnt < 1) + cnt = 1; + otmp = mksobj(GOLD_PIECE, FALSE, FALSE); + otmp->quan = (long) cnt; + otmp->owt = weight(otmp); + context.botl = 1; + return otmp; + } + + /* check for single character object class code ("/" for wand, &c) */ + if (strlen(bp) == 1 && (i = def_char_to_objclass(*bp)) < MAXOCLASSES + && i > ILLOBJ_CLASS && (i != VENOM_CLASS || wizard)) { + oclass = i; + goto any; + } + + /* Search for class names: XXXXX potion, scroll of XXXXX. Avoid */ + /* false hits on, e.g., rings for "ring mail". */ + if (strncmpi(bp, "enchant ", 8) + && strncmpi(bp, "destroy ", 8) + && strncmpi(bp, "detect food", 11) + && strncmpi(bp, "food detection", 14) + && strncmpi(bp, "ring mail", 9) + && strncmpi(bp, "studded leather armor", 21) + && strncmpi(bp, "leather armor", 13) + && strncmpi(bp, "tooled horn", 11) + && strncmpi(bp, "food ration", 11) + && strncmpi(bp, "meat ring", 9)) + for (i = 0; i < (int) (sizeof wrpsym); i++) { + register int j = strlen(wrp[i]); + + if (!strncmpi(bp, wrp[i], j)) { + oclass = wrpsym[i]; + if (oclass != AMULET_CLASS) { + bp += j; + if (!strncmpi(bp, " of ", 4)) + actualn = bp + 4; + /* else if(*bp) ?? */ + } else + actualn = bp; + goto srch; + } + if (!BSTRCMPI(bp, p - j, wrp[i])) { + oclass = wrpsym[i]; + p -= j; + *p = 0; + if (p > bp && p[-1] == ' ') + p[-1] = 0; + actualn = dn = bp; + goto srch; + } + } + + /* Wishing in wizard mode can create traps and furniture. + * Part I: distinguish between trap and object for the two + * types of traps which have corresponding objects: bear trap + * and land mine. "beartrap" (object) and "bear trap" (trap) + * have a difference in spelling which we used to exploit by + * adding a special case in wishymatch(), but "land mine" is + * spelled the same either way so needs different handing. + * Since we need something else for land mine, we've dropped + * the bear trap hack so that both are handled exactly the + * same. To get an armed trap instead of a disarmed object, + * the player can prefix either the object name or the trap + * name with "trapped " (which ordinarily applies to chests + * and tins), or append something--anything at all except for + * " object", but " trap" is suggested--to either the trap + * name or the object name. + */ + if (wizard && (!strncmpi(bp, "bear", 4) || !strncmpi(bp, "land", 4))) { + boolean beartrap = (lowc(*bp) == 'b'); + char *zp = bp + 4; /* skip "bear"/"land" */ + + if (*zp == ' ') + ++zp; /* embedded space is optional */ + if (!strncmpi(zp, beartrap ? "trap" : "mine", 4)) { + zp += 4; + if (trapped == 2 || !strcmpi(zp, " object")) { + /* "untrapped " or " object" */ + typ = beartrap ? BEARTRAP : LAND_MINE; + goto typfnd; + } else if (trapped == 1 || *zp != '\0') { + /* "trapped " or " trap" (actually "*") */ + int idx = trap_to_defsym(beartrap ? BEAR_TRAP : LANDMINE); + + /* use canonical trap spelling, skip object matching */ + Strcpy(bp, defsyms[idx].explanation); + goto wiztrap; + } + /* [no prefix or suffix; we're going to end up matching + the object name and getting a disarmed trap object] */ + } + } + +retry: + /* "grey stone" check must be before general "stone" */ + for (i = 0; i < SIZE(o_ranges); i++) + if (!strcmpi(bp, o_ranges[i].name)) { + typ = rnd_class(o_ranges[i].f_o_range, o_ranges[i].l_o_range); + goto typfnd; + } + + if (!BSTRCMPI(bp, p - 6, " stone") || !BSTRCMPI(bp, p - 4, " gem")) { + p[!strcmpi(p - 4, " gem") ? -4 : -6] = '\0'; + oclass = GEM_CLASS; + dn = actualn = bp; + goto srch; + } else if (!strcmpi(bp, "looking glass")) { + ; /* avoid false hit on "* glass" */ + } else if (!BSTRCMPI(bp, p - 6, " glass") || !strcmpi(bp, "glass")) { + register char *g = bp; + if (strstri(g, "broken")) + return (struct obj *) 0; + if (!strncmpi(g, "worthless ", 10)) + g += 10; + if (!strncmpi(g, "piece of ", 9)) + g += 9; + if (!strncmpi(g, "colored ", 8)) + g += 8; + else if (!strncmpi(g, "coloured ", 9)) + g += 9; + if (!strcmpi(g, "glass")) { /* choose random color */ + /* 9 different kinds */ + typ = LAST_GEM + rnd(9); + if (objects[typ].oc_class == GEM_CLASS) + goto typfnd; + else + typ = 0; /* somebody changed objects[]? punt */ + } else { /* try to construct canonical form */ + char tbuf[BUFSZ]; + + Strcpy(tbuf, "worthless piece of "); + Strcat(tbuf, g); /* assume it starts with the color */ + Strcpy(bp, tbuf); + } + } + + actualn = bp; + if (!dn) + dn = actualn; /* ex. "skull cap" */ +srch: + /* check real names of gems first */ + if (!oclass && actualn) { + for (i = bases[GEM_CLASS]; i <= LAST_GEM; i++) { + register const char *zn; + + if ((zn = OBJ_NAME(objects[i])) != 0 && !strcmpi(actualn, zn)) { + typ = i; + goto typfnd; + } + } + /* "tin of foo" would be caught above, but plain "tin" has + a random chance of yielding "tin wand" unless we do this */ + if (!strcmpi(actualn, "tin")) { + typ = TIN; + goto typfnd; + } + } + + if (((typ = rnd_otyp_by_namedesc(actualn, oclass)) != STRANGE_OBJECT) + || ((typ = rnd_otyp_by_namedesc(dn, oclass)) != STRANGE_OBJECT) + || ((typ = rnd_otyp_by_namedesc(un, oclass)) != STRANGE_OBJECT) + || ((typ = rnd_otyp_by_namedesc(origbp, oclass)) != STRANGE_OBJECT)) + goto typfnd; + typ = 0; + + if (actualn) { + struct Jitem *j = Japanese_items; + + while (j->item) { + if (actualn && !strcmpi(actualn, j->name)) { + typ = j->item; + goto typfnd; + } + j++; + } + } + /* if we've stripped off "armor" and failed to match anything + in objects[], append "mail" and try again to catch misnamed + requests like "plate armor" and "yellow dragon scale armor" */ + if (oclass == ARMOR_CLASS && !strstri(bp, "mail")) { + /* modifying bp's string is ok; we're about to resort + to random armor if this also fails to match anything */ + Strcat(bp, " mail"); + goto retry; + } + if (!strcmpi(bp, "spinach")) { + contents = SPINACH; + typ = TIN; + goto typfnd; + } + /* Note: not strcmpi. 2 fruits, one capital, one not, are possible. + Also not strncmp. We used to ignore trailing text with it, but + that resulted in "grapefruit" matching "grape" if the latter came + earlier than the former in the fruit list. */ + { + char *fp; + int l, cntf; + int blessedf, iscursedf, uncursedf, halfeatenf; + + blessedf = iscursedf = uncursedf = halfeatenf = 0; + cntf = 0; + + fp = fruitbuf; + for (;;) { + if (!fp || !*fp) + break; + if (!strncmpi(fp, "an ", l = 3) || !strncmpi(fp, "a ", l = 2)) { + cntf = 1; + } else if (!cntf && digit(*fp)) { + cntf = atoi(fp); + while (digit(*fp)) + fp++; + while (*fp == ' ') + fp++; + l = 0; + } else if (!strncmpi(fp, "blessed ", l = 8)) { + blessedf = 1; + } else if (!strncmpi(fp, "cursed ", l = 7)) { + iscursedf = 1; + } else if (!strncmpi(fp, "uncursed ", l = 9)) { + uncursedf = 1; + } else if (!strncmpi(fp, "partly eaten ", l = 13) + || !strncmpi(fp, "partially eaten ", l = 16)) { + halfeatenf = 1; + } else + break; + fp += l; + } + + for (f = ffruit; f; f = f->nextf) { + /* match type: 0=none, 1=exact, 2=singular, 3=plural */ + int ftyp = 0; + + if (!strcmp(fp, f->fname)) + ftyp = 1; + else if (!strcmp(fp, makesingular(f->fname))) + ftyp = 2; + else if (!strcmp(fp, makeplural(f->fname))) + ftyp = 3; + if (ftyp) { + typ = SLIME_MOLD; + blessed = blessedf; + iscursed = iscursedf; + uncursed = uncursedf; + halfeaten = halfeatenf; + /* adjust count if user explicitly asked for + singular amount (can't happen unless fruit + has been given an already pluralized name) + or for plural amount */ + if (ftyp == 2 && !cntf) + cntf = 1; + else if (ftyp == 3 && !cntf) + cntf = 2; + cnt = cntf; + ftype = f->fid; + goto typfnd; + } + } + } + + if (!oclass && actualn) { + short objtyp; + + /* Perhaps it's an artifact specified by name, not type */ + name = artifact_name(actualn, &objtyp); + if (name) { + typ = objtyp; + goto typfnd; + } + } +/* Let wizards wish for traps and furniture. + * Must come after objects check so wizards can still wish for + * trap objects like beartraps. + * Disallow such topology tweaks for WIZKIT startup wishes. + */ +wiztrap: + if (wizard && !program_state.wizkit_wishing) { + struct rm *lev; + int trap, x = u.ux, y = u.uy; + + for (trap = NO_TRAP + 1; trap < TRAPNUM; trap++) { + struct trap *t; + const char *tname; + + tname = defsyms[trap_to_defsym(trap)].explanation; + if (strncmpi(tname, bp, strlen(tname))) + continue; + /* found it; avoid stupid mistakes */ + if ((trap == TRAPDOOR || trap == HOLE) && !Can_fall_thru(&u.uz)) + trap = ROCKTRAP; + if ((t = maketrap(x, y, trap)) != 0) { + trap = t->ttyp; + tname = defsyms[trap_to_defsym(trap)].explanation; + pline("%s%s.", An(tname), + (trap != MAGIC_PORTAL) ? "" : " to nowhere"); + } else + pline("Creation of %s failed.", an(tname)); + return &zeroobj; + } + + /* furniture and terrain */ + lev = &levl[x][y]; + p = eos(bp); + if (!BSTRCMPI(bp, p - 8, "fountain")) { + lev->typ = FOUNTAIN; + level.flags.nfountains++; + if (!strncmpi(bp, "magic ", 6)) + lev->blessedftn = 1; + pline("A %sfountain.", lev->blessedftn ? "magic " : ""); + newsym(x, y); + return &zeroobj; + } + if (!BSTRCMPI(bp, p - 6, "throne")) { + lev->typ = THRONE; + pline("A throne."); + newsym(x, y); + return &zeroobj; + } + if (!BSTRCMPI(bp, p - 4, "sink")) { + lev->typ = SINK; + level.flags.nsinks++; + pline("A sink."); + newsym(x, y); + return &zeroobj; + } + /* ("water" matches "potion of water" rather than terrain) */ + if (!BSTRCMPI(bp, p - 4, "pool") || !BSTRCMPI(bp, p - 4, "moat")) { + lev->typ = !BSTRCMPI(bp, p - 4, "pool") ? POOL : MOAT; + del_engr_at(x, y); + pline("A %s.", (lev->typ == POOL) ? "pool" : "moat"); + /* Must manually make kelp! */ + water_damage_chain(level.objects[x][y], TRUE); + newsym(x, y); + return &zeroobj; + } + if (!BSTRCMPI(bp, p - 4, "lava")) { /* also matches "molten lava" */ + lev->typ = LAVAPOOL; + del_engr_at(x, y); + pline("A pool of molten lava."); + if (!(Levitation || Flying)) + (void) lava_effects(); + newsym(x, y); + return &zeroobj; + } + + if (!BSTRCMPI(bp, p - 5, "altar")) { + aligntyp al; + + lev->typ = ALTAR; + if (!strncmpi(bp, "chaotic ", 8)) + al = A_CHAOTIC; + else if (!strncmpi(bp, "neutral ", 8)) + al = A_NEUTRAL; + else if (!strncmpi(bp, "lawful ", 7)) + al = A_LAWFUL; + else if (!strncmpi(bp, "unaligned ", 10)) + al = A_NONE; + else /* -1 - A_CHAOTIC, 0 - A_NEUTRAL, 1 - A_LAWFUL */ + al = (!rn2(6)) ? A_NONE : rn2((int) A_LAWFUL + 2) - 1; + lev->altarmask = Align2amask(al); + pline("%s altar.", An(align_str(al))); + newsym(x, y); + return &zeroobj; + } + + if (!BSTRCMPI(bp, p - 5, "grave") + || !BSTRCMPI(bp, p - 9, "headstone")) { + make_grave(x, y, (char *) 0); + pline("%s.", IS_GRAVE(lev->typ) ? "A grave" + : "Can't place a grave here"); + newsym(x, y); + return &zeroobj; + } + + if (!BSTRCMPI(bp, p - 4, "tree")) { + lev->typ = TREE; + pline("A tree."); + newsym(x, y); + block_point(x, y); + return &zeroobj; + } + + if (!BSTRCMPI(bp, p - 4, "bars")) { + lev->typ = IRONBARS; + pline("Iron bars."); + newsym(x, y); + return &zeroobj; + } + } + + if (!oclass && !typ) { + if (!strncmpi(bp, "polearm", 7)) { + typ = rnd_otyp_by_wpnskill(P_POLEARMS); + goto typfnd; + } else if (!strncmpi(bp, "hammer", 6)) { + typ = rnd_otyp_by_wpnskill(P_HAMMER); + goto typfnd; + } + } + + if (!oclass) + return ((struct obj *) 0); +any: + if (!oclass) + oclass = wrpsym[rn2((int) sizeof(wrpsym))]; +typfnd: + if (typ) + oclass = objects[typ].oc_class; + + /* handle some objects that are only allowed in wizard mode */ + if (typ && !wizard) { + switch (typ) { + case AMULET_OF_YENDOR: + typ = FAKE_AMULET_OF_YENDOR; + break; + case CANDELABRUM_OF_INVOCATION: + typ = rnd_class(TALLOW_CANDLE, WAX_CANDLE); + break; + case BELL_OF_OPENING: + typ = BELL; + break; + case SPE_BOOK_OF_THE_DEAD: + typ = SPE_BLANK_PAPER; + break; + case MAGIC_LAMP: + typ = OIL_LAMP; + break; + default: + /* catch any other non-wishable objects (venom) */ + if (objects[typ].oc_nowish) + return (struct obj *) 0; + break; + } + } + + /* + * Create the object, then fine-tune it. + */ + otmp = typ ? mksobj(typ, TRUE, FALSE) : mkobj(oclass, FALSE); + typ = otmp->otyp, oclass = otmp->oclass; /* what we actually got */ + + if (islit && (typ == OIL_LAMP || typ == MAGIC_LAMP || typ == BRASS_LANTERN + || Is_candle(otmp) || typ == POT_OIL)) { + place_object(otmp, u.ux, u.uy); /* make it viable light source */ + begin_burn(otmp, FALSE); + obj_extract_self(otmp); /* now release it for caller's use */ + } + + /* if player specified a reasonable count, maybe honor it */ + if (cnt > 0 && objects[typ].oc_merge + && (wizard || cnt < rnd(6) || (cnt <= 7 && Is_candle(otmp)) + || (cnt <= 20 && ((oclass == WEAPON_CLASS && is_ammo(otmp)) + || typ == ROCK || is_missile(otmp))))) + otmp->quan = (long) cnt; + + if (oclass == VENOM_CLASS) + otmp->spe = 1; + + if (spesgn == 0) { + spe = otmp->spe; + } else if (wizard) { + ; /* no alteration to spe */ + } else if (oclass == ARMOR_CLASS || oclass == WEAPON_CLASS + || is_weptool(otmp) + || (oclass == RING_CLASS && objects[typ].oc_charged)) { + if (spe > rnd(5) && spe > otmp->spe) + spe = 0; + if (spe > 2 && Luck < 0) + spesgn = -1; + } else { + if (oclass == WAND_CLASS) { + if (spe > 1 && spesgn == -1) + spe = 1; + } else { + if (spe > 0 && spesgn == -1) + spe = 0; + } + if (spe > otmp->spe) + spe = otmp->spe; + } + + if (spesgn == -1) + spe = -spe; + + /* set otmp->spe. This may, or may not, use spe... */ + switch (typ) { + case TIN: + if (contents == EMPTY) { + otmp->corpsenm = NON_PM; + otmp->spe = 0; + } else if (contents == SPINACH) { + otmp->corpsenm = NON_PM; + otmp->spe = 1; + } + break; + case TOWEL: + if (wetness) + otmp->spe = wetness; + break; + case SLIME_MOLD: + otmp->spe = ftype; + /* Fall through */ + case SKELETON_KEY: + case CHEST: + case LARGE_BOX: + case HEAVY_IRON_BALL: + case IRON_CHAIN: + case STATUE: + /* otmp->cobj already done in mksobj() */ + break; +#ifdef MAIL + case SCR_MAIL: + /* 0: delivered in-game via external event (or randomly for fake mail); + 1: from bones or wishing; 2: written with marker */ + otmp->spe = 1; + break; +#endif + case WAN_WISHING: + if (!wizard) { + otmp->spe = (rn2(10) ? -1 : 0); + break; + } + /* fall through, if wizard */ + default: + otmp->spe = spe; + } + + /* set otmp->corpsenm or dragon scale [mail] */ + if (mntmp >= LOW_PM) { + if (mntmp == PM_LONG_WORM_TAIL) + mntmp = PM_LONG_WORM; + + switch (typ) { + case TIN: + otmp->spe = 0; /* No spinach */ + if (dead_species(mntmp, FALSE)) { + otmp->corpsenm = NON_PM; /* it's empty */ + } else if ((!(mons[mntmp].geno & G_UNIQ) || wizard) + && !(mvitals[mntmp].mvflags & G_NOCORPSE) + && mons[mntmp].cnutrit != 0) { + otmp->corpsenm = mntmp; + } + break; + case CORPSE: + if ((!(mons[mntmp].geno & G_UNIQ) || wizard) + && !(mvitals[mntmp].mvflags & G_NOCORPSE)) { + if (mons[mntmp].msound == MS_GUARDIAN) + mntmp = genus(mntmp, 1); + set_corpsenm(otmp, mntmp); + } + break; + case EGG: + mntmp = can_be_hatched(mntmp); + /* this also sets hatch timer if appropriate */ + set_corpsenm(otmp, mntmp); + break; + case FIGURINE: + if (!(mons[mntmp].geno & G_UNIQ) && !is_human(&mons[mntmp]) +#ifdef MAIL + && mntmp != PM_MAIL_DAEMON +#endif + ) + otmp->corpsenm = mntmp; + break; + case STATUE: + otmp->corpsenm = mntmp; + if (Has_contents(otmp) && verysmall(&mons[mntmp])) + delete_contents(otmp); /* no spellbook */ + otmp->spe = ishistoric ? STATUE_HISTORIC : 0; + break; + case SCALE_MAIL: + /* Dragon mail - depends on the order of objects & dragons. */ + if (mntmp >= PM_GRAY_DRAGON && mntmp <= PM_YELLOW_DRAGON) + otmp->otyp = GRAY_DRAGON_SCALE_MAIL + mntmp - PM_GRAY_DRAGON; + break; + } + } + + /* set blessed/cursed -- setting the fields directly is safe + * since weight() is called below and addinv() will take care + * of luck */ + if (iscursed) { + curse(otmp); + } else if (uncursed) { + otmp->blessed = 0; + otmp->cursed = (Luck < 0 && !wizard); + } else if (blessed) { + otmp->blessed = (Luck >= 0 || wizard); + otmp->cursed = (Luck < 0 && !wizard); + } else if (spesgn < 0) { + curse(otmp); + } + + /* set eroded and erodeproof */ + if (erosion_matters(otmp)) { + if (eroded && (is_flammable(otmp) || is_rustprone(otmp))) + otmp->oeroded = eroded; + if (eroded2 && (is_corrodeable(otmp) || is_rottable(otmp))) + otmp->oeroded2 = eroded2; + /* + * 3.6.1: earlier versions included `&& !eroded && !eroded2' here, + * but damageproof combined with damaged is feasible (eroded + * armor modified by confused reading of cursed destroy armor) + * so don't prevent player from wishing for such a combination. + */ + if (erodeproof && (is_damageable(otmp) || otmp->otyp == CRYSKNIFE)) + otmp->oerodeproof = (Luck >= 0 || wizard); + } + + /* set otmp->recharged */ + if (oclass == WAND_CLASS) { + /* prevent wishing abuse */ + if (otmp->otyp == WAN_WISHING && !wizard) + rechrg = 1; + otmp->recharged = (unsigned) rechrg; + } + + /* set poisoned */ + if (ispoisoned) { + if (is_poisonable(otmp)) + otmp->opoisoned = (Luck >= 0); + else if (oclass == FOOD_CLASS) + /* try to taint by making it as old as possible */ + otmp->age = 1L; + } + /* and [un]trapped */ + if (trapped) { + if (Is_box(otmp) || typ == TIN) + otmp->otrapped = (trapped == 1); + } + + if (isgreased) + otmp->greased = 1; + + if (isdiluted && otmp->oclass == POTION_CLASS && otmp->otyp != POT_WATER) + otmp->odiluted = 1; + + /* set tin variety */ + if (otmp->otyp == TIN && tvariety >= 0 && (rn2(4) || wizard)) + set_tin_variety(otmp, tvariety); + + if (name) { + const char *aname; + short objtyp; + + /* an artifact name might need capitalization fixing */ + aname = artifact_name(name, &objtyp); + if (aname && objtyp == otmp->otyp) + name = aname; + + /* 3.6 tribute - fix up novel */ + if (otmp->otyp == SPE_NOVEL) { + const char *novelname; + + novelname = lookup_novel(name, &otmp->novelidx); + if (novelname) + name = novelname; + } + + otmp = oname(otmp, name); + /* name==aname => wished for artifact (otmp->oartifact => got it) */ + if (otmp->oartifact || name == aname) { + otmp->quan = 1L; + u.uconduct.wisharti++; /* KMH, conduct */ + } + } + + /* more wishing abuse: don't allow wishing for certain artifacts */ + /* and make them pay; charge them for the wish anyway! */ + if ((is_quest_artifact(otmp) + || (otmp->oartifact && rn2(nartifact_exist()) > 1)) && !wizard) { + artifact_exists(otmp, safe_oname(otmp), FALSE); + obfree(otmp, (struct obj *) 0); + otmp = &zeroobj; + pline("For a moment, you feel %s in your %s, but it disappears!", + something, makeplural(body_part(HAND))); + } + + if (halfeaten && otmp->oclass == FOOD_CLASS) { + if (otmp->otyp == CORPSE) + otmp->oeaten = mons[otmp->corpsenm].cnutrit; + else + otmp->oeaten = objects[otmp->otyp].oc_nutrition; + /* (do this adjustment before setting up object's weight) */ + consume_oeaten(otmp, 1); + } + otmp->owt = weight(otmp); + if (very && otmp->otyp == HEAVY_IRON_BALL) + otmp->owt += IRON_BALL_W_INCR; + else if (gsize > 1 && otmp->globby) + /* 0: unspecified => small; 1: small => keep default owt of 20; + 2: medium => 120; 3: large => 320; 4: very large => 520 */ + otmp->owt += 100 + (gsize - 2) * 200; + + return otmp; +} + +int +rnd_class(first, last) +int first, last; +{ + int i, x, sum = 0; + + if (first == last) + return first; + for (i = first; i <= last; i++) + sum += objects[i].oc_prob; + if (!sum) /* all zero */ + return first + rn2(last - first + 1); + x = rnd(sum); + for (i = first; i <= last; i++) + if (objects[i].oc_prob && (x -= objects[i].oc_prob) <= 0) + return i; + return 0; +} + +STATIC_OVL const char * +Japanese_item_name(i) +int i; +{ + struct Jitem *j = Japanese_items; + + while (j->item) { + if (i == j->item) + return j->name; + j++; + } + return (const char *) 0; +} + +const char * +suit_simple_name(suit) +struct obj *suit; +{ + const char *suitnm, *esuitp; + + if (Is_dragon_mail(suit)) + return "dragon mail"; /* dragon scale mail */ + else if (Is_dragon_scales(suit)) + return "dragon scales"; + suitnm = OBJ_NAME(objects[suit->otyp]); + esuitp = eos((char *) suitnm); + if (strlen(suitnm) > 5 && !strcmp(esuitp - 5, " mail")) + return "mail"; /* most suits fall into this category */ + else if (strlen(suitnm) > 7 && !strcmp(esuitp - 7, " jacket")) + return "jacket"; /* leather jacket */ + /* suit is lame but armor is ambiguous and body armor is absurd */ + return "suit"; +} + +const char * +cloak_simple_name(cloak) +struct obj *cloak; +{ + if (cloak) { + switch (cloak->otyp) { + case ROBE: + return "robe"; + case MUMMY_WRAPPING: + return "wrapping"; + case ALCHEMY_SMOCK: + return (objects[cloak->otyp].oc_name_known && cloak->dknown) + ? "smock" + : "apron"; + default: + break; + } + } + return "cloak"; +} + +/* helm vs hat for messages */ +const char * +helm_simple_name(helmet) +struct obj *helmet; +{ + /* + * There is some wiggle room here; the result has been chosen + * for consistency with the "protected by hard helmet" messages + * given for various bonks on the head: headgear that provides + * such protection is a "helm", that which doesn't is a "hat". + * + * elven leather helm / leather hat -> hat + * dwarvish iron helm / hard hat -> helm + * The rest are completely straightforward: + * fedora, cornuthaum, dunce cap -> hat + * all other types of helmets -> helm + */ + return (helmet && !is_metallic(helmet)) ? "hat" : "helm"; +} + +const char * +mimic_obj_name(mtmp) +struct monst *mtmp; +{ + if (mtmp->m_ap_type == M_AP_OBJECT) { + if (mtmp->mappearance == GOLD_PIECE) + return "gold"; + if (mtmp->mappearance != STRANGE_OBJECT) + return simple_typename(mtmp->mappearance); + } + return "whatcha-may-callit"; +} + +/* + * Construct a query prompt string, based around an object name, which is + * guaranteed to fit within [QBUFSZ]. Takes an optional prefix, three + * choices for filling in the middle (two object formatting functions and a + * last resort literal which should be very short), and an optional suffix. + */ +char * +safe_qbuf(qbuf, qprefix, qsuffix, obj, func, altfunc, lastR) +char *qbuf; /* output buffer */ +const char *qprefix, *qsuffix; +struct obj *obj; +char *FDECL((*func), (OBJ_P)), *FDECL((*altfunc), (OBJ_P)); +const char *lastR; +{ + char *bufp, *endp; + /* convert size_t (or int for ancient systems) to ordinary unsigned */ + unsigned len, lenlimit, + len_qpfx = (unsigned) (qprefix ? strlen(qprefix) : 0), + len_qsfx = (unsigned) (qsuffix ? strlen(qsuffix) : 0), + len_lastR = (unsigned) strlen(lastR); + + lenlimit = QBUFSZ - 1; + endp = qbuf + lenlimit; + /* sanity check, aimed mainly at paniclog (it's conceivable for + the result of short_oname() to be shorter than the length of + the last resort string, but we ignore that possibility here) */ + if (len_qpfx > lenlimit) + impossible("safe_qbuf: prefix too long (%u characters).", len_qpfx); + else if (len_qpfx + len_qsfx > lenlimit) + impossible("safe_qbuf: suffix too long (%u + %u characters).", + len_qpfx, len_qsfx); + else if (len_qpfx + len_lastR + len_qsfx > lenlimit) + impossible("safe_qbuf: filler too long (%u + %u + %u characters).", + len_qpfx, len_lastR, len_qsfx); + + /* the output buffer might be the same as the prefix if caller + has already partially filled it */ + if (qbuf == qprefix) { + /* prefix is already in the buffer */ + *endp = '\0'; + } else if (qprefix) { + /* put prefix into the buffer */ + (void) strncpy(qbuf, qprefix, lenlimit); + *endp = '\0'; + } else { + /* no prefix; output buffer starts out empty */ + qbuf[0] = '\0'; + } + len = (unsigned) strlen(qbuf); + + if (len + len_lastR + len_qsfx > lenlimit) { + /* too long; skip formatting, last resort output is truncated */ + if (len < lenlimit) { + (void) strncpy(&qbuf[len], lastR, lenlimit - len); + *endp = '\0'; + len = (unsigned) strlen(qbuf); + if (qsuffix && len < lenlimit) { + (void) strncpy(&qbuf[len], qsuffix, lenlimit - len); + *endp = '\0'; + /* len = (unsigned) strlen(qbuf); */ + } + } + } else { + /* suffix and last resort are guaranteed to fit */ + len += len_qsfx; /* include the pending suffix */ + /* format the object */ + bufp = short_oname(obj, func, altfunc, lenlimit - len); + if (len + strlen(bufp) <= lenlimit) + Strcat(qbuf, bufp); /* formatted name fits */ + else + Strcat(qbuf, lastR); /* use last resort */ + releaseobuf(bufp); + + if (qsuffix) + Strcat(qbuf, qsuffix); + } + /* assert( strlen(qbuf) < QBUFSZ ); */ + return qbuf; +} + +/*objnam.c*/ From ff67fac61492204d6c3b883c5f1818a55890c31f Mon Sep 17 00:00:00 2001 From: nhmall Date: Sun, 18 Mar 2018 10:09:45 -0400 Subject: [PATCH 21/31] remove a couple where normal man->men rules should apply --- src/objnam.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/objnam.c b/src/objnam.c index 01adc2943..95d67edd4 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -2472,18 +2472,18 @@ boolean to_plural; /* true => makeplural, false => makesingular */ char *endstr, *spot; /* these are all the prefixes for *man that don't have a *men plural */ const char *no_men[] = { - "albu", "antihu", "anti", "ata", "auto", "cai", - "cay", "ceru", "corner", "decu", "des", "dura", "fir", - "glass", "hanu", "het", "infrahu", "inhu", "land", - "meat", "nonhu", "otto", "out", "prehu", "protohu", + "albu", "antihu", "anti", "ata", "auto", "bildungsro", "cai", "cay", + "ceru", "corner", "decu", "des", "dura", "fir", "hanu", "het", + "infrahu", "inhu", "nonhu", "otto", "out", "prehu", "protohu", "subhu", "superhu", "talis", "unhu", "sha", "hu", "un", "le", "re", "so", "to", "at", "a", }; /* these are all the prefixes for *men that don't have a *man singular */ const char *no_man[] = { - "acu", "ceru", "cogno", "cycla", "fleh", "hegu", "preno", "sonar", - "dai", "exa", "fla", "sta", "teg", "tegu", "vela", - "da", "hy", "lu", "no", "nu", "ra", "ru", "se", "vi", "ya", "o" + "abdo", "acu", "agno", "ceru", "cogno", "cycla", "fleh", "grava", + "hegu", "preno", "sonar", "dai", "exa", "fla", "sta", "teg", "tegu", + "vela", "da", "hy", "lu", "no", "nu", "ra", "ru", "se", "vi", "ya", + "o", "a", }; if (!basestr || strlen(basestr) < 4) From 1d5f67c9ed9b462c7198add9297cc1026de2a33f Mon Sep 17 00:00:00 2001 From: nhmall Date: Sun, 18 Mar 2018 10:11:51 -0400 Subject: [PATCH 22/31] misplacement --- objnam.c | 4062 ------------------------------------------------------ 1 file changed, 4062 deletions(-) delete mode 100644 objnam.c diff --git a/objnam.c b/objnam.c deleted file mode 100644 index ecb5de377..000000000 --- a/objnam.c +++ /dev/null @@ -1,4062 +0,0 @@ -/* NetHack 3.6 objnam.c $NHDT-Date: 1521377345 2018/03/18 12:49:05 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.194 $ */ -/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ -/* NetHack may be freely redistributed. See license for details. */ - -#include "hack.h" - -/* "an uncursed greased partly eaten guardian naga hatchling [corpse]" */ -#define PREFIX 80 /* (56) */ -#define SCHAR_LIM 127 -#define NUMOBUF 12 - -STATIC_DCL char *FDECL(strprepend, (char *, const char *)); -STATIC_DCL short FDECL(rnd_otyp_by_wpnskill, (SCHAR_P)); -STATIC_DCL short FDECL(rnd_otyp_by_namedesc, (char *, CHAR_P)); -STATIC_DCL boolean FDECL(wishymatch, (const char *, const char *, BOOLEAN_P)); -STATIC_DCL char *NDECL(nextobuf); -STATIC_DCL void FDECL(releaseobuf, (char *)); -STATIC_DCL char *FDECL(minimal_xname, (struct obj *)); -STATIC_DCL void FDECL(add_erosion_words, (struct obj *, char *)); -STATIC_DCL char *FDECL(doname_base, (struct obj *obj, unsigned)); -STATIC_DCL boolean FDECL(singplur_lookup, (char *, char *, BOOLEAN_P, - const char *const *)); -STATIC_DCL char *FDECL(singplur_compound, (char *)); -STATIC_DCL char *FDECL(xname_flags, (struct obj *, unsigned)); -STATIC_DCL boolean FDECL(badman, (const char *, BOOLEAN_P)); - -struct Jitem { - int item; - const char *name; -}; - -#define BSTRCMPI(base, ptr, str) ((ptr) < base || strcmpi((ptr), str)) -#define BSTRNCMPI(base, ptr, str, num) \ - ((ptr) < base || strncmpi((ptr), str, num)) -#define Strcasecpy(dst, src) (void) strcasecpy(dst, src) - -/* true for gems/rocks that should have " stone" appended to their names */ -#define GemStone(typ) \ - (typ == FLINT \ - || (objects[typ].oc_material == GEMSTONE \ - && (typ != DILITHIUM_CRYSTAL && typ != RUBY && typ != DIAMOND \ - && typ != SAPPHIRE && typ != BLACK_OPAL && typ != EMERALD \ - && typ != OPAL))) - -STATIC_OVL struct Jitem Japanese_items[] = { { SHORT_SWORD, "wakizashi" }, - { BROADSWORD, "ninja-to" }, - { FLAIL, "nunchaku" }, - { GLAIVE, "naginata" }, - { LOCK_PICK, "osaku" }, - { WOODEN_HARP, "koto" }, - { KNIFE, "shito" }, - { PLATE_MAIL, "tanko" }, - { HELMET, "kabuto" }, - { LEATHER_GLOVES, "yugake" }, - { FOOD_RATION, "gunyoki" }, - { POT_BOOZE, "sake" }, - { 0, "" } }; - -STATIC_DCL const char *FDECL(Japanese_item_name, (int i)); - -STATIC_OVL char * -strprepend(s, pref) -register char *s; -register const char *pref; -{ - register int i = (int) strlen(pref); - - if (i > PREFIX) { - impossible("PREFIX too short (for %d).", i); - return s; - } - s -= i; - (void) strncpy(s, pref, i); /* do not copy trailing 0 */ - return s; -} - -/* manage a pool of BUFSZ buffers, so callers don't have to */ -static char NEARDATA obufs[NUMOBUF][BUFSZ]; -static int obufidx = 0; - -STATIC_OVL char * -nextobuf() -{ - obufidx = (obufidx + 1) % NUMOBUF; - return obufs[obufidx]; -} - -/* put the most recently allocated buffer back if possible */ -STATIC_OVL void -releaseobuf(bufp) -char *bufp; -{ - /* caller may not know whether bufp is the most recently allocated - buffer; if it isn't, do nothing; note that because of the somewhat - obscure PREFIX handling for object name formatting by xname(), - the pointer our caller has and is passing to us might be into the - middle of an obuf rather than the address returned by nextobuf() */ - if (bufp >= obufs[obufidx] - && bufp < obufs[obufidx] + sizeof obufs[obufidx]) /* obufs[][BUFSZ] */ - obufidx = (obufidx - 1 + NUMOBUF) % NUMOBUF; -} - -char * -obj_typename(otyp) -register int otyp; -{ - char *buf = nextobuf(); - struct objclass *ocl = &objects[otyp]; - const char *actualn = OBJ_NAME(*ocl); - const char *dn = OBJ_DESCR(*ocl); - const char *un = ocl->oc_uname; - int nn = ocl->oc_name_known; - - if (Role_if(PM_SAMURAI) && Japanese_item_name(otyp)) - actualn = Japanese_item_name(otyp); - switch (ocl->oc_class) { - case COIN_CLASS: - Strcpy(buf, "coin"); - break; - case POTION_CLASS: - Strcpy(buf, "potion"); - break; - case SCROLL_CLASS: - Strcpy(buf, "scroll"); - break; - case WAND_CLASS: - Strcpy(buf, "wand"); - break; - case SPBOOK_CLASS: - if (otyp != SPE_NOVEL) { - Strcpy(buf, "spellbook"); - } else { - Strcpy(buf, !nn ? "book" : "novel"); - nn = 0; - } - break; - case RING_CLASS: - Strcpy(buf, "ring"); - break; - case AMULET_CLASS: - if (nn) - Strcpy(buf, actualn); - else - Strcpy(buf, "amulet"); - if (un) - Sprintf(eos(buf), " called %s", un); - if (dn) - Sprintf(eos(buf), " (%s)", dn); - return buf; - default: - if (nn) { - Strcpy(buf, actualn); - if (GemStone(otyp)) - Strcat(buf, " stone"); - if (un) - Sprintf(eos(buf), " called %s", un); - if (dn) - Sprintf(eos(buf), " (%s)", dn); - } else { - Strcpy(buf, dn ? dn : actualn); - if (ocl->oc_class == GEM_CLASS) - Strcat(buf, - (ocl->oc_material == MINERAL) ? " stone" : " gem"); - if (un) - Sprintf(eos(buf), " called %s", un); - } - return buf; - } - /* here for ring/scroll/potion/wand */ - if (nn) { - if (ocl->oc_unique) - Strcpy(buf, actualn); /* avoid spellbook of Book of the Dead */ - else - Sprintf(eos(buf), " of %s", actualn); - } - if (un) - Sprintf(eos(buf), " called %s", un); - if (dn) - Sprintf(eos(buf), " (%s)", dn); - return buf; -} - -/* less verbose result than obj_typename(); either the actual name - or the description (but not both); user-assigned name is ignored */ -char * -simple_typename(otyp) -int otyp; -{ - char *bufp, *pp, *save_uname = objects[otyp].oc_uname; - - objects[otyp].oc_uname = 0; /* suppress any name given by user */ - bufp = obj_typename(otyp); - objects[otyp].oc_uname = save_uname; - if ((pp = strstri(bufp, " (")) != 0) - *pp = '\0'; /* strip the appended description */ - return bufp; -} - -boolean -obj_is_pname(obj) -struct obj *obj; -{ - if (!obj->oartifact || !has_oname(obj)) - return FALSE; - if (!program_state.gameover && !iflags.override_ID) { - if (not_fully_identified(obj)) - return FALSE; - } - return TRUE; -} - -/* used by distant_name() to pass extra information to xname_flags(); - it would be much cleaner if this were a parameter, but that would - require all of the xname() and doname() calls to be modified */ -static int distantname = 0; - -/* Give the name of an object seen at a distance. Unlike xname/doname, - * we don't want to set dknown if it's not set already. - */ -char * -distant_name(obj, func) -struct obj *obj; -char *FDECL((*func), (OBJ_P)); -{ - char *str; - - /* 3.6.1: this used to save Blind, set it, make the call, then restore - * the saved value; but the Eyes of the Overworld override blindness - * and let characters wearing them get dknown set for distant items. - * - * TODO? if the hero is wearing those Eyes, figure out whether the - * object is within X-ray radius and only treat it as distant when - * beyond that radius. Logic is iffy but result might be interesting. - */ - ++distantname; - str = (*func)(obj); - --distantname; - return str; -} - -/* convert player specified fruit name into corresponding fruit juice name - ("slice of pizza" -> "pizza juice" rather than "slice of pizza juice") */ -char * -fruitname(juice) -boolean juice; /* whether or not to append " juice" to the name */ -{ - char *buf = nextobuf(); - const char *fruit_nam = strstri(pl_fruit, " of "); - - if (fruit_nam) - fruit_nam += 4; /* skip past " of " */ - else - fruit_nam = pl_fruit; /* use it as is */ - - Sprintf(buf, "%s%s", makesingular(fruit_nam), juice ? " juice" : ""); - return buf; -} - -/* look up a named fruit by index (1..127) */ -struct fruit * -fruit_from_indx(indx) -int indx; -{ - struct fruit *f; - - for (f = ffruit; f; f = f->nextf) - if (f->fid == indx) - break; - return f; -} - -/* look up a named fruit by name */ -struct fruit * -fruit_from_name(fname, exact, highest_fid) -const char *fname; -boolean exact; /* False => prefix or exact match, True = exact match only */ -int *highest_fid; /* optional output; only valid if 'fname' isn't found */ -{ - struct fruit *f, *tentativef; - char *altfname; - unsigned k; - /* - * note: named fruits are case-senstive... - */ - - if (highest_fid) - *highest_fid = 0; - /* first try for an exact match */ - for (f = ffruit; f; f = f->nextf) - if (!strcmp(f->fname, fname)) - return f; - else if (highest_fid && f->fid > *highest_fid) - *highest_fid = f->fid; - - /* didn't match as-is; if caller is willing to accept a prefix - match, try to find one; we want to find the longest prefix that - matches, not the first */ - if (!exact) { - tentativef = 0; - for (f = ffruit; f; f = f->nextf) { - k = strlen(f->fname); - if (!strncmp(f->fname, fname, k) - && (!fname[k] || fname[k] == ' ') - && (!tentativef || k > strlen(tentativef->fname))) - tentativef = f; - } - f = tentativef; - } - /* if we still don't have a match, try singularizing the target; - for exact match, that's trivial, but for prefix, it's hard */ - if (!f) { - altfname = makesingular(fname); - for (f = ffruit; f; f = f->nextf) { - if (!strcmp(f->fname, altfname)) - break; - } - releaseobuf(altfname); - } - if (!f && !exact) { - char fnamebuf[BUFSZ], *p; - unsigned fname_k = strlen(fname); /* length of assumed plural fname */ - - tentativef = 0; - for (f = ffruit; f; f = f->nextf) { - k = strlen(f->fname); - /* reload fnamebuf[] each iteration in case it gets modified; - there's no need to recalculate fname_k */ - Strcpy(fnamebuf, fname); - /* bug? if singular of fname is longer than plural, - failing the 'fname_k > k' test could skip a viable - candidate; unfortunately, we can't singularize until - after stripping off trailing stuff and we can't get - accurate fname_k until fname has been singularized; - compromise and use 'fname_k >= k' instead of '>', - accepting 1 char length discrepancy without risking - false match (I hope...) */ - if (fname_k >= k && (p = index(&fnamebuf[k], ' ')) != 0) { - *p = '\0'; /* truncate at 1st space past length of f->fname */ - altfname = makesingular(fnamebuf); - k = strlen(altfname); /* actually revised 'fname_k' */ - if (!strcmp(f->fname, altfname) - && (!tentativef || k > strlen(tentativef->fname))) - tentativef = f; - releaseobuf(altfname); /* avoid churning through all obufs */ - } - } - f = tentativef; - } - return f; -} - -/* sort the named-fruit linked list by fruit index number */ -void -reorder_fruit(forward) -boolean forward; -{ - struct fruit *f, *allfr[1 + 127]; - int i, j, k = SIZE(allfr); - - for (i = 0; i < k; ++i) - allfr[i] = (struct fruit *) 0; - for (f = ffruit; f; f = f->nextf) { - /* without sanity checking, this would reduce to 'allfr[f->fid]=f' */ - j = f->fid; - if (j < 1 || j >= k) { - impossible("reorder_fruit: fruit index (%d) out of range", j); - return; /* don't sort after all; should never happen... */ - } else if (allfr[j]) { - impossible("reorder_fruit: duplicate fruit index (%d)", j); - return; - } - allfr[j] = f; - } - ffruit = 0; /* reset linked list; we're rebuilding it from scratch */ - /* slot [0] will always be empty; must start 'i' at 1 to avoid - [k - i] being out of bounds during first iteration */ - for (i = 1; i < k; ++i) { - /* for forward ordering, go through indices from high to low; - for backward ordering, go from low to high */ - j = forward ? (k - i) : i; - if (allfr[j]) { - allfr[j]->nextf = ffruit; - ffruit = allfr[j]; - } - } -} - -char * -xname(obj) -struct obj *obj; -{ - return xname_flags(obj, CXN_NORMAL); -} - -char * -xname_flags(obj, cxn_flags) -register struct obj *obj; -unsigned cxn_flags; /* bitmask of CXN_xxx values */ -{ - register char *buf; - register int typ = obj->otyp; - register struct objclass *ocl = &objects[typ]; - int nn = ocl->oc_name_known, omndx = obj->corpsenm; - const char *actualn = OBJ_NAME(*ocl); - const char *dn = OBJ_DESCR(*ocl) ? OBJ_DESCR(*ocl) : actualn; - const char *un = ocl->oc_uname; - boolean pluralize = (obj->quan != 1L) && !(cxn_flags & CXN_SINGULAR); - boolean known, dknown, bknown; - - buf = nextobuf() + PREFIX; /* leave room for "17 -3 " */ - if (Role_if(PM_SAMURAI) && Japanese_item_name(typ)) - actualn = Japanese_item_name(typ); - - 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 - * printed for the object is tied to the combination of the two - * and printing the wrong article gives away information. - */ - if (!nn && ocl->oc_uses_known && ocl->oc_unique) - obj->known = 0; - if (!Blind && !distantname) - obj->dknown = TRUE; - if (Role_if(PM_PRIEST)) - obj->bknown = TRUE; - - if (iflags.override_ID) { - known = dknown = bknown = TRUE; - nn = 1; - } else { - known = obj->known; - dknown = obj->dknown; - bknown = obj->bknown; - } - - if (obj_is_pname(obj)) - goto nameit; - switch (obj->oclass) { - case AMULET_CLASS: - if (!dknown) - Strcpy(buf, "amulet"); - else if (typ == AMULET_OF_YENDOR || typ == FAKE_AMULET_OF_YENDOR) - /* each must be identified individually */ - Strcpy(buf, known ? actualn : dn); - else if (nn) - Strcpy(buf, actualn); - else if (un) - Sprintf(buf, "amulet called %s", un); - else - Sprintf(buf, "%s amulet", dn); - break; - case WEAPON_CLASS: - if (is_poisonable(obj) && obj->opoisoned) - Strcpy(buf, "poisoned "); - case VENOM_CLASS: - case TOOL_CLASS: - if (typ == LENSES) - Strcpy(buf, "pair of "); - else if (is_wet_towel(obj)) - Strcpy(buf, (obj->spe < 3) ? "moist " : "wet "); - - if (!dknown) - Strcat(buf, dn); - else if (nn) - Strcat(buf, actualn); - else if (un) { - Strcat(buf, dn); - Strcat(buf, " called "); - Strcat(buf, un); - } else - Strcat(buf, dn); - /* If we use an() here we'd have to remember never to use */ - /* it whenever calling doname() or xname(). */ - if (typ == FIGURINE && omndx != NON_PM) { - Sprintf(eos(buf), " of a%s %s", - index(vowels, *mons[omndx].mname) ? "n" : "", - mons[omndx].mname); - } else if (is_wet_towel(obj)) { - if (wizard) - Sprintf(eos(buf), " (%d)", obj->spe); - } - break; - case ARMOR_CLASS: - /* depends on order of the dragon scales objects */ - if (typ >= GRAY_DRAGON_SCALES && typ <= YELLOW_DRAGON_SCALES) { - Sprintf(buf, "set of %s", actualn); - break; - } - if (is_boots(obj) || is_gloves(obj)) - Strcpy(buf, "pair of "); - - if (obj->otyp >= ELVEN_SHIELD && obj->otyp <= ORCISH_SHIELD - && !dknown) { - Strcpy(buf, "shield"); - break; - } - if (obj->otyp == SHIELD_OF_REFLECTION && !dknown) { - Strcpy(buf, "smooth shield"); - break; - } - - if (nn) - Strcat(buf, actualn); - else if (un) { - if (is_boots(obj)) - Strcat(buf, "boots"); - else if (is_gloves(obj)) - Strcat(buf, "gloves"); - else if (is_cloak(obj)) - Strcpy(buf, "cloak"); - else if (is_helmet(obj)) - Strcpy(buf, "helmet"); - else if (is_shield(obj)) - Strcpy(buf, "shield"); - else - Strcpy(buf, "armor"); - Strcat(buf, " called "); - Strcat(buf, un); - } else - Strcat(buf, dn); - break; - case FOOD_CLASS: - if (typ == SLIME_MOLD) { - struct fruit *f = fruit_from_indx(obj->spe); - - if (!f) { - impossible("Bad fruit #%d?", obj->spe); - Strcpy(buf, "fruit"); - } else { - Strcpy(buf, f->fname); - if (pluralize) { - /* ick; already pluralized fruit names - are allowed--we want to try to avoid - adding a redundant plural suffix */ - Strcpy(buf, makeplural(makesingular(buf))); - pluralize = FALSE; - } - } - break; - } - if (obj->globby) { - Sprintf(buf, "%s%s", - (obj->owt <= 100) - ? "small " - : (obj->owt > 500) - ? "very large " - : (obj->owt > 300) - ? "large " - : "", - actualn); - break; - } - - Strcpy(buf, actualn); - if (typ == TIN && known) - tin_details(obj, omndx, buf); - break; - case COIN_CLASS: - case CHAIN_CLASS: - Strcpy(buf, actualn); - break; - case ROCK_CLASS: - if (typ == STATUE && omndx != NON_PM) - Sprintf(buf, "%s%s of %s%s", - (Role_if(PM_ARCHEOLOGIST) && (obj->spe & STATUE_HISTORIC)) - ? "historic " - : "", - actualn, - type_is_pname(&mons[omndx]) - ? "" - : the_unique_pm(&mons[omndx]) - ? "the " - : index(vowels, *mons[omndx].mname) - ? "an " - : "a ", - mons[omndx].mname); - else - Strcpy(buf, actualn); - break; - case BALL_CLASS: - Sprintf(buf, "%sheavy iron ball", - (obj->owt > ocl->oc_weight) ? "very " : ""); - break; - case POTION_CLASS: - if (dknown && obj->odiluted) - Strcpy(buf, "diluted "); - if (nn || un || !dknown) { - Strcat(buf, "potion"); - if (!dknown) - break; - if (nn) { - Strcat(buf, " of "); - if (typ == POT_WATER && bknown - && (obj->blessed || obj->cursed)) { - Strcat(buf, obj->blessed ? "holy " : "unholy "); - } - Strcat(buf, actualn); - } else { - Strcat(buf, " called "); - Strcat(buf, un); - } - } else { - Strcat(buf, dn); - Strcat(buf, " potion"); - } - break; - case SCROLL_CLASS: - Strcpy(buf, "scroll"); - if (!dknown) - break; - if (nn) { - Strcat(buf, " of "); - Strcat(buf, actualn); - } else if (un) { - Strcat(buf, " called "); - Strcat(buf, un); - } else if (ocl->oc_magic) { - Strcat(buf, " labeled "); - Strcat(buf, dn); - } else { - Strcpy(buf, dn); - Strcat(buf, " scroll"); - } - break; - case WAND_CLASS: - if (!dknown) - Strcpy(buf, "wand"); - else if (nn) - Sprintf(buf, "wand of %s", actualn); - else if (un) - Sprintf(buf, "wand called %s", un); - else - Sprintf(buf, "%s wand", dn); - break; - case SPBOOK_CLASS: - if (typ == SPE_NOVEL) { /* 3.6 tribute */ - if (!dknown) - Strcpy(buf, "book"); - else if (nn) - Strcpy(buf, actualn); - else if (un) - Sprintf(buf, "novel called %s", un); - else - Sprintf(buf, "%s book", dn); - break; - /* end of tribute */ - } else if (!dknown) { - Strcpy(buf, "spellbook"); - } else if (nn) { - if (typ != SPE_BOOK_OF_THE_DEAD) - Strcpy(buf, "spellbook of "); - Strcat(buf, actualn); - } else if (un) { - Sprintf(buf, "spellbook called %s", un); - } else - Sprintf(buf, "%s spellbook", dn); - break; - case RING_CLASS: - if (!dknown) - Strcpy(buf, "ring"); - else if (nn) - Sprintf(buf, "ring of %s", actualn); - else if (un) - Sprintf(buf, "ring called %s", un); - else - Sprintf(buf, "%s ring", dn); - break; - case GEM_CLASS: { - const char *rock = (ocl->oc_material == MINERAL) ? "stone" : "gem"; - - if (!dknown) { - Strcpy(buf, rock); - } else if (!nn) { - if (un) - Sprintf(buf, "%s called %s", rock, un); - else - Sprintf(buf, "%s %s", dn, rock); - } else { - Strcpy(buf, actualn); - if (GemStone(typ)) - Strcat(buf, " stone"); - } - break; - } - default: - Sprintf(buf, "glorkum %d %d %d", obj->oclass, typ, obj->spe); - } - if (pluralize) - Strcpy(buf, makeplural(buf)); - - if (obj->otyp == T_SHIRT && program_state.gameover) { - char tmpbuf[BUFSZ]; - - Sprintf(eos(buf), " with text \"%s\"", tshirt_text(obj, tmpbuf)); - } - - if (has_oname(obj) && dknown) { - Strcat(buf, " named "); - nameit: - Strcat(buf, ONAME(obj)); - } - - if (!strncmpi(buf, "the ", 4)) - buf += 4; - return buf; -} - -/* similar to simple_typename but minimal_xname operates on a particular - object rather than its general type; it formats the most basic info: - potion -- if description not known - brown potion -- if oc_name_known not set - potion of object detection -- if discovered - */ -STATIC_OVL char * -minimal_xname(obj) -struct obj *obj; -{ - char *bufp; - struct obj bareobj; - struct objclass saveobcls; - int otyp = obj->otyp; - - /* suppress user-supplied name */ - saveobcls.oc_uname = objects[otyp].oc_uname; - objects[otyp].oc_uname = 0; - /* suppress actual name if object's description is unknown */ - saveobcls.oc_name_known = objects[otyp].oc_name_known; - if (!obj->dknown) - objects[otyp].oc_name_known = 0; - - /* caveat: this makes a lot of assumptions about which fields - are required in order for xname() to yield a sensible result */ - bareobj = zeroobj; - bareobj.otyp = otyp; - bareobj.oclass = obj->oclass; - bareobj.dknown = obj->dknown; - /* suppress known except for amulets (needed for fakes and real A-of-Y) */ - bareobj.known = (obj->oclass == AMULET_CLASS) - ? obj->known - /* default is "on" for types which don't use it */ - : !objects[otyp].oc_uses_known; - bareobj.quan = 1L; /* don't want plural */ - bareobj.corpsenm = NON_PM; /* suppress statue and figurine details */ - /* but suppressing fruit details leads to "bad fruit #0" - [perhaps we should force "slime mold" rather than use xname?] */ - if (obj->otyp == SLIME_MOLD) - bareobj.spe = obj->spe; - - bufp = distant_name(&bareobj, xname); /* xname(&bareobj) */ - if (!strncmp(bufp, "uncursed ", 9)) - bufp += 9; /* Role_if(PM_PRIEST) */ - - objects[otyp].oc_uname = saveobcls.oc_uname; - objects[otyp].oc_name_known = saveobcls.oc_name_known; - return bufp; -} - -/* xname() output augmented for multishot missile feedback */ -char * -mshot_xname(obj) -struct obj *obj; -{ - char tmpbuf[BUFSZ]; - char *onm = xname(obj); - - if (m_shot.n > 1 && m_shot.o == obj->otyp) { - /* "the Nth arrow"; value will eventually be passed to an() or - The(), both of which correctly handle this "the " prefix */ - Sprintf(tmpbuf, "the %d%s ", m_shot.i, ordin(m_shot.i)); - onm = strprepend(onm, tmpbuf); - } - return onm; -} - -/* used for naming "the unique_item" instead of "a unique_item" */ -boolean -the_unique_obj(obj) -struct obj *obj; -{ - boolean known = (obj->known || iflags.override_ID); - - if (!obj->dknown && !iflags.override_ID) - return FALSE; - else if (obj->otyp == FAKE_AMULET_OF_YENDOR && !known) - return TRUE; /* lie */ - else - return (boolean) (objects[obj->otyp].oc_unique - && (known || obj->otyp == AMULET_OF_YENDOR)); -} - -/* should monster type be prefixed with "the"? (mostly used for corpses) */ -boolean -the_unique_pm(ptr) -struct permonst *ptr; -{ - boolean uniq; - - /* even though monsters with personal names are unique, we want to - describe them as "Name" rather than "the Name" */ - if (type_is_pname(ptr)) - return FALSE; - - uniq = (ptr->geno & G_UNIQ) ? TRUE : FALSE; - /* high priest is unique if it includes "of ", otherwise not - (caller needs to handle the 1st possibility; we assume the 2nd); - worm tail should be irrelevant but is included for completeness */ - if (ptr == &mons[PM_HIGH_PRIEST] || ptr == &mons[PM_LONG_WORM_TAIL]) - uniq = FALSE; - /* Wizard no longer needs this; he's flagged as unique these days */ - if (ptr == &mons[PM_WIZARD_OF_YENDOR]) - uniq = TRUE; - return uniq; -} - -STATIC_OVL void -add_erosion_words(obj, prefix) -struct obj *obj; -char *prefix; -{ - boolean iscrys = (obj->otyp == CRYSKNIFE); - boolean rknown; - - rknown = (iflags.override_ID == 0) ? obj->rknown : TRUE; - - if (!is_damageable(obj) && !iscrys) - return; - - /* The only cases where any of these bits do double duty are for - * rotted food and diluted potions, which are all not is_damageable(). - */ - if (obj->oeroded && !iscrys) { - switch (obj->oeroded) { - case 2: - Strcat(prefix, "very "); - break; - case 3: - Strcat(prefix, "thoroughly "); - break; - } - Strcat(prefix, is_rustprone(obj) ? "rusty " : "burnt "); - } - if (obj->oeroded2 && !iscrys) { - switch (obj->oeroded2) { - case 2: - Strcat(prefix, "very "); - break; - case 3: - Strcat(prefix, "thoroughly "); - break; - } - Strcat(prefix, is_corrodeable(obj) ? "corroded " : "rotted "); - } - if (rknown && obj->oerodeproof) - Strcat(prefix, iscrys - ? "fixed " - : is_rustprone(obj) - ? "rustproof " - : is_corrodeable(obj) - ? "corrodeproof " /* "stainless"? */ - : is_flammable(obj) - ? "fireproof " - : ""); -} - -/* used to prevent rust on items where rust makes no difference */ -boolean -erosion_matters(obj) -struct obj *obj; -{ - switch (obj->oclass) { - case TOOL_CLASS: - /* it's possible for a rusty weptool to be polymorphed into some - non-weptool iron tool, in which case the rust implicitly goes - away, but it's also possible for it to be polymorphed into a - non-iron tool, in which case rust also implicitly goes away, - so there's no particular reason to try to handle the first - instance differently [this comment belongs in poly_obj()...] */ - return is_weptool(obj) ? TRUE : FALSE; - case WEAPON_CLASS: - case ARMOR_CLASS: - case BALL_CLASS: - case CHAIN_CLASS: - return TRUE; - default: - break; - } - return FALSE; -} - -#define DONAME_WITH_PRICE 1 -#define DONAME_VAGUE_QUAN 2 - -STATIC_OVL char * -doname_base(obj, doname_flags) -struct obj *obj; -unsigned doname_flags; -{ - boolean ispoisoned = FALSE, - with_price = (doname_flags & DONAME_WITH_PRICE) != 0, - vague_quan = (doname_flags & DONAME_VAGUE_QUAN) != 0; - boolean known, dknown, cknown, bknown, lknown; - int omndx = obj->corpsenm; - char prefix[PREFIX]; - char tmpbuf[PREFIX + 1]; /* for when we have to add something at - the start of prefix instead of the - end (Strcat is used on the end) */ - register char *bp = xname(obj); - - if (iflags.override_ID) { - known = dknown = cknown = bknown = lknown = TRUE; - } else { - known = obj->known; - dknown = obj->dknown; - cknown = obj->cknown; - bknown = obj->bknown; - lknown = obj->lknown; - } - - /* When using xname, we want "poisoned arrow", and when using - * doname, we want "poisoned +0 arrow". This kludge is about the only - * way to do it, at least until someone overhauls xname() and doname(), - * combining both into one function taking a parameter. - */ - /* must check opoisoned--someone can have a weirdly-named fruit */ - if (!strncmp(bp, "poisoned ", 9) && obj->opoisoned) { - bp += 9; - ispoisoned = TRUE; - } - - if (obj->quan != 1L) { - if (dknown || !vague_quan) - Sprintf(prefix, "%ld ", obj->quan); - else - Strcpy(prefix, "some "); - } else if (obj->otyp == CORPSE) { - /* skip article prefix for corpses [else corpse_xname() - would have to be taught how to strip it off again] */ - *prefix = '\0'; - } else if (obj_is_pname(obj) || the_unique_obj(obj)) { - if (!strncmpi(bp, "the ", 4)) - bp += 4; - Strcpy(prefix, "the "); - } else { - Strcpy(prefix, "a "); - } - - /* "empty" goes at the beginning, but item count goes at the end */ - if (cknown - /* bag of tricks: include "empty" prefix if it's known to - be empty but its precise number of charges isn't known - (when that is known, suffix of "(n:0)" will be appended, - making the prefix be redundant; note that 'known' flag - isn't set when emptiness gets discovered because then - charging magic would yield known number of new charges) */ - && ((obj->otyp == BAG_OF_TRICKS) - ? (obj->spe == 0 && !obj->known) - /* not bag of tricks: empty if container which has no contents */ - : ((Is_container(obj) || obj->otyp == STATUE) - && !Has_contents(obj)))) - Strcat(prefix, "empty "); - - if (bknown && obj->oclass != COIN_CLASS - && (obj->otyp != POT_WATER || !objects[POT_WATER].oc_name_known - || (!obj->cursed && !obj->blessed))) { - /* allow 'blessed clear potion' if we don't know it's holy water; - * always allow "uncursed potion of water" - */ - if (obj->cursed) - Strcat(prefix, "cursed "); - else if (obj->blessed) - Strcat(prefix, "blessed "); - else if (!iflags.implicit_uncursed - /* For most items with charges or +/-, if you know how many - * charges are left or what the +/- is, then you must have - * totally identified the item, so "uncursed" is unnecessary, - * because an identified object not described as "blessed" or - * "cursed" must be uncursed. - * - * If the charges or +/- is not known, "uncursed" must be - * printed to avoid ambiguity between an item whose curse - * status is unknown, and an item known to be uncursed. - */ - || ((!known || !objects[obj->otyp].oc_charged - || obj->oclass == ARMOR_CLASS - || obj->oclass == RING_CLASS) -#ifdef MAIL - && obj->otyp != SCR_MAIL -#endif - && obj->otyp != FAKE_AMULET_OF_YENDOR - && obj->otyp != AMULET_OF_YENDOR - && !Role_if(PM_PRIEST))) - Strcat(prefix, "uncursed "); - } - - if (lknown && Is_box(obj)) { - if (obj->obroken) - /* 3.6.0 used an "unlockable" prefix here but that could be - misunderstood to mean "capable of being unlocked" rather - than the intended "not capable of being locked" */ - Strcat(bp, " with a broken lock"); - else if (obj->olocked) - Strcat(prefix, "locked "); - else - Strcat(prefix, "unlocked "); - } - - if (obj->greased) - Strcat(prefix, "greased "); - - if (cknown && Has_contents(obj)) { - /* 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); - - Sprintf(eos(bp), " 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)"); - break; - case ARMOR_CLASS: - if (obj->owornmask & W_ARMOR) - Strcat(bp, (obj == uskin) ? " (embedded in your skin)" - : " (being worn)"); - /*FALLTHRU*/ - case WEAPON_CLASS: - if (ispoisoned) - Strcat(prefix, "poisoned "); - add_erosion_words(obj, prefix); - if (known) { - Strcat(prefix, sitoa(obj->spe)); - Strcat(prefix, " "); - } - break; - case TOOL_CLASS: - if (obj->owornmask & (W_TOOL | W_SADDLE)) { /* blindfold */ - Strcat(bp, " (being worn)"); - break; - } - 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 { - Sprintf(eos(bp), " (attached to %s)", - a_monnam(mlsh)); - } - break; - } - if (obj->otyp == CANDELABRUM_OF_INVOCATION) { - if (!obj->spe) - Strcpy(tmpbuf, "no"); - else - Sprintf(tmpbuf, "%d", obj->spe); - Sprintf(eos(bp), " (%s candle%s%s)", tmpbuf, plur(obj->spe), - !obj->lamplit ? " attached" : ", lit"); - break; - } else if (obj->otyp == OIL_LAMP || obj->otyp == MAGIC_LAMP - || obj->otyp == BRASS_LANTERN || Is_candle(obj)) { - if (Is_candle(obj) - && obj->age < 20L * (long) objects[obj->otyp].oc_cost) - Strcat(prefix, "partly used "); - if (obj->lamplit) - Strcat(bp, " (lit)"); - break; - } - if (objects[obj->otyp].oc_charged) - goto charges; - break; - case WAND_CLASS: - charges: - if (known) - Sprintf(eos(bp), " (%d:%d)", (int) obj->recharged, obj->spe); - break; - case POTION_CLASS: - if (obj->otyp == POT_OIL && obj->lamplit) - Strcat(bp, " (lit)"); - break; - case RING_CLASS: - ring: - if (obj->owornmask & W_RINGR) - Strcat(bp, " (on right "); - if (obj->owornmask & W_RINGL) - Strcat(bp, " (on left "); - if (obj->owornmask & W_RING) { - Strcat(bp, body_part(HAND)); - Strcat(bp, ")"); - } - if (known && objects[obj->otyp].oc_charged) { - Strcat(prefix, sitoa(obj->spe)); - Strcat(prefix, " "); - } - break; - case FOOD_CLASS: - if (obj->oeaten) - Strcat(prefix, "partly eaten "); - if (obj->otyp == CORPSE) { - /* (quan == 1) => want corpse_xname() to supply article, - (quan != 1) => already have count or "some" as prefix; - "corpse" is already in the buffer returned by xname() */ - unsigned cxarg = (((obj->quan != 1L) ? 0 : CXN_ARTICLE) - | CXN_NOCORPSE); - char *cxstr = corpse_xname(obj, prefix, cxarg); - - Sprintf(prefix, "%s ", cxstr); - /* avoid having doname(corpse) consume an extra obuf */ - releaseobuf(cxstr); - } else if (obj->otyp == EGG) { -#if 0 /* corpses don't tell if they're stale either */ - if (known && stale_egg(obj)) - Strcat(prefix, "stale "); -#endif - if (omndx >= LOW_PM - && (known || (mvitals[omndx].mvflags & MV_KNOWS_EGG))) { - Strcat(prefix, mons[omndx].mname); - Strcat(prefix, " "); - if (obj->spe) - Strcat(bp, " (laid by you)"); - } - } - if (obj->otyp == MEAT_RING) - goto ring; - break; - case BALL_CLASS: - case CHAIN_CLASS: - add_erosion_words(obj, prefix); - if (obj->owornmask & W_BALL) - Strcat(bp, " (chained to you)"); - break; - } - - if ((obj->owornmask & W_WEP) && !mrg_to_wielded) { - if (obj->quan != 1L) { - Strcat(bp, " (wielded)"); - } else { - const char *hand_s = body_part(HAND); - - if (bimanual(obj)) - hand_s = makeplural(hand_s); - Sprintf(eos(bp), " (weapon in %s)", hand_s); - - if (warn_obj_cnt && obj == uwep && (EWarn_of_mon & W_WEP) != 0L) { - /* presumably can be felt when blind */ - Strcat(bp, " (glowing"); - if (!Blind) - Sprintf(eos(bp), " %s", glow_color(obj->oartifact)); - Strcat(bp, ")"); - } - } - } - if (obj->owornmask & W_SWAPWEP) { - if (u.twoweap) - Sprintf(eos(bp), " (wielded in other %s)", body_part(HAND)); - else - Strcat(bp, " (alternate weapon; not wielded)"); - } - if (obj->owornmask & W_QUIVER) { - 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 */ - case RING_CLASS: - case AMULET_CLASS: - case WAND_CLASS: - case COIN_CLASS: - case GEM_CLASS: - Strcat(bp, " (in quiver pouch)"); - break; - default: /* odd things */ - Strcat(bp, " (at the ready)"); - } - } - if (!iflags.suppress_price && is_unpaid(obj)) { - long quotedprice = unpaid_cost(obj, TRUE); - - Sprintf(eos(bp), " (%s, %ld %s)", - obj->unpaid ? "unpaid" : "contents", - quotedprice, currency(quotedprice)); - } else if (with_price) { - long price = get_cost_of_shop_item(obj); - - if (price > 0) - Sprintf(eos(bp), " (%ld %s)", price, currency(price)); - } - if (!strncmp(prefix, "a ", 2) - && index(vowels, *(prefix + 2) ? *(prefix + 2) : *bp) - && (*(prefix + 2) - || (strncmp(bp, "uranium", 7) && strncmp(bp, "unicorn", 7) - && strncmp(bp, "eucalyptus", 10)))) { - Strcpy(tmpbuf, prefix); - Strcpy(prefix, "an "); - Strcpy(prefix + 3, tmpbuf + 2); - } - - /* show weight for items (debug tourist info) - * aum is stolen from Crawl's "Arbitrary Unit of Measure" */ - if (wizard && iflags.wizweight) { - Sprintf(eos(bp), " (%d aum)", obj->owt); - } - bp = strprepend(bp, prefix); - return bp; -} - -char * -doname(obj) -struct obj *obj; -{ - return doname_base(obj, (unsigned) 0); -} - -/* Name of object including price. */ -char * -doname_with_price(obj) -struct obj *obj; -{ - return doname_base(obj, DONAME_WITH_PRICE); -} - -/* "some" instead of precise quantity if obj->dknown not set */ -char * -doname_vague_quan(obj) -struct obj *obj; -{ - /* Used by farlook. - * If it hasn't been seen up close and quantity is more than one, - * use "some" instead of the quantity: "some gold pieces" rather - * than "25 gold pieces". This is suboptimal, to put it mildly, - * because lookhere and pickup report the precise amount. - * Picking the item up while blind also shows the precise amount - * for inventory display, then dropping it while still blind leaves - * obj->dknown unset so the count reverts to "some" for farlook. - * - * TODO: add obj->qknown flag for 'quantity known' on stackable - * items; it could overlay obj->cknown since no containers stack. - */ - return doname_base(obj, DONAME_VAGUE_QUAN); -} - -/* used from invent.c */ -boolean -not_fully_identified(otmp) -struct obj *otmp; -{ - /* gold doesn't have any interesting attributes [yet?] */ - if (otmp->oclass == COIN_CLASS) - return FALSE; /* always fully ID'd */ - /* check fundamental ID hallmarks first */ - if (!otmp->known || !otmp->dknown -#ifdef MAIL - || (!otmp->bknown && otmp->otyp != SCR_MAIL) -#else - || !otmp->bknown -#endif - || !objects[otmp->otyp].oc_name_known) - return TRUE; - if ((!otmp->cknown && (Is_container(otmp) || otmp->otyp == STATUE)) - || (!otmp->lknown && Is_box(otmp))) - return TRUE; - if (otmp->oartifact && undiscovered_artifact(otmp->oartifact)) - return TRUE; - /* otmp->rknown is the only item of interest if we reach here */ - /* - * Note: if a revision ever allows scrolls to become fireproof or - * rings to become shockproof, this checking will need to be revised. - * `rknown' ID only matters if xname() will provide the info about it. - */ - if (otmp->rknown - || (otmp->oclass != ARMOR_CLASS && otmp->oclass != WEAPON_CLASS - && !is_weptool(otmp) /* (redundant) */ - && otmp->oclass != BALL_CLASS)) /* (useless) */ - return FALSE; - else /* lack of `rknown' only matters for vulnerable objects */ - return (boolean) (is_rustprone(otmp) || is_corrodeable(otmp) - || is_flammable(otmp)); -} - -/* format a corpse name (xname() omits monster type; doname() calls us); - eatcorpse() also uses us for death reason when eating tainted glob */ -char * -corpse_xname(otmp, adjective, cxn_flags) -struct obj *otmp; -const char *adjective; -unsigned cxn_flags; /* bitmask of CXN_xxx values */ -{ - char *nambuf = nextobuf(); - int omndx = otmp->corpsenm; - boolean ignore_quan = (cxn_flags & CXN_SINGULAR) != 0, - /* suppress "the" from "the unique monster corpse" */ - no_prefix = (cxn_flags & CXN_NO_PFX) != 0, - /* include "the" for "the woodchuck corpse */ - the_prefix = (cxn_flags & CXN_PFX_THE) != 0, - /* include "an" for "an ogre corpse */ - any_prefix = (cxn_flags & CXN_ARTICLE) != 0, - /* leave off suffix (do_name() appends "corpse" itself) */ - omit_corpse = (cxn_flags & CXN_NOCORPSE) != 0, - possessive = FALSE, - glob = (otmp->otyp != CORPSE && otmp->globby); - const char *mname; - - if (glob) { - mname = OBJ_NAME(objects[otmp->otyp]); /* "glob of " */ - } else if (omndx == NON_PM) { /* paranoia */ - mname = "thing"; - /* [Possible enhancement: check whether corpse has monster traits - attached in order to use priestname() for priests and minions.] */ - } else if (omndx == PM_ALIGNED_PRIEST) { - /* avoid "aligned priest"; it just exposes internal details */ - mname = "priest"; - } else { - mname = mons[omndx].mname; - if (the_unique_pm(&mons[omndx]) || type_is_pname(&mons[omndx])) { - mname = s_suffix(mname); - possessive = TRUE; - /* don't precede personal name like "Medusa" with an article */ - if (type_is_pname(&mons[omndx])) - no_prefix = TRUE; - /* always precede non-personal unique monster name like - "Oracle" with "the" unless explicitly overridden */ - else if (the_unique_pm(&mons[omndx]) && !no_prefix) - the_prefix = TRUE; - } - } - if (no_prefix) - the_prefix = any_prefix = FALSE; - else if (the_prefix) - any_prefix = FALSE; /* mutually exclusive */ - - *nambuf = '\0'; - /* can't use the() the way we use an() below because any capitalized - Name causes it to assume a personal name and return Name as-is; - that's usually the behavior wanted, but here we need to force "the" - to precede capitalized unique monsters (pnames are handled above) */ - if (the_prefix) - Strcat(nambuf, "the "); - - if (!adjective || !*adjective) { - /* normal case: newt corpse */ - Strcat(nambuf, mname); - } else { - /* adjective positioning depends upon format of monster name */ - if (possessive) /* Medusa's cursed partly eaten corpse */ - Sprintf(eos(nambuf), "%s %s", mname, adjective); - else /* cursed partly eaten troll corpse */ - Sprintf(eos(nambuf), "%s %s", adjective, mname); - /* in case adjective has a trailing space, squeeze it out */ - mungspaces(nambuf); - /* doname() might include a count in the adjective argument; - if so, don't prepend an article */ - if (digit(*adjective)) - any_prefix = FALSE; - } - - if (glob) { - ; /* omit_corpse doesn't apply; quantity is always 1 */ - } else if (!omit_corpse) { - Strcat(nambuf, " corpse"); - /* makeplural(nambuf) => append "s" to "corpse" */ - if (otmp->quan > 1L && !ignore_quan) { - Strcat(nambuf, "s"); - any_prefix = FALSE; /* avoid "a newt corpses" */ - } - } - - /* it's safe to overwrite our nambuf after an() has copied - its old value into another buffer */ - if (any_prefix) - Strcpy(nambuf, an(nambuf)); - - return nambuf; -} - -/* xname doesn't include monster type for "corpse"; cxname does */ -char * -cxname(obj) -struct obj *obj; -{ - if (obj->otyp == CORPSE) - return corpse_xname(obj, (const char *) 0, CXN_NORMAL); - return xname(obj); -} - -/* like cxname, but ignores quantity */ -char * -cxname_singular(obj) -struct obj *obj; -{ - if (obj->otyp == CORPSE) - return corpse_xname(obj, (const char *) 0, CXN_SINGULAR); - return xname_flags(obj, CXN_SINGULAR); -} - -/* treat an object as fully ID'd when it might be used as reason for death */ -char * -killer_xname(obj) -struct obj *obj; -{ - struct obj save_obj; - unsigned save_ocknown; - char *buf, *save_ocuname, *save_oname = (char *) 0; - - /* bypass object twiddling for artifacts */ - if (obj->oartifact) - return bare_artifactname(obj); - - /* remember original settings for core of the object; - oextra structs other than oname don't matter here--since they - aren't modified they don't need to be saved and restored */ - save_obj = *obj; - if (has_oname(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 */ - obj->known = obj->dknown = 1; - obj->bknown = obj->rknown = obj->greased = 0; - /* if character is a priest[ess], bknown will get toggled back on */ - if (obj->otyp != POT_WATER) - obj->blessed = obj->cursed = 0; - else - obj->bknown = 1; /* describe holy/unholy water as such */ - /* "killed by poisoned " would be misleading when poison is - not the cause of death and "poisoned by poisoned " would - be redundant when it is, so suppress "poisoned" prefix */ - obj->opoisoned = 0; - /* strip user-supplied name; artifacts keep theirs */ - if (!obj->oartifact && save_oname) - ONAME(obj) = (char *) 0; - /* temporarily identify the type of object */ - save_ocknown = objects[obj->otyp].oc_name_known; - objects[obj->otyp].oc_name_known = 1; - save_ocuname = objects[obj->otyp].oc_uname; - objects[obj->otyp].oc_uname = 0; /* avoid "foo called bar" */ - - /* format the object */ - if (obj->otyp == CORPSE) { - buf = nextobuf(); - Strcpy(buf, corpse_xname(obj, (const char *) 0, CXN_NORMAL)); - } else if (obj->otyp == SLIME_MOLD) { - /* concession to "most unique deaths competition" in the annual - devnull tournament, suppress player supplied fruit names because - those can be used to fake other objects and dungeon features */ - buf = nextobuf(); - Sprintf(buf, "deadly slime mold%s", plur(obj->quan)); - } else { - buf = xname(obj); - } - /* apply an article if appropriate; caller should always use KILLED_BY */ - if (obj->quan == 1L && !strstri(buf, "'s ") && !strstri(buf, "s' ")) - buf = (obj_is_pname(obj) || the_unique_obj(obj)) ? the(buf) : an(buf); - - objects[obj->otyp].oc_name_known = save_ocknown; - objects[obj->otyp].oc_uname = save_ocuname; - *obj = save_obj; /* restore object's core settings */ - if (!obj->oartifact && save_oname) - ONAME(obj) = save_oname; - - return buf; -} - -/* xname,doname,&c with long results reformatted to omit some stuff */ -char * -short_oname(obj, func, altfunc, lenlimit) -struct obj *obj; -char *FDECL((*func), (OBJ_P)), /* main formatting routine */ - *FDECL((*altfunc), (OBJ_P)); /* alternate for shortest result */ -unsigned lenlimit; -{ - struct obj save_obj; - char unamebuf[12], onamebuf[12], *save_oname, *save_uname, *outbuf; - - outbuf = (*func)(obj); - if ((unsigned) strlen(outbuf) <= lenlimit) - return outbuf; - - /* shorten called string to fairly small amount */ - save_uname = objects[obj->otyp].oc_uname; - if (save_uname && strlen(save_uname) >= sizeof unamebuf) { - (void) strncpy(unamebuf, save_uname, sizeof unamebuf - 4); - Strcpy(unamebuf + sizeof unamebuf - 4, "..."); - objects[obj->otyp].oc_uname = unamebuf; - releaseobuf(outbuf); - outbuf = (*func)(obj); - objects[obj->otyp].oc_uname = save_uname; /* restore called string */ - if ((unsigned) strlen(outbuf) <= lenlimit) - return outbuf; - } - - /* shorten named string to fairly small amount */ - save_oname = has_oname(obj) ? ONAME(obj) : 0; - if (save_oname && strlen(save_oname) >= sizeof onamebuf) { - (void) strncpy(onamebuf, save_oname, sizeof onamebuf - 4); - Strcpy(onamebuf + sizeof onamebuf - 4, "..."); - ONAME(obj) = onamebuf; - releaseobuf(outbuf); - outbuf = (*func)(obj); - ONAME(obj) = save_oname; /* restore named string */ - if ((unsigned) strlen(outbuf) <= lenlimit) - return outbuf; - } - - /* shorten both called and named strings; - unamebuf and onamebuf have both already been populated */ - if (save_uname && strlen(save_uname) >= sizeof unamebuf && save_oname - && strlen(save_oname) >= sizeof onamebuf) { - objects[obj->otyp].oc_uname = unamebuf; - ONAME(obj) = onamebuf; - releaseobuf(outbuf); - outbuf = (*func)(obj); - if ((unsigned) strlen(outbuf) <= lenlimit) { - objects[obj->otyp].oc_uname = save_uname; - ONAME(obj) = save_oname; - return outbuf; - } - } - - /* still long; strip several name-lengthening attributes; - called and named strings are still in truncated form */ - save_obj = *obj; - obj->bknown = obj->rknown = obj->greased = 0; - obj->oeroded = obj->oeroded2 = 0; - releaseobuf(outbuf); - outbuf = (*func)(obj); - if (altfunc && (unsigned) strlen(outbuf) > lenlimit) { - /* still long; use the alternate function (usually one of - the jackets around minimal_xname()) */ - releaseobuf(outbuf); - outbuf = (*altfunc)(obj); - } - /* restore the object */ - *obj = save_obj; - if (save_oname) - ONAME(obj) = save_oname; - if (save_uname) - objects[obj->otyp].oc_uname = save_uname; - - /* use whatever we've got, whether it's too long or not */ - return outbuf; -} - -/* - * Used if only one of a collection of objects is named (e.g. in eat.c). - */ -const char * -singular(otmp, func) -register struct obj *otmp; -char *FDECL((*func), (OBJ_P)); -{ - long savequan; - char *nam; - - /* using xname for corpses does not give the monster type */ - if (otmp->otyp == CORPSE && func == xname) - func = cxname; - - savequan = otmp->quan; - otmp->quan = 1L; - nam = (*func)(otmp); - otmp->quan = savequan; - return nam; -} - -char * -an(str) -register const char *str; -{ - char *buf = nextobuf(); - - buf[0] = '\0'; - - if (strncmpi(str, "the ", 4) && strcmp(str, "molten lava") - && strcmp(str, "iron bars") && strcmp(str, "ice")) { - if (index(vowels, *str) && strncmp(str, "one-", 4) - && strncmp(str, "useful", 6) && strncmp(str, "unicorn", 7) - && strncmp(str, "uranium", 7) && strncmp(str, "eucalyptus", 10)) - Strcpy(buf, "an "); - else - Strcpy(buf, "a "); - } - - Strcat(buf, str); - return buf; -} - -char * -An(str) -const char *str; -{ - char *tmp = an(str); - - *tmp = highc(*tmp); - return tmp; -} - -/* - * Prepend "the" if necessary; assumes str is a subject derived from xname. - * Use type_is_pname() for monster names, not the(). the() is idempotent. - */ -char * -the(str) -const char *str; -{ - char *buf = nextobuf(); - boolean insert_the = FALSE; - - if (!strncmpi(str, "the ", 4)) { - buf[0] = lowc(*str); - Strcpy(&buf[1], str + 1); - return buf; - } else if (*str < 'A' || *str > 'Z' - /* treat named fruit as not a proper name, even if player - has assigned a capitalized proper name as his/her fruit */ - || fruit_from_name(str, TRUE, (int *) 0)) { - /* not a proper name, needs an article */ - insert_the = TRUE; - } else { - /* Probably a proper name, might not need an article */ - register char *tmp, *named, *called; - int l; - - /* some objects have capitalized adjectives in their names */ - if (((tmp = rindex(str, ' ')) != 0 || (tmp = rindex(str, '-')) != 0) - && (tmp[1] < 'A' || tmp[1] > 'Z')) { - insert_the = TRUE; - } else if (tmp && index(str, ' ') < tmp) { /* has spaces */ - /* it needs an article if the name contains "of" */ - tmp = strstri(str, " of "); - named = strstri(str, " named "); - called = strstri(str, " called "); - if (called && (!named || called < named)) - named = called; - - if (tmp && (!named || tmp < named)) /* found an "of" */ - insert_the = TRUE; - /* stupid special case: lacks "of" but needs "the" */ - else if (!named && (l = strlen(str)) >= 31 - && !strcmp(&str[l - 31], - "Platinum Yendorian Express Card")) - insert_the = TRUE; - } - } - if (insert_the) - Strcpy(buf, "the "); - else - buf[0] = '\0'; - Strcat(buf, str); - - return buf; -} - -char * -The(str) -const char *str; -{ - char *tmp = the(str); - - *tmp = highc(*tmp); - return tmp; -} - -/* returns "count cxname(otmp)" or just cxname(otmp) if count == 1 */ -char * -aobjnam(otmp, verb) -struct obj *otmp; -const char *verb; -{ - char prefix[PREFIX]; - char *bp = cxname(otmp); - - if (otmp->quan != 1L) { - Sprintf(prefix, "%ld ", otmp->quan); - bp = strprepend(bp, prefix); - } - if (verb) { - Strcat(bp, " "); - Strcat(bp, otense(otmp, verb)); - } - return bp; -} - -/* combine yname and aobjnam eg "your count cxname(otmp)" */ -char * -yobjnam(obj, verb) -struct obj *obj; -const char *verb; -{ - char *s = aobjnam(obj, verb); - - /* leave off "your" for most of your artifacts, but prepend - * "your" for unique objects and "foo of bar" quest artifacts */ - if (!carried(obj) || !obj_is_pname(obj) - || obj->oartifact >= ART_ORB_OF_DETECTION) { - char *outbuf = shk_your(nextobuf(), obj); - int space_left = BUFSZ - 1 - strlen(outbuf); - - s = strncat(outbuf, s, space_left); - } - return s; -} - -/* combine Yname2 and aobjnam eg "Your count cxname(otmp)" */ -char * -Yobjnam2(obj, verb) -struct obj *obj; -const char *verb; -{ - register char *s = yobjnam(obj, verb); - - *s = highc(*s); - return s; -} - -/* like aobjnam, but prepend "The", not count, and use xname */ -char * -Tobjnam(otmp, verb) -struct obj *otmp; -const char *verb; -{ - char *bp = The(xname(otmp)); - - if (verb) { - Strcat(bp, " "); - Strcat(bp, otense(otmp, verb)); - } - return bp; -} - -/* capitalized variant of doname() */ -char * -Doname2(obj) -struct obj *obj; -{ - char *s = doname(obj); - - *s = highc(*s); - return s; -} - -/* returns "[your ]xname(obj)" or "Foobar's xname(obj)" or "the xname(obj)" */ -char * -yname(obj) -struct obj *obj; -{ - char *s = cxname(obj); - - /* leave off "your" for most of your artifacts, but prepend - * "your" for unique objects and "foo of bar" quest artifacts */ - if (!carried(obj) || !obj_is_pname(obj) - || obj->oartifact >= ART_ORB_OF_DETECTION) { - char *outbuf = shk_your(nextobuf(), obj); - int space_left = BUFSZ - 1 - strlen(outbuf); - - s = strncat(outbuf, s, space_left); - } - - return s; -} - -/* capitalized variant of yname() */ -char * -Yname2(obj) -struct obj *obj; -{ - char *s = yname(obj); - - *s = highc(*s); - return s; -} - -/* returns "your minimal_xname(obj)" - * or "Foobar's minimal_xname(obj)" - * or "the minimal_xname(obj)" - */ -char * -ysimple_name(obj) -struct obj *obj; -{ - char *outbuf = nextobuf(); - char *s = shk_your(outbuf, obj); /* assert( s == outbuf ); */ - int space_left = BUFSZ - 1 - strlen(s); - - return strncat(s, minimal_xname(obj), space_left); -} - -/* capitalized variant of ysimple_name() */ -char * -Ysimple_name2(obj) -struct obj *obj; -{ - char *s = ysimple_name(obj); - - *s = highc(*s); - return s; -} - -/* "scroll" or "scrolls" */ -char * -simpleonames(obj) -struct obj *obj; -{ - char *simpleoname = minimal_xname(obj); - - if (obj->quan != 1L) - simpleoname = makeplural(simpleoname); - return simpleoname; -} - -/* "a scroll" or "scrolls"; "a silver bell" or "the Bell of Opening" */ -char * -ansimpleoname(obj) -struct obj *obj; -{ - char *simpleoname = simpleonames(obj); - int otyp = obj->otyp; - - /* prefix with "the" if a unique item, or a fake one imitating same, - has been formatted with its actual name (we let typename() handle - any `known' and `dknown' checking necessary) */ - if (otyp == FAKE_AMULET_OF_YENDOR) - otyp = AMULET_OF_YENDOR; - if (objects[otyp].oc_unique - && !strcmp(simpleoname, OBJ_NAME(objects[otyp]))) - return the(simpleoname); - - /* simpleoname is singular if quan==1, plural otherwise */ - if (obj->quan == 1L) - simpleoname = an(simpleoname); - return simpleoname; -} - -/* "the scroll" or "the scrolls" */ -char * -thesimpleoname(obj) -struct obj *obj; -{ - char *simpleoname = simpleonames(obj); - - return the(simpleoname); -} - -/* artifact's name without any object type or known/dknown/&c feedback */ -char * -bare_artifactname(obj) -struct obj *obj; -{ - char *outbuf; - - if (obj->oartifact) { - outbuf = nextobuf(); - Strcpy(outbuf, artiname(obj->oartifact)); - if (!strncmp(outbuf, "The ", 4)) - outbuf[0] = lowc(outbuf[0]); - } else { - outbuf = xname(obj); - } - return outbuf; -} - -static const char *wrp[] = { - "wand", "ring", "potion", "scroll", "gem", - "amulet", "spellbook", "spell book", - /* for non-specific wishes */ - "weapon", "armor", "tool", "food", "comestible", -}; -static const char wrpsym[] = { WAND_CLASS, RING_CLASS, POTION_CLASS, - SCROLL_CLASS, GEM_CLASS, AMULET_CLASS, - SPBOOK_CLASS, SPBOOK_CLASS, WEAPON_CLASS, - ARMOR_CLASS, TOOL_CLASS, FOOD_CLASS, - FOOD_CLASS }; - -/* return form of the verb (input plural) if xname(otmp) were the subject */ -char * -otense(otmp, verb) -struct obj *otmp; -const char *verb; -{ - char *buf; - - /* - * verb is given in plural (without trailing s). Return as input - * if the result of xname(otmp) would be plural. Don't bother - * recomputing xname(otmp) at this time. - */ - if (!is_plural(otmp)) - return vtense((char *) 0, verb); - - buf = nextobuf(); - Strcpy(buf, verb); - return buf; -} - -/* various singular words that vtense would otherwise categorize as plural; - also used by makesingular() to catch some special cases */ -static const char *const special_subjs[] = { - "erinys", "manes", /* this one is ambiguous */ - "Cyclops", "Hippocrates", "Pelias", "aklys", - "amnesia", "detect monsters", "paralysis", "shape changers", - "nemesis", 0 - /* note: "detect monsters" and "shape changers" are normally - caught via "(s) of ", but they can be - wished for using the shorter form, so we include them here - to accommodate usage by makesingular during wishing */ -}; - -/* return form of the verb (input plural) for present tense 3rd person subj */ -char * -vtense(subj, verb) -register const char *subj; -register const char *verb; -{ - char *buf = nextobuf(), *bspot; - int len, ltmp; - const char *sp, *spot; - const char *const *spec; - - /* - * verb is given in plural (without trailing s). Return as input - * if subj appears to be plural. Add special cases as necessary. - * Many hard cases can already be handled by using otense() instead. - * If this gets much bigger, consider decomposing makeplural. - * Note: monster names are not expected here (except before corpse). - * - * Special case: allow null sobj to get the singular 3rd person - * present tense form so we don't duplicate this code elsewhere. - */ - if (subj) { - if (!strncmpi(subj, "a ", 2) || !strncmpi(subj, "an ", 3)) - goto sing; - spot = (const char *) 0; - for (sp = subj; (sp = index(sp, ' ')) != 0; ++sp) { - if (!strncmpi(sp, " of ", 4) || !strncmpi(sp, " from ", 6) - || !strncmpi(sp, " called ", 8) || !strncmpi(sp, " named ", 7) - || !strncmpi(sp, " labeled ", 9)) { - if (sp != subj) - spot = sp - 1; - break; - } - } - len = (int) strlen(subj); - if (!spot) - spot = subj + len - 1; - - /* - * plural: anything that ends in 's', but not '*us' or '*ss'. - * Guess at a few other special cases that makeplural creates. - */ - if ((lowc(*spot) == 's' && spot != subj - && !index("us", lowc(*(spot - 1)))) - || !BSTRNCMPI(subj, spot - 3, "eeth", 4) - || !BSTRNCMPI(subj, spot - 3, "feet", 4) - || !BSTRNCMPI(subj, spot - 1, "ia", 2) - || !BSTRNCMPI(subj, spot - 1, "ae", 2)) { - /* check for special cases to avoid false matches */ - len = (int) (spot - subj) + 1; - for (spec = special_subjs; *spec; spec++) { - ltmp = strlen(*spec); - if (len == ltmp && !strncmpi(*spec, subj, len)) - goto sing; - /* also check for - to catch things like "the invisible erinys" */ - if (len > ltmp && *(spot - ltmp) == ' ' - && !strncmpi(*spec, spot - ltmp + 1, ltmp)) - goto sing; - } - - return strcpy(buf, verb); - } - /* - * 3rd person plural doesn't end in telltale 's'; - * 2nd person singular behaves as if plural. - */ - if (!strcmpi(subj, "they") || !strcmpi(subj, "you")) - return strcpy(buf, verb); - } - -sing: - Strcpy(buf, verb); - len = (int) strlen(buf); - bspot = buf + len - 1; - - if (!strcmpi(buf, "are")) { - Strcasecpy(buf, "is"); - } else if (!strcmpi(buf, "have")) { - Strcasecpy(bspot - 1, "s"); - } else if (index("zxs", lowc(*bspot)) - || (len >= 2 && lowc(*bspot) == 'h' - && index("cs", lowc(*(bspot - 1)))) - || (len == 2 && lowc(*bspot) == 'o')) { - /* Ends in z, x, s, ch, sh; add an "es" */ - Strcasecpy(bspot + 1, "es"); - } else if (lowc(*bspot) == 'y' && !index(vowels, lowc(*(bspot - 1)))) { - /* like "y" case in makeplural */ - Strcasecpy(bspot, "ies"); - } else { - Strcasecpy(bspot + 1, "s"); - } - - return buf; -} - -struct sing_plur { - const char *sing, *plur; -}; - -/* word pairs that don't fit into formula-based transformations; - also some suffices which have very few--often one--matches or - which aren't systematically reversible (knives, staves) */ -static struct sing_plur one_off[] = { - { "child", - "children" }, /* (for wise guys who give their food funny names) */ - { "cubus", "cubi" }, /* in-/suc-cubus */ - { "culus", "culi" }, /* homunculus */ - { "djinni", "djinn" }, - { "erinys", "erinyes" }, - { "foot", "feet" }, - { "fungus", "fungi" }, - { "goose", "geese" }, - { "knife", "knives" }, - { "labrum", "labra" }, /* candelabrum */ - { "louse", "lice" }, - { "mouse", "mice" }, - { "mumak", "mumakil" }, - { "nemesis", "nemeses" }, - { "ovum", "ova" }, - { "ox", "oxen" }, - { "passerby", "passersby" }, - { "rtex", "rtices" }, /* vortex */ - { "serum", "sera" }, - { "staff", "staves" }, - { "tooth", "teeth" }, - { 0, 0 } -}; - -static const char *const as_is[] = { - /* makesingular() leaves these plural due to how they're used */ - "boots", "shoes", "gloves", "lenses", "scales", - "eyes", "gauntlets", "iron bars", - /* both singular and plural are spelled the same */ - "bison", "deer", "elk", "fish", "fowl", - "tuna", "yaki", "-hai", "krill", "manes", - "moose", "ninja", "sheep", "ronin", "roshi", - "shito", "tengu", "ki-rin", "Nazgul", "gunyoki", - "piranha", "samurai", "shuriken", 0, - /* Note: "fish" and "piranha" are collective plurals, suitable - for "wiped out all ". For "3 ", they should be - "fishes" and "piranhas" instead. We settle for collective - variant instead of attempting to support both. */ -}; - -/* singularize/pluralize decisions common to both makesingular & makeplural */ -STATIC_OVL boolean -singplur_lookup(basestr, endstring, to_plural, alt_as_is) -char *basestr, *endstring; /* base string, pointer to eos(string) */ -boolean to_plural; /* true => makeplural, false => makesingular */ -const char *const *alt_as_is; /* another set like as_is[] */ -{ - const struct sing_plur *sp; - const char *same, *other, *const *as; - int al; - - for (as = as_is; *as; ++as) { - al = (int) strlen(*as); - if (!BSTRCMPI(basestr, endstring - al, *as)) - return TRUE; - } - if (alt_as_is) { - for (as = alt_as_is; *as; ++as) { - al = (int) strlen(*as); - if (!BSTRCMPI(basestr, endstring - al, *as)) - return TRUE; - } - } - - /* avoid false hit on one_off[].plur == "lice" or .sing == "goose"; - if more of these turn up, one_off[] entries will need to flagged - as to which are whole words and which are matchable as suffices - then matching in the loop below will end up becoming more complex */ - if (!strcmpi(basestr, "slice") - || !strcmpi(basestr, "mongoose")) { - if (to_plural) - Strcasecpy(endstring, "s"); - return TRUE; - } - /* skip "ox" -> "oxen" entry when pluralizing "ox" - unless it is muskox */ - if (to_plural && strlen(basestr) > 2 && !strcmpi(endstring - 2, "ox") - && strcmpi(endstring - 6, "muskox")) { - /* "fox" -> "foxes" */ - Strcasecpy(endstring, "es"); - return TRUE; - } - if (to_plural) { - if (!strcmpi(endstring - 3, "man") - && badman(basestr, to_plural)) { - Strcasecpy(endstring, "s"); - return TRUE; - } - } else { - if (!strcmpi(endstring - 3, "men") - && badman(basestr, to_plural)) - return TRUE; - } - for (sp = one_off; sp->sing; sp++) { - /* check whether endstring already matches */ - same = to_plural ? sp->plur : sp->sing; - al = (int) strlen(same); - if (!BSTRCMPI(basestr, endstring - al, same)) - return TRUE; /* use as-is */ - /* check whether it matches the inverse; if so, transform it */ - other = to_plural ? sp->sing : sp->plur; - al = (int) strlen(other); - if (!BSTRCMPI(basestr, endstring - al, other)) { - Strcasecpy(endstring - al, same); - return TRUE; /* one_off[] transformation */ - } - } - return FALSE; -} - -/* searches for common compounds, ex. lump of royal jelly */ -STATIC_OVL char * -singplur_compound(str) -char *str; -{ - /* if new entries are added, be sure to keep compound_start[] in sync */ - static const char *const compounds[] = - { - " of ", " labeled ", " called ", - " named ", " above", /* lurkers above */ - " versus ", " from ", " in ", - " on ", " a la ", " with", /* " with "? */ - " de ", " d'", " du ", - "-in-", "-at-", 0 - }, /* list of first characters for all compounds[] entries */ - compound_start[] = " -"; - - const char *const *cmpd; - char *p; - - for (p = str; *p; ++p) { - /* substring starting at p can only match if *p is found - within compound_start[] */ - if (!index(compound_start, *p)) - continue; - - /* check current substring against all words in the compound[] list */ - for (cmpd = compounds; *cmpd; ++cmpd) - if (!strncmpi(p, *cmpd, (int) strlen(*cmpd))) - return p; - } - /* wasn't recognized as a compound phrase */ - return 0; -} - -/* Plural routine; once upon a time it may have been chiefly used for - * user-defined fruits, but it is now used extensively throughout the - * program. - * - * For fruit, we have to try to account for everything reasonable the - * player has; something unreasonable can still break the code. - * However, it's still a lot more accurate than "just add an 's' at the - * end", which Rogue uses... - * - * Also used for plural monster names ("Wiped out all homunculi." or the - * vanquished monsters list) and body parts. A lot of unique monsters have - * names which get mangled by makeplural and/or makesingular. They're not - * genocidable, and vanquished-mon handling does its own special casing - * (for uniques who've been revived and re-killed), so we don't bother - * trying to get those right here. - * - * Also misused by muse.c to convert 1st person present verbs to 2nd person. - * 3.6.0: made case-insensitive. - */ -char * -makeplural(oldstr) -const char *oldstr; -{ - register char *spot; - char lo_c, *str = nextobuf(); - const char *excess = (char *) 0; - int len; - - if (oldstr) - while (*oldstr == ' ') - oldstr++; - if (!oldstr || !*oldstr) { - impossible("plural of null?"); - Strcpy(str, "s"); - return str; - } - Strcpy(str, oldstr); - - /* - * Skip changing "pair of" to "pairs of". According to Webster, usual - * English usage is use pairs for humans, e.g. 3 pairs of dancers, - * and pair for objects and non-humans, e.g. 3 pair of boots. We don't - * refer to pairs of humans in this game so just skip to the bottom. - */ - if (!strncmpi(str, "pair of ", 8)) - goto bottom; - - /* look for "foo of bar" so that we can focus on "foo" */ - if ((spot = singplur_compound(str)) != 0) { - excess = oldstr + (int) (spot - str); - *spot = '\0'; - } else - spot = eos(str); - - spot--; - while (spot > str && *spot == ' ') - spot--; /* Strip blanks from end */ - *(spot + 1) = '\0'; - /* Now spot is the last character of the string */ - - len = strlen(str); - - /* Single letters */ - if (len == 1 || !letter(*spot)) { - Strcpy(spot + 1, "'s"); - goto bottom; - } - - /* dispense with some words which don't need pluralization */ - { - static const char *const already_plural[] = { - "ae", /* algae, larvae, &c */ - "matzot", 0, - }; - - /* spot+1: synch up with makesingular's usage */ - if (singplur_lookup(str, spot + 1, TRUE, already_plural)) - goto bottom; - - /* more of same, but not suitable for blanket loop checking */ - if ((len == 2 && !strcmpi(str, "ya")) - || (len >= 3 && !strcmpi(spot - 2, " ya"))) - goto bottom; - } - - /* man/men ("Wiped out all cavemen.") */ - if (len >= 3 && !strcmpi(spot - 2, "man") - /* exclude shamans and humans etc */ - && !badman(str, TRUE)) { - Strcasecpy(spot - 1, "en"); - goto bottom; - } - if (lowc(*spot) == 'f') { /* (staff handled via one_off[]) */ - lo_c = lowc(*(spot - 1)); - if (len >= 3 && !strcmpi(spot - 2, "erf")) { - /* avoid "nerf" -> "nerves", "serf" -> "serves" */ - ; /* fall through to default (append 's') */ - } else if (index("lr", lo_c) || index(vowels, lo_c)) { - /* [aeioulr]f to [aeioulr]ves */ - Strcasecpy(spot, "ves"); - goto bottom; - } - } - /* ium/ia (mycelia, baluchitheria) */ - if (len >= 3 && !strcmpi(spot - 2, "ium")) { - Strcasecpy(spot - 2, "ia"); - goto bottom; - } - /* algae, larvae, hyphae (another fungus part) */ - if ((len >= 4 && !strcmpi(spot - 3, "alga")) - || (len >= 5 - && (!strcmpi(spot - 4, "hypha") || !strcmpi(spot - 4, "larva"))) - || (len >= 6 && !strcmpi(spot - 5, "amoeba")) - || (len >= 8 && (!strcmpi(spot - 7, "vertebra")))) { - /* a to ae */ - Strcasecpy(spot + 1, "e"); - goto bottom; - } - /* fungus/fungi, homunculus/homunculi, but buses, lotuses, wumpuses */ - if (len > 3 && !strcmpi(spot - 1, "us") - && !((len >= 5 && !strcmpi(spot - 4, "lotus")) - || (len >= 6 && !strcmpi(spot - 5, "wumpus")))) { - Strcasecpy(spot - 1, "i"); - goto bottom; - } - /* sis/ses (nemesis) */ - if (len >= 3 && !strcmpi(spot - 2, "sis")) { - Strcasecpy(spot - 1, "es"); - goto bottom; - } - /* matzoh/matzot, possible food name */ - if (len >= 6 - && (!strcmpi(spot - 5, "matzoh") || !strcmpi(spot - 5, "matzah"))) { - Strcasecpy(spot - 1, "ot"); /* oh/ah -> ot */ - goto bottom; - } - if (len >= 5 - && (!strcmpi(spot - 4, "matzo") || !strcmpi(spot - 4, "matza"))) { - Strcasecpy(spot, "ot"); /* o/a -> ot */ - goto bottom; - } - - /* note: -eau/-eaux (gateau, bordeau...) */ - /* note: ox/oxen, VAX/VAXen, goose/geese */ - - lo_c = lowc(*spot); - - /* Ends in z, x, s, ch, sh; add an "es" */ - if (index("zxs", lo_c) - || (len >= 2 && lo_c == 'h' && index("cs", lowc(*(spot - 1)))) - /* Kludge to get "tomatoes" and "potatoes" right */ - || (len >= 4 && !strcmpi(spot - 2, "ato")) - || (len >= 5 && !strcmpi(spot - 4, "dingo"))) { - Strcasecpy(spot + 1, "es"); /* append es */ - goto bottom; - } - /* Ends in y preceded by consonant (note: also "qu") change to "ies" */ - if (lo_c == 'y' && !index(vowels, lowc(*(spot - 1)))) { - Strcasecpy(spot, "ies"); /* y -> ies */ - goto bottom; - } - /* Default: append an 's' */ - Strcasecpy(spot + 1, "s"); - -bottom: - if (excess) - Strcat(str, excess); - return str; -} - -/* - * Singularize a string the user typed in; this helps reduce the complexity - * of readobjnam, and is also used in pager.c to singularize the string - * for which help is sought. - * - * "Manes" is ambiguous: monster type (keep s), or horse body part (drop s)? - * Its inclusion in as_is[]/special_subj[] makes it get treated as the former. - * - * A lot of unique monsters have names ending in s; plural, or singular - * from plural, doesn't make much sense for them so we don't bother trying. - * 3.6.0: made case-insensitive. - */ -char * -makesingular(oldstr) -const char *oldstr; -{ - register char *p, *bp; - const char *excess = 0; - char *str = nextobuf(); - - if (oldstr) - while (*oldstr == ' ') - oldstr++; - if (!oldstr || !*oldstr) { - impossible("singular of null?"); - str[0] = '\0'; - return str; - } - - bp = strcpy(str, oldstr); - - /* check for "foo of bar" so that we can focus on "foo" */ - if ((p = singplur_compound(bp)) != 0) { - excess = oldstr + (int) (p - bp); - *p = '\0'; - } else - p = eos(bp); - - /* dispense with some words which don't need singularization */ - if (singplur_lookup(bp, p, FALSE, special_subjs)) - goto bottom; - - /* remove -s or -es (boxes) or -ies (rubies) */ - if (p >= bp + 1 && lowc(p[-1]) == 's') { - if (p >= bp + 2 && lowc(p[-2]) == 'e') { - if (p >= bp + 3 && lowc(p[-3]) == 'i') { /* "ies" */ - if (!BSTRCMPI(bp, p - 7, "cookies") - || !BSTRCMPI(bp, p - 4, "pies") - || !BSTRCMPI(bp, p - 5, "mbies") /* zombie */ - || !BSTRCMPI(bp, p - 5, "yries")) /* valkyrie */ - goto mins; - Strcasecpy(p - 3, "y"); /* ies -> y */ - goto bottom; - } - /* wolves, but f to ves isn't fully reversible */ - if (p - 4 >= bp && (index("lr", lowc(*(p - 4))) - || index(vowels, lowc(*(p - 4)))) - && !BSTRCMPI(bp, p - 3, "ves")) { - if (!BSTRCMPI(bp, p - 6, "cloves") - || !BSTRCMPI(bp, p - 6, "nerves")) - goto mins; - Strcasecpy(p - 3, "f"); /* ves -> f */ - goto bottom; - } - /* note: nurses, axes but boxes, wumpuses */ - if (!BSTRCMPI(bp, p - 4, "eses") - || !BSTRCMPI(bp, p - 4, "oxes") /* boxes, foxes */ - || !BSTRCMPI(bp, p - 4, "nxes") /* lynxes */ - || !BSTRCMPI(bp, p - 4, "ches") - || !BSTRCMPI(bp, p - 4, "uses") /* lotuses */ - || !BSTRCMPI(bp, p - 4, "sses") /* priestesses */ - || !BSTRCMPI(bp, p - 5, "atoes") /* tomatoes */ - || !BSTRCMPI(bp, p - 7, "dingoes") - || !BSTRCMPI(bp, p - 7, "Aleaxes")) { - *(p - 2) = '\0'; /* drop es */ - goto bottom; - } /* else fall through to mins */ - - /* ends in 's' but not 'es' */ - } else if (!BSTRCMPI(bp, p - 2, "us")) { /* lotus, fungus... */ - if (BSTRCMPI(bp, p - 6, "tengus") /* but not these... */ - && BSTRCMPI(bp, p - 7, "hezrous")) - goto bottom; - } else if (!BSTRCMPI(bp, p - 2, "ss") - || !BSTRCMPI(bp, p - 5, " lens") - || (p - 4 == bp && !strcmpi(p - 4, "lens"))) { - goto bottom; - } - mins: - *(p - 1) = '\0'; /* drop s */ - - } else { /* input doesn't end in 's' */ - - if (!BSTRCMPI(bp, p - 3, "men") - && !badman(bp, FALSE)) { - Strcasecpy(p - 2, "an"); - goto bottom; - } - /* matzot -> matzo, algae -> alga */ - if (!BSTRCMPI(bp, p - 6, "matzot") || !BSTRCMPI(bp, p - 2, "ae")) { - *(p - 1) = '\0'; /* drop t/e */ - goto bottom; - } - /* balactheria -> balactherium */ - if (p - 4 >= bp && !strcmpi(p - 2, "ia") - && index("lr", lowc(*(p - 3))) && lowc(*(p - 4)) == 'e') { - Strcasecpy(p - 1, "um"); /* a -> um */ - } - - /* here we cannot find the plural suffix */ - } - -bottom: - /* if we stripped off a suffix (" of bar" from "foo of bar"), - put it back now [strcat() isn't actually 100% safe here...] */ - if (excess) - Strcat(bp, excess); - - return bp; -} - -boolean -badman(basestr, to_plural) -const char *basestr; -boolean to_plural; /* true => makeplural, false => makesingular */ -{ - int i, al; - char *endstr, *spot; - /* these are all the prefixes for *man that don't have a *men plural */ - const char *no_men[] = { - "albu", "antihu", "anti", "ata", "auto", "bildungsro", "cai", - "cay", "ceru", "corner", "decu", "des", "dura", "fir", - "glass", "hanu", "het", "infrahu", "inhu", "land", - "meat", "nonhu", "otto", "out", "prehu", "protohu", - "subhu", "superhu", "talis", "unhu", "sha", - "hu", "un", "le", "re", "so", "to", "at", "a", - }; - /* these are all the prefixes for *men that don't have a *man singular */ - const char *no_man[] = { - "abdo", "acu", "agno", "ceru", "cogno", "cycla", "fleh", "grava", - "hegu", "preno", "sonar", "dai", "exa", "fla", "sta", "teg", "tegu", - "vela", "da", "hy", "lu", "no", "nu", "ra", "ru", "se", "vi", "ya", - "o", "a", - }; - - if (!basestr || strlen(basestr) < 4) - return FALSE; - - endstr = eos((char *)basestr); - - if (to_plural) { - for (i = 0; i < SIZE(no_men); i++) { - al = (int) strlen(no_men[i]); - spot = endstr - (al + 3); - if (!BSTRNCMPI(basestr, spot, no_men[i], al) - && (spot == basestr || *(spot - 1) == ' ')) - return TRUE; - } - } else { - for (i = 0; i < SIZE(no_man); i++) { - al = (int) strlen(no_man[i]); - spot = endstr - (al + 3); - if (!BSTRNCMPI(basestr, spot, no_man[i], al) - && (spot == basestr || *(spot - 1) == ' ')) - return TRUE; - } - } - return FALSE; -} - -/* compare user string against object name string using fuzzy matching */ -STATIC_OVL boolean -wishymatch(u_str, o_str, retry_inverted) -const char *u_str; /* from user, so might be variant spelling */ -const char *o_str; /* from objects[], so is in canonical form */ -boolean retry_inverted; /* optional extra "of" handling */ -{ - static NEARDATA const char detect_SP[] = "detect ", - SP_detection[] = " detection"; - char *p, buf[BUFSZ]; - - /* ignore spaces & hyphens and upper/lower case when comparing */ - if (fuzzymatch(u_str, o_str, " -", TRUE)) - return TRUE; - - if (retry_inverted) { - const char *u_of, *o_of; - - /* when just one of the strings is in the form "foo of bar", - convert it into "bar foo" and perform another comparison */ - u_of = strstri(u_str, " of "); - o_of = strstri(o_str, " of "); - if (u_of && !o_of) { - Strcpy(buf, u_of + 4); - p = eos(strcat(buf, " ")); - while (u_str < u_of) - *p++ = *u_str++; - *p = '\0'; - return fuzzymatch(buf, o_str, " -", TRUE); - } else if (o_of && !u_of) { - Strcpy(buf, o_of + 4); - p = eos(strcat(buf, " ")); - while (o_str < o_of) - *p++ = *o_str++; - *p = '\0'; - return fuzzymatch(u_str, buf, " -", TRUE); - } - } - - /* [note: if something like "elven speed boots" ever gets added, these - special cases should be changed to call wishymatch() recursively in - order to get the "of" inversion handling] */ - if (!strncmp(o_str, "dwarvish ", 9)) { - if (!strncmpi(u_str, "dwarven ", 8)) - return fuzzymatch(u_str + 8, o_str + 9, " -", TRUE); - } else if (!strncmp(o_str, "elven ", 6)) { - if (!strncmpi(u_str, "elvish ", 7)) - return fuzzymatch(u_str + 7, o_str + 6, " -", TRUE); - else if (!strncmpi(u_str, "elfin ", 6)) - return fuzzymatch(u_str + 6, o_str + 6, " -", TRUE); - } else if (!strncmp(o_str, detect_SP, sizeof detect_SP - 1)) { - /* check for "detect " vs " detection" */ - if ((p = strstri(u_str, SP_detection)) != 0 - && !*(p + sizeof SP_detection - 1)) { - /* convert " detection" into "detect " */ - *p = '\0'; - Strcat(strcpy(buf, detect_SP), u_str); - /* "detect monster" -> "detect monsters" */ - if (!strcmpi(u_str, "monster")) - Strcat(buf, "s"); - *p = ' '; - return fuzzymatch(buf, o_str, " -", TRUE); - } - } else if (strstri(o_str, SP_detection)) { - /* and the inverse, " detection" vs "detect " */ - if (!strncmpi(u_str, detect_SP, sizeof detect_SP - 1)) { - /* convert "detect s" into " detection" */ - p = makesingular(u_str + sizeof detect_SP - 1); - Strcat(strcpy(buf, p), SP_detection); - /* caller may be looping through objects[], so avoid - churning through all the obufs */ - releaseobuf(p); - return fuzzymatch(buf, o_str, " -", TRUE); - } - } else if (strstri(o_str, "ability")) { - /* when presented with "foo of bar", makesingular() used to - singularize both foo & bar, but now only does so for foo */ - /* catch "{potion(s),ring} of {gain,restore,sustain} abilities" */ - if ((p = strstri(u_str, "abilities")) != 0 - && !*(p + sizeof "abilities" - 1)) { - (void) strncpy(buf, u_str, (unsigned) (p - u_str)); - Strcpy(buf + (p - u_str), "ability"); - return fuzzymatch(buf, o_str, " -", TRUE); - } - } else if (!strcmp(o_str, "aluminum")) { - /* this special case doesn't really fit anywhere else... */ - /* (note that " wand" will have been stripped off by now) */ - if (!strcmpi(u_str, "aluminium")) - return fuzzymatch(u_str + 9, o_str + 8, " -", TRUE); - } - - return FALSE; -} - -struct o_range { - const char *name, oclass; - int f_o_range, l_o_range; -}; - -/* wishable subranges of objects */ -STATIC_OVL NEARDATA const struct o_range o_ranges[] = { - { "bag", TOOL_CLASS, SACK, BAG_OF_TRICKS }, - { "lamp", TOOL_CLASS, OIL_LAMP, MAGIC_LAMP }, - { "candle", TOOL_CLASS, TALLOW_CANDLE, WAX_CANDLE }, - { "horn", TOOL_CLASS, TOOLED_HORN, HORN_OF_PLENTY }, - { "shield", ARMOR_CLASS, SMALL_SHIELD, SHIELD_OF_REFLECTION }, - { "hat", ARMOR_CLASS, FEDORA, DUNCE_CAP }, - { "helm", ARMOR_CLASS, ELVEN_LEATHER_HELM, HELM_OF_TELEPATHY }, - { "gloves", ARMOR_CLASS, LEATHER_GLOVES, GAUNTLETS_OF_DEXTERITY }, - { "gauntlets", ARMOR_CLASS, LEATHER_GLOVES, GAUNTLETS_OF_DEXTERITY }, - { "boots", ARMOR_CLASS, LOW_BOOTS, LEVITATION_BOOTS }, - { "shoes", ARMOR_CLASS, LOW_BOOTS, IRON_SHOES }, - { "cloak", ARMOR_CLASS, MUMMY_WRAPPING, CLOAK_OF_DISPLACEMENT }, - { "shirt", ARMOR_CLASS, HAWAIIAN_SHIRT, T_SHIRT }, - { "dragon scales", ARMOR_CLASS, GRAY_DRAGON_SCALES, - YELLOW_DRAGON_SCALES }, - { "dragon scale mail", ARMOR_CLASS, GRAY_DRAGON_SCALE_MAIL, - YELLOW_DRAGON_SCALE_MAIL }, - { "sword", WEAPON_CLASS, SHORT_SWORD, KATANA }, - { "venom", VENOM_CLASS, BLINDING_VENOM, ACID_VENOM }, - { "gray stone", GEM_CLASS, LUCKSTONE, FLINT }, - { "grey stone", GEM_CLASS, LUCKSTONE, FLINT }, -}; - -/* alternate spellings; if the difference is only the presence or - absence of spaces and/or hyphens (such as "pickaxe" vs "pick axe" - vs "pick-axe") then there is no need for inclusion in this list; - likewise for ``"of" inversions'' ("boots of speed" vs "speed boots") */ -struct alt_spellings { - const char *sp; - int ob; -} spellings[] = { - { "pickax", PICK_AXE }, - { "whip", BULLWHIP }, - { "saber", SILVER_SABER }, - { "silver sabre", SILVER_SABER }, - { "smooth shield", SHIELD_OF_REFLECTION }, - { "grey dragon scale mail", GRAY_DRAGON_SCALE_MAIL }, - { "grey dragon scales", GRAY_DRAGON_SCALES }, - { "iron ball", HEAVY_IRON_BALL }, - { "lantern", BRASS_LANTERN }, - { "mattock", DWARVISH_MATTOCK }, - { "amulet of poison resistance", AMULET_VERSUS_POISON }, - { "potion of sleep", POT_SLEEPING }, - { "stone", ROCK }, - { "camera", EXPENSIVE_CAMERA }, - { "tee shirt", T_SHIRT }, - { "can", TIN }, - { "can opener", TIN_OPENER }, - { "kelp", KELP_FROND }, - { "eucalyptus", EUCALYPTUS_LEAF }, - { "royal jelly", LUMP_OF_ROYAL_JELLY }, - { "lembas", LEMBAS_WAFER }, - { "marker", MAGIC_MARKER }, - { "hook", GRAPPLING_HOOK }, - { "grappling iron", GRAPPLING_HOOK }, - { "grapnel", GRAPPLING_HOOK }, - { "grapple", GRAPPLING_HOOK }, - { "protection from shape shifters", RIN_PROTECTION_FROM_SHAPE_CHAN }, - /* normally we wouldn't have to worry about unnecessary , but - " stone" will get stripped off, preventing a wishymatch; that actually - lets "flint stone" be a match, so we also accept bogus "flintstone" */ - { "luck stone", LUCKSTONE }, - { "load stone", LOADSTONE }, - { "touch stone", TOUCHSTONE }, - { "flintstone", FLINT }, - { (const char *) 0, 0 }, -}; - -STATIC_OVL short -rnd_otyp_by_wpnskill(skill) -schar skill; -{ - int i, n = 0; - short otyp = STRANGE_OBJECT; - for (i = bases[WEAPON_CLASS]; - i < NUM_OBJECTS && objects[i].oc_class == WEAPON_CLASS; i++) - if (objects[i].oc_skill == skill) { - n++; - otyp = i; - } - if (n > 0) { - n = rn2(n); - for (i = bases[WEAPON_CLASS]; - i < NUM_OBJECTS && objects[i].oc_class == WEAPON_CLASS; i++) - if (objects[i].oc_skill == skill) - if (--n < 0) - return i; - } - return otyp; -} - -STATIC_OVL short -rnd_otyp_by_namedesc(name, oclass) -char *name; -char oclass; -{ - int i, n = 0; - short validobjs[NUM_OBJECTS]; - register const char *zn; - long maxprob = 0; - - if (!name) - return STRANGE_OBJECT; - - memset((genericptr_t) validobjs, 0, sizeof(validobjs)); - - for (i = oclass ? bases[(int)oclass] : STRANGE_OBJECT + 1; - i < NUM_OBJECTS && (!oclass || objects[i].oc_class == oclass); - ++i) { - /* don't match extra descriptions (w/o real name) */ - if ((zn = OBJ_NAME(objects[i])) == 0) - continue; - if (wishymatch(name, zn, TRUE) - || ((zn = OBJ_DESCR(objects[i])) != 0 - && wishymatch(name, zn, FALSE)) - || ((zn = objects[i].oc_uname) != 0 - && wishymatch(name, zn, FALSE))) { - validobjs[n++] = (short) i; - maxprob += (objects[i].oc_prob + 1); - } - } - - if (n > 0 && maxprob) { - long prob = rn2(maxprob); - - i = 0; - while (i < n - 1 - && (prob -= (objects[validobjs[i]].oc_prob + 1)) >= 0) - i++; - return validobjs[i]; - } - return STRANGE_OBJECT; -} - -/* - * Return something wished for. Specifying a null pointer for - * the user request string results in a random object. Otherwise, - * if asking explicitly for "nothing" (or "nil") return no_wish; - * if not an object return &zeroobj; if an error (no matching object), - * return null. - */ -struct obj * -readobjnam(bp, no_wish) -register char *bp; -struct obj *no_wish; -{ - register char *p; - register int i; - register struct obj *otmp; - int cnt, spe, spesgn, typ, very, rechrg; - int blessed, uncursed, iscursed, ispoisoned, isgreased; - int eroded, eroded2, erodeproof; - int halfeaten, mntmp, contents; - int islit, unlabeled, ishistoric, isdiluted, trapped; - int tmp, tinv, tvariety; - int wetness, gsize = 0; - struct fruit *f; - int ftype = context.current_fruit; - char fruitbuf[BUFSZ]; - /* Fruits may not mess up the ability to wish for real objects (since - * you can leave a fruit in a bones file and it will be added to - * another person's game), so they must be checked for last, after - * stripping all the possible prefixes and seeing if there's a real - * name in there. So we have to save the full original name. However, - * it's still possible to do things like "uncursed burnt Alaska", - * or worse yet, "2 burned 5 course meals", so we need to loop to - * strip off the prefixes again, this time stripping only the ones - * possible on food. - * We could get even more detailed so as to allow food names with - * prefixes that _are_ possible on food, so you could wish for - * "2 3 alarm chilis". Currently this isn't allowed; options.c - * automatically sticks 'candied' in front of such names. - */ - char oclass; - char *un, *dn, *actualn, *origbp = bp; - const char *name = 0; - - cnt = spe = spesgn = typ = very = rechrg = blessed = uncursed = iscursed = - ispoisoned = isgreased = eroded = eroded2 = erodeproof = halfeaten = - islit = unlabeled = ishistoric = isdiluted = trapped = 0; - tvariety = RANDOM_TIN; - mntmp = NON_PM; -#define UNDEFINED 0 -#define EMPTY 1 -#define SPINACH 2 - contents = UNDEFINED; - oclass = 0; - actualn = dn = un = 0; - wetness = 0; - - if (!bp) - goto any; - /* first, remove extra whitespace they may have typed */ - (void) mungspaces(bp); - /* allow wishing for "nothing" to preserve wishless conduct... - [now requires "wand of nothing" if that's what was really wanted] */ - if (!strcmpi(bp, "nothing") || !strcmpi(bp, "nil") - || !strcmpi(bp, "none")) - return no_wish; - /* save the [nearly] unmodified choice string */ - Strcpy(fruitbuf, bp); - - for (;;) { - register int l; - - if (!bp || !*bp) - goto any; - if (!strncmpi(bp, "an ", l = 3) || !strncmpi(bp, "a ", l = 2)) { - cnt = 1; - } else if (!strncmpi(bp, "the ", l = 4)) { - ; /* just increment `bp' by `l' below */ - } else if (!cnt && digit(*bp) && strcmp(bp, "0")) { - cnt = atoi(bp); - while (digit(*bp)) - bp++; - while (*bp == ' ') - bp++; - l = 0; - } else if (*bp == '+' || *bp == '-') { - spesgn = (*bp++ == '+') ? 1 : -1; - spe = atoi(bp); - while (digit(*bp)) - bp++; - while (*bp == ' ') - bp++; - l = 0; - } else if (!strncmpi(bp, "blessed ", l = 8) - || !strncmpi(bp, "holy ", l = 5)) { - blessed = 1; - } else if (!strncmpi(bp, "moist ", l = 6) - || !strncmpi(bp, "wet ", l = 4)) { - if (!strncmpi(bp, "wet ", 4)) - wetness = rn2(3) + 3; - else - wetness = rnd(2); - } else if (!strncmpi(bp, "cursed ", l = 7) - || !strncmpi(bp, "unholy ", l = 7)) { - iscursed = 1; - } else if (!strncmpi(bp, "uncursed ", l = 9)) { - uncursed = 1; - } else if (!strncmpi(bp, "rustproof ", l = 10) - || !strncmpi(bp, "erodeproof ", l = 11) - || !strncmpi(bp, "corrodeproof ", l = 13) - || !strncmpi(bp, "fixed ", l = 6) - || !strncmpi(bp, "fireproof ", l = 10) - || !strncmpi(bp, "rotproof ", l = 9)) { - erodeproof = 1; - } else if (!strncmpi(bp, "lit ", l = 4) - || !strncmpi(bp, "burning ", l = 8)) { - islit = 1; - } else if (!strncmpi(bp, "unlit ", l = 6) - || !strncmpi(bp, "extinguished ", l = 13)) { - islit = 0; - /* "unlabeled" and "blank" are synonymous */ - } else if (!strncmpi(bp, "unlabeled ", l = 10) - || !strncmpi(bp, "unlabelled ", l = 11) - || !strncmpi(bp, "blank ", l = 6)) { - unlabeled = 1; - } else if (!strncmpi(bp, "poisoned ", l = 9)) { - ispoisoned = 1; - /* "trapped" recognized but not honored outside wizard mode */ - } else if (!strncmpi(bp, "trapped ", l = 8)) { - trapped = 0; /* undo any previous "untrapped" */ - if (wizard) - trapped = 1; - } else if (!strncmpi(bp, "untrapped ", l = 10)) { - trapped = 2; /* not trapped */ - } else if (!strncmpi(bp, "greased ", l = 8)) { - isgreased = 1; - } else if (!strncmpi(bp, "very ", l = 5)) { - /* very rusted very heavy iron ball */ - very = 1; - } else if (!strncmpi(bp, "thoroughly ", l = 11)) { - very = 2; - } else if (!strncmpi(bp, "rusty ", l = 6) - || !strncmpi(bp, "rusted ", l = 7) - || !strncmpi(bp, "burnt ", l = 6) - || !strncmpi(bp, "burned ", l = 7)) { - eroded = 1 + very; - very = 0; - } else if (!strncmpi(bp, "corroded ", l = 9) - || !strncmpi(bp, "rotted ", l = 7)) { - eroded2 = 1 + very; - very = 0; - } else if (!strncmpi(bp, "partly eaten ", l = 13) - || !strncmpi(bp, "partially eaten ", l = 16)) { - halfeaten = 1; - } else if (!strncmpi(bp, "historic ", l = 9)) { - ishistoric = 1; - } else if (!strncmpi(bp, "diluted ", l = 8)) { - isdiluted = 1; - } else if (!strncmpi(bp, "empty ", l = 6)) { - contents = EMPTY; - } else if (!strncmpi(bp, "small ", l = 6)) { /* glob sizes */ - gsize = 1; - } else if (!strncmpi(bp, "medium ", l = 7)) { - /* xname() doesn't display "medium" but without this - there'd be no way to ask for the intermediate size */ - gsize = 2; - } else if (!strncmpi(bp, "large ", l = 6)) { - /* "very large " had "very " peeled off on previous iteration */ - gsize = (very != 1) ? 3 : 4; - } else - break; - bp += l; - } - if (!cnt) - cnt = 1; /* %% what with "gems" etc. ? */ - if (strlen(bp) > 1 && (p = rindex(bp, '(')) != 0) { - boolean keeptrailingchars = TRUE; - - p[(p > bp && p[-1] == ' ') ? -1 : 0] = '\0'; /*terminate bp */ - ++p; /* advance past '(' */ - if (!strncmpi(p, "lit)", 4)) { - islit = 1; - p += 4 - 1; /* point at ')' */ - } else { - spe = atoi(p); - while (digit(*p)) - p++; - if (*p == ':') { - p++; - rechrg = spe; - spe = atoi(p); - while (digit(*p)) - p++; - } - if (*p != ')') { - spe = rechrg = 0; - /* mis-matched parentheses; rest of string will be ignored - * [probably we should restore everything back to '(' - * instead since it might be part of "named ..."] - */ - keeptrailingchars = FALSE; - } else { - spesgn = 1; - } - } - if (keeptrailingchars) { - char *pp = eos(bp); - - /* 'pp' points at 'pb's terminating '\0', - 'p' points at ')' and will be incremented past it */ - do { - *pp++ = *++p; - } while (*p); - } - } - /* - * otmp->spe is type schar, so we don't want spe to be any bigger or - * smaller. Also, spe should always be positive --some cheaters may - * try to confuse atoi(). - */ - if (spe < 0) { - spesgn = -1; /* cheaters get what they deserve */ - spe = abs(spe); - } - if (spe > SCHAR_LIM) - spe = SCHAR_LIM; - if (rechrg < 0 || rechrg > 7) - rechrg = 7; /* recharge_limit */ - - /* now we have the actual name, as delivered by xname, say - * green potions called whisky - * scrolls labeled "QWERTY" - * egg - * fortune cookies - * very heavy iron ball named hoei - * wand of wishing - * elven cloak - */ - if ((p = strstri(bp, " named ")) != 0) { - *p = 0; - name = p + 7; - } - if ((p = strstri(bp, " called ")) != 0) { - *p = 0; - un = p + 8; - /* "helmet called telepathy" is not "helmet" (a specific type) - * "shield called reflection" is not "shield" (a general type) - */ - for (i = 0; i < SIZE(o_ranges); i++) - if (!strcmpi(bp, o_ranges[i].name)) { - oclass = o_ranges[i].oclass; - goto srch; - } - } - if ((p = strstri(bp, " labeled ")) != 0) { - *p = 0; - dn = p + 9; - } else if ((p = strstri(bp, " labelled ")) != 0) { - *p = 0; - dn = p + 10; - } - if ((p = strstri(bp, " of spinach")) != 0) { - *p = 0; - contents = SPINACH; - } - - /* - * Skip over "pair of ", "pairs of", "set of" and "sets of". - * - * Accept "3 pair of boots" as well as "3 pairs of boots". It is - * valid English either way. See makeplural() for more on pair/pairs. - * - * We should only double count if the object in question is not - * referred to as a "pair of". E.g. We should double if the player - * types "pair of spears", but not if the player types "pair of - * lenses". Luckily (?) all objects that are referred to as pairs - * -- boots, gloves, and lenses -- are also not mergable, so cnt is - * ignored anyway. - */ - if (!strncmpi(bp, "pair of ", 8)) { - bp += 8; - cnt *= 2; - } else if (!strncmpi(bp, "pairs of ", 9)) { - bp += 9; - if (cnt > 1) - cnt *= 2; - } else if (!strncmpi(bp, "set of ", 7)) { - bp += 7; - } else if (!strncmpi(bp, "sets of ", 8)) { - bp += 8; - } - - /* intercept pudding globs here; they're a valid wish target, - * but we need them to not get treated like a corpse. - * - * also don't let player wish for multiple globs. - */ - if ((p = strstri(bp, "glob of ")) != 0 - || (p = strstri(bp, "globs of ")) != 0) { - int globoffset = (*(p + 4) == 's') ? 9 : 8; - - if ((mntmp = name_to_mon(p + globoffset)) >= PM_GRAY_OOZE - && mntmp <= PM_BLACK_PUDDING) { - mntmp = NON_PM; /* lie to ourselves */ - cnt = 0; /* force only one */ - } - } else { - /* - * Find corpse type using "of" (figurine of an orc, tin of orc meat) - * Don't check if it's a wand or spellbook. - * (avoid "wand/finger of death" confusion). - */ - if (!strstri(bp, "wand ") && !strstri(bp, "spellbook ") - && !strstri(bp, "finger ")) { - if ((p = strstri(bp, "tin of ")) != 0) { - if (!strcmpi(p + 7, "spinach")) { - contents = SPINACH; - mntmp = NON_PM; - } else { - tmp = tin_variety_txt(p + 7, &tinv); - tvariety = tinv; - mntmp = name_to_mon(p + 7 + tmp); - } - typ = TIN; - goto typfnd; - } else if ((p = strstri(bp, " of ")) != 0 - && (mntmp = name_to_mon(p + 4)) >= LOW_PM) - *p = 0; - } - } - /* Find corpse type w/o "of" (red dragon scale mail, yeti corpse) */ - if (strncmpi(bp, "samurai sword", 13) /* not the "samurai" monster! */ - && strncmpi(bp, "wizard lock", 11) /* not the "wizard" monster! */ - && strncmpi(bp, "ninja-to", 8) /* not the "ninja" rank */ - && strncmpi(bp, "master key", 10) /* not the "master" rank */ - && strncmpi(bp, "magenta", 7)) { /* not the "mage" rank */ - if (mntmp < LOW_PM && strlen(bp) > 2 - && (mntmp = name_to_mon(bp)) >= LOW_PM) { - int mntmptoo, mntmplen; /* double check for rank title */ - char *obp = bp; - - mntmptoo = title_to_mon(bp, (int *) 0, &mntmplen); - bp += (mntmp != mntmptoo) ? (int) strlen(mons[mntmp].mname) - : mntmplen; - if (*bp == ' ') { - bp++; - } else if (!strncmpi(bp, "s ", 2)) { - bp += 2; - } else if (!strncmpi(bp, "es ", 3)) { - bp += 3; - } else if (!*bp && !actualn && !dn && !un && !oclass) { - /* no referent; they don't really mean a monster type */ - bp = obp; - mntmp = NON_PM; - } - } - } - - /* first change to singular if necessary */ - if (*bp) { - char *sng = makesingular(bp); - if (strcmp(bp, sng)) { - if (cnt == 1) - cnt = 2; - Strcpy(bp, sng); - } - } - - /* Alternate spellings (pick-ax, silver sabre, &c) */ - { - struct alt_spellings *as = spellings; - - while (as->sp) { - if (fuzzymatch(bp, as->sp, " -", TRUE)) { - typ = as->ob; - goto typfnd; - } - as++; - } - /* can't use spellings list for this one due to shuffling */ - if (!strncmpi(bp, "grey spell", 10)) - *(bp + 2) = 'a'; - - if ((p = strstri(bp, "armour")) != 0) { - /* skip past "armo", then copy remainder beyond "u" */ - p += 4; - while ((*p = *(p + 1)) != '\0') - ++p; /* self terminating */ - } - } - - /* dragon scales - assumes order of dragons */ - if (!strcmpi(bp, "scales") && mntmp >= PM_GRAY_DRAGON - && mntmp <= PM_YELLOW_DRAGON) { - typ = GRAY_DRAGON_SCALES + mntmp - PM_GRAY_DRAGON; - mntmp = NON_PM; /* no monster */ - goto typfnd; - } - - p = eos(bp); - if (!BSTRCMPI(bp, p - 10, "holy water")) { - typ = POT_WATER; - if ((p - bp) >= 12 && *(p - 12) == 'u') - iscursed = 1; /* unholy water */ - else - blessed = 1; - goto typfnd; - } - if (unlabeled && !BSTRCMPI(bp, p - 6, "scroll")) { - typ = SCR_BLANK_PAPER; - goto typfnd; - } - if (unlabeled && !BSTRCMPI(bp, p - 9, "spellbook")) { - typ = SPE_BLANK_PAPER; - goto typfnd; - } - /* - * NOTE: Gold pieces are handled as objects nowadays, and therefore - * this section should probably be reconsidered as well as the entire - * gold/money concept. Maybe we want to add other monetary units as - * well in the future. (TH) - */ - if (!BSTRCMPI(bp, p - 10, "gold piece") - || !BSTRCMPI(bp, p - 7, "zorkmid") - || !strcmpi(bp, "gold") || !strcmpi(bp, "money") - || !strcmpi(bp, "coin") || *bp == GOLD_SYM) { - if (cnt > 5000 && !wizard) - cnt = 5000; - else if (cnt < 1) - cnt = 1; - otmp = mksobj(GOLD_PIECE, FALSE, FALSE); - otmp->quan = (long) cnt; - otmp->owt = weight(otmp); - context.botl = 1; - return otmp; - } - - /* check for single character object class code ("/" for wand, &c) */ - if (strlen(bp) == 1 && (i = def_char_to_objclass(*bp)) < MAXOCLASSES - && i > ILLOBJ_CLASS && (i != VENOM_CLASS || wizard)) { - oclass = i; - goto any; - } - - /* Search for class names: XXXXX potion, scroll of XXXXX. Avoid */ - /* false hits on, e.g., rings for "ring mail". */ - if (strncmpi(bp, "enchant ", 8) - && strncmpi(bp, "destroy ", 8) - && strncmpi(bp, "detect food", 11) - && strncmpi(bp, "food detection", 14) - && strncmpi(bp, "ring mail", 9) - && strncmpi(bp, "studded leather armor", 21) - && strncmpi(bp, "leather armor", 13) - && strncmpi(bp, "tooled horn", 11) - && strncmpi(bp, "food ration", 11) - && strncmpi(bp, "meat ring", 9)) - for (i = 0; i < (int) (sizeof wrpsym); i++) { - register int j = strlen(wrp[i]); - - if (!strncmpi(bp, wrp[i], j)) { - oclass = wrpsym[i]; - if (oclass != AMULET_CLASS) { - bp += j; - if (!strncmpi(bp, " of ", 4)) - actualn = bp + 4; - /* else if(*bp) ?? */ - } else - actualn = bp; - goto srch; - } - if (!BSTRCMPI(bp, p - j, wrp[i])) { - oclass = wrpsym[i]; - p -= j; - *p = 0; - if (p > bp && p[-1] == ' ') - p[-1] = 0; - actualn = dn = bp; - goto srch; - } - } - - /* Wishing in wizard mode can create traps and furniture. - * Part I: distinguish between trap and object for the two - * types of traps which have corresponding objects: bear trap - * and land mine. "beartrap" (object) and "bear trap" (trap) - * have a difference in spelling which we used to exploit by - * adding a special case in wishymatch(), but "land mine" is - * spelled the same either way so needs different handing. - * Since we need something else for land mine, we've dropped - * the bear trap hack so that both are handled exactly the - * same. To get an armed trap instead of a disarmed object, - * the player can prefix either the object name or the trap - * name with "trapped " (which ordinarily applies to chests - * and tins), or append something--anything at all except for - * " object", but " trap" is suggested--to either the trap - * name or the object name. - */ - if (wizard && (!strncmpi(bp, "bear", 4) || !strncmpi(bp, "land", 4))) { - boolean beartrap = (lowc(*bp) == 'b'); - char *zp = bp + 4; /* skip "bear"/"land" */ - - if (*zp == ' ') - ++zp; /* embedded space is optional */ - if (!strncmpi(zp, beartrap ? "trap" : "mine", 4)) { - zp += 4; - if (trapped == 2 || !strcmpi(zp, " object")) { - /* "untrapped " or " object" */ - typ = beartrap ? BEARTRAP : LAND_MINE; - goto typfnd; - } else if (trapped == 1 || *zp != '\0') { - /* "trapped " or " trap" (actually "*") */ - int idx = trap_to_defsym(beartrap ? BEAR_TRAP : LANDMINE); - - /* use canonical trap spelling, skip object matching */ - Strcpy(bp, defsyms[idx].explanation); - goto wiztrap; - } - /* [no prefix or suffix; we're going to end up matching - the object name and getting a disarmed trap object] */ - } - } - -retry: - /* "grey stone" check must be before general "stone" */ - for (i = 0; i < SIZE(o_ranges); i++) - if (!strcmpi(bp, o_ranges[i].name)) { - typ = rnd_class(o_ranges[i].f_o_range, o_ranges[i].l_o_range); - goto typfnd; - } - - if (!BSTRCMPI(bp, p - 6, " stone") || !BSTRCMPI(bp, p - 4, " gem")) { - p[!strcmpi(p - 4, " gem") ? -4 : -6] = '\0'; - oclass = GEM_CLASS; - dn = actualn = bp; - goto srch; - } else if (!strcmpi(bp, "looking glass")) { - ; /* avoid false hit on "* glass" */ - } else if (!BSTRCMPI(bp, p - 6, " glass") || !strcmpi(bp, "glass")) { - register char *g = bp; - if (strstri(g, "broken")) - return (struct obj *) 0; - if (!strncmpi(g, "worthless ", 10)) - g += 10; - if (!strncmpi(g, "piece of ", 9)) - g += 9; - if (!strncmpi(g, "colored ", 8)) - g += 8; - else if (!strncmpi(g, "coloured ", 9)) - g += 9; - if (!strcmpi(g, "glass")) { /* choose random color */ - /* 9 different kinds */ - typ = LAST_GEM + rnd(9); - if (objects[typ].oc_class == GEM_CLASS) - goto typfnd; - else - typ = 0; /* somebody changed objects[]? punt */ - } else { /* try to construct canonical form */ - char tbuf[BUFSZ]; - - Strcpy(tbuf, "worthless piece of "); - Strcat(tbuf, g); /* assume it starts with the color */ - Strcpy(bp, tbuf); - } - } - - actualn = bp; - if (!dn) - dn = actualn; /* ex. "skull cap" */ -srch: - /* check real names of gems first */ - if (!oclass && actualn) { - for (i = bases[GEM_CLASS]; i <= LAST_GEM; i++) { - register const char *zn; - - if ((zn = OBJ_NAME(objects[i])) != 0 && !strcmpi(actualn, zn)) { - typ = i; - goto typfnd; - } - } - /* "tin of foo" would be caught above, but plain "tin" has - a random chance of yielding "tin wand" unless we do this */ - if (!strcmpi(actualn, "tin")) { - typ = TIN; - goto typfnd; - } - } - - if (((typ = rnd_otyp_by_namedesc(actualn, oclass)) != STRANGE_OBJECT) - || ((typ = rnd_otyp_by_namedesc(dn, oclass)) != STRANGE_OBJECT) - || ((typ = rnd_otyp_by_namedesc(un, oclass)) != STRANGE_OBJECT) - || ((typ = rnd_otyp_by_namedesc(origbp, oclass)) != STRANGE_OBJECT)) - goto typfnd; - typ = 0; - - if (actualn) { - struct Jitem *j = Japanese_items; - - while (j->item) { - if (actualn && !strcmpi(actualn, j->name)) { - typ = j->item; - goto typfnd; - } - j++; - } - } - /* if we've stripped off "armor" and failed to match anything - in objects[], append "mail" and try again to catch misnamed - requests like "plate armor" and "yellow dragon scale armor" */ - if (oclass == ARMOR_CLASS && !strstri(bp, "mail")) { - /* modifying bp's string is ok; we're about to resort - to random armor if this also fails to match anything */ - Strcat(bp, " mail"); - goto retry; - } - if (!strcmpi(bp, "spinach")) { - contents = SPINACH; - typ = TIN; - goto typfnd; - } - /* Note: not strcmpi. 2 fruits, one capital, one not, are possible. - Also not strncmp. We used to ignore trailing text with it, but - that resulted in "grapefruit" matching "grape" if the latter came - earlier than the former in the fruit list. */ - { - char *fp; - int l, cntf; - int blessedf, iscursedf, uncursedf, halfeatenf; - - blessedf = iscursedf = uncursedf = halfeatenf = 0; - cntf = 0; - - fp = fruitbuf; - for (;;) { - if (!fp || !*fp) - break; - if (!strncmpi(fp, "an ", l = 3) || !strncmpi(fp, "a ", l = 2)) { - cntf = 1; - } else if (!cntf && digit(*fp)) { - cntf = atoi(fp); - while (digit(*fp)) - fp++; - while (*fp == ' ') - fp++; - l = 0; - } else if (!strncmpi(fp, "blessed ", l = 8)) { - blessedf = 1; - } else if (!strncmpi(fp, "cursed ", l = 7)) { - iscursedf = 1; - } else if (!strncmpi(fp, "uncursed ", l = 9)) { - uncursedf = 1; - } else if (!strncmpi(fp, "partly eaten ", l = 13) - || !strncmpi(fp, "partially eaten ", l = 16)) { - halfeatenf = 1; - } else - break; - fp += l; - } - - for (f = ffruit; f; f = f->nextf) { - /* match type: 0=none, 1=exact, 2=singular, 3=plural */ - int ftyp = 0; - - if (!strcmp(fp, f->fname)) - ftyp = 1; - else if (!strcmp(fp, makesingular(f->fname))) - ftyp = 2; - else if (!strcmp(fp, makeplural(f->fname))) - ftyp = 3; - if (ftyp) { - typ = SLIME_MOLD; - blessed = blessedf; - iscursed = iscursedf; - uncursed = uncursedf; - halfeaten = halfeatenf; - /* adjust count if user explicitly asked for - singular amount (can't happen unless fruit - has been given an already pluralized name) - or for plural amount */ - if (ftyp == 2 && !cntf) - cntf = 1; - else if (ftyp == 3 && !cntf) - cntf = 2; - cnt = cntf; - ftype = f->fid; - goto typfnd; - } - } - } - - if (!oclass && actualn) { - short objtyp; - - /* Perhaps it's an artifact specified by name, not type */ - name = artifact_name(actualn, &objtyp); - if (name) { - typ = objtyp; - goto typfnd; - } - } -/* Let wizards wish for traps and furniture. - * Must come after objects check so wizards can still wish for - * trap objects like beartraps. - * Disallow such topology tweaks for WIZKIT startup wishes. - */ -wiztrap: - if (wizard && !program_state.wizkit_wishing) { - struct rm *lev; - int trap, x = u.ux, y = u.uy; - - for (trap = NO_TRAP + 1; trap < TRAPNUM; trap++) { - struct trap *t; - const char *tname; - - tname = defsyms[trap_to_defsym(trap)].explanation; - if (strncmpi(tname, bp, strlen(tname))) - continue; - /* found it; avoid stupid mistakes */ - if ((trap == TRAPDOOR || trap == HOLE) && !Can_fall_thru(&u.uz)) - trap = ROCKTRAP; - if ((t = maketrap(x, y, trap)) != 0) { - trap = t->ttyp; - tname = defsyms[trap_to_defsym(trap)].explanation; - pline("%s%s.", An(tname), - (trap != MAGIC_PORTAL) ? "" : " to nowhere"); - } else - pline("Creation of %s failed.", an(tname)); - return &zeroobj; - } - - /* furniture and terrain */ - lev = &levl[x][y]; - p = eos(bp); - if (!BSTRCMPI(bp, p - 8, "fountain")) { - lev->typ = FOUNTAIN; - level.flags.nfountains++; - if (!strncmpi(bp, "magic ", 6)) - lev->blessedftn = 1; - pline("A %sfountain.", lev->blessedftn ? "magic " : ""); - newsym(x, y); - return &zeroobj; - } - if (!BSTRCMPI(bp, p - 6, "throne")) { - lev->typ = THRONE; - pline("A throne."); - newsym(x, y); - return &zeroobj; - } - if (!BSTRCMPI(bp, p - 4, "sink")) { - lev->typ = SINK; - level.flags.nsinks++; - pline("A sink."); - newsym(x, y); - return &zeroobj; - } - /* ("water" matches "potion of water" rather than terrain) */ - if (!BSTRCMPI(bp, p - 4, "pool") || !BSTRCMPI(bp, p - 4, "moat")) { - lev->typ = !BSTRCMPI(bp, p - 4, "pool") ? POOL : MOAT; - del_engr_at(x, y); - pline("A %s.", (lev->typ == POOL) ? "pool" : "moat"); - /* Must manually make kelp! */ - water_damage_chain(level.objects[x][y], TRUE); - newsym(x, y); - return &zeroobj; - } - if (!BSTRCMPI(bp, p - 4, "lava")) { /* also matches "molten lava" */ - lev->typ = LAVAPOOL; - del_engr_at(x, y); - pline("A pool of molten lava."); - if (!(Levitation || Flying)) - (void) lava_effects(); - newsym(x, y); - return &zeroobj; - } - - if (!BSTRCMPI(bp, p - 5, "altar")) { - aligntyp al; - - lev->typ = ALTAR; - if (!strncmpi(bp, "chaotic ", 8)) - al = A_CHAOTIC; - else if (!strncmpi(bp, "neutral ", 8)) - al = A_NEUTRAL; - else if (!strncmpi(bp, "lawful ", 7)) - al = A_LAWFUL; - else if (!strncmpi(bp, "unaligned ", 10)) - al = A_NONE; - else /* -1 - A_CHAOTIC, 0 - A_NEUTRAL, 1 - A_LAWFUL */ - al = (!rn2(6)) ? A_NONE : rn2((int) A_LAWFUL + 2) - 1; - lev->altarmask = Align2amask(al); - pline("%s altar.", An(align_str(al))); - newsym(x, y); - return &zeroobj; - } - - if (!BSTRCMPI(bp, p - 5, "grave") - || !BSTRCMPI(bp, p - 9, "headstone")) { - make_grave(x, y, (char *) 0); - pline("%s.", IS_GRAVE(lev->typ) ? "A grave" - : "Can't place a grave here"); - newsym(x, y); - return &zeroobj; - } - - if (!BSTRCMPI(bp, p - 4, "tree")) { - lev->typ = TREE; - pline("A tree."); - newsym(x, y); - block_point(x, y); - return &zeroobj; - } - - if (!BSTRCMPI(bp, p - 4, "bars")) { - lev->typ = IRONBARS; - pline("Iron bars."); - newsym(x, y); - return &zeroobj; - } - } - - if (!oclass && !typ) { - if (!strncmpi(bp, "polearm", 7)) { - typ = rnd_otyp_by_wpnskill(P_POLEARMS); - goto typfnd; - } else if (!strncmpi(bp, "hammer", 6)) { - typ = rnd_otyp_by_wpnskill(P_HAMMER); - goto typfnd; - } - } - - if (!oclass) - return ((struct obj *) 0); -any: - if (!oclass) - oclass = wrpsym[rn2((int) sizeof(wrpsym))]; -typfnd: - if (typ) - oclass = objects[typ].oc_class; - - /* handle some objects that are only allowed in wizard mode */ - if (typ && !wizard) { - switch (typ) { - case AMULET_OF_YENDOR: - typ = FAKE_AMULET_OF_YENDOR; - break; - case CANDELABRUM_OF_INVOCATION: - typ = rnd_class(TALLOW_CANDLE, WAX_CANDLE); - break; - case BELL_OF_OPENING: - typ = BELL; - break; - case SPE_BOOK_OF_THE_DEAD: - typ = SPE_BLANK_PAPER; - break; - case MAGIC_LAMP: - typ = OIL_LAMP; - break; - default: - /* catch any other non-wishable objects (venom) */ - if (objects[typ].oc_nowish) - return (struct obj *) 0; - break; - } - } - - /* - * Create the object, then fine-tune it. - */ - otmp = typ ? mksobj(typ, TRUE, FALSE) : mkobj(oclass, FALSE); - typ = otmp->otyp, oclass = otmp->oclass; /* what we actually got */ - - if (islit && (typ == OIL_LAMP || typ == MAGIC_LAMP || typ == BRASS_LANTERN - || Is_candle(otmp) || typ == POT_OIL)) { - place_object(otmp, u.ux, u.uy); /* make it viable light source */ - begin_burn(otmp, FALSE); - obj_extract_self(otmp); /* now release it for caller's use */ - } - - /* if player specified a reasonable count, maybe honor it */ - if (cnt > 0 && objects[typ].oc_merge - && (wizard || cnt < rnd(6) || (cnt <= 7 && Is_candle(otmp)) - || (cnt <= 20 && ((oclass == WEAPON_CLASS && is_ammo(otmp)) - || typ == ROCK || is_missile(otmp))))) - otmp->quan = (long) cnt; - - if (oclass == VENOM_CLASS) - otmp->spe = 1; - - if (spesgn == 0) { - spe = otmp->spe; - } else if (wizard) { - ; /* no alteration to spe */ - } else if (oclass == ARMOR_CLASS || oclass == WEAPON_CLASS - || is_weptool(otmp) - || (oclass == RING_CLASS && objects[typ].oc_charged)) { - if (spe > rnd(5) && spe > otmp->spe) - spe = 0; - if (spe > 2 && Luck < 0) - spesgn = -1; - } else { - if (oclass == WAND_CLASS) { - if (spe > 1 && spesgn == -1) - spe = 1; - } else { - if (spe > 0 && spesgn == -1) - spe = 0; - } - if (spe > otmp->spe) - spe = otmp->spe; - } - - if (spesgn == -1) - spe = -spe; - - /* set otmp->spe. This may, or may not, use spe... */ - switch (typ) { - case TIN: - if (contents == EMPTY) { - otmp->corpsenm = NON_PM; - otmp->spe = 0; - } else if (contents == SPINACH) { - otmp->corpsenm = NON_PM; - otmp->spe = 1; - } - break; - case TOWEL: - if (wetness) - otmp->spe = wetness; - break; - case SLIME_MOLD: - otmp->spe = ftype; - /* Fall through */ - case SKELETON_KEY: - case CHEST: - case LARGE_BOX: - case HEAVY_IRON_BALL: - case IRON_CHAIN: - case STATUE: - /* otmp->cobj already done in mksobj() */ - break; -#ifdef MAIL - case SCR_MAIL: - /* 0: delivered in-game via external event (or randomly for fake mail); - 1: from bones or wishing; 2: written with marker */ - otmp->spe = 1; - break; -#endif - case WAN_WISHING: - if (!wizard) { - otmp->spe = (rn2(10) ? -1 : 0); - break; - } - /* fall through, if wizard */ - default: - otmp->spe = spe; - } - - /* set otmp->corpsenm or dragon scale [mail] */ - if (mntmp >= LOW_PM) { - if (mntmp == PM_LONG_WORM_TAIL) - mntmp = PM_LONG_WORM; - - switch (typ) { - case TIN: - otmp->spe = 0; /* No spinach */ - if (dead_species(mntmp, FALSE)) { - otmp->corpsenm = NON_PM; /* it's empty */ - } else if ((!(mons[mntmp].geno & G_UNIQ) || wizard) - && !(mvitals[mntmp].mvflags & G_NOCORPSE) - && mons[mntmp].cnutrit != 0) { - otmp->corpsenm = mntmp; - } - break; - case CORPSE: - if ((!(mons[mntmp].geno & G_UNIQ) || wizard) - && !(mvitals[mntmp].mvflags & G_NOCORPSE)) { - if (mons[mntmp].msound == MS_GUARDIAN) - mntmp = genus(mntmp, 1); - set_corpsenm(otmp, mntmp); - } - break; - case EGG: - mntmp = can_be_hatched(mntmp); - /* this also sets hatch timer if appropriate */ - set_corpsenm(otmp, mntmp); - break; - case FIGURINE: - if (!(mons[mntmp].geno & G_UNIQ) && !is_human(&mons[mntmp]) -#ifdef MAIL - && mntmp != PM_MAIL_DAEMON -#endif - ) - otmp->corpsenm = mntmp; - break; - case STATUE: - otmp->corpsenm = mntmp; - if (Has_contents(otmp) && verysmall(&mons[mntmp])) - delete_contents(otmp); /* no spellbook */ - otmp->spe = ishistoric ? STATUE_HISTORIC : 0; - break; - case SCALE_MAIL: - /* Dragon mail - depends on the order of objects & dragons. */ - if (mntmp >= PM_GRAY_DRAGON && mntmp <= PM_YELLOW_DRAGON) - otmp->otyp = GRAY_DRAGON_SCALE_MAIL + mntmp - PM_GRAY_DRAGON; - break; - } - } - - /* set blessed/cursed -- setting the fields directly is safe - * since weight() is called below and addinv() will take care - * of luck */ - if (iscursed) { - curse(otmp); - } else if (uncursed) { - otmp->blessed = 0; - otmp->cursed = (Luck < 0 && !wizard); - } else if (blessed) { - otmp->blessed = (Luck >= 0 || wizard); - otmp->cursed = (Luck < 0 && !wizard); - } else if (spesgn < 0) { - curse(otmp); - } - - /* set eroded and erodeproof */ - if (erosion_matters(otmp)) { - if (eroded && (is_flammable(otmp) || is_rustprone(otmp))) - otmp->oeroded = eroded; - if (eroded2 && (is_corrodeable(otmp) || is_rottable(otmp))) - otmp->oeroded2 = eroded2; - /* - * 3.6.1: earlier versions included `&& !eroded && !eroded2' here, - * but damageproof combined with damaged is feasible (eroded - * armor modified by confused reading of cursed destroy armor) - * so don't prevent player from wishing for such a combination. - */ - if (erodeproof && (is_damageable(otmp) || otmp->otyp == CRYSKNIFE)) - otmp->oerodeproof = (Luck >= 0 || wizard); - } - - /* set otmp->recharged */ - if (oclass == WAND_CLASS) { - /* prevent wishing abuse */ - if (otmp->otyp == WAN_WISHING && !wizard) - rechrg = 1; - otmp->recharged = (unsigned) rechrg; - } - - /* set poisoned */ - if (ispoisoned) { - if (is_poisonable(otmp)) - otmp->opoisoned = (Luck >= 0); - else if (oclass == FOOD_CLASS) - /* try to taint by making it as old as possible */ - otmp->age = 1L; - } - /* and [un]trapped */ - if (trapped) { - if (Is_box(otmp) || typ == TIN) - otmp->otrapped = (trapped == 1); - } - - if (isgreased) - otmp->greased = 1; - - if (isdiluted && otmp->oclass == POTION_CLASS && otmp->otyp != POT_WATER) - otmp->odiluted = 1; - - /* set tin variety */ - if (otmp->otyp == TIN && tvariety >= 0 && (rn2(4) || wizard)) - set_tin_variety(otmp, tvariety); - - if (name) { - const char *aname; - short objtyp; - - /* an artifact name might need capitalization fixing */ - aname = artifact_name(name, &objtyp); - if (aname && objtyp == otmp->otyp) - name = aname; - - /* 3.6 tribute - fix up novel */ - if (otmp->otyp == SPE_NOVEL) { - const char *novelname; - - novelname = lookup_novel(name, &otmp->novelidx); - if (novelname) - name = novelname; - } - - otmp = oname(otmp, name); - /* name==aname => wished for artifact (otmp->oartifact => got it) */ - if (otmp->oartifact || name == aname) { - otmp->quan = 1L; - u.uconduct.wisharti++; /* KMH, conduct */ - } - } - - /* more wishing abuse: don't allow wishing for certain artifacts */ - /* and make them pay; charge them for the wish anyway! */ - if ((is_quest_artifact(otmp) - || (otmp->oartifact && rn2(nartifact_exist()) > 1)) && !wizard) { - artifact_exists(otmp, safe_oname(otmp), FALSE); - obfree(otmp, (struct obj *) 0); - otmp = &zeroobj; - pline("For a moment, you feel %s in your %s, but it disappears!", - something, makeplural(body_part(HAND))); - } - - if (halfeaten && otmp->oclass == FOOD_CLASS) { - if (otmp->otyp == CORPSE) - otmp->oeaten = mons[otmp->corpsenm].cnutrit; - else - otmp->oeaten = objects[otmp->otyp].oc_nutrition; - /* (do this adjustment before setting up object's weight) */ - consume_oeaten(otmp, 1); - } - otmp->owt = weight(otmp); - if (very && otmp->otyp == HEAVY_IRON_BALL) - otmp->owt += IRON_BALL_W_INCR; - else if (gsize > 1 && otmp->globby) - /* 0: unspecified => small; 1: small => keep default owt of 20; - 2: medium => 120; 3: large => 320; 4: very large => 520 */ - otmp->owt += 100 + (gsize - 2) * 200; - - return otmp; -} - -int -rnd_class(first, last) -int first, last; -{ - int i, x, sum = 0; - - if (first == last) - return first; - for (i = first; i <= last; i++) - sum += objects[i].oc_prob; - if (!sum) /* all zero */ - return first + rn2(last - first + 1); - x = rnd(sum); - for (i = first; i <= last; i++) - if (objects[i].oc_prob && (x -= objects[i].oc_prob) <= 0) - return i; - return 0; -} - -STATIC_OVL const char * -Japanese_item_name(i) -int i; -{ - struct Jitem *j = Japanese_items; - - while (j->item) { - if (i == j->item) - return j->name; - j++; - } - return (const char *) 0; -} - -const char * -suit_simple_name(suit) -struct obj *suit; -{ - const char *suitnm, *esuitp; - - if (Is_dragon_mail(suit)) - return "dragon mail"; /* dragon scale mail */ - else if (Is_dragon_scales(suit)) - return "dragon scales"; - suitnm = OBJ_NAME(objects[suit->otyp]); - esuitp = eos((char *) suitnm); - if (strlen(suitnm) > 5 && !strcmp(esuitp - 5, " mail")) - return "mail"; /* most suits fall into this category */ - else if (strlen(suitnm) > 7 && !strcmp(esuitp - 7, " jacket")) - return "jacket"; /* leather jacket */ - /* suit is lame but armor is ambiguous and body armor is absurd */ - return "suit"; -} - -const char * -cloak_simple_name(cloak) -struct obj *cloak; -{ - if (cloak) { - switch (cloak->otyp) { - case ROBE: - return "robe"; - case MUMMY_WRAPPING: - return "wrapping"; - case ALCHEMY_SMOCK: - return (objects[cloak->otyp].oc_name_known && cloak->dknown) - ? "smock" - : "apron"; - default: - break; - } - } - return "cloak"; -} - -/* helm vs hat for messages */ -const char * -helm_simple_name(helmet) -struct obj *helmet; -{ - /* - * There is some wiggle room here; the result has been chosen - * for consistency with the "protected by hard helmet" messages - * given for various bonks on the head: headgear that provides - * such protection is a "helm", that which doesn't is a "hat". - * - * elven leather helm / leather hat -> hat - * dwarvish iron helm / hard hat -> helm - * The rest are completely straightforward: - * fedora, cornuthaum, dunce cap -> hat - * all other types of helmets -> helm - */ - return (helmet && !is_metallic(helmet)) ? "hat" : "helm"; -} - -const char * -mimic_obj_name(mtmp) -struct monst *mtmp; -{ - if (mtmp->m_ap_type == M_AP_OBJECT) { - if (mtmp->mappearance == GOLD_PIECE) - return "gold"; - if (mtmp->mappearance != STRANGE_OBJECT) - return simple_typename(mtmp->mappearance); - } - return "whatcha-may-callit"; -} - -/* - * Construct a query prompt string, based around an object name, which is - * guaranteed to fit within [QBUFSZ]. Takes an optional prefix, three - * choices for filling in the middle (two object formatting functions and a - * last resort literal which should be very short), and an optional suffix. - */ -char * -safe_qbuf(qbuf, qprefix, qsuffix, obj, func, altfunc, lastR) -char *qbuf; /* output buffer */ -const char *qprefix, *qsuffix; -struct obj *obj; -char *FDECL((*func), (OBJ_P)), *FDECL((*altfunc), (OBJ_P)); -const char *lastR; -{ - char *bufp, *endp; - /* convert size_t (or int for ancient systems) to ordinary unsigned */ - unsigned len, lenlimit, - len_qpfx = (unsigned) (qprefix ? strlen(qprefix) : 0), - len_qsfx = (unsigned) (qsuffix ? strlen(qsuffix) : 0), - len_lastR = (unsigned) strlen(lastR); - - lenlimit = QBUFSZ - 1; - endp = qbuf + lenlimit; - /* sanity check, aimed mainly at paniclog (it's conceivable for - the result of short_oname() to be shorter than the length of - the last resort string, but we ignore that possibility here) */ - if (len_qpfx > lenlimit) - impossible("safe_qbuf: prefix too long (%u characters).", len_qpfx); - else if (len_qpfx + len_qsfx > lenlimit) - impossible("safe_qbuf: suffix too long (%u + %u characters).", - len_qpfx, len_qsfx); - else if (len_qpfx + len_lastR + len_qsfx > lenlimit) - impossible("safe_qbuf: filler too long (%u + %u + %u characters).", - len_qpfx, len_lastR, len_qsfx); - - /* the output buffer might be the same as the prefix if caller - has already partially filled it */ - if (qbuf == qprefix) { - /* prefix is already in the buffer */ - *endp = '\0'; - } else if (qprefix) { - /* put prefix into the buffer */ - (void) strncpy(qbuf, qprefix, lenlimit); - *endp = '\0'; - } else { - /* no prefix; output buffer starts out empty */ - qbuf[0] = '\0'; - } - len = (unsigned) strlen(qbuf); - - if (len + len_lastR + len_qsfx > lenlimit) { - /* too long; skip formatting, last resort output is truncated */ - if (len < lenlimit) { - (void) strncpy(&qbuf[len], lastR, lenlimit - len); - *endp = '\0'; - len = (unsigned) strlen(qbuf); - if (qsuffix && len < lenlimit) { - (void) strncpy(&qbuf[len], qsuffix, lenlimit - len); - *endp = '\0'; - /* len = (unsigned) strlen(qbuf); */ - } - } - } else { - /* suffix and last resort are guaranteed to fit */ - len += len_qsfx; /* include the pending suffix */ - /* format the object */ - bufp = short_oname(obj, func, altfunc, lenlimit - len); - if (len + strlen(bufp) <= lenlimit) - Strcat(qbuf, bufp); /* formatted name fits */ - else - Strcat(qbuf, lastR); /* use last resort */ - releaseobuf(bufp); - - if (qsuffix) - Strcat(qbuf, qsuffix); - } - /* assert( strlen(qbuf) < QBUFSZ ); */ - return qbuf; -} - -/*objnam.c*/ From 5b03dccb196d418919a350e8415ab1ea9221a072 Mon Sep 17 00:00:00 2001 From: nhmall Date: Sun, 18 Mar 2018 10:29:45 -0400 Subject: [PATCH 23/31] missed one, transcription error --- src/objnam.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/objnam.c b/src/objnam.c index 95d67edd4..d453d564e 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -2481,8 +2481,8 @@ boolean to_plural; /* true => makeplural, false => makesingular */ /* these are all the prefixes for *men that don't have a *man singular */ const char *no_man[] = { "abdo", "acu", "agno", "ceru", "cogno", "cycla", "fleh", "grava", - "hegu", "preno", "sonar", "dai", "exa", "fla", "sta", "teg", "tegu", - "vela", "da", "hy", "lu", "no", "nu", "ra", "ru", "se", "vi", "ya", + "hegu", "preno", "sonar", "speci", "dai", "exa", "fla", "sta", "teg", + "tegu", "vela", "da", "hy", "lu", "no", "nu", "ra", "ru", "se", "vi", "ya", "o", "a", }; From 60454b4f92a1f90ec6752a5e36e2f789c3969b25 Mon Sep 17 00:00:00 2001 From: Alex Kompel Date: Sun, 18 Mar 2018 12:52:30 -0700 Subject: [PATCH 24/31] win32-gui: Do not auto-assign non-alphabet accelerator characters to menu items. Picking up pile that contains gold forces accelerators to start with $ so the next one becomes %, ... --- win/win32/mhmenu.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/win/win32/mhmenu.c b/win/win32/mhmenu.c index 6bdea0a2f..b541af305 100644 --- a/win/win32/mhmenu.c +++ b/win/win32/mhmenu.c @@ -160,10 +160,11 @@ mswin_menu_window_select_menu(HWND hWnd, int how, MENU_ITEM_P **_selected, ap = data->menu.gacc; for (i = 0; i < data->menu.size; i++) { if (data->menu.items[i].accelerator != 0) { - next_char = (char) (data->menu.items[i].accelerator + 1); + if (isalpha(data->menu.items[i].accelerator)) { + next_char = (char)(data->menu.items[i].accelerator + 1); + } } else if (NHMENU_IS_SELECTABLE(data->menu.items[i])) { - if ((next_char >= 'a' && next_char <= 'z') - || (next_char >= 'A' && next_char <= 'Z')) { + if (isalpha(next_char)) { data->menu.items[i].accelerator = next_char; } else { if (next_char > 'z') From d6e43a32ecaff6119b975e6be73f1e5f542e6e40 Mon Sep 17 00:00:00 2001 From: Alex Kompel Date: Sun, 18 Mar 2018 21:57:38 -0700 Subject: [PATCH 25/31] win32-gui: fix truncated status fields call to get dimensions of the text bounding rectangle needs to be made after the font is set in order to get the accurate reading --- win/win32/mhstatus.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/win/win32/mhstatus.c b/win/win32/mhstatus.c index a362c60bd..52d24621a 100644 --- a/win/win32/mhstatus.c +++ b/win/win32/mhstatus.c @@ -2,6 +2,7 @@ /* Copyright (C) 2001 by Alex Kompel */ /* NetHack may be freely redistributed. See license for details. */ +#include #include "winMS.h" #include "mhstatus.h" #include "mhmsg.h" @@ -352,16 +353,21 @@ onWMPaint(HWND hWnd, WPARAM wParam, LPARAM lParam) : status_fg_color)); nBg = status_bg_color; - GetTextExtentPoint32(hdc, wbuf, vlen, &sz); + sz.cy = -1; if (*f == BL_TITLE && iflags.wc2_hitpointbar) { HBRUSH back_brush = CreateSolidBrush(nhcolor_to_RGB(hpbar_color)); RECT barrect; - /* first draw title normally */ + /* prepare for drawing */ SelectObject(hdc, fnt); SetBkMode(hdc, OPAQUE); SetBkColor(hdc, status_bg_color); SetTextColor(hdc, nhcolor_to_RGB(hpbar_color)); + + /* get bounding rectangle */ + GetTextExtentPoint32(hdc, wbuf, vlen, &sz); + + /* first draw title normally */ DrawText(hdc, wbuf, vlen, &rt, DT_LEFT); /* calc bar length */ @@ -386,12 +392,20 @@ onWMPaint(HWND hWnd, WPARAM wParam, LPARAM lParam) nFg = nBg; nBg = tmp; } + + /* prepare for drawing */ SelectObject(hdc, fnt); SetBkMode(hdc, OPAQUE); SetBkColor(hdc, nBg); SetTextColor(hdc, nFg); + + /* get bounding rectangle */ + GetTextExtentPoint32(hdc, wbuf, vlen, &sz); + + /* draw */ DrawText(hdc, wbuf, vlen, &rt, DT_LEFT); } + assert(sz.cy >= 0); rt.left += sz.cx; cy = max(cy, sz.cy); From b85b7b93f08e8e89739b60faafd2427b577430fc Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Mon, 19 Mar 2018 08:22:14 +0200 Subject: [PATCH 26/31] Typofix --- src/rip.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rip.c b/src/rip.c index b8ef7f594..ce3c8d6e7 100644 --- a/src/rip.c +++ b/src/rip.c @@ -143,7 +143,7 @@ time_t when; #ifdef DUMPLOG if (tmpwin == 0) - dump_forward_putstr(0, 0, "Gave over:", TRUE); + dump_forward_putstr(0, 0, "Game over:", TRUE); else #endif putstr(tmpwin, 0, ""); From 7238803b2509cd8d7b7a316b1c1c214c0a0a2b92 Mon Sep 17 00:00:00 2001 From: nhmall Date: Mon, 19 Mar 2018 07:13:07 -0400 Subject: [PATCH 27/31] revert box naming --- doc/fixes36.1 | 2 -- src/lock.c | 15 +++------------ src/objnam.c | 8 ++++---- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index fc7fbaa95..73c66e65d 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -609,8 +609,6 @@ when clairvoyance lets you move the cursor to examine the map (if it occurs prevent Mjollnir from being auto-quivered if it's been thrown without return and then picked back up while quiver slot is empty plural of "fox" is not "foxen" -change wording from "broken chest" to "chest with a broken lock" and during - #force" suppress redundant info when lock state is already known Platform- and/or Interface-Specific Fixes diff --git a/src/lock.c b/src/lock.c index 1e3e43d06..996784ca7 100644 --- a/src/lock.c +++ b/src/lock.c @@ -548,18 +548,9 @@ doforce() xlock.box = (struct obj *) 0; for (otmp = level.objects[u.ux][u.uy]; otmp; otmp = otmp->nexthere) if (Is_box(otmp)) { - if (otmp->obroken) { - There("is %s here%s.", doname(otmp), - /* The displayed name will have already stated - * "with a broken lock" if otmp->lknown is already set - * so suppress the additional notification about the - * lock in that case. */ - !otmp->lknown ? ", but its lock is already broken" : ""); - otmp->lknown = 1; - continue; - } else if (!otmp->olocked) { - There("is %s here, but its lock is already unlocked.", - doname(otmp)); + if (otmp->obroken || !otmp->olocked) { + There("is %s here, but its lock is already %s.", doname(otmp), + otmp->obroken ? "broken" : "unlocked"); otmp->lknown = 1; continue; } diff --git a/src/objnam.c b/src/objnam.c index d453d564e..120aaef55 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -995,10 +995,10 @@ unsigned doname_flags; if (lknown && Is_box(obj)) { if (obj->obroken) - /* 3.6.0 used an "unlockable" prefix here but that could be - misunderstood to mean "capable of being unlocked" rather - than the intended "not capable of being locked" */ - Strcat(bp, " with a broken lock"); + /* 3.6.0 used "unlockable" here but that could be misunderstood + to mean "capable of being unlocked" rather than the intended + "not capable of being locked" */ + Strcat(prefix, "broken "); else if (obj->olocked) Strcat(prefix, "locked "); else From 0419f097f12171b04b6666b0cef7dc217a46c711 Mon Sep 17 00:00:00 2001 From: PatR Date: Mon, 19 Mar 2018 15:48:46 -0700 Subject: [PATCH 28/31] fix #H6960 - redundant feedback for '#force' When using #force at a spot which has a broken or unlocked chest (or large box) whose lock state has been previously discovered, avoid |There is a broken chest here, but its lock is already broken. |There is an unlocked chest here, but its lock is already unlocked. by suppressing "broken"/"unlocked" from the chest description for that particular message. We might still want to change "broken chest" to "damaged chest" but I don't think there should be any reference to its lock as the reason it's broken or damaged. The fact that #loot, #force, and applying a key still treat it as a container is sufficient to reveal that it functions as one. --- doc/fixes36.1 | 3 +++ src/lock.c | 11 ++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index 73c66e65d..5fb21cb77 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -527,6 +527,9 @@ fix 'object lost' panic if hero with lycanthropy but in human form is wielding prevent segfault if pline() is called recursively (which could happen if the interface code issues a debugpline() while processing putstr()) open at yourself is the same as #loot +when #force reports that a chest's lock is already broken or already unlocked, + force it to be described as "a chest", even when its lock state is + already known, rather than as "a broken chest" or "an unlocked chest" Fixes to Post-3.6.0 Problems that Were Exposed Via git Repository diff --git a/src/lock.c b/src/lock.c index 996784ca7..9de1e29c5 100644 --- a/src/lock.c +++ b/src/lock.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 lock.c $NHDT-Date: 1521377334 2018/03/18 12:48:54 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.78 $ */ +/* NetHack 3.6 lock.c $NHDT-Date: 1521499715 2018/03/19 22:48:35 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.80 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -549,8 +549,13 @@ doforce() for (otmp = level.objects[u.ux][u.uy]; otmp; otmp = otmp->nexthere) if (Is_box(otmp)) { if (otmp->obroken || !otmp->olocked) { - There("is %s here, but its lock is already %s.", doname(otmp), - otmp->obroken ? "broken" : "unlocked"); + /* force doname() to omit known "broken" or "unlocked" + prefix so that the message isn't worded redundantly; + since we're about to set lknown, there's no need to + remember and then reset its current value */ + otmp->lknown = 0; + There("is %s here, but its lock is already %s.", + doname(otmp), otmp->obroken ? "broken" : "unlocked"); otmp->lknown = 1; continue; } From 906818f5cbab69438778cf2f101eb539927ebb0a Mon Sep 17 00:00:00 2001 From: PatR Date: Mon, 19 Mar 2018 17:59:24 -0700 Subject: [PATCH 29/31] wishing for containers Noticed while investigating the broken chest whose lock was already broken: wishing for locked, unlocked, or broken chest (or large box) was treated as asking for something unknown. Add support for those three prefixes, although they only have meaning for chest and box. If more that one is specified in the same wish, whichever one comes last overrides the others. Also, "empty" was already an accepted prefix (for tins); honor it for containers too. Lastly, wishing for "box" failed. Give a large box instead. I went back and forth about whether to do the same for "small box" and ended up not including it, but turns out that small/medium/large prefix for globs ends up making "small box" and "medium box" match "box" which has now become a synonym for "large box". I'm not sure whether that is a bonus or a bug; small box is clearly not the same thing as large box, but getting the only available box when asking for any box seems better than claiming not to understand the request. --- doc/fixes36.1 | 2 ++ src/objnam.c | 48 ++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index 5fb21cb77..f48947ce9 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -530,6 +530,8 @@ open at yourself is the same as #loot when #force reports that a chest's lock is already broken or already unlocked, force it to be described as "a chest", even when its lock state is already known, rather than as "a broken chest" or "an unlocked chest" +honor wish for "locked", "unlocked", or "broken" chest or box +honor wish for "empty" container including statue, bag-o-tricks, horn-o-plenty Fixes to Post-3.6.0 Problems that Were Exposed Via git Repository diff --git a/src/objnam.c b/src/objnam.c index 120aaef55..5eb969e42 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 objnam.c $NHDT-Date: 1521377345 2018/03/18 12:49:05 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.194 $ */ +/* NetHack 3.6 objnam.c $NHDT-Date: 1521507553 2018/03/20 00:59:13 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.199 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -2670,6 +2670,8 @@ struct alt_spellings { { "grapnel", GRAPPLING_HOOK }, { "grapple", GRAPPLING_HOOK }, { "protection from shape shifters", RIN_PROTECTION_FROM_SHAPE_CHAN }, + /* if we ever add other sizes, move this to o_ranges[] with "bag" */ + { "box", LARGE_BOX }, /* normally we wouldn't have to worry about unnecessary , but " stone" will get stripped off, preventing a wishymatch; that actually lets "flint stone" be a match, so we also accept bogus "flintstone" */ @@ -2763,7 +2765,7 @@ struct obj *no_wish; register struct obj *otmp; int cnt, spe, spesgn, typ, very, rechrg; int blessed, uncursed, iscursed, ispoisoned, isgreased; - int eroded, eroded2, erodeproof; + int eroded, eroded2, erodeproof, locked, unlocked, broken; int halfeaten, mntmp, contents; int islit, unlabeled, ishistoric, isdiluted, trapped; int tmp, tinv, tvariety; @@ -2789,9 +2791,11 @@ struct obj *no_wish; char *un, *dn, *actualn, *origbp = bp; const char *name = 0; - cnt = spe = spesgn = typ = very = rechrg = blessed = uncursed = iscursed = - ispoisoned = isgreased = eroded = eroded2 = erodeproof = halfeaten = - islit = unlabeled = ishistoric = isdiluted = trapped = 0; + cnt = spe = spesgn = typ = 0; + very = rechrg = blessed = uncursed = iscursed = ispoisoned = + isgreased = eroded = eroded2 = erodeproof = halfeaten = + islit = unlabeled = ishistoric = isdiluted = trapped = + locked = unlocked = broken = 0; tvariety = RANDOM_TIN; mntmp = NON_PM; #define UNDEFINED 0 @@ -2879,6 +2883,13 @@ struct obj *no_wish; trapped = 1; } else if (!strncmpi(bp, "untrapped ", l = 10)) { trapped = 2; /* not trapped */ + /* locked, unlocked, broken: box/chest lock states */ + } else if (!strncmpi(bp, "locked ", l = 7)) { + locked = 1, unlocked = broken = 0; + } else if (!strncmpi(bp, "unlocked ", l = 9)) { + unlocked = 1, locked = broken = 0; + } else if (!strncmpi(bp, "broken ", l = 7)) { + broken = 1, locked = unlocked = 0; } else if (!strncmpi(bp, "greased ", l = 8)) { isgreased = 1; } else if (!strncmpi(bp, "very ", l = 5)) { @@ -3284,7 +3295,10 @@ retry: ; /* avoid false hit on "* glass" */ } else if (!BSTRCMPI(bp, p - 6, " glass") || !strcmpi(bp, "glass")) { register char *g = bp; - if (strstri(g, "broken")) + + /* treat "broken glass" as a non-existent item; since "broken" is + also a chest/box prefix it might have been stripped off above */ + if (broken || strstri(g, "broken")) return (struct obj *) 0; if (!strncmpi(g, "worthless ", 10)) g += 10; @@ -3804,6 +3818,28 @@ typfnd: if (Is_box(otmp) || typ == TIN) otmp->otrapped = (trapped == 1); } + /* empty for containers rather than for tins */ + if (contents == EMPTY) { + if (otmp->otyp == BAG_OF_TRICKS || otmp->otyp == HORN_OF_PLENTY) { + if (otmp->spe > 0) + otmp->spe = 0; + } else if (Has_contents(otmp)) { + /* this assumes that artifacts can't be randomly generated + inside containers */ + delete_contents(otmp); + otmp->owt = weight(otmp); + } + } + /* set locked/unlocked/broken */ + if (Is_box(otmp)) { + if (locked) { + otmp->olocked = 1, otmp->obroken = 0; + } else if (unlocked) { + otmp->olocked = 0, otmp->obroken = 0; + } else if (broken) { + otmp->olocked = 0, otmp->obroken = 1; + } + } if (isgreased) otmp->greased = 1; From 2ee6412a2fb645d0fd1a2d47dfd5dc6c30f1c5c7 Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Tue, 20 Mar 2018 17:34:49 +0200 Subject: [PATCH 30/31] Fix X11 segfault when quitting without dumplogfile defined Guard dumplog-specific output by checking we actually are putting the output into the dumplog. This is a post-3.6.0 bug. --- src/end.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/end.c b/src/end.c index 427f4501c..322ab4636 100644 --- a/src/end.c +++ b/src/end.c @@ -1301,7 +1301,8 @@ int how; for normal end of game, genocide doesn't either */ if (how <= GENOCIDED) { dump_redirect(TRUE); - genl_outrip(0, how, endtime); + if (iflags.in_dumplog) + genl_outrip(0, how, endtime); dump_redirect(FALSE); } #endif @@ -1382,7 +1383,8 @@ int how; artifact_score(invent, FALSE, endwin); /* list artifacts */ #ifdef DUMPLOG dump_redirect(TRUE); - artifact_score(invent, FALSE, 0); + if (iflags.in_dumplog) + artifact_score(invent, FALSE, 0); dump_redirect(FALSE); #endif From 66e50aeeac8cd9e484af40f881afbac82a9690fd Mon Sep 17 00:00:00 2001 From: PatR Date: Wed, 21 Mar 2018 19:12:51 -0700 Subject: [PATCH 31/31] Cleaver tidbit Fix a comment typo. While in there, change the cleave attack to swing counter-clockwise the very first time instead of setting up for that but then toggling to the opposite direction before the actual attack. Also, refactor a bit of common code for choosing < xdir[], ydir[] > index for next target. --- src/uhitm.c | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/uhitm.c b/src/uhitm.c index bfc103e0a..8f9e3a0b9 100644 --- a/src/uhitm.c +++ b/src/uhitm.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 uhitm.c $NHDT-Date: 1520043553 2018/03/03 02:19:13 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.175 $ */ +/* NetHack 3.6 uhitm.c $NHDT-Date: 1521684760 2018/03/22 02:12:40 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.176 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -513,14 +513,10 @@ struct attack *uattk; /* ... but we don't enforce that here; Null works ok */ u.dx, u.dy, u.dz); return TRUE; /* target hasn't been killed */ } - clockwise = !clockwise; /* alternate */ /* adjust direction by two so that loop's increment (for clockwise) or decrement (for counter-clockwise) will point at the spot next to primary target */ - if (clockwise) - i = (i + 6) % 8; - else - i = (i + 2) % 8; + i = (i + (clockwise ? 6 : 2)) % 8; umort = u.umortality; /* used to detect life-saving */ /* @@ -535,10 +531,8 @@ struct attack *uattk; /* ... but we don't enforce that here; Null works ok */ struct monst *mtmp; int tx, ty, tmp, dieroll, mhit, attknum, armorpenalty; - if (clockwise) - i = (i + 1) % 8; /* ++i, wrap 8 to i=0 */ - else - i = (i + 7) % 8; /* --i, wrap -1 to i=7 */ + /* ++i, wrap 8 to i=0 /or/ --i, wrap -1 to i=7 */ + i = (i + (clockwise ? 1 : 7)) % 8; tx = x + xdir[i], ty = y + ydir[i]; /* current target location */ if (!isok(tx, ty)) @@ -563,6 +557,8 @@ struct attack *uattk; /* ... but we don't enforce that here; Null works ok */ if (!uwep || u.umortality > umort) break; } + /* set up for next time */ + clockwise = !clockwise; /* alternate */ /* return False if primary target died, True otherwise; note: if 'target' was nonNull upon entry then it's still nonNull even if *target died */ @@ -583,8 +579,8 @@ struct attack *uattk; int dieroll = rnd(20); int mhit = (tmp > dieroll || u.uswallow); - /* Cleaver attacks three spots, one on either side of 'mon'; - it can't we part of dual-wielding but we guard against that anyway; + /* Cleaver attacks three spots, 'mon' and one on either side of 'mon'; + it can't be part of dual-wielding but we guard against that anyway; cleave return value reflects status of primary target ('mon') */ if (uwep && uwep->oartifact == ART_CLEAVER && !u.twoweap && !u.uswallow && !u.ustuck && !NODIAG(u.umonnum))