/**************************************************************************** * [S]imulated [M]edieval [A]dventure multi[U]ser [G]ame | \\._.// * * -----------------------------------------------------------| (0...0) * * SMAUG 1.4 (C) 1994, 1995, 1996, 1998 by Derek Snider | ).:.( * * -----------------------------------------------------------| {o o} * * SMAUG code team: Thoric, Altrag, Blodkai, Narn, Haus, | / ' ' \ * * Scryn, Rennard, Swordbearer, Gorog, Grishnakh, Nivek, |~'~.VxvxV.~'~* * Tricops and Fireblade | * * ------------------------------------------------------------------------ * * Merc 2.1 Diku Mud improvments copyright (C) 1992, 1993 by Michael * * Chastain, Michael Quan, and Mitchell Tse. * * Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer, * * Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe. * * ------------------------------------------------------------------------ * * Spell handling module * ****************************************************************************/ #include <sys/types.h> #include <ctype.h> #include <stdio.h> #include <string.h> #ifdef sun #include <strings.h> #endif #include <time.h> #include "mud.h" /* * Local functions. */ void say_spell args((CHAR_DATA * ch, int sn)); /* CHAR_DATA *make_poly_mob args( (CHAR_DATA *ch, int vnum) ); */ ch_ret spell_affect args((int sn, int level, CHAR_DATA * ch, void *vo)); ch_ret spell_affectchar args((int sn, int level, CHAR_DATA * ch, void *vo)); int dispel_casting(AFFECT_DATA * paf, CHAR_DATA * ch, CHAR_DATA * victim, int affect, bool dispel); bool can_charm(CHAR_DATA * ch); /* * Is immune to a damage type */ bool is_immune(CHAR_DATA * ch, sh_int damtype) { switch (damtype) { case SD_FIRE: if (IS_SET(ch->immune, RIS_FIRE)) return TRUE; case SD_COLD: if (IS_SET(ch->immune, RIS_COLD)) return TRUE; case SD_ELECTRICITY: if (IS_SET(ch->immune, RIS_ELECTRICITY)) return TRUE; case SD_ENERGY: if (IS_SET(ch->immune, RIS_ENERGY)) return TRUE; case SD_ACID: if (IS_SET(ch->immune, RIS_ACID)) return TRUE; case SD_POISON: if (IS_SET(ch->immune, RIS_POISON)) return TRUE; case SD_DRAIN: if (IS_SET(ch->immune, RIS_DRAIN)) return TRUE; } return FALSE; } /* * Lookup a skill by name, only stopping at skills the player has. */ int ch_slookup(CHAR_DATA * ch, const char *name) { int sn; if (IS_NPC(ch)) return skill_lookup(name); for (sn = 0; sn < top_sn; sn++) { if (!skill_table[sn]->name) break; if (ch->pcdata->learned[sn] > 0 && ch->exp >= skill_table[sn]->skill_level[ch->class] && LOWER(name[0]) == LOWER(skill_table[sn]->name[0]) && !str_prefix(name, skill_table[sn]->name)) return sn; } return -1; } /* * Lookup an herb by name. */ int herb_lookup(const char *name) { int sn; for (sn = 0; sn < top_herb; sn++) { if (!herb_table[sn] || !herb_table[sn]->name) return -1; if (LOWER(name[0]) == LOWER(herb_table[sn]->name[0]) && !str_prefix(name, herb_table[sn]->name)) return sn; } return -1; } /* * Lookup a personal skill * Unused for now. In place to allow a player to have a custom spell/skill. * When this is put in make sure you put in cleanup code if you do any * sort of allocating memory in free_char --Shaddai */ int personal_lookup(CHAR_DATA * ch, const char *name) { int sn; if (!ch->pcdata) return -1; for (sn = 0; sn < MAX_PERSONAL; sn++) { if (!ch->pcdata->special_skills[sn] || !ch->pcdata->special_skills[sn]->name) return -1; if (LOWER(name[0]) == LOWER(ch->pcdata->special_skills[sn]->name[0]) && !str_prefix(name, ch->pcdata->special_skills[sn]->name)) return sn; } return -1; } /* * Lookup a skill by name. */ int skill_lookup(const char *name) { int sn; if ((sn = bsearch_skill_exact(name, gsn_first_spell, gsn_first_skill - 1)) == -1 && (sn = bsearch_skill_exact(name, gsn_first_skill, gsn_first_ability - 1)) == -1 && (sn = bsearch_skill_exact(name, gsn_first_ability, gsn_first_weapon - 1)) == -1 && (sn = bsearch_skill_exact(name, gsn_first_weapon, gsn_first_tongue - 1)) == -1 && (sn = bsearch_skill_exact(name, gsn_first_tongue, gsn_top_sn - 1)) == -1 && (sn = bsearch_skill_prefix(name, gsn_first_spell, gsn_first_skill - 1)) == -1 && (sn = bsearch_skill_prefix(name, gsn_first_skill, gsn_first_ability - 1)) == -1 && (sn = bsearch_skill_prefix(name, gsn_first_ability, gsn_first_weapon - 1)) == -1 && (sn = bsearch_skill_prefix(name, gsn_first_weapon, gsn_first_tongue - 1)) == -1 && ((sn = bsearch_skill_prefix(name, gsn_first_tongue, gsn_top_sn - 1)) == -1 && gsn_top_sn < top_sn)) { for (sn = gsn_top_sn; sn < top_sn; sn++) { if (!skill_table[sn] || !skill_table[sn]->name) return -1; if (LOWER(name[0]) == LOWER(skill_table[sn]->name[0]) && !str_cmp(name, skill_table[sn]->name)) return sn; } return -1; } return sn; } /* * Return a skilltype pointer based on sn -Thoric * Returns NULL if bad, unused or personal sn. */ SKILLTYPE * get_skilltype(int sn) { if (sn >= TYPE_PERSONAL) return NULL; if (sn >= TYPE_HERB) return IS_VALID_HERB(sn - TYPE_HERB) ? herb_table[sn - TYPE_HERB] : NULL; if (sn >= TYPE_HIT) return NULL; return IS_VALID_SN(sn) ? skill_table[sn] : NULL; } /* * Perform a binary search on a section of the skill table -Thoric * Each different section of the skill table is sorted alphabetically * * Check for prefix matches */ int bsearch_skill_prefix(const char *name, int first, int top) { int sn; for (;;) { sn = (first + top) >> 1; if (!IS_VALID_SN(sn)) return -1; if (LOWER(name[0]) == LOWER(skill_table[sn]->name[0]) && !str_prefix(name, skill_table[sn]->name)) return sn; if (first >= top) return -1; if (strcmp(name, skill_table[sn]->name) < 1) top = sn - 1; else first = sn + 1; } return -1; } /* * Perform a binary search on a section of the skill table -Thoric * Each different section of the skill table is sorted alphabetically * * Check for exact matches only */ int bsearch_skill_exact(const char *name, int first, int top) { int sn; for (;;) { sn = (first + top) >> 1; if (!IS_VALID_SN(sn)) return -1; if (!str_cmp(name, skill_table[sn]->name)) return sn; if (first >= top) return -1; if (strcmp(name, skill_table[sn]->name) < 1) top = sn - 1; else first = sn + 1; } return -1; } /* * Perform a binary search on a section of the skill table -Thoric * Each different section of the skill table is sorted alphabetically * * Check exact match first, then a prefix match */ int bsearch_skill(const char *name, int first, int top) { int sn = bsearch_skill_exact(name, first, top); return (sn == -1) ? bsearch_skill_prefix(name, first, top) : sn; } /* * Perform a binary search on a section of the skill table * Each different section of the skill table is sorted alphabetically * Only match skills player knows -Thoric */ int ch_bsearch_skill_prefix(CHAR_DATA * ch, const char *name, int first, int top) { int sn; for (;;) { sn = (first + top) >> 1; if (LOWER(name[0]) == LOWER(skill_table[sn]->name[0]) && !str_prefix(name, skill_table[sn]->name) && ch->pcdata->learned[sn] > 0 && ch->exp >= skill_table[sn]->skill_level[ch->class]) return sn; if (first >= top) return -1; if (strcmp(name, skill_table[sn]->name) < 1) top = sn - 1; else first = sn + 1; } return -1; } int ch_bsearch_skill_exact(CHAR_DATA * ch, const char *name, int first, int top) { int sn; for (;;) { sn = (first + top) >> 1; if (!str_cmp(name, skill_table[sn]->name) && ch->pcdata->learned[sn] > 0 && ch->exp >= skill_table[sn]->skill_level[ch->class]) return sn; if (first >= top) return -1; if (strcmp(name, skill_table[sn]->name) < 1) top = sn - 1; else first = sn + 1; } return -1; } int ch_bsearch_skill(CHAR_DATA * ch, const char *name, int first, int top) { int sn = ch_bsearch_skill_exact(ch, name, first, top); return (sn == -1) ? ch_bsearch_skill_prefix(ch, name, first, top) : sn; } int find_spell(CHAR_DATA * ch, const char *name, bool know) { if (IS_NPC(ch) || !know) return bsearch_skill(name, gsn_first_spell, gsn_first_skill - 1); else return ch_bsearch_skill(ch, name, gsn_first_spell, gsn_first_skill - 1); } int find_skill(CHAR_DATA * ch, const char *name, bool know) { if (IS_NPC(ch) || !know) return bsearch_skill(name, gsn_first_skill, gsn_first_weapon - 1); else return ch_bsearch_skill(ch, name, gsn_first_skill, gsn_first_weapon - 1); } int find_weapon(CHAR_DATA * ch, const char *name, bool know) { if (IS_NPC(ch) || !know) return bsearch_skill(name, gsn_first_weapon, gsn_first_tongue - 1); else return ch_bsearch_skill(ch, name, gsn_first_weapon, gsn_first_tongue - 1); } int find_tongue(CHAR_DATA * ch, const char *name, bool know) { if (IS_NPC(ch) || !know) return bsearch_skill(name, gsn_first_tongue, gsn_top_sn - 1); else return ch_bsearch_skill(ch, name, gsn_first_tongue, gsn_top_sn - 1); } /* * Lookup a skill by slot number. * Used for object loading. */ int slot_lookup(int slot) { extern bool fBootDb; int sn; if (slot <= 0) return -1; for (sn = 0; sn < top_sn; sn++) if (slot == skill_table[sn]->slot) return sn; if (fBootDb) { bug("Slot_lookup: bad slot %d.", slot); abort(); } return -1; } /* * Handler to tell the victim which spell is being affected. * Shaddai */ int dispel_casting(AFFECT_DATA * paf, CHAR_DATA * ch, CHAR_DATA * victim, int affect, bool dispel) { char buf[MAX_STRING_LENGTH]; char *spell; SKILLTYPE *sktmp; bool is_mage = FALSE, has_detect = FALSE; EXT_BV ext_bv = meb(affect); if (IS_NPC(ch)) is_mage = TRUE; if (IS_AFFECTED(ch, AFF_DETECT_MAGIC)) has_detect = TRUE; if (paf) { if ((sktmp = get_skilltype(paf->type)) == NULL) return 0; spell = sktmp->name; } else spell = affect_bit_name(&ext_bv); set_char_color(AT_MAGIC, ch); set_char_color(AT_HITME, victim); if (!can_see(ch, victim)) strcpy(buf, "Someone"); else { strcpy(buf, (IS_NPC(victim) ? victim->short_descr : victim->name)); buf[0] = toupper(buf[0]); } if (dispel) { ch_printf(victim, "Your %s vanishes.\n\r", spell); if (is_mage && has_detect) ch_printf(ch, "%s's %s vanishes.\n\r", buf, spell); else return 0; /* So we give the default Ok. Message */ } else { if (is_mage && has_detect) ch_printf(ch, "%s's %s wavers but holds.\n\r", buf, spell); else return 0; /* The wonderful Failed. Message */ } return 1; } /* * Fancy message handling for a successful casting -Thoric */ void successful_casting(SKILLTYPE * skill, CHAR_DATA * ch, CHAR_DATA * victim, OBJ_DATA * obj) { sh_int chitroom = (skill->type == SKILL_SPELL ? AT_MAGIC : AT_ACTION); sh_int chit = (skill->type == SKILL_SPELL ? AT_MAGIC : AT_HIT); sh_int chitme = (skill->type == SKILL_SPELL ? AT_MAGIC : AT_HITME); if (skill->target != TAR_CHAR_OFFENSIVE) { chit = chitroom; chitme = chitroom; } if (ch && ch != victim) { if (skill->hit_char && skill->hit_char[0] != '\0') { if (str_cmp(skill->hit_char, SPELL_SILENT_MARKER)) act(AT_COLORIZE, skill->hit_char, ch, obj, victim, TO_CHAR); } else if (skill->type == SKILL_SPELL) act(AT_COLORIZE, "Ok.", ch, NULL, NULL, TO_CHAR); } if (ch && skill->hit_room && skill->hit_room[0] != '\0' && str_cmp(skill->hit_room, SPELL_SILENT_MARKER)) act(AT_COLORIZE, skill->hit_room, ch, obj, victim, TO_NOTVICT); if (ch && victim && skill->hit_vict && skill->hit_vict[0] != '\0') { if (str_cmp(skill->hit_vict, SPELL_SILENT_MARKER)) { if (ch != victim) act(AT_COLORIZE, skill->hit_vict, ch, obj, victim, TO_VICT); else act(AT_COLORIZE, skill->hit_vict, ch, obj, victim, TO_CHAR); } } else if (ch && ch == victim && skill->type == SKILL_SPELL) act(AT_COLORIZE, "Ok.", ch, NULL, NULL, TO_CHAR); } /* * Fancy message handling for a failed casting -Thoric */ void failed_casting(SKILLTYPE * skill, CHAR_DATA * ch, CHAR_DATA * victim, OBJ_DATA * obj) { sh_int chitroom = (skill->type == SKILL_SPELL ? AT_MAGIC : AT_ACTION); sh_int chit = (skill->type == SKILL_SPELL ? AT_MAGIC : AT_HIT); sh_int chitme = (skill->type == SKILL_SPELL ? AT_MAGIC : AT_HITME); if (skill->target != TAR_CHAR_OFFENSIVE) { chit = chitroom; chitme = chitroom; } if (ch && ch != victim) { if (skill->miss_char && skill->miss_char[0] != '\0') { if (str_cmp(skill->miss_char, SPELL_SILENT_MARKER)) act(AT_COLORIZE, skill->miss_char, ch, obj, victim, TO_CHAR); } else if (skill->type == SKILL_SPELL) act(chitme, "You failed.", ch, NULL, NULL, TO_CHAR); } if (ch && skill->miss_room && skill->miss_room[0] != '\0' && str_cmp(skill->miss_room, SPELL_SILENT_MARKER) && str_cmp(skill->miss_room, "supress")) /* Back Compat -- Alty */ act(AT_COLORIZE, skill->miss_room, ch, obj, victim, TO_NOTVICT); if (ch && victim && skill->miss_vict && skill->miss_vict[0] != '\0') { if (str_cmp(skill->miss_vict, SPELL_SILENT_MARKER)) { if (ch != victim) act(AT_COLORIZE, skill->miss_vict, ch, obj, victim, TO_VICT); else act(AT_COLORIZE, skill->miss_vict, ch, obj, victim, TO_CHAR); } } else if (ch && ch == victim) { if (skill->miss_char && skill->miss_char[0] != '\0') { if (str_cmp(skill->miss_char, SPELL_SILENT_MARKER)) act(AT_COLORIZE, skill->miss_char, ch, obj, victim, TO_CHAR); } else if (skill->type == SKILL_SPELL) act(chitme, "You failed.", ch, NULL, NULL, TO_CHAR); } } /* * Fancy message handling for being immune to something -Thoric */ void immune_casting(SKILLTYPE * skill, CHAR_DATA * ch, CHAR_DATA * victim, OBJ_DATA * obj) { sh_int chitroom = (skill->type == SKILL_SPELL ? AT_MAGIC : AT_ACTION); sh_int chit = (skill->type == SKILL_SPELL ? AT_MAGIC : AT_HIT); sh_int chitme = (skill->type == SKILL_SPELL ? AT_MAGIC : AT_HITME); if (skill->target != TAR_CHAR_OFFENSIVE) { chit = chitroom; chitme = chitroom; } if (ch && ch != victim) { if (skill->imm_char && skill->imm_char[0] != '\0') { if (str_cmp(skill->imm_char, SPELL_SILENT_MARKER)) act(AT_COLORIZE, skill->imm_char, ch, obj, victim, TO_CHAR); } else if (skill->miss_char && skill->miss_char[0] != '\0') { if (str_cmp(skill->miss_char, SPELL_SILENT_MARKER)) act(AT_COLORIZE, skill->hit_char, ch, obj, victim, TO_CHAR); } else if (skill->type == SKILL_SPELL || skill->type == SKILL_SKILL) act(chit, "That appears to have no effect.", ch, NULL, NULL, TO_CHAR); } if (ch && skill->imm_room && skill->imm_room[0] != '\0') { if (str_cmp(skill->imm_room, SPELL_SILENT_MARKER)) act(AT_COLORIZE, skill->imm_room, ch, obj, victim, TO_NOTVICT); } else if (ch && skill->miss_room && skill->miss_room[0] != '\0') { if (str_cmp(skill->miss_room, SPELL_SILENT_MARKER)) act(AT_COLORIZE, skill->miss_room, ch, obj, victim, TO_NOTVICT); } if (ch && victim && skill->imm_vict && skill->imm_vict[0] != '\0') { if (str_cmp(skill->imm_vict, SPELL_SILENT_MARKER)) { if (ch != victim) act(AT_COLORIZE, skill->imm_vict, ch, obj, victim, TO_VICT); else act(AT_COLORIZE, skill->imm_vict, ch, obj, victim, TO_CHAR); } } else if (ch && victim && skill->miss_vict && skill->miss_vict[0] != '\0') { if (str_cmp(skill->miss_vict, SPELL_SILENT_MARKER)) { if (ch != victim) act(AT_COLORIZE, skill->miss_vict, ch, obj, victim, TO_VICT); else act(AT_COLORIZE, skill->miss_vict, ch, obj, victim, TO_CHAR); } } else if (ch && ch == victim) { if (skill->imm_char && skill->imm_char[0] != '\0') { if (str_cmp(skill->imm_char, SPELL_SILENT_MARKER)) act(AT_COLORIZE, skill->imm_char, ch, obj, victim, TO_CHAR); } else if (skill->miss_char && skill->miss_char[0] != '\0') { if (str_cmp(skill->hit_char, SPELL_SILENT_MARKER)) act(AT_COLORIZE, skill->hit_char, ch, obj, victim, TO_CHAR); } else if (skill->type == SKILL_SPELL || skill->type == SKILL_SKILL) act(chit, "That appears to have no affect.", ch, NULL, NULL, TO_CHAR); } } /* * Utter mystical words for an sn. */ 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; SKILLTYPE *skill = get_skilltype(sn); 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"}, {"polymorph", "iaddahs"}, {"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 = skill->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 utters the words, '%s'.", buf); sprintf(buf, "$n utters the words, '%s'.", skill->name); for (rch = ch->in_room->first_person; rch; rch = rch->next_in_room) { if (rch != ch) act(AT_MAGIC, ch->class == rch->class ? buf : buf2, ch, NULL, rch, TO_VICT); } return; } /* * Make adjustments to saving throw based in RIS -Thoric */ int ris_save(CHAR_DATA * ch, int chance, int ris) { sh_int modifier; modifier = 10; if (IS_SET(ch->immune, ris)) modifier -= 10; if (IS_SET(ch->resistant, ris)) modifier -= 2; if (IS_SET(ch->susceptible, ris)) { if (IS_NPC(ch) && IS_SET(ch->immune, ris)) modifier += 0; else modifier += 2; } if (modifier <= 0) return 1000; if (modifier == 10) return chance; return (chance * modifier) / 10; } /* -Thoric * Fancy dice expression parsing complete with order of operations, * simple exponent support, dice support as well as a few extra * variables: L = level, H = hp, M = mana, V = move, S = str, X = dex * I = int, W = wis, C = con, A = cha, U = luck, A = age * * Used for spell dice parsing, ie: 3d8+L-6 * */ int rd_parse(CHAR_DATA * ch, int level, char *exp) { int x, lop = 0, gop = 0, eop = 0; char operation; char *sexp[2]; int total = 0, len = 0; /* * take care of nulls coming in */ if (!exp || !strlen(exp)) return 0; /* * get rid of brackets if they surround the entire expresion */ if ((*exp == '(') && !index(exp + 1, '(') && exp[strlen(exp) - 1] == ')') { exp[strlen(exp) - 1] = '\0'; exp++; } /* * check if the expresion is just a number */ len = strlen(exp); if (len == 1 && isalpha(exp[0])) { switch (exp[0]) { case 'L': case 'l': return level; case 'H': case 'h': return ch->hit; case 'M': case 'm': return ch->mana; case 'V': case 'v': return ch->max_move; case 'S': case 's': return get_curr_str(ch); case 'I': case 'i': return get_curr_int(ch); case 'X': case 'x': return get_curr_dex(ch); case 'C': case 'c': return get_curr_con(ch); case 'U': case 'u': return get_curr_lck(ch); case 'Y': case 'y': return get_age(ch); } } for (x = 0; x < len; ++x) if (!isdigit(exp[x]) && !isspace(exp[x])) break; if (x == len) return atoi(exp); /* * break it into 2 parts */ for (x = 0; x < strlen(exp); ++x) switch (exp[x]) { case '^': if (!total) eop = x; break; case '-': case '+': if (!total) lop = x; break; case '*': case '/': case '%': case 'd': case 'D': case '<': case '>': case '{': case '}': case '=': if (!total) gop = x; break; case '(': ++total; break; case ')': --total; break; } if (lop) x = lop; else if (gop) x = gop; else x = eop; operation = exp[x]; exp[x] = '\0'; sexp[0] = exp; sexp[1] = (char *) (exp + x + 1); /* * work it out */ total = rd_parse(ch, level, sexp[0]); switch (operation) { case '-': total -= rd_parse(ch, level, sexp[1]); break; case '+': total += rd_parse(ch, level, sexp[1]); break; case '*': total *= rd_parse(ch, level, sexp[1]); break; case '/': total /= rd_parse(ch, level, sexp[1]); break; case '%': total %= rd_parse(ch, level, sexp[1]); break; case 'd': case 'D': total = dice(total, rd_parse(ch, level, sexp[1])); break; case '<': total = (total < rd_parse(ch, level, sexp[1])); break; case '>': total = (total > rd_parse(ch, level, sexp[1])); break; case '=': total = (total == rd_parse(ch, level, sexp[1])); break; case '{': total = UMIN(total, rd_parse(ch, level, sexp[1])); break; case '}': total = UMAX(total, rd_parse(ch, level, sexp[1])); break; case '^': { int y = rd_parse(ch, level, sexp[1]), z = total; for (x = 1; x < y; ++x, z *= total); total = z; break; } } return total; } /* wrapper function so as not to destroy exp */ int dice_parse(CHAR_DATA * ch, int level, char *exp) { char buf[MAX_INPUT_LENGTH]; strcpy(buf, exp); return rd_parse(ch, level, buf); } /* * Compute a saving throw. * Negative apply's make saving throw better. */ bool saves_poison_death(int level, CHAR_DATA * victim) { int save; save = 50 + (victim->level - level - victim->saving_poison_death) * 5; save = URANGE(5, save, 95); return chance(victim, save); } bool saves_wands(int level, CHAR_DATA * victim) { int save; if (IS_SET(victim->immune, RIS_MAGIC)) return TRUE; save = 50 + (victim->level - level - victim->saving_wand) * 5; save = URANGE(5, save, 95); return chance(victim, save); } bool saves_para_petri(int level, CHAR_DATA * victim) { int save; save = 50 + (victim->level - level - victim->saving_para_petri) * 5; save = URANGE(5, save, 95); return chance(victim, save); } bool saves_breath(int level, CHAR_DATA * victim) { int save; save = 50 + (victim->level - level - victim->saving_breath) * 5; save = URANGE(5, save, 95); return chance(victim, save); } bool saves_spell_staff(int level, CHAR_DATA * victim) { int save; if (IS_SET(victim->immune, RIS_MAGIC)) return TRUE; if (IS_NPC(victim) && level > 10) level -= 5; save = 50 + (victim->level - level - victim->saving_spell_staff) * 5; save = URANGE(5, save, 95); return chance(victim, save); } /* * Process the spell's required components, if any -Thoric * ----------------------------------------------- * T### check for item of type ### * V##### check for item of vnum ##### * Kword check for item with keyword 'word' * G##### check if player has ##### amount of gold * H#### check if player has #### amount of hitpoints * * Special operators: * ! spell fails if player has this * + don't consume this component * @ decrease component's value[0], and extract if it reaches 0 * # decrease component's value[1], and extract if it reaches 0 * $ decrease component's value[2], and extract if it reaches 0 * % decrease component's value[3], and extract if it reaches 0 * ^ decrease component's value[4], and extract if it reaches 0 * & decrease component's value[5], and extract if it reaches 0 */ bool process_spell_components(CHAR_DATA * ch, int sn) { SKILLTYPE *skill = get_skilltype(sn); char *comp = skill->components; char *check; char arg[MAX_INPUT_LENGTH]; bool consume, fail, found; int val, value; OBJ_DATA *obj; /* * if no components necessary, then everything is cool */ if (!comp || comp[0] == '\0') return TRUE; while (comp[0] != '\0') { comp = one_argument(comp, arg); consume = TRUE; fail = found = FALSE; val = -1; switch (arg[1]) { default: check = arg + 1; break; case '!': check = arg + 2; fail = TRUE; break; case '+': check = arg + 2; consume = FALSE; break; case '@': check = arg + 2; val = 0; break; case '#': check = arg + 2; val = 1; break; case '$': check = arg + 2; val = 2; break; case '%': check = arg + 2; val = 3; break; case '^': check = arg + 2; val = 4; break; case '&': check = arg + 2; val = 5; break; /* * reserve '*', '(' and ')' for v6, v7 and v8 */ } value = atoi(check); obj = NULL; switch (UPPER(arg[0])) { case 'T': for (obj = ch->first_carrying; obj; obj = obj->next_content) if (obj->item_type == value) { if (fail) { send_to_char ("Something disrupts the casting of this spell...\n\r", ch); return FALSE; } found = TRUE; break; } break; case 'V': for (obj = ch->first_carrying; obj; obj = obj->next_content) if (obj->pIndexData->vnum == value) { if (fail) { send_to_char ("Something disrupts the casting of this spell...\n\r", ch); return FALSE; } found = TRUE; break; } break; case 'K': for (obj = ch->first_carrying; obj; obj = obj->next_content) if (nifty_is_name(check, obj->name)) { if (fail) { send_to_char ("Something disrupts the casting of this spell...\n\r", ch); return FALSE; } found = TRUE; break; } break; case 'G': if (ch->gold >= value) { if (fail) { send_to_char ("Something disrupts the casting of this spell...\n\r", ch); return FALSE; } else { if (consume) { set_char_color(AT_GOLD, ch); send_to_char("You feel a little lighter...\n\r", ch); ch->gold -= value; } continue; } } break; case 'H': if (ch->hit >= value) { if (fail) { send_to_char ("Something disrupts the casting of this spell...\n\r", ch); return FALSE; } else { if (consume) { set_char_color(AT_BLOOD, ch); send_to_char("You feel a little weaker...\n\r", ch); ch->hit -= value; update_pos(ch); } continue; } } break; } /* * having this component would make the spell fail... if we get * here, then the caster didn't have that component */ if (fail) continue; if (!found) { send_to_char("Something is missing...\n\r", ch); return FALSE; } if (obj) { if (val >= 0 && val < 6) { separate_obj(obj); if (obj->value[val] <= 0) { act(AT_MAGIC, "$p disappears in a puff of smoke!", ch, obj, NULL, TO_CHAR); act(AT_MAGIC, "$p disappears in a puff of smoke!", ch, obj, NULL, TO_ROOM); extract_obj(obj); return FALSE; } else if (--obj->value[val] == 0) { act(AT_MAGIC, "$p glows briefly, then disappears in a puff of smoke!", ch, obj, NULL, TO_CHAR); act(AT_MAGIC, "$p glows briefly, then disappears in a puff of smoke!", ch, obj, NULL, TO_ROOM); extract_obj(obj); } else act(AT_MAGIC, "$p glows briefly and a whisp of smoke rises from it.", ch, obj, NULL, TO_CHAR); } else if (consume) { separate_obj(obj); act(AT_MAGIC, "$p glows brightly, then disappears in a puff of smoke!", ch, obj, NULL, TO_CHAR); act(AT_MAGIC, "$p glows brightly, then disappears in a puff of smoke!", ch, obj, NULL, TO_ROOM); extract_obj(obj); } else { int count = obj->count; obj->count = 1; act(AT_MAGIC, "$p glows briefly.", ch, obj, NULL, TO_CHAR); obj->count = count; } } } return TRUE; } int pAbort; /* * Locate targets. */ /* Turn off annoying message and just abort if needed */ bool silence_locate_targets; void * locate_targets(CHAR_DATA * ch, char *arg, int sn, CHAR_DATA ** victim, OBJ_DATA ** obj) { SKILLTYPE *skill = get_skilltype(sn); void *vo = NULL; *victim = NULL; *obj = NULL; switch (skill->target) { default: bug("Do_cast: bad target for sn %d.", sn); return &pAbort; case TAR_IGNORE: break; case TAR_CHAR_OFFENSIVE: { if (arg[0] == '\0') { if ((*victim = who_fighting(ch)) == NULL) { if (!silence_locate_targets) send_to_char("Cast the spell on whom?\n\r", ch); return &pAbort; } } else { if ((*victim = get_char_room(ch, arg)) == NULL) { if (!silence_locate_targets) send_to_char("They aren't here.\n\r", ch); return &pAbort; } } } /* * Offensive spells will choose the ch up to 92% of the time * * if the nuisance flag is set -- Shaddai */ if (!IS_NPC(ch) && ch->pcdata->nuisance && ch->pcdata->nuisance->flags > 5 && number_percent() < (((ch->pcdata->nuisance->flags - 5) * 8) + ch->pcdata->nuisance->power * 6)) *victim = ch; if (is_safe(ch, *victim, TRUE)) return &pAbort; if (ch == *victim) { if (SPELL_FLAG(get_skilltype(sn), SF_NOSELF)) { if (!silence_locate_targets) send_to_char("You can't cast this on yourself!\n\r", ch); return &pAbort; } if (!silence_locate_targets) send_to_char("Cast this on yourself? Okay...\n\r", ch); /* * send_to_char( "You can't do that to yourself.\n\r", ch ); * return &pAbort; */ } if (!IS_NPC(ch)) { if (!IS_NPC(*victim)) { if (get_timer(ch, TIMER_PKILLED) > 0) { if (!silence_locate_targets) send_to_char("You have been killed in the last 5 minutes.\n\r", ch); return &pAbort; } if (get_timer(*victim, TIMER_PKILLED) > 0) { if (!silence_locate_targets) send_to_char ("This player has been killed in the last 5 minutes.\n\r", ch); return &pAbort; } if (xIS_SET(ch->act, PLR_NICE) && ch != *victim) { if (!silence_locate_targets) send_to_char("You are too nice to attack another player.\n\r", ch); return &pAbort; } if (*victim != ch) { if (!silence_locate_targets) send_to_char ("You really shouldn't do this to another player...\n\r", ch); else if (who_fighting(*victim) != ch) { /* * Only auto-attack those that are hitting you. */ return &pAbort; } } } if (IS_AFFECTED(ch, AFF_CHARM) && ch->master == *victim) { if (!silence_locate_targets) send_to_char("You can't do that on your own follower.\n\r", ch); return &pAbort; } } check_illegal_pk(ch, *victim); vo = (void *) *victim; break; case TAR_CHAR_DEFENSIVE: { if (arg[0] == '\0') *victim = ch; else { if ((*victim = get_char_room(ch, arg)) == NULL) { if (!silence_locate_targets) send_to_char("They aren't here.\n\r", ch); return &pAbort; } } } /* * Nuisance flag will pick who you are fighting for defensive * * spells up to 36% of the time -- Shaddai */ if (!IS_NPC(ch) && ch->fighting && ch->pcdata->nuisance && ch->pcdata->nuisance->flags > 5 && number_percent() < (((ch->pcdata->nuisance->flags - 5) * 8) + 6 * ch->pcdata->nuisance->power)) *victim = who_fighting(ch); if (ch == *victim && SPELL_FLAG(get_skilltype(sn), SF_NOSELF)) { if (!silence_locate_targets) send_to_char("You can't cast this on yourself!\n\r", ch); return &pAbort; } vo = (void *) *victim; break; case TAR_CHAR_SELF: if (arg[0] != '\0' && !nifty_is_name(arg, ch->name)) { if (!silence_locate_targets) send_to_char("You cannot cast this spell on another.\n\r", ch); return &pAbort; } vo = (void *) ch; break; case TAR_OBJ_INV: { if (arg[0] == '\0') { if (!silence_locate_targets) send_to_char("What should the spell be cast upon?\n\r", ch); return &pAbort; } if ((*obj = get_obj_carry(ch, arg)) == NULL) { if (!silence_locate_targets) send_to_char("You are not carrying that.\n\r", ch); return &pAbort; } } vo = (void *) *obj; break; } return vo; } /* * The kludgy global is for spells who want more stuff from command line. */ char *target_name; char *ranged_target_name = NULL; /* * Cast a spell. Multi-caster and component support by Thoric */ void do_cast(CHAR_DATA * ch, char *argument) { char arg1[MAX_INPUT_LENGTH]; char arg2[MAX_INPUT_LENGTH]; static char staticbuf[MAX_INPUT_LENGTH]; CHAR_DATA *victim; OBJ_DATA *obj; void *vo = NULL; int mana; int sn; ch_ret retcode; bool dont_wait = FALSE; SKILLTYPE *skill = NULL; struct timeval time_used; retcode = rNONE; switch (ch->substate) { default: /* * no ordering charmed mobs to cast spells */ if (IS_NPC(ch) && (IS_AFFECTED(ch, AFF_CHARM) || IS_AFFECTED(ch, AFF_POSSESS))) { send_to_char("You can't seem to do that right now...\n\r", ch); return; } if (xIS_SET(ch->in_room->room_flags, ROOM_NO_MAGIC)) { set_char_color(AT_MAGIC, ch); send_to_char("You failed.\n\r", ch); return; } target_name = one_argument(argument, arg1); one_argument(target_name, arg2); if (ranged_target_name) DISPOSE(ranged_target_name); ranged_target_name = str_dup(target_name); if (arg1[0] == '\0') { send_to_char("Cast which what where?\n\r", ch); return; } /* * Regular mortal spell casting */ if (get_trust(ch) < LEVEL_GOD) { if ((sn = find_spell(ch, arg1, TRUE)) < 0 || (!IS_NPC(ch) && ch->level < skill_table[sn]->skill_level[ch->class])) { send_to_char("You can't do that.\n\r", ch); return; } if ((skill = get_skilltype(sn)) == NULL) { send_to_char("You can't do that right now...\n\r", ch); return; } } else /* * Godly "spell builder" spell casting with debugging messages */ { if ((sn = skill_lookup(arg1)) < 0) { send_to_char("We didn't create that yet...\n\r", ch); return; } if (sn >= MAX_SKILL) { send_to_char("Hmm... that might hurt.\n\r", ch); return; } if ((skill = get_skilltype(sn)) == NULL) { send_to_char("Something is severely wrong with that one...\n\r", ch); return; } if (skill->type != SKILL_SPELL) { send_to_char("That isn't a spell.\n\r", ch); return; } if (!skill->spell_fun) { send_to_char("We didn't finish that one yet...\n\r", ch); return; } } /* * Something else removed by Merc -Thoric */ /* * Band-aid alert! !IS_NPC check -- Blod */ if (ch->position < skill->minimum_position && !IS_NPC(ch)) { switch (ch->position) { default: send_to_char("You can't concentrate enough.\n\r", ch); break; case POS_SITTING: send_to_char("You can't summon enough energy sitting down.\n\r", ch); break; case POS_RESTING: send_to_char("You're too relaxed to cast that spell.\n\r", ch); break; case POS_FIGHTING: if (skill->minimum_position <= POS_EVASIVE) { send_to_char ("This fighting style is too demanding for that!\n\r", ch); } else { send_to_char("No way! You are still fighting!\n\r", ch); } break; case POS_DEFENSIVE: if (skill->minimum_position <= POS_EVASIVE) { send_to_char ("This fighting style is too demanding for that!\n\r", ch); } else { send_to_char("No way! You are still fighting!\n\r", ch); } break; case POS_AGGRESSIVE: if (skill->minimum_position <= POS_EVASIVE) { send_to_char ("This fighting style is too demanding for that!\n\r", ch); } else { send_to_char("No way! You are still fighting!\n\r", ch); } break; case POS_BERSERK: if (skill->minimum_position <= POS_EVASIVE) { send_to_char ("This fighting style is too demanding for that!\n\r", ch); } else { send_to_char("No way! You are still fighting!\n\r", ch); } break; case POS_EVASIVE: send_to_char("No way! You are still fighting!\n\r", ch); break; case POS_SLEEPING: send_to_char("You dream about great feats of magic.\n\r", ch); break; } return; } if (skill->spell_fun == spell_null) { send_to_char("That's not a spell!\n\r", ch); return; } if (!skill->spell_fun) { send_to_char("You cannot cast that... yet.\n\r", ch); return; } if (!IS_NPC(ch) /* fixed by Thoric */ &&!IS_IMMORTAL(ch) && skill->guild != CLASS_NONE && (!ch->pcdata->clan || skill->guild != ch->pcdata->clan->class)) { send_to_char ("That is only available to members of a certain guild.\n\r", ch); return; } /* * Mystaric, 980908 - Added checks for spell sector type */ if (!ch->in_room || (skill->spell_sector && !IS_SET(skill->spell_sector, (1 << ch->in_room->sector_type)))) { send_to_char("You can not cast that here.\n\r", ch); return; } mana = IS_NPC(ch) ? 0 : UMAX(skill->min_mana, 100 / (2 + ch->level - skill->skill_level[ch->class])); /* * Locate targets. */ vo = locate_targets(ch, arg2, sn, &victim, &obj); if (vo == &pAbort) return; if (!IS_NPC(ch) && victim && !IS_NPC(victim) && CAN_PKILL(victim) && !CAN_PKILL(ch) && !in_arena(ch) && !in_arena(victim)) { set_char_color(AT_MAGIC, ch); send_to_char ("The gods will not permit you to cast spells on that character.\n\r", ch); return; } if (!IS_NPC(ch) && ch->mana < mana) { send_to_char("You don't have enough mana.\n\r", ch); return; } if (skill->participants <= 1) break; /* * multi-participant spells -Thoric */ add_timer(ch, TIMER_DO_FUN, UMIN(skill->beats / 10, 3), do_cast, 1); act(AT_MAGIC, "You begin to chant...", ch, NULL, NULL, TO_CHAR); act(AT_MAGIC, "$n begins to chant...", ch, NULL, NULL, TO_ROOM); sprintf(staticbuf, "%s %s", arg2, target_name); ch->alloc_ptr = str_dup(staticbuf); ch->tempnum = sn; return; case SUB_TIMER_DO_ABORT: DISPOSE(ch->alloc_ptr); if (IS_VALID_SN((sn = ch->tempnum))) { if ((skill = get_skilltype(sn)) == NULL) { send_to_char("Something went wrong...\n\r", ch); bug("do_cast: SUB_TIMER_DO_ABORT: bad sn %d", sn); return; } mana = IS_NPC(ch) ? 0 : UMAX(skill->min_mana, 100 / (2 + ch->level - skill->skill_level[ch->class])); if (ch->level < LEVEL_IMMORTAL) /* so imms dont lose mana */ ch->mana -= mana / 3; } set_char_color(AT_MAGIC, ch); send_to_char("You stop chanting...\n\r", ch); /* * should add chance of backfire here */ return; case 1: sn = ch->tempnum; if ((skill = get_skilltype(sn)) == NULL) { send_to_char("Something went wrong...\n\r", ch); bug("do_cast: substate 1: bad sn %d", sn); return; } if (!ch->alloc_ptr || !IS_VALID_SN(sn) || skill->type != SKILL_SPELL) { send_to_char("Something cancels out the spell!\n\r", ch); bug("do_cast: ch->alloc_ptr NULL or bad sn (%d)", sn); return; } mana = IS_NPC(ch) ? 0 : UMAX(skill->min_mana, 100 / (2 + ch->level - skill->skill_level[ch->class])); strcpy(staticbuf, ch->alloc_ptr); target_name = one_argument(staticbuf, arg2); DISPOSE(ch->alloc_ptr); ch->substate = SUB_NONE; if (skill->participants > 1) { int cnt = 1; CHAR_DATA *tmp; TIMER *t; for (tmp = ch->in_room->first_person; tmp; tmp = tmp->next_in_room) if (tmp != ch && (t = get_timerptr(tmp, TIMER_DO_FUN)) != NULL && t->count >= 1 && t->do_fun == do_cast && tmp->tempnum == sn && tmp->alloc_ptr && !str_cmp(tmp->alloc_ptr, staticbuf)) ++cnt; if (cnt >= skill->participants) { for (tmp = ch->in_room->first_person; tmp; tmp = tmp->next_in_room) if (tmp != ch && (t = get_timerptr(tmp, TIMER_DO_FUN)) != NULL && t->count >= 1 && t->do_fun == do_cast && tmp->tempnum == sn && tmp->alloc_ptr && !str_cmp(tmp->alloc_ptr, staticbuf)) { extract_timer(tmp, t); act(AT_MAGIC, "Channeling your energy into $n, you help cast the spell!", ch, NULL, tmp, TO_VICT); act(AT_MAGIC, "$N channels $S energy into you!", ch, NULL, tmp, TO_CHAR); act(AT_MAGIC, "$N channels $S energy into $n!", ch, NULL, tmp, TO_NOTVICT); learn_from_success(tmp, sn); tmp->mana -= mana; tmp->substate = SUB_NONE; tmp->tempnum = -1; DISPOSE(tmp->alloc_ptr); } dont_wait = TRUE; send_to_char ("You concentrate all the energy into a burst of mystical words!\n\r", ch); vo = locate_targets(ch, arg2, sn, &victim, &obj); if (vo == &pAbort) return; } else { set_char_color(AT_MAGIC, ch); send_to_char ("There was not enough power for the spell to succeed...\n\r", ch); if (ch->level < LEVEL_IMMORTAL) /* so imms dont lose mana */ ch->mana -= mana / 2; learn_from_failure(ch, sn); return; } } } /* * uttering those magic words unless casting "ventriloquate" */ if (str_cmp(skill->name, "ventriloquate")) say_spell(ch, sn); if (!dont_wait) WAIT_STATE(ch, skill->beats); /* * Getting ready to cast... check for spell components -Thoric */ if (!process_spell_components(ch, sn)) { if (ch->level < LEVEL_IMMORTAL) /* so imms dont lose mana */ ch->mana -= mana / 2; learn_from_failure(ch, sn); return; } if (!IS_NPC(ch) && (number_percent() + skill->difficulty * 5) > ch->pcdata->learned[sn]) { /* * Some more interesting loss of concentration messages -Thoric */ switch (number_bits(2)) { case 0: /* too busy */ if (ch->fighting) send_to_char ("This round of battle is too hectic to concentrate properly.\n\r", ch); else send_to_char("You lost your concentration.\n\r", ch); break; case 1: /* irritation */ if (number_bits(2) == 0) { switch (number_bits(2)) { case 0: send_to_char ("A tickle in your nose prevents you from keeping your concentration.\n\r", ch); break; case 1: send_to_char ("An itch on your leg keeps you from properly casting your spell.\n\r", ch); break; case 2: send_to_char ("Something in your throat prevents you from uttering the proper phrase.\n\r", ch); break; case 3: send_to_char ("A twitch in your eye disrupts your concentration for a moment.\n\r", ch); break; } } else send_to_char ("Something distracts you, and you lose your concentration.\n\r", ch); break; case 2: /* not enough time */ if (ch->fighting) send_to_char ("There wasn't enough time this round to complete the casting.\n\r", ch); else send_to_char("You lost your concentration.\n\r", ch); break; case 3: send_to_char ("You get a mental block mid-way through the casting.\n\r", ch); break; } if (ch->level < LEVEL_IMMORTAL) /* so imms dont lose mana */ ch->mana -= mana / 2; learn_from_failure(ch, sn); return; } else { ch->mana -= mana; /* * check for immunity to magic if victim is known... * and it is a TAR_CHAR_DEFENSIVE/SELF spell * otherwise spells will have to check themselves */ if (((skill->target == TAR_CHAR_DEFENSIVE || skill->target == TAR_CHAR_SELF) && victim && IS_SET(victim->immune, RIS_MAGIC))) { immune_casting(skill, ch, victim, NULL); retcode = rSPELL_FAILED; } else { start_timer(&time_used); retcode = (*skill->spell_fun) (sn, ch->level, ch, vo); end_timer(&time_used); update_userec(&time_used, &skill->userec); } } if (ch->in_room && IS_SET(ch->in_room->area->flags, AFLAG_SPELLLIMIT)) ch->in_room->area->curr_spell_count++; if (retcode == rCHAR_DIED || retcode == rERROR || char_died(ch)) return; /* * learning */ if (retcode != rSPELL_FAILED) learn_from_success(ch, sn); else learn_from_failure(ch, sn); /* * favor adjustments */ if (victim && victim != ch && !IS_NPC(victim) && skill->target == TAR_CHAR_DEFENSIVE) adjust_favor(ch, 7, 1); if (victim && victim != ch && !IS_NPC(ch) && skill->target == TAR_CHAR_DEFENSIVE) adjust_favor(victim, 13, 1); if (victim && victim != ch && !IS_NPC(ch) && skill->target == TAR_CHAR_OFFENSIVE) adjust_favor(ch, 4, 1); /* * Fixed up a weird mess here, and added double safeguards -Thoric */ if (skill->target == TAR_CHAR_OFFENSIVE && victim && !char_died(victim) && victim != ch) { CHAR_DATA *vch, *vch_next; for (vch = ch->in_room->first_person; vch; vch = vch_next) { vch_next = vch->next_in_room; if (vch == victim) { if (vch->master != ch && !vch->fighting) retcode = multi_hit(vch, ch, TYPE_UNDEFINED); break; } } } return; } /* * Cast spells at targets using a magical object. */ ch_ret obj_cast_spell(int sn, int level, CHAR_DATA * ch, CHAR_DATA * victim, OBJ_DATA * obj) { void *vo; ch_ret retcode = rNONE; int levdiff = ch->level - level; SKILLTYPE *skill = get_skilltype(sn); struct timeval time_used; if (sn == -1) return retcode; if (!skill || !skill->spell_fun) { bug("Obj_cast_spell: bad sn %d.", sn); return rERROR; } if (xIS_SET(ch->in_room->room_flags, ROOM_NO_MAGIC)) { set_char_color(AT_MAGIC, ch); send_to_char("Nothing seems to happen...\n\r", ch); return rNONE; } if (xIS_SET(ch->in_room->room_flags, ROOM_SAFE) && skill->target == TAR_CHAR_OFFENSIVE) { set_char_color(AT_MAGIC, ch); send_to_char("Nothing seems to happen...\n\r", ch); return rNONE; } /* * Basically this was added to cut down on level 5 players using level * 40 scrolls in battle too often ;) -Thoric */ if ((skill->target == TAR_CHAR_OFFENSIVE || number_bits(7) == 1) /* 1/128 chance if non-offensive */ &&skill->type != SKILL_HERB && !chance(ch, 95 + levdiff)) { switch (number_bits(2)) { case 0: failed_casting(skill, ch, victim, NULL); break; case 1: act(AT_MAGIC, "The $t spell backfires!", ch, skill->name, victim, TO_CHAR); if (victim) act(AT_MAGIC, "$n's $t spell backfires!", ch, skill->name, victim, TO_VICT); act(AT_MAGIC, "$n's $t spell backfires!", ch, skill->name, victim, TO_NOTVICT); return damage(ch, ch, number_range(1, level), TYPE_UNDEFINED); case 2: failed_casting(skill, ch, victim, NULL); break; case 3: act(AT_MAGIC, "The $t spell backfires!", ch, skill->name, victim, TO_CHAR); if (victim) act(AT_MAGIC, "$n's $t spell backfires!", ch, skill->name, victim, TO_VICT); act(AT_MAGIC, "$n's $t spell backfires!", ch, skill->name, victim, TO_NOTVICT); return damage(ch, ch, number_range(1, level), TYPE_UNDEFINED); } return rNONE; } target_name = ""; switch (skill->target) { default: bug("Obj_cast_spell: bad target for sn %d.", sn); return rERROR; case TAR_IGNORE: vo = NULL; if (victim) target_name = victim->name; else if (obj) target_name = obj->name; break; case TAR_CHAR_OFFENSIVE: if (victim != ch) { if (!victim) victim = who_fighting(ch); if (!victim || (!IS_NPC(victim) && !in_arena(victim))) { send_to_char("You can't do that.\n\r", ch); return rNONE; } } if (ch != victim && is_safe(ch, victim, TRUE)) return rNONE; vo = (void *) victim; break; case TAR_CHAR_DEFENSIVE: if (victim == NULL) victim = ch; vo = (void *) victim; if (skill->type != SKILL_HERB && IS_SET(victim->immune, RIS_MAGIC)) { immune_casting(skill, ch, victim, NULL); return rNONE; } break; case TAR_CHAR_SELF: vo = (void *) ch; if (skill->type != SKILL_HERB && IS_SET(ch->immune, RIS_MAGIC)) { immune_casting(skill, ch, victim, NULL); return rNONE; } break; case TAR_OBJ_INV: if (obj == NULL) { send_to_char("You can't do that.\n\r", ch); return rNONE; } vo = (void *) obj; break; } start_timer(&time_used); retcode = (*skill->spell_fun) (sn, level, ch, vo); end_timer(&time_used); update_userec(&time_used, &skill->userec); if (retcode == rSPELL_FAILED) retcode = rNONE; if (retcode == rCHAR_DIED || retcode == rERROR) return retcode; if (char_died(ch)) return rCHAR_DIED; if (skill->target == TAR_CHAR_OFFENSIVE && victim != ch && !char_died(victim)) { CHAR_DATA *vch; CHAR_DATA *vch_next; for (vch = ch->in_room->first_person; vch; vch = vch_next) { vch_next = vch->next_in_room; if (victim == vch && !vch->fighting && vch->master != ch) { retcode = multi_hit(vch, ch, TYPE_UNDEFINED); break; } } } return retcode; } /* * Spell functions. */ /******************************************************* * Everything after this point is part of SMAUG SPELLS * *******************************************************/ /* * saving throw check -Thoric */ bool check_save(int sn, int level, CHAR_DATA * ch, CHAR_DATA * victim) { SKILLTYPE *skill = get_skilltype(sn); bool saved = FALSE; if (SPELL_FLAG(skill, SF_PKSENSITIVE) && !IS_NPC(ch) && !IS_NPC(victim)) level /= 2; if (skill->saves) switch (skill->saves) { case SS_POISON_DEATH: saved = saves_poison_death(level, victim); break; case SS_ROD_WANDS: saved = saves_wands(level, victim); break; case SS_PARA_PETRI: saved = saves_para_petri(level, victim); break; case SS_BREATH: saved = saves_breath(level, victim); break; case SS_SPELL_STAFF: saved = saves_spell_staff(level, victim); break; } return saved; } /* * Generic offensive spell damage attack -Thoric */ ch_ret spell_attack(int sn, int level, CHAR_DATA * ch, void *vo) { CHAR_DATA *victim = (CHAR_DATA *) vo; SKILLTYPE *skill = get_skilltype(sn); bool saved = check_save(sn, level, ch, victim); int dam; ch_ret retcode = rNONE; if (saved && SPELL_SAVE(skill) == SE_NEGATE) { failed_casting(skill, ch, victim, NULL); return rSPELL_FAILED; } if (skill->dice) dam = UMAX(0, dice_parse(ch, level, skill->dice)); else dam = dice(1, level / 2); if (saved) { switch (SPELL_SAVE(skill)) { case SE_3QTRDAM: dam = (dam * 3) / 4; break; case SE_HALFDAM: dam >>= 1; break; case SE_QUARTERDAM: dam >>= 2; break; case SE_EIGHTHDAM: dam >>= 3; break; case SE_ABSORB: /* victim absorbs spell for hp's */ act(AT_MAGIC, "$N absorbs your $t!", ch, skill->noun_damage, victim, TO_CHAR); act(AT_MAGIC, "You absorb $N's $t!", victim, skill->noun_damage, ch, TO_CHAR); act(AT_MAGIC, "$N absorbs $n's $t!", ch, skill->noun_damage, victim, TO_NOTVICT); victim->hit = URANGE(0, victim->hit + dam, victim->max_hit); update_pos(victim); if ((dam > 0 && ch->fighting && ch->fighting->who == victim) || (dam > 0 && victim->fighting && victim->fighting->who == ch)) { int xp = ch->fighting ? ch->fighting->xp : victim->fighting->xp; int xp_gain = (int) (xp * dam * 2) / victim->max_hit; gain_exp(ch, 0 - xp_gain); } if (skill->affects) retcode = spell_affectchar(sn, level, ch, victim); return retcode; case SE_REFLECT: /* reflect the spell to the caster */ return spell_attack(sn, level, victim, ch); } } dam = get_attmod(ch, victim) * dam; retcode = damage(ch, victim, dam, sn); if (retcode == rNONE && skill->affects && !char_died(ch) && !char_died(victim) && (!is_affected(victim, sn) || SPELL_FLAG(skill, SF_ACCUMULATIVE) || SPELL_FLAG(skill, SF_RECASTABLE))) retcode = spell_affectchar(sn, level, ch, victim); return retcode; } /* * Generic area attack -Thoric */ ch_ret spell_area_attack(int sn, int level, CHAR_DATA * ch, void *vo) { CHAR_DATA *vch, *vch_next; SKILLTYPE *skill = get_skilltype(sn); bool saved; bool affects; int dam; bool ch_died = FALSE; ch_ret retcode = rNONE; if (xIS_SET(ch->in_room->room_flags, ROOM_SAFE)) { failed_casting(skill, ch, NULL, NULL); return rSPELL_FAILED; } affects = (skill->affects ? TRUE : FALSE); if (skill->hit_char && skill->hit_char[0] != '\0') act(AT_COLORIZE, skill->hit_char, ch, NULL, NULL, TO_CHAR); if (skill->hit_room && skill->hit_room[0] != '\0') act(AT_COLORIZE, skill->hit_room, ch, NULL, NULL, TO_ROOM); for (vch = ch->in_room->first_person; vch; vch = vch_next) { vch_next = vch->next_in_room; if (!IS_NPC(vch) && xIS_SET(vch->act, PLR_WIZINVIS) && vch->pcdata->wizinvis >= LEVEL_IMMORTAL) continue; if (vch == ch) continue; if (is_safe(ch, vch, FALSE)) continue; if (!IS_NPC(ch) && !IS_NPC(vch) && !in_arena(ch) && (!IS_PKILL(ch) || !IS_PKILL(vch))) continue; saved = check_save(sn, level, ch, vch); if (saved && SPELL_SAVE(skill) == SE_NEGATE) { failed_casting(skill, ch, vch, NULL); continue; } else if (skill->dice) dam = dice_parse(ch, level, skill->dice); else dam = dice(1, level / 2); if (saved) { switch (SPELL_SAVE(skill)) { case SE_3QTRDAM: dam = (dam * 3) / 4; break; case SE_HALFDAM: dam >>= 1; break; case SE_QUARTERDAM: dam >>= 2; break; case SE_EIGHTHDAM: dam >>= 3; break; case SE_ABSORB: /* victim absorbs spell for hp's */ act(AT_MAGIC, "$N absorbs your $t!", ch, skill->noun_damage, vch, TO_CHAR); act(AT_MAGIC, "You absorb $N's $t!", vch, skill->noun_damage, ch, TO_CHAR); act(AT_MAGIC, "$N absorbs $n's $t!", ch, skill->noun_damage, vch, TO_NOTVICT); vch->hit = URANGE(0, vch->hit + dam, vch->max_hit); update_pos(vch); if ((dam > 0 && ch->fighting && ch->fighting->who == vch) || (dam > 0 && vch->fighting && vch->fighting->who == ch)) { int xp = ch->fighting ? ch->fighting->xp : vch->fighting->xp; int xp_gain = (int) (xp * dam * 2) / vch->max_hit; gain_exp(ch, 0 - xp_gain); } continue; case SE_REFLECT: /* reflect the spell to the caster */ retcode = spell_attack(sn, level, vch, ch); if (char_died(ch)) { ch_died = TRUE; break; } continue; } } dam = get_attmod(ch, vch) * dam; retcode = damage(ch, vch, dam, sn); if (retcode == rNONE && affects && !char_died(ch) && !char_died(vch) && (!is_affected(vch, sn) || SPELL_FLAG(skill, SF_ACCUMULATIVE) || SPELL_FLAG(skill, SF_RECASTABLE))) retcode = spell_affectchar(sn, level, ch, vch); if (retcode == rCHAR_DIED || char_died(ch)) { ch_died = TRUE; break; } } return retcode; } ch_ret spell_affectchar(int sn, int level, CHAR_DATA * ch, void *vo) { AFFECT_DATA af; SMAUG_AFF *saf; SKILLTYPE *skill = get_skilltype(sn); CHAR_DATA *victim = (CHAR_DATA *) vo; int chance; ch_ret retcode = rNONE; if (SPELL_FLAG(skill, SF_RECASTABLE)) affect_strip(victim, sn); for (saf = skill->affects; saf; saf = saf->next) { if (saf->location >= REVERSE_APPLY) victim = ch; else victim = (CHAR_DATA *) vo; /* * Check if char has this bitvector already */ af.bitvector = meb(saf->bitvector); if (saf->bitvector >= 0 && xIS_SET(victim->affected_by, saf->bitvector) && !SPELL_FLAG(skill, SF_ACCUMULATIVE)) continue; /* * necessary for affect_strip to work properly... */ switch (saf->bitvector) { default: af.type = sn; break; case AFF_POISON: af.type = gsn_poison; chance = ris_save(victim, level, RIS_POISON); if (chance == 1000) { retcode = rVICT_IMMUNE; if (SPELL_FLAG(skill, SF_STOPONFAIL)) return retcode; continue; } if (saves_poison_death(chance, victim)) { if (SPELL_FLAG(skill, SF_STOPONFAIL)) return retcode; continue; } victim->mental_state = URANGE(30, victim->mental_state + 2, 100); break; case AFF_BLIND: af.type = gsn_blindness; break; case AFF_CURSE: af.type = gsn_curse; break; case AFF_INVISIBLE: af.type = gsn_invis; break; case AFF_SLEEP: af.type = gsn_sleep; chance = ris_save(victim, level, RIS_SLEEP); if (chance == 1000) { retcode = rVICT_IMMUNE; if (SPELL_FLAG(skill, SF_STOPONFAIL)) return retcode; continue; } break; case AFF_CHARM: af.type = gsn_charm_person; chance = ris_save(victim, level, RIS_CHARM); if (chance == 1000) { retcode = rVICT_IMMUNE; if (SPELL_FLAG(skill, SF_STOPONFAIL)) return retcode; continue; } break; case AFF_POSSESS: af.type = gsn_possess; break; } af.duration = dice_parse(ch, level, saf->duration); af.modifier = dice_parse(ch, level, saf->modifier); af.location = saf->location % REVERSE_APPLY; if (af.duration == 0) { int xp_gain; switch (af.location) { case APPLY_HIT: victim->hit = URANGE(0, victim->hit + af.modifier, victim->max_hit); update_pos(victim); if ((af.modifier > 0 && ch->fighting && ch->fighting->who == victim) || (af.modifier > 0 && victim->fighting && victim->fighting->who == ch)) { int xp = ch->fighting ? ch->fighting->xp : victim->fighting->xp; xp_gain = (int) (xp * af.modifier * 2) / victim->max_hit; gain_exp(ch, 0 - xp_gain); } if (IS_NPC(victim) && victim->hit <= 0) damage(ch, victim, 5, TYPE_UNDEFINED); break; case APPLY_MANA: victim->mana = URANGE(0, victim->mana + af.modifier, victim->max_mana); update_pos(victim); break; case APPLY_MOVE: victim->max_move = af.modifier; update_pos(victim); break; default: affect_modify(victim, &af, TRUE); break; } } else if (SPELL_FLAG(skill, SF_ACCUMULATIVE)) affect_join(victim, &af); else affect_to_char(victim, &af); } update_pos(victim); return retcode; } /* * Generic spell affect -Thoric */ ch_ret spell_affect(int sn, int level, CHAR_DATA * ch, void *vo) { SMAUG_AFF *saf; SKILLTYPE *skill = get_skilltype(sn); CHAR_DATA *victim = (CHAR_DATA *) vo; bool groupsp; bool areasp; bool hitchar = FALSE, hitroom = FALSE, hitvict = FALSE; ch_ret retcode; if (!skill->affects) { bug("spell_affect has no affects sn %d", sn); return rNONE; } if (SPELL_FLAG(skill, SF_GROUPSPELL)) groupsp = TRUE; else groupsp = FALSE; if (SPELL_FLAG(skill, SF_AREA)) areasp = TRUE; else areasp = FALSE; if (!groupsp && !areasp) { /* * Can't find a victim */ if (!victim) { failed_casting(skill, ch, victim, NULL); return rSPELL_FAILED; } if ((skill->type != SKILL_HERB && IS_SET(victim->immune, RIS_MAGIC)) || is_immune(victim, SPELL_DAMAGE(skill))) { immune_casting(skill, ch, victim, NULL); return rSPELL_FAILED; } /* * Spell is already on this guy */ if (is_affected(victim, sn) && !SPELL_FLAG(skill, SF_ACCUMULATIVE) && !SPELL_FLAG(skill, SF_RECASTABLE)) { failed_casting(skill, ch, victim, NULL); return rSPELL_FAILED; } if ((saf = skill->affects) && !saf->next && saf->location == APPLY_STRIPSN && !is_affected(victim, dice_parse(ch, level, saf->modifier))) { failed_casting(skill, ch, victim, NULL); return rSPELL_FAILED; } if (check_save(sn, level, ch, victim)) { failed_casting(skill, ch, victim, NULL); return rSPELL_FAILED; } } else { if (skill->hit_char && skill->hit_char[0] != '\0') { if (strstr(skill->hit_char, "$N")) hitchar = TRUE; else act(AT_COLORIZE, skill->hit_char, ch, NULL, NULL, TO_CHAR); } if (skill->hit_room && skill->hit_room[0] != '\0') { if (strstr(skill->hit_room, "$N")) hitroom = TRUE; else act(AT_COLORIZE, skill->hit_room, ch, NULL, NULL, TO_ROOM); } if (skill->hit_vict && skill->hit_vict[0] != '\0') hitvict = TRUE; if (victim) victim = victim->in_room->first_person; else victim = ch->in_room->first_person; } if (!victim) { bug("spell_affect: could not find victim: sn %d", sn); failed_casting(skill, ch, victim, NULL); return rSPELL_FAILED; } for (; victim; victim = victim->next_in_room) { if (groupsp || areasp) { if ((groupsp && !is_same_group(victim, ch)) || IS_SET(victim->immune, RIS_MAGIC) || is_immune(victim, SPELL_DAMAGE(skill)) || check_save(sn, level, ch, victim) || (!SPELL_FLAG(skill, SF_RECASTABLE) && is_affected(victim, sn))) continue; if (hitvict && ch != victim) { act(AT_COLORIZE, skill->hit_vict, ch, NULL, victim, TO_VICT); if (hitroom) { act(AT_COLORIZE, skill->hit_room, ch, NULL, victim, TO_NOTVICT); act(AT_COLORIZE, skill->hit_room, ch, NULL, victim, TO_CHAR); } } else if (hitroom) act(AT_COLORIZE, skill->hit_room, ch, NULL, victim, TO_ROOM); if (ch == victim) { if (hitvict) act(AT_COLORIZE, skill->hit_vict, ch, NULL, ch, TO_CHAR); else if (hitchar) act(AT_COLORIZE, skill->hit_char, ch, NULL, ch, TO_CHAR); } else if (hitchar) act(AT_COLORIZE, skill->hit_char, ch, NULL, victim, TO_CHAR); } retcode = spell_affectchar(sn, level, ch, victim); if (!groupsp && !areasp) { if (retcode == rVICT_IMMUNE) immune_casting(skill, ch, victim, NULL); else successful_casting(skill, ch, victim, NULL); break; } } return rNONE; } /* * Generic inventory object spell -Thoric */ ch_ret spell_obj_inv(int sn, int level, CHAR_DATA * ch, void *vo) { OBJ_DATA *obj = (OBJ_DATA *) vo; SKILLTYPE *skill = get_skilltype(sn); if (!obj) { failed_casting(skill, ch, NULL, NULL); return rNONE; } switch (SPELL_ACTION(skill)) { default: case SA_NONE: return rNONE; case SA_CREATE: if (SPELL_FLAG(skill, SF_WATER)) { /* create water */ int water; WEATHER_DATA *weath = ch->in_room->area->weather; if (obj->item_type != ITEM_DRINK_CON) { send_to_char("It is unable to hold water.\n\r", ch); return rSPELL_FAILED; } if (obj->value[2] != LIQ_WATER && obj->value[1] != 0) { send_to_char("It contains some other liquid.\n\r", ch); return rSPELL_FAILED; } water = UMIN((skill->dice ? dice_parse(ch, level, skill->dice) : level) * (weath->precip >= 0 ? 2 : 1), obj->value[0] - obj->value[1]); if (water > 0) { separate_obj(obj); obj->value[2] = LIQ_WATER; obj->value[1] += water; if (!is_name("water", obj->name)) { char buf[MAX_STRING_LENGTH]; sprintf(buf, "%s water", obj->name); STRFREE(obj->name); obj->name = STRALLOC(buf); } } successful_casting(skill, ch, NULL, obj); return rNONE; } if (SPELL_DAMAGE(skill) == SD_FIRE) { /* burn object */ /* * return rNONE; */ } if (SPELL_DAMAGE(skill) == SD_POISON /* poison object */ || SPELL_CLASS(skill) == SC_DEATH) { switch (obj->item_type) { default: failed_casting(skill, ch, NULL, obj); break; case ITEM_COOK: case ITEM_FOOD: case ITEM_DRINK_CON: separate_obj(obj); obj->value[3] = 1; successful_casting(skill, ch, NULL, obj); break; } return rNONE; } if (SPELL_CLASS(skill) == SC_LIFE /* purify food/water */ && (obj->item_type == ITEM_FOOD || obj->item_type == ITEM_DRINK_CON || obj->item_type == ITEM_COOK)) { switch (obj->item_type) { default: failed_casting(skill, ch, NULL, obj); break; case ITEM_COOK: case ITEM_FOOD: case ITEM_DRINK_CON: separate_obj(obj); obj->value[3] = 0; successful_casting(skill, ch, NULL, obj); break; } return rNONE; } if (SPELL_CLASS(skill) != SC_NONE) { failed_casting(skill, ch, NULL, obj); return rNONE; } switch (SPELL_POWER(skill)) { /* clone object */ OBJ_DATA *clone; default: case SP_NONE: if (ch->level - obj->level < 10 || obj->cost > ch->level * get_curr_int(ch)) { failed_casting(skill, ch, NULL, obj); return rNONE; } break; case SP_MINOR: if (ch->level - obj->level < 20 || obj->cost > ch->level * get_curr_int(ch) / 5) { failed_casting(skill, ch, NULL, obj); return rNONE; } break; case SP_GREATER: if (ch->level - obj->level < 5 || obj->cost > ch->level * 10 * get_curr_int(ch)) { failed_casting(skill, ch, NULL, obj); return rNONE; } break; case SP_MAJOR: if (ch->level - obj->level < 0 || obj->cost > ch->level * 50 * get_curr_int(ch)) { failed_casting(skill, ch, NULL, obj); return rNONE; } break; clone = clone_object(obj); clone->timer = skill->dice ? dice_parse(ch, level, skill->dice) : 0; obj_to_char(clone, ch); successful_casting(skill, ch, NULL, obj); } return rNONE; case SA_DESTROY: case SA_RESIST: case SA_SUSCEPT: case SA_DIVINATE: if (SPELL_DAMAGE(skill) == SD_POISON) { /* detect poison */ if (obj->item_type == ITEM_DRINK_CON || obj->item_type == ITEM_FOOD || obj->item_type == ITEM_COOK) { if (obj->item_type == ITEM_COOK && obj->value[2] == 0) send_to_char("It looks undercooked.\n\r", ch); else if (obj->value[3] != 0) send_to_char("You smell poisonous fumes.\n\r", ch); else send_to_char("It looks very delicious.\n\r", ch); } else send_to_char("It doesn't look poisoned.\n\r", ch); return rNONE; } return rNONE; case SA_OBSCURE: /* make obj invis */ if (IS_OBJ_STAT(obj, ITEM_INVIS) || chance(ch, skill->dice ? dice_parse(ch, level, skill->dice) : 20)) { failed_casting(skill, ch, NULL, NULL); return rSPELL_FAILED; } successful_casting(skill, ch, NULL, obj); xSET_BIT(obj->extra_flags, ITEM_INVIS); return rNONE; case SA_CHANGE: return rNONE; } return rNONE; } /* * Generic object creating spell -Thoric */ ch_ret spell_create_obj(int sn, int level, CHAR_DATA * ch, void *vo) { SKILLTYPE *skill = get_skilltype(sn); int lvl; int vnum = skill->value; OBJ_DATA *obj; OBJ_INDEX_DATA *oi; switch (SPELL_POWER(skill)) { default: case SP_NONE: lvl = 10; break; case SP_MINOR: lvl = 0; break; case SP_GREATER: lvl = level / 2; break; case SP_MAJOR: lvl = level; break; } /* * Add predetermined objects here */ if (vnum == 0) { if (!str_cmp(target_name, "sword")) vnum = OBJ_VNUM_SCHOOL_SWORD; if (!str_cmp(target_name, "shield")) vnum = OBJ_VNUM_SCHOOL_SHIELD; } if ((oi = get_obj_index(vnum)) == NULL || (obj = create_object(oi, lvl)) == NULL) { failed_casting(skill, ch, NULL, NULL); return rNONE; } obj->timer = skill->dice ? dice_parse(ch, level, skill->dice) : 0; successful_casting(skill, ch, NULL, obj); if (CAN_WEAR(obj, ITEM_TAKE)) obj_to_char(obj, ch); else obj_to_room(obj, ch->in_room); return rNONE; } /* * Generic mob creating spell -Thoric */ ch_ret spell_create_mob(int sn, int level, CHAR_DATA * ch, void *vo) { SKILLTYPE *skill = get_skilltype(sn); int lvl; int vnum = skill->value; CHAR_DATA *mob; MOB_INDEX_DATA *mi; AFFECT_DATA af; /* * set maximum mob level */ switch (SPELL_POWER(skill)) { default: case SP_NONE: lvl = 20; break; case SP_MINOR: lvl = 5; break; case SP_GREATER: lvl = level / 2; break; case SP_MAJOR: lvl = level; break; } /* * Add predetermined mobiles here */ if (vnum == 0) { if (!str_cmp(target_name, "cityguard")) vnum = MOB_VNUM_CITYGUARD; if (!str_cmp(target_name, "vampire")) vnum = MOB_VNUM_VAMPIRE; } if ((mi = get_mob_index(vnum)) == NULL || (mob = create_mobile(mi)) == NULL) { failed_casting(skill, ch, NULL, NULL); return rNONE; } mob->level = UMIN(lvl, skill->dice ? dice_parse(ch, level, skill->dice) : mob->level); mob->armor = interpolate(mob->level, 100, -100); mob->max_hit = mob->level * 8 + number_range(mob->level * mob->level / 4, mob->level * mob->level); mob->hit = mob->max_hit; mob->gold = 0; successful_casting(skill, ch, mob, NULL); char_to_room(mob, ch->in_room); add_follower(mob, ch); af.type = sn; af.duration = (number_fuzzy((level + 1) / 3) + 1) * DUR_CONV; af.location = 0; af.modifier = 0; af.bitvector = meb(AFF_CHARM); affect_to_char(mob, &af); return rNONE; } ch_ret ranged_attack(CHAR_DATA *, char *, OBJ_DATA *, OBJ_DATA *, sh_int, sh_int); /* * Generic handler for new "SMAUG" spells -Thoric */ ch_ret spell_smaug(int sn, int level, CHAR_DATA * ch, void *vo) { CHAR_DATA *victim; struct skill_type *skill = get_skilltype(sn); /* * Put this check in to prevent crashes from this getting a bad skill */ if (!skill) { // bug ( "spell_smaug: Called with a null skill for sn %d", sn ); return rERROR; } switch (skill->target) { case TAR_IGNORE: /* * offensive area spell */ if (SPELL_FLAG(skill, SF_AREA) && ((SPELL_ACTION(skill) == SA_DESTROY && SPELL_CLASS(skill) == SC_LIFE) || (SPELL_ACTION(skill) == SA_CREATE && SPELL_CLASS(skill) == SC_DEATH))) return spell_area_attack(sn, level, ch, vo); if (SPELL_ACTION(skill) == SA_CREATE) { if (SPELL_FLAG(skill, SF_OBJECT)) /* create object */ return spell_create_obj(sn, level, ch, vo); if (SPELL_CLASS(skill) == SC_LIFE) /* create mob */ return spell_create_mob(sn, level, ch, vo); } /* * affect a distant player */ if (SPELL_FLAG(skill, SF_DISTANT) && (victim = get_char_world(ch, target_name)) && !xIS_SET(victim->in_room->room_flags, ROOM_NO_ASTRAL) && SPELL_FLAG(skill, SF_CHARACTER)) return spell_affect(sn, level, ch, get_char_world(ch, target_name)); /* * affect a player in this room (should have been TAR_CHAR_XXX) */ if (SPELL_FLAG(skill, SF_CHARACTER)) return spell_affect(sn, level, ch, get_char_room(ch, target_name)); if (skill->range > 0 && ((SPELL_ACTION(skill) == SA_DESTROY && SPELL_CLASS(skill) == SC_LIFE) || (SPELL_ACTION(skill) == SA_CREATE && SPELL_CLASS(skill) == SC_DEATH))) return ranged_attack(ch, ranged_target_name, NULL, NULL, sn, skill->range); /* * will fail, or be an area/group affect */ return spell_affect(sn, level, ch, vo); case TAR_CHAR_OFFENSIVE: /* * a regular damage inflicting spell attack */ if ((SPELL_ACTION(skill) == SA_DESTROY && SPELL_CLASS(skill) == SC_LIFE) || (SPELL_ACTION(skill) == SA_CREATE && SPELL_CLASS(skill) == SC_DEATH)) return spell_attack(sn, level, ch, vo); /* * a nasty spell affect */ return spell_affect(sn, level, ch, vo); case TAR_CHAR_DEFENSIVE: case TAR_CHAR_SELF: if (SPELL_FLAG(skill, SF_NOFIGHT) && (ch->position == POS_FIGHTING || ch->position == POS_EVASIVE || ch->position == POS_DEFENSIVE || ch->position == POS_AGGRESSIVE || ch->position == POS_BERSERK)) { send_to_char("You can't concentrate enough for that!\n\r", ch); return rNONE; } if (vo && SPELL_ACTION(skill) == SA_DESTROY) { CHAR_DATA *victim = (CHAR_DATA *) vo; /* * cure poison */ if (SPELL_DAMAGE(skill) == SD_POISON) { if (is_affected(victim, gsn_poison)) { affect_strip(victim, gsn_poison); victim->mental_state = URANGE(-100, victim->mental_state, -10); successful_casting(skill, ch, victim, NULL); return rNONE; } failed_casting(skill, ch, victim, NULL); return rSPELL_FAILED; } /* * cure blindness */ if (SPELL_CLASS(skill) == SC_ILLUSION) { if (is_affected(victim, gsn_blindness)) { affect_strip(victim, gsn_blindness); successful_casting(skill, ch, victim, NULL); return rNONE; } failed_casting(skill, ch, victim, NULL); return rSPELL_FAILED; } } return spell_affect(sn, level, ch, vo); case TAR_OBJ_INV: return spell_obj_inv(sn, level, ch, vo); } return rNONE; } ch_ret spell_null(int sn, int level, CHAR_DATA * ch, void *vo) { send_to_char("That's not a spell!\n\r", ch); return rNONE; } /* don't remove, may look redundant, but is important */ ch_ret spell_notfound(int sn, int level, CHAR_DATA * ch, void *vo) { send_to_char("That's not a spell!\n\r", ch); return rNONE; }