
This snippet allows the removal of mana from the mud and the use of
memorization to accomplish spell_casting.  The concept it uses is that of
circles of power.  Each spell has a certain power "rating", which is referred
to as a circle.  The circle rating of a spell decides when the spell can be
memorized (what level).  The number of spells of a given level that can be
memorized also changes as the player's level increases.  Mobs and immortals
do not have to memorize a spell to be able to cast it.  We allow only mages
and clerics to cast spells, so I have checks for that (two macros, IS_MAGE and
IS_CLERIC).  If you want warriors and thieves to be able to cast spells too,
just take out those checks.  If you have any problems, feel free to email
me (the address is below) and let me know of them, and I'll try and help.

Use this code at your own risk.  I make no guarantees about anything. :)
Also, it should be used according to the diku/merc/ROM copywrite licenses.
If you use it, I would appreciate appropriate credit within the code (leaving
the headers to functions, etc) as well as in any help files you have to give
credit to writers of snippets.  Also, please email me to let me know that
you're using it at the following address: amaslin@hotmail.com.  Thanks!

Because the Tobril of Krynn is still in pre-alpha testing, we don't have a
lot of players on to test new stuff out.  This has been tested as much as
we can to make sure it is working properly, but we may have overlooked
something that only full-fledged use would bring to light.  Most problems
would probably be in the form of needing to tweak some numbers, such as
the number of spells that a given level can memorize of a given circle.
If you find any problems, PLEASE email them to me so I can fix them in
the snippet for others!!!

Any questions, comments, suggestions, or bug reports (please sent NICELY)
should also be sent to amaslin@hotmail.com.

Thale (Andrew Maslin - amaslin@hotmail.com)



-------------------------------
Ok, this is long and involved, tedious, and sometimes a tad complicated to
boot. :)  I'll try and be as clear as possible.

Syntax of commands found in this snippet:
MEMORIZE
   * This will show all spells that are already memorized
MEMORIZE <spell name>
   * This will attempt to memorize a spell
FORGET <spell name>
   * This will forget a spell that has been memorized, allow room for a
     different spell to be memorized.
SPELLS MEMORIZE (or just SPELLS MEM)
   * This will show how long you have to go to finish memorizing any
     spells you are in the process of memorizing.


***in merc.h***
Thanks to Erwin for suggesting the unsigned ints below in the spell_mem_data
struct declaration to save memory usage.

right after this line:
typedef struct  mprog_code              MPROG_CODE;

add:
typedef struct  spell_mem_data          SPELL_MEM_DATA;


where various structures are defined such as:
/* memory for mobs */
struct mem_data
{
    MEM_DATA    *next;
    bool        valid;
    int         id;
    int         reaction;
    time_t      when;
};

put this:
/* individual memorize data for spells */
struct spell_mem_data
{
    unsigned int   sn: 10;    /* sn of the spell - place # in skill_table */
    unsigned int   ticks: 10; /* number of ticks until the spell is learned */
    bool           good_mem;  /* TRUE = memorized correctly.  If FALSE, spell
                               * will fail when the spell is cast.
                               */
};

Now find a place that's convenient in merc.h (I put it right after Hero, Avatar
, etc is defined) and add this:
/*
 * Skill/Spell circle level definitions
 */
#define MAX_CIRCLE_LEVEL        16   /* maximum # of circles */
#define MC                      MAX_CIRCLE_LEVEL
/* Max spells per circle */
#define MAX_PER_CIRCLE          10
/*
 * Max spells ever memorizable for each circle.
 * Must never be higher than MAX_PER_CIRCLE!
 */
#define MAX_CIRCLE1             MAX_PER_CIRCLE
#define MAX_CIRCLE2             MAX_PER_CIRCLE
#define MAX_CIRCLE3             (MAX_PER_CIRCLE - 1)
#define MAX_CIRCLE4             (MAX_PER_CIRCLE - 2)
#define MAX_CIRCLE5             (MAX_PER_CIRCLE - 2)
#define MAX_CIRCLE6             (MAX_PER_CIRCLE - 3)
#define MAX_CIRCLE7             (MAX_PER_CIRCLE - 3)
#define MAX_CIRCLE8             (MAX_PER_CIRCLE - 4)
#define MAX_CIRCLE9             (MAX_PER_CIRCLE - 5)
#define MAX_CIRCLE10            (MAX_PER_CIRCLE - 5)
#define MAX_CIRCLE11            (MAX_PER_CIRCLE - 5)
#define MAX_CIRCLE12            (MAX_PER_CIRCLE - 6)
#define MAX_CIRCLE13            (MAX_PER_CIRCLE - 6)
#define MAX_CIRCLE14            (MAX_PER_CIRCLE - 7)
#define MAX_CIRCLE15            (MAX_PER_CIRCLE - 7)
/* only special skills/spells in circle16 */
#define MAX_CIRCLE16            (MAX_PER_CIRCLE - 8)


Look for the skill_type structure definition.  The first few lines looks like
this:
/*
 * Skills include spells as a particular case.
 */
struct  skill_type

and at the end, add this:
    sh_int      circle[MAX_CLASS];      /* Circle it belongs to         */


Find POS_SLEEPING and after it add this:
#define POS_MEMORIZE                  5

