This is a slightly modified version of Pasi Kallinen configurable
statuslines patch.  The differences are:

    - Re-worked for use with patchable Nethack.

    - Added the "Held" status flag to the patch.  It will mostly work
      as-is, but you should add the "Held" status flag patch for complete
      support.

Bones/save compatibility:  This patch doesn't affect saved games or bones
files.

This patch is available at http://www.argon.org/~roderick/nethack/.

Roderick Schertler <roderick@argon.org>


diff -r -X /home/roderick/.diff-exclude -uN base.patchable/README.cnf_botl work.conf-botl/README.cnf_botl
--- base.patchable/README.cnf_botl	1969-12-31 19:00:00.000000000 -0500
+++ work.conf-botl/README.cnf_botl	2003-03-20 17:17:04.000000000 -0500
@@ -0,0 +1,187 @@
+
+Patch: Configurable Statuslines v2.1
+---
+
+ Allows the user to change what is shown on the bottom statuslines,
+ by setting the options in the config file.
+
+
+ Here's an example of how the default statuslines would look:
+ 
+OPTIONS=statline1:"%-.10a the %-12b St:%c Dx:%d Co:%e In:%f Wi:%g Ch:%h %i %y"
+OPTIONS=statline2:"%-7k %$:%-4l HP:%n(%m) Pw:%p(%o) AC:%-2q %t %v %w"
+
+ and here's alternative ones:
+
+OPTIONS=statline1:"%-.10a the %b S:%c D:%d C:%e I:%f W:%g C:%h %.3i %R %Z"
+OPTIONS=statline2:"%-7k %$:%-4l HP:%n(%m) Pw:%p(%o) AC:%-2q %t %v %S%T%U%V%W%X%Y"
+ or
+OPTIONS=statline2:'%-7k %$:%-2l HP:%{s:n;m}%n%{c}(%m) Pw:%{s:p;o}%p%{c}(%o) AC:%-2q %t %v %w'
+
+
+Variables and commands begin with percent sign ('%'), everything else before
+and after is shown normally.
+
+Variable format:
+
+  %[-][x][.y]z
+  
+  -	field output is left justified
+  x	field length
+  .y	number of characters to output on the field
+  z	one of the following:
+
+	  % = "%"
+	  $ = symbol for gold
+
+	  a = name
+	  b = rank
+
+	  c = strength
+	  d = dexterity
+	  e = constitution
+	  f = intelligence
+	  g = wisdom
+	  h = charisma
+
+	  i = alignment
+
+	  j = dungeon depth (number)
+	  k = dungeon name  ("Dlvl:%j", varies depending on the branch)
+
+	  l = gold
+
+	  m = max hit points
+	  n = current hit points
+
+	  o = max power
+	  p = current power
+
+	  q = armor class
+
+	  r = experience level (or hit dice if polymorphed)
+	  s = experience points (empty string if !showexp)
+	  t = "Exp:%r"  OR  "Xp:%r/%s"   depending on showexp-option,
+	      		OR  "HD:%r"      if polymorphed
+
+	  u = time   (or empty string if time-option is off)
+	  v = "T:%u" (or empty string if time-option if off)
+
+	  w = all status flags (blind, ill, foodpois, ...)
+
+	  x = score   (if compiled with SCORE_ON_BOTL, else empty string)
+	  y = "S:%x"  (if compiled with SCORE_ON_BOTL, else empty string)
+
+	  Q = status: held
+	  R = status: encumbrance level
+	  S = status: slime
+	  T = status: hallu
+	  U = status: stun
+	  V = status: blind
+	  W = status: ill
+	  X = status: foodpois
+	  Y = status: conf
+	  Z = status: hunger level
+
+
+Command format:
+
+  %{x[:y[;y]]}
+  
+  y	optional parameters for the command, separated with ';'
+  x	command identifier character, one of the following:
+  
+	  c = Set color and attribute.
+	      Parameters: 0, 1, or 2.
+	      First parameter is color, the second parameter.
+	      If parameters are omitted, the color and attribute are
+	       reset to defaults, if only one parameter is given, it's
+	       assumed to be a color.
+	      Valid values for color are black, red, green, brown, blue,
+	       magenta, cyan, gray, orange, lightgreen, yellow, lightblue,
+	       lightmagenta, lightcyan and white.
+	      Valid values for attributes are none, bold, dim, underline,
+	       blink, and inverse.
+	      You can also use numbers instead of named colors or attributes,
+	      eg. 0 is black, 1 is red, 2 is green, and so on.
+	      Note that the terminal is free to interpret the colors and
+	      attributes however it wants.
+	      Examples: %{c}
+			%{c:green}
+			%{c:blue;bold}
+			%{c:2;1}
+
+	  s = Use color "slide".
+	      Changes the printing color according to the parameters.
+	      The smaller the first parameter is when compared to the
+	      second parameter, the more eye-catching colors are used.
+	      (The colors are orange, yellow, white and no color)
+	      
+	      Parameters: 2.
+	      First is the current value, Second is the maximum value.
+	      You can either use constant positive integer values, or
+	      variable characters without the '%'.
+	      Examples: HP:%{s:n;m}%n%{c}(%m)
+	                Pw:%{s:p;o}%p%{c}(%o)
+
+
+
+
+
+For example, if the character's name is "Abcdefg":
+
+options str      status line
+"%10a"       =>  "   Abcdefg"
+"%-10a"      =>  "Abcdefg   "
+"%.3a"       =>  "Abc"
+"%10.3a"     =>  "       Abc"
+"%-10.3a"    =>  "Abc       "
+
+"%{c:green;bold}%a"  => Prints the player's name with green and bold.
+"HP:%{s:n;m}%n%{c}(%m)" => Prints current hitpoints with different colors,
+                           depending on how much you have compared to the
+			   maximum hitpoints.
+
+
+Affected files:
+---
+ src/save.c
+ src/botl.c
+ src/options.c
+ 
+
+
+Todo:
+---
+ -still only works with TTY
+ -win/tty/wintty.c:tty_putstr() changes the string, but it might not
+  be in the same 'format' as it expects. (eg. it removes chars before 
+  'St:')
+ 
+ v2.1-patchable
+ -tweaked for use with the patchable Nethack patch
+ -add "Held" status flag (%Q) , but note you've got to apply the botl-held
+  patch for this to work correctly
+
+ v2.1
+ -updated to use 3.4.1 codebase
+ -compile time option CONF_BOTL
+ 
+ v2.0
+ -cleaned up the code a bit
+ -you can now use colors and attributes
+
+ v1.1
+ -uses Nethack 3.4.0 codebase
+ -major code cleanup
+
+ v1.0
+ -initial release
+
+
+---
+ Pasi Kallinen
+ pkalli@cs.joensuu.fi
+
+
+ You're free to (ab)use the code in any way you wish.
diff -r -X /home/roderick/.diff-exclude -uN base.patchable/include/config.h work.conf-botl/include/config.h
--- base.patchable/include/config.h	2003-03-20 16:41:57.000000000 -0500
+++ work.conf-botl/include/config.h	2003-03-20 17:33:28.000000000 -0500
@@ -364,6 +364,14 @@
 /*
  * options patch point; see $top/README.patchable
  */
