void calculateDistances(short **distanceMap, short destinationX, short destinationY, unsigned long blockingTerrainFlags, creature *traveler, boolean canUseSecretDoors, boolean eightWays) { creature *monst; static pdsMap map; short i, j; for (i=0; i<DCOLS; i++) { for (j=0; j<DROWS; j++) { char cost; monst = monsterAtLoc(i, j); if (monst && (monst->info.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE)) && (monst->info.flags & (MONST_IMMOBILE | MONST_GETS_TURN_ON_ACTIVATION))) { // Always avoid damage-immune stationary monsters. cost = PDS_FORBIDDEN; } else if (canUseSecretDoors && cellHasTMFlag(i, j, TM_IS_SECRET) && cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY) && !(discoveredTerrainFlagsAtLoc(i, j) & T_OBSTRUCTS_PASSABILITY)) { cost = 1; } else if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY) || (traveler && traveler == &player && !(pmap[i][j].flags & (DISCOVERED | MAGIC_MAPPED)))) { cost = cellHasTerrainFlag(i, j, T_OBSTRUCTS_DIAGONAL_MOVEMENT) ? PDS_OBSTRUCTION : PDS_FORBIDDEN; } else if ((traveler && monsterAvoids(traveler, i, j)) || cellHasTerrainFlag(i, j, blockingTerrainFlags)) { cost = PDS_FORBIDDEN; } else { cost = 1; } PDS_CELL(&map, i, j)->cost = cost; } } pdsClear(&map, 30000, eightWays); pdsSetDistance(&map, destinationX, destinationY, 0); pdsBatchOutput(&map, distanceMap); }
// Fills grid locations with the given value if they match any terrain flags or map flags. // Otherwise does not change the grid location. void getTerrainGrid(short **grid, short value, unsigned long terrainFlags, unsigned long mapFlags) { short i, j; for(i = 0; i < DCOLS; i++) { for(j = 0; j < DROWS; j++) { if (grid[i][j] != value && cellHasTerrainFlag(i, j, terrainFlags) || (pmap[i][j].flags & mapFlags)) { grid[i][j] = value; } } } }
// This is a custom implementation of recursive shadowcasting. void scanOctantFOV(char grid[VIEWWIDTH][VIEWHEIGHT], short xLoc, short yLoc, short octant, float maxRadius, short columnsRightFromOrigin, long startSlope, long endSlope, unsigned long forbiddenTerrain, unsigned long forbiddenFlags, boolean cautiousOnWalls) { if (columnsRightFromOrigin >= maxRadius) return; short i, a, b, iStart, iEnd, x, y, x2, y2; // x and y are temporary variables on which we do the octant transform long newStartSlope, newEndSlope; boolean cellObstructed; newStartSlope = startSlope; a = ((LOS_SLOPE_GRANULARITY / -2 + 1) + startSlope * columnsRightFromOrigin) / LOS_SLOPE_GRANULARITY; b = ((LOS_SLOPE_GRANULARITY / -2 + 1) + endSlope * columnsRightFromOrigin) / LOS_SLOPE_GRANULARITY; iStart = min(a, b); iEnd = max(a, b); // restrict vision to a circle of radius maxRadius if ((columnsRightFromOrigin*columnsRightFromOrigin + iEnd*iEnd) >= maxRadius*maxRadius) { return; } if ((columnsRightFromOrigin*columnsRightFromOrigin + iStart*iStart) >= maxRadius*maxRadius) { iStart = (int) (-1 * sqrt(maxRadius*maxRadius - columnsRightFromOrigin*columnsRightFromOrigin) + FLOAT_FUDGE); } x = xLoc + columnsRightFromOrigin; y = yLoc + iStart; betweenOctant1andN(&x, &y, xLoc, yLoc, octant); boolean currentlyLit = coordinatesAreInMap(x, y) && !(cellHasTerrainFlag(x, y, forbiddenTerrain) || (pmap[x][y].flags & forbiddenFlags)); for (i = iStart; i <= iEnd; i++) { x = xLoc + columnsRightFromOrigin; y = yLoc + i; betweenOctant1andN(&x, &y, xLoc, yLoc, octant); if (!coordinatesAreInMap(x, y)) { // We're off the map -- here there be memory corruption. continue; } cellObstructed = (cellHasTerrainFlag(x, y, forbiddenTerrain) || (pmap[x][y].flags & forbiddenFlags)); // if we're cautious on walls and this is a wall: if (cautiousOnWalls && cellObstructed) { // (x2, y2) is the tile one space closer to the origin from the tile we're on: x2 = xLoc + columnsRightFromOrigin - 1; y2 = yLoc + i; if (i < 0) { y2++; } else if (i > 0) { y2--; } betweenOctant1andN(&x2, &y2, xLoc, yLoc, octant); if (pmap[x2][y2].flags & IN_FIELD_OF_VIEW) { // previous tile is visible, so illuminate grid[x][y] = 1; } } else { // illuminate grid[x][y] = 1; } if (!cellObstructed && !currentlyLit) { // next column slope starts here newStartSlope = (long int) ((LOS_SLOPE_GRANULARITY * (i) - LOS_SLOPE_GRANULARITY / 2) / (columnsRightFromOrigin + 0.5)); currentlyLit = true; } else if (cellObstructed && currentlyLit) { // next column slope ends here newEndSlope = (long int) ((LOS_SLOPE_GRANULARITY * (i) - LOS_SLOPE_GRANULARITY / 2) / (columnsRightFromOrigin - 0.5)); if (newStartSlope <= newEndSlope) { // run next column scanOctantFOV(grid, xLoc, yLoc, octant, maxRadius, columnsRightFromOrigin + 1, newStartSlope, newEndSlope, forbiddenTerrain, forbiddenFlags, cautiousOnWalls); } currentlyLit = false; } } if (currentlyLit) { // got to the bottom of the scan while lit newEndSlope = endSlope; if (newStartSlope <= newEndSlope) { // run next column scanOctantFOV(grid, xLoc, yLoc, octant, maxRadius, columnsRightFromOrigin + 1, newStartSlope, newEndSlope, forbiddenTerrain, forbiddenFlags, cautiousOnWalls); } } }
boolean getQualifyingPathLocNear(short *retValX, short *retValY, short x, short y, boolean hallwaysAllowed, unsigned long blockingTerrainFlags, unsigned long blockingMapFlags, unsigned long forbiddenTerrainFlags, unsigned long forbiddenMapFlags, boolean deterministic) { short **grid, **costMap; short loc[2]; // First check the given location to see if it works, as an optimization. if (!cellHasTerrainFlag(x, y, blockingTerrainFlags | forbiddenTerrainFlags) && !(pmap[x][y].flags & (blockingMapFlags | forbiddenMapFlags)) && (hallwaysAllowed || passableArcCount(x, y) <= 1)) { *retValX = x; *retValY = y; return true; } // Allocate the grids. grid = allocGrid(); costMap = allocGrid(); // Start with a base of a high number everywhere. fillGrid(grid, 30000); fillGrid(costMap, 1); // Block off the pathing blockers. getTerrainGrid(costMap, PDS_FORBIDDEN, blockingTerrainFlags, blockingMapFlags); if (blockingTerrainFlags & (T_OBSTRUCTS_DIAGONAL_MOVEMENT | T_OBSTRUCTS_PASSABILITY)) { getTerrainGrid(costMap, PDS_OBSTRUCTION, T_OBSTRUCTS_DIAGONAL_MOVEMENT, 0); } // Run the distance scan. grid[x][y] = 1; costMap[x][y] = 1; dijkstraScan(grid, costMap, true); findReplaceGrid(grid, 30000, 30000, 0); // Block off invalid targets that aren't pathing blockers. getTerrainGrid(grid, 0, forbiddenTerrainFlags, forbiddenMapFlags); if (!hallwaysAllowed) { getPassableArcGrid(grid, 2, 10, 0); } // Get the solution. randomLeastPositiveLocationInGrid(grid, retValX, retValY, deterministic); // dumpLevelToScreen(); // displayGrid(grid); // if (coordinatesAreInMap(*retValX, *retValY)) { // hiliteCell(*retValX, *retValY, &yellow, 100, true); // } // temporaryMessage("Qualifying path selected:", true); freeGrid(grid); freeGrid(costMap); // Fall back to a pathing-agnostic alternative if there are no solutions. if (*retValX == -1 && *retValY == -1) { if (getQualifyingLocNear(loc, x, y, hallwaysAllowed, NULL, (blockingTerrainFlags | forbiddenTerrainFlags), (blockingMapFlags | forbiddenMapFlags), false, deterministic)) { *retValX = loc[0]; *retValY = loc[1]; return true; // Found a fallback solution. } else { return false; // No solutions. } } else { return true; // Found a primary solution. } }
void pdsBatchInput(pdsMap *map, short **distanceMap, short **costMap, short maxDistance, boolean eightWays) { short i, j; pdsLink *left, *right; map->eightWays = eightWays; left = NULL; right = NULL; map->front.right = NULL; for (i=0; i<DCOLS; i++) { for (j=0; j<DROWS; j++) { pdsLink *link = PDS_CELL(map, i, j); if (distanceMap != NULL) { link->distance = distanceMap[i][j]; } else { if (costMap != NULL) { // totally hackish; refactor link->distance = maxDistance; } } int cost; if (i == 0 || j == 0 || i == DCOLS - 1 || j == DROWS - 1) { cost = PDS_OBSTRUCTION; } else if (costMap == NULL) { if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY) && cellHasTerrainFlag(i, j, T_OBSTRUCTS_DIAGONAL_MOVEMENT)) cost = PDS_OBSTRUCTION; else cost = PDS_FORBIDDEN; } else { cost = costMap[i][j]; } link->cost = cost; if (cost > 0) { if (link->distance < maxDistance) { if (right == NULL || right->distance > link->distance) { // left and right are used to traverse the list; if many cells have similar values, // some time can be saved by not clearing them with each insertion. this time, // sadly, we have to start from the front. left = &map->front; right = map->front.right; } while (right != NULL && right->distance < link->distance) { left = right; right = right->right; } link->right = right; link->left = left; left->right = link; if (right != NULL) right->left = link; left = link; } else { link->right = NULL; link->left = NULL; } } else { link->right = NULL; link->left = NULL; } } } }