407 lines
12 KiB
C
407 lines
12 KiB
C
/* ************************************************************************
|
|
* File: spells.c Part of CircleMUD *
|
|
* Usage: Implementation of "manual spells". Circle 2.2 spell compat. *
|
|
* *
|
|
* All rights reserved. See license.doc for complete information. *
|
|
* *
|
|
* Copyright (C) 1993, 94 by the Trustees of the Johns Hopkins University *
|
|
* CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991. *
|
|
************************************************************************ */
|
|
|
|
|
|
#include "conf.h"
|
|
#include "sysdep.h"
|
|
|
|
#include "structs.h"
|
|
#include "utils.h"
|
|
#include "comm.h"
|
|
#include "spells.h"
|
|
#include "handler.h"
|
|
#include "db.h"
|
|
#include "constants.h"
|
|
#include "interpreter.h"
|
|
|
|
|
|
/* external variables */
|
|
extern room_rnum r_mortal_start_room;
|
|
extern int mini_mud;
|
|
extern int pk_allowed;
|
|
|
|
/* external functions */
|
|
void clearMemory(struct char_data *ch);
|
|
void weight_change_object(struct obj_data *obj, int weight);
|
|
int mag_savingthrow(struct char_data *ch, int type, int modifier);
|
|
void name_to_drinkcon(struct obj_data *obj, int type);
|
|
void name_from_drinkcon(struct obj_data *obj);
|
|
int compute_armor_class(struct char_data *ch);
|
|
|
|
/*
|
|
* Special spells appear below.
|
|
*/
|
|
|
|
ASPELL(spell_create_water)
|
|
{
|
|
int water;
|
|
|
|
if (ch == NULL || obj == NULL)
|
|
return;
|
|
/* level = MAX(MIN(level, LVL_IMPL), 1); - not used */
|
|
|
|
if (GET_OBJ_TYPE(obj) == ITEM_DRINKCON) {
|
|
if ((GET_OBJ_VAL(obj, 2) != LIQ_WATER) && (GET_OBJ_VAL(obj, 1) != 0)) {
|
|
name_from_drinkcon(obj);
|
|
GET_OBJ_VAL(obj, 2) = LIQ_SLIME;
|
|
name_to_drinkcon(obj, LIQ_SLIME);
|
|
} else {
|
|
water = MAX(GET_OBJ_VAL(obj, 0) - GET_OBJ_VAL(obj, 1), 0);
|
|
if (water > 0) {
|
|
if (GET_OBJ_VAL(obj, 1) >= 0)
|
|
name_from_drinkcon(obj);
|
|
GET_OBJ_VAL(obj, 2) = LIQ_WATER;
|
|
GET_OBJ_VAL(obj, 1) += water;
|
|
name_to_drinkcon(obj, LIQ_WATER);
|
|
weight_change_object(obj, water);
|
|
act("$p is filled.", FALSE, ch, obj, 0, TO_CHAR);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
ASPELL(spell_recall)
|
|
{
|
|
if (victim == NULL || IS_NPC(victim))
|
|
return;
|
|
|
|
act("$n disappears.", TRUE, victim, 0, 0, TO_ROOM);
|
|
char_from_room(victim);
|
|
char_to_room(victim, r_mortal_start_room);
|
|
act("$n appears in the middle of the room.", TRUE, victim, 0, 0, TO_ROOM);
|
|
look_at_room(victim, 0);
|
|
}
|
|
|
|
|
|
ASPELL(spell_teleport)
|
|
{
|
|
room_rnum to_room;
|
|
|
|
if (victim == NULL || IS_NPC(victim))
|
|
return;
|
|
|
|
do {
|
|
to_room = rand_number(0, top_of_world);
|
|
} while (ROOM_FLAGGED(to_room, ROOM_PRIVATE | ROOM_DEATH | ROOM_GODROOM));
|
|
|
|
act("$n slowly fades out of existence and is gone.",
|
|
FALSE, victim, 0, 0, TO_ROOM);
|
|
char_from_room(victim);
|
|
char_to_room(victim, to_room);
|
|
act("$n slowly fades into existence.", FALSE, victim, 0, 0, TO_ROOM);
|
|
look_at_room(victim, 0);
|
|
}
|
|
|
|
#define SUMMON_FAIL "You failed.\r\n"
|
|
|
|
ASPELL(spell_summon)
|
|
{
|
|
if (ch == NULL || victim == NULL)
|
|
return;
|
|
|
|
if (GET_LEVEL(victim) > MIN(LVL_IMMORT - 1, level + 3)) {
|
|
send_to_char(ch, "%s", SUMMON_FAIL);
|
|
return;
|
|
}
|
|
|
|
if (!pk_allowed) {
|
|
if (MOB_FLAGGED(victim, MOB_AGGRESSIVE)) {
|
|
act("As the words escape your lips and $N travels\r\n"
|
|
"through time and space towards you, you realize that $E is\r\n"
|
|
"aggressive and might harm you, so you wisely send $M back.",
|
|
FALSE, ch, 0, victim, TO_CHAR);
|
|
return;
|
|
}
|
|
if (!IS_NPC(victim) && !PRF_FLAGGED(victim, PRF_SUMMONABLE) &&
|
|
!PLR_FLAGGED(victim, PLR_KILLER)) {
|
|
send_to_char(victim, "%s just tried to summon you to: %s.\r\n"
|
|
"%s failed because you have summon protection on.\r\n"
|
|
"Type NOSUMMON to allow other players to summon you.\r\n",
|
|
GET_NAME(ch), world[IN_ROOM(ch)].name,
|
|
(ch->player.sex == SEX_MALE) ? "He" : "She");
|
|
|
|
send_to_char(ch, "You failed because %s has summon protection on.\r\n", GET_NAME(victim));
|
|
mudlog(BRF, LVL_IMMORT, TRUE, "%s failed summoning %s to %s.", GET_NAME(ch), GET_NAME(victim), world[IN_ROOM(ch)].name);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (MOB_FLAGGED(victim, MOB_NOSUMMON) ||
|
|
(IS_NPC(victim) && mag_savingthrow(victim, SAVING_SPELL, 0))) {
|
|
send_to_char(ch, "%s", SUMMON_FAIL);
|
|
return;
|
|
}
|
|
|
|
act("$n disappears suddenly.", TRUE, victim, 0, 0, TO_ROOM);
|
|
|
|
char_from_room(victim);
|
|
char_to_room(victim, IN_ROOM(ch));
|
|
|
|
act("$n arrives suddenly.", TRUE, victim, 0, 0, TO_ROOM);
|
|
act("$n has summoned you!", FALSE, ch, 0, victim, TO_VICT);
|
|
look_at_room(victim, 0);
|
|
}
|
|
|
|
|
|
|
|
ASPELL(spell_locate_object)
|
|
{
|
|
struct obj_data *i;
|
|
char name[MAX_INPUT_LENGTH];
|
|
int j;
|
|
|
|
/*
|
|
* FIXME: This is broken. The spell parser routines took the argument
|
|
* the player gave to the spell and located an object with that keyword.
|
|
* Since we're passed the object and not the keyword we can only guess
|
|
* at what the player originally meant to search for. -gg
|
|
*/
|
|
strlcpy(name, fname(obj->name), sizeof(name));
|
|
j = level / 2;
|
|
|
|
for (i = object_list; i && (j > 0); i = i->next) {
|
|
if (!isname(name, i->name))
|
|
continue;
|
|
|
|
send_to_char(ch, "%c%s", UPPER(*i->short_description), i->short_description+1);
|
|
|
|
if (i->carried_by)
|
|
send_to_char(ch, " is being carried by %s.\r\n", PERS(i->carried_by, ch));
|
|
else if (IN_ROOM(i) != NOWHERE)
|
|
send_to_char(ch, " is in %s.\r\n", world[IN_ROOM(i)].name);
|
|
else if (i->in_obj)
|
|
send_to_char(ch, " is in %s.\r\n", i->in_obj->short_description);
|
|
else if (i->worn_by)
|
|
send_to_char(ch, " is being worn by %s.\r\n", PERS(i->worn_by, ch));
|
|
else
|
|
send_to_char(ch, "'s location is uncertain.\r\n");
|
|
|
|
j--;
|
|
}
|
|
|
|
if (j == level / 2)
|
|
send_to_char(ch, "You sense nothing.\r\n");
|
|
}
|
|
|
|
|
|
|
|
ASPELL(spell_charm)
|
|
{
|
|
struct affected_type af;
|
|
|
|
if (victim == NULL || ch == NULL)
|
|
return;
|
|
|
|
if (victim == ch)
|
|
send_to_char(ch, "You like yourself even better!\r\n");
|
|
else if (!IS_NPC(victim) && !PRF_FLAGGED(victim, PRF_SUMMONABLE))
|
|
send_to_char(ch, "You fail because SUMMON protection is on!\r\n");
|
|
else if (AFF_FLAGGED(victim, AFF_SANCTUARY))
|
|
send_to_char(ch, "Your victim is protected by sanctuary!\r\n");
|
|
else if (MOB_FLAGGED(victim, MOB_NOCHARM))
|
|
send_to_char(ch, "Your victim resists!\r\n");
|
|
else if (AFF_FLAGGED(ch, AFF_CHARM))
|
|
send_to_char(ch, "You can't have any followers of your own!\r\n");
|
|
else if (AFF_FLAGGED(victim, AFF_CHARM) || level < GET_LEVEL(victim))
|
|
send_to_char(ch, "You fail.\r\n");
|
|
/* player charming another player - no legal reason for this */
|
|
else if (!pk_allowed && !IS_NPC(victim))
|
|
send_to_char(ch, "You fail - shouldn't be doing it anyway.\r\n");
|
|
else if (circle_follow(victim, ch))
|
|
send_to_char(ch, "Sorry, following in circles can not be allowed.\r\n");
|
|
else if (mag_savingthrow(victim, SAVING_PARA, 0))
|
|
send_to_char(ch, "Your victim resists!\r\n");
|
|
else {
|
|
if (victim->master)
|
|
stop_follower(victim);
|
|
|
|
add_follower(victim, ch);
|
|
|
|
af.type = SPELL_CHARM;
|
|
af.duration = 24 * 2;
|
|
if (GET_CHA(ch))
|
|
af.duration *= GET_CHA(ch);
|
|
if (GET_INT(victim))
|
|
af.duration /= GET_INT(victim);
|
|
af.modifier = 0;
|
|
af.location = 0;
|
|
af.bitvector = AFF_CHARM;
|
|
affect_to_char(victim, &af);
|
|
|
|
act("Isn't $n just such a nice fellow?", FALSE, ch, 0, victim, TO_VICT);
|
|
if (IS_NPC(victim))
|
|
REMOVE_BIT(MOB_FLAGS(victim), MOB_SPEC);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
ASPELL(spell_identify)
|
|
{
|
|
int i, found;
|
|
size_t len;
|
|
|
|
if (obj) {
|
|
char bitbuf[MAX_STRING_LENGTH];
|
|
|
|
sprinttype(GET_OBJ_TYPE(obj), item_types, bitbuf, sizeof(bitbuf));
|
|
send_to_char(ch, "You feel informed:\r\nObject '%s', Item type: %s\r\n", obj->short_description, bitbuf);
|
|
|
|
if (GET_OBJ_AFFECT(obj)) {
|
|
sprintbit(GET_OBJ_AFFECT(obj), affected_bits, bitbuf, sizeof(bitbuf));
|
|
send_to_char(ch, "Item will give you following abilities: %s\r\n", bitbuf);
|
|
}
|
|
|
|
sprintbit(GET_OBJ_EXTRA(obj), extra_bits, bitbuf, sizeof(bitbuf));
|
|
send_to_char(ch, "Item is: %s\r\n", bitbuf);
|
|
|
|
send_to_char(ch, "Weight: %d, Value: %d, Rent: %d\r\n", GET_OBJ_WEIGHT(obj), GET_OBJ_COST(obj), GET_OBJ_RENT(obj));
|
|
|
|
switch (GET_OBJ_TYPE(obj)) {
|
|
case ITEM_SCROLL:
|
|
case ITEM_POTION:
|
|
len = i = 0;
|
|
|
|
if (GET_OBJ_VAL(obj, 1) >= 1) {
|
|
i = snprintf(bitbuf + len, sizeof(bitbuf) - len, " %s", skill_name(GET_OBJ_VAL(obj, 1)));
|
|
if (i >= 0)
|
|
len += i;
|
|
}
|
|
|
|
if (GET_OBJ_VAL(obj, 2) >= 1 && len < sizeof(bitbuf)) {
|
|
i = snprintf(bitbuf + len, sizeof(bitbuf) - len, " %s", skill_name(GET_OBJ_VAL(obj, 2)));
|
|
if (i >= 0)
|
|
len += i;
|
|
}
|
|
|
|
if (GET_OBJ_VAL(obj, 3) >= 1 && len < sizeof(bitbuf)) {
|
|
i = snprintf(bitbuf + len, sizeof(bitbuf) - len, " %s", skill_name(GET_OBJ_VAL(obj, 3)));
|
|
if (i >= 0)
|
|
len += i;
|
|
}
|
|
|
|
send_to_char(ch, "This %s casts: %s\r\n", item_types[(int) GET_OBJ_TYPE(obj)], bitbuf);
|
|
break;
|
|
case ITEM_WAND:
|
|
case ITEM_STAFF:
|
|
send_to_char(ch, "This %s casts: %s\r\nIt has %d maximum charge%s and %d remaining.\r\n",
|
|
item_types[(int) GET_OBJ_TYPE(obj)], skill_name(GET_OBJ_VAL(obj, 3)),
|
|
GET_OBJ_VAL(obj, 1), GET_OBJ_VAL(obj, 1) == 1 ? "" : "s", GET_OBJ_VAL(obj, 2));
|
|
break;
|
|
case ITEM_WEAPON:
|
|
send_to_char(ch, "Damage Dice is '%dD%d' for an average per-round damage of %.1f.\r\n",
|
|
GET_OBJ_VAL(obj, 1), GET_OBJ_VAL(obj, 2), ((GET_OBJ_VAL(obj, 2) + 1) / 2.0) * GET_OBJ_VAL(obj, 1));
|
|
break;
|
|
case ITEM_ARMOR:
|
|
send_to_char(ch, "AC-apply is %d\r\n", GET_OBJ_VAL(obj, 0));
|
|
break;
|
|
}
|
|
found = FALSE;
|
|
for (i = 0; i < MAX_OBJ_AFFECT; i++) {
|
|
if ((obj->affected[i].location != APPLY_NONE) &&
|
|
(obj->affected[i].modifier != 0)) {
|
|
if (!found) {
|
|
send_to_char(ch, "Can affect you as :\r\n");
|
|
found = TRUE;
|
|
}
|
|
sprinttype(obj->affected[i].location, apply_types, bitbuf, sizeof(bitbuf));
|
|
send_to_char(ch, " Affects: %s By %d\r\n", bitbuf, obj->affected[i].modifier);
|
|
}
|
|
}
|
|
} else if (victim) { /* victim */
|
|
send_to_char(ch, "Name: %s\r\n", GET_NAME(victim));
|
|
if (!IS_NPC(victim))
|
|
send_to_char(ch, "%s is %d years, %d months, %d days and %d hours old.\r\n",
|
|
GET_NAME(victim), age(victim)->year, age(victim)->month,
|
|
age(victim)->day, age(victim)->hours);
|
|
send_to_char(ch, "Height %d cm, Weight %d pounds\r\n", GET_HEIGHT(victim), GET_WEIGHT(victim));
|
|
send_to_char(ch, "Level: %d, Hits: %d, Mana: %d\r\n", GET_LEVEL(victim), GET_HIT(victim), GET_MANA(victim));
|
|
send_to_char(ch, "AC: %d, Hitroll: %d, Damroll: %d\r\n", compute_armor_class(victim), GET_HITROLL(victim), GET_DAMROLL(victim));
|
|
send_to_char(ch, "Str: %d/%d, Int: %d, Wis: %d, Dex: %d, Con: %d, Cha: %d\r\n",
|
|
GET_STR(victim), GET_ADD(victim), GET_INT(victim),
|
|
GET_WIS(victim), GET_DEX(victim), GET_CON(victim), GET_CHA(victim));
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Cannot use this spell on an equipped object or it will mess up the
|
|
* wielding character's hit/dam totals.
|
|
*/
|
|
ASPELL(spell_enchant_weapon)
|
|
{
|
|
int i;
|
|
|
|
if (ch == NULL || obj == NULL)
|
|
return;
|
|
|
|
/* Either already enchanted or not a weapon. */
|
|
if (GET_OBJ_TYPE(obj) != ITEM_WEAPON || OBJ_FLAGGED(obj, ITEM_MAGIC))
|
|
return;
|
|
|
|
/* Make sure no other affections. */
|
|
for (i = 0; i < MAX_OBJ_AFFECT; i++)
|
|
if (obj->affected[i].location != APPLY_NONE)
|
|
return;
|
|
|
|
SET_BIT(GET_OBJ_EXTRA(obj), ITEM_MAGIC);
|
|
|
|
obj->affected[0].location = APPLY_HITROLL;
|
|
obj->affected[0].modifier = 1 + (level >= 18);
|
|
|
|
obj->affected[1].location = APPLY_DAMROLL;
|
|
obj->affected[1].modifier = 1 + (level >= 20);
|
|
|
|
if (IS_GOOD(ch)) {
|
|
SET_BIT(GET_OBJ_EXTRA(obj), ITEM_ANTI_EVIL);
|
|
act("$p glows blue.", FALSE, ch, obj, 0, TO_CHAR);
|
|
} else if (IS_EVIL(ch)) {
|
|
SET_BIT(GET_OBJ_EXTRA(obj), ITEM_ANTI_GOOD);
|
|
act("$p glows red.", FALSE, ch, obj, 0, TO_CHAR);
|
|
} else
|
|
act("$p glows yellow.", FALSE, ch, obj, 0, TO_CHAR);
|
|
}
|
|
|
|
|
|
ASPELL(spell_detect_poison)
|
|
{
|
|
if (victim) {
|
|
if (victim == ch) {
|
|
if (AFF_FLAGGED(victim, AFF_POISON))
|
|
send_to_char(ch, "You can sense poison in your blood.\r\n");
|
|
else
|
|
send_to_char(ch, "You feel healthy.\r\n");
|
|
} else {
|
|
if (AFF_FLAGGED(victim, AFF_POISON))
|
|
act("You sense that $E is poisoned.", FALSE, ch, 0, victim, TO_CHAR);
|
|
else
|
|
act("You sense that $E is healthy.", FALSE, ch, 0, victim, TO_CHAR);
|
|
}
|
|
}
|
|
|
|
if (obj) {
|
|
switch (GET_OBJ_TYPE(obj)) {
|
|
case ITEM_DRINKCON:
|
|
case ITEM_FOUNTAIN:
|
|
case ITEM_FOOD:
|
|
if (GET_OBJ_VAL(obj, 3))
|
|
act("You sense that $p has been contaminated.",FALSE,ch,obj,0,TO_CHAR);
|
|
else
|
|
act("You sense that $p is safe for consumption.", FALSE, ch, obj, 0,
|
|
TO_CHAR);
|
|
break;
|
|
default:
|
|
send_to_char(ch, "You sense that it should not be consumed.\r\n");
|
|
}
|
|
}
|
|
}
|