1stMUD/corefiles/
1stMUD/gods/
1stMUD/notes/
1stMUD/player/
1stMUD/win32/
1stMUD/win32/ROM/
/**************************************************************************
*  Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,        *
*  Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe.   *
*                                                                         *
*  Merc Diku Mud improvements copyright (C) 1992, 1993 by Michael         *
*  Chastain, Michael Quan, and Mitchell Tse.                              *
*                                                                         *
*  In order to use any part of this Merc Diku Mud, you must comply with   *
*  both the original Diku license in 'license.doc' as well the Merc       *
*  license in 'license.txt'.  In particular, you may not remove either of *
*  these copyright notices.                                               *
*                                                                         *
*  Much time and thought has gone into this software and you are          *
*  benefiting.  We hope that you share your changes too.  What goes       *
*  around, comes around.                                                  *
***************************************************************************
*       ROM 2.4 is copyright 1993-1998 Russ Taylor                        *
*       ROM has been brought to you by the ROM consortium                 *
*           Russ Taylor (rtaylor@hypercube.org)                           *
*           Gabrielle Taylor (gtaylor@hypercube.org)                      *
*           Brian Moore (zump@rom.org)                                    *
*       By using this code, you have agreed to follow the terms of the    *
*       ROM license, in the file Rom24/doc/rom.license                    *
***************************************************************************
*       1stMUD ROM Derivative (c) 2001-2002 by Ryan Jennings              *
*            http://1stmud.dlmud.com/  <r-jenn@shaw.ca>                   *
***************************************************************************/

#include <sys/types.h>
#if !defined(WIN32)
#include <sys/time.h>
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "merc.h"
#include "interp.h"
#include "magic.h"
#include "recycle.h"

