/* テスト */
/*
 * Debug Status: Not checked.
 * - Fixed __T on this page.
 */
/*
 * File: files.c
 * Purpose: Various file-related activities, poorly organised
 *
 * Copyright (c) 1997 Ben Harrison, James E. Wilson, Robert A. Koeneke
 *
 * This work is free software; you can redistribute it and/or modify it
 * under the terms of either:
 *
 * a) the GNU General Public License as published by the Free Software
 *    Foundation, version 2, or
 *
 * b) the "Angband licence":
 *    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.  Other copyrights may also apply.
 */
#include "angband.h"
#include "object/tvalsval.h"
#include "ui-menu.h"
#include "game-cmd.h"
#include "cmds.h"
#include "option.h"

#define MAX_PANEL 12

/*
 * Returns a "rating" of x depending on y, and sets "attr" to the
 * corresponding "attribute".
 */
static const _TCHAR *likert(int x, int y, byte *attr)
{
	/* Paranoia */
	if (y <= 0) y = 1;

	/* Negative value */
	if (x < 0)
	{
		*attr = TERM_RED;
		return (__T("Very Bad"));
	}

	/* Analyze the value */
	switch ((x / y))
	{
		case 0:
		case 1:
		{
			*attr = TERM_RED;
			return (__T("Bad"));
		}
		case 2:
		{
			*attr = TERM_RED;
			return (__T("Poor"));
		}
		case 3:
		case 4:
		{
			*attr = TERM_YELLOW;
			return (__T("Fair"));
		}
		case 5:
		{
			*attr = TERM_YELLOW;
			return (__T("Good"));
		}
		case 6:
		{
			*attr = TERM_YELLOW;
			return (__T("Very Good"));
		}
		case 7:
		case 8:
		{
			*attr = TERM_L_GREEN;
			return (__T("Excellent"));
		}
		case 9:
		case 10:
		case 11:
		case 12:
		case 13:
		{
			*attr = TERM_L_GREEN;
			return (__T("Superb"));
		}
		case 14:
		case 15:
		case 16:
		case 17:
		{
			*attr = TERM_L_GREEN;
			return (__T("Heroic"));
		}
		default:
		{
			*attr = TERM_L_GREEN;
			return (__T("Legendary"));
		}
	}
}

/*
 * Obtain the "flags" for the player as if he was an item
 */
void player_flags(u32b f[OBJ_FLAG_N])
{
	/* Add racial flags */
	memcpy(f, rp_ptr->flags, sizeof(rp_ptr->flags)); // TODO Check this works

	/* Some classes become immune to fear at a certain plevel */
	if ((cp_ptr->flags & CF_BRAVERY_30) && p_ptr->lev >= 30)
		f[1] |= TR1_RES_FEAR;
}

/*
 * Equippy chars
 */
static void display_player_equippy(int y, int x)
{
	int i;

	byte a;
	char c;

	object_type *o_ptr;

	/* Dump equippy chars */
	for (i = INVEN_WIELD; i < INVEN_TOTAL; ++i)
	{
		/* Object */
		o_ptr = &inventory[i];

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

		/* Get attr/char for display */
		a = object_attr(o_ptr);
		c = object_char(o_ptr);

		/* Dump */
		Term_putch(x+i-INVEN_WIELD, y, a, c);
	}
}

/*
 * List of resistances and abilities to display
 */
#define RES_ROWS 9

struct player_flag_record
{
	const _TCHAR name[7];		/* Name of resistance/ability */
	byte set;				/* Which field this resistance is in { 1 2 3 } */
	u32b res_flag;			/* resistance flag bit */
	u32b im_flag;			/* corresponding immunity bit, if any */
	u32b vuln_flag;			/* corresponding vulnerability flag, if any */
};

static const struct player_flag_record player_flag_table[RES_ROWS*4] =
{
	{ __T(" Acid"),	1, TR1_RES_ACID,	TR1_IM_ACID,	TR1_VULN_ACID },
	{ __T(" Elec"),	1, TR1_RES_ELEC,	TR1_IM_ELEC,	TR1_VULN_ELEC },
	{ __T(" Fire"),	1, TR1_RES_FIRE,	TR1_IM_FIRE,	TR1_VULN_FIRE },
	{ __T(" Cold"),	1, TR1_RES_COLD,	TR1_IM_COLD,	TR1_VULN_COLD },
	{ __T(" Pois"),	1, TR1_RES_POIS,	0, 0 },
	{ __T(" Fear"),	1, TR1_RES_FEAR,	0, 0 },
	{ __T(" Lite"),	1, TR1_RES_LITE,	0, 0 },
	{ __T(" Dark"),	1, TR1_RES_DARK,	0, 0 },
	{ __T("Blind"),	1, TR1_RES_BLIND,	0, 0 },

	{ __T("Confu"),	1, TR1_RES_CONFU,	0, 0 },
	{ __T("Sound"),	1, TR1_RES_SOUND,	0, 0 },
	{ __T("Shard"),	1, TR1_RES_SHARD,	0, 0 },
	{ __T("Nexus"),	1, TR1_RES_NEXUS,	0, 0 },
	{ __T("Nethr"),	1, TR1_RES_NETHR,	0, 0 },
	{ __T("Chaos"),	1, TR1_RES_CHAOS,	0, 0 },
	{ __T("Disen"),	1, TR1_RES_DISEN,	0, 0 },
	{ __T("S.Dig"),	2, TR2_SLOW_DIGEST,	0, 0 },
	{ __T("Feath"),	2, TR2_FEATHER, 	0, 0 },

	{ __T("PLite"),	2, TR2_LITE, 		0, 0 },
	{ __T("Regen"),	2, TR2_REGEN, 		0, 0 },
	{ __T("Telep"),	2, TR2_TELEPATHY, 	0, 0 },
	{ __T("Invis"),	2, TR2_SEE_INVIS, 	0, 0 },
	{ __T("FrAct"),	2, TR2_FREE_ACT, 	0, 0 },
	{ __T("HLife"),	2, TR2_HOLD_LIFE, 	0, 0 },
	{ __T("ImpHP"),	2, TR2_IMPAIR_HP,	0, 0 },
	{ __T("ImpSP"),	2, TR2_IMPAIR_MANA,	0, 0 },
	{ __T(" Fear"),      2, TR2_AFRAID,          0, 0 },

	{ __T("Aggrv"),      2, TR2_AGGRAVATE,       0, 0 },
	{ __T("Stea."),	0, TR0_STEALTH,		0, 0 },
	{ __T("Sear."),	0, TR0_SEARCH,		0, 0 },
	{ __T("Infra"),	0, TR0_INFRA,		0, 0 },
	{ __T("Tunn."),	0, TR0_TUNNEL,		0, 0 },
	{ __T("Speed"),	0, TR0_SPEED,		0, 0 },
	{ __T("Blows"),	0, TR0_BLOWS,		0, 0 },
	{ __T("Shots"),	0, TR0_SHOTS,		0, 0 },
	{ __T("Might"),	0, TR0_MIGHT,		0, 0 },
};