+
+#ifdef TTY_GRAPHICS
+# define CONF_BOTL		/* Configurable status lines */
+#endif
+
+/*
+ * options patch point; see $top/README.patchable
+ */
 /*
  * options patch point; see $top/README.patchable
  */
diff -r -X /home/roderick/.diff-exclude -uN base.patchable/include/extern.h work.conf-botl/include/extern.h
--- base.patchable/include/extern.h	2003-02-23 09:43:20.000000000 -0500
+++ work.conf-botl/include/extern.h	2003-03-20 17:04:40.000000000 -0500
@@ -131,6 +131,10 @@
 E int FDECL(describe_level, (char *));
 E const char *FDECL(rank_of, (int,SHORT_P,BOOLEAN_P));
 E void NDECL(bot);
+#ifdef CONF_BOTL
+E void FDECL(parsebotl, (char *, int));
+E void NDECL(free_botlines);
+#endif
 
 /* ### cmd.c ### */
 
diff -r -X /home/roderick/.diff-exclude -uN base.patchable/src/botl.c work.conf-botl/src/botl.c
--- base.patchable/src/botl.c	2003-02-23 09:43:25.000000000 -0500
+++ work.conf-botl/src/botl.c	2003-03-20 17:16:19.000000000 -0500
@@ -3,6 +3,9 @@
 /* NetHack may be freely redistributed.  See license for details. */
 
 #include "hack.h"
+#ifdef CONF_BOTL
+# include <ctype.h>
+#endif
 
 #ifdef OVL0
 extern const char *hu_stat[];	/* defined in eat.c */
@@ -16,8 +19,10 @@
 	"Overloaded"
 };
 
