kbk/kbk3/
kbk/kbk3/area/
kbk/kbk3/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@pacinfo.com)				   *
*	    Gabrielle Taylor (gtaylor@pacinfo.com)			   *
*	    Brian Moore (rom@rom.efn.org)				   *
*	By using this code, you have agreed to follow the terms of the	   *
*	ROM license, in the file Tartarus/doc/rom.license                  *
***************************************************************************/

/***************************************************************************
*       Tartarus code is copyright (C) 1997-1998 by Daniel Graham          *
*	In using this code you agree to comply with the Tartarus license   *
*       found in the file /Tartarus/doc/tartarus.doc                       *
***************************************************************************/

#include "include.h"

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


int is_spec_skill args((int sn));
int is_mob_spec args((int sn));
bool is_specced args((CHAR_DATA *ch,int spec));
char *target_name;

int weapon_num_lookup args((const char *name));
char * weapon_name_lookup args((int type));

/* used to get new skills */
void do_gain(CHAR_DATA *ch, char *argument)
{
    char arg[MAX_INPUT_LENGTH];
    CHAR_DATA *trainer;
    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)
 || IS_SET(trainer->act,ACT_TRAIN)) )
	    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,"Use gain convert to convert 10 practices into 1 train.");
	do_say(trainer,"Or use gain revert to revert 1 train into 10 practices.");
	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;
    }

