/*
 * Copyright (c) 1989 James E. Wilson, Robert A. Koeneke
 *
 * This software may be copied and distributed for educational, research, and
 * not for profit purposes provided that this copyright and statement are
 * included in all such copies.
 */

#include "files.hpp"
#include "files.h"

#include "cave.hpp"
#include "cave_type.hpp"
#include "corrupt.hpp"
#include "cmd3.hpp"
#include "dungeon_info_type.hpp"
#include "feature_type.hpp"
#include "hiscore.hpp"
#include "hook_chardump_in.hpp"
#include "hooks.hpp"
#include "init1.hpp"
#include "levels.hpp"
#include "loadsave.h"
#include "loadsave.hpp"
#include "mimic.hpp"
#include "monster2.hpp"
#include "monster3.hpp"
#include "monster_ego.hpp"
#include "monster_race.hpp"
#include "monster_type.hpp"
#include "notes.hpp"
#include "object1.hpp"
#include "object2.hpp"
#include "object_kind.hpp"
#include "options.hpp"
#include "player_class.hpp"
#include "player_race.hpp"
#include "player_race_mod.hpp"
#include "player_spec.hpp"
#include "player_type.hpp"
#include "skills.hpp"
#include "spells2.hpp"
#include "store_info_type.hpp"
#include "store_type.hpp"
#include "tables.hpp"
#include "town_type.hpp"
#include "trap_type.hpp"
#include "util.hpp"
#include "util.h"
#include "variable.h"
#include "variable.hpp"
#include "wilderness_map.hpp"
#include "wilderness_type_info.hpp"
#include "xtra1.hpp"
#include "z-rand.hpp"

#include <memory>
#include <unordered_set>

/*
 * Extract the first few "tokens" from a buffer
 *
 * This function uses "colon" and "slash" and delim arg as the delimeter characters.
 *
 * We never extract more than "num" tokens.  The "last" token may include
 * "delimeter" characters, allowing the buffer to include a "string" token.
 *
 * We save pointers to the tokens in "tokens", and return the number found.
 *
 * Hack -- Attempt to handle the 'c' character formalism
 *
 * Hack -- An empty buffer, or a final delimeter, yields an "empty" token.
 *
 * Hack -- We will always extract at least one token
 */
s16b tokenize(char *buf, s16b num, char **tokens, char delim1, char delim2)
{
	int i = 0;

	char *s = buf;


	/* Process */
	while (i < num - 1)
	{
		char *t;

		/* Scan the string */
		for (t = s; *t; t++)
		{
			/* Found a delimiter */
			if ((*t == delim1) || (*t == delim2)) break;

			/* Handle single quotes */
			if (*t == '\'')
			{
				/* Advance */
				t++;

				/* Handle backslash */
				if (*t == '\\') t++;

				/* Require a character */
				if (!*t) break;

				/* Advance */
				t++;

				/* Hack -- Require a close quote */
				if (*t != '\'') *t = '\'';
			}

			/* Handle back-slash */
			if (*t == '\\') t++;
		}

		/* Nothing left */
		if (!*t) break;

		/* Nuke and advance */
		*t++ = '\0';

		/* Save the token */
		tokens[i++] = s;

		/* Advance */
		s = t;
	}

	/* Save the token */
	tokens[i++] = s;

	/* Number found */
	return (i);
}



/*
 * Parse a sub-file of the "extra info" (format shown below)
 *
 * Each "action" line has an "action symbol" in the first column,
 * followed by a colon, followed by some command specific info,
 * usually in the form of "tokens" separated by colons or slashes.
 *
 * Blank lines, lines starting with white space, and lines starting
 * with pound signs ("#") are ignored (as comments).
 *
 * Note the use of "tokenize()" to allow the use of both colons and
 * slashes as delimeters, while still allowing final tokens which
 * may contain any characters including "delimiters".
 *
 * Note the use of "strtol()" to allow all "integers" to be encoded
 * in decimal, hexidecimal, or octal form.
 *
 * Note that "monster zero" is used for the "player" attr/char, "object
 * zero" will be used for the "stack" attr/char, and "feature zero" is
 * used for the "nothing" attr/char.
 *
 * Parse another file recursively, see below for details
 *   %:<filename>
 *
 * Specify the attr/char values for "monsters" by race index
 *   R:<num>:<a>:<c>
 *
 * Specify the attr/char values for "objects" by kind index
 *   K:<num>:<a>:<c>
 *
 * Specify the attr/char values for "features" by feature index
 *   F:<num>:<a>:<c>
 *
 * Specify the attr/char values for "stores" by store index
 *   B:<num>:<a>:<c>
 *
 * Specify the attr/char values for unaware "objects" by kind tval
 *   U:<tv>:<a>:<c>
 *
 * Specify the attr/char values for inventory "objects" by kind tval
 *   E:<tv>:<a>:<c>
 *
 * Define a macro action, given an encoded macro action
 *   A:<str>
 *
 * Create a normal macro, given an encoded macro trigger
 *   P:<str>
 *
 * Create a command macro, given an encoded macro trigger
 *   C:<str>
 *
 * Create a keyset mapping
 *   S:<key>:<key>:<dir>
 *
 * Turn an option off, given its name
 *   X:<str>
 *
 * Turn an option on, given its name
 *   Y:<str>
 *
 * Specify visual information, given an index, and some data
 *   V:<num>:<kv>:<rv>:<gv>:<bv>
 *
 * Specify squelch settings
 *   Q:<num>:<squelch>
 */
errr process_pref_file_aux(char *buf)
{
	int i, j, n1, n2;

	char *zz[16];


	/* Skip "empty" lines */
	if (!buf[0]) return (0);

	/* Skip "blank" lines */
	if (isspace(buf[0])) return (0);

	/* Skip comments */
	if (buf[0] == '#') return (0);

	/* Require "?:*" format */
	if (buf[1] != ':') return (1);


	/* Process "%:<fname>" */
	if (buf[0] == '%')
	{
		/* Attempt to Process the given file */
		return (process_pref_file(buf + 2));
	}


	/* Process "R:<num>:<a>/<c>" -- attr/char for monster races */
	if (buf[0] == 'R')
	{
		if (tokenize(buf + 2, 3, zz, ':', '/') == 3)
		{
			monster_race *r_ptr;
			i = (huge)strtol(zz[0], NULL, 0);
			n1 = strtol(zz[1], NULL, 0);
			n2 = strtol(zz[2], NULL, 0);
			if (i >= max_r_idx) return (1);
			r_ptr = &r_info[i];
			if (n1) r_ptr->x_attr = n1;
			if (n2)
			{
				r_ptr->x_char = n2;
			}
			return (0);
		}
	}


	/* Process "G:<type>:<num>:<a>/<c>" -- attr/char for overlay graphics */
	if (buf[0] == 'G')
	{
		/* Process "G:M:<num>:<a>/<c>" -- attr/char for ego monsters */
		if (buf[2] == 'M')
		{
			if (tokenize(buf + 4, 3, zz, ':', '/') == 3)
			{
				monster_ego *re_ptr;
				i = (huge)strtol(zz[0], NULL, 0);
				n1 = strtol(zz[1], NULL, 0);
				n2 = strtol(zz[2], NULL, 0);
				if (i >= max_re_idx) return (1);
				re_ptr = &re_info[i];
				if (n1) re_ptr->g_attr = n1;
				if (n2)
				{
					re_ptr->g_char = n2;
				}
				return (0);
			}
		}

		/* Process "G:P:<num>:<a>/<c>" -- attr/char for race modifiers */
		if (buf[2] == 'P')
		{
			if (tokenize(buf + 4, 3, zz, ':', '/') == 3)
			{
				player_race_mod *rmp_ptr;
				i = (huge)strtol(zz[0], NULL, 0);
				n1 = strtol(zz[1], NULL, 0);
				n2 = strtol(zz[2], NULL, 0);
				if (i >= max_rmp_idx) return (1);
				rmp_ptr = &race_mod_info[i];
				if (n1) rmp_ptr->g_attr = n1;
				if (n2)
				{
					rmp_ptr->g_char = n2;
				}
				return (0);
			}
		}

		/* Process "G:T:<num>:<a>/<c>" -- attr/char for traps */
		if (buf[2] == 'T')
		{
			if (tokenize(buf + 4, 3, zz, ':', '/') == 3)
			{
				trap_type *t_ptr;
				i = (huge)strtol(zz[0], NULL, 0);
				n1 = strtol(zz[1], NULL, 0);
				n2 = strtol(zz[2], NULL, 0);
				if (i >= max_t_idx) return (1);
				t_ptr = &t_info[i];
				if (n1) t_ptr->g_attr = n1;
				if (n2)
				{
					t_ptr->g_char = n2;
				}
				return (0);
			}
		}
	}


	/* Process "K:<num>:<a>/<c>"  -- attr/char for object kinds */
	else if (buf[0] == 'K')
	{
		if (tokenize(buf + 2, 3, zz, ':', '/') == 3)
		{
			object_kind *k_ptr;
			i = (huge)strtol(zz[0], NULL, 0);
			n1 = strtol(zz[1], NULL, 0);
			n2 = strtol(zz[2], NULL, 0);
			if (i >= max_k_idx) return (1);
			k_ptr = &k_info[i];
			if (n1) k_ptr->x_attr = n1;
			if (n2)
			{
				k_ptr->x_char = n2;
			}
			return (0);
		}
	}


	/* Process "F:<num>:<a>/<c>" -- attr/char for terrain features */
	else if (buf[0] == 'F')
	{
		if (tokenize(buf + 2, 3, zz, ':', '/') == 3)
		{
			feature_type *f_ptr;
			i = (huge)strtol(zz[0], NULL, 0);
			n1 = strtol(zz[1], NULL, 0);
			n2 = strtol(zz[2], NULL, 0);
			if (i >= max_f_idx) return (1);
			f_ptr = &f_info[i];
			if (n1) f_ptr->x_attr = n1;
			if (n2)
			{
				f_ptr->x_char = n2;
			}
			return (0);
		}
	}

	/* Process "B:<num>:<a>/<c>" -- attr/char for stores */
	else if (buf[0] == 'B')
	{
		if (tokenize(buf + 2, 3, zz, ':', '/') == 3)
		{
			store_info_type *st_ptr;
			i = (huge)strtol(zz[0], NULL, 0);
			n1 = strtol(zz[1], NULL, 0);
			n2 = strtol(zz[2], NULL, 0);
			if (i >= max_st_idx) return (1);
			st_ptr = &st_info[i];
			if (n1) st_ptr->x_attr = n1;
			if (n2) st_ptr->x_char = n2;
			return (0);
		}
	}

	/* Process "S:<num>:<a>/<c>" -- attr/char for special things */
	else if (buf[0] == 'S')
	{
		if (tokenize(buf + 2, 3, zz, ':', '/') == 3)
		{
			j = (byte)strtol(zz[0], NULL, 0);
			n1 = strtol(zz[1], NULL, 0);
			n2 = strtol(zz[2], NULL, 0);
			misc_to_attr[j] = n1;
			misc_to_char[j] = n2;
			return (0);
		}
	}

	/* Process "U:<tv>:<a>/<c>" -- attr/char for unaware items */
	else if (buf[0] == 'U')
	{
		if (tokenize(buf + 2, 3, zz, ':', '/') == 3)
		{
			j = (huge)strtol(zz[0], NULL, 0);
			n1 = strtol(zz[1], NULL, 0);
			n2 = strtol(zz[2], NULL, 0);
			for (i = 1; i < max_k_idx; i++)
			{
				object_kind *k_ptr = &k_info[i];
				if (k_ptr->tval == j)
				{
					if (n1) k_ptr->d_attr = n1;
					if (n2) k_ptr->d_char = n2;
				}
			}
			return (0);
		}
	}


	/* Process "E:<tv>:<a>" -- attribute for inventory objects */
	else if (buf[0] == 'E')
	{
		if (tokenize(buf + 2, 2, zz, ':', '/') == 2)
		{
			j = (byte)strtol(zz[0], NULL, 0) % 128;
			n1 = strtol(zz[1], NULL, 0);
			if (n1) tval_to_attr[j] = n1;
			return (0);
		}
	}


	/* Process "A:<str>" -- save an "action" for later */
	else if (buf[0] == 'A')
	{
		text_to_ascii(macro__buf, buf + 2);
		return (0);
	}

	/* Process "P:<str>" -- normal macro */
	else if (buf[0] == 'P')
	{
		char tmp[1024];
		text_to_ascii(tmp, buf + 2);
		macro_add(tmp, macro__buf);
		return (0);
	}

	/* Process "L:<num>:<trigger>:<descr> -- extended command macro */
	else if (buf[0] == 'L')
	{
		switch (tokenize(buf + 2, 3, zz, ':', 0))
		{
		case 3:
			cli_add(zz[0], zz[1], zz[2]);
			return 0;
		case 2:
			cli_add(zz[0], zz[1], 0);
			return 0;
		default:
			return 1;
		}
	}

	/* Process "C:<str>" -- create keymap */
	else if (buf[0] == 'C')
	{
		int mode;

		char tmp[1024];

		if (tokenize(buf + 2, 2, zz, ':', '/') != 2) return (1);

		mode = strtol(zz[0], NULL, 0);
		if ((mode < 0) || (mode >= KEYMAP_MODES)) return (1);

		text_to_ascii(tmp, zz[1]);
		if (!tmp[0] || tmp[1]) return (1);
		i = (byte)(tmp[0]);

		free(keymap_act[mode][i]);

		keymap_act[mode][i] = strdup(macro__buf);

		return (0);
	}


	/* Process "V:<num>:<kv>:<rv>:<gv>:<bv>" -- visual info */
	else if (buf[0] == 'V')
	{
		if (tokenize(buf + 2, 5, zz, ':', '/') == 5)
		{
			i = (byte)strtol(zz[0], NULL, 0);
			angband_color_table[i][0] = (byte)strtol(zz[1], NULL, 0);
			angband_color_table[i][1] = (byte)strtol(zz[2], NULL, 0);
			angband_color_table[i][2] = (byte)strtol(zz[3], NULL, 0);
			angband_color_table[i][3] = (byte)strtol(zz[4], NULL, 0);
			return (0);
		}
	}
	/* set macro trigger names and a template */
	/* Process "T:<trigger>:<keycode>:<shift-keycode>" */
	/* Process "T:<template>:<modifier chr>:<modifier name>:..." */
	else if (buf[0] == 'T')
	{
		int len, tok;
		tok = tokenize(buf + 2, 2 + MAX_MACRO_MOD, zz, ':', '/');
		if (tok >= 4)
		{
			int i;
			int num;

			if (macro_template != NULL)
			{
				delete[] macro_template;
				macro_template = NULL;

				for (i = 0; i < max_macrotrigger; i++)
				{
					delete[] macro_trigger_name[i];
					macro_trigger_name[i] = nullptr;
				}
				max_macrotrigger = 0;
			}

			if (*zz[0] == '\0') return 0;  /* clear template */
			num = strlen(zz[1]);
			if (2 + num != tok) return 1;  /* error */

			len = strlen(zz[0]) + 1 + num + 1;
			for (i = 0; i < num; i++)
				len += strlen(zz[2 + i]) + 1;
			macro_template = new char[len];

			strcpy(macro_template, zz[0]);
			macro_modifier_chr =
			        macro_template + strlen(macro_template) + 1;
			strcpy(macro_modifier_chr, zz[1]);
			macro_modifier_name[0] =
			        macro_modifier_chr + strlen(macro_modifier_chr) + 1;
			for (i = 0; i < num; i++)
			{
				strcpy(macro_modifier_name[i], zz[2 + i]);
				macro_modifier_name[i + 1] = macro_modifier_name[i] +
				                             strlen(macro_modifier_name[i]) + 1;
			}
		}
		else if (tok >= 2)
		{
			int m;
			char *t, *s;
			if (max_macrotrigger >= MAX_MACRO_TRIG)
			{
				msg_print("Too many macro triggers!");
				return 1;
			}
			m = max_macrotrigger;
			max_macrotrigger++;

			len = strlen(zz[0]) + 1 + strlen(zz[1]) + 1;
			if (tok == 3)
				len += strlen(zz[2]) + 1;
			macro_trigger_name[m] = new char[len];

			t = macro_trigger_name[m];
			s = zz[0];
			while (*s)
			{
				if ('\\' == *s) s++;
				*t++ = *s++;
			}
			*t = '\0';

			macro_trigger_keycode[0][m] = macro_trigger_name[m] +
			                              strlen(macro_trigger_name[m]) + 1;
			strcpy(macro_trigger_keycode[0][m], zz[1]);
			if (tok == 3)
			{
				macro_trigger_keycode[1][m] = macro_trigger_keycode[0][m] +
				                              strlen(macro_trigger_keycode[0][m]) + 1;
				strcpy(macro_trigger_keycode[1][m], zz[2]);
			}
			else
			{
				macro_trigger_keycode[1][m] = macro_trigger_keycode[0][m];
			}
		}
		return 0;
	}

	/* Process "X:<str>" -- turn option off */
	else if (buf[0] == 'X')
	{
		for (i = 0; option_info[i].o_desc; i++)
		{
			if (option_info[i].o_var &&
			                option_info[i].o_text &&
			                streq(option_info[i].o_text, buf + 2))
			{
				(*option_info[i].o_var) = FALSE;
				return (0);
			}
		}
	}

	/* Process "Y:<str>" -- turn option on */
	else if (buf[0] == 'Y')
	{
		for (i = 0; option_info[i].o_desc; i++)
		{
			if (option_info[i].o_var &&
			                option_info[i].o_text &&
			                streq(option_info[i].o_text, buf + 2))
			{
				(*option_info[i].o_var) = TRUE;
				return (0);
			}
		}
	}

	/* Process "W:<win>:<flag>:<value>" -- window flags */
	else if (buf[0] == 'W')
	{
		int win, flag, value;

		if (tokenize(buf + 2, 3, zz, ':', '/') == 3)
		{
			win = strtol(zz[0], NULL, 0);
			flag = strtol(zz[1], NULL, 0);
			value = strtol(zz[2], NULL, 0);

			/* Ignore illegal windows */
			/* Hack -- Ignore the main window */
			if ((win <= 0) || (win >= ANGBAND_TERM_MAX)) return (1);

			/* Ignore illegal flags */
			if ((flag < 0) || (flag >= 32)) return (1);

			/* Require a real flag */
			if (window_flag_desc[flag])
			{
				if (value)
				{
					/* Turn flag on */
					window_flag[win] |= (1L << flag);
				}
				else
				{
					/* Turn flag off */
					window_flag[win] &= ~(1L << flag);
				}
			}

			/* Success */
			return (0);
		}
	}

	/*  Process "Q:<num>:<squelch>" -- item squelch flags */
	else if (buf[0] == 'Q')
	{
		/* This option isn't used anymore */
		return (0);
	}
	/* Failure */
	return (1);
}


