alanthia/area/
alanthia/gods/
alanthia/player/
/***************************************************************************
 *  Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,	   *
 *  Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe.   *
 *									   *
 *  Merc Diku Mud improvments 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	   *
 *  benefitting.  We hope that you share your changes too.  What goes	   *
 *  around, comes around.						   *
 ***************************************************************************/

/***************************************************************************
*	ROM 2.4 is copyright 1993-1996 Russ Taylor			   *
*	ROM has been brought to you by the ROM consortium		   *
*	    Russ Taylor (rtaylor@efn.org)				   *
*	    Gabrielle Taylor						   *
*	    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			   *
***************************************************************************/

#if defined(macintosh)
#include <types.h>
#include <time.h>
#else
#include <sys/types.h>
#include <sys/time.h>
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "merc.h"
#include "utils.h"
#include "magic.h"
#include "recycle.h"

/* command procedures needed */
DECLARE_DO_FUN(do_groups);
DECLARE_DO_FUN(do_help);
DECLARE_DO_FUN(do_say);
DECLARE_DO_FUN(do_breathe);

/* used to get new skills */
void do_gain(CHAR_DATA * ch, char *argument)
{
  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->people; 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))
	{
		send_to_char("You can't do that here.\n\r", ch);
		return;
  }
  one_argument(argument, arg);

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

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

		send_to_char("\r\n", ch);

		sprintf(buf, "%-18s %-5s %-18s %-5s %-18s %-5s\n\r",
			"group", "cost", "group", "cost", "group", "cost");
		send_to_char(buf, ch);

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

	    if (!ch->pcdata->group_known[gn] && group_table[gn].rating[ch->class] > 0)
			{
				sprintf(buf, "{y%-18s {c%-5d ",
				group_table[gn].name,
				group_table[gn].rating[ch->class]);
				send_to_char(buf, ch);
				if (++col % 3 == 0)
					send_to_char("\n\r", ch);
	    }
		}
		if (col % 3 != 0)
			send_to_char("\n\r", ch);

		send_to_char("\n\r", ch);

		col = 0;

		sprintf(buf, "{x%-18s %-5s %-5s %-18s %-5s %-5s\n\r",
			"skill", "cost", "level", "skill", "cost", "level");
		send_to_char(buf, ch);

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

			if (!ch->pcdata->learned[sn]
					&& skill_table[sn].rating[ch->class] > 0
					&& skill_table[sn].spell_fun == spell_null)
			{
				sprintf(buf, "{y%-18s {c%-5d {g%-5d ",
					skill_table[sn].name,
					skill_table[sn].rating[ch->class],
					skill_table[sn].skill_level[ch->class]);
				send_to_char(buf, ch);
				if (++col % 2 == 0)
					send_to_char("\n\r", ch);
			}
		}
		if (col % 3 != 0)
			send_to_char("\n\r{x", 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_table[gn].rating[ch->class] <= 0)
		{
	    act("$N tells you 'That group is beyond your powers.'",
			ch, NULL, trainer, TO_CHAR);
	    return;
		}
		if (ch->train < group_table[gn].rating[ch->class])
		{
	    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_table[gn].rating[ch->class];
		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_table[sn].rating[ch->class] <= 0)
		{
	    act("$N tells you 'That skill is beyond your powers.'",
			ch, NULL, trainer, TO_CHAR);
	    return;
		}
		if (ch->train < skill_table[sn].rating[ch->class])
		{
	    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_table[sn].rating[ch->class];
		return;
  }
  act("$N tells you 'I do not understand...'", ch, NULL, trainer,
		TO_CHAR);
}




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

