/* CrossFire, A Multiplayer game for X-windows Copyright (C) 2007 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 projectile.c * This file contains code common to projectile objects. For now it is limited * to arrows and thrown objects. */ #include #include #include #include /** * Handle an arrow or thrown object stopping. * @param op The arrow or thrown object that is stopping. */ void stop_projectile(object *op) { /* Lauwenmark: Handle for plugin stop event */ execute_event(op, EVENT_STOP, NULL, NULL, NULL, SCRIPT_FIX_NOTHING); if (op->inv) { object *payload = op->inv; remove_ob(payload); clear_owner(payload); insert_ob_in_map(payload, op->map, payload, 0); remove_ob(op); free_object(op); } else { op = fix_stopped_arrow(op); if (op) merge_ob(op, NULL); } } /** * Move an arrow or thrown object along its course. * @param context The method context * @param op The arrow or thrown object being moved. * @todo Split this function up. * @return METHOD_ERROR if op is not in a map, otherwise METHOD_OK */ method_ret common_process_projectile(ob_methods *context, object *op) { object *tmp; sint16 new_x, new_y; int was_reflected, mflags; mapstruct *m; if (op->map == NULL) { LOG(llevError, "BUG: Projectile had no map.\n"); remove_ob(op); free_object(op); return METHOD_ERROR; } /* Calculate target map square */ new_x = op->x+DIRX(op); new_y = op->y+DIRY(op); was_reflected = 0; m = op->map; mflags = get_map_flags(m, &m, new_x, new_y, &new_x, &new_y); if (mflags&P_OUT_OF_MAP) { stop_projectile(op); return METHOD_OK; } /* only need to look for living creatures if this flag is set */ if (mflags&P_IS_ALIVE) { for (tmp = GET_MAP_OB(m, new_x, new_y); tmp != NULL; tmp = tmp->above) if (QUERY_FLAG(tmp, FLAG_ALIVE)) break; /* Not really fair, but don't let monsters hit themselves with * their own arrow - this can be because they fire it then * move into it. */ if (tmp != NULL && tmp != op->owner) { /* Found living object, but it is reflecting the missile. Update * as below. (Note that for living creatures there is a small * chance that reflect_missile fails.) */ if (QUERY_FLAG(tmp, FLAG_REFL_MISSILE) && (rndm(0, 99)) < (90-op->level/10)) { int number = op->face->number; op->direction = absdir(op->direction+4); op->state = 0; if (GET_ANIM_ID(op)) { number += 4; if (number > GET_ANIMATION(op, 8)) number -= 8; op->face = &new_faces[number]; } was_reflected = 1; /* skip normal movement calculations */ } else { /* Attack the object. */ op = hit_with_arrow(op, tmp); if (op == NULL) return METHOD_OK; } } /* if this is not hitting its owner */ } /* if there is something alive on this space */ if (OB_TYPE_MOVE_BLOCK(op, GET_MAP_MOVE_BLOCK(m, new_x, new_y))) { int retry = 0; /* if the object doesn't reflect, stop the arrow from moving * note that this code will now catch cases where a monster is * on a wall but has reflecting - the arrow won't reflect. * Mapmakers shouldn't put monsters on top of wall in the first * place, so I don't consider that a problem. */ if (!QUERY_FLAG(op, FLAG_REFLECTING) || !(rndm(0, 19))) { stop_projectile(op); return METHOD_OK; } else { /* If one of the major directions (n,s,e,w), just reverse it */ if (op->direction&1) { op->direction = absdir(op->direction+4); retry = 1; } /* There were two blocks with identical code - * use this retry here to make this one block * that did the same thing. */ while (retry < 2) { int left, right, mflags; mapstruct *m1; sint16 x1, y1; retry++; /* Need to check for P_OUT_OF_MAP: if the arrow 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, &m1, op->x+freearr_x[absdir(op->direction-1)], op->y+freearr_y[absdir(op->direction-1)], &x1, &y1); left = (mflags&P_OUT_OF_MAP) ? 0 : OB_TYPE_MOVE_BLOCK(op, (GET_MAP_MOVE_BLOCK(m1, x1, y1))); mflags = get_map_flags(op->map, &m1, op->x+freearr_x[absdir(op->direction+1)], op->y+freearr_y[absdir(op->direction+1)], &x1, &y1); right = (mflags&P_OUT_OF_MAP) ? 0 : OB_TYPE_MOVE_BLOCK(op, (GET_MAP_MOVE_BLOCK(m1, x1, y1))); 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); mflags = get_map_flags(op->map, &m1, op->x+DIRX(op), op->y+DIRY(op), &x1, &y1); /* If this space is not out of the map and not blocked, valid space - * don't need to retry again. */ if (!(mflags&P_OUT_OF_MAP) && !OB_TYPE_MOVE_BLOCK(op, GET_MAP_MOVE_BLOCK(m1, x1, y1))) break; } /* Couldn't find a direction to move the arrow to - just * top it from moving. */ if (retry == 2) { stop_projectile(op); return METHOD_OK; } /* update object image for new facing */ /* many thrown objects *don't *have more than one face */ if (GET_ANIM_ID(op)) SET_ANIMATION(op, op->direction); } /* object is reflected */ } /* object ran into a wall */ /* Move the arrow. */ remove_ob(op); op->x = new_x; op->y = new_y; /* decrease the speed as it flies. 0.05 means a standard bow will shoot * about 17 squares. Tune as needed. */ op->speed -= 0.05; insert_ob_in_map(op, m, op, 0); return METHOD_OK; } /** * Move on this Thrown Object object. * @param context The method context * @param trap The thrown object or arrow we're moving on * @param victim The object moving over this one * @param originator The object that caused the move_on event * @return METHOD_OK */ method_ret common_projectile_move_on(ob_methods *context, object *trap, object *victim, object *originator) { if (common_pre_ob_move_on(trap, victim, originator) == METHOD_ERROR) return METHOD_OK; if (trap->inv == NULL) { common_post_ob_move_on(trap, victim, originator); return METHOD_OK; } /* bad bug: monster throw a object, make a step forwards, step on object , * trigger this here and get hit by own missile - and will be own enemy. * Victim then is his own enemy and will start to kill herself (this is * removed) but we have not synced victim and his missile. To avoid senseless * action, we avoid hits here */ if ((QUERY_FLAG(victim, FLAG_ALIVE) && trap->speed) && trap->owner != victim) hit_with_arrow(trap, victim); common_post_ob_move_on(trap, victim, originator); return METHOD_OK; }