/* テスト */
/*
 * Debug Status: Not checked.
 * - Fixed __T on this page (none present).
 */
/*
 * File: object/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.
 */
extern "C"
{
#include "angband.h"
#include "effects.h"
#include "cmds.h"
#include "tvalsval.h"
}
extern size_t object_desc(_TCHAR *buf, size_t max, const object_type *o_ptr, bool prefix, int mode);

/*
 * 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(__T(", "));
	}

	text_out(__T(".\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,     __T("strength") },
	{ TR0_INT,     __T("intelligence") },
	{ TR0_WIS,     __T("wisdom") },
	{ TR0_DEX,     __T("dexterity") },
	{ TR0_CON,     __T("constitution") },
	{ TR0_CHR,     __T("charisma") },
	{ TR0_STEALTH, __T("stealth") },
	{ TR0_INFRA,   __T("infravision") },
	{ TR0_TUNNEL,  __T("tunneling") },
	{ TR0_SPEED,   __T("speed") },
	{ TR0_BLOWS,   __T("attack speed") },
	{ TR0_SHOTS,   __T("shooting speed") },
	{ TR0_MIGHT,   __T("shooting power") },
};

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

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

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

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

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

static const flag_type f3_misc[] =
{
	{ TR2_BLESSED, __T("Blessed by the gods") },
	{ TR2_SLOW_DIGEST, __T("Slows your metabolism") },
	{ TR2_IMPAIR_HP, __T("Impairs hitpoint recovery") },
	{ TR2_IMPAIR_MANA, __T("Impairs mana recovery") },
	{ TR2_AFRAID, __T("Makes you afraid of melee, and worse at shooting and casting spells") },
	{ TR2_FEATHER, __T("Feather Falling") },
	{ TR2_REGEN, __T("Speeds regeneration") },
	{ TR2_FREE_ACT, __T("Prevents paralysis") },
	{ TR2_HOLD_LIFE, __T("Stops experience drain") },
	{ TR2_TELEPATHY, __T("Grants telepathy") },
	{ TR2_SEE_INVIS, __T("Grants the ability to see invisible things") },
	{ TR2_AGGRAVATE, __T("Aggravates creatures nearby") },
	{ TR2_DRAIN_EXP, __T("Drains experience") },
	{ TR2_TELEPORT, __T("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, /* slay_flag */
		RF2_ANIMAL,		/* monster_flag */
		0,				/* resist_flag */
		2,				/* mult */
		__T("pierces"),	/* range_verb */
		__T("smite"),	/* melee_verb */
		{NULL, NULL},	/* active_verb */
		__T("animals"),	/* desc */
		NULL			/* brand */
	},
	{   TR0_SLAY_EVIL,  /* slay_flag */
		RF2_EVIL,		/* monster_flag */
		0,				/* resist_flag */
		2,				/* mult */
		__T("pierces"),	/* range_verb */
		__T("smite"),	/* melee_verb */
		{NULL, NULL},	/* active_verb */
		__T("evil creatures"), /* desc */
		NULL			/* brand */
	},
	{	TR0_SLAY_UNDEAD, 
		RF2_UNDEAD, 
		0, 
		3, 
		__T("pierces"),  
		__T("smite"), 
		{NULL, NULL},	/* active_verb */
		__T("undead"),                                 
		NULL 
	},
	{	TR0_SLAY_DEMON,  
		RF2_DEMON,  
		0, 
		3, 
		__T("pierces"),  
		__T("smite"),
		{NULL, NULL},	/* active_verb */
		__T("demons"),                                 
		NULL 
	},
	{	TR0_SLAY_ORC,    
		RF2_ORC,    
		0, 
		3, 
		__T("pierces"),  
		__T("smite"), 
		{NULL, NULL},	/* active_verb */
		__T("orcs"),                                   
		NULL 
	},
	{	TR0_SLAY_TROLL,  
		RF2_TROLL,  
		0, 
		3, 
		__T("pierces"),  
		__T("smite"), 
		{NULL, NULL},	/* active_verb */
		__T("trolls"),                                 
		NULL },
	{	TR0_SLAY_GIANT, 
		RF2_GIANT,  
		0, 
		3, 
		__T("pierces"),  
		__T("smite"), 
		{NULL, NULL},	/* active_verb */
		__T("giants"),                                  
		NULL 
	},
	{	TR0_SLAY_DRAGON, 
		RF2_DRAGON, 
		0, 
		3, 
		__T("pierces"),  
		__T("smite"), 
		{NULL, NULL},	/* active_verb */
		__T("dragons"),                                
		NULL 
	},
	{	TR0_BRAND_ACID, 
		0, 
		RF2_IM_ACID, 
		3, 
		__T("corrodes"), 
		__T("corrode"), 
		{__T("Your %s spits!"), __T("%sから酸が滴っている！")}, /* Translation TBC */
		__T("creatures not resistant to acid"),        
		__T("acid") 
	},
	{	TR0_BRAND_ELEC, 
		0, 
		RF2_IM_ELEC, 
		3, 
		__T("zaps"),
		__T("zap"),
		{__T("Your %s crackles!"), __T("%sが雷を放っている！")}, /* Translation TBC */
		__T("creatures not resistant to electricity"), 
		__T("lightning") 
	},
	{	TR0_BRAND_FIRE, 
		0, 
		RF2_IM_FIRE, 
		3, 
		__T("burns"),
		__T("burn"),
		{__T("Your %s flares!"), __T("%sが炎に覆われている！")}, /* Translation TBC */
		__T("creatures not resistant to fire"),        
		__T("flames") 
	},
	{	TR0_BRAND_COLD, 
		0, 
		RF2_IM_COLD, 
		3, 
		__T("freezes"),  
		__T("freeze"), 
		{__T("Your %s grows cold!"), __T("%sが冷たくなった！")}, /* Translation TBC */
		__T("creatures not resistant to cold"),        
		__T("frost") 
	},
	{	TR0_BRAND_POIS, 
		0, 
		RF2_IM_POIS, 
		3, 
		__T("poisons"),  
		__T("poison"), 
		{__T("Your %s seethes!"), __T("%sから怪しい液が・・・!")}, /* Translation TBC */
		__T("creatures not resistant to poison"),      
		__T("venom") 
	},
	{	TR0_KILL_DRAGON, 
		RF2_DRAGON, 
		0, 
		5, 
		__T("deeply pierces"),
		__T("fiercely smite"), 
		{NULL, NULL},	/* active_verb */
		__T("dragons"),         
		NULL 
	},
	{	TR0_KILL_DEMON,  
		RF2_DEMON,  
		0, 
		5, 
		__T("deeply pierces"),
		__T("fiercely smite"), 
		{NULL, NULL},	/* active_verb */
		__T("demons"),          
		NULL 
	},
	{	TR0_KILL_UNDEAD, 
		RF2_UNDEAD, 
		0, 
		5, 
		__T("deeply pierces"),
		__T("fiercely smite"), 
		{NULL, NULL},	/* active_verb */
		__T("undead"),          
		NULL 
	},
	{0, 0, 0, 0, NULL, NULL, {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, __T("Permanently cursed.\n"));
		else if (f3 & TR2_HEAVY_CURSE)
			text_out_c(TERM_L_RED, __T("Heavily cursed.\n"));
		else if (object_known_p(o_ptr))
			text_out_c(TERM_L_RED, __T("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, __T("%+i "), pval);
		info_out_list(descs, count);
	}
	if (f1 & TR0_SEARCH)
	{
		text_out_c((pval > 0) ? TERM_L_GREEN : TERM_RED, __T("%+i%% "), pval * 5);
		text_out(__T("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(__T("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(__T("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(__T("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(__T("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(__T("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(__T("%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(__T("It causes your melee attacks to slay "));
		else 
			text_out(__T("Slays "));
		info_out_list(slay_descs, x);
		printed = TRUE;
	}

	/* Kills */
	if (y)
	{
		if (fulldesc) 
			text_out(__T("It causes your melee attacks to *slay* "));
		else 
			text_out(__T("*Slays* "));
		info_out_list(kill_descs, y);
		printed = TRUE;
	}
	/* Brands */
	if (z)
	{
		if (fulldesc) text_out(__T("It brands your melee attacks with "));
		else text_out(__T("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(__T("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, __T("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, __T("You are too weak to use this weapon.\n"));

		text_out_c(TERM_L_GREEN, __T("%d "), state.num_blow);
		text_out(__T("blow%s/round.\n"), (state.num_blow > 1) ? __T("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(__T("Hits targets up to "));
		text_out_c(TERM_L_GREEN, format(__T("%d"), tdis * 10));
		text_out(__T(" 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(__T("This weapon benefits from one or more ring brands.\n"));

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

	text_out(__T("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, __T("%d"), 0);
		else if (total_dam % 10)
			text_out_c(TERM_L_GREEN, __T("%d.%d"),
			           total_dam / 10, total_dam % 10);
		else
			text_out_c(TERM_L_GREEN, __T("%d"), total_dam / 10);


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

	if (cnt) text_out(__T("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, __T("%d"), 0);
	else if (total_dam % 10)
		text_out_c(TERM_L_GREEN, __T("%d.%d"),
				total_dam / 10, total_dam % 10);
	else
		text_out_c(TERM_L_GREEN, __T("%d"), total_dam / 10);

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

	/* Add breakage chance */
	if (ammo)
	{
		text_out_c(TERM_L_GREEN, __T("%d%%"), breakage_chance(o_ptr));
		text_out(__T(" 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] = { __T("rubble"), __T("magma veins"), __T("quartz veins"), __T("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(__T("Clears "));
		if (i == 3 || (i != 0 && chance == 0))
			text_out(__T("and "));

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

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

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

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

	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(__T("Nourishes for around "));
		text_out_c(TERM_L_GREEN, __T("%d"), (o_ptr->pval / 2) * multiplier / 10);
		text_out(__T(" 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(__T("Radius "));
	text_out_c(TERM_L_GREEN, format(__T("%d"), rad));
	if (no_fuel && !artifact)
		text_out(__T(" light.  No fuel required."));
	else if (is_lite && o_ptr->sval == SV_LITE_TORCH)
		text_out(__T(" light, reduced when running out of fuel."));
	else
		text_out(__T(" light."));

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

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

	text_out(__T("\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(__T("When activated, it "));
	else if (effect_aim(effect))
		text_out(__T("When aimed, it "));
	else if (o_ptr->tval == TV_FOOD)
		text_out(__T("When eaten, it "));
	else if (o_ptr->tval == TV_POTION)
		text_out(__T("When drunk, it "));
	else if (o_ptr->tval == TV_SCROLL)
	    text_out(__T("When read, it "));
	else
	    text_out(__T("When used, it "));

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

	text_out(__T(".\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(__T("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, __T("%d"), min_time);

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

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

		text_out(__T(".\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, __T("%^s\n"), o_name);

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

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

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

		case ORIGIN_FLOOR:
			text_out(__T("(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(__T("dropped by "));

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

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

 			break;
		}

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

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

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

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

	text_out(__T("\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(__T("\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(__T("  "));
			text_out(e_text + e_info[o_ptr->name2].text);
			text_out(__T("\n\n"));
		}
		else if (did_desc)
		{
			text_out(__T("\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(__T("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(__T("\n"));
	
	if (describe_activation(o_ptr, f[2], full, terse, subjective))
	{
		something = TRUE;
		text_out(__T("\n"));
	}

	if (subjective && describe_combat(o_ptr, full))
	{
		something = TRUE;
		text_out(__T("\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);
}