/*
 * Helper function for "process_pref_file()"
 *
 * Input:
 *   v: output buffer array
 *   f: final character
 *
 * Output:
 *   result
 */
static cptr process_pref_file_expr(char **sp, char *fp)
{
	cptr v;

	char *b;
	char *s;

	char b1 = '[';
	char b2 = ']';

	char f = ' ';

	/* Initial */
	s = (*sp);

	/* Skip spaces */
	while (isspace(*s)) s++;

	/* Save start */
	b = s;

	/* Default */
	v = "?o?o?";

	/* Analyze */
	if (*s == b1)
	{
		const char *p;
		const char *t;

		/* Skip b1 */
		s++;

		/* First */
		t = process_pref_file_expr(&s, &f);

		/* Oops */
		if (!*t)
		{
			/* Nothing */
		}

		/* Function: IOR */
		else if (streq(t, "IOR"))
		{
			v = "0";
			while (*s && (f != b2))
			{
				t = process_pref_file_expr(&s, &f);
				if (*t && !streq(t, "0")) v = "1";
			}
		}

		/* Function: AND */
		else if (streq(t, "AND"))
		{
			v = "1";
			while (*s && (f != b2))
			{
				t = process_pref_file_expr(&s, &f);
				if (*t && streq(t, "0")) v = "0";
			}
		}

		/* Function: NOT */
		else if (streq(t, "NOT"))
		{
			v = "1";
			while (*s && (f != b2))
			{
				t = process_pref_file_expr(&s, &f);
				if (*t && !streq(t, "0")) v = "0";
			}
		}

		/* Function: EQU */
		else if (streq(t, "EQU"))
		{
			v = "1";
			if (*s && (f != b2))
			{
				t = process_pref_file_expr(&s, &f);
			}
			while (*s && (f != b2))
			{
				p = t;
				t = process_pref_file_expr(&s, &f);
				if (*t && !streq(p, t)) v = "0";
			}
		}

		/* Function: LEQ */
		else if (streq(t, "LEQ"))
		{
			v = "1";
			if (*s && (f != b2))
			{
				t = process_pref_file_expr(&s, &f);
			}
			while (*s && (f != b2))
			{
				p = t;
				t = process_pref_file_expr(&s, &f);
				if (*t && (strcmp(p, t) > 0)) v = "0";
			}
		}

		/* Function: GEQ */
		else if (streq(t, "GEQ"))
		{
			v = "1";
			if (*s && (f != b2))
			{
				t = process_pref_file_expr(&s, &f);
			}
			while (*s && (f != b2))
			{
				p = t;
				t = process_pref_file_expr(&s, &f);
				if (*t && (strcmp(p, t) < 0)) v = "0";
			}
		}

		/* Function: LEQN */
		else if (streq(t, "LEQN"))
		{
			int n = 0;
			v = "1";
			if (*s && (f != b2))
			{
				t = process_pref_file_expr(&s, &f);
				n = atoi(t);
			}
			while (*s && (f != b2))
			{
				p = t;
				t = process_pref_file_expr(&s, &f);
				if (*t && (atoi(t) < n)) v = "0";
			}
		}

		/* Function: GEQN */
		else if (streq(t, "GEQN"))
		{
			int n = 0;
			v = "1";
			if (*s && (f != b2))
			{
				t = process_pref_file_expr(&s, &f);
				n = atoi(t);
			}
			while (*s && (f != b2))
			{
				p = t;
				t = process_pref_file_expr(&s, &f);
				if (*t && (atoi(t) > n)) v = "0";
			}
		}

		/* Function SKILL */
		else if (streq(t, "SKILL"))
		{
			static char skill_val[4*sizeof(int) + 1];
			s16b skill = -1;
			v = "0";
			while (*s && (f != b2))
			{
				t = process_pref_file_expr(&s, &f);
				if (*t) skill = find_skill_i(t);
			}
			if (skill > 0)
			{
				sprintf(skill_val, "%d", (int)get_skill(skill));
				v = skill_val;
			}
		}

		/* Oops */
		else
		{
			while (*s && (f != b2))
			{
				t = process_pref_file_expr(&s, &f);
			}
		}

		/* Verify ending */
		if (f != b2) v = "?x?x?";

		/* Extract final and Terminate */
		if ((f = *s) != '\0') * s++ = '\0';
	}

	/* Other */
	else
	{
		/* Accept all printables except spaces and brackets */
		while (isprint(*s) && !strchr(" []", *s)) ++s;

		/* Extract final and Terminate */
		if ((f = *s) != '\0') * s++ = '\0';

		/* Variable */
		if (*b == '$')
		{
			/* System */
			if (streq(b + 1, "SYS"))
			{
				v = ANGBAND_SYS;
			}

			/* Race */
			else if (streq(b + 1, "RACE"))
			{
				v = rp_ptr->title;
			}

			/* Race */
			else if (streq(b + 1, "RACEMOD"))
			{
				v = rmp_ptr->title;
			}

			/* Class */
			else if (streq(b + 1, "CLASS"))
			{
				v = spp_ptr->title;
			}

			/* Player */
			else if (streq(b + 1, "PLAYER"))
			{
				v = player_base;
			}
		}

		/* Constant */
		else
		{
			v = b;
		}
	}

	/* Save */
	(*fp) = f;

	/* Save */
	(*sp) = s;

	/* Result */
	return (v);
}




/*
 * Process the "user pref file" with the given name
 *
 * See the function above for a list of legal "commands".
 *
 * We also accept the special "?" and "%" directives, which
 * allow conditional evaluation and filename inclusion.
 */
errr process_pref_file(cptr name)
{
	FILE *fp;

	char buf[1024];

	int num = -1;

	errr err = 0;

	bool_ bypass = FALSE;

	/* Build the filename -- Allow users to override system pref files */
	path_build(buf, 1024, ANGBAND_DIR_USER, name);

	/* Open the file */
	fp = my_fopen(buf, "r");

	/* No such file -- Try system pref file */
	if (!fp)
	{
		/* Build the pathname, this time using the system pref directory */
		path_build(buf, 1024, ANGBAND_DIR_PREF, name);

		/* Open the file */
		fp = my_fopen(buf, "r");

		/* Failed again */
		if (!fp) return ( -1);
	}


	/* Process the file */
	while (0 == my_fgets(fp, buf, 1024))
	{
		/* Count lines */
		num++;


		/* Skip "empty" lines */
		if (!buf[0]) continue;

		/* Skip "blank" lines */
		if (isspace(buf[0])) continue;

		/* Skip comments */
		if (buf[0] == '#') continue;


		/* Process "?:<expr>" */
		if ((buf[0] == '?') && (buf[1] == ':'))
		{
			char f;
			cptr v;
			char *s;

			/* Start */
			s = buf + 2;

			/* Parse the expr */
			v = process_pref_file_expr(&s, &f);

			/* Set flag */
			bypass = (streq(v, "0") ? TRUE : FALSE);

			/* Continue */
			continue;
		}

		/* Apply conditionals */
		if (bypass) continue;


		/* Process "%:<file>" */
		if (buf[0] == '%')
		{
			/* Process that file if allowed */
			(void)process_pref_file(buf + 2);

			/* Continue */
			continue;
		}


		/* Process the line */
		err = process_pref_file_aux(buf);

		/* Oops */
		if (err) break;
	}


	/* Error */
	if (err)
	{
		/* Useful error message */
		msg_format("Error %d in line %d of file '%s'.", err, num, name);
		msg_format("Parsing '%s'", buf);
	}

	/* Close the file */
	my_fclose(fp);

	/* Result */
	return (err);
}




/*
 * Print long number with header at given row, column
 * Use the color for the number, not the header
 */
static void prt_lnum(cptr header, s32b num, int row, int col, byte color)
{
	int len = strlen(header);
	char out_val[32];

	put_str(header, row, col);
	(void)sprintf(out_val, "%9ld", (long)num);
	c_put_str(color, out_val, row, col + len);
}


/*
 * Print number with header at given row, column
 */
static void prt_num(cptr header, int num, int row, int col, byte color,
                    cptr space)
{
	int len = strlen(header);
	char out_val[32];

	put_str(header, row, col);
	put_str(space, row, col + len);
	(void)sprintf(out_val, "%6ld", (long)num);
	c_put_str(color, out_val, row, col + len + strlen(space));
}


/*
 * Print str with header at given row, column
 */
static void prt_str(cptr header, cptr str, int row, int col, byte color)
{
	int len = strlen(header);
	char out_val[32];

	put_str(header, row, col);
	put_str("   ", row, col + len);
	(void)sprintf(out_val, "%6s", str);
	c_put_str(color, out_val, row, col + len + 3);
}


/*
 * Prints the following information on the screen.
 *
 * For this to look right, the following should be spaced the
 * same as in the prt_lnum code... -CFT
 */
static void display_player_middle(void)
{
	int show_tohit = p_ptr->dis_to_h;
	int show_todam = p_ptr->dis_to_d;

	object_type *o_ptr = &p_ptr->inventory[INVEN_WIELD];
	char num[7];
	byte color;
	int speed;


	/* Hack -- add in weapon info if known */
	if (object_known_p(o_ptr)) show_tohit = p_ptr->dis_to_h + p_ptr->to_h_melee + o_ptr->to_h;
	else show_tohit = p_ptr->dis_to_h + p_ptr->to_h_melee;
	if (object_known_p(o_ptr)) show_todam = p_ptr->dis_to_d + p_ptr->to_d_melee + o_ptr->to_d;
	else show_todam = p_ptr->dis_to_d + p_ptr->to_d_melee;

	/* Dump the bonuses to hit/dam */
	prt_num("+ To Melee Hit   ", show_tohit, 9, 1, TERM_L_BLUE, "   ");
	prt_num("+ To Melee Damage", show_todam, 10, 1, TERM_L_BLUE, "   ");

	o_ptr = &p_ptr->inventory[INVEN_BOW];

	/* Hack -- add in weapon info if known */
	if (object_known_p(o_ptr)) show_tohit = p_ptr->dis_to_h + p_ptr->to_h_ranged + o_ptr->to_h;
	else show_tohit = p_ptr->dis_to_h + p_ptr->to_h_ranged;
	if (object_known_p(o_ptr)) show_todam = p_ptr->to_d_ranged + o_ptr->to_d;
	else show_todam = p_ptr->to_d_ranged;

	prt_num("+ To Ranged Hit   ", show_tohit, 11, 1, TERM_L_BLUE, "  ");
	prt_num("+ To Ranged Damage", show_todam, 12, 1, TERM_L_BLUE, "  ");

	/* Dump the total armor class */
	prt_str("  AC             ", format("%d+%d", p_ptr->ac, p_ptr->dis_to_a), 13, 1, TERM_L_BLUE);

	prt_num("Level      ", (int)p_ptr->lev, 9, 28, TERM_L_GREEN, "   ");

	if (p_ptr->exp >= p_ptr->max_exp)
	{
		prt_lnum("Experience ", p_ptr->exp, 10, 28, TERM_L_GREEN);
	}
	else
	{
		prt_lnum("Experience ", p_ptr->exp, 10, 28, TERM_YELLOW);
	}

	prt_lnum("Max Exp    ", p_ptr->max_exp, 11, 28, TERM_L_GREEN);

	if ((p_ptr->lev >= PY_MAX_LEVEL) || (p_ptr->lev >= max_plev))
	{
		put_str("Exp to Adv.", 12, 28);
		c_put_str(TERM_L_GREEN, "    *****", 12, 28 + 11);
	}
	else
	{
		prt_lnum("Exp to Adv.",
		         (s32b)(player_exp[p_ptr->lev - 1] * p_ptr->expfact / 100L),
		         12, 28, TERM_L_GREEN);
	}

	prt_lnum("Gold       ", p_ptr->au, 13, 28, TERM_L_GREEN);

	if (p_ptr->necro_extra & CLASS_UNDEAD)
	{
		put_str("Death Points ", 9, 52);
		if (p_ptr->chp >= p_ptr->mhp)
		{
			color = TERM_L_BLUE;
		}
		else if (p_ptr->chp > (p_ptr->mhp * hitpoint_warn) / 10)
		{
			color = TERM_VIOLET;
		}
		else
		{
			color = TERM_L_RED;
		}
		(void)sprintf(num, "%6ld", (long)p_ptr->chp);
		c_put_str(color, num, 9, 65);
		put_str("/", 9, 71);
		(void)sprintf(num, "%6ld", (long)p_ptr->mhp);
		c_put_str(TERM_L_BLUE, num, 9, 72);
	}
	else
	{
		put_str("Hit Points   ", 9, 52);
		if (p_ptr->chp >= p_ptr->mhp)
		{
			color = TERM_L_GREEN;
		}
		else if (p_ptr->chp > (p_ptr->mhp * hitpoint_warn) / 10)
		{
			color = TERM_YELLOW;
		}
		else
		{
			color = TERM_RED;
		}
		(void)sprintf(num, "%6ld", (long)p_ptr->chp);
		c_put_str(color, num, 9, 65);
		put_str("/", 9, 71);
		(void)sprintf(num, "%6ld", (long)p_ptr->mhp);
		c_put_str(TERM_L_GREEN, num, 9, 72);
	}

	put_str("Spell Points ", 10, 52);
	if (p_ptr->csp >= p_ptr->msp)
	{
		color = TERM_L_GREEN;
	}
	else if (p_ptr->csp > (p_ptr->msp * hitpoint_warn) / 10)
	{
		color = TERM_YELLOW;
	}
	else
	{
		color = TERM_RED;
	}
	(void)sprintf(num, "%6ld", (long)p_ptr->csp);
	c_put_str(color, num, 10, 65);
	put_str("/", 10, 71);
	(void)sprintf(num, "%6ld", (long)p_ptr->msp);
	c_put_str(TERM_L_GREEN, num, 10, 72);

	put_str("Sanity       ", 11, 52);
	if (p_ptr->csane >= p_ptr->msane)
	{
		color = TERM_L_GREEN;
	}
	else if (p_ptr->csane > (p_ptr->msane * hitpoint_warn) / 10)
	{
		color = TERM_YELLOW;
	}
	else
	{
		color = TERM_RED;
	}
	(void)sprintf(num, "%6ld", (long)p_ptr->csane);
	c_put_str(color, num, 11, 65);
	put_str("/", 11, 71);
	(void)sprintf(num, "%6ld", (long)p_ptr->msane);
	c_put_str(TERM_L_GREEN, num, 11, 72);

	if (p_ptr->pgod != GOD_NONE)
	{
		prt_num("Piety          ", p_ptr->grace, 12, 52, TERM_L_GREEN, "     ");
	}

	put_str("Speed           ", 13, 52);
	speed = p_ptr->pspeed;
	/* Hack -- Visually "undo" the Search Mode Slowdown */
	if (p_ptr->searching) speed += 10;
	if (speed > 110)
	{
		char s[11];
		(void)sprintf(s, "Fast (+%d)", speed - 110);
		c_put_str(TERM_L_GREEN, s, 13, (speed >= 120) ? 68 : 69);
	}
	else if (speed < 110)
	{
		char s[11];
		(void)sprintf(s, "Slow (-%d)", 110 - speed);
		c_put_str(TERM_L_UMBER, s, 13, (speed <= 100) ? 68 : 69);
	}
	else
	{
		put_str("Normal", 13, 72);
	}
}




/*
 * Hack -- pass color info around this file
 */
static byte likert_color = TERM_WHITE;


/*
 * Returns a "rating" of x depending on y
 */
static cptr likert(int x, int y)
{
	static char dummy[20] = "";

	/* Paranoia */
	if (y <= 0) y = 1;

	/* Negative value */
	if (x < 0)
	{
		likert_color = TERM_L_DARK;
		return ("Very Bad");
	}

	/* Analyze the value */
	switch ((x / y))
	{
	case 0:
	case 1:
		{
			likert_color = TERM_RED;
			return ("Bad");
		}
	case 2:
		{
			likert_color = TERM_L_RED;
			return ("Poor");
		}
	case 3:
	case 4:
		{
			likert_color = TERM_ORANGE;
			return ("Fair");
		}
	case 5:
		{
			likert_color = TERM_YELLOW;
			return ("Good");
		}
	case 6:
		{
			likert_color = TERM_YELLOW;
			return ("Very Good");
		}
	case 7:
	case 8:
		{
			likert_color = TERM_L_GREEN;
			return ("Excellent");
		}
	case 9:
	case 10:
	case 11:
	case 12:
	case 13:
		{
			likert_color = TERM_GREEN;
			return ("Superb");
		}
	case 14:
	case 15:
	case 16:
	case 17:
		{
			likert_color = TERM_L_GREEN;
			return ("Heroic");
		}
	default:
		{
			likert_color = TERM_L_GREEN;
			sprintf(dummy, "Legendary[%d]", (int)((((x / y) - 17) * 5) / 2));
			return dummy;
		}
	}
}


/*
 * Prints ratings on certain abilities
 *
 * This code is "imitated" elsewhere to "dump" a character sheet.
 */
