/* テスト */
/*
 * File: obj-info.c
 * Purpose: Object description code.
 *
 * Copyright (c) 2002,2007,2008 Andi Sidwell <andi@takkaria.org>
 * Copyright (c) 2002,2003,2004 Robert Ruehlmann <rr9@thangorodrim.net>
 *
 * 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 "effects.h"
#include "cmds.h"
#include "tvalsval.h"

/*
 * Describes a flag-name pair.
 */
typedef struct
{
	u32b flag;
	const _TCHAR *name;
} flag_type;



/*** Utility code ***/

/*
 * Given an array of strings, as so:
 *  { "intelligence", "fish", "lens", "prime", "number" },
 *
 * ... output a list like "intelligence, fish, lens, prime, number.\n".
 */
static void info_out_list(const _TCHAR *list[], size_t count)
{
	size_t i;

	for (i = 0; i < count; i++)
	{
		text_out(list[i]);
		if (i != (count - 1)) text_out(L", ");
	}

	text_out(L".\n");
}


/*
 *
 */
static size_t info_collect(const flag_type list[], size_t max, u32b flag, const _TCHAR *recepticle[])
{
	size_t i, count = 0;

	for (i = 0; i < max; i++)
	{
		if (flag & list[i].flag)
			recepticle[count++] = list[i].name;
	}

	return count;
}


/*** Big fat data tables ***/

static const flag_type f1_pval[] =
{
	{ TR0_STR,     L"strength" },
	{ TR0_INT,     L"intelligence" },
	{ TR0_WIS,     L"wisdom" },
	{ TR0_DEX,     L"dexterity" },
	{ TR0_CON,     L"constitution" },
	{ TR0_CHR,     L"charisma" },
	{ TR0_STEALTH, L"stealth" },
	{ TR0_INFRA,   L"infravision" },
	{ TR0_TUNNEL,  L"tunneling" },
	{ TR0_SPEED,   L"speed" },
	{ TR0_BLOWS,   L"attack speed" },
	{ TR0_SHOTS,   L"shooting speed" },
	{ TR0_MIGHT,   L"shooting power" },
};

static const flag_type f2_immunity[] =
{
	{ TR1_IM_ACID, L"acid" },
	{ TR1_IM_ELEC, L"lightning" },
	{ TR1_IM_FIRE, L"fire" },
	{ TR1_IM_COLD, L"cold" },
};

static const flag_type f2_vuln[] =
{
	{ TR1_VULN_ACID, L"acid" },
	{ TR1_VULN_ELEC, L"electricity" },
	{ TR1_VULN_FIRE, L"fire" },
	{ TR1_VULN_COLD, L"cold" },
};

static const flag_type f2_resist[] =
{
	{ TR1_RES_ACID,  L"acid" },
	{ TR1_RES_ELEC,  L"lightning" },
	{ TR1_RES_FIRE,  L"fire" },
	{ TR1_RES_COLD,  L"cold" },
	{ TR1_RES_POIS,  L"poison" },
	{ TR1_RES_FEAR,  L"fear" },
	{ TR1_RES_LITE,  L"light" },
	{ TR1_RES_DARK,  L"dark" },
	{ TR1_RES_BLIND, L"blindness" },
	{ TR1_RES_CONFU, L"confusion" },
	{ TR1_RES_SOUND, L"sound" },
	{ TR1_RES_SHARD, L"shards" },
	{ TR1_RES_NEXUS, L"nexus"  },
	{ TR1_RES_NETHR, L"nether" },
	{ TR1_RES_CHAOS, L"chaos" },
	{ TR1_RES_DISEN, L"disenchantment" },
};

static const flag_type f3_ignore[] =
{
	{ TR2_IGNORE_ACID, L"acid" },
	{ TR2_IGNORE_ELEC, L"electricity" },
	{ TR2_IGNORE_FIRE, L"fire" },
	{ TR2_IGNORE_COLD, L"cold" },
};

static const flag_type f2_sustains[] =
{
	{ TR1_SUST_STR, L"strength" },
	{ TR1_SUST_INT, L"intelligence" },
	{ TR1_SUST_WIS, L"wisdom" },
	{ TR1_SUST_DEX, L"dexterity" },
	{ TR1_SUST_CON, L"constitution" },
	{ TR1_SUST_CHR, L"charisma" },
};