/* used to get new skills */
CH_CMD(do_gain)
{
	char buf[MAX_STRING_LENGTH];
	char arg[MAX_INPUT_LENGTH];
	CHAR_DATA *trainer;
	int gn = 0, sn = 0;

	if (IS_NPC(ch))
		return;

	/* find a trainer */
	for (trainer = ch->in_room->first_person; trainer != NULL;
		 trainer = trainer->next_in_room)
		if (IS_NPC(trainer) && IS_SET(trainer->act, ACT_GAIN))
			break;

	if (trainer == NULL || !can_see(ch, trainer))
	{
		chprintln(ch, "You can't do that here.");
		return;
	}

	one_argument(argument, arg);

	if (arg[0] == '\0')
	{
		do_function(trainer, &do_say, "Pardon me?");
		return;
	}

	if (!str_prefix(arg, "list"))
	{
		int col;

		col = 0;

		chprintlnf(ch, "%-18s %-5s %-18s %-5s %-18s %-5s", "group",
				   "cost", "group", "cost", "group", "cost");

		for (gn = 0; gn < maxGroup; gn++)
		{
			if (group_table[gn].name == NULL)
				break;

			if (!ch->pcdata->group_known[gn] && group_rating(ch, gn) > 0)
			{
				sprintf(buf, "%-18s %-5d ", group_table[gn].name,
						group_rating(ch, gn));
				chprint(ch, buf);
				if (++col % 3 == 0)
					chprintln(ch, "");
			}
		}
		if (col % 3 != 0)
			chprintln(ch, "");

		chprintln(ch, "");

		col = 0;

		chprintlnf(ch, "%-18s %-5s %-18s %-5s %-18s %-5s", "skill",
				   "cost", "skill", "cost", "skill", "cost");

		for (sn = 0; sn < maxSkill; sn++)
		{
			if (skill_table[sn].name == NULL)
				break;

			if (!ch->pcdata->learned[sn] &&
				skill_rating(ch, sn) > 0 &&
				skill_table[sn].spell_fun == spell_null)
			{
				sprintf(buf, "%-18s %-5d ", skill_table[sn].name,
						skill_rating(ch, sn));
				chprint(ch, buf);
				if (++col % 3 == 0)
					chprintln(ch, "");
			}
		}
		if (col % 3 != 0)
			chprintln(ch, "");
		return;
	}

	if (!str_prefix(arg, "convert"))
	{
		if (ch->practice < 10)
		{
			act("$N tells you 'You are not yet ready.'", ch, NULL,
				trainer, TO_CHAR);
			return;
		}

		act("$N helps you apply your practice to training", ch, NULL,
			trainer, TO_CHAR);
		ch->practice -= 10;
		ch->train += 1;
		return;
	}

	if (!str_prefix(arg, "points"))
	{
		if (ch->train < 2)
		{
			act("$N tells you 'You are not yet ready.'", ch, NULL,
				trainer, TO_CHAR);
			return;
		}

		if (ch->pcdata->points <= 40)
		{
			act("$N tells you 'There would be no point in that.'",
				ch, NULL, trainer, TO_CHAR);
			return;
		}

		act("$N trains you, and you feel more at ease with your skills.",
			ch, NULL, trainer, TO_CHAR);

		ch->train -= 2;
		ch->pcdata->points -= 1;
		ch->exp = exp_per_level(ch, ch->pcdata->points) * ch->level;
		return;
	}

	/* else add a group/skill */

	gn = group_lookup(argument);
	if (gn > 0)
	{
		if (ch->pcdata->group_known[gn])
		{
			act("$N tells you 'You already know that group!'", ch,
				NULL, trainer, TO_CHAR);
			return;
		}

		if (group_rating(ch, gn) < 1)
		{
			act("$N tells you 'That group is beyond your powers.'",
				ch, NULL, trainer, TO_CHAR);
			return;
		}

		if (ch->train < group_rating(ch, gn))
		{
			act("$N tells you 'You are not yet ready for that group.'",
				ch, NULL, trainer, TO_CHAR);
			return;
		}

		/* add the group */
		gn_add(ch, gn);
		act("$N trains you in the art of $t", ch, group_table[gn].name,
			trainer, TO_CHAR);
		ch->train -= group_rating(ch, gn);
		return;
	}

	sn = skill_lookup(argument);
	if (sn > -1)
	{
		if (skill_table[sn].spell_fun != spell_null)
		{
			act("$N tells you 'You must learn the full group.'", ch,
				NULL, trainer, TO_CHAR);
			return;
		}

		if (ch->pcdata->learned[sn])
		{
			act("$N tells you 'You already know that skill!'", ch,
				NULL, trainer, TO_CHAR);
			return;
		}

		if (skill_rating(ch, sn) < 1)
		{
			act("$N tells you 'That skill is beyond your powers.'",
				ch, NULL, trainer, TO_CHAR);
			return;
		}

		if (ch->train < skill_rating(ch, sn))
		{
			act("$N tells you 'You are not yet ready for that skill.'",
				ch, NULL, trainer, TO_CHAR);
			return;
		}

		/* add the skill */
		ch->pcdata->learned[sn] = 1;
		act("$N trains you in the art of $t", ch, skill_table[sn].name,
			trainer, TO_CHAR);
		ch->train -= skill_rating(ch, sn);
		return;
	}

	act("$N tells you 'I do not understand...'", ch, NULL, trainer, TO_CHAR);
}

/* RT spells and skills show the players spells (or skills) */