static void display_player_various(void)
{
	int tmp, tmp2, damdice, damsides, dambonus, blows;
	int xthn, xthb, xfos, xsrh;
	int xdis, xdev, xsav, xstl;
	cptr desc;
	int i;

	object_type *o_ptr;


	/* Fighting Skill (with current weapon) */
	o_ptr = &p_ptr->inventory[INVEN_WIELD];
	tmp = p_ptr->to_h + o_ptr->to_h + p_ptr->to_h_melee;
	xthn = p_ptr->skill_thn + (tmp * BTH_PLUS_ADJ);

	/* Shooting Skill (with current bow and normal missile) */
	o_ptr = &p_ptr->inventory[INVEN_BOW];
	tmp = p_ptr->to_h + o_ptr->to_h + p_ptr->to_h_ranged;
	xthb = p_ptr->skill_thb + (tmp * BTH_PLUS_ADJ);

	/* variables for all types of melee damage */
	dambonus = p_ptr->dis_to_d;
	blows = p_ptr->num_blow;

	/* Basic abilities */
	xdis = p_ptr->skill_dis;
	xdev = p_ptr->skill_dev;
	xsav = p_ptr->skill_sav;
	xstl = p_ptr->skill_stl;
	xsrh = p_ptr->skill_srh;
	xfos = p_ptr->skill_fos;


	put_str("Fighting    :", 16, 1);
	desc = likert(xthn, 12);
	c_put_str(likert_color, desc, 16, 15);

	put_str("Bows/Throw  :", 17, 1);
	desc = likert(xthb, 12);
	c_put_str(likert_color, desc, 17, 15);

	put_str("Saving Throw:", 18, 1);
	desc = likert(xsav, 6);
	c_put_str(likert_color, desc, 18, 15);

	put_str("Stealth     :", 19, 1);
	desc = likert(xstl, 1);
	c_put_str(likert_color, desc, 19, 15);


	put_str("Perception  :", 16, 28);
	desc = likert(xfos, 6);
	c_put_str(likert_color, desc, 16, 42);

	put_str("Searching   :", 17, 28);
	desc = likert(xsrh, 6);
	c_put_str(likert_color, desc, 17, 42);

	put_str("Disarming   :", 18, 28);
	desc = likert(xdis, 8);
	c_put_str(likert_color, desc, 18, 42);

	put_str("Magic Device:", 19, 28);
	desc = likert(xdev, 6);
	c_put_str(likert_color, desc, 19, 42);


	put_str("Blows/Round:", 16, 55);
	put_str(format("%d", p_ptr->num_blow), 16, 69);

	put_str("Shots/Round:", 17, 55);
	put_str(format("%d", p_ptr->num_fire), 17, 69);

	put_str("Mel.dmg/Rnd:", 18, 55);     /* From PsiAngband */

	if (p_ptr->melee_style == SKILL_HAND || p_ptr->melee_style == SKILL_BEAR)
	{
		/* This is all based on py_attack_hand */
		martial_arts *blow_table, *min_attack, *max_attack;
		int max_blow, plev, i;

		if (p_ptr->melee_style == SKILL_HAND)
		{
			blow_table = ma_blows;
			max_blow = MAX_MA;
			plev = get_skill(SKILL_HAND);
		}
		else /* SKILL_BEAR */
		{
			blow_table = bear_blows;
			max_blow = MAX_BEAR;
			plev = get_skill(SKILL_BEAR);
		}
		min_attack = blow_table;
		i = max_blow - 1;
		while (blow_table[i].min_level > plev && i != 0)
			--i;
		max_attack = &blow_table[i];

		dambonus += p_ptr->to_d_melee;
		tmp = min_attack->dd + dambonus;
		if (tmp < 0) tmp = 0;
		tmp2 = maxroll(max_attack->dd, max_attack->ds) + dambonus;
		if (tmp2 < 0) tmp2 = 0;
		if (!tmp && !tmp2)
			desc = "0";
		else
			desc = format("%d-%d", blows * tmp, blows * tmp2);
	}
	else if (!r_info[p_ptr->body_monster].body_parts[BODY_WEAPON])
	{
		if (r_info[p_ptr->body_monster].flags1 & RF1_NEVER_BLOW)
			desc = "nil!";
		else
		{
			tmp = tmp2 = 0;
			for (i = 0; i < blows; i++)
			{
				tmp += r_info[p_ptr->body_monster].blow[i].d_dice;
				tmp2 += maxroll(r_info[p_ptr->body_monster].blow[i].d_dice,
				                r_info[p_ptr->body_monster].blow[i].d_side);
			}
			if (dambonus > 0)
			{
				tmp += dambonus;
				tmp2 += dambonus;
			}
			desc = format("%d-%d", tmp, tmp2);
		}
	}
	else
	{
		/* Increase the bonus to damage for weapon combat */
		dambonus += p_ptr->to_d_melee;

		/* Access the first weapon */
		o_ptr = &p_ptr->inventory[INVEN_WIELD];

		if (object_known_p(o_ptr)) dambonus += o_ptr->to_d;

		damdice = o_ptr->dd;
		damsides = o_ptr->ds;

		if ((damdice == 0) || (damsides == 0))
		{
			if (dambonus <= 0)
				desc = "nil!";
			else
				desc = format("%d", blows * dambonus);
		}
		else
		{
			if (dambonus == 0)
				desc = format("%dd%d", blows * damdice, damsides);
			else
				desc = format("%dd%d%c%d", blows * damdice, damsides,
				              ( dambonus > 0 ? '+' : '\0' ), blows * dambonus );
		}
	}
	put_str(desc, 18, 69);


	put_str("Infra-Vision:", 19, 55);
	put_str(format("%d feet", p_ptr->see_infra * 10), 19, 69);

	/* jk - add tactic */
	put_str("Tactic:", 20, 55);
	c_put_str(TERM_L_BLUE, tactic_info[(byte)p_ptr->tactic].name, 20, 69);

	/* jk - add movement */
	put_str("Explor:", 21, 55);
	c_put_str(TERM_L_BLUE, move_info[(byte)p_ptr->movement].name, 21, 69);
}



/*
 * Obtain the "flags" of the wielded symbiote
 */

void wield_monster_flags(u32b *f1, u32b *f2, u32b *f3, u32b *f4, u32b *f5, u32b *esp)
{
	object_type *o_ptr;
	monster_race *r_ptr;

	/* Clear */
	(*f1) = (*f2) = (*f3) = (*f4) = (*f5) = (*esp) = 0L;

	/* Get the carried monster */
	o_ptr = &p_ptr->inventory[INVEN_CARRY];

	if (o_ptr->k_idx)
	{
		r_ptr = &r_info[o_ptr->pval];

		if (r_ptr->flags2 & RF2_INVISIBLE)
			(*f2) |= TR2_INVIS;
		if (r_ptr->flags2 & RF2_REFLECTING)
			(*f2) |= TR2_REFLECT;
		if (r_ptr->flags7 & RF7_CAN_FLY)
			(*f3) |= TR3_FEATHER;
		if (r_ptr->flags7 & RF7_AQUATIC)
			(*f5) |= TR5_WATER_BREATH;
	}
}


/*
 * Obtain the "flags" for the player as if he was an item
 */
void player_flags(u32b *f1, u32b *f2, u32b *f3, u32b *f4, u32b *f5, u32b *esp)
{
	int i;

	/* Clear */
	(*f1) = (*f2) = (*f3) = (*f4) = (*f5) = (*esp) = 0L;

	/* Astral chars */
	if (p_ptr->astral)
	{
		(*f3) |= TR3_WRAITH;
	}

/* Skills */
	if (get_skill(SKILL_DAEMON) > 20) (*f2) |= TR2_RES_CONF;
	if (get_skill(SKILL_DAEMON) > 30) (*f2) |= TR2_RES_FEAR;
	if (get_skill(SKILL_MINDCRAFT) >= 40) (*esp) |= ESP_ALL;
	if (p_ptr->melee_style == SKILL_HAND && get_skill(SKILL_HAND) > 24 && !monk_heavy_armor())
		(*f2) |= TR2_FREE_ACT;
/* Hack - from Lua */
	if (get_skill(SKILL_MANA) >= 35) (*f1) |= TR1_MANA;
	if (get_skill(SKILL_AIR) >= 50) (*f5) |= (TR5_MAGIC_BREATH | TR5_WATER_BREATH);
	if (get_skill(SKILL_WATER) >= 30) (*f5) |= TR5_WATER_BREATH;

/* Gods */
	if (p_ptr->pgod == GOD_ERU)
	{
		if ((p_ptr->grace >= 100) || (p_ptr->grace <= -100))  (*f1) |= TR1_MANA;
		if (p_ptr->grace > 10000) (*f1) |= TR1_WIS;
	}

	if (p_ptr->pgod == GOD_MELKOR)
	{
		(*f2) |= TR2_RES_FIRE;
		if (p_ptr->melkor_sacrifice > 0) (*f2) |= TR2_LIFE;
		if (p_ptr->grace > 10000) (*f1) |= (TR1_STR | TR1_CON | TR1_INT | TR1_WIS | TR1_CHR);
		if (p_ptr->praying)
		{
			if (p_ptr->grace > 5000)  (*f2) |= TR2_INVIS;
			if (p_ptr->grace > 15000) (*f2) |= TR2_IM_FIRE;
		}
	}

	if (p_ptr->pgod == GOD_MANWE)
	{
		if (p_ptr->grace >= 2000) (*f3) |= TR3_FEATHER;
		if (p_ptr->praying)
		{
			if (p_ptr->grace >= 7000)  (*f2) |= TR2_FREE_ACT;
			if (p_ptr->grace >= 15000) (*f4) |= TR4_FLY;
			if ((p_ptr->grace >= 5000) || (p_ptr->grace <= -5000)) (*f1) |= TR1_SPEED;
		}
	}

	if (p_ptr->pgod == GOD_TULKAS)
	{
		if (p_ptr->grace > 5000)  (*f1) |= TR1_CON;
		if (p_ptr->grace > 10000) (*f1) |= TR1_STR;
	}

	if (p_ptr->pgod == GOD_AULE)
	{
		if (p_ptr->grace > 5000)
		{
			(*f2) |= TR2_RES_FIRE;
		}
	}

	if (p_ptr->pgod == GOD_MANDOS)
	{
		(*f2) |= TR2_RES_NETHER;

		if ((p_ptr->grace > 10000) &&
		    (p_ptr->praying == TRUE))
		{
			(*f3) |= TR3_NO_TELE;
		}

		if ((p_ptr->grace > 20000) &&
		    (p_ptr->praying == TRUE))
		{
			(*f4) |= TR4_IM_NETHER;
		}
	}

	if (p_ptr->pgod == GOD_ULMO)
	{
		(*f5) |= TR5_WATER_BREATH;

		if ((p_ptr->grace > 1000) &&
		    (p_ptr->praying == TRUE))
		{
			(*f2) |= TR2_RES_POIS;
		}

		if ((p_ptr->grace > 15000) &&
		    (p_ptr->praying == TRUE))
		{
			(*f5) |= TR5_MAGIC_BREATH;
		}
	}

	/* Classes */
	for (i = 1; i <= p_ptr->lev; i++)
	{
		(*f1) |= cp_ptr->oflags1[i];
		(*f2) |= cp_ptr->oflags2[i];
		(*f3) |= cp_ptr->oflags3[i];
		(*f4) |= cp_ptr->oflags4[i];
		(*f5) |= cp_ptr->oflags5[i];
		(*esp) |= cp_ptr->oesp[i];
	}

	/* Races */
	if ((!p_ptr->mimic_form) && (!p_ptr->body_monster))
	{
		for (i = 1; i <= p_ptr->lev; i++)
		{
			(*f1) |= rp_ptr->oflags1[i];
			(*f2) |= rp_ptr->oflags2[i];
			(*f3) |= rp_ptr->oflags3[i];
			(*f4) |= rp_ptr->oflags4[i];
			(*f5) |= rp_ptr->oflags5[i];
			(*esp) |= rp_ptr->oesp[i];

			(*f1) |= rmp_ptr->oflags1[i];
			(*f2) |= rmp_ptr->oflags2[i];
			(*f3) |= rmp_ptr->oflags3[i];
			(*f4) |= rmp_ptr->oflags4[i];
			(*f5) |= rmp_ptr->oflags5[i];
			(*esp) |= rmp_ptr->oesp[i];
		}
	}
	else
	{
		monster_race *r_ptr = &r_info[p_ptr->body_monster];

		if (r_ptr->flags2 & RF2_REFLECTING) (*f2) |= TR2_REFLECT;
		if (r_ptr->flags2 & RF2_REGENERATE) (*f3) |= TR3_REGEN;
		if (r_ptr->flags2 & RF2_AURA_FIRE) (*f3) |= TR3_SH_FIRE;
		if (r_ptr->flags2 & RF2_AURA_ELEC) (*f3) |= TR3_SH_ELEC;
		if (r_ptr->flags2 & RF2_PASS_WALL) (*f3) |= TR3_WRAITH;
		if (r_ptr->flags3 & RF3_SUSCEP_FIRE) (*f2) |= TR2_SENS_FIRE;
		if (r_ptr->flags3 & RF3_IM_ACID) (*f2) |= TR2_RES_ACID;
		if (r_ptr->flags3 & RF3_IM_ELEC) (*f2) |= TR2_RES_ELEC;
		if (r_ptr->flags3 & RF3_IM_FIRE) (*f2) |= TR2_RES_FIRE;
		if (r_ptr->flags3 & RF3_IM_POIS) (*f2) |= TR2_RES_POIS;
		if (r_ptr->flags3 & RF3_IM_COLD) (*f2) |= TR2_RES_COLD;
		if (r_ptr->flags3 & RF3_RES_NETH) (*f2) |= TR2_RES_NETHER;
		if (r_ptr->flags3 & RF3_RES_NEXU) (*f2) |= TR2_RES_NEXUS;
		if (r_ptr->flags3 & RF3_RES_DISE) (*f2) |= TR2_RES_DISEN;
		if (r_ptr->flags3 & RF3_NO_FEAR) (*f2) |= TR2_RES_FEAR;
		if (r_ptr->flags3 & RF3_NO_SLEEP) (*f2) |= TR2_FREE_ACT;
		if (r_ptr->flags3 & RF3_NO_CONF) (*f2) |= TR2_RES_CONF;
		if (r_ptr->flags7 & RF7_CAN_FLY) (*f3) |= TR3_FEATHER;
	}

	(*f1) |= p_ptr->xtra_f1;
	(*f2) |= p_ptr->xtra_f2;
	(*f3) |= p_ptr->xtra_f3;
	(*f4) |= p_ptr->xtra_f4;
	(*f5) |= p_ptr->xtra_f5;
	(*esp) |= p_ptr->xtra_esp;

	if (p_ptr->black_breath)
	{
		(*f4) |= TR4_BLACK_BREATH;
	}

	if (p_ptr->hp_mod != 0)
	{
		(*f2) |= TR2_LIFE;
	}
}

/*
 * Object flag names
 */
static cptr object_flag_names[192] =
{
	"Add Str",
	"Add Int",
	"Add Wis",
	"Add Dex",
	"Add Con",
	"Add Chr",
	"Mul Mana",
	"Mul SPower",
	"Add Stea.",
	"Add Sear.",
	"Add Infra",
	"Add Tun..",
	"Add Speed",
	"Add Blows",
	"Chaotic",
	"Vampiric",
	"Slay Anim.",
	"Slay Evil",
	"Slay Und.",
	"Slay Demon",
	"Slay Orc",
	"Slay Troll",
	"Slay Giant",
	"Slay Drag.",
	"Kill Drag.",
	"Sharpness",
	"Impact",
	"Poison Brd",
	"Acid Brand",
	"Elec Brand",
	"Fire Brand",
	"Cold Brand",

	"Sust Str",
	"Sust Int",
	"Sust Wis",
	"Sust Dex",
	"Sust Con",
	"Sust Chr",
	"Invisible",
	"Mul life",
	"Imm Acid",
	"Imm Elec",
	"Imm Fire",
	"Imm Cold",
	"Sens Fire",
	"Reflect",
	"Free Act",
	"Hold Life",
	"Res Acid",
	"Res Elec",
	"Res Fire",
	"Res Cold",
	"Res Pois",
	"Res Fear",
	"Res Light",
	"Res Dark",
	"Res Blind",
	"Res Conf",
	"Res Sound",
	"Res Shard",
	"Res Neth",
	"Res Nexus",
	"Res Chaos",
	"Res Disen",



	"Aura Fire",
	"Aura Elec",
	"Auto Curse",
	NULL,
	"NoTeleport",
	"AntiMagic",
	"WraithForm",
	"EvilCurse",
	NULL,
	NULL,
	NULL,
	NULL,
	"Levitate",
	"Lite",
	"See Invis",
	NULL,
	"Digestion",
	"Regen",
	"Xtra Might",
	"Xtra Shots",
	NULL,
	NULL,
	NULL,
	NULL,
	"Activate",
	"Drain Exp",
	"Teleport",
	"Aggravate",
	"Blessed",
	"Cursed",
	"Hvy Curse",
	"Prm Curse",

	"No blows",
	"Precogn.",
	"B.Breath",
	"Recharge",
	"Fly",
	"Mrg.Curse",
	NULL,
	NULL,
	"Sentient",
	"Clone",
	NULL,
	"Climb",
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	"Imm Neth",
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,

	"Orc.ESP",
	"Troll.ESP",
	"Dragon.ESP",
	"Giant.ESP",
	"Demon.ESP",
	"Undead.ESP",
	"Evil.ESP",
	"Animal.ESP",
	"TLord.ESP",
	"Good.ESP",
	"Nlive.ESP",
	"Unique.ESP",
	"Spider ESP",
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	"Full ESP",
};

/*
 * Summarize resistances
 */