static const flag_type f3_misc[] =
{
	{ TR2_BLESSED, L"Blessed by the gods" },
	{ TR2_SLOW_DIGEST, L"Slows your metabolism" },
	{ TR2_IMPAIR_HP, L"Impairs hitpoint recovery" },
	{ TR2_IMPAIR_MANA, L"Impairs mana recovery" },
	{ TR2_AFRAID, L"Makes you afraid of melee, and worse at shooting and casting spells" },
	{ TR2_FEATHER, L"Feather Falling" },
	{ TR2_REGEN, L"Speeds regeneration" },
	{ TR2_FREE_ACT, L"Prevents paralysis" },
	{ TR2_HOLD_LIFE, L"Stops experience drain" },
	{ TR2_TELEPATHY, L"Grants telepathy" },
	{ TR2_SEE_INVIS, L"Grants the ability to see invisible things" },
	{ TR2_AGGRAVATE, L"Aggravates creatures nearby" },
	{ TR2_DRAIN_EXP, L"Drains experience" },
	{ TR2_TELEPORT, L"Induces random teleportation" },
};


/** Slays **/
/*
 * Entries in this table should be in ascending order of multiplier, to 
 * ensure that the highest one takes precedence 
 * object flag, vulnerable flag, resist flag, multiplier, ranged verb, 
 * melee verb, verb describing what the thing branded does when it is active,
 * description of affected creatures, brand
 */
const slay_t slay_table[] =
{
	{ TR0_SLAY_ANIMAL, RF2_ANIMAL, 0, 2, L"pierces",  L"smite", NULL,
	  L"animals",                                NULL },
	{ TR0_SLAY_EVIL,   RF2_EVIL,   0, 2, L"pierces",  L"smite", NULL,
	  L"evil creatures",                         NULL },
	{ TR0_SLAY_UNDEAD, RF2_UNDEAD, 0, 3, L"pierces",  L"smite", NULL,
	  L"undead",                                 NULL },
	{ TR0_SLAY_DEMON,  RF2_DEMON,  0, 3, L"pierces",  L"smite", NULL,
	  L"demons",                                 NULL },
	{ TR0_SLAY_ORC,    RF2_ORC,    0, 3, L"pierces",  L"smite", NULL,
	  L"orcs",                                   NULL },
	{ TR0_SLAY_TROLL,  RF2_TROLL,  0, 3, L"pierces",  L"smite", NULL,
	  L"trolls",                                 NULL },
	{ TR0_SLAY_GIANT,  RF2_GIANT,  0, 3, L"pierces",  L"smite", NULL,
	 L"giants",                                  NULL },
	{ TR0_SLAY_DRAGON, RF2_DRAGON, 0, 3, L"pierces",  L"smite", NULL,
	  L"dragons",                                NULL },
	{ TR0_BRAND_ACID, 0, RF2_IM_ACID, 3, L"corrodes", L"corrode", L"spits",
	  L"creatures not resistant to acid",        L"acid" },
	{ TR0_BRAND_ELEC, 0, RF2_IM_ELEC, 3, L"zaps",     L"zap", L"crackles",
	  L"creatures not resistant to electricity", L"lightning" },
	{ TR0_BRAND_FIRE, 0, RF2_IM_FIRE, 3, L"burns",    L"burn", L"flares",
	  L"creatures not resistant to fire",        L"flames" },
	{ TR0_BRAND_COLD, 0, RF2_IM_COLD, 3, L"freezes",  L"freeze", L"grows cold",
	  L"creatures not resistant to cold",        L"frost" },
	{ TR0_BRAND_POIS, 0, RF2_IM_POIS, 3, L"poisons",  L"poison", L"seethes",
	  L"creatures not resistant to poison",      L"venom" },
	{ TR0_KILL_DRAGON, RF2_DRAGON, 0, 5, L"deeply pierces",
	 L"fiercely smite", NULL, L"dragons",         NULL },
	{ TR0_KILL_DEMON,  RF2_DEMON,  0, 5, L"deeply pierces",
	 L"fiercely smite", NULL, L"demons",          NULL },
	{ TR0_KILL_UNDEAD, RF2_UNDEAD, 0, 5, L"deeply pierces",
	 L"fiercely smite", NULL, L"undead",          NULL },
	{ 0, 0, 0, 0, NULL, NULL, NULL, NULL }
};

/*
 * Helper function to externalise N_ELEMENTS(slay_table), which itself is not
 * available outside this compilation unit
 */
size_t num_slays(void)
{
	return N_ELEMENTS(slay_table);
}

/*** Code that makes use of the data tables ***/

/*
 * Describe an item's curses.
 */