#define RES_COLS (5 + 2 + INVEN_TOTAL - INVEN_WIELD)
static const region resist_region[] =
{
	{  0*(RES_COLS+1), 10, RES_COLS, RES_ROWS+2 },
	{  1*(RES_COLS+1), 10, RES_COLS, RES_ROWS+2 },
	{  2*(RES_COLS+1), 10, RES_COLS, RES_ROWS+2 },
	{  3*(RES_COLS+1), 10, RES_COLS, RES_ROWS+2 },
};

static void display_resistance_panel(const struct player_flag_record *resists,
									size_t size, const region *bounds)
{
	size_t i, j;
	int col = bounds->col;
	int row = bounds->row;
	Term_putstr(col, row++, RES_COLS, TERM_WHITE, __T("      abcdefghijkl@"));
	for (i = 0; i < size-3; i++, row++)
	{
		byte name_attr = TERM_WHITE;
		Term_gotoxy(col+6, row);
		/* repeated extraction of flags is inefficient but more natural */
		for (j = INVEN_WIELD; j <= INVEN_TOTAL; j++)
		{
			object_type *o_ptr = &inventory[j];
			u32b f[OBJ_FLAG_N];

			byte attr = TERM_WHITE | (j % 2) * 8; /* alternating columns */
			char sym = '.';

			bool res, imm, vuln;

			if (j < INVEN_TOTAL)
				object_flags_known(o_ptr, f);
			else
			{
				player_flags(f);

				/* If the race has innate infravision, force the corresponding flag
				   here.  If we set it in player_flags(), then all callers of that
				   function will think the infravision is caused by equipment. */
				if (rp_ptr->infra > 0)
					f[0] |= (TR0_INFRA);
			}

			res = (0 != (f[resists[i].set] & resists[i].res_flag));
			imm = (0 != (f[resists[i].set] & resists[i].im_flag));
			vuln = (0 != (f[resists[i].set] & resists[i].vuln_flag));

			if (imm) name_attr = TERM_GREEN;
			else if (res && name_attr == TERM_WHITE) name_attr = TERM_L_BLUE;

			if (vuln) sym = '-';
			else if (imm) sym = '*';
			else if (res) sym = '+';
			Term_addch(attr, sym);
		}
		Term_putstr(col, row, 6, name_attr, format(__T("%5s:"), resists[i].name));
	}
	Term_putstr(col, row++, RES_COLS, TERM_WHITE, __T("      abcdefghijkl@"));
	/* Equippy */
	display_player_equippy(row++, col+6);
}

static void display_player_flag_info(void)
{
	int i;
	for (i = 0; i < 4; i++)
	{
		display_resistance_panel(player_flag_table+(i*RES_ROWS), RES_ROWS+3,
								&resist_region[i]);
	}
}

/*
 * Special display, part 2b
 */
void display_player_stat_info(void)
{
	int i, row, col;

	_TCHAR buf[80];

	/* Row */
	row = 2;

	/* Column */
	col = 42;

	/* Print out the labels for the columns */
	c_put_str(TERM_WHITE, __T("  Self"), row-1, col+5);
	c_put_str(TERM_WHITE, __T(" RB"), row-1, col+12);
	c_put_str(TERM_WHITE, __T(" CB"), row-1, col+16);
	c_put_str(TERM_WHITE, __T(" EB"), row-1, col+20);
	c_put_str(TERM_WHITE, __T("  Best"), row-1, col+24);

	/* Display the stats */
	for (i = 0; i < A_MAX; i++)
	{
		/* Reduced */
		if (p_ptr->state.stat_use[i] < p_ptr->state.stat_top[i])
		{
			/* Use lowercase stat name */
			put_str(stat_names_reduced[i], row+i, col);
		}

		/* Normal */
		else
		{
			/* Assume uppercase stat name */
			put_str(stat_names[i], row+i, col);
		}

		/* Indicate natural maximum */
		if (p_ptr->stat_max[i] == 18+100)
		{
			put_str(__T("!"), row+i, col+3);
		}

		/* Internal "natural" maximum value */
		cnv_stat(p_ptr->stat_max[i], buf, _countof(buf));
		c_put_str(TERM_L_GREEN, buf, row+i, col+5);

		/* Race Bonus */
		strnfmt(buf, _countof(buf), __T("%+3d"), rp_ptr->r_adj[i]);
		c_put_str(TERM_L_BLUE, buf, row+i, col+12);

		/* Class Bonus */
		strnfmt(buf, _countof(buf), __T("%+3d"), cp_ptr->c_adj[i]);
		c_put_str(TERM_L_BLUE, buf, row+i, col+16);

		/* Equipment Bonus */
		strnfmt(buf, _countof(buf), __T("%+3d"), p_ptr->state.stat_add[i]);
		c_put_str(TERM_L_BLUE, buf, row+i, col+20);

		/* Resulting "modified" maximum value */
		cnv_stat(p_ptr->state.stat_top[i], buf, _countof(buf));
		c_put_str(TERM_L_GREEN, buf, row+i, col+24);

		/* Only display stat_use if not maximal */
		if (p_ptr->state.stat_use[i] < p_ptr->state.stat_top[i])
		{
			cnv_stat(p_ptr->state.stat_use[i], buf, _countof(buf));
			c_put_str(TERM_YELLOW, buf, row+i, col+31);
		}
	}
}

/*
 * Special display, part 2c
 *
 * How to print out the modifications and sustains.
 * Positive mods with no sustain will be light green.
 * Positive mods with a sustain will be dark green.
 * Sustains (with no modification) will be a dark green 's'.
 * Negative mods (from a curse) will be red.
 * Huge mods (>9), like from MICoMorgoth, will be a '*'
 * No mod, no sustain, will be a slate '.'
 */
