/* File: skills.c */

/* Purpose: player skills */

/*
 * Copyright (c) 2001 DarkGod
 *
 * This software may be copied and distributed for educational, research, and
 * not for profit purposes provided that this copyright and statement are
 * included in all such copies.
 */

#include "angband.h"


/*
 * Advance the skill point of the skill specified by i and
 * modify related skills
 */
void increase_skill(int i, s16b *invest)
{
	s32b max_skill_overage;

	/* No skill points to be allocated */
	if (!p_ptr->skill_points) return;

	/* The skill cannot be increased */
	if (!s_info[i].mod) return;

	/* The skill is already maxed */
	if (s_info[i].value >= SKILL_MAX) return;

	/* Cannot allocate more than player level + max_skill_overage levels */
	call_lua("get_module_info", "(s)", "d", "max_skill_overage", &max_skill_overage);
	if (((s_info[i].value + s_info[i].mod) / SKILL_STEP) >= (p_ptr->lev + max_skill_overage + 1))
	{
		int hgt, wid;

		Term_get_size(&wid, &hgt);
		msg_box(format("Cannot raise a skill value above %i + player level.", max_skill_overage), (int)(hgt / 2), (int)(wid / 2));
		return;
	}

	/* Spend an unallocated skill point */
	p_ptr->skill_points--;

	/* Increase the skill */
	s_info[i].value += s_info[i].mod;
	invest[i]++;
}


/*
 * Descrease the skill point of the skill specified by i and
 * modify related skills
 */
void decrease_skill(int i, s16b *invest)
{
	/* Cannot decrease more */
	if (!invest[i]) return;

	/* The skill cannot be decreased */
	if (!s_info[i].mod) return;

	/* The skill already has minimal value */
	if (!s_info[i].value) return;

	/* Free a skill point */
	p_ptr->skill_points++;

	/* Decrease the skill */
	s_info[i].value -= s_info[i].mod;
	invest[i]--;
}


/*
 * Given the name of a skill, returns skill index or -1 if no
 * such skill is found
 */
s16b find_skill(cptr name)
{
	u16b i;

	/* Scan skill list */
	for (i = 1; i < max_s_idx; i++)
	{
		/* The name matches */
		if (streq(s_info[i].name + s_name, name)) return (i);
	}

	/* No match found */
	return ( -1);
}
s16b find_skill_i(cptr name)
{
	u16b i;

	/* Scan skill list */
	for (i = 1; i < max_s_idx; i++)
	{
		/* The name matches */
		if (0 == stricmp(s_info[i].name + s_name, name)) return (i);
	}

	/* No match found */
	return ( -1);
}


/*
 *
 */
s16b get_skill(int skill)
{
	return (s_info[skill].value / SKILL_STEP);
}


/*
 * Return "scale" (a misnomer -- this is max value) * (current skill value)
 * / (max skill value)
 */
s16b get_skill_scale(int skill, u32b scale)
{
	s32b temp;

	/*
	* SKILL_STEP shouldn't matter here because the second parameter is
	* relatively small (the largest one being somewhere around 200),
	* AND because we could have used much simpler 0--50 if the ability
	* progression were only possible at step boundaries.
	*
	* Because I'm not at all certain about my interpretation of the mysterious
	* formula given above, I verified this works the same by using a tiny
	* scheme program... -- pelpel
	*/
	temp = scale * s_info[skill].value;

	return (temp / SKILL_MAX);
}


/*
 *
 */
int get_idx(int i)
{
	int j;

	for (j = 1; j < max_s_idx; j++)
	{
		if (s_info[j].order == i)
			return (j);
	}
	return (0);
}

static bool_ is_known(int s_idx)
{
	int i;

	if (wizard) return TRUE;
	if (s_info[s_idx].value || s_info[s_idx].mod) return TRUE;

	for (i = 0; i < max_s_idx; i++)
	{
		/* It is our child, if we don't know it we continue to search, if we know it it is enough*/
		if (s_info[i].father == s_idx)
		{
			if (is_known(i))
				return TRUE;
		}
	}

	/* Ok know none */
	return FALSE;
}

/*
 *
 */
void init_table_aux(int table[MAX_SKILLS][2], int *idx, int father, int lev,
                    bool_ full)
{
	int j, i;

	for (j = 1; j < max_s_idx; j++)
	{
		i = get_idx(j);
		if (s_info[i].father != father) continue;
		if (s_info[i].hidden) continue;
		if (!is_known(i)) continue;

		table[*idx][0] = i;
		table[*idx][1] = lev;
		(*idx)++;
		if (s_info[i].dev || full) init_table_aux(table, idx, i, lev + 1, full);
	}
}


void init_table(int table[MAX_SKILLS][2], int *max, bool_ full)
{
	*max = 0;
	init_table_aux(table, max, -1, 0, full);
}


bool_ has_child(int sel)
{
	int i;

	for (i = 1; i < max_s_idx; i++)
	{
		if ((s_info[i].father == sel) && (is_known(i)))
			return (TRUE);
	}
	return (FALSE);
}


/*
 * Dump the skill tree
 */
