/*========================================== * path search (x0,y0)->(x1,y1) * wpd: path info will be written here * flag: &1 = easy path search only * cell: type of obstruction to check for *------------------------------------------*/ bool path_search(struct walkpath_data *wpd, int16 m, int16 x0, int16 y0, int16 x1, int16 y1, int flag, cell_chk cell) { register int i, j, x, y, dx, dy; struct map_data *md; struct walkpath_data s_wpd; if (wpd == NULL) wpd = &s_wpd; // use dummy output variable if (!map[m].cell) return false; md = &map[m]; #ifdef CELL_NOSTACK //Do not check starting cell as that would get you stuck. if (x0 < 0 || x0 >= md->xs || y0 < 0 || y0 >= md->ys) #else if (x0 < 0 || x0 >= md->xs || y0 < 0 || y0 >= md->ys /*|| md->getcellp(md,x0,y0,cell)*/) #endif return false; // Check destination cell if (x1 < 0 || x1 >= md->xs || y1 < 0 || y1 >= md->ys || md->getcellp(md,x1,y1,cell)) return false; if (flag&1) { // Try finding direct path to target // Direct path goes diagonally first, then in straight line. // calculate (sgn(x1-x0), sgn(y1-y0)) dx = ((dx = x1-x0)) ? ((dx<0) ? -1 : 1) : 0; dy = ((dy = y1-y0)) ? ((dy<0) ? -1 : 1) : 0; x = x0; // Current position = starting cell y = y0; i = 0; while( i < ARRAYLENGTH(wpd->path) ) { wpd->path[i] = walk_choices[-dy + 1][dx + 1]; i++; x += dx; // Advance current position y += dy; if( x == x1 ) dx = 0; // destination x reached, no longer move along x-axis if( y == y1 ) dy = 0; // destination y reached, no longer move along y-axis if( dx == 0 && dy == 0 ) break; // success if( md->getcellp(md,x,y,cell) ) break; // obstacle = failure } if( x == x1 && y == y1 ) { // easy path successful. wpd->path_len = i; wpd->path_pos = 0; return true; } return false; // easy path unsuccessful } else { // !(flag&1) // A* (A-star) pathfinding // We always use A* for finding walkpaths because it is what game client uses. // Easy pathfinding cuts corners of non-walkable cells, but client always walks around it. BHEAP_STRUCT_VAR(node_heap, open_set); // 'Open' set // FIXME: This array is too small to ensure all paths shorter than MAX_WALKPATH // can be found without node collision: calc_index(node1) = calc_index(node2). // Figure out more proper size or another way to keep track of known nodes. struct path_node tp[MAX_WALKPATH * MAX_WALKPATH]; struct path_node *current, *it; int xs = md->xs - 1; int ys = md->ys - 1; int len = 0; memset(tp, 0, sizeof(tp)); // Start node i = calc_index(x0, y0); tp[i].parent = NULL; tp[i].x = x0; tp[i].y = y0; tp[i].g_cost = 0; tp[i].f_cost = heuristic(x0, y0, x1, y1); tp[i].flag = SET_OPEN; heap_push_node(&open_set, &tp[i]); // Put start node to 'open' set for(;;) { int e = 0; // error flag // Saves allowed directions for the current cell. Diagonal directions // are only allowed if both directions around it are allowed. This is // to prevent cutting corner of nearby wall. // For example, you can only go NW from the current cell, if you can // go N *and* you can go W. Otherwise you need to walk around the // (corner of the) non-walkable cell. int allowed_dirs = 0; int g_cost; if (BHEAP_LENGTH(open_set) == 0) { BHEAP_CLEAR(open_set); return false; } current = BHEAP_PEEK(open_set); // Look for the lowest f_cost node in the 'open' set BHEAP_POP(open_set, NODE_MINTOPCMP, swap_ptr); // Remove it from 'open' set x = current->x; y = current->y; g_cost = current->g_cost; current->flag = SET_CLOSED; // Add current node to 'closed' set if (x == x1 && y == y1) { BHEAP_CLEAR(open_set); break; } if (y < ys && !md->getcellp(md, x, y+1, cell)) allowed_dirs |= DIR_NORTH; if (y > 0 && !md->getcellp(md, x, y-1, cell)) allowed_dirs |= DIR_SOUTH; if (x < xs && !md->getcellp(md, x+1, y, cell)) allowed_dirs |= DIR_EAST; if (x > 0 && !md->getcellp(md, x-1, y, cell)) allowed_dirs |= DIR_WEST; #define chk_dir(d) ((allowed_dirs & (d)) == (d)) // Process neighbors of current node // TODO: Processing order affects chosen path if there is more than one path with same cost. // In few cases path found by server will be different than path found by game client. if (chk_dir(DIR_SOUTH)) e += add_path(&open_set, tp, x, y-1, g_cost + MOVE_COST, current, heuristic(x, y-1, x1, y1)); // (x, y-1) 4 if (chk_dir(DIR_SOUTH|DIR_WEST) && !md->getcellp(md, x-1, y-1, cell)) e += add_path(&open_set, tp, x-1, y-1, g_cost + MOVE_DIAGONAL_COST, current, heuristic(x-1, y-1, x1, y1)); // (x-1, y-1) 3 if (chk_dir(DIR_WEST)) e += add_path(&open_set, tp, x-1, y, g_cost + MOVE_COST, current, heuristic(x-1, y, x1, y1)); // (x-1, y) 2 if (chk_dir(DIR_NORTH|DIR_WEST) && !md->getcellp(md, x-1, y+1, cell)) e += add_path(&open_set, tp, x-1, y+1, g_cost + MOVE_DIAGONAL_COST, current, heuristic(x-1, y+1, x1, y1)); // (x-1, y+1) 1 if (chk_dir(DIR_NORTH)) e += add_path(&open_set, tp, x, y+1, g_cost + MOVE_COST, current, heuristic(x, y+1, x1, y1)); // (x, y+1) 0 if (chk_dir(DIR_NORTH|DIR_EAST) && !md->getcellp(md, x+1, y+1, cell)) e += add_path(&open_set, tp, x+1, y+1, g_cost + MOVE_DIAGONAL_COST, current, heuristic(x+1, y+1, x1, y1)); // (x+1, y+1) 7 if (chk_dir(DIR_EAST)) e += add_path(&open_set, tp, x+1, y, g_cost + MOVE_COST, current, heuristic(x+1, y, x1, y1)); // (x+1, y) 6 if (chk_dir(DIR_SOUTH|DIR_EAST) && !md->getcellp(md, x+1, y-1, cell)) e += add_path(&open_set, tp, x+1, y-1, g_cost + MOVE_DIAGONAL_COST, current, heuristic(x+1, y-1, x1, y1)); // (x+1, y-1) 5 #undef chk_dir if (e) { BHEAP_CLEAR(open_set); return false; } } for (it = current; it->parent != NULL; it = it->parent, len++); if (len > sizeof(wpd->path)) { return false; } // Recreate path wpd->path_len = len; wpd->path_pos = 0; for (it = current, j = len-1; j >= 0; it = it->parent, j--) { dx = it->x - it->parent->x; dy = it->y - it->parent->y; wpd->path[j] = walk_choices[-dy + 1][dx + 1]; } return true; } // A* end return false; }
void do_final_path(){ BHEAP_CLEAR(g_open_set); }//