Don't forget to increment the positions that come after it so that you don't
have two positions with the same definitions!

then, in struct pc_data, right after this:
    char *              title;

add this:
    SPELL_MEM_DATA      circles[MC][MAX_PER_CIRCLE];

I use 2 macros in my code, IS_MAGE and IS_CLERIC.  If you don't have these
already defined, add these lines in your macro section (where IS_NPC is
located):

#define IS_CLERIC(ch)   (ch->class == class_lookup("cleric"))
#define IS_MAGE(ch)     (ch->class == class_lookup("mage"))


Ok, now do a search for magic.c (where functions are defined).  At the bottom
of the definitions for magic.c, add this:
void    list_mem_spells args((CHAR_DATA *ch, CHAR_DATA *victim));

I defined it there for a future immort command I plan to make (but isn't
written yet) - I'll post it on my web page when I get it finished.

***lookup.h***
in lookup.h, add these lines:

int     find_circle_slot args((CHAR_DATA *ch, int search_circle));
int     find_mem_spell  args((CHAR_DATA *ch,int sn));
int     max_mem_circle_lookup args((CHAR_DATA *ch, int circle));

***lookup.c***
in lookup.c, add these functions:

/*
 * Find the maximum spells one can memorize in a given circle
 * Thale (7/10/98)
 */
int max_mem_circle_lookup(CHAR_DATA *ch,int circle)
{
  int x, max = 0;

  /* find the max spells in search_circle this person can memorize */
  for (x = 0; max_mem_table[x].level < MAX_LEVEL; x++)
  {
    if (ch->level >= max_mem_table[x].level)
      max = max_mem_table[x].circle_max[circle];
  }

  return max;
}


/*
 * Finds an empty memorization slot in the circle of search_circle.
 * Thale (7/10/98)
 */
int find_circle_slot(CHAR_DATA *ch,int search_circle)
{
  int x, max_spells = 0;

  if (IS_NPC(ch))
  {
    send_to_char("NPCs can't learn spells.\n\r",ch);
    return -2;
  }

  max_spells = max_mem_circle_lookup(ch,search_circle);

  /* can't memorize any spells of that level */
  if (max_spells == 0)
    return -2;

  /* find an empty slot to memorize the spell */
  for (x = 0; x < max_spells; x++)
  {
    if (ch->pcdata->circles[search_circle][x].sn < 1)
      return x;
  }

  return -1;
}


/*
 * Find the slot a spell is memorized in
 * Thale (7/10/98)
 */
int find_mem_spell(CHAR_DATA *ch,int sn)
{
  int circle, max_spells, x;

  if (IS_NPC(ch))
  {
    send_to_char("NPCs do not memorize spells.\n\r",ch);
    return 0;
  }

  /* non-usable or non-existent spells */
  if (sn < 1)
    return -1;

  /*
   * Circle is set to (table - 1) because of difference between
   * logic and the first value of arrays.
   */
  circle = (skill_table[sn].circle[ch->class] - 1);
  max_spells = max_mem_circle_lookup(ch,circle);

  if (max_spells == 0)
  {
    send_to_char("There's no way you could know spells of that level!\n\r",ch);
    return -1;
  }

  /* find where the spell is memorized*/
  for (x = 0; x < max_spells; x++)
  {
    if (ch->pcdata->circles[circle][x].sn == sn )
      return x;
  }

  return -1;
}


***interp.c***
in interp.c, add these lines in the command definitions section:

    { "memorize",       do_memorize,    POS_MEMORIZE,    0,  LOG_NORMAL, 1 },
    { "forget",         do_forget,      POS_MEMORIZE,    0,  LOG_NORMAL, 1 },

In the void interpret function, add these lines right after the POS_SLEEPING
section:

        case POS_MEMORIZE:
            send_to_char( "No way!  You'd lose your concentration!\n\r",ch);
            break;

Now you'll need to go through and decide which commands you want your
players to have while their memorizing.  Supposedly these commands will
not disrupt or distract a character and thus hinder their memorization
attempts.  I do suggest allowing them to eat and drink, since they lose
any spells not already memorized if they get hungry or thirsty.  Besides,
how many of you much on things while trying to learn something? :)



***act_move.c***
Ok, now in act_move, you need to edit do_sit, do_stand, do_sleep, and do_rest.
In do_sit, do_stand, and do_sleep, find the section that says POS_RESTING and
copy it (so you have two of those parts), then rename the first one (the one
immediately following the POS_SLEEPING section) to POS_MEMORIZE:  In do_rest,
find the POS_SITTING section and copy that in front of the POS_RESTING section
(thus immediately after POS_SLEEPING) and rename the copy to POS_MEMORIZE.

