/* * Architect.c * Brogue * * Created by Brian Walker on 1/10/09. * Copyright 2012. All rights reserved. * * This file is part of Brogue. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include "Rogue.h" #include "IncludeGlobals.h" short topBlobMinX, topBlobMinY, blobWidth, blobHeight; #ifdef BROGUE_ASSERTS // otherwise handled as a macro in rogue.h boolean cellHasTerrainFlag(short x, short y, unsigned long flagMask) { assert(coordinatesAreInMap(x, y)); return ((flagMask) & terrainFlags((x), (y)) ? true : false); } #endif boolean checkLoopiness(short x, short y) { boolean inString; short newX, newY, dir, sdir; short numStrings, maxStringLength, currentStringLength; if (!(pmap[x][y].flags & IN_LOOP)) { return false; } // find an unloopy neighbor to start on for (sdir = 0; sdir < DIRECTION_COUNT; sdir++) { newX = x + cDirs[sdir][0]; newY = y + cDirs[sdir][1]; if (!coordinatesAreInMap(newX, newY) || !(pmap[newX][newY].flags & IN_LOOP)) { break; } } if (sdir == 8) { // no unloopy neighbors return false; // leave cell loopy } // starting on this unloopy neighbor, work clockwise and count up (a) the number of strings // of loopy neighbors, and (b) the length of the longest such string. numStrings = maxStringLength = currentStringLength = 0; inString = false; for (dir = sdir; dir < sdir + 8; dir++) { newX = x + cDirs[dir % 8][0]; newY = y + cDirs[dir % 8][1]; if (coordinatesAreInMap(newX, newY) && (pmap[newX][newY].flags & IN_LOOP)) { currentStringLength++; if (!inString) { if (numStrings > 0) { return false; // more than one string here; leave loopy } numStrings++; inString = true; } } else if (inString) { if (currentStringLength > maxStringLength) { maxStringLength = currentStringLength; } currentStringLength = 0; inString = false; } } if (inString && currentStringLength > maxStringLength) { maxStringLength = currentStringLength; } if (numStrings == 1 && maxStringLength <= 4) { pmap[x][y].flags &= ~IN_LOOP; for (dir = 0; dir < DIRECTION_COUNT; dir++) { newX = x + cDirs[dir][0]; newY = y + cDirs[dir][1]; if (coordinatesAreInMap(newX, newY)) { checkLoopiness(newX, newY); } } return true; } else { return false; } } void auditLoop(short x, short y, char grid[DCOLS][DROWS]) { short dir, newX, newY; if (coordinatesAreInMap(x, y) && !grid[x][y] && !(pmap[x][y].flags & IN_LOOP)) { grid[x][y] = true; for (dir = 0; dir < DIRECTION_COUNT; dir++) { newX = x + nbDirs[dir][0]; newY = y + nbDirs[dir][1]; if (coordinatesAreInMap(newX, newY)) { auditLoop(newX, newY, grid); } } } } // Assumes it is called with respect to a passable (startX, startY), and that the same is not already included in results. // Returns 10000 if the area included an area machine. short floodFillCount(char results[DCOLS][DROWS], char passMap[DCOLS][DROWS], short startX, short startY) { short dir, newX, newY, count; count = (passMap[startX][startY] == 2 ? 5000 : 1); if (pmap[startX][startY].flags & IS_IN_AREA_MACHINE) { count = 10000; } results[startX][startY] = true; for(dir=0; dir<4; dir++) { newX = startX + nbDirs[dir][0]; newY = startY + nbDirs[dir][1]; if (coordinatesAreInMap(newX, newY) && passMap[newX][newY] && !results[newX][newY]) { count += floodFillCount(results, passMap, newX, newY); } } return min(count, 10000); } // Rotates around the cell, counting up the number of distinct strings of passable neighbors in a single revolution. // Zero means there are no impassable tiles adjacent. // One means it is adjacent to a wall. // Two means it is in a hallway or something similar. // Three means it is the center of a T-intersection or something similar. // Four means it is in the intersection of two hallways. // Five or more means there is a bug. short passableArcCount(short x, short y) { short arcCount, dir, oldX, oldY, newX, newY; brogueAssert(coordinatesAreInMap(x, y)); arcCount = 0; for (dir = 0; dir < DIRECTION_COUNT; dir++) { oldX = x + cDirs[(dir + 7) % 8][0]; oldY = y + cDirs[(dir + 7) % 8][1]; newX = x + cDirs[dir][0]; newY = y + cDirs[dir][1]; // Counts every transition from passable to impassable or vice-versa on the way around the cell: if ((coordinatesAreInMap(newX, newY) && cellIsPassableOrDoor(newX, newY)) != (coordinatesAreInMap(oldX, oldY) && cellIsPassableOrDoor(oldX, oldY))) { arcCount++; } } return arcCount / 2; // Since we added one when we entered a wall and another when we left. } // locates all loops and chokepoints void analyzeMap(boolean calculateChokeMap) { short i, j, i2, j2, dir, newX, newY, oldX, oldY, passableArcCount, cellCount; char grid[DCOLS][DROWS], passMap[DCOLS][DROWS]; boolean designationSurvives; // first find all of the loops rogue.staleLoopMap = false; for(i=0; i 2) { if (!passMap[i-1][j] && !passMap[i+1][j] || !passMap[i][j-1] && !passMap[i][j+1]) { pmap[i][j].flags |= IS_CHOKEPOINT; } break; } } } } } } if (calculateChokeMap) { // Done finding chokepoints; now create a chokepoint map. // The chokepoint map is a number for each passable tile. If the tile is a chokepoint, // then the number indicates the number of tiles that would be rendered unreachable if the // chokepoint were blocked. If the tile is not a chokepoint, then the number indicates // the number of tiles that would be rendered unreachable if the nearest exit chokepoint // were blocked. // The cost of all of this is one depth-first flood-fill per open point that is adjacent to a chokepoint. // Start by setting the chokepoint values really high, and roping off room machines. for(i=0; i= 4) { // Now, on the chokemap, all of those flooded cells should take the lesser of their current value or this resultant number. for(i2=0; i2 0 && grid[oppX][oppY] > 0) { // If the tile being inspected has floor on both sides, fillGrid(pathMap, 30000); pathMap[newX][newY] = 0; dijkstraScan(pathMap, costMap, false); if (pathMap[oppX][oppY] > minimumPathingDistance) { // and if the pathing distance between the two flanking floor tiles exceeds minimumPathingDistance, grid[x][y] = 2; // then turn the tile into a doorway. costMap[x][y] = 1; // (Cost map also needs updating.) if (D_INSPECT_LEVELGEN) { plotCharWithColor(G_CLOSED_DOOR, mapToWindowX(x), mapToWindowY(y), &black, &green); } break; } } } } } if (D_INSPECT_LEVELGEN) { temporaryMessage("Added secondary connections:", true); } freeGrid(pathMap); freeGrid(costMap); } // Assumes (startX, startY) is in the machine. // Returns true if everything went well, and false if we ran into a machine component // that was already there, as we don't want to build a machine around it. boolean addTileToMachineInteriorAndIterate(char interior[DCOLS][DROWS], short startX, short startY) { short dir, newX, newY; boolean goodSoFar = true; interior[startX][startY] = true; for (dir = 0; dir < 4 && goodSoFar; dir++) { newX = startX + nbDirs[dir][0]; newY = startY + nbDirs[dir][1]; if (coordinatesAreInMap(newX, newY)) { if ((pmap[newX][newY].flags & HAS_ITEM) || ((pmap[newX][newY].flags & IS_IN_MACHINE) && !(pmap[newX][newY].flags & IS_GATE_SITE))) { // Abort if there's an item in the room. // Items haven't been populated yet, so the only way this could happen is if another machine // previously placed an item here. // Also abort if we're touching another machine at any point other than a gate tile. return false; } if (!interior[newX][newY] && chokeMap[newX][newY] <= chokeMap[startX][startY] // don't have to worry about walls since they're all 30000 && !(pmap[newX][newY].flags & IS_IN_MACHINE)) { //goodSoFar = goodSoFar && addTileToMachineInteriorAndIterate(interior, newX, newY); if (goodSoFar) { goodSoFar = addTileToMachineInteriorAndIterate(interior, newX, newY); } } } } return goodSoFar; } void copyMap(pcell from[DCOLS][DROWS], pcell to[DCOLS][DROWS]) { short i, j; for(i=0; icategory & (STAFF | WAND | POTION | SCROLL | RING | WEAPON | ARMOR | CHARM)) { for (i = 0; i < itemCount; i++) { if (spawnedItems[i]->category == theItem->category && spawnedItems[i]->kind == theItem->kind) { return true; } } } return false; } boolean blueprintQualifies(short i, unsigned long requiredMachineFlags) { if (blueprintCatalog[i].depthRange[0] > rogue.depthLevel || blueprintCatalog[i].depthRange[1] < rogue.depthLevel // Must have the required flags: || (~(blueprintCatalog[i].flags) & requiredMachineFlags) // May NOT have BP_ADOPT_ITEM unless that flag is required: || (blueprintCatalog[i].flags & BP_ADOPT_ITEM & ~requiredMachineFlags) // May NOT have BP_VESTIBULE unless that flag is required: || (blueprintCatalog[i].flags & BP_VESTIBULE & ~requiredMachineFlags)) { return false; } return true; } void abortItemsAndMonsters(item *spawnedItems[MACHINES_BUFFER_LENGTH], creature *spawnedMonsters[MACHINES_BUFFER_LENGTH]) { short i, j; for (i=0; icarriedItem == spawnedItems[i]) { spawnedMonsters[j]->carriedItem = NULL; break; } } deleteItem(spawnedItems[i]); spawnedItems[i] = NULL; } for (i=0; i 1) { return false; } // No building along the perimeter of the level if it's prohibited. if ((featureFlags & MF_NOT_ON_LEVEL_PERIMETER) && (x == 0 || x == DCOLS - 1 || y == 0 || y == DROWS - 1)) { return false; } // The origin is a candidate if the feature is flagged to be built at the origin. // If it's a room, the origin (i.e. doorway) is otherwise NOT a candidate. if (featureFlags & MF_BUILD_AT_ORIGIN) { return ((x == originX && y == originY) ? true : false); } else if ((bpFlags & BP_ROOM) && x == originX && y == originY) { return false; } // No building in another feature's personal space! if (occupied[x][y]) { return false; } // Must be in the viewmap if the appropriate flag is set. if ((featureFlags & (MF_IN_VIEW_OF_ORIGIN | MF_IN_PASSABLE_VIEW_OF_ORIGIN)) && !viewMap[x][y]) { return false; } // Do a distance check if the feature requests it. if (cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY)) { // Distance is calculated for walls too. distance = 10000; for (dir = 0; dir < 4; dir++) { newX = x + nbDirs[dir][0]; newY = y + nbDirs[dir][1]; if (coordinatesAreInMap(newX, newY) && !cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY) && distance > distanceMap[newX][newY] + 1) { distance = distanceMap[newX][newY] + 1; } } } else { distance = distanceMap[x][y]; } if (distance > distanceBound[1] // distance exceeds max || distance < distanceBound[0]) { // distance falls short of min return false; } if (featureFlags & MF_BUILD_IN_WALLS) { // If we're supposed to build in a wall... if (!interior[x][y] && (pmap[x][y].machineNumber == 0 || pmap[x][y].machineNumber == machineNumber) && cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY)) { // ...and this location is a wall that's not already machined... for (dir=0; dir<4; dir++) { newX = x + nbDirs[dir][0]; newY = y + nbDirs[dir][1]; if (coordinatesAreInMap(newX, newY) // ...and it's next to an interior spot or permitted elsewhere and next to passable spot... && ((interior[newX][newY] && !(newX==originX && newY==originY)) || ((featureFlags & MF_BUILD_ANYWHERE_ON_LEVEL) && !cellHasTerrainFlag(newX, newY, T_PATHING_BLOCKER) && pmap[newX][newY].machineNumber == 0))) { return true; // ...then we're golden! } } } return false; // Otherwise, no can do. } else if (cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY)) { // Can't build in a wall unless instructed to do so. return false; } else if (featureFlags & MF_BUILD_ANYWHERE_ON_LEVEL) { if ((featureFlags & MF_GENERATE_ITEM) && (cellHasTerrainFlag(x, y, T_OBSTRUCTS_ITEMS | T_PATHING_BLOCKER) || (pmap[x][y].flags & (IS_CHOKEPOINT | IN_LOOP | IS_IN_MACHINE)))) { return false; } else { return !(pmap[x][y].flags & IS_IN_MACHINE); } } else if (interior[x][y]) { return true; } return false; } void addLocationToKey(item *theItem, short x, short y, boolean disposableHere) { short i; for (i=0; i < KEY_ID_MAXIMUM && (theItem->keyLoc[i].x || theItem->keyLoc[i].machine); i++); theItem->keyLoc[i].x = x; theItem->keyLoc[i].y = y; theItem->keyLoc[i].disposableHere = disposableHere; } void addMachineNumberToKey(item *theItem, short machineNumber, boolean disposableHere) { short i; for (i=0; i < KEY_ID_MAXIMUM && (theItem->keyLoc[i].x || theItem->keyLoc[i].machine); i++); theItem->keyLoc[i].machine = machineNumber; theItem->keyLoc[i].disposableHere = disposableHere; } void expandMachineInterior(char interior[DCOLS][DROWS], short minimumInteriorNeighbors) { boolean madeChange; short nbcount, newX, newY, i, j, layer; enum directions dir; do { madeChange = false; for(i=1; i= minimumInteriorNeighbors) { // Make sure zero exterior open/machine neighbors out of eight: for (nbcount = dir = 0; dir < DIRECTION_COUNT; dir++) { newX = i + nbDirs[dir][0]; newY = j + nbDirs[dir][1]; if (!interior[newX][newY] && (!cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY) || pmap[newX][newY].machineNumber != 0)) { nbcount++; break; } } if (!nbcount) { // Eliminate this obstruction; welcome its location into the machine. madeChange = true; interior[i][j] = true; for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) { if (tileCatalog[pmap[i][j].layers[layer]].flags & T_PATHING_BLOCKER) { pmap[i][j].layers[layer] = (layer == DUNGEON ? FLOOR : NOTHING); } } for (dir = 0; dir < DIRECTION_COUNT; dir++) { newX = i + nbDirs[dir][0]; newY = j + nbDirs[dir][1]; if (pmap[newX][newY].layers[DUNGEON] == GRANITE) { pmap[newX][newY].layers[DUNGEON] = WALL; } } } } } else if (pmap[i][j].layers[DUNGEON] == DOOR || pmap[i][j].layers[DUNGEON] == SECRET_DOOR) { pmap[i][j].layers[DUNGEON] = FLOOR; } } } } while (madeChange); } boolean fillInteriorForVestibuleMachine(char interior[DCOLS][DROWS], short bp, short originX, short originY) { short **distanceMap, **costMap, qualifyingTileCount, totalFreq, sRows[DROWS], sCols[DCOLS], i, j, k; boolean success = true; zeroOutGrid(interior); distanceMap = allocGrid(); fillGrid(distanceMap, 30000); distanceMap[originX][originY] = 0; costMap = allocGrid(); populateGenericCostMap(costMap); for(i=0; i 0) { pathingGrid = allocGrid(); costGrid = allocGrid(); for (n = 0; n < orphanCount; n++) { if (D_INSPECT_MACHINES) { dumpLevelToScreen(); copyGrid(pathingGrid, grid); findReplaceGrid(pathingGrid, -1, -1, 0); hiliteGrid(pathingGrid, &green, 50); plotCharWithColor('X', mapToWindowX(orphanList[n][0]), mapToWindowY(orphanList[n][1]), &black, &orange); temporaryMessage("Orphan detected:", true); } for (i=0; i 0) { pathingGrid[i][j] = 0; costGrid[i][j] = 1; } else { pathingGrid[i][j] = 30000; costGrid[i][j] = 1; } } else { pathingGrid[i][j] = 30000; costGrid[i][j] = PDS_OBSTRUCTION; } } } dijkstraScan(pathingGrid, costGrid, false); i = orphanList[n][0]; j = orphanList[n][1]; while (pathingGrid[i][j] > 0) { for (dir = 0; dir < 4; dir++) { newX = i + nbDirs[dir][0]; newY = j + nbDirs[dir][1]; if (coordinatesAreInMap(newX, newY) && pathingGrid[newX][newY] < pathingGrid[i][j]) { grid[i][j] = 1; i = newX; j = newY; break; } } brogueAssert(dir < 4); if (D_INSPECT_MACHINES) { dumpLevelToScreen(); displayGrid(pathingGrid); plotCharWithColor('X', mapToWindowX(i), mapToWindowY(j), &black, &orange); temporaryMessage("Orphan connecting:", true); } } } freeGrid(pathingGrid); freeGrid(costGrid); } addLoops(grid, 10); for(i=0; i= 0) { pmap[i][j].layers[SURFACE] = pmap[i][j].layers[GAS] = NOTHING; } if (grid[i][j] == 0) { pmap[i][j].layers[DUNGEON] = GRANITE; interior[i][j] = false; } if (grid[i][j] >= 1) { pmap[i][j].layers[DUNGEON] = FLOOR; } } } } freeGrid(grid); } void prepareInteriorWithMachineFlags(char interior[DCOLS][DROWS], short originX, short originY, unsigned long flags, short dungeonProfileIndex) { short i, j, newX, newY; enum dungeonLayers layer; enum directions dir; // If requested, clear and expand the room as far as possible until either it's convex or it bumps into surrounding rooms if (flags & BP_MAXIMIZE_INTERIOR) { expandMachineInterior(interior, 1); } else if (flags & BP_OPEN_INTERIOR) { expandMachineInterior(interior, 4); } // If requested, cleanse the interior -- no interesting terrain allowed. if (flags & BP_PURGE_INTERIOR) { for(i=0; i0); } // Find a location and map out the machine interior. if (blueprintCatalog[bp].flags & BP_ROOM) { // If it's a room machine, count up the gates of appropriate // choke size and remember where they are. The origin of the room will be the gate location. zeroOutGrid(p->interior); if (chooseLocation) { analyzeMap(true); // Make sure the chokeMap is up to date. totalFreq = 0; for(i=0; i= blueprintCatalog[bp].roomSize[0] && chokeMap[i][j] <= blueprintCatalog[bp].roomSize[1]) { //DEBUG printf("\nDepth %i: Gate site qualified with interior size of %i.", rogue.depthLevel, chokeMap[i][j]); p->gateCandidates[totalFreq][0] = i; p->gateCandidates[totalFreq][1] = j; totalFreq++; } } } if (totalFreq) { // Choose the gate. randIndex = rand_range(0, totalFreq - 1); originX = p->gateCandidates[randIndex][0]; originY = p->gateCandidates[randIndex][1]; } else { // If no suitable sites, abort. if (distanceMap) { freeGrid(distanceMap); } if (D_MESSAGE_MACHINE_GENERATION) printf("\nDepth %i: Failed to build a machine; there was no eligible door candidate for the chosen room machine from blueprint %i.", rogue.depthLevel, bp); free(p); return false; } } // Now map out the interior into interior[][]. // Start at the gate location and do a depth-first floodfill to grab all adjoining tiles with the // same or lower choke value, ignoring any tiles that are already part of a machine. // If we get false from this, try again. If we've tried too many times already, abort. tryAgain = !addTileToMachineInteriorAndIterate(p->interior, originX, originY); } else if (blueprintCatalog[bp].flags & BP_VESTIBULE) { if (chooseLocation) { // Door machines must have locations passed in. We can't pick one ourselves. if (distanceMap) { freeGrid(distanceMap); } if (D_MESSAGE_MACHINE_GENERATION) printf("\nDepth %i: ERROR: Attempted to build a door machine from blueprint %i without a location being provided.", rogue.depthLevel, bp); free(p); return false; } if (!fillInteriorForVestibuleMachine(p->interior, bp, originX, originY)) { if (distanceMap) { freeGrid(distanceMap); } if (D_MESSAGE_MACHINE_GENERATION) printf("\nDepth %i: Failed to build a door machine from blueprint %i; not enough room.", rogue.depthLevel, bp); free(p); return false; } } else { // Find a location and map out the interior for a non-room machine. // The strategy here is simply to pick a random location on the map, // expand it along a pathing map by one space in all directions until the size reaches // the chosen size, and then make sure the resulting space qualifies. // If not, try again. If we've tried too many times already, abort. locationFailsafe = 10; do { zeroOutGrid(p->interior); tryAgain = false; if (chooseLocation) { // Pick a random origin location. randomMatchingLocation(&originX, &originY, FLOOR, NOTHING, -1); } if (!distanceMap) { distanceMap = allocGrid(); } fillGrid(distanceMap, 0); calculateDistances(distanceMap, originX, originY, T_PATHING_BLOCKER, NULL, true, false); qualifyingTileCount = 0; // Keeps track of how many interior cells we've added. totalFreq = rand_range(blueprintCatalog[bp].roomSize[0], blueprintCatalog[bp].roomSize[1]); // Keeps track of the goal size. fillSequentialList(p->sCols, DCOLS); shuffleList(p->sCols, DCOLS); fillSequentialList(p->sRows, DROWS); shuffleList(p->sRows, DROWS); for (k=0; k<1000 && qualifyingTileCount < totalFreq; k++) { for(i=0; isCols[i]][p->sRows[j]] == k) { p->interior[p->sCols[i]][p->sRows[j]] = true; qualifyingTileCount++; if (pmap[p->sCols[i]][p->sRows[j]].flags & (HAS_ITEM | HAS_MONSTER | IS_IN_MACHINE)) { // Abort if we've entered another machine or engulfed another machine's item or monster. tryAgain = true; qualifyingTileCount = totalFreq; // This is a hack to drop out of these three for-loops. } } } } } // Now make sure the interior map satisfies the machine's qualifications. if ((blueprintCatalog[bp].flags & BP_TREAT_AS_BLOCKING) && levelIsDisconnectedWithBlockingMap(p->interior, false)) { tryAgain = true; } else if ((blueprintCatalog[bp].flags & BP_REQUIRE_BLOCKING) && levelIsDisconnectedWithBlockingMap(p->interior, true) < 100) { tryAgain = true; // BP_REQUIRE_BLOCKING needs some work to make sure the disconnect is interesting. } // If locationFailsafe runs out, tryAgain will still be true, and we'll try a different machine. // If we're not choosing the blueprint, then don't bother with the locationFailsafe; just use the higher-level failsafe. } while (chooseBP && tryAgain && --locationFailsafe); } // If something went wrong, but we haven't been charged with choosing blueprint OR location, // then there is nothing to try again, so just fail. if (tryAgain && !chooseBP && !chooseLocation) { if (distanceMap) { freeGrid(distanceMap); } free(p); return false; } // Now loop if necessary. } while (tryAgain); // This is the point of no return. Back up the level so it can be restored if we have to abort this machine after this point. copyMap(pmap, p->levelBackup); // Perform any transformations to the interior indicated by the blueprint flags, including expanding the interior if requested. prepareInteriorWithMachineFlags(p->interior, originX, originY, blueprintCatalog[bp].flags, blueprintCatalog[bp].dungeonProfileType); // If necessary, label the interior as IS_IN_AREA_MACHINE or IS_IN_ROOM_MACHINE and mark down the number. machineNumber = ++rogue.machineNumber; // Reserve this machine number, starting with 1. for(i=0; iinterior[i][j]) { pmap[i][j].flags |= ((blueprintCatalog[bp].flags & BP_ROOM) ? IS_IN_ROOM_MACHINE : IS_IN_AREA_MACHINE); pmap[i][j].machineNumber = machineNumber; // also clear any secret doors, since they screw up distance mapping and aren't fun inside machines if (pmap[i][j].layers[DUNGEON] == SECRET_DOOR) { pmap[i][j].layers[DUNGEON] = DOOR; } } } } // DEBUG printf("\n\nWorking on blueprint %i, with origin at (%i, %i). Here's the initial interior map:", bp, originX, originY); // DEBUG logBuffer(interior); // Calculate the distance map (so that features that want to be close to or far from the origin can be placed accordingly) // and figure out the 33rd and 67th percentiles for features that want to be near or far from the origin. if (!distanceMap) { distanceMap = allocGrid(); } fillGrid(distanceMap, 0); calculateDistances(distanceMap, originX, originY, T_PATHING_BLOCKER, NULL, true, true); qualifyingTileCount = 0; for (i=0; i<100; i++) { p->distances[i] = 0; } for(i=0; iinterior[i][j] && distanceMap[i][j] < 100) { p->distances[distanceMap[i][j]]++; // create a histogram of distances -- poor man's sort function qualifyingTileCount++; } } } distance25 = (int) (qualifyingTileCount / 4); distance75 = (int) (3 * qualifyingTileCount / 4); for (i=0; i<100; i++) { if (distance25 <= p->distances[i]) { distance25 = i; break; } else { distance25 -= p->distances[i]; } } for (i=0; i<100; i++) { if (distance75 <= p->distances[i]) { distance75 = i; break; } else { distance75 -= p->distances[i]; } } //DEBUG printf("\nDistances calculated: 33rd percentile of distance is %i, and 67th is %i.", distance25, distance75); // Now decide which features will be skipped -- of the features marked MF_ALTERNATIVE, skip all but one, chosen randomly. // Then repeat and do the same with respect to MF_ALTERNATIVE_2, to provide up to two independent sets of alternative features per machine. for (i=0; i 0) { randIndex = rand_range(1, totalFreq); for (i=0; ioccupied); // Now tick through the features and build them. for (feat = 0; feat < blueprintCatalog[bp].featureCount; feat++) { if (skipFeature[feat]) { continue; // Skip the alternative features that were not selected for building. } feature = &(blueprintCatalog[bp].feature[feat]); // Figure out the distance bounds. distanceBound[0] = 0; distanceBound[1] = 10000; if (feature->flags & MF_NEAR_ORIGIN) { distanceBound[1] = distance25; } if (feature->flags & MF_FAR_FROM_ORIGIN) { distanceBound[0] = distance75; } if (feature->flags & (MF_IN_VIEW_OF_ORIGIN | MF_IN_PASSABLE_VIEW_OF_ORIGIN)) { zeroOutGrid(p->viewMap); if (feature->flags & MF_IN_PASSABLE_VIEW_OF_ORIGIN) { getFOVMask(p->viewMap, originX, originY, max(DCOLS, DROWS) * FP_FACTOR, T_PATHING_BLOCKER, 0, false); } else { getFOVMask(p->viewMap, originX, originY, max(DCOLS, DROWS) * FP_FACTOR, (T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_VISION), 0, false); } p->viewMap[originX][originY] = true; if (D_INSPECT_MACHINES) { dumpLevelToScreen(); hiliteCharGrid(p->viewMap, &omniscienceColor, 75); temporaryMessage("Showing visibility.", true); } } do { // If the MF_REPEAT_UNTIL_NO_PROGRESS flag is set, repeat until we fail to build the required number of instances. // Make a master map of candidate locations for this feature. qualifyingTileCount = 0; for(i=0; iinterior, p->occupied, p->viewMap, distanceMap, machineNumber, feature->flags, blueprintCatalog[bp].flags)) { qualifyingTileCount++; p->candidates[i][j] = true; } else { p->candidates[i][j] = false; } } } if (D_INSPECT_MACHINES) { dumpLevelToScreen(); hiliteCharGrid(p->occupied, &red, 75); hiliteCharGrid(p->candidates, &green, 75); hiliteCharGrid(p->interior, &blue, 75); temporaryMessage("Indicating: Occupied (red); Candidates (green); Interior (blue).", true); } if (feature->flags & MF_EVERYWHERE & ~MF_BUILD_AT_ORIGIN) { // Generate everywhere that qualifies -- instead of randomly picking tiles, keep spawning until we run out of eligible tiles. generateEverywhere = true; } else { // build as many instances as required generateEverywhere = false; instanceCount = rand_range(feature->instanceCountRange[0], feature->instanceCountRange[1]); } // Cache the personal space constant. personalSpace = feature->personalSpace; for (instance = 0; (generateEverywhere || instance < instanceCount) && qualifyingTileCount > 0;) { // Find a location for the feature. if (feature->flags & MF_BUILD_AT_ORIGIN) { // Does the feature want to be at the origin? If so, put it there. (Just an optimization.) featX = originX; featY = originY; } else { // Pick our candidate location randomly, and also strike it from // the candidates map so that subsequent instances of this same feature can't choose it. featX = -1; featY = -1; randIndex = rand_range(1, qualifyingTileCount); for(i=0; icandidates[i][j]) { if (randIndex == 1) { // This is the place! featX = i; featY = j; i = DCOLS; // break out of the loops j = DROWS; } else { randIndex--; } } } } } // Don't waste time trying the same place again whether or not this attempt succeeds. p->candidates[featX][featY] = false; qualifyingTileCount--; DFSucceeded = terrainSucceeded = true; // Try to build the DF first, if any, since we don't want it to be disrupted by subsequently placed terrain. if (feature->featureDF) { DFSucceeded = spawnDungeonFeature(featX, featY, &dungeonFeatureCatalog[feature->featureDF], false, !(feature->flags & MF_PERMIT_BLOCKING)); } // Now try to place the terrain tile, if any. if (DFSucceeded && feature->terrain) { // Must we check for blocking? if (!(feature->flags & MF_PERMIT_BLOCKING) && ((tileCatalog[feature->terrain].flags & T_PATHING_BLOCKER) || (feature->flags & MF_TREAT_AS_BLOCKING))) { // Yes, check for blocking. zeroOutGrid(p->blockingMap); p->blockingMap[featX][featY] = true; terrainSucceeded = !levelIsDisconnectedWithBlockingMap(p->blockingMap, false); } if (terrainSucceeded) { pmap[featX][featY].layers[feature->layer] = feature->terrain; } } // OK, if placement was successful, clear some personal space around the feature so subsequent features can't be generated too close. // Personal space of 0 means nothing gets cleared, 1 means that only the tile itself gets cleared, and 2 means the 3x3 grid centered on it. if (DFSucceeded && terrainSucceeded) { for (i = featX - personalSpace + 1; i <= featX + personalSpace - 1; i++) { for (j = featY - personalSpace + 1; j <= featY + personalSpace - 1; j++) { if (coordinatesAreInMap(i, j)) { if (p->candidates[i][j]) { brogueAssert(!occupied[i][j] || (i == originX && j == originY)); // Candidates[][] should never be true where occupied[][] is true. p->candidates[i][j] = false; qualifyingTileCount--; } p->occupied[i][j] = true; } } } instance++; // we've placed an instance //DEBUG printf("\nPlaced instance #%i of feature %i at (%i, %i).", instance, feat, featX, featY); } if (DFSucceeded && terrainSucceeded) { // Proceed only if the terrain stuff for this instance succeeded. theItem = NULL; // Mark the feature location as part of the machine, in case it is not already inside of it. pmap[featX][featY].flags |= ((blueprintCatalog[bp].flags & BP_ROOM) ? IS_IN_ROOM_MACHINE : IS_IN_AREA_MACHINE); pmap[featX][featY].machineNumber = machineNumber; // Mark the feature location as impregnable if requested. if (feature->flags & MF_IMPREGNABLE) { pmap[featX][featY].flags |= IMPREGNABLE; } // Generate an item as necessary. if ((feature->flags & MF_GENERATE_ITEM) || (adoptiveItem && (feature->flags & MF_ADOPT_ITEM) && (blueprintCatalog[bp].flags & BP_ADOPT_ITEM))) { // Are we adopting an item instead of generating one? if (adoptiveItem && (feature->flags & MF_ADOPT_ITEM) && (blueprintCatalog[bp].flags & BP_ADOPT_ITEM)) { theItem = adoptiveItem; adoptiveItem = NULL; // can be adopted only once } else { // Have to create an item ourselves. theItem = generateItem(feature->itemCategory, feature->itemKind); failsafe = 1000; while ((theItem->flags & ITEM_CURSED) || ((feature->flags & MF_REQUIRE_GOOD_RUNIC) && (!(theItem->flags & ITEM_RUNIC))) // runic if requested || ((feature->flags & MF_NO_THROWING_WEAPONS) && theItem->category == WEAPON && theItem->quantity > 1) // no throwing weapons if prohibited || itemIsADuplicate(theItem, p->spawnedItems, itemCount)) { // don't want to duplicates of rings, staffs, etc. deleteItem(theItem); theItem = generateItem(feature->itemCategory, feature->itemKind); if (failsafe <= 0) { break; } failsafe--; } p->spawnedItems[itemCount] = theItem; // Keep a list of generated items so that we can delete them all if construction fails. itemCount++; } theItem->flags |= feature->itemFlags; addLocationToKey(theItem, featX, featY, (feature->flags & MF_KEY_DISPOSABLE) ? true : false); theItem->originDepth = rogue.depthLevel; if (feature->flags & MF_SKELETON_KEY) { addMachineNumberToKey(theItem, machineNumber, (feature->flags & MF_KEY_DISPOSABLE) ? true : false); } if (!(feature->flags & MF_OUTSOURCE_ITEM_TO_MACHINE) && !(feature->flags & MF_MONSTER_TAKE_ITEM)) { // Place the item at the feature location. placeItem(theItem, featX, featY); } } if (feature->flags & (MF_OUTSOURCE_ITEM_TO_MACHINE | MF_BUILD_VESTIBULE)) { // Put this item up for adoption, or generate a door guard machine. // Try to create a sub-machine that qualifies. // If we fail 10 times, abort the entire machine (including any sub-machines already built). // Also, if we build a sub-machine, and it succeeds, but this (its parent machine) fails, // we pass the monsters and items that it spawned back to the parent, // so that if the parent fails, they can all be freed. for (i=10; i > 0; i--) { // First make sure our adopted item, if any, is not on the floor or in the pack already. // Otherwise, a previous attempt to place it may have put it on the floor in a different // machine, only to have that machine fail and be deleted, leaving the item remaining on // the floor where placed. if ((feature->flags & MF_OUTSOURCE_ITEM_TO_MACHINE) && theItem) { removeItemFromChain(theItem, floorItems); removeItemFromChain(theItem, packItems); theItem->nextItem = NULL; success = buildAMachine(-1, -1, -1, BP_ADOPT_ITEM, theItem, p->spawnedItemsSub, p->spawnedMonstersSub); } else if (feature->flags & MF_BUILD_VESTIBULE) { success = buildAMachine(-1, featX, featY, BP_VESTIBULE, NULL, p->spawnedItemsSub, p->spawnedMonstersSub); } // Now put the item up for adoption. if (success) { // Success! Now we have to add that machine's items and monsters to our own list, so they // all get deleted if this machine or its parent fails. for (j=0; jspawnedItemsSub[j]; j++) { p->spawnedItems[itemCount] = p->spawnedItemsSub[j]; itemCount++; p->spawnedItemsSub[j] = NULL; } for (j=0; jspawnedMonstersSub[j]; j++) { p->spawnedMonsters[monsterCount] = p->spawnedMonstersSub[j]; monsterCount++; p->spawnedMonstersSub[j] = NULL; } break; } } if (!i) { if (D_MESSAGE_MACHINE_GENERATION) printf("\nDepth %i: Failed to place blueprint %i because it requires an adoptive machine and we couldn't place one.", rogue.depthLevel, bp); // failure! abort! copyMap(p->levelBackup, pmap); abortItemsAndMonsters(p->spawnedItems, p->spawnedMonsters); freeGrid(distanceMap); free(p); return false; } theItem = NULL; } // Generate a horde as necessary. if ((feature->flags & MF_GENERATE_HORDE) || feature->monsterID) { if (feature->flags & MF_GENERATE_HORDE) { monst = spawnHorde(0, featX, featY, ((HORDE_IS_SUMMONED | HORDE_LEADER_CAPTIVE) & ~(feature->hordeFlags)), feature->hordeFlags); if (monst) { monst->bookkeepingFlags |= MB_JUST_SUMMONED; } } if (feature->monsterID) { monst = monsterAtLoc(featX, featY); if (monst) { killCreature(monst, true); // If there's already a monster here, quietly bury the body. } monst = generateMonster(feature->monsterID, true, true); if (monst) { monst->xLoc = featX; monst->yLoc = featY; pmap[monst->xLoc][monst->yLoc].flags |= HAS_MONSTER; monst->bookkeepingFlags |= MB_JUST_SUMMONED; } } if (monst) { if (!leader) { leader = monst; } // Give our item to the monster leader if appropriate. // Actually just remember that we have to give it to this monster; the actual // hand-off happens after we're sure that the machine will succeed. if (theItem && (feature->flags & MF_MONSTER_TAKE_ITEM)) { torchBearer = monst; torch = theItem; } } for (monst = monsters->nextCreature; monst; monst = nextMonst) { // Have to cache the next monster, as the chain can get disrupted by making a monster dormant below. nextMonst = monst->nextCreature; if (monst->bookkeepingFlags & MB_JUST_SUMMONED) { // All monsters spawned by a machine are tribemates. // Assign leader/follower roles if they are not yet assigned. if (!(monst->bookkeepingFlags & (MB_LEADER | MB_FOLLOWER))) { if (leader && leader != monst) { monst->leader = leader; monst->bookkeepingFlags &= ~MB_LEADER; monst->bookkeepingFlags |= MB_FOLLOWER; leader->bookkeepingFlags |= MB_LEADER; } else { leader = monst; } } monst->bookkeepingFlags &= ~MB_JUST_SUMMONED; p->spawnedMonsters[monsterCount] = monst; monsterCount++; if (feature->flags & MF_MONSTER_SLEEPING) { monst->creatureState = MONSTER_SLEEPING; } if (feature->flags & MF_MONSTER_FLEEING) { monst->creatureState = MONSTER_FLEEING; monst->creatureMode = MODE_PERM_FLEEING; } if (feature->flags & MF_MONSTERS_DORMANT) { toggleMonsterDormancy(monst); if (!(feature->flags & MF_MONSTER_SLEEPING) && monst->creatureState != MONSTER_ALLY) { monst->creatureState = MONSTER_TRACKING_SCENT; } } monst->machineHome = machineNumber; // Monster remembers the machine that spawned it. } } } } theItem = NULL; // Finished with this instance! } } while ((feature->flags & MF_REPEAT_UNTIL_NO_PROGRESS) && instance >= feature->minimumInstanceCount); //DEBUG printf("\nFinished feature %i. Here's the candidates map:", feat); //DEBUG logBuffer(candidates); if (instance < feature->minimumInstanceCount && !(feature->flags & MF_REPEAT_UNTIL_NO_PROGRESS)) { // failure! abort! if (D_MESSAGE_MACHINE_GENERATION) printf("\nDepth %i: Failed to place blueprint %i because of feature %i; needed %i instances but got only %i.", rogue.depthLevel, bp, feat, feature->minimumInstanceCount, instance); // Restore the map to how it was before we touched it. copyMap(p->levelBackup, pmap); abortItemsAndMonsters(p->spawnedItems, p->spawnedMonsters); freeGrid(distanceMap); free(p); return false; } } // Clear out the interior flag for all non-wired cells, if requested. if (blueprintCatalog[bp].flags & BP_NO_INTERIOR_FLAG) { for(i=0; icarriedItem) { deleteItem(torchBearer->carriedItem); } removeItemFromChain(torch, floorItems); torchBearer->carriedItem = torch; } freeGrid(distanceMap); if (D_MESSAGE_MACHINE_GENERATION) printf("\nDepth %i: Built a machine from blueprint %i with an origin at (%i, %i).", rogue.depthLevel, bp, originX, originY); //Pass created items and monsters to parent where they will be deleted on failure to place parent machine if (parentSpawnedItems) { for (i=0; ispawnedItems[i]; } } if (parentSpawnedMonsters) { for (i=0; ispawnedMonsters[i]; } } free(p); return true; } // add machines to the dungeon. void addMachines() { short machineCount, failsafe; short randomMachineFactor; analyzeMap(true); // Add the amulet holder if it's depth 26: if (rogue.depthLevel == AMULET_LEVEL) { for (failsafe = 50; failsafe; failsafe--) { if (buildAMachine(MT_AMULET_AREA, -1, -1, 0, NULL, NULL, NULL)) { break; } } } // Add reward rooms, if any: machineCount = 0; while (rogue.depthLevel <= AMULET_LEVEL && (rogue.rewardRoomsGenerated + machineCount) * 4 + 2 < rogue.depthLevel * MACHINES_FACTOR / FP_FACTOR) { // try to build at least one every four levels on average machineCount++; } randomMachineFactor = (rogue.depthLevel < 3 && (rogue.rewardRoomsGenerated + machineCount) == 0 ? 40 : 15); while (rand_percent(max(randomMachineFactor, 15 * MACHINES_FACTOR / FP_FACTOR)) && machineCount < 100) { randomMachineFactor = 15; machineCount++; } for (failsafe = 50; machineCount && failsafe; failsafe--) { if (buildAMachine(-1, -1, -1, BP_REWARD, NULL, NULL, NULL)) { machineCount--; rogue.rewardRoomsGenerated++; } } } // Add terrain, DFs and flavor machines. Includes traps, torches, funguses, flavor machines, etc. // If buildAreaMachines is true, build ONLY the autogenerators that include machines. // If false, build all EXCEPT the autogenerators that include machines. void runAutogenerators(boolean buildAreaMachines) { short AG, count, x, y, i; const autoGenerator *gen; char grid[DCOLS][DROWS]; // Cycle through the autoGenerators. for (AG=1; AGmachine > 0 == buildAreaMachines) { // Enforce depth constraints. if (rogue.depthLevel < gen->minDepth || rogue.depthLevel > gen->maxDepth) { continue; } // Decide how many of this AG to build. count = min((gen->minNumberIntercept + rogue.depthLevel * gen->minNumberSlope) / 100, gen->maxNumber); while (rand_percent(gen->frequency) && count < gen->maxNumber) { count++; } // Build that many instances. for (i = 0; i < count; i++) { // Find a location for DFs and terrain generations. //if (randomMatchingLocation(&x, &y, gen->requiredDungeonFoundationType, NOTHING, -1)) { //if (randomMatchingLocation(&x, &y, -1, -1, gen->requiredDungeonFoundationType)) { if (randomMatchingLocation(&x, &y, gen->requiredDungeonFoundationType, gen->requiredLiquidFoundationType, -1)) { // Spawn the DF. if (gen->DFType) { spawnDungeonFeature(x, y, &(dungeonFeatureCatalog[gen->DFType]), false, true); if (D_INSPECT_LEVELGEN) { dumpLevelToScreen(); hiliteCell(x, y, &yellow, 50, true); temporaryMessage("Dungeon feature added.", true); } } // Spawn the terrain if it's got the priority to spawn there and won't disrupt connectivity. if (gen->terrain && tileCatalog[pmap[x][y].layers[gen->layer]].drawPriority >= tileCatalog[gen->terrain].drawPriority) { // Check connectivity. zeroOutGrid(grid); grid[x][y] = true; if (!(tileCatalog[gen->terrain].flags & T_PATHING_BLOCKER) || !levelIsDisconnectedWithBlockingMap(grid, false)) { // Build! pmap[x][y].layers[gen->layer] = gen->terrain; if (D_INSPECT_LEVELGEN) { dumpLevelToScreen(); hiliteCell(x, y, &yellow, 50, true); temporaryMessage("Terrain added.", true); } } } } // Attempt to build the machine if requested. // Machines will find their own locations, so it will not be at the same place as terrain and DF. if (gen->machine > 0) { buildAMachine(gen->machine, -1, -1, 0, NULL, NULL, NULL); } } } } } // Knock down the boundaries between similar lakes where possible. void cleanUpLakeBoundaries() { short i, j, x, y, failsafe, layer; boolean reverse, madeChange; unsigned long subjectFlags; reverse = true; failsafe = 100; do { madeChange = false; reverse = !reverse; failsafe--; for (i = (reverse ? DCOLS - 2 : 1); (reverse ? i > 0 : i < DCOLS - 1); (reverse ? i-- : i++)) { for (j = (reverse ? DROWS - 2 : 1); (reverse ? j > 0 : j < DROWS - 1); (reverse ? j-- : j++)) { //assert(i >= 1 && i <= DCOLS - 2 && j >= 1 && j <= DROWS - 2); //if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY) if (cellHasTerrainFlag(i, j, T_LAKE_PATHING_BLOCKER | T_OBSTRUCTS_PASSABILITY) && !cellHasTMFlag(i, j, TM_IS_SECRET) && !(pmap[i][j].flags & IMPREGNABLE)) { subjectFlags = terrainFlags(i, j) & (T_LAKE_PATHING_BLOCKER | T_OBSTRUCTS_PASSABILITY); x = y = 0; if ((terrainFlags(i - 1, j) & T_LAKE_PATHING_BLOCKER & ~subjectFlags) && !cellHasTMFlag(i - 1, j, TM_IS_SECRET) && !cellHasTMFlag(i + 1, j, TM_IS_SECRET) && (terrainFlags(i - 1, j) & T_LAKE_PATHING_BLOCKER & ~subjectFlags) == (terrainFlags(i + 1, j) & T_LAKE_PATHING_BLOCKER & ~subjectFlags)) { x = i + 1; y = j; } else if ((terrainFlags(i, j - 1) & T_LAKE_PATHING_BLOCKER & ~subjectFlags) && !cellHasTMFlag(i, j - 1, TM_IS_SECRET) && !cellHasTMFlag(i, j + 1, TM_IS_SECRET) && (terrainFlags(i, j - 1) & T_LAKE_PATHING_BLOCKER & ~subjectFlags) == (terrainFlags(i, j + 1) & T_LAKE_PATHING_BLOCKER & ~subjectFlags)) { x = i; y = j + 1; } if (x) { madeChange = true; for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) { pmap[i][j].layers[layer] = pmap[x][y].layers[layer]; } //pmap[i][j].layers[DUNGEON] = CRYSTAL_WALL; } } } } } while (madeChange && failsafe > 0); } void removeDiagonalOpenings() { short i, j, k, x1, y1, x2, layer; boolean diagonalCornerRemoved; do { diagonalCornerRemoved = false; for (i=0; i 6 && rand_percent(50)) { drawCircleOnGrid(grid, DCOLS/2, DROWS/2, rand_range(3, radius - 3), 0); } } void designChunkyRoom(short **grid) { short i, x, y; short minX, maxX, minY, maxY; short chunkCount = rand_range(2, 8); fillGrid(grid, 0); drawCircleOnGrid(grid, DCOLS/2, DROWS/2, 2, 1); minX = DCOLS/2 - 3; maxX = DCOLS/2 + 3; minY = DROWS/2 - 3; maxY = DROWS/2 + 3; for (i=0; i 0) { return false; } } } } } } return true; } void attachRooms(short **grid, const dungeonProfile *theDP, short attempts, short maxRoomCount) { short roomsBuilt, roomsAttempted; short **roomMap; short doorSites[4][2]; short i, x, y, sCoord[DCOLS*DROWS]; enum directions dir, oppDir; fillSequentialList(sCoord, DCOLS*DROWS); shuffleList(sCoord, DCOLS*DROWS); roomMap = allocGrid(); for (roomsBuilt = roomsAttempted = 0; roomsBuilt < maxRoomCount && roomsAttempted < attempts; roomsAttempted++) { // Build a room in hyperspace. fillGrid(roomMap, 0); designRandomRoom(roomMap, roomsAttempted <= attempts - 5 && rand_percent(theDP->corridorChance), doorSites, theDP->roomFrequencies); if (D_INSPECT_LEVELGEN) { colorOverDungeon(&darkGray); hiliteGrid(roomMap, &blue, 100); if (doorSites[0][0] != -1) plotCharWithColor('^', mapToWindowX(doorSites[0][0]), mapToWindowY(doorSites[0][1]), &black, &green); if (doorSites[1][0] != -1) plotCharWithColor('v', mapToWindowX(doorSites[1][0]), mapToWindowY(doorSites[1][1]), &black, &green); if (doorSites[2][0] != -1) plotCharWithColor('<', mapToWindowX(doorSites[2][0]), mapToWindowY(doorSites[2][1]), &black, &green); if (doorSites[3][0] != -1) plotCharWithColor('>', mapToWindowX(doorSites[3][0]), mapToWindowY(doorSites[3][1]), &black, &green); temporaryMessage("Generating this room:", true); } // Slide hyperspace across real space, in a random but predetermined order, until the room matches up with a wall. for (i = 0; i < DCOLS*DROWS; i++) { x = sCoord[i] / DROWS; y = sCoord[i] % DROWS; dir = directionOfDoorSite(grid, x, y); oppDir = oppositeDirection(dir); if (dir != NO_DIRECTION && doorSites[oppDir][0] != -1 && roomFitsAt(grid, roomMap, x - doorSites[oppDir][0], y - doorSites[oppDir][1])) { // Room fits here. if (D_INSPECT_LEVELGEN) { colorOverDungeon(&darkGray); hiliteGrid(grid, &white, 100); } insertRoomAt(grid, roomMap, x - doorSites[oppDir][0], y - doorSites[oppDir][1], doorSites[oppDir][0], doorSites[oppDir][1]); grid[x][y] = 2; // Door site. if (D_INSPECT_LEVELGEN) { hiliteGrid(grid, &green, 50); temporaryMessage("Added room.", true); } roomsBuilt++; break; } } } freeGrid(roomMap); } void adjustDungeonProfileForDepth(dungeonProfile *theProfile) { const short descentPercent = clamp(100 * (rogue.depthLevel - 1) / (AMULET_LEVEL - 1), 0, 100); theProfile->roomFrequencies[0] += 20 * (100 - descentPercent) / 100; theProfile->roomFrequencies[1] += 10 * (100 - descentPercent) / 100; theProfile->roomFrequencies[3] += 7 * (100 - descentPercent) / 100; theProfile->roomFrequencies[5] += 10 * descentPercent / 100; theProfile->corridorChance += 80 * (100 - descentPercent) / 100; } void adjustDungeonFirstRoomProfileForDepth(dungeonProfile *theProfile) { short i; const short descentPercent = clamp(100 * (rogue.depthLevel - 1) / (AMULET_LEVEL - 1), 0, 100); if (rogue.depthLevel == 1) { // All dungeons start with the entrance room on depth 1. for (i = 0; i < ROOM_TYPE_COUNT; i++) { theProfile->roomFrequencies[i] = 0; } theProfile->roomFrequencies[7] = 1; } else { theProfile->roomFrequencies[6] += 50 * descentPercent / 100; } } // Called by digDungeon(). // Slaps a bunch of rooms and hallways into the grid. // On the grid, a 0 denotes granite, a 1 denotes floor, and a 2 denotes a possible door site. // -1 denotes off-limits areas -- rooms can't be placed there and also can't sprout off of there. // Parent function will translate this grid into pmap[][] to make floors, walls, doors, etc. void carveDungeon(short **grid) { dungeonProfile theDP, theFirstRoomDP; theDP = dungeonProfileCatalog[DP_BASIC]; adjustDungeonProfileForDepth(&theDP); theFirstRoomDP = dungeonProfileCatalog[DP_BASIC_FIRST_ROOM]; adjustDungeonFirstRoomProfileForDepth(&theFirstRoomDP); designRandomRoom(grid, false, NULL, theFirstRoomDP.roomFrequencies); if (D_INSPECT_LEVELGEN) { colorOverDungeon(&darkGray); hiliteGrid(grid, &white, 100); temporaryMessage("First room placed:", true); } attachRooms(grid, &theDP, 35, 35); // colorOverDungeon(&darkGray); // hiliteGrid(grid, &white, 100); // temporaryMessage("How does this finished level look?", true); } void finishWalls(boolean includingDiagonals) { short i, j, x1, y1; boolean foundExposure; enum directions dir; for (i=0; i=10; lakeMaxHeight--, lakeMaxWidth -= 2) { // lake generations fillGrid(grid, 0); createBlobOnGrid(grid, &lakeX, &lakeY, &lakeWidth, &lakeHeight, 5, 4, 4, lakeMaxWidth, lakeMaxHeight, 55, "ffffftttt", "ffffttttt"); // if (D_INSPECT_LEVELGEN) { // colorOverDungeon(&darkGray); // hiliteGrid(grid, &white, 100); // temporaryMessage("Generated a lake.", true); // } for (k=0; k<20; k++) { // placement attempts // propose a position for the top-left of the grid in the dungeon x = rand_range(1 - lakeX, DCOLS - lakeWidth - lakeX - 2); y = rand_range(1 - lakeY, DROWS - lakeHeight - lakeY - 2); if (!lakeDisruptsPassability(grid, lakeMap, -x, -y)) { // level with lake is completely connected //printf("Placed a lake!"); // copy in lake for (i = 0; i < lakeWidth; i++) { for (j = 0; j < lakeHeight; j++) { if (grid[i + lakeX][j + lakeY]) { lakeMap[i + lakeX + x][j + lakeY + y] = true; pmap[i + lakeX + x][j + lakeY + y].layers[DUNGEON] = FLOOR; } } } if (D_INSPECT_LEVELGEN) { dumpLevelToScreen(); hiliteGrid(lakeMap, &white, 50); temporaryMessage("Added a lake location.", true); } break; } } } freeGrid(grid); } void createWreath(short shallowLiquid, short wreathWidth, char wreathMap[DCOLS][DROWS]) { short i, j, k, l; for (i=0; i= 3) { // If the door has three or more pathing blocker neighbors in the four cardinal directions, // then the door is orphaned and must be removed. pmap[i][j].layers[DUNGEON] = FLOOR; } else if (rand_percent(secretDoorChance)) { pmap[i][j].layers[DUNGEON] = SECRET_DOOR; } } } } } void clearLevel() { short i, j; for( i=0; i 3) // Can't have bridges shorter than 3 spaces. && foundExposure && !cellHasTerrainFlag(k, j, T_PATHING_BLOCKER | T_CAN_BE_BRIDGED) // Must end on an unobstructed land tile. && !pmap[k][j].machineNumber // Cannot end in a machine. && 100 * pathingDistance(i, j, k, j, T_PATHING_BLOCKER) / (k - i) > bridgeRatioX) { // Must shorten the pathing distance enough. for (l=i+1; l < k; l++) { pmap[l][j].layers[LIQUID] = BRIDGE; } pmap[i][j].layers[SURFACE] = BRIDGE_EDGE; pmap[k][j].layers[SURFACE] = BRIDGE_EDGE; return true; } // try a vertical bridge foundExposure = false; for (k = j + 1; k < DROWS && !pmap[i][k].machineNumber && cellHasTerrainFlag(i, k, T_CAN_BE_BRIDGED) && !cellHasTMFlag(i, k, TM_IS_SECRET) && !cellHasTerrainFlag(i, k, T_OBSTRUCTS_PASSABILITY) && cellHasTerrainFlag(i-1, k, (T_CAN_BE_BRIDGED | T_OBSTRUCTS_PASSABILITY)) && cellHasTerrainFlag(i+1, k, (T_CAN_BE_BRIDGED | T_OBSTRUCTS_PASSABILITY)); k++) { if (!cellHasTerrainFlag(i-1, k, T_OBSTRUCTS_PASSABILITY) && !cellHasTerrainFlag(i+1, k, T_OBSTRUCTS_PASSABILITY)) { foundExposure = true; } } if (k < DROWS && (k - j > 3) && foundExposure && !cellHasTerrainFlag(i, k, T_PATHING_BLOCKER | T_CAN_BE_BRIDGED) && !pmap[i][k].machineNumber // Cannot end in a machine. && 100 * pathingDistance(i, j, i, k, T_PATHING_BLOCKER) / (k - j) > bridgeRatioY) { for (l=j+1; l < k; l++) { pmap[i][l].layers[LIQUID] = BRIDGE; } pmap[i][j].layers[SURFACE] = BRIDGE_EDGE; pmap[i][k].layers[SURFACE] = BRIDGE_EDGE; return true; } } } } return false; } // This is the master function for digging out a dungeon level. // Finishing touches -- items, monsters, staircases, etc. -- are handled elsewhere. void digDungeon() { short i, j; short **grid; rogue.machineNumber = 0; topBlobMinX = topBlobMinY = blobWidth = blobHeight = 0; #ifdef AUDIT_RNG char RNGMessage[100]; sprintf(RNGMessage, "\n\n\nDigging dungeon level %i:\n", rogue.depthLevel); RNGLog(RNGMessage); #endif // Clear level and fill with granite clearLevel(); grid = allocGrid(); carveDungeon(grid); addLoops(grid, 20); for (i=0; inextCreature; monst != NULL; monst = monst->nextCreature) { if ((monst->creatureState == MONSTER_SLEEPING || (monst->info.flags & MONST_IMMOBILE) || (monst->bookkeepingFlags & MB_CAPTIVE)) && costMap[monst->xLoc][monst->yLoc] >= 0) { costMap[monst->xLoc][monst->yLoc] = PDS_FORBIDDEN; } } fillGrid(rogue.wpDistance[wpIndex], 30000); rogue.wpDistance[wpIndex][rogue.wpCoordinates[wpIndex][0]][rogue.wpCoordinates[wpIndex][1]] = 0; dijkstraScan(rogue.wpDistance[wpIndex], costMap, true); freeGrid(costMap); } void setUpWaypoints() { short i, j, sCoord[DCOLS * DROWS], x, y; char grid[DCOLS][DROWS]; zeroOutGrid(grid); for (i=0; i= tileCatalog[surfaceTileType].drawPriority) // and we won't be painting into the surface layer when that cell forbids it, && !(layer == SURFACE && cellHasTerrainFlag(i, j, T_OBSTRUCTS_SURFACE_EFFECTS)) // and, if requested, the fill won't violate the priority of the most important terrain in this cell: && (!blockedByOtherLayers || tileCatalog[pmap[i][j].layers[highestPriorityLayer(i, j, true)]].drawPriority >= tileCatalog[surfaceTileType].drawPriority) ) { if ((tileCatalog[surfaceTileType].flags & T_IS_FIRE) && !(tileCatalog[pmap[i][j].layers[layer]].flags & T_IS_FIRE)) { pmap[i][j].flags |= CAUGHT_FIRE_THIS_TURN; } if ((tileCatalog[pmap[i][j].layers[layer]].flags & T_PATHING_BLOCKER) != (tileCatalog[surfaceTileType].flags & T_PATHING_BLOCKER)) { rogue.staleLoopMap = true; } pmap[i][j].layers[layer] = surfaceTileType; // Place the terrain! accomplishedSomething = true; if (refresh) { refreshDungeonCell(i, j); if (player.xLoc == i && player.yLoc == j && !player.status[STATUS_LEVITATING] && refresh) { flavorMessage(tileFlavor(player.xLoc, player.yLoc)); } if (pmap[i][j].flags & (HAS_MONSTER)) { monst = monsterAtLoc(i, j); applyInstantTileEffectsToCreature(monst); if (rogue.gameHasEnded) { return true; } } if (tileCatalog[surfaceTileType].flags & T_IS_FIRE) { if (pmap[i][j].flags & HAS_ITEM) { theItem = itemAtLoc(i, j); if (theItem->flags & ITEM_FLAMMABLE) { burnItem(theItem); } } } } } else { spawnMap[i][j] = false; // so that the spawnmap reflects what actually got built } } } return accomplishedSomething; } void spawnMapDF(short x, short y, enum tileType propagationTerrain, boolean requirePropTerrain, short startProb, short probDec, char spawnMap[DCOLS][DROWS]) { short i, j, dir, t, x2, y2; boolean madeChange; spawnMap[x][y] = t = 1; // incremented before anything else happens madeChange = true; while (madeChange && startProb > 0) { madeChange = false; t++; for (i = 0; i < DCOLS; i++) { for (j=0; j < DROWS; j++) { if (spawnMap[i][j] == t - 1) { for (dir = 0; dir < 4; dir++) { x2 = i + nbDirs[dir][0]; y2 = j + nbDirs[dir][1]; if (coordinatesAreInMap(x2, y2) && (!requirePropTerrain || (propagationTerrain > 0 && cellHasTerrainType(x2, y2, propagationTerrain))) && (!cellHasTerrainFlag(x2, y2, T_OBSTRUCTS_SURFACE_EFFECTS) || (propagationTerrain > 0 && cellHasTerrainType(x2, y2, propagationTerrain))) && rand_percent(startProb)) { spawnMap[x2][y2] = t; madeChange = true; } } } } } startProb -= probDec; if (t > 100) { for (i = 0; i < DCOLS; i++) { for (j=0; j < DROWS; j++) { if (spawnMap[i][j] == t) { spawnMap[i][j] = 2; } else if (spawnMap[i][j] > 0) { spawnMap[i][j] = 1; } } } t = 2; } } if (requirePropTerrain && !cellHasTerrainType(x, y, propagationTerrain)) { spawnMap[x][y] = 0; } } void evacuateCreatures(char blockingMap[DCOLS][DROWS]) { short i, j, newLoc[2]; creature *monst; for (i=0; iinfo)), (HAS_MONSTER | HAS_PLAYER), false, false); monst->xLoc = newLoc[0]; monst->yLoc = newLoc[1]; pmap[i][j].flags &= ~(HAS_MONSTER | HAS_PLAYER); pmap[newLoc[0]][newLoc[1]].flags |= (monst == &player ? HAS_PLAYER : HAS_MONSTER); } } } } // returns whether the feature was successfully generated (false if we aborted because of blocking) boolean spawnDungeonFeature(short x, short y, dungeonFeature *feat, boolean refreshCell, boolean abortIfBlocking) { short i, j, layer; char blockingMap[DCOLS][DROWS]; boolean blocking; boolean succeeded; creature *monst; if ((feat->flags & DFF_RESURRECT_ALLY) && !resurrectAlly(x, y)) { return false; } if (feat->description[0] && !feat->messageDisplayed && playerCanSee(x, y)) { feat->messageDisplayed = true; message(feat->description, false); } zeroOutGrid(blockingMap); // Blocking keeps track of whether to abort if it turns out that the DF would obstruct the level. blocking = ((abortIfBlocking && !(feat->flags & DFF_PERMIT_BLOCKING) && ((tileCatalog[feat->tile].flags & (T_PATHING_BLOCKER)) || (feat->flags & DFF_TREAT_AS_BLOCKING))) ? true : false); if (feat->tile) { if (feat->layer == GAS) { pmap[x][y].volume += feat->startProbability; pmap[x][y].layers[GAS] = feat->tile; if (refreshCell) { refreshDungeonCell(x, y); } succeeded = true; } else { spawnMapDF(x, y, feat->propagationTerrain, (feat->propagationTerrain ? true : false), feat->startProbability, feat->probabilityDecrement, blockingMap); if (!blocking || !levelIsDisconnectedWithBlockingMap(blockingMap, false)) { if (feat->flags & DFF_EVACUATE_CREATURES_FIRST) { // first, evacuate creatures if necessary, so that they do not re-trigger the tile. evacuateCreatures(blockingMap); } //succeeded = fillSpawnMap(feat->layer, feat->tile, blockingMap, (feat->flags & DFF_BLOCKED_BY_OTHER_LAYERS), refreshCell, (feat->flags & DFF_SUPERPRIORITY)); fillSpawnMap(feat->layer, feat->tile, blockingMap, (feat->flags & DFF_BLOCKED_BY_OTHER_LAYERS), refreshCell, (feat->flags & DFF_SUPERPRIORITY)); // this can tweak the spawn map too succeeded = true; // fail ONLY if we blocked the level. We succeed even if, thanks to priority, nothing gets built. } else { succeeded = false; } } } else { blockingMap[x][y] = true; succeeded = true; // Automatically succeed if there is no terrain to place. if (feat->flags & DFF_EVACUATE_CREATURES_FIRST) { // first, evacuate creatures if necessary, so that they do not re-trigger the tile. evacuateCreatures(blockingMap); } } if (succeeded && (feat->flags & DFF_CLEAR_OTHER_TERRAIN)) { for (i=0; ilayer && layer != GAS) { pmap[i][j].layers[layer] = (layer == DUNGEON ? FLOOR : NOTHING); } } } } } } if (succeeded) { if ((feat->flags & DFF_AGGRAVATES_MONSTERS) && feat->effectRadius) { aggravateMonsters(feat->effectRadius, x, y, &gray); } if (refreshCell && feat->flashColor && feat->effectRadius) { colorFlash(feat->flashColor, 0, (IN_FIELD_OF_VIEW | CLAIRVOYANT_VISIBLE), 4, feat->effectRadius, x, y); } if (refreshCell && feat->lightFlare) { createFlare(x, y, feat->lightFlare); } } if (refreshCell && (tileCatalog[feat->tile].flags & (T_IS_FIRE | T_AUTO_DESCENT)) && cellHasTerrainFlag(player.xLoc, player.yLoc, (T_IS_FIRE | T_AUTO_DESCENT))) { applyInstantTileEffectsToCreature(&player); } if (rogue.gameHasEnded) { return succeeded; } // if (succeeded && feat->description[0] && !feat->messageDisplayed && playerCanSee(x, y)) { // feat->messageDisplayed = true; // message(feat->description, false); // } if (succeeded) { if (feat->subsequentDF) { if (feat->flags & DFF_SUBSEQ_EVERYWHERE) { for (i=0; isubsequentDF], refreshCell, abortIfBlocking); } } } } else { spawnDungeonFeature(x, y, &dungeonFeatureCatalog[feat->subsequentDF], refreshCell, abortIfBlocking); } } if (feat->tile && (tileCatalog[feat->tile].flags & (T_IS_DEEP_WATER | T_LAVA_INSTA_DEATH | T_AUTO_DESCENT))) { rogue.updatedMapToShoreThisTurn = false; } // awaken dormant creatures? if (feat->flags & DFF_ACTIVATE_DORMANT_MONSTER) { for (monst = dormantMonsters->nextCreature; monst != NULL; monst = monst->nextCreature) { if (monst->xLoc == x && monst->yLoc == y || blockingMap[monst->xLoc][monst->yLoc]) { // found it! toggleMonsterDormancy(monst); monst = dormantMonsters; } } } } return succeeded; } void restoreMonster(creature *monst, short **mapToStairs, short **mapToPit) { short i, *x, *y, turnCount;//, loc[2]; creature *leader; boolean foundLeader = false; short **theMap; enum directions dir; x = &(monst->xLoc); y = &(monst->yLoc); if (monst->status[STATUS_ENTERS_LEVEL_IN] > 0) { if (monst->bookkeepingFlags & (MB_APPROACHING_PIT)) { theMap = mapToPit; } else { theMap = mapToStairs; } if(rogue.patchVersion >= 3) { pmap[*x][*y].flags &= ~HAS_MONSTER; } if (theMap) { // STATUS_ENTERS_LEVEL_IN accounts for monster speed; convert back to map distance and subtract from distance to stairs turnCount = rogue.patchVersion < 3 ? ((theMap[monst->xLoc][monst->yLoc] * monst->movementSpeed / 100) - monst->status[STATUS_ENTERS_LEVEL_IN]) : (theMap[monst->xLoc][monst->yLoc] - (monst->status[STATUS_ENTERS_LEVEL_IN] * 100 / monst->movementSpeed)); for (i=0; i < turnCount; i++) { if ((dir = nextStep(theMap, monst->xLoc, monst->yLoc, NULL, true)) != NO_DIRECTION) { monst->xLoc += nbDirs[dir][0]; monst->yLoc += nbDirs[dir][1]; } else { break; } } } monst->bookkeepingFlags |= MB_PREPLACED; } if ((pmap[*x][*y].flags & (HAS_PLAYER | HAS_STAIRS)) || (monst->bookkeepingFlags & MB_PREPLACED)) { if (!(monst->bookkeepingFlags & MB_PREPLACED)) { // (If if it's preplaced, it won't have set the HAS_MONSTER flag in the first place, // so clearing it might screw up an existing monster.) pmap[*x][*y].flags &= ~HAS_MONSTER; } getQualifyingPathLocNear(x, y, *x, *y, true, T_DIVIDES_LEVEL & avoidedFlagsForMonster(&(monst->info)), 0, avoidedFlagsForMonster(&(monst->info)), (HAS_MONSTER | HAS_PLAYER | HAS_STAIRS), true); } pmap[*x][*y].flags |= HAS_MONSTER; monst->bookkeepingFlags &= ~(MB_PREPLACED | MB_APPROACHING_DOWNSTAIRS | MB_APPROACHING_UPSTAIRS | MB_APPROACHING_PIT | MB_ABSORBING); monst->status[STATUS_ENTERS_LEVEL_IN] = 0; monst->corpseAbsorptionCounter = 0; if ((monst->bookkeepingFlags & MB_SUBMERGED) && !cellHasTMFlag(*x, *y, TM_ALLOWS_SUBMERGING)) { monst->bookkeepingFlags &= ~MB_SUBMERGED; } if (monst->bookkeepingFlags & MB_FOLLOWER) { // is the leader on the same level? for (leader = monsters->nextCreature; leader != NULL; leader = leader->nextCreature) { if (leader == monst->leader) { foundLeader = true; break; } } // if not, it is time to spread your wings and fly solo if (!foundLeader) { monst->bookkeepingFlags &= ~MB_FOLLOWER; monst->leader = NULL; } } } void restoreItem(item *theItem) { short *x, *y, loc[2]; x = &(theItem->xLoc); y = &(theItem->yLoc); if (theItem->flags & ITEM_PREPLACED) { theItem->flags &= ~ITEM_PREPLACED; getQualifyingLocNear(loc, *x, *y, true, 0, (T_OBSTRUCTS_ITEMS | T_AUTO_DESCENT | T_IS_DEEP_WATER | T_LAVA_INSTA_DEATH), (HAS_MONSTER | HAS_ITEM | HAS_STAIRS), true, false); *x = loc[0]; *y = loc[1]; } pmap[*x][*y].flags |= HAS_ITEM; if (theItem->flags & ITEM_MAGIC_DETECTED && itemMagicPolarity(theItem)) { pmap[*x][*y].flags |= ITEM_DETECTED; } } // Returns true iff the location is a plain wall, three of the four cardinal neighbors are walls, the remaining cardinal neighbor // is not a pathing blocker, the two diagonals between the three cardinal walls are also walls, and none of the eight neighbors are in machines. boolean validStairLoc(short x, short y) { short newX, newY, dir, neighborWallCount; if (x < 1 || x >= DCOLS - 1 || y < 1 || y >= DROWS - 1 || pmap[x][y].layers[DUNGEON] != WALL) { return false; } for (dir=0; dir< DIRECTION_COUNT; dir++) { newX = x + nbDirs[dir][0]; newY = y + nbDirs[dir][1]; if (pmap[newX][newY].flags & IS_IN_MACHINE) { return false; } } neighborWallCount = 0; for (dir=0; dir<4; dir++) { newX = x + nbDirs[dir][0]; newY = y + nbDirs[dir][1]; if (cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)) { // neighbor is a wall neighborWallCount++; } else { // neighbor is not a wall if (cellHasTerrainFlag(newX, newY, T_PATHING_BLOCKER) || passableArcCount(newX, newY) >= 2) { return false; } // now check the two diagonals between the walls newX = x - nbDirs[dir][0] + nbDirs[dir][1]; newY = y - nbDirs[dir][1] + nbDirs[dir][0]; if (!cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)) { return false; } newX = x - nbDirs[dir][0] - nbDirs[dir][1]; newY = y - nbDirs[dir][1] - nbDirs[dir][0]; if (!cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)) { return false; } } } if (neighborWallCount != 3) { return false; } return true; } // The walls on either side become torches. Any adjacent granite then becomes top_wall. All wall neighbors are un-tunnelable. // Grid is zeroed out within 5 spaces in all directions. void prepareForStairs(short x, short y, char grid[DCOLS][DROWS]) { short newX, newY, dir; // Add torches to either side. for (dir=0; dir<4; dir++) { if (!cellHasTerrainFlag(x + nbDirs[dir][0], y + nbDirs[dir][1], T_OBSTRUCTS_PASSABILITY)) { newX = x - nbDirs[dir][1]; newY = y - nbDirs[dir][0]; pmap[newX][newY].layers[DUNGEON] = TORCH_WALL; newX = x + nbDirs[dir][1]; newY = y + nbDirs[dir][0]; pmap[newX][newY].layers[DUNGEON] = TORCH_WALL; break; } } // Expose granite. for (dir=0; dir< DIRECTION_COUNT; dir++) { newX = x + nbDirs[dir][0]; newY = y + nbDirs[dir][1]; if (pmap[newX][newY].layers[DUNGEON] == GRANITE) { pmap[newX][newY].layers[DUNGEON] = WALL; } if (cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)) { pmap[newX][newY].flags |= IMPREGNABLE; } } // Zero out grid in the vicinity. for (newX = max(0, x - 5); newX < min(DCOLS, x + 5); newX++) { for (newY = max(0, y - 5); newY < min(DROWS, y + 5); newY++) { grid[newX][newY] = false; } } } // Places the player, monsters, items and stairs. void initializeLevel() { short i, j, dir; short upLoc[2], downLoc[2], **mapToStairs, **mapToPit; creature *monst; item *theItem; char grid[DCOLS][DROWS]; short n = rogue.depthLevel - 1; // Place the stairs. for (i=0; i < DCOLS; i++) { for (j=0; j < DROWS; j++) { grid[i][j] = validStairLoc(i, j); } } if (D_INSPECT_LEVELGEN) { dumpLevelToScreen(); hiliteCharGrid(grid, &teal, 100); temporaryMessage("Stair location candidates:", true); } if (getQualifyingGridLocNear(downLoc, levels[n].downStairsLoc[0], levels[n].downStairsLoc[1], grid, false)) { prepareForStairs(downLoc[0], downLoc[1], grid); } else { getQualifyingLocNear(downLoc, levels[n].downStairsLoc[0], levels[n].downStairsLoc[1], false, 0, (T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_ITEMS | T_AUTO_DESCENT | T_IS_DEEP_WATER | T_LAVA_INSTA_DEATH | T_IS_DF_TRAP), (HAS_MONSTER | HAS_ITEM | HAS_STAIRS | IS_IN_MACHINE), true, false); } if (rogue.depthLevel == DEEPEST_LEVEL) { pmap[downLoc[0]][downLoc[1]].layers[DUNGEON] = DUNGEON_PORTAL; } else { pmap[downLoc[0]][downLoc[1]].layers[DUNGEON] = DOWN_STAIRS; } pmap[downLoc[0]][downLoc[1]].layers[LIQUID] = NOTHING; pmap[downLoc[0]][downLoc[1]].layers[SURFACE] = NOTHING; if (!levels[n+1].visited) { levels[n+1].upStairsLoc[0] = downLoc[0]; levels[n+1].upStairsLoc[1] = downLoc[1]; } levels[n].downStairsLoc[0] = downLoc[0]; levels[n].downStairsLoc[1] = downLoc[1]; if (getQualifyingGridLocNear(upLoc, levels[n].upStairsLoc[0], levels[n].upStairsLoc[1], grid, false)) { prepareForStairs(upLoc[0], upLoc[1], grid); } else { // Hopefully this never happens. getQualifyingLocNear(upLoc, levels[n].upStairsLoc[0], levels[n].upStairsLoc[1], false, 0, (T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_ITEMS | T_AUTO_DESCENT | T_IS_DEEP_WATER | T_LAVA_INSTA_DEATH | T_IS_DF_TRAP), (HAS_MONSTER | HAS_ITEM | HAS_STAIRS | IS_IN_MACHINE), true, false); } levels[n].upStairsLoc[0] = upLoc[0]; levels[n].upStairsLoc[1] = upLoc[1]; if (rogue.depthLevel == 1) { pmap[upLoc[0]][upLoc[1]].layers[DUNGEON] = DUNGEON_EXIT; } else { pmap[upLoc[0]][upLoc[1]].layers[DUNGEON] = UP_STAIRS; } pmap[upLoc[0]][upLoc[1]].layers[LIQUID] = NOTHING; pmap[upLoc[0]][upLoc[1]].layers[SURFACE] = NOTHING; rogue.downLoc[0] = downLoc[0]; rogue.downLoc[1] = downLoc[1]; pmap[downLoc[0]][downLoc[1]].flags |= HAS_STAIRS; rogue.upLoc[0] = upLoc[0]; rogue.upLoc[1] = upLoc[1]; pmap[upLoc[0]][upLoc[1]].flags |= HAS_STAIRS; if (!levels[rogue.depthLevel-1].visited) { // Run a field of view check from up stairs so that monsters do not spawn within sight of it. for (dir=0; dir<4; dir++) { if (coordinatesAreInMap(upLoc[0] + nbDirs[dir][0], upLoc[1] + nbDirs[dir][1]) && !cellHasTerrainFlag(upLoc[0] + nbDirs[dir][0], upLoc[1] + nbDirs[dir][1], T_OBSTRUCTS_PASSABILITY)) { upLoc[0] += nbDirs[dir][0]; upLoc[1] += nbDirs[dir][1]; break; } } zeroOutGrid(grid); getFOVMask(grid, upLoc[0], upLoc[1], max(DCOLS, DROWS) * FP_FACTOR, (T_OBSTRUCTS_VISION), 0, false); for (i=0; inextItem; theItem != NULL; theItem = theItem->nextItem) { restoreItem(theItem); } // Restore creatures that fell from the previous depth or that have been pathing toward the stairs. mapToStairs = allocGrid(); fillGrid(mapToStairs, 0); mapToPit = allocGrid(); fillGrid(mapToPit, 0); calculateDistances(mapToStairs, player.xLoc, player.yLoc, T_PATHING_BLOCKER, NULL, true, true); calculateDistances(mapToPit, levels[rogue.depthLevel - 1].playerExitedVia[0], levels[rogue.depthLevel - 1].playerExitedVia[1], T_PATHING_BLOCKER, NULL, true, true); for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) { restoreMonster(monst, mapToStairs, mapToPit); } freeGrid(mapToStairs); freeGrid(mapToPit); } // fills (*x, *y) with the coordinates of a random cell with // no creatures, items or stairs and with either a matching liquid and dungeon type // or at least one layer of type terrainType. // A dungeon, liquid type of -1 will match anything. boolean randomMatchingLocation(short *x, short *y, short dungeonType, short liquidType, short terrainType) { short failsafeCount = 0; do { failsafeCount++; *x = rand_range(0, DCOLS - 1); *y = rand_range(0, DROWS - 1); } while (failsafeCount < 500 && ((terrainType >= 0 && !cellHasTerrainType(*x, *y, terrainType)) || (((dungeonType >= 0 && pmap[*x][*y].layers[DUNGEON] != dungeonType) || (liquidType >= 0 && pmap[*x][*y].layers[LIQUID] != liquidType)) && terrainType < 0) || (pmap[*x][*y].flags & (HAS_PLAYER | HAS_MONSTER | HAS_STAIRS | HAS_ITEM | IS_IN_MACHINE)) || (terrainType < 0 && !(tileCatalog[dungeonType].flags & T_OBSTRUCTS_ITEMS) && cellHasTerrainFlag(*x, *y, T_OBSTRUCTS_ITEMS)))); if (failsafeCount >= 500) { return false; } return true; }