static bool describe_curses(const object_type *o_ptr, u32b f3)
{
	if (cursed_p(o_ptr))
	{
		if (f3 & TR2_PERMA_CURSE)
			text_out_c(TERM_L_RED, L"Permanently cursed.\n");
		else if (f3 & TR2_HEAVY_CURSE)
			text_out_c(TERM_L_RED, L"Heavily cursed.\n");
		else if (object_known_p(o_ptr))
			text_out_c(TERM_L_RED, L"Cursed.\n");
		else
			return FALSE;

		return TRUE;
	}

	return FALSE;
}


/*
 * Describe stat modifications.
 */
static bool describe_stats(u32b f1, int pval)
{
	const _TCHAR *descs[N_ELEMENTS(f1_pval)];
	size_t count;

	if (!pval) return FALSE;

	count = info_collect(f1_pval, N_ELEMENTS(f1_pval), f1, descs);
	if (count)
	{
		text_out_c((pval > 0) ? TERM_L_GREEN : TERM_RED, L"%+i ", pval);
		info_out_list(descs, count);
	}

	if (f1 & TR0_SEARCH)
	{
		text_out_c((pval > 0) ? TERM_L_GREEN : TERM_RED, L"%+i%% ", pval * 5);
		text_out(L"to searching.\n");
	}

	return TRUE;
}


/*
 * Describe immunities granted by an object.
 */
static bool describe_immune(u32b f2)
{
	const _TCHAR *descs[N_ELEMENTS(f2_resist)];
	size_t count;

	bool prev = FALSE;

	/* Immunities */
	count = info_collect(f2_immunity, N_ELEMENTS(f2_immunity), f2, descs);
	if (count)
	{
		text_out(L"Provides immunity to ");
		info_out_list(descs, count);
		prev = TRUE;
	}

	/* Resistances */
	count = info_collect(f2_resist, N_ELEMENTS(f2_resist), f2, descs);
	if (count)
	{
		text_out(L"Provides resistance to ");
		info_out_list(descs, count);
		prev = TRUE;
	}

	/* Resistances */
	count = info_collect(f2_vuln, N_ELEMENTS(f2_vuln), f2, descs);
	if (count)
	{
		text_out(L"Makes you vulnerable to ");
		info_out_list(descs, count);
		prev = TRUE;
	}

	return prev;
}


/*
 * Describe 'ignores' of an object.
 */
static bool describe_ignores(u32b f3)
{
	const _TCHAR *descs[N_ELEMENTS(f3_ignore)];
	size_t count = info_collect(f3_ignore, N_ELEMENTS(f3_ignore), f3, descs);

	if (!count) return FALSE;

	text_out(L"Cannot be harmed by ");
	info_out_list(descs, count);

	return TRUE;
}


/*
 * Describe stat sustains.
 */
static bool describe_sustains(u32b f2)
{
	const _TCHAR *descs[N_ELEMENTS(f2_sustains)];
	size_t count = info_collect(f2_sustains, N_ELEMENTS(f2_sustains), f2, descs);

	if (!count) return FALSE;

	text_out(L"Sustains ");
	info_out_list(descs, count);

	return TRUE;
}


/*
 * Describe miscellaneous powers.
 */
static bool describe_misc_magic(u32b f3)
{
	size_t i;
	bool printed = FALSE;

	for (i = 0; i < N_ELEMENTS(f3_misc); i++)
	{
		if (f3 & f3_misc[i].flag)
		{
			text_out(L"%s.\n", f3_misc[i].name);
			printed = TRUE;
		}
	}

	return printed;
}


/*
 * Describe slays and brands on weapons
 */