void dump_skills(FILE *fff)
{
	int i, j, max = 0;
	int table[MAX_SKILLS][2];
	char buf[80];

	init_table(table, &max, TRUE);

	fprintf(fff, "\nSkills (points left: %d)", p_ptr->skill_points);

	for (j = 0; j < max; j++)
	{
		int z;

		i = table[j][0];

		if ((s_info[i].value == 0) && (i != SKILL_MISC))
		{
			if (s_info[i].mod == 0) continue;
		}

		sprintf(buf, "\n");

		for (z = 0; z < table[j][1]; z++) strcat(buf, "         ");

		if (!has_child(i))
		{
			strcat(buf, format(" . %s", s_info[i].name + s_name));
		}
		else
		{
			strcat(buf, format(" - %s", s_info[i].name + s_name));
		}

		fprintf(fff, "%-49s%s%06.3f [%05.3f]",
		        buf, s_info[i].value < 0 ? "-" : " ",
			((double) ABS(s_info[i].value)) / SKILL_STEP,
		        ((double) s_info[i].mod) / 1000);
	}

	fprintf(fff, "\n");
}


/*
 * Draw the skill tree
 */
void print_skills(int table[MAX_SKILLS][2], int max, int sel, int start)
{
	int i, j;
	int wid, hgt;
	cptr keys;

	Term_clear();
	Term_get_size(&wid, &hgt);

	c_prt(TERM_WHITE, format("%s Skills Screen", game_module), 0, 28);
	keys = format("#BEnter#W to develop a branch, #Bup#W/#Bdown#W to move, #Bright#W/#Bleft#W to modify, #B?#W for help");
	display_message(0, 1, strlen(keys), TERM_WHITE, keys);
	c_prt((p_ptr->skill_points) ? TERM_L_BLUE : TERM_L_RED,
	      format("Skill points left: %d", p_ptr->skill_points), 2, 0);
	print_desc_aux(s_info[table[sel][0]].desc + s_text, 3, 0);

	for (j = start; j < start + (hgt - 7); j++)
	{
		byte color = TERM_WHITE;
		char deb = ' ', end = ' ';

		if (j >= max) break;

		i = table[j][0];

		if ((s_info[i].value == 0) && (i != SKILL_MISC))
		{
			if (s_info[i].mod == 0) color = TERM_L_DARK;
			else color = TERM_ORANGE;
		}
		else if (s_info[i].value == SKILL_MAX) color = TERM_L_BLUE;
		if (s_info[i].hidden) color = TERM_L_RED;
		if (j == sel)
		{
			color = TERM_L_GREEN;
			deb = '[';
			end = ']';
		}
		if (!has_child(i))
		{
			c_prt(color, format("%c.%c%s", deb, end, s_info[i].name + s_name),
			      j + 7 - start, table[j][1] * 4);
		}
		else if (s_info[i].dev)
		{
			c_prt(color, format("%c-%c%s", deb, end, s_info[i].name + s_name),
			      j + 7 - start, table[j][1] * 4);
		}
		else
		{
			c_prt(color, format("%c+%c%s", deb, end, s_info[i].name + s_name),
			      j + 7 - start, table[j][1] * 4);
		}
		c_prt(color,
		      format("%s%02ld.%03ld [%01d.%03d]",
		             s_info[i].value < 0 ? "-" : " ",
			     ABS(s_info[i].value) / SKILL_STEP,
			     ABS(s_info[i].value) % SKILL_STEP,
			     ABS(s_info[i].mod) / 1000,
			     ABS(s_info[i].mod) % 1000),
		      j + 7 - start, 60);
	}
}

/*
 * Checks various stuff to do when skills change, like new spells, ...
 */
void recalc_skills(bool_ init)
{
	static int thaum_level = 0;

	/* TODO: This should be a hook in ToME's lua */
	if (init)
	{
		thaum_level = get_skill_scale(SKILL_THAUMATURGY, 100);
	}
	else
	{
		int thaum_gain = 0;

		/* Gain thaum spells */
		while (thaum_level < get_skill_scale(SKILL_THAUMATURGY, 100))
		{
			if (spell_num == MAX_SPELLS) break;
			thaum_level++;
			generate_spell((thaum_level + 1) / 2);
			thaum_gain++;
		}
		if (thaum_gain)
		{
			if (thaum_gain == 1)
				msg_print("You have gained one new thaumaturgy spell.");
			else
				msg_format("You have gained %d new thaumaturgy spells.", thaum_gain);
		}

		process_hooks(HOOK_RECALC_SKILLS, "()");

		/* Update stuffs */
		p_ptr->update |= (PU_BONUS | PU_HP | PU_MANA | PU_SPELLS | PU_POWERS |
		                  PU_SANITY | PU_BODY);

		/* Redraw various info */
		p_ptr->redraw |= (PR_WIPE | PR_BASIC | PR_EXTRA | PR_MAP);
	}
}

/*
 * Recalc the skill value
 */
void recalc_skills_theory(s16b *invest, s32b *base_val, s32b *base_mod, s32b *bonus)
{
	int i, j;

	/* First we assign the normal points */
	for (i = 0; i < max_s_idx; i++)
	{
		/* Calc the base */
		s_info[i].value = base_val[i] + (base_mod[i] * invest[i]) + bonus[i];

		/* It cannot exceed SKILL_MAX */
		if (s_info[i].value > SKILL_MAX) s_info[i].value = SKILL_MAX;
	}

	/* Then we modify related skills */
	for (i = 0; i < max_s_idx; i++)
	{
		for (j = 1; j < max_s_idx; j++)
		{
			/* Ignore self */
			if (j == i) continue;

			/* Exclusive skills */
			if ((s_info[i].action[j] == SKILL_EXCLUSIVE) && invest[i])
			{
				/* Turn it off */
				p_ptr->skill_points += invest[j];
				invest[j] = 0;
				s_info[j].value = 0;
			}

			/* Non-exclusive skills */
			else if (s_info[i].action[j])
			{
				/* Increase / decrease with a % */
				s32b val = s_info[j].value + (invest[i] * s_info[j].mod * s_info[i].action[j] / 100);

				/* It cannot exceed SKILL_MAX */
				if (val > SKILL_MAX) val = SKILL_MAX;

				/* Save the modified value */
				s_info[j].value = val;
			}
		}
	}
}

