1stMud/CVS/
1stMud/area/CVS/
1stMud/backup/CVS/
1stMud/bin/
1stMud/bin/CVS/
1stMud/bin/extras/
1stMud/bin/extras/CVS/
1stMud/data/CVS/
1stMud/data/i3/CVS/
1stMud/doc/1stMud/
1stMud/doc/1stMud/CVS/
1stMud/doc/CVS/
1stMud/doc/Diku/
1stMud/doc/Diku/CVS/
1stMud/doc/MPDocs/CVS/
1stMud/doc/Merc/CVS/
1stMud/doc/Rom/
1stMud/doc/Rom/CVS/
1stMud/log/CVS/
1stMud/notes/
1stMud/notes/CVS/
1stMud/player/CVS/
1stMud/player/backup/CVS/
1stMud/player/deleted/CVS/
1stMud/src/CVS/
1stMud/src/config/CVS/
1stMud/src/h/CVS/
1stMud/src/o/CVS/
1stMud/win/CVS/
/**************************************************************************
*  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-2004 by Markanth                *
*            http://www.firstmud.com/  <markanth@firstmud.com>            *
*         By using this code you have agreed to follow the term of        *
*             the 1stMud license in ../doc/1stMud/LICENSE                 *
***************************************************************************/


#include "merc.h"
#include "interp.h"
#include "magic.h"
#include "recycle.h"


