/* * This file is part of Deliantra, the Roguelike Realtime MMORPG. * * Copyright (©) 2005,2006,2007,2008,2009,2010 Marc Alexander Lehmann / Robin Redeker / the Deliantra team * Copyright (©) 2002-2003 Mark Wedel & Crossfire Development Team * Copyright (©) 1992 Frank Tore Johansen * * Deliantra is free software: you can redistribute it and/or modify it under * the terms of the Affero GNU General Public License as published by the * Free Software Foundation, either version 3 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 Affero GNU General Public License * and the GNU General Public License along with this program. If not, see * . * * The authors can be reached via e-mail to */ /* This file contains all the spell attack code. Grouping this code * together should hopefully make it easier to find the relevent bits * of code */ #include #include #include #include #include #include /* this function checks to see if a spell pushes objects as well * as flies over and damages them (only used for cones for now) * but moved here so it could be applied to bolts too * op is the spell object. */ static void check_spell_knockback (object *op) { int weight_move; int frictionmod = 2; /*poor man's physics - multipy targets weight by this amount */ if (!op->weight) { /*shouldn't happen but if cone object has no weight drop out */ /*LOG (llevDebug, "DEBUG: arch weighs nothing\n"); */ return; } else { weight_move = op->weight + (op->weight * op->level) / 3; /*LOG (llevDebug, "DEBUG: arch weighs %d and masses %d (%s,level %d)\n", op->weight,weight_move,op->name,op->level); */ } for (object *tmp = op->ms ().bot; tmp; tmp = tmp->above) { int num_sections = 1; /* don't move DM */ if (tmp->flag [FLAG_WIZ]) return; /* don't move parts of objects */ if (tmp->head) continue; /* don't move floors or immobile objects */ if (tmp->flag [FLAG_IS_FLOOR] || (!tmp->flag [FLAG_ALIVE] && tmp->flag [FLAG_NO_PICK])) continue; /* count the object's sections */ for (object *tmp2 = tmp; tmp2; tmp2 = tmp2->more) num_sections++; /* I'm not sure if it makes sense to divide by num_sections - bigger * objects should be harder to move, and we are moving the entire * object, not just the head, so the total weight should be relevant. */ /* surface area? -tm */ if (tmp->move_type & MOVE_FLYING) frictionmod = 1; /* flying objects loose the friction modifier */ if (rndm (0, weight_move - 1) > ((tmp->weight / num_sections) * frictionmod)) { /* move it. */ /* move_object is really for monsters, but looking at * the move_object function, it appears that it should * also be safe for objects. * This does return if successful or not, but * I don't see us doing anything useful with that information * right now. */ tmp->move (absdir (op->stats.sp)); } } } /*************************************************************************** * * BOLT CODE * ***************************************************************************/ /* Causes op to fork. op is the original bolt, tmp * is the first piece of the fork. */ static void forklightning (object *op, object *tmp) { int new_dir = 1; /* direction or -1 for left, +1 for right 0 if no new bolt */ int t_dir; /* stores temporary dir calculation */ maptile *m; sint16 sx, sy; object *new_bolt; /* pick a fork direction. tmp->stats.Con is the left bias * i.e., the chance in 100 of forking LEFT * Should start out at 50, down to 25 for one already going left * down to 0 for one going 90 degrees left off original path */ if (rndm (0, 99) < tmp->stats.Con) /* fork left */ new_dir = -1; /* check the new dir for a wall and in the map */ t_dir = absdir (tmp->direction + new_dir); if (get_map_flags (tmp->map, &m, tmp->x + freearr_x[t_dir], tmp->y + freearr_y[t_dir], &sx, &sy) & P_OUT_OF_MAP) return; if (OB_TYPE_MOVE_BLOCK (tmp, GET_MAP_MOVE_BLOCK (m, sx, sy))) return; /* OK, we made a fork */ new_bolt = tmp->clone (); /* reduce chances of subsequent forking */ new_bolt->stats.Dex -= 10; tmp->stats.Dex -= 10; /* less forks from main bolt too */ new_bolt->stats.Con += 25 * new_dir; /* adjust the left bias */ new_bolt->speed_left = -0.1f; new_bolt->direction = t_dir; new_bolt->duration++; new_bolt->stats.dam /= 2; /* reduce daughter bolt damage */ new_bolt->stats.dam++; tmp->stats.dam /= 2; /* reduce father bolt damage */ tmp->stats.dam++; if ((new_bolt = m->insert (new_bolt, sx, sy, op))) update_turn_face (new_bolt); } /* move_bolt: moves bolt 'op'. Basically, it just advances a space, * and checks for various things that may stop it. */ void move_bolt (object *op) { int mflags; sint16 x, y; maptile *m; if (--op->duration < 0) { op->drop_and_destroy (); return; } hit_map (op, 0, op->attacktype, 1); if (!op->direction) return; if (--op->range < 0) op->range = 0; else { x = op->x + DIRX (op); y = op->y + DIRY (op); m = op->map; mflags = get_map_flags (m, &m, x, y, &x, &y); if (mflags & P_OUT_OF_MAP) return; /* We are about to run into something - we may bounce */ /* Calling reflwall is pretty costly, as it has to look at all the objects * on the space. So only call reflwall if we think the data it returns * will be useful. */ if (OB_TYPE_MOVE_BLOCK (op, GET_MAP_MOVE_BLOCK (m, x, y)) || ((mflags & P_IS_ALIVE) && reflwall (m, x, y, op))) { if (!op->flag [FLAG_REFLECTING]) return; /* Since walls don't run diagonal, if the bolt is in * one of 4 main directions, it just reflects back in the * opposite direction. However, if the bolt is travelling * on the diagonal, it is trickier - eg, a bolt travelling * northwest bounces different if it hits a north/south * wall (bounces to northeast) vs an east/west (bounces * to the southwest. */ if (op->direction & 1) op->direction = absdir (op->direction + 4); else { int left, right; int mflags; /* Need to check for P_OUT_OF_MAP: if the bolt is tavelling * over a corner in a tiled map, it is possible that * op->direction is within an adjacent map but either * op->direction-1 or op->direction+1 does not exist. */ mflags = get_map_flags (op->map, &m, op->x + freearr_x[absdir (op->direction - 1)], op->y + freearr_y[absdir (op->direction - 1)], &x, &y); left = (mflags & P_OUT_OF_MAP) ? 0 : OB_TYPE_MOVE_BLOCK (op, GET_MAP_MOVE_BLOCK (m, x, y)); mflags = get_map_flags (op->map, &m, op->x + freearr_x[absdir (op->direction + 1)], op->y + freearr_y[absdir (op->direction + 1)], &x, &y); right = (mflags & P_OUT_OF_MAP) ? 0 : OB_TYPE_MOVE_BLOCK (op, GET_MAP_MOVE_BLOCK (m, x, y)); if (left == right) op->direction = absdir (op->direction + 4); else if (left) op->direction = absdir (op->direction + 2); else if (right) op->direction = absdir (op->direction - 2); } update_turn_face (op); /* A bolt *must* be IS_TURNABLE */ return; } else { /* Create a copy of this object and put it ahead */ object *tmp = op->clone (); m->insert (tmp, x, y, op); tmp->speed_left = -0.1f; /* To make up for the decrease at the top of the function */ tmp->duration++; /* New forking code. Possibly create forks of this object * going off in other directions. */ if (tmp->stats.Dex && rndm (0, 99) < tmp->stats.Dex) forklightning (op, tmp); /* stats.Dex % of forking */ /* In this way, the object left behind sticks on the space, but * doesn't create any bolts that continue to move onward. */ op->range = 0; } /* copy object and move it along */ } /* if move bolt along */ } /* fire_bolt * object op (cast from caster) files a bolt in dir. * spob is the spell object for the bolt. * we remove the magic flag - that can be derived from * spob->attacktype. * This function sets up the appropriate owner and skill * pointers. */ 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 = spob->other_arch->instance (); 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 = 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 (tmp->flag [FLAG_IS_TURNABLE]) SET_ANIMATION (tmp, dir); tmp->set_owner (op); set_spell_skill (op, caster, spob, tmp); tmp->x = op->x + DIRX (tmp); tmp->y = op->y + DIRY (tmp); tmp->map = op->map; maptile *newmap; mflags = get_map_flags (tmp->map, &newmap, tmp->x, tmp->y, &tmp->x, &tmp->y); if (mflags & P_OUT_OF_MAP) { tmp->drop_and_destroy (); return 0; } tmp->map = newmap; if (OB_TYPE_MOVE_BLOCK (tmp, GET_MAP_MOVE_BLOCK (tmp->map, tmp->x, tmp->y))) { if (!tmp->flag [FLAG_REFLECTING]) { tmp->drop_and_destroy (); return 0; } tmp->x = op->x; tmp->y = op->y; tmp->direction = absdir (tmp->direction + 4); tmp->map = op->map; } if ((tmp = tmp->insert_at (tmp, op))) move_bolt (tmp); return 1; } /*************************************************************************** * * BULLET/BALL CODE * ***************************************************************************/ /* expands an explosion. op is a piece of the * explosion - this expands it in the different directions. * At least that is what I think this does. */ void explosion (object *op) { maptile *m = op->map; int i; if (--op->duration < 0) { op->destroy (); return; } hit_map (op, 0, op->attacktype, 0); if (op->range > 0) { for (i = 1; i < 9; i++) { sint16 dx, dy; dx = op->x + freearr_x[i]; dy = op->y + freearr_y[i]; /* ok_to_put_more already does things like checks for walls, * out of map, etc. */ if (ok_to_put_more (op->map, dx, dy, op, op->attacktype)) { object *tmp = op->clone (); tmp->state = 0; tmp->speed_left = -0.21f; tmp->range--; tmp->value = 0; m->insert (tmp, dx, dy, op); } } } } /* Causes an object to explode, eg, a firebullet, * poison cloud ball, etc. op is the object to * explode. */ static void explode_bullet (object *op) { object *tmp, *owner; if (!op->other_arch) { LOG (llevError, "BUG: explode_bullet(): op without other_arch\n"); op->destroy (); return; } if (op->env) { object *env = op->outer_env (); if (!env->map || out_of_map (env->map, env->x, env->y)) { LOG (llevError, "BUG: explode_bullet(): env out of map\n"); op->destroy (); return; } op->insert_at (env, 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"); op->destroy (); return; } // elmex Tue Aug 15 17:46:51 CEST 2006: Prevent explosions of any kind on safe maps // NOTE: If this breaks something important: remove this. I can't think of anything // bad at the moment that might happen from this. if (get_map_flags (op->map, NULL, op->x, op->y, NULL, NULL) & P_SAFE) { op->destroy (); return; } if (op->attacktype) { hit_map (op, 0, op->attacktype, 1); if (op->destroyed ()) return; } /* other_arch contains what this explodes into */ tmp = op->other_arch->instance (); tmp->set_owner (op); tmp->skill = op->skill; owner = op->owner; if ((tmp->attacktype & AT_HOLYWORD || tmp->attacktype & AT_GODPOWER) && owner && !tailor_god_spell (tmp, owner)) { op->destroy (); return; } /* 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; tmp->insert_at (op, op); tmp->play_sound (tmp->sound); /* remove the firebullet */ op->destroy (); } /* checks to see what op should do, given the space it is on * (eg, explode, damage player, etc) */ void check_bullet (object *op) { object *tmp; int dam, mflags; maptile *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 = op->ms ().bot; tmp; tmp = tmp->above) { if (tmp->flag [FLAG_ALIVE]) { dam = hit_player (tmp, op->stats.dam, op, op->attacktype, 1); // TODO: can't understand the following if's if (op->destroyed () || !tmp->destroyed () || (op->stats.dam -= dam) < 0) { if (!op->flag [FLAG_REMOVED]) { op->destroy (); return; } } } } } /* Basically, we move 'op' one square, and if it hits something, * call check_bullet. * This function is only applicable to bullets, but not to all * fired arches (eg, bolts). */ void move_bullet (object *op) { #if 0 /* We need a better general purpose way to do this */ /* peterm: added to make comet leave a trail of burnouts it's an unadulterated hack, but the effect is cool. */ if (op->stats.sp == SP_METEOR) { replace_insert_ob_in_map ("fire_trail", op); if (op->destroyed ()) return; } /* end addition. */ #endif /* Reached the end of its life - remove it */ if (--op->range <= 0) { if (op->other_arch) explode_bullet (op); else op->destroy (); return; } mapxy pos (op); pos.move (op->direction); if (!pos.normalise ()) { op->destroy (); return; } mapspace &ms = pos.ms (); ms.update (); if (!op->direction || OB_TYPE_MOVE_BLOCK (op, ms.move_block)) { if (op->other_arch) explode_bullet (op); else op->destroy (); return; } if (!(op = pos.insert (op, op))) return; if (reflwall (op->map, op->x, op->y, op)) { op->direction = absdir (op->direction + 4); update_turn_face (op); } else check_bullet (op); } /* fire_bullet * object op (cast from caster) files a bolt in dir. * spob is the spell object for the bolt. * we remove the magic flag - that can be derived from * spob->attacktype. * This function sets up the appropriate owner and skill * pointers. */ int fire_bullet (object *op, object *caster, int dir, object *spob) { object *tmp = NULL; int mflags; if (!spob->other_arch) return 0; tmp = spob->other_arch->instance (); if (!tmp) 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 = 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 (tmp->flag [FLAG_IS_TURNABLE]) SET_ANIMATION (tmp, dir); tmp->set_owner (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; maptile *newmap; mflags = get_map_flags (tmp->map, &newmap, tmp->x, tmp->y, &tmp->x, &tmp->y); if (mflags & P_OUT_OF_MAP) { tmp->destroy (); return 0; } tmp->map = newmap; // in case the bullet has direction 0 we explode it in place. // direction 0 is possible for instance when a poison cloud trap springs. if (tmp->direction == 0) { if (tmp->other_arch && (tmp = tmp->insert_at (tmp, op))) // insert before explode cleanly explode_bullet (tmp); // explode object will/should remove tmp else tmp->destroy (); return 0; } if (OB_TYPE_MOVE_BLOCK (tmp, GET_MAP_MOVE_BLOCK (tmp->map, tmp->x, tmp->y))) { if (!tmp->flag [FLAG_REFLECTING]) { tmp->destroy (); return 0; } tmp->x = op->x; tmp->y = op->y; tmp->direction = absdir (tmp->direction + 4); tmp->map = op->map; } if ((tmp = tmp->insert_at (tmp, op))) check_bullet (tmp); return 1; } /***************************************************************************** * * CONE RELATED FUNCTIONS * *****************************************************************************/ /* drops an object based on what is in the cone's "other_arch" */ static void cone_drop (object *op) { object *new_ob = op->other_arch->instance (); new_ob->level = op->level; new_ob->set_owner (op->owner); /* preserve skill ownership */ if (op->skill && op->skill != new_ob->skill) new_ob->skill = op->skill; new_ob->insert_at (op, op); } /* move_cone: causes cone object 'op' to move a space/hit creatures */ void move_cone (object *op) { /* if no map then hit_map will crash so just ignore object */ if (!op->map) { LOG (llevError, "Tried to move_cone object %s without a map.\n", op->name ? &op->name : "unknown"); op->set_speed (0); return; } /* lava saves it's life, but not yours :) */ if (op->flag [FLAG_LIFESAVE]) { hit_map (op, 0, op->attacktype, 0); return; } #if 0 /* Disable this - enabling it makes monsters easier, as * when their cone dies when they die. */ /* If no owner left, the spell dies out. */ if (op->owner == NULL) { op->destroy (); return; } #endif hit_map (op, 0, op->attacktype, 0); if (!op->is_on_map ()) return; /* Check to see if we should push anything. * Spell objects with weight push whatever they encounter to some * degree. */ if (op->weight) { check_spell_knockback (op); if (!op->is_on_map ()) return; } if (op->duration-- < 0) { op->destroy (); return; } /* Object has hit maximum range, so don't have it move * any further. When the duration above expires, * then the object will get removed. */ if (--op->range < 0) { op->range = 0; /* just so it doesn't wrap */ return; } for (int i = -1; i <= 1; i++) { sint16 x = op->x + freearr_x[absdir (op->stats.sp + i)], y = op->y + freearr_y[absdir (op->stats.sp + i)]; if (ok_to_put_more (op->map, x, y, op, op->attacktype)) { object *tmp = op->clone (); tmp->duration = op->duration + 1; /* Use for spell tracking - see ok_to_put_more() */ tmp->stats.maxhp = op->stats.maxhp; op->map->insert (tmp, x, y, op); if (tmp->other_arch) cone_drop (tmp); } } } /* cast_cone: casts a cone spell. * op: person firing the object. * caster: object casting the spell. * dir: direction to fire in. * spell: spell that is being fired. It uses other_arch for the archetype * to fire. * returns 0 on failure, 1 on success. */ int cast_cone (object *op, object *caster, int dir, object *spell) { object *tmp; int i, success = 0, range_min = -1, range_max = 1; maptile *m; sint16 sx, sy; MoveType movetype; if (!spell->other_arch) return 0; if (op->type == PLAYER && op->flag [FLAG_UNDEAD] && op->attacktype & AT_TURN_UNDEAD) { op->failmsg ("Your undead nature prevents you from turning undead!"); 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->move_type; for (i = range_min; i <= range_max; i++) { sint16 x, y; /* 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. */ int d = dir ? absdir (dir + i) : i; /* 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; } 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 = spell->other_arch->instance (); tmp->set_owner (op); set_spell_skill (op, caster, spell, tmp); tmp->level = casting_level (caster, spell); 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->archname); if (!tmp->move_on && tmp->stats.dam) LOG (llevDebug, "cast_cone(): arch %s doesn't have move_on set\n", &spell->other_arch->archname); m->insert (tmp, sx, sy, op); /* 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 * ****************************************************************************/ /* This handles an exploding bomb. * op is the original bomb object. */ void animate_bomb (object *op) { if (op->state != NUM_ANIMATIONS (op) - 1) return; object *env = op->outer_env (); if (op->env) { if (!env->map) return; if (!(op = op->insert_at (env, op))) return; } // elmex Tue Aug 15 17:46:51 CEST 2006: Prevent bomb from exploding // on a safe map. I don't like this special casing, but it seems to be neccessary // as bombs can be carried. if (op->ms ().flags () & P_SAFE) { op->destroy (); return; } /* This copies a lot of the code from the fire bullet, * but using the cast_bullet isn't really feasible, * so just set up the appropriate values. */ if (archetype *at = archetype::find (SPLINT)) { for (int i = 1; i < 9; i++) { if (out_of_map (op->map, op->x + freearr_x[i], op->y + freearr_x[i])) continue; object *tmp = at->instance (); tmp->direction = i; tmp->range = op->range; tmp->stats.dam = op->stats.dam; tmp->duration = op->duration; tmp->attacktype = op->attacktype; tmp->set_owner (op); if (op->skill && op->skill != tmp->skill) tmp->skill = op->skill; if (tmp->flag [FLAG_IS_TURNABLE]) SET_ANIMATION (tmp, i); op->map->insert (tmp, op->x + freearr_x[i], op->y + freearr_x[i], op); move_bullet (tmp); } } explode_bullet (op); } 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]; maptile *m; mflags = get_map_flags (op->map, &m, dx, dy, &dx, &dy); // when creating a bomb below ourself it should always work, even // when movement is blocked (somehow we got here, somehow we are here, // so we should also be able to make a bomb here). (originally added // to fix create bomb traps in doors, which cast with dir=0). if (dir) { if ((mflags & P_OUT_OF_MAP) || (GET_MAP_MOVE_BLOCK (m, dx, dy) & MOVE_WALK)) { op->failmsg ("There is something in the way."); return 0; } } tmp = spell->other_arch->instance (); /* 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; tmp->set_owner (op); set_spell_skill (op, caster, spell, tmp); m->insert (tmp, dx, dy, op); return 1; } /**************************************************************************** * * smite related spell code. * ****************************************************************************/ /* get_pointed_target() - this is used by finger of death * and the 'smite' spells. Returns the pointer to the first * monster in the direction which is pointed to by op. b.t. * op is the caster - really only used for the source location. * dir is the direction to look in. * range is how far out to look. * type is the type of spell - either SPELL_MANA or SPELL_GRACE. * this info is used for blocked magic/unholy spaces. */ static object * get_pointed_target (object *op, int dir, int range, int type) { object *target; sint16 x, y; int dist, mflags; maptile *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 (target->flag [FLAG_MONSTER]) return target; } return NULL; } /* cast_smite_arch() - the priest points to a creature and causes * a 'godly curse' to decend. * usual params - * op = player * caster = object casting the spell. * dir = direction being cast * spell = spell object */ int cast_smite_spell (object *op, object *caster, int dir, object *spell) { object *effect, *target; object *god = find_god (determine_god (op)); int range; 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 || target->flag [FLAG_REFL_SPELL] || (!god && spell->stats.grace) || (god && target->title == god->name) || (god && target->race.contains (god->race))) { op->failmsg ("Your request is unheeded."); return 0; } if (spell->other_arch) effect = spell->other_arch->instance (); else return 0; /* tailor the effect by priest level and worshipped God */ effect->level = casting_level (caster, spell); effect->attacktype = spell->attacktype; if (effect->attacktype & (AT_HOLYWORD | AT_GODPOWER)) { if (tailor_god_spell (effect, op)) new_draw_info_format (NDI_UNIQUE, 0, op, "%s answers your call!", (const char *)determine_god (op)); else { op->failmsg ("Your request is ignored."); 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 (target->flag [FLAG_UNDEAD]) { if (random_roll (0, 2, op, PREFER_LOW)) { new_draw_info (NDI_UNIQUE, 0, op, "Idiot! Your spell boomerangs!"); effect->x = op->x; effect->y = op->y; } else { new_draw_info_format (NDI_UNIQUE, 0, op, "The %s looks stronger!", query_name (target)); target->stats.hp = target->stats.maxhp * 2; effect->destroy (); return 0; } } } else { /* how much woe to inflict :) */ effect->stats.dam = spell->stats.dam + SP_level_dam_adjust (caster, spell); } effect->set_owner (op); set_spell_skill (op, caster, spell, effect); /* ok, tell it where to be, and insert! */ effect->insert_at (target, op); return 1; } /**************************************************************************** * * MAGIC MISSILE code. * note that the fire_bullet is used to fire the missile. The * code here is just to move the missile. ****************************************************************************/ /* op is a missile that needs to be moved */ void move_missile (object *op) { if (op->range-- <= 0) { op->drop_and_destroy (); return; } mapxy pos (op); pos.move (op->direction); if (!pos.normalise ()) { op->destroy (); return; } mapspace &ms = pos.ms (); if (ms.flags () & P_IS_ALIVE || ms.blocks (op)) { hit_map (op, op->direction, AT_MAGIC, 1); /* Basically, missile only hits one thing then goes away. * we need to remove it if someone hasn't already done so. */ op->destroy (); return; } if (!op->direction) { op->destroy (); return; } int i = spell_find_dir (pos.m, pos.x, pos.y, op->owner); if (i > 0 && i != op->direction) { op->direction = i; SET_ANIMATION (op, op->direction); } pos.insert (op, op); } /**************************************************************************** * Destruction ****************************************************************************/ /* make_object_glow() - currently only makes living objects glow. * we do this by creating a force and inserting it in the * object. if time is 0, the object glows permanently. To truely * make this work for non-living objects, we would have to * give them the capability to have an inventory. b.t. */ static int make_object_glow (object *op, int radius, int time) { /* some things are unaffected... */ if (op->path_denied & PATH_LIGHT) return 0; object *tmp = get_archetype (FORCE_NAME); tmp->speed = 0.01; tmp->stats.food = time; tmp->set_flag (FLAG_IS_USED_UP); tmp->set_glow_radius (min (MAX_LIGHT_RADIUS, radius)); tmp = insert_ob_in_ob (tmp, op); if (tmp->glow_radius > op->glow_radius) op->set_glow_radius (tmp->glow_radius); return 1; } int cast_destruction (object *op, object *caster, object *spell_ob) { int range = spell_ob->range + SP_level_range_adjust (caster, spell_ob); int dam = spell_ob->stats.dam + SP_level_dam_adjust (caster, spell_ob); int dur = spell_ob->duration + SP_level_duration_adjust (caster, spell_ob); bool friendly = op->flag [FLAG_FRIENDLY] || op->is_player (); dynbuf buf; unordered_mapwalk (buf, op, -range, -range, range, range) { mapspace &ms = m->at (nx, ny); if (ms.flags () & P_IS_ALIVE) for (object *next, *tmp = ms.bot; tmp; tmp = next) { next = tmp->above; if (tmp->flag [FLAG_ALIVE] || tmp->is_player ()) { tmp = tmp->head_ (); if ((friendly && !tmp->flag [FLAG_FRIENDLY] && !tmp->is_player ()) || (!friendly && (tmp->flag [FLAG_FRIENDLY] || tmp->is_player ()))) { if (spell_ob->subtype == SP_DESTRUCTION) { hit_player (tmp, dam, op, spell_ob->attacktype, 0); if (spell_ob->other_arch) m->insert (spell_ob->other_arch->instance (), nx, ny, op); } else if (spell_ob->subtype == SP_FAERY_FIRE && tmp->resist [ATNR_MAGIC] != 100) { if (make_object_glow (tmp, 1, dur) && spell_ob->other_arch) m->insert (spell_ob->other_arch->instance (), nx, ny, op); } } } } } return 1; } /*************************************************************************** * * CURSE * ***************************************************************************/ int cast_curse (object *op, object *caster, object *spell_ob, int dir) { 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) { op->failmsg ("There is no one in that direction to curse."); return 0; } tmp = tmp->head_ (); /* If we've already got a force of this type, don't add a new one. */ for (force = tmp->inv; force; 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) { new_draw_info_format (NDI_UNIQUE, 0, op, "You can not cast %s while %s is in effect", &spell_ob->name, &force->name_pl); return 0; } } } if (!force) { force = get_archetype (FORCE_NAME); force->subtype = FORCE_CHANGE_ABILITY; if (spell_ob->race) force->name = spell_ob->race; else force->name = spell_ob->name; force->name_pl = 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; new_draw_info (NDI_UNIQUE, 0, op, "You recast the spell while in effect."); } else new_draw_info (NDI_UNIQUE, 0, op, "Recasting the spell had no effect."); return 1; } force->duration = spell_ob->duration + SP_level_duration_adjust (caster, spell_ob) * 50; force->speed = 1.f; force->speed_left = -1.f; force->set_flag (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; new_draw_info_format (NDI_UNIQUE, 0, tmp, "You are a victim of %s's curse!", &god->name); } else new_draw_info (NDI_UNIQUE, 0, op, "Your curse seems empty."); if (tmp != op && op->type == PLAYER) new_draw_info_format (NDI_UNIQUE, 0, op, "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); tmp->update_stats (); 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. */ int mood_change (object *op, object *caster, object *spell) { object *tmp, *god, *head; int done_one, range, level, at, best_at; 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 = casting_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 && spell->race == shstr_GOD_SLAYING) race = god->slaying; else if (god && spell->race == shstr_GOD_FRIEND) race = god->race; else race = spell->race; dynbuf buf; unordered_mapwalk (buf, op, -range, -range, range, range) { mapspace &ms = m->at (nx, ny); /* If there is nothing living on this space, no need to go further */ if (!ms.flags () & P_IS_ALIVE) continue; // players can only affect spaces that they can actually see if (caster && caster->contr && caster->contr->darkness_at (m, nx, ny) == LOS_BLOCKED) continue; for (tmp = ms.top; tmp; tmp = tmp->below) if (tmp->flag [FLAG_MONSTER]) break; /* There can be living objects that are not monsters */ if (!tmp) continue; /* Only the head has meaningful data, so resolve to that */ head = tmp->head_ (); /* 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 (head->flag [FLAG_UNDEAD] && !spell->flag [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 affecting the monster */ done_one = 0; /* aggravation */ if (spell->flag [FLAG_MONSTER]) { head->clr_flag (FLAG_SLEEP); remove_friendly_object (head); done_one = 1; head->enemy = op; } /* calm monsters */ if (spell->flag [FLAG_UNAGGRESSIVE] && !head->flag [FLAG_UNAGGRESSIVE]) { head->set_flag (FLAG_UNAGGRESSIVE); head->enemy = NULL; done_one = 1; } /* berserk monsters */ if (spell->flag [FLAG_BERSERK] && !head->flag [FLAG_BERSERK]) { head->set_flag (FLAG_BERSERK); done_one = 1; } /* charm */ if (spell->flag [FLAG_NO_ATTACK] && !head->flag [FLAG_FRIENDLY]) { INVOKE_OBJECT (KILL, head, ARG_OBJECT (caster)); /* Prevent uncontrolled 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. */ head->clr_flag (FLAG_GENERATOR); head->set_owner (op); set_spell_skill (op, caster, spell, head); add_friendly_object (head); head->attack_movement = PETMOVE; done_one = 1; change_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) m->insert (spell->other_arch->instance (), nx, ny, op); } return 1; } /* Move_ball_spell: This handles ball type spells that just sort of wander * about. was called move_ball_lightning, but since more than the ball * lightning spell used it, that seemed misnamed. * op is the spell effect. * note that duration is handled by process_object() in time.c */ void move_ball_spell (object *op) { int i, j, dam_save, dir, mflags; sint16 nx, ny, hx, hy; object *owner; maptile *m; owner = op->owner; /* the following logic makes sure that the ball doesn't move into a wall, * and makes sure that it will move along a wall to try and get at it's * victim. The block immediately below more or less chooses a random * offset to move the ball, eg, keep it mostly on course, with some * deviations. */ dir = 0; if (!(rndm (0, 3))) j = rndm (0, 1); else j = 0; for (i = 1; i < 9; i++) { /* i bit 0: alters sign of offset * other bits (i / 2): absolute value of offset */ int offset = ((i ^ j) & 1) ? (i / 2) : -(i / 2); int tmpdir = absdir (op->direction + offset); nx = op->x + freearr_x[tmpdir]; ny = op->y + freearr_y[tmpdir]; if (!(get_map_flags (op->map, &m, nx, ny, &nx, &ny) & P_OUT_OF_MAP) && !(OB_TYPE_MOVE_BLOCK (op, GET_MAP_MOVE_BLOCK (m, nx, ny)))) { dir = tmpdir; break; } } if (dir == 0) { nx = op->x; ny = op->y; m = op->map; } m->insert (op, nx, ny, op); dam_save = op->stats.dam; /* save the original dam: we do halfdam on surrounding squares */ /* loop over current square and neighbors to hit. * if this has an other_arch field, we insert that in * the surround spaces. */ for (j = 0; j < 9; j++) { hx = nx + freearr_x[j]; hy = ny + freearr_y[j]; m = op->map; mflags = get_map_flags (m, &m, hx, hy, &hx, &hy); if (mflags & P_OUT_OF_MAP) continue; /* first, don't ever, ever hit the owner. Don't hit out * of the map either. */ if ((mflags & P_IS_ALIVE) && (!owner || owner->x != hx || owner->y != hy || !on_same_map (owner, op))) { if (j) op->stats.dam = dam_save / 2; hit_map (op, j, op->attacktype, 1); } /* insert the other arch */ if (op->other_arch && !(OB_TYPE_MOVE_BLOCK (op, GET_MAP_MOVE_BLOCK (m, hx, hy)))) m->insert (op->other_arch->instance (), hx, hy, op); } /* restore to the center location and damage */ op->stats.dam = dam_save; i = spell_find_dir (op->map, op->x, op->y, op->owner); if (i >= 0) { /* we have a preferred direction! */ /* pick another direction if the preferred dir is blocked. */ if (get_map_flags (op->map, &m, nx + freearr_x[i], ny + freearr_y[i], &hx, &hy) & P_OUT_OF_MAP || OB_TYPE_MOVE_BLOCK (op, GET_MAP_MOVE_BLOCK (m, hx, hy))) i = absdir (i + rndm (0, 2) - 1); /* -1, 0, +1 */ op->direction = i; } } /* move_swarm_spell: peterm * This is an implementation of the swarm spell. It was written for * meteor swarm, but it could be used for any swarm. A swarm spell * is a special type of object that casts swarms of other types * of spells. Which spell it casts is flexible. It fires the spells * from a set of squares surrounding the caster, in a given direction. */ void move_swarm_spell (object *op) { #if 0 static int cardinal_adjust[9] = { -3, -2, -1, 0, 0, 0, 1, 2, 3 }; static int diagonal_adjust[10] = { -3, -2, -2, -1, 0, 0, 1, 2, 2, 3 }; sint16 target_x, target_y, origin_x, origin_y; int adjustdir; maptile *m; #endif object *owner = op->env; if (!owner) // MUST not happen, remove when true TODO { LOG (llevError, "swarm spell found outside inventory: %s\n", op->debug_desc ()); op->destroy (); return; } if (!op->duration || !owner->is_on_map ()) { op->drop_and_destroy (); return; } op->duration--; int basedir = op->direction; if (!basedir) { /* spray in all directions! 8) */ op->facing = (op->facing + op->state) & 7; basedir = op->facing + 1; } #if 0 // this is bogus: it causes wrong places to be checked below // (a wall 2 cells away will block the effect...) and // doesn't work for SP_BULLET anyhow, so again tests the wrong // space. // should be fixed later, but correctness before features... // (schmorp) /* new offset calculation to make swarm element distribution * more uniform */ if (op->duration) { if (basedir & 1) { adjustdir = cardinal_adjust[rndm (0, 8)]; } else { adjustdir = diagonal_adjust[rndm (0, 9)]; } } else { adjustdir = 0; /* fire the last one from forward. */ } target_x = op->x + freearr_x[absdir (basedir + adjustdir)]; target_y = op->y + freearr_y[absdir (basedir + adjustdir)]; /* back up one space so we can hit point-blank targets, but this * necessitates extra out_of_map check below */ origin_x = target_x - freearr_x[basedir]; origin_y = target_y - freearr_y[basedir]; /* spell pointer is set up for the spell this casts. Since this * should just be a pointer to the spell in some inventory, * it is unlikely to disappear by the time we need it. However, * do some sanity checking anyways. */ if (op->spell && op->spell->type == SPELL && !(get_map_flags (op->map, &m, target_x, target_y, &target_x, &target_y) & P_OUT_OF_MAP) && !(OB_TYPE_MOVE_BLOCK (op->spell, GET_MAP_MOVE_BLOCK (m, target_x, target_y)))) { /* Bullet spells have a bunch more customization that needs to be done */ if (op->spell->subtype == SP_BULLET) fire_bullet (owner, op, basedir, op->spell); else if (op->spell->subtype == SP_MAGIC_MISSILE) fire_arch_from_position (owner, op, origin_x, origin_y, basedir, op->spell); } #endif /* spell pointer is set up for the spell this casts. Since this * should just be a pointer to the spell in some inventory, * it is unlikely to disappear by the time we need it. However, * do some sanity checking anyways. */ if (op->spell && op->spell->type == SPELL) { /* Bullet spells have a bunch more customization that needs to be done */ if (op->spell->subtype == SP_BULLET) fire_bullet (owner, op, basedir, op->spell); else if (op->spell->subtype == SP_MAGIC_MISSILE) fire_arch_from_position (owner, op, owner->x, owner->y, basedir, op->spell); } } /* fire_swarm: * 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. * * op: the owner * caster: the caster (owner, wand, rod, scroll) * dir: the direction everything will be fired in * spell - the spell that is this spell. * n: the number to be fired. */ int fire_swarm (object *op, object *caster, object *spell, int dir) { if (!spell->other_arch) return 0; object *tmp = archetype::get (SWARM_SPELL); set_spell_skill (op, caster, spell, tmp); tmp->level = casting_level (caster, spell); /* needed later, to get level dep. right. */ tmp->spell = spell->other_arch->instance (); 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 (int i = 0; i < spell->duration; i++) tmp->duration += die_roll (1, 3, op, PREFER_HIGH); tmp->invisible = 1; tmp->flag [FLAG_NO_DROP] = 1; // make sure it stays in inv, or else tmp->direction = dir; tmp->facing = rndm (1, 8); // initial firing direction tmp->state = rndm (4) * 2 + 1; // direction increment op->insert (tmp); return 1; } /* See the spells documentation file for why this is its own * function. */ int cast_light (object *op, object *caster, object *spell, int dir) { object *target = NULL, *tmp = NULL; sint16 x, y; int dam, mflags; maptile *m; dam = spell->stats.dam + SP_level_dam_adjust (caster, spell); if (dir) { 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) { op->failmsg ("Nothing is there."); return 0; } if (mflags & P_IS_ALIVE && spell->attacktype) { for (target = GET_MAP_OB (m, x, y); target; target = target->above) if (target->flag [FLAG_MONSTER]) { /* oky doky. got a target monster. Lets make a blinding attack */ if (target->head) target = target->head; 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))) { op->failmsg ("Something is in the way."); return 0; } } /* ok, looks groovy to just insert a new light on the map */ tmp = spell->other_arch->instance (); 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->set_glow_radius ( clamp (spell->range + SP_level_range_adjust (caster, spell), 1, MAX_LIGHT_RADIUS) ); if (dir) m->insert (tmp, x, y, op); else caster->outer_env_or_self ()->insert (tmp); return 1; } /* cast_cause_disease: this spell looks along from the * player and infects someone. * op is the player/monster, caster is the object, dir is the direction * to cast, disease_arch is the specific disease, and type is the spell number * perhaps this should actually be in disease.c? */ 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; maptile *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) if (walk->flag [FLAG_MONSTER] || (walk->type == PLAYER)) { /* found a victim */ object *disease = spell->other_arch->instance (); disease->set_owner (op); set_spell_skill (op, caster, spell, disease); disease->stats.exp = 0; disease->level = casting_level (caster, spell); /* do level adjustments */ if (disease->stats.wc ) disease->stats.wc += dur_mod / 2; if (disease->magic > 0) disease->magic += dur_mod / 8; if (disease->stats.maxhp > 0) disease->stats.maxhp += dur_mod; if (disease->stats.maxgrace > 0) disease->stats.maxgrace += dur_mod; if (disease->last_sp) { disease->last_sp -= 2 * dam_mod; if (disease->last_sp < 1) disease->last_sp = 1; } if (disease->stats.dam ) disease->stats.dam += copysign (disease->stats.dam , dam_mod); if (disease->stats.maxsp) disease->stats.maxsp += copysign (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 (walk, disease, 1)) { op->statusmsg (format ("You inflict %s on %s!", &disease->name, &walk->name)); disease->destroy (); /* don't need this one anymore */ walk->map->insert (get_archetype (shstr_detect_magic), x, y, op); return 1; } disease->destroy (); } } /* if living creature */ } /* for range of spaces */ new_draw_info (NDI_UNIQUE, 0, op, "No one caught anything!"); return 1; }