CH_CMD(do_spells)
{
	BUFFER *buffer;
	char arg[MAX_INPUT_LENGTH];
	char spell_list[LEVEL_HERO + 1][MAX_STRING_LENGTH];
	char spell_columns[LEVEL_HERO + 1];
	int sn, level, min_lev = 1, max_lev = LEVEL_HERO, mana;
	bool fAll = FALSE, found = FALSE;
	char buf[MAX_STRING_LENGTH];

	if (IS_NPC(ch))
		return;

	if (argument[0] != '\0')
	{
		fAll = TRUE;

		if (str_prefix(argument, "all"))
		{
			argument = one_argument(argument, arg);
			if (!is_number(arg))
			{
				chprintln(ch, "Arguments must be numerical or all.");
				return;
			}
			max_lev = atoi(arg);

			if (max_lev < 1 || max_lev > LEVEL_HERO)
			{
				chprintlnf(ch, "Levels must be between 1 and %d.", LEVEL_HERO);
				return;
			}

			if (argument[0] != '\0')
			{
				argument = one_argument(argument, arg);
				if (!is_number(arg))
				{
					chprintln(ch, "Arguments must be numerical or all.");
					return;
				}
				min_lev = max_lev;
				max_lev = atoi(arg);

				if (max_lev < 1 || max_lev > LEVEL_HERO)
				{
					chprintlnf(ch,
							   "Levels must be between 1 and %d.", LEVEL_HERO);
					return;
				}

				if (min_lev > max_lev)
				{
					chprintln(ch, "That would be silly.");
					return;
				}
			}
		}
	}

	/* initialize data */
	for (level = 0; level < LEVEL_HERO + 1; level++)
	{
		spell_columns[level] = 0;
		spell_list[level][0] = '\0';
	}

	for (sn = 0; sn < maxSkill; sn++)
	{
		if (skill_table[sn].name == NULL)
			break;

		if ((level = skill_level(ch, sn)) < LEVEL_HERO + 1 &&
			(fAll || level <= ch->level) && level >= min_lev &&
			level <= max_lev && skill_table[sn].spell_fun != spell_null &&
			ch->pcdata->learned[sn] > 0)
		{
			found = TRUE;
			level = skill_level(ch, sn);
			if (ch->level < level)
				sprintf(buf, "%-18s n/a      ", skill_table[sn].name);
			else
			{
				mana =
					UMAX(skill_table[sn].min_mana,
						 100 / (2 + ch->level - level));
				sprintf(buf, "%-18s  %3d mana  ", skill_table[sn].name, mana);
			}

			if (spell_list[level][0] == '\0')
				sprintf(spell_list[level], "\n\rLevel %2d: %s", level, buf);
			else				/* append */
			{
				if (++spell_columns[level] % 2 == 0)
					strcat(spell_list[level], "\n\r          ");
				strcat(spell_list[level], buf);
			}
		}
	}

	/* return results */

	if (!found)
	{
		chprintln(ch, "No spells found.");
		return;
	}

	buffer = new_buf();
	for (level = 0; level < LEVEL_HERO + 1; level++)
		if (spell_list[level][0] != '\0')
			add_buf(buffer, spell_list[level]);
	add_buf(buffer, "\n\r");
	page_to_char(buf_string(buffer), ch);
	free_buf(buffer);
}