static void display_player_sust_info(void)
{
	int i, row, col, stat;

	object_type *o_ptr;
	u32b f[OBJ_FLAG_N];

	byte a;
	char c;

	/* Row */
	row = 2;

	/* Column */
	col = 26;

	/* low-level dependencies */
	assert(TR0_STR == (1<<A_STR));
	assert(TR0_INT == (1<<A_INT));
	assert(TR0_WIS == (1<<A_WIS));
	assert(TR0_DEX == (1<<A_DEX));
	assert(TR0_CON == (1<<A_CON));
	assert(TR0_CHR == (1<<A_CHR));

	assert(TR1_SUST_STR == (1<<A_STR));
	assert(TR1_SUST_INT == (1<<A_INT));
	assert(TR1_SUST_WIS == (1<<A_WIS));
	assert(TR1_SUST_DEX == (1<<A_DEX));
	assert(TR1_SUST_CON == (1<<A_CON));
	assert(TR1_SUST_CHR == (1<<A_CHR));

	/* Header */
	c_put_str(TERM_WHITE, __T("abcdefghijkl@"), row-1, col);

	/* Process equipment */
	for (i = INVEN_WIELD; i < INVEN_TOTAL; ++i)
	{
		/* Get the object */
		o_ptr = &inventory[i];

		/* Get the "known" flags */
		object_flags_known(o_ptr, f);

		/* Initialize color based of sign of pval. */
		for (stat = 0; stat < A_MAX; stat++)
		{
			/* Default */
			a = TERM_SLATE;
			c = '.';

			/* Boost */
			if (f[0] & (1<<stat))
			{
				/* Default */
				c = '*';

				/* Good */
				if (o_ptr->pval > 0)
				{
					/* Good */
					a = TERM_L_GREEN;

					/* Label boost */
					if (o_ptr->pval < 10) c = I2D(o_ptr->pval);
				}

				/* Bad */
				if (o_ptr->pval < 0)
				{
					/* Bad */
					a = TERM_RED;

					/* Label boost */
					if (o_ptr->pval > -10) c = I2D(-(o_ptr->pval));
				}
			}

			/* Sustain */
			if (f[1] & (1<<stat))
			{
				/* Dark green */
				a = TERM_GREEN;

				/* Convert '.' to 's' */
				if (c == '.') c = 's';
			}

			/* Dump proper character */
			Term_putch(col, row+stat, a, c);
		}
		/* Advance */
		col++;
	}

	/* Player flags */
	player_flags(f);

	/* Check stats */
	for (stat = 0; stat < A_MAX; ++stat)
	{
		/* Default */
		a = TERM_SLATE;
		c = '.';

		/* Sustain */
		if (f[1] & (1<<stat))
		{
			/* Dark green "s" */
			a = TERM_GREEN;
			c = 's';
		}

		/* Dump */
		Term_putch(col, row+stat, a, c);
	}

	/* Column */
	col = 26;

	/* Footer */
	c_put_str(TERM_WHITE, __T("abcdefghijkl@"), row+6, col);

	/* Equippy */
	display_player_equippy(row+7, col);
}

static void display_panel(const data_panel *panel, int count, bool left_adj, const region *bounds)
{
	int i;
	_TCHAR buffer[50];
	int col = bounds->col;
	int row = bounds->row;
	int w = bounds->width;
	int offset = 0;

	region_erase(bounds);

	if (left_adj)
	{
		for (i = 0; i < count; i++)
		{
			int len = panel[i].label ? _tcslen(panel[i].label) : 0;
			if (offset < len) 
				offset = len;
		}
		offset += 2;
	}

	for (i = 0; i < count; i++, row++)
	{
		int len;
		if (!panel[i].label) 
			continue;
		Term_putstr(col, row, _tcslen(panel[i].label), TERM_WHITE, panel[i].label);

		strnfmt(buffer, _countof(buffer), panel[i].fmt, panel[i].value[0], panel[i].value[1]);

		len = _tcslen(buffer);
		len = len < w - offset ? len : w - offset - 1;
		if (left_adj)
			Term_putstr(col+offset, row, len, panel[i].color, buffer);
		else
			Term_putstr(col+w-len, row, len, panel[i].color, buffer);
	}
}

static const region boundaries [] =
{
	/* x   y     width, rows */
	{ 0,   0,		0,		0 },
	{ 1,   1,		40,		8 }, /* Name, Class, ... */
	{ 1,  10,		22,		8 }, /* Cur Exp, Max Exp, ... */
	{ 26, 10,		17,		8 }, /* AC, melee, ... */
	{ 48, 10,		24,		8 }, /* skills */
	{ 21,  2,		18,		5 }, /* Age, ht, wt, ... */
};

static const _TCHAR *show_title(void)
{
	if (p_ptr->wizard)
		return __T("[=-WIZARD-=]");
	else if (p_ptr->total_winner || p_ptr->lev > PY_MAX_LEVEL)
		return __T("***WINNER***");
	else
		return c_text + cp_ptr->title[(p_ptr->lev - 1) / 5];
}

static const _TCHAR *show_adv_exp(void)
{
	if (p_ptr->lev < PY_MAX_LEVEL)
	{
		static _TCHAR buffer[30];
		s32b advance = (player_exp[p_ptr->lev - 1] * p_ptr->expfact / 100L);
		strnfmt(buffer, _countof(buffer), __T("%d"), advance);
		return buffer;
	}
	else 
	{
		return __T("********");
	}
}

static const _TCHAR *show_depth(void)
{
	static _TCHAR buffer[13];

	if (p_ptr->max_depth == 0) 
		return __T("Town");

	strnfmt(buffer, _countof(buffer), __T("%d' (L%d)"),
	        p_ptr->max_depth * 50, p_ptr->max_depth);
	return buffer;
}

static const _TCHAR *show_speed(void)
{
	static _TCHAR buffer[10];
	int tmp = p_ptr->state.speed;
	if (p_ptr->timed[TMD_FAST]) tmp -= 10;
	if (p_ptr->timed[TMD_SLOW]) tmp += 10;
	if (p_ptr->searching) tmp += 10;
	if (tmp == 110) return __T("Normal");
	strnfmt(buffer, _countof(buffer), __T("%d"), tmp - 110);
	return buffer;
}

static const _TCHAR *show_melee_weapon(const object_type *o_ptr)
{
	static _TCHAR buffer[12];
	int hit = p_ptr->state.dis_to_h;
	int dam = p_ptr->state.dis_to_d;

	if (object_known_p(o_ptr))
	{
		hit += o_ptr->to_h;
		dam += o_ptr->to_d;
	}

	strnfmt(buffer, _countof(buffer), __T("(%+d,%+d)"), hit, dam);
	return buffer;
}

static const _TCHAR *show_missile_weapon(const object_type *o_ptr)
{
	static _TCHAR buffer[12];
	int hit = p_ptr->state.dis_to_h;
	int dam = 0;

	if (object_known_p(o_ptr))
	{
		hit += o_ptr->to_h;
		dam += o_ptr->to_d;
	}

	strnfmt(buffer, _countof(buffer), __T("(%+d,%+d)"), hit, dam);
	return buffer;
}

static byte max_color(int val, int max)
{
	return val < max ? TERM_YELLOW : TERM_L_GREEN;
}