static void display_player_ben_one(int mode)
{
	int i, n, x, y, z, dispx, modetemp, xtemp;

	object_type *o_ptr;

	char dummy[80], c;

	u32b f1, f2, f3, f4, f5, esp;

	u16b b[INVEN_TOTAL - INVEN_WIELD + 1][10];

	int d[INVEN_TOTAL - INVEN_WIELD + 1];

	bool_ got;

	byte a;

	cptr name;

	/* Scan equipment */
	for (i = INVEN_WIELD; i < INVEN_TOTAL; i++)
	{
		/* Index */
		n = (i - INVEN_WIELD);

		/* Object */
		o_ptr = &p_ptr->inventory[i];

		/* Known object flags */
		object_flags_known(o_ptr, &f1, &f2, &f3, &f4, &f5, &esp);

		/* Incorporate */
		b[n][0] = (u16b)(f1 & 0xFFFF);
		b[n][1] = (u16b)(f1 >> 16);
		b[n][2] = (u16b)(f2 & 0xFFFF);
		b[n][3] = (u16b)(f2 >> 16);
		b[n][4] = (u16b)(f3 & 0xFFFF);
		b[n][5] = (u16b)(f3 >> 16);
		b[n][6] = (u16b)(f4 & 0xFFFF);
		b[n][7] = (u16b)(f4 >> 16);
		b[n][8] = (u16b)(esp & 0xFFFF);
		b[n][9] = (u16b)(esp >> 16);
		d[n] = o_ptr->pval;
	}

	/* Carried symbiote */
	n = INVEN_CARRY - INVEN_WIELD;

	/* Player flags */
	wield_monster_flags(&f1, &f2, &f3, &f4, &f5, &esp);

	/* Incorporate */
	b[n][0] = (u16b)(f1 & 0xFFFF);
	b[n][1] = (u16b)(f1 >> 16);
	b[n][2] = (u16b)(f2 & 0xFFFF);
	b[n][3] = (u16b)(f2 >> 16);
	b[n][4] = (u16b)(f3 & 0xFFFF);
	b[n][5] = (u16b)(f3 >> 16);
	b[n][6] = (u16b)(f4 & 0xFFFF);
	b[n][7] = (u16b)(f4 >> 16);
	b[n][8] = (u16b)(esp & 0xFFFF);
	b[n][9] = (u16b)(esp >> 16);

	/* Index */
	n = INVEN_TOTAL - INVEN_WIELD;

	/* Player flags */
	player_flags(&f1, &f2, &f3, &f4, &f5, &esp);

	/* Incorporate */
	b[n][0] = (u16b)(f1 & 0xFFFF);
	b[n][1] = (u16b)(f1 >> 16);
	b[n][2] = (u16b)(f2 & 0xFFFF);
	b[n][3] = (u16b)(f2 >> 16);
	b[n][4] = (u16b)(f3 & 0xFFFF);
	b[n][5] = (u16b)(f3 >> 16);
	b[n][6] = (u16b)(f4 & 0xFFFF);
	b[n][7] = (u16b)(f4 >> 16);
	b[n][8] = (u16b)(esp & 0xFFFF);
	b[n][9] = (u16b)(esp >> 16);

	/* Generate the equip chars */
	sprintf(dummy, " ");
	for (i = 0; i < INVEN_TOTAL - INVEN_WIELD; i++)
	{
		/* If you have that body part then show it */
		if (p_ptr->body_parts[i])
		{
			strcat(dummy, format("%c", i + 'a'));
		}
	}
	strcat(dummy, "@");

	/* Scan cols */
	for (x = 1; x > -1; x--)
	{
		/* Label */
		Term_putstr(x * 40 + 11, 3, -1, TERM_WHITE, dummy);

		/* Scan rows */
		for (y = 0; y < 16; y++)
		{
			if (mode == 3 && x == 1)
			{
				modetemp = 4;
				xtemp = 0;
			}
			else
			{
				modetemp = mode;
				xtemp = x;
			}

			for (z = mode; z <= modetemp; z++)
			{
				if (mode == 3 && x == 1 && z == modetemp) xtemp = 1;
				name = object_flag_names[32 * modetemp + 16 * xtemp + y];
				got = FALSE;

				/* No name */
				if (!name) continue;

				/* Dump colon */
				if (!(modetemp == 1 && x == 0 && y > 7 && y < 12))
				{
					Term_putch(x * 40 + 10, y + 4, TERM_WHITE, ':');
				}

				/* Check flags */
				dispx = 0;
				for (n = 0; n < INVEN_TOTAL - INVEN_WIELD + 1; n++)
				{
					/* Change colour every two columns */
					bool_ is_green = (dispx & 0x02);
					a = (is_green ? TERM_GREEN : TERM_SLATE);
					c = '.';

					/* If the body part doesn't exists then skip it :) */
					if ((n < INVEN_TOTAL - INVEN_WIELD) && (!p_ptr->body_parts[n])) continue;

					/* Increment the drawing coordinates */
					dispx++;

					/* Check flag */
					if (b[n][2 * modetemp + xtemp] & (1 << y))
					{
						a = (is_green ? TERM_L_GREEN : TERM_WHITE);
						if (modetemp == 1 && x == 0 && y > 7 && y < 12)
						{
							c = '*';
						}
						else if (modetemp == 0 && x == 0 && y < 14 && (y < 6 || y > 7))
						{
							if (n == INVEN_TOTAL - INVEN_WIELD)
							{
								c = '+';
							}
							else
							{
								c = d[n];
								if (c < 0)
								{
									c = -c;
									a = TERM_RED;
								}
								c = (c > 9 ? '*' : I2D(c));
							}
						}
						else
						{
							c = '+';
						}
						got = TRUE;
					}

					/* HACK - Check for nether immunity and
					   apply to Res Neth line */
					if (modetemp == 1 && x == 1 && y == 12)
					{
						if (b[n][7] & (1 << 6))
						{
							a = (is_green ? TERM_L_GREEN : TERM_WHITE);
							c = '*';
							got = TRUE;
						}
					}

					/* Dump flag */
					if (modetemp == 1 && x == 0 && y > 7 && y < 12)
					{
						if (c == '*') Term_putch(40 + 11 + dispx, y - 4, a, c);
					}
					else
					{
						Term_putch(x * 40 + 11 + dispx, y + 4, a, c);
					}
				}

				a = TERM_WHITE;
				if (got)
				{
					if (modetemp == 1 && x == 0 && y > 7 && y < 12)
					{
						a = TERM_L_GREEN;
					}
					else if (modetemp != 0)
					{
						a = TERM_GREEN;
					}
				}

				/* HACK - Check for nether immunity and change "Res Neth" */
				if (modetemp == 1 && x == 1 && y == 12 && p_ptr->immune_neth)
				{
					name = "Imm Neth";
					a = TERM_L_GREEN;
				}

				/* Dump name */
				if (modetemp == 1 && x == 0 && y > 7 && y < 12)
				{
					if (got) Term_putstr(40, y - 4, -1, a, name);
				}
				else
				{
					Term_putstr(x * 40, y + 4, -1, a, name);
				}
			}
		}
	}
}


/*
 * Display the character on the screen (various modes)
 *
 * The top two and bottom two lines are left blank.
 *
 * Mode 0 = standard display with skills
 * Mode 1 = standard display with history
 * Mode 2 = current flags (part 1)
 * Mode 3 = current flags (part 2)
 * Mode 4 = current flags (part 3)
 * Mode 5 = current flags (part 4)
 * Mode 6 = current flags (part 5 -- esp)
 */
void display_player(int mode)
{
	int i;

	char buf[80];


	/* Erase screen */
	clear_from(0);

	/* Standard */
	if ((mode == 0) || (mode == 1))
	{
		monster_race *r_ptr = &r_info[p_ptr->body_monster];

		/* Name, Sex, Race, Class */
		put_str("Name  :", 2, 1);
		put_str("Sex   :", 3, 1);
		put_str("Race  :", 4, 1);
		put_str("Class :", 5, 1);
		put_str("Body  :", 6, 1);
		put_str("God   :", 7, 1);
		c_put_str(TERM_L_BLUE, player_name, 2, 9);
		if (p_ptr->body_monster != 0)
		{
			monster_race *r_ptr = &r_info[p_ptr->body_monster];
			char tmp[12];

			if ((r_ptr->flags1 & RF1_MALE) != 0)
				strcpy(tmp, "Male");
			else if ((r_ptr->flags1 & RF1_FEMALE) != 0)
				strcpy(tmp, "Female");
			else
				strcpy(tmp, "Neuter");
			c_put_str(TERM_L_BLUE, tmp, 3, 9);
		}
		else
			c_put_str(TERM_L_BLUE, sp_ptr->title, 3, 9);
		sprintf(buf, "%s", get_player_race_name(p_ptr->prace, p_ptr->pracem));
		c_put_str(TERM_L_BLUE, buf, 4, 9);
		c_put_str(TERM_L_BLUE, spp_ptr->title, 5, 9);
		c_put_str(TERM_L_BLUE, r_ptr->name, 6, 9);
		c_put_str(TERM_L_BLUE, deity_info[p_ptr->pgod].name, 7, 9);

		/* Age, Height, Weight, Social */
		prt_num("Age          ", (int)p_ptr->age + bst(YEAR, turn), 2, 32, TERM_L_BLUE, "   ");
		prt_num("Height       ", (int)p_ptr->ht, 3, 32, TERM_L_BLUE, "   ");
		prt_num("Weight       ", (int)p_ptr->wt, 4, 32, TERM_L_BLUE, "   ");
		prt_num("Social Class ", (int)p_ptr->sc, 5, 32, TERM_L_BLUE, "   ");

		/* Display the stats */
		for (i = 0; i < 6; i++)
		{
			char punctuation = p_ptr->stat_max[i] == 18 + 100 ? '!' : ':';
			/* Special treatment of "injured" stats */
			if (p_ptr->stat_cur[i] < p_ptr->stat_max[i])
			{
				int value;
				int colour;

				if (p_ptr->stat_cnt[i])
					colour = TERM_ORANGE;
				else
					colour = TERM_YELLOW;

				/* Use lowercase stat name */
				put_str(format("%s%c ", stat_names_reduced[i], punctuation), 2 + i, 61);

				/* Get the current stat */
				value = p_ptr->stat_use[i];

				/* Obtain the current stat (modified) */
				cnv_stat(value, buf);

				/* Display the current stat (modified) */
				c_put_str(colour, buf, 2 + i, 66);

				/* Acquire the max stat */
				value = p_ptr->stat_top[i];

				/* Obtain the maximum stat (modified) */
				cnv_stat(value, buf);

				/* Display the maximum stat (modified) */
				c_put_str(TERM_L_GREEN, buf, 2 + i, 73);
			}

			/* Normal treatment of "normal" stats */
			else
			{
				/* Assume uppercase stat name */
				put_str(format("%s%c ", stat_names[i], punctuation), 2 + i, 61);

				/* Obtain the current stat (modified) */
				cnv_stat(p_ptr->stat_use[i], buf);

				/* Display the current stat (modified) */
				c_put_str(TERM_L_GREEN, buf, 2 + i, 66);
			}
		}

		/* Extra info */
		display_player_middle();

		/* Display "history" info */
		if (mode == 1)
		{
			put_str("(Character Background)", 15, 25);

			for (i = 0; i < 4; i++)
			{
				put_str(history[i], i + 16, 10);
			}
		}

		/* Display "various" info */
		else
		{
			put_str("(Miscellaneous Abilities)", 15, 25);

			display_player_various();
		}
	}

	/* Special */
	else
	{
		display_player_ben_one(mode - 2);
	}
}

/*
 * Utility function; should probably be in some other file...
 *
 * Describe the player's location -- either by dungeon level, town, or in
 * wilderness with landmark reference.
 */
cptr describe_player_location()
{
	int i;
	static char desc[80];
	int pwx = (p_ptr->wild_mode ? p_ptr->px : p_ptr->wilderness_x);
	int pwy = (p_ptr->wild_mode ? p_ptr->py : p_ptr->wilderness_y);
	int feat = wild_map[pwy][pwx].feat;

	if (dungeon_type != DUNGEON_WILDERNESS && dun_level > 0)
	{
		sprintf(desc, "on level %d of %s", dun_level, d_info[dungeon_type].name);
	}
	else if (wf_info[feat].terrain_idx == TERRAIN_TOWN)
	{
		sprintf(desc, "in the town of %s", wf_info[feat].name);
	}
	else if (wf_info[feat].entrance)
	{
		sprintf(desc, "near %s", wf_info[feat].name);
	}
	else
	{
		/*
		 * The complicated case.  Find the nearest known landmark,
		 * and describe our position relative to that.  Note that
		 * we may not even have any known landmarks (for instance,
		 * a Lost Soul character just after escaping the Halls of
		 * Mandos).
		 */
		int landmark = 0, lwx = 0, lwy = 0;
		int l_dist = -1;
		int i;

		for (i = 0; i < max_wf_idx; i++)
		{
			int wx = wf_info[i].wild_x;
			int wy = wf_info[i].wild_y;
			int dist;

			/* Skip if not a landmark */
			if (!wf_info[i].entrance) continue;

			/* Skip if we haven't seen it */
			if (!wild_map[wy][wx].known) continue;

			dist = distance(wy, wx, pwy, pwx);
			if (dist < l_dist || l_dist < 0)
			{
				landmark = i;
				l_dist = dist;
				lwx = wx;
				lwy = wy;
			}
		}

		if (!landmark)
		{
			sprintf(desc, "in %s", wf_info[feat].text);
		}
		else if (pwx == lwx && pwy == lwy)
		{
			/* Paranoia; this should have been caught above */
			sprintf(desc, "near %s", wf_info[feat].name);
		}
		else
		{
			/*
			 * We split the circle into eight equal octants of
			 * size pi/4 radians; the "east" octant, for
			 * instance, is defined as due east plus or minus
			 * pi/8 radians.  Now sin(pi/8) ~= 0.3826 ~= 31/81,
			 * so we check |dx|/|dy| and |dy|/|dx| against that
			 * ratio to determine which octant we're in.
			 */
			int dx = pwx - lwx;
			int dy = pwy - lwy;
			cptr ns = (dy > 0 ? "south" : "north");
			cptr ew = (dx > 0 ? "east" : "west");

			dx = (dx < 0 ? -dx : dx);
			dy = (dy < 0 ? -dy : dy);
			if (dy * 81 < dx * 31) ns = "";
			if (dx * 81 < dy * 31) ew = "";

			sprintf(desc, "in %s %s%s of %s",
				wf_info[feat].text,
				ns,
				ew,
				wf_info[landmark].name);
		}
	}

	/* strip trailing whitespace */
	for (i = 0; desc[i]; ++i);
	while (desc[--i] == ' ')
		desc[i] = 0;

	return desc;
}

/*
 * Helper function or file_character_print_grid
 *
 * Figure out if a row on the grid is empty
 */
static bool_ file_character_print_grid_check_row(const char *buf)
{
	if (strstr(buf + 12, "+")) return TRUE;
	if (strstr(buf + 12, "*")) return TRUE;
	if (strstr(buf + 12, "1")) return TRUE;
	if (strstr(buf + 12, "2")) return TRUE;
	if (strstr(buf + 12, "3")) return TRUE;
	if (strstr(buf + 12, "4")) return TRUE;
	if (strstr(buf + 12, "5")) return TRUE;
	if (strstr(buf + 12, "6")) return TRUE;
	if (strstr(buf + 12, "7")) return TRUE;
	if (strstr(buf + 12, "8")) return TRUE;
	if (strstr(buf + 12, "9")) return TRUE;
	return FALSE;
}

/*
 * Helper function for file_character
 *
 * Prints the big ugly grid
 */
static void file_character_print_grid(FILE *fff, bool_ show_gaps, bool_ show_legend)
{
	static cptr blank_line = "                                        ";
	static char buf[1024];
	byte a;
	char c;
	int x, y;

	y = show_legend ? 3 : 4;
	for (; y < 23; y++)
	{
		for (x = 0; x < 40; x++)
		{
			(void)(Term_what(x, y, &a, &c));
			buf[x] = c;
		}

		buf[x] = '\0';
		if (strcmp(buf, blank_line) &&
		                (y == 3 || show_gaps || file_character_print_grid_check_row(buf)))
			fprintf (fff, "        %s\n", buf);
	}
	for (y = 4; y < 23; y++)
	{
		for (x = 40; x < 80; x++)
		{
			(void)(Term_what(x, y, &a, &c));
			buf[x - 40] = c;
		}

		buf[x] = '\0';
		if (strcmp(buf, blank_line) &&
		                (show_gaps || file_character_print_grid_check_row(buf)))
			fprintf (fff, "        %s\n", buf);
	}
}

/*
 * Helper function for file_character
 *
 * Outputs one item (for Inventory, Equipment, Home, and Mathom-house)
 */
void file_character_print_item(FILE *fff, char label, object_type *obj, bool_ full)
{
	static char o_name[80];
	static cptr paren = ")";
	object_desc(o_name, obj, TRUE, 3);
	fprintf(fff, "%c%s %s\n", label, paren, o_name);

	if ((artifact_p(obj) || ego_item_p(obj) || obj->tval == TV_RING || obj->tval == TV_AMULET || full) &&
	                (obj->ident & IDENT_MENTAL))
	{
		object_out_desc(obj, fff, TRUE, TRUE);
	}
}

/*
 * Helper function for file_character
 *
 * Prints out one "store" (for Home and Mathom-house)
 */
void file_character_print_store(FILE *fff, wilderness_type_info *place, int store, bool_ full)
{
	int i;
	town_type *town = &town_info[place->entrance];
	store_type *st_ptr = &town->store[store];

	if (st_ptr->stock_num)
	{
		/* Header with name of the town */
		fprintf(fff, "  [%s Inventory - %s]\n\n", st_info[store].name, place->name);

		/* Dump all available items */
		for (i = 0; i < st_ptr->stock_num; i++)
		{
			file_character_print_item(fff, I2A(i%24), &st_ptr->stock[i], full);
		}

		/* Add an empty line */
		fprintf(fff, "\n\n");
	}
}

/**
 * Helper function for file_character
 *
 * Checks if the store hasn't been added to the list yet, and then adds it if it
 * was not already there.  XXX This is an ugly workaround for the double Gondolin
 * problem.
 */
static bool_ file_character_check_stores(std::unordered_set<store_type *> *seen_stores, wilderness_type_info *place, int store)
{
	town_type *town = &town_info[place->entrance];
	store_type *st_ptr = &town->store[store];

	// Already seen store?
	if (seen_stores->find(st_ptr) != seen_stores->end())
	{
		return FALSE;
	}

	// Add
	seen_stores->insert(st_ptr);
	return TRUE;
}

/*
 * Hack -- Dump a character description file
 *
 * XXX XXX XXX Allow the "full" flag to dump additional info,
 * and trigger its usage from various places in the code.
 */