CH_CMD(do_skills)
{
	BUFFER *buffer;
	char arg[MAX_INPUT_LENGTH];
	char skill_list[LEVEL_HERO + 1][MAX_STRING_LENGTH];
	char skill_columns[LEVEL_HERO + 1];
	int sn, level, min_lev = 1, max_lev = LEVEL_HERO;
	bool fAll = FALSE, found = FALSE;
	char buf[MAX_STRING_LENGTH];

	if (IS_NPC(ch))
		return;

	if (argument[0] != '\0')
	{
		fAll = TRUE;

		if (str_prefix(argument, "all"))
		{
			argument = one_argument(argument, arg);
			if (!is_number(arg))
			{
				chprintln(ch, "Arguments must be numerical or all.");
				return;
			}
			max_lev = atoi(arg);

			if (max_lev < 1 || max_lev > LEVEL_HERO)
			{
				chprintlnf(ch, "Levels must be between 1 and %d.", LEVEL_HERO);
				return;
			}

			if (argument[0] != '\0')
			{
				argument = one_argument(argument, arg);
				if (!is_number(arg))
				{
					chprintln(ch, "Arguments must be numerical or all.");
					return;
				}
				min_lev = max_lev;
				max_lev = atoi(arg);

				if (max_lev < 1 || max_lev > LEVEL_HERO)
				{
					chprintlnf(ch,
							   "Levels must be between 1 and %d.", LEVEL_HERO);
					return;
				}

				if (min_lev > max_lev)
				{
					chprintln(ch, "That would be silly.");
					return;
				}
			}
		}
	}

	/* initialize data */
	for (level = 0; level < LEVEL_HERO + 1; level++)
	{
		skill_columns[level] = 0;
		skill_list[level][0] = '\0';
	}

	for (sn = 0; sn < maxSkill; sn++)
	{
		if (skill_table[sn].name == NULL)
			break;

		if ((level = skill_level(ch, sn)) < LEVEL_HERO + 1 &&
			(fAll || level <= ch->level) && level >= min_lev &&
			level <= max_lev && skill_table[sn].spell_fun == spell_null &&
			ch->pcdata->learned[sn] > 0)
		{
			found = TRUE;
			level = skill_level(ch, sn);
			if (ch->level < level)
				sprintf(buf, "%-18s n/a      ", skill_table[sn].name);
			else
				sprintf(buf, "%-18s %3d%%      ",
						skill_table[sn].name, ch->pcdata->learned[sn]);

			if (skill_list[level][0] == '\0')
				sprintf(skill_list[level], "\n\rLevel %2d: %s", level, buf);
			else				/* append */
			{
				if (++skill_columns[level] % 2 == 0)
					strcat(skill_list[level], "\n\r          ");
				strcat(skill_list[level], buf);
			}
		}
	}

	/* return results */

	if (!found)
	{
		chprintln(ch, "No skills found.");
		return;
	}

	buffer = new_buf();
	for (level = 0; level < LEVEL_HERO + 1; level++)
		if (skill_list[level][0] != '\0')
			add_buf(buffer, skill_list[level]);
	add_buf(buffer, "\n\r");
	page_to_char(buf_string(buffer), ch);
	free_buf(buffer);
}

/* shows skills, groups and costs (only if not bought) */
void list_group_costs(CHAR_DATA * ch)
{
	char buf[100];
	int gn, sn, col;

	if (IS_NPC(ch))
		return;

	col = 0;

	chprintlnf(ch, "%-18s %-5s %-18s %-5s %-18s %-5s", "group", "cp",
			   "group", "cp", "group", "cp");

	for (gn = 0; gn < maxGroup; gn++)
	{
		if (group_table[gn].name == NULL)
			break;

		if (!ch->gen_data->group_chosen[gn]
			&& !ch->pcdata->group_known[gn] && group_rating(ch, gn) > 0)
		{
			sprintf(buf, "%-18s %-5d ", group_table[gn].name,
					group_rating(ch, gn));
			chprint(ch, buf);
			if (++col % 3 == 0)
				chprintln(ch, "");
		}
	}
	if (col % 3 != 0)
		chprintln(ch, "");
	chprintln(ch, "");

	col = 0;

	chprintlnf(ch, "%-18s %-5s %-18s %-5s %-18s %-5s", "skill", "cp",
			   "skill", "cp", "skill", "cp");

	for (sn = 0; sn < maxSkill; sn++)
	{
		if (skill_table[sn].name == NULL)
			break;

		if (!ch->gen_data->skill_chosen[sn]
			&& ch->pcdata->learned[sn] == 0
			&& skill_table[sn].spell_fun == spell_null
			&& skill_rating(ch, sn) > 0)
		{
			sprintf(buf, "%-18s %-5d ", skill_table[sn].name,
					skill_rating(ch, sn));
			chprint(ch, buf);
			if (++col % 3 == 0)
				chprintln(ch, "");
		}
	}
	if (col % 3 != 0)
		chprintln(ch, "");
	chprintln(ch, "");

	chprintlnf(ch, "Creation points: %d", ch->pcdata->points);
	chprintlnf(ch, "Experience per level: %d",
			   exp_per_level(ch, ch->gen_data->points_chosen));
	return;
}

void list_group_chosen(CHAR_DATA * ch);