static const _TCHAR *show_status(void)
{
	int sc = p_ptr->sc;
	sc /= 10;

	switch (sc)
	{
		case 0:
		case 1:
			return __T("Pariah");

		case 2:
			return __T("Outcast");

		case 3:
		case 4:
			return __T("Unknown");

		case 5:
			return __T("Known");

		case 6:
		/* Maximum status by birth 75 = 7 */
		case 7:
			return __T("Liked");

		case 8:
			return __T("Well-liked");

		case 9:
		case 10:
			return __T("Respected");

		case 11:
		case 12:
			return __T("Role model");

		case 13:
			return __T("Feared");

		case 14:
		case 15:
			return __T("Lordly");
	}

	return format(__T("%d"), sc);
}

/* data_panel array element initializer, for ansi compliance */
#define P_I(col, lab, format, val1, val2) \
	{ panel[i].color = col; panel[i].label = lab; panel[i].fmt = format; \
	 panel[i].value[0] = val1; panel[i].value[1] = val2; \
	 i++; }

/* colours for table items */
static const byte colour_table[] =
{
	TERM_RED, TERM_RED, TERM_RED, TERM_L_RED, TERM_ORANGE,
	TERM_YELLOW, TERM_YELLOW, TERM_GREEN, TERM_GREEN, TERM_L_GREEN,
	TERM_L_BLUE
};

static int get_panel(int oid, data_panel *panel, size_t size)
{
	int ret = (s32b) size;
	switch(oid)
	{
		case 1:
		{
			int i = 0;
			assert( size >= (u32b) boundaries[1].page_rows);
			ret = boundaries[1].page_rows;
			P_I(TERM_L_BLUE, __T("Name"),	__T("%y"),	  s2u(op_ptr->full_name), END  );
			P_I(TERM_L_BLUE, __T("Sex"),	__T("%y"),	  s2u(sp_ptr->title), END  );
			P_I(TERM_L_BLUE, __T("Race"),	__T("%y"),	  s2u(p_name + rp_ptr->name), END  );
			P_I(TERM_L_BLUE, __T("Class"),	__T("%y"),	  s2u(c_name + cp_ptr->name), END  );
			P_I(TERM_L_BLUE, __T("Title"),	__T("%y"),	  s2u(show_title()), END  );
			P_I(TERM_L_BLUE, __T("HP"),		__T("%y/%y"), i2u(p_ptr->chp), i2u(p_ptr->mhp)  );
			P_I(TERM_L_BLUE, __T("SP"),		__T("%y/%y"), i2u(p_ptr->csp), i2u(p_ptr->msp)  );
			P_I(TERM_L_BLUE, __T("Level"),	__T("%y"),	  i2u(p_ptr->lev), END  );
			assert(i == boundaries[1].page_rows);
			return ret;
		}
		case 2:
		{
			int i = 0;
			assert( ret >= boundaries[2].page_rows);
			ret = boundaries[2].page_rows;
			P_I(max_color(p_ptr->lev, p_ptr->max_lev), __T("Level"), __T("%y"), i2u(p_ptr->lev), END  );
			P_I(max_color(p_ptr->exp, p_ptr->max_exp), __T("Cur Exp"), __T("%y"), i2u(p_ptr->exp), END  );
			P_I(TERM_L_GREEN, __T("Max Exp"),	__T("%y"),	i2u(p_ptr->max_exp), END  );
			P_I(TERM_L_GREEN, __T("Adv Exp"),	__T("%y"),	s2u(show_adv_exp()), END  );
			P_I(TERM_L_GREEN, __T("MaxDepth"),	__T("%y"),	s2u(show_depth()), END  );
			P_I(TERM_L_GREEN, __T("Turns"),		__T("%y"),	i2u(turn), END  );
			P_I(TERM_L_GREEN, __T("Gold"),		__T("%y"),	i2u(p_ptr->au), END  );
			P_I(TERM_L_GREEN, __T("Burden"),	__T("%.1y lbs"),	f2u(p_ptr->total_weight/10.0f), END  );
			assert(i == boundaries[2].page_rows);
			return ret;
			}
			case 3:
			{
			int i = 0;
			assert(ret >= boundaries[3].page_rows);
			ret = boundaries[3].page_rows;
			P_I(TERM_L_BLUE, __T("Armor"), __T("[%y,%+y]"),	i2u(p_ptr->state.dis_ac), i2u(p_ptr->state.dis_to_a)  );
			P_I(TERM_L_BLUE, __T("Fight"), __T("(%+y,%+y)"),	i2u(p_ptr->state.dis_to_h), i2u(p_ptr->state.dis_to_d)  );
			P_I(TERM_L_BLUE, __T("Melee"), __T("%y"),			s2u(show_melee_weapon(&inventory[INVEN_WIELD])), END  );
			P_I(TERM_L_BLUE, __T("Shoot"), __T("%y"),			s2u(show_missile_weapon(&inventory[INVEN_BOW])), END  );
			P_I(TERM_L_BLUE, __T("Blows"), __T("%y/turn"),	i2u(p_ptr->state.num_blow), END  );
			P_I(TERM_L_BLUE, __T("Shots"), __T("%y/turn"),	i2u(p_ptr->state.num_fire), END  );
			P_I(TERM_L_BLUE, __T("Infra"), __T("%y ft"),		i2u(p_ptr->state.see_infra * 10), END  );
			P_I(TERM_L_BLUE, __T("Speed"), __T("%y"),			s2u(show_speed()), END );
			assert(i == boundaries[3].page_rows);
			return ret;
		}
		case 4:
		{
			static struct 
			{
				const _TCHAR *name;
				int skill;
				int div;
			} skills[] =
			{
				{ __T("Saving Throw"), SKILL_SAVE, 6 },
				{ __T("Stealth"), SKILL_STEALTH, 1 },
				{ __T("Fighting"), SKILL_TO_HIT_MELEE, 12 },
				{ __T("Shooting"), SKILL_TO_HIT_BOW, 12 },
				{ __T("Disarming"), SKILL_DISARM, 8 },
				{ __T("Magic Device"), SKILL_DEVICE, 6 },
				{ __T("Perception"), SKILL_SEARCH_FREQUENCY, 6 },
				{ __T("Searching"), SKILL_SEARCH, 6 }
			};
			int i;
			assert(N_ELEMENTS(skills) == boundaries[4].page_rows);
			ret = N_ELEMENTS(skills);
			if ((u32b) ret > size) ret = size;
			for (i = 0; i < ret; i++)
			{
				s16b skill = p_ptr->state.skills[skills[i].skill];
				panel[i].color = TERM_L_BLUE;
				panel[i].label = skills[i].name;
				if (skills[i].skill == SKILL_SAVE ||
						skills[i].skill == SKILL_SEARCH)
				{
					if (skill > 100) skill = 100;
					panel[i].fmt = __T("%y%%");
					panel[i].value[0] = i2u(skill);
					panel[i].color = colour_table[skill / 10];
				}
				else if (skills[i].skill == SKILL_SEARCH_FREQUENCY)
				{
					if (skill <= 0) skill = 1;
					if (skill >= 50)
					{
						panel[i].fmt = __T("1 in 1");
						panel[i].color = colour_table[10];
					}
					else
					{
						/* convert to % chance of searching */
						skill = 50 - skill;
						panel[i].fmt = __T("1 in %y");
						panel[i].value[0] = i2u(skill);
						panel[i].color =
							colour_table[(100 - skill*2) / 10];
					}
				}
				else if (skills[i].skill == SKILL_DISARM)
				{
					/* assume disarming a dungeon trap */
					skill -= 5;
					if (skill > 100) skill = 100;
					if (skill < 2) skill = 2;
					panel[i].fmt = __T("%y%%");
					panel[i].value[0] = i2u(skill);
					panel[i].color = colour_table[skill / 10];
				}
				else
				{
					panel[i].fmt = __T("%y");
					panel[i].value[0] = s2u(likert(skill, skills[i].div, &panel[i].color));
				}
			}
			return ret;
		}
		case 5:
		{
			int i = 0;
			assert(ret >= boundaries[5].page_rows);
			ret = boundaries[5].page_rows;
			P_I(TERM_L_BLUE, __T("Age"),		__T("%y"),	i2u(p_ptr->age), END );
			P_I(TERM_L_BLUE, __T("Height"),		__T("%y"),	i2u(p_ptr->ht), END  );
			P_I(TERM_L_BLUE, __T("Weight"),		__T("%y"),	i2u(p_ptr->wt), END  );
			P_I(TERM_L_BLUE, __T("Social"),		__T("%y"),	s2u(show_status()), END  );
			P_I(TERM_L_BLUE, __T("Maximize"),	__T("%y"),	c2u(OPT(adult_maximize) ? 'Y' : 'N'), END);
	#if 0
			/* Preserve mode deleted */
			P_I(TERM_L_BLUE, __T("Preserve"),	__T("%y"),	c2u(adult_preserve ? 'Y' : 'N'), END);
	#endif
			assert(i == boundaries[5].page_rows);
			return ret;
		}
	}
	/* hopefully not reached */
	return 0;
}