Do_Fun (do_gain)
{
  char arg[MAX_INPUT_LENGTH];
  CharData *trainer;
  int gn = 0, sn = 0, val;
  int mult;

  if (IsNPC (ch))
    return;


  for (trainer = ch->in_room->person_first; trainer != NULL;
       trainer = trainer->next_in_room)
    if (IsNPC (trainer) && IsSet (trainer->act, ACT_GAIN))
      break;

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

  mult = mult_argument (argument, arg);

  if (NullStr (arg))
    {
      do_function (trainer, &do_say, "Pardon me?");
      return;
    }

  if (!str_prefix (arg, "list"))
    {
      Buffer *output;
      Column *Cd;

      Cd = new_column ();
      output = new_buf ();

      set_cols (Cd, ch, 3, COLS_BUF, output);

      cols_header (Cd, "{w%-18s Cost ", "Group");

      for (gn = 0; gn < top_group; gn++)
	{
	  if (!ch->pcdata->group_known[gn]
	      && (val = group_rating (ch, gn)) > 0)
	    {
	      print_cols (Cd, "%-18s %-4d ", group_table[gn].name, val);
	    }
	}
      cols_nl (Cd);

      bprintln (output, NULL);

      set_cols (Cd, ch, 3, COLS_BUF, output);

      cols_header (Cd, "{w%-15s %-4s Lev ", "Skill", "Cost");

      for (sn = 0; sn < top_skill; sn++)
	{
	  if (!ch->pcdata->learned[sn] &&
	      (val = skill_rating (ch, sn)) > 0 &&
	      skill_table[sn].spell_fun == spell_null)
	    {
	      print_cols (Cd, "%-16s %-3d %-3d ",
			  skill_table[sn].name, val, skill_level (ch, sn));
	    }
	}
      cols_nl (Cd);
      bprintlnf (output, "You have %s left.", intstr (ch->train, "train"));
      bprintlnf (output,
		 "Use 'grlist' to see what skills are within a group.");

      sendpage (ch, buf_string (output));
      free_buf (output);
      free_column (Cd);
      return;
    }

  if (!str_prefix (arg, "convert"))
    {
      if (ch->practice < 10 * mult)
	{
	  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 * mult;
      ch->train += mult;
      return;
    }

  if (!str_prefix (arg, "points"))
    {
      int xp;

      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;
      xp = ch->exp - (ch->level * exp_per_level (ch, ch->pcdata->points));
      ch->pcdata->points -= 1;
      ch->exp = exp_per_level (ch, ch->pcdata->points) * ch->level;
      ch->exp += xp;
      return;
    }



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

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

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


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

  sn = skill_lookup (argument);
  if (sn > -1 && sn < top_skill)
    {
      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 ((val = 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;
	}


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

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



Do_Fun (do_spells)
{
  Buffer *buffer;
  char arg[MAX_INPUT_LENGTH];
  char spell_list[MAX_MORTAL_LEVEL + 1][MAX_STRING_LENGTH];
  char spell_columns[MAX_MORTAL_LEVEL + 1];
  int sn, level, min_lev = 1, max_lev = MAX_MORTAL_LEVEL, mana;
  bool fAll = false, found = false;
  char buf[MAX_STRING_LENGTH];

  if (IsNPC (ch))
    return;

  if (!NullStr (argument))
    {
      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 > MAX_MORTAL_LEVEL)
	    {
	      chprintlnf (ch, "Levels must be between 1 and %d.",
			  MAX_MORTAL_LEVEL);
	      return;
	    }

	  if (!NullStr (argument))
	    {
	      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 > MAX_MORTAL_LEVEL)
		{
		  chprintlnf (ch,
			      "Levels must be between 1 and %d.",
			      MAX_MORTAL_LEVEL);
		  return;
		}

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


  for (level = 0; level < MAX_MORTAL_LEVEL + 1; level++)
    {
      spell_columns[level] = 0;
      spell_list[level][0] = '\0';
    }

  for (sn = 0; sn < top_skill; sn++)
    {
      if ((level = skill_level (ch, sn)) < MAX_MORTAL_LEVEL + 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 =
		Max (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], NEWLINE "Level %2d: %s", level, buf);
	  else
	    {

	      if (++spell_columns[level] % 2 == 0)
		strcat (spell_list[level], NEWLINE "          ");
	      strcat (spell_list[level], buf);
	    }
	}
    }



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

  buffer = new_buf ();
  for (level = 0; level < MAX_MORTAL_LEVEL + 1; level++)
    if (spell_list[level][0] != '\0')
      bprint (buffer, spell_list[level]);
  bprintln (buffer, NULL);
  sendpage (ch, buf_string (buffer));
  free_buf (buffer);
}

Do_Fun (do_skills)
{
  Buffer *buffer;
  char arg[MAX_INPUT_LENGTH];
  char skill_list[MAX_MORTAL_LEVEL + 1][MAX_STRING_LENGTH];
  char skill_columns[MAX_MORTAL_LEVEL + 1];
  int sn, level, min_lev = 1, max_lev = MAX_MORTAL_LEVEL;
  bool fAll = false, found = false;
  char buf[MAX_STRING_LENGTH];

  if (IsNPC (ch))
    return;

  if (!NullStr (argument))
    {
      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 > MAX_MORTAL_LEVEL)
	    {
	      chprintlnf (ch, "Levels must be between 1 and %d.",
			  MAX_MORTAL_LEVEL);
	      return;
	    }

	  if (!NullStr (argument))
	    {
	      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 > MAX_MORTAL_LEVEL)
		{
		  chprintlnf (ch,
			      "Levels must be between 1 and %d.",
			      MAX_MORTAL_LEVEL);
		  return;
		}

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


  for (level = 0; level < MAX_MORTAL_LEVEL + 1; level++)
    {
      skill_columns[level] = 0;
      skill_list[level][0] = '\0';
    }

  for (sn = 0; sn < top_skill; sn++)
    {
      if ((level = skill_level (ch, sn)) < MAX_MORTAL_LEVEL + 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, "{c%-18s n/a      ", skill_table[sn].name);
	  else
	    sprintf (buf, "{c%-18s {W%3d%%      ",
		     skill_table[sn].name, ch->pcdata->learned[sn]);

	  if (skill_list[level][0] == '\0')
	    sprintf (skill_list[level], NEWLINE "{cLevel {W%2d{c: %s{x",
		     level, buf);
	  else
	    {

	      if (++skill_columns[level] % 2 == 0)
		strcat (skill_list[level], NEWLINE "{x          ");
	      strcat (skill_list[level], buf);
	    }
	}
    }



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

  buffer = new_buf ();
  for (level = 0; level < MAX_MORTAL_LEVEL + 1; level++)
    if (skill_list[level][0] != '\0')
      bprint (buffer, skill_list[level]);
  bprintln (buffer, NULL);
  sendpage (ch, buf_string (buffer));
  free_buf (buffer);
}


void
list_group_costs (CharData * ch)
{
  int gn, sn, val;
  Buffer *output;
  Column *Cd;

  if (IsNPC (ch))
    return;

  output = new_buf ();
  Cd = new_column ();
  set_cols (Cd, ch, 3, COLS_BUF, output);

  bprintlnf (output, "{c%s", draw_line (ch, NULL, 0));

  cols_header (Cd, "{w%-18s CP ", "Group");

  for (gn = 0; gn < top_group; gn++)
    {
      if (!ch->gen_data->group_chosen[gn]
	  && !ch->pcdata->group_known[gn]
	  && (val = group_rating (ch, gn)) > 0)
	{
	  print_cols (Cd, "{c%-18s {W%-2d ", group_table[gn].name, val);
	}
    }
  cols_nl (Cd);

  bprintlnf (output, "{c%s", draw_line (ch, NULL, 0));

  set_cols (Cd, ch, 2, COLS_BUF, output);

  cols_header (Cd, "{w%-18s %-2s Lev ", "Skill", "CP");

  for (sn = 0; sn < top_skill; sn++)
    {
      if (!ch->gen_data->skill_chosen[sn]
	  && ch->pcdata->learned[sn] == 0
	  && skill_table[sn].spell_fun == spell_null
	  && (val = skill_rating (ch, sn)) > 0)
	{
	  print_cols (Cd, "{c%-18s {W%-2d %-3d ",
		      skill_table[sn].name, val, skill_level (ch, sn));
	}
    }
  cols_nl (Cd);

  bprintlnf (output, "{c%s", draw_line (ch, NULL, 0));

  bprintln (output, NULL);

  bprintlnf (output, "{cCreation points: {W%d{x", ch->pcdata->points);
  bprintlnf (output, "{cExperience per level: {W%d{x",
	     exp_per_level (ch,
			    Max (ch->gen_data->points_chosen,
				 ch->pcdata->points)));
  sendpage (ch, buf_string (output));
  free_buf (output);
  free_column (Cd);
  return;
}

void
list_group_chosen (CharData * ch)
{
  int gn, sn, val;
  Buffer *output;
  Column *Cd;

  if (IsNPC (ch))
    return;

  output = new_buf ();

  Cd = new_column ();
  set_cols (Cd, ch, 3, COLS_BUF, output);


  bprintlnf (output, "{c%s", draw_line (ch, NULL, 0));

  print_cols (Cd, "{w%-18s CP ", "Group");

  for (gn = 0; gn < top_group; gn++)
    {
      if (ch->gen_data->group_chosen[gn] && (val = group_rating (ch, gn)) > 0)
	{
	  print_cols (Cd, "{c%-18s {W%-2d ", group_table[gn].name, val);
	}
    }
  cols_nl (Cd);

  bprintlnf (output, "{c%s", draw_line (ch, NULL, 0));

  set_cols (Cd, ch, 2, COLS_BUF, output);

  cols_header (Cd, "{w%-18s %-2s Lev", "Skill", "CP");

  for (sn = 0; sn < top_skill; sn++)
    {
      if (ch->gen_data->skill_chosen[sn] && (val = skill_rating (ch, sn)) > 0)
	{
	  print_cols (Cd, "{c%-18s {W%-2d %-3d ", skill_table[sn].name,
		      val, skill_level (ch, sn));
	}
    }
  cols_nl (Cd);

  bprintlnf (output, "{c%s", draw_line (ch, NULL, 0));

  bprintlnf (output, "{cCreation points: {W%d{x",
	     ch->gen_data->points_chosen);
  bprintlnf (output, "{cExperience per level: {W%d{x",
	     exp_per_level (ch, ch->gen_data->points_chosen));
  sendpage (ch, buf_string (output));
  free_buf (output);
  free_column (Cd);
  return;
}

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

  if (IsNPC (ch))
    return 1000;

  expl = 1000;
  inc = 500;

  if (points >= mud_info.max_points)
    {

      points -= mud_info.max_points;

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

      expl += points * inc / 10;
    }

  expl *= class_mult (ch);
  expl /= 100;

  return expl;
}


bool
parse_gen_groups (CharData * ch, const char *argument)
{
  char arg[MAX_INPUT_LENGTH];
  int gn, sn, i;

  if (NullStr (argument))
    return false;

  argument = one_argument (argument, arg);

  if (!str_prefix (arg, "help"))
    {
      if (NullStr (argument))
	{
	  chprintln (ch, "{cThe following commands are available:{x");
	  chprintln (ch,
		     "{clist          display all groups and skills not yet bought{x");
	  chprintln (ch,
		     "{clearned       show all groups and skills bought {x");
	  chprintln (ch,
		     "{cpremise       brief explanation of creation points and skill groups{x");
	  chprintln (ch, "{cadd <name>    buy a skill or group{x");
	  chprintln (ch, "{cdrop <name>   discard a skill or group{x");
	  chprintln (ch,
		     "{cinfo <name>   list the skills or spells contained within a group{x");
	  chprintln (ch, "{clookup <name> show class levels for skill{x");
	  chprintln (ch,
		     "{chelp <name>   help on skills and groups, or other help topics{x");
	  chprintln (ch,
		     "{cdone          exit the character generation process{x");
	  return true;
	}

      chprint (ch, "{c");
      do_function (ch, &do_oldhelp, argument);
      chprint (ch, "{x");

      return true;
    }

  if (!str_prefix (arg, "add"))
    {
      int val;

      if (NullStr (argument))
	{
	  chprintln (ch, "{cYou must provide a skill name.{x");
	  return true;
	}

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

	  if ((val = group_rating (ch, gn)) < 1)
	    {
	      chprintln (ch, "{cThat group is not available.{x");
	      return true;
	    }


	  if (ch->gen_data->points_chosen + val > 300)
	    {
	      chprintln (ch,
			 "{cYou cannot take more than 300 creation points.{x");
	      return true;
	    }

	  chprintlnf (ch, "{W%s{c group added.{x",
		      Upper (group_table[gn].name));
	  ch->gen_data->group_chosen[gn] = true;
	  ch->gen_data->points_chosen += val;
	  gn_add (ch, gn);
	  ch->pcdata->points += val;
	  return true;
	}

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

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


	  if (ch->gen_data->points_chosen + val > 300)
	    {
	      chprintln (ch,
			 "{cYou cannot take more than 300 creation points.{x");
	      return true;
	    }
	  chprintlnf (ch, "{W%s{c skill added.{x",
		      Upper (skill_table[sn].name));
	  ch->gen_data->skill_chosen[sn] = true;
	  ch->gen_data->points_chosen += val;
	  ch->pcdata->learned[sn] = 1;
	  ch->pcdata->points += val;
	  return true;
	}

      chprintln (ch, "{cNo skills or groups by that name...{x");
      return true;
    }

  if (!str_cmp (arg, "drop"))
    {
      int val;

      if (NullStr (argument))
	{
	  chprintln (ch, "{cYou must provide a skill to drop.{x");
	  return true;
	}

      gn = group_lookup (argument);
      if (gn != -1 && ch->gen_data->group_chosen[gn])
	{
	  chprintlnf (ch, "{W%s{c group dropped.",
		      Upper (group_table[gn].name));
	  ch->gen_data->group_chosen[gn] = false;
	  val = group_rating (ch, gn);
	  ch->gen_data->points_chosen -= val;
	  gn_remove (ch, gn);
	  for (i = 0; i < top_group; i++)
	    {
	      if (ch->gen_data->group_chosen[gn])
		gn_add (ch, gn);
	    }
	  ch->pcdata->points -= val;
	  return true;
	}

      sn = skill_lookup (argument);
      if (sn != -1 && ch->gen_data->skill_chosen[sn])
	{
	  chprintlnf (ch, "{W%s{c skill dropped.",
		      Upper (skill_table[sn].name));
	  ch->gen_data->skill_chosen[sn] = false;
	  val = skill_rating (ch, sn);
	  ch->gen_data->points_chosen -= val;
	  ch->pcdata->learned[sn] = 0;
	  ch->pcdata->points -= val;
	  return true;
	}

      chprintln (ch, "{cYou haven't bought any such skill or group.{x");
      return true;
    }

  if (!str_prefix (arg, "premise"))
    {
      chprint (ch, "{c");
      do_function (ch, &do_oldhelp, "premise");
      chprint (ch, "{x");
      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_grlist ("info", ch, argument);
      return true;
    }
  if (!str_prefix (arg, "lookup"))
    {
      do_slist ("lookup", ch, argument);
      return true;
    }
  return false;
}