void list_group_chosen(CHAR_DATA * ch)
{
	char buf[100];
	int gn, sn, col;

	if (IS_NPC(ch))
		return;

	col = 0;

	chprintlnf(ch, "%-18s %-5s %-18s %-5s %-18s %-5s", "group", "cp",
			   "group", "cp", "group", "cp");

	for (gn = 0; gn < maxGroup; gn++)
	{
		if (group_table[gn].name == NULL)
			break;

		if (ch->gen_data->group_chosen[gn] && group_rating(ch, gn) > 0)
		{
			sprintf(buf, "%-18s %-5d ", group_table[gn].name,
					group_rating(ch, gn));
			chprint(ch, buf);
			if (++col % 3 == 0)
				chprintln(ch, "");
		}
	}
	if (col % 3 != 0)
		chprintln(ch, "");
	chprintln(ch, "");

	col = 0;

	chprintlnf(ch, "%-18s %-5s %-18s %-5s %-18s %-5s", "skill", "cp",
			   "skill", "cp", "skill", "cp");

	for (sn = 0; sn < maxSkill; sn++)
	{
		if (skill_table[sn].name == NULL)
			break;

		if (ch->gen_data->skill_chosen[sn] && skill_rating(ch, sn) > 0)
		{
			sprintf(buf, "%-18s %-5d ", skill_table[sn].name,
					skill_rating(ch, sn));
			chprint(ch, buf);
			if (++col % 3 == 0)
				chprintln(ch, "");
		}
	}
	if (col % 3 != 0)
		chprintln(ch, "");
	chprintln(ch, "");

	chprintlnf(ch, "Creation points: %d", ch->gen_data->points_chosen);
	chprintlnf(ch, "Experience per level: %d",
			   exp_per_level(ch, ch->gen_data->points_chosen));
	return;
}

int exp_per_level(CHAR_DATA * ch, int points)
{
	int expl, inc, mult;

	if (IS_NPC(ch))
		return 1000;

	expl = 1000;
	inc = 500;
	mult = class_mult(ch);

	if (points < 40)
		return 1000 * (mult ? mult / 100 : 1);

	/* processing */
	points -= 40;

	while (points > 9)
	{
		expl += inc;
		points -= 10;
		if (points > 9)
		{
			expl += inc;
			inc *= 2;
			points -= 10;
		}
	}

	expl += points * inc / 10;

	return expl * mult / 100;
}