void display_player_xtra_info(void)
{
	int i;
	int panels [] = { 1, 2, 3, 4, 5};
	bool left_adj [] = { 1, 0, 0, 0, 0 };
	data_panel data[MAX_PANEL];

	for (i = 0; i < (int)N_ELEMENTS(panels); i++)
	{
		int oid = panels[i];
		int rows = get_panel(oid, data, N_ELEMENTS(data));

		/* Hack:  Don't show 'Level' in the name, class ...  panel */
		if (oid == 1) 
			rows -= 1;

		display_panel(data, rows, left_adj[i], &boundaries[oid]);
	}
	/* Indent output by 1 character, and wrap at column 72 */
	text_out_wrap = 72;
	text_out_indent = 1;

	/* History */
	Term_gotoxy(text_out_indent, 19);
	text_out_to_screen(TERM_WHITE, p_ptr->history);

	/* Reset text_out() vars */
	text_out_wrap = 0;
	text_out_indent = 0;

	return;
}

/*
 * Display the character on the screen (two different modes)
 *
 * The top two lines, and the bottom line (or two) are left blank.
 *
 * Mode 0 = standard display with skills/history
 * Mode 1 = special display with equipment flags
 */
void display_player(int mode)
{
	/* Erase screen */
	clear_from(0);

	/* Stat info */
	display_player_stat_info();

	if (mode)
	{
		data_panel data[MAX_PANEL];
		int rows = get_panel(1, data, N_ELEMENTS(data));

		display_panel(data, rows, 1, &boundaries[1]);

		/* Stat/Sustain flags */
		display_player_sust_info();

		/* Other flags */
		display_player_flag_info();
	}

	/* Standard */
	else
	{
		/* Extra info */
		display_player_xtra_info();
	}
}

/*
 * 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(const _TCHAR *path, bool full)
{
	int i, x, y;

	byte a;
	_TCHAR c;

	ang_file *fp;

	store_type *st_ptr = &store[STORE_HOME];

	_TCHAR o_name[80];

	_TCHAR buf[1024];

	UNREFERENCED_PARAMETER(full);

	/* Open the file for writing */
	fp = file_open(path, MODE_WRITE, FTYPE_TEXT);
	if (!fp) return (-1);

	text_out_hook = text_out_to_file;
	text_out_file = fp;

	/* Begin dump */
	file_putf(fp, __T("  [%s %s Character Dump]\n\n"),
	        VERSION_NAME, VERSION_STRING);

	/* Display player */
	display_player(0);

	/* Dump part of the screen */
	for (y = 1; y < 23; 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;
		}

		/* Back up over spaces */
		while ((x > 0) && (buf[x-1] == ' ')) --x;

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

		/* End the row */
		file_putf(fp, __T("%s\n"), buf);
	}

	/* Skip a line */
	file_putf(fp, __T("\n"));

	/* Display player */
	display_player(1);

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

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

		/* Back up over spaces */
		while ((x > 0) && (buf[x-1] == ' ')) --x;

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

		/* End the row */
		file_putf(fp, __T("%s\n"), buf);
	}

	/* Skip a line */
	file_putf(fp, __T("\n"));

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

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

		/* Back up over spaces */
		while ((x > 0) && (buf[x-1] == ' ')) --x;

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

		/* End the row */
		file_putf(fp, __T("%s\n"), buf);
	}

	/* Skip some lines */
	file_putf(fp, __T("\n\n"));

	/* If dead, dump last messages -- Prfnoff */
	if (p_ptr->is_dead)
	{
		i = messages_num();
		if (i > 15) i = 15;
		file_putf(fp, __T("  [Last Messages]\n\n"));
		while (i-- > 0)
		{
			file_putf(fp, __T("> %s\n"), message_str((s16b)i));
		}
		file_putf(fp, __T("\nKilled by %s.\n\n"), p_ptr->died_from);
	}

	/* Set the indent/wrap */
	text_out_indent = 5;
	text_out_wrap = 72;

	/* Dump the equipment */
	if (p_ptr->equip_cnt)
	{
		file_putf(fp, __T("  [Character Equipment]\n\n"));
		for (i = INVEN_WIELD; i < INVEN_TOTAL; i++)
		{
			object_desc(o_name, _countof(o_name), &inventory[i], TRUE, ODESC_FULL);

			file_putf(fp, __T("%c) %s\n"), index_to_label(i), o_name);
			if (inventory[i].k_idx)
				object_info_chardump(&inventory[i]);
		}
		file_putf(fp, __T("\n\n"));
	}

	/* Dump the inventory */
	file_putf(fp, __T("  [Character Inventory]\n\n"));
	for (i = 0; i < INVEN_PACK; i++)
	{
		if (!inventory[i].k_idx) break;

		object_desc(o_name, _countof(o_name), &inventory[i], TRUE, ODESC_FULL);

		file_putf(fp, __T("%c) %s\n"), index_to_label(i), o_name);
		object_info_chardump(&inventory[i]);
	}
	file_putf(fp, __T("\n\n"));

	/* Dump the Home -- if anything there */
	if (st_ptr->stock_num)
	{
		/* Header */
		file_putf(fp, __T("  [Home Inventory]\n\n"));

		/* Dump all available items */
		for (i = 0; i < st_ptr->stock_num; i++)
		{
			object_desc(o_name, _countof(o_name), &st_ptr->stock[i], TRUE, ODESC_FULL);
			file_putf(fp, __T("%c) %s\n"), I2A(i), o_name);

			object_info_chardump(&st_ptr->stock[i]);
		}

		/* Add an empty line */
		file_putf(fp, __T("\n\n"));
	}

	text_out_indent = text_out_wrap = 0;

	/* Dump character history */
	dump_history(fp);
	file_putf(fp, __T("\n\n"));

	/* Dump options */
	file_putf(fp, __T("  [Options]\n\n"));

	/* Dump options */
	for (i = OPT_ADULT; i < OPT_MAX; i++)
	{
		if (option_name(i))
		{
			file_putf(fp, __T("%-45s: %s (%s)\n"),
			        option_desc(i),
			        op_ptr->opt[i] ? __T("yes") : __T("no "),
			        option_name(i));
		}
	}

	/* Skip some lines */
	file_putf(fp, __T("\n\n"));

	file_close(fp);

	/* Success */
	return (0);
}

