diff --git a/Source/path.cpp b/Source/path.cpp index 5a22d6a46..66e499081 100644 --- a/Source/path.cpp +++ b/Source/path.cpp @@ -2,417 +2,339 @@ #include "../types.h" +// preallocated nodes, all searches are terminated after visiting 300 nodes PATHNODE path_nodes[300]; -int gdwCurPathStep; -int pnode_vals[26]; +// the number of in-use nodes in path_nodes +int num_path_nodes; +// all visited nodes PATHNODE *pnode_ptr; +// a stack for recursively processing nodes PATHNODE *pnode_tblptr[300]; -PATHNODE path_2_nodes[300]; +// the stack size +int gdwCurPathStep; +// a linked list of nodes: the A* frontier sorted by distance, so we can pop off the front to keep searching +PATHNODE* frontier_ptr; + +// Diablo is on a square grid, so you can move in at most 8 different directions +#define NUM_DIRS 8 -char pathxdir[8] = { -1, -1, 1, 1, -1, 0, 1, 0 }; -char pathydir[8] = { -1, 1, -1, 1, 0, -1, 0, 1 }; +// TODO doesn't need to be global, used for converting the linked list of nodes to an array of directions +int pnode_vals[25]; + +// these are for iterating through the 8 adjacent directions +char pathxdir[NUM_DIRS] = { -1, -1, 1, 1, -1, 0, 1, 0 }; +char pathydir[NUM_DIRS] = { -1, 1, -1, 1, 0, -1, 0, 1 }; /* rdata */ char path_directions[9] = { 5, 1, 6, 2, 0, 3, 8, 4, 7 }; int __fastcall FindPath(bool (__fastcall *PosOk)(int, int, int), int PosOkArg, int sx, int sy, int dx, int dy, char *path) { - PATHNODE *v8; // esi - char v9; // al - PATHNODE *v11; // eax - int result; // eax - PATHNODE *v13; // edx - int v14; // eax - int v15; // edi - bool v16; // zf - int *v17; // ecx - char v18; // dl - - pnode_vals[0] = 0; - *(_DWORD *)&path_2_nodes[0].f = (unsigned int)path_new_step(); - gdwCurPathStep = 0; + num_path_nodes = 0; // empty the preallocated path nodes + gdwCurPathStep = 0; // empty the pnode_tableptr stack + // create root nodes for the frontier/visited lists + frontier_ptr = path_new_step(); pnode_ptr = path_new_step(); - v8 = path_new_step(); - v8->g = 0; - v9 = path_get_h_cost(sx, sy, dx, dy); - v8->h = v9; - v8->x = sx; - v8->f = v9 + v8->g; - v8->y = sy; - *(_DWORD *)(*(_DWORD *)&path_2_nodes[0].f + 48) = (unsigned int)v8; - while ( 1 ) + + // create a node for the path's starting position + PATHNODE* path_start = path_new_step(); + path_start->x = sx; + path_start->y = sy; + path_start->g = 0; + path_start->h = path_get_h_cost(sx, sy, dx, dy); + path_start->f = path_start->g + path_start->h; + // it is our frontier + frontier_ptr->NextNode = path_start; + + // find the shortest path + PATHNODE *current; + while ( true ) { - v11 = GetNextPath(); - if ( !v11 ) - return 0; - if ( v11->x == dx && v11->y == dy ) - break; - if ( !path_get_path(PosOk, PosOkArg, v11, dx, dy) ) - return 0; + // get the next frontier node + current = GetNextPath(); + // frontier is empty, no path + if ( !current ) return 0; + // we've reached our destination, we're done + if ( current->x == dx && current->y == dy ) break; + // we ran out of preallocated nodes while searching, no path + if ( !path_get_path(PosOk, PosOkArg, current, dx, dy) ) return 0; } - v13 = v11; - v14 = (int)&v11->Parent; - v15 = 0; - if ( *(_DWORD *)v14 ) + + PATHNODE* destination = current; + PATHNODE* previous = destination->Parent; + + // walk backwards from end to start, reconstructing the movement directions + int num_steps = 0; + while ( true ) { - while ( 1 ) - { - v16 = v15 == 25; - if ( v15 >= 25 ) - break; - pnode_vals[++v15] = path_directions[3 * (v13->y - *(_DWORD *)(*(_DWORD *)v14 + 8)) - - *(_DWORD *)(*(_DWORD *)v14 + 4) - + 4 - + v13->x]; - v13 = *(PATHNODE **)v14; - v14 = *(_DWORD *)v14 + 12; - if ( !*(_DWORD *)v14 ) - { - v16 = v15 == 25; - break; - } - } - if ( v16 ) - return 0; + // TODO we *can* fit a path of 25 steps, but someone decided that 24 was the cutoff + if ( num_steps >= 25 ) return 0; + if ( !previous ) break; + pnode_vals[num_steps++] = path_directions[3 * (destination->y - previous->y + 1) + (destination->x - previous->x + 1)]; + destination = previous; + previous = destination->Parent; } - result = 0; - if ( v15 > 0 ) + + // reverse the path to go from start to end + int result; + for (result = 0; num_steps > 0; ++result) { - v17 = &pnode_vals[v15]; - do - { - v18 = *(_BYTE *)v17; - --v17; - path[result++] = v18; - } - while ( result < v15 ); + path[result] = pnode_vals[--num_steps]; } return result; } +/* heuristic, estimated cost from (sx,sy) to (dx,dy) */ int __fastcall path_get_h_cost(int sx, int sy, int dx, int dy) { - int v4; // esi - int v5; // edi - int v6; // eax - int v7; // ecx - - v4 = sy; - v5 = abs(sx - dx); - v6 = abs(v4 - dy); - v7 = v5; - if ( v5 >= v6 ) - { - v7 = v6; - if ( v5 > v6 ) - v6 = v5; - } - return 2 * (v7 + v6); + int delta_x = abs(sx - dx); + int delta_y = abs(sy - dy); + // see path_check_equal for why this is times 2 + return 2 * (delta_x + delta_y); } +/* return 2 if pPath is horizontally/vertically aligned with (dx, dy), else 3. + * + * This is to approximate that diagonal movement should have a cost of sqrt(2). + * That's approx. 1.5, so they multiply all step costs by 2 except diagonals + * which are multiplied by 3 + */ int __fastcall path_check_equal(PATHNODE *pPath, int dx, int dy) { - int v4; // [esp-4h] [ebp-4h] - - if ( pPath->x == dx || pPath->y == dy ) - v4 = 2; - else - v4 = 3; - return v4; + return ( pPath->x == dx || pPath->y == dy ) ? 2 : 3; } +/* if the frontier is not empty, remove the first node (shortest distance), + * insert it at the head of pnode's list, and return it + */ PATHNODE *__cdecl GetNextPath() { - PATHNODE *result; // eax - - result = *(PATHNODE **)(*(_DWORD *)&path_2_nodes[0].f + 48); + PATHNODE* result = frontier_ptr->NextNode; if ( result ) { - *(_DWORD *)(*(_DWORD *)&path_2_nodes[0].f + 48) = (unsigned int)result->NextNode; + frontier_ptr->NextNode = result->NextNode; result->NextNode = pnode_ptr->NextNode; pnode_ptr->NextNode = result; } return result; } +/* Make sure the path isn't cutting a corner. If you want to move from + * A to B, both Xs need to be clear: + * + * XA + * BX + * + * return true if the path is allowed. + */ bool __fastcall path_solid_pieces(PATHNODE *pPath, int dx, int dy) { - bool result; // eax - int dir; // ecx - int v8; // ecx - int v10; // edx - - result = 1; - dir = path_directions[3 * (dy - pPath->y) - pPath->x + 4 + dx] - 5; - if ( !dir ) - { - result = 0; - if ( nSolidTable[dPiece[dx][dy + 1]] ) - return result; - v8 = dPiece[dx + 1][dy]; - goto LABEL_13; - } - dir--; - if ( !dir ) - { - v10 = dPiece[dx][dy + 1]; - goto LABEL_9; - } - dir--; - if ( !dir ) - { - v10 = dPiece[dx][dy-1]; /* check */ -LABEL_9: - result = 0; - if ( nSolidTable[v10] ) - return result; - v8 = dPiece[dx-4][dy]; /* check */ - goto LABEL_13; - } - if ( dir == 1 ) - { - result = 0; - if ( !nSolidTable[dPiece[dx + 1][dy]] ) - { - v8 = dPiece[dx][dy-1]; /* check */ -LABEL_13: - if ( nSolidTable[v8] == result ) - result = 1; - return result; - } - } - return result; + if (path_check_equal(pPath, dx, dy) == 2) return 1; + return !(nSolidTable[dPiece[dx][pPath->y]] || nSolidTable[dPiece[pPath->x][dy]]); } +/* Extend pPath towards (x,y) by expanding in every possible direction i.e. a + * single step of A* breadth-first search. + * + * return 0 if we're out of preallocated nodes to use, else 1 + */ int __fastcall path_get_path(bool (__fastcall *PosOk)(int, int, int), int PosOkArg, PATHNODE *pPath, int x, int y) { - int v5; // eax - int dx; // esi - int dy; // edi - int i; // [esp+14h] [ebp-4h] - - v5 = 0; - for ( i = 0; ; v5 = i ) + // try moving every direction from the path's current end + for (int i = 0; i < NUM_DIRS; ++i) { - dx = pPath->x + pathxdir[v5]; - dy = pPath->y + pathydir[v5]; - if ( !PosOk(PosOkArg, dx, dy) ) - break; - if ( path_solid_pieces(pPath, dx, dy) ) - goto LABEL_8; -LABEL_9: - if ( ++i >= 8 ) - return 1; + int dx = pPath->x + pathxdir[i]; + int dy = pPath->y + pathydir[i]; + + // if position is OK + if ( PosOk(PosOkArg, dx, dy) ) + { + // check that we aren't cutting a corner + if ( !path_solid_pieces(pPath, dx, dy) ) continue; + } + else + { + /* if (dx,dy) is where we want to go we don't care that it's not OK + * TODO could probably safely combine this whole thing into one check: + * if (((ok && solid) || equal) && !parent) return 0 + */ + if ( dx != x || dy != y ) continue; + } + + /* This direction could work, extend pPath in that direction. + * This only fails if we're out of path nodes to use, so abort + * in that case + */ + if ( !path_parent_path(pPath, dx, dy, x, y) ) return 0; } - if ( dx != x || dy != y ) - goto LABEL_9; -LABEL_8: - if ( path_parent_path(pPath, dx, dy, x, y) ) - goto LABEL_9; - return 0; + + // successfully expanded in every possible direction + return 1; } +/* add a step from pPath to (dx,dy), return 1 if successful, update the + * frontier/visited nodes accordingly + */ int __fastcall path_parent_path(PATHNODE *pPath, int dx, int dy, int sx, int sy) { - PATHNODE *v5; // edi - int v6; // ebx - PATHNODE *v7; // esi - signed int v8; // eax - struct PATHNODE **v9; // ecx - char v10; // al - PATHNODE *v11; // esi - signed int v12; // eax - struct PATHNODE **v13; // ecx - char v14; // al - PATHNODE *result; // eax - PATHNODE *v16; // esi - char v17; // al - signed int v18; // ecx - struct PATHNODE **v19; // eax - int a1; // [esp+Ch] [ebp-4h] - - a1 = dx; - v5 = pPath; - v6 = pPath->g + path_check_equal(pPath, dx, dy); - v7 = path_get_node1(a1, dy); - if ( v7 ) + PATHNODE *dxdy_node; + int empty_slot; + + // current path cost plus next step to get to (dx,dy) + int g_next = pPath->g + path_check_equal(pPath, dx, dy); + + /* if (dx,dy) is on the frontier it's easy: just update that one node + * because we haven't explored from it yet + */ + if ( dxdy_node = path_get_node1(dx, dy) ) { - v8 = 0; - v9 = v5->Child; - do - { - if ( !*v9 ) - break; - ++v8; - ++v9; - } - while ( v8 < 8 ); - v5->Child[v8] = v7; - if ( v6 < v7->g ) + for (empty_slot = 0; empty_slot < NUM_DIRS && pPath->Child[empty_slot]; ++empty_slot); + pPath->Child[empty_slot] = dxdy_node; + + // it is a shortcut + if ( g_next < dxdy_node->g && path_solid_pieces(pPath, dx, dy) ) { - if ( path_solid_pieces(v5, a1, dy) ) - { - v10 = v7->h; - v7->Parent = v5; - v7->g = v6; - v7->f = v6 + v10; - } + // update the path/cost for getting there + dxdy_node->Parent = pPath; + dxdy_node->g = g_next; + dxdy_node->f = dxdy_node->g + dxdy_node->h; } + + return 1; } - else + + /* if (dx,dy) has already been visited, it's hard: we need to + * recursively update nodes starting from (dx,dy) because we might have + * found a new shortcut + */ + if ( dxdy_node = path_get_node2(dx, dy) ) { - v11 = path_get_node2(a1, dy); - if ( v11 ) - { - v12 = 0; - v13 = v5->Child; - do - { - if ( !*v13 ) - break; - ++v12; - ++v13; - } - while ( v12 < 8 ); - v5->Child[v12] = v11; - if ( v6 < v11->g && path_solid_pieces(v5, a1, dy) ) - { - v14 = v6 + v11->h; - v11->Parent = v5; - v11->g = v6; - v11->f = v14; - path_set_coords(v11); - } - } - else + for (empty_slot = 0; empty_slot < NUM_DIRS && pPath->Child[empty_slot]; ++empty_slot); + pPath->Child[empty_slot] = dxdy_node; + + // it is a shortcut + if ( g_next < dxdy_node->g && path_solid_pieces(pPath, dx, dy) ) { - result = path_new_step(); - v16 = result; - if ( !result ) - return 0; - result->Parent = v5; - result->g = v6; - v17 = path_get_h_cost(a1, dy, sx, sy); - v16->h = v17; - v16->f = v6 + v17; - v16->x = a1; - v16->y = dy; - path_next_node(v16); - v18 = 0; - v19 = v5->Child; - do - { - if ( !*v19 ) - break; - ++v18; - ++v19; - } - while ( v18 < 8 ); - v5->Child[v18] = v16; + dxdy_node->Parent = pPath; + dxdy_node->g = g_next; + dxdy_node->f = dxdy_node->g + dxdy_node->h; + // update all of this node's neighbors + path_set_coords(dxdy_node); } + + return 1; } + + // else just get a new node + dxdy_node = path_new_step(); + if ( !dxdy_node ) return 0; + + dxdy_node->Parent = pPath; + dxdy_node->g = g_next; + dxdy_node->h = path_get_h_cost(dx, dy, sx, sy); + dxdy_node->f = dxdy_node->g + dxdy_node->h; + dxdy_node->x = dx; + dxdy_node->y = dy; + // and add it to the frontier + path_next_node(dxdy_node); + + for (empty_slot = 0; empty_slot < NUM_DIRS && pPath->Child[empty_slot]; ++empty_slot); + pPath->Child[empty_slot] = dxdy_node; + return 1; } +// return a node for (dx,dy) on the frontier, or NULL if not found PATHNODE *__fastcall path_get_node1(int dx, int dy) { - PATHNODE *result; // eax - - result = *(PATHNODE **)&path_2_nodes[0].f; - do - result = result->NextNode; - while ( result && (result->x != dx || result->y != dy) ); - return result; + for (PATHNODE* result = frontier_ptr->NextNode; result; result = result->NextNode ) + { + if ( result->x == dx && result->y == dy ) + { + return result; + } + } + return NULL; } +// return an already visited node for (dx,dy), or NULL if not found PATHNODE *__fastcall path_get_node2(int dx, int dy) { - PATHNODE *result; // eax - - result = pnode_ptr; - do - result = result->NextNode; - while ( result && (result->x != dx || result->y != dy) ); - return result; + for (PATHNODE* result = pnode_ptr->NextNode; result; result = result->NextNode ) + { + if ( result->x == dx && result->y == dy ) + { + return result; + } + } + return NULL; } +/* Insert pPath into the frontier list such that the total path costs are in + * ascending order + */ void __fastcall path_next_node(PATHNODE *pPath) { - PATHNODE *v1; // edx - PATHNODE *v2; // eax - - v1 = *(PATHNODE **)&path_2_nodes[0].f; - v2 = *(PATHNODE **)(*(_DWORD *)&path_2_nodes[0].f + 48); - if ( v2 ) + PATHNODE* current = frontier_ptr; + PATHNODE* next = frontier_ptr->NextNode; + if ( next ) { - do + while ( next && next->f < pPath->f ) { - if ( v2->f >= pPath->f ) - break; - v1 = v2; - v2 = v2->NextNode; + current = next; + next = current->NextNode; } - while ( v2 ); - pPath->NextNode = v2; + pPath->NextNode = next; } - v1->NextNode = pPath; + current->NextNode = pPath; } +/* update all path costs using depth-first search starting at pPath */ void __fastcall path_set_coords(PATHNODE *pPath) { - PATHNODE *PathOld; // edi - PATHNODE *PathAct; // esi - char v6; // al - int i; // [esp+0h] [ebp-8h] - PATHNODE **v9; // [esp+4h] [ebp-4h] - path_push_active_step(pPath); + // while stack is nonempty while ( gdwCurPathStep ) { - PathOld = path_pop_active_step(); - v9 = PathOld->Child; - for(i = 0; i < 8; i++) + PATHNODE* PathOld = path_pop_active_step(); + // iterate over next node's children + PATHNODE* PathAct; + for (int i = 0; i < NUM_DIRS && (PathAct = PathOld->Child[i]); ++i) { - PathAct = *v9; - if ( !*v9 ) - break; - - if ( PathOld->g + path_check_equal(PathOld, PathAct->x, PathAct->y) < PathAct->g ) + short g_next = PathOld->g + path_check_equal(PathOld, PathAct->x, PathAct->y); + // if we found a faster path to the child + if ( g_next < PathAct->g && path_solid_pieces(PathOld, PathAct->x, PathAct->y) ) { - if ( path_solid_pieces(PathOld, PathAct->x, PathAct->y) ) - { - PathAct->Parent = PathOld; - v6 = PathOld->g + path_check_equal(PathOld, PathAct->x, PathAct->y); - PathAct->g = v6; - PathAct->f = v6 + PathAct->h; - path_push_active_step(PathAct); - } + // update it + PathAct->Parent = PathOld; + PathAct->g = g_next; + PathAct->f = g_next + PathAct->h; + // add it to the stack + path_push_active_step(PathAct); } - ++v9; } } } +/* push pPath onto the pnode_tblptr stack */ void __fastcall path_push_active_step(PATHNODE *pPath) { - int v1; // eax - - v1 = gdwCurPathStep++; - pnode_tblptr[v1] = pPath; + pnode_tblptr[gdwCurPathStep++] = pPath; } +/* pop and return a node from the pnode_tblptr stack */ PATHNODE *__cdecl path_pop_active_step() { return pnode_tblptr[--gdwCurPathStep]; } +/* zero one of the 300 preallocated path nodes and return a pointer to it, or + * NULL if none are available + */ PATHNODE *__cdecl path_new_step() { - PATHNODE *v1; // esi - - if ( pnode_vals[0] == 300 ) - return 0; - v1 = &path_nodes[pnode_vals[0]++]; - memset(v1, 0, 0x34u); - return v1; + if ( num_path_nodes == 300 ) return NULL; + PATHNODE* new_node = &path_nodes[num_path_nodes++]; + memset(new_node, 0, sizeof(struct PATHNODE)); + return new_node; } diff --git a/Source/path.h b/Source/path.h index 92fb21211..fab5e8aa8 100644 --- a/Source/path.h +++ b/Source/path.h @@ -4,10 +4,11 @@ extern PATHNODE path_nodes[300]; extern int gdwCurPathStep; -extern int pnode_vals[26]; +extern int num_path_nodes; +extern int pnode_vals[25]; extern PATHNODE *pnode_ptr; extern PATHNODE *pnode_tblptr[300]; -extern PATHNODE path_2_nodes[300]; +extern PATHNODE *frontier_ptr; int __fastcall FindPath(bool (__fastcall *PosOk)(int, int, int), int PosOkArg, int sx, int sy, int dx, int dy, char *path); int __fastcall path_get_h_cost(int sx, int sy, int dx, int dy);