+#ifndef CONF_BOTL
 STATIC_DCL void NDECL(bot1);
 STATIC_DCL void NDECL(bot2);
+#endif /* !CONF_BOTL */
 #endif /* OVL0 */
 
 /* MAXCO must hold longest uncompressed status line, and must be larger
@@ -44,6 +49,58 @@
 
 #ifdef OVL1
 
+#ifdef CONF_BOTL
+#define BOTLPARMTYP_TXT 0 /* printable text           eg. "HP: "  */
+#define BOTLPARMTYP_VAR 1 /* variable and formatting  eg. "%-2.5a" */
+#define BOTLPARMTYP_CMD 2 /* command          eg. "%{_blue&blink}" */
+
+struct botlparam {
+   struct botlparam *nextparam;
+   xchar bptyp;  /* one of BOTLPARMTYP_foo */
+   char valuechar; /* the variable */
+   char *params;         /* variable formatting string */
+   int parami1, parami2;
+};
+
+#define MAX_BOTLINES 2
+
+static struct botlparam *botlbase[MAX_BOTLINES] = { NULL, NULL };
+
+static const struct {
+   const char *name;
+   const int color;
+} colornames[] = {
+   {"black", CLR_BLACK},
+   {"red", CLR_RED},
+   {"green", CLR_GREEN},
+   {"brown", CLR_BROWN},
+   {"blue", CLR_BLUE},
+   {"magenta", CLR_MAGENTA},
+   {"cyan", CLR_CYAN},
+   {"gray", CLR_GRAY},
+   {"orange", CLR_ORANGE},
+   {"lightgreen", CLR_BRIGHT_GREEN},
+   {"yellow", CLR_YELLOW},
+   {"lightblue", CLR_BRIGHT_BLUE},
+   {"lightmagenta", CLR_BRIGHT_MAGENTA},
+   {"lightcyan", CLR_BRIGHT_CYAN},
+   {"white", CLR_WHITE}
+};
+
+static const struct {
+   const char *name;
+   const int attr;
+} attrnames[] = {
+     {"none", ATR_NONE},
+     {"bold", ATR_BOLD},
+     {"dim", ATR_DIM},
+     {"underline", ATR_ULINE},
+     {"blink", ATR_BLINK},
+     {"inverse", ATR_INVERSE}
+
+};
+#endif /* CONF_BOTL */
+
 /* convert experience level (1..30) to rank index (0..8) */
 int
 xlev_to_rank(xlev)
@@ -165,6 +222,7 @@
 }
 #endif
 
+#ifndef CONF_BOTL
 STATIC_OVL void
 bot1()
 {
@@ -218,6 +276,7 @@
 	curs(WIN_STATUS, 1, 0);
 	putstr(WIN_STATUS, 0, newbot1);
 }
+#endif /* !CONF_BOTL */
 
 /* provide the name of the current level for display by various ports */
 int
@@ -228,20 +287,37 @@
 
 	/* TODO:	Add in dungeon name */
 	if (Is_knox(&u.uz))
+#ifndef CONF_BOTL
 		Sprintf(buf, "%s ", dungeons[u.uz.dnum].dname);
+#else
+		Sprintf(buf, "%s", dungeons[u.uz.dnum].dname);
+#endif
 	else if (In_quest(&u.uz))
+#ifndef CONF_BOTL
 		Sprintf(buf, "Home %d ", dunlev(&u.uz));
+#else
+		Sprintf(buf, "Home %d", dunlev(&u.uz));
+#endif
 	else if (In_endgame(&u.uz))
 		Sprintf(buf,
+#ifndef CONF_BOTL
 			Is_astralevel(&u.uz) ? "Astral Plane " : "End Game ");
+#else
+			Is_astralevel(&u.uz) ? "Astral Plane" : "End Game");
+#endif
 	else {
 		/* ports with more room may expand this one */
+#ifndef CONF_BOTL
 		Sprintf(buf, "Dlvl:%-2d ", depth(&u.uz));
+#else
+		Sprintf(buf, "Dlvl:%-2d", depth(&u.uz));
+#endif
 		ret = 0;
 	}
 	return ret;
 }
 
+#ifndef CONF_BOTL
 STATIC_OVL void
 bot2()
 {
@@ -304,6 +380,514 @@
 	flags.botl = flags.botlx = 0;
 }
 