static bool describe_slays(u32b f1, int tval)
{
	bool printed = FALSE;

	const _TCHAR *slay_descs[N_ELEMENTS(slay_table)];
	const _TCHAR *kill_descs[N_ELEMENTS(slay_table)];
	const _TCHAR *brand_descs[N_ELEMENTS(slay_table)];
	const slay_t *s_ptr;

	size_t x = 0;
	size_t y = 0;
	size_t z = 0;

	bool fulldesc;

	if ((tval == TV_SWORD) || (tval == TV_HAFTED) || (tval == TV_POLEARM) ||
		(tval == TV_DIGGING ) || (tval == TV_BOW) || (tval == TV_SHOT) ||
		(tval == TV_ARROW) || (tval == TV_BOLT)) fulldesc = FALSE;
	else fulldesc = TRUE;

	for (s_ptr = slay_table; s_ptr->slay_flag; s_ptr++)
	{
		if (f1 & (s_ptr->slay_flag & TR0_SLAY_MASK))
			slay_descs[x++] = s_ptr->desc;
		else if (f1 & (s_ptr->slay_flag & TR0_KILL_MASK))
			kill_descs[y++] = s_ptr->desc;
		else if (f1 & (s_ptr->slay_flag & TR0_BRAND_MASK))
			brand_descs[z++] = s_ptr->brand;
	}

	/* Slays */
	if (x)
	{
		if (fulldesc) text_out(L"It causes your melee attacks to slay ");
		else text_out(L"Slays ");
		info_out_list(slay_descs, x);
		printed = TRUE;
	}

	/* Kills */
	if (y)
	{
		if (fulldesc) text_out(L"It causes your melee attacks to *slay* ");
		else text_out(L"*Slays* ");
		info_out_list(kill_descs, y);
		printed = TRUE;
	}

	/* Brands */
	if (z)
	{
		if (fulldesc) text_out(L"It brands your melee attacks with ");
		else text_out(L"Branded with ");
		info_out_list(brand_descs, z);
		printed = TRUE;
	}

	return printed;
}



/*
 * list[] and mult[] must be > 16 in size
 */
static int collect_slays(const _TCHAR *desc[], int mult[], u32b f1)
{
	int cnt = 0;
	const slay_t *s_ptr;

	/* Collect slays */
	for (s_ptr = slay_table; s_ptr->slay_flag; s_ptr++)
	{
		if (f1 & s_ptr->slay_flag)
		{
			mult[cnt] = s_ptr->mult;
			desc[cnt++] = s_ptr->desc;
		}
	}

	return cnt;
}



/*
 * Account for criticals in the calculation of melee prowess
 *
 * Note -- This relies on the criticals being an affine function
 * of previous damage, since we are used to transform the mean
 * of a roll.
 *
 * Also note -- rounding error makes this not completely accurate
 * (but for the big crit weapons like Grond an odd point of damage
 * won't be missed)
 *
 * This code written according to the KISS principle.  650 adds
 * are cheaper than a FOV call and get the job done fine.
 */
static void calculate_melee_crits(player_state *state, int weight,
		int plus, int *mult, int *add, int *div)
{
	int k, to_crit = weight + 5*(state->to_h + plus) + 3*p_ptr->lev;
	to_crit = MIN(5000, MAX(0, to_crit));

	*mult = *add = 0;

	for (k = weight; k < weight + 650; k++)
	{
		if (k <  400) { *mult += 4; *add += 10; continue; }
		if (k <  700) { *mult += 4; *add += 20; continue; }
		if (k <  900) { *mult += 6; *add += 30; continue; }
		if (k < 1300) { *mult += 6; *add += 40; continue; }
		                *mult += 7; *add += 50;
	}

	/*
	 * Scale the output down to a more reasonable size, to prevent
	 * integer overflow downstream.
	 */
	*mult = 100 + to_crit*(*mult - 1300)/(50*1300);
	*add  = *add * to_crit / (500*50);
	*div  = 100;
}

/*
 * Missile crits follow the same approach as melee crits.
 */
static void calculate_missile_crits(player_state *state, int weight,
		int plus, int *mult, int *add, int *div)
{
	int k, to_crit = weight + 4*(state->to_h + plus) + 2*p_ptr->lev;
	to_crit = MIN(5000, MAX(0, to_crit));

	*mult = *add = 0;

	for (k = weight; k < weight + 500; k++)
	{
		if (k <  500) { *mult += 2; *add +=  5; continue; }
		if (k < 1000) { *mult += 2; *add += 10; continue; }
		                *mult += 3; *add += 15;
	}

	*mult = 100 + to_crit*(*mult - 500)/(500*50);
	*add  = *add * to_crit / (500*50);
	*div  = 100;
}

/*
 * Describe combat advantages.
 */