void
check_improve (CharData * ch, int sn, bool success, int multiplier)
{
  int chance;

  if (IsNPC (ch))
    return;

  if (!can_use_skpell (ch, sn)
      || skill_rating (ch, sn) < 1 ||
      ch->pcdata->learned[sn] == 0 || ch->pcdata->learned[sn] == 100)
    return;


  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;



  if (success)
    {
      chance = Range (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 = Range (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] = Min (ch->pcdata->learned[sn], 100);
	  gain_exp (ch, 2 * skill_rating (ch, sn));
	}
    }
  if (ch->pcdata->learned[sn] == 100)
    {
      chprintlnf (ch, "{GYou have now mastered the '%s' %s!",
		  skill_table[sn].name,
		  skill_table[sn].spell_fun ? "spell" : "skill");
    }
}


Lookup_Fun (group_lookup)
{
  int gn;

  for (gn = 0; gn < top_group; gn++)
    {
      if (tolower (name[0]) == tolower (group_table[gn].name[0]) &&
	  !str_prefix (name, group_table[gn].name))
	return gn;
    }

  return -1;
}


void
gn_add (CharData * ch, int gn)
{
  int i;

  if (IsNPC (ch) || gn >= top_group || gn < 0)
    return;
  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);
    }
}


void
gn_remove (CharData * ch, int gn)
{
  int i;

  if (IsNPC (ch) || gn >= top_group || gn < 0)
    return;

  if (check_base_group (ch, gn))
    return;

  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]);
    }
}