/*
 * Interreact with skills
 */
void do_cmd_skill()
{
	int sel = 0, start = 0, max;
	char c;
	int table[MAX_SKILLS][2];
	int i;
	int wid, hgt;
	s16b skill_points_save;
	s32b *skill_values_save;
	s32b *skill_mods_save;
	s16b *skill_rates_save;
	s16b *skill_invest;
	s32b *skill_bonus;

	recalc_skills(TRUE);

	/* Save the screen */
	screen_save();

	/* Allocate arrays to save skill values */
	C_MAKE(skill_values_save, MAX_SKILLS, s32b);
	C_MAKE(skill_mods_save, MAX_SKILLS, s32b);
	C_MAKE(skill_rates_save, MAX_SKILLS, s16b);
	C_MAKE(skill_invest, MAX_SKILLS, s16b);
	C_MAKE(skill_bonus, MAX_SKILLS, s32b);

	/* Save skill points */
	skill_points_save = p_ptr->skill_points;

	/* Save skill values */
	for (i = 0; i < max_s_idx; i++)
	{
		skill_type *s_ptr = &s_info[i];

		skill_values_save[i] = s_ptr->value;
		skill_mods_save[i] = s_ptr->mod;
		skill_rates_save[i] = s_ptr->rate;
		skill_invest[i] = 0;
	}

	/* Clear the screen */
	Term_clear();

	/* Initialise the skill list */
	init_table(table, &max, FALSE);

	while (TRUE)
	{
		Term_get_size(&wid, &hgt);

		/* Display list of skills */
		recalc_skills_theory(skill_invest, skill_values_save, skill_mods_save, skill_bonus);
		print_skills(table, max, sel, start);

		/* Wait for user input */
		c = inkey();

		/* Leave the skill screen */
		if (c == ESCAPE) break;

		/* Expand / collapse list of skills */
		else if (c == '\r')
		{
			if (s_info[table[sel][0]].dev) s_info[table[sel][0]].dev = FALSE;
			else s_info[table[sel][0]].dev = TRUE;
			init_table(table, &max, FALSE);
		}

		/* Next page */
		else if (c == 'n')
		{
			sel += (hgt - 7);
			if (sel >= max) sel = max - 1;
		}

		/* Previous page */
		else if (c == 'p')
		{
			sel -= (hgt - 7);
			if (sel < 0) sel = 0;
		}

		/* Select / increase a skill */
		else
		{
			int dir;

			/* Allow use of numpad / arrow keys / roguelike keys */
			dir = get_keymap_dir(c);

			/* Move cursor down */
			if (dir == 2) sel++;

			/* Move cursor up */
			if (dir == 8) sel--;

			/* Miscellaneous skills cannot be increased/decreased as a group */
			if (table[sel][0] == SKILL_MISC) continue;

			/* Increase the current skill */
			if (dir == 6) increase_skill(table[sel][0], skill_invest);

			/* Decrease the current skill */
			if (dir == 4) decrease_skill(table[sel][0], skill_invest);

			/* XXX XXX XXX Wizard mode commands outside of wizard2.c */

			/* Increase the skill */
			if (wizard && (c == '+')) skill_bonus[table[sel][0]] += SKILL_STEP;

			/* Decrease the skill */
			if (wizard && (c == '-')) skill_bonus[table[sel][0]] -= SKILL_STEP;

			/* Contextual help */
			if (c == '?') exec_lua(format("ingame_help('select_context', 'skill', '%s')", s_info[table[sel][0]].name + s_name));
			;

			/* Handle boundaries and scrolling */
			if (sel < 0) sel = max - 1;
			if (sel >= max) sel = 0;
			if (sel < start) start = sel;
			if (sel >= start + (hgt - 7)) start = sel - (hgt - 7) + 1;
		}
	}


	/* Some skill points are spent */
	if (p_ptr->skill_points != skill_points_save)
	{
		/* Flush input as we ask an important and irreversible question */
		flush();

		/* Ask we can commit the change */
		if (msg_box("Save and use these skill values? (y/n)", (int)(hgt / 2), (int)(wid / 2)) != 'y')
		{
			/* User declines -- restore the skill values before exiting */

			/* Restore skill points */
			p_ptr->skill_points = skill_points_save;

			/* Restore skill values */
			for (i = 0; i < max_s_idx; i++)
			{
				skill_type *s_ptr = &s_info[i];

				s_ptr->value = skill_values_save[i];
				s_ptr->mod = skill_mods_save[i];
				s_ptr->rate = skill_rates_save[i];
			}
		}
	}


	/* Free arrays to save skill values */
	C_FREE(skill_values_save, MAX_SKILLS, s32b);
	C_FREE(skill_mods_save, MAX_SKILLS, s32b);
	C_FREE(skill_rates_save, MAX_SKILLS, s16b);
	C_FREE(skill_invest, MAX_SKILLS, s16b);
	C_FREE(skill_bonus, MAX_SKILLS, s32b);

	/* Load the screen */
	screen_load();

	recalc_skills(FALSE);
}



