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;