void
group_add (CharData * ch, const char *name, bool deduct)
{
  int sn, gn;

  if (IsNPC (ch))
    return;

  sn = skill_lookup (name);

  if (sn != -1)
    {
      if (ch->pcdata->learned[sn] == 0)
	{
	  ch->pcdata->learned[sn] = 1;
	  if (deduct)
	    ch->pcdata->points += skill_rating (ch, sn);
	}
      return;
    }



  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);
    }
}



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

  sn = skill_lookup (name);

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



  gn = group_lookup (name);

  if (gn != -1 && ch->pcdata->group_known[gn] == true)
    {
      ch->pcdata->group_known[gn] = false;
      gn_remove (ch, gn);
    }
}

Do_Fun (do_slist)
{
  int sn, clas;
  bool found = false;

  if (NullStr (argument))
    {
      cmd_syntax (ch, NULL, n_fun, "<skill>", "<spell>", "<class>", NULL);
      return;
    }
  else if ((clas = class_lookup (argument)) != -1)
    {
      char buf[MSL];
      Buffer *buffer;
      char skill_list[MAX_MORTAL_LEVEL + 1][5000];
      int skill_columns[MAX_MORTAL_LEVEL + 1];
      int level;

      for (level = 0; level < MAX_MORTAL_LEVEL + 1; level++)
	{
	  skill_columns[level] = 0;
	  skill_list[level][0] = NUL;
	}

      for (sn = 0; sn < top_skill; sn++)
	{
	  if ((level = skill_table[sn].skill_level[clas]) <= MAX_MORTAL_LEVEL)
	    {
	      found = true;
	      sprintf (buf, "{c%-18s      ", skill_table[sn].name);

	      if (skill_list[level][0] == NUL)
		sprintf (skill_list[level],
			 NEWLINE "{cLevel {W%3d{c: %s{x", level, buf);
	      else
		{

		  if (++skill_columns[level] % 2 == 0)
		    strcat (skill_list[level], NEWLINE "{x           ");

		  strcat (skill_list[level], buf);
		}

	    }

	}

      buffer = new_buf ();
      for (level = 0; level < MAX_MORTAL_LEVEL + 1; level++)
	{
	  if (skill_list[level][0] != NUL)
	    bprint (buffer, skill_list[level]);
	}
      bprintln (buffer, NULL);
      sendpage (ch, buf_string (buffer));
      free_buf (buffer);
      return;
    }
  else if ((sn = skill_lookup (argument)) != -1)
    {
      int x;

      chprintf (ch, NEWLINE "{c%s: [ ", capitalize (skill_table[sn].name));
      for (x = 0; x < top_class; x++)
	{
	  if (skill_table[sn].skill_level[x] > MAX_MORTAL_LEVEL)
	    chprintf (ch, "{W%.3s: %3s{c  ", ClassName (ch, x), "n/a");
	  else
	    chprintf (ch, "{W%.3s: %03d{c  ", ClassName (ch, x),
		      skill_table[sn].skill_level[x]);
	}
      chprintln (ch, "]{x");
      return;
    }
  else
    {
      do_slist (n_fun, ch, "");
      return;
    }
}