/* 'gain revert' converts one train into 10 pracs */
    if (!str_prefix(arg,"revert"))
    {
	if (ch->train < 1)
	{
	    act("$N tells you 'You do not have the training to apply to your skill practices yet.'",
		ch,NULL,trainer,TO_CHAR);
	    return;
	}

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

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



/* 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 = FALSE, found = FALSE;
    char buf[MAX_STRING_LENGTH];
 
    if (IS_NPC(ch))
      return;
    if(class_table[ch->class].ctype!=CLASS_CASTER)
    {
	send_to_char("Your class knows no spells.\n\r",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
	&&  level >= min_lev && level <= max_lev
	&&  skill_table[sn].spell_fun != spell_null
	&&  ch->pcdata->learned[sn] > 0
	&&  (skill_table[sn].ctype==CMD_SPELL || skill_table[sn].ctype==CMD_BOTH))
        {
	    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 = 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))
	    {
		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)*/
	if ((level = skill_table[sn].skill_level[ch->class]) < LEVEL_HERO + 1
	&&  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)
{
    return;
}


void list_group_chosen(CHAR_DATA *ch)
{
    if (IS_NPC(ch))
        return;
    return;
}

int exp_per_level(CHAR_DATA *ch)
{
    int epl;

    if (IS_NPC(ch))
        return 1500; 

    epl = pc_race_table[ch->race].xpadd + class_table[ch->class].xpadd + 1500;
    epl += (ch->level -1) * epl * .08;
    return epl;
}

/* 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);
	    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;
	    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);
	    }
	    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;
	    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)
{
    if (IS_NPC(ch))
	return;
    return;
}

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

    if (IS_NPC(ch))
	return;

    if (isShifted(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 ((victim = ch->fighting) != NULL)
	{
		if (IS_NPC(victim))
		{
			if (victim->level > ch->level)
			{
				chance += (victim->level - ch->level)*10;
			}
		}
		if (!IS_NPC(victim))
		{
			chance += (victim->level)*2;
		}
	}
	else
	{
		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)
	{
	    ch->pcdata->learned[sn]++;
	    gain_exp(ch,2 * skill_table[sn].rating[ch->class]);
		if (get_skill(ch,sn) == 100)
		{
			sprintf(buf,"{GYou have perfected %s!{x\n\r",skill_table[sn].name);
		}
		else
		{
			sprintf(buf,"{YYou have become better at %s!{x\n\r",skill_table[sn].name);
		}
	    send_to_char(buf,ch);
	}
    }

    else
    {
	chance = URANGE(5,ch->pcdata->learned[sn]/2,30);
	if (number_percent() < chance)
	{
	    ch->pcdata->learned[sn] += number_range(1,3);
	    ch->pcdata->learned[sn] = UMIN(ch->pcdata->learned[sn],100);
	    gain_exp(ch,2 * skill_table[sn].rating[ch->class]);
		if (get_skill(ch,sn) == 100)
		{
		    sprintf(buf,"{GYou learn from your mistakes, and manage to perfect %s!{x\n\r",skill_table[sn].name);
		}
		else
		{
		    sprintf(buf,"{YYou learn from your mistakes, and your %s skill improves!{x\n\r",skill_table[sn].name);

		}
	    send_to_char(buf,ch);
	}
    }
}

/* 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;
	}
	return;
    }
	
    /* now check groups */

    gn = group_lookup(name);

    if (gn != -1)
    {
	if (ch->pcdata->group_known[gn] == FALSE)  
	{
	    ch->pcdata->group_known[gn] = TRUE;
	}
	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_specialize(CHAR_DATA *ch,char *argument)
{
	char arg[MAX_INPUT_LENGTH];
	char buf[MAX_STRING_LENGTH];
	int i;
	bool already = FALSE;
	bool anyspecs = FALSE;
	
	if (IS_NPC(ch))
		return;

	if (IS_SET(ch->act,PLR_BETRAYER)) {
	send_to_char("You don't feel ready to specialize.\n\r",ch);
	return;
	}

	if (ch->pcdata->special == 0)
	{
	send_to_char("You have no specializations available.\n\r",ch);
	return;
	}

	one_argument(argument,arg);
	if (arg[0] == '\0')
	{
	send_to_char("You can currently specialize in: ",ch);
	for (i=1;i<=MAX_WEAPON;i++) {
		already = FALSE;

		already = is_specced(ch,i);

		if (already == FALSE) {
			anyspecs = TRUE;
			send_to_char(weapon_name_lookup(i),ch);
			send_to_char(" ",ch);
		}			
	}
	if (anyspecs)
		send_to_char("\n\r",ch);
	else
		send_to_char("nothing.\n\r",ch);
	
	return;
	}

	if (weapon_num_lookup(arg) > 0) {
		for (i=0;i<=MAX_SPECS;i++) {
			if (get_skill(ch,skill_lookup(arg)) < 90) {
				send_to_char("You do not feel knowledgable enough in that weapon to specialize in it.\n\r",ch);
				return;
			}
			if (!ch->pcdata->warrior_specs[i]) {
				ch->pcdata->warrior_specs[i] = weapon_num_lookup(arg);
				already = TRUE;
				break;
			} else {
				if ((ch->pcdata->warrior_specs[i] == weapon_num_lookup(arg)) || 
				(ch->pcdata->warrior_specs[i] == WEAPON_STAFF && weapon_num_lookup(arg) == WEAPON_SPEAR) || 
				(ch->pcdata->warrior_specs[i] == WEAPON_SPEAR && weapon_num_lookup(arg) == WEAPON_STAFF) || 
				(ch->pcdata->warrior_specs[i] == WEAPON_WHIP && weapon_num_lookup(arg) == WEAPON_FLAIL) || 
				(ch->pcdata->warrior_specs[i] == WEAPON_FLAIL && weapon_num_lookup(arg) == WEAPON_WHIP)) {
					send_to_char("You are already specialized in that.\n\r",ch);
					return;
				}
			}

		}
		if (!already) {
			send_to_char("You cannot specialize anymore!\n\r",ch);
			return;
		}
		sprintf(buf,"ws %s",weapon_name_lookup(weapon_num_lookup(arg)));
		group_add(ch,buf,FALSE);
		send_to_char("You are now specialized in ",ch);
		send_to_char(weapon_name_lookup(weapon_num_lookup(arg)),ch);
		send_to_char(".\n\r",ch);
		ch->pcdata->special--;
	} else {
		send_to_char("You can't specialize in that.\n\r",ch);
		return;
	}

	return;

}

int is_spec_skill(int sn) {
	int i,gn,gns,gsn;
	char buf[MAX_STRING_LENGTH];

	for (i=1;i<=MAX_WEAPON;i++) {
		sprintf(buf,"ws %s",weapon_name_lookup(i));
		gn = group_lookup(buf);
		for (gns = 0; gns <= MAX_SPEC_SKILLS; gns++)
		{
			gsn = skill_lookup(group_table[gn].spells[gns]);
			if (gsn == sn) {
				return i;
                        }
                }
        }
	return -1;
}
int is_mob_spec(int sn) {
        int i,gns,gsn;
        for (i=GROUP_SWORD;i<=GROUP_POLEARM;i++) {
            for (gns = 0; gns <=4; gns++)
              {   
		 if(!group_table[i].spells[gns])
			break;
                 gsn = skill_lookup(group_table[i].spells[gns]);
                 if (gsn == sn)
                     return i;
              }
	}
        return -1;
}
bool is_specced(CHAR_DATA *ch,int spec) {
	int j;

	if (IS_SET(ch->act,PLR_BETRAYER)) {
		return FALSE;
	}
	if(IS_NPC(ch))
	{
		if(spec==GROUP_SWORD && IS_SET(ch->extended_flags,OFF_SWORD_SPEC))
			return TRUE;
                if(spec==GROUP_AXE && IS_SET(ch->extended_flags,OFF_AXE_SPEC))
                        return TRUE;
                if(spec==GROUP_HTH && IS_SET(ch->extended_flags,OFF_HTH_SPEC))
                        return TRUE;
                if(spec==GROUP_DAGGER && IS_SET(ch->extended_flags,OFF_DAGGER_SPEC))
                        return TRUE;
                if(spec==GROUP_SPEAR && IS_SET(ch->extended_flags,OFF_SPEAR_SPEC))
                        return TRUE;
                if(spec==GROUP_MACE && IS_SET(ch->extended_flags,OFF_MACE_SPEC))
                        return TRUE;
                if(spec==GROUP_WHIP && IS_SET(ch->extended_flags,OFF_WHIP_SPEC))
                        return TRUE;
                if(spec==GROUP_POLEARM && IS_SET(ch->extended_flags,OFF_POLEARM_SPEC))
                        return TRUE;
	return FALSE;
	}
	for (j=0;j<=MAX_SPECS;j++) {
		if ((ch->pcdata->warrior_specs[j] == spec) ||
		(ch->pcdata->warrior_specs[j] == WEAPON_STAFF && spec == WEAPON_SPEAR) ||
		(ch->pcdata->warrior_specs[j] == WEAPON_SPEAR && spec == WEAPON_STAFF) ||
		(ch->pcdata->warrior_specs[j] == WEAPON_WHIP && spec == WEAPON_FLAIL) ||
		(ch->pcdata->warrior_specs[j] == WEAPON_FLAIL && spec == WEAPON_WHIP)) {
			return TRUE;
		}

	}
	return FALSE;
} 

void do_trip_wire(CHAR_DATA *ch, char *argument)
{
    AFFECT_DATA af;
    int chance;
    if ( (chance = get_skill(ch,gsn_trip_wire)) == 0)
    {
        send_to_char("You don't know how to set a trip wire.\n\r",ch);
        return;
    }
    if (is_affected(ch,gsn_trip_wire))
    {
	send_to_char("You already have a trip wire set up.\n\r",ch);
	return;
    }
    if (is_affected(ch,gsn_wire_delay))
    {
	send_to_char("You are not ready to set another trip wire.\n\r",ch);
	return;
    }
    if (ch->mana < 50)
    {
	send_to_char("You don't have enough energy to set up a trip wire.\n\r",ch);
	return;
    }
    ch-> mana -= 50;
    WAIT_STATE(ch,skill_table[gsn_trip_wire].beats );
    if (chance < number_percent())
    {
	send_to_char("You failed to set up a trip wire.\n\r",ch);
        check_improve(ch,gsn_trip_wire,FALSE,1);	
	return; 
    }
    act("You set up a trip wire and wait for your victim.",ch,NULL,NULL,TO_CHAR);
    init_affect(&af);
    af.where		= TO_AFFECTS;
    af.type             = gsn_trip_wire;
    af.aftype		= AFT_SKILL;
    af.level            = ch->level;
    af.duration         = ch->level/5;
    af.location         = 0;
    af.modifier         = 0;
    af.bitvector        = 0;
    affect_to_char(ch,&af);
    check_improve(ch,gsn_trip_wire,TRUE,1);	
}

void do_gag( CHAR_DATA *ch, char *argument)
{
    char arg[MIL], buf[MSL];
    CHAR_DATA *victim;
    AFFECT_DATA af;
    int percent, chance;
    one_argument(argument,arg);
    if ((chance = get_skill(ch,gsn_gag)) == 0 )
    {
	send_to_char("You do not know where to start.\n\r",ch);
	return;
    }
    if (arg[0] == '\0')
    {
	send_to_char("Gag who?\n\r",ch);
	return;
    }
    if (ch->fighting != NULL)
    {
	send_to_char("You can't do that while fighting.\n\r",ch);
	return;
    }
    if ((victim = get_char_room(ch,arg)) == NULL)
    {
	send_to_char("They aren't here.\n\r",ch);
	return;
    }
    if (victim == ch)
	return;
    if (is_safe(ch,victim))
	return;
    chance = chance;
    chance += get_curr_stat(ch,STAT_DEX) - get_curr_stat(victim,STAT_DEX);
    chance += URANGE(-10, (ch->level - victim->level), 10);
    WAIT_STATE(ch,skill_table[gsn_gag].beats);
    act("You attempt to fasten a gag on $N.",ch,NULL,victim,TO_CHAR);
    act("$n attempts to gag $N.",ch,NULL,victim,TO_NOTVICT);
    percent = number_percent();
    if (percent > chance || percent > 90)
    {
	sprintf(buf, "Help!  %s just tried to gag me!",PERS(ch,victim));
	do_myell(victim,buf);
	check_improve(ch,gsn_gag,FALSE,1);
	damage(ch,victim,1,gsn_gag,DAM_NONE,TRUE);
    }
    else if (percent < 3 * chance /4)
    {
	if (!IS_AWAKE(victim))
	    do_wake(victim,"");
	if (IS_NPC(victim) && IS_AWAKE(victim))
	    multi_hit(victim,ch,TYPE_UNDEFINED);
	init_affect(&af);
    	af.type			= gsn_gag;
    	af.aftype		= AFT_SKILL;
    	af.level		= ch->level;
    	af.duration		= number_range(0,1);
    	af.location		= APPLY_NONE;
    	af.modifier		= 0;
    	af.bitvector		= 0;
    	affect_to_char(victim,&af);
	check_improve(ch,gsn_gag,TRUE,1);
    }
    else
	check_improve(ch,gsn_gag,FALSE,1);
}

void do_call( CHAR_DATA *ch, char *argument )
{
    char buf[MAX_STRING_LENGTH];
    char arg1[MAX_INPUT_LENGTH];
    char arg2[MAX_INPUT_LENGTH];
    CHAR_DATA *victim;
    OBJ_DATA *obj;
    void *vo;
    int mana;
    int sn;
  
    int target;
    if ( IS_NPC(ch) && ch->desc == NULL)
	return;
    target_name = one_argument( argument, arg1 );
    one_argument( target_name, arg2 );
    if ( arg1[0] == '\0' )
	return send_to_char( "Call which what where?\n\r", ch );

    if ((sn = find_spell(ch,arg1)) < 1
    || (!IS_NPC(ch) && get_skill(ch,sn) < 5)
    || (!IS_NPC(ch) && ch->pcdata->learned[sn] == 0))
	return send_to_char( "You don't know any powers of that name.\n\r", ch );

    if ( ch->position < skill_table[sn].minimum_position && get_trust(ch)<MAX_LEVEL-1)
	return send_to_char( "You can't concentrate enough.\n\r", ch );

    if(skill_table[sn].ctype!=CMD_POWER && !IS_IMMORTAL(ch))
	return send_to_char("You can't call that.\n\r",ch);

    if (ch->level + 2 == skill_table[sn].skill_level[ch->class])
	mana = 50;
    else
    	mana = UMAX(
	    skill_table[sn].min_mana,
	    100 / ( 2 + ch->level - skill_table[sn].skill_level[ch->class] ) );
    {

    }
    /*
     * Locate targets.
     */
    victim	= NULL;
    obj		= NULL;
    vo		= NULL;
    target	= TARGET_NONE;
    switch ( skill_table[sn].target )
    {
    default:
	bug( "Do_cast: bad target for sn %d.", sn );
	return;
    case TAR_IGNORE:
	break;
    case TAR_CHAR_OFFENSIVE:
	if ( arg2[0] == '\0' )
	{
	    if ( ( victim = ch->fighting ) == NULL )
		return send_to_char( "Call the power on whom?\n\r", ch );
	}
	else
	{
	    if ( ( victim = get_char_room( ch, target_name ) ) == NULL )
		return send_to_char( "They aren't here.\n\r", ch );
	}
	if ( !IS_NPC(ch) )
		check_killer(ch,victim);

	if ( IS_AFFECTED(ch, AFF_CHARM) && ch->master == victim )
		return send_to_char( "You can't do that on your own master.\n\r", ch );

	vo = (void *) victim;
	target = TARGET_CHAR;
	break;
    case TAR_CHAR_DEFENSIVE:
	if ( arg2[0] == '\0' )
	    victim = ch;
	else
	{
	    if ( ( victim = get_char_room( ch, target_name ) ) == NULL )
		return send_to_char( "They aren't here.\n\r", ch );
	}
	vo = (void *) victim;
	target = TARGET_CHAR;
	break;
    case TAR_CHAR_SELF:
	if ( arg2[0] != '\0' && !is_name( target_name, ch->name ) )
		return send_to_char( "You cannot call this power on another.\n\r", ch );

	vo = (void *) ch;
	target = TARGET_CHAR;
	break;
    case TAR_OBJ_INV:
	if ( arg2[0] == '\0' )
		return send_to_char( "What should the power be called upon?\n\r", ch );

	if ( ( obj = get_obj_carry( ch, target_name, ch ) ) == NULL )
		return send_to_char( "You are not carrying that.\n\r", ch );

	vo = (void *) obj;
	target = TARGET_OBJ;
	break;
    case TAR_OBJ_CHAR_OFF:
	if (arg2[0] == '\0')
	{
	    if ((victim = ch->fighting) == NULL)
		return send_to_char("Call the power on who or what?\n\r",ch);
	    target = TARGET_CHAR;
	}
	else if ((victim = get_char_room(ch,target_name)) != NULL)
	{
	    target = TARGET_CHAR;
	}
	if (target == TARGET_CHAR) /* check the sanity of the attack */
	{
            if ( IS_AFFECTED(ch, AFF_CHARM) && ch->master == victim )
                return send_to_char( "You can't do that on your own follower.\n\r", ch );
	    if (!IS_NPC(ch))
		check_killer(ch,victim);

	    vo = (void *) victim;
 	}
	else if ((obj = get_obj_here(ch,target_name)) != NULL)
	{
	    vo = (void *) obj;
	    target = TARGET_OBJ;
	}
	else
		return send_to_char("You don't see that here.\n\r",ch);
	break; 
    case TAR_OBJ_CHAR_DEF:
        if (arg2[0] == '\0')
        {
            vo = (void *) ch;
            target = TARGET_CHAR;                                                 
        }
        else if ((victim = get_char_room(ch,target_name)) != NULL)
        {
            vo = (void *) victim;
            target = TARGET_CHAR;
	}
	else if ((obj = get_obj_carry(ch,target_name,ch)) != NULL)
	{
	    vo = (void *) obj;
	    target = TARGET_OBJ;
	}
	else
		return send_to_char("You don't see that here.\n\r",ch);
	break;
    }    
    if ( !IS_NPC(ch) && ch->mana < mana )
	return send_to_char( "You don't have enough mana.\n\r", ch );

    WAIT_STATE( ch, skill_table[sn].beats );
    if ( !IS_NPC(ch) && (number_percent( ) > get_skill(ch,sn)))
    {
	send_to_char( "You lost your concentration.\n\r", ch );
	check_improve(ch,sn,FALSE,1);
	ch->mana -= mana / 2;
    }
    else
    {
        ch->mana -= mana;
	if (skill_table[sn].target == TAR_CHAR_OFFENSIVE
	&& is_safe(ch,victim))
		return;
	
	if (skill_table[sn].target == TAR_CHAR_OFFENSIVE)
	{
	if (!IS_NPC(ch) && !IS_NPC(victim)
	&& (ch->fighting == NULL || victim->fighting == NULL))
		{
		switch(number_range(0,2))
		{
		case (0):
		case (1):
		sprintf(buf,"Die, %s you sorcerous dog!",PERS(ch,victim));
		break;
		case (2):
		sprintf(buf,"Help! %s is casting a spell on me!",PERS(ch,victim));
		}
	if (victim != ch && !IS_NPC(ch))
		do_myell(victim,buf);
	}
                if(check_volley(ch,victim))
                {
                        act("$N reflects your spell right back at you!",ch,0,victim,TO_CHAR);
                        act("You reflect $n's spell right back at $m!",ch,0,victim,TO_VICT);
                        act("$N reflects $n's spell right back at $m!",ch,0,victim,TO_NOTVICT);
                        (*skill_table[sn].spell_fun) ( sn, ch->level*2, victim, ch, target);
                        return;   
                }
	}
            (*skill_table[sn].spell_fun) ( sn, ch->level, ch, vo,target);
        check_improve(ch,sn,TRUE,1);
    }
    if ((skill_table[sn].target == TAR_CHAR_OFFENSIVE
    ||   (skill_table[sn].target == TAR_OBJ_CHAR_OFF && target == TARGET_CHAR))
    &&   victim != ch
    &&   victim->master != ch)
    {
	CHAR_DATA *vch;
	CHAR_DATA *vch_next;
	for ( vch = ch->in_room->people; vch; vch = vch_next )
	{
	    vch_next = vch->next_in_room;
	    if ( victim == vch && victim->fighting == NULL )
	    {	check_killer(victim,ch);
		multi_hit( victim, ch, TYPE_UNDEFINED );
		break;
	    }
	}
    }
    return;
}

/* Nasty assassinattion skill. Chance of success is very high, should be
changed if you plan on using this on a PK mud. -Ceran
*/
void do_assassinate(CHAR_DATA *ch,char *argument)
{
        CHAR_DATA *victim;
        int chance, dam;
        char arg[MAX_INPUT_LENGTH];
        char buf[MAX_INPUT_LENGTH];

        one_argument(argument,arg);
        if (IS_NPC(ch))
                return;

        if ((get_skill(ch,gsn_assassinate) == 0)
        || ch->level < skill_table[gsn_assassinate].skill_level[ch->class])
	{
	send_to_char("Huh?\n\r",ch);
	return;
	}

        if (arg[0] == '\0')
        {
        send_to_char("Attempt to assassinate who?\n\r",ch);
        return;
        }
        if ( (victim = get_char_room(ch,arg)) == NULL)
        {
        send_to_char("They aren't here.\n\r",ch);
        return;
        }
        if (victim == ch)
        {
        send_to_char("You can't do that.\n\r",ch);
        return;
        }
        if (victim->fighting != NULL || victim->position == POS_FIGHTING)
        {
        send_to_char("They are moving around too much to get in close for the kill.\n\r",ch);
        return;
        }

        if (is_safe(ch,victim))
                return;

        if (victim->hit < victim->max_hit - 50)
        {
        send_to_char("They have too much blood on them right now to locate their strike point.\n\r",ch);
        return;
        }

        chance = get_skill(ch,gsn_assassinate);

        if (number_percent() > chance)
        {
        sprintf(buf,"Help! %s just tried to assassinate me!",PERS(ch,victim));
        do_myell(victim,buf);
        damage_old(ch,victim,0,gsn_assassinate,DAM_OTHER,TRUE);
        check_improve(ch,gsn_assassinate,1,TRUE);
        WAIT_STATE(ch,PULSE_VIOLENCE*2);
        return;
        }

	chance *=2;
	chance /=3;

        chance += (ch->level * 3);
        chance -= (victim->level * 4);
        if (!can_see(victim,ch))
                chance += 10;
        if (victim->position == POS_FIGHTING)
                chance -= 10;
        else if (victim->position == POS_SLEEPING)
                chance += 10;

        chance /= 2;
        chance = URANGE(2,chance,25);

        if (victim->level > (ch->level + 8))
            chance = 0;

/*      act("$n tries to strike at $N's critical nerves!",ch,0,victim,TO_NOTVICT);
        act("You try to strike $N's critical nerves!",ch,0,victim,TO_CHAR);
        act("$n strikes at your critical nerves!",ch,0,victim,TO_VICT); */

	if (ch->level == MAX_LEVEL) {
		chance = 100;
	}
        if (number_percent() < chance)
        {
        act("$n {r+++ ASSASSINATES +++{x $N!",ch,0,victim,TO_NOTVICT);
        act("You {r+++ ASSASSINATE +++{x $N!",ch,0,victim,TO_CHAR);
        act("$n {r+++ ASSASSINATES +++{x you!",ch,0,victim,TO_VICT);
	send_to_char("You have been KILLED!!\n\r", victim);
        raw_kill(ch,victim);
        check_improve(ch,gsn_assassinate,TRUE,3);
        return;
        }
        else
        {
        dam = ch->damroll*2;
        if (number_percent() < get_skill(ch,gsn_enhanced_damage))
        {
            dam += (number_percent() *dam/50);
            dam += (number_percent() * dam/100);
        }

        damage_old(ch,victim,dam,gsn_assassinate,DAM_ENERGY,TRUE);
        check_improve(ch,gsn_assassinate,FALSE,1);
        if (!IS_NPC(victim) && !IS_NPC(ch) && (victim->ghost == 0))
               {
                sprintf(buf,"Help! %s just tried to assassinate me!",PERS(ch,victim));
		do_myell(victim,buf);
                }
        multi_hit(victim,ch,TYPE_UNDEFINED);
		WAIT_STATE(ch,PULSE_VIOLENCE*2);
        }

        return;
}