/*
 * List of melee skills
 */
s16b melee_skills[MAX_MELEE] =
{
	SKILL_MASTERY,
	SKILL_HAND,
	SKILL_BEAR,
};
char *melee_names[MAX_MELEE] =
{
	"Weapon combat",
	"Barehanded combat",
	"Bearform combat",
};
static bool_ melee_bool[MAX_MELEE];
static int melee_num[MAX_MELEE];

s16b get_melee_skill()
{
	int i;

	for (i = 0; i < MAX_MELEE; i++)
	{
		if (p_ptr->melee_style == melee_skills[i])
			return (i);
	}
	return (0);
}

s16b get_melee_skills()
{
	int i, j = 0;

	for (i = 0; i < MAX_MELEE; i++)
	{
		if ((s_info[melee_skills[i]].value > 0) && (!s_info[melee_skills[i]].hidden))
		{
			melee_bool[i] = TRUE;
			j++;
		}
		else
			melee_bool[i] = FALSE;
	}

	return (j);
}

static void choose_melee()
{
	int i, j, z = 0;
	int force_drop = FALSE, style_unchanged = FALSE;

	character_icky = TRUE;
	Term_save();
	Term_clear();

	j = get_melee_skills();
	prt("Choose a melee style:", 0, 0);
	for (i = 0; i < MAX_MELEE; i++)
	{
		if (melee_bool[i])
		{
			prt(format("%c) %s", I2A(z), melee_names[i]), z + 1, 0);
			melee_num[z] = i;
			z++;
		}
	}

	while (TRUE)
	{
		char c = inkey();

		if (c == ESCAPE) break;
		if (A2I(c) < 0) continue;
		if (A2I(c) >= j) continue;

		for (i = 0, z = 0; z < A2I(c); i++)
			if (melee_bool[i]) z++;

		if (p_ptr->melee_style == melee_skills[melee_num[z]])
		{
			style_unchanged = TRUE;
			break;
		}

		for (i = INVEN_WIELD; p_ptr->body_parts[i - INVEN_WIELD] == INVEN_WIELD; i++)
		{
			if (p_ptr->inventory[i].k_idx)
			{
				if (cursed_p(&p_ptr->inventory[i]))
				{
					char name[80];
					object_desc(name, &p_ptr->inventory[i], 0, 0);
					msg_format("Hmmm, your %s seems to be cursed.", name);
					break;
				}
				else if (INVEN_PACK == inven_takeoff(i, 255, force_drop))
				{
					force_drop = TRUE;
				}
			}
		}
		p_ptr->melee_style = melee_skills[melee_num[z]];
		energy_use = 100;
		break;
	}

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

	/* Recalculate hitpoint */
	p_ptr->update |= (PU_HP);

	/* Redraw monster hitpoint */
	p_ptr->redraw |= (PR_MH);

	Term_load();
	character_icky = FALSE;

	if (style_unchanged)
	{
		msg_format("You are already using %s.", melee_names[melee_num[z]]);
	}
}

void select_default_melee()
{
	int i;

	get_melee_skills();
	p_ptr->melee_style = SKILL_MASTERY;
	for (i = 0; i < MAX_MELEE; i++)
	{
		if (melee_bool[i])
		{
			p_ptr->melee_style = melee_skills[i];
			break;
		}
	}
}

/*
 * Print a batch of skills.
 */
static void print_skill_batch(int *p, cptr *p_desc, int start, int max, bool_ mode)
{
	char buff[80];
	int i = start, j = 0;

	if (mode) prt(format("         %-31s", "Name"), 1, 20);

	for (i = start; i < (start + 20); i++)
	{
		if (i >= max) break;

		if (p[i] > 0)
			sprintf(buff, "  %c - %d) %-30s", I2A(j), p[i], p_desc[i]);
		else
			sprintf(buff, "  %c - %d) %-30s", I2A(j), p[i], "Change melee style");

		if (mode) prt(buff, 2 + j, 20);
		j++;
	}
	if (mode) prt("", 2 + j, 20);
	prt(format("Select a skill (a-%c), @ to select by name, +/- to scroll:", I2A(j - 1)), 0, 0);
}