/*
 * Make a string lower case.
 */
static void string_lower(_TCHAR *buf)
{
	_TCHAR *s;

	/* Lowercase the string */
	for (s = buf; *s != 0; s++) *s = _totlower(*s);
}

/*
 * Recursive file perusal.
 *
 * Return FALSE on "?", otherwise TRUE.
 *
 * This function could be made much more efficient with the use of "seek"
 * functionality, especially when moving backwards through a file, or
 * forwards through a file by less than a page at a time.  XXX XXX XXX
 */
bool show_file(_TCHAR *name, const _TCHAR *what, int line, int mode)
{
	int i, k, n;

	_TCHAR ch;

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

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

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

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

	/* Case sensitive search */
	bool case_sensitive = FALSE;

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

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

	/* Jump to this tag */
	const _TCHAR *tag = NULL;

	/* Hold a string to find */
	_TCHAR finder[80] = __T("");

	/* Hold a string to show */
	_TCHAR shower[80] = __T("");

	/* Filename */
	_TCHAR filename[1024];

	/* Describe this thing */
	_TCHAR caption[128] = __T("");

	/* Path buffer */
	_TCHAR path[1024];

	/* General buffer */
	_TCHAR buf[1024];

	/* Lower case version of the buffer, for searching */
	_TCHAR lc_buf[1024];

	/* Sub-menu information */
	_TCHAR hook[26][32];

	int wid, hgt;

	/* Wipe the hooks */
	for (i = 0; i < 26; i++) hook[i][0] = 0;

	/* Get size */
	Term_get_size(&wid, &hgt);

	/* Copy the filename */
	_tcscpy_s(filename, _countof(filename), name);

	n = _tcslen(filename);

	/* Extract the tag from the filename */
	for (i = 0; i < n; i++)
	{
		if (filename[i] == '#')
		{
			filename[i] = 0;
			tag = filename + i + 1;
			break;
		}
	}
	/* Redirect the name */
	name = filename;

	/* Hack XXX XXX XXX */
	if (what)
	{
		_tcscpy_s(caption, _countof(caption), what);

		_tcscpy_s(path, _countof(path), name);
		fff = file_open(path, MODE_READ, -1);
	}
	/* Look in "help" */
	if (!fff)
	{
		strnfmt(caption, _countof(caption), __T("Help file '%s'"), name);

		path_build(path, _countof(path), ANGBAND_DIR_HELP, name);
		fff = file_open(path, MODE_READ, -1);
	}
	/* Look in "info" */
	if (!fff)
	{
		strnfmt(caption, _countof(caption), __T("Info file '%s'"), name);

		path_build(path, _countof(path), ANGBAND_DIR_INFO, name);
		fff = file_open(path, MODE_READ, -1);
	}
	/* Oops */
	if (!fff)
	{
		/* Message */
		msg_format(__T("Cannot open '%s'."), name);
		message_flush();

		/* Oops */
		return (TRUE);
	}
	/* Pre-Parse the file */
	while (TRUE)
	{
		/* Read a line or stop */
		if (!file_getl(fff, buf, _countof(buf))) break;

		/* XXX Parse "menu" items */
		if (prefix(buf, __T("***** ")))
		{
			char b1 = '[', b2 = ']';

			/* Notice "menu" requests */
			if ((buf[6] == b1) && isalpha(buf[7]) &&
			    (buf[8] == b2) && (buf[9] == ' '))
			{
				/* This is a menu file */
				menu = TRUE;

				/* Extract the menu item */
				k = A2I(buf[7]);

				/* Store the menu item (if valid) */
				if ((k >= 0) && (k < 26))
					_tcscpy_s(hook[k], _countof(hook[0]), buf + 10);
			}
			/* Notice "tag" requests */
			else if (buf[6] == '<')
			{
				if (tag)
				{
					/* Remove the closing '>' of the tag */
					buf[_tcslen(buf) - 1] = 0;

					/* Compare with the requested tag */
					if (streq(buf + 7, tag))
					{
						/* Remember the tagged line */
						line = next;
					}
				}
			}
			/* Skip this */
			continue;
		}
		/* Count the "real" lines */
		next++;
	}

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

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

		/* Restrict the visible range */
		if (line > (size - (hgt - 4))) line = size - (hgt - 4);
		if (line < 0) line = 0;

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

			/* Hack -- Re-Open the file */
			fff = file_open(path, MODE_READ, -1);
			if (!fff) 
				return (TRUE);

			/* File has been restarted */
			next = 0;
		}
		/* Goto the selected line */
		while (next < line)
		{
			/* Get a line */
			if (!file_getl(fff, buf, _countof(buf))) break;

			/* Skip tags/links */
			if (prefix(buf, __T("***** "))) continue;

			/* Count the lines */
			next++;
		}
		/* Dump the next lines of the file */
		for (i = 0; i < hgt - 4; )
		{
			/* Hack -- track the "first" line */
			if (!i) line = next;

			/* Get a line of the file or stop */
			if (!file_getl(fff, buf, _countof(buf))) break;

			/* Hack -- skip "special" lines */
			if (prefix(buf, __T("***** "))) continue;

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

			/* Make a copy of the current line for searching */
			_tcscpy_s(lc_buf, _countof(lc_buf), buf);

			/* Make the line lower case */
			if (!case_sensitive) string_lower(lc_buf);

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

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

			/* Dump the line */
			Term_putstr(0, i+2, -1, TERM_WHITE, buf);

			/* Hilite "shower" */
			if (shower[0])
			{
				const _TCHAR *str = lc_buf;

				/* Display matches */
				while ((str = _tcsstr(str, shower)) != NULL)
				{
					int len = _tcslen(shower);

					/* Display the match */
					Term_putstr(str-lc_buf, i+2, len, TERM_YELLOW, &buf[str-lc_buf]);

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

			/* Count the printed lines */
			i++;
		}
		/* Hack -- failed search */
		if (find)
		{
			bell(__T("Search string not found!"));
			line = back;
			find = NULL;
			continue;
		}
		/* Show a general "title" */
		prt(format(__T("[%s %s, %s, Line %d-%d/%d]"), VERSION_NAME, VERSION_STRING,
		           caption, line, line + hgt - 4, size), 0, 0);

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

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

		/* Prompt -- large files */
		else
		{
			/* Wait for it */
			prt(__T("[Press Space to advance, or ESC to exit.]"), hgt - 1, 0);
		}
		/* Get a keypress */
		ch = inkey();

		/* Exit the help */
		if (ch == '?') break;

		/* Toggle case sensitive on/off */
		if (ch == '!')
		{
			case_sensitive = !case_sensitive;
		}
		/* Try showing */
		if (ch == '&')
		{
			/* Get "shower" */
			prt(__T("Show: "), hgt - 1, 0);
			(void)askfor_aux(shower, _countof(shower), NULL);

			/* Make the "shower" lowercase */
			if (!case_sensitive) string_lower(shower);
		}

		/* Try finding */
		if (ch == '/')
		{
			/* Get "finder" */
			prt(__T("Find: "), hgt - 1, 0);
			if (askfor_aux(finder, _countof(finder), NULL))
			{
				/* Find it */
				find = finder;
				back = line;
				line = line + 1;

				/* Make the "finder" lowercase */
				if (!case_sensitive) string_lower(finder);

				/* Show it */
				_tcscpy_s(shower, _countof(shower), finder);
			}
		}
		/* Go to a specific line */
		if (ch == '#')
		{
			_TCHAR tmp[80] = __T("0");

			prt(__T("Goto Line: "), hgt - 1, 0);
			if (askfor_aux(tmp, _countof(tmp), NULL))
				line = _tstoi(tmp);
		}
		/* Go to a specific file */
		if (ch == '%')
		{
			_TCHAR ftmp[80] = __T("help.hlp");

			prt(__T("Goto File: "), hgt - 1, 0);
			if (askfor_aux(ftmp, _countof(ftmp), NULL))
			{
				if (!show_file(ftmp, NULL, 0, mode))
					ch = ESCAPE;
			}
		}
		/* Back up one line */
		if (ch == ARROW_UP || ch == '8')
		{
			line = line - 1;
		}
		/* Back up one full page */
		if ((ch == '9') || (ch == '-'))
		{
			line = line - (hgt - 4);
		}
		/* Back to the top */
		if (ch == '7')
		{
			line = 0;
		}
		/* Advance one line */
		if ((ch == ARROW_DOWN) || (ch == '2') || (ch == '\n') || (ch == '\r'))
		{
			line = line + 1;
		}
		/* Advance one full page */
		if ((ch == '3') || (ch == ' '))
		{
			line = line + (hgt - 4);
		}
		/* Advance to the bottom */
		if (ch == '1')
		{
			line = size;
		}

		/* Recurse on letters */
		if (menu && isalpha(ch))
		{
			/* Extract the requested menu item */
			k = A2I(ch);

			/* Verify the menu item */
			if ((k >= 0) && (k <= 25) && hook[k][0])
			{
				/* Recurse on that file */
				if (!show_file(hook[k], NULL, 0, mode)) ch = ESCAPE;
			}
		}

		/* Exit on escape */
		if (ch == ESCAPE) break;
	}
	/* Close the file */
	file_close(fff);

	/* Done */
	return (ch != '?');
}

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

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

	/* Load screen */
	screen_load();
}