+#else /* CONF_BOTL is defined */
+
+/* Deallocates memory used by botlbase[botlnum] */
+void 
+deallocbotlparam(botlnum)
+  const int botlnum;
+{
+   if ((botlnum >= 0) && (botlnum < MAX_BOTLINES)) {
+      struct botlparam *botlp = 0;
+      struct botlparam *tmpbotlp = botlbase[botlnum];
+      while (tmpbotlp) {
+	 botlp = tmpbotlp;
+	 tmpbotlp = tmpbotlp->nextparam;
+	 free(botlp->params);
+	 free(botlp);
+      }
+      botlbase[botlnum] = 0;
+   }
+}
+
+/* Frees all the memory taken by botlines */
+void
+free_botlines()
+{
+   int line;
+
+   for (line = 0; line < MAX_BOTLINES; line++) 
+     deallocbotlparam(line);
+}
+ 
+/* flips the chain in botlbase[basenum], because parsebotl() inserts
+   the params in wrong order into the chain */
+void
+flipbotlbase(basenum)
+int basenum;
+{
+   struct botlparam *b1 = 0;
+   
+   if ((basenum < 0) || (basenum >= MAX_BOTLINES)) return;
+
+   while (botlbase[basenum]) {
+      struct botlparam *tmpb = botlbase[basenum]->nextparam;
+      botlbase[basenum]->nextparam = b1;
+      b1 = botlbase[basenum];
+      botlbase[basenum] = tmpb;
+   }
+   botlbase[basenum] = b1;
+}
+
+/* Returns a string variable depending on valchar */
+char *
+botlvaluechar(valchar, buf)
+const char valchar;
+char *buf;
+{
+   int hp = Upolyd ? u.mh : u.uhp;
+   int hpmax = Upolyd ? u.mhmax : u.uhpmax;
+   int cap = near_capacity();
+
+   buf[0] = '\0';
+   
+   switch (valchar) {
+    case '%': /* percent sign */
+        Strcpy(buf, "%");
+        break;
+    case '$': /* gold symbol */
+        Sprintf(buf, "%c", oc_syms[COIN_CLASS]);
+	break;
+    case 'a': /* Player's name */
+  	Strcpy(buf, plname);
+	if('a' <= buf[0] && buf[0] <= 'z') buf[0] += 'A'-'a';
+        break;
+    case 'b': /* Player's rank */
+    	if (Upolyd) {
+	   char mbot[BUFSZ];
+	   int k = 0;
+
+	   Strcpy(mbot, mons[u.umonnum].mname);
+	   while(mbot[k] != 0) {
+	      if ((k == 0 || (k > 0 && mbot[k-1] == ' ')) &&
+		  'a' <= mbot[k] && mbot[k] <= 'z')
+		mbot[k] += 'A' - 'a';
+	      k++;
+	   }
+	   Sprintf(buf, "%s", mbot);
+	} else Sprintf(buf, "%s", rank());
+        break;
+    case 'c': /* current Str */
+      	if (ACURR(A_STR) > 18) {
+	   if (ACURR(A_STR) > STR18(100))
+	     Sprintf(buf,"%2d",ACURR(A_STR)-100);
+	   else if (ACURR(A_STR) < STR18(100))
+	     Sprintf(buf, "18/%02d",ACURR(A_STR)-18);
+	   else
+	     Sprintf(buf,"18/**");
+	} else Sprintf(buf, "%-1d",ACURR(A_STR));
+        break;
+    case 'd': /* current Dex */ 
+        Sprintf(buf, "%-1d", ACURR(A_DEX));
+        break;
+    case 'e': /* current Con */ 
+        Sprintf(buf, "%-1d", ACURR(A_CON));
+        break;
+    case 'f': /* current Int */ 
+        Sprintf(buf, "%-1d", ACURR(A_INT));
+        break;
+    case 'g': /* current Wis */ 
+        Sprintf(buf, "%-1d", ACURR(A_WIS));
+        break;
+    case 'h': /* current Cha */ 
+        Sprintf(buf, "%-1d", ACURR(A_CHA));
+        break;  
+    case 'i': /* current Alignment */
+	Sprintf(buf, (u.ualign.type == A_CHAOTIC) ? "Chaotic" :
+			(u.ualign.type == A_NEUTRAL) ? "Neutral" : "Lawful");
+        break;
+    case 'j': /* dungeon depth */
+        Sprintf(buf, "%-1d", depth(&u.uz));
+        break;
+    case 'k': /* dungeon name */
+        (void) describe_level(buf);
+        break;
+    case 'l': /* gold */
+        Sprintf(buf, "%-1ld",
+#ifndef GOLDOBJ
+		u.ugold
+#else
+		money_cnt(invent)
+#endif
+		);
+        break;
+    case 'm': /* max HP */
+        Sprintf(buf, "%d", hpmax);
+        break;
+    case 'n': /* current HP */
+	if (hp < 0) hp = 0;
+        Sprintf(buf, "%d", hp);
+        break;
+    case 'o': /* max power */
+        Sprintf(buf, "%d", u.uenmax);
+        break;
+    case 'p': /* current power */
+        Sprintf(buf, "%d", u.uen);
+        break;
+    case 'q': /* AC */
+        Sprintf(buf, "%-1d", u.uac);
+        break;  
+    case 'r': /* experience level */
+      	if (Upolyd) Sprintf(buf, "%d", mons[u.umonnum].mlevel);
+#ifdef EXP_ON_BOTL
+	else if(flags.showexp) Sprintf(buf, "%u", u.ulevel);
+#endif
+	else Sprintf(buf, "%u", u.ulevel);
+        break;
+#ifdef EXP_ON_BOTL
+    case 's': /* experience points */
+	if(flags.showexp) Sprintf(buf, "%-1ld", u.uexp);
+        break;
+#endif
+    case 't': /* full experience string */
+ 	if (Upolyd) Sprintf(buf, "HD:%d", mons[u.umonnum].mlevel);
+ #ifdef EXP_ON_BOTL
+ 	else if(flags.showexp) Sprintf(buf, "Xp:%u/%-1ld", u.ulevel,u.uexp);
+ #endif
+ 	else Sprintf(buf, "Exp:%u", u.ulevel);
+        break;    
+    case 'u': /* time value */
+	if(flags.time) Sprintf(buf, "%ld", moves);
+        break;
+    case 'v': /* full time string */
+	if(flags.time) Sprintf(buf, "T:%ld", moves);
+        break;
+    case 'w': /* all misc. flags */
+        if (strcmp(hu_stat[u.uhs], "        "))
+	    Sprintf(buf, "%s", hu_stat[u.uhs]);
+	if(Confusion)	   Strcat(buf, " Conf");
+ 	if(Sick) {
+	   if (u.usick_type & SICK_VOMITABLE)    Strcat(buf, " FoodPois");
+	   if (u.usick_type & SICK_NONVOMITABLE) Strcat(buf, " Ill");
+ 	}
+	if(Blind)	   Strcat(buf, " Blind");
+	if(Stunned)	   Strcat(buf, " Stun");
+	if(Hallucination)  Strcat(buf, " Hallu");
+	if(Slimed)         Strcat(buf, " Slime");
+	if(u.ustuck && !u.uswallow)
+			   Strcat(buf, " Held");
+	if(cap > UNENCUMBERED) {
+	   Strcat(buf, " ");
+	   Strcat(buf, enc_stat[cap]);
+	}
+        break;
+#ifdef SCORE_ON_BOTL
+    case 'x': /* score value */
+	if (flags.showscore) Sprintf(buf, "%ld", botl_score());
+        break;
+    case 'y': /* full score string */
+	if (flags.showscore) Sprintf(buf, "S:%ld", botl_score());
+        break;
+#endif
+    case 'Z': /* Hunger Status */
+	if (strcmp(hu_stat[u.uhs], "        "))	
+	    Sprintf(buf, "%s", hu_stat[u.uhs]);
+        break;
+    case 'Y': /* Confusion status */
+	if (Confusion) Sprintf(buf, "%s", "Conf");
+        break;
+    case 'X': /* FoodPois status */
+	if ((Sick) && (u.usick_type & SICK_VOMITABLE)) 
+	   Sprintf(buf, "%s", "FoodPois");
+        break;
+    case 'W': /*Ill status */
+	if ((Sick) && (u.usick_type & SICK_NONVOMITABLE)) 
+	   Sprintf(buf, "%s", "Ill");
+        break;
+    case 'V': /* Blind status */
+	if (Blind) Sprintf(buf, "%s", "Blind");
+        break;
+    case 'U': /* Stun status */
+	if (Stunned) Sprintf(buf, "%s", "Stun");
+        break;
+    case 'T': /* Hallu status */
+	if (Hallucination) Sprintf(buf, "%s", "Hallu");
+        break;
+    case 'S': /* Slimed status */
+	if (Slimed) Sprintf(buf, "%s", "Slime");
+        break;
+    case 'R': /* Encumbrance status */
+	if (cap>UNENCUMBERED) Sprintf(buf, "%s", enc_stat[cap]);
+        break;    
+    case 'Q': /* Held status */
+	if (u.ustuck && !u.uswallow) Sprintf(buf, "%s", "Held");
+        break;
+    default:;
+   }
+   return buf;
+}
+
+/* Shows the botl on screen */
+void
+showbotl(botlnum)
+const int botlnum;
+{
+    char tmpstr[BUFSZ], tmpbuf[BUFSZ], buf[BUFSZ];
+    struct botlparam *tmpparm;
+    int x = 1;
+    int color = NO_COLOR, attr = ATR_NONE;
+    int do_color_slide = -1;
+
+    if ((botlnum < 0) || (botlnum >= MAX_BOTLINES)) return;
+
+    tmpparm = botlbase[botlnum];
+
+    while (tmpparm) {
+	tmpbuf[0] = tmpstr[0] = buf[0] = '\0';
+
+	switch (tmpparm->bptyp) {
+	    case BOTLPARMTYP_TXT: {
+		curs(WIN_STATUS, x, botlnum);
+		putstr(WIN_STATUS, 0, tmpparm->params);
+		x += strlen(tmpparm->params);
+		break;
+	    }
+	    case BOTLPARMTYP_VAR: {
+		Sprintf(tmpstr, "%%%ss", tmpparm->params);
+		Sprintf(buf, tmpstr, botlvaluechar(tmpparm->valuechar, tmpbuf));
+		curs(WIN_STATUS, x, botlnum);
+		putstr(WIN_STATUS, 0, buf);
+		x += strlen(buf);
+		break;
+	    }
+	    case BOTLPARMTYP_CMD: {
+		switch (tmpparm->valuechar) {
+		    case 'c': { /* Set color and attribute */
+			if (iflags.wc_color && (color != NO_COLOR)) 
+			  term_end_color();
+			term_end_attr(attr);
+
+			if (iflags.wc_color) {
+			    color = tmpparm->parami1;
+			    if (color != NO_COLOR) {
+				term_end_color();
+				term_start_color(color);
+			    }
+			}
+			attr = tmpparm->parami2;
+			term_start_attr(attr);
+			break;
+		    }
+		    case 's': { /* Use color slider */
+			char slidebuf[BUFSZ];
+			long v1, v2;
+			char tmpc;
+			const static int slideri[5] =
+			     {CLR_ORANGE, CLR_YELLOW, CLR_WHITE, 
+			      NO_COLOR, NO_COLOR};
+
+			if (iflags.wc_color && (color != NO_COLOR)) 
+			  term_end_color();
+			term_end_attr(attr);
+
+			if (tmpparm->parami1 <= 0)
+			  v1 = -tmpparm->parami1;
+			else {
+			    tmpc = tmpparm->parami1;
+			    v1 = atoi(botlvaluechar(tmpc, slidebuf));
+			}
+			if (tmpparm->parami2 < 0)
+			  v2 = -tmpparm->parami2;
+			else {
+			    tmpc = tmpparm->parami2;
+			    v2 = atoi(botlvaluechar(tmpc, slidebuf));
+			}
+			if (iflags.wc_color) {
+			    int idx = (v1 * 5) / v2;
+			    if (idx < 0) idx = 0;
+			    if (idx >= 5) idx = 5 - 1;
+			    color = slideri[idx];
+			    if (color != NO_COLOR) {
+				term_end_color();
+				term_start_color(color);
+			    }
+			}
+			attr = ATR_NONE;
+			term_start_attr(attr);
+			break;
+		    }
+		    default: break;
+		}
+		break;
+	    }
+	    default: break;
+	}
+	tmpparm = tmpparm->nextparam;      
+    }
+    if (iflags.wc_color && (color != NO_COLOR)) term_end_color();
+    term_end_attr(attr);
+}
+
+struct botlparam *
+parsebotl_cmd(str)
+char *str;
+{
+    if (str) {
+	struct botlparam *botlp = (struct botlparam *) alloc(sizeof(struct botlparam));
+	char *params = (char *)0;
+
+	botlp->bptyp = BOTLPARMTYP_CMD;
+	botlp->valuechar = str[0];
+	str++;
+	if (*str && (*str == ':')) str++;
+	params = str;
+
+	switch (botlp->valuechar) {
+	    case 'c': { /* Set color and attribute */
+
+		int i, clr = NO_COLOR, atr = ATR_NONE;
+
+		if (*params) {
+		    for (i = 0; i < SIZE(colornames); i++)
+		      if (strstri(params, colornames[i].name)) {
+			  clr = colornames[i].color;
+			  break;
+		      }
+		    if ((i == SIZE(colornames)) && 
+			(*params >= '0' && *params <= '9')) {
+			clr = colornames[atoi(params) % SIZE(colornames)].color;
+		    }
+
+		    while (*params && (*params != ';')) params++;
+		    if (*params) {
+			for (i = 0; i < SIZE(attrnames); i++)
+			  if (strstri(params, attrnames[i].name)) {
+			      atr = attrnames[i].attr;
+			      break;
+			  }
+			if ((i == SIZE(attrnames)) && 
+			    (*params >= '0' && *params <= '9')) {
+			    atr = attrnames[atoi(params) % SIZE(attrnames)].attr;
+			}
+		    }
+		}
+
+		botlp->parami1 = clr;
+		botlp->parami2 = atr;
+		botlp->params = (char *)0;
+
+		break;
+	    }
+	    case 's': { /* Use color slide */
+		if (*params) {
+		    if (*params >= '0' && *params <= '9')
+		      botlp->parami1 = -atoi(params);
+		    else
+		      botlp->parami1 = *params;
+		}
+		while (*params && (*params != ';')) params++;
+		params++;
+		if (*params) {
+		    if (*params >= '0' && *params <= '9')
+		      botlp->parami2 = -atoi(params);
+		    else
+		      botlp->parami2 = *params;
+		}
+		break;
+	    }
+	    default:  break;
+	}
+
+	return botlp;
+    } else return 0;
+}
+
+/* Parse "%1.4s %d Yuck! %Z" or something like that into botlbase[botlnum] */
+void
+parsebotl(str, botlnum)
+   char *str;
+   int botlnum;
+{
+   char params[BUFSZ];
+   int params_pos = 0;
+   boolean hasquotes = FALSE;
+   struct botlparam *botlp = (struct botlparam *)0;
+
+   if ((botlnum < 0) || (botlnum >= MAX_BOTLINES)) return;
+
+   if (botlbase[botlnum]) deallocbotlparam(botlnum);
+
+   if (str && (strlen(str) > 1) 
+       && ((*str == '"') || (*str == '\'')) 
+       && (*str == str[strlen(str)-1])) {
+      hasquotes = TRUE;
+      str++;
+   }
+
+   while (*str) {
+       if (hasquotes && (*(str+1) == '\0')) break;
+       if (*str == '%') {
+	   /* It's a variable or a command */
+	   str++;
+	   if (*str == '{') {
+	       /* It's a command */
+	       str++;
+	       params[0] = '\0';
+	       params_pos = 0;
+	       while (*str && (*str != '}')) {
+		   params[params_pos++] = *str;
+		   params[params_pos] = '\0';
+		   str++;
+	       }
+	       if (*str && (*str == '}')) str++;
+	       botlp = parsebotl_cmd(params);
+	       if (botlp) {
+		   botlp->nextparam = botlbase[botlnum];
+		   botlbase[botlnum] = botlp;
+	       }
+	   } else if (*str) {
+	       /* It's a variable */
+	       params[0] = '\0';
+	       params_pos = 0;
+	       while (*str && !isalpha(*str) && (*str != '%') && (*str != '$')) {
+		   params[params_pos++] = *str;
+		   params[params_pos] = '\0';
+		   str++;
+	       }
+	       botlp = (struct botlparam *) alloc(sizeof(struct botlparam));
+	       botlp->bptyp = BOTLPARMTYP_VAR;
+	       botlp->params = (char *) alloc(strlen(params)+1);
+	       Strcpy(botlp->params, params);
+	       botlp->valuechar = *str;
+	       str++;
+	       botlp->nextparam = botlbase[botlnum];
+	       botlbase[botlnum] = botlp;	       
+	   }
+       } else {
+	   /* It's printable text */
+	   params[0] = '\0';
+	   params_pos = 0;
+	   while (*str && (*str != '%')) {
+	       params[params_pos++] = *str;
+	       params[params_pos] = '\0';
+	       str++;
+	   }
+	   botlp = (struct botlparam *) alloc(sizeof(struct botlparam));
+	   botlp->bptyp = BOTLPARMTYP_TXT;
+	   botlp->params = (char *) alloc(strlen(params)+1);
+	   Strcpy(botlp->params, params);
+	   botlp->nextparam = botlbase[botlnum];
+	   botlbase[botlnum] = botlp;
+       }
+   }
+   flipbotlbase(botlnum);
+}
+
+void
+bot()
+{
+    int y;
+
+    for (y = 0; y < MAX_BOTLINES; y++) {
+	curs(WIN_STATUS, 1, y);
+	showbotl(y);
+    }
+
+    flags.botl = flags.botlx = 0;
+}
+    
+#endif /* CONF_BOTL */
+
 #endif /* OVL0 */
 
 /*botl.c*/