int do_cmd_activate_skill_aux()
{
	char which;
	int max = 0, i, start = 0;
	int ret;
	bool_ mode = FALSE;
	int *p;
	cptr *p_desc;

	C_MAKE(p, max_s_idx + max_ab_idx, int);
	C_MAKE(p_desc, max_s_idx + max_ab_idx, cptr);

	/* Count the max */

	/* More than 1 melee skill ? */
	if (get_melee_skills() > 1)
	{
		p_desc[max] = "Change melee mode";
		p[max++] = 0;
	}

	for (i = 1; i < max_s_idx; i++)
	{
		if (s_info[i].action_mkey && s_info[i].value && ((!s_info[i].hidden) || (i == SKILL_LEARN)))
		{
			int j;
			bool_ next = FALSE;

			/* Already got it ? */
			for (j = 0; j < max; j++)
			{
				if (s_info[i].action_mkey == p[j])
				{
					next = TRUE;
					break;
				}
			}
			if (next) continue;

			p_desc[max] = s_text + s_info[i].action_desc;
			p[max++] = s_info[i].action_mkey;
		}
	}

	for (i = 0; i < max_ab_idx; i++)
	{
		if (ab_info[i].action_mkey && ab_info[i].acquired)
		{
			int j;
			bool_ next = FALSE;

			/* Already got it ? */
			for (j = 0; j < max; j++)
			{
				if (ab_info[i].action_mkey == p[j])
				{
					next = TRUE;
					break;
				}
			}
			if (next) continue;

			p_desc[max] = ab_text + ab_info[i].action_desc;
			p[max++] = ab_info[i].action_mkey;
		}
	}

	if (!max)
	{
		msg_print("You don't have any activable skills or abilities.");
		return -1;
	}

	character_icky = TRUE;
	Term_save();

	while (1)
	{
		print_skill_batch(p, p_desc, start, max, mode);
		which = inkey();

		if (which == ESCAPE)
		{
			ret = -1;
			break;
		}
		else if (which == '*' || which == '?' || which == ' ')
		{
			mode = (mode) ? FALSE : TRUE;
			Term_load();
			character_icky = FALSE;
		}
		else if (which == '+')
		{
			start += 20;
			if (start >= max) start -= 20;
			Term_load();
			character_icky = FALSE;
		}
		else if (which == '-')
		{
			start -= 20;
			if (start < 0) start += 20;
			Term_load();
			character_icky = FALSE;
		}
		else if (which == '@')
		{
			char buf[80];

			strcpy(buf, "Cast a spell");
			if (!get_string("Skill action? ", buf, 79))
				return FALSE;

			/* Find the skill it is related to */
			for (i = 0; i < max; i++)
			{
				if (!strcmp(buf, p_desc[i]))
					break;
			}
			if ((i < max))
			{
				ret = p[i];
				break;
			}

		}
		else
		{
			which = tolower(which);
			if (start + A2I(which) >= max)
			{
				bell();
				continue;
			}
			if (start + A2I(which) < 0)
			{
				bell();
				continue;
			}

			ret = p[start + A2I(which)];
			break;
		}
	}
	Term_load();
	character_icky = FALSE;

	C_FREE(p, max_s_idx + max_ab_idx, int);
	C_FREE(p_desc, max_s_idx + max_ab_idx, cptr);

	return ret;
}

/* Ask & execute a skill */
void do_cmd_activate_skill()
{
	int x_idx;
	bool_ push = TRUE;

	/* Get the skill, if available */
	if (repeat_pull(&x_idx))
	{
		push = FALSE;
	}
	else if (!command_arg) x_idx = do_cmd_activate_skill_aux();
	else
	{
		int i, j;

		x_idx = command_arg;

		/* Check validity */
		for (i = 1; i < max_s_idx; i++)
		{
			if (s_info[i].value && (s_info[i].action_mkey == x_idx))
				break;
		}
		for (j = 0; j < max_ab_idx; j++)
		{
			if (ab_info[j].acquired && (ab_info[j].action_mkey == x_idx))
				break;
		}

		if ((j == max_ab_idx) && (i == max_s_idx))
		{
			msg_print("Uh?");
			return;
		}
	}

	if (x_idx == -1) return;

	if (push) repeat_push(x_idx);

	if (!x_idx)
	{
		choose_melee();
		return;
	}

	/* Break goi/manashield */
	if (p_ptr->invuln)
	{
		set_invuln(0);
	}
	if (p_ptr->disrupt_shield)
	{
		set_disrupt_shield(0);
	}

	switch (x_idx)
	{
	case MKEY_ANTIMAGIC:
		do_cmd_unbeliever();
		break;
	case MKEY_MINDCRAFT:
		do_cmd_mindcraft();
		break;
	case MKEY_ALCHEMY:
		do_cmd_alchemist();
		break;
	case MKEY_MIMIC:
		do_cmd_mimic();
		break;
	case MKEY_POWER_MAGE:
		do_cmd_powermage();
		break;
	case MKEY_RUNE:
		do_cmd_runecrafter();
		break;
	case MKEY_FORGING:
		do_cmd_archer();
		break;
	case MKEY_INCARNATION:
		do_cmd_possessor();
		break;
	case MKEY_TELEKINESIS:
		do_cmd_portable_hole();
		break;
	case MKEY_BLADE:
		do_cmd_blade();
		break;
	case MKEY_SUMMON:
		do_cmd_summoner();
		break;
	case MKEY_NECRO:
		do_cmd_necromancer();
		break;
	case MKEY_SYMBIOTIC:
		do_cmd_symbiotic();
		break;
	case MKEY_TRAP:
		do_cmd_set_trap();
		break;
	case MKEY_STEAL:
		do_cmd_steal();
		break;
	case MKEY_DODGE:
		use_ability_blade();
		break;
	case MKEY_SCHOOL:
		cast_school_spell();
		break;
	case MKEY_COPY:
		do_cmd_copy_spell();
		break;
	case MKEY_BOULDER:
		do_cmd_create_boulder();
		break;
	case MKEY_COMPANION:
		if (get_skill(SKILL_LORE) >= 12)
			do_cmd_companion();
		else
			msg_print("You need a skill level of at least 12.");
		break;
	case MKEY_PIERCING:
		do_cmd_set_piercing();
		break;
	default:
		process_hooks(HOOK_MKEY, "(d)", x_idx);
		break;
	}
}


/* Which magic forbids non FA gloves */
bool_ forbid_gloves()
{
	if (get_skill(SKILL_SORCERY)) return (TRUE);
	if (get_skill(SKILL_MANA)) return (TRUE);
	if (get_skill(SKILL_FIRE)) return (TRUE);
	if (get_skill(SKILL_AIR)) return (TRUE);
	if (get_skill(SKILL_WATER)) return (TRUE);
	if (get_skill(SKILL_EARTH)) return (TRUE);
	if (get_skill(SKILL_THAUMATURGY)) return (TRUE);
	return (FALSE);
}