Now, in each of these functions, immediately after the POS_MEMORIZE line (and
the starting bracket), add this:

            if (!IS_NPC(ch))
            {
              send_to_char("You stop studying in frustration.\n\r",ch);
              act("$n closes a spellbook in frustration.",ch,NULL,NULL,TO_ROOM)
;
              for(x = 0; x < MAX_CIRCLE_LEVEL; x++)
                for (y = 0; y < MAX_PER_CIRCLE; y++)
                {
                  if (ch->pcdata->circles[x][y].ticks > 0)
                  {
                    ch->pcdata->circles[x][y].sn = 0;
                    ch->pcdata->circles[x][y].ticks = 0;
                    ch->pcdata->circles[x][y].good_mem = FALSE;
                    stopped = TRUE;
                  }
                }
              if (stopped)
              {
                send_to_char("You close your spellbook in frustration.\n\r",ch)
;
                act("$n closes $s spellbook in frustration.",ch,NULL,NULL,TO_RO
OM);
              }
            }

You'll also need to declare the x and y variables (as integers) at the very
beginning of each of those functions, as well as the stopped variable, like so:
    int x, y;
    bool stopped = FALSE;

***tables.h***
In tables.h, you need to add the following lines after the flag tables:

/*
 * Circle (memorized spells) tables
 * By Thale (Andrew Maslin) - amaslin@hotmail.com
 */
extern  const   struct  max_mem_type max_mem_table[];
extern  const   struct  circle_wait_type circle_wait_table[];

Elsewhere in the file, add these table definitions:

struct max_mem_type
{
    sh_int level;
    sh_int circle_max[MAX_CIRCLE_LEVEL];
};

struct circle_wait_type
{
    sh_int  level;
    sh_int  mem_wait;

};


***tables.c***
First find the position_table.  Immediately following the "sleeping" entry,
add this:

    {   "memorizing",           "mem"   },

Then somewhere in tables.c, add this long section:

/* Max spells memorizable per level in format for max_mem_table */
#define MC1                     MAX_CIRCLE1
#define MC2                     MAX_CIRCLE2
#define MC3                     MAX_CIRCLE3
#define MC4                     MAX_CIRCLE4
#define MC5                     MAX_CIRCLE5
#define MC6                     MAX_CIRCLE6
#define MC7                     MAX_CIRCLE7
#define MC8                     MAX_CIRCLE8
#define MC9                     MAX_CIRCLE9
#define MC10                    MAX_CIRCLE10
#define MC11                    MAX_CIRCLE11
#define MC12                    MAX_CIRCLE12
#define MC13                    MAX_CIRCLE13
#define MC14                    MAX_CIRCLE14
#define MC15                    MAX_CIRCLE15
#define MC16                    MAX_CIRCLE16 /* only special skills/spells */

/*
 * Shows the maximum spells a player of the given level or higher can
 * memorize in any given circle.
 * Thale (Andrew Maslin)
 */
const   struct  max_mem_type  max_mem_table []  =
{
  {1,  {4,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0}},
  {6,  {5,3,1,0,0,0,0,0,0,0,0,0,0,0,0,0}},
  {11, {6,5,2,1,0,0,0,0,0,0,0,0,0,0,0,0}},
  {16, {6,6,4,3,1,0,0,0,0,0,0,0,0,0,0,0}},
  {21, {7,6,5,4,2,1,0,0,0,0,0,0,0,0,0,0}},
  {26, {8,7,6,4,3,2,1,0,0,0,0,0,0,0,0,0}},
  {31, {8,7,7,4,4,3,3,1,0,0,0,0,0,0,0,0}},
  {36, {9,8,7,6,5,4,3,2,1,0,0,0,0,0,0,0}},
  {41, {MC1,9,8,6,6,5,4,3,2,1,0,0,0,0,0,0}},
  {46, {MC1,MC2,8,7,6,5,5,4,3,2,1,0,0,0,0,0}},
  {51, {MC1,MC2,MC3,MC4,6,6,5,5,4,3,3,1,0,0,0,0}},
  {56, {MC1,MC2,MC3,MC4,MC5,MC6,MC7,5,4,4,3,2,1,0,0,0}},
  {61, {MC1,MC2,MC3,MC4,MC5,MC6,MC7,MC8,MC9,MC10,4,2,2,1,0,0}},
  {66, {MC1,MC2,MC3,MC4,MC5,MC6,MC7,MC8,MC9,MC10,MC11,MC12,3,2,1,0}},
  {71, {MC1,MC2,MC3,MC4,MC5,MC6,MC7,MC8,MC9,MC10,MC11,MC12,MC13,MC14,MC15,0}},
  {76, {MC1,MC2,MC3,MC4,MC5,MC6,MC7,MC8,MC9,MC10,MC11,MC12,MC13,MC14,MC15,MC16}
},
  {77, {MC1,MC2,MC3,MC4,MC5,MC6,MC7,MC8,MC9,MC10,MC11,MC12,MC13,MC14,MC15,MC16}
},
  {(MAX_LEVEL + 1), {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}}
};

#undef  MC1
#undef  MC2
#undef  MC3
#undef  MC4
#undef  MC5
#undef  MC6
#undef  MC7
#undef  MC8
#undef  MC9
#undef  MC10
#undef  MC11
#undef  MC12
#undef  MC13
#undef  MC14
#undef  MC15
#undef  MC16


/*
 * This table holds the number of ticks necessary to memorize a spell
 * in a given circle.  The value shown is used when memorizing a single spell.
 * When memorizing multiple spells every spell after the first one started
 * requires 2 additional ticks to complete.
 * Thale (Andrew Maslin)
 */
