/*************************************************************************** * Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer, * * Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe. * * * * Merc Diku Mud improvments copyright (C) 1992, 1993 by Michael * * Chastain, Michael Quan, and Mitchell Tse. * * * * In order to use any part of this Merc Diku Mud, you must comply with * * both the original Diku license in 'license.doc' as well the Merc * * license in 'license.txt'. In particular, you may not remove either of * * these copyright notices. * * * * Much time and thought has gone into this software and you are * * benefitting. We hope that you share your changes too. What goes * * around, comes around. * ***************************************************************************/ /********************************************************** *************** S U N D E R M U D *** 2 . 0 ************** ********************************************************** * The unique portions of the SunderMud code as well as * * the integration efforts for code from other sources is * * based primarily on the efforts of: * * * * Lotherius <aelfwyne@operamail.com> (Alvin W. Brinson) * * and many others, see "help sundermud" in the mud. * **********************************************************/ /* This file is the non-spell magical stuff. See spells.c for spells. */ #include "everything.h" #include "magic.h" /* command procedures needed */ DECLARE_DO_FUN ( do_help ); /* RT spells and skills show the players spells (or skills) */ void do_spells ( CHAR_DATA * ch, char *argument ) { char spell_list[LEVEL_HERO][MAX_STRING_LENGTH]; char spell_columns[LEVEL_HERO]; int sn, lev, mana, filter; bool found = FALSE; char buf[MAX_STRING_LENGTH]; BUFFER *outbuf; if ( IS_NPC ( ch ) ) return; outbuf = buffer_new(1000); if ( argument == NULL || argument[0] == '\0' ) /* This character's own list. Normal. */ { filter = -1; } else { filter = class_lookup ( argument ); if ( filter == -1 ) { send_to_char ( "That's not a class.\n\r", ch ); buffer_free(outbuf); return; } else { bprintf( outbuf, "Spells For %s", class_table[filter].name ); } } /* initilize data */ for ( lev = 0; lev < LEVEL_HERO; lev++ ) { spell_columns[lev] = 0; spell_list[lev][0] = '\0'; } for ( sn = 0; sn < MAX_SKILL; sn++ ) { if ( skill_table[sn].name == NULL ) break; if ( ( ( (filter == -1) && (skill_table[sn].skill_level[ch->pcdata->pclass] <= LEVEL_HERO) ) || ( (filter >= 0) && (skill_table[sn].skill_level[filter] <= LEVEL_HERO ) ) ) && skill_table[sn].spell_fun != spell_null ) { found = TRUE; if (filter == -1) lev = skill_table[sn].skill_level[ch->pcdata->pclass]; else lev = skill_table[sn].skill_level[filter]; if ( ( ch->level < lev ) || (filter >= 0) ) SNP ( buf, "%-18s n/a ", skill_table[sn].name ); else { mana = UMAX ( skill_table[sn].min_mana, 100 / ( 2 + ch->level - lev ) ); SNP ( buf, "%-18s %3d mana ", skill_table[sn].name, mana ); } if ( spell_list[lev][0] == '\0' ) SNP ( spell_list[lev], "\n\rLevel %2d: %s", lev, buf ); else /* append */ { if ( ++spell_columns[lev] % 2 == 0 ) SLCAT ( spell_list[lev], "\n\r " ); SLCAT ( spell_list[lev], buf ); } } } /* return results */ if ( !found ) { send_to_char ( "No spells found.\n\r", ch ); } else { for ( lev = 0; lev < LEVEL_HERO; lev++ ) if ( spell_list[lev][0] != '\0' ) buffer_strcat ( outbuf, spell_list[lev] ); buffer_strcat ( outbuf, "\n\r" ); page_to_char ( outbuf->data, ch ); } buffer_free(outbuf); return; } /* * Utter mystical words for an sn. * Modified to set npc "class" by act flags - Lotherius */ void say_spell ( CHAR_DATA * ch, int sn ) { char buf[MAX_STRING_LENGTH]; char buf2[MAX_STRING_LENGTH]; CHAR_DATA *rch; char *pName; int iSyl; int length; int sclass; int oclass; struct syl_type { char *old; char *new; }; static const struct syl_type syl_table[] = { {" ", " " }, {"ar", "octa" }, {"au", "erae" }, {"bless", "dise" }, {"blind", "nosii" }, {"bur", "mosa" }, {"cu", "sucri" }, {"de", "oculo" }, {"en", "unso" }, {"light", "onis" }, {"lo", "hi" }, {"mor", "zak" }, {"move", "sido" }, {"ness", "dimo" }, {"ning", "illa" }, {"per", "duda" }, {"ra", "gru" }, {"fresh", "ima" }, {"re", "lamo" }, {"son", "sabru" }, {"tect", "ocri" }, {"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 = skill_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 ) ) { SLCAT ( buf, syl_table[iSyl].new ); break; } } if ( length == 0 ) length = 1; } SNP ( buf2, "$n intones the words, '%s'.", buf ); SNP ( buf, "$n intones the words, '%s'.", skill_table[sn].name ); if ( !IS_NPC ( ch ) ) sclass = ch->pcdata->pclass; else { if ( IS_SET(ch->act, ACT_MAGE) ) sclass = class_lookup ( "mage" ); else if ( IS_SET ( ch->act, ACT_THIEF ) ) sclass = class_lookup ( "thief" ); else if ( IS_SET ( ch->act, ACT_CLERIC ) ) { if ( ch->alignment > 0 ) sclass = class_lookup ( "avenger" ); else sclass = class_lookup ( "defiler" ); } else sclass = class_lookup ( "warrior" ); } for ( rch = ch->in_room->people; rch; rch = rch->next_in_room ) { if ( rch != ch ) { if ( !IS_NPC ( rch ) ) oclass = rch->pcdata->pclass; else { if ( IS_SET( rch->act, ACT_MAGE) ) oclass = class_lookup ( "mage" ); else if ( IS_SET ( rch->act, ACT_THIEF ) ) oclass = class_lookup ( "thief" ); else if ( IS_SET ( rch->act, ACT_CLERIC ) ) { if ( ch->alignment > 0 ) oclass = class_lookup ( "avenger" ); else oclass = class_lookup ( "defiler" ); } else oclass = class_lookup ( "warrior" ); } act ( sclass == oclass ? buf : 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 save; save = 50 + ( victim->level - level - victim->saving_throw ) * 5; if ( IS_AFFECTED ( victim, AFF_BERSERK ) ) save += victim->level / 2; save = URANGE ( 5, save, 95 ); return number_percent ( ) < save; } /* RT save for dispels */ bool saves_dispel ( int dis_level, int spell_level, int duration ) { int save; if ( duration == -1 ) spell_level += 2; /* very hard to dispel permanent effects */ save = 50 + ( spell_level - dis_level ) * 5; save = URANGE ( 5, save, 95 ); return number_percent ( ) < save; } /* co-routine for dispel magic and cancellation */ bool check_dispel ( int dis_level, CHAR_DATA * victim, int sn ) { AFFECT_DATA *af; if ( is_affected ( victim, sn ) ) { for ( af = victim->affected; af != NULL; af = af->next ) { if ( af->type == sn ) { if ( !saves_dispel ( dis_level, af->level, af->duration ) ) { affect_strip ( victim, sn ); if ( skill_table[sn].msg_off ) { send_to_char ( skill_table[sn].msg_off, victim ); send_to_char ( "\n\r", victim ); } return TRUE; } else af->level--; } } } return FALSE; } /* for finding mana costs -- temporary version */ int mana_cost ( CHAR_DATA * ch, int min_mana, int level ) { if ( ch->level + 2 == level ) return 1000; return UMAX ( min_mana, ( 100 / ( 2 + ch->level - level ) ) ); } /* * for standardizing spell damages. * Yes, I know it's not popular, but many spells in Rom had no relation * between how POWERFUL they were and their LEVEL. And they should. * */ int spell_calc ( CHAR_DATA * ch, int sn ) { int dam; int level; // Set's the spell's strength to match its level // // Prevents necessity of changing a spell when the level is changed. if ( !IS_NPC(ch) ) level = skill_table[sn].skill_level[ch->pcdata->pclass]; else { if ( IS_SET(ch->act,ACT_CLERIC) && ch->alignment >= 1 ) level = skill_table[sn].skill_level[CLASS_AVENGER]; else if ( IS_SET(ch->act, ACT_CLERIC) && ch->alignment <= 0 ) level = skill_table[sn].skill_level[CLASS_DEFILER]; else if ( IS_SET(ch->act, ACT_UNDEAD) ) level = skill_table[sn].skill_level[CLASS_DEFILER] + 5; else if ( IS_SET(ch->act, ACT_MAGE) ) level = skill_table[sn].skill_level[CLASS_MAGE]; else level = 5; } dam = number_range( 1, level*2); /* IS_NPC must come before class to avoid mobs getting class check */ /* If you're not a chaosmage, you get a more average damage */ if (!IS_NPC(ch) && ch->pcdata->pclass != CLASS_CHAOSMAGE) { dam = (dam + number_range(level/2, level*2) ) / 2; } /* Give a bonus for the character's own level, ramp it up as levels increase */ if ( ch->level <= 10 ) dam += number_fuzzy(number_range(ch->level/2, ch->level *1.5)); else if (ch->level <= 25 ) dam += number_fuzzy(number_range(ch->level/2, ch->level *1.75)); else if ( ch->level <= 40 ) dam += number_fuzzy(number_range(ch->level/2, ch->level *2)); else if ( ch->level <= 80 ) dam += number_fuzzy(number_range(ch->level/2, ch->level *2.25)); else dam += number_fuzzy(number_range(ch->level/2, ch->level *2.5)); /* Now another bonus so that higher level spells do better */ if ( level > 50 ) dam += number_fuzzy(number_range(level/4, level/2)); /* Give an intelligence bonus on the spell for mages, wisdom for clerics */ if (!IS_NPC(ch) ) { float bonus = 0; if (ch->pcdata->pclass == CLASS_AVENGER || ch->pcdata->pclass == CLASS_DEFILER) bonus = wis_app[get_curr_stat ( ch, STAT_WIS )].cspell; else if (ch->pcdata->pclass == CLASS_MAGE || ch->pcdata->pclass == CLASS_CHAOSMAGE) bonus = int_app[get_curr_stat ( ch, STAT_INT )].mspell; bonus = number_fuzzy(bonus); if (bonus != 0) dam +=dam * (float) (bonus/100); } return dam; } /* * The kludgy global is for spells who want more stuff from command line. * Now NPC's can "cast" spells :) */ extern char *target_name; void do_cast ( CHAR_DATA * ch, char *argument ) { char arg1[MAX_INPUT_LENGTH]; char arg2[MAX_INPUT_LENGTH]; OBJ_DATA *component = NULL; CHAR_DATA *victim; OBJ_DATA *obj; void *vo; int mana; int sn; int nclass; 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; } /* Set NCLASS to player's class or NPC's act flag */ if ( IS_NPC ( ch ) ) { if ( IS_SET(ch->act, ACT_MAGE) ) nclass = class_lookup ( "mage" ); else if ( IS_SET ( ch->act, ACT_THIEF ) ) nclass = class_lookup ( "thief" ); else if ( IS_SET ( ch->act, ACT_CLERIC ) ) { if ( ch->alignment > 0 ) nclass = class_lookup ( "avenger" ); else nclass = class_lookup ( "defiler" ); } else nclass = class_lookup ( "warrior" ); } else nclass = ch->pcdata->pclass; if ( ( sn = skill_lookup ( arg1 ) ) < 1 || ( ch->level < skill_table[sn].skill_level[nclass] ) || ( ch->pcdata->learned[sn] < 1 ) ) { if ( IS_NPC ( ch ) ) bugf ( "A mob tried casting a spell it doesn't know.", arg1, ch->name ); send_to_char ( "You don't know any spells of that name.\n\r", ch ); return; } if ( skill_table[sn].spell_fun == spell_null ) { send_to_char ( "Didn't your teachers tell you that wasn't a spell?\n\r", ch ); return; } if ( is_affected ( ch, skill_lookup ( "mute" ) ) && sn != skill_lookup ( "cancellation" ) && !is_affected ( ch, skill_lookup ( "vocalize" ) ) ) { send_to_char ( "Mute people may only cast 'cancellation'.\n\r", ch ); return; } if ( ch->position < skill_table[sn].minimum_position ) { send_to_char ( "You can't concentrate enough.\n\r", ch ); return; } /* Zeran - gonna override components for immortals */ if ( !IS_IMMORTAL ( ch ) ) { if ( str_cmp ( skill_table[sn].component, "" ) ) component = get_obj_carry ( ch, skill_table[sn].component, NULL ); if ( ( str_cmp ( skill_table[sn].component, "" ) ) && ( !component ) ) { char cbuf[80]; SNP ( cbuf, "You need to have the component '%s' to cast this.\n\r", skill_table[sn].component ); send_to_char ( cbuf, ch ); return; } if ( str_cmp ( skill_table[sn].component, "" ) && ( component->item_type != ITEM_COMPONENT ) ) { char cbuf[80]; SNP ( cbuf, "Your '%s' is not the correct component.\n\r", component->short_descr ); send_to_char ( cbuf, ch ); return; } } /* end IMMORTAL checking block for component */ if ( ch->level + 2 == skill_table[sn].skill_level[nclass] ) mana = 50; else mana = UMAX ( skill_table[sn].min_mana, 100 / ( 2 + ch->level - skill_table[sn].skill_level[nclass] ) ); /* * Locate targets. */ victim = NULL; obj = NULL; vo = NULL; switch ( skill_table[sn].target ) { default: bugf ( "Do_cast: bad target for sn %d.", sn ); return; case TAR_IGNORE: break; case TAR_CHAR_OFFENSIVE: if ( IS_AFFECTED ( ch, AFF_FEAR ) ) { send_to_char ( "You are too scared to attack anyone...\n\r", ch ); return; } if ( arg2[0] == '\0' ) { if ( ( victim = ch->fighting ) == NULL ) { send_to_char ( "Cast the spell on whom?\n\r", ch ); return; } } else { if ( ( victim = get_char_room ( ch, NULL, arg2 ) ) == NULL ) { send_to_char ( "Funny, where'd they go?\n\r", ch ); return; } } /* if ( ch == victim ) { send_to_char( "You can't do that to yourself.\n\r", ch ); return; } */ if ( !IS_NPC ( ch ) ) { if ( is_safe_spell ( ch, victim, FALSE ) && victim != ch ) return; if ( !IS_NPC ( victim ) && is_safe ( ch, victim, TRUE ) ) return; } if ( IS_AFFECTED ( ch, AFF_CHARM ) && ch->master == victim ) { send_to_char( "But you love your master so much!\n\r", ch ); return; } vo = ( void * ) victim; break; case TAR_CHAR_DEFENSIVE: if ( arg2[0] == '\0' ) { victim = ch; } else { if ( ( victim = get_char_room ( ch, NULL, arg2 ) ) == NULL ) { 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; } victim = ch; 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, NULL ) ) == NULL ) { send_to_char ( "You are not carrying that.\n\r", ch ); return; } vo = ( void * ) obj; break; } if ( ch->mana < mana ) { send_to_char ( "You don't have enough mana.\n\r", ch ); return; } if ( str_cmp ( skill_table[sn].name, "ventriloquate" ) && !is_affected ( ch, skill_lookup ( "mute" ) ) ) say_spell ( ch, sn ); WAIT_STATE ( ch, skill_table[sn].beats ); if ( number_percent ( ) > get_skill ( ch, sn ) ) { send_to_char ( "You lost your concentration.\n\r", ch ); if ( !IS_NPC ( ch ) ) check_improve ( ch, sn, FALSE, 1 ); ch->mana -= mana / 2; /* Zeran - override component section for immortals */ if ( !IS_IMMORTAL ( ch ) ) { if ( str_cmp ( skill_table[sn].component, "" ) ) { send_to_char ( "The spell component dissolves!\n\r", ch ); extract_obj ( component ); } } } else { ch->mana -= mana; /* Zeran - override component section for immortals */ if ( !IS_IMMORTAL ( ch ) ) { if ( str_cmp ( skill_table[sn].component, "" ) ) { send_to_char ( "Your component is consumed by the spell.\n\r", ch ); extract_obj ( component ); } } if ( ( skill_table[sn].target == TAR_CHAR_OFFENSIVE ) && IS_PROTECTED ( victim, PROT_ABSORB ) && ( skill_lookup ( "dispel magic" ) != sn ) ) { if ( ( number_percent ( ) - 5 * ( ch->level - victim->level ) ) > get_skill( ch, sn ) / 2 ) { char messbuf[80]; // Needs a better message... geez, Z... SNP ( messbuf, "You absorb the magic of %s's offensive spell!\n\r", ch->name ); send_to_char ( messbuf, victim ); SNP ( messbuf, "%s absorbs your offensive spell!\n\r", PERS ( victim, ch ) ); send_to_char ( messbuf, ch ); } else ( *skill_table[sn].spell_fun ) ( sn, ch->level, ch, vo ); } else ( *skill_table[sn].spell_fun ) ( sn, ch->level, ch, vo ); check_improve ( ch, sn, TRUE, 1 ); } if ( skill_table[sn].target == TAR_CHAR_OFFENSIVE && victim != ch && victim->master != ch ) { CHAR_DATA *vch; CHAR_DATA *vch_next; for ( vch = ch->in_room->people; vch; vch = vch_next ) { vch_next = vch->next_in_room; if ( victim == vch && victim->fighting == NULL && skill_lookup ( "fear" ) != sn ) { 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_SKILL || skill_table[sn].spell_fun == 0 ) { bugf ( "Obj_cast_spell: bad sn %d.", sn ); return; } switch ( skill_table[sn].target ) { default: bugf ( "Obj_cast_spell: bad target for sn %d.", sn ); return; case TAR_IGNORE: vo = NULL; break; case TAR_CHAR_OFFENSIVE: if ( victim == NULL ) victim = ch->fighting; if ( victim == NULL ) { send_to_char ( "You can't do that.\n\r", ch ); return; } if ( is_safe_spell ( ch, victim, FALSE ) && ch != victim ) return; vo = ( void * ) victim; break; case TAR_CHAR_DEFENSIVE: if ( victim == NULL ) victim = ch; vo = ( void * ) victim; break; case TAR_CHAR_SELF: vo = ( void * ) ch; break; case TAR_OBJ_INV: if ( obj == NULL ) { send_to_char ( "You can't do that.\n\r", ch ); return; } vo = ( void * ) obj; break; } target_name = ""; ( *skill_table[sn].spell_fun ) ( sn, level, ch, vo ); if ( skill_table[sn].target == TAR_CHAR_OFFENSIVE && victim != ch && victim->master != ch ) { CHAR_DATA *vch; CHAR_DATA *vch_next; for ( vch = ch->in_room->people; vch; vch = vch_next ) { vch_next = vch->next_in_room; if ( victim == vch && victim->fighting == NULL ) { multi_hit ( victim, ch, TYPE_UNDEFINED ); break; } } } return; }