From 84ddf85cb4a0039f72c974a2644375ec89188d72 Mon Sep 17 00:00:00 2001 From: PatR Date: Thu, 16 Apr 2026 13:35:08 -0700 Subject: [PATCH] new composite status conditions: weaponstatus,\ armorstatus, and terrainstatus This adds three special status items to show at a glance what the hero is wielding, wearing, and standing on. Each of the three items has its own boolean option rather than try to fix them in with the existing opttional status conditions. After a lot of testing, I think the weapon and armor ones will prove useful but the terrain one probably won't be. Presently it is implemented for tty and curses. When I developed it six years ago, it was also working for X11 but I'm not able to test the resurrection of that part so have left it out. --- dat/opthelp | 3 + doc/Guidebook.mn | 42 +++++- doc/Guidebook.tex | 42 +++++- include/botl.h | 32 +++-- include/extern.h | 3 + include/flag.h | 4 + include/optlist.h | 9 ++ include/rm.h | 13 ++ include/winprocs.h | 3 + src/botl.c | 290 +++++++++++++++++++++++++++++++++++++++--- src/dungeon.c | 12 +- src/hack.c | 118 ++++++++++++++++- src/options.c | 21 ++- src/u_init.c | 2 +- src/uhitm.c | 2 +- src/wield.c | 17 ++- src/worn.c | 8 ++ win/curses/cursmain.c | 4 +- win/curses/cursstat.c | 75 +++++++++-- win/tty/wintty.c | 31 +++-- 20 files changed, 659 insertions(+), 72 deletions(-) diff --git a/dat/opthelp b/dat/opthelp index 68eb53215..2decd609c 100644 --- a/dat/opthelp +++ b/dat/opthelp @@ -3,6 +3,7 @@ Boolean options not under specific compile flags (with default values in []): option setting, which is reached via the 'O' command.) acoustics can your character hear anything [True] +armorstatus show extra status field summarizing worn armor [False] autodescribe describe the terrain under cursor [False] autodig dig if moving and wielding digging tool [False] autoopen walking into a door attempts to open it [True] @@ -73,6 +74,7 @@ sparkle display sparkly effect for resisted magical [True] attacks (e.g. fire attack on fire-resistant monster) standout use standout mode for --More-- on messages [False] status_updates update the status lines [True] +terrainstatus show extra status field describing current location [False] time display elapsed game time, in moves [False] tips show some helpful tips during gameplay [True] tombstone print tombstone when you die [True] @@ -83,6 +85,7 @@ travel enables travelling via mouse click if supported; [True] use_darkgray use bold black instead of blue for black glyphs. [True] use_inverse display detected monsters in highlighted manner [False] verbose print more commentary during the game [True] +weaponstatus show extra status field listing wielded weapon(s) [False] whatis_menu show menu when getting a map location [False] whatis_moveskip skip same glyphs when getting a map location [False] diff --git a/doc/Guidebook.mn b/doc/Guidebook.mn index aec4cbd6f..09fce691f 100644 --- a/doc/Guidebook.mn +++ b/doc/Guidebook.mn @@ -2724,6 +2724,12 @@ Some of the more obscure weapons (such as the \fIaklys\fP, \fIlucern hammer\fP, and \fIbec-de-corbin\fP) are defined in an appendix to \fIUnearthed Arcana\fP, an AD&D supplement. .pg +Some interfaces support the +.op weaponstatus +option. +When it is enabled, an extra status condition is displayed, describing +the currently wielded weapon. +.pg The commands to use weapons are \(oqw\(cq (wield), \(oqt\(cq (throw), \(oqf\(cq (fire), \(oqQ\(cq (quiver), \(oqx\(cq (exchange), \(oqX\(cq (twoweapon), and \(lq#enhance\(rq @@ -2956,6 +2962,12 @@ option can be set (prior to game start) to attempt to play the entire game without wearing any armor (a self-imposed challenge which is extremely difficult to accomplish). .pg +Some interfaces support the +.op armorstatus +option. +When it is enabled, an extra status condition is displayed, summarizing +currently worn armor. +.pg The commands to use armor are \(oqW\(cq (wear) and \(oqT\(cq (take off). The \(oqA\(cq command can be used to take off armor as well as other worn items. @@ -3734,7 +3746,7 @@ Otherwise the last section extends to the end of the options file. Highlight menu lines with different colors. See the \(lqConfiguring Menu Colors\(rq section. .lp MSGTYPE -Change the way messages are shown in the top status line. +Change the way messages are shown in the top line. See the \(lqConfiguring Message Types\(rq section. .lp ROGUESYMBOLS Custom symbols for the rogue level's symbol set. @@ -3883,6 +3895,21 @@ If \f(CRalign\fP is not specified, there is no default value; player will be prompted unless role and/or race forces a choice for alignment. Cannot be set with the \(oq\f(CRO\fP\(cq command. Persistent. +.lp armorstatus +Display an extra status condition which summarizes currently worn armor +(default off, not supported by all interfaces). +.lp "" +For the usual case where more than one piece of armor is worn, a list of +letters is shown in the following order: +.sd +\f(CRG\fP - gloves; +\f(CRC\fP - cloak; +\f(CRA\fP - suit; +\f(CRU\fP - shirt; +\f(CRH\fP - helmet; +\f(CRB\fP - boots; +\f(CRS\fP - shield. +.ed .lp autodescribe Automatically describe the terrain under cursor when asked to get a location on the map (default true). @@ -4877,6 +4904,9 @@ and prior versions (for example \(lqsuppress_alert:3.3.1\(rq). This option may be used to select one of the named symbol sets found within \(lqsymbols\(rq to alter the symbols displayed on the screen. Use \(lqsymset:default\(rq to explicitly select the default symbols. +.lp terrainstatus +Display an extra status condition describing the spot beneath the hero's +feet (default off, not supported by all interfaces). .lp "time " Show the elapsed game time in turns on bottom line (default off). Persistent. @@ -4913,6 +4943,16 @@ Play a tutorial level at the start of the game. Setting this option on or off in the config file will skip the query. .lp "verbose " Provide more commentary during the game (default on). Persistent. +.lp weaponstatus +Display an extra status condition which describes currently wielded weapon +(default off, not supported by all interfaces). +.lp "" +Some possible displayed values are: +.sd +\f(CRbare-hnds\fP - no weapon and no gloves; +\f(CRempty-hnd\fP - no weapon but gloves are worn; +\f(CRdual-weps\fP - wielding two weapons. +.ed .lp whatis_coord When using the \(oq/\(cq or \(oq;\(cq commands to look around on the map with .op autodescribe diff --git a/doc/Guidebook.tex b/doc/Guidebook.tex index 8051d8117..cf4a9c687 100644 --- a/doc/Guidebook.tex +++ b/doc/Guidebook.tex @@ -2933,6 +2933,11 @@ weapon which existed in AD\&D does roughly the same damage to monsters in \textit{aklys}, \textit{lucern hammer}, and \textit{bec-de-corbin}) are defined in an appendix to \textit{Unearthed Arcana}, an AD\&D supplement. +%.pg +Some interfaces support the \texttt{weaponstatus} option. +When it is enabled, an extra status condition is displayed, describing +the currently wielded weapon. + %.pg The commands to use weapons are `\texttt{w}' (wield), `\texttt{t}' (throw), `\texttt{f}' (fire), `\texttt{Q}' (quiver), @@ -3161,6 +3166,11 @@ option can be set (prior to game start) to attempt to play the entire game without wearing any armor (a self-imposed challenge which is extremely difficult to accomplish). +%.pg +Some interfaces support the \texttt{armorstatus} option. +When it is enabled, an extra status condition is displayed, summarizing +currently worn armor. + %.pg The commands to use armor are `\texttt{W}' (wear) and `\texttt{T}' (take off). The `\texttt{A}' command can be used to take off armor as well as other @@ -4053,7 +4063,7 @@ Highlight menu lines with different colors. See the ``Configuring Menu Colors`` section. %.lp \item[MSGTYPE] -Change the way messages are shown in the top status line. +Change the way messages are shown in the top line. See the ``Configuring Message Types`` section. %.lp \item[ROGUESYMBOLS] @@ -4215,6 +4225,21 @@ If \texttt{align} is not specified, there is no default value; player will be prompted unless role and/or race forces a choice for alignment. Cannot be set with the `\texttt{O}' command. Persistent. %.lp +\item[armorstatus] +Display an extra status condition which summarizes currently worn armor +(default off, not supported by all interfaces). +%.lp "" +\\ +For the usual case where more than one piece of armor is worn, a list of +letters is shown in the following order: +\\ +\texttt{G} - gloves;\\ +\texttt{C} - cloak;\\ +\texttt{A} - suit;\\ +\texttt{U} - shirt;\\ +\texttt{H} - helmet;\\ +\texttt{B} - boots;\\ +\texttt{S} - shield. \item[autodescribe] Automatically describe the terrain under cursor when asked to get a location on the map (default true). @@ -5307,6 +5332,10 @@ This option may be used to select one of the named symbol sets found within \texttt{symbols} to alter the symbols displayed on the screen. Use ``\texttt{symset:default}'' to explicitly select the default symbols. %.lp +\item[terrainstatus] +Display an extra status condition describing the spot beneath the hero's +feet (default off, not supported by all interfaces). +%.lp \item[time] Show the elapsed game time in turns on bottom line (default off). Persistent. %.lp @@ -5347,6 +5376,17 @@ Setting this option on or off in the config file will skip the query. \item[verbose] Provide more commentary during the game (default on). Persistent. %.lp +\item[weaponstatus] +Display an extra status condition which describes currently wielded weapon +(default off, not supported by all interfaces). +%.lp "" +\\ +Some possible displayed values are: +\\ +\texttt{bare-hnds} - no weapon and no gloves;\\ +\texttt{empty-hnd} - no weapon but gloves are worn;\\ +\texttt{dual-weps} - wielding two weapons. +%.lp \item[whatis\textunderscore coord] When using the `\texttt{/}' or `\texttt{;}' commands to look around on the map with ``\texttt{autodescribe}'' diff --git a/include/botl.h b/include/botl.h index 1a1507010..ad09d51ba 100644 --- a/include/botl.h +++ b/include/botl.h @@ -5,7 +5,12 @@ #ifndef BOTL_H #define BOTL_H -/* MAXCO must hold longest uncompressed status line, and must be larger +/* Note: this comment is about the pre-VIA_WINDOWPORT two line status + * which is still available but has not added a bunch of conditional + * extra status conditions (Grab, InLava, Held, Zzz and many others) + * or the new fields Weapon, Armor, and Terrain. + * + * MAXCO must hold longest uncompressed status line, and must be larger * than COLNO * * longest practical second status line at the moment is @@ -39,13 +44,20 @@ enum statusfields { BL_CHARACTERISTICS = -3, /* alias for BL_STR..BL_CH */ BL_RESET = -2, /* Force everything to redisplay */ BL_FLUSH = -1, /* Finished cycling through bot fields */ + /* + * Note: status_sanity_check() in wintty.c has strings for the rest + * of these, so if any get renumbered or more get added, be sure to + * keep those in sync. + */ BL_TITLE = 0, - BL_STR, BL_DX, BL_CO, BL_IN, BL_WI, BL_CH, /* 1..6 */ - BL_ALIGN, BL_SCORE, BL_CAP, BL_GOLD, BL_ENE, BL_ENEMAX, /* 7..12 */ - BL_XP, BL_AC, BL_HD, BL_TIME, BL_HUNGER, BL_HP, /* 13..18 */ - BL_HPMAX, BL_LEVELDESC, BL_EXP, BL_CONDITION, /* 19..22 */ - BL_VERS, /* 23 */ - MAXBLSTATS, /* [24] */ + BL_STR, BL_DX, BL_CO, BL_IN, BL_WI, BL_CH, /* 1.. 6 */ + BL_ALIGN, BL_SCORE, BL_CAP, BL_GOLD, /* 7..10 */ + BL_ENE, BL_ENEMAX, BL_XP, BL_AC, BL_HD, /* 11..15 */ + BL_TIME, BL_HUNGER, BL_HP, BL_HPMAX, /* 16..19 */ + BL_LEVELDESC, BL_EXP, BL_CONDITION, /* 20..22 */ + BL_WEAPON, BL_ARMOR, BL_TERRAIN, /* 23..25 */ + BL_VERS, /* 26 */ + MAXBLSTATS /* [27] */ }; enum relationships { @@ -55,7 +67,7 @@ enum relationships { }; enum blconditions { - bl_bareh, + bl_bareh, /* deprecated -- bl_weapon encompasses this */ bl_blind, bl_busy, bl_conf, @@ -67,7 +79,7 @@ enum blconditions { bl_grab, bl_hallu, bl_held, - bl_icy, + bl_icy, /* bl_terrain encompasses this */ bl_inlava, bl_lev, bl_parlyz, @@ -78,7 +90,7 @@ enum blconditions { bl_stone, bl_strngl, bl_stun, - bl_submerged, + bl_submerged, /* bl_terrain encompasses this */ bl_termill, bl_tethered, bl_trapped, diff --git a/include/extern.h b/include/extern.h index 3b357ec0d..67fc2292d 100644 --- a/include/extern.h +++ b/include/extern.h @@ -276,6 +276,8 @@ extern void max_rank_sz(void); extern long botl_score(void); #endif extern int describe_level(char *, int); +extern char *weapon_status(char *) NONNULL NONNULLARG1; +extern char *armor_status(char *) NONNULL NONNULLARG1; extern void status_initialize(boolean); extern void status_finish(void); extern boolean exp_percent_changing(void); @@ -1215,6 +1217,7 @@ extern void runmode_delay_output(void); extern void overexert_hp(void); extern boolean overexertion(void); extern void invocation_message(void); +extern void classify_terrain(void); extern void switch_terrain(void); extern void set_uinwater(int); extern boolean pooleffects(boolean); diff --git a/include/flag.h b/include/flag.h index c251236f0..f48b4693c 100644 --- a/include/flag.h +++ b/include/flag.h @@ -17,6 +17,7 @@ struct flag { boolean acoustics; /* allow dungeon sound messages */ + boolean armorstatus; /* show armor info on status lines */ boolean autodig; /* MRKR: Automatically dig */ boolean autoquiver; /* Automatically fill quiver */ boolean autoopen; /* open doors by walking into them */ @@ -63,9 +64,11 @@ struct flag { boolean sortpack; /* sorted inventory */ boolean sparkle; /* show "resisting" special FX (Scott Bigham) */ boolean standout; /* use standout for --More-- */ + boolean terrainstatus; /* show terrain info on status lines */ boolean time; /* display elapsed 'time' */ boolean tombstone; /* print tombstone */ boolean verbose; /* max battle info */ + boolean weaponstatus; /* show weapon info on status lines */ int end_top, end_around; /* describe desired score list */ unsigned autounlock; /* locked door/chest action */ #define AUTOUNLOCK_UNTRAP 1 @@ -307,6 +310,7 @@ struct instance_flags { int getpos_coords; /* show coordinates when getting cursor position */ int menuinvertmode; /* 0 = invert toggles every item; * 1 = invert skips 'all items' item */ + int terrain_typ; /* index into terrain_descr[] for botl */ color_attr menu_headings; /* CLR_ and ATR_ for menu headings */ uint32_t colorcount; /* store how many colors terminal is capable of */ boolean use_truecolor; /* force use of truecolor */ diff --git a/include/optlist.h b/include/optlist.h index 29f376dd8..05a2ca6ec 100644 --- a/include/optlist.h +++ b/include/optlist.h @@ -164,6 +164,9 @@ static int optfn_##a(int, int, boolean, char *, char *); Off, Yes, No, No, NoAlias, (boolean *) 0, Term_False, (char *)0) #endif + NHOPTB(armorstatus, Status, 0, opt_in, set_in_game, + Off, Yes, No, No, NoAlias, &flags.armorstatus, Term_False, + "summarize currently worn armor in a status field") NHOPTB(ascii_map, Advanced, 0, opt_in, set_in_game, ascii_map_Def, Yes, No, No, NoAlias, &iflags.wc_ascii_map, Term_False, "show map as text") @@ -741,6 +744,9 @@ static int optfn_##a(int, int, boolean, char *, char *); No, Yes, No, No, "termcolumns", "number of columns") NHOPTC(term_rows, Advanced, 6, opt_in, set_in_config, No, Yes, No, No, NoAlias, "number of rows") + NHOPTB(terrainstatus, Status, 0, opt_in, set_in_game, + Off, Yes, No, No, NoAlias, &flags.terrainstatus, Term_False, + "show hero's location as a status field") NHOPTC(tile_file, Advanced, 70, opt_in, set_gameview, No, Yes, No, No, NoAlias, "name of tile file") NHOPTC(tile_height, Advanced, 20, opt_in, set_gameview, @@ -853,6 +859,9 @@ static int optfn_##a(int, int, boolean, char *, char *); #endif NHOPTC(warnings, Advanced, 10, opt_in, set_in_config, No, Yes, No, No, NoAlias, "display characters for warnings") + NHOPTB(weaponstatus, Status, 0, opt_in, set_in_game, + Off, Yes, No, No, NoAlias, &flags.weaponstatus, Term_False, + "show currently wielded weapon in a status field") NHOPTC(whatis_coord, Advanced, 1, opt_in, set_in_game, Yes, Yes, No, Yes, NoAlias, "show coordinates when auto-describing cursor position") diff --git a/include/rm.h b/include/rm.h index 8811deae1..0819b4d52 100644 --- a/include/rm.h +++ b/include/rm.h @@ -92,7 +92,20 @@ enum levl_typ_types { CLOUD = 36, MAX_TYPE = 37, + /* for special levels */ MATCH_WALL = 38, + + /* these aren't levl[][].typ values, they're additional indices + into terrain_descr[] for status feedback */ + xFLOOR = 39, + xGROUND = 40, + xOPENDOOR = 41, + xSHUTDOOR = 42, + xSWAMP = 43, + xSUBMERGED = 44, + xSEA = 45, + xWATERWALL = 46, + INVALID_TYPE = 127 }; diff --git a/include/winprocs.h b/include/winprocs.h index 45463205e..0abe61d8c 100644 --- a/include/winprocs.h +++ b/include/winprocs.h @@ -262,6 +262,9 @@ extern #define WC2_U_UTF8STR 0x020000L /* 18 utf8str support */ #define WC2_EXTRACOLORS 0x040000L /* 19 color support beyond NH_BASIC_COLOR */ /* 13 free bits */ +#define WC2_EXTRASTATUS 0x080000L /* 20 optional weaponstatus, armorstatus, + * terrainstatus */ + /* 12 free bits */ #define ALIGN_LEFT 1 #define ALIGN_RIGHT 2 diff --git a/src/botl.c b/src/botl.c index 934d9e7ee..66c4b8950 100644 --- a/src/botl.c +++ b/src/botl.c @@ -475,6 +475,145 @@ describe_level( return ret; } +/* weapon description for status lines; started as a terser version of + what ^X shows but has diverged to some extent */ +char * +weapon_status(char *outbuf) +{ + const char *res = 0; + + *outbuf = '\0'; /* lint suppression */ + if (!uwep) { + /* no weapon; gloves imply hands; humanoid also implies hands; + otherwise make no assumptions */ + res = uarmg ? "Empty-hnd" /* empty handed means "gloves only" */ + : humanoid(gy.youmonst.data) ? "Bare-hnds" /* bare hands */ + : "No-weapon"; + } else if (u.twoweap) { + /* two-weaponing implies hands and a weapon or wep-tool + (not other odd stuff) in each hand */ + res = "Dual-weps"; + /* note: dual wielding two lances doesn't produce double joust */ + if (u.usteed && (weapon_type(uwep) == P_LANCE + || weapon_type(uswapwep) == P_LANCE)) + res = "Dual+joust"; /* lance behaves specially when mounted */ + } else { + /* report most weapons by their skill class (so a katana will be + described as a long sword, for instance; mattock and hook are + exceptions), or wielded non-weapon item by its object class */ + char *p; + int skill = weapon_type(uwep); + + if (u.usteed && skill == P_LANCE) { + /* lance behaves specially when hero is mounted */ + res = "joust"; + } else if (uwep->otyp == AKLYS) { + /* aklys behaves specially when thrown while wielded, so + give it a distinct name instead of skill name of "club"; + [maybe FIXME?] for the time being + use real name even if 'obj' is undiscovered "thonged club" */ + res = "aklys"; + } else if (is_sword(uwep)) { + /* simplify short short/broad sword/long sword/two-handed sword + (similar to messages when dropped due to slippery fingers) */ + res = "sword"; + } else { + /* shorten several */ + switch (skill) { + case P_QUARTERSTAFF: + res = "staff"; + break; + case P_MORNING_STAR: + res = "mrng-star"; /* still pretty long */ + break; + case P_POLEARMS: + res = "pole"; + break; + case P_UNICORN_HORN: + res = "unihorn"; + break; + default: + res = weapon_descr(uwep); + /* [should this be moved into weapon_descr()?] */ + if (!strcmpi(res, "food") && uwep->otyp == CREAM_PIE) + res = "pie"; + break; + } + } + + if ((uwep->oclass == WEAPON_CLASS || is_weptool(uwep)) + && bimanual(uwep) && *res != '2' && strncmpi(res, "two", 3)) + Strcat(outbuf, "2H-"); + Strcpy(p = eos(outbuf), res), res = outbuf; + *p = highc(*p); + /* avoid embedded spaces since its designed to appear as part + of a space-separated status line */ + (void) strNsubst(outbuf, " ", "-", 0); + } + + return (outbuf == res) ? outbuf : strcpy(outbuf, res); +} + +/* armor description for status lines */ +char * +armor_status(char *armbuf) +{ + int n = !!uarmg + !!uarmc + !!uarm + !!uarmu + !!uarmh + !!uarmf + !!uarms; + + /* + * FIXME: ^X needs to provide non-abbreviated version of this info. + * At present it just reports the "no armor" case. + */ + if (n == 0) { /* no armor */ + Strcpy(armbuf, "naked"); + } else if (n == 1) { /* just one piece; spell it out */ + Strcpy(armbuf, uarmg ? "gloves" + : uarmc ? "cloak" + : uarm ? "suit" + : uarmu ? "shirt" + : uarmh ? helm_simple_name(uarmh) /* hat|helm */ + : uarmf ? "boots" + : uarms ? "shield" + : ""); /* not possible */ + } else { /* more than one piece */ + char *p = armbuf; + + /* gloves first since this is expected to follow weapon_status(); + cloak next since it tends to provide the most protection + aside from raw AC */ + if (uarmg) + *p++ = 'G'; /* gloves */ + if (uarmc) + *p++ = 'C'; /* cloak */ + if (uarm) + *p++ = 'A'; /* suit but 's' is for shield */ + if (uarmu) + *p++ = 'U'; /* underwear? => shirt */ + if (uarmh) + *p++ = 'H'; /* hat/helm */ + if (uarmf) + *p++ = 'B'; /* footwear => boots */ + if (uarms) + *p++ = 'S'; /* shield */ + *p = '\0'; + } + /* + * Add a hint about MC by appending a plus sign if that's augmented. + * Bug: we should modfiy magical_negation() to return extra info and + * call it to scan whole inventory looking for sources of protection. + * This is a hack for efficiency to avoid that during status updates. + */ + if ((uright && uright->otyp == RIN_PROTECTION) + || (uleft && uleft->otyp == RIN_PROTECTION) + || (uamul && uamul->otyp == AMULET_OF_GUARDING) + || (uarmc && uarmc->otyp == CLOAK_OF_PROTECTION) + || (uarmh && uarmh->oartifact == ART_MITRE_OF_HOLINESS) + || (uwep && uwep->oartifact == ART_TSURUGI_OF_MURAMASA)) + (void) strkitten(armbuf, '+'); + + return upstart(armbuf); +} + /* =======================================================================*/ /* statusnew routines */ /* =======================================================================*/ @@ -550,9 +689,17 @@ staticfn void status_hilites_viewall(void); { (genericptr_t) 0 }, { (genericptr_t) 0 }, (char *) 0, \ wid, maxfld, fld INIT_THRESH } -/* If entries are added to this, botl.h will require updating too. - 'max' value of BL_EXP gets special handling since the percentage - involved isn't a direct 100*current/maximum calculation. */ +/* + * If entries are added to this, botl.h will require updating too. + * + * 'max' values of BL_XP and BL_EXP get special handling since the + * percentage involved isn't a direct 100*current/maximum calculation. + * + * long int fields are given a buffer size of 30 which is guaranteed to + * be big enough for short prefix followed by a 20+ digit 64-bit long + * even though the actual values will be much smaller. The gold field + * is even bigger due to its encoded dollar sign prefix. + */ static struct istat_s initblstats[MAXBLSTATS] = { INIT_BLSTAT("title", "%s", ANY_STR, MAXVALWIDTH, BL_TITLE), INIT_BLSTAT("strength", " St:%s", ANY_INT, 10, BL_STR), @@ -561,28 +708,36 @@ static struct istat_s initblstats[MAXBLSTATS] = { INIT_BLSTAT("intelligence", " In:%s", ANY_INT, 10, BL_IN), INIT_BLSTAT("wisdom", " Wi:%s", ANY_INT, 10, BL_WI), INIT_BLSTAT("charisma", " Ch:%s", ANY_INT, 10, BL_CH), - INIT_BLSTAT("alignment", " %s", ANY_STR, 40, BL_ALIGN), - INIT_BLSTAT("score", " S:%s", ANY_LONG, 20, BL_SCORE), + INIT_BLSTAT("alignment", " %s", ANY_STR, 20, BL_ALIGN), + INIT_BLSTAT("score", " S:%s", ANY_LONG, 30, BL_SCORE), INIT_BLSTAT("carrying-capacity", " %s", ANY_INT, 20, BL_CAP), - INIT_BLSTAT("gold", " %s", ANY_LONG, 30, BL_GOLD), + INIT_BLSTAT("gold", " %s", ANY_LONG, 40, BL_GOLD), INIT_BLSTATP("power", " Pw:%s", ANY_INT, 10, BL_ENEMAX, BL_ENE), INIT_BLSTAT("power-max", "(%s)", ANY_INT, 10, BL_ENEMAX), INIT_BLSTATP("experience-level", " Xp:%s", ANY_INT, 10, BL_EXP, BL_XP), INIT_BLSTAT("armor-class", " AC:%s", ANY_INT, 10, BL_AC), INIT_BLSTAT("HD", " HD:%s", ANY_INT, 10, BL_HD), - INIT_BLSTAT("time", " T:%s", ANY_LONG, 20, BL_TIME), + INIT_BLSTAT("time", " T:%s", ANY_LONG, 30, BL_TIME), /* hunger used to be 'ANY_UINT'; see note below in bot_via_windowport() */ - INIT_BLSTAT("hunger", " %s", ANY_INT, 40, BL_HUNGER), + INIT_BLSTAT("hunger", " %s", ANY_INT, 20, BL_HUNGER), INIT_BLSTATP("hitpoints", " HP:%s", ANY_INT, 10, BL_HPMAX, BL_HP), INIT_BLSTAT("hitpoints-max", "(%s)", ANY_INT, 10, BL_HPMAX), INIT_BLSTAT("dungeon-level", "%s", ANY_STR, MAXVALWIDTH, BL_LEVELDESC), - INIT_BLSTATP("experience", "/%s", ANY_LONG, 20, BL_EXP, BL_EXP), + INIT_BLSTATP("experience", "/%s", ANY_LONG, 30, BL_EXP, BL_EXP), INIT_BLSTAT("condition", "%s", ANY_MASK32, 0, BL_CONDITION), /* optional; once set it doesn't change unless 'showvers' option is toggled or player modifies the 'versinfo' option; available mostly for screenshots or someone looking over shoulder; blstat[][BL_VERS] is actually an int copy of flags.versinfo (0...7) */ INIT_BLSTAT("version", " %s", ANY_STR, MAXVALWIDTH, BL_VERS), + /* weapon and armor are constructed strings with no particular numeric + equivalent */ + INIT_BLSTAT("weapon", " %s", ANY_STR, 20, BL_WEAPON), + INIT_BLSTAT("armor", " %s", ANY_STR, 20, BL_ARMOR), + /* terrain is tracked by a number but designating it as type 'int' + isn't useful; using type 'string' allows highlighting based on text + matching which is potentially useful */ + INIT_BLSTAT("terrain", " %s", ANY_STR, 20, BL_TERRAIN), }; #undef INIT_BLSTATP @@ -638,25 +793,27 @@ const struct conditions_t conditions[] = { { 10, BL_MASK_HALLU, bl_hallu, { "Hallu", "Hal", "Hl" } }, { 20, BL_MASK_HELD, bl_held, { "Held", "Hld", "Hd" } }, { 20, BL_MASK_ICY, bl_icy, { "Icy", "Icy", "Ic" } }, - { 8, BL_MASK_INLAVA, bl_inlava, { "Lava", "Lav", "La" } }, + { 8, BL_MASK_INLAVA, bl_inlava, { "InLava", "Lav", "La" } }, { 10, BL_MASK_LEV, bl_lev, { "Lev", "Lev", "Lv" } }, { 20, BL_MASK_PARLYZ, bl_parlyz, { "Parlyz", "Para", "Par" } }, { 10, BL_MASK_RIDE, bl_ride, { "Ride", "Rid", "Rd" } }, { 20, BL_MASK_SLEEPING, bl_sleeping, { "Zzz", "Zzz", "Zz" } }, { 6, BL_MASK_SLIME, bl_slime, { "Slime", "Slim", "Slm" } }, - { 20, BL_MASK_SLIPPERY, bl_slippery, { "Slip", "Sli", "Sl" } }, + { 20, BL_MASK_SLIPPERY, bl_slippery, { "Slip", "Slp", "Sl" } }, { 6, BL_MASK_STONE, bl_stone, { "Stone", "Ston", "Sto" } }, { 4, BL_MASK_STRNGL, bl_strngl, { "Strngl", "Stngl", "Str" } }, { 10, BL_MASK_STUN, bl_stun, { "Stun", "Stun", "St" } }, - { 15, BL_MASK_SUBMERGED, bl_submerged, { "Sub", "Sub", "Sw" } }, + { 15, BL_MASK_SUBMERGED, bl_submerged, { "Submrg", "Subm", "Sm" } }, { 6, BL_MASK_TERMILL, bl_termill, { "TermIll", "Ill", "Ill" } }, { 20, BL_MASK_TETHERED, bl_tethered, { "Teth", "Tth", "Te" } }, { 20, BL_MASK_TRAPPED, bl_trapped, { "Trap", "Trp", "Tr" } }, { 20, BL_MASK_UNCONSC, bl_unconsc, { "Out", "Out", "KO" } }, - { 20, BL_MASK_WOUNDEDL, bl_woundedl, { "Legs", "Leg", "Lg" } }, + { 20, BL_MASK_WOUNDEDL, bl_woundedl, { "WLegs", "Leg", "Lg" } }, { 20, BL_MASK_HOLDING, bl_holding, { "UHold", "UHld", "UHd" } }, }; +/* [perhaps these should all be opt_out with default of 'in'; + otherwise some players may never learn about them] */ struct condtests_t condtests[CONDITION_COUNT] = { /* id, useropt, opt_in or out, enabled, configchoice, testresult; default value for enabled is !opt_in but can get changed via options */ @@ -694,6 +851,70 @@ struct condtests_t condtests[CONDITION_COUNT] = { /* condition indexing */ int cond_idx[CONDITION_COUNT] = { 0 }; +static const char c_Wall[] = "Wall"; +/* + * Terrain descriptions for flags.terrainstatus; simplified from + * def_syms[].name and indexed by iflags.terrain_typ; should be + * kept in sync with rm.h types and the first half of def_syms[]. + * The extra pseudo-types are specified by classify_terrain() + * when it sets up iflags.terrain_typ. Walls and a few of the + * others can only occur when hero has the Passes_walls ability. + */ +const char *terrain_descr[] = { +/* 0*/ "Stone", /* stone */ + c_Wall, /* vwall */ + c_Wall, /* hwall */ + c_Wall, /* tlcorner */ + c_Wall, /* trcorner */ + c_Wall, /* blcorner */ + c_Wall, /* brcorner */ + c_Wall, /* crosswall */ + c_Wall, /* tuwall */ + c_Wall, /* tdwall */ +/*10*/ c_Wall, /* tlwall */ + c_Wall, /* trwall */ + "Portcullis", /* dbwall, closed drawbridge 'door' */ + "Tree", + c_Wall, /* sdoor: secret door */ + "Stone", /* scorr: secret corridor */ + "Pool", /* pool or non-moat water; can be boiled away */ + "Moat", /* water that can't be boiled away */ + "Water", /* water on Water level; can't be boiled or frozen */ + "(gap)", /* drawbridge_up; replaced by whatever is under */ +/*20*/ "Lava", /* lavapool */ + "LavaWall", /* lava that extends to ceiling */ + "Bars", /* ironbars */ + "Doorway", /* doorless or broken door; diagonal movement is ok */ + "Corridor", /* replaced by "Floor" */ + "Room", /* also replaced by "Floor" */ + "Stairs", + "Ladder", + "Fountain", + "Throne", +/*30*/ "Sink", + "Grave", + "Altar", + "Ice", + "Bridge", /* drawbridge_down, span across moat/ice/lava/floor */ + "Air", /* open air on Air level or bubble on Water level */ + "Cloud", /* [part of] a cloud or Air level */ + /* + */ +/*37*/ "", /* MAX_TYPE; skipped ratther than overloaded */ +/*38*/ c_Wall, /* MATCH_WALL for special levels; shouldn't happen */ + /* + * additional terrain names that aren't simple levl[][].typ values + */ +/*39*/ "Floor", /* substituted for room or corridor */ +/*40*/ "Ground", /* 'room' on Earth level */ + "Open-door", /* open (not broken or doorless) */ + "Shut-door", /* closed or locked (or trapped) */ + "Swamp", /* Juiblex level */ + "Submerged", /* under water */ + "Sea", /* moat terrain on Medusa's level: "shallow sea" */ + "WaterWall", /* water that extends to the ceiling */ +}; + /* cache-related */ static boolean cache_avail[3] = { FALSE, FALSE, FALSE }; static boolean cache_reslt[3] = { FALSE, FALSE, FALSE }; @@ -1024,6 +1245,35 @@ bot_via_windowport(void) } #undef cond_bitset + /* + * Optionally displayed weapon(s), armor, and terrain. + */ + if (flags.weaponstatus) + (void) weapon_status(gb.blstats[idx][BL_WEAPON].val); + else + *gb.blstats[idx][BL_WEAPON].val = '\0'; + + if (flags.armorstatus) + (void) armor_status(gb.blstats[idx][BL_ARMOR].val); + else + *gb.blstats[idx][BL_ARMOR].val = '\0'; + + if (flags.terrainstatus) { + if (iflags.terrain_typ == MAX_TYPE) + classify_terrain(); + i = iflags.terrain_typ; + if (gb.blstats[idx][BL_TERRAIN].a.a_int != i) { + Strcpy(gb.blstats[idx][BL_TERRAIN].val, terrain_descr[i]); + gb.blstats[idx][BL_TERRAIN].a.a_int = i; + } + } else { + *gb.blstats[idx][BL_TERRAIN].val = '\0'; + /* MAX_TYPE is "none of the above" for levl[][].typ */ + gb.blstats[idx][BL_TERRAIN].a.a_int = MAX_TYPE; + } + gv.valset[BL_TERRAIN] = TRUE; + + /* now request rendering */ evaluate_and_notify_windowport(gv.valset, idx); #undef test_if_enabled } @@ -1383,8 +1633,11 @@ evaluate_and_notify_windowport( || ((fld == BL_EXP) && !flags.showexp) || ((fld == BL_TIME) && !flags.time) || ((fld == BL_HD) && !Upolyd) - || ((fld == BL_XP || i == BL_EXP) && Upolyd) + || ((fld == BL_XP || fld == BL_EXP) && Upolyd) || ((fld == BL_VERS) && !flags.showvers) + || ((fld == BL_TERRAIN) && !flags.terrainstatus) + || ((fld == BL_WEAPON) && !flags.weaponstatus) + || ((fld == BL_ARMOR) && !flags.armorstatus) ) { continue; } @@ -1452,7 +1705,10 @@ status_initialize( : (fld == BL_XP) ? (boolean) !Upolyd : (fld == BL_HD) ? (boolean) Upolyd : (fld == BL_VERS) ? flags.showvers - : TRUE; + : (fld == BL_WEAPON) ? flags.weaponstatus + : (fld == BL_ARMOR) ? flags.armorstatus + : (fld == BL_TERRAIN) ? flags.terrainstatus + : TRUE; fieldname = initblstats[i].fldname; fieldfmt = (fld == BL_TITLE && iflags.wc2_hitpointbar) ? "%-30.30s" @@ -1569,6 +1825,10 @@ compare_blstats(struct istat_s *bl1, struct istat_s *bl2) fmt_ptr((genericptr_t) bl1->a.a_void), fmt_ptr((genericptr_t) bl2->a.a_void)); } + /* cheat; terrain is highlighted as a string but we have a handy int + reflecting its value to use when checking for changes */ + if (bl1->fld == BL_TERRAIN) + anytype = ANY_INT; fld = bl1->fld; use_rawval = (fld == BL_HP || fld == BL_HPMAX diff --git a/src/dungeon.c b/src/dungeon.c index e0bd72d08..72bb26206 100644 --- a/src/dungeon.c +++ b/src/dungeon.c @@ -1585,12 +1585,18 @@ u_on_newpos(coordxy x, coordxy y) u.usteed->mx = u.ux, u.usteed->my = u.uy; /* when changing levels, don't leave old position set with stale values from previous level */ - if (!on_level(&u.uz, &u.uz0)) + if (!on_level(&u.uz, &u.uz0)) { u.ux0 = u.ux, u.uy0 = u.uy; - else if (!Blind && !Hallucination && !u.uswallow) + /* sets lastseentyp[u.ux][u.uy]; needed for switch_terrain() + somewhere back up the call chain */ + map_location(u.ux, u.uy, FALSE); + iflags.terrain_typ = MAX_TYPE; /* "none of the above" value */ + } else { /* still on same level; might have come close enough to generic object(s) to redisplay them as specific objects */ - see_nearby_objects(); + if (!Blind && !Hallucination && !u.uswallow) + see_nearby_objects(); + } earth_sense(); } diff --git a/src/hack.c b/src/hack.c index 38a24c6a7..da7391e9f 100644 --- a/src/hack.c +++ b/src/hack.c @@ -3084,6 +3084,93 @@ invocation_message(void) } } +/* for status: set up iflags.terrain_typ, an index into terrain_descrp[]; + some types need fixing up */ +void +classify_terrain(void) +{ + struct rm *lev = &levl[u.ux][u.uy]; + int typ = svl.lastseentyp[u.ux][u.uy]; /* lev->typ */ + + /* + * If the terrain under the hero is different now from what it + * was on the previous check, bring iflags.terrain_typ up to date + * and request a status update. Unless hero is running--then the + * update request will be suppressed. + */ + + if (Underwater) { + typ = xSUBMERGED; + } else { + switch (typ) { + case STONE: + if (svl.level.flags.arboreal) + typ = TREE; + break; + case CORR: + case ROOM: + /* this matches surface() but 'floor' is odd in many places */ + typ = !Is_earthlevel(&u.uz) ? xFLOOR : xGROUND; + break; + case DOOR: + /* defaults to "doorway" (door-less or broken) */ + if ((lev->doormask & D_ISOPEN) != 0) + typ = xOPENDOOR; + else if ((lev->doormask & (D_CLOSED | D_LOCKED | D_TRAPPED)) != 0) + typ = xSHUTDOOR; + break; + case DRAWBRIDGE_UP: + /* ICE, MOAT, LAVA, or 'STONE' (which ought to be 'room') */ + typ = db_under_typ(lev->drawbridgemask); + if (typ == STONE || typ == ROOM) + typ = xGROUND; + break; + case MOAT: + /* moat and swamp handling match waterbody_name()'s result */ + if (Is_medusa_level(&u.uz)) + typ = xSEA; + else if (Is_juiblex_level(&u.uz)) + typ = xSWAMP; + break; + case WATER: + if (!Is_waterlevel(&u.uz)) + typ = xWATERWALL; + break; +#if 0 /* don't bother -- Passes_walls for hero is rare, moving + * from one type of wall to another even rarer, and the + * cost of some extra once per move status updates is low */ + case VWALL: + case HWALL: + case TLCORNER: + case TRCORNER: + case BLCORNER: + case BRCORNER: + case CROSSWALL: + case TUWALL: + case TDWALL: + case TLWALL: + case TRWALL: + case SDOOR: /* (note: lastseentyp[][] never yields SDOOR) */ + /* any wall type would do, terrain_descr[] is "Wall" for all; + forcing just one avoids false 'changed' detection below if + hero with Passes_walls ability moves from one to another */ + typ = VWALL; + break; +#endif + default: + break; + } + } + + if (typ != iflags.terrain_typ) { + /* terrain at hero's spot is different */ + iflags.terrain_typ = typ; + /* request a status update unless hero is running */ + if (flags.terrainstatus && !svc.context.run) + disp.botl = TRUE; + } +} + /* moving onto different terrain; might be going into solid rock, inhibiting levitation or flight, or coming back out of such, reinstating levitation/flying */ @@ -3124,13 +3211,19 @@ switch_terrain(void) } if ((!!Levitation ^ was_levitating) || (!!Flying ^ was_flying)) disp.botl = TRUE; /* update Lev/Fly status condition */ + + if (flags.terrainstatus) + classify_terrain(); } /* set or clear u.uinwater */ void set_uinwater(int in_out) { - u.uinwater = in_out ? 1 : 0; + if (in_out != u.uinwater) { + u.uinwater = in_out ? 1 : 0; + switch_terrain(); + } } /* extracted from spoteffects; called by spoteffects to check for entering or @@ -3246,8 +3339,11 @@ spoteffects(boolean pick) spotterrain = levl[u.ux][u.uy].typ; spotloc.x = u.ux, spotloc.y = u.uy; - /* moving onto different terrain might cause Lev or Fly to toggle */ - if (spotterrain != levl[u.ux0][u.uy0].typ || !on_level(&u.uz, &u.uz0)) + /* moving onto different terrain might cause Lev or Fly to toggle; + level change sets to , so this spotterrain + check always fails then, but it also sets iflags.terrain_typ */ + if (spotterrain != levl[u.ux0][u.uy0].typ + || iflags.terrain_typ == MAX_TYPE) switch_terrain(); if (pooleffects(TRUE)) @@ -4035,9 +4131,19 @@ end_running(boolean and_travel) { /* moveloop() suppresses time_botl when context.run is non-zero; when running stops, update 'time' even if other botl status is unchanged */ - if (flags.time && svc.context.run) - disp.time_botl = TRUE; - svc.context.run = 0; + if (svc.context.run) { + svc.context.run = 0; + if (flags.time) + disp.time_botl = TRUE; + /* classify_terrain() suppresses setting disp.botl when + running; after that, it can no longer compare current terrain + against iflaga.terrain_typ to detect a change, so recompute */ + if (flags.terrainstatus) { + iflags.terrain_typ = MAX_TYPE; /* "none of the above" value */ + classify_terrain(); + } + } + /* 'context.mv' isn't travel but callers who want to end travel all clear it too */ if (and_travel) diff --git a/src/options.c b/src/options.c index a2dc7cedf..02606b509 100644 --- a/src/options.c +++ b/src/options.c @@ -5320,12 +5320,24 @@ optfn_boolean( return optn_ok; switch (optidx) { - case opt_time: -#ifdef SCORE_ON_BOTL + case opt_terrainstatus: + classify_terrain(); /* bring iflags.terrain_typ up to date */ + FALLTHROUGH; + /*FALLTHRU*/ + case opt_weaponstatus: + case opt_armorstatus: + if (!wc2_supported(allopt[optidx].name)) { + /* not actually an error */ + config_error_add("'%s' is not supported.", + allopt[optidx].name); + return optn_ok; + } + FALLTHROUGH; + /*FALLTHRU*/ case opt_showscore: -#endif case opt_showvers: case opt_showexp: + case opt_time: if (VIA_WINDOWPORT()) status_initialize(REASSESS_ONLY); disp.botl = TRUE; @@ -9827,6 +9839,7 @@ static struct wc_Opt wc_options[] = { { (char *) 0, 0L } }; static struct wc_Opt wc2_options[] = { + { "armorstatus", WC2_EXTRASTATUS }, { "fullscreen", WC2_FULLSCREEN }, { "guicolor", WC2_GUICOLOR }, { "hilite_status", WC2_HILITE_STATUS }, @@ -9841,7 +9854,9 @@ static struct wc_Opt wc2_options[] = { { "statuslines", WC2_STATUSLINES }, { "term_cols", WC2_TERM_SIZE }, { "term_rows", WC2_TERM_SIZE }, + { "terrainstatus", WC2_EXTRASTATUS }, { "use_darkgray", WC2_DARKGRAY }, + { "weaponstatus", WC2_EXTRASTATUS }, { "windowborders", WC2_WINDOWBORDERS }, { "wraptext", WC2_WRAPTEXT }, { (char *) 0, 0L } diff --git a/src/u_init.c b/src/u_init.c index dcf3e4077..2b322b497 100644 --- a/src/u_init.c +++ b/src/u_init.c @@ -1261,12 +1261,12 @@ ini_inv_use_obj(struct obj *obj) if (obj->oclass == ARMOR_CLASS) { if (is_shield(obj) && !uarms && !(uwep && bimanual(uwep))) { - setworn(obj, W_ARMS); /* Prior to 3.6.2 this used to unset uswapwep if it was set, but wearing a shield doesn't prevent having an alternate weapon ready to swap with the primary; just make sure we aren't two-weaponing (academic; no one starts that way) */ set_twoweap(FALSE); /* u.twoweap = FALSE */ + setworn(obj, W_ARMS); } else if (is_helmet(obj) && !uarmh) setworn(obj, W_ARMH); else if (is_gloves(obj) && !uarmg) diff --git a/src/uhitm.c b/src/uhitm.c index af4e8ca8a..00858508e 100644 --- a/src/uhitm.c +++ b/src/uhitm.c @@ -1551,12 +1551,12 @@ hmon_hitmon_jousting( first_weapon_hit(obj); if (hmd->jousting < 0) { - pline("%s shatters on impact!", Yname2(obj)); /* (must be either primary or secondary weapon to get here) */ set_twoweap(FALSE); /* sets u.twoweap = FALSE; * untwoweapon() is too verbose here */ if (obj == uwep) uwepgone(); /* set gu.unweapon */ + pline("%s shatters on impact!", Yname2(obj)); /* minor side-effect: broken lance won't split puddings */ useup(obj); obj = (struct obj *) 0; diff --git a/src/wield.c b/src/wield.c index 7b24b3df7..21aad53c4 100644 --- a/src/wield.c +++ b/src/wield.c @@ -103,10 +103,15 @@ setuwep(struct obj *obj) if (obj == uwep) return; /* necessary to not set gu.unweapon */ - /* This message isn't printed in the caller because it happens - * *whenever* Sunsword is unwielded, from whatever cause. - */ setworn(obj, W_WEP); + /* handle Ogresmasher before Sunsword; even though they can't be happening + at the same time, botl flag update should come before pline message */ + if (uwep == obj + && ((uwep && uwep->oartifact == ART_OGRESMASHER) + || (olduwep && olduwep->oartifact == ART_OGRESMASHER))) + disp.botl = TRUE; /* gaining or losing Con bonus */ + /* This message isn't printed in the caller because it happens + * *whenever* Sunsword is unwielded, from whatever cause. */ if (uwep == obj && artifact_light(olduwep) && olduwep->lamplit) { end_burn(olduwep, FALSE); if (!Blind) @@ -828,7 +833,11 @@ drop_uswapwep(void) void set_twoweap(boolean on_off) { - u.twoweap = on_off; + if (on_off != u.twoweap) { + u.twoweap = on_off; + if (flags.weaponstatus) + disp.botl = TRUE; + } } /* the #twoweapon command */ diff --git a/src/worn.c b/src/worn.c index 6b2c082c1..a481f3372 100644 --- a/src/worn.c +++ b/src/worn.c @@ -137,6 +137,9 @@ setworn(struct obj *obj, long mask) /* tux -> tuxedo -> "monkey suit" -> monk's suit */ iflags.tux_penalty = (uarm && Role_if(PM_MONK) && gu.urole.spelarmr); } + if ((flags.weaponstatus && (mask & W_WEP) != 0L) + || (flags.armorstatus && (mask & W_ARMOR) != 0L)) + disp.botl = TRUE; update_inventory(); recalc_telepat_range(); } @@ -148,6 +151,7 @@ setnotworn(struct obj *obj) { const struct worn *wp; int p; + long unworn = 0L; if (!obj) return; @@ -160,6 +164,7 @@ setnotworn(struct obj *obj) cancel_doff(obj, wp->w_mask); *(wp->w_obj) = (struct obj *) 0; + unworn |= wp->w_mask; p = objects[obj->otyp].oc_oprop; u.uprops[p].extrinsic = u.uprops[p].extrinsic & ~wp->w_mask; monstunseesu_prop(p); /* remove this extrinsic from seenres */ @@ -171,6 +176,9 @@ setnotworn(struct obj *obj) } if (!uarm) iflags.tux_penalty = FALSE; + if ((flags.weaponstatus && (unworn & W_WEP) != 0L) + || (flags.armorstatus && (unworn & W_ARMOR) != 0L)) + disp.botl = TRUE; update_inventory(); recalc_telepat_range(); } diff --git a/win/curses/cursmain.c b/win/curses/cursmain.c index 77d1013f6..4d1923eda 100644 --- a/win/curses/cursmain.c +++ b/win/curses/cursmain.c @@ -72,7 +72,9 @@ struct window_procs curses_procs = { #endif | WC2_FLUSH_STATUS | WC2_TERM_SIZE | WC2_STATUSLINES | WC2_WINDOWBORDERS | WC2_PETATTR | WC2_GUICOLOR - | WC2_SUPPRESS_HIST | WC2_URGENT_MESG | WC2_MENU_SHIFT), + | WC2_SUPPRESS_HIST | WC2_URGENT_MESG | WC2_MENU_SHIFT + | WC2_EXTRASTATUS + ), {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, /* color availability */ curses_init_nhwindows, curses_player_selection, diff --git a/win/curses/cursstat.c b/win/curses/cursstat.c index 67691bb59..b44fae883 100644 --- a/win/curses/cursstat.c +++ b/win/curses/cursstat.c @@ -254,12 +254,13 @@ draw_horizontal(boolean border) /* almost all fields already come with a leading space; "xspace" indicates places where we'll generate an extra one */ static const enum statusfields - twolineorder[3][16] = { + twolineorder[3][19] = { { BL_TITLE, /*xspace*/ BL_STR, BL_DX, BL_CO, BL_IN, BL_WI, BL_CH, /*xspace*/ BL_ALIGN, /*xspace*/ BL_SCORE, - BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD }, + BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, + blPAD, blPAD, blPAD }, { BL_LEVELDESC, /*xspace*/ BL_GOLD, /*xspace*/ BL_HP, BL_HPMAX, @@ -267,32 +268,35 @@ draw_horizontal(boolean border) /*xspace*/ BL_AC, /*xspace*/ BL_XP, BL_EXP, BL_HD, /*xspace*/ BL_TIME, - /*xspace*/ BL_HUNGER, BL_CAP, BL_CONDITION, BL_VERS, + /*xspace*/ BL_HUNGER, BL_CAP, + BL_WEAPON, BL_ARMOR, BL_TERRAIN, BL_CONDITION, BL_VERS, BL_FLUSH }, { BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, - blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD } + blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, + blPAD, blPAD, blPAD } }, - threelineorder[3][16] = { /* moves align to line 2, leveldesc+ to 3 */ + threelineorder[3][19] = { /* moves align to line 2, leveldesc+ to 3 */ { BL_TITLE, /*xspace*/ BL_STR, BL_DX, BL_CO, BL_IN, BL_WI, BL_CH, /*xspace*/ BL_SCORE, - BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD }, + BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, + blPAD, blPAD, blPAD }, { BL_ALIGN, /*xspace*/ BL_GOLD, /*xspace*/ BL_HP, BL_HPMAX, /*xspace*/ BL_ENE, BL_ENEMAX, /*xspace*/ BL_AC, /*xspace*/ BL_XP, BL_EXP, BL_HD, - /*xspace*/ BL_HUNGER, BL_CAP, + /*xspace*/ BL_HUNGER, BL_CAP, BL_WEAPON, BL_ARMOR, BL_TERRAIN, BL_FLUSH, blPAD, blPAD, blPAD }, { BL_LEVELDESC, /*xspace*/ BL_TIME, /*xspecial*/ BL_CONDITION, /*xspecial*/ BL_VERS, BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, - blPAD, blPAD, blPAD, blPAD } + blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD } }; - const enum statusfields (*fieldorder)[16]; + const enum statusfields (*fieldorder)[19]; coordxy spacing[MAXBLSTATS], valline[MAXBLSTATS]; enum statusfields fld, prev_fld; char *text, *p, cbuf[BUFSZ], ebuf[STATVAL_WIDTH]; @@ -401,6 +405,9 @@ draw_horizontal(boolean border) case BL_ENEMAX: spacing[fld] = 0; /* no leading or extra space */ break; + case BL_WEAPON: + case BL_ARMOR: + case BL_TERRAIN: case BL_DX: case BL_CO: case BL_IN: @@ -705,16 +712,18 @@ draw_vertical(boolean border) /* 6:blank (if any of hunger, encumbrance, or conditions appear) */ BL_HUNGER, BL_CAP, /* these two are shown on same line */ BL_CONDITION, /* shown three per line so may take up to four lines */ + BL_WEAPON, BL_ARMOR, BL_TERRAIN, /* same line if 2, two lines if 3 */ /* 1:blank (bottom justified) */ BL_VERS, BL_FLUSH }; static const enum statusfields shrinkorder[] = { - BL_VERS, BL_STR, BL_SCORE, BL_TIME, BL_LEVELDESC, BL_HP, - BL_CONDITION, BL_CAP, BL_HUNGER + BL_VERS, BL_STR, BL_SCORE, BL_TIME, BL_LEVELDESC, BL_HP, + BL_TERRAIN, BL_ARMOR, BL_WEAPON, + BL_CONDITION, BL_CAP, BL_HUNGER }; coordxy spacing[MAXBLSTATS]; - int i, fld, cap_and_hunger, time_and_score, cond_count, + int i, fld, cap_and_hunger, time_and_score, cond_count, wep_arm_ter, sho_vers, per_line; char *text; #ifdef STATUS_HILITES @@ -758,6 +767,13 @@ draw_vertical(boolean border) } per_line = 2; /* will be changed to 3 if status becomes too tall */ sho_vers = (status_activefields[BL_VERS] ? 1 : 0); + wep_arm_ter = 0; + if (status_activefields[BL_WEAPON] && *status_vals_long[BL_WEAPON]) + wep_arm_ter |= 1; + if (status_activefields[BL_ARMOR] && *status_vals_long[BL_ARMOR]) + wep_arm_ter |= 2; + if (status_activefields[BL_TERRAIN] && *status_vals_long[BL_TERRAIN]) + wep_arm_ter |= 4; /* count how many lines we'll need; we normally space several groups of fields with blank lines but might need to compress some of those out */ @@ -802,6 +818,32 @@ draw_vertical(boolean border) if (cond_count > per_line) height_needed += (cond_count - 1) / per_line; break; + case BL_WEAPON: + /* two lines if no hunger or encumbrance and no conditions, + otherwise one line if weapon status being is shown */ + spacing[fld] = ((wep_arm_ter & 1) == 1) /* show wep */ + ? ((!cond_count && !cap_and_hunger) ? 2 : 1) + : 0; /* no wep */ + break; + case BL_ARMOR: + /* two lines if no hunger or encumbrance and no conditions + and no weapon status, otherwise one line if no weapon + status, else same line as weapon status */ + spacing[fld] = ((wep_arm_ter & 3) == 2) /* show arm, no wep */ + ? ((!cond_count && !cap_and_hunger) ? 2 : 1) + : 0; /* wep and arm (on same line) or no arm */ + break; + case BL_TERRAIN: + /* two lines if no hunger or encumbrance and no conditions + and no weapon status and no armor status, otherwise one + line if both weapon status and armor status or neither of + them and terrain status is being shown */ + spacing[fld] = ((wep_arm_ter & 7) == 4) /* show ter, no wep|arm */ + ? ((!cond_count && !cap_and_hunger) ? 2 : 1) + /* if all three, put terrain on next line; else + same line for wep+ter or arm+ter or no ter */ + : (((wep_arm_ter & 7) == 7) ? 1 : 0); + break; case BL_VERS: spacing[fld] = sho_vers ? 2 : 0; break; @@ -883,7 +925,11 @@ draw_vertical(boolean border) the first (or only) and keep it for the second (if both) */ if (*text == ' ' && (fld == BL_HUNGER - || (fld == BL_CAP && cap_and_hunger != 3))) + || (fld == BL_CAP && cap_and_hunger != 3) + || fld == BL_WEAPON + || (fld == BL_ARMOR && (wep_arm_ter & 3) == 2) + || (fld == BL_TERRAIN && ((wep_arm_ter & 7) == 4 + || (wep_arm_ter & 7) == 7)))) ++text; #ifdef STATUS_HILITES coloridx = curses_status_colors[fld]; /* includes attributes */ @@ -1239,6 +1285,9 @@ curs_vert_status_vals(int win_width) case BL_EXP: case BL_HUNGER: case BL_CAP: + case BL_WEAPON: + case BL_ARMOR: + case BL_TERRAIN: case BL_TITLE: use_name = FALSE; break; diff --git a/win/tty/wintty.c b/win/tty/wintty.c index fb9ec4dbf..b2c6a5c42 100644 --- a/win/tty/wintty.c +++ b/win/tty/wintty.c @@ -121,6 +121,7 @@ struct window_procs tty_procs = { #if !defined(NO_TERMS) || defined(WIN32CON) | WC2_EXTRACOLORS #endif + | WC2_EXTRASTATUS ), {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, /* color availability */ tty_init_nhwindows, tty_player_selection, tty_askname, tty_get_nh_event, @@ -4271,27 +4272,29 @@ static const char *const encvals[3][6] = { { "", "Brd", "Strs", "Strn", "Ovtx", "Ovld" } }; #define blPAD BL_FLUSH -#define MAX_PER_ROW 16 +#define MAX_PER_ROW 19 /* 2 or 3 status lines */ static const enum statusfields twolineorder[3][MAX_PER_ROW] = { { BL_TITLE, BL_STR, BL_DX, BL_CO, BL_IN, BL_WI, BL_CH, BL_ALIGN, - BL_SCORE, BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD }, + BL_SCORE, BL_FLUSH, + blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD }, { BL_LEVELDESC, BL_GOLD, BL_HP, BL_HPMAX, BL_ENE, BL_ENEMAX, - BL_AC, BL_XP, BL_EXP, BL_HD, BL_TIME, BL_HUNGER, - BL_CAP, BL_CONDITION, BL_VERS, BL_FLUSH }, + BL_AC, BL_XP, BL_EXP, BL_HD, BL_TIME, BL_HUNGER, BL_CAP, + BL_CONDITION, BL_WEAPON, BL_ARMOR, BL_TERRAIN, BL_VERS, BL_FLUSH }, /* third row of array isn't used for twolineorder */ { BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD } }, /* Align moved from 1 to 2, Leveldesc+Time+Cond+Vers moved from 2 to 3 */ threelineorder[3][MAX_PER_ROW] = { - { BL_TITLE, BL_STR, BL_DX, BL_CO, BL_IN, BL_WI, BL_CH, - BL_SCORE, BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD }, + { BL_TITLE, BL_STR, BL_DX, BL_CO, BL_IN, BL_WI, BL_CH, BL_SCORE, BL_FLUSH, + blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD }, { BL_ALIGN, BL_GOLD, BL_HP, BL_HPMAX, BL_ENE, BL_ENEMAX, - BL_AC, BL_XP, BL_EXP, BL_HD, BL_HUNGER, - BL_CAP, BL_FLUSH, blPAD, blPAD, blPAD }, - { BL_LEVELDESC, BL_TIME, BL_CONDITION, BL_VERS, BL_FLUSH, blPAD, + BL_AC, BL_XP, BL_EXP, BL_HD, BL_HUNGER, BL_CAP, + BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD }, + { BL_LEVELDESC, BL_TIME, BL_CONDITION, BL_WEAPON, BL_ARMOR, BL_TERRAIN, + BL_VERS, BL_FLUSH, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD, blPAD } }; static const enum statusfields (*fieldorder)[MAX_PER_ROW]; @@ -4378,7 +4381,8 @@ tty_status_enablefield( * BL_TITLE, BL_STR, BL_DX, BL_CO, BL_IN, BL_WI, BL_CH, * BL_ALIGN, BL_SCORE, BL_CAP, BL_GOLD, BL_ENE, BL_ENEMAX, * BL_XP, BL_AC, BL_HD, BL_TIME, BL_HUNGER, BL_HP, BL_HPMAX, - * BL_LEVELDESC, BL_EXP, BL_CONDITION, BL_VERS + * BL_LEVELDESC, BL_EXP, BL_CONDITION, + * BL_WEAPON, BL_ARMOR, BL_TERRAIN, BL_VERS * -- fldindex could also be BL_FLUSH (-1), which is not really * a field index, but is a special trigger to tell the * windowport that it should output all changes received @@ -4748,7 +4752,8 @@ status_sanity_check(void) "BL_ENE", "BL_ENEMAX", "BL_XP", "BL_AC", "BL_HD", /* 11..15 */ "BL_TIME", "BL_HUNGER", "BL_HP", "BL_HPMAX", /* 16..19 */ "BL_LEVELDESC", "BL_EXP", "BL_CONDITION", /* 20..22 */ - "BL_VERS", /* 23 */ + "BL_WEAPON", "BL_ARMOR", "BL_TERRAIN", /* 23..25 */ + "BL_VERS", /* 26 */ }; static boolean in_sanity_check = FALSE; int i; @@ -4768,8 +4773,8 @@ status_sanity_check(void) a corresponding expansion of idxtext[] here */ if (i < SIZE(idxtext)) Strcpy(indxtxt, idxtext[i]); - else - Sprintf(indxtxt, "%d", i); + else /* blstats[] increased without doing same for idxtext[] */ + Sprintf(indxtxt, "#%d", i); Sprintf(panicmsg, "failed on tty_status[NOW][%s].", indxtxt); paniclog("status_sanity_check", panicmsg); tty_status[NOW][i].sanitycheck = FALSE;