const   struct  circle_wait_type  circle_wait_table []  =
{
  {1,  1},
  {2,  2},
  {3,  2},
  {4,  2},
  {5,  3},
  {6,  3},
  {7,  4},
  {8,  4},
  {9,  5},
  {10, 5},
  {11, 6},
  {12, 6},
  {13, 7},
  {14, 7},
  {15, 8},
  {16, 9},
  {0, 0}     /* included for a stopping point for possible future searches */
};


***magic.c***
Somewhere in magic.c, add these functions:

/*
 * Memorize command is for memorizing a spell for casting.
 */
void do_memorize(CHAR_DATA *ch, char *argument)
{
  int put_here, circ_level;
  char arg[MAX_INPUT_LENGTH];
  int sn = 0, chance = 0;
  bool already_memming = FALSE;
  int x = 0, y = 0;

  /*
   * NPCs can't memorize spells.
   */
  if (IS_NPC(ch))
  {
    send_to_char("NPCs can't memorize spells!\n\r",ch);
    return;
  }

  /*
   * Only mages and clerics can cast, and hence memorize, spells.
   */
  if (!IS_MAGE(ch) && !IS_CLERIC(ch))
  {
    send_to_char("You want to try what?\n\r",ch);
    return;
  }

  one_argument(argument,arg);

  if (arg[0] == '\0')
  {
    list_mem_spells(ch,ch);
    return;
  }

  if ((ch->position > POS_SITTING) && !IS_IMMORTAL(ch))
  {
    send_to_char("You must be in a more comfortable position than you are right
 now.\n\r",ch);
    return;
  }

  sn = find_spell(ch,arg);
  if (sn < 1 || skill_table[sn].spell_fun == spell_null)
  {
    send_to_char("You've never heard of a spell that does that.\n\r",ch);
    return;
  }

  if (ch->pcdata->learned[sn] < 1) /* skill is not known */
  {
    send_to_char("You don't know anything about that spell.\n\r",ch);
    return;
  }

  circ_level = skill_table[sn].circle[ch->class] - 1;
  put_here = find_circle_slot(ch,circ_level);

  /*
   * Bail out if spell too high for them.
   */
  if (put_here < -1)
  {
    send_to_char("You are not sufficiently advanced to memorize a spell of that
 power.\n\r",ch);
    return;
  }
  /*
   * Bail out if max spells for that circle reached.
   */
  if (put_here == -1)
  {
    send_to_char("You can't memorize any more spells of that circle.\n\r",ch);
    return;
  }

  /*
   * Are they successful with the spell?
   */
  chance = number_percent();
  if (IS_MAGE(ch))
  {
    if (get_curr_stat(ch,STAT_INT) >= 15)
      chance -= (get_curr_stat(ch,STAT_INT) - 15) * 2;
    else
      chance -= (get_curr_stat(ch,STAT_INT) - 15) * 4;
    chance -= get_curr_stat(ch,STAT_WIS) - 13;
  }
  else if (IS_CLERIC(ch))
  {
    if (get_curr_stat(ch,STAT_WIS) >= 15)
      chance -= (get_curr_stat(ch,STAT_WIS) - 15) * 2;
    else
      chance -= (get_curr_stat(ch,STAT_WIS) - 15) * 4;
    chance -= get_curr_stat(ch,STAT_INT) - 13;
  }

  ch->pcdata->circles[circ_level][put_here].sn = sn;
  /*
   * If the player is not an immortal, set the ticks needed to memorize.
   * If an immortal, then make sure the spell is instantly (ticks set to 0).
   */
  if (IS_IMMORTAL(ch))
  {
    ch->pcdata->circles[circ_level][put_here].ticks = 0;
    send_to_char("Done.  Easy, huh?\n\r",ch);
  }
  else
  {
    for (x = 0; x < MAX_CIRCLE_LEVEL; x++)
      for (y = 0; y < MAX_PER_CIRCLE; y++)
      {
        /* Count how many other spells are being memorized */
        if (ch->pcdata->circles[x][y].ticks > 0)
          count++;
      }

    /* Set the time needed to memorize if not previously memorizing */
    if (count == 0)
      ch->pcdata->circles[circ_level][put_here].ticks = circle_wait_table[circ_
level].mem_wait;
    else /* time needed increases as the number being memorized increases */
      ch->pcdata->circles[circ_level][put_here].ticks = circle_wait_table[circ_
level].mem_wait + 2 + (count - 1);
  }

  if (chance > get_skill(ch,sn))
  ch->pcdata->circles[circ_level][put_here].good_mem = FALSE;
  else
  ch->pcdata->circles[circ_level][put_here].good_mem = TRUE;

  ch->position = POS_MEMORIZE;

  return;
}


/*
 * This is a separate function for clarity within the code.
 * I pass both a character and a victim (to allow use by imms
 * in later functions).
 */
void list_mem_spells(CHAR_DATA *ch, CHAR_DATA *victim)
{
  int x = 0, y = 0;
  int count = 0;
  int total = 0;
  char buf[MAX_INPUT_LENGTH];
  char buf1[MAX_INPUT_LENGTH];
  BUFFER *buffer;

  /* to prevent bugs or crashes */
  if (ch == NULL)
    ch = victim;
  if (victim == NULL && ch != NULL)
    victim = ch;
  else if (victim == NULL)
  {
    bug("NULL victim in list_mem_spells",0);
    return;
  }

  if (IS_NPC(ch))
  {
    send_to_char("NPCs can't memorize spells.\n\r",ch);
    return;
  }

  buffer = new_buf();

  for (x = 0; x < MAX_CIRCLE_LEVEL; x++)
  {
    count = 0;
    sprintf(buf1,"   ");

    for (y = 0; y < MAX_PER_CIRCLE; y++)
    {
      if ((ch->pcdata->circles[x][y].sn > 0)
       && (ch->pcdata->circles[x][y].ticks == 0))
      {
        count++;
        total++;
        strcat(buf1,skill_table[ch->pcdata->circles[x][y].sn].name);
        strcat(buf1," ");
      }
    }

    /* send the memorized spells */
    if (count > 0)
    {
      sprintf(buf,"{RC{rircle {R%d{x\n\r",(x + 1));
      add_buf(buffer,buf);
      add_buf(buffer,buf1);
      /* send 2 line breaks for spacing */
      add_buf(buffer,"\n\r\n\r");
    }
  }

  if (total == 0)
    add_buf(buffer,"You have {gno spells{x memorized.\n\r");
  page_to_char(buf_string(buffer),ch);
  free_buf(buffer);

  return;
}


/* forget a spell  that has been memorized */
void do_forget(CHAR_DATA *ch, char *argument)
{
  char arg[MAX_INPUT_LENGTH];
  int circle,slot,sn;

  if (IS_NPC(ch))
  {
    send_to_char("How do you expect to forget something you can't memorize in t
he first place?\n\r",ch);
    return;
  }

  one_argument(argument,arg);

  if (arg[0] == '\0')
  {
    send_to_char("You want to forget what?  Can you remember?\n\r",ch);
    return;
  }

  sn = skill_lookup(arg);
  /*
   * Circle is set to (table - 1) because of difference between
   * logic and the first value of arrays.
   */
  circle = (skill_table[sn].circle[ch->class] - 1);
  slot = find_mem_spell(ch,sn);

  if ((sn < 1) || (slot < 0))
  {
    send_to_char("You have not memorized anything like that.\n\r",ch);
    return;
  }

  if (!IS_IMMORTAL(ch))
    WAIT_STATE(ch,PULSE_VIOLENCE);

  ch->pcdata->circles[circle][slot].sn = 0;
  ch->pcdata->circles[circle][slot].ticks = 0;
  ch->pcdata->circles[circle][slot].good_mem = FALSE;

  send_to_char("Whatever it was is gone now....\n\r",ch);

  return;
}

In do_cast, find the section that looks like this:
    if ((sn = find_spell(ch,arg1)) < 1
    ||  skill_table[sn].spell_fun == spell_null
    || (!IS_NPC(ch) && (ch->level < skill_table[sn].skill_level[ch->class]
    ||                   ch->pcdata->learned[sn] == 0)))
    {
        send_to_char( "You don't know any spells of that name.\n\r", ch );
        return;
    }

and replace it with this:
    if ((sn = find_spell(ch,arg1)) < 1
    ||  skill_table[sn].spell_fun == spell_null
    || (!IS_NPC(ch)
    && ((slot = find_mem_spell(ch,sn)) < 0) && !IS_IMMORTAL(ch)))
    {
        send_to_char( "You have not memorized such a spell.\n\r", ch );
        return;
    }

    /* 
     * Circle is set to (table - 1) because of difference between
     * logic and the first value of arrays.
     */
    if (!IS_NPC(ch))
      circle = (skill_table[sn].circle[ch->class] - 1);


Now at the variable-declaration section at the top of do_cast, add this:
    int circle = -1;
    int slot = -1;

Next find this and delete it (no more mana):
    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] ) );