static bool describe_combat(const object_type *o_ptr, bool full)
{
	const _TCHAR *desc[16];
	int i;
	int mult[16];
	int cnt, dam, total_dam, plus = 0;
	int xtra_postcrit = 0, xtra_precrit = 0;
	int crit_mult, crit_div, crit_add;
	object_type *j_ptr = &inventory[INVEN_BOW];

	u32b f[OBJ_FLAG_N];

	bool weapon = (wield_slot(o_ptr) == INVEN_WIELD);
	bool ammo   = (p_ptr->state.ammo_tval == o_ptr->tval) &&
	              (j_ptr->k_idx);

	/* Abort if we've nothing to say */
	if (!weapon && !ammo)
	{
		/* Potions can have special text */
		if (o_ptr->tval != TV_POTION) return FALSE;
		if (!o_ptr->dd || !o_ptr->ds) return FALSE;
		if (!object_known_p(o_ptr)) return FALSE;

		text_out(L"It can be thrown at creatures with damaging effect.\n");
		return TRUE;
	}

	if (full)
		object_flags(o_ptr, f);
	else
		object_flags_known(o_ptr, f);

	text_out_c(TERM_L_WHITE, L"Combat info:\n");

	if (weapon)
	{
		/*
		 * Get the player's hypothetical state, were they to be
		 * wielding this item.
		 */
		player_state state;
		object_type inven[INVEN_TOTAL];

		memcpy(inven, inventory, INVEN_TOTAL * sizeof(object_type));
		inven[INVEN_WIELD] = *o_ptr;

		calc_bonuses(inven, &state, TRUE);

		dam = ((o_ptr->ds + 1) * o_ptr->dd * 5);

		xtra_postcrit = state.dis_to_d * 10;
		if (object_known_p(o_ptr) || o_ptr->ident & IDENT_ATTACK)
		{
			xtra_precrit += o_ptr->to_d * 10;
			plus += o_ptr->to_h;
		}

		calculate_melee_crits(&state, o_ptr->weight, plus,
				&crit_mult, &crit_add, &crit_div);

		/* Warn about heavy weapons */
		if (adj_str_hold[state.stat_ind[A_STR]] < o_ptr->weight / 10)
			text_out_c(TERM_L_RED, L"You are too weak to use this weapon.\n");

		text_out_c(TERM_L_GREEN, L"%d ", state.num_blow);
		text_out(L"blow%s/round.\n", (state.num_blow > 1) ? L"s" : __T(""));
	}
	else
	{
		int tdis = 6 + 2 * p_ptr->state.ammo_mult;
		u32b g[OBJ_FLAG_N];

		if (object_known_p(o_ptr)) plus += o_ptr->to_h;

		calculate_missile_crits(&p_ptr->state, o_ptr->weight, plus,
				&crit_mult, &crit_add, &crit_div);

		/* Calculate damage */
		dam = ((o_ptr->ds + 1) * o_ptr->dd * 5);
		if (object_known_p(o_ptr)) dam += (o_ptr->to_d * 10);
		if (object_known_p(j_ptr)) dam += (j_ptr->to_d * 10);
		dam *= p_ptr->state.ammo_mult;

		/* Apply brands from the shooter to the ammo */
		object_flags(j_ptr, g);
		f[0] |= g[0];

		text_out(L"Hits targets up to ");
		text_out_c(TERM_L_GREEN, format(L"%d", tdis * 10));
		text_out(L" feet away.\n");
	}

	/* Collect slays */
	/* Melee weapons get slays from rings now */
	if (weapon)
	{
		u32b g[OBJ_FLAG_N];
		u32b h[OBJ_FLAG_N];
		
		object_flags_known(&inventory[INVEN_LEFT], g);
		object_flags_known(&inventory[INVEN_RIGHT], h);

		if (!((f[0] & TR0_BRAND_MASK) ==
			((f[0] | g[0] | h[0]) & TR0_BRAND_MASK)))
			text_out(L"This weapon benefits from one or more ring brands.\n");

		f[0] |= (g[0] | h[0]);
	}

	text_out(L"Average damage/hit: ");
	
	cnt = collect_slays(desc, mult, f[0]);
	for (i = 0; i < cnt; i++)
	{
		/* Include bonus damage and slay in stated average */
		total_dam = dam * mult[i] + xtra_precrit;
		total_dam = (total_dam * crit_mult + crit_add) / crit_div;
		total_dam += xtra_postcrit;

		if (total_dam <= 0)
			text_out_c(TERM_L_RED, L"%d", 0);
		else if (total_dam % 10)
			text_out_c(TERM_L_GREEN, L"%d.%d",
			           total_dam / 10, total_dam % 10);
		else
			text_out_c(TERM_L_GREEN, L"%d", total_dam / 10);


		text_out(L" vs. %s, ", desc[i]);
	}

	if (cnt) text_out(L"and ");

	/* Include bonus damage in stated average */
	total_dam = dam + xtra_precrit;
	total_dam = (total_dam * crit_mult + crit_add) / crit_div;
	total_dam += xtra_postcrit;

	if (total_dam <= 0)
		text_out_c(TERM_L_RED, L"%d", 0);
	else if (total_dam % 10)
		text_out_c(TERM_L_GREEN, L"%d.%d",
				total_dam / 10, total_dam % 10);
	else
		text_out_c(TERM_L_GREEN, L"%d", total_dam / 10);

	if (cnt) text_out(L" vs. others");
	text_out(L".\n");
	
	/* Note the impact flag */
	if (f[2] & TR2_IMPACT)
		text_out(L"Sometimes creates earthquakes on impact.\n");

	/* Add breakage chance */
	if (ammo)
	{
		text_out_c(TERM_L_GREEN, L"%d%%", breakage_chance(o_ptr));
		text_out(L" chance of breaking upon contact.\n");
	}

	/* You always have something to say... */
	return TRUE;
}