/* Which gods forbid edged weapons */
bool_ forbid_non_blessed()
{
	GOD(GOD_ERU) return (TRUE);
	return (FALSE);
}


/*
 * Gets the base value of a skill, given a race/class/...
 */
void compute_skills(s32b *v, s32b *m, int i)
{
	s32b value, mod;

	/***** general skills *****/

	/* If the skill correspond to the magic school lets pump them a bit */
	value = gen_skill_base[i];
	mod = gen_skill_mod[i];

	*v = modify_aux(*v,
	                value, gen_skill_basem[i]);
	*m = modify_aux(*m,
	                mod, gen_skill_modm[i]);

	/***** race skills *****/

	value = rp_ptr->skill_base[i];
	mod = rp_ptr->skill_mod[i];

	*v = modify_aux(*v,
	                value, rp_ptr->skill_basem[i]);
	*m = modify_aux(*m,
	                mod, rp_ptr->skill_modm[i]);

	/***** race mod skills *****/

	value = rmp_ptr->skill_base[i];
	mod = rmp_ptr->skill_mod[i];

	*v = modify_aux(*v,
	                value, rmp_ptr->skill_basem[i]);
	*m = modify_aux(*m,
	                mod, rmp_ptr->skill_modm[i]);

	/***** class skills *****/

	value = cp_ptr->skill_base[i];
	mod = cp_ptr->skill_mod[i];

	*v = modify_aux(*v,
	                value, cp_ptr->skill_basem[i]);
	*m = modify_aux(*m,
	                mod, cp_ptr->skill_modm[i]);

	/***** class spec skills *****/

	value = spp_ptr->skill_base[i];
	mod = spp_ptr->skill_mod[i];

	*v = modify_aux(*v,
	                value, spp_ptr->skill_basem[i]);
	*m = modify_aux(*m,
	                mod, spp_ptr->skill_modm[i]);
}

/*
 * Initialize a skill with given values
 */
void init_skill(s32b value, s32b mod, int i)
{
	s_info[i].value = value;
	s_info[i].mod = mod;

	if (s_info[i].flags1 & SKF1_HIDDEN)
		s_info[i].hidden = TRUE;
	else
		s_info[i].hidden = FALSE;
}

void do_get_new_skill()
{
	char *items[LOST_SWORD_NSKILLS];
	int skl[LOST_SWORD_NSKILLS];
	s32b val[LOST_SWORD_NSKILLS], mod[LOST_SWORD_NSKILLS];
	bool_ used[MAX_SKILLS];
	int available_skills[MAX_SKILLS];
	int max = 0, max_a = 0, res, i;

	/* Check if some skills didn't influence other stuff */
	recalc_skills(TRUE);

	/* Grab the ones we can gain */
	max = 0;
	for (i = 0; i < max_s_idx; i++)
	{
		if (s_info[i].flags1 & SKF1_RANDOM_GAIN)
			available_skills[max++] = i;
	}
	available_skills[max++] = -1;

	/* Init */
	for (max = 0; max < MAX_SKILLS; max++)
	{
		used[max] = FALSE;
	}

	/* Count the number of available skills */
	while (available_skills[max_a] != -1) max_a++;

	/* Get LOST_SWORD_NSKILLS skills */
	for (max = 0; max < LOST_SWORD_NSKILLS; max++)
	{
		int i;
		skill_type *s_ptr;

		/* Get an non used skill */
		do
		{
			i = rand_int(max_a);

			/* Does it pass the check? */
			if (!magik(s_info[available_skills[i]].random_gain_chance))
				continue;
		}
		while (used[available_skills[i]]);

		s_ptr = &s_info[available_skills[i]];
		used[available_skills[i]] = TRUE;

		if (s_ptr->mod)
		{
			if (s_ptr->mod < 300)
			{
				val[max] = 1000;
				mod[max] = 300 - s_ptr->mod;
			}
			else if (s_ptr->mod < 500)
			{
				val[max] = s_ptr->mod * 1;
				mod[max] = 100;
				if (mod[max] + s_ptr->mod > 500)
					mod[max] = 500 - s_ptr->mod;
			}
			else
			{
				val[max] = s_ptr->mod * 3;
				mod[max] = 0;
			}
		}
		else
		{
			mod[max] = 300;
			val[max] = 1000;
		}
		if (s_ptr->value + val[max] > SKILL_MAX) val[max] = SKILL_MAX - s_ptr->value;
		skl[max] = available_skills[i];
		items[max] = (char *)string_make(format("%-40s: +%02ld.%03ld value, +%01d.%03d modifier", s_ptr->name + s_name, val[max] / SKILL_STEP, val[max] % SKILL_STEP, mod[max] / SKILL_STEP, mod[max] % SKILL_STEP));
	}

	while (TRUE)
	{
		char last = 'a' + (LOST_SWORD_NSKILLS-1);
		char buf[80];
		sprintf(buf, "Choose a skill to learn(a-%c to choose, ESC to cancel)?", last);
		res = ask_menu(buf, (char **)items, LOST_SWORD_NSKILLS);

		/* Ok ? lets learn ! */
		if (res > -1)
		{
			skill_type *s_ptr;
			bool_ oppose = FALSE;
			int oppose_skill = -1;

			/* Check we don't oppose an existing skill */
			for (i = 0; i < max_s_idx; i++)
			{
				if ((s_info[i].action[skl[res]] == SKILL_EXCLUSIVE) &&
				                (s_info[i].value != 0))
				{
					oppose = TRUE;
					oppose_skill = i;
					break;
				}
			}

			/* Ok we oppose, so be sure */
			if (oppose)
			{
				cptr msg;

				/*
				 * Because this is SO critical a question, we must flush
				 * input to prevent killing character off -- pelpel
				 */
				flush();

				/* Prepare prompt */
				msg = format("This skill is mutually exclusive with "
				             "at least %s, continue?",
				             s_info[oppose_skill].name + s_name);

				/* The player rejected the choice */
				if (!get_check(msg)) continue;
			}

			s_ptr = &s_info[skl[res]];
			s_ptr->value += val[res];
			s_ptr->mod += mod[res];
			if (mod[res])
			{
				msg_format("You can now learn the %s skill.",
				           s_ptr->name + s_name);
			}
			else
			{
				msg_format("Your knowledge of the %s skill increases.",
				           s_ptr->name + s_name);
			}
			break;
		}
	}

	/* Free them ! */
	for (max = 0; max < LOST_SWORD_NSKILLS; max++)
	{
		string_free(items[max]);
	}

	/* Check if some skills didn't influence other stuff */
	recalc_skills(FALSE);
}