Now find this and delete it as well:
    if ( !IS_NPC(ch) && ch->mana < mana )
    {
        send_to_char( "You don't have enough mana.\n\r", ch );
        return;
    }

Now find this:

    if ( 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 (IS_NPC(ch) || class_table[ch->class].fMana)
        /* class has spells */
            (*skill_table[sn].spell_fun) ( sn, ch->level, ch, vo,target);
        else
            (*skill_table[sn].spell_fun) (sn, 3 * ch->level/4, ch, vo,target);
        check_improve(ch,sn,TRUE,1);
    }

Replace it with this:

    if (IS_NPC(ch)
     && number_percent() > get_skill(ch,sn) )
    {
        send_to_char( "You lost your concentration.\n\r", ch );
    }
    else if (IS_NPC(ch))
      (*skill_table[sn].spell_fun) ( sn, ch->level, ch, vo,target);
    else
    {
      if (ch->pcdata->circles[circle][slot].good_mem || IS_IMMORTAL(ch))
      {
        (*skill_table[sn].spell_fun) ( sn, ch->level, ch, vo,target);
        check_improve(ch,sn,TRUE,1);
      }
      else
      {
        if ((chance = number_percent()) < 51)
          send_to_char("You lost your concentration.\n\r",ch);
        else if (chance < 81)
          send_to_char("You mispronounced a syllable!\n\r",ch);
        else
          send_to_char("Your mind just went blank!\n\r",ch);

        check_improve(ch,sn,FALSE,1);
      }
    }

    if (!IS_NPC(ch))
    {
      if ((IS_IMMORTAL(ch)
       && (slot >= 0) && (circle >= 0) && (sn > 0))
       || !IS_IMMORTAL(ch))
      {
      	ch->pcdata->circles[circle][slot].sn = 0;
      	ch->pcdata->circles[circle][slot].ticks = 0;
      	ch->pcdata->circles[circle][slot].good_mem = FALSE;
      }
    }


