/*
* 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 <http://www.gnu.org/licenses/>.
*/
#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<DCOLS; i++) {
for(j=0; j<DROWS; j++) {
if (cellHasTerrainFlag(i, j, T_PATHING_BLOCKER)
&& !cellHasTMFlag(i, j, TM_IS_SECRET)) {
pmap[i][j].flags &= ~IN_LOOP;
passMap[i][j] = false;
} else {
pmap[i][j].flags |= IN_LOOP;
passMap[i][j] = true;
}
}
}
for(i=0; i<DCOLS; i++) {
for(j=0; j<DROWS; j++) {
checkLoopiness(i, j);
}
}
// remove extraneous loop markings
zeroOutGrid(grid);
auditLoop(0, 0, grid);
for(i=0; i<DCOLS; i++) {
for(j=0; j<DROWS; j++) {
if (pmap[i][j].flags & IN_LOOP) {
designationSurvives = false;
for (dir = 0; dir < DIRECTION_COUNT; dir++) {
newX = i + nbDirs[dir][0];
newY = j + nbDirs[dir][1];
if (coordinatesAreInMap(newX, newY)
&& !grid[newX][newY]
&& !(pmap[newX][newY].flags & IN_LOOP)) {
designationSurvives = true;
break;
}
}
if (!designationSurvives) {
grid[i][j] = true;
pmap[i][j].flags &= ~IN_LOOP;
}
}
}
}
// done finding loops; now flag chokepoints
for(i=1; i<DCOLS-1; i++) {
for(j=1; j<DROWS-1; j++) {
pmap[i][j].flags &= ~IS_CHOKEPOINT;
if (passMap[i][j] && !(pmap[i][j].flags & IN_LOOP)) {
passableArcCount = 0;
for (dir = 0; dir < DIRECTION_COUNT; dir++) {
oldX = i + cDirs[(dir + 7) % 8][0];
oldY = j + cDirs[(dir + 7) % 8][1];
newX = i + cDirs[dir][0];
newY = j + cDirs[dir][1];
if ((coordinatesAreInMap(newX, newY) && passMap[newX][newY])
!= (coordinatesAreInMap(oldX, oldY) && passMap[oldX][oldY])) {
if (++passableArcCount > 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<DCOLS; i++) {
for(j=0; j<DROWS; j++) {
chokeMap[i][j] = 30000;
if (pmap[i][j].flags & IS_IN_ROOM_MACHINE) {
passMap[i][j] = false;
}
}
}
// Scan through and find a chokepoint next to an open point.
for(i=0; i<DCOLS; i++) {
for(j=0; j<DROWS; j++) {
if (passMap[i][j] && (pmap[i][j].flags & IS_CHOKEPOINT)) {
for (dir=0; dir<4; dir++) {
newX = i + nbDirs[dir][0];
newY = j + nbDirs[dir][1];
if (coordinatesAreInMap(newX, newY)
&& passMap[newX][newY]
&& !(pmap[newX][newY].flags & IS_CHOKEPOINT)) {
// OK, (newX, newY) is an open point and (i, j) is a chokepoint.
// Pretend (i, j) is blocked by changing passMap, and run a flood-fill cell count starting on (newX, newY).
// Keep track of the flooded region in grid[][].
zeroOutGrid(grid);
passMap[i][j] = false;
cellCount = floodFillCount(grid, passMap, newX, newY);
passMap[i][j] = true;
// CellCount is the size of the region that would be obstructed if the chokepoint were blocked.
// CellCounts less than 4 are not useful, so we skip those cases.
if (cellCount >= 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<DCOLS; i2++) {
for(j2=0; j2<DROWS; j2++) {
if (grid[i2][j2] && cellCount < chokeMap[i2][j2]) {
chokeMap[i2][j2] = cellCount;
pmap[i2][j2].flags &= ~IS_GATE_SITE;
}
}
}
// The chokepoint itself should also take the lesser of its current value or the flood count.
if (cellCount < chokeMap[i][j]) {
chokeMap[i][j] = cellCount;
pmap[i][j].flags |= IS_GATE_SITE;
}
}
}
}
}
}
}
}
}
// Add some loops to the otherwise simply connected network of rooms.
void addLoops(short **grid, short minimumPathingDistance) {
short newX, newY, oppX, oppY;
short **pathMap, **costMap;
short i, d, x, y, sCoord[DCOLS*DROWS];
const short dirCoords[2][2] = {{1, 0}, {0, 1}};
fillSequentialList(sCoord, DCOLS*DROWS);
shuffleList(sCoord, DCOLS*DROWS);
if (D_INSPECT_LEVELGEN) {
colorOverDungeon(&darkGray);
hiliteGrid(grid, &white, 100);
}
pathMap = allocGrid();
costMap = allocGrid();
copyGrid(costMap, grid);
findReplaceGrid(costMap, 0, 0, PDS_OBSTRUCTION);
findReplaceGrid(costMap, 1, 30000, 1);
for (i = 0; i < DCOLS*DROWS; i++) {
x = sCoord[i]/DROWS;
y = sCoord[i] % DROWS;
if (!grid[x][y]) {
for (d=0; d <= 1; d++) { // Try a horizontal door, and then a vertical door.
newX = x + dirCoords[d][0];
oppX = x - dirCoords[d][0];
newY = y + dirCoords[d][1];
oppY = y - dirCoords[d][1];
if (coordinatesAreInMap(newX, newY)
&& coordinatesAreInMap(oppX, oppY)
&& grid[newX][newY] > 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; i<DCOLS; i++) {
for(j=0; j<DROWS; j++) {
to[i][j] = from[i][j];
}
}
}
boolean itemIsADuplicate(item *theItem, item **spawnedItems, short itemCount) {
short i;
if (theItem->category & (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; i<MACHINES_BUFFER_LENGTH && spawnedItems[i]; i++) {
removeItemFromChain(spawnedItems[i], floorItems);
removeItemFromChain(spawnedItems[i], packItems); // just in case; can't imagine why this would arise.
for (j=0; j<MACHINES_BUFFER_LENGTH && spawnedMonsters[j]; j++) {
// Remove the item from spawned monsters, so it doesn't get double-freed when the creature is killed below.
if (spawnedMonsters[j]->carriedItem == spawnedItems[i]) {
spawnedMonsters[j]->carriedItem = NULL;
break;
}
}
deleteItem(spawnedItems[i]);
spawnedItems[i] = NULL;
}
for (i=0; i<MACHINES_BUFFER_LENGTH && spawnedMonsters[i]; i++) {
killCreature(spawnedMonsters[i], true);
spawnedMonsters[i] = NULL;
}
}
boolean cellIsFeatureCandidate(short x, short y,
short originX, short originY,
short distanceBound[2],
char interior[DCOLS][DROWS],
char occupied[DCOLS][DROWS],
char viewMap[DCOLS][DROWS],
short **distanceMap,
short machineNumber,
unsigned long featureFlags,
unsigned long bpFlags) {
short newX, newY, dir, distance;
// No building in the hallway if it's prohibited.
// This check comes before the origin check, so an area machine will fail altogether
// if its origin is in a hallway and the feature that must be built there does not permit as much.
if ((featureFlags & MF_NOT_IN_HALLWAY)
&& passableArcCount(x, y) > 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<DCOLS-1; i++) {
for(j=1; j < DROWS-1; j++) {
if (cellHasTerrainFlag(i, j, T_PATHING_BLOCKER)
&& pmap[i][j].machineNumber == 0) {
// Count up the number of interior open 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_PATHING_BLOCKER)) {
nbcount++;
}
}
if (nbcount >= 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<DCOLS; i++) {
for(j=0; j<DROWS; j++) {
if (costMap[i][j] == 1 && (pmap[i][j].flags & IS_IN_MACHINE)) { //pmap[i][j].machineNumber) {
costMap[i][j] = PDS_FORBIDDEN;
}
}
}
costMap[originX][originY] = 1;
dijkstraScan(distanceMap, costMap, false);
freeGrid(costMap);
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(sCols, DCOLS);
shuffleList(sCols, DCOLS);
fillSequentialList(sRows, DROWS);
shuffleList(sRows, DROWS);
for (k=0; k<1000 && qualifyingTileCount < totalFreq; k++) {
for(i=0; i<DCOLS && qualifyingTileCount < totalFreq; i++) {
for(j=0; j<DROWS && qualifyingTileCount < totalFreq; j++) {
if (distanceMap[sCols[i]][sRows[j]] == k) {
interior[sCols[i]][sRows[j]] = true;
qualifyingTileCount++;
if (pmap[sCols[i]][sRows[j]].flags & HAS_ITEM) {
// Abort if we've engulfed another machine's item.
success = false;
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(interior, false)) {
success = false;
} else if ((blueprintCatalog[bp].flags & BP_REQUIRE_BLOCKING)
&& levelIsDisconnectedWithBlockingMap(interior, true) < 100) {
success = false;
}
freeGrid(distanceMap);
return success;
}
void redesignInterior(char interior[DCOLS][DROWS], short originX, short originY, short theProfileIndex) {
short i, j, n, newX, newY;
enum directions dir;
short orphanList[20][2];
short orphanCount = 0;
short **grid, **pathingGrid, **costGrid;
grid = allocGrid();
for (i=0; i<DCOLS; i++) {
for (j=0; j<DROWS; j++) {
if (interior[i][j]) {
if (i == originX && j == originY) {
grid[i][j] = 1; // All rooms must grow from this space.
} else {
grid[i][j] = 0; // Other interior squares are fair game for placing rooms.
}
} else if (cellIsPassableOrDoor(i, j)) {
grid[i][j] = 1; // Treat existing level as already built (though shielded by a film of -1s).
for (dir = 0; dir < 4; dir++) {
newX = i + nbDirs[dir][0];
newY = j + nbDirs[dir][1];
if (coordinatesAreInMap(newX, newY)
&& interior[newX][newY]
&& (newX != originX || newY != originY)) {
orphanList[orphanCount][0] = newX;
orphanList[orphanCount][1] = newY;
orphanCount++;
grid[i][j] = -1; // Treat the orphaned door as off limits.
break;
}
}
} else {
grid[i][j] = -1; // Exterior spaces are off limits.
}
}
}
attachRooms(grid, &dungeonProfileCatalog[theProfileIndex], 40, 40);
// Connect to preexisting rooms that were orphaned (mostly preexisting machine rooms).
if (orphanCount > 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<DCOLS; i++) {
for (j=0; j<DROWS; j++) {
if (interior[i][j]) {
if (grid[i][j] > 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<DCOLS; i++) {
for(j=0; j<DROWS; j++) {
if (interior[i][j]) {
if (grid[i][j] >= 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; i<DCOLS; i++) {
for(j=0; j<DROWS; j++) {
if (interior[i][j]) {
for (layer=0; layer<NUMBER_TERRAIN_LAYERS; layer++) {
pmap[i][j].layers[layer] = (layer == DUNGEON ? FLOOR : NOTHING);
}
}
}
}
}
// If requested, purge pathing blockers -- no traps allowed.
if (flags & BP_PURGE_PATHING_BLOCKERS) {
for(i=0; i<DCOLS; i++) {
for(j=0; j<DROWS; j++) {
if (interior[i][j]) {
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);
}
}
}
}
}
}
// If requested, purge the liquid layer in the interior -- no liquids allowed.
if (flags & BP_PURGE_LIQUIDS) {
for(i=0; i<DCOLS; i++) {
for(j=0; j<DROWS; j++) {
if (interior[i][j]) {
pmap[i][j].layers[LIQUID] = NOTHING;
}
}
}
}
// Surround with walls if requested.
if (flags & BP_SURROUND_WITH_WALLS) {
for(i=0; i<DCOLS; i++) {
for(j=0; j<DROWS; j++) {
if (interior[i][j] && !(pmap[i][j].flags & IS_GATE_SITE)) {
for (dir=0; dir< DIRECTION_COUNT; dir++) {
newX = i + nbDirs[dir][0];
newY = j + nbDirs[dir][1];
if (coordinatesAreInMap(newX, newY)
&& !interior[newX][newY]
&& !cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)
&& !(pmap[newX][newY].flags & IS_GATE_SITE)
&& !pmap[newX][newY].machineNumber
&& cellHasTerrainFlag(newX, newY, T_PATHING_BLOCKER)) {
for (layer=0; layer<NUMBER_TERRAIN_LAYERS; layer++) {
pmap[newX][newY].layers[layer] = (layer == DUNGEON ? WALL : 0);
}
}
}
}
}
}
}
// Completely clear the interior, fill with granite, and cut entirely new rooms into it from the gate site.
// Then zero out any portion of the interior that is still wall.
if (flags & BP_REDESIGN_INTERIOR) {
redesignInterior(interior, originX, originY, dungeonProfileIndex);
}
// Reinforce surrounding tiles and interior tiles if requested to prevent tunneling in or through.
if (flags & BP_IMPREGNABLE) {
for(i=0; i<DCOLS; i++) {
for(j=0; j<DROWS; j++) {
if (interior[i][j]
&& !(pmap[i][j].flags & IS_GATE_SITE)) {
pmap[i][j].flags |= IMPREGNABLE;
for (dir=0; dir< DIRECTION_COUNT; dir++) {
newX = i + nbDirs[dir][0];
newY = j + nbDirs[dir][1];
if (coordinatesAreInMap(newX, newY)
&& !interior[newX][newY]
&& !(pmap[newX][newY].flags & IS_GATE_SITE)) {
pmap[newX][newY].flags |= IMPREGNABLE;
}
}
}
}
}
}
}
typedef struct machineData {
// Our boolean grids:
char interior[DCOLS][DROWS]; // This is the master grid for the machine. All area inside the machine are set to true.
char occupied[DCOLS][DROWS]; // This keeps track of what is within the personal space of a previously built feature in the same machine.
char candidates[DCOLS][DROWS]; // This is calculated at the start of each feature, and is true where that feature is eligible for building.
char blockingMap[DCOLS][DROWS]; // Used during terrain/DF placement in features that are flagged not to tolerate blocking, to see if they block.
char viewMap[DCOLS][DROWS]; // Used for features with MF_IN_VIEW_OF_ORIGIN, to calculate which cells are in view of the origin.
pcell levelBackup[DCOLS][DROWS];
item *spawnedItems[MACHINES_BUFFER_LENGTH];
item *spawnedItemsSub[MACHINES_BUFFER_LENGTH];
creature *spawnedMonsters[MACHINES_BUFFER_LENGTH];
creature *spawnedMonstersSub[MACHINES_BUFFER_LENGTH];
short gateCandidates[50][2];
short distances[100];
short sRows[DROWS];
short sCols[DCOLS];
} machineData;
// Returns true if the machine got built; false if it was aborted.
// If empty array parentSpawnedItems or parentSpawnedMonsters is given, will pass those back for deletion if necessary.
boolean buildAMachine(enum machineTypes bp,
short originX, short originY,
unsigned long requiredMachineFlags,
item *adoptiveItem,
item *parentSpawnedItems[MACHINES_BUFFER_LENGTH],
creature *parentSpawnedMonsters[MACHINES_BUFFER_LENGTH]) {
short i, j, k, feat, randIndex, totalFreq, instance, instanceCount = 0,
featX, featY, itemCount, monsterCount, qualifyingTileCount,
**distanceMap = NULL, distance25, distance75, distanceBound[2],
personalSpace, failsafe, locationFailsafe,
machineNumber;
const unsigned long alternativeFlags[2] = {MF_ALTERNATIVE, MF_ALTERNATIVE_2};
boolean DFSucceeded, terrainSucceeded, generateEverywhere, chooseBP,
chooseLocation, tryAgain, success = false, skipFeature[20];
creature *monst = NULL, *nextMonst, *torchBearer = NULL, *leader = NULL;
item *theItem = NULL, *torch = NULL;
const machineFeature *feature;
machineData *p = malloc(sizeof(machineData));
memset(p, 0, sizeof(machineData));
chooseBP = (((signed short) bp) <= 0 ? true : false);
chooseLocation = (originX <= 0 || originY <= 0 ? true : false);
failsafe = 10;
do {
tryAgain = false;
if (--failsafe <= 0) {
if (distanceMap) {
freeGrid(distanceMap);
}
if (D_MESSAGE_MACHINE_GENERATION) {
if (chooseBP || chooseLocation) {
printf("\nDepth %i: Failed to build a machine; gave up after 10 unsuccessful attempts to find a suitable blueprint and/or location.",
rogue.depthLevel);
} else {
printf("\nDepth %i: Failed to build a machine; requested blueprint and location did not work.",
rogue.depthLevel);
}
}
free(p);
return false;
}
if (chooseBP) { // If no blueprint is given, then pick one:
// First, choose the blueprint. We choose from among blueprints
// that have the required blueprint flags and that satisfy the depth requirements.
totalFreq = 0;
for (i=1; i<NUMBER_BLUEPRINTS; i++) {
if (blueprintQualifies(i, requiredMachineFlags)) {
totalFreq += blueprintCatalog[i].frequency;
}
}
if (!totalFreq) { // If no suitable blueprints are in the library, fail.
if (distanceMap) {
freeGrid(distanceMap);
}
if (D_MESSAGE_MACHINE_GENERATION) printf("\nDepth %i: Failed to build a machine because no suitable blueprints were available.",
rogue.depthLevel);
free(p);
return false;
}
// Pick from among the suitable blueprints.
randIndex = rand_range(1, totalFreq);
for (i=1; i<NUMBER_BLUEPRINTS; i++) {
if (blueprintQualifies(i, requiredMachineFlags)) {
if (randIndex <= blueprintCatalog[i].frequency) {
bp = i;
break;
} else {
randIndex -= blueprintCatalog[i].frequency;
}
}
}
// If we don't have a blueprint yet, something went wrong.
brogueAssert(bp>0);
}
// 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<DCOLS; i++) {
for(j=0; j<DROWS && totalFreq < 50; j++) {
if ((pmap[i][j].flags & IS_GATE_SITE)
&& !(pmap[i][j].flags & IS_IN_MACHINE)
&& chokeMap[i][j] >= 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; i<DCOLS && qualifyingTileCount < totalFreq; i++) {
for(j=0; j<DROWS && qualifyingTileCount < totalFreq; j++) {
if (distanceMap[p->sCols[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; i<DCOLS; i++) {
for(j=0; j<DROWS; j++) {
if (p->interior[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; i<DCOLS; i++) {
for(j=0; j<DROWS; j++) {
if (p->interior[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<blueprintCatalog[bp].featureCount; i++) {
skipFeature[i] = false;
}
for (j = 0; j <= 1; j++) {
totalFreq = 0;
for (i=0; i<blueprintCatalog[bp].featureCount; i++) {
if (blueprintCatalog[bp].feature[i].flags & alternativeFlags[j]) {
skipFeature[i] = true;
totalFreq++;
}
}
if (totalFreq > 0) {
randIndex = rand_range(1, totalFreq);
for (i=0; i<blueprintCatalog[bp].featureCount; i++) {
if (blueprintCatalog[bp].feature[i].flags & alternativeFlags[j]) {
if (randIndex == 1) {
skipFeature[i] = false; // This is the alternative that gets built. The rest do not.
break;
} else {
randIndex--;
}
}
}
}
}
// Keep track of all monsters and items that we spawn -- if we abort, we have to go back and delete them all.
itemCount = monsterCount = 0;
// Zero out occupied[][], and use it to keep track of the personal space around each feature that gets placed.
zeroOutGrid(p->occupied);
// 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; i<DCOLS; i++) {
for(j=0; j<DROWS; j++) {
if (cellIsFeatureCandidate(i, j,
originX, originY,
distanceBound,
p->interior, 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; i<DCOLS && featX < 0; i++) {
for(j=0; j<DROWS && featX < 0; j++) {
if (p->candidates[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; j<MACHINES_BUFFER_LENGTH && p->spawnedItemsSub[j]; j++) {
p->spawnedItems[itemCount] = p->spawnedItemsSub[j];
itemCount++;
p->spawnedItemsSub[j] = NULL;
}
for (j=0; j<MACHINES_BUFFER_LENGTH && p->spawnedMonstersSub[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; i<DCOLS; i++) {
for(j=0; j<DROWS; j++) {
if (pmap[i][j].machineNumber == machineNumber
&& !cellHasTMFlag(i, j, (TM_IS_WIRED | TM_IS_CIRCUIT_BREAKER))) {
pmap[i][j].flags &= ~IS_IN_MACHINE;
pmap[i][j].machineNumber = 0;
}
}
}
}
if (torchBearer && torch) {
if (torchBearer->carriedItem) {
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; i<itemCount; i++) {
parentSpawnedItems[i] = p->spawnedItems[i];
}
}
if (parentSpawnedMonsters) {
for (i=0; i<monsterCount; i++) {
parentSpawnedMonsters[i] = p->spawnedMonsters[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; AG<NUMBER_AUTOGENERATORS; AG++) {
// Shortcut:
gen = &(autoGeneratorCatalog[AG]);
if (gen->machine > 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<DCOLS-1; i++) {
for (j=0; j<DROWS-1; j++) {
for (k=0; k<=1; k++) {
if (!(tileCatalog[pmap[i + k][j].layers[DUNGEON]].flags & T_OBSTRUCTS_PASSABILITY)
&& (tileCatalog[pmap[i + (1-k)][j].layers[DUNGEON]].flags & T_OBSTRUCTS_PASSABILITY)
&& (tileCatalog[pmap[i + (1-k)][j].layers[DUNGEON]].flags & T_OBSTRUCTS_DIAGONAL_MOVEMENT)
&& (tileCatalog[pmap[i + k][j+1].layers[DUNGEON]].flags & T_OBSTRUCTS_PASSABILITY)
&& (tileCatalog[pmap[i + k][j+1].layers[DUNGEON]].flags & T_OBSTRUCTS_DIAGONAL_MOVEMENT)
&& !(tileCatalog[pmap[i + (1-k)][j+1].layers[DUNGEON]].flags & T_OBSTRUCTS_PASSABILITY)) {
if (rand_percent(50)) {
x1 = i + (1-k);
x2 = i + k;
y1 = j;
} else {
x1 = i + k;
x2 = i + (1-k);
y1 = j + 1;
}
if (!(pmap[x1][y1].flags & HAS_MONSTER) && pmap[x1][y1].machineNumber == 0) {
diagonalCornerRemoved = true;
for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
pmap[x1][y1].layers[layer] = pmap[x2][y1].layers[layer];
}
}
}
}
}
}
} while (diagonalCornerRemoved == true);
}
void insertRoomAt(short **dungeonMap, short **roomMap, const short roomToDungeonX, const short roomToDungeonY, const short xRoom, const short yRoom) {
short newX, newY;
enum directions dir;
brogueAssert(coordinatesAreInMap(xRoom + roomToDungeonX, yRoom + roomToDungeonY));
dungeonMap[xRoom + roomToDungeonX][yRoom + roomToDungeonY] = 1;
for (dir = 0; dir < 4; dir++) {
newX = xRoom + nbDirs[dir][0];
newY = yRoom + nbDirs[dir][1];
if (coordinatesAreInMap(newX, newY)
&& roomMap[newX][newY]
&& coordinatesAreInMap(newX + roomToDungeonX, newY + roomToDungeonY)
&& dungeonMap[newX + roomToDungeonX][newY + roomToDungeonY] == 0) {
insertRoomAt(dungeonMap, roomMap, roomToDungeonX, roomToDungeonY, newX, newY);
}
}
}
void designCavern(short **grid, short minWidth, short maxWidth, short minHeight, short maxHeight) {
short destX, destY;
short caveX, caveY, caveWidth, caveHeight;
short fillX = 0, fillY = 0;
boolean foundFillPoint = false;
short **blobGrid;
blobGrid = allocGrid();
fillGrid(grid, 0);
createBlobOnGrid(blobGrid,
&caveX, &caveY, &caveWidth, &caveHeight,
5, minWidth, minHeight, maxWidth, maxHeight, 55, "ffffffttt", "ffffttttt");
// colorOverDungeon(&darkGray);
// hiliteGrid(blobGrid, &tanColor, 80);
// temporaryMessage("Here's the cave:", true);
// Position the new cave in the middle of the grid...
destX = (DCOLS - caveWidth) / 2;
destY = (DROWS - caveHeight) / 2;
// ...pick a floodfill insertion point...
for (fillX = 0; fillX < DCOLS && !foundFillPoint; fillX++) {
for (fillY = 0; fillY < DROWS && !foundFillPoint; fillY++) {
if (blobGrid[fillX][fillY]) {
foundFillPoint = true;
}
}
}
// ...and copy it to the master grid.
insertRoomAt(grid, blobGrid, destX - caveX, destY - caveY, fillX, fillY);
freeGrid(blobGrid);
}
// This is a special room that appears at the entrance to the dungeon on depth 1.
void designEntranceRoom(short **grid) {
short roomWidth, roomHeight, roomWidth2, roomHeight2, roomX, roomY, roomX2, roomY2;
fillGrid(grid, 0);
roomWidth = 8;
roomHeight = 10;
roomWidth2 = 20;
roomHeight2 = 5;
roomX = DCOLS/2 - roomWidth/2 - 1;
roomY = DROWS - roomHeight - 2;
roomX2 = DCOLS/2 - roomWidth2/2 - 1;
roomY2 = DROWS - roomHeight2 - 2;
drawRectangleOnGrid(grid, roomX, roomY, roomWidth, roomHeight, 1);
drawRectangleOnGrid(grid, roomX2, roomY2, roomWidth2, roomHeight2, 1);
}
void designCrossRoom(short **grid) {
short roomWidth, roomHeight, roomWidth2, roomHeight2, roomX, roomY, roomX2, roomY2;
fillGrid(grid, 0);
roomWidth = rand_range(3, 12);
roomX = rand_range(max(0, DCOLS/2 - (roomWidth - 1)), min(DCOLS, DCOLS/2));
roomWidth2 = rand_range(4, 20);
roomX2 = (roomX + (roomWidth / 2) + rand_range(0, 2) + rand_range(0, 2) - 3) - (roomWidth2 / 2);
roomHeight = rand_range(3, 7);
roomY = (DROWS/2 - roomHeight);
roomHeight2 = rand_range(2, 5);
roomY2 = (DROWS/2 - roomHeight2 - (rand_range(0, 2) + rand_range(0, 1)));
drawRectangleOnGrid(grid, roomX - 5, roomY + 5, roomWidth, roomHeight, 1);
drawRectangleOnGrid(grid, roomX2 - 5, roomY2 + 5, roomWidth2, roomHeight2, 1);
}
void designSymmetricalCrossRoom(short **grid) {
short majorWidth, majorHeight, minorWidth, minorHeight;
fillGrid(grid, 0);
majorWidth = rand_range(4, 8);
majorHeight = rand_range(4, 5);
minorWidth = rand_range(3, 4);
if (majorHeight % 2 == 0) {
minorWidth -= 1;
}
minorHeight = 3;//rand_range(2, 3);
if (majorWidth % 2 == 0) {
minorHeight -= 1;
}
drawRectangleOnGrid(grid, (DCOLS - majorWidth)/2, (DROWS - minorHeight)/2, majorWidth, minorHeight, 1);
drawRectangleOnGrid(grid, (DCOLS - minorWidth)/2, (DROWS - majorHeight)/2, minorWidth, majorHeight, 1);
}
void designSmallRoom(short **grid) {
short width, height;
fillGrid(grid, 0);
width = rand_range(3, 6);
height = rand_range(2, 4);
drawRectangleOnGrid(grid, (DCOLS - width) / 2, (DROWS - height) / 2, width, height, 1);
}
void designCircularRoom(short **grid) {
short radius;
if (rand_percent(5)) {
radius = rand_range(4, 10);
} else {
radius = rand_range(2, 4);
}
fillGrid(grid, 0);
drawCircleOnGrid(grid, DCOLS/2, DROWS/2, radius, 1);
if (radius > 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<chunkCount;) {
x = rand_range(minX, maxX);
y = rand_range(minY, maxY);
if (grid[x][y]) {
// colorOverDungeon(&darkGray);
// hiliteGrid(grid, &white, 100);
drawCircleOnGrid(grid, x, y, 2, 1);
i++;
minX = max(1, min(x - 3, minX));
maxX = min(DCOLS - 2, max(x + 3, maxX));
minY = max(1, min(y - 3, minY));
maxY = min(DROWS - 2, max(y + 3, maxY));
// hiliteGrid(grid, &green, 50);
// temporaryMessage("Added a chunk:", true);
}
}
}
// If the indicated tile is a wall on the room stored in grid, and it could be the site of
// a door out of that room, then return the outbound direction that the door faces.
// Otherwise, return NO_DIRECTION.
enum directions directionOfDoorSite(short **grid, short x, short y) {
enum directions dir, solutionDir;
short newX, newY, oppX, oppY;
if (grid[x][y]) { // Already occupied
return NO_DIRECTION;
}
solutionDir = NO_DIRECTION;
for (dir=0; dir<4; dir++) {
newX = x + nbDirs[dir][0];
newY = y + nbDirs[dir][1];
oppX = x - nbDirs[dir][0];
oppY = y - nbDirs[dir][1];
if (coordinatesAreInMap(oppX, oppY)
&& coordinatesAreInMap(newX, newY)
&& grid[oppX][oppY] == 1) {
// This grid cell would be a valid tile on which to place a door that, facing outward, points dir.
if (solutionDir != NO_DIRECTION) {
// Already claimed by another direction; no doors here!
return NO_DIRECTION;
}
solutionDir = dir;
}
}
return solutionDir;
}
void chooseRandomDoorSites(short **roomMap, short doorSites[4][2]) {
short i, j, k, newX, newY;
enum directions dir;
short **grid;
boolean doorSiteFailed;
grid = allocGrid();
copyGrid(grid, roomMap);
// colorOverDungeon(&darkGray);
// hiliteGrid(grid, &blue, 100);
// temporaryMessage("Generating this room:", true);
// const char dirChars[] = "^v<>";
for (i=0; i<DCOLS; i++) {
for (j=0; j<DROWS; j++) {
if (!grid[i][j]) {
dir = directionOfDoorSite(roomMap, i, j);
if (dir != NO_DIRECTION) {
// Trace a ray 10 spaces outward from the door site to make sure it doesn't intersect the room.
// If it does, it's not a valid door site.
newX = i + nbDirs[dir][0];
newY = j + nbDirs[dir][1];
doorSiteFailed = false;
for (k=0; k<10 && coordinatesAreInMap(newX, newY) && !doorSiteFailed; k++) {
if (grid[newX][newY]) {
doorSiteFailed = true;
}
newX += nbDirs[dir][0];
newY += nbDirs[dir][1];
}
if (!doorSiteFailed) {
// plotCharWithColor(dirChars[dir], mapToWindowX(i), mapToWindowY(j), &black, &green);
grid[i][j] = dir + 2; // So as not to conflict with 0 or 1, which are used to indicate exterior/interior.
}
}
}
}
}
// temporaryMessage("Door candidates:", true);
// Pick four doors, one in each direction, and store them in doorSites[dir].
for (dir=0; dir<4; dir++) {
randomLocationInGrid(grid, &(doorSites[dir][0]), &(doorSites[dir][1]), dir + 2);
}
freeGrid(grid);
}
void attachHallwayTo(short **grid, short doorSites[4][2]) {
short i, x, y, newX, newY, dirs[4];
short length;
enum directions dir, dir2;
boolean allowObliqueHallwayExit;
// Pick a direction.
fillSequentialList(dirs, 4);
shuffleList(dirs, 4);
for (i=0; i<4; i++) {
dir = dirs[i];
if (doorSites[dir][0] != -1
&& doorSites[dir][1] != -1
&& coordinatesAreInMap(doorSites[dir][0] + nbDirs[dir][0] * HORIZONTAL_CORRIDOR_MAX_LENGTH,
doorSites[dir][1] + nbDirs[dir][1] * VERTICAL_CORRIDOR_MAX_LENGTH)) {
break; // That's our direction!
}
}
if (i==4) {
return; // No valid direction for hallways.
}
if (dir == UP || dir == DOWN) {
length = rand_range(VERTICAL_CORRIDOR_MIN_LENGTH, VERTICAL_CORRIDOR_MAX_LENGTH);
} else {
length = rand_range(HORIZONTAL_CORRIDOR_MIN_LENGTH, HORIZONTAL_CORRIDOR_MAX_LENGTH);
}
x = doorSites[dir][0];
y = doorSites[dir][1];
for (i = 0; i < length; i++) {
if (coordinatesAreInMap(x, y)) {
grid[x][y] = true;
}
x += nbDirs[dir][0];
y += nbDirs[dir][1];
}
x = clamp(x - nbDirs[dir][0], 0, DCOLS - 1);
y = clamp(y - nbDirs[dir][1], 0, DROWS - 1); // Now (x, y) points at the last interior cell of the hallway.
allowObliqueHallwayExit = rand_percent(15);
for (dir2 = 0; dir2 < 4; dir2++) {
newX = x + nbDirs[dir2][0];
newY = y + nbDirs[dir2][1];
if ((dir2 != dir && !allowObliqueHallwayExit)
|| !coordinatesAreInMap(newX, newY)
|| grid[newX][newY]) {
doorSites[dir2][0] = -1;
doorSites[dir2][1] = -1;
} else {
doorSites[dir2][0] = newX;
doorSites[dir2][1] = newY;
}
}
}
// Put a random room shape somewhere on the binary grid,
// and optionally record the coordinates of up to four door sites in doorSites.
// If attachHallway is true, then it will bolt a perpendicular hallway onto the room at one of the four standard door sites,
// and then relocate three of the door sites to radiate from the end of the hallway. (The fourth is defunct.)
// RoomTypeFrequencies specifies the probability of each room type, in the following order:
// 0. Cross room
// 1. Small symmetrical cross room
// 2. Small room
// 3. Circular room
// 4. Chunky room
// 5. Cave
// 6. Cavern (the kind that fills a level)
// 7. Entrance room (the big upside-down T room at the start of depth 1)
void designRandomRoom(short **grid, boolean attachHallway, short doorSites[4][2],
const short roomTypeFrequencies[ROOM_TYPE_COUNT]) {
short randIndex, i, sum;
enum directions dir;
sum = 0;
for (i=0; i<ROOM_TYPE_COUNT; i++) {
sum += roomTypeFrequencies[i];
}
randIndex = rand_range(0, sum - 1);
for (i=0; i<ROOM_TYPE_COUNT; i++) {
if (randIndex < roomTypeFrequencies[i]) {
break; // "i" is our room type.
} else {
randIndex -= roomTypeFrequencies[i];
}
}
switch (i) {
case 0:
designCrossRoom(grid);
break;
case 1:
designSymmetricalCrossRoom(grid);
break;
case 2:
designSmallRoom(grid);
break;
case 3:
designCircularRoom(grid);
break;
case 4:
designChunkyRoom(grid);
break;
case 5:
switch (rand_range(0, 2)) {
case 0:
designCavern(grid, 3, 12, 4, 8); // Compact cave room.
break;
case 1:
designCavern(grid, 3, 12, 15, DROWS-2); // Large north-south cave room.
break;
case 2:
designCavern(grid, 20, DROWS-2, 4, 8); // Large east-west cave room.
break;
default:
break;
}
break;
case 6:
designCavern(grid, CAVE_MIN_WIDTH, DCOLS - 2, CAVE_MIN_HEIGHT, DROWS - 2);
break;
case 7:
designEntranceRoom(grid);
break;
default:
break;
}
if (doorSites) {
chooseRandomDoorSites(grid, doorSites);
if (attachHallway) {
dir = rand_range(0, 3);
for (i=0; doorSites[dir][0] == -1 && i < 3; i++) {
dir = (dir + 1) % 4; // Each room will have at least 2 valid directions for doors.
}
attachHallwayTo(grid, doorSites);
}
}
}
boolean roomFitsAt(short **dungeonMap, short **roomMap, short roomToDungeonX, short roomToDungeonY) {
short xRoom, yRoom, xDungeon, yDungeon, i, j;
for (xRoom = 0; xRoom < DCOLS; xRoom++) {
for (yRoom = 0; yRoom < DROWS; yRoom++) {
if (roomMap[xRoom][yRoom]) {
xDungeon = xRoom + roomToDungeonX;
yDungeon = yRoom + roomToDungeonY;
for (i = xDungeon - 1; i <= xDungeon + 1; i++) {
for (j = yDungeon - 1; j <= yDungeon + 1; j++) {
if (!coordinatesAreInMap(i, j)
|| dungeonMap[i][j] > 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<DCOLS; i++) {
for (j=0; j<DROWS; j++) {
if (pmap[i][j].layers[DUNGEON] == GRANITE) {
foundExposure = false;
for (dir = 0; dir < (includingDiagonals ? 8 : 4) && !foundExposure; dir++) {
x1 = i + nbDirs[dir][0];
y1 = j + nbDirs[dir][1];
if (coordinatesAreInMap(x1, y1)
&& (!cellHasTerrainFlag(x1, y1, T_OBSTRUCTS_VISION) || !cellHasTerrainFlag(x1, y1, T_OBSTRUCTS_PASSABILITY))) {
pmap[i][j].layers[DUNGEON] = WALL;
foundExposure = true;
}
}
} else if (pmap[i][j].layers[DUNGEON] == WALL) {
foundExposure = false;
for (dir = 0; dir < (includingDiagonals ? 8 : 4) && !foundExposure; dir++) {
x1 = i + nbDirs[dir][0];
y1 = j + nbDirs[dir][1];
if (coordinatesAreInMap(x1, y1)
&& (!cellHasTerrainFlag(x1, y1, T_OBSTRUCTS_VISION) || !cellHasTerrainFlag(x1, y1, T_OBSTRUCTS_PASSABILITY))) {
foundExposure = true;
}
}
if (foundExposure == false) {
pmap[i][j].layers[DUNGEON] = GRANITE;
}
}
}
}
}
void liquidType(short *deep, short *shallow, short *shallowWidth) {
short randMin, randMax, rand;
randMin = (rogue.depthLevel < 4 ? 1 : 0); // no lava before level 4
randMax = (rogue.depthLevel < 17 ? 2 : 3); // no brimstone before level 18
rand = rand_range(randMin, randMax);
if (rogue.depthLevel == DEEPEST_LEVEL) {
rand = 1;
}
switch(rand) {
case 0:
*deep = LAVA;
*shallow = NOTHING;
*shallowWidth = 0;
break;
case 1:
*deep = DEEP_WATER;
*shallow = SHALLOW_WATER;
*shallowWidth = 2;
break;
case 2:
*deep = CHASM;
*shallow = CHASM_EDGE;
*shallowWidth = 1;
break;
case 3:
*deep = INERT_BRIMSTONE;
*shallow = OBSIDIAN;
*shallowWidth = 2;
break;
}
}
// Fills a lake marked in unfilledLakeMap with the specified liquid type, scanning outward to reach other lakes within scanWidth.
// Any wreath of shallow liquid must be done elsewhere.
void fillLake(short x, short y, short liquid, short scanWidth, char wreathMap[DCOLS][DROWS], short **unfilledLakeMap) {
short i, j;
for (i = x - scanWidth; i <= x + scanWidth; i++) {
for (j = y - scanWidth; j <= y + scanWidth; j++) {
if (coordinatesAreInMap(i, j) && unfilledLakeMap[i][j]) {
unfilledLakeMap[i][j] = false;
pmap[i][j].layers[LIQUID] = liquid;
wreathMap[i][j] = 1;
fillLake(i, j, liquid, scanWidth, wreathMap, unfilledLakeMap); // recursive
}
}
}
}
void lakeFloodFill(short x, short y, short **floodMap, short **grid, short **lakeMap, short dungeonToGridX, short dungeonToGridY) {
short newX, newY;
enum directions dir;
floodMap[x][y] = true;
for (dir=0; dir<4; dir++) {
newX = x + nbDirs[dir][0];
newY = y + nbDirs[dir][1];
if (coordinatesAreInMap(newX, newY)
&& !floodMap[newX][newY]
&& (!cellHasTerrainFlag(newX, newY, T_PATHING_BLOCKER) || cellHasTMFlag(newX, newY, TM_CONNECTS_LEVEL))
&& !lakeMap[newX][newY]
&& (!coordinatesAreInMap(newX+dungeonToGridX, newY+dungeonToGridY) || !grid[newX+dungeonToGridX][newY+dungeonToGridY])) {
lakeFloodFill(newX, newY, floodMap, grid, lakeMap, dungeonToGridX, dungeonToGridY);
}
}
}
boolean lakeDisruptsPassability(short **grid, short **lakeMap, short dungeonToGridX, short dungeonToGridY) {
boolean result;
short i, j, x, y;
short **floodMap;
floodMap = allocGrid();
fillGrid(floodMap, 0);
x = y = -1;
// Get starting location for the fill.
for (i=0; i<DCOLS && x == -1; i++) {
for (j=0; j<DROWS && x == -1; j++) {
if (!cellHasTerrainFlag(i, j, T_PATHING_BLOCKER)
&& !lakeMap[i][j]
&& (!coordinatesAreInMap(i+dungeonToGridX, j+dungeonToGridY) || !grid[i+dungeonToGridX][j+dungeonToGridY])) {
x = i;
y = j;
}
}
}
brogueAssert(x != -1);
// Do the flood fill.
lakeFloodFill(x, y, floodMap, grid, lakeMap, dungeonToGridX, dungeonToGridY);
// See if any dry tiles weren't reached by the flood fill.
result = false;
for (i=0; i<DCOLS && result == false; i++) {
for (j=0; j<DROWS && result == false; j++) {
if (!cellHasTerrainFlag(i, j, T_PATHING_BLOCKER)
&& !lakeMap[i][j]
&& !floodMap[i][j]
&& (!coordinatesAreInMap(i+dungeonToGridX, j+dungeonToGridY) || !grid[i+dungeonToGridX][j+dungeonToGridY])) {
// if (D_INSPECT_LEVELGEN) {
// dumpLevelToScreen();
// hiliteGrid(lakeMap, &darkBlue, 75);
// hiliteGrid(floodMap, &white, 20);
// plotCharWithColor('X', mapToWindowX(i), mapToWindowY(j), &black, &red);
// temporaryMessage("Failed here.", true);
// }
result = true;
}
}
}
freeGrid(floodMap);
return result;
}
void designLakes(short **lakeMap) {
short i, j, k;
short x, y;
short lakeMaxHeight, lakeMaxWidth;
short lakeX, lakeY, lakeWidth, lakeHeight;
short **grid; // Holds the current lake.
grid = allocGrid();
fillGrid(lakeMap, 0);
for (lakeMaxHeight = 15, lakeMaxWidth = 30; lakeMaxHeight >=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<DCOLS; i++) {
for (j=0; j<DROWS; j++) {
if (wreathMap[i][j]) {
for (k = i-wreathWidth; k<= i+wreathWidth; k++) {
for (l = j-wreathWidth; l <= j+wreathWidth; l++) {
if (coordinatesAreInMap(k, l) && pmap[k][l].layers[LIQUID] == NOTHING
&& (i-k)*(i-k) + (j-l)*(j-l) <= wreathWidth*wreathWidth) {
pmap[k][l].layers[LIQUID] = shallowLiquid;
if (pmap[k][l].layers[DUNGEON] == DOOR) {
pmap[k][l].layers[DUNGEON] = FLOOR;
}
}
}
}
}
}
}
}
void fillLakes(short **lakeMap) {
short deepLiquid = CRYSTAL_WALL, shallowLiquid = CRYSTAL_WALL, shallowLiquidWidth = 0;
char wreathMap[DCOLS][DROWS];
short i, j;
for (i=0; i<DCOLS; i++) {
for (j=0; j<DROWS; j++) {
if (lakeMap[i][j]) {
liquidType(&deepLiquid, &shallowLiquid, &shallowLiquidWidth);
zeroOutGrid(wreathMap);
fillLake(i, j, deepLiquid, 4, wreathMap, lakeMap);
createWreath(shallowLiquid, shallowLiquidWidth, wreathMap);
if (D_INSPECT_LEVELGEN) {
dumpLevelToScreen();
hiliteGrid(lakeMap, &white, 75);
temporaryMessage("Lake filled.", true);
}
}
}
}
}
void finishDoors() {
short i, j;
const short secretDoorChance = clamp((rogue.depthLevel - 1) * 67 / 25, 0, 67);
for (i=1; i<DCOLS-1; i++) {
for (j=1; j<DROWS-1; j++) {
if (pmap[i][j].layers[DUNGEON] == DOOR
&& pmap[i][j].machineNumber == 0) {
if ((!cellHasTerrainFlag(i+1, j, T_OBSTRUCTS_PASSABILITY) || !cellHasTerrainFlag(i-1, j, T_OBSTRUCTS_PASSABILITY))
&& (!cellHasTerrainFlag(i, j+1, T_OBSTRUCTS_PASSABILITY) || !cellHasTerrainFlag(i, j-1, T_OBSTRUCTS_PASSABILITY))) {
// If there's passable terrain to the left or right, and there's passable terrain
// above or below, then the door is orphaned and must be removed.
pmap[i][j].layers[DUNGEON] = FLOOR;
} else if ((cellHasTerrainFlag(i+1, j, T_PATHING_BLOCKER) ? 1 : 0)
+ (cellHasTerrainFlag(i-1, j, T_PATHING_BLOCKER) ? 1 : 0)
+ (cellHasTerrainFlag(i, j+1, T_PATHING_BLOCKER) ? 1 : 0)
+ (cellHasTerrainFlag(i, j-1, T_PATHING_BLOCKER) ? 1 : 0) >= 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<DCOLS; i++ ) {
for( j=0; j<DROWS; j++ ) {
pmap[i][j].layers[DUNGEON] = GRANITE;
pmap[i][j].layers[LIQUID] = NOTHING;
pmap[i][j].layers[GAS] = NOTHING;
pmap[i][j].layers[SURFACE] = NOTHING;
pmap[i][j].machineNumber = 0;
pmap[i][j].rememberedTerrain = NOTHING;
pmap[i][j].rememberedTerrainFlags = (T_OBSTRUCTS_EVERYTHING);
pmap[i][j].rememberedTMFlags = 0;
pmap[i][j].rememberedCellFlags = 0;
pmap[i][j].rememberedItemCategory = 0;
pmap[i][j].rememberedItemKind = 0;
pmap[i][j].rememberedItemQuantity = 0;
pmap[i][j].rememberedItemOriginDepth = 0;
pmap[i][j].flags = 0;
pmap[i][j].volume = 0;
}
}
}
// Scans the map in random order looking for a good place to build a bridge.
// If it finds one, it builds a bridge there, halts and returns true.
boolean buildABridge() {
short i, j, k, l, i2, j2, nCols[DCOLS], nRows[DROWS];
short bridgeRatioX, bridgeRatioY;
boolean foundExposure;
bridgeRatioX = (short) (100 + (100 + 100 * rogue.depthLevel / 9) * rand_range(10, 20) / 10);
bridgeRatioY = (short) (100 + (400 + 100 * rogue.depthLevel / 18) * rand_range(10, 20) / 10);
fillSequentialList(nCols, DCOLS);
shuffleList(nCols, DCOLS);
fillSequentialList(nRows, DROWS);
shuffleList(nRows, DROWS);
for (i2=1; i2<DCOLS-1; i2++) {
i = nCols[i2];
for (j2=1; j2<DROWS-1; j2++) {
j = nRows[j2];
if (!cellHasTerrainFlag(i, j, (T_CAN_BE_BRIDGED | T_PATHING_BLOCKER))
&& !pmap[i][j].machineNumber) {
// try a horizontal bridge
foundExposure = false;
for (k = i + 1;
k < DCOLS // Iterate across the prospective length of the bridge.
&& !pmap[k][j].machineNumber // No bridges in machines.
&& cellHasTerrainFlag(k, j, T_CAN_BE_BRIDGED) // Candidate tile must be chasm.
&& !cellHasTMFlag(k, j, TM_IS_SECRET) // Can't bridge over secret trapdoors.
&& !cellHasTerrainFlag(k, j, T_OBSTRUCTS_PASSABILITY) // Candidate tile cannot be a wall.
&& cellHasTerrainFlag(k, j-1, (T_CAN_BE_BRIDGED | T_OBSTRUCTS_PASSABILITY)) // Only chasms or walls are permitted next to the length of the bridge.
&& cellHasTerrainFlag(k, j+1, (T_CAN_BE_BRIDGED | T_OBSTRUCTS_PASSABILITY));
k++) {
if (!cellHasTerrainFlag(k, j-1, T_OBSTRUCTS_PASSABILITY) // Can't run against a wall the whole way.
&& !cellHasTerrainFlag(k, j+1, T_OBSTRUCTS_PASSABILITY)) {
foundExposure = true;
}
}
if (k < DCOLS
&& (k - 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; i<DCOLS; i++) {
for (j=0; j<DROWS; j++) {
if (grid[i][j] == 1) {
pmap[i][j].layers[DUNGEON] = FLOOR;
} else if (grid[i][j] == 2) {
pmap[i][j].layers[DUNGEON] = (rand_percent(60) && rogue.depthLevel < DEEPEST_LEVEL ? DOOR : FLOOR);
}
}
}
freeGrid(grid);
finishWalls(false);
if (D_INSPECT_LEVELGEN) {
dumpLevelToScreen();
temporaryMessage("Carved into the granite:", true);
}
//DEBUG printf("\n%i loops created.", numLoops);
//DEBUG logLevel();
// Time to add lakes and chasms. Strategy is to generate a series of blob lakes of decreasing size. For each lake,
// propose a position, and then check via a flood fill that the level would remain connected with that placement (i.e. that
// each passable tile can still be reached). If not, make 9 more placement attempts before abandoning that lake
// and proceeding to generate the next smaller one.
// Canvas sizes start at 30x15 and decrease by 2x1 at a time down to a minimum of 20x10. Min generated size is always 4x4.
// DEBUG logLevel();
// Now design the lakes and then fill them with various liquids (lava, water, chasm, brimstone).
short **lakeMap = allocGrid();
designLakes(lakeMap);
fillLakes(lakeMap);
freeGrid(lakeMap);
// Run the non-machine autoGenerators.
runAutogenerators(false);
// Remove diagonal openings.
removeDiagonalOpenings();
if (D_INSPECT_LEVELGEN) {
dumpLevelToScreen();
temporaryMessage("Diagonal openings removed.", true);
}
// Now add some treasure machines.
addMachines();
if (D_INSPECT_LEVELGEN) {
dumpLevelToScreen();
temporaryMessage("Machines added.", true);
}
// Run the machine autoGenerators.
runAutogenerators(true);
// Now knock down the boundaries between similar lakes where possible.
cleanUpLakeBoundaries();
if (D_INSPECT_LEVELGEN) {
dumpLevelToScreen();
temporaryMessage("Lake boundaries cleaned up.", true);
}
// Now add some bridges.
while (buildABridge());
if (D_INSPECT_LEVELGEN) {
dumpLevelToScreen();
temporaryMessage("Bridges added.", true);
}
// Now remove orphaned doors and upgrade some doors to secret doors
finishDoors();
// Now finish any exposed granite with walls and revert any unexposed walls to granite
finishWalls(true);
if (D_INSPECT_LEVELGEN) {
dumpLevelToScreen();
temporaryMessage("Finishing touches added. Level has been generated.", true);
}
}
void updateMapToShore() {
short i, j;
short **costMap;
rogue.updatedMapToShoreThisTurn = true;
costMap = allocGrid();
// Calculate the map to shore for this level
if (!rogue.mapToShore) {
rogue.mapToShore = allocGrid();
fillGrid(rogue.mapToShore, 0);
}
for (i=0; i<DCOLS; i++) {
for (j=0; j<DROWS; j++) {
if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY)) {
costMap[i][j] = cellHasTerrainFlag(i, j, T_OBSTRUCTS_DIAGONAL_MOVEMENT) ? PDS_OBSTRUCTION : PDS_FORBIDDEN;
rogue.mapToShore[i][j] = 30000;
} else {
costMap[i][j] = 1;
rogue.mapToShore[i][j] = (cellHasTerrainFlag(i, j, T_LAVA_INSTA_DEATH | T_IS_DEEP_WATER | T_AUTO_DESCENT)
&& !cellHasTMFlag(i, j, TM_IS_SECRET)) ? 30000 : 0;
}
}
}
dijkstraScan(rogue.mapToShore, costMap, true);
freeGrid(costMap);
}
// Calculates the distance map for the given waypoint.
// This is called on all waypoints during setUpWaypoints(),
// and then one waypoint is recalculated per turn thereafter.
void refreshWaypoint(short wpIndex) {
short **costMap;
creature *monst;
costMap = allocGrid();
populateGenericCostMap(costMap);
for (monst = monsters->nextCreature; 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<DCOLS; i++) {
for (j=0; j<DROWS; j++) {
if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_SCENT)) {
grid[i][j] = 1;
}
}
}
rogue.wpCount = 0;
rogue.wpRefreshTicker = 0;
fillSequentialList(sCoord, DCOLS*DROWS);
shuffleList(sCoord, DCOLS*DROWS);
for (i = 0; i < DCOLS*DROWS && rogue.wpCount < MAX_WAYPOINT_COUNT; i++) {
x = sCoord[i]/DROWS;
y = sCoord[i] % DROWS;
if (!grid[x][y]) {
getFOVMask(grid, x, y, WAYPOINT_SIGHT_RADIUS * FP_FACTOR, T_OBSTRUCTS_SCENT, 0, false);
grid[x][y] = true;
rogue.wpCoordinates[rogue.wpCount][0] = x;
rogue.wpCoordinates[rogue.wpCount][1] = y;
rogue.wpCount++;
// blackOutScreen();
// dumpLevelToScreen();
// hiliteCharGrid(grid, &yellow, 50);
// temporaryMessage("Waypoint coverage so far:", true);
}
}
for (i=0; i<rogue.wpCount; i++) {
refreshWaypoint(i);
// blackOutScreen();
// dumpLevelToScreen();
// displayGrid(rogue.wpDistance[i]);
// temporaryMessage("Waypoint distance map:", true);
}
}
void zeroOutGrid(char grid[DCOLS][DROWS]) {
short i, j;
for (i=0; i<DCOLS; i++) {
for (j=0; j<DROWS; j++) {
grid[i][j] = 0;
}
}
}
short oppositeDirection(short theDir) {
switch (theDir) {
case UP:
return DOWN;
case DOWN:
return UP;
case LEFT:
return RIGHT;
case RIGHT:
return LEFT;
case UPRIGHT:
return DOWNLEFT;
case DOWNLEFT:
return UPRIGHT;
case UPLEFT:
return DOWNRIGHT;
case DOWNRIGHT:
return UPLEFT;
case NO_DIRECTION:
return NO_DIRECTION;
default:
return -1;
}
}
// blockingMap is optional.
// Returns the size of the connected zone, and marks visited[][] with the zoneLabel.
short connectCell(short x, short y, short zoneLabel, char blockingMap[DCOLS][DROWS], char zoneMap[DCOLS][DROWS]) {
enum directions dir;
short newX, newY, size;
zoneMap[x][y] = zoneLabel;
size = 1;
for (dir = 0; dir < 4; dir++) {
newX = x + nbDirs[dir][0];
newY = y + nbDirs[dir][1];
if (coordinatesAreInMap(newX, newY)
&& zoneMap[newX][newY] == 0
&& (!blockingMap || !blockingMap[newX][newY])
&& cellIsPassableOrDoor(newX, newY)) {
size += connectCell(newX, newY, zoneLabel, blockingMap, zoneMap);
}
}
return size;
}
// Make a zone map of connected passable regions that include at least one passable
// cell that borders the blockingMap if blockingMap blocks. Keep track of the size of each zone.
// Then pretend that the blockingMap no longer blocks, and grow these zones into the resulting area
// (without changing the stored zone sizes). If two or more zones now touch, then we block.
// At that point, return the size in cells of the smallest of all of the touching regions
// (or just 1, i.e. true, if countRegionSize is false). If no zones touch, then we don't block, and we return zero, i.e. false.
short levelIsDisconnectedWithBlockingMap(char blockingMap[DCOLS][DROWS], boolean countRegionSize) {
char zoneMap[DCOLS][DROWS];
short i, j, dir, zoneSizes[200], zoneCount, smallestQualifyingZoneSize, borderingZone;
zoneCount = 0;
smallestQualifyingZoneSize = 10000;
zeroOutGrid(zoneMap);
// dumpLevelToScreen();
// hiliteCharGrid(blockingMap, &omniscienceColor, 100);
// temporaryMessage("Blocking map:", true);
// Map out the zones with the blocking area blocked.
for (i=1; i<DCOLS-1; i++) {
for (j=1; j<DROWS-1; j++) {
if (cellIsPassableOrDoor(i, j) && zoneMap[i][j] == 0 && !blockingMap[i][j]) {
for (dir=0; dir<4; dir++) {
if (blockingMap[i + nbDirs[dir][0]][j + nbDirs[dir][1]]) {
zoneCount++;
zoneSizes[zoneCount - 1] = connectCell(i, j, zoneCount, blockingMap, zoneMap);
break;
}
}
}
}
}
// Expand the zones into the blocking area.
for (i=1; i<DCOLS-1; i++) {
for (j=1; j<DROWS-1; j++) {
if (blockingMap[i][j] && zoneMap[i][j] == 0 && cellIsPassableOrDoor(i, j)) {
for (dir=0; dir<4; dir++) {
borderingZone = zoneMap[i + nbDirs[dir][0]][j + nbDirs[dir][1]];
if (borderingZone != 0) {
connectCell(i, j, borderingZone, NULL, zoneMap);
break;
}
}
}
}
}
// Figure out which zones touch.
for (i=1; i<DCOLS-1; i++) {
for (j=1; j<DROWS-1; j++) {
if (zoneMap[i][j] != 0) {
for (dir=0; dir<4; dir++) {
borderingZone = zoneMap[i + nbDirs[dir][0]][j + nbDirs[dir][1]];
if (zoneMap[i][j] != borderingZone && borderingZone != 0) {
if (!countRegionSize) {
return true;
}
smallestQualifyingZoneSize = min(smallestQualifyingZoneSize, zoneSizes[zoneMap[i][j] - 1]);
smallestQualifyingZoneSize = min(smallestQualifyingZoneSize, zoneSizes[borderingZone - 1]);
break;
}
}
}
}
}
return (smallestQualifyingZoneSize < 10000 ? smallestQualifyingZoneSize : 0);
}
void resetDFMessageEligibility() {
short i;
for (i=0; i<NUMBER_DUNGEON_FEATURES; i++) {
dungeonFeatureCatalog[i].messageDisplayed = false;
}
}
boolean fillSpawnMap(enum dungeonLayers layer,
enum tileType surfaceTileType,
char spawnMap[DCOLS][DROWS],
boolean blockedByOtherLayers,
boolean refresh,
boolean superpriority) {
short i, j;
creature *monst;
item *theItem;
boolean accomplishedSomething;
accomplishedSomething = false;
for (i=0; i<DCOLS; i++) {
for (j=0; j<DROWS; j++) {
if ( // If it's flagged for building in the spawn map,
spawnMap[i][j]
// and the new cell doesn't already contain the fill terrain,
&& pmap[i][j].layers[layer] != surfaceTileType
// and the terrain in the layer to be overwritten has a higher priority number (unless superpriority),
&& (superpriority || tileCatalog[pmap[i][j].layers[layer]].drawPriority >= 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; i<DCOLS; i++) {
for (j=0; j<DROWS; j++) {
if (blockingMap[i][j]
&& (pmap[i][j].flags & (HAS_MONSTER | HAS_PLAYER))) {
monst = monsterAtLoc(i, j);
getQualifyingLocNear(newLoc,
i, j,
true,
blockingMap,
forbiddenFlagsForMonster(&(monst->info)),
(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; i<DCOLS; i++) {
for (j=0; j<DROWS; j++) {
if (blockingMap[i][j]) {
for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
if (layer != feat->layer && 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; i<DCOLS; i++) {
for (j=0; j<DROWS; j++) {
if (blockingMap[i][j]) {
spawnDungeonFeature(i, j, &dungeonFeatureCatalog[feat->subsequentDF], 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; i<DCOLS; i++) {
for (j=0; j<DROWS; j++) {
if (grid[i][j]) {
pmap[i][j].flags |= IN_FIELD_OF_VIEW;
}
}
}
populateItems(upLoc[0], upLoc[1]);
populateMonsters();
}
// Restore items that fell from the previous depth.
for (theItem = floorItems->nextItem; 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;
}