Skip to content

Commit

Permalink
Engine: fix legacy pathfinder imprecision in some old games
Browse files Browse the repository at this point in the history
This fixes at least one known case in "Maniac Mansion Mania 16: Meteor Family".
See #663

Solution hinted by @tag2015
  • Loading branch information
ivan-mogilko committed Apr 26, 2024
1 parent db4efc8 commit 1c22681
Showing 1 changed file with 39 additions and 22 deletions.
61 changes: 39 additions & 22 deletions Engine/ac/route_finder_impl_legacy.cpp
Expand Up @@ -49,6 +49,19 @@ static int waspossible = 1;
static int suggestx;
static int suggesty;

// Configuration for the pathfinder
struct PathfinderConfig
{
const int MaxGranularity = 3;

// Short sweep is performed in certain radius around requested destination,
// when searching for a nearest walkable area in the vicinity
const int ShortSweepRadius = 50;
int ShortSweepGranularity = 3; // variable, depending on loaded game version
// Full sweep is performed over a whole walkable area
const int FullSweepGranularity = 5;
};

void init_pathfinder()
{
pathbackx = (int *)malloc(sizeof(int) * MAXPATHBACK);
Expand Down Expand Up @@ -139,9 +152,8 @@ int find_nearest_walkable_area(Bitmap *tempw, int fromX, int fromY, int toX, int
return 0;
}

#define MAX_GRANULARITY 3
static int walk_area_granularity[MAX_WALK_AREAS];
static int is_route_possible(int fromx, int fromy, int tox, int toy, Bitmap *wss)
static int is_route_possible(int fromx, int fromy, int tox, int toy, Bitmap *wss, const PathfinderConfig &pfc)
{
wallscreen = wss;
suggestx = -1;
Expand Down Expand Up @@ -205,7 +217,7 @@ static int is_route_possible(int fromx, int fromy, int tox, int toy, Bitmap *wss
// find the average "width" of a path in this walkable area
for (dd = 1; dd < MAX_WALK_AREAS; dd++) {
if (walk_area_times[dd] == 0) {
walk_area_granularity[dd] = MAX_GRANULARITY;
walk_area_granularity[dd] = pfc.MaxGranularity;
continue;
}

Expand All @@ -215,26 +227,26 @@ static int is_route_possible(int fromx, int fromy, int tox, int toy, Bitmap *wss
else if (walk_area_granularity[dd] <= 15)
walk_area_granularity[dd] = 3;
else
walk_area_granularity[dd] = MAX_GRANULARITY;
walk_area_granularity[dd] = pfc.MaxGranularity;

#ifdef DEBUG_PATHFINDER
AGS::Common::Debug::Printf("area %d: Gran %d", dd, walk_area_granularity[dd]);
#endif
}
walk_area_granularity[0] = MAX_GRANULARITY;
walk_area_granularity[0] = pfc.MaxGranularity;

tempw->FloodFill(fromx, fromy, 232);
if (tempw->GetPixel(tox, toy) != 232)
{
// Destination pixel is not walkable
// Try the 100x100 square around the target first at 3-pixel granularity
int tryFirstX = tox - 50, tryToX = tox + 50;
int tryFirstY = toy - 50, tryToY = toy + 50;
// Try the N x N square around the target first at X-pixel granularity
int tryFirstX = tox - pfc.ShortSweepRadius, tryToX = tox + pfc.ShortSweepRadius;
int tryFirstY = toy - pfc.ShortSweepRadius, tryToY = toy + pfc.ShortSweepRadius;

if (!find_nearest_walkable_area(tempw, tryFirstX, tryFirstY, tryToX, tryToY, tox, toy, 3))
if (!find_nearest_walkable_area(tempw, tryFirstX, tryFirstY, tryToX, tryToY, tox, toy, pfc.ShortSweepGranularity))
{
// Nothing found, sweep the whole room at 5 pixel granularity
find_nearest_walkable_area(tempw, 0, 0, tempw->GetWidth(), tempw->GetHeight(), tox, toy, 5);
find_nearest_walkable_area(tempw, 0, 0, tempw->GetWidth(), tempw->GetHeight(), tox, toy, pfc.FullSweepGranularity);
}

delete tempw;
Expand Down Expand Up @@ -396,7 +408,7 @@ static void round_down_coords(int &tmpx, int &tmpy)
}
}

static int find_route_dijkstra(int fromx, int fromy, int destx, int desty)
static int find_route_dijkstra(int fromx, int fromy, int destx, int desty, const PathfinderConfig &pfc)
{
int i, j;

Expand Down Expand Up @@ -433,10 +445,10 @@ static int find_route_dijkstra(int fromx, int fromy, int destx, int desty)

int granularity = 3, newx = -1, newy, foundAnswer = -1, numreplace;
int changeiter, numfound, adjcount;
int destxlow = destx - MAX_GRANULARITY;
int destylow = desty - MAX_GRANULARITY;
int destxhi = destxlow + MAX_GRANULARITY * 2;
int destyhi = destylow + MAX_GRANULARITY * 2;
int destxlow = destx - pfc.MaxGranularity;
int destylow = desty - pfc.MaxGranularity;
int destxhi = destxlow + pfc.MaxGranularity * 2;
int destyhi = destylow + pfc.MaxGranularity * 2;
int modifier = 0;
int totalfound = 0;
int DIRECTION_BONUS = 0;
Expand Down Expand Up @@ -503,10 +515,10 @@ static int find_route_dijkstra(int fromx, int fromy, int destx, int desty)

// edges of screen pose a problem, so if current and dest are within
// certain distance of the edge, say we've got it
if ((newx >= wallscreen->GetWidth() - MAX_GRANULARITY) && (destx >= wallscreen->GetWidth() - MAX_GRANULARITY))
if ((newx >= wallscreen->GetWidth() - pfc.MaxGranularity) && (destx >= wallscreen->GetWidth() - pfc.MaxGranularity))
newx = destx;

if ((newy >= wallscreen->GetHeight() - MAX_GRANULARITY) && (desty >= wallscreen->GetHeight() - MAX_GRANULARITY))
if ((newy >= wallscreen->GetHeight() - pfc.MaxGranularity) && (desty >= wallscreen->GetHeight() - pfc.MaxGranularity))
newy = desty;

// Found the desination, abort loop
Expand Down Expand Up @@ -578,7 +590,7 @@ static int find_route_dijkstra(int fromx, int fromy, int destx, int desty)
return 1;
}

static int __find_route(int srcx, int srcy, short *tox, short *toy, int noredx)
static int __find_route(int srcx, int srcy, short *tox, short *toy, int noredx, const PathfinderConfig &pfc)
{
assert(wallscreen != nullptr);
assert(beenhere != nullptr);
Expand All @@ -599,7 +611,7 @@ static int __find_route(int srcx, int srcy, short *tox, short *toy, int noredx)
return 1;
}

if ((waspossible = is_route_possible(srcx, srcy, tox[0], toy[0], wallscreen)) == 0) {
if ((waspossible = is_route_possible(srcx, srcy, tox[0], toy[0], wallscreen, pfc)) == 0) {
if (suggestx >= 0) {
tox[0] = suggestx;
toy[0] = suggesty;
Expand All @@ -615,7 +627,7 @@ static int __find_route(int srcx, int srcy, short *tox, short *toy, int noredx)
}

// Try the new pathfinding algorithm
if (find_route_dijkstra(srcx, srcy, tox[0], toy[0])) {
if (find_route_dijkstra(srcx, srcy, tox[0], toy[0], pfc)) {
return 1;
}

Expand Down Expand Up @@ -731,6 +743,11 @@ int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int
assert(pathbackx != nullptr);
assert(pathbacky != nullptr);

// Setup pathfinder configuration, depending on the loaded game version;
// sweep granularity has changed between 3.0.0 and 3.0.1; see issue #663
PathfinderConfig pfc;
pfc.ShortSweepGranularity = (loaded_game_file_version > kGameVersion_300) ? 3 : 1;

#ifdef DEBUG_PATHFINDER
// __wnormscreen();
#endif
Expand Down Expand Up @@ -767,9 +784,9 @@ int find_route(short srcx, short srcy, short xx, short yy, int move_speed_x, int
for (aaa = 1; aaa < wallscreen->GetHeight(); aaa++)
beenhere[aaa] = beenhere[0] + aaa * (wallscreen->GetWidth());

if (__find_route(srcx, srcy, &xx, &yy, nocross) == 0) {
if (__find_route(srcx, srcy, &xx, &yy, nocross, pfc) == 0) {
leftorright = 1;
if (__find_route(srcx, srcy, &xx, &yy, nocross) == 0)
if (__find_route(srcx, srcy, &xx, &yy, nocross, pfc) == 0)
pathbackstage = -1;
}
free(beenhere[0]);
Expand Down

0 comments on commit 1c22681

Please sign in to comment.