/*************************************************************************** * 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. * * * * Envy Diku Mud improvements copyright (C) 1994 by Michael Quan, David * * Love, Guilherme 'Willie' Arnold, and Mitchell Tse. * * * * EnvyMud 2.0 improvements copyright (C) 1995 by Michael Quan and * * Mitchell Tse. * * * * EnvyMud 2.2 improvements copyright (C) 1996, 1997 by Michael Quan. * * * * In order to use any part of this Envy Diku Mud, you must comply with * * the original Diku license in 'license.doc', the Merc license in * * 'license.txt', as well as the Envy license in 'license.nvy'. * * 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. * ***************************************************************************/ #if defined( macintosh ) #include <types.h> #else #include <sys/types.h> #endif #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include "merc.h" MEM_DATA *mem_first = NULL; MEM_DATA *memdata_free = NULL; /* * Lookup a skill by name. */ int skill_lookup( const char *name ) { int sn; for ( sn = 0; sn < MAX_SKILL; sn++ ) { if ( !skills_table[sn].name ) break; if ( LOWER( name[0] ) == LOWER( skills_table[sn].name[0] ) && !str_prefix( name, skills_table[sn].name ) ) return sn; } return -1; } /* * Lookup a spell by name. */ int spell_lookup( const char *name ) { int sn; for ( sn = 0; sn < MAX_SPELL; sn++ ) { if ( !spells_table[sn].name ) break; if ( LOWER( name[0] ) == LOWER( spells_table[sn].name[0] ) && !str_prefix( name, spells_table[sn].name ) ) return sn; } return -1; } /* * Utter mystical words for an sn. */ void say_spell( CHAR_DATA *ch, int sn ) { CHAR_DATA *rch; char *pName; char buf [ MAX_STRING_LENGTH ]; char buf2 [ MAX_STRING_LENGTH ]; int iSyl; int length; struct syl_type { char * old; char * new; }; static const struct syl_type syl_table [ ] = { { " ", " " }, { "ar", "abra" }, { "au", "kada" }, { "bless", "fido" }, { "blind", "nose" }, { "bur", "mosa" }, { "cu", "judi" }, { "de", "oculo" }, { "en", "unso" }, { "light", "dies" }, { "lo", "hi" }, { "mor", "zak" }, { "move", "sido" }, { "ness", "lacri" }, { "ning", "illa" }, { "per", "duda" }, { "ra", "gru" }, { "re", "candus" }, { "son", "sabru" }, { "tect", "infra" }, { "tri", "cula" }, { "ven", "nofo" }, { "a", "a" }, { "b", "b" }, { "c", "q" }, { "d", "e" }, { "e", "z" }, { "f", "y" }, { "g", "o" }, { "h", "p" }, { "i", "u" }, { "j", "y" }, { "k", "t" }, { "l", "r" }, { "m", "w" }, { "n", "i" }, { "o", "a" }, { "p", "s" }, { "q", "d" }, { "r", "f" }, { "s", "g" }, { "t", "h" }, { "u", "j" }, { "v", "z" }, { "w", "x" }, { "x", "n" }, { "y", "l" }, { "z", "k" }, { "", "" } }; buf[0] = '\0'; for ( pName = spells_table[sn].name; *pName != '\0'; pName += length ) { for ( iSyl = 0; ( length = strlen( syl_table[iSyl].old ) ) != 0; iSyl++ ) { if ( !str_prefix( syl_table[iSyl].old, pName ) ) { strcat( buf, syl_table[iSyl].new ); break; } } if ( length == 0 ) length = 1; } sprintf( buf2, "$n&n utters the words, '%s'.", buf ); sprintf( buf, "$n&n utters the words, '%s'.", spells_table[sn].name ); for ( rch = ch->in_room->people; rch; rch = rch->next_in_room ) { if ( rch != ch && (( rch->class == ch->class) || IS_IMMORTAL( ch ))) { act( buf, ch, NULL, rch, TO_VICT ); } else if( rch != ch ) { act( buf2, ch, NULL, rch, TO_VICT ); } } return; } /* * Compute a saving throw. * Negative apply's make saving throw better. */ bool saves_spell( int level, CHAR_DATA *victim, int dam_type ) { int save; int base = 50; if ( IS_NPC( victim ) ) base += 25; save = base + ( victim->level - level - victim->saving_throw[4] ) * 5; switch( check_ris( victim, dam_type ) ) { case IS_RESISTANT: save += 2; break; case IS_IMMUNE: return TRUE; case IS_SUSCEPTIBLE: save -= 2; break; } save = URANGE( 5, save, 95 ); return number_percent( ) < save; } int find_char_mana( CHAR_DATA *ch, int bit ) { OBJ_DATA *obj_next; OBJ_DATA *obj; int mana; for ( obj = ch->carrying, mana = 0; obj; obj = obj_next ) { obj_next = obj->next_content; if ( obj->deleted ) continue; if ( obj->item_type == TYPE_GEM && IS_SET( obj->value[0], bit ) ) mana += obj->value[1]; } return mana; } void take_mana_char( CHAR_DATA *ch, int mana, int bit ) { OBJ_DATA *obj_next; OBJ_DATA *obj; for ( obj = ch->carrying; obj; obj = obj_next ) { obj_next = obj->next_content; if ( obj->deleted ) continue; if ( obj->item_type == TYPE_GEM && IS_SET( obj->value[0], bit ) ) { if ( obj->value[1] >= mana ) { obj->value[1] -= mana; if ( obj->value[1] <= 0 ) { act( "$p is drained of all its power, and shatters into thousands of shards!", ch, obj, NULL, TO_CHAR ); obj_from_char( obj ); extract_obj( obj ); } return; } else { mana -= obj->value[1]; act( "$p is drained of all its power, and shatters into thousands of shards!", ch, obj, NULL, TO_CHAR ); obj_from_char( obj ); extract_obj( obj ); } } } return; } /* * The kludgy global is for spells who want more stuff from command line. */ char *target_name; void do_cast( CHAR_DATA *ch, char *argument ) { void *vo; OBJ_DATA *obj; CHAR_DATA *victim; MEM_DATA *mem; bool found; char arg1 [ MAX_INPUT_LENGTH ]; char arg2 [ MAX_INPUT_LENGTH ]; int sn; int beats; // For quick chant target_name = one_argument( argument, arg1 ); one_argument( target_name, arg2 ); if ( arg1[0] == '\0' ) { send_to_char( "Cast which what where?\n\r", ch ); return; } if ( IS_NPC( ch ) ) return; if( ch->class == CLASS_PSIONICIST ) { send_to_char( "Psionicists use the WILL command to invoke their powers.\n\r", ch ); return; } if ( ( sn = spell_lookup( arg1 ) ) < 0 || ch->level < (spells_table[sn].spell_circle[ch->class] * 5 - 4) ) { send_to_char( "You can't do that.\n\r", ch ); return; } if ( ch->position == POS_FIGHTING && !(spells_table[sn].cast_combat) ) { send_to_char( "You can't concentrate enough.\n\r", ch ); return; } else if( ch->position != POS_STANDING && ch->position != POS_FIGHTING ) { send_to_char( "You must be standing to cast spells!\n\r", ch ); return; } if ( IS_AFFECTED( ch, AFF_MUTE ) || IS_SET( race_table[ch->race].race_abilities, RACE_MUTE ) ) { send_to_char( "Your lips move but no sound comes out.\n\r", ch ); return; } if ( IS_SET( ch->in_room->room_flags, ROOM_SILENT ) ) { send_to_char( "You can't...you are in a Cone of Silence!\n\r", ch ); return; } if( IS_SET( ch->in_room->room_flags, ROOM_NO_MAGIC ) ) { send_to_char( "You start casting...", ch ); send_to_char( "After a brief gathering of energy, your spell fizzles.\n\r", ch ); WAIT_STATE( ch, 6 ); return; } found = FALSE; if( !IS_IMMORTAL( ch )) { for( mem = ch->pcdata->memorized; mem; mem = mem->next ) { if( !mem->memmed ) continue; if( mem->sn == sn ) { found = TRUE; break; } } if( !found ) { send_to_char( "You do not have that spell memorized!\n\r", ch ); return; } } /* * Locate targets. */ victim = NULL; vo = NULL; switch ( spells_table[sn].target ) { default: bug( "Do_cast: bad target for sn %d.", sn ); return; case TAR_IGNORE: break; case TAR_CHAR_OFFENSIVE: if ( arg2[0] == '\0' ) { if ( !( victim = ch->fighting ) ) { send_to_char( "Cast the spell on whom?\n\r", ch ); return; } } else { if ( !( victim = get_char_room( ch, arg2 ) ) ) { send_to_char( "They aren't here.\n\r", ch ); return; } } if ( IS_AFFECTED( ch, AFF_CHARM ) && ch->master == victim ) { send_to_char( "You can't do that on your own follower.\n\r", ch ); return; } if ( is_safe( ch, victim ) ) return; check_killer( ch, victim ); vo = (void *) victim; break; case TAR_CHAR_DEFENSIVE: if ( arg2[0] == '\0' ) { victim = ch; } else { if ( !( victim = get_char_room( ch, arg2 ) ) ) { send_to_char( "They aren't here.\n\r", ch ); return; } } vo = (void *) victim; break; case TAR_CHAR_SELF: if ( arg2[0] != '\0' && !is_name( arg2, ch->name ) ) { send_to_char( "You cannot cast this spell on another.\n\r", ch ); return; } vo = (void *) ch; break; case TAR_OBJ_INV: if ( arg2[0] == '\0' ) { send_to_char( "What should the spell be cast upon?\n\r", ch ); return; } if ( !( obj = get_obj_carry( ch, arg2 ) ) ) { send_to_char( "You are not carrying that.\n\r", ch ); return; } vo = (void *) obj; break; } send_to_char( "You begin casting...\n\r", ch ); if ( str_cmp( spells_table[sn].name, "ventriloquate" ) ) { act( "$n&n begins casting...", ch, NULL, NULL, TO_ROOM ); } beats = spells_table[sn].beats; if( ch->level >= skills_table[gsn_quick_chant].skill_level[ch->class] ) { if( number_percent() < ch->pcdata->skl_lrn[gsn_quick_chant] ) { // Want this one to go up slowly, so it only checks when successful skill_practice( ch, gsn_quick_chant ); beats = beats * 3 / 4; } } WAIT_STATE( ch, beats ); // Create an event to handle the spell create_event( EVENT_SPELL_CAST, beats, ch, arg2, sn ); if( !IS_NPC( ch )) SET_BIT( ch->act, PLR_CASTING ); else bug( "Trying to setbit an NPC as casting", 0 ); return; } // When the spell event terminates, we need something to happen... // // By this point we should have terminated the spell event data // and should only need the info about the character and the sn // and maybe the argument... // // Passing of the correct function parameters should be handled by the // event system. void finish_spell( CHAR_DATA *ch, int sn, char *argument ) { void *vo; OBJ_DATA *obj; CHAR_DATA *victim; MEM_DATA *mem; bool found; char arg2 [ MAX_INPUT_LENGTH ]; char buf[MAX_STRING_LENGTH]; // Keep in mind this needs changed because we already have the SN that they are casting. // all we need is the target data from the command line. one_argument( argument, arg2 ); if( !IS_NPC( ch )) REMOVE_BIT( ch->act, PLR_CASTING ); else bug( "Trying to removebit an NPC casting", 0 ); // Bail if they're casting a spell that requires concentration if ( ch->position == POS_FIGHTING && !(spells_table[sn].cast_combat) ) { send_to_char( "You can't concentrate enough.\n\r", ch ); return; } // Bail if they got bashed else if( ch->position != POS_STANDING && ch->position != POS_FIGHTING ) { send_to_char( "You must be standing to cast spells!\n\r", ch ); return; } // We checked for all this stuff before, but the state of things may // have changed. if ( IS_AFFECTED( ch, AFF_MUTE ) || IS_SET( race_table[ch->race].race_abilities, RACE_MUTE ) ) { send_to_char( "Your lips move but no sound comes out.\n\r", ch ); return; } if ( IS_SET( ch->in_room->room_flags, ROOM_SILENT ) ) { send_to_char( "You can't...you are in a Cone of Silence!\n\r", ch ); return; } if( IS_SET( ch->in_room->room_flags, ROOM_NO_MAGIC ) ) { send_to_char( "After a brief gathering of energy, your spell fizzles.\n\r", ch ); return; } found = FALSE; if( !IS_IMMORTAL( ch )) { for( mem = ch->pcdata->memorized; mem; mem = mem->next ) { if( !mem->memmed ) continue; if( mem->sn == sn ) { found = TRUE; break; } } if( !found ) { send_to_char( "You do not have that spell memorized!\n\r", ch ); return; } } /* * Locate targets. */ victim = NULL; vo = NULL; switch ( spells_table[sn].target ) { default: bug( "Do_cast: bad target for sn %d.", sn ); return; case TAR_IGNORE: break; case TAR_CHAR_OFFENSIVE: if ( arg2[0] == '\0' ) { if ( !( victim = ch->fighting ) ) { send_to_char( "Cast the spell on whom?\n\r", ch ); return; } } else { if ( !( victim = get_char_room( ch, arg2 ) ) ) { send_to_char( "They aren't here.\n\r", ch ); return; } } if ( IS_AFFECTED( ch, AFF_CHARM ) && ch->master == victim ) { send_to_char( "You can't do that on your own follower.\n\r", ch ); return; } if ( is_safe( ch, victim ) ) return; check_killer( ch, victim ); vo = (void *) victim; break; case TAR_CHAR_DEFENSIVE: if ( arg2[0] == '\0' ) { victim = ch; } else { if ( !( victim = get_char_room( ch, arg2 ) ) ) { send_to_char( "They aren't here.\n\r", ch ); return; } } vo = (void *) victim; break; case TAR_CHAR_SELF: if ( arg2[0] != '\0' && !is_name( arg2, ch->name ) ) { send_to_char( "You cannot cast this spell on another.\n\r", ch ); return; } vo = (void *) ch; break; case TAR_OBJ_INV: if ( arg2[0] == '\0' ) { send_to_char( "What should the spell be cast upon?\n\r", ch ); return; } if ( !( obj = get_obj_carry( ch, arg2 ) ) ) { send_to_char( "You are not carrying that.\n\r", ch ); return; } vo = (void *) obj; break; } // No wait state - we already made them wait. if ( str_cmp( spells_table[sn].name, "ventriloquate" ) ) say_spell( ch, sn ); spell_practice( ch, sn ); if ( !IS_IMMORTAL( ch ) && number_percent( ) > ch->pcdata->spl_lrn[sn] ) { send_to_char( "You lost your concentration.\n\r", ch ); } else { send_to_char( "You complete your spell...\n\r", ch ); sprintf( buf, "Spell %d (%s) being cast by %s", sn, spells_table[sn].name, ch->name ); log_string( buf ); (*spells_table[sn].spell_fun) ( sn, URANGE( 1, ch->level, LEVEL_HERO ), ch, vo ); if( !IS_IMMORTAL( ch )) { mem->memmed = FALSE; mem->memtime = mem->full_memtime; } } if ( spells_table[sn].target == TAR_CHAR_OFFENSIVE && victim->master != ch && victim != ch && IS_AWAKE( victim ) ) { CHAR_DATA *vch; for ( vch = ch->in_room->people; vch; vch = vch->next_in_room ) { if ( vch->deleted ) continue; if ( victim == vch && !victim->fighting ) { multi_hit( victim, ch, TYPE_UNDEFINED ); break; } } } return; } // By all rights psionics should be instantaneous and exempt from // the casting stuff, and suffer lag afterward - Veygoth void do_will( CHAR_DATA *ch, char *argument ) { void *vo; OBJ_DATA *obj; CHAR_DATA *victim; char arg1 [ MAX_INPUT_LENGTH ]; char arg2 [ MAX_INPUT_LENGTH ]; char buf[MAX_STRING_LENGTH]; int total_mana; int gem_mana; int mana; int sn; target_name = one_argument( argument, arg1 ); one_argument( target_name, arg2 ); if ( arg1[0] == '\0' ) { send_to_char( "Cast which what where?\n\r", ch ); return; } if ( IS_NPC( ch ) ) return; if( ch->class != CLASS_PSIONICIST ) { send_to_char( "Your mind is much too puny for you to focus your will.\n\r", ch ); return; } if ( ( sn = spell_lookup( arg1 ) ) < 0 || ch->level < (spells_table[sn].spell_circle[ch->class] * 5 - 4)) { send_to_char( "You can't do that.\n\r", ch ); return; } if ( ch->position == POS_FIGHTING && !(spells_table[sn].cast_combat) ) { send_to_char( "You can't concentrate enough.\n\r", ch ); return; } else if( ch->position != POS_STANDING && ch->position != POS_FIGHTING ) { send_to_char( "You must be standing to cast spells!\n\r", ch ); return; } if ( IS_AFFECTED( ch, AFF_MUTE ) || IS_SET( race_table[ch->race].race_abilities, RACE_MUTE ) ) { send_to_char( "Your lips move but no sound comes out.\n\r", ch ); return; } if ( IS_SET( ch->in_room->room_flags, ROOM_SILENT ) ) { send_to_char( "You can't...you are in a Cone of Silence!\n\r", ch ); return; } if( IS_SET( ch->in_room->room_flags, ROOM_NO_PSIONICS ) ) { send_to_char( "Something here prevents you from focusing your will.\n\r", ch ); WAIT_STATE( ch, 6 ); return; } mana = MANA_COST( ch, sn ); /* * Locate targets. */ victim = NULL; vo = NULL; switch ( spells_table[sn].target ) { default: bug( "Do_cast: bad target for sn %d.", sn ); return; case TAR_IGNORE: break; case TAR_CHAR_OFFENSIVE: if ( arg2[0] == '\0' ) { if ( !( victim = ch->fighting ) ) { send_to_char( "Cast the spell on whom?\n\r", ch ); return; } } else { if ( !( victim = get_char_room( ch, arg2 ) ) ) { send_to_char( "They aren't here.\n\r", ch ); return; } } if ( IS_AFFECTED( ch, AFF_CHARM ) && ch->master == victim ) { send_to_char( "You can't do that on your own follower.\n\r", ch ); return; } if ( is_safe( ch, victim ) ) return; check_killer( ch, victim ); vo = (void *) victim; break; case TAR_CHAR_DEFENSIVE: if ( arg2[0] == '\0' ) { victim = ch; } else { if ( !( victim = get_char_room( ch, arg2 ) ) ) { send_to_char( "They aren't here.\n\r", ch ); return; } } vo = (void *) victim; break; case TAR_CHAR_SELF: if ( arg2[0] != '\0' && !is_name( arg2, ch->name ) ) { send_to_char( "You cannot cast this spell on another.\n\r", ch ); return; } vo = (void *) ch; break; case TAR_OBJ_INV: if ( arg2[0] == '\0' ) { send_to_char( "What should the spell be cast upon?\n\r", ch ); return; } if ( !( obj = get_obj_carry( ch, arg2 ) ) ) { send_to_char( "You are not carrying that.\n\r", ch ); return; } vo = (void *) obj; break; } if ( ch->mana < mana && ( gem_mana = find_char_mana( ch, spells_table[sn].mana_type ) ) > 0 ) { total_mana = gem_mana + ch->mana; } else { gem_mana = 0; total_mana = ch->mana; } if ( total_mana < mana ) { char *msg; switch ( spells_table[sn].mana_type ) { default: msg = "You don't have enough mana.\n\r"; break; case MANA_FIRE: msg = "You don't have enough fire mana.\n\r"; break; case MANA_AIR: msg = "You don't have enough air mana.\n\r"; break; case MANA_WATER: msg = "You don't have enough water mana.\n\r"; break; case MANA_EARTH: msg = "You don't have enough earth mana.\n\r"; break; } send_to_char( msg, ch ); return; } if ( str_cmp( spells_table[sn].name, "ventriloquate" ) ) say_spell( ch, sn ); WAIT_STATE( ch, spells_table[sn].beats ); spell_practice( ch, sn ); if ( !IS_IMMORTAL( ch ) && number_percent( ) > ch->pcdata->spl_lrn[sn] ) { send_to_char( "You lost your concentration.\n\r", ch ); if ( gem_mana > 0 ) { take_mana_char( ch, mana/2, spells_table[sn].mana_type ); if ( gem_mana < mana / 2 ) ch->mana -= mana/2 - gem_mana; } else ch->mana -= mana/2; } else { if ( gem_mana > 0 ) { take_mana_char( ch, mana, spells_table[sn].mana_type ); if ( gem_mana < mana ) ch->mana -= mana - gem_mana; } else ch->mana -= mana; sprintf( buf, "Spell %d (%s) being willed by %s", sn, spells_table[sn].name, ch->name ); log_string( buf ); (*spells_table[sn].spell_fun) ( sn, URANGE( 1, ch->level, LEVEL_HERO ), ch, vo ); } if ( spells_table[sn].target == TAR_CHAR_OFFENSIVE && victim->master != ch && victim != ch && IS_AWAKE( victim ) ) { CHAR_DATA *vch; for ( vch = ch->in_room->people; vch; vch = vch->next_in_room ) { if ( vch->deleted ) continue; if ( victim == vch && !victim->fighting ) { multi_hit( victim, ch, TYPE_UNDEFINED ); break; } } } return; } /* * Cast spells at targets using a magical object. */ void obj_cast_spell( int sn, int level, CHAR_DATA *ch, CHAR_DATA *victim, OBJ_DATA *obj ) { void *vo; if ( sn <= 0 ) return; if ( sn >= MAX_SPELL || spells_table[sn].spell_fun == 0 ) { bug( "Obj_cast_spell: bad sn %d.", sn ); return; } switch ( spells_table[sn].target ) { default: bug( "Obj_cast_spell: bad target for sn %d.", sn ); return; case TAR_IGNORE: vo = NULL; break; case TAR_CHAR_OFFENSIVE: if ( !victim ) victim = ch->fighting; if ( !victim ) { send_to_char( "You can't do that.\n\r", ch ); return; } if ( is_safe( ch, victim ) ) return; check_killer( ch, victim ); vo = (void *) victim; break; case TAR_CHAR_DEFENSIVE: if ( !victim ) victim = ch; vo = (void *) victim; break; case TAR_CHAR_SELF: vo = (void *) ch; break; case TAR_OBJ_INV: if ( !obj ) { send_to_char( "You can't do that.\n\r", ch ); return; } vo = (void *) obj; break; } target_name = ""; (*spells_table[sn].spell_fun) ( sn, level, ch, vo ); if ( spells_table[sn].target == TAR_CHAR_OFFENSIVE && victim->master != ch && ch != victim ) { CHAR_DATA *vch; for ( vch = ch->in_room->people; vch; vch = vch->next_in_room ) { if ( vch->deleted ) continue; if ( victim == vch && !victim->fighting ) { multi_hit( victim, ch, TYPE_UNDEFINED ); break; } } } return; } void do_memorize( CHAR_DATA *ch, char *argument ) { MEM_DATA *mem = NULL; int sn; char buf[MAX_STRING_LENGTH]; char buf2[MAX_STRING_LENGTH]; int memmed[MAX_SPELL]; int count; int total_mem; bool found; bool lesser; // Do they use the lesser mem chart? (For future classes) bool left; int circle[MAX_CIRCLE]; int circfree[MAX_CIRCLE]; int lvltotal; // Mobiles cannot mem spells if( IS_NPC( ch )) return; // Check to see if they are allowed to mem anything switch( ch->class ) { default: send_to_char( "You are not a spellcaster!\n\r", ch ); return; break; case CLASS_CLERIC: case CLASS_SORCERER: lesser = FALSE; break; } // with an argument they want to start memorizing a new spell. if( argument[0] != '\0' ) { // Must be in the proper position if( ch->position != POS_RESTING ) { send_to_char( "You can only memorize spells while resting.\n\r", ch ); return; } // Find the spell they want sn = spell_lookup( argument ); if( sn <= 0 ) { send_to_char( "Never heard of that spell...\n\r", ch ); return; } // Check to see that they can memorize another spell // Immortals have no limits if( !IS_IMMORTAL( ch )) { if( ch->level < (spells_table[sn].spell_circle[ch->class] * 5 - 4)) { send_to_char( "That spell is beyond you.\n\r", ch ); return; } if( ch->pcdata->spl_lrn[sn] < 1 ) { send_to_char( "You have not yet learned that spell. Find a place to scribe it.\n\r", ch ); return; } lvltotal = 0; for( mem = ch->pcdata->memorized; mem; mem = mem->next ) { if( mem->circle == spells_table[sn].spell_circle[ch->class]) lvltotal += 1; } if( lvltotal >= memchart[(ch->level - 1)][(spells_table[sn].spell_circle[ch->class] - 1)]) { send_to_char( "You can memorize no more spells of that level.\n\r", ch ); return; } } // if( !IS_IMMORTAL( ch )) // If we know what they want and they can have it, let's create it. if(!(mem = (create_memdata( ch, sn )))) { bug( "Unable to create memorization (sn %d)", sn ); return; } // If they're not already memorizing, they are now SET_BIT( ch->act, PLR_MEMORIZING ); sprintf( buf, "You start memorizing %s which will take about %d seconds.\n\r", spells_table[mem->sn].name, (mem->memtime / PULSE_PER_SECOND) ); send_to_char( buf, ch ); return; } else { found = FALSE; // If they didn't give us an argument, that means that they must // want to either see their spell list or continue memorizing // Either way we show their spell list. // make sure they have some mem data first... if( ch->pcdata->memorized ) { for( count = 0; count < MAX_SPELL; count ++ ) memmed[count] = 0; // Figure out what spells they have memorized for( mem = ch->pcdata->memorized; mem; mem = mem->next ) { if( mem->memmed ) memmed[mem->sn] += 1; } // Show memorized spells send_to_char( "You have memorized the following spells:\n\r", ch ); for( count = 0; count < MAX_SPELL; count++ ) { if( memmed[count] > 0 && spells_table[count].name != NULL) { sprintf( buf, "(%2d%s circle) %d - %s\n\r", spells_table[count].spell_circle[ch->class], text_number( spells_table[count].spell_circle[ch->class] ), memmed[count], spells_table[count].name ); send_to_char( buf, ch ); } } // Figure out what spells they are working on send_to_char( "You are memorizing the following spells:\n\r", ch ); total_mem = 0; for( mem = ch->pcdata->memorized_last; mem; mem = mem->prev ) { if( !spells_table[mem->sn].name ) break; if( mem->memmed ) continue; else found = TRUE; sprintf( buf, " %d seconds: (%d%s) %s\n\r", ((total_mem + mem->memtime) / PULSE_PER_SECOND), spells_table[mem->sn].spell_circle[ch->class], text_number(spells_table[mem->sn].spell_circle[ch->class]), spells_table[mem->sn].name ); send_to_char( buf, ch ); total_mem += mem->memtime; } } // if( ch->pcdata->memorized ) // // Tell them what they still have slots open for... for( count = 0; count < MAX_CIRCLE; count++ ) circle[count] = 0; for( mem = ch->pcdata->memorized; mem; mem = mem->next ) { circle[(mem->circle - 1)] += 1; } left = FALSE; for( count = 0; count < MAX_CIRCLE; count++ ) { circfree[count] = memchart[(ch->level - 1)][count] - circle[count]; if( circfree[count] > 0 ) left = TRUE; } if( !left ) { send_to_char( "\n\rYou can memorize no more spells.\n\r", ch ); } else { sprintf( buf, "\n\rYou can memorize" ); for( count = 0; count < MAX_CIRCLE; count++ ) { if( circfree[count] > 0 ) { sprintf( buf2, " %d-%d%s", circfree[count], (count + 1), text_number( count ) ); strcat( buf, buf2 ); } } strcat( buf, " level spells.\n\r" ); send_to_char( buf, ch ); } // // If they aren't memming and they should be, start 'em up. if( found && !IS_SET( ch->act, PLR_MEMORIZING ) && ch->position == POS_RESTING ) { SET_BIT( ch->act, PLR_MEMORIZING ); send_to_char( "You continue your studies.\n\r", ch ); } } return; } void do_forget( CHAR_DATA *ch, char *argument ) { MEM_DATA *mem; MEM_DATA *prev; int sn; bool found; char buf[MAX_STRING_LENGTH]; if( IS_NPC( ch )) return; if( argument[0] == '\0' ) { send_to_char( "Looks like your forgot what you were going to forget.\n\r", ch ); return; } if( !ch->pcdata->memorized ) { send_to_char( "You don't have anything to forget.\n\r", ch ); return; } if( !str_cmp( argument, "all" )) { forget_all( ch ); send_to_char( "You forget everything.\n\r", ch ); return; } sn = spell_lookup( argument ); if( sn <= 0 ) { send_to_char( "Forget what?\n\r", ch ); return; } found = FALSE; prev = NULL; for( mem = ch->pcdata->memorized; mem; mem = mem->next ) { if( mem->sn == sn ) { found = TRUE; break; } prev = mem; } if( !found ) { send_to_char( "You do not have that spell memorized.\n\r", ch ); return; } // This is where we remove the spell from their double-linked // list and tack it onto the free list. Too bad I don't know how // yet -- Veygoth sprintf( buf, "You forget %s.\n\r", spells_table[sn].name ); send_to_char( buf, ch ); // Put the data on the free stack. // Reset memorized_last since that doesen't affect much of anything if( mem == ch->pcdata->memorized_last ) { if( mem->prev ) ch->pcdata->memorized_last = mem->prev; else ch->pcdata->memorized_last = NULL; } // repoint the previous data to the next data if any if( prev ) { prev->next = mem->next; } else if( ch->pcdata->memorized == mem ) { ch->pcdata->memorized = mem->next; } else { bug( "Previous data not found and mem data not at top of list", 0 ); } // repoint the next memorization data to the right place if( mem->next ) { if( prev ) mem->next->prev = prev; else mem->next->prev = NULL; } // put it on the free stack mem->next = memdata_free; memdata_free = mem; // reset the previous pointer mem->prev = NULL; return; } // Should hard code level checks to see if it is okay to mem a certain // spell - Veygoth MEM_DATA *create_memdata( CHAR_DATA *ch, int sn ) { MEM_DATA *mem; if( IS_NPC( ch )) return FALSE; if( !memdata_free ) { mem = alloc_perm( sizeof( *mem )); top_memdata++; } else { mem = memdata_free; memdata_free = memdata_free->next; } memset( mem, 0, sizeof( MEM_DATA )); mem->sn = sn; mem->memtime = calc_memtime( ch, sn ); mem->full_memtime = calc_memtime( ch, sn ); mem->circle = spells_table[sn].spell_circle[ch->class]; mem->memmed = FALSE; // Point the new data to all the rest of the list. // The prev pointer is reset when the data is actually // used so we don't have to worry about it in the forget // command. if( ch->pcdata->memorized ) ch->pcdata->memorized->prev = mem; mem->next = ch->pcdata->memorized; mem->prev = NULL; if( !ch->pcdata->memorized_last ) ch->pcdata->memorized_last = mem; // Put the new mem data at the top of the list. ch->pcdata->memorized = mem; return mem; } int calc_memtime( CHAR_DATA *ch, int sn ) { int memtime; int attribute; if( ch->class == CLASS_CLERIC ) attribute = get_curr_wis( ch ); else attribute = get_curr_int( ch ); memtime = 220 - attribute - (ch->level * 3) + (spells_table[sn].spell_circle[ch->class] * 8); if( memtime < 4 ) memtime = 4; return memtime; } void memorize_update( void ) { MEM_DATA *mem; MEM_DATA *chkspl; CHAR_DATA *ch; bool done; for( ch = char_list; ch; ch = ch->next ) { done = FALSE; if( IS_NPC( ch )) continue; if( !(IS_SET( ch->act, PLR_MEMORIZING ))) continue; if( !ch->pcdata->memorized ) { bug( "Memorizing character with no mem_data", 0 ); REMOVE_BIT( ch->act, PLR_MEMORIZING ); } // Find the oldest unmemmed piece of spell data for( mem = ch->pcdata->memorized_last; mem; mem = mem->prev ) { if( !mem->memmed ) { chkspl = mem; break; } } if( !chkspl ) { REMOVE_BIT( ch->act, PLR_MEMORIZING ); send_to_char( "Your studies are complete...", ch ); continue; } else { if( chkspl->memtime < 1 ) { char buf[MAX_STRING_LENGTH]; chkspl->memmed = TRUE; sprintf( buf, "You have finished memorizing %s.\n\r", spells_table[chkspl->sn].name ); send_to_char( buf, ch ); } else { if( IS_SET( ch->act, PLR_MEDITATING )) if( ( chkspl->memtime >= (chkspl->full_memtime / 2)) && (( chkspl->memtime - PULSE_MEMORIZE) < ( chkspl->full_memtime / 2 ))) { if( number_percent() < ch->pcdata->skl_lrn[gsn_meditate] ) { char buf[MAX_STRING_LENGTH]; chkspl->memmed = TRUE; sprintf( buf, "You have finished memorizing %s.\n\r", spells_table[chkspl->sn].name ); send_to_char( buf, ch ); } } chkspl->memtime -= PULSE_MEMORIZE; } // if( chkspl->memtime < 1 ) } // if( chkspl ) done = TRUE; for( mem = ch->pcdata->memorized; mem; mem = mem->next ) { if( !mem->memmed ) { done = FALSE; break; } } if( (done) && (IS_SET( ch->act, PLR_MEMORIZING ))) { REMOVE_BIT( ch->act, PLR_MEMORIZING ); send_to_char( "Your studies are complete.\n\r", ch ); act( "$n&n is finished memorizing.", ch, NULL, NULL, TO_ROOM ); } } return; } void do_meditate( CHAR_DATA *ch, char *argument ) { if( IS_NPC( ch )) return; if( IS_SET( ch->act, PLR_MEDITATING )) { send_to_char( "You are already metitating!\n\r", ch ); return; } if( ch->position != POS_RESTING ) { send_to_char( "You must be resting on order to meditate.\n\r", ch ); return; } if( ch->fighting ) { send_to_char( "Meditation during battle leads to permenant inner peace.\n\r", ch ); return; } SET_BIT( ch->act, PLR_MEDITATING ); WAIT_STATE( ch, skills_table[gsn_meditate].beats ); send_to_char( "You start meditating...\n\r", ch ); return; } // Used for both death and for 'forget all' void forget_all( CHAR_DATA *ch ) { MEM_DATA *mem; if( IS_NPC( ch )) return; if( !ch->pcdata->memorized ) return; // No clue whether this works, but we yank the whole // data chain from the character and place it on the // free stack - Veygoth for( mem = ch->pcdata->memorized; mem; mem = mem->next ) { mem->prev = NULL; if( mem->next == NULL ) break; } mem->next = memdata_free; memdata_free = ch->pcdata->memorized; ch->pcdata->memorized = NULL; ch->pcdata->memorized_last = NULL; return; } void do_scribe( CHAR_DATA *ch, char *argument ) { CHAR_DATA *teacher; OBJ_DATA *quill; char buf[MAX_STRING_LENGTH]; int sn; if( IS_NPC( ch )) return; for( teacher = ch->in_room->people; teacher; teacher = teacher->next_in_room ) { if( !IS_NPC( teacher )) continue; if( IS_SET( teacher->act, ACT_TEACHER )) break; } if( !teacher ) { send_to_char( "Nobody here can teach you anything.\n\r", ch ); return; } for( quill = ch->carrying; quill; quill = quill->next_content ) { if( quill->item_type == TYPE_PEN ) break; } if( !quill ) { send_to_char( "You have nothing to write with!\n\r", ch ); return; } if( argument[0] == '\0' ) { send_to_char( "Scribe what?\n\r", ch ); return; } sn = spell_lookup( argument ); if( sn <= 0 ) { send_to_char( "No such spell.\n\r", ch ); return; } if( (spells_table[sn].spell_circle[teacher->class] * 5) > (teacher->level + 4) ) { send_to_char( "The teacher does not know that spell.\n\r", ch ); return; } if( (spells_table[sn].spell_circle[ch->class] * 5) > (ch->level + 4) ) { send_to_char( "That spell is beyond you.\n\r", ch ); return; } if( ch->pcdata->spl_lrn[sn] < 1 ) { // Scribe is used so rarely give them 5 chances to learn it... skill_practice( ch, gsn_scribe ); skill_practice( ch, gsn_scribe ); skill_practice( ch, gsn_scribe ); skill_practice( ch, gsn_scribe ); skill_practice( ch, gsn_scribe ); sprintf( buf, "You scribe %s.\n\r", spells_table[sn].name ); send_to_char( buf, ch ); ch->pcdata->spl_lrn[sn] = BASE_SPELL_ADEPT; if( number_percent() < ch->pcdata->skl_lrn[gsn_scribe] ) { ch->pcdata->spl_lrn[sn] += 10; } if( number_percent() < ch->pcdata->skl_lrn[gsn_scribe] ) { ch->pcdata->spl_lrn[sn] += 5; } WAIT_STATE( ch, skills_table[gsn_scribe].beats ); } else { send_to_char( "You already know that spell.\n\r", ch ); } return; }