void do_spells(CHAR_DATA * ch, char *argument)
{
  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 = TRUE, 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))
			{
				send_to_char("Arguments must be numerical or all.\n\r", ch);
				return;
	    }
	    max_lev = atoi(arg);

	    if (max_lev < 1 || max_lev > LEVEL_HERO)
			{
				sprintf(buf, "Levels must be between 1 and %d.\n\r", LEVEL_HERO);
				send_to_char(buf, ch);
				return;
	    }
	    if (argument[0] != '\0')
			{
				argument = one_argument(argument, arg);
				if (!is_number(arg))
				{
					send_to_char("Arguments must be numerical or all.\n\r", ch);
					return;
				}
				min_lev = max_lev;
				max_lev = atoi(arg);

				if (max_lev < 1 || max_lev > LEVEL_HERO)
				{
					sprintf(buf,"Levels must be between 1 and %d.\n\r", LEVEL_HERO);
					send_to_char(buf, ch);
					return;
				}
				if (min_lev > max_lev)
				{
					send_to_char("That would be silly.\n\r", ch);
					return;
				}
	    }
		}
  }
  /* initialize data */
  for (level = 0; level < LEVEL_HERO + 1; level++)
	{
		spell_columns[level] = 0;
		spell_list[level][0] = '\0';
  }

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

		if ((level = skill_table[sn].skill_level[ch->class]) <
				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_table[sn].skill_level[ch->class];
	    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)
	{
		send_to_char("No spells found.\n\r", ch);
		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);
}

void do_skills(CHAR_DATA * ch, char *argument)
{
  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 = TRUE, 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))
			{
				send_to_char("Arguments must be numerical or all.\n\r", ch);
				return;
	    }
	    max_lev = atoi(arg);

	    if (max_lev < 1 || max_lev > LEVEL_HERO)
			{
				sprintf(buf, "Levels must be between 1 and %d.\n\r", LEVEL_HERO);
				send_to_char(buf, ch);
				return;
	    }
	    if (argument[0] != '\0')
			{
				argument = one_argument(argument, arg);
				if (!is_number(arg))
				{
					send_to_char("Arguments must be numerical or all.\n\r", ch);
					return;
				}
				min_lev = max_lev;
				max_lev = atoi(arg);
				if (max_lev < 1 || max_lev > LEVEL_HERO)
				{
					sprintf(buf, "Levels must be between 1 and %d.\n\r", LEVEL_HERO);
					send_to_char(buf, ch);
					return;
				}
				if (min_lev > max_lev)
				{
					send_to_char("That would be silly.\n\r", ch);
					return;
				}
	    }
		}
  }
  /* initialize data */
  for (level = 0; level < LEVEL_HERO + 1; level++)
	{
		skill_columns[level] = 0;
		skill_list[level][0] = '\0';
  }

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

		if ((level = skill_table[sn].skill_level[ch->class]) <
				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_table[sn].skill_level[ch->class];
	    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) {
	send_to_char("No skills found.\n\r", ch);
	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;

    sprintf(buf, "%-18s %-5s %-18s %-5s %-18s %-5s\n\r", "group", "cp",
	    "group", "cp", "group", "cp");
    send_to_char(buf, ch);

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

	if (!ch->gen_data->group_chosen[gn]
	    && !ch->pcdata->group_known[gn]
	    && group_table[gn].rating[ch->class] > 0) {
	    sprintf(buf, "%-18s %-5d ", group_table[gn].name,
		    group_table[gn].rating[ch->class]);
	    send_to_char(buf, ch);
	    if (++col % 3 == 0)
		send_to_char("\n\r", ch);
	}
    }
    if (col % 3 != 0)
	send_to_char("\n\r", ch);
    send_to_char("\n\r", ch);

    col = 0;

    sprintf(buf, "%-18s %-5s %-18s %-5s %-18s %-5s\n\r", "skill", "cp",
	    "skill", "cp", "skill", "cp");
    send_to_char(buf, ch);

    for (sn = 0; sn < MAX_SKILL; 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_table[sn].rating[ch->class] > 0) {
	    sprintf(buf, "%-18s %-5d ", skill_table[sn].name,
		    skill_table[sn].rating[ch->class]);
	    send_to_char(buf, ch);
	    if (++col % 3 == 0)
		send_to_char("\n\r", ch);
	}
    }
    if (col % 3 != 0)
	send_to_char("\n\r", ch);
    send_to_char("\n\r", ch);

    sprintf(buf, "Creation points: %d\n\r", ch->pcdata->points);
    send_to_char(buf, ch);
    sprintf(buf, "Experience per level: %d\n\r",
	    exp_per_level(ch, ch->gen_data->points_chosen));
    send_to_char(buf, ch);
    return;
}


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

    if (IS_NPC(ch))
	return;

    col = 0;

    sprintf(buf, "%-18s %-5s %-18s %-5s %-18s %-5s", "group", "cp",
	    "group", "cp", "group", "cp\n\r");
    send_to_char(buf, ch);

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

	if (ch->gen_data->group_chosen[gn]
	    && group_table[gn].rating[ch->class] > 0) {
	    sprintf(buf, "%-18s %-5d ", group_table[gn].name,
		    group_table[gn].rating[ch->class]);
	    send_to_char(buf, ch);
	    if (++col % 3 == 0)
		send_to_char("\n\r", ch);
	}
    }
    if (col % 3 != 0)
	send_to_char("\n\r", ch);
    send_to_char("\n\r", ch);

    col = 0;

    sprintf(buf, "%-18s %-5s %-18s %-5s %-18s %-5s", "skill", "cp",
	    "skill", "cp", "skill", "cp\n\r");
    send_to_char(buf, ch);

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

	if (ch->gen_data->skill_chosen[sn]
	    && skill_table[sn].rating[ch->class] > 0) {
	    sprintf(buf, "%-18s %-5d ", skill_table[sn].name,
		    skill_table[sn].rating[ch->class]);
	    send_to_char(buf, ch);
	    if (++col % 3 == 0)
		send_to_char("\n\r", ch);
	}
    }
    if (col % 3 != 0)
	send_to_char("\n\r", ch);
    send_to_char("\n\r", ch);

    sprintf(buf, "Creation points: %d\n\r", ch->gen_data->points_chosen);
    send_to_char(buf, ch);
    sprintf(buf, "Experience per level: %d\n\r",
	    exp_per_level(ch, ch->gen_data->points_chosen));
    send_to_char(buf, ch);
    return;
}