diff -r -X /home/roderick/.diff-exclude -uN base.patchable/src/options.c work.conf-botl/src/options.c
--- base.patchable/src/options.c	2003-03-18 20:58:07.000000000 -0500
+++ work.conf-botl/src/options.c	2003-03-20 17:04:40.000000000 -0500
@@ -304,6 +304,10 @@
 #ifdef MSDOS
 	{ "soundcard", "type of sound card to use", 20, SET_IN_FILE },
 #endif
+#ifdef CONF_BOTL
+	{ "statline1", "the first status line",  COLNO,   SET_IN_FILE },
+	{ "statline2", "the second status line", COLNO,   SET_IN_FILE },
+#endif
 	{ "suppress_alert", "suppress alerts about version-specific features",
 						8, SET_IN_GAME },
 	{ "tile_width", "width of tiles", 20, DISP_IN_GAME},	/*WC*/
@@ -502,6 +506,10 @@
 	flags.initgend = -1;
 	flags.initalign = -1;
 
+#ifdef CONF_BOTL
+	parsebotl("%-.10a the %-12b St:%c Dx:%d Co:%e In:%f Wi:%g Ch:%h  %i  %y", 0);
+	parsebotl("%-7k %$:%-2l HP:%n(%m) Pw:%p(%o) AC:%-2q %t %v %w", 1);
+#endif
 	/* Set the default monster and object class symbols.  Don't use */
 	/* memcpy() --- sizeof char != sizeof uchar on some machines.	*/
 	for (i = 0; i < MAXOCLASSES; i++)
