Crossfire Server, Branch 1.12  R12190
move.c
Go to the documentation of this file.
00001 /*
00002  * static char *rcsid_move_c =
00003  *    "$Id: move.c 11578 2009-02-23 22:02:27Z lalo $";
00004  */
00005 
00006 /*
00007     CrossFire, A Multiplayer game for X-windows
00008 
00009     Copyright (C) 2002-2006 Mark Wedel & Crossfire Development Team
00010     Copyright (C) 1992 Frank Tore Johansen
00011 
00012     This program is free software; you can redistribute it and/or modify
00013     it under the terms of the GNU General Public License as published by
00014     the Free Software Foundation; either version 2 of the License, or
00015     (at your option) any later version.
00016 
00017     This program is distributed in the hope that it will be useful,
00018     but WITHOUT ANY WARRANTY; without even the implied warranty of
00019     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00020     GNU General Public License for more details.
00021 
00022     You should have received a copy of the GNU General Public License
00023     along with this program; if not, write to the Free Software
00024     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
00025 
00026     The author can be reached via e-mail to crossfire-devel@real-time.com
00027 */
00028 
00034 #include <global.h>
00035 #ifndef __CEXTRACT__
00036 #include <sproto.h>
00037 #endif
00038 
00039 static int roll_ob(object *op, int dir, object *pusher);
00040 
00053 int move_object(object *op, int dir) {
00054     return move_ob(op, dir, op);
00055 }
00056 
00072 int move_ob(object *op, int dir, object *originator) {
00073     sint16 newx = op->x+freearr_x[dir];
00074     sint16 newy = op->y+freearr_y[dir];
00075     object *tmp;
00076     mapstruct *m;
00077     int mflags;
00078 
00079     if (op == NULL) {
00080         LOG(llevError, "Trying to move NULL.\n");
00081         return 0;
00082     }
00083 
00084     m = op->map;
00085     mflags = get_map_flags(m, &m, newx, newy, &newx, &newy);
00086 
00087     /* If the space the player is trying to is out of the map,
00088      * bail now - we know it can't work.
00089      */
00090     if (mflags&P_OUT_OF_MAP)
00091         return 0;
00092 
00093 
00094     /* Is this space blocked?  Players with wizpass are immune to
00095      * this condition.
00096      */
00097     if (blocked_link(op, m, newx, newy)
00098     && !QUERY_FLAG(op, FLAG_WIZPASS))
00099         return 0;
00100 
00101     /* 0.94.2 - we need to set the direction for the new animation code.
00102      * it uses it to figure out face to use - I can't see it
00103      * breaking anything, but it might.
00104      */
00105     if (op->more != NULL && !move_ob(op->more, dir, op->more->head))
00106         return 0;
00107 
00108     op->direction = dir;
00109 
00110     if (op->will_apply&WILL_APPLY_EARTHWALL)
00111         check_earthwalls(op, m, newx, newy);
00112     if (op->will_apply&WILL_APPLY_DOOR)
00113         check_doors(op, m, newx, newy);
00114 
00115     /* 0.94.1 - I got a stack trace that showed it crash with remove_ob trying
00116      * to remove a removed object, and this function was the culprit.  A possible
00117      * guess I have is that check_doors above ran into a trap, killing the
00118      * monster.
00119      *
00120      * Unfortunately, it doesn't appear that the calling functions of move_object
00121      * deal very well with op being killed, so all this might do is just
00122      * migrate the problem someplace else.
00123      */
00124 
00125     if (QUERY_FLAG(op, FLAG_REMOVED)) {
00126         LOG(llevDebug, "move_object: monster has been removed - will not process further\n");
00127         /* Was not successful, but don't want to try and move again */
00128         return 1;
00129     }
00130 
00131     /* If this is a tail portion, just want to tell caller that move is
00132      * ok - the caller will deal with actual object removal/insertion
00133      */
00134     if (op->head)
00135         return 1;
00136 
00137     remove_ob(op);
00138 
00139     /* we already have newx, newy, and m, so lets use them.
00140      * In addition, this fixes potential crashes, because multipart object was
00141      * on edge of map, +=x, +=y doesn't make correct coordinates.
00142      */
00143     for (tmp = op; tmp != NULL; tmp = tmp->more) {
00144         tmp->x += freearr_x[dir];
00145         tmp->y += freearr_y[dir];
00146         tmp->map = get_map_from_coord(tmp->map, &tmp->x, &tmp->y);
00147     }
00148 
00149     /* insert_ob_in_map will deal with any tiling issues */
00150     insert_ob_in_map(op, m, originator, 0);
00151 
00152     /* Hmmm.  Should be possible for multispace players now */
00153     if (op->type == PLAYER) {
00154         esrv_map_scroll(&op->contr->socket, freearr_x[dir], freearr_y[dir]);
00155         op->contr->socket.update_look = 1;
00156         op->contr->socket.look_position = 0;
00157     } else if (op->type == TRANSPORT) {
00158         object *pl;
00159 
00160         for (pl = op->inv; pl; pl = pl->below) {
00161             if (pl->type == PLAYER) {
00162                 pl->contr->do_los = 1;
00163                 pl->map = op->map;
00164                 pl->x = op->x;
00165                 pl->y = op->y;
00166                 esrv_map_scroll(&pl->contr->socket, freearr_x[dir], freearr_y[dir]);
00167                 pl->contr->socket.update_look = 1;
00168                 pl->contr->socket.look_position = 0;
00169             }
00170         }
00171     }
00172 
00173     return 1; /* this shouldn't be reached */
00174 }
00175 
00197 int transfer_ob(object *op, int x, int y, int randomly, object *originator) {
00198     int i;
00199     object *tmp;
00200 
00201     if (randomly)
00202         i = find_free_spot(op, op->map, x, y, 0, SIZEOFFREE);
00203     else
00204         i = find_first_free_spot(op, op->map, x, y);
00205 
00206     if (i == -1)
00207         return 0; /* No free spot */
00208 
00209     if (op->head != NULL)
00210         op = op->head;
00211     remove_ob(op);
00212     for (tmp = op; tmp != NULL; tmp = tmp->more)
00213         tmp->x = x+freearr_x[i]+(tmp->arch == NULL ? 0 : tmp->arch->clone.x),
00214         tmp->y = y+freearr_y[i]+(tmp->arch == NULL ? 0 : tmp->arch->clone.y);
00215 
00216     tmp = insert_ob_in_map(op, op->map, originator, 0);
00217     if (op && op->type == PLAYER)
00218         map_newmap_cmd(&op->contr->socket);
00219     if (tmp)
00220         return 0;
00221     else
00222         return 1;
00223 }
00224 
00244 int teleport(object *teleporter, uint8 tele_type, object *user) {
00245     object *altern[120]; /* Better use c/malloc here in the future */
00246     int i, j, k, nrofalt = 0;
00247     object *other_teleporter, *tmp;
00248     mapstruct *m;
00249     sint16 sx, sy;
00250 
00251     if (user == NULL)
00252         return 0;
00253     if (user->head != NULL)
00254         user = user->head;
00255 
00256     /* Find all other teleporters within range.  This range
00257      * should really be setable by some object attribute instead of
00258      * using hard coded values.
00259      */
00260     for (i = -5; i < 6; i++)
00261         for (j = -5; j < 6; j++) {
00262             if (i == 0 && j == 0)
00263                 continue;
00264             /* Perhaps this should be extended to support tiled maps */
00265             if (OUT_OF_REAL_MAP(teleporter->map, teleporter->x+i, teleporter->y+j))
00266                 continue;
00267             other_teleporter = GET_MAP_OB(teleporter->map, teleporter->x+i, teleporter->y+j);
00268 
00269             while (other_teleporter) {
00270                 if (other_teleporter->type == tele_type)
00271                     break;
00272                 other_teleporter = other_teleporter->above;
00273             }
00274             if (other_teleporter)
00275                 altern[nrofalt++] = other_teleporter;
00276         }
00277 
00278     if (!nrofalt) {
00279         LOG(llevError, "No alternative teleporters around!\n");
00280         return 0;
00281     }
00282 
00283     other_teleporter = altern[RANDOM()%nrofalt];
00284     k = find_free_spot(user, other_teleporter->map, other_teleporter->x, other_teleporter->y, 1, 9);
00285 
00286     /* if k==-1, unable to find a free spot.  If this is shop
00287      * mat that the player is using, find someplace to move
00288      * the player - otherwise, player can get trapped in the shops
00289      * that appear in random dungeons.  We basically just make
00290      * sure the space isn't no pass (eg wall), and don't care
00291      * about is alive.
00292      */
00293     if (k == -1) {
00294         if (tele_type == SHOP_MAT && user->type == PLAYER) {
00295             for (k = 1; k < 9; k++) {
00296                 if (get_map_flags(other_teleporter->map, &m,
00297                                   other_teleporter->x+freearr_x[k],
00298                                   other_teleporter->y+freearr_y[k], &sx, &sy)&P_OUT_OF_MAP)
00299                     continue;
00300 
00301                 if (!OB_TYPE_MOVE_BLOCK(user, GET_MAP_MOVE_BLOCK(m, sx, sy)))
00302                     break;
00303 
00304             }
00305             if (k == 9) {
00306                 LOG(llevError, "Shop mat %s (%d, %d) is in solid rock?\n", other_teleporter->name, other_teleporter->x, other_teleporter->y);
00307                 /* Teleport player on top of blocked destination: this prevents
00308                  * players from being trapped inside shops if the destination
00309                  * is blocked with earth walls.
00310                  */
00311                 k = 0;
00312             }
00313         } else
00314             return 0;
00315     }
00316 
00317     remove_ob(user);
00318 
00319     /* Update location for the object */
00320     for (tmp = user; tmp != NULL; tmp = tmp->more) {
00321         tmp->x = other_teleporter->x+freearr_x[k]+(tmp->arch == NULL ? 0 : tmp->arch->clone.x);
00322         tmp->y = other_teleporter->y+freearr_y[k]+(tmp->arch == NULL ? 0 : tmp->arch->clone.y);
00323     }
00324     tmp = insert_ob_in_map(user, other_teleporter->map, NULL, 0);
00325     if (tmp && tmp->type == PLAYER)
00326         map_newmap_cmd(&tmp->contr->socket);
00327     return (tmp == NULL);
00328 }
00329 
00340 void recursive_roll(object *op, int dir, object *pusher) {
00341     char name[MAX_BUF];
00342 
00343     query_name(op, name, MAX_BUF);
00344     if (!roll_ob(op, dir, pusher)) {
00345         draw_ext_info_format(NDI_UNIQUE, 0, pusher, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_FAILURE,
00346                              "You fail to push the %s.",
00347                              "You fail to push the %s.",
00348                              name);
00349         return;
00350     }
00351     (void)move_ob(pusher, dir, pusher);
00352     draw_ext_info_format(NDI_BLACK, 0, pusher, MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_SUCCESS,
00353                          "You move the %s.",
00354                          "You move the %s.",
00355                          name);
00356     return;
00357 }
00358 
00380 static int try_fit(object *op, mapstruct *m, int x, int y) {
00381     object *tmp, *more;
00382     sint16 tx, ty;
00383     int mflags;
00384     mapstruct *m2;
00385 
00386     if (op->head)
00387         op = op->head;
00388 
00389     for (more = op; more; more = more->more) {
00390         tx = x+more->x-op->x;
00391         ty = y+more->y-op->y;
00392 
00393         mflags = get_map_flags(m, &m2, tx, ty, &tx, &ty);
00394 
00395         if (mflags&P_OUT_OF_MAP)
00396             return 1;
00397 
00398         for (tmp = GET_MAP_OB(m2, tx, ty); tmp; tmp = tmp->above) {
00399             if (tmp->head == op || tmp == op)
00400                 continue;
00401 
00402             if ((QUERY_FLAG(tmp, FLAG_ALIVE) && tmp->type != DOOR))
00403                 return 1;
00404 
00405             if (OB_MOVE_BLOCK(op, tmp))
00406                 return 1;
00407 
00408         }
00409     }
00410     return 0;
00411 }
00412 
00432 static int roll_ob(object *op, int dir, object *pusher) {
00433     object *tmp;
00434     sint16 x, y;
00435     int flags;
00436     mapstruct *m;
00437     MoveType move_block;
00438 
00439     if (op->head)
00440         op = op->head;
00441 
00442     x = op->x+freearr_x[dir];
00443     y = op->y+freearr_y[dir];
00444 
00445     if (!QUERY_FLAG(op, FLAG_CAN_ROLL)
00446     || (op->weight && random_roll(0, op->weight/50000-1, pusher, PREFER_LOW) > pusher->stats.Str))
00447         return 0;
00448 
00449     m = op->map;
00450     flags = get_map_flags(m, &m, x, y, &x, &y);
00451 
00452     if (flags&(P_OUT_OF_MAP|P_IS_ALIVE))
00453         return 0;
00454 
00455     move_block = GET_MAP_MOVE_BLOCK(m, x, y);
00456 
00457     /* If the target space is not blocked, no need to look at the objects on it */
00458     if ((op->move_type&move_block) == op->move_type) {
00459         for (tmp = GET_MAP_OB(m, x, y); tmp != NULL; tmp = tmp->above) {
00460             if (tmp->head == op)
00461                 continue;
00462             if (OB_MOVE_BLOCK(op, tmp) && !roll_ob(tmp, dir, pusher))
00463                 return 0;
00464         }
00465     }
00466     if (try_fit(op, m, x, y))
00467         return 0;
00468 
00469     remove_ob(op);
00470     for (tmp = op; tmp != NULL; tmp = tmp->more)
00471         tmp->x += freearr_x[dir],
00472         tmp->y += freearr_y[dir];
00473     insert_ob_in_map(op, op->map, pusher, 0);
00474     return 1;
00475 }
00476 
00492 int push_ob(object *who, int dir, object *pusher) {
00493     int str1, str2;
00494     object *owner;
00495 
00496     if (who->head != NULL)
00497         who = who->head;
00498     owner = get_owner(who);
00499 
00500     /* Wake up sleeping monsters that may be pushed */
00501     CLEAR_FLAG(who, FLAG_SLEEP);
00502 
00503     /* player change place with his pets or summoned creature */
00504     /* TODO: allow multi arch pushing. Can't be very difficult */
00505     if (who->more == NULL
00506     && (owner == pusher || (owner != NULL && owner->type == PLAYER && owner->contr->party != NULL && owner->contr->party == pusher->contr->party))) {
00507         int temp;
00508         mapstruct *m;
00509 
00510         remove_ob(who);
00511         remove_ob(pusher);
00512         temp = pusher->x;
00513         pusher->x = who->x;
00514         who->x = temp;
00515 
00516         temp = pusher->y;
00517         pusher->y = who->y;
00518         who->y = temp;
00519 
00520         m = pusher->map;
00521         pusher->map = who->map;
00522         who->map = m;
00523 
00524         insert_ob_in_map(who, who->map, pusher, 0);
00525         insert_ob_in_map(pusher, pusher->map, pusher, 0);
00526 
00527         /* we presume that if the player is pushing his put, he moved in
00528          * direction 'dir'.  I can' think of any case where this would not be
00529          * the case.  Putting the map_scroll should also improve performance some.
00530          */
00531         if (pusher->type == PLAYER) {
00532             esrv_map_scroll(&pusher->contr->socket, freearr_x[dir], freearr_y[dir]);
00533             pusher->contr->socket.update_look = 1;
00534             pusher->contr->socket.look_position = 0;
00535         }
00536         return 0;
00537     }
00538 
00539     /* We want ONLY become enemy of evil, unaggressive monster. We must RUN in them */
00540     /* In original we have here a unaggressive check only - that was the reason why */
00541     /* we so often become an enemy of friendly monsters... */
00542     /* funny: was they set to unaggressive 0 (= not so nice) they don't attack */
00543     if (owner != pusher
00544     && pusher->type == PLAYER
00545     && who->type != PLAYER
00546     && !QUERY_FLAG(who, FLAG_FRIENDLY)&& !QUERY_FLAG(who, FLAG_NEUTRAL)) {
00547         if (pusher->contr->run_on) { /* only when we run */
00548             draw_ext_info_format(NDI_UNIQUE, 0, pusher,
00549                                  MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_FAILURE,
00550                                  "You start to attack %s !!",
00551                                  "You start to attack %s !!",
00552                                  who->name);
00553             CLEAR_FLAG(who, FLAG_UNAGGRESSIVE); /* the sucker don't like you anymore */
00554             who->enemy = pusher;
00555             return 1;
00556         } else {
00557             draw_ext_info_format(NDI_UNIQUE, 0, pusher,
00558                                  MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_FAILURE,
00559                                  "You avoid attacking %s.",
00560                                  "You avoid attacking %s.",
00561                                  who->name);
00562         }
00563     }
00564 
00565     /* now, lets test stand still. we NEVER can push stand_still monsters. */
00566     if (QUERY_FLAG(who, FLAG_STAND_STILL)) {
00567         draw_ext_info_format(NDI_UNIQUE, 0, pusher,
00568                              MSG_TYPE_COMMAND, MSG_TYPE_COMMAND_FAILURE,
00569                              "You can't push %s.",
00570                              "You can't push %s.",
00571                              who->name);
00572         return 0;
00573     }
00574 
00575     /* This block is basically if you are pushing friendly but
00576      * non pet creaturs.
00577      * It basically does a random strength comparision to
00578      * determine if you can push someone around.  Note that
00579      * this pushes the other person away - its not a swap.
00580      */
00581 
00582     str1 = (who->stats.Str > 0 ? who->stats.Str : who->level);
00583     str2 = (pusher->stats.Str > 0 ? pusher->stats.Str : pusher->level);
00584     if (QUERY_FLAG(who, FLAG_WIZ)
00585     || random_roll(str1, str1/2+str1*2, who, PREFER_HIGH) >= random_roll(str2, str2/2+str2*2, pusher, PREFER_HIGH)
00586     || !move_object(who, dir)) {
00587         if (who->type == PLAYER) {
00588             draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_VICTIM, MSG_TYPE_VICTIM_WAS_PUSHED,
00589                                  "%s tried to push you.",
00590                                  "%s tried to push you.",
00591                                  pusher->name);
00592         }
00593         return 0;
00594     }
00595 
00596     /* If we get here, the push succeeded.
00597      * Let everyone know the status.
00598      */
00599     if (who->type == PLAYER) {
00600         draw_ext_info_format(NDI_UNIQUE, 0, who, MSG_TYPE_VICTIM, MSG_TYPE_VICTIM_WAS_PUSHED,
00601                              "%s pushed you.",
00602                              "%s pushed you.",
00603                              pusher->name);
00604     }
00605     if (pusher->type == PLAYER) {
00606         draw_ext_info_format(NDI_UNIQUE, 0, pusher, MSG_TYPE_VICTIM, MSG_TYPE_VICTIM_WAS_PUSHED,
00607                              "You pushed %s back.",
00608                              "You pushed %s back.",
00609                              who->name);
00610     }
00611 
00612     return 1;
00613 }
00614 
00626 int move_to(object *op, int x, int y) {
00627     int direction;
00628 
00629     if (op->x == x && op->y == y)
00630         return 0;
00631 
00632     if (GET_MAP_FLAGS(op->map, x, y)&P_OUT_OF_MAP)
00633         return 2;
00634 
00635     direction = compute_path(op, GET_MAP_OB(op->map, x, y), -1);
00636     if (direction == -1)
00637         return 2;
00638 
00639     /* this shouldn't fail, as the direction computing takes into account the blocked state... */
00640     move_ob(op, direction, op);
00641     return 1;
00642 }