/**************************************** ABILITIES *****************************************/

/*
 * Given the name of an ability, returns ability index or -1 if no
 * such ability is found
 */
s16b find_ability(cptr name)
{
	u16b i;

	/* Scan ability list */
	for (i = 0; i < max_ab_idx; i++)
	{
		/* The name matches */
		if (streq(ab_info[i].name + ab_name, name)) return (i);
	}

	/* No match found */
	return ( -1);
}

/*
 * Do the player have the ability
 */
bool_ has_ability(int ab)
{
	return ab_info[ab].acquired;
}

/* Do we meet the requirements */
bool_ can_learn_ability(int ab)
{
	ability_type *ab_ptr = &ab_info[ab];
	int i;

	if (ab_ptr->acquired)
		return FALSE;

	if (p_ptr->skill_points < ab_info[ab].cost)
		return FALSE;

	for (i = 0; i < 10; i++)
	{
		/* Must have skill level */
		if (ab_ptr->skills[i] > -1)
		{
			if (get_skill(ab_ptr->skills[i]) < ab_ptr->skill_levels[i])
				return FALSE;
		}

		/* Must have ability */
		if (ab_ptr->need_abilities[i] > -1)
		{
			if (!ab_info[ab_ptr->need_abilities[i]].acquired)
				return FALSE;
		}

		/* Must not have ability */
		if (ab_ptr->forbid_abilities[i] > -1)
		{
			if (ab_info[ab_ptr->forbid_abilities[i]].acquired)
				return FALSE;
		}
	}

	for (i = 0; i < 6; i++)
	{
		/* Must have stat */
		if (ab_ptr->stat[i] > -1)
		{
			if (p_ptr->stat_ind[i] < ab_ptr->stat[i] - 3)
				return FALSE;
		}
	}

	/* Do the script allow us? */
	if (process_hooks(HOOK_LEARN_ABILITY, "(d)", ab))
		return FALSE;

	return TRUE;
}

/* Learn an ability */
void gain_ability(int ab)
{
	int wid, hgt;
	Term_get_size(&wid, &hgt);

	if (!can_learn_ability(ab))
	{
		msg_box("You cannot learn this ability.", (int)(hgt / 2), (int)(wid / 2));
		return;
	}

	/* Flush input as we ask an important and irreversible question */
	flush();

	/* Ask we can commit the change */
	if (msg_box("Learn this ability(this is permanent)? (y/n)", (int)(hgt / 2), (int)(wid / 2)) != 'y')
	{
		return;
	}

	ab_info[ab].acquired = TRUE;
	p_ptr->skill_points -= ab_info[ab].cost;
}

/* helper function to generate a sorted table */
static void add_sorted_ability(int *table, int *max, int ab)
{
	int i;

	for (i = 0; i < *max; i++)
	{
		if (strcmp(ab_name + ab_info[ab].name, ab_name + ab_info[table[i]].name) < 0)
		{
			int z;

			/* Move all indexes up */
			for (z = *max; z > i; z--)
			{
				table[z] = table[z - 1];
			}
			break;
		}
	}
	table[i] = ab;

	(*max)++;
}

/*
 * Print the abilities list
 */
