RaM Fire Updated/
/*
 * RAM $Id: skills.c 64 2008-12-11 14:35:49Z quixadhal $
 */

/***************************************************************************
 *  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-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                     *
***************************************************************************/

#include <sys/types.h>
#include <sys/time.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "merc.h"
#include "strings.h"
#include "random.h"
#include "db.h"
#include "interp.h"
#include "magic.h"
#include "tables.h"

/* stuff for recycling gen_data */
GEN_DATA               *gen_data_free = NULL;

GEN_DATA               *new_gen_data( void )
{
    static GEN_DATA         gen_zero;
    GEN_DATA               *gen = NULL;

    if ( gen_data_free == NULL )
        gen = ( GEN_DATA * ) alloc_perm( sizeof( *gen ) );
    else
    {
        gen = gen_data_free;
        gen_data_free = gen_data_free->next;
    }
    *gen = gen_zero;
    VALIDATE( gen );
    return gen;
}

void free_gen_data( GEN_DATA *gen )
{
    if ( !IS_VALID( gen ) )
        return;

    INVALIDATE( gen );

    gen->next = gen_data_free;
    gen_data_free = gen;
}

/* used to get new skills */
void do_gain( CHAR_DATA *ch, const char *argument )
{
    char                    arg[MAX_INPUT_LENGTH] = "\0\0\0\0\0\0\0";
    CHAR_DATA              *trainer = NULL;
    int                     gn = -1;
    int                     sn = -1;

    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 ) )
    {
        ch_printf( ch, "You can't do that here.\r\n" );
        return;
    }

    one_argument( argument, arg );

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

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

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

        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->iclass] > 0 )
            {
                ch_printf( ch, "%-18s %-5d ",
                           group_table[gn].name, group_table[gn].rating[ch->iclass] );
                if ( ++col % 3 == 0 )
                    ch_printf( ch, "\r\n" );
            }
        }
        if ( col % 3 != 0 )
            ch_printf( ch, "\r\n" );

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

        col = 0;

        ch_printf( ch, "%-18s %-5s %-18s %-5s %-18s %-5s\r\n",
                   "skill", "cost", "skill", "cost", "skill", "cost" );

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

            if ( !ch->pcdata->learned[sn]
                 && skill_table[sn].rating[ch->iclass] > 0
                 && skill_table[sn].spell_fun == spell_null )
            {
                ch_printf( ch, "%-18s %-5d ",
                           skill_table[sn].name, skill_table[sn].rating[ch->iclass] );
                if ( ++col % 3 == 0 )
                    ch_printf( ch, "\r\n" );
            }
        }
        if ( col % 3 != 0 )
            ch_printf( ch, "\r\n" );
        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->iclass] <= 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->iclass] )
        {
            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->iclass];
        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->iclass] <= 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->iclass] )
        {
            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->iclass];
        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, const char *argument )
{
    BUFFER                 *buffer = NULL;
    char                    arg[MAX_INPUT_LENGTH] = "\0\0\0\0\0\0\0";
    char                    spell_list[LEVEL_HERO + 1][MAX_STRING_LENGTH];
    char                    spell_columns[LEVEL_HERO + 1];
    int                     sn = 0;
    int                     level = 0;
    int                     min_lev = 1;
    int                     max_lev = LEVEL_HERO;
    int                     mana = 0;
    bool                    fAll = false;
    bool                    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 ) )
            {
                ch_printf( ch, "Arguments must be numerical or all.\r\n" );
                return;
            }
            max_lev = atoi( arg );

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

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

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

                if ( min_lev > max_lev )
                {
                    ch_printf( ch, "That would be silly.\r\n" );
                    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->iclass] ) < 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->iclass];
            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], "\r\nLevel %2d: %s", level, buf );
            else                                       /* append */
            {
                if ( ++spell_columns[level] % 2 == 0 )
                    strcat( spell_list[level], "\r\n          " );
                strcat( spell_list[level], buf );
            }
        }
    }

    /*
     * return results 
     */

    if ( !found )
    {
        ch_printf( ch, "No spells found.\r\n" );
        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, "\r\n" );
    page_to_char( buf_string( buffer ), ch );
    free_buf( buffer );
}

