#include "game.h" #define WALLHUG_C #include "wallhug.h" #undef ERROR #define ERROR(err) {TRACE("Wallhug error...\n"); TRACE(err); TRACE("\n"); ASSERT(0);} wallhug_waypoint wallhug_dirn_steps[4] = {{0, -1}, {1, 0}, {0, 1}, {-1, 0}}; //---------------------------------------------------------------------------- #define STEP_DIRN(waypoint, dirn) \ { \ if (dirn != WALLHUG_NORTH && \ dirn != WALLHUG_EAST && \ dirn != WALLHUG_SOUTH && \ dirn != WALLHUG_WEST) \ { \ ERROR("Step in which direction?"); \ } \ \ (waypoint).x += wallhug_dirn_steps[dirn].x; \ (waypoint).y += wallhug_dirn_steps[dirn].y; \ } #define MAX_LOOKAHEAD 4 // for line-of-sight stuff //---------------------------------------------------------------------------- static SLONG dx, dy, bresval; static ULONG y_dirn, x_dirn; static BOOL x_longer; ULONG wallhug_current_count; UBYTE wallhug_last_hugstart; UBYTE wallhug_last_handed; UBYTE wallhug_last_dirn; ULONG wallhug_last_hug_count; BOOL wallhug_looking_for_last = FALSE; //---------------------------------------------------------------------------- // set up the info for the bresenham line-draw static void bresenham_start(wallhug_waypoint start, wallhug_waypoint end) { SLONG xdiff, ydiff; xdiff = (ULONG)end.x; xdiff -= (ULONG)start.x; ydiff = (ULONG)end.y; ydiff -= (ULONG)start.y; if (abs(xdiff) > abs(ydiff)) x_longer = TRUE; else x_longer = FALSE; // work out which direction the line is, for stepping along the x or // the y. if (xdiff > 0) x_dirn = WALLHUG_EAST; else x_dirn = WALLHUG_WEST; if (ydiff > 0) y_dirn = WALLHUG_SOUTH; else y_dirn = WALLHUG_NORTH; // if the line is horizontal or vertical, just force the // bresenham stuff to always return the same direction if (start.x == end.x) x_dirn = y_dirn; if (start.y == end.y) y_dirn = x_dirn; // and do the bresenham thingies dx = abs(xdiff); dy = abs(ydiff); bresval = dx / 2 + dy / 2; } //---------------------------------------------------------------------------- // return the direction for the next step of the current bresenham draw static ULONG bresenham() { if (bresval >= dx) { bresval -= dx; return y_dirn; } bresval += dy; return x_dirn; } //---------------------------------------------------------------------------- // the hugger is in the process of hugging a wall. this steps it along. inline void wallhug_hugstep(wallhug_info *hugger) { // has this hugger failed already? if (hugger->dirn == WALLHUG_FAILED_DIRN) return; // is there a wall in front? if so, turn so that your hand is touching // it. so if you're left-handed, turn right, and vice versa. if (WALLHUG_WALL_IN_WAY(hugger->current.x, hugger->current.y, hugger->dirn)) { hugger->dirn = WALLHUG_ADDMOD4(hugger->dirn, -hugger->handed); // and write out a waypoint hugger->path.waypoints[hugger->path.length] = hugger->current; hugger->path.length++; if (hugger->path.length > WALLHUG_MAX_PTS - 1) { hugger->dirn = WALLHUG_FAILED_DIRN; return; } } else { // it's clear to step forward STEP_DIRN(hugger->current, hugger->dirn); // check if your hand will still be touching a wall one step in // the future. // define the side your hand sticks out { ULONG hugside = WALLHUG_ADDMOD4(hugger->dirn, hugger->handed); // if there isn't, then you should turn towards your hugside if (!WALLHUG_WALL_IN_WAY(hugger->current.x, hugger->current.y, hugside)) { hugger->dirn = hugside; // and write out a waypoint hugger->path.waypoints[hugger->path.length] = hugger->current; hugger->path.length++; if (hugger->path.length > WALLHUG_MAX_PTS - 1) { hugger->dirn = WALLHUG_FAILED_DIRN; return; } } } } // now check if you can release the wall and set off towards your // destination again. // first check: if the plane of the wall you're hugging is between you // and your destination, forget it. switch(WALLHUG_ADDMOD4(hugger->dirn, hugger->handed)) { case WALLHUG_NORTH: if (hugger->path.end.y < hugger->current.y) return; break; case WALLHUG_EAST: if (hugger->path.end.x > hugger->current.x) return; break; case WALLHUG_SOUTH: if (hugger->path.end.y > hugger->current.y) return; break; case WALLHUG_WEST: if (hugger->path.end.x < hugger->current.x) return; break; #if DEBUG == 1 default: ERROR("Wall being hugged is an invalid direction"); #endif } // next check: are you facing towards the destination? // i.e. if the plane of your back is between you and your destination, // forget it. switch(hugger->dirn) { case WALLHUG_NORTH: if (hugger->path.end.y > hugger->current.y) return; break; case WALLHUG_EAST: if (hugger->path.end.x < hugger->current.x) return; break; case WALLHUG_SOUTH: if (hugger->path.end.y < hugger->current.y) return; break; case WALLHUG_WEST: if (hugger->path.end.x > hugger->current.x) return; break; #if DEBUG == 1 default: ERROR("Hugger is facing an invalid direction"); #endif } // third check: are you closer to the destination than you were when // you started hugging this bit of wall? this is defined as being not // further away on either axis where there is a difference between the // start and end points. also, you've got to be not equal to the start on // the axis that has the greater difference. // also, you've got to be not beyond the destination. if (hugger->old.x <= hugger->path.end.x) { if (hugger->current.x < hugger->old.x) return; if (hugger->current.x > hugger->path.end.x) return; } if (hugger->old.x >= hugger->path.end.x) { if (hugger->current.x > hugger->old.x) return; if (hugger->current.x < hugger->path.end.x) return; } if (hugger->old.y <= hugger->path.end.y) { if (hugger->current.y < hugger->old.y) return; if (hugger->current.y > hugger->path.end.y) return; } if (hugger->old.y >= hugger->path.end.y) { if (hugger->current.y > hugger->old.y) return; if (hugger->current.y < hugger->path.end.y) return; } if (hugger->old.x == hugger->current.x && hugger->old.y == hugger->current.y) return; // otherwise, you can release the wall. hugger->dirn = WALLHUG_DONE; } //---------------------------------------------------------------------------- // true if there's a direct line-of-sight from start to end static BOOL line_of_sight(wallhug_waypoint start, wallhug_waypoint end) { wallhug_waypoint current = start; ULONG dirn; bresenham_start(start, end); while (current.x != end.x || current.y != end.y) { dirn = bresenham(); if (WALLHUG_WALL_IN_WAY(current.x, current.y, dirn)) return FALSE; STEP_DIRN(current, dirn); } return TRUE; } //---------------------------------------------------------------------------- inline static BOOL huggers_met_again(wallhug_info *huggers) { wallhug_waypoint one_ahead; if (huggers[0].dirn == WALLHUG_FAILED_DIRN || huggers[0].dirn == WALLHUG_DONE) return FALSE; one_ahead = huggers[0].current; STEP_DIRN(one_ahead, huggers[0].dirn); if (huggers[1].current.x == one_ahead.x && huggers[1].current.y == one_ahead.y && WALLHUG_ADDMOD4(huggers[0].dirn, 2) == huggers[1].dirn) { return TRUE; } else { return FALSE; } } //---------------------------------------------------------------------------- inline BOOL wallhug_add_huggers_path(wallhug_path *path, wallhug_info *successful_hugger) { ULONG c1; if (successful_hugger->path.length + path->length + 2 > WALLHUG_MAX_PTS) return 0; for (c1 = 0; c1 < successful_hugger->path.length; c1++) { path->waypoints[path->length] = successful_hugger->path.waypoints[c1]; path->length++; } path->waypoints[path->length] = successful_hugger->current; path->length++; return 1; } //---------------------------------------------------------------------------- static BOOL line_of_sight_cleanup(wallhug_path *path, ULONG first_waypoint) { BOOL deleted_waypoint, done_anything_at_all = FALSE; wallhug_waypoint start; ULONG finalised, walker; do { // finalised = the next waypoint in the finalised version of the path. // walker is the next waypoint being considered. walker = finalised = first_waypoint; if (first_waypoint == 0) start = path->start; else start = path->waypoints[first_waypoint - 1]; for (deleted_waypoint = FALSE; walker < path->length;) { ULONG lookahead; for (lookahead = MAX_LOOKAHEAD; lookahead > 0; lookahead--) { if (walker + lookahead < path->length && line_of_sight(start, path->waypoints[walker + lookahead])) { walker += lookahead; deleted_waypoint = TRUE; done_anything_at_all = TRUE; goto found_line; } } // no line of sight to later waypoints - just go for this one start = path->waypoints[finalised] = path->waypoints[walker]; finalised++; walker++; found_line:; } path->length = finalised; } while (deleted_waypoint); return done_anything_at_all; } //---------------------------------------------------------------------------- // removes redundant waypoints from an already calculated path ULONG wallhug_cleanup(wallhug_path *path, ULONG retval) { wallhug_waypoint start; ULONG finalised, walker, lookahead_done; ULONG count = 10; ULONG changed_waypoint = 0; // keep iterating the following until no further waypoints deleted. line_of_sight_stuff: line_of_sight_cleanup(path, changed_waypoint); if (!--count) { TRACE("Something bad has happened in the wallhug.c!\n"); return retval; } // another post-process. for each waypoint, navigate from the one before // to the one after, and if that new path does not include the middle // waypoint, replace the waypoint with the new path. { ULONG c1, c2; wallhug_path new_path; for (c1 = 0; c1 + 1 < path->length; c1++) { if (c1 == 0) { new_path.start = path->start; } else { new_path.start = path->waypoints[c1 - 1]; } new_path.end = path->waypoints[c1 + 1]; if (wallhug_trivial(&new_path) == WALLHUG_FAILED) continue; for (c2 = 0; c2 + 1 < new_path.length; c2++) { if (new_path.waypoints[c2].x == path->waypoints[c1].x && new_path.waypoints[c2].y == path->waypoints[c1].y) { goto non_silly_waypoint; } } // ok, the waypoint c1 is not really necessary - replace it // with the waypoints in new_path. if (new_path.length + path->length - 2 > WALLHUG_MAX_PTS) { continue; // too many waypoints } // move the remaining waypoints in the path out of the way memmove((UBYTE*)path->waypoints + c1 + new_path.length, (UBYTE*)path->waypoints + c1 + 2, (path->length - c1 - 2) * sizeof(wallhug_waypoint)); // and copy in the new waypoints memcpy((UBYTE*)path->waypoints + c1, (UBYTE*)new_path.waypoints, new_path.length * sizeof(wallhug_waypoint)); path->length = new_path.length + path->length - 2; if (c1 < MAX_LOOKAHEAD) changed_waypoint = 0; else changed_waypoint = c1 - MAX_LOOKAHEAD; goto line_of_sight_stuff; non_silly_waypoint:; } } return retval; } //---------------------------------------------------------------------------- // returns the number of steps taken ULONG wallhug_tricky(wallhug_path *path) { ULONG retval; // first get a simple answer. even if the path fails, we want to optimise // the path, because we'll return a path that gets you close. retval = wallhug_trivial(path); return wallhug_cleanup(path, retval); } //---------------------------------------------------------------------------- // carries on the path from current ULONG wallhug_continue_trivial(wallhug_path *path, wallhug_waypoint current, ULONG max_count) { wallhug_waypoint start = current; ULONG dirn; #if DEBUG == 1 if (path->end.x >= WALLHUG_WIDTH || path->end.y >= WALLHUG_HEIGHT) { ERROR("Wallhugging has been asked for an invalid path"); } #endif // boldly set off on a straight line for the destination bresenham_start(start, path->end); for (; wallhug_current_count < max_count && (current.x != path->end.x || current.y != path->end.y); wallhug_current_count++) { // get the next step of the current line dirn = bresenham(); // will this step make you crash into a wall? if (WALLHUG_WALL_IN_WAY(current.x, current.y, dirn)) { // hit a wall - must start hugging. Output a waypoint, then // set up a left-handed and a right-handed hugger if (wallhug_looking_for_last) { // also note which waypoint nr it was when you started hugging. wallhug_last_hugstart = path->length; wallhug_last_hug_count = wallhug_current_count; wallhug_last_dirn = dirn; } wallhug_info huggers[2]; path->waypoints[path->length] = current; path->length++; huggers[0].current = current; huggers[1].current = current; huggers[0].old = current; huggers[1].old = current; huggers[0].handed = -1; huggers[1].handed = 1; huggers[0].dirn = WALLHUG_ADDMOD4(dirn, 1); huggers[1].dirn = WALLHUG_ADDMOD4(dirn, -1); huggers[0].path.start = start; huggers[1].path.start = start; huggers[0].path.end = path->end; huggers[1].path.end = path->end; huggers[0].path.length = 0; huggers[1].path.length = 0; // and set them off hugging, until one succeeds or both fail. while(wallhug_current_count < max_count) { wallhug_hugstep(huggers + 0); if (huggers_met_again(huggers)) goto fail_hugging_and_return; wallhug_hugstep(huggers + 1); if (huggers_met_again(huggers)) goto fail_hugging_and_return; // check if either of the huggers have decided to let go if (huggers[0].dirn == WALLHUG_DONE) { if (!wallhug_add_huggers_path(path, &huggers[0])) goto fail_hugging_and_return; if (wallhug_looking_for_last) wallhug_last_handed = -1; // note which hand the hugger was using. break; } if (huggers[1].dirn == WALLHUG_DONE) { if (!wallhug_add_huggers_path(path, &huggers[1])) goto fail_hugging_and_return; if (wallhug_looking_for_last) wallhug_last_handed = 1; // note which hand the hugger was using. break; } // if both have failed, it's bad. if (huggers[0].dirn == WALLHUG_FAILED_DIRN && huggers[1].dirn == WALLHUG_FAILED_DIRN) { goto fail_hugging_and_return; } wallhug_current_count++; } if (wallhug_current_count == max_count) goto fail_hugging_and_return; // must restart all the shit from here. current = path->waypoints[path->length - 1]; start = current; bresenham_start(start, path->end); } else { // can still walk along the current line - just go for it. STEP_DIRN(current, dirn); } } if (path->length >= WALLHUG_MAX_PTS - 2) return WALLHUG_FAILED; if (current.x == path->end.x && current.y == path->end.y) { path->waypoints[path->length] = current; path->length++; return wallhug_current_count; } else return WALLHUG_FAILED; // extra stuff... fail_hugging_and_return: // put a waypoint on the end of the path, that's where you started // hugging. path->waypoints[path->length] = current; path->length++; return WALLHUG_FAILED; } //---------------------------------------------------------------------------- // returns the number of steps taken ULONG wallhug_trivial(wallhug_path *path) { path->length = 0; // initialise path. wallhug_current_count = 0; if (wallhug_looking_for_last) { wallhug_last_hugstart = WALLHUG_INVALID_WAYPOINT; // so we can tell later on if we never hugged. } return wallhug_continue_trivial(path, path->start, WALLHUG_MAX_COUNT); }