***save.c***

in fwrite_char, define the integer variable 'y'.  Replace the first line below
with the second line:

    int sn, gn, pos;

    int sn, gn, pos, y;

Now find the condition section that looks like this:
        fprintf( fp, "Cnd  %d %d %d %d\n",
            ch->pcdata->condition[0],
            ch->pcdata->condition[1],
            ch->pcdata->condition[2],
            ch->pcdata->condition[3] );

And after it add this:

        /* write memorized spells */
        for (pos = 0; pos < MAX_CIRCLE_LEVEL; pos++)
          for (y = 0; y < MAX_PER_CIRCLE; y++)
          {
            if (ch->pcdata->circles[pos][y].sn < 1)
              continue;

            fprintf(fp, "Circ %d %d~\n",
              ch->pcdata->circles[pos][y].sn,
              ch->pcdata->circles[pos][y].good_mem ? 1 : 0);
          }

Now in load_char_obj add this.  I put it after the security line, which looks
like this:
    ch->pcdata->security                = 0;    /* OLC */

    for (x = 0; x < MAX_CIRCLE_LEVEL; x++)
      for (y = 0; y < MAX_PER_CIRCLE; y++)
      {
        ch->pcdata->circles[x][y].sn = 0;
        ch->pcdata->circles[x][y].ticks = 0;
        ch->pcdata->circles[x][y].good_mem = FALSE;
      }

You'll also need to define the integer variables x and y at the beginning
of the load_char_obj function.

Now in fread_char, find the 'C' section.  It should start with a line like
this:
        case 'C':

Scroll down until you come to the section beginning with this line:
            if (!str_cmp(word,"Cnd"))

After that section add this:

            if (!str_cmp( word, "Circ"))
            {
              int sn, circle, slot, memmd;
              char *arg;
              char arg1[MAX_INPUT_LENGTH];
              char arg2[MAX_INPUT_LENGTH];

              arg = fread_string( fp );
              arg = one_argument(arg,arg1);
              arg = one_argument(arg,arg2);

              if (!is_number(arg1))
              {
                bug("Circ pfile: non-numeric sn #",0);
                break;
              }
              if (!is_number(arg2))
              {
                bug("Circ pfile: non-numeric good_mem indicator",0);
                break;
              }

              sn = atoi(arg1);
              /*
               * Circle is set to (table - 1) because of difference between
               * logic and the first value of arrays.
               */
              circle = (skill_table[sn].circle[ch->class] - 1);
              slot = find_circle_slot(ch,circle);

              if (slot < 0)
              {
                bug("Circle overflow with sn %d",sn);
                break;
              }

              memmd = atoi(arg2);
              ch->pcdata->circles[circle][slot].sn = sn;
              ch->pcdata->circles[circle][slot].ticks = 0;
              if (memmd > 0)
                ch->pcdata->circles[circle][slot].good_mem = TRUE;
              else
                ch->pcdata->circles[circle][slot].good_mem = FALSE;
        
                fMatch = TRUE;
                break;
            }


***skills.c***
This section will have to be changed slightly for your mud, most likely.
We are taking out level restrictions and using the circle level restrictions
instead.  If you are using memorization to replace mana but keeping
customization and the use of levels to limit when someone gets to practice a
skill/spell, then instead of using do_spells and do_skills below to replace
the current do_spells and do_skills you'll probably want to just add these
as functions of different names.  You might also leave them out entirely
and just get the one change from do_spells (which I'll include separate
for clarity) in your do_spells.  Whichever way you do it, you'll need to
add the new function.

