Atrinik Server 2.5
types/waypoint.c
Go to the documentation of this file.
00001 /************************************************************************
00002 *            Atrinik, a Multiplayer Online Role Playing Game            *
00003 *                                                                       *
00004 *    Copyright (C) 2009-2011 Alex Tokar and Atrinik Development Team    *
00005 *                                                                       *
00006 * Fork from Daimonin (Massive Multiplayer Online Role Playing Game)     *
00007 * and Crossfire (Multiplayer game for X-windows).                       *
00008 *                                                                       *
00009 * This program is free software; you can redistribute it and/or modify  *
00010 * it under the terms of the GNU General Public License as published by  *
00011 * the Free Software Foundation; either version 2 of the License, or     *
00012 * (at your option) any later version.                                   *
00013 *                                                                       *
00014 * This program is distributed in the hope that it will be useful,       *
00015 * but WITHOUT ANY WARRANTY; without even the implied warranty of        *
00016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
00017 * GNU General Public License for more details.                          *
00018 *                                                                       *
00019 * You should have received a copy of the GNU General Public License     *
00020 * along with this program; if not, write to the Free Software           *
00021 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.             *
00022 *                                                                       *
00023 * The author can be reached at admin@atrinik.org                        *
00024 ************************************************************************/
00025 
00030 #include <global.h>
00031 
00036 object *get_active_waypoint(object *op)
00037 {
00038     object *wp;
00039 
00040     for (wp = op->inv; wp; wp = wp->below)
00041     {
00042         if (wp->type == WAYPOINT_OBJECT && QUERY_FLAG(wp, FLAG_CURSED))
00043         {
00044             return wp;
00045         }
00046     }
00047 
00048     return NULL;
00049 }
00050 
00055 object *get_aggro_waypoint(object *op)
00056 {
00057     object *wp;
00058 
00059     for (wp = op->inv; wp; wp = wp->below)
00060     {
00061         if (wp->type == WAYPOINT_OBJECT && QUERY_FLAG(wp, FLAG_DAMNED))
00062         {
00063             return wp;
00064         }
00065     }
00066 
00067     return NULL;
00068 }
00069 
00075 object *get_return_waypoint(object *op)
00076 {
00077     object *wp;
00078 
00079     for (wp = op->inv; wp; wp = wp->below)
00080     {
00081         if (wp->type == WAYPOINT_OBJECT && QUERY_FLAG(wp, FLAG_REFLECTING))
00082         {
00083             return wp;
00084         }
00085     }
00086 
00087     return NULL;
00088 }
00089 
00095 static object *find_waypoint(object *op, shstr *name)
00096 {
00097     object *wp;
00098 
00099     if (name == NULL)
00100     {
00101         return NULL;
00102     }
00103 
00104     for (wp = op->inv; wp; wp = wp->below)
00105     {
00106         if (wp->type == WAYPOINT_OBJECT && wp->name == name)
00107         {
00108             return wp;
00109         }
00110     }
00111 
00112     return NULL;
00113 }
00114 
00121 static mapstruct *waypoint_load_dest(object *op, object *waypoint)
00122 {
00123     mapstruct *destmap;
00124     int unique = !strncmp(op->map->path, settings.localdir, strlen(settings.localdir));
00125 
00126     /* If path is not normalized, normalize it */
00127     if (!unique && *waypoint->slaying != '/')
00128     {
00129         char temp_path[HUGE_BUF];
00130 
00131         if (*waypoint->slaying == '\0')
00132         {
00133             return NULL;
00134         }
00135 
00136         normalize_path(op->map->path, waypoint->slaying, temp_path);
00137         FREE_AND_COPY_HASH(waypoint->slaying, temp_path);
00138     }
00139 
00140     if (waypoint->slaying == op->map->path)
00141     {
00142         destmap = op->map;
00143     }
00144     else
00145     {
00146         destmap = ready_map_name(waypoint->slaying, MAP_NAME_SHARED | (unique ? MAP_PLAYER_UNIQUE : 0));
00147     }
00148 
00149     return destmap;
00150 }
00151 
00157 void waypoint_compute_path(object *waypoint)
00158 {
00159     object *op = waypoint->env;
00160     mapstruct *destmap = op->map;
00161     path_node *path;
00162 
00163     /* Store final path destination (used by aggro wp) */
00164     if (QUERY_FLAG(waypoint, FLAG_DAMNED))
00165     {
00166         if (OBJECT_VALID(waypoint->enemy, waypoint->enemy_count))
00167         {
00168             FREE_AND_COPY_HASH(waypoint->slaying, waypoint->enemy->map->path);
00169             waypoint->x = waypoint->stats.hp = waypoint->enemy->x;
00170             waypoint->y = waypoint->stats.sp = waypoint->enemy->y;
00171         }
00172         else
00173         {
00174             LOG(llevBug, "waypoint_compute_path(): Dynamic waypoint without valid target: '%s'\n", waypoint->name);
00175             return;
00176         }
00177     }
00178 
00179     if (waypoint->slaying)
00180     {
00181         destmap = waypoint_load_dest(op, waypoint);
00182     }
00183 
00184     if (!destmap)
00185     {
00186         LOG(llevBug, "waypoint_compute_path(): Invalid destination map '%s'\n", waypoint->slaying);
00187         return;
00188     }
00189 
00190     path = compress_path(find_path(op, op->map, op->x, op->y, destmap, waypoint->stats.hp, waypoint->stats.sp));
00191 
00192     if (!path)
00193     {
00194         LOG(llevBug, "waypoint_compute_path(): No path to destination ('%s' -> '%s')\n", op->name, waypoint->name);
00195         return;
00196     }
00197 
00198     /* Skip the first path element (always the starting position) */
00199     path = path->next;
00200 
00201     if (!path)
00202     {
00203         return;
00204     }
00205 
00206 #ifdef DEBUG_PATHFINDING
00207     {
00208         path_node *tmp;
00209 
00210         LOG(llevDebug, "waypoint_compute_path(): '%s' new path -> '%s': ", op->name, waypoint->name);
00211 
00212         for (tmp = path; tmp; tmp = tmp->next)
00213         {
00214             LOG(llevDebug, "(%d, %d) ", tmp->x, tmp->y);
00215         }
00216 
00217         LOG(llevDebug, "\n");
00218     }
00219 #endif
00220 
00221     /* Textually encoded path */
00222     FREE_AND_CLEAR_HASH(waypoint->msg);
00223     /* Map file for last local path step */
00224     FREE_AND_CLEAR_HASH(waypoint->race);
00225 
00226     waypoint->msg = encode_path(path);
00227     /* Path offset */
00228     waypoint->stats.food = 0;
00229     /* Msg boundary */
00230     waypoint->attacked_by_distance = strlen(waypoint->msg);
00231 
00232     /* Number of fails */
00233     waypoint->stats.Int = 0;
00234     /* Number of fails */
00235     waypoint->stats.Str = 0;
00236     /* Best distance */
00237     waypoint->stats.dam = 30000;
00238     CLEAR_FLAG(waypoint, FLAG_CONFUSED);
00239 }
00240 
00245 void waypoint_move(object *op, object *waypoint)
00246 {
00247     mapstruct *destmap = op->map;
00248     rv_vector local_rv, global_rv, *dest_rv;
00249     int dir;
00250     sint16 new_offset = 0;
00251 
00252     if (!waypoint || !op || !op->map)
00253     {
00254         return;
00255     }
00256 
00257     /* Aggro or static waypoint? */
00258     if (QUERY_FLAG(waypoint, FLAG_DAMNED))
00259     {
00260         /* Verify enemy */
00261         if (waypoint->enemy == op->enemy && waypoint->enemy_count == op->enemy_count && OBJECT_VALID(waypoint->enemy, waypoint->enemy_count))
00262         {
00263             destmap = waypoint->enemy->map;
00264             waypoint->stats.hp = waypoint->enemy->x;
00265             waypoint->stats.sp = waypoint->enemy->y;
00266         }
00267         else
00268         {
00269             /* Owner has either switched or lost enemy. This should work for both cases.
00270              * switched -> similar to if target moved
00271              * lost -> we shouldn't be called again without new data */
00272             waypoint->enemy = op->enemy;
00273             waypoint->enemy_count = op->enemy_count;
00274             return;
00275         }
00276     }
00277     else if (waypoint->slaying)
00278     {
00279         destmap = waypoint_load_dest(op, waypoint);
00280     }
00281 
00282     if (!destmap)
00283     {
00284         LOG(llevBug, "waypoint_move(): Invalid destination map '%s' for '%s' -> '%s'\n", waypoint->slaying, op->name, waypoint->name);
00285         return;
00286     }
00287 
00288     if (!get_rangevector_from_mapcoords(op->map, op->x, op->y, destmap, waypoint->stats.hp, waypoint->stats.sp, &global_rv, RV_RECURSIVE_SEARCH | RV_DIAGONAL_DISTANCE))
00289     {
00290         /* Disable this waypoint */
00291         CLEAR_FLAG(waypoint, FLAG_CURSED);
00292         return;
00293     }
00294 
00295     dest_rv = &global_rv;
00296 
00297     /* Reached the final destination? */
00298     if ((int) global_rv.distance <= waypoint->stats.grace)
00299     {
00300         object *nextwp = NULL;
00301 
00302         /* Just arrived? */
00303         if (waypoint->stats.ac == 0)
00304         {
00305 #ifdef DEBUG_PATHFINDING
00306             LOG(llevDebug, "move_waypoint(): '%s' reached destination '%s'\n", op->name, waypoint->name);
00307 #endif
00308 
00309             /* Trigger the TRIGGER event */
00310             if (trigger_event(EVENT_TRIGGER, op, waypoint, NULL, NULL, 0, 0, 0, SCRIPT_FIX_NOTHING))
00311             {
00312                 return;
00313             }
00314         }
00315 
00316         /* Waiting at this waypoint? */
00317         if (waypoint->stats.ac < waypoint->stats.wc)
00318         {
00319             waypoint->stats.ac++;
00320             return;
00321         }
00322 
00323         /* Clear timer */
00324         waypoint->stats.ac = 0;
00325         /* Set as inactive */
00326         CLEAR_FLAG(waypoint, FLAG_CURSED);
00327         /* Remove precomputed path */
00328         FREE_AND_CLEAR_HASH(waypoint->msg);
00329         /* Remove precomputed path data */
00330         FREE_AND_CLEAR_HASH(waypoint->race);
00331 
00332         /* Is it a return-home waypoint? */
00333         if (QUERY_FLAG(waypoint, FLAG_REFLECTING))
00334         {
00335             op->move_type = waypoint->move_type;
00336         }
00337 
00338         /* Start over with the new waypoint, if any */
00339         if (!QUERY_FLAG(waypoint, FLAG_DAMNED))
00340         {
00341             nextwp = find_waypoint(op, waypoint->title);
00342 
00343             if (nextwp)
00344             {
00345 #ifdef DEBUG_PATHFINDING
00346                 LOG(llevDebug, "waypoint_move(): '%s' next waypoint: '%s'\n", op->name, waypoint->title);
00347 #endif
00348                 SET_FLAG(nextwp, FLAG_CURSED);
00349                 waypoint_move(op, get_active_waypoint(op));
00350             }
00351 #ifdef DEBUG_PATHFINDING
00352             else
00353             {
00354                 LOG(llevDebug, "waypoint_move(): '%s' is missing next waypoint.\n", op->name);
00355             }
00356 #endif
00357         }
00358 
00359         waypoint->enemy = NULL;
00360         return;
00361     }
00362 
00363     if (HAS_EVENT(waypoint, EVENT_CLOSE) && trigger_event(EVENT_CLOSE, op, waypoint, NULL, NULL, 0, 0, 0, SCRIPT_FIX_NOTHING))
00364     {
00365         return;
00366     }
00367 
00368     /* Handle precomputed paths */
00369 
00370     /* If we finished our current path, clear it so that we can get a new one. */
00371     if (waypoint->msg && (waypoint->msg[waypoint->stats.food] == '\0' || global_rv.distance <= 0))
00372     {
00373         FREE_AND_CLEAR_HASH(waypoint->msg);
00374     }
00375 
00376     /* Get new path if target has moved much since the path was created */
00377     if (QUERY_FLAG(waypoint, FLAG_DAMNED) && waypoint->msg && (waypoint->stats.hp != waypoint->x || waypoint->stats.sp != waypoint->y))
00378     {
00379         rv_vector rv;
00380         mapstruct *path_destmap = ready_map_name(waypoint->slaying, MAP_NAME_SHARED);
00381 
00382         get_rangevector_from_mapcoords(destmap, waypoint->stats.hp, waypoint->stats.sp, path_destmap, waypoint->x, waypoint->y, &rv, RV_DIAGONAL_DISTANCE);
00383 
00384         if (rv.distance > 1 && rv.distance > global_rv.distance)
00385         {
00386 #ifdef DEBUG_PATHFINDING
00387             LOG(llevDebug, "waypoint_move(): Path distance = %d for '%s' -> '%s'. Discarding old path.\n", rv.distance, op->name, op->enemy->name);
00388 #endif
00389             FREE_AND_CLEAR_HASH(waypoint->msg);
00390         }
00391     }
00392 
00393     /* Are we far enough from the target to require a path? */
00394     if (global_rv.distance > 1)
00395     {
00396         if (!waypoint->msg)
00397         {
00398             /* Request a path if we don't have one */
00399             request_new_path(waypoint);
00400         }
00401         else
00402         {
00403             /* If we have precalculated path, take direction to next subwaypoint */
00404             int destx = waypoint->stats.hp, desty = waypoint->stats.sp;
00405 
00406             new_offset = waypoint->stats.food;
00407 
00408             if (new_offset < waypoint->attacked_by_distance && get_path_next(waypoint->msg, &new_offset, &waypoint->race, &destmap, &destx, &desty))
00409             {
00410                 get_rangevector_from_mapcoords(op->map, op->x, op->y, destmap, destx, desty, &local_rv, RV_RECURSIVE_SEARCH | RV_DIAGONAL_DISTANCE);
00411                 dest_rv = &local_rv;
00412             }
00413             else
00414             {
00415                 /* We seem to have an invalid path string or offset. */
00416                 FREE_AND_CLEAR_HASH(waypoint->msg);
00417                 request_new_path(waypoint);
00418             }
00419         }
00420     }
00421 
00422     /* Did we get closer to our goal last time? */
00423     if ((int) dest_rv->distance < waypoint->stats.dam)
00424     {
00425         waypoint->stats.dam = dest_rv->distance;
00426         /* Number of times we failed getting closer to (sub)goal */
00427         waypoint->stats.Str = 0;
00428     }
00429     else if (waypoint->stats.Str++ > 4)
00430     {
00431         /* Discard the current path, so that we can get a new one */
00432         FREE_AND_CLEAR_HASH(waypoint->msg);
00433 
00434         /* For best-effort waypoints don't try too many times. */
00435         if (QUERY_FLAG(waypoint, FLAG_NO_ATTACK) && waypoint->stats.Int++ > 10)
00436         {
00437 #ifdef DEBUG_PATHFINDING
00438             LOG(llevDebug, "Stuck with a best-effort waypoint (%s). Accepting current position\n", waypoint->name);
00439 #endif
00440             /* A bit ugly, but will work for now (we want to trigger the "reached goal" above) */
00441             waypoint->stats.hp = op->x;
00442             waypoint->stats.sp = op->y;
00443             return;
00444         }
00445     }
00446 
00447     if (global_rv.distance > 1 && !waypoint->msg && QUERY_FLAG(waypoint, FLAG_CONFUSED))
00448     {
00449 #ifdef DEBUG_PATHFINDING
00450         LOG(llevDebug, "waypoint_move(): No path found. '%s' standing still.\n", op->name);
00451 #endif
00452         return;
00453     }
00454 
00455     /* Perform the actual move */
00456     dir = dest_rv->direction;
00457 
00458     if (QUERY_FLAG(op, FLAG_CONFUSED))
00459     {
00460         dir = get_randomized_dir(dir);
00461     }
00462 
00463     if (dir && !QUERY_FLAG(op, FLAG_STAND_STILL))
00464     {
00465         /* Can the monster move directly toward waypoint? */
00466         if (!move_object(op, dir))
00467         {
00468             int diff;
00469 
00470             /* Try move around corners otherwise */
00471             for (diff = 1; diff <= 2; diff++)
00472             {
00473                 /* Try left or right first? */
00474                 int m = 1 - (RANDOM() & 2);
00475 
00476                 if (move_object(op, absdir(dir + diff * m)) || move_object(op, absdir(dir - diff * m)))
00477                 {
00478                     break;
00479                 }
00480             }
00481         }
00482 
00483         /* If we had a local destination and we got close enough to it, accept it. */
00484         if (dest_rv == &local_rv && dest_rv->distance == 1)
00485         {
00486             waypoint->stats.food = new_offset;
00487             /* Number of fails */
00488             waypoint->stats.Str = 0;
00489             /* Best distance */
00490             waypoint->stats.dam = 30000;
00491         }
00492     }
00493 }