void do_skills( CHAR_DATA *ch, const char *argument )
{
    BUFFER                 *buffer = NULL;
    char                    arg[MAX_INPUT_LENGTH] = "\0\0\0\0\0\0\0";
    char                    skill_list[LEVEL_HERO + 1][MAX_STRING_LENGTH];
    char                    skill_columns[LEVEL_HERO + 1];
    int                     sn = -1;
    int                     level = 0;
    int                     min_lev = 1;
    int                     max_lev = LEVEL_HERO;
    bool                    fAll = false;
    bool                    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 ) )
            {
                ch_printf( ch, "Arguments must be numerical or all.\r\n" );
                return;
            }
            max_lev = atoi( arg );

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

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

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

                if ( min_lev > max_lev )
                {
                    ch_printf( ch, "That would be silly.\r\n" );
                    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->iclass] ) < 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->iclass];
            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], "\r\nLevel %2d: %s", level, buf );
            else                                       /* append */
            {
                if ( ++skill_columns[level] % 2 == 0 )
                    strcat( skill_list[level], "\r\n          " );
                strcat( skill_list[level], buf );
            }
        }
    }

    /*
     * return results 
     */

    if ( !found )
    {
        ch_printf( ch, "No skills found.\r\n" );
        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, "\r\n" );
    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 )
{
    int                     gn = -1;
    int                     sn = -1;
    int                     col = 0;

    if ( IS_NPC( ch ) )
        return;

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

    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->iclass] > 0 )
        {
            ch_printf( ch, "%-18s %-5d ", group_table[gn].name,
                       group_table[gn].rating[ch->iclass] );
            if ( ++col % 3 == 0 )
                ch_printf( ch, "\r\n" );
        }
    }
    if ( col % 3 != 0 )
        ch_printf( ch, "\r\n" );
    ch_printf( ch, "\r\n" );

    col = 0;

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

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

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

void list_group_chosen( CHAR_DATA *ch )
{
    int                     gn = -1;
    int                     sn = -1;
    int                     col = 0;

    if ( IS_NPC( ch ) )
        return;

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

    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->iclass] > 0 )
        {
            ch_printf( ch, "%-18s %-5d ", group_table[gn].name,
                       group_table[gn].rating[ch->iclass] );
            if ( ++col % 3 == 0 )
                ch_printf( ch, "\r\n" );
        }
    }
    if ( col % 3 != 0 )
        ch_printf( ch, "\r\n" );
    ch_printf( ch, "\r\n" );

    col = 0;

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

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

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