/*
 * Describe objects that can be used for digging.
 */
static bool describe_digger(const object_type *o_ptr, bool full)
{
	player_state st;

	object_type inven[INVEN_TOTAL];

	int sl = wield_slot(o_ptr);
	int i;

	u32b f[OBJ_FLAG_N];

	int chances[4]; /* These are out of 1600 */
	static const _TCHAR *names[4] = { L"rubble", L"magma veins", L"quartz veins", L"granite" };

	if (full)
		object_flags(o_ptr, f);
	else
		object_flags_known(o_ptr, f);

	if (sl < 0 || ((sl != INVEN_WIELD) && !(f[0] & TR0_TUNNEL)))
		return FALSE;

	memcpy(inven, inventory, INVEN_TOTAL * sizeof(object_type));

	/*
	 * Hack -- if we examine a ring that is worn on the right finger,
	 * we shouldn't put a copy of it on the left finger before calculating
	 * digging skills.
	 */
	if (o_ptr != &inventory[INVEN_RIGHT])
		inven[sl] = *o_ptr;

	calc_bonuses(inven, &st, TRUE);

	chances[0] = st.skills[SKILL_DIGGING] * 8;
	chances[1] = (st.skills[SKILL_DIGGING] - 10) * 4;
	chances[2] = (st.skills[SKILL_DIGGING] - 20) * 2;
	chances[3] = (st.skills[SKILL_DIGGING] - 40) * 1;

	for (i = 0; i < 4; i++)
	{
		int chance = MAX(0, MIN(1600, chances[i]));
		int decis = chance ? (16000 / chance) : 0;

		if (i == 0 && chance > 0)
			text_out(L"Clears ");
		if (i == 3 || (i != 0 && chance == 0))
			text_out(L"and ");

		if (chance == 0)
		{
			text_out_c(TERM_L_RED, L"doesn't affect ");
			text_out(L"%s.\n", names[i]);
			break;
		}

		text_out(L"%s in ", names[i]);

		if (chance == 1600) {
			text_out_c(TERM_L_GREEN, L"1 ");
		} else if (decis < 100) {
			text_out_c(TERM_GREEN, L"%d.%d ", decis/10, decis%10);
		} else {
			text_out_c((decis < 1000) ? TERM_YELLOW : TERM_RED,
			           L"%d ", (decis+5)/10);
		}

		text_out(L"turn%s%s", decis == 10 ? __T("") : L"s",
				(i == 3) ? L".\n" : L", ");
	}

	return TRUE;
}


static bool describe_food(const object_type *o_ptr, bool subjective)
{
	/* Describe boring bits */
	if ((o_ptr->tval == TV_FOOD || o_ptr->tval == TV_POTION) &&
		o_ptr->pval)
	{
		/* Sometimes adjust for player speed */
		int multiplier = extract_energy[p_ptr->state.speed];
		if (!subjective) multiplier = 10;

		text_out(L"Nourishes for around ");
		text_out_c(TERM_L_GREEN, L"%d", (o_ptr->pval / 2) * multiplier / 10);
		text_out(L" turns.\n");

		return TRUE;
	}

	return FALSE;
}


/*
 * Describe things that look like lights.
 */
static bool describe_light(const object_type *o_ptr, u32b f3, bool terse)
{
	int rad = 0;

	bool artifact = artifact_p(o_ptr);
	bool no_fuel = (f3 & TR2_NO_FUEL) ? TRUE : FALSE;
	bool is_lite = (o_ptr->tval == TV_LITE) ? TRUE : FALSE;

	if ((o_ptr->tval != TV_LITE) && !(f3 & TR2_LITE))
		return FALSE;

	/* Work out radius */
	if (artifact && is_lite) rad = 3;
	else if (is_lite)  rad = 2;
	if (f3 & TR2_LITE) rad++;

	/* Describe here */
	text_out(L"Radius ");
	text_out_c(TERM_L_GREEN, format(L"%d", rad));
	if (no_fuel && !artifact)
		text_out(L" light.  No fuel required.");
	else if (is_lite && o_ptr->sval == SV_LITE_TORCH)
		text_out(L" light, reduced when running out of fuel.");
	else
		text_out(L" light.");

	if (!terse && is_lite && !artifact)
	{
		const _TCHAR *name = (o_ptr->sval == SV_LITE_TORCH) ? L"torches" : L"lanterns";
		int turns = (o_ptr->sval == SV_LITE_TORCH) ? FUEL_TORCH : FUEL_LAMP;

		text_out(L"  Refills other %s up to %d turns of fuel.", name, turns);
	}

	text_out(L"\n");

	return TRUE;
}