int exp_per_level(CHAR_DATA * ch, int points)
{
	return 1000;

/*
    if (IS_NPC(ch))
	return 1000;

    if (points > 100)
	return 9999999;

    expl = 1000;
    inc = 500;

    if (points < 40)
	return 1000 * (race_table[ch->race].class_mult[ch->class] ?
		       race_table[ch->race].class_mult[ch->class] /
		       100 : 1);


    points -= 40;

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

    expl += points * inc / 10;
    if (expl < 0)
	expl = 9999999;
    return expl * race_table[ch->race].class_mult[ch->class] / 100;
*/
}

/* this procedure handles the input parsing for the skill generator */
bool parse_gen_groups(CHAR_DATA * ch, char *argument)
{
    char arg[MAX_INPUT_LENGTH];
    char buf[100];
    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_help(ch, "group help");
	    return TRUE;
	}
	do_help(ch, argument);
	return TRUE;
    }
    if (!str_prefix(arg, "add")) {
	if (argument[0] == '\0') {
	    send_to_char("You must provide a skill name.\n\r", ch);
	    return TRUE;
	}
	gn = group_lookup(argument);
	if (gn != -1) {
	    if (ch->gen_data->group_chosen[gn]
		|| ch->pcdata->group_known[gn]) {
		send_to_char("You already know that group!\n\r", ch);
		return TRUE;
	    }
	    if (group_table[gn].rating[ch->class] < 1) {
		send_to_char("That group is not available.\n\r", ch);
		return TRUE;
	    }
	    sprintf(buf, "%s group added\n\r", group_table[gn].name);
	    send_to_char(buf, ch);
	    ch->gen_data->group_chosen[gn] = TRUE;
	    ch->gen_data->points_chosen +=
		group_table[gn].rating[ch->class];
	    gn_add(ch, gn);
	    ch->pcdata->points += group_table[gn].rating[ch->class];
	    return TRUE;
	}
	sn = skill_lookup(argument);
	if (sn != -1) {
	    if (ch->gen_data->skill_chosen[sn]
		|| ch->pcdata->learned[sn] > 0) {
		send_to_char("You already know that skill!\n\r", ch);
		return TRUE;
	    }
	    if (skill_table[sn].rating[ch->class] < 1
		|| skill_table[sn].spell_fun != spell_null) {
		send_to_char("That skill is not available.\n\r", ch);
		return TRUE;
	    }
	    sprintf(buf, "%s skill added\n\r", skill_table[sn].name);
	    send_to_char(buf, ch);
	    ch->gen_data->skill_chosen[sn] = TRUE;
	    ch->gen_data->points_chosen +=
		skill_table[sn].rating[ch->class];
	    ch->pcdata->learned[sn] = 1;
	    ch->pcdata->points += skill_table[sn].rating[ch->class];
	    return TRUE;
	}
	send_to_char("No skills or groups by that name...\n\r", ch);
	return TRUE;
    }
    if (!strcmp(arg, "drop")) {
	if (argument[0] == '\0') {
	    send_to_char("You must provide a skill to drop.\n\r", ch);
	    return TRUE;
	}
	gn = group_lookup(argument);
	if (gn != -1 && ch->gen_data->group_chosen[gn]) {
	    send_to_char("Group dropped.\n\r", ch);
	    ch->gen_data->group_chosen[gn] = FALSE;
	    ch->gen_data->points_chosen -=
		group_table[gn].rating[ch->class];
	    gn_remove(ch, gn);
	    for (i = 0; i < MAX_GROUP; i++) {
		if (ch->gen_data->group_chosen[gn])
		    gn_add(ch, gn);
	    }
	    ch->pcdata->points -= group_table[gn].rating[ch->class];
	    return TRUE;
	}
	sn = skill_lookup(argument);
	if (sn != -1 && ch->gen_data->skill_chosen[sn]) {
	    send_to_char("Skill dropped.\n\r", ch);
	    ch->gen_data->skill_chosen[sn] = FALSE;
	    ch->gen_data->points_chosen -=
		skill_table[sn].rating[ch->class];
	    ch->pcdata->learned[sn] = 0;
	    ch->pcdata->points -= skill_table[sn].rating[ch->class];
	    return TRUE;
	}
	send_to_char("You haven't bought any such skill or group.\n\r",
		     ch);
	return TRUE;
    }
    if (!str_prefix(arg, "premise")) {
	do_help(ch, "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_groups(ch, argument);
	return TRUE;
    }
    return FALSE;
}