/*
 * Process the player name and extract a clean "base name".
 *
 * If "sf" is TRUE, then we initialize "savefile" based on player name.
 *
 * Some platforms (Windows, Macintosh, Amiga) leave the "savefile" empty
 * when a new character is created, and then when the character is done
 * being created, they call this function to choose a new savefile name.
 *
 * This also now handles the turning on and off of the automatic
 * sequential numbering of character names with Roman numerals.
 */
void process_player_name(bool sf)
{
	int i;

	/* If the selected name ends in " I", i.e. "the first", then we're using
	 * Roman numerals style suffices, so set the savefile flag.
	 * Equally if there's no roman numerals at all, switch off suffices.*/
	_TCHAR *buf = find_roman_suffix_start(op_ptr->full_name);
	if (!buf)
	{
		op_ptr->name_suffix = 0;
	}
	else
	{
		if (buf[0] == 'I' && buf[1] == 0)
		{
			op_ptr->name_suffix = 1;
		}
	}
	/* Process the player name */
	for (i = 0; op_ptr->full_name[i]; i++)
	{
		_TCHAR c = op_ptr->full_name[i];

		/* No control characters */
		if (iscntrl(c)) /* TODO Check how control characters work with wide characters */
		{
			/* Illegal characters */
			quit_fmt(__T("Illegal control char (0x%02X) in player name"), c);
		}
		/* Convert all non-alphanumeric symbols */
		if (!isalpha(c) && !isdigit(c)) c = '_';

		/* Build "base_name" */
		op_ptr->base_name[i] = c;
	}

#if defined(WINDOWS)

	/* Max length */
	if (i > 8) i = 8;
#endif
	/* Terminate */
	op_ptr->base_name[i] = 0;

	/* Require a "base" name */
	if (!op_ptr->base_name[0])
	{
		_tcscpy_s(op_ptr->base_name, _countof(op_ptr->base_name), __T("PLAYER"));
	}

	/* Pick savefile name if needed */
	if (sf)
	{
		_TCHAR temp[128];

#if defined(SET_UID)
		/* Rename the savefile, using the player_uid and base_name */
		strnfmt(temp, _countof(temp), __T("%d.%s"), player_uid, op_ptr->base_name);
#else
		/* Rename the savefile, using the base name */
		strnfmt(temp, _countof(temp), __T("%s"), op_ptr->base_name);
#endif
		/* Build the filename */
		path_build(savefile, _countof(savefile), ANGBAND_DIR_SAVE, temp);
	}
}

/*
 * Save the game
 */