int
spell_avail (CharData * ch, const char *name)
{

  int sn;

  sn = skill_lookup (name);

  if (sn != -1 && skill_level (ch, sn) > MAX_MORTAL_LEVEL)
    return -1;

  return sn;
}

Do_Fun (do_grlist)
{
  int gn, sn, tn;
  bool found = false;
  Column *Cd;

  if (!ch || IsNPC (ch))
    return;

  Cd = new_column ();
  set_cols (Cd, ch, 4, COLS_CHAR, ch);

  if (NullStr (argument))
    {
      for (gn = 0; gn < top_group; gn++)
	{
	  if (ch->pcdata->group_known[gn])
	    {
	      if (!found)
		{
		  chprintln (ch, "{cGroups you currently have:");
		  chprintln (ch, draw_line (ch, NULL, 0));
		}
	      print_cols (Cd, "{W%s", group_table[gn].name);
	      found = true;
	    }
	}
      if (!found)
	chprintln (ch, "{cYou know no groups.{x");
      else
	cols_nl (Cd);
      chprintlnf (ch, "{cCreation points: {W%d{x", ch->pcdata->points);
    }
  else if (!str_cmp (argument, "all"))
    {

      for (gn = 0; gn < top_group; gn++)
	{
	  if (IsImmortal (ch) || group_rating (ch, gn) > 0)
	    {
	      if (!found)
		{
		  chprintln (ch, "{cGroups available to you:");
		  chprintln (ch, draw_line (ch, NULL, 0));
		}
	      print_cols (Cd, "{W%s", group_table[gn].name);
	      found = true;
	    }
	}
      if (!found)
	chprintln (ch, "{cNo groups are available to you.{x");
      else
	cols_nl (Cd);
    }
  else if ((tn = class_lookup (argument)) != -1)
    {
      for (gn = 0; gn < top_group; gn++)
	{
	  if (group_table[gn].rating[tn] <= 0)
	    continue;

	  if (!found)
	    {
	      chprintlnf (ch, "{cGroups available for the {W%s{c"
			  " class:", class_table[tn].name[0]);
	      chprintln (ch, draw_line (ch, NULL, 0));
	    }
	  print_cols (Cd, "{W%s", group_table[gn].name);
	  found = true;
	}
      if (!found)
	chprintlnf (ch, "{cThere are no groups available to the {W%s{c"
		    " class.{x", class_table[tn].name[0]);
      else
	cols_nl (Cd);
    }
  else if ((gn = group_lookup (argument)) != -1)
    {
      for (sn = 0; sn < MAX_IN_GROUP; sn++)
	{
	  if (group_table[gn].spells[sn] == NULL)
	    break;
	  if ((tn = spell_avail (ch, group_table[gn].spells[sn])) >= 0)
	    {
	      if (!found)
		{
		  chprintlnf (ch, "{cSpells available in {W%s{c:{x",
			      group_table[gn].name);
		  cols_header (Cd, "{cLevel {WSpell{x");

		  chprintlnf (ch, "{c%s{x", draw_line (ch, NULL, 0));
		}
	      print_cols (Cd, "{c%-5d {W%s{x", skill_level (ch, tn),
			  group_table[gn].spells[sn]);
	      found = true;
	    }
	}
      if (!found)
	chprintlnf (ch, "{cNo spells available in the {W%s{c group.{x",
		    group_table[gn].name);
      else
	cols_nl (Cd);
    }
  else if ((sn = skill_lookup (argument)) != -1)
    {
      for (gn = 0; gn < top_group; gn++)
	{
	  for (tn = 0; tn < MAX_IN_GROUP; tn++)
	    {
	      if (group_table[gn].spells[tn] == NULL)
		break;

	      if (skill_lookup (group_table[gn].spells[tn]) != sn)
		continue;

	      if (!found)
		{
		  chprintlnf (ch, "{W%s{c is in the following groups:{x",
			      skill_table[sn].name);
		  chprintlnf (ch, "{c%s{x", draw_line (ch, NULL, 0));
		}
	      print_cols (Cd, "{W%s", group_table[gn].name);
	      found = true;
	    }
	}
      if (!found)
	chprintlnf (ch, "{W%s{c can't be found in any groups.{x",
		    skill_table[sn].name);
      else
	cols_nl (Cd);
    }
  else
    {
      cmd_syntax (ch, NULL, n_fun,
		  "        -list your current groups",
		  "all     -list all available groups",
		  "<group> -list all spells in a group",
		  "<skill> -list all groups a skill is in",
		  "<class> -list all groups available to a class", NULL);
    }
  free_column (Cd);
}