errr file_character(cptr name, bool_ full)
{
	int i, j, x, y;
	byte a;
	char c;
	int fd = -1;
	FILE *fff = NULL;
	char buf[1024];

	/* Build the filename */
	path_build(buf, 1024, ANGBAND_DIR_USER, name);

	/* Check for existing file */
	fd = fd_open(buf, O_RDONLY);

	/* Existing file */
	if (fd >= 0)
	{
		char out_val[160];

		/* Close the file */
		(void)fd_close(fd);

		/* Build query */
		(void)sprintf(out_val, "Replace existing file %s? ", buf);

		/* Ask */
		if (get_check(out_val)) fd = -1;
	}

	/* Open the non-existing file */
	if (fd < 0) fff = my_fopen(buf, "w");

	/* Invalid file */
	if (!fff)
	{
		/* Message */
		msg_format("Character sheet creation failed!");
		msg_print(NULL);

		/* Error */
		return ( -1);
	}


	/* Begin dump */
	fprintf(fff, "  [%s Character Sheet]\n\n", get_version_string());


	/* Display player */
	display_player(0);

	/* Dump part of the screen */
	for (y = 2; y < 22; y++)
	{
		/* Dump each row */
		for (x = 0; x < 79; x++)
		{
			/* Get the attr/char */
			(void)(Term_what(x, y, &a, &c));

			/* Dump it */
			buf[x] = c;
		}

		/* Terminate */
		buf[x] = '\0';

		/* End the row */
		fprintf(fff, "%s\n", buf);
	}

	/* Display history */
	display_player(1);

	/* Dump part of the screen */
	for (y = 15; y < 20; y++)
	{
		/* Dump each row */
		for (x = 0; x < 79; x++)
		{
			/* Get the attr/char */
			(void)(Term_what(x, y, &a, &c));

			/* Dump it */
			buf[x] = c;
		}

		/* Terminate */
		buf[x] = '\0';

		/* End the row */
		fprintf(fff, "%s\n", buf);
	}

	/* List the patches */
	fprintf(fff, "\n\n  [Miscellaneous information]\n");
	if (joke_monsters)
		fprintf(fff, "\n Joke monsters:        ON");
	else
		fprintf(fff, "\n Joke monsters:        OFF");

	if (p_ptr->preserve)
		fprintf(fff, "\n Preserve Mode:        ON");
	else
		fprintf(fff, "\n Preserve Mode:        OFF");

	if (auto_scum)
		fprintf(fff, "\n Autoscum:             ON");
	else
		fprintf(fff, "\n Autoscum:             OFF");

	if (always_small_level)
		fprintf(fff, "\n Small Levels:         ALWAYS");
	else if (small_levels)
		fprintf(fff, "\n Small Levels:         ON");
	else
		fprintf(fff, "\n Small Levels:         OFF");

	if (empty_levels)
		fprintf(fff, "\n Arena Levels:         ON");
	else
		fprintf(fff, "\n Arena Levels:         OFF");

	if (ironman_rooms)
		fprintf(fff, "\n Always unusual rooms: ON");
	else
		fprintf(fff, "\n Always unusual rooms: OFF");

	fprintf(fff, "\n\n Recall Depth:");
	for (y = 1; y < max_d_idx; y++)
	{
		if (max_dlv[y])
			fprintf(fff, "\n        %s: Level %d (%d')",
				d_info[y].name,
			        max_dlv[y], 50 * (max_dlv[y]));
	}
	fprintf(fff, "\n");

	if (noscore)
		fprintf(fff, "\n You have done something illegal.");

	if (race_flags1_p(PR1_EXPERIMENTAL))
		fprintf(fff, "\n You have done something experimental.");

	{
		char desc[80];
		cptr mimic;

		monster_race_desc(desc, p_ptr->body_monster, 0);
		fprintf(fff, "\n Your body %s %s.", (death ? "was" : "is"), desc);

		if (p_ptr->tim_mimic)
		{
			mimic = get_mimic_name(p_ptr->mimic_form);
			fprintf(fff, "\n You %s disguised as a %s.", (death ? "were" : "are"), mimic);
		}
	}

	/* Where we are, if we're alive */
	if (!death) fprintf(fff, "\n You are currently %s.", describe_player_location());

	/* Monsters slain */
	{
		int k;
		s32b Total = 0;

		for (k = 1; k < max_r_idx; k++)
		{
			monster_race *r_ptr = &r_info[k];

			if (r_ptr->flags1 & (RF1_UNIQUE))
			{
				bool_ dead = (r_ptr->max_num == 0);
				if (dead)
				{
					Total++;
				}
			}
			else
			{
				s16b This = r_ptr->r_pkills;
				if (This > 0)
				{
					Total += This;
				}
			}
		}

		if (Total < 1)
			fprintf(fff, "\n You have defeated no enemies yet.");
		else if (Total == 1)
			fprintf(fff, "\n You have defeated one enemy.");
		else
                  fprintf(fff, "\n You have defeated %ld enemies.", (long int) Total);
	}

	struct hook_chardump_in in = { fff };
	process_hooks_new(HOOK_CHAR_DUMP, &in, NULL);

	/* Date */
	{
		u32b days = bst(DAY, turn);
		fprintf(fff,
		        (death ? "\n Your adventure lasted %ld day%s." : "\n You have been adventuring for %ld day%s."),
		        (long int) days, (days == 1) ? "" : "s");
	}

	fprintf (fff, "\n\n");

	/* Emit the self-knowledge lines, even though they duplicate the
	   information in the grids (below), because they contain information
	   that's not in the grids (racial abilities, luck, etc.). */
	if (full)
	{
		self_knowledge(fff);
		fprintf(fff, "\n\n");
	}

	/* adds and slays */
	display_player (2);
	file_character_print_grid(fff, FALSE, TRUE);

	/* sustains and resistances */
	display_player (3);
	file_character_print_grid(fff, TRUE, FALSE);

	/* stuff */
	display_player (4);
	file_character_print_grid(fff, FALSE, FALSE);

	/* a little bit of stuff */
	display_player (5);
	file_character_print_grid(fff, FALSE, FALSE);

	/* Dump corruptions */
	dump_corruptions(fff, FALSE, TRUE);

	/* Dump skills */
	dump_skills(fff);
	dump_abilities(fff);

	/* Dump companions. */
	dump_companions(fff);

	if (p_ptr->companion_killed)
	{
		if (p_ptr->companion_killed == 1)
			fprintf(fff, "\n One of your companion(s) has been killed.");
		else
			fprintf(fff, "\n %d of your companions have been killed.", p_ptr->companion_killed);
	}

	for (i = 0; i < MAX_FATES; i++)
	{
		if ((fates[i].fate) && (fates[i].know))
		{
			fprintf(fff, "\n\n  [Fates]\n\n");
			dump_fates(fff);
			break;
		}
	}

	/* Skip some lines */
	fprintf(fff, "\n\n");


	/* Dump the equipment */
	text_out_indent = 4;
	if (equip_cnt)
	{
		fprintf(fff, "  [Character Equipment]\n\n");
		for (i = INVEN_WIELD; i < INVEN_TOTAL; i++)
		{
			if (!p_ptr->body_parts[i - INVEN_WIELD]) continue;

			file_character_print_item(fff, index_to_label(i), &p_ptr->inventory[i], full);
		}
		fprintf(fff, "\n\n");
	}

	/* Dump the inventory */
	fprintf(fff, "  [Character Inventory]\n\n");
	for (i = 0; i < INVEN_PACK; i++)
	{
		file_character_print_item(fff, index_to_label(i), &p_ptr->inventory[i], full);
	}
	fprintf(fff, "\n\n");

	/* Print all homes in the different towns */
	{
		std::unordered_set<store_type *> seen_stores;
		for (j = 0; j < max_wf_idx; j++)
		{
			if (wf_info[j].feat == FEAT_TOWN &&
			    file_character_check_stores(&seen_stores, &wf_info[j], 7))
			{
				file_character_print_store(fff, &wf_info[j], 7, full);
			}
		}
	}

	/* Print all Mathom-houses in the different towns */
	{
		std::unordered_set<store_type *> seen_stores;
		for (j = 0; j < max_wf_idx; j++)
		{
			if (wf_info[j].feat == FEAT_TOWN &&
			    file_character_check_stores(&seen_stores, &wf_info[j], 57))
			{
				file_character_print_store(fff, &wf_info[j], 57, full);
			}
		}
	}

	text_out_indent = 0;

	/* Close it */
	my_fclose(fff);


	/* Message */
	msg_print("Character sheet creation successful.");
	msg_print(NULL);

	/* Success */
	return (0);
}


/*
 * Recursive file perusal.
 *
 * Return FALSE on "ESCAPE", otherwise TRUE.
 *
 * Process various special text in the input file, including
 * the "menu" structures used by the "help file" system.
 *
 * XXX XXX XXX Consider using a temporary file.
 *
 * XXX XXX XXX Allow the user to "save" the current file.
 */

/*
 * A structure to hold (some of == XXX) the hyperlink information.
 * This prevents excessive use of stack.
 */
#define MAX_LINKS 1024
struct hyperlink
{
	/* Path buffer */
	char path[1024];

	/* General buffer */
	char rbuf[1024];

	/* Hold a string to find */
	char finder[81];

	/* Hold a string to show */
	char shower[81];

	/* Describe this thing */
	char caption[128];

	/* Hypertext info */
	char link[MAX_LINKS][32], link_key[MAX_LINKS];
	int link_x[MAX_LINKS], link_y[MAX_LINKS], link_line[MAX_LINKS];
};

typedef struct hyperlink hyperlink_type;

bool_ show_file(cptr name, cptr what, int line, int mode)
{
	int i, k, x;

	byte link_color = TERM_ORANGE, link_color_sel = TERM_YELLOW;

	/* Number of "real" lines passed by */
	int next = 0;

	/* Number of "real" lines in the file */
	int size = 0;

	/* Backup value for "line" */
	int back = 0;

	/* Color of the next line */
	byte color = TERM_WHITE;

	/* This screen has sub-screens */
	bool_ menu = FALSE;

	/* Current help file */
	FILE *fff = NULL;

	/* Find this string (if any) */
	cptr find = NULL;

	/* Pointer to general buffer in the above */
	char *buf;

	int cur_link = 0, max_link = 0;

	/* Read size of screen for big-screen stuff -pav- */
	int wid, hgt;

	/* Allocate hyperlink data */
	std::unique_ptr<hyperlink_type> h_ptr(new hyperlink_type);
	memset(h_ptr.get(), 0, sizeof(hyperlink_type));

	/* Setup buffer pointer */
	buf = h_ptr->rbuf;

	/* Wipe the links */
	for (i = 0; i < MAX_LINKS; i++)
	{
		h_ptr->link_x[i] = -1;
	}

	/* Hack XXX XXX XXX */
	if (what)
	{
		/* h_ptr->caption */
		strcpy(h_ptr->caption, what);

		/* Access the "file" */
		strcpy(h_ptr->path, name);

		/* Open */
		fff = my_fopen(h_ptr->path, "r");
	}

	/* Look in "help" */
	if (!fff)
	{
		/* h_ptr->caption */
		sprintf(h_ptr->caption, "Help file '%s'", name);

		/* Build the filename */
		path_build(h_ptr->path, 1024, ANGBAND_DIR_HELP, name);

		/* Open the file */
		fff = my_fopen(h_ptr->path, "r");
	}

	/* Look in "info" */
	if (!fff)
	{
		/* h_ptr->caption */
		sprintf(h_ptr->caption, "Info file '%s'", name);

		/* Build the filename */
		path_build(h_ptr->path, 1024, ANGBAND_DIR_INFO, name);

		/* Open the file */
		fff = my_fopen(h_ptr->path, "r");
	}

	/* Look in "file" */
	if (!fff)
	{
		/* h_ptr->caption */
		sprintf(h_ptr->caption, "File '%s'", name);

		/* Build the filename */
		path_build(h_ptr->path, 1024, ANGBAND_DIR_FILE, name);

		/* Open the file */
		fff = my_fopen(h_ptr->path, "r");
	}

	/* Oops */
	if (!fff)
	{
		/* Message */
		msg_format("Cannot open '%s'.", name);
		msg_print(NULL);

		/* Oops */
		return (TRUE);
	}


	/* Pre-Parse the file */
	while (TRUE)
	{
		/* Read a line or stop */
		if (my_fgets(fff, h_ptr->rbuf, 1024)) break;

		/* Get a color */
		if (prefix(h_ptr->rbuf, "#####"))
		{
			buf = &h_ptr->rbuf[6];
		}
		else buf = h_ptr->rbuf;

		/* Get the link colors */
		if (prefix(buf, "|||||"))
		{
			link_color = color_char_to_attr(buf[5]);
			link_color_sel = color_char_to_attr(buf[6]);
		}

		/* Tag ? */
		if (prefix(buf, "~~~~~"))
		{
			if (line < 0)
			{
				int i;
				char old_c;

				for (i = 5; (buf[i] >= '0') && (buf[i] <= '9'); i++)
					;
				old_c = buf[i];
				buf[i] = '\0';

				if (atoi(buf + 5) == -line)
				{
					line = next + 1;
				}
				buf[i] = old_c;
			}
		}

		x = 0;
		while (buf[x])
		{
			/* Hyperlink ? */
			if (prefix(buf + x, "*****"))
			{
				int xx = x + 5, stmp, xdeb = x + 5, z;
				char tmp[20];

				for (z = 0; z < 20; z++) tmp[z] = '\0';

				h_ptr->link_x[max_link] = x;
				h_ptr->link_y[max_link] = next;

				if (buf[xx] == '/')
				{
					xx++;
					h_ptr->link_key[max_link] = buf[xx];
					xx++;
					xdeb += 2;
				}
				else
				{
					h_ptr->link_key[max_link] = 0;
				}

				/* Zap the link info */
				while (buf[xx] != '*')
				{
					h_ptr->link[max_link][xx - xdeb] = buf[xx];
					xx++;
				}
				h_ptr->link[max_link][xx - xdeb] = '\0';
				xx++;
				stmp = xx;
				while (buf[xx] != '[')
				{
					tmp[xx - stmp] = buf[xx];
					xx++;
				}
				xx++;
				tmp[xx - stmp] = '\0';
				h_ptr->link_line[max_link] = -atoi(tmp);
				max_link++;
			}
			x++;
		}

		/* Count the "real" lines */
		next++;
	}

	/* Save the number of "real" lines */
	size = next;



	/* Display the file */
	while (TRUE)
	{
		/* Clear screen */
		Term_clear();

		Term_get_size(&wid, &hgt);

		/* Restart when necessary */
		if (line >= size) line = 0;


		/* Re-open the file if needed */
		if (next > line)
		{
			/* Close it */
			my_fclose(fff);

			/* Hack -- Re-Open the file */
			fff = my_fopen(h_ptr->path, "r");

			/* Oops */
			if (!fff)
			{
				return (FALSE);
			}

			/* File has been restarted */
			next = 0;
		}

		/* Skip lines if needed */
		for (; next < line; next++)
		{
			/* Skip a line */
			if (my_fgets(fff, buf, 1024)) break;
		}


		/* Dump the next 20 (or more in bigscreen) lines of the file */
		for (i = 0; i < (hgt - 4); )
		{
			int print_x;

			/* Hack -- track the "first" line */
			if (!i) line = next;

			/* Get a line of the file or stop */
			if (my_fgets(fff, h_ptr->rbuf, 1024)) break;

			/* Get a color */
			if (prefix(h_ptr->rbuf, "#####"))
			{
				color = color_char_to_attr(h_ptr->rbuf[5]);
				buf = &h_ptr->rbuf[6];
			}
			else buf = h_ptr->rbuf;

			/* Count the "real" lines */
			next++;

			/* Skip link colors */
			if (prefix(buf, "|||||")) continue;

			/* Skip tags */
			if (prefix(buf, "~~~~~"))
			{
				i++;
				continue;
			}

			/* Hack -- keep searching */
			if (find && !i && !strstr(buf, find)) continue;

			/* Hack -- stop searching */
			find = NULL;

			/* Be sure to get a correct cur_link */
			if (h_ptr->link_y[cur_link] >= line + (hgt - 4))
			{
				while ((cur_link > 0) && (h_ptr->link_y[cur_link] >= line + (hgt - 4)))
				{
					cur_link--;
				}
			}
			if (h_ptr->link_y[cur_link] < line)
			{
				while ((cur_link < max_link) && (h_ptr->link_y[cur_link] < line))
				{
					cur_link++;
				}
			}

			/* Dump the line */
			print_x = 0;
			if (!prefix(buf, "&&&&&"))
			{
				x = 0;
				while (buf[x])
				{
					/* Hyperlink ? */
					if (prefix(buf + x, "*****"))
					{
						int xx = x + 5;

						/* Zap the link info */
						while (buf[xx] != '[')
						{
							xx++;
						}
						xx++;
						/* Ok print the link name */
						while (buf[xx] != ']')
						{
							byte color = link_color;

							if ((h_ptr->link_x[cur_link] == x) && (h_ptr->link_y[cur_link] == line + i))
								color = link_color_sel;

							/* Now we treat the next char as printable */
							if (buf[xx] == '\\')
								xx++;

							Term_putch(print_x, i + 2, color, buf[xx]);
							xx++;
							print_x++;
						}
						x = xx;
					}
					/* Color ? */
					else if (prefix(buf + x, "[[[[["))
					{
						int xx = x + 6;

						/* Ok print the link name */
						while (buf[xx] != ']')
						{
							/* Now we treat the next char as printable */
							if (buf[xx] == '\\')
								xx++;
							Term_putch(print_x, i + 2, color_char_to_attr(buf[x + 5]), buf[xx]);
							xx++;
							print_x++;
						}
						x = xx;
					}
					/* Remove HTML ? */
					else if (prefix(buf + x, "{{{{{"))
					{
						int xx = x + 6;

						/* Ok remove this section */
						while (buf[xx] != '}')
						{
							xx++;
						}
						x = xx;
					}
					else
					{
						Term_putch(print_x, i + 2, color, buf[x]);
						print_x++;
					}

					x++;
				}
			}
			/* Verbatim mode: i.e: acacacac */
			else
			{
				x = 5;
				while (buf[x])
				{
					Term_putch(print_x, i + 2, color_char_to_attr(buf[x]), buf[x + 1]);
					print_x++;
					x += 2;
				}
			}
			color = TERM_WHITE;

			/* Hilite "h_ptr->shower" */
			if (h_ptr->shower[0])
			{
				cptr str = buf;

				/* Display matches */
				while ((str = strstr(str, h_ptr->shower)) != NULL)
				{
					int len = strlen(h_ptr->shower);

					/* Display the match */
					Term_putstr(str - buf, i + 2, len, TERM_YELLOW, h_ptr->shower);

					/* Advance */
					str += len;
				}
			}

			/* Count the printed lines */
			i++;
		}

		/* Hack -- failed search */
		if (find)
		{
			bell();
			line = back;
			find = NULL;
			continue;
		}


		/* Show a general "title" */
		prt(format("[%s, %s, Line %d/%d]", get_version_string(),
		           h_ptr->caption, line, size), 0, 0);

		/* Prompt -- menu screen */
		if (menu)
		{
			/* Wait for it */
			prt("[Press a Number, or ESC to exit.]", hgt - 1, 0);
		}

		/* Prompt -- small files */
		else if (size <= (hgt - 4))
		{
			/* Wait for it */
			prt("[Press ESC to exit.]", hgt - 1, 0);
		}

		/* Prompt -- large files */
		else
		{
			/* Wait for it */
			prt("[Press 2, 8, 4, 6, /, =, #, %, backspace, or ESC to exit.]", hgt - 1, 0);
		}

		/* Get a keypress */
		k = inkey();

		/* Hack -- return to last screen */
		if ((k == '?') || (k == 0x7F) || (k == '\010')) break;

		/* Hack -- try showing */
		if (k == '=')
		{
			/* Get "h_ptr->shower" */
			prt("Show: ", hgt - 1, 0);
			(void)askfor_aux(h_ptr->shower, 80);
		}

		/* Hack -- try finding */
		if (k == '/')
		{
			/* Get "h_ptr->finder" */
			prt("Find: ", hgt - 1, 0);
			if (askfor_aux(h_ptr->finder, 80))
			{
				/* Find it */
				find = h_ptr->finder;
				back = line;
				line = line + 1;

				/* Show it */
				strcpy(h_ptr->shower, h_ptr->finder);
			}
		}

		/* Hack -- go to a specific line */
		if (k == '#')
		{
			char tmp[81];
			prt("Goto Line: ", hgt - 1, 0);
			strcpy(tmp, "0");
			if (askfor_aux(tmp, 80))
			{
				line = atoi(tmp);
			}
		}

		/* Hack -- go to a specific file */
		if (k == '%')
		{
			char tmp[81];
			prt("Goto File: ", hgt - 1, 0);
			strcpy(tmp, "help.hlp");
			if (askfor_aux(tmp, 80))
			{
				if (!show_file(tmp, NULL, 0, mode)) k = ESCAPE;
			}
		}

		/* Hack -- Allow backing up */
		if (k == '-')
		{
			line = line - (hgt - 4);
			if (line < 0) line = 0;
		}

		if (k == '8')
		{
			line--;
			if (line < 0) line = 0;
		}

		/* Hack -- Advance a single line */
		if (k == '2')
		{
			line = line + 1;
		}

		/* Advance one page */
		if (k == ' ')
		{
			line = line + (hgt - 4);
		}

		/* Advance one link */
		if ((k == '6') || (k == '\t'))
		{
			cur_link++;
			if (cur_link >= max_link) cur_link = max_link - 1;

			if (h_ptr->link_y[cur_link] < line) line = h_ptr->link_y[cur_link];
			if (h_ptr->link_y[cur_link] >= line + (hgt - 4)) line = h_ptr->link_y[cur_link] - (hgt - 4);
		}
		/* Return one link */
		if (k == '4')
		{
			cur_link--;
			if (cur_link < 0) cur_link = 0;

			if (h_ptr->link_y[cur_link] < line) line = h_ptr->link_y[cur_link];
			if (h_ptr->link_y[cur_link] >= line + (hgt - 4)) line = h_ptr->link_y[cur_link] - (hgt - 4);
		}

		/* Recurse on numbers */
		if (k == '\r')
		{
			if (h_ptr->link_x[cur_link] != -1)
			{
				/* Recurse on that file */
				if (!show_file(h_ptr->link[cur_link], NULL, h_ptr->link_line[cur_link], mode)) k = ESCAPE;
			}
		}

		/* Exit on escape */
		if (k == ESCAPE) break;

		/* No other key ? lets look for a shortcut */
		for (i = 0; i < max_link; i++)
		{
			if (h_ptr->link_key[i] == k)
			{
				/* Recurse on that file */
				if (!show_file(h_ptr->link[i], NULL, h_ptr->link_line[i], mode)) k = ESCAPE;
				break;
			}
		}
	}

	/* Close the file */
	my_fclose(fff);

	/* Escape */
	if (k == ESCAPE) return (FALSE);

	/* Normal return */
	return (TRUE);
}