void save_game(void)
{
	/* Disturb the player */
	disturb(1, 0);

	/* Clear messages */
	message_flush();

	/* Handle stuff */
	handle_stuff();

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

	/* Refresh */
	Term_fresh();

	/* The player is not dead */
	_tcscpy_s(p_ptr->died_from, _countof(p_ptr->died_from), __T("(saved)"));

	/* Forbid suspend */
	signals_ignore_tstp();

	/* Save the player */
	if (old_save())
	{
		prt(__T("Saving game... done."), 0, 0);
	}
	else /* Save failed (oops) */
	{
		prt(__T("Saving game... failed!"), 0, 0);
	}
	/* Allow suspend again */
	signals_handle_tstp();

	/* Refresh */
	Term_fresh();

	/* Note that the player is not dead */
	_tcscpy_s(p_ptr->died_from, _countof(p_ptr->died_from), __T("(alive and well)"));
}

/*
 * Close up the current game (player may or may not be dead)
 *
 * Note that the savefile is not saved until the tombstone is
 * actually displayed and the player has a chance to examine
 * the inventory and such.  This allows cheating if the game
 * is equipped with a "quit without save" method.  XXX XXX XXX
 */
void close_game(void)
{
	/* Handle stuff */
	handle_stuff();

	/* Flush the messages */
	message_flush();

	/* Flush the input */
	flush();

	/* No suspending now */
	signals_ignore_tstp();

	/* Hack -- Increase "icky" depth */
	character_icky++;

	/* Handle death */
	if (p_ptr->is_dead)
	{
		death_screen();
	}

	/* Still alive */
	else
	{
		/* Save the game */
		save_game();

		if (Term->mapped_flag)
		{
			/* Prompt for scores XXX XXX XXX */
			prt(__T("Press Return (or Escape)."), 0, 40);

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

	/* Hack -- Decrease "icky" depth */
	character_icky--;

	/* Allow suspending now */
	signals_handle_tstp();
}

/*
 * Handle abrupt death of the visual system
 *
 * This routine is called only in very rare situations, and only
 * by certain visual systems, when they experience fatal errors.
 *
 * XXX XXX Hack -- clear the death flag when creating a HANGUP
 * save file so that player can see tombstone when restart.
 */
void exit_game_panic(void)
{
	/* If nothing important has happened, just quit */
	if (!character_generated || character_saved)
		quit(__T("panic"));

	/* Mega-Hack -- see "msg_print()" */
	msg_flag = FALSE;

	/* Clear the top line */
	prt(__T(""), 0, 0);

	/* Hack -- turn off some things */
	disturb(1, 0);

	/* Hack -- Delay death XXX XXX XXX */
	if (p_ptr->chp < 0) p_ptr->is_dead = FALSE;

	/* Hardcode panic save */
	p_ptr->panic_save = 1;

	/* Forbid suspend */
	signals_ignore_tstp();

	/* Indicate panic save */
	_tcscpy_s(p_ptr->died_from, _countof(p_ptr->died_from), __T("(panic save)"));

	/* Panic save, or get worried */
	if (!old_save())
		quit(__T("panic save failed!"));

	/* Successful panic save */
	quit(__T("panic save succeeded!"));
}

static void write_html_escape_char(ang_file *fp, _TCHAR c)
{
	switch (c)
	{
		case '<':
			file_putf(fp, __T("&lt;"));
			break;
		case '>':
			file_putf(fp, __T("&gt;"));
			break;
		case '&':
			file_putf(fp, __T("&amp;"));
			break;
		default:
			file_putf(fp, __T("%c"), c);
			break;
	}
}

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

	byte a = TERM_WHITE;
	byte oa = TERM_WHITE;
	_TCHAR c = ' ';

	const _TCHAR *new_color_fmt = (mode == 0) ?
			__T("<font color=\"#%02X%02X%02X\">")
		 	: __T("[COLOR=\"#%02X%02X%02X\"]");
	const _TCHAR *change_color_fmt = (mode == 0) ?
			__T("</font><font color=\"#%02X%02X%02X\">")
			: __T("[/COLOR][COLOR=\"#%02X%02X%02X\"]");
	const _TCHAR *close_color_fmt = mode ==  0 ? __T("</font>") : __T("[/COLOR]");

	ang_file *fp;
	_TCHAR buf[1024];

	path_build(buf, _countof(buf), ANGBAND_DIR_USER, name);
	fp = file_open(buf, MODE_WRITE, FTYPE_TEXT);
	/* Oops */
	if (!fp)
	{
		plog_fmt(__T("Cannot write the '%s' file!"), buf);
		return;
	}
	/* Retrieve current screen size */
	Term_get_size(&wid, &hgt);
	if (mode == 0)
	{
		file_putf(fp, __T("<!DOCTYPE html><html><head>\n"));
		file_putf(fp, __T("  <meta='generator' content='%s %s'>\n"),
	            	VERSION_NAME, VERSION_STRING);
		file_putf(fp, __T("  <title>%s</title>\n"), name);
		file_putf(fp, __T("</head>\n\n"));
		file_putf(fp, __T("<body style='color: #fff; background: #000;'>\n"));
		file_putf(fp, __T("<pre>\n"));
	}
	else
	{
		file_putf(fp, __T("[CODE][TT][BC=black][COLOR=white]\n"));
	}
	/* Dump the screen */
	for (y = 0; y < hgt; y++)
	{
		for (x = 0; x < wid; x++)
		{
			/* Get the attr/char */
			(void)(Term_what(x, y, &a, &c));

			/* Color change */
			if (oa != a && c != ' ')
			{
				/* From the default white to another color */
				if (oa == TERM_WHITE)
				{
					file_putf(fp, new_color_fmt,
					        angband_color_table[a][1],
					        angband_color_table[a][2],
					        angband_color_table[a][3]);
				}

				/* From another color to the default white */
				else if (a == TERM_WHITE)
				{
					file_putf(fp, close_color_fmt);
				}
				else /* Change colors */
				{
					file_putf(fp, change_color_fmt,
					        angband_color_table[a][1],
					        angband_color_table[a][2],
					        angband_color_table[a][3]);
				}
				/* Remember the last color */
				oa = a;
			}
			/* Write the character and escape special HTML characters */
			if (mode == 0)
				write_html_escape_char(fp, c);
			else
				file_putf(fp, __T("%c"), c);
		}
		/* End the row */
		file_putf(fp, __T("\n"));
	}
	/* Close the last font-color tag if necessary */
	if (oa != TERM_WHITE) file_putf(fp, close_color_fmt);

	if (mode == 0)
	{
		file_putf(fp, __T("</pre>\n"));
		file_putf(fp, __T("</body>\n"));
		file_putf(fp, __T("</html>\n"));
	}
	else
	{
		file_putf(fp, __T("[/COLOR][/BC][/TT][/CODE]\n"));
	}

	/* Close it */
	file_close(fp);
}
