00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019 #include "pent_include.h"
00020 #include "Pathfinder.h"
00021 #include "Actor.h"
00022 #include "AnimationTracker.h"
00023 #include "SDL_timer.h"
00024 #include <cmath>
00025
00026 #include "RenderSurface.h"
00027 #include "GameMapGump.h"
00028 #include "GUIApp.h"
00029
00030 #ifdef DEBUG
00031 ObjId Pathfinder::visualdebug_actor = 0xFFFF;
00032 #endif
00033
00034 struct PathNode
00035 {
00036 PathfindingState state;
00037 unsigned int depth;
00038 unsigned int cost;
00039 unsigned int heuristicTotalCost;
00040 PathNode* parent;
00041 uint32 stepsfromparent;
00042 };
00043
00044
00045 static unsigned int expandednodes = 0;
00046
00047 void PathfindingState::load(Actor* actor)
00048 {
00049 actor->getLocation(x, y, z);
00050 lastanim = actor->getLastAnim();
00051 direction = actor->getDir();
00052 firststep = (actor->getActorFlags() & Actor::ACT_FIRSTSTEP) != 0;
00053 flipped = (actor->getFlags() & Item::FLG_FLIPPED) != 0;
00054 combat = actor->isInCombat();
00055 }
00056
00057 bool PathfindingState::checkPoint(sint32 x_, sint32 y_, sint32 z_,
00058 int range)
00059 {
00060 int distance = (x - x_) * (x - x_) + (y - y_) * (y - y_) + (z - z_) * (z - z_);
00061 return distance < range * range;
00062 }
00063
00064 bool PathfindingState::checkItem(Item* item, int xyRange, int zRange)
00065 {
00066 sint32 itemX, itemY, itemZ;
00067 sint32 itemXd, itemYd, itemZd;
00068 sint32 itemXmin, itemYmin;
00069
00070 item->getLocationAbsolute(itemX, itemY, itemZ);
00071 item->getFootpadWorld(itemXd, itemYd, itemZd);
00072
00073 itemXmin = itemX - itemXd;
00074 itemYmin = itemY - itemYd;
00075
00076 int range = 0;
00077 if (x - itemX > range)
00078 range = x - itemX;
00079 if (itemXmin - x > range)
00080 range = itemXmin - x;
00081 if (y - itemY > range)
00082 range = y - itemY;
00083 if (itemYmin - y > range)
00084 range = itemYmin - y;
00085
00086
00087
00088 return (range <= xyRange);
00089 }
00090
00091 bool PathfindingState::checkHit(Actor* actor, Actor* target)
00092 {
00093 #if 0
00094 pout << "Trying hit in direction " << actor->getDirToItemCentre(*target) << std::endl;
00095 #endif
00096 AnimationTracker tracker;
00097 if (!tracker.init(actor, Animation::attack,
00098 actor->getDirToItemCentre(*target), this))
00099 {
00100 return false;
00101 }
00102
00103 while (tracker.step()) {
00104 if (tracker.hitSomething()) break;
00105 }
00106
00107 ObjId hit = tracker.hitSomething();
00108 if (hit == target->getObjId()) return true;
00109
00110 return false;
00111 }
00112
00113 bool PathNodeCmp::operator()(PathNode* n1, PathNode* n2)
00114 {
00115 return (n1->heuristicTotalCost > n2->heuristicTotalCost);
00116 }
00117
00118 Pathfinder::Pathfinder()
00119 {
00120 expandednodes = 0;
00121 }
00122
00123 Pathfinder::~Pathfinder()
00124 {
00125 #if 1
00126 pout << "~Pathfinder: " << nodelist.size() << " nodes, "
00127 << expandednodes << " expanded nodes in " << expandtime << "ms." << std::endl;
00128 #endif
00129
00130
00131 std::list<PathNode*>::iterator iter;
00132 for (iter = nodelist.begin(); iter != nodelist.end(); ++iter)
00133 delete *iter;
00134 nodelist.clear();
00135 }
00136
00137 void Pathfinder::init(Actor* actor_, PathfindingState* state)
00138 {
00139 actor = actor_;
00140
00141 actor->getFootpadWorld(actor_xd,actor_yd,actor_zd);
00142
00143 if (state)
00144 start = *state;
00145 else
00146 start.load(actor);
00147 }
00148
00149 void Pathfinder::setTarget(sint32 x, sint32 y, sint32 z)
00150 {
00151 targetx = x;
00152 targety = y;
00153 targetz = z;
00154 targetitem = 0;
00155 hitmode = false;
00156 }
00157
00158 void Pathfinder::setTarget(Item* item, bool hit)
00159 {
00160 targetitem = item;
00161 while (targetitem->getParentAsContainer())
00162 targetitem = targetitem->getParentAsContainer();
00163
00164
00165 item->getCentre(targetx, targety, targetz);
00166 targetz = item->getZ();
00167
00168 if (hit) {
00169 assert(start.combat);
00170 assert(p_dynamic_cast<Actor*>(targetitem));
00171 hitmode = true;
00172 } else {
00173 hitmode = false;
00174 }
00175 }
00176
00177 bool Pathfinder::canReach()
00178 {
00179 std::vector<PathfindingAction> path;
00180 return pathfind(path);
00181 }
00182
00183 bool Pathfinder::alreadyVisited(sint32 x, sint32 y, sint32 z)
00184 {
00186
00187 std::list<PathfindingState>::iterator iter;
00188
00189 for (iter = visited.begin(); iter != visited.end(); ++iter)
00190 if (iter->checkPoint(x,y,z,8))
00191 return true;
00192
00193 return false;
00194 }
00195
00196 bool Pathfinder::checkTarget(PathNode* node)
00197 {
00198
00199
00200 if (targetitem) {
00201 if (hitmode) {
00202 return node->state.checkHit(actor,
00203 p_dynamic_cast<Actor*>(targetitem));
00204 } else {
00205 return node->state.checkItem(targetitem, 32, 8);
00206 }
00207 } else {
00208 return node->state.checkPoint(targetx, targety, targetz, 48);
00209 }
00210 }
00211
00212 unsigned int Pathfinder::costHeuristic(PathNode* node)
00213 {
00214 unsigned int cost = node->cost;
00215
00216 #if 0
00217 double sqrddist;
00218
00219 sqrddist = (targetx - node->state.x + actor_xd/2) *
00220 (targetx - node->state.x + actor_xd/2);
00221 sqrddist += (targety - node->state.y + actor_yd/2) *
00222 (targety - node->state.y + actor_yd/2);
00223
00224 unsigned int dist = static_cast<unsigned int>(std::sqrt(sqrddist));
00225 #else
00226
00227
00228 int xd = abs(targetx - node->state.x + actor_xd/2);
00229 int yd = abs(targety - node->state.y + actor_yd/2);
00230 double m = (xd < yd) ? xd : yd;
00231 unsigned int dist = abs(xd-yd) + static_cast<unsigned int>(m*1.41421356);
00232
00233 #endif
00234
00235 #if 0
00237 // (using 32 for now)
00238 dist /= 32;
00239
00240 node->heuristicTotalCost = cost + (dist*4);
00241 #else
00242
00243
00244
00245 node->heuristicTotalCost = 2*cost + 3*dist;
00246 #endif
00247
00248 return node->heuristicTotalCost;
00249 }
00250
00251
00252 #ifdef DEBUG
00253
00254
00255
00256
00257 static void drawbox(Item* item)
00258 {
00259 RenderSurface* screen = GUIApp::get_instance()->getScreen();
00260 sint32 cx, cy, cz;
00261
00262 GUIApp::get_instance()->getGameMapGump()->GetCameraLocation(cx, cy, cz);
00263
00264 Pentagram::Rect d;
00265 screen->GetSurfaceDims(d);
00266
00267 sint32 ix, iy, iz;
00268 item->getLocation(ix, iy, iz);
00269
00270 sint32 xd, yd, zd;
00271 item->getFootpadWorld(xd, yd, zd);
00272
00273 ix -= cx;
00274 iy -= cy;
00275 iz -= cz;
00276
00277 sint32 x0,y0,x1,y1,x2,y2,x3,y3;
00278
00279 x0 = (d.w/2) + (ix - iy)/2;
00280 y0 = (d.h/2) + (ix + iy)/4 - iz*2;
00281
00282 x1 = (d.w/2) + (ix - iy)/2;
00283 y1 = (d.h/2) + (ix + iy)/4 - (iz+zd)*2;
00284
00285 x2 = (d.w/2) + (ix-xd - iy)/2;
00286 y2 = (d.h/2) + (ix-xd + iy)/4 - iz*2;
00287
00288 x3 = (d.w/2) + (ix - iy+yd)/2;
00289 y3 = (d.h/2) + (ix + iy-yd)/4 - iz*2;
00290
00291 screen->Fill32(0xFF0000FF, x0-1, y0-1, 3, 3);
00292
00293 screen->DrawLine32(0xFF00FF00, x0, y0, x1, y1);
00294 screen->DrawLine32(0xFF00FF00, x0, y0, x2, y2);
00295 screen->DrawLine32(0xFF00FF00, x0, y0, x3, y3);
00296 }
00297
00298 static void drawdot(sint32 x, sint32 y, sint32 z, int size, uint32 rgb)
00299 {
00300 RenderSurface* screen = GUIApp::get_instance()->getScreen();
00301 sint32 cx, cy, cz;
00302
00303 GUIApp::get_instance()->getGameMapGump()->GetCameraLocation(cx, cy, cz);
00304
00305 Pentagram::Rect d;
00306 screen->GetSurfaceDims(d);
00307 x -= cx;
00308 y -= cy;
00309 z -= cz;
00310 sint32 x0,y0;
00311 x0 = (d.w/2) + (x - y)/2;
00312 y0 = (d.h/2) + (x + y)/4 - z*2;
00313 screen->Fill32(rgb, x0-size, y0-size, 2*size+1, 2*size+1);
00314 }
00315
00316 static void drawedge(PathNode* from, PathNode* to, uint32 rgb)
00317 {
00318 RenderSurface* screen = GUIApp::get_instance()->getScreen();
00319 sint32 cx, cy, cz;
00320
00321 GUIApp::get_instance()->getGameMapGump()->GetCameraLocation(cx, cy, cz);
00322
00323 Pentagram::Rect d;
00324 screen->GetSurfaceDims(d);
00325
00326 sint32 x0, y0, x1, y1;
00327
00328 cx = from->state.x - cx;
00329 cy = from->state.y - cy;
00330 cz = from->state.z - cz;
00331
00332 x0 = (d.w/2) + (cx - cy)/2;
00333 y0 = (d.h/2) + (cx + cy)/4 - cz*2;
00334
00335 GUIApp::get_instance()->getGameMapGump()->GetCameraLocation(cx, cy, cz);
00336
00337 cx = to->state.x - cx;
00338 cy = to->state.y - cy;
00339 cz = to->state.z - cz;
00340
00341 x1 = (d.w/2) + (cx - cy)/2;
00342 y1 = (d.h/2) + (cx + cy)/4 - cz*2;
00343
00344 screen->DrawLine32(rgb, x0, y0, x1, y1);
00345 }
00346
00347 static void drawpath(PathNode* to, uint32 rgb, bool done)
00348 {
00349 PathNode* n1 = to;
00350 PathNode* n2 = to->parent;
00351
00352 while (n2) {
00353 drawedge(n1, n2, rgb);
00354
00355 if (done && n1 == to)
00356 drawdot(n1->state.x, n1->state.y, n1->state.z, 2, 0xFFFF0000);
00357 else
00358 drawdot(n1->state.x, n1->state.y, n1->state.z, 1, 0xFFFFFFFF);
00359
00360
00361 drawdot(n2->state.x, n2->state.y, n2->state.z, 2, 0xFFFFFFFF);
00362
00363 n1 = n2;
00364 n2 = n1->parent;
00365 }
00366 }
00367
00368 #endif
00369
00370 void Pathfinder::newNode(PathNode* oldnode, PathfindingState& state,
00371 unsigned int steps)
00372 {
00373 PathNode* newnode = new PathNode();
00374 nodelist.push_back(newnode);
00375 newnode->state = state;
00376 newnode->parent = oldnode;
00377 newnode->depth = oldnode->depth + 1;
00378 newnode->stepsfromparent = 0;
00379
00380 double sqrddist;
00381
00382 sqrddist = ((newnode->state.x - oldnode->state.x)*
00383 (newnode->state.x - oldnode->state.x));
00384 sqrddist += ((newnode->state.y - oldnode->state.y)*
00385 (newnode->state.y - oldnode->state.y));
00386 sqrddist += ((newnode->state.z - oldnode->state.z)*
00387 (newnode->state.z - oldnode->state.z));
00388
00389 unsigned int dist;
00390 dist = static_cast<unsigned int>(std::sqrt(sqrddist));
00391
00392 int turn = 0;
00393
00394 if (oldnode->depth > 0) {
00395 turn = state.direction - oldnode->state.direction;
00396 if (turn < 0) turn = -turn;
00397 if (turn > 4) turn = 8 - turn;
00398 }
00399
00400 newnode->cost = oldnode->cost + dist + 32*turn;
00401
00402 bool done = checkTarget(newnode);
00403 if (done)
00404 newnode->heuristicTotalCost = 0;
00405 else
00406 costHeuristic(newnode);
00407
00408 #if 0
00409 perr << "trying dir " << state.direction;
00410
00411 if (steps > 0) {
00412 perr << ", " << steps << " steps";
00413 }
00414 perr << " from ("
00415 << oldnode->state.x << "," << oldnode->state.y << ") to ("
00416 << newnode->state.x << "," << newnode->state.y
00417 << "), cost = " << newnode->cost << ", heurtotcost = "
00418 << newnode->heuristicTotalCost << std::endl;
00419 #endif
00420
00421 #ifdef DEBUG
00422 if (actor->getObjId() == visualdebug_actor) {
00423 RenderSurface* screen = GUIApp::get_instance()->getScreen();
00424 screen->BeginPainting();
00425 drawpath(newnode, 0xFFFFFF00, done);
00426 screen->EndPainting();
00427 SDL_Delay(250);
00428 if (!done) {
00429 screen->BeginPainting();
00430 drawpath(newnode, 0xFFB0B000, done);
00431 screen->EndPainting();
00432 }
00433 }
00434 #endif
00435
00436 nodes.push(newnode);
00437 }
00438
00439 void Pathfinder::expandNode(PathNode* node)
00440 {
00441 Animation::Sequence walkanim = Animation::walk;
00442 PathfindingState state, closeststate;
00443 AnimationTracker tracker;
00444 expandednodes++;
00445
00446 if (actor->isInCombat())
00447 walkanim = Animation::advance;
00448
00449
00450 for (uint32 dir = 0; dir < 8; ++dir) {
00451 state = node->state;
00452 state.lastanim = walkanim;
00453 state.direction = dir;
00454 state.combat = actor->isInCombat();
00455 uint32 steps = 0, beststeps = 0;
00456 int bestsqdist;
00457 bestsqdist = (targetx - node->state.x + actor_xd/2) *
00458 (targetx - node->state.x + actor_xd/2);
00459 bestsqdist += (targety - node->state.y + actor_yd/2) *
00460 (targety - node->state.y + actor_yd/2);
00461
00462 if (!tracker.init(actor, walkanim, dir, &state)) continue;
00463
00464
00465 sint32 max_endx, max_endy;
00466 tracker.evaluateMaxAnimTravel(max_endx, max_endy, dir);
00467 if(alreadyVisited(max_endx, max_endy, state.z)) continue;
00468 int sqrddist;
00469 int x_travel = abs(max_endx - state.x);
00470 int xy_maxtravel = x_travel;
00471 int y_travel = abs(max_endy - state.y);
00472 if(y_travel > xy_maxtravel) xy_maxtravel = y_travel;
00473
00474 sqrddist = x_travel * x_travel + y_travel * y_travel;
00475 if(sqrddist > 400)
00476 {
00477
00478 if(alreadyVisited( state.x + x_travel * 10 / xy_maxtravel,
00479 state.y + y_travel * 10 / xy_maxtravel,
00480 state.z)) {
00481 continue;
00482 }
00483 }
00484 while (tracker.step())
00485 {
00486 steps++;
00487 tracker.updateState(state);
00488
00489 int sqrddist;
00490 sqrddist = (targetx - state.x + actor_xd/2) *
00491 (targetx - state.x + actor_xd/2);
00492 sqrddist += (targety - state.y + actor_yd/2) *
00493 (targety - state.y + actor_yd/2);
00494
00495 if (sqrddist < bestsqdist) {
00496 bestsqdist = sqrddist;
00497 beststeps = steps;
00498 closeststate = state;
00499 }
00500 }
00501
00502 if (tracker.isDone()) {
00503 tracker.updateState(state);
00504 if (!alreadyVisited(state.x, state.y, state.z)) {
00505 newNode(node, state, 0);
00506 visited.push_back(state);
00507 }
00508 }
00509 else
00510 {
00511
00512
00513 visited.push_back(state);
00514 }
00515
00516
00517 if (beststeps != 0 && (beststeps != steps ||
00518 (!tracker.isDone() && targetitem)))
00519 {
00520 newNode(node, closeststate, beststeps);
00521 visited.push_back(closeststate);
00522 }
00523 }
00524 }
00525
00526 bool Pathfinder::pathfind(std::vector<PathfindingAction>& path)
00527 {
00528 #if 0
00529 pout << "Actor " << actor->getObjId();
00530
00531 if (targetitem) {
00532 pout << " pathfinding to item: ";
00533 targetitem->dumpInfo();
00534 } else {
00535 pout << " pathfinding to (" << targetx << "," << targety << "," << targetz << ")" << std::endl;
00536 }
00537 #endif
00538
00539 #ifdef DEBUG
00540 if (actor->getObjId() == visualdebug_actor) {
00541 RenderSurface* screen = GUIApp::get_instance()->getScreen();
00542 screen->BeginPainting();
00543 if (targetitem)
00544 drawbox(targetitem);
00545 else
00546 drawdot(targetx, targety, targetz, 2, 0xFF0000FF);
00547 screen->EndPainting();
00548 }
00549 #endif
00550
00551
00552 path.clear();
00553
00554 PathNode* startnode = new PathNode();
00555 startnode->state = start;
00556 startnode->cost = 0;
00557 startnode->parent = 0;
00558 startnode->depth = 0;
00559 startnode->stepsfromparent = 0;
00560 nodelist.push_back(startnode);
00561 nodes.push(startnode);
00562
00563 unsigned int expandednodes = 0;
00564 const unsigned int NODELIMIT_MIN = 30;
00565 const unsigned int NODELIMIT_MAX = 200;
00566 bool found = false;
00567 Uint32 starttime = SDL_GetTicks();
00568
00569 while (expandednodes < NODELIMIT_MAX && !nodes.empty() && !found) {
00570 PathNode* node = nodes.top(); nodes.pop();
00571
00572 #if 0
00573 pout << "Trying node: (" << node->state.x << "," << node->state.y
00574 << "," << node->state.z << ") target=(" << targetx << ","
00575 << targety << "," << targetz << ")" << std::endl;
00576 #endif
00577
00578 if (checkTarget(node)) {
00579
00580
00581
00582 PathNode* n = node;
00583 unsigned int length = 0;
00584 while (n->parent) {
00585 n = n->parent;
00586 length++;
00587 }
00588 #if 0
00589 pout << "Pathfinder: path found (length = " << length << ")"
00590 << std::endl;
00591 #endif
00592
00593 unsigned int i = length;
00594 if (length > 0) length++;
00595 path.resize(length);
00596
00597
00598 while (node->parent) {
00599 PathfindingAction action;
00600 action.action = node->state.lastanim;
00601 action.direction = node->state.direction;
00602 action.steps = node->stepsfromparent;
00603 path[--i] = action;
00604 #if 0
00605 pout << "anim = " << node->state.lastanim << ", dir = "
00606 << node->state.direction << ", steps = "
00607 << node->stepsfromparent << std::endl;
00608 #endif
00609
00610
00611
00612
00613 node = node->parent;
00614 }
00615
00616 if (length) {
00617 if (node->state.combat)
00618 path[length-1].action = Animation::combatStand;
00619 else
00620 path[length-1].action = Animation::stand;
00621 path[length-1].direction = path[length-2].direction;
00622 }
00623
00624 expandtime = SDL_GetTicks() - starttime;
00625 return true;
00626 }
00627
00628 expandNode(node);
00629 expandednodes++;
00630
00631 if(expandednodes >= NODELIMIT_MIN && ((expandednodes) % 5) == 0)
00632 {
00633 Uint32 elapsed_ms = SDL_GetTicks() - starttime;
00634 if(elapsed_ms > 350) break;
00635 }
00636 }
00637
00638 expandtime = SDL_GetTicks() - starttime;
00639
00640 #if 0
00641 static sint32 pfcalls = 0;
00642 static sint32 pftotaltime = 0;
00643 pfcalls++;
00644 pftotaltime += expandtime;
00645 pout << "maxout average = " << (pftotaltime / pfcalls) << "ms." << std::endl;
00646 #endif
00647
00648 return false;
00649 }
00650
00651
00652 #ifdef DEBUG
00653 void Pathfinder::ConCmd_visualDebug(const Console::ArgvType &argv)
00654 {
00655 if (argv.size() != 2) {
00656 pout << "Usage: Pathfinder::visualDebug objid" << std::endl;
00657 pout << "Specify objid -1 to stop tracing." << std::endl;
00658 return;
00659 }
00660 int p = strtol(argv[1].c_str(), 0, 0);
00661 if (p == -1) {
00662 visualdebug_actor = 0xFFFF;
00663 pout << "Pathfinder: stopped visual tracing" << std::endl;
00664 } else {
00665 visualdebug_actor = (uint16)p;
00666 pout << "Pathfinder: visually tracing actor " << visualdebug_actor << std::endl;
00667 }
00668 }
00669 #endif