void dump_abilities(FILE *fff)
{
	int i, j;
	int *table;
	int max = 0;

	C_MAKE(table, max_ab_idx, int);

	/* Initialise the abilities list */
	for (i = 0; i < max_ab_idx; i++)
	{
		if (ab_info[i].name && has_ability(i))
			add_sorted_ability(table, &max, i);
	}

	if (max)
	{
		fprintf(fff, "\nAbilities");

		for (j = 0; j < max; j++)
		{
			i = table[j];

			fprintf(fff, "\n * %s", ab_info[i].name + ab_name);
		}

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

/*
 * Draw the abilities list
 */
void print_abilities(int table[], int max, int sel, int start)
{
	int i, j;
	int wid, hgt;
	cptr keys;

	Term_clear();
	Term_get_size(&wid, &hgt);

	c_prt(TERM_WHITE, format("%s Abilities Screen", game_module), 0, 28);
	keys = format("#Bup#W/#Bdown#W to move, #Bright#W to buy, #B?#W for help");
	display_message(0, 1, strlen(keys), TERM_WHITE, keys);
	c_prt((p_ptr->skill_points) ? TERM_L_BLUE : TERM_L_RED,
	      format("Skill points left: %d", p_ptr->skill_points), 2, 0);

	print_desc_aux(ab_info[table[sel]].desc + ab_text, 3, 0);

	for (j = start; j < start + (hgt - 7); j++)
	{
		byte color = TERM_WHITE;
		char deb = ' ', end = ' ';

		if (j >= max) break;

		i = table[j];

		if (ab_info[i].acquired)
			color = TERM_L_BLUE;
		else if (can_learn_ability(i))
			color = TERM_WHITE;
		else
			color = TERM_L_DARK;


		if (j == sel)
		{
			color = TERM_L_GREEN;
			deb = '[';
			end = ']';
		}

		c_prt(color, format("%c.%c%s", deb, end, ab_info[i].name + ab_name),
		      j + 7 - start, 0);

		if (!ab_info[i].acquired)
		{
			c_prt(color, format("%d", ab_info[i].cost), j + 7 - start, 60);
		}
		else
		{
			c_prt(color, "Known", j + 7 - start, 60);
		}
	}
}

/*
 * Interreact with abilitiess
 */
void do_cmd_ability()
{
	int sel = 0, start = 0, max = 0;
	char c;
	int *table;
	int i;
	int wid, hgt;

	C_MAKE(table, max_ab_idx, int);

	/* Save the screen */
	screen_save();

	/* Clear the screen */
	Term_clear();

	/* Initialise the abilities list */
	for (i = 0; i < max_ab_idx; i++)
	{
if (ab_info[i].name)
			add_sorted_ability(table, &max, i);
	}

	while (TRUE)
	{
		Term_get_size(&wid, &hgt);

		/* Display list of skills */
		print_abilities(table, max, sel, start);

		/* Wait for user input */
		c = inkey();

		/* Leave the skill screen */
		if (c == ESCAPE) break;

		/* Next page */
		else if (c == 'n')
		{
			sel += (hgt - 7);
			if (sel >= max) sel = max - 1;
		}

		/* Previous page */
		else if (c == 'p')
		{
			sel -= (hgt - 7);
			if (sel < 0) sel = 0;
		}

		/* Select / increase a skill */
		else
		{
			int dir;

			/* Allow use of numpad / arrow keys / roguelike keys */
			dir = get_keymap_dir(c);

			/* Move cursor down */
			if (dir == 2) sel++;

			/* Move cursor up */
			if (dir == 8) sel--;

			/* gain ability */
			if (dir == 6) gain_ability(table[sel]);

			/* XXX XXX XXX Wizard mode commands outside of wizard2.c */

			if (wizard && (c == '+')) ab_info[table[sel]].acquired = TRUE;
			if (wizard && (c == '-')) ab_info[table[sel]].acquired = FALSE;

			/* Contextual help */
			if (c == '?') exec_lua(format("ingame_help('select_context', 'ability', '%s')", ab_info[table[sel]].name + ab_name));
			;

			/* Handle boundaries and scrolling */
			if (sel < 0) sel = max - 1;
			if (sel >= max) sel = 0;
			if (sel < start) start = sel;
			if (sel >= start + (hgt - 7)) start = sel - (hgt - 7) + 1;
		}
	}

	/* Load the screen */
	screen_load();

	C_FREE(table, max_ab_idx, int);

	/* Update stuffs */
	p_ptr->update |= (PU_BONUS | PU_HP | PU_MANA | PU_SPELLS | PU_POWERS |
	                  PU_SANITY | PU_BODY);

	/* Redraw various info */
	p_ptr->redraw |= (PR_WIPE | PR_BASIC | PR_EXTRA | PR_MAP);
}

/*
 * Apply abilities to be granted this level
 */
void apply_level_abilities(int level)
{
	int i;

	for (i = 0; i < 10; i++)
	{
		if (cp_ptr->abilities[i].level == level)
		{
			if ((level > 1) && (!ab_info[cp_ptr->abilities[i].ability].acquired))
				cmsg_format(TERM_L_GREEN, "You have learned the ability '%s'.", ab_name + ab_info[cp_ptr->abilities[i].ability].name);
			ab_info[cp_ptr->abilities[i].ability].acquired = TRUE;
		}
		if (spp_ptr->abilities[i].level == level)
		{
			if ((level > 1) && (!ab_info[spp_ptr->abilities[i].ability].acquired))
				cmsg_format(TERM_L_GREEN, "You have learned the ability '%s'.", ab_name + ab_info[spp_ptr->abilities[i].ability].name);
			ab_info[spp_ptr->abilities[i].ability].acquired = TRUE;
		}
		if (rp_ptr->abilities[i].level == level)
		{
			if ((level > 1) && (!ab_info[rp_ptr->abilities[i].ability].acquired))
				cmsg_format(TERM_L_GREEN, "You have learned the ability '%s'.", ab_name + ab_info[rp_ptr->abilities[i].ability].name);
			ab_info[rp_ptr->abilities[i].ability].acquired = TRUE;
		}
		if (rmp_ptr->abilities[i].level == level)
		{
			if ((level > 1) && (!ab_info[rmp_ptr->abilities[i].ability].acquired))
				cmsg_format(TERM_L_GREEN, "You have learned the ability '%s'.", ab_name + ab_info[rmp_ptr->abilities[i].ability].name);
			ab_info[rmp_ptr->abilities[i].ability].acquired = TRUE;
		}
	}
}