/* this procedure handles the input parsing for the skill generator */
bool parse_gen_groups(CHAR_DATA * ch, const char *argument)
{
	char arg[MAX_INPUT_LENGTH];
	int gn, sn, i;

	if (argument[0] == '\0')
		return FALSE;

	argument = one_argument(argument, arg);

	if (!str_prefix(arg, "help"))
	{
		if (argument[0] == '\0')
		{
			do_function(ch, &do_oldhelp, "group help");
			return TRUE;
		}

		do_function(ch, &do_oldhelp, argument);
		return TRUE;
	}

	if (!str_prefix(arg, "add"))
	{
		if (argument[0] == '\0')
		{
			chprintln(ch, "You must provide a skill name.");
			return TRUE;
		}

		gn = group_lookup(argument);
		if (gn != -1)
		{
			if (ch->gen_data->group_chosen[gn] || ch->pcdata->group_known[gn])
			{
				chprintln(ch, "You already know that group!");
				return TRUE;
			}

			if (group_rating(ch, gn) < 1)
			{
				chprintln(ch, "That group is not available.");
				return TRUE;
			}

			/* Close security hole */
			if (ch->gen_data->points_chosen + group_rating(ch, gn) > 300)
			{
				chprintln(ch, "You cannot take more than 300 creation points.");
				return TRUE;
			}

			chprintlnf(ch, "%s group added.", group_table[gn].name);
			ch->gen_data->group_chosen[gn] = TRUE;
			ch->gen_data->points_chosen += group_rating(ch, gn);
			gn_add(ch, gn);
			ch->pcdata->points += group_rating(ch, gn);
			return TRUE;
		}

		sn = skill_lookup(argument);
		if (sn != -1)
		{
			if (ch->gen_data->skill_chosen[sn] || ch->pcdata->learned[sn] > 0)
			{
				chprintln(ch, "You already know that skill!");
				return TRUE;
			}

			if (skill_rating(ch, sn) < 1 ||
				skill_table[sn].spell_fun != spell_null)
			{
				chprintln(ch, "That skill is not available.");
				return TRUE;
			}

			/* Close security hole */
			if (ch->gen_data->points_chosen + skill_rating(ch, sn) > 300)
			{
				chprintln(ch, "You cannot take more than 300 creation points.");
				return TRUE;
			}
			chprintlnf(ch, "%s skill added.", skill_table[sn].name);
			ch->gen_data->skill_chosen[sn] = TRUE;
			ch->gen_data->points_chosen += skill_rating(ch, sn);
			ch->pcdata->learned[sn] = 1;
			ch->pcdata->points += skill_rating(ch, sn);
			return TRUE;
		}

		chprintln(ch, "No skills or groups by that name...");
		return TRUE;
	}

	if (!strcmp(arg, "drop"))
	{
		if (argument[0] == '\0')
		{
			chprintln(ch, "You must provide a skill to drop.");
			return TRUE;
		}

		gn = group_lookup(argument);
		if (gn != -1 && ch->gen_data->group_chosen[gn])
		{
			chprintln(ch, "Group dropped.");
			ch->gen_data->group_chosen[gn] = FALSE;
			ch->gen_data->points_chosen -= group_rating(ch, gn);
			gn_remove(ch, gn);
			for (i = 0; i < maxGroup; i++)
			{
				if (ch->gen_data->group_chosen[gn])
					gn_add(ch, gn);
			}
			ch->pcdata->points -= group_rating(ch, gn);
			return TRUE;
		}

		sn = skill_lookup(argument);
		if (sn != -1 && ch->gen_data->skill_chosen[sn])
		{
			chprintln(ch, "Skill dropped.");
			ch->gen_data->skill_chosen[sn] = FALSE;
			ch->gen_data->points_chosen -= skill_rating(ch, sn);
			ch->pcdata->learned[sn] = 0;
			ch->pcdata->points -= skill_rating(ch, sn);
			return TRUE;
		}

		chprintln(ch, "You haven't bought any such skill or group.");
		return TRUE;
	}

	if (!str_prefix(arg, "premise"))
	{
		do_function(ch, &do_oldhelp, "premise");
		return TRUE;
	}

	if (!str_prefix(arg, "list"))
	{
		list_group_costs(ch);
		return TRUE;
	}

	if (!str_prefix(arg, "learned"))
	{
		list_group_chosen(ch);
		return TRUE;
	}

	if (!str_prefix(arg, "info"))
	{
		do_function(ch, &do_groups, argument);
		return TRUE;
	}

	return FALSE;
}

/* shows all groups, or the sub-members of a group */
CH_CMD(do_groups)
{
	char buf[100];
	int gn, sn, col;

	if (IS_NPC(ch))
		return;

	col = 0;

	if (argument[0] == '\0')
	{							/* show all groups */

		for (gn = 0; gn < maxGroup; gn++)
		{
			if (group_table[gn].name == NULL)
				break;
			if (ch->pcdata->group_known[gn])
			{
				sprintf(buf, "%-20s ", group_table[gn].name);
				chprint(ch, buf);
				if (++col % 3 == 0)
					chprintln(ch, "");
			}
		}
		if (col % 3 != 0)
			chprintln(ch, "");
		chprintlnf(ch, "Creation points: %d", ch->pcdata->points);
		return;
	}

	if (!str_cmp(argument, "all"))	/* show all groups */
	{
		for (gn = 0; gn < maxGroup; gn++)
		{
			if (group_table[gn].name == NULL)
				break;
			sprintf(buf, "%-20s ", group_table[gn].name);
			chprint(ch, buf);
			if (++col % 3 == 0)
				chprintln(ch, "");
		}
		if (col % 3 != 0)
			chprintln(ch, "");
		return;
	}

	/* show the sub-members of a group */
	gn = group_lookup(argument);
	if (gn == -1)
	{
		chprintln(ch, "No group of that name exist.");
		chprintln(ch, "Type 'groups all' or 'info all' for a full listing.");
		return;
	}

	for (sn = 0; sn < MAX_IN_GROUP; sn++)
	{
		if (group_table[gn].spells[sn] == NULL)
			break;
		sprintf(buf, "%-20s ", group_table[gn].spells[sn]);
		chprint(ch, buf);
		if (++col % 3 == 0)
			chprintln(ch, "");
	}
	if (col % 3 != 0)
		chprintln(ch, "");
}