/*
 * Describe an object's activation, if any.
 */
static bool describe_activation(const object_type *o_ptr, u32b f3, bool full,
		bool only_artifacts, bool subjective)
{
	const object_kind *k_ptr = &k_info[o_ptr->k_idx];
	const _TCHAR *desc;

	int effect, base, dice, sides;

	if (o_ptr->name1)
	{
		const artifact_type *a_ptr = &a_info[o_ptr->name1];
		if (!object_known_p(o_ptr) && !full) return FALSE;

		effect = a_ptr->effect;
		base = a_ptr->time_base;
		dice = a_ptr->time_dice;
		sides = a_ptr->time_sides;
	}
	else
	{
		if (!object_aware_p(o_ptr) && !full) return FALSE;

		effect = k_ptr->effect;
		base = k_ptr->time_base;
		dice = k_ptr->time_dice;
		sides = k_ptr->time_sides;
	}

	/* Forget it without an effect */
	if (!effect) return FALSE;

	/* Obtain the description */
	desc = effect_desc(effect);
	if (!desc) return FALSE;

	/* Sometimes only print artifact activation info */
	if (only_artifacts == TRUE && !(f3 & TR2_ACTIVATE))
		return FALSE;

	if (f3 & TR2_ACTIVATE)
		text_out(L"When activated, it ");
	else if (effect_aim(effect))
		text_out(L"When aimed, it ");
	else if (o_ptr->tval == TV_FOOD)
		text_out(L"When eaten, it ");
	else if (o_ptr->tval == TV_POTION)
		text_out(L"When drunk, it ");
	else if (o_ptr->tval == TV_SCROLL)
	    text_out(L"When read, it ");
	else
	    text_out(L"When used, it ");

	/* Print a colourised description */
	do
	{
		if (isdigit(*desc))
			text_out_c(TERM_L_GREEN, L"%c", *desc);
		else
			text_out(L"%c", *desc);
	} while (*desc++);

	text_out(L".\n");

	if (base || dice || sides)
	{
		int min_time, max_time;

		/* Sometimes adjust for player speed */
		int multiplier = extract_energy[p_ptr->state.speed];
		if (!subjective) multiplier = 10;

		text_out(L"Takes ");

		/* Correct for player speed */
		min_time = (dice*1     + base) * multiplier / 10;
		max_time = (dice*sides + base) * multiplier / 10;

		text_out_c(TERM_L_GREEN, L"%d", min_time);

		if (min_time != max_time)
		{
			text_out(L" to ");
			text_out_c(TERM_L_GREEN, L"%d", max_time);
		}

		text_out(L" turns to recharge");
		if (subjective && p_ptr->state.speed != 110)
			text_out(L" at your current speed");

		text_out(L".\n");
	}

	return TRUE;
}


/*** Different ways to present the data ***/


/*
 * Print name, origin, and descriptive text for a given object.
 */