/* shows all groups, or the sub-members of a group */
void do_groups(CHAR_DATA * ch, char *argument)
{
    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 < MAX_GROUP; gn++) {
	    if (group_table[gn].name == NULL)
		break;
	    if (ch->pcdata->group_known[gn]) {
		sprintf(buf, "%-20s ", group_table[gn].name);
		send_to_char(buf, ch);
		if (++col % 3 == 0)
		    send_to_char("\n\r", ch);
	    }
	}
	if (col % 3 != 0)
	    send_to_char("\n\r", ch);
	sprintf(buf, "Creation points: %d\n\r", ch->pcdata->points);
	send_to_char(buf, ch);
	return;
    }
    if (!str_cmp(argument, "all")) {	/* show all groups */
	for (gn = 0; gn < MAX_GROUP; gn++) {
	    if (group_table[gn].name == NULL)
		break;
	    sprintf(buf, "%-20s ", group_table[gn].name);
	    send_to_char(buf, ch);
	    if (++col % 3 == 0)
		send_to_char("\n\r", ch);
	}
	if (col % 3 != 0)
	    send_to_char("\n\r", ch);
	return;
    }
    /* show the sub-members of a group */
    gn = group_lookup(argument);
    if (gn == -1) {
	send_to_char("No group of that name exist.\n\r", ch);
	send_to_char
	    ("Type 'groups all' or 'info all' for a full listing.\n\r",
	     ch);
	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]);
	send_to_char(buf, ch);
	if (++col % 3 == 0)
	    send_to_char("\n\r", ch);
    }
    if (col % 3 != 0)
	send_to_char("\n\r", ch);
}

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

    if (IS_NPC(ch))
	return;

    if (ch->level < skill_table[sn].skill_level[ch->class]
	|| skill_table[sn].rating[ch->class] == 0
	|| 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_table[sn].rating[ch->class]
	       * 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) {
	    sprintf(buf, "You have become better at %s!\n\r",
		    skill_table[sn].name);
	    send_to_char(buf, ch);
	    ch->pcdata->learned[sn]++;
	    gain_exp(ch, skill_table[sn].rating[ch->class]);
	}
    } else {
	chance = URANGE(5, ch->pcdata->learned[sn] / 2, 30);
	if (number_percent() < chance) {
	    sprintf(buf,
		    "You learn from your mistakes, and your %s skill improves.\n\r",
		    skill_table[sn].name);
	    send_to_char(buf, ch);
	    ch->pcdata->learned[sn] += number_range(1, 3);
	    ch->pcdata->learned[sn] = UMIN(ch->pcdata->learned[sn], 100);
	    gain_exp(ch, skill_table[sn].rating[ch->class]);
	}
    }
}

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

    for (gn = 0; gn < MAX_GROUP; 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_table[sn].rating[ch->class];
	}
	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_table[gn].rating[ch->class];
	}
	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 */
    }
}