/* checks for skill improvement */
void check_improve(CHAR_DATA * ch, int sn, bool success, int multiplier)
{
	int chance;

	if (IS_NPC(ch))
		return;

	if (!can_use_skpell(ch, sn)
		|| skill_rating(ch, sn) < 1 ||
		ch->pcdata->learned[sn] == 0 || ch->pcdata->learned[sn] == 100)
		return;					/* skill is not known */

	/* check to see if the character has a chance to learn */
	chance = 10 * int_app[get_curr_stat(ch, STAT_INT)].learn;
	chance /= (multiplier * skill_rating(ch, sn) * 4);
	chance += ch->level;

	if (number_range(1, 1000) > chance)
		return;

	/* now that the character has a CHANCE to learn, see if they really have */

	if (success)
	{
		chance = URANGE(5, 100 - ch->pcdata->learned[sn], 95);
		if (number_percent() < chance)
		{
			chprintlnf(ch, "You have become better at %s!",
					   skill_table[sn].name);
			ch->pcdata->learned[sn]++;
			gain_exp(ch, 2 * skill_rating(ch, sn));
		}
	}

	else
	{
		chance = URANGE(5, ch->pcdata->learned[sn] / 2, 30);
		if (number_percent() < chance)
		{
			chprintlnf(ch,
					   "You learn from your mistakes, and your %s skill improves.",
					   skill_table[sn].name);
			ch->pcdata->learned[sn] += number_range(1, 3);
			ch->pcdata->learned[sn] = UMIN(ch->pcdata->learned[sn], 100);
			gain_exp(ch, 2 * skill_rating(ch, sn));
		}
	}
}

/* returns a group index number given the name */
int group_lookup(const char *name)
{
	int gn;

	for (gn = 0; gn < maxGroup; gn++)
	{
		if (group_table[gn].name == NULL)
			break;
		if (LOWER(name[0]) == LOWER(group_table[gn].name[0]) &&
			!str_prefix(name, group_table[gn].name))
			return gn;
	}

	return -1;
}

/* recursively adds a group given its number -- uses group_add */
void gn_add(CHAR_DATA * ch, int gn)
{
	int i;

	ch->pcdata->group_known[gn] = TRUE;
	for (i = 0; i < MAX_IN_GROUP; i++)
	{
		if (group_table[gn].spells[i] == NULL)
			break;
		group_add(ch, group_table[gn].spells[i], FALSE);
	}
}

/* recusively removes a group given its number -- uses group_remove */
void gn_remove(CHAR_DATA * ch, int gn)
{
	int i;

	ch->pcdata->group_known[gn] = FALSE;

	for (i = 0; i < MAX_IN_GROUP; i++)
	{
		if (group_table[gn].spells[i] == NULL)
			break;
		group_remove(ch, group_table[gn].spells[i]);
	}
}

