server-1.12/server/spell_attack.c

1441 lines
45 KiB
C

/*
* static char *rcsid_spell_attack_c =
* "$Id: spell_attack.c 11578 2009-02-23 22:02:27Z lalo $";
*/
/*
CrossFire, A Multiplayer game for X-windows
Copyright (C) 2002-2006 Mark Wedel & Crossfire Development Team
Copyright (C) 1992 Frank Tore Johansen
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
The authors can be reached via e-mail at crossfire-devel@real-time.com
*/
/**
* @file
* This file contains all the spell attack code. Grouping this code
* together should hopefully make it easier to find the relevent bits
* of code.
*
* @todo
* put parameters in the same order, use same name.
*/
#include <global.h>
#include <object.h>
#include <living.h>
#ifndef __CEXTRACT__
#include <sproto.h>
#endif
#include <spells.h>
#include <sounds.h>
/***************************************************************************
*
* BOLT CODE
*
***************************************************************************/
/**
* Cast a bolt-like spell.
*
* We remove the magic flag - that can be derived from
* spob->attacktype.
* This function sets up the appropriate owner and skill
* pointers.
*
* @param op
* who is casting the spell.
* @param caster
* what object is casting the spell (rod, ...).
* @param dir
* firing direction.
* @param spob
* spell object for the bolt.
* @param skill
* skill to credit kill experience to.
* @retval 0
* no bolt could be fired.
* @retval 1
* bolt was fired (but may have been destroyed already).
*/
int fire_bolt(object *op, object *caster, int dir, object *spob, object *skill) {
object *tmp = NULL;
int mflags;
if (!spob->other_arch)
return 0;
tmp = arch_to_object(spob->other_arch);
if (tmp == NULL)
return 0;
/* peterm: level dependency for bolts */
tmp->stats.dam = spob->stats.dam+SP_level_dam_adjust(caster, spob);
tmp->attacktype = spob->attacktype;
if (spob->slaying)
tmp->slaying = add_refcount(spob->slaying);
tmp->range = spob->range+SP_level_range_adjust(caster, spob);
tmp->duration = spob->duration+SP_level_duration_adjust(caster, spob);
tmp->stats.Dex = spob->stats.Dex;
tmp->stats.Con = spob->stats.Con;
tmp->direction = dir;
if (QUERY_FLAG(tmp, FLAG_IS_TURNABLE))
SET_ANIMATION(tmp, dir);
set_owner(tmp, op);
set_spell_skill(op, caster, spob, tmp);
tmp->x = op->x+DIRX(tmp);
tmp->y = op->y+DIRY(tmp);
tmp->map = op->map;
mflags = get_map_flags(tmp->map, &tmp->map, tmp->x, tmp->y, &tmp->x, &tmp->y);
if (mflags&P_OUT_OF_MAP) {
free_object(tmp);
return 0;
}
if (OB_TYPE_MOVE_BLOCK(tmp, GET_MAP_MOVE_BLOCK(tmp->map, tmp->x, tmp->y))) {
if (!QUERY_FLAG(tmp, FLAG_REFLECTING)) {
free_object(tmp);
return 0;
}
tmp->x = op->x;
tmp->y = op->y;
tmp->direction = absdir(tmp->direction+4);
tmp->map = op->map;
}
if ((tmp = insert_ob_in_map(tmp, tmp->map, op, 0)) != NULL)
ob_process(tmp);
return 1;
}
/***************************************************************************
*
* BULLET/BALL CODE
*
***************************************************************************/
/**
* Causes an object to explode, eg, a firebullet, poison cloud ball, etc.
*
* @param op
* the object to explode.
*/
void explode_bullet(object *op) {
tag_t op_tag = op->count;
object *tmp, *owner;
if (op->other_arch == NULL) {
LOG(llevError, "BUG: explode_bullet(): op without other_arch\n");
remove_ob(op);
free_object(op);
return;
}
if (op->env) {
object *env;
env = object_get_env_recursive(op);
if (env->map == NULL || out_of_map(env->map, env->x, env->y)) {
LOG(llevError, "BUG: explode_bullet(): env out of map\n");
remove_ob(op);
free_object(op);
return;
}
remove_ob(op);
op->x = env->x;
op->y = env->y;
insert_ob_in_map(op, env->map, op, INS_NO_MERGE|INS_NO_WALK_ON);
} else if (out_of_map(op->map, op->x, op->y)) {
LOG(llevError, "BUG: explode_bullet(): op out of map\n");
remove_ob(op);
free_object(op);
return;
}
if (op->attacktype) {
hit_map(op, 0, op->attacktype, 1);
if (was_destroyed(op, op_tag))
return;
}
/* other_arch contains what this explodes into */
tmp = arch_to_object(op->other_arch);
copy_owner(tmp, op);
if (tmp->skill)
FREE_AND_CLEAR_STR(tmp->skill);
if (op->skill)
tmp->skill = add_refcount(op->skill);
owner = get_owner(op);
if ((tmp->attacktype&AT_HOLYWORD || tmp->attacktype&AT_GODPOWER) && owner && !tailor_god_spell(tmp, owner)) {
remove_ob(op);
free_object(op);
return;
}
tmp->x = op->x;
tmp->y = op->y;
/* special for bombs - it actually has sane values for these */
if (op->type == SPELL_EFFECT && op->subtype == SP_BOMB) {
tmp->attacktype = op->attacktype;
tmp->range = op->range;
tmp->stats.dam = op->stats.dam;
tmp->duration = op->duration;
} else {
if (op->attacktype&AT_MAGIC)
tmp->attacktype |= AT_MAGIC;
/* Spell doc describes what is going on here */
tmp->stats.dam = op->dam_modifier;
tmp->range = op->stats.maxhp;
tmp->duration = op->stats.hp;
}
/* Used for spell tracking - just need a unique val for this spell -
* the count of the parent should work fine.
*/
tmp->stats.maxhp = op->count;
/* Set direction of cone explosion */
if (tmp->type == SPELL_EFFECT && tmp->subtype == SP_CONE)
tmp->stats.sp = op->direction;
/* Prevent recursion */
op->move_on = 0;
insert_ob_in_map(tmp, op->map, op, 0);
/* remove the firebullet */
if (!was_destroyed(op, op_tag)) {
remove_ob(op);
free_object(op);
}
}
/**
* Checks to see what op should do, given the space it is on (eg, explode,
* damage player, etc).
*
* @param op
* object to check.
*/
void check_bullet(object *op) {
tag_t op_tag = op->count, tmp_tag;
object *tmp;
int dam, mflags;
mapstruct *m;
sint16 sx, sy;
mflags = get_map_flags(op->map, &m, op->x, op->y, &sx, &sy);
if (!(mflags&P_IS_ALIVE) && !OB_TYPE_MOVE_BLOCK(op, GET_MAP_MOVE_BLOCK(m, sx, sy)))
return;
if (op->other_arch) {
/* explode object will also remove op */
explode_bullet(op);
return;
}
/* If nothing alive on this space, no reason to do anything further */
if (!(mflags&P_IS_ALIVE))
return;
for (tmp = GET_MAP_OB(op->map, op->x, op->y); tmp != NULL; tmp = tmp->above) {
if (QUERY_FLAG(tmp, FLAG_ALIVE)) {
tmp_tag = tmp->count;
dam = hit_player(tmp, op->stats.dam, op, op->attacktype, 1);
if (was_destroyed(op, op_tag) || !was_destroyed(tmp, tmp_tag) || (op->stats.dam -= dam) < 0) {
if (!QUERY_FLAG(op, FLAG_REMOVED)) {
remove_ob(op);
free_object(op);
return;
}
}
}
}
}
/**
* Casts a bullet-like spell.
*
* We remove the magic flag - that can be derived from
* spob->attacktype.
* This function sets up the appropriate owner and skill
* pointers.
*
* @param op
* who is casting.
* @param caster
* what object is really casting.
* @param dir
* casting direction.
* @param spob
* spell object for the bullet.
* @retval 0
* no bullet could be fired.
* @retval 1
* bullet was fired (but may have been destroyed already).
*/
int fire_bullet(object *op, object *caster, int dir, object *spob) {
object *tmp = NULL;
int mflags;
if (!spob->other_arch)
return 0;
tmp = arch_to_object(spob->other_arch);
if (tmp == NULL)
return 0;
/* peterm: level dependency for bolts */
tmp->stats.dam = spob->stats.dam+SP_level_dam_adjust(caster, spob);
tmp->attacktype = spob->attacktype;
if (spob->slaying)
tmp->slaying = add_refcount(spob->slaying);
tmp->range = 50;
/* Need to store duration/range for the ball to use */
tmp->stats.hp = spob->duration+SP_level_duration_adjust(caster, spob);
tmp->stats.maxhp = spob->range+SP_level_range_adjust(caster, spob);
tmp->dam_modifier = spob->stats.food+SP_level_dam_adjust(caster, spob);
tmp->direction = dir;
if (QUERY_FLAG(tmp, FLAG_IS_TURNABLE))
SET_ANIMATION(tmp, dir);
set_owner(tmp, op);
set_spell_skill(op, caster, spob, tmp);
tmp->x = op->x+freearr_x[dir];
tmp->y = op->y+freearr_y[dir];
tmp->map = op->map;
mflags = get_map_flags(tmp->map, &tmp->map, tmp->x, tmp->y, &tmp->x, &tmp->y);
if (mflags&P_OUT_OF_MAP) {
free_object(tmp);
return 0;
}
if (OB_TYPE_MOVE_BLOCK(tmp, GET_MAP_MOVE_BLOCK(tmp->map, tmp->x, tmp->y))) {
if (!QUERY_FLAG(tmp, FLAG_REFLECTING)) {
free_object(tmp);
return 0;
}
tmp->x = op->x;
tmp->y = op->y;
tmp->direction = absdir(tmp->direction+4);
tmp->map = op->map;
}
if ((tmp = insert_ob_in_map(tmp, tmp->map, op, 0)) != NULL) {
check_bullet(tmp);
}
return 1;
}
/*****************************************************************************
*
* CONE RELATED FUNCTIONS
*
*****************************************************************************/
/**
* Drops an object based on what is in the cone's "other_arch".
*
* @param op
* what object should drop.
*/
void cone_drop(object *op) {
object *new_ob = arch_to_object(op->other_arch);
new_ob->x = op->x;
new_ob->y = op->y;
new_ob->level = op->level;
set_owner(new_ob, op->owner);
/* preserve skill ownership */
if (op->skill && op->skill != new_ob->skill) {
if (new_ob->skill)
free_string(new_ob->skill);
new_ob->skill = add_refcount(op->skill);
}
insert_ob_in_map(new_ob, op->map, op, 0);
}
/**
* Casts a cone spell.
*
* @param op
* person firing the object.
* @param caster
* object casting the spell.
* @param dir
* direction to fire in.
* @param spell
* spell that is being fired. It uses other_arch for the archetype
* to fire.
* @retval 0
* couldn't cast.
* @retval 1
* successful cast.
*/
int cast_cone(object *op, object *caster, int dir, object *spell) {
object *tmp;
int i, success = 0, range_min = -1, range_max = 1;
mapstruct *m;
sint16 sx, sy;
MoveType movetype;
if (!spell->other_arch)
return 0;
if (op->type == PLAYER && QUERY_FLAG(op, FLAG_UNDEAD) && op->attacktype&AT_TURN_UNDEAD) {
draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_ERROR, "Your undead nature prevents you from turning undead!", NULL);
return 0;
}
if (!dir) {
range_min = 0;
range_max = 8;
}
/* Need to know what the movetype of the object we are about
* to create is, so we can know if the space we are about to
* insert it into is blocked.
*/
movetype = spell->other_arch->clone.move_type;
for (i = range_min; i <= range_max; i++) {
sint16 x, y, d;
/* We can't use absdir here, because it never returns
* 0. If this is a rune, we want to hit the person on top
* of the trap (d==0). If it is not a rune, then we don't want
* to hit that person.
*/
d = dir+i;
while (d < 0)
d += 8;
while (d > 8)
d -= 8;
/* If it's not a rune, we don't want to blast the caster.
* In that case, we have to see - if dir is specified,
* turn this into direction 8. If dir is not specified (all
* direction) skip - otherwise, one line would do more damage
* becase 0 direction will go through 9 directions - necessary
* for the rune code.
*/
if (caster->type != RUNE && d == 0) {
if (dir != 0)
d = 8;
else
continue;
}
/* Special handling for making the spell emit from a thrown
* potion's location
*/
if (caster->type == POTION && caster->subtype == POT_THROW) {
x = caster->x+freearr_x[d];
y = caster->y+freearr_y[d];
if (get_map_flags(caster->map, &m, x, y, &sx, &sy)&P_OUT_OF_MAP)
continue;
} else {
x = op->x+freearr_x[d];
y = op->y+freearr_y[d];
if (get_map_flags(op->map, &m, x, y, &sx, &sy)&P_OUT_OF_MAP)
continue;
}
if ((movetype&GET_MAP_MOVE_BLOCK(m, sx, sy)) == movetype)
continue;
success = 1;
tmp = arch_to_object(spell->other_arch);
set_owner(tmp, op);
set_spell_skill(op, caster, spell, tmp);
tmp->level = caster_level(caster, spell);
tmp->x = sx;
tmp->y = sy;
tmp->attacktype = spell->attacktype;
/* holy word stuff */
if ((tmp->attacktype&AT_HOLYWORD) || (tmp->attacktype&AT_GODPOWER)) {
if (!tailor_god_spell(tmp, op))
return 0;
}
if (dir)
tmp->stats.sp = dir;
else
tmp->stats.sp = i;
tmp->range = spell->range+SP_level_range_adjust(caster, spell);
/* If casting it in all directions, it doesn't go as far */
if (dir == 0) {
tmp->range /= 4;
if (tmp->range < 2 && spell->range >= 2)
tmp->range = 2;
}
tmp->stats.dam = spell->stats.dam+SP_level_dam_adjust(caster, spell);
tmp->duration = spell->duration+SP_level_duration_adjust(caster, spell);
/* Special bonus for fear attacks */
if (tmp->attacktype&AT_FEAR) {
if (caster->type == PLAYER)
tmp->duration += fear_bonus[caster->stats.Cha];
else
tmp->duration += caster->level/3;
}
if (tmp->attacktype&(AT_HOLYWORD|AT_TURN_UNDEAD)) {
if (caster->type == PLAYER)
tmp->duration += turn_bonus[caster->stats.Wis]/5;
else
tmp->duration += caster->level/3;
}
if (!(tmp->move_type&MOVE_FLY_LOW))
LOG(llevDebug, "cast_cone(): arch %s doesn't have flying 1\n", spell->other_arch->name);
if (!tmp->move_on && tmp->stats.dam) {
LOG(llevDebug, "cast_cone(): arch %s doesn't have move_on set\n", spell->other_arch->name);
}
insert_ob_in_map(tmp, m, op, 0);
/* This is used for tracking spells so that one effect doesn't hit
* a single space too many times.
*/
tmp->stats.maxhp = tmp->count;
if (tmp->other_arch)
cone_drop(tmp);
}
return success;
}
/****************************************************************************
*
* BOMB related code
*
****************************************************************************/
/**
* Create a bomb.
*
* @param op
* who is casting.
* @param caster
* what object is casting.
* @param dir
* cast direction.
* @param spell
* spell object to cast.
* @retval 0
* no bomb was placed.
* @retval 1
* bomb was placed on map.
*/
int create_bomb(object *op, object *caster, int dir, object *spell) {
object *tmp;
int mflags;
sint16 dx = op->x+freearr_x[dir], dy = op->y+freearr_y[dir];
mapstruct *m;
mflags = get_map_flags(op->map, &m, dx, dy, &dx, &dy);
if ((mflags&P_OUT_OF_MAP) || (GET_MAP_MOVE_BLOCK(m, dx, dy)&MOVE_WALK)) {
draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_ERROR, "There is something in the way.", NULL);
return 0;
}
tmp = arch_to_object(spell->other_arch);
/* level dependencies for bomb */
tmp->range = spell->range+SP_level_range_adjust(caster, spell);
tmp->stats.dam = spell->stats.dam+SP_level_dam_adjust(caster, spell);
tmp->duration = spell->duration+SP_level_duration_adjust(caster, spell);
tmp->attacktype = spell->attacktype;
set_owner(tmp, op);
set_spell_skill(op, caster, spell, tmp);
tmp->x = dx;
tmp->y = dy;
insert_ob_in_map(tmp, m, op, 0);
return 1;
}
/****************************************************************************
*
* smite related spell code.
*
****************************************************************************/
/**
* Returns the pointer to the first monster in the direction which is pointed to by op.
*
* This is used by finger of death and the 'smite' spells.
*
* @author b.t.
* @param op
* caster - really only used for the source location.
* @param dir
* direction to look in.
* @param range
* how far out to look.
* @param type
* type of spell - either ::SPELL_MANA or ::SPELL_GRACE.
* This info is used for blocked magic/unholy spaces.
* @return
* suitable victim, or NULL if none was found.
*/
static object *get_pointed_target(object *op, int dir, int range, int type) {
object *target;
sint16 x, y;
int dist, mflags;
mapstruct *mp;
if (dir == 0)
return NULL;
for (dist = 1; dist < range; dist++) {
x = op->x+freearr_x[dir]*dist;
y = op->y+freearr_y[dir]*dist;
mp = op->map;
mflags = get_map_flags(op->map, &mp, x, y, &x, &y);
if (mflags&P_OUT_OF_MAP)
return NULL;
if ((type&SPELL_MANA) && (mflags&P_NO_MAGIC))
return NULL;
if ((type&SPELL_GRACE) && (mflags&P_NO_CLERIC))
return NULL;
if (GET_MAP_MOVE_BLOCK(mp, x, y)&MOVE_FLY_LOW)
return NULL;
if (mflags&P_IS_ALIVE) {
for (target = GET_MAP_OB(mp, x, y); target; target = target->above) {
if (QUERY_FLAG(target->head ? target->head : target, FLAG_MONSTER)) {
return target;
}
}
}
}
return NULL;
}
/**
* The priest points to a creature and causes a 'godly curse' to descend.
*
* @param op
* who is casting.
* @param caster
* what object is casting.
* @param dir
* cast direction.
* @param spell
* spell object to cast.
* @retval 0
* spell had no effect.
* @retval 1
* something was affected by the spell.
*/
int cast_smite_spell(object *op, object *caster, int dir, object *spell) {
object *effect, *target;
const object *god = find_god(determine_god(op));
int range;
/* BUG? The value in range doesn't seem to be used. */
range = spell->range+SP_level_range_adjust(caster, spell);
target = get_pointed_target(op, dir, 50, spell->stats.grace ? SPELL_GRACE : SPELL_MANA);
/* Bunch of conditions for casting this spell. Note that only
* require a god if this is a cleric spell (requires grace).
* This makes this spell much more general purpose - it can be used
* by wizards also, which is good, because I think this is a very
* interesting spell.
* if it is a cleric spell, you need a god, and the creature
* can't be friendly to your god.
*/
if (!target
|| QUERY_FLAG(target, FLAG_REFL_SPELL)
|| (!god && spell->stats.grace)
|| (target->title && god && !strcmp(target->title, god->name))
|| (target->race && god && strstr(target->race, god->race))) {
draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_FAILURE, "Your request is unheeded.", NULL);
return 0;
}
if (spell->other_arch)
effect = arch_to_object(spell->other_arch);
else
return 0;
/* tailor the effect by priest level and worshipped God */
effect->level = caster_level(caster, spell);
effect->attacktype = spell->attacktype;
if (effect->attacktype&(AT_HOLYWORD|AT_GODPOWER)) {
if (tailor_god_spell(effect, op))
draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_SUCCESS,
"%s answers your call!",
"%s answers your call!",
determine_god(op));
else {
draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_FAILURE, "Your request is ignored.", NULL);
return 0;
}
}
/* size of the area of destruction */
effect->range = spell->range+SP_level_range_adjust(caster, spell);
effect->duration = spell->duration+SP_level_range_adjust(caster, spell);
if (effect->attacktype&AT_DEATH) {
effect->level = spell->stats.dam+SP_level_dam_adjust(caster, spell);
/* casting death spells at undead isn't a good thing */
if QUERY_FLAG(target, FLAG_UNDEAD) {
if (random_roll(0, 2, op, PREFER_LOW)) {
draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_FAILURE, "Idiot! Your spell boomerangs!", NULL);
effect->x = op->x;
effect->y = op->y;
} else {
char target_name[HUGE_BUF];
query_name(target, target_name, HUGE_BUF);
draw_ext_info_format(NDI_UNIQUE, 0, op,
MSG_TYPE_SPELL, MSG_TYPE_SPELL_FAILURE,
"The %s looks stronger!",
"The %s looks stronger!",
target_name);
target->stats.hp = target->stats.maxhp*2;
free_object(effect);
return 0;
}
}
} else {
/* how much woe to inflict :) */
effect->stats.dam = spell->stats.dam+SP_level_dam_adjust(caster, spell);
}
if (effect->type == SPELL_EFFECT && effect->subtype == SP_EXPLOSION) {
/* Used for spell tracking - just need a unique val for this spell -
* the count of the parent should work fine.
*
* Without this the server can easily get overloaded at high level
* spells.
*/
effect->stats.maxhp = spell->count;
}
set_owner(effect, op);
set_spell_skill(op, caster, spell, effect);
/* ok, tell it where to be, and insert! */
effect->x = target->x;
effect->y = target->y;
insert_ob_in_map(effect, target->map, op, 0);
return 1;
}
/****************************************************************************
* Destruction
****************************************************************************/
/**
* Makes living objects glow. We do this by creating a force and
* inserting it in the object.
*
* Creatures denied the path of light are unaffected.
*
* @param op
* what to make glow.
* @param radius
* glow radius.
* @param time
* glow duration. If 0, the object glows permanently.
* @retval 0
* nothing happened.
* @retval 1
* op is now glowing.
* @author b.t.
*/
static int make_object_glow(object *op, int radius, int time) {
object *tmp;
/* some things are unaffected... */
if (op->path_denied&PATH_LIGHT)
return 0;
tmp = create_archetype(FORCE_NAME);
tmp->speed = 0.01;
tmp->stats.food = time;
SET_FLAG(tmp, FLAG_IS_USED_UP);
tmp->glow_radius = radius;
if (tmp->glow_radius > MAX_LIGHT_RADII)
tmp->glow_radius = MAX_LIGHT_RADII;
tmp->x = op->x;
tmp->y = op->y;
if (tmp->speed < MIN_ACTIVE_SPEED)
tmp->speed = MIN_ACTIVE_SPEED; /* safety */
tmp = insert_ob_in_ob(tmp, op);
if (tmp->glow_radius > op->glow_radius)
op->glow_radius = tmp->glow_radius;
if (!tmp->env || op != tmp->env) {
LOG(llevError, "make_object_glow() failed to insert glowing force in %s\n", op->name);
return 0;
}
return 1;
}
/**
* Hit all monsters around the caster.
*
* @param op
* who is casting.
* @param caster
* what object is casting.
* @param spell_ob
* spell object to cast.
* @return
* 1.
*/
int cast_destruction(object *op, object *caster, object *spell_ob) {
int i, j, range, mflags, friendly = 0, dam, dur;
sint16 sx, sy;
mapstruct *m;
object *tmp;
const char *skill;
range = spell_ob->range+SP_level_range_adjust(caster, spell_ob);
dam = spell_ob->stats.dam+SP_level_dam_adjust(caster, spell_ob);
dur = spell_ob->duration+SP_level_duration_adjust(caster, spell_ob);
if (QUERY_FLAG(op, FLAG_FRIENDLY) || op->type == PLAYER)
friendly = 1;
/* destruction doesn't use another spell object, so we need
* update op's skill pointer so that exp is properly awarded.
* We do some shortcuts here - since this is just temporary
* and we'll reset the values back, we don't need to go through
* the full share string/free_string route.
*/
skill = op->skill;
if (caster == op)
op->skill = spell_ob->skill;
else if (caster->skill)
op->skill = caster->skill;
else
op->skill = NULL;
change_skill(op, find_skill_by_name(op, op->skill), 1);
for (i = -range; i < range; i++) {
for (j = -range; j < range; j++) {
m = op->map;
sx = op->x+i;
sy = op->y+j;
mflags = get_map_flags(m, &m, sx, sy, &sx, &sy);
if (mflags&P_OUT_OF_MAP)
continue;
if (mflags&P_IS_ALIVE) {
for (tmp = GET_MAP_OB(m, sx, sy); tmp; tmp = tmp->above) {
if (QUERY_FLAG(tmp, FLAG_ALIVE) || tmp->type == PLAYER)
break;
}
if (tmp) {
if (tmp->head)
tmp = tmp->head;
if ((friendly && !QUERY_FLAG(tmp, FLAG_FRIENDLY) && tmp->type != PLAYER)
|| (!friendly && (QUERY_FLAG(tmp, FLAG_FRIENDLY) || tmp->type == PLAYER))) {
if (spell_ob->subtype == SP_DESTRUCTION) {
hit_player(tmp, dam, op, spell_ob->attacktype, 0);
if (spell_ob->other_arch) {
tmp = arch_to_object(spell_ob->other_arch);
tmp->x = sx;
tmp->y = sy;
insert_ob_in_map(tmp, m, op, 0);
}
} else if (spell_ob->subtype == SP_FAERY_FIRE && tmp->resist[ATNR_MAGIC] != 100) {
if (make_object_glow(tmp, 1, dur) && spell_ob->other_arch) {
object *effect = arch_to_object(spell_ob->other_arch);
effect->x = sx;
effect->y = sy;
insert_ob_in_map(effect, m, op, 0);
}
}
}
}
}
}
}
op->skill = skill;
return 1;
}
/***************************************************************************
*
* CURSE
*
***************************************************************************/
/**
* Curse an object, reducing its statistics.
*
* @param op
* who is casting.
* @param caster
* what object is casting.
* @param spell_ob
* spell object to cast.
* @param dir
* cast direction.
* @retval 0
* curse had no effect.
* @retval 1
* something was cursed.
*/
int cast_curse(object *op, object *caster, object *spell_ob, int dir) {
const object *god = find_god(determine_god(op));
object *tmp, *force;
tmp = get_pointed_target(op, (dir == 0) ? op->direction : dir, spell_ob->range, SPELL_GRACE);
if (!tmp) {
draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_FAILURE, "There is no one in that direction to curse.", NULL);
return 0;
}
/* If we've already got a force of this type, don't add a new one. */
for (force = tmp->inv; force != NULL; force = force->below) {
if (force->type == FORCE && force->subtype == FORCE_CHANGE_ABILITY) {
if (force->name == spell_ob->name) {
break;
} else if (spell_ob->race && spell_ob->race == force->name) {
draw_ext_info_format(NDI_UNIQUE, 0, op,
MSG_TYPE_SPELL, MSG_TYPE_SPELL_FAILURE,
"You can not cast %s while %s is in effect",
"You can not cast %s while %s is in effect",
spell_ob->name, force->name_pl);
return 0;
}
}
}
if (force == NULL) {
force = create_archetype(FORCE_NAME);
force->subtype = FORCE_CHANGE_ABILITY;
free_string(force->name);
if (spell_ob->race)
force->name = add_refcount(spell_ob->race);
else
force->name = add_refcount(spell_ob->name);
free_string(force->name_pl);
force->name_pl = add_refcount(spell_ob->name);
} else {
int duration;
duration = spell_ob->duration+SP_level_duration_adjust(caster, spell_ob)*50;
if (duration > force->duration) {
force->duration = duration;
draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_SUCCESS, "You recast the spell while in effect.", NULL);
} else {
draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_FAILURE, "Recasting the spell had no effect.", NULL);
}
return 1;
}
force->duration = spell_ob->duration+SP_level_duration_adjust(caster, spell_ob)*50;
force->speed = 1.0;
force->speed_left = -1.0;
SET_FLAG(force, FLAG_APPLIED);
if (god) {
if (spell_ob->last_grace)
force->path_repelled = god->path_repelled;
if (spell_ob->last_grace)
force->path_denied = god->path_denied;
draw_ext_info_format(NDI_UNIQUE, 0, tmp, MSG_TYPE_VICTIM, MSG_TYPE_VICTIM_SPELL,
"You are a victim of %s's curse!",
"You are a victim of %s's curse!",
god->name);
} else
draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_FAILURE, "Your curse seems empty.", NULL);
if (tmp != op && op->type == PLAYER)
draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_SUCCESS,
"You curse %s!",
"You curse %s!",
tmp->name);
force->stats.ac = spell_ob->stats.ac;
force->stats.wc = spell_ob->stats.wc;
change_abil(tmp, force); /* Mostly to display any messages */
insert_ob_in_ob(force, tmp);
fix_object(tmp);
return 1;
}
/**********************************************************************
* mood change
* Arguably, this may or may not be an attack spell. But since it
* effects monsters, it seems best to put it into this file
***********************************************************************/
/**
* This covers the various spells that change the moods of monsters - makes
* them angry, peacful, friendly, etc.
*
* @param op
* who is casting.
* @param caster
* what object is casting.
* @param spell
* spell object to cast.
* @return
* 1.
*/
int mood_change(object *op, object *caster, object *spell) {
object *tmp, *head;
const object *god;
int done_one, range, mflags, level, at, best_at;
sint16 x, y, nx, ny;
mapstruct *m;
const char *race;
/* We precompute some values here so that we don't have to keep
* doing it over and over again.
*/
god = find_god(determine_god(op));
level = caster_level(caster, spell);
range = spell->range+SP_level_range_adjust(caster, spell);
/* On the bright side, no monster should ever have a race of GOD_...
* so even if the player doesn't worship a god, if race=GOD_.., it
* won't ever match anything.
*/
if (!spell->race)
race = NULL;
else if (god && !strcmp(spell->race, "GOD_SLAYING"))
race = god->slaying;
else if (god && !strcmp(spell->race, "GOD_FRIEND"))
race = god->race;
else
race = spell->race;
for (x = op->x-range; x <= op->x+range; x++)
for (y = op->y-range; y <= op->y+range; y++) {
done_one = 0;
m = op->map;
nx = x;
ny = y;
mflags = get_map_flags(m, &m, x, y, &nx, &ny);
if (mflags&P_OUT_OF_MAP)
continue;
/* If there is nothing living on this space, no need to go further */
if (!(mflags&P_IS_ALIVE))
continue;
for (tmp = GET_MAP_OB(m, nx, ny); tmp; tmp = tmp->above)
if (QUERY_FLAG(tmp, FLAG_MONSTER))
break;
/* There can be living objects that are not monsters */
if (!tmp || tmp->type == PLAYER)
continue;
/* Only the head has meaningful data, so resolve to that */
if (tmp->head)
head = tmp->head;
else
head = tmp;
/* Make sure the race is OK. Likewise, only effect undead if spell specifically allows it */
if (race && head->race && !strstr(race, head->race))
continue;
if (QUERY_FLAG(head, FLAG_UNDEAD) && !QUERY_FLAG(spell, FLAG_UNDEAD))
continue;
/* Now do a bunch of stuff related to saving throws */
best_at = -1;
if (spell->attacktype) {
for (at = 0; at < NROFATTACKS; at++)
if (spell->attacktype&(1<<at))
if (best_at == -1 || head->resist[at] > head->resist[best_at])
best_at = at;
if (best_at == -1)
at = 0;
else {
if (head->resist[best_at] == 100)
continue;
else
at = head->resist[best_at]/5;
}
at -= level/5;
if (did_make_save(head, head->level, at))
continue;
} else { /* spell->attacktype */
/*
* Spell has no attacktype (charm&such), so we'll have a specific saving:
* if spell level < monster level, no go
* else, chance of effect = 20+min(50, 2*(spell level-monster level))
*
* The chance will then be in the range [20-70] percent, not too bad.
*
* This is required to fix the 'charm monster' abuse, where a player level 1 can
* charm a level 125 monster...
*
* Ryo, august 14th
*/
if (head->level > level)
continue;
if (random_roll(0, 100, caster, PREFER_LOW) >= (20+MIN(50, 2*(level-head->level))))
/* Failed, no effect */
continue;
}
/* Done with saving throw. Now start effecting the monster */
/* aggravation */
if (QUERY_FLAG(spell, FLAG_MONSTER)) {
CLEAR_FLAG(head, FLAG_SLEEP);
if (QUERY_FLAG(head, FLAG_FRIENDLY))
remove_friendly_object(head);
done_one = 1;
head->enemy = op;
}
/* calm monsters */
if (QUERY_FLAG(spell, FLAG_UNAGGRESSIVE) && !QUERY_FLAG(head, FLAG_UNAGGRESSIVE)) {
SET_FLAG(head, FLAG_UNAGGRESSIVE);
head->enemy = NULL;
done_one = 1;
}
/* berserk monsters */
if (QUERY_FLAG(spell, FLAG_BERSERK) && !QUERY_FLAG(head, FLAG_BERSERK)) {
SET_FLAG(head, FLAG_BERSERK);
done_one = 1;
}
/* charm */
if (QUERY_FLAG(spell, FLAG_NO_ATTACK) && !QUERY_FLAG(head, FLAG_FRIENDLY)) {
SET_FLAG(head, FLAG_FRIENDLY);
/* Prevent uncontolled outbreaks of self replicating monsters.
Typical use case is charm, go somwhere, use aggravation to make hostile.
This could lead to fun stuff like mice outbreak in bigworld and server crawl. */
CLEAR_FLAG(head, FLAG_GENERATOR);
set_owner(head, op);
set_spell_skill(op, caster, spell, head);
add_friendly_object(head);
head->attack_movement = PETMOVE;
done_one = 1;
share_exp(op, head->stats.exp/2, head->skill, SK_EXP_ADD_SKILL);
head->stats.exp = 0;
}
/* If a monster was effected, put an effect in */
if (done_one && spell->other_arch) {
tmp = arch_to_object(spell->other_arch);
tmp->x = nx;
tmp->y = ny;
insert_ob_in_map(tmp, m, op, 0);
}
} /* for y */
return 1;
}
/**
* The following routine creates a swarm of objects. It actually sets up a
* specific swarm object, which then fires off all the parts of the swarm.
*
* @param op
* who is casting.
* @param caster
* what object is casting.
* @param spell
* spell object to cast.
* @param dir
* cast direction.
* @retval 0
* nothing happened.
* @retval 1
* swarm was placed on map.
*/
int fire_swarm(object *op, object *caster, object *spell, int dir) {
object *tmp;
int i;
if (!spell->other_arch)
return 0;
tmp = create_archetype(SWARM_SPELL);
tmp->x = op->x;
tmp->y = op->y;
set_owner(tmp, op); /* needed so that if swarm elements kill, caster gets xp.*/
set_spell_skill(op, caster, spell, tmp);
tmp->level = caster_level(caster, spell); /*needed later, to get level dep. right.*/
tmp->spell = arch_to_object(spell->other_arch);
tmp->attacktype = tmp->spell->attacktype;
if (tmp->attacktype&AT_HOLYWORD || tmp->attacktype&AT_GODPOWER) {
if (!tailor_god_spell(tmp, op))
return 1;
}
tmp->duration = SP_level_duration_adjust(caster, spell);
for (i = 0; i < spell->duration; i++)
tmp->duration += die_roll(1, 3, op, PREFER_HIGH);
tmp->direction = dir;
tmp->invisible = 1;
insert_ob_in_map(tmp, op->map, op, 0);
return 1;
}
/**
* Illuminates something on a map, or try to blind a living thing.
*
* See the spells documentation file for why this is its own function.
*
* @param op
* who is casting.
* @param caster
* what object is casting.
* @param spell
* spell object to cast.
* @param dir
* cast direction.
* @retval 0
* no effect.
* @retval 1
* lighting successful.
*/
int cast_light(object *op, object *caster, object *spell, int dir) {
object *target = NULL, *tmp = NULL;
sint16 x, y;
int dam, mflags;
mapstruct *m;
dam = spell->stats.dam+SP_level_dam_adjust(caster, spell);
if (!dir) {
draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_ERROR, "In what direction?", NULL);
return 0;
}
x = op->x+freearr_x[dir];
y = op->y+freearr_y[dir];
m = op->map;
mflags = get_map_flags(m, &m, x, y, &x, &y);
if (mflags&P_OUT_OF_MAP) {
draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_FAILURE, "Nothing is there.", NULL);
return 0;
}
if (mflags&P_IS_ALIVE && spell->attacktype) {
for (target = GET_MAP_OB(m, x, y); target; target = target->above)
if (QUERY_FLAG(target, FLAG_MONSTER)) {
/* oky doky. got a target monster. Lets make a blinding attack */
if (target->head)
target = target->head;
(void)hit_player(target, dam, op, spell->attacktype, 1);
return 1; /* one success only! */
}
}
/* no live target, perhaps a wall is in the way? */
if (OB_TYPE_MOVE_BLOCK(op, GET_MAP_MOVE_BLOCK(m, x, y))) {
draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_ERROR, "Something is in the way.", NULL);
return 0;
}
/* ok, looks groovy to just insert a new light on the map */
tmp = arch_to_object(spell->other_arch);
if (!tmp) {
LOG(llevError, "Error: spell arch for cast_light() missing.\n");
return 0;
}
tmp->stats.food = spell->duration+SP_level_duration_adjust(caster, spell);
if (tmp->glow_radius) {
tmp->glow_radius = spell->range+SP_level_range_adjust(caster, spell);
if (tmp->glow_radius > MAX_LIGHT_RADII)
tmp->glow_radius = MAX_LIGHT_RADII;
}
tmp->x = x;
tmp->y = y;
insert_ob_in_map(tmp, m, op, 0);
return 1;
}
/**
* Let's try to infect something.
*
* @param op
* who is casting.
* @param caster
* what object is casting.
* @param spell
* spell object to cast.
* @param dir
* cast direction.
* @retval 0
* no one caught anything.
* @retval 1
* at least one living was affected.
*/
int cast_cause_disease(object *op, object *caster, object *spell, int dir) {
sint16 x, y;
int i, mflags, range, dam_mod, dur_mod;
object *walk, *target_head;
mapstruct *m;
x = op->x;
y = op->y;
/* If casting from a scroll, no direction will be available, so refer to the
* direction the player is pointing.
*/
if (!dir)
dir = op->facing;
if (!dir)
return 0; /* won't find anything if casting on ourself, so just return */
/* Calculate these once here */
range = spell->range+SP_level_range_adjust(caster, spell);
dam_mod = SP_level_dam_adjust(caster, spell);
dur_mod = SP_level_duration_adjust(caster, spell);
/* search in a line for a victim */
for (i = 1; i < range; i++) {
x = op->x+i*freearr_x[dir];
y = op->y+i*freearr_y[dir];
m = op->map;
mflags = get_map_flags(m, &m, x, y, &x, &y);
if (mflags&P_OUT_OF_MAP)
return 0;
/* don't go through walls - presume diseases are airborne */
if (GET_MAP_MOVE_BLOCK(m, x, y)&MOVE_FLY_LOW)
return 0;
/* Only bother looking on this space if there is something living here */
if (mflags&P_IS_ALIVE) {
/* search this square for a victim */
for (walk = GET_MAP_OB(m, x, y); walk; walk = walk->above) {
/* Flags for monster is set on head only, so get it now */
target_head = walk;
while (target_head->head)
target_head = target_head->head;
if (QUERY_FLAG(target_head, FLAG_MONSTER) || (target_head->type == PLAYER)) { /* found a victim */
object *disease = arch_to_object(spell->other_arch);
set_owner(disease, op);
set_spell_skill(op, caster, spell, disease);
disease->stats.exp = 0;
disease->level = caster_level(caster, spell);
/* do level adjustments */
if (disease->stats.wc)
disease->stats.wc += dur_mod/2;
if (disease->magic > 0)
disease->magic += dur_mod/4;
if (disease->stats.maxhp > 0)
disease->stats.maxhp += dur_mod;
if (disease->stats.maxgrace > 0)
disease->stats.maxgrace += dur_mod;
if (disease->stats.dam) {
if (disease->stats.dam > 0)
disease->stats.dam += dam_mod;
else
disease->stats.dam -= dam_mod;
}
if (disease->last_sp) {
disease->last_sp -= 2*dam_mod;
if (disease->last_sp < 1)
disease->last_sp = 1;
}
if (disease->stats.maxsp) {
if (disease->stats.maxsp > 0)
disease->stats.maxsp += dam_mod;
else
disease->stats.maxsp -= dam_mod;
}
if (disease->stats.ac)
disease->stats.ac += dam_mod;
if (disease->last_eat)
disease->last_eat -= dam_mod;
if (disease->stats.hp)
disease->stats.hp -= dam_mod;
if (disease->stats.sp)
disease->stats.sp -= dam_mod;
if (infect_object(target_head, disease, 1)) {
object *flash; /* visual effect for inflicting disease */
draw_ext_info_format(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_SUCCESS, "You inflict %s on %s!", NULL, disease->name, target_head->name);
free_object(disease); /* don't need this one anymore */
flash = create_archetype(ARCH_DETECT_MAGIC);
flash->x = x;
flash->y = y;
flash->map = walk->map;
insert_ob_in_map(flash, walk->map, op, 0);
return 1;
}
free_object(disease);
} /* Found a victim */
} /* Search squares for living creature */
} /* if living creature on square */
} /* for range of spaces */
draw_ext_info(NDI_UNIQUE, 0, op, MSG_TYPE_SPELL, MSG_TYPE_SPELL_FAILURE, "No one caught anything!", NULL);
return 1;
}