void object_info_header(const object_type *o_ptr)
{
	_TCHAR o_name[120];

	/* Object name */
	object_desc(o_name, sizeof(o_name), o_ptr, TRUE, ODESC_FULL);
	text_out_c(TERM_L_BLUE, L"%^s\n", o_name);

	/* Display the origin */
	switch (o_ptr->origin)
	{
		case ORIGIN_NONE:
		case ORIGIN_MIXED:
			break;

		case ORIGIN_BIRTH:
			text_out(L"(an inheritance from your family)\n");
			break;

		case ORIGIN_STORE:
			text_out(L"(bought in a store)\n");
			break;

		case ORIGIN_FLOOR:
			text_out(L"(lying on the floor at %d feet (level %d))\n",
			         o_ptr->origin_depth * 50,
			         o_ptr->origin_depth);
 			break;

		case ORIGIN_DROP:
		{
			const _TCHAR *name = r_name + r_info[o_ptr->origin_xtra].name;
			bool unique = (r_info[o_ptr->origin_xtra].flags[0] & RF0_UNIQUE) ? TRUE : FALSE;

			text_out(L"dropped by ");

			if (unique)
				text_out(L"%s", name);
			else
				text_out(L"%s%s", is_a_vowel(name[0]) ? L"an " : L"a ", name);

			text_out(L" at %d feet (level %d)\n",
			         o_ptr->origin_depth * 50,
			         o_ptr->origin_depth);

 			break;
		}

		case ORIGIN_DROP_UNKNOWN:
			text_out(L"(dropped by an unknown monster)\n");
			break;

		case ORIGIN_ACQUIRE:
			text_out(L"(conjured forth by magic)\n");
 			break;

		case ORIGIN_CHEAT:
			text_out(L"(created by debug option)\n");
 			break;

		case ORIGIN_CHEST:
			text_out(L"(found in a chest at %d feet (level %d))\n",
			         o_ptr->origin_depth * 50,
			         o_ptr->origin_depth);
			break;
	}

	text_out(L"\n");

	/* Display the known artifact description */
	if (!OPT(adult_randarts) && o_ptr->name1 &&
	    object_known_p(o_ptr) && a_info[o_ptr->name1].text)
	{
		text_out(a_text + a_info[o_ptr->name1].text);
		text_out(L"\n\n");
	}

	/* Display the known object description */
	else if (object_aware_p(o_ptr) || object_known_p(o_ptr))
	{
		bool did_desc = FALSE;

		if (k_info[o_ptr->k_idx].text)
		{
			text_out(k_text + k_info[o_ptr->k_idx].text);
			did_desc = TRUE;
		}

		/* Display an additional ego-item description */
		if (o_ptr->name2 && object_known_p(o_ptr) && e_info[o_ptr->name2].text)
		{
			if (did_desc) text_out(L"  ");
			text_out(e_text + e_info[o_ptr->name2].text);
			text_out(L"\n\n");
		}
		else if (did_desc)
		{
			text_out(L"\n\n");
		}
	}

	return;
}




/*
 * Output object information
 */
static bool object_info_out(const object_type *o_ptr, bool full, bool terse, bool subjective)
{
	u32b f[OBJ_FLAG_N];
	bool something = FALSE;
	bool known = object_known_p(o_ptr);
	
	/* Grab the object flags */
	if (full)
		object_flags(o_ptr, f);
	else
		object_flags_known(o_ptr, f);

	if (!full && !known)
	{
		text_out(L"You do not know the full extent of this item's powers.\n");
		something = TRUE;
	}	
	
	if (describe_curses(o_ptr, f[2])) something = TRUE;
	if (describe_stats(f[0], o_ptr->pval)) something = TRUE;
	if (describe_slays(f[0], o_ptr->tval)) something = TRUE;
	if (describe_immune(f[1])) something = TRUE;
	if (describe_ignores(f[2])) something = TRUE;
	if (describe_sustains(f[1])) something = TRUE;
	if (describe_misc_magic(f[2])) something = TRUE;
	if (something) text_out(L"\n");
	
	if (describe_activation(o_ptr, f[2], full, terse, subjective))
	{
		something = TRUE;
		text_out(L"\n");
	}

	if (subjective && describe_combat(o_ptr, full))
	{
		something = TRUE;
		text_out(L"\n");
	}

	if (!terse && describe_food(o_ptr, subjective)) something = TRUE;
	if (describe_light(o_ptr, f[2], terse)) something = TRUE;
	if (!terse && subjective && describe_digger(o_ptr, full)) something = TRUE;

	return something;
}


/**
 * Provide information on an item, including how it would affect the current player's state.
 * 
 * \param full should be set if actual player knowledge should be ignored in favour of
 *              full knowledge.
 *
 * \returns TRUE if anything is printed.
 */
bool object_info(const object_type *o_ptr, bool full)
{
	return object_info_out(o_ptr, full, FALSE, TRUE);
}


/**
 * Provide information on an item suitable for writing to the character dump - keep it brief.
 */
bool object_info_chardump(const object_type *o_ptr)
{
	return object_info_out(o_ptr, FALSE, TRUE, TRUE);
}


/**
 * Provide spoiler information on an item.
 *
 * Practically, this means that we should not print anything which relies upon the player's
 * current state, since that is not suitable for spoiler material.
 */
bool object_info_spoil(const object_type *o_ptr)
{
	return object_info_out(o_ptr, TRUE, FALSE, FALSE);
}