int exp_per_level( CHAR_DATA *ch, int points )
{
    int                     expl = 1000;
    int                     inc = 500;

    if ( IS_NPC( ch ) )
        return 1000;

    if ( points < 40 )
        return 1000 * ( pc_race_table[ch->race].class_mult[ch->iclass] ?
                        pc_race_table[ch->race].class_mult[ch->iclass] / 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 * pc_race_table[ch->race].class_mult[ch->iclass] / 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] = "\0\0\0\0\0\0\0";
    int                     gn = -1;
    int                     sn = -1;
    int                     i = 0;

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

    argument = one_argument( argument, arg );

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

        do_function( ch, &do_help, argument );
        return true;
    }

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

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

            if ( group_table[gn].rating[ch->iclass] < 1 )
            {
                ch_printf( ch, "That group is not available.\r\n" );
                return true;
            }

            /*
             * Close security hole 
             */
            if ( ch->gen_data->points_chosen + group_table[gn].rating[ch->iclass] > 300 )
            {
                ch_printf( ch, "You cannot take more than 300 creation points.\r\n" );
                return true;
            }

            ch_printf( ch, "%s group added\r\n", group_table[gn].name );
            ch->gen_data->group_chosen[gn] = true;
            ch->gen_data->points_chosen += group_table[gn].rating[ch->iclass];
            gn_add( ch, gn );
            ch->pcdata->points += group_table[gn].rating[ch->iclass];
            return true;
        }

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

            if ( skill_table[sn].rating[ch->iclass] < 1
                 || skill_table[sn].spell_fun != spell_null )
            {
                ch_printf( ch, "That skill is not available.\r\n" );
                return true;
            }

            /*
             * Close security hole 
             */
            if ( ch->gen_data->points_chosen + skill_table[sn].rating[ch->iclass] > 300 )
            {
                ch_printf( ch, "You cannot take more than 300 creation points.\r\n" );
                return true;
            }
            ch_printf( ch, "%s skill added\r\n", skill_table[sn].name );
            ch->gen_data->skill_chosen[sn] = true;
            ch->gen_data->points_chosen += skill_table[sn].rating[ch->iclass];
            ch->pcdata->learned[sn] = 1;
            ch->pcdata->points += skill_table[sn].rating[ch->iclass];
            return true;
        }

        ch_printf( ch, "No skills or groups by that name...\r\n" );
        return true;
    }

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

        gn = group_lookup( argument );
        if ( gn != -1 && ch->gen_data->group_chosen[gn] )
        {
            ch_printf( ch, "Group dropped.\r\n" );
            ch->gen_data->group_chosen[gn] = false;
            ch->gen_data->points_chosen -= group_table[gn].rating[ch->iclass];
            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->iclass];
            return true;
        }

        sn = skill_lookup( argument );
        if ( sn != -1 && ch->gen_data->skill_chosen[sn] )
        {
            ch_printf( ch, "Skill dropped.\r\n" );
            ch->gen_data->skill_chosen[sn] = false;
            ch->gen_data->points_chosen -= skill_table[sn].rating[ch->iclass];
            ch->pcdata->learned[sn] = 0;
            ch->pcdata->points -= skill_table[sn].rating[ch->iclass];
            return true;
        }

        ch_printf( ch, "You haven't bought any such skill or group.\r\n" );
        return true;
    }

    if ( !str_prefix( arg, "premise" ) )
    {
        do_function( ch, &do_help, "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 */
void do_groups( CHAR_DATA *ch, const char *argument )
{
    int                     gn = -1;
    int                     sn = -1;
    int                     col = 0;

    if ( IS_NPC( ch ) )
        return;

    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] )
            {
                ch_printf( ch, "%-20s ", group_table[gn].name );
                if ( ++col % 3 == 0 )
                    ch_printf( ch, "\r\n" );
            }
        }
        if ( col % 3 != 0 )
            ch_printf( ch, "\r\n" );
        ch_printf( ch, "Creation points: %d\r\n", ch->pcdata->points );
        return;
    }

    if ( !str_cmp( argument, "all" ) )                 /* show all groups */
    {
        for ( gn = 0; gn < MAX_GROUP; gn++ )
        {
            if ( group_table[gn].name == NULL )
                break;
            ch_printf( ch, "%-20s ", group_table[gn].name );
            if ( ++col % 3 == 0 )
                ch_printf( ch, "\r\n" );
        }
        if ( col % 3 != 0 )
            ch_printf( ch, "\r\n" );
        return;
    }

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

    for ( sn = 0; sn < MAX_IN_GROUP; sn++ )
    {
        if ( group_table[gn].spells[sn] == NULL )
            break;
        ch_printf( ch, "%-20s ", group_table[gn].spells[sn] );
        if ( ++col % 3 == 0 )
            ch_printf( ch, "\r\n" );
    }
    if ( col % 3 != 0 )
        ch_printf( ch, "\r\n" );
}

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

    if ( IS_NPC( ch ) )
        return;

    if ( ch->level < skill_table[sn].skill_level[ch->iclass]
         || skill_table[sn].rating[ch->iclass] == 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->iclass] * 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 )
        {
            ch_printf( ch, "You have become better at %s!\r\n", skill_table[sn].name );
            ch->pcdata->learned[sn]++;
            gain_exp( ch, 2 * skill_table[sn].rating[ch->iclass] );
        }
    }

    else
    {
        chance = URANGE( 5, ch->pcdata->learned[sn] / 2, 30 );
        if ( number_percent(  ) < chance )
        {
            ch_printf( ch,
                       "You learn from your mistakes, and your %s skill improves.\r\n",
                       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_table[sn].rating[ch->iclass] );
        }
    }
}

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

    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 = 0;

    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 = 0;

    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 = -1;
    int                     gn = -1;

    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->iclass];
        }
        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->iclass];
        }
        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 = -1;
    int                     gn = -1;

    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 */
    }
}