Find the do_spells and do_skills functions.  Delete them and replace them with
these two versions of them.  And yes, the the changes are fairly minor (if you
decide to compare them).

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_circ = 1, max_circ = MAX_CIRCLE_LEVEL;
    bool fAll = FALSE, found = FALSE;
    char buf[MAX_STRING_LENGTH];

    if (IS_NPC(ch))
      return;

    if (argument[0] != '\0')
    {
        if (!str_prefix(argument,"memorizing"))
        {
          show_mem_times(ch);
          return;
        }

        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_circ = atoi(arg);

            if (max_circ < 1 || max_circ > MAX_CIRCLE_LEVEL)
            {
                sprintf(buf,"Levels must be between 1 and %d.\n\r",MAX_CIRCLE_L
EVEL);
                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_circ = max_circ;
                max_circ = atoi(arg);

                if (max_circ < 1 || max_circ > MAX_CIRCLE_LEVEL)
                {
                    sprintf(buf,
                        "Levels must be between 1 and %d.\n\r",MAX_CIRCLE_LEVEL
);
                    send_to_char(buf,ch);
                    return;
                }

                if (min_circ > max_circ)
                {
                    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].circle[ch->class]) < MAX_CIRCLE_LEVEL
        &&  (fAll || max_mem_circle_lookup(ch,level) > 0)
        &&  level >= min_circ && level <= max_circ
        &&  skill_table[sn].spell_fun != spell_null
        &&  ch->pcdata->learned[sn] > 0)
        {
            found = TRUE;
            level = skill_table[sn].circle[ch->class];
            sprintf(buf,"%-18s          ", skill_table[sn].name);

            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 < MAX_CIRCLE_LEVEL; 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_circ = 1, max_circ = MAX_CIRCLE_LEVEL;
    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_circ = atoi(arg);

            if (max_circ < 1 || max_circ > MAX_CIRCLE_LEVEL)
            {
                sprintf(buf,"Levels must be between 1 and %d.\n\r",MAX_CIRCLE_L
EVEL);
                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_circ = max_circ;
                max_circ = atoi(arg);

                if (max_circ < 1 || max_circ > MAX_CIRCLE_LEVEL)
                {
                    sprintf(buf,
                        "Levels must be between 1 and %d.\n\r",MAX_CIRCLE_LEVEL
);
                    send_to_char(buf,ch);
                    return;
                }

                if (min_circ > max_circ)
                {
                    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].circle[ch->class]) < MAX_CIRCLE_LEVEL
        &&  (fAll || max_mem_circle_lookup(ch,level) > 0)
        &&  level >= min_circ && level <= max_circ
        &&  skill_table[sn].spell_fun == spell_null
        &&  ch->pcdata->learned[sn] > 0)
        {
            found = TRUE;
            level = skill_table[sn].circle[ch->class];
            if (max_mem_circle_lookup(ch,level) < 1)
                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 < MAX_CIRCLE_LEVEL; 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);
}


Now add this function somewhere in the file:

/* Shows the amount of time left before spells are memorized */
void show_mem_times(CHAR_DATA *ch)
{
  int x, y, j;
  SPELL_MEM_DATA *newc = NULL, *temp = NULL;
  SPELL_MEM_DATA *spells[MAX_CIRCLE_LEVEL * MAX_PER_CIRCLE] = {NULL};
  char buf[MAX_STRING_LENGTH];
  bool memming = FALSE;

  if (IS_NPC(ch))
  {
    send_to_char("NPCs can't memorize spells!",ch);
    return;
  }

  if (ch->position != POS_MEMORIZE)
  {
    send_to_char("You should be able to tell if you're memorizing something or
not...\n\r",ch);
    return;
  }

  for (x = 0; x < MAX_CIRCLE_LEVEL; x++)
    for (y = 0; y < MAX_PER_CIRCLE; y++)
    {
      if ((ch->pcdata->circles[x][y].sn > 0)
       && (ch->pcdata->circles[x][y].ticks > 0))
      {
        memming = TRUE;

        if (spells[0] == NULL)
        {
          spells[0] = &ch->pcdata->circles[x][y];
        }
        else if (spells[1] == NULL)
        {
          if (ch->pcdata->circles[x][y].ticks > spells[0]->ticks)
            spells[1] = &ch->pcdata->circles[x][y];
          else
          {
            spells[1] = spells[0];
            spells[0] = &ch->pcdata->circles[x][y];
          }
        }
        else
        {
          newc = &ch->pcdata->circles[x][y];
          for (j = 0; j < MAX_CIRCLE_LEVEL * MAX_PER_CIRCLE; j++)
          {
            if (spells[j] == NULL)
            {
              spells[j] = newc;
              break;
            }
            else if (newc->ticks < spells[j]->ticks)
            {
              temp = spells[j];
              spells[j] = newc;
              newc = temp;
            }
          }
        }
      }
    }

  if (!memming) /* not memorizing */
    send_to_char("You are not memorizing any spells at this time.\n\r",ch);
  else /* is memorizing */
    for (j = 0; j < MAX_CIRCLE_LEVEL * MAX_PER_CIRCLE; j++)
    {
      if (spells[j] == NULL)
        break;

      sprintf(buf,"%-18s  %2d\n\r",
              skill_table[spells[j]->sn].name,
              spells[j]->ticks);
      send_to_char(buf,ch);
    }

  return;
}


At the start of the file, after the DECLARE_DO_FUN statements, add this:

/* local functions */
void  show_mem_times args( (CHAR_DATA *ch) );


If you're keeping your current version of do_spells and do_skills and not
adding the other varieties under different names, this is what you'll need
to add to do_spells:

Immediately following this:

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

Add this:

        if (!str_prefix(argument,"memorizing"))
        {
          show_mem_times(ch);
          return;
        }


***update.c***
In the local functions area at the top of the file, add this declaration:

void    spell_mem_update  args( (CHAR_DATA *ch) );


In char_update, find this line:

            gain_condition( ch, COND_HUNGER, ch->size > SIZE_MEDIUM ? -2 : -1);

After it, add this:

            if ((IS_MAGE(ch) || IS_CLERIC(ch))
             && ch->position == POS_MEMORIZE)
              spell_mem_update(ch);

Now somewhere in the file add this function:

/*
 * Go through every slot in every circle to check for any spell
 * that is being memorized and needs the counter incremented.
 */
void spell_mem_update(CHAR_DATA *ch)
{
  char buf[MAX_STRING_LENGTH];
  int x = 0, y = 0, message = 0;
  bool still_memming = FALSE;
  bool lost_spells = FALSE;

  for (x = 0; x < MAX_CIRCLE_LEVEL; x++)
    for (y = 0; y < MAX_PER_CIRCLE; y++)
    {
      if (ch->pcdata->circles[x][y].ticks > 0)
      {
        /*
         * If the character is not in the correct position, then they're
         * not going to be able to memorize anything.  Included to try
         * to head off any missed bugs.
         */
        if (ch->position != POS_MEMORIZE)
        {
          ch->pcdata->circles[x][y].ticks = 0;
          ch->pcdata->circles[x][y].sn = 0;
          ch->pcdata->circles[x][y].good_mem = FALSE;
          continue;
        }

        /*
         * Now check to see if the character is hungry or thirsty.
         * If so, ch loses all spells not memorized already because
         * of the difficulty concentrating.
         */
        if (ch->pcdata->condition[COND_HUNGER] == 0
         || ch->pcdata->condition[COND_THIRST] == 0)
        {
          ch->pcdata->circles[x][y].sn = 0;
          ch->pcdata->circles[x][y].ticks = 0;
          ch->pcdata->circles[x][y].good_mem = FALSE;
          lost_spells = TRUE;
          continue;
        }

        /*
         * If drunk only with value of 2, the character is not inebriated
         * enough to not be able to memorize something.  It could take them
         * longer, however.  If the character is inebriated, there's a 40%
         * chance for every spell they are in the process of memorizing
         * that they will memorize it incorrectly.
         */
        if (ch->pcdata->condition[COND_DRUNK] > 2
         && number_percent() <= 40)
          ch->pcdata->circles[x][y].good_mem = FALSE;

        if (ch->pcdata->condition[COND_DRUNK] > 0
         && number_percent() <= 55)
          ++ch->pcdata->circles[x][y].ticks;

        /* lower the tick counter */
        --ch->pcdata->circles[x][y].ticks;

        /* now send messages if a spell has been memorized */
        if (ch->pcdata->circles[x][y].ticks == 0)
        {
          /* let's have some variety in messages */
          message = number_range(1,10);
          if (message <= 4)
          {
            sprintf(buf,"Your blood burns as you finish memorizing %s.\n\r",
              skill_table[ch->pcdata->circles[x][y].sn].name);
            send_to_char(buf,ch);
          }
          else if (message <= 7)
          {
            sprintf(buf,"You feel that familiar tingle as you finish studying %
s.\n\r", skill_table[ch->pcdata->circles[x][y].sn].name);
            send_to_char(buf,ch);
          }
          else
          {
            sprintf(buf,"A jolt travels your spine as you study %s\n\r",
              skill_table[ch->pcdata->circles[x][y].sn].name);
            send_to_char(buf,ch);
          }

          message = number_range(1,10);
          if (message <= 3)
            act("$n closes a spellbook with a smile.",ch,NULL,NULL,TO_ROOM);
          else if (message <= 5)
            act("$n smiles broadly as $e puts away a spellbook.",ch,NULL,NULL,T
O_ROOM);
          else if (message <= 8)
            act("$n grins and packs up a spellbook.",ch,NULL,NULL,TO_ROOM);
          else
            act("$n puts away a spellbook with a sigh of relief.",ch,NULL,NULL,
TO_ROOM);
        }
        else  /* show that spells are still being memorized */
          still_memming = TRUE;
      }
    }

  /* If the character is hungry or thirsty (and hence is unable to memorize
   * spells) tell them.  If not, then keep them from going idle because of
   * no activity (when to do most things would ruin the memorization process).
   * Also set character position to POS_SITTING if not memorizing any spells.
   */
  if (lost_spells) /* hungry/thirsty */
  {
    send_to_char("Argh!  You can't concentrate when you're so uncomfortable!\n\
r",ch);
    ch->position = POS_SITTING;
  }
  else if (!still_memming)  /* finished memorizing */
    ch->position = POS_SITTING;
  else                      /* still memorizing, so don't go idle */
    if (ch->timer >= 10)
      --ch->timer;

  return;
}



***const.c***
This is perhaps the single most tedious portion of the whole thing.  Every
single entry in skill_table needs to have a new field added.  The first one,
with the name "reserved", gets this added to the end:

        { MC, MC, MC, MC }

Don't forget to put a , after the previous last entry (in the case of reserved,
the last "").  This will also have to be done for every entry.  This is the
declaration for various spells and what circle level they are at.  This in
turn decides at what level a spell caster can begin memorizing that spell and
how many they can memorize at various levels.  For instance, the armor and
magic missile spells we made to be circle 1 for clerics and mages,
respectively.  The higher the circle level (to a max of MAX_CIRCLE_LEVEL, which
is 16 in the snippet but can, of course, be changed) the fewer total spells
of that circle level can be memorized and the higher the level is before any
of them can be memorized at all (much as in AD&D).  Skills need the entry
as well, but the circle levels there are not used by memorization.

