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_ROOM); } } 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 the 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; } 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_LEVEL); 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_LEVEL); 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,TO_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.