/* use for processing a skill or group for addition  */
void group_add(CHAR_DATA * ch, const char *name, bool deduct)
{
	int sn, gn;

	if (IS_NPC(ch))				/* NPCs do not have skills */
		return;

	sn = skill_lookup(name);

	if (sn != -1)
	{
		if (ch->pcdata->learned[sn] == 0)	/* i.e. not known */
		{
			ch->pcdata->learned[sn] = 1;
			if (deduct)
				ch->pcdata->points += skill_rating(ch, sn);
		}
		return;
	}

	/* now check groups */

	gn = group_lookup(name);

	if (gn != -1)
	{
		if (ch->pcdata->group_known[gn] == FALSE)
		{
			ch->pcdata->group_known[gn] = TRUE;
			if (deduct)
				ch->pcdata->points += group_rating(ch, gn);
		}
		gn_add(ch, gn);			/* make sure all skills in the group are known */
	}
}

/* used for processing a skill or group for deletion -- no points back! */

void group_remove(CHAR_DATA * ch, const char *name)
{
	int sn, gn;

	sn = skill_lookup(name);

	if (sn != -1)
	{
		ch->pcdata->learned[sn] = 0;
		return;
	}

	/* now check groups */

	gn = group_lookup(name);

	if (gn != -1 && ch->pcdata->group_known[gn] == TRUE)
	{
		ch->pcdata->group_known[gn] = FALSE;
		gn_remove(ch, gn);		/* be sure to call gn_add on all remaining groups */
	}
}

CH_CMD(do_slist)
{
	char buf[MSL];
	char buf2[MSL];
	char buf3[MSL];
	BUFFER *buffer;
	char skill_list[LEVEL_HERO + 1][MSL];
	char skill_columns[LEVEL_HERO + 1];
	int sn, snc, level;
	int x;
	int clas;
	bool found = FALSE;

	buf[0] = '\0';
	buf2[0] = '\0';
	buf3[0] = '\0';

	if (IS_NULLSTR(argument))
	{
		chprintln(ch, "Syntax: slist <skill/spell/class>");
		return;
	}

	if ((clas = class_lookup(argument)) > -1)
	{
		for (level = 0; level < LEVEL_HERO + 1; level++)
		{
			skill_columns[level] = 0;
			skill_list[level][0] = '\0';
		}

		for (snc = 0; snc < maxSkill; snc++)
		{
			if (skill_table[snc].name == NULL)
				break;

			if ((level = skill_table[snc].skill_level[clas]) <= LEVEL_HERO)
			{
				found = TRUE;
				level = skill_table[snc].skill_level[clas];
				sprintf(buf3, "{W%-18s      ", skill_table[snc].name);

				if (IS_NULLSTR(skill_list[level]))
					sprintf(skill_list[level], "\n\r{cLevel %3d: %s{x",
							level, buf3);
				else
				{

					if (++skill_columns[level] % 2 == 0)
						strcat(skill_list[level], "\n\r           ");

					strcat(skill_list[level], buf3);
				}

			}

		}

		buffer = new_buf();
		for (level = 0; level < LEVEL_HERO + 1; level++)
		{
			if (!IS_NULLSTR(skill_list[level]))
				add_buf(buffer, skill_list[level]);
		}
		add_buf(buffer, "\n\r");
		page_to_char(buf_string(buffer), ch);
		free_buf(buffer);
		return;
	}
	else if ((sn = skill_lookup(argument)) != -1)
	{
		strcat(buf, "\n\r{W");
		strcat(buf, capitalize(skill_table[sn].name));
		strcat(buf, "{m: [");
		for (x = 0; x < maxClass; x++)
		{
			if (skill_table[sn].skill_level[x] > LEVEL_HERO)
				sprintf(buf2, "{W%s: {c%s  ", class_table[x].who_name, "n/a");
			else
				sprintf(buf2, "{W%s: {c%03d  ", class_table[x].who_name,
						skill_table[sn].skill_level[x]);
			strcat(buf, buf2);
		}
		strcat(buf, "{m]{x");
		chprintln(ch, buf);
		return;
	}
	else
	{
		do_function(ch, &do_slist, "");
		return;
	}
}