/*************************************************************************** * 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. * ***************************************************************************/ /*************************************************************************** * ROM 2.4 is copyright 1993-1996 Russ Taylor * * ROM has been brought to you by the ROM consortium * * Russ Taylor (rtaylor@efn.org) * * Gabrielle Taylor * * Brian Moore (zump@rom.org) * * By using this code, you have agreed to follow the terms of the * * ROM license, in the file Rom24/doc/rom.license * ***************************************************************************/ #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" #include "utils.h" #include "magic.h" #include "recycle.h" /* command procedures needed */ DECLARE_DO_FUN(do_look); DECLARE_DO_FUN(do_emote); DECLARE_DO_FUN(do_say); DECLARE_DO_FUN(do_at); DECLARE_DO_FUN(do_flee); /* * Local functions. */ void say_spell args((CHAR_DATA * ch, int sn)); /* imported functions */ bool remove_obj args((CHAR_DATA * ch, int iWear, bool fReplace)); void wear_obj args((CHAR_DATA * ch, OBJ_DATA * obj, bool fReplace)); /* * Lookup a skill by name. */ int skill_lookup(const char *name) { int sn; for (sn = 0; sn < MAX_SKILL; sn++) { if (skill_table[sn].name == NULL) break; if (LOWER(name[0]) == LOWER(skill_table[sn].name[0]) && !str_prefix(name, skill_table[sn].name)) return sn; } return -1; } int find_spell(CHAR_DATA * ch, const char *name) { /* finds a spell the character can cast if possible */ int sn, found = -1; if (IS_NPC(ch)) return skill_lookup(name); for(sn = 0; sn < MAX_SKILL; sn++) { if (skill_table[sn].name == NULL) break; if (LOWER(name[0]) == LOWER(skill_table[sn].name[0]) && !str_prefix(name, skill_table[sn].name)) { if (found == -1) found = sn; if (ch->level >= skill_table[sn].skill_level[ch->class] && ch->pcdata->learned[sn] > 0) return sn; } } return found; } /* * 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 < MAX_SKILL; sn++) { if (slot == skill_table[sn].slot) return sn; } if (fBootDb) { bug("Slot_lookup: bad slot %d.", slot); abort(); } return -1; } /* * 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; 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"}, {"fresh", "ima"}, {"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_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 utters the words, '%s'.", buf); sprintf(buf, "$n utters the words, '%s'.", skill_table[sn].name); for (rch = ch->in_room->people; rch; rch = rch->next_in_room) { if (rch != ch) act((!IS_NPC(rch) && ch->class == rch->class) ? 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 dam_type) { int save; save = ((((50 + (victim->level - level)) - (victim->saving_throw)) * 3) / 4); if (IS_AFFECTED(victim, AFF_BERSERK)) save += victim->level / 2; switch (check_immune(victim, dam_type)) { case IS_IMMUNE: return TRUE; case IS_RESISTANT: save += 15; break; case IS_VULNERABLE: save -= 25; break; } if (!IS_NPC(victim) && class_table[victim->class].fMana) { save = 9 * save / 10; } 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 += 5; /* 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))); } /* * The kludgy global is for spells who want more stuff from command line. */ char *target_name; void do_cast(CHAR_DATA * ch, char *argument) { char arg1[MAX_INPUT_LENGTH]; char arg2[MAX_INPUT_LENGTH]; CHAR_DATA *victim; OBJ_DATA *obj; void *vo; int mana; int sn; int target; int level; /* * Switched NPC's can cast spells, but others can't. */ if (IS_NPC(ch) && ch->desc == NULL) return; 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 ((sn = find_spell(ch, arg1)) < 1 || skill_table[sn].spell_fun == spell_null || (!IS_NPC(ch) && (ch->level < skill_table[sn].skill_level[ch->class] || ch->pcdata->learned[sn] == 0))) { send_to_char("You don't know any spells of that name.\n\r", ch); return; } if (ch->position < skill_table[sn].minimum_position) { send_to_char("You can't concentrate enough.\n\r", ch); return; } if (ch->level + 2 == skill_table[sn].skill_level[ch->class]) mana = 50; else mana = UMAX(skill_table[sn].min_mana, 100 / (2 + ch->level - skill_table[sn].skill_level[ch->class])); level = ch->level; /* * Locate targets. */ victim = NULL; obj = NULL; vo = NULL; target = TARGET_NONE; switch (skill_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) == NULL) { send_to_char("Cast the spell on whom?\n\r", ch); return; } } else { if ((victim = get_char_room(ch, target_name)) == NULL) { send_to_char("They aren't here.\n\r", ch); return; } } if (!IS_NPC(ch)) { if (is_safe(ch, victim) && victim != ch) { send_to_char("Not on that target.\n\r", ch); return; } check_killer(ch, victim); } if (victim->fighting != NULL && !is_same_group(ch, victim->fighting)) { send_to_char("Kill stealing is not permitted.\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; } vo = (void *) victim; target = TARGET_CHAR; break; case TAR_CHAR_DEFENSIVE: if (arg2[0] == '\0') { victim = ch; } else { if ((victim = get_char_room(ch, target_name)) == NULL) { send_to_char("They aren't here.\n\r", ch); return; } } if (victim->level < (ch->level - 10)) level = victim->level + (ch->level / 15); vo = (void *) victim; target = TARGET_CHAR; break; case TAR_CHAR_SELF: if (arg2[0] != '\0' && !is_name(target_name, ch->name)) { send_to_char("You cannot cast this spell on another.\n\r", ch); return; } vo = (void *) ch; target = TARGET_CHAR; 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, target_name, ch)) == NULL) { send_to_char("You are not carrying that.\n\r", ch); return; } vo = (void *) obj; target = TARGET_OBJ; break; case TAR_OBJ_CHAR_OFF: if (arg2[0] == '\0') { if ((victim = ch->fighting) == NULL) { send_to_char("Cast the spell on whom or what?\n\r", ch); return; } target = TARGET_CHAR; } else if ((victim = get_char_room(ch, target_name)) != NULL) { target = TARGET_CHAR; } if (target == TARGET_CHAR) { /* check the sanity of the attack */ if (victim->fighting != NULL && !is_same_group(ch, victim->fighting)) { send_to_char("Kill stealing is not permitted.\n\r", ch); return; } if (is_safe_spell(ch, victim, FALSE) && victim != ch) { send_to_char("Not on that target.\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_NPC(ch)) check_killer(ch, victim); vo = (void *) victim; } else if ((obj = get_obj_here(ch, target_name)) != NULL) { vo = (void *) obj; target = TARGET_OBJ; } else { send_to_char("You don't see that here.\n\r", ch); return; } break; case TAR_OBJ_CHAR_DEF: if (arg2[0] == '\0') { vo = (void *) ch; target = TARGET_CHAR; } else if ((victim = get_char_room(ch, target_name)) != NULL) { vo = (void *) victim; target = TARGET_CHAR; if (victim->level < (ch->level - 10)) level = victim->level + (ch->level / 10); } else if ((obj = get_obj_carry(ch, target_name, ch)) != NULL) { vo = (void *) obj; target = TARGET_OBJ; } else { send_to_char("You don't see that here.\n\r", ch); return; } break; }//end switch 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")) say_spell(ch, sn); if (ch->level < 301) WAIT_STATE(ch, skill_table[sn].beats); if (number_percent() > get_skill(ch, sn)) { send_to_char("You lost your concentration.\n\r", ch); check_improve(ch, sn, FALSE, 1); ch->mana -= mana / 2; } else { ch->mana -= mana; if (level < 0) level = 1; if ((level > ch->level) || IS_IMMORTAL(ch) || IS_NPC(ch)) level = ch->level; if (class_table[ch->class].fMana) (*skill_table[sn].spell_fun) (sn, level, ch, vo, target); else (*skill_table[sn].spell_fun) (sn, 3 * level / 4, ch, vo, target); check_improve(ch, sn, TRUE, 1); } if (level < ch->level) act("Your spell's energy leaks from the body of $N.", ch, NULL, victim, TO_CHAR); if ((skill_table[sn].target == TAR_CHAR_OFFENSIVE || (skill_table[sn].target == TAR_OBJ_CHAR_OFF && target == TARGET_CHAR)) && victim != ch && victim->master != ch) { CHAR_DATA *vch; CHAR_DATA *vch_next; if (ch->hit > 0) { for (vch = ch->in_room->people; vch; vch = vch_next) { vch_next = vch->next_in_room; if (victim == vch && victim->fighting == NULL) { check_killer(victim, ch); 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; int target = TARGET_NONE; if (sn <= 0) return; if (sn >= MAX_SKILL || skill_table[sn].spell_fun == 0) { bug("Obj_cast_spell: bad sn %d.", sn); return; } switch (skill_table[sn].target) { default: bug("Obj_cast_spell: bad target for sn %d.", sn); return; case TAR_IGNORE: vo = NULL; /* fix to make it impossible to cast multible spells on one self */ if (is_affected(victim, sn)) { if (victim == ch) { send_to_char("This person is already under that effect", ch); } else { act("$N is already under that effect.", ch, NULL, victim, TO_CHAR); } return; } 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(ch, victim) && ch != victim) { send_to_char("Something isn't right...\n\r", ch); return; } vo = (void *) victim; target = TARGET_CHAR; /* fix to make it impossible to cast multible spells on one self */ if (is_affected(victim, sn)) { if (victim == ch) { send_to_char("This person is already under that effect", ch); } else { act("$N is already under that effect.", ch, NULL, victim, TO_CHAR); } return; } break; case TAR_CHAR_DEFENSIVE: case TAR_CHAR_SELF: if (victim == NULL) victim = ch; vo = (void *) victim; target = TARGET_CHAR; /* fix to make it impossible to cast multible spells on one self */ if (is_affected(victim, sn)) { if (victim == ch) { send_to_char("This person is already under that effect", ch); } else { act("$N is already under that effect.", ch, NULL, victim, TO_CHAR); } return; } break; case TAR_OBJ_INV: if (obj == NULL) { send_to_char("You can't do that.\n\r", ch); return; } vo = (void *) obj; target = TARGET_OBJ; /* fix to make it impossible to cast multible spells on one self */ if (is_affected(victim, sn)) { if (victim == ch) { send_to_char("This person is already under that effect", ch); } else { act("$N is already under that effect.", ch, NULL, victim, TO_CHAR); } return; } break; case TAR_OBJ_CHAR_OFF: if (victim == NULL && obj == NULL) { if (ch->fighting != NULL) victim = ch->fighting; else { send_to_char("You can't do that.\n\r", ch); return; } } if (victim != NULL) { if (is_safe_spell(ch, victim, FALSE) && ch != victim) { send_to_char("Somehting isn't right...\n\r", ch); return; } vo = (void *) victim; target = TARGET_CHAR; } else { vo = (void *) obj; target = TARGET_OBJ; } /* fix to make it impossible to cast multible spells on one self */ if (is_affected(victim, sn)) { if (victim == ch) { send_to_char("This person is already under that effect", ch); } else { act("$N is already under that effect.", ch, NULL, victim, TO_CHAR); } return; } break; case TAR_OBJ_CHAR_DEF: if (victim == NULL && obj == NULL) { vo = (void *) ch; target = TARGET_CHAR; } else if (victim != NULL) { vo = (void *) victim; target = TARGET_CHAR; } else { vo = (void *) obj; target = TARGET_OBJ; } /* fix to make it impossible to cast multible spells on one self */ if (is_affected(victim, sn)) { if (victim == ch) { send_to_char("This person is already under that effect", ch); } else { act("$N is already under that effect.", ch, NULL, victim, TO_CHAR); } return; } break; } /* fix to make it impossible to cast multible spells on one self */ if (is_affected(victim, sn)) { if (victim == ch) { send_to_char("This person is already under that effect", ch); } else { act("$N is already under that effect.", ch, NULL, victim, TO_CHAR); } return; } target_name = ""; (*skill_table[sn].spell_fun) (sn, level, ch, vo, target); if ((skill_table[sn].target == TAR_CHAR_OFFENSIVE || (skill_table[sn].target == TAR_OBJ_CHAR_OFF && target == TARGET_CHAR)) && 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) { check_killer(victim, ch); multi_hit(victim, ch, TYPE_UNDEFINED); break; } } } return; } bool generic_creature_damage_spell(int sn, int level, CHAR_DATA * ch, void *vo, int target, int damtype, int numdice) { CHAR_DATA *victim = (CHAR_DATA *) vo; int dam; dam = dice(level, numdice); if (saves_spell(level, victim, damtype)) dam /= 2; damage(ch, victim, dam, sn, damtype, TRUE); return TRUE; } bool modify_generic_character_affect(int sn, int level, CHAR_DATA * ch, void *vo, int target, char *name_text, int af_duration, int af_modifier, int af_location) { CHAR_DATA *victim = (CHAR_DATA *) vo; AFFECT_DATA af; char buf[MAX_INPUT_LENGTH]; if (is_affected(victim, sn)) { if (victim == ch) { sprintf(buf, "You are already under the effects of %s.\n\r", name_text); send_to_char(buf, ch); return FALSE; } else { sprintf(buf, "$N is already under the effects of %s.\n\r", name_text); act(buf, ch, NULL, victim, TO_CHAR); return FALSE; } } af.where = TO_AFFECTS; af.type = sn; af.level = level; af.duration = af_duration; af.modifier = af_modifier; af.location = af_location; af.bitvector = 0; affect_to_char(victim, &af); return TRUE; }