@@ -1066,6 +1074,30 @@
 		return;
 	}
 
+#ifdef CONF_BOTL
+	fullname = "statline1";
+	if (match_optname(opts, fullname, 9, TRUE)) {
+		if (negated) bad_negation(fullname, FALSE);
+		else if ((op = string_for_env_opt(fullname, opts, FALSE)) != 0) {
+		    char tempbotlinebuf[BUFSZ];
+
+		    nmcpy(tempbotlinebuf, op, BUFSZ);
+		    parsebotl(tempbotlinebuf, 0);
+		}
+		return;
+	}
+	fullname = "statline2";
+	if (match_optname(opts, fullname, 9, TRUE)) {
+		if (negated) bad_negation(fullname, FALSE);
+		else if ((op = string_for_env_opt(fullname, opts, FALSE)) != 0) {
+		     char tempbotlinebuf[BUFSZ];
+
+		     nmcpy(tempbotlinebuf, op, BUFSZ);
+		     parsebotl(tempbotlinebuf, 1);
+		}
+		return;
+	}
+#endif /* CONF_BOTL */
 	fullname = "runmode";
 	if (match_optname(opts, fullname, 4, TRUE)) {
 		if (negated) {
diff -r -X /home/roderick/.diff-exclude -uN base.patchable/src/save.c work.conf-botl/src/save.c
--- base.patchable/src/save.c	2003-02-23 09:43:30.000000000 -0500
+++ work.conf-botl/src/save.c	2003-03-20 17:04:40.000000000 -0500
@@ -1001,6 +1001,9 @@
 	free_animals();
 	free_oracles();
 	freefruitchn();
+#ifdef CONF_BOTL
+	free_botlines();
+#endif
 	freenames();
 	free_waterlevel();
 	free_dungeons();
diff -r -X /home/roderick/.diff-exclude -uN base.patchable/util/makedefs.c work.conf-botl/util/makedefs.c
--- base.patchable/util/makedefs.c	2003-03-20 16:41:56.000000000 -0500
+++ work.conf-botl/util/makedefs.c	2003-03-20 17:06:35.000000000 -0500
@@ -752,6 +752,12 @@
 		/*
 		 * patch list patch point; see $top/README.patchable
 		 */
+#ifdef CONF_BOTL
+    	    	"patch: configurable status lines v2.1-patchable",
+#endif
+		/*
+		 * patch list patch point; see $top/README.patchable
+		 */
 		/*
 		 * patch list patch point; see $top/README.patchable
 		 */