void do_breathe(CHAR_DATA * ch, char *argument)
{
  CHAR_DATA *victim;
  int damdice, damtype, sn;
  char target[MAX_STRING_LENGTH];
  char points[MAX_STRING_LENGTH];
  damdice = 0;
  argument = one_argument(argument, target);
  argument = one_argument(argument, points);
  if (target[0] == '\0')
	{
		if ((victim = ch->fighting) == NULL)
		{
	    send_to_char("You must specify a target if not fighting.\n\r",
										ch);
	    return;
		}
  }
	else
	{
		if ((victim = get_char_room(ch, target)) == NULL)
		{
	    send_to_char("They aren't here.\n\r", ch);
	    return;
		}
  }
  
	if (ch->race == race_lookup("reddrg"))
	{
	sn = skill_lookup("fire breath");
	damtype = DAM_FIRE;
    } else if (ch->race == race_lookup("grndrg")) {
	sn = skill_lookup("venom breath");
	damtype = DAM_POISON;
    } else if (ch->race == race_lookup("bludrg")) {
	sn = skill_lookup("stormsfury breath");
	damtype = DAM_LIGHTNING;
    } else if (ch->race == race_lookup("whtdrg")) {
	sn = skill_lookup("frost breath");
	damtype = DAM_COLD;
    } else if (ch->race == race_lookup("blkdrg")) {
	sn = skill_lookup("corrosive breath");
	damtype = DAM_ACID;
    } else if (ch->race == race_lookup("glddrg")) {
	sn = skill_lookup("ray breath");
	damtype = DAM_LIGHT;
    } else if (ch->race == race_lookup("prsmdrg")) {
	sn = skill_lookup("prismatic breath");
	damtype = number_range( DAM_FIRE, DAM_DISEASE);
    } else {
	send_to_char("You huff and puff..\r\n", ch);
	return;
    }
  if (!IS_NPC(ch))
	{
		if (is_safe(ch, victim) && victim != ch)
		{
	    send_to_char("Not on that target.\n\r", ch);
	    return;
		}


	check_killer(ch, victim);
    }
    if (IS_AFFECTED(ch, AFF_CHARM) && ch->master == victim) {
	send_to_char("You can't do that on your own follower.\n\r", ch);
	return;
    }
    if (ch->blood < 1) {
	send_to_char
	    ("Little tendrils of smoke come out your nostrils..\r\n", ch);
	return;
    }
    if ((points[0] == '\0') && (ch->remorts == 0)) {
	if (ch->max_blood > 0) {

	    damdice = ((ch->max_blood / 10));
	    if (damdice > ch->blood) {
		damdice = (ch->blood);
		ch->blood = 0;
	    } else {
		ch->blood -= damdice;
	    }
	}
    } else if (ch->remorts == 0) {
	if ((damdice = atoi(points)) > ch->blood) {
	    damdice = ch->blood;
	    ch->blood = 0;
	} else {
	    ch->blood -= damdice;
	}
    }

    else if ((points[0] == '\0') && (ch->remorts > 0)) {
	if (ch->max_blood > 0) {
	    damdice = (ch->max_blood / 10);
	    if (damdice > ch->blood) {
		damdice = ch->blood;
		damdice = (damdice / ((ch->remorts * (ch->remorts + 1))));
		ch->blood = 0;
	    } else {
		ch->blood -= damdice;
		damdice = (damdice / ((ch->remorts * (ch->remorts + 1))));
	    }
	}
    }

    else if (ch->remorts > 0) {
	if ((damdice = atoi(points)) > ch->blood) {
	    damdice = (damdice / ((ch->remorts * (ch->remorts + 1))));
	    ch->blood = 0;
	} else {
	    ch->blood -= damdice;
	}
    }

	damage(ch, victim, number_range((damdice * (get_age(ch) / 2)),
					(damdice * get_age(ch))), sn, damtype, TRUE);
	WAIT_STATE(ch, ((24) + (damdice / 5)));

    return;
}