bool_ txt_to_html(cptr head, cptr foot, cptr base, cptr ext, bool_ force, bool_ recur)
{
	int i, x;

	/* Number of "real" lines passed by */
	int next = 0;

	char buf_name[80];

	/* Color of the next line */
	byte color = TERM_WHITE;

	/* Current help file */
	FILE *fff = NULL;

	/* Current aux file */
	FILE *aux = NULL;

	/* Current html file */
	FILE *htm = NULL;

	cptr file_ext = "html";
	cptr link_prefix = "";
	cptr link_suffix = "";

	/* Pointer to general buffer in the above */
	char *buf;

	/* Allocate hyperlink data */
	std::unique_ptr<hyperlink_type> h_ptr(new hyperlink_type);
	memset(h_ptr.get(), 0, sizeof(hyperlink_type));

	/* Setup buffer pointer */
	buf = h_ptr->rbuf;

	/* Wipe the links */
	for (i = 0; i < MAX_LINKS; i++)
	{
		h_ptr->link_x[i] = -1;
	}

	sprintf(buf_name, "%s.%s", base, file_ext);

	if ((!force) && file_exist(buf_name)) return FALSE;

	/* Build the filename */
	path_build(h_ptr->path, 1024, ANGBAND_DIR_HELP, buf_name);

	/* Open the file */
	htm = my_fopen(h_ptr->path, "w");

	sprintf(buf_name, "%s.%s", base, ext);

	/* h_ptr->caption */
	sprintf(h_ptr->caption, "Help file '%s'", buf_name);

	/* Build the filename */
	path_build(h_ptr->path, 1024, ANGBAND_DIR_HELP, buf_name);

	/* Open the file */
	fff = my_fopen(h_ptr->path, "r");

	/* Oops */
	if (!fff || !htm)
	{
		my_fclose(fff);
		my_fclose(htm);

		/* Oops */
		return (TRUE);
	}

	/* Build the filename */
	path_build(h_ptr->path, 1024, ANGBAND_DIR_HELP, head);

	/* Open the file */
	aux = my_fopen(h_ptr->path, "r");

	/* Copy the header */
	if (aux)
	{
		while (TRUE)
		{
			char *find;

			if (my_fgets(aux, h_ptr->rbuf, 1024)) break;
			find = strstr(h_ptr->rbuf, "%t");
			if (find != NULL)
			{
				*find = '\0';
				find += 2;
				fprintf(htm, "%s", h_ptr->rbuf);
				fprintf(htm, "%s", base);
				fprintf(htm, "%s\n", find);
			}
			else
				fprintf(htm, "%s\n", h_ptr->rbuf);
		}
		my_fclose(aux);
	}

	/* Display the file */
	while (TRUE)
	{
		bool_ do_color = FALSE;

		/* Skip a line */
		if (my_fgets(fff, h_ptr->rbuf, 1024)) break;

		color = TERM_WHITE;

		{
			int print_x;

			/* Get a color */
			if (prefix(h_ptr->rbuf, "#####"))
			{
				color = color_char_to_attr(h_ptr->rbuf[5]);
				do_color = TRUE;
				fprintf(htm, "<FONT COLOR=\"#%02X%02X%02X\">",
				        angband_color_table[color][1],
				        angband_color_table[color][2],
				        angband_color_table[color][3]);
				buf = &h_ptr->rbuf[6];
			}
			else buf = h_ptr->rbuf;

			/* Count the "real" lines */
			next++;

			/* Skip link colors */
			if (prefix(buf, "|||||")) continue;

			/* Skip tags */
			if (prefix(buf, "~~~~~"))
			{
				int i;

				for (i = 5; (buf[i] >= '0') && (buf[i] <= '9'); i++)
					;
				buf[i] = '\0';
				fprintf(htm, "<A NAME=\"%s\"></A>", buf + 5);
				continue;
			}

			/* Dump the line */
			print_x = 0;
			if (!prefix(buf, "&&&&&"))
			{
				x = 0;
				while (buf[x])
				{
					/* Hyperlink ? */
					if (prefix(buf + x, "*****"))
					{
						int xx = x + 5, z = 0;
						char buff[80];
						char link_line[80], *s;

						if (buf[xx] == '/') xx += 2;

						/* Zap the link info */
						while (buf[xx] != '*')
						{
							buff[z++] = buf[xx];
							xx++;
						}
						xx++;
						buff[z] = '\0';

						/* Zap the link info */
						z = 0;
						while (buf[xx] != '[')
						{
							link_line[z++] = buf[xx];
							xx++;
						}
						xx++;
						link_line[z] = '\0';

						/* parse it */
						s = buff;
						while (*s != '.') s++;
						*s = '\0';
						s++;
						if (recur) txt_to_html(head, foot, buff, s, FALSE, recur);

						if (atoi(link_line)) fprintf(htm, "<A HREF=\"%s%s.%s%s#%d\">", link_prefix, buff, file_ext, link_suffix, atoi(link_line));
						else fprintf(htm, "<A HREF=\"%s%s.%s%s\">", link_prefix, buff, file_ext, link_suffix);

						/* Ok print the link name */
						while (buf[xx] != ']')
						{
							/* Now we treat the next char as printable */
							if (buf[xx] == '\\')
								xx++;
							fprintf(htm, "%c", buf[xx]);
							xx++;
							print_x++;
						}
						x = xx;

						fprintf(htm, "</A>");
					}
					/* Color ? */
					else if (prefix(buf + x, "[[[[["))
					{
						int xx = x + 6;

						color = color_char_to_attr(buf[x + 5]);
						fprintf(htm, "<FONT COLOR=\"#%02X%02X%02X\">",
						        angband_color_table[color][1],
						        angband_color_table[color][2],
						        angband_color_table[color][3]);

						/* Ok print the link name */
						while (buf[xx] != ']')
						{
							/* Now we treat the next char as printable */
							if (buf[xx] == '\\')
								xx++;
							fprintf(htm, "%c", buf[xx]);
							xx++;
							print_x++;
						}
						x++;
						x = xx;

						fprintf(htm, "</FONT>");
					}
					/* Hidden HTML tag? */
					else if (prefix(buf + x, "{{{{{"))
					{
						int xx = x + 5;

						/* Ok output the tag inside */
						while (buf[xx] != '}')
						{
							fprintf(htm, "%c", buf[xx]);
							xx++;
						}
						x++;
						x = xx;
					}
					else
					{
						fprintf(htm, "%c", buf[x]);
						print_x++;
					}

					x++;
				}
			}
			/* Verbatim mode: i.e: acacacac */
			else
			{
				byte old_color;

				x = 5;
				old_color = color_char_to_attr(buf[x]);
				fprintf(htm, "<FONT COLOR=\"#%02X%02X%02X\">",
				        angband_color_table[color][1],
				        angband_color_table[color][2],
				        angband_color_table[color][3]);
				while (buf[x])
				{
					color = color_char_to_attr(buf[x]);
					if (color != old_color)
						fprintf(htm, "</FONT><FONT COLOR=\"#%02X%02X%02X\">",
						        angband_color_table[color][1],
						        angband_color_table[color][2],
						        angband_color_table[color][3]);

					fprintf(htm, "%c", buf[x + 1]);
					print_x++;
					x += 2;
				}
				fprintf(htm, "</FONT>");
			}
		}
		if (do_color)
		{
			fprintf(htm, "</FONT>");
		}
		fprintf(htm, "\n");
	}

	/* Build the filename */
	path_build(h_ptr->path, 1024, ANGBAND_DIR_HELP, foot);

	/* Open the file */
	aux = my_fopen(h_ptr->path, "r");

	/* Copy the footer */
	if (aux)
	{
		while (TRUE)
		{
			if (my_fgets(aux, h_ptr->rbuf, 1024)) break;
			fprintf(htm, "%s\n", h_ptr->rbuf);
		}
		my_fclose(aux);
	}

	/* Close the file */
	my_fclose(htm);
	my_fclose(fff);

	/* Normal return */
	return (TRUE);
}

static void cmovie_clean_line(int y, char *abuf, char *cbuf)
{
	const byte *ap = Term->scr->a[y];
	const char *cp = Term->scr->c[y];

	byte a;
	char c;

	int x;
	int wid, hgt;
	int screen_wid, screen_hgt;


	/* Retrieve current screen size */
	Term_get_size(&wid, &hgt);

	/* Calculate the size of dungeon map area */
	screen_wid = wid - (COL_MAP + 1);
	screen_hgt = hgt - (ROW_MAP + 1);

	/* For the time being, assume 80 column display XXX XXX XXX */
	for (x = 0; x < wid; x++)
	{
		/* Convert dungeon map into default attr/chars */
		if (!character_icky &&
				((x - COL_MAP) >= 0) &&
				((x - COL_MAP) < screen_wid) &&
				((y - ROW_MAP) >= 0) &&
				((y - ROW_MAP) < screen_hgt))
		{
			/* Retrieve default attr/char */
			map_info_default(y + panel_row_prt, x + panel_col_prt, &a, &c);

			abuf[x] = conv_color[a & 0xf];

			if (c == '\0') cbuf[x] = ' ';
			else cbuf[x] = c;
		}

		else
		{
			abuf[x] = conv_color[ap[x] & 0xf];
			cbuf[x] = cp[x];
		}
	}

	/* Null-terminate the prepared strings */
	abuf[x] = '\0';
	cbuf[x] = '\0';
}

/* Take an help file screenshot(yes yes I know..) */
void help_file_screenshot(cptr name)
{
	int y, x;
	int wid, hgt;

	byte a = 0;
	char c = ' ';

	FILE *htm;

	char buf[1024];

	/* The terms package supports up to 255x255 screen size */
	char abuf[256];
	char cbuf[256];


	/* Build the filename */
	path_build(buf, 1024, ANGBAND_DIR_USER, name);

	/* Append to the file */
	htm = my_fopen(buf, "w");

	/* Oops */
	if (!htm) return;

	/* Retrieve current screen size */
	Term_get_size(&wid, &hgt);

	/* Dump the screen */
	for (y = 0; y < hgt; y++)
	{
		cmovie_clean_line(y, abuf, cbuf);

		/* Dump each row */
		fprintf(htm, "&&&&&");
		for (x = 0; x < wid; x++)
		{
			a = abuf[x];
			c = cbuf[x];

			fprintf(htm, "%c%c", a, c);
		}

		/* End the row */
		fprintf(htm, "\n");
	}

	/* Close it */
	my_fclose(htm);
}

/* Take an html screenshot */
void html_screenshot(cptr name)
{
	int y, x;
	int wid, hgt;

	byte a = 0, oa = TERM_WHITE;
	char c = ' ';

	FILE *htm;

	char buf[1024];

	/* The terms package supports up to 255x255 screen size */
	char abuf[256];
	char cbuf[256];


	/* Build the filename */
	path_build(buf, 1024, ANGBAND_DIR_USER, name);

	/* Append to the file */
	htm = my_fopen(buf, "w");

	/* Oops */
	if (!htm) return;

	/* Retrieve current screen size */
	Term_get_size(&wid, &hgt);

	fprintf(htm, "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
	             "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"DTD/xhtml1-strict.dtd\">\n"
	             "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
	             "<head>\n");
	fprintf(htm, "<meta name=\"GENERATOR\" content=\"%s\"/>\n",
	        get_version_string());
	fprintf(htm, "<title>%s</title>\n", name);
	fprintf(htm, "</head>\n"
	             "<body>\n"
	             "<pre style=\"color: #ffffff; background-color: #000000; font-family: monospace\">\n");
	fprintf(htm, "<span style=\"color: #%02X%02X%02X\">\n",
	        angband_color_table[TERM_WHITE][1],
	        angband_color_table[TERM_WHITE][2],
	        angband_color_table[TERM_WHITE][3]);

	/* Dump the screen */
	for (y = 0; y < hgt; y++)
	{
		cmovie_clean_line(y, abuf, cbuf);

		/* Dump each row */
		for (x = 0; x < wid; x++)
		{
			a = color_char_to_attr(abuf[x]);
			c = cbuf[x];

			if (oa != a)
			{
				fprintf(htm, "</span><span style=\"color: #%02X%02X%02X\">", angband_color_table[a][1], angband_color_table[a][2], angband_color_table[a][3]);
				oa = a;
			}
			if (c == '<')
				fprintf(htm, "&lt;");
			else if (c == '>')
				fprintf(htm, "&gt;");
			else if (c == '&')
				fprintf(htm, "&amp;");
			else
				fprintf(htm, "%c", c);
		}

		/* End the row */
		fprintf(htm, "\n");
	}
	fprintf(htm, "</span>\n"
	             "</pre>\n"
	             "</body>\n"
	             "</html>\n");

	/* Close it */
	my_fclose(htm);
}


/*
 * Peruse the On-Line-Help
 */
void do_cmd_help(void)
{
	/* Save screen */
	screen_save();

	/* Peruse the main help file */
	(void)show_file("help.hlp", NULL, 0, 0);

	/* Load screen */
	screen_load();
}




/*
 * Process the player name.
 * Extract a clean "base name".
 * Build the savefile name if needed.
 */
void process_player_base()
{
	char temp[128];

	/* Rename the savefile, using the player_base */
	(void)sprintf(temp, "%s", player_base);

	/* Build the filename */
	path_build(savefile, 1024, ANGBAND_DIR_SAVE, temp);
}

void process_player_name(bool_ sf)
{
	int i, k = 0;
	char tmp[50];

	/* Cannot be too long */
	if (strlen(player_base) > 15)
	{
		/* Name too long */
		quit_fmt("The name '%s' is too long!", player_base);
	}

	/* Cannot contain "icky" characters */
	for (i = 0; player_base[i]; i++)
	{
		/* No control characters */
		if (iscntrl(player_base[i]))
		{
			/* Illegal characters */
			quit_fmt("The name '%s' contains control chars!", player_base);
		}
	}

	/* Extract "useful" letters */
	for (i = 0; player_base[i]; i++)
	{
		char c = player_base[i];

		/* Accept some letters */
		if (isalpha(c) || isdigit(c)) tmp[k++] = c;

		/* Convert space, dot, and underscore to underscore */
		else if (strchr("@. _", c)) tmp[k++] = '_';
	}


#if defined(WINDOWS)

	/* Hack -- max length */
	if (k > 8) k = 8;

#endif

	/* Terminate */
	tmp[k] = '\0';
	sprintf(player_base, "%s", tmp);

	/* Require a "base" name */
	if (!player_base[0]) strcpy(player_base, "PLAYER");


	/* Change the savefile name */
	if (sf)
	{
		process_player_base();
	}
}


/*
 * Gets a name for the character, reacting to name changes.
 *
 * Assumes that "display_player(0)" has just been called
 *
 * Perhaps we should NOT ask for a name (at "birth()") on
 * Unix machines?  XXX XXX
 *
 * What a horrible name for a global function.  XXX XXX XXX
 */
void get_name(void)
{
	char tmp[32];

	/* Clear last line */
	clear_from(22);

	/* Prompt and ask */
	prt("[Enter your player's name above, or hit ESCAPE]", 23, 2);

	/* Ask until happy */
	while (1)
	{
		/* Go to the "name" field */
		move_cursor(2, 9);

		/* Save the player name */
		strcpy(tmp, player_name);

		/* Get an input, ignore "Escape" */
		if (askfor_aux(tmp, 31)) strcpy(player_name, tmp);

		/* Process the player name */
		process_player_name(FALSE);

		/* All done */
		break;
	}

	/* Pad the name (to clear junk) */
	sprintf(tmp, "%-31.31s", player_name);

	/* Re-Draw the name (in light blue) */
	c_put_str(TERM_L_BLUE, tmp, 2, 9);

	/* Erase the prompt, etc */
	clear_from(22);
}



/*
 * Hack -- commit suicide
 */
void do_cmd_suicide(void)
{
	int i;

	/* Flush input */
	flush();

	/* Verify Retirement */
	if (total_winner)
	{
		/* Verify */
		if (!get_check("Do you want to retire? ")) return;
	}

	/* Verify Suicide */
	else
	{
		/* Verify */
		if (!get_check("Do you really want to quit? ")) return;

		if (!noscore)
		{
			/* Special Verification for suicide */
			prt("Please verify QUITTING by typing the '@' sign: ", 0, 0);
			flush();
			i = inkey();
			prt("", 0, 0);
			if (i != '@') return;
		}
	}

	/* Stop playing */
	alive = FALSE;

	/* Kill the player */
	death = TRUE;

	/* Leaving */
	p_ptr->leaving = TRUE;

	/* Cause of death */
	(void)strcpy(died_from, "Quitting");
}


	/* HACK - Remove / set the CAVE_VIEW flag, since view_x / view_y
	 * is not saved, and the visible locations are not lighted correctly
	 * when the game is loaded again
	 * Alternatively forget_view() and update_view() can be used
	 */
void remove_cave_view(bool_ remove)
{
	int i;
	cave_type *c_ptr;

	if (view_n)
	{
		/* Clear them all */
		for (i = 0; i < view_n; i++)
		{
			int y = view_y[i];
			int x = view_x[i];

			/* Access the grid */
			c_ptr = &cave[y][x];

			if (remove)
				c_ptr->info &= ~(CAVE_VIEW);
			else
				c_ptr->info |= (CAVE_VIEW);
		}
	}
}

/*
 * Save the game
 */
void do_cmd_save_game(void)
{
	remove_cave_view(TRUE);

	/* Save the current level if in a persistent level */
	save_dungeon();

	/* Autosaves do not disturb */
	if (!is_autosave)
	{
		/* Disturb the player */
		disturb(1);
	}

	/* Clear messages */
	msg_print(NULL);

	/* Handle stuff */
	handle_stuff();

	/* Message */
	prt("Saving game...", 0, 0);

	/* Refresh */
	Term_fresh();

	/* The player is not dead */
	(void)strcpy(died_from, "(saved)");

	/* Save the player */
	if (save_player())
	{
		prt("Saving game... done.", 0, 0);
	}

	/* Save failed (oops) */
	else
	{
		prt("Saving game... failed!", 0, 0);
	}

	remove_cave_view(FALSE);

	/* Refresh */
	Term_fresh();

	/* Note that the player is not dead */
	(void)strcpy(died_from, "(alive and well)");
}

/*
 * Auto-save depending on whether the auto save flag is set.
 */
void autosave_checkpoint()
{
	if (autosave_l)
	{
		is_autosave = TRUE;
		msg_print("Autosaving the game...");
		do_cmd_save_game();
		is_autosave = FALSE;
	}
}

/*
 * Hack -- Calculates the total number of points earned                -JWT-
 */
static long total_points(void)
{
	s16b max_dl = 0, i, k;
	long temp, Total = 0;
	long mult = 20; /* was 100. Divided values by 5 because of an overflow error */
	long comp_death = (p_ptr->companion_killed * 2 / 5);

	if (!comp_death) comp_death = 1;

	if (p_ptr->preserve) mult -= 1;  /* Penalize preserve, maximize modes */
	mult -= 1; /* maximize pentalty, always on */
	if (auto_scum) mult -= 4;
	if (small_levels) mult += ((always_small_level) ? 4 : 10);
	if (empty_levels) mult += 2;
	if (smart_learn) mult += 4;

	if (mult < 2) mult = 2;  /* At least 10% of the original score */
	/* mult is now between 2 and 40, i.e. 10% and 200% */

	for (i = 0; i < max_d_idx; i++)
		if (max_dlv[i] > max_dl)
			max_dl = max_dlv[i];

	temp = p_ptr->lev * p_ptr->lev * p_ptr->lev * p_ptr->lev + (100 * max_dl);

	temp += p_ptr->max_exp / 5;

	temp = (temp * mult / 20);

	/* Gold increases score */
	temp += p_ptr->au / 5;

	/* Completing quest increase score */
	for (i = 0; i < MAX_Q_IDX; i++)
	{
		if (quest[i].status >= QUEST_STATUS_COMPLETED)
		{
			temp += 2000;
			temp += quest[i].level * 100;
		}
	}

	/* Death of a companion is BAD */
	temp /= comp_death;

	/* The know objects increase the score */
	/* Scan the object kinds */
	for (k = 1; k < max_k_idx; k++)
	{
		object_kind *k_ptr = &k_info[k];

		/* Hack -- skip artifacts */
		if (k_ptr->flags3 & (TR3_INSTA_ART)) continue;

		/* List known flavored objects */
		if (k_ptr->flavor && k_ptr->aware)
		{
			object_type *i_ptr;
			object_type object_type_body;

			/* Get local object */
			i_ptr = &object_type_body;

			/* Create fake object */
			object_prep(i_ptr, k);

			temp += object_value_real(i_ptr);
		}
	}

	for (k = 1; k < max_r_idx; k++)
	{
		monster_race *r_ptr = &r_info[k];

		if (r_ptr->flags1 & (RF1_UNIQUE))
		{
			bool_ dead = (r_ptr->max_num == 0);

			if (dead)
			{
				/* Uniques are supposed to be harder */
				Total += 50;
			}
		}
		else
		{
			s16b This = r_ptr->r_pkills;

			if (This > 0)
			{
				Total += This;
			}
		}
	}
	temp += Total * 50;

	if (total_winner) temp += 1000000;



	return (temp);
}



/*
 * Centers a string within a 31 character string                -JWT-
 */
static void center_string(char *buf, cptr str)
{
	int i, j;

	/* Total length */
	i = strlen(str);

	/* Necessary border */
	j = 15 - i / 2;

	/* Mega-Hack */
	(void)sprintf(buf, "%*s%s%*s", j, "", str, 31 - i - j, "");
}


/*
 * Display a "tomb-stone"
 */
static void print_tomb(void)
{
	cptr p;

	char tmp[160];

	char buf[1024];
	char dummy[80];

	FILE *fp;

	time_t ct = time(nullptr);


	/* Clear screen */
	Term_clear();

	/* Build the filename */
	path_build(buf, 1024, ANGBAND_DIR_FILE, "dead.txt");

	/* Open the News file */
	fp = my_fopen(buf, "r");

	/* Dump */
	if (fp)
	{
		int i = 0;

		/* Dump the file to the screen */
		while (0 == my_fgets(fp, buf, 1024))
		{
			/* Display and advance */
			display_message(0, i++, strlen(buf), TERM_WHITE, buf);
		}

		/* Close */
		my_fclose(fp);
	}


	/* King or Queen */
	if (total_winner || (p_ptr->lev > PY_MAX_LEVEL))
	{
		p = "Magnificent";
	}

	/* Normal */
	else
	{
		p = cp_ptr->titles[(p_ptr->lev - 1) / 5];
	}

	center_string(buf, player_name);
	put_str(buf, 6, 11);

	center_string(buf, "the");
	put_str(buf, 7, 11);

	center_string(buf, p);
	put_str(buf, 8, 11);


	center_string(buf, spp_ptr->title);
	put_str(buf, 10, 11);

	(void)sprintf(tmp, "Level: %d", (int)p_ptr->lev);
	center_string(buf, tmp);
	put_str(buf, 11, 11);

	(void)sprintf(tmp, "Exp: %ld", (long)p_ptr->exp);
	center_string(buf, tmp);
	put_str(buf, 12, 11);

	(void)sprintf(tmp, "AU: %ld", (long)p_ptr->au);
	center_string(buf, tmp);
	put_str(buf, 13, 11);

	(void)sprintf(tmp, "Killed on Level %d", dun_level);
	center_string(buf, tmp);
	put_str(buf, 14, 11);


	if (strlen(died_from) > 24)
	{
		strncpy(dummy, died_from, 24);
		dummy[24] = '\0';
		(void)sprintf(tmp, "by %s.", dummy);
	}
	else
		(void)sprintf(tmp, "by %s.", died_from);

	center_string(buf, tmp);
	put_str(buf, 15, 11);


	(void)sprintf(tmp, "%-.24s", ctime(&ct));
	center_string(buf, tmp);
	put_str(buf, 17, 11);
}


/*
 * Display some character info
 */
static void show_info(void)
{
	int i, j, k;
	object_type *o_ptr;
	store_type *st_ptr;

	/* Hack -- Know everything in the inven/equip */
	for (i = 0; i < INVEN_TOTAL; i++)
	{
		o_ptr = &p_ptr->inventory[i];

		/* Skip non-objects */
		if (!o_ptr->k_idx) continue;

		/* Aware and Known */
		object_aware(o_ptr);
		object_known(o_ptr);
	}

	for (i = 1; i < max_towns; i++)
	{
		st_ptr = &town_info[i].store[7];

		/* Hack -- Know everything in the home */
		for (j = 0; j < st_ptr->stock_num; j++)
		{
			o_ptr = &st_ptr->stock[j];

			/* Skip non-objects */
			if (!o_ptr->k_idx) continue;

			/* Aware and Known */
			object_aware(o_ptr);
			object_known(o_ptr);
		}
	}

	/* Hack -- Recalculate bonuses */
	p_ptr->update |= (PU_BONUS);

	/* Handle stuff */
	handle_stuff();

	/* Flush all input keys */
	flush();

	/* Flush messages */
	msg_print(NULL);


	/* Describe options */
	prt("You may now dump a character record to one or more files.", 21, 0);
	prt("Then, hit RETURN to see the character, or ESC to abort.", 22, 0);

	/* Dump character records as requested */
	while (TRUE)
	{
		char out_val[160];

		/* Prompt */
		put_str("Filename(you can post it to http://angband.oook.cz/): ", 23, 0);

		/* Default */
		strcpy(out_val, "");

		/* Ask for filename (or abort) */
		if (!askfor_aux(out_val, 60)) return;

		/* Return means "show on screen" */
		if (!out_val[0]) break;

		/* Save screen */
		character_icky = TRUE;
		Term_save();

		/* Dump a character file */
		(void)file_character(out_val, TRUE);

		/* Load screen */
		Term_load();
		character_icky = FALSE;
	}


	/* Display player */
	display_player(0);

	/* Prompt for p_ptr->inventory */
	prt("Hit any key to see more information (ESC to abort): ", 23, 0);

	/* Allow abort at this point */
	if (inkey() == ESCAPE) return;


	/* Show equipment and inventory */

	/* Equipment -- if any */
	if (equip_cnt)
	{
		Term_clear();
		show_equip_full();
		prt("You are using: -more-", 0, 0);
		if (inkey() == ESCAPE) return;
	}

	/* Inventory -- if any */
	if (inven_cnt)
	{
		Term_clear();
		show_inven_full();
		prt("You are carrying: -more-", 0, 0);
		if (inkey() == ESCAPE) return;
	}

	/* Homes in the different towns */
	for (k = 1; k < max_towns; k++)
	{
		st_ptr = &town_info[k].store[7];

		/* Home -- if anything there */
		if (st_ptr->stock_num)
		{
			/* Display contents of the home */
			for (k = 0, i = 0; i < st_ptr->stock_num; k++)
			{
				/* Clear screen */
				Term_clear();

				/* Show 12 items */
				for (j = 0; (j < 12) && (i < st_ptr->stock_num); j++, i++)
				{
					char o_name[80];
					char tmp_val[80];

					/* Acquire item */
					o_ptr = &st_ptr->stock[i];

					/* Print header, clear line */
					sprintf(tmp_val, "%c) ", I2A(j));
					prt(tmp_val, j + 2, 4);

					/* Display object description */
					object_desc(o_name, o_ptr, TRUE, 3);
					c_put_str(tval_to_attr[o_ptr->tval], o_name, j + 2, 7);
				}

				/* h_ptr->caption */
				prt(format("Your home contains (page %d): -more-", k + 1), 0, 0);

				/* Wait for it */
				if (inkey() == ESCAPE) return;
			}
		}
	}
}





/*
 * Display the scores in a given range.
 * Assumes the high score list is already open.
 * Only five entries per line, too much info.
 *
 * Mega-Hack -- allow "fake" entry at the given position.
 */
static void display_scores_aux(int highscore_fd, int from, int to, int note, high_score *score)
{
	int i, j, k, n, place;
	byte attr;
	char out_val[256];
	char tmp_val[160];
	high_score the_score;


	/* Paranoia -- it may not have opened */
	if (highscore_fd < 0) return;


	/* Assume we will show the first 10 */
	if (from < 0) from = 0;
	if (to < 0) to = 10;
	if (to > MAX_HISCORES) to = MAX_HISCORES;


	/* Seek to the beginning */
	if (highscore_seek(highscore_fd, 0)) return;

	/* Hack -- Count the high scores */
	for (i = 0; i < MAX_HISCORES; i++)
	{
		if (highscore_read(highscore_fd, &the_score)) break;
	}

	/* Hack -- allow "fake" entry to be last */
	if ((note == i) && score) i++;

	/* Forget about the last entries */
	if (i > to) i = to;


	/* Show 5 per page, until "done" */
	for (k = from, place = k + 1; k < i; k += 5)
	{
		/* Clear screen */
		Term_clear();

		/* Title */
		put_str(format("              %s Hall of Fame", game_module), 0, 0);

		/* Indicate non-top scores */
		if (k > 0)
		{
			sprintf(tmp_val, "(from position %d)", k + 1);
			put_str(tmp_val, 0, 40);
		}

		/* Dump 5 entries */
		for (j = k, n = 0; j < i && n < 5; place++, j++, n++)
		{
			int pcs, pr, ps, pc, clev, mlev, cdun, mdun;

			cptr gold, when, aged;

			int in_quest;

			/* Hack -- indicate death in yellow */
			attr = (j == note) ? TERM_YELLOW : TERM_WHITE;


			/* Mega-Hack -- insert a "fake" record */
			if ((note == j) && score)
			{
				the_score = (*score);
				attr = TERM_L_GREEN;
				score = NULL;
				note = -1;
				j--;
			}

			/* Read a normal record */
			else
			{
				/* Read the proper record */
				if (highscore_seek(highscore_fd, j)) break;
				if (highscore_read(highscore_fd, &the_score)) break;
			}

			/* Extract the race/class */
			pr = atoi(the_score.p_r);
			ps = atoi(the_score.p_s);
			pc = atoi(the_score.p_c);
			pcs = atoi(the_score.p_cs);

			/* Extract the level info */
			clev = atoi(the_score.cur_lev);
			mlev = atoi(the_score.max_lev);
			cdun = atoi(the_score.cur_dun);
			mdun = atoi(the_score.max_dun);

			in_quest = atoi(the_score.inside_quest);

			/* Hack -- extract the gold and such */
			for (when = the_score.day; isspace(*when); when++) /* loop */;
			for (gold = the_score.gold; isspace(*gold); gold++) /* loop */;
			for (aged = the_score.turns; isspace(*aged); aged++) /* loop */;

			/* Dump some info */
			sprintf(out_val, "%3d.%9s  %s the %s %s, Level %d",
				place,
				the_score.pts,
				the_score.who,
				get_player_race_name(pr, ps),
				class_info[pc].spec[pcs].title,
			        clev);

			/* Append a "maximum level" */
			if (mlev > clev) strcat(out_val, format(" (Max %d)", mlev));

			/* Dump the first line */
			c_put_str(attr, out_val, n*4 + 2, 0);

			/* Another line of info */
			if (in_quest)
			{
				sprintf(out_val, "               Killed by %s while questing",
				        the_score.how);
			}
			/* Hack -- some people die in the town */
			else if (!cdun)
			{
				sprintf(out_val, "               Killed by %s in the Town",
				        the_score.how);
			}
			else
			{
				sprintf(out_val, "               Killed by %s on %s %d",
				        the_score.how, "Dungeon Level", cdun);
			}

			/* Append a "maximum level" */
			if (mdun > cdun) strcat(out_val, format(" (Max %d)", mdun));

			/* Dump the info */
			c_put_str(attr, out_val, n*4 + 3, 0);

			/* And still another line of info */
			sprintf(out_val,
			        "               (Date %s, Gold %s, Turn %s).",
			        when, gold, aged);
			c_put_str(attr, out_val, n*4 + 4, 0);
		}


		/* Wait for response */
		prt("[Press ESC to quit, any other key to continue.]", 23, 17);
		j = inkey();
		prt("", 23, 0);

		/* Hack -- notice Escape */
		if (j == ESCAPE) break;
	}
}


/*
 * show_highclass - selectively list highscores based on class
 * -KMW-
 */
void show_highclass(int building)
{

	int i = 0, j, m = 0;
	int pr, pc, clev;
	high_score the_score;
	char buf[1024], out_val[256];
	int highscore_fd;

	switch (building)
	{
	case 1:
		prt("               Busts of Greatest Kings", 5, 0);
		break;
	case 2:
		prt("               Plaque - Greatest Arena Champions", 5, 0);
		break;
	case 10:
		prt("               Plaque - Greatest Fighters", 5, 0);
		break;
	case 11:
		prt("               Spires of the Greatest Magic-Users", 5, 0);
		break;
	case 12:
		prt("               Busts of Greatest Priests", 5, 0);
		break;
	case 13:
		prt("               Wall Inscriptions - Greatest Thieves", 5, 0);
		break;
	case 14:
		prt("               Plaque - Greatest Rangers", 5, 0);
		break;
	case 15:
		prt("               Plaque - Greatest Paladins", 5, 0);
		break;
	case 16:
		prt("               Spires of the Greatest Illusionists", 5, 0);
		break;
	default:
		bell();
		break;
	}

	/* Build the filename */
	path_build(buf, 1024, ANGBAND_DIR_USER, "scores.raw");

	/* Open file */
	highscore_fd = fd_open(buf, O_RDONLY);

	if (highscore_fd < 0)
	{
		msg_print("Score file unavailable.");
		msg_print(NULL);
		return;
	}

	if (highscore_seek(highscore_fd, 0)) return;

	for (i = 0; i < MAX_HISCORES; i++)
		if (highscore_read(highscore_fd, &the_score)) break;

	m = 0;
	j = 0;
	clev = 0;

	while ((m < 9) || (j < MAX_HISCORES))
	{
		if (highscore_seek(highscore_fd, j)) break;
		if (highscore_read(highscore_fd, &the_score)) break;
		pr = atoi(the_score.p_r);
		pc = atoi(the_score.p_c);
		clev = atoi(the_score.cur_lev);
		if (((pc == (building - 10)) && (building != 1)) ||
				((building == 1) && (clev >= PY_MAX_LEVEL)))
		{
			sprintf(out_val, "%3d) %s the %s (Level %2d)",
				(m + 1), the_score.who, race_info[pr].title, clev);
			prt(out_val, (m + 7), 0);
			m++;
		}
		j++;
	}

	/* Now, list the active player if they qualify */
	if ((building == 1) && (p_ptr->lev >= PY_MAX_LEVEL))
	{
		sprintf(out_val, "You) %s the %s (Level %2d)",
			player_name, race_info[p_ptr->prace].title, p_ptr->lev);
		prt(out_val, (m + 8), 0);
	}
	else if ((building != 1))
	{
		if ((p_ptr->lev > clev) && (p_ptr->pclass == (building - 10)))
		{
			sprintf(out_val, "You) %s the %s (Level %2d)",
				player_name, race_info[p_ptr->prace].title, p_ptr->lev);
			prt(out_val, (m + 8), 0);
		}
	}

	fd_close(highscore_fd);

	msg_print("Hit any key to continue");
	msg_print(NULL);
	for (j = 5; j < 18; j++)
		prt("", j, 0);
}


/*
 * Race Legends
 * -KMW-
 */
void race_score(int race_num)
{
	int i = 0, j, m = 0;
	int pr, clev, lastlev;
	high_score the_score;
	char buf[1024], out_val[256], tmp_str[80];
	int highscore_fd;

	lastlev = 0;

	/* rr9: TODO - pluralize the race */
	sprintf(tmp_str, "The Greatest of all the %s", race_info[race_num].title);
	prt(tmp_str, 5, 3);

	/* Build the filename */
	path_build(buf, 1024, ANGBAND_DIR_USER, "scores.raw");

	/* Open the highscore file */
	highscore_fd = fd_open(buf, O_RDONLY);

	if (highscore_fd < 0)
	{
		msg_print("Score file unavailable.");
		msg_print(NULL);
		return;
	}

	if (highscore_seek(highscore_fd, 0)) return;

	for (i = 0; i < MAX_HISCORES; i++)
	{
		if (highscore_read(highscore_fd, &the_score)) break;
	}

	m = 0;
	j = 0;

	while ((m < 10) && (j < i))
	{
		if (highscore_seek(highscore_fd, j)) break;
		if (highscore_read(highscore_fd, &the_score)) break;
		pr = atoi(the_score.p_r);
		clev = atoi(the_score.cur_lev);
		if (pr == race_num)
		{
			sprintf(out_val, "%3d) %s the %s (Level %3d)",
			        (m + 1), the_score.who,
				race_info[pr].title, clev);
			prt(out_val, (m + 7), 0);
			m++;
			lastlev = clev;
		}
		j++;
	}

	/* add player if qualified */
	if ((p_ptr->prace == race_num) && (p_ptr->lev >= lastlev))
	{
		sprintf(out_val, "You) %s the %s (Level %3d)",
			player_name, race_info[p_ptr->prace].title, p_ptr->lev);
		prt(out_val, (m + 8), 0);
	}

	fd_close(highscore_fd);
}


/*
 * Race Legends
 * -KMW-
 */
void race_legends(void)
{
	int i, j;

	for (i = 0; i < max_rp_idx; i++)
	{
		race_score(i);
		msg_print("Hit any key to continue");
		msg_print(NULL);
		for (j = 5; j < 19; j++)
			prt("", j, 0);
	}
}




/*
 * Enters a players name on a hi-score table, if "legal", and in any
 * case, displays some relevant portion of the high score list.
 */
static errr top_twenty(void)
{
	int j;

	high_score the_score;

	time_t ct = time((time_t*)0);

	char buf[1024];

	int highscore_fd = 0;

	/* Build the filename */
	path_build(buf, sizeof(buf), ANGBAND_DIR_USER, "scores.raw");

	/* Open the highscore file, for reading/writing */
	highscore_fd = fd_open(buf, O_RDWR);

	if (highscore_fd < 0)
	{
		msg_print("Score file unavailable.");
		msg_print(NULL);
		goto out;
	}

	/* Clear screen */
	Term_clear();

	/* Wizard-mode pre-empts scoring */
	if (noscore & 0x000F)
	{
		msg_print("Score not registered for wizards.");
		msg_print(NULL);
		display_scores_aux(highscore_fd, 0, 10, -1, NULL);
		goto out;
	}

	/* Borg-mode pre-empts scoring */
	if (noscore & 0x00F0)
	{
		msg_print("Score not registered for borgs.");
		msg_print(NULL);
		display_scores_aux(highscore_fd, 0, 10, -1, NULL);
		goto out;
	}

	/* Cheaters are not scored */
	if (noscore & 0xFF00)
	{
		msg_print("Score not registered for cheaters.");
		msg_print(NULL);
		display_scores_aux(highscore_fd, 0, 10, -1, NULL);
		goto out;
	}

	/* Interupted */
	if (!total_winner && streq(died_from, "Interrupting"))
	{
		msg_print("Score not registered due to interruption.");
		msg_print(NULL);
		display_scores_aux(highscore_fd, 0, 10, -1, NULL);
		goto out;
	}

	/* Quitter */
	if (!total_winner && streq(died_from, "Quitting"))
	{
		msg_print("Score not registered due to quitting.");
		msg_print(NULL);
		display_scores_aux(highscore_fd, 0, 10, -1, NULL);
		goto out;
	}


	/* Clear the record */
	static_assert(std::is_pod<high_score>::value,
		      "Cannot memset a non-POD type");
	memset(&the_score, 0, sizeof(high_score));

	/* Save the version */
	sprintf(the_score.what, "%ld.%ld.%ld",
	        (long int) VERSION_MAJOR, (long int) VERSION_MINOR, (long int) VERSION_PATCH);

	/* Calculate and save the points */
	sprintf(the_score.pts, "%9lu", (long)total_points());
	the_score.pts[9] = '\0';

	/* Save the current gold */
	sprintf(the_score.gold, "%9lu", (long)p_ptr->au);
	the_score.gold[9] = '\0';

	/* Save the current turn */
	sprintf(the_score.turns, "%9lu", (long)turn);
	the_score.turns[9] = '\0';

	/* Save the date in standard form (8 chars) */
	strftime(the_score.day, 9, "%m/%d/%y", localtime(&ct));

	/* Save the player name (15 chars) */
	sprintf(the_score.who, "%-.15s", player_name);

	/* Save the player info XXX XXX XXX */
	sprintf(the_score.sex, "%c", (p_ptr->psex ? 'm' : 'f'));
	sprintf(the_score.p_r, "%2d", p_ptr->prace);
	sprintf(the_score.p_s, "%2d", p_ptr->pracem);
	sprintf(the_score.p_c, "%2d", p_ptr->pclass);
	sprintf(the_score.p_cs, "%2d", p_ptr->pspec);

	/* Save the level and such */
	sprintf(the_score.cur_lev, "%3d", p_ptr->lev);
	sprintf(the_score.cur_dun, "%3d", dun_level);
	sprintf(the_score.max_lev, "%3d", p_ptr->max_plv);
	sprintf(the_score.max_dun, "%3d", max_dlv[dungeon_type]);

	sprintf(the_score.inside_quest, "%3d", p_ptr->inside_quest);

	/* Save the cause of death (31 chars) */
	sprintf(the_score.how, "%-.31s", died_from);


	/* Add a new entry to the score list, see where it went */
	j = highscore_add(highscore_fd, &the_score);


	/* Hack -- Display the top fifteen scores */
	if (j < 10)
	{
		display_scores_aux(highscore_fd, 0, 15, j, NULL);
	}

	/* Display the scores surrounding the player */
	else
	{
		display_scores_aux(highscore_fd, 0, 5, j, NULL);
		display_scores_aux(highscore_fd, j - 2, j + 7, j, NULL);
	}


out:
	if (highscore_fd >= 0)
	{
		fd_close(highscore_fd);
	}

	/* Success */
	return (0);
}


/*
 * Predict the players location, and display it.
 */
static errr predict_score(void)
{
	int j;

	high_score the_score;

	char buf[1024];

	int highscore_fd = 0;

	/* Build the filename */
	path_build(buf, sizeof(buf), ANGBAND_DIR_USER, "scores.raw");

	/* Open the highscore file */
	highscore_fd = fd_open(buf, O_RDONLY);

	if (highscore_fd < 0)
	{
		msg_print("Score file unavailable.");
		msg_print(NULL);
		goto out;
	}

	/* Clear the record */
	static_assert(std::is_pod<high_score>::value,
		      "Cannot memset a non-POD type");
	memset(&the_score, 0, sizeof(high_score));

	/* Save the version */
	sprintf(the_score.what, "%ld.%ld.%ld",
	        (long int) VERSION_MAJOR, (long int) VERSION_MINOR, (long int) VERSION_PATCH);

	/* Calculate and save the points */
	sprintf(the_score.pts, "%9lu", (long)total_points());
	the_score.pts[9] = '\0';

	/* Save the current gold */
	sprintf(the_score.gold, "%9lu", (long)p_ptr->au);
	the_score.gold[9] = '\0';

	/* Save the current turn */
	sprintf(the_score.turns, "%9lu", (long)turn);
	the_score.turns[9] = '\0';

	/* Hack -- no time needed */
	strcpy(the_score.day, "TODAY");

	/* Save the player name (15 chars) */
	sprintf(the_score.who, "%-.15s", player_name);

	/* Save the player info XXX XXX XXX */
	sprintf(the_score.sex, "%c", (p_ptr->psex ? 'm' : 'f'));
	sprintf(the_score.p_r, "%2d", p_ptr->prace);
	sprintf(the_score.p_s, "%2d", p_ptr->pracem);
	sprintf(the_score.p_c, "%2d", p_ptr->pclass);
	sprintf(the_score.p_cs, "%2d", p_ptr->pspec);

	/* Save the level and such */
	sprintf(the_score.cur_lev, "%3d", p_ptr->lev);
	sprintf(the_score.cur_dun, "%3d", dun_level);
	sprintf(the_score.max_lev, "%3d", p_ptr->max_plv);
	sprintf(the_score.max_dun, "%3d", max_dlv[dungeon_type]);

	sprintf(the_score.inside_quest, "%3d", p_ptr->inside_quest);

	/* Hack -- no cause of death */
	strcpy(the_score.how, "nobody (yet!)");


	/* See where the entry would be placed */
	j = highscore_where(highscore_fd, &the_score);


	/* Hack -- Display the top fifteen scores */
	if (j < 10)
	{
		display_scores_aux(highscore_fd, 0, 15, j, &the_score);
	}

	/* Display some "useful" scores */
	else
	{
		display_scores_aux(highscore_fd, 0, 5, -1, NULL);
		display_scores_aux(highscore_fd, j - 2, j + 7, j, &the_score);
	}

out:
	if (highscore_fd >= 0)
	{
		fd_close(highscore_fd);
	}

	/* Success */
	return (0);
}


void predict_score_gui(bool_ *initialized_p, bool_ *game_in_progress_p)
{
	char buf[1024];
	int highscore_fd;

	/* Paranoia */
	if (!(*initialized_p) || character_icky ||
	    !(*game_in_progress_p) || !character_generated)
	{
		/* Can't happen but just in case */
		plog("You may not do that right now.");
		return;
	}

	/* Build the pathname of the score file */
	path_build(buf, sizeof(buf), ANGBAND_DIR_USER, "scores.raw");

	/* Hack - open the score file for reading */
	highscore_fd = fd_open(buf, O_RDONLY);

	/* Paranoia - No score file */
	if (highscore_fd < 0)
	{
		msg_print("Score file is not available.");
		return;
	}

	/* Mega-Hack - prevent various functions XXX XXX XXX */
	*initialized_p = FALSE;

	/* Save screen */
	screen_save();

	/* Clear screen */
	Term_clear();

	/* Prepare scores */
	if ((*game_in_progress_p) && character_generated)
	{
		predict_score();
	}

	/* Close the high score file */
	(void)fd_close(highscore_fd);

	/* Forget the fd */
	highscore_fd = -1;

	/* Restore screen */
	screen_load();

	/* Hack - Flush it */
	Term_fresh();

	/* Mega-Hack - We are ready again */
	*initialized_p = TRUE;
}


/*
 * Change the player into a King!                        -RAK-
 */
static void kingly(void)
{
	/* Hack -- retire in town */
	dun_level = 0;

	/* Fake death */
	(void)strcpy(died_from, "Ripe Old Age");

	/* Restore the experience */
	p_ptr->exp = p_ptr->max_exp;

	/* Restore the level */
	p_ptr->lev = p_ptr->max_plv;

	/* Hack -- Instant Gold */
	p_ptr->au += 10000000L;

	/* Clear screen */
	Term_clear();

	/* Would like to see something more Tolkienian here... */

	/* Display a crown */
	put_str("#", 1, 34);
	put_str("#####", 2, 32);
	put_str("#", 3, 34);
	put_str(",,,  $$$  ,,,", 4, 28);
	put_str(",,=$   \"$$$$$\"   $=,,", 5, 24);
	put_str(",$$        $$$        $$,", 6, 22);
	put_str("*>         <*>         <*", 7, 22);
	put_str("$$         $$$         $$", 8, 22);
	put_str("\"$$        $$$        $$\"", 9, 22);
	put_str("\"$$       $$$       $$\"", 10, 23);
	put_str("*#########*#########*", 11, 24);
	put_str("*#########*#########*", 12, 24);

	/* Display a message */
	put_str("Veni, Vidi, Vici!", 15, 26);
	put_str("I came, I saw, I conquered!", 16, 21);
	put_str(format("All Hail the Mighty %s!", sp_ptr->winner), 17, 22);

	/* Flush input */
	flush();

	/* Wait for response */
	pause_line(23);
}


/*
 * Wipe the saved levels
 */
void wipe_saved()
{
	int d, l, od = dungeon_type, ol = dun_level;

	for (d = 0; d < max_d_idx; d++)
	{
		dungeon_info_type *d_ptr = &d_info[d];

		for (l = d_ptr->mindepth; l <= d_ptr->maxdepth; l++)
		{
			char buf[10];

			dun_level = l;
			dungeon_type = d;
			if (get_dungeon_save(buf))
			{
				char tmp[80], name[1024];

				sprintf(tmp, "%s.%s", player_base, buf);
				path_build(name, 1024, ANGBAND_DIR_SAVE, tmp);

				/* Remove the dungeon save file */
				fd_kill(name);
			}
		}
	}

	dungeon_type = od;
	dun_level = ol;
}


/*
 * Close up the current game (player may or may not be dead)
 *
 * This function is called only from "main.c" and "signals.c".
 */
void close_game(void)
{
	/* Handle stuff */
	handle_stuff();

	/* Flush the messages */
	msg_print(NULL);

	/* Flush the input */
	flush();


	/* Hack -- Character is now "icky" */
	character_icky = TRUE;


	/* Handle death */
	if (death)
	{
		/* Handle retirement */
		if (total_winner)
		{
			/* Make a note */
			add_note_type(NOTE_WINNER);

			kingly();
		}

		/* Wipe the saved levels */
		wipe_saved();

		/* Save memories */
		if (!save_player()) msg_print("Death save failed!");

		/* You are dead */
		print_tomb();

		/* Show more info */
		show_info();

		/* Make a note */
		{
			char long_day[30];
			char buf[80];
			time_t ct = time((time_t*)NULL);

			/* Get the date */
			strftime(long_day, 30,
			         "%Y-%m-%d at %H:%M:%S", localtime(&ct));

			/* Create string */
			sprintf(buf, "\n%s was killed by %s on %s\n", player_name,
			        died_from, long_day);

			/* Output to the notes file */
			output_note(buf);
		}

		/* Handle score, show Top scores */
		top_twenty();
	}

	/* Still alive */
	else
	{
		is_autosave = FALSE;

		/* Save the game */
		do_cmd_save_game();

		/* Make a note pf session end */
		add_note_type(NOTE_SAVE_GAME);

		/* Prompt for scores XXX XXX XXX */
		prt("Press Return (or Escape).", 0, 40);

		/* Predict score (or ESCAPE) */
		if (inkey() != ESCAPE) predict_score();
	}
}


/*
 * Grab a randomly selected line in lib/file/file_name
 */
errr get_rnd_line(const char *file_name, char *output)
{
	FILE *fp;

	char buf[1024];

	int lines = 0;

	int line;

	int i;


	/* Clear the output buffer */
	strcpy(output, "");

	/* test hack */
	if (wizard && cheat_xtra) msg_print(file_name);

	/* Build the filename */
	path_build(buf, 1024, ANGBAND_DIR_FILE, file_name);

	/* Open the file */
	fp = my_fopen(buf, "r");

	/* Failed */
	if (!fp) return ( -1);

	/* Read the first line */
	if (0 != my_fgets(fp, buf, 80))
	{
		my_fclose(fp);
		return ( -1);
	}

	/* Retrieve number of valid lines in the file */
	lines = atoi(buf);

	/* Pick a line in the file */
	line = randint(lines);

	/*
	 * Scan through the file XXX XXX XXX
	 * Seemingly wrong use of the counter is justified by the
	 * stupid 'buffer' lines in the random text files.
	 */
	for (i = 0; i <= line; i++)
	{
		if (0 != my_fgets(fp, buf, 80))
		{
			my_fclose(fp);
			return ( -1);
		}

		/* Found the line */
		if (i == line) break;
	}

	/* Copy the line to the output buffer */
	strcpy(output, buf);

	/* Close the file */
	my_fclose(fp);

	/* Success */
	return (0);
}


/*
 * Read line'th line file the file
 * and return pointer to it, or NULL if it fails.
 *
 * Nuked the static buffer. Caller should provide one. -- pelpel
 *
 * Caution: 'linbuf' should be at least 80 byte long.
 */
char *get_line(const char* fname, cptr fdir, char *linbuf, int line)
{
	FILE* fp;
	int i;
	char buf[1024];


	/* Don't count the first line in the file, which is a comment line */
	line++;

	/* Build the filename */
	path_build(buf, 1024, fdir, fname);

	/* Open the file */
	fp = my_fopen(buf, "r");

	/* Failed */
	if (!fp) return (NULL);

	/* Read past specified number of lines */
	for (i = 0; i <= line; i++)
	{
		/* Oops */
		if (my_fgets(fp, linbuf, 80) != 0)
		{
			my_fclose(fp);
			return (NULL);
		}
	}

	my_fclose(fp);

	return (linbuf);
}


/*
 * Return a line for a speaking unique, by Matt G.
 *
 * XXX XXX XXX Opening a file and scanning it through whenever a unique
 * tries to say something? Something like DELAY_LOAD_?_TEXT would be
 * much better -- pelpel
 *
 * XXX XXX XXX I must say the original is an extremely poor and unreliable
 * implementation...  I removed noxious flag -- I'm too stupid to
 * understand such complexities -- and added extra error checkings
 * and made sure fd is always closed -- pelpel
 */
errr get_xtra_line(const char *file_name, monster_type *m_ptr, char *output)
{
	FILE *fp;
	char buf[1024];
	int line;
	int num_entries;
	int i;
	int mnum;


	/* Clear the message buffer */
	strcpy(output, "");

	/* test and DEBUG hack */
	if (wizard && cheat_xtra)
	{
		msg_print(file_name);
	}

	/* Build the filename */
	path_build(buf, 1024, ANGBAND_DIR_FILE, file_name);

	/* Open the file */
	fp = my_fopen(buf, "r");

	/* Failed */
	if (!fp) return ( -1);

	/* Monster number we are looking for */
	mnum = m_ptr->r_idx;

	/* Find matching N: line */
	while (1)
	{
		int n;

		/* Read a line */
		if (my_fgets(fp, buf, 90) != 0)
		{
			my_fclose(fp);
			return ( -1);
		}

		/* Not a N: line */
		if (buf[0] != 'N') continue;

		/* Skip "N:" and parse off a number */
		sscanf(buf + 2, "%d", &n);

		/* Match found */
		if (n == mnum) break;
	}

	/* Retrieve number of normal messages */
	while (1)
	{
		/* Read next line */
		if (my_fgets(fp, buf, 90) != 0)
		{
			my_fclose(fp);
			return ( -1);
		}

		/* The first line not beginning with 'N:' holds number of lines */
		if (buf[0] != 'N')
		{
			num_entries = atoi(buf);
			break;
		}
	}

	/* The monster is afraid */
	if (m_ptr->monfear)
	{
		/* Read past normal lines */
		for (line = 0; line < num_entries + 1; line++)
		{
			if (my_fgets(fp, buf, 90))
			{
				my_fclose(fp);
				return ( -1);
			}
		}

		/* Retrieve number of 'afraid' lines */
		num_entries = atoi(buf);
	}


	/* Pick a random line */
	line = rand_int(num_entries);

	/* test and DEBUG hack */
	if (wizard && cheat_xtra)
	{
		sprintf(buf, "Line number %d", line);
		msg_print(buf);
	}

	/* Find the selected line */
	for (i = 0; i <= line; i++)
	{
		/* Oops */
		if (0 != my_fgets(fp, buf, 90))
		{
			my_fclose(fp);
			return ( -1);
		}

		/* Found it */
		if (i == line) break;
	}

	/* Copy it to the output buffer */
	strcpy(output, buf);

	/* Close the file */
	my_fclose(fp);

	/* Success */
	return (0);
}
