1 /*
2  *  Architect.c
3  *  Brogue
4  *
5  *  Created by Brian Walker on 1/10/09.
6  *  Copyright 2012. All rights reserved.
7  *
8  *  This file is part of Brogue.
9  *
10  *  This program is free software: you can redistribute it and/or modify
11  *  it under the terms of the GNU Affero General Public License as
12  *  published by the Free Software Foundation, either version 3 of the
13  *  License, or (at your option) any later version.
14  *
15  *  This program is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU Affero General Public License for more details.
19  *
20  *  You should have received a copy of the GNU Affero General Public License
21  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
22  */
23 
24 #include "Rogue.h"
25 #include "IncludeGlobals.h"
26 
27 short topBlobMinX, topBlobMinY, blobWidth, blobHeight;
28 
29 #ifdef BROGUE_ASSERTS // otherwise handled as a macro in rogue.h
cellHasTerrainFlag(short x,short y,unsigned long flagMask)30 boolean cellHasTerrainFlag(short x, short y, unsigned long flagMask) {
31     assert(coordinatesAreInMap(x, y));
32     return ((flagMask) & terrainFlags((x), (y)) ? true : false);
33 }
34 #endif
35 
checkLoopiness(short x,short y)36 boolean checkLoopiness(short x, short y) {
37     boolean inString;
38     short newX, newY, dir, sdir;
39     short numStrings, maxStringLength, currentStringLength;
40 
41     if (!(pmap[x][y].flags & IN_LOOP)) {
42         return false;
43     }
44 
45     // find an unloopy neighbor to start on
46     for (sdir = 0; sdir < DIRECTION_COUNT; sdir++) {
47         newX = x + cDirs[sdir][0];
48         newY = y + cDirs[sdir][1];
49         if (!coordinatesAreInMap(newX, newY)
50             || !(pmap[newX][newY].flags & IN_LOOP)) {
51             break;
52         }
53     }
54     if (sdir == 8) { // no unloopy neighbors
55         return false; // leave cell loopy
56     }
57 
58     // starting on this unloopy neighbor, work clockwise and count up (a) the number of strings
59     // of loopy neighbors, and (b) the length of the longest such string.
60     numStrings = maxStringLength = currentStringLength = 0;
61     inString = false;
62     for (dir = sdir; dir < sdir + 8; dir++) {
63         newX = x + cDirs[dir % 8][0];
64         newY = y + cDirs[dir % 8][1];
65         if (coordinatesAreInMap(newX, newY) && (pmap[newX][newY].flags & IN_LOOP)) {
66             currentStringLength++;
67             if (!inString) {
68                 if (numStrings > 0) {
69                     return false; // more than one string here; leave loopy
70                 }
71                 numStrings++;
72                 inString = true;
73             }
74         } else if (inString) {
75             if (currentStringLength > maxStringLength) {
76                 maxStringLength = currentStringLength;
77             }
78             currentStringLength = 0;
79             inString = false;
80         }
81     }
82     if (inString && currentStringLength > maxStringLength) {
83         maxStringLength = currentStringLength;
84     }
85     if (numStrings == 1 && maxStringLength <= 4) {
86         pmap[x][y].flags &= ~IN_LOOP;
87 
88         for (dir = 0; dir < DIRECTION_COUNT; dir++) {
89             newX = x + cDirs[dir][0];
90             newY = y + cDirs[dir][1];
91             if (coordinatesAreInMap(newX, newY)) {
92                 checkLoopiness(newX, newY);
93             }
94         }
95         return true;
96     } else {
97         return false;
98     }
99 }
100 
auditLoop(short x,short y,char grid[DCOLS][DROWS])101 void auditLoop(short x, short y, char grid[DCOLS][DROWS]) {
102     short dir, newX, newY;
103     if (coordinatesAreInMap(x, y)
104         && !grid[x][y]
105         && !(pmap[x][y].flags & IN_LOOP)) {
106 
107         grid[x][y] = true;
108         for (dir = 0; dir < DIRECTION_COUNT; dir++) {
109             newX = x + nbDirs[dir][0];
110             newY = y + nbDirs[dir][1];
111             if (coordinatesAreInMap(newX, newY)) {
112                 auditLoop(newX, newY, grid);
113             }
114         }
115     }
116 }
117 
118 // Assumes it is called with respect to a passable (startX, startY), and that the same is not already included in results.
119 // Returns 10000 if the area included an area machine.
floodFillCount(char results[DCOLS][DROWS],char passMap[DCOLS][DROWS],short startX,short startY)120 short floodFillCount(char results[DCOLS][DROWS], char passMap[DCOLS][DROWS], short startX, short startY) {
121     short dir, newX, newY, count;
122 
123     count = (passMap[startX][startY] == 2 ? 5000 : 1);
124 
125     if (pmap[startX][startY].flags & IS_IN_AREA_MACHINE) {
126         count = 10000;
127     }
128 
129     results[startX][startY] = true;
130 
131     for(dir=0; dir<4; dir++) {
132         newX = startX + nbDirs[dir][0];
133         newY = startY + nbDirs[dir][1];
134         if (coordinatesAreInMap(newX, newY)
135             && passMap[newX][newY]
136             && !results[newX][newY]) {
137 
138             count += floodFillCount(results, passMap, newX, newY);
139         }
140     }
141     return min(count, 10000);
142 }
143 
144 // Rotates around the cell, counting up the number of distinct strings of passable neighbors in a single revolution.
145 //      Zero means there are no impassable tiles adjacent.
146 //      One means it is adjacent to a wall.
147 //      Two means it is in a hallway or something similar.
148 //      Three means it is the center of a T-intersection or something similar.
149 //      Four means it is in the intersection of two hallways.
150 //      Five or more means there is a bug.
passableArcCount(short x,short y)151 short passableArcCount(short x, short y) {
152     short arcCount, dir, oldX, oldY, newX, newY;
153 
154     brogueAssert(coordinatesAreInMap(x, y));
155 
156     arcCount = 0;
157     for (dir = 0; dir < DIRECTION_COUNT; dir++) {
158         oldX = x + cDirs[(dir + 7) % 8][0];
159         oldY = y + cDirs[(dir + 7) % 8][1];
160         newX = x + cDirs[dir][0];
161         newY = y + cDirs[dir][1];
162         // Counts every transition from passable to impassable or vice-versa on the way around the cell:
163         if ((coordinatesAreInMap(newX, newY) && cellIsPassableOrDoor(newX, newY))
164             != (coordinatesAreInMap(oldX, oldY) && cellIsPassableOrDoor(oldX, oldY))) {
165             arcCount++;
166         }
167     }
168     return arcCount / 2; // Since we added one when we entered a wall and another when we left.
169 }
170 
171 // locates all loops and chokepoints
analyzeMap(boolean calculateChokeMap)172 void analyzeMap(boolean calculateChokeMap) {
173     short i, j, i2, j2, dir, newX, newY, oldX, oldY, passableArcCount, cellCount;
174     char grid[DCOLS][DROWS], passMap[DCOLS][DROWS];
175     boolean designationSurvives;
176 
177     // first find all of the loops
178     rogue.staleLoopMap = false;
179 
180     for(i=0; i<DCOLS; i++) {
181         for(j=0; j<DROWS; j++) {
182             if (cellHasTerrainFlag(i, j, T_PATHING_BLOCKER)
183                 && !cellHasTMFlag(i, j, TM_IS_SECRET)) {
184 
185                 pmap[i][j].flags &= ~IN_LOOP;
186                 passMap[i][j] = false;
187             } else {
188                 pmap[i][j].flags |= IN_LOOP;
189                 passMap[i][j] = true;
190             }
191         }
192     }
193 
194     for(i=0; i<DCOLS; i++) {
195         for(j=0; j<DROWS; j++) {
196             checkLoopiness(i, j);
197         }
198     }
199 
200     // remove extraneous loop markings
201     zeroOutGrid(grid);
202     auditLoop(0, 0, grid);
203 
204     for(i=0; i<DCOLS; i++) {
205         for(j=0; j<DROWS; j++) {
206             if (pmap[i][j].flags & IN_LOOP) {
207                 designationSurvives = false;
208                 for (dir = 0; dir < DIRECTION_COUNT; dir++) {
209                     newX = i + nbDirs[dir][0];
210                     newY = j + nbDirs[dir][1];
211                     if (coordinatesAreInMap(newX, newY)
212                         && !grid[newX][newY]
213                         && !(pmap[newX][newY].flags & IN_LOOP)) {
214                         designationSurvives = true;
215                         break;
216                     }
217                 }
218                 if (!designationSurvives) {
219                     grid[i][j] = true;
220                     pmap[i][j].flags &= ~IN_LOOP;
221                 }
222             }
223         }
224     }
225 
226     // done finding loops; now flag chokepoints
227     for(i=1; i<DCOLS-1; i++) {
228         for(j=1; j<DROWS-1; j++) {
229             pmap[i][j].flags &= ~IS_CHOKEPOINT;
230             if (passMap[i][j] && !(pmap[i][j].flags & IN_LOOP)) {
231                 passableArcCount = 0;
232                 for (dir = 0; dir < DIRECTION_COUNT; dir++) {
233                     oldX = i + cDirs[(dir + 7) % 8][0];
234                     oldY = j + cDirs[(dir + 7) % 8][1];
235                     newX = i + cDirs[dir][0];
236                     newY = j + cDirs[dir][1];
237                     if ((coordinatesAreInMap(newX, newY) && passMap[newX][newY])
238                         != (coordinatesAreInMap(oldX, oldY) && passMap[oldX][oldY])) {
239                         if (++passableArcCount > 2) {
240                             if (!passMap[i-1][j] && !passMap[i+1][j] || !passMap[i][j-1] && !passMap[i][j+1]) {
241                                 pmap[i][j].flags |= IS_CHOKEPOINT;
242                             }
243                             break;
244                         }
245                     }
246                 }
247             }
248         }
249     }
250 
251     if (calculateChokeMap) {
252 
253         // Done finding chokepoints; now create a chokepoint map.
254 
255         // The chokepoint map is a number for each passable tile. If the tile is a chokepoint,
256         // then the number indicates the number of tiles that would be rendered unreachable if the
257         // chokepoint were blocked. If the tile is not a chokepoint, then the number indicates
258         // the number of tiles that would be rendered unreachable if the nearest exit chokepoint
259         // were blocked.
260         // The cost of all of this is one depth-first flood-fill per open point that is adjacent to a chokepoint.
261 
262         // Start by setting the chokepoint values really high, and roping off room machines.
263         for(i=0; i<DCOLS; i++) {
264             for(j=0; j<DROWS; j++) {
265                 chokeMap[i][j] = 30000;
266                 if (pmap[i][j].flags & IS_IN_ROOM_MACHINE) {
267                     passMap[i][j] = false;
268                 }
269             }
270         }
271 
272         // Scan through and find a chokepoint next to an open point.
273 
274         for(i=0; i<DCOLS; i++) {
275             for(j=0; j<DROWS; j++) {
276                 if (passMap[i][j] && (pmap[i][j].flags & IS_CHOKEPOINT)) {
277                     for (dir=0; dir<4; dir++) {
278                         newX = i + nbDirs[dir][0];
279                         newY = j + nbDirs[dir][1];
280                         if (coordinatesAreInMap(newX, newY)
281                             && passMap[newX][newY]
282                             && !(pmap[newX][newY].flags & IS_CHOKEPOINT)) {
283                             // OK, (newX, newY) is an open point and (i, j) is a chokepoint.
284                             // Pretend (i, j) is blocked by changing passMap, and run a flood-fill cell count starting on (newX, newY).
285                             // Keep track of the flooded region in grid[][].
286                             zeroOutGrid(grid);
287                             passMap[i][j] = false;
288                             cellCount = floodFillCount(grid, passMap, newX, newY);
289                             passMap[i][j] = true;
290 
291                             // CellCount is the size of the region that would be obstructed if the chokepoint were blocked.
292                             // CellCounts less than 4 are not useful, so we skip those cases.
293 
294                             if (cellCount >= 4) {
295                                 // Now, on the chokemap, all of those flooded cells should take the lesser of their current value or this resultant number.
296                                 for(i2=0; i2<DCOLS; i2++) {
297                                     for(j2=0; j2<DROWS; j2++) {
298                                         if (grid[i2][j2] && cellCount < chokeMap[i2][j2]) {
299                                             chokeMap[i2][j2] = cellCount;
300                                             pmap[i2][j2].flags &= ~IS_GATE_SITE;
301                                         }
302                                     }
303                                 }
304 
305                                 // The chokepoint itself should also take the lesser of its current value or the flood count.
306                                 if (cellCount < chokeMap[i][j]) {
307                                     chokeMap[i][j] = cellCount;
308                                     pmap[i][j].flags |= IS_GATE_SITE;
309                                 }
310                             }
311                         }
312                     }
313                 }
314             }
315         }
316     }
317 }
318 
319 // Add some loops to the otherwise simply connected network of rooms.
addLoops(short ** grid,short minimumPathingDistance)320 void addLoops(short **grid, short minimumPathingDistance) {
321     short newX, newY, oppX, oppY;
322     short **pathMap, **costMap;
323     short i, d, x, y, sCoord[DCOLS*DROWS];
324     const short dirCoords[2][2] = {{1, 0}, {0, 1}};
325 
326     fillSequentialList(sCoord, DCOLS*DROWS);
327     shuffleList(sCoord, DCOLS*DROWS);
328 
329     if (D_INSPECT_LEVELGEN) {
330         colorOverDungeon(&darkGray);
331         hiliteGrid(grid, &white, 100);
332     }
333 
334     pathMap = allocGrid();
335     costMap = allocGrid();
336     copyGrid(costMap, grid);
337     findReplaceGrid(costMap, 0, 0, PDS_OBSTRUCTION);
338     findReplaceGrid(costMap, 1, 30000, 1);
339 
340     for (i = 0; i < DCOLS*DROWS; i++) {
341         x = sCoord[i]/DROWS;
342         y = sCoord[i] % DROWS;
343         if (!grid[x][y]) {
344             for (d=0; d <= 1; d++) { // Try a horizontal door, and then a vertical door.
345                 newX = x + dirCoords[d][0];
346                 oppX = x - dirCoords[d][0];
347                 newY = y + dirCoords[d][1];
348                 oppY = y - dirCoords[d][1];
349                 if (coordinatesAreInMap(newX, newY)
350                     && coordinatesAreInMap(oppX, oppY)
351                     && grid[newX][newY] == 1
352                     && grid[oppX][oppY] == 1) { // If the tile being inspected has floor on both sides,
353 
354                     fillGrid(pathMap, 30000);
355                     pathMap[newX][newY] = 0;
356                     dijkstraScan(pathMap, costMap, false);
357                     if (pathMap[oppX][oppY] > minimumPathingDistance) { // and if the pathing distance between the two flanking floor tiles exceeds minimumPathingDistance,
358                         grid[x][y] = 2;             // then turn the tile into a doorway.
359                         costMap[x][y] = 1;          // (Cost map also needs updating.)
360                         if (D_INSPECT_LEVELGEN) {
361                             plotCharWithColor(G_CLOSED_DOOR, mapToWindowX(x), mapToWindowY(y), &black, &green);
362                         }
363                         break;
364                     }
365                 }
366             }
367         }
368     }
369     if (D_INSPECT_LEVELGEN) {
370         temporaryMessage("Added secondary connections:", REQUIRE_ACKNOWLEDGMENT);
371     }
372     freeGrid(pathMap);
373     freeGrid(costMap);
374 }
375 
376 // Assumes (startX, startY) is in the machine.
377 // Returns true if everything went well, and false if we ran into a machine component
378 // that was already there, as we don't want to build a machine around it.
addTileToMachineInteriorAndIterate(char interior[DCOLS][DROWS],short startX,short startY)379 boolean addTileToMachineInteriorAndIterate(char interior[DCOLS][DROWS], short startX, short startY) {
380     short dir, newX, newY;
381     boolean goodSoFar = true;
382 
383     interior[startX][startY] = true;
384 
385     for (dir = 0; dir < 4 && goodSoFar; dir++) {
386         newX = startX + nbDirs[dir][0];
387         newY = startY + nbDirs[dir][1];
388         if (coordinatesAreInMap(newX, newY)) {
389             if ((pmap[newX][newY].flags & HAS_ITEM)
390                 || ((pmap[newX][newY].flags & IS_IN_MACHINE) && !(pmap[newX][newY].flags & IS_GATE_SITE))) {
391                 // Abort if there's an item in the room.
392                 // Items haven't been populated yet, so the only way this could happen is if another machine
393                 // previously placed an item here.
394                 // Also abort if we're touching another machine at any point other than a gate tile.
395                 return false;
396             }
397             if (!interior[newX][newY]
398                 && chokeMap[newX][newY] <= chokeMap[startX][startY] // don't have to worry about walls since they're all 30000
399                 && !(pmap[newX][newY].flags & IS_IN_MACHINE)) {
400                 //goodSoFar = goodSoFar && addTileToMachineInteriorAndIterate(interior, newX, newY);
401                 if (goodSoFar) {
402                     goodSoFar = addTileToMachineInteriorAndIterate(interior, newX, newY);
403                 }
404             }
405         }
406     }
407     return goodSoFar;
408 }
409 
copyMap(pcell from[DCOLS][DROWS],pcell to[DCOLS][DROWS])410 void copyMap(pcell from[DCOLS][DROWS], pcell to[DCOLS][DROWS]) {
411     short i, j;
412 
413     for(i=0; i<DCOLS; i++) {
414         for(j=0; j<DROWS; j++) {
415             to[i][j] = from[i][j];
416         }
417     }
418 }
419 
itemIsADuplicate(item * theItem,item ** spawnedItems,short itemCount)420 boolean itemIsADuplicate(item *theItem, item **spawnedItems, short itemCount) {
421     short i;
422     if (theItem->category & (STAFF | WAND | POTION | SCROLL | RING | WEAPON | ARMOR | CHARM)) {
423         for (i = 0; i < itemCount; i++) {
424             if (spawnedItems[i]->category == theItem->category
425                 && spawnedItems[i]->kind == theItem->kind) {
426 
427                 return true;
428             }
429         }
430     }
431     return false;
432 }
433 
blueprintQualifies(short i,unsigned long requiredMachineFlags)434 boolean blueprintQualifies(short i, unsigned long requiredMachineFlags) {
435     if (blueprintCatalog[i].depthRange[0] > rogue.depthLevel
436         || blueprintCatalog[i].depthRange[1] < rogue.depthLevel
437                 // Must have the required flags:
438         || (~(blueprintCatalog[i].flags) & requiredMachineFlags)
439                 // May NOT have BP_ADOPT_ITEM unless that flag is required:
440         || (blueprintCatalog[i].flags & BP_ADOPT_ITEM & ~requiredMachineFlags)
441                 // May NOT have BP_VESTIBULE unless that flag is required:
442         || (blueprintCatalog[i].flags & BP_VESTIBULE & ~requiredMachineFlags)) {
443 
444         return false;
445     }
446     return true;
447 }
448 
abortItemsAndMonsters(item * spawnedItems[MACHINES_BUFFER_LENGTH],creature * spawnedMonsters[MACHINES_BUFFER_LENGTH])449 void abortItemsAndMonsters(item *spawnedItems[MACHINES_BUFFER_LENGTH], creature *spawnedMonsters[MACHINES_BUFFER_LENGTH]) {
450     short i, j;
451 
452     for (i=0; i<MACHINES_BUFFER_LENGTH && spawnedItems[i]; i++) {
453         removeItemFromChain(spawnedItems[i], floorItems);
454         removeItemFromChain(spawnedItems[i], packItems); // just in case; can't imagine why this would arise.
455         for (j=0; j<MACHINES_BUFFER_LENGTH && spawnedMonsters[j]; j++) {
456             // Remove the item from spawned monsters, so it doesn't get double-freed when the creature is killed below.
457             if (spawnedMonsters[j]->carriedItem == spawnedItems[i]) {
458                 spawnedMonsters[j]->carriedItem = NULL;
459                 break;
460             }
461         }
462         deleteItem(spawnedItems[i]);
463         spawnedItems[i] = NULL;
464     }
465     for (i=0; i<MACHINES_BUFFER_LENGTH && spawnedMonsters[i]; i++) {
466         killCreature(spawnedMonsters[i], true);
467         spawnedMonsters[i] = NULL;
468     }
469 }
470 
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)471 boolean cellIsFeatureCandidate(short x, short y,
472                                short originX, short originY,
473                                short distanceBound[2],
474                                char interior[DCOLS][DROWS],
475                                char occupied[DCOLS][DROWS],
476                                char viewMap[DCOLS][DROWS],
477                                short **distanceMap,
478                                short machineNumber,
479                                unsigned long featureFlags,
480                                unsigned long bpFlags) {
481     short newX, newY, dir, distance;
482 
483     // No building in the hallway if it's prohibited.
484     // This check comes before the origin check, so an area machine will fail altogether
485     // if its origin is in a hallway and the feature that must be built there does not permit as much.
486     if ((featureFlags & MF_NOT_IN_HALLWAY)
487         && passableArcCount(x, y) > 1) {
488         return false;
489     }
490 
491     // No building along the perimeter of the level if it's prohibited.
492     if ((featureFlags & MF_NOT_ON_LEVEL_PERIMETER)
493         && (x == 0 || x == DCOLS - 1 || y == 0 || y == DROWS - 1)) {
494         return false;
495     }
496 
497     // The origin is a candidate if the feature is flagged to be built at the origin.
498     // If it's a room, the origin (i.e. doorway) is otherwise NOT a candidate.
499     if (featureFlags & MF_BUILD_AT_ORIGIN) {
500         return ((x == originX && y == originY) ? true : false);
501     } else if ((bpFlags & BP_ROOM) && x == originX && y == originY) {
502         return false;
503     }
504 
505     // No building in another feature's personal space!
506     if (occupied[x][y]) {
507         return false;
508     }
509 
510     // Must be in the viewmap if the appropriate flag is set.
511     if ((featureFlags & (MF_IN_VIEW_OF_ORIGIN | MF_IN_PASSABLE_VIEW_OF_ORIGIN))
512         && !viewMap[x][y]) {
513         return false;
514     }
515 
516     // Do a distance check if the feature requests it.
517     if (cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY)) { // Distance is calculated for walls too.
518         distance = 10000;
519         for (dir = 0; dir < 4; dir++) {
520             newX = x + nbDirs[dir][0];
521             newY = y + nbDirs[dir][1];
522             if (coordinatesAreInMap(newX, newY)
523                 && !cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)
524                 && distance > distanceMap[newX][newY] + 1) {
525 
526                 distance = distanceMap[newX][newY] + 1;
527             }
528         }
529     } else {
530         distance = distanceMap[x][y];
531     }
532 
533     if (distance > distanceBound[1]     // distance exceeds max
534         || distance < distanceBound[0]) {   // distance falls short of min
535         return false;
536     }
537     if (featureFlags & MF_BUILD_IN_WALLS) {             // If we're supposed to build in a wall...
538         if (!interior[x][y]
539             && (pmap[x][y].machineNumber == 0 || pmap[x][y].machineNumber == machineNumber)
540             && cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY)) { // ...and this location is a wall that's not already machined...
541             for (dir=0; dir<4; dir++) {
542                 newX = x + nbDirs[dir][0];
543                 newY = y + nbDirs[dir][1];
544                 if (coordinatesAreInMap(newX, newY)     // ...and it's next to an interior spot or permitted elsewhere and next to passable spot...
545                     && ((interior[newX][newY] && !(newX==originX && newY==originY))
546                         || ((featureFlags & MF_BUILD_ANYWHERE_ON_LEVEL)
547                             && !cellHasTerrainFlag(newX, newY, T_PATHING_BLOCKER)
548                             && pmap[newX][newY].machineNumber == 0))) {
549                     return true;                        // ...then we're golden!
550                 }
551             }
552         }
553         return false;                                   // Otherwise, no can do.
554     } else if (cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY)) { // Can't build in a wall unless instructed to do so.
555         return false;
556     } else if (featureFlags & MF_BUILD_ANYWHERE_ON_LEVEL) {
557         if ((featureFlags & MF_GENERATE_ITEM)
558             && (cellHasTerrainFlag(x, y, T_OBSTRUCTS_ITEMS | T_PATHING_BLOCKER) || (pmap[x][y].flags & (IS_CHOKEPOINT | IN_LOOP | IS_IN_MACHINE)))) {
559             return false;
560         } else {
561             return !(pmap[x][y].flags & IS_IN_MACHINE);
562         }
563     } else if (interior[x][y]) {
564         return true;
565     }
566     return false;
567 }
568 
569 
addLocationToKey(item * theItem,short x,short y,boolean disposableHere)570 void addLocationToKey(item *theItem, short x, short y, boolean disposableHere) {
571     short i;
572 
573     for (i=0; i < KEY_ID_MAXIMUM && (theItem->keyLoc[i].x || theItem->keyLoc[i].machine); i++);
574     theItem->keyLoc[i].x = x;
575     theItem->keyLoc[i].y = y;
576     theItem->keyLoc[i].disposableHere = disposableHere;
577 }
578 
addMachineNumberToKey(item * theItem,short machineNumber,boolean disposableHere)579 void addMachineNumberToKey(item *theItem, short machineNumber, boolean disposableHere) {
580     short i;
581 
582     for (i=0; i < KEY_ID_MAXIMUM && (theItem->keyLoc[i].x || theItem->keyLoc[i].machine); i++);
583     theItem->keyLoc[i].machine = machineNumber;
584     theItem->keyLoc[i].disposableHere = disposableHere;
585 }
586 
expandMachineInterior(char interior[DCOLS][DROWS],short minimumInteriorNeighbors)587 void expandMachineInterior(char interior[DCOLS][DROWS], short minimumInteriorNeighbors) {
588     boolean madeChange;
589     short nbcount, newX, newY, i, j, layer;
590     enum directions dir;
591 
592     do {
593         madeChange = false;
594         for(i=1; i<DCOLS-1; i++) {
595             for(j=1; j < DROWS-1; j++) {
596                 if (cellHasTerrainFlag(i, j, T_PATHING_BLOCKER)
597                     && pmap[i][j].machineNumber == 0) {
598 
599                     // Count up the number of interior open neighbors out of eight:
600                     for (nbcount = dir = 0; dir < DIRECTION_COUNT; dir++) {
601                         newX = i + nbDirs[dir][0];
602                         newY = j + nbDirs[dir][1];
603                         if (interior[newX][newY]
604                             && !cellHasTerrainFlag(newX, newY, T_PATHING_BLOCKER)) {
605                             nbcount++;
606                         }
607                     }
608                     if (nbcount >= minimumInteriorNeighbors) {
609                         // Make sure zero exterior open/machine neighbors out of eight:
610                         for (nbcount = dir = 0; dir < DIRECTION_COUNT; dir++) {
611                             newX = i + nbDirs[dir][0];
612                             newY = j + nbDirs[dir][1];
613                             if (!interior[newX][newY]
614                                 && (!cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY) || pmap[newX][newY].machineNumber != 0)) {
615                                 nbcount++;
616                                 break;
617                             }
618                         }
619                         if (!nbcount) {
620                             // Eliminate this obstruction; welcome its location into the machine.
621                             madeChange = true;
622                             interior[i][j] = true;
623                             for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
624                                 if (tileCatalog[pmap[i][j].layers[layer]].flags & T_PATHING_BLOCKER) {
625                                     pmap[i][j].layers[layer] = (layer == DUNGEON ? FLOOR : NOTHING);
626                                 }
627                             }
628                             for (dir = 0; dir < DIRECTION_COUNT; dir++) {
629                                 newX = i + nbDirs[dir][0];
630                                 newY = j + nbDirs[dir][1];
631                                 if (pmap[newX][newY].layers[DUNGEON] == GRANITE) {
632                                     pmap[newX][newY].layers[DUNGEON] = WALL;
633                                 }
634                             }
635                         }
636                     }
637                 }
638             }
639         }
640     } while (madeChange);
641 
642     // Clear doors and secret doors out of the interior of the machine.
643     for(i=1; i<DCOLS-1; i++) {
644         for(j=1; j < DROWS-1; j++) {
645             if (interior[i][j]
646                 && (pmap[i][j].layers[DUNGEON] == DOOR || pmap[i][j].layers[DUNGEON] == SECRET_DOOR)) {
647 
648                 pmap[i][j].layers[DUNGEON] = FLOOR;
649             }
650         }
651     }
652 }
653 
fillInteriorForVestibuleMachine(char interior[DCOLS][DROWS],short bp,short originX,short originY)654 boolean fillInteriorForVestibuleMachine(char interior[DCOLS][DROWS], short bp, short originX, short originY) {
655     short **distanceMap, **costMap, qualifyingTileCount, totalFreq, sRows[DROWS], sCols[DCOLS], i, j, k;
656     boolean success = true;
657 
658     zeroOutGrid(interior);
659 
660     distanceMap = allocGrid();
661     fillGrid(distanceMap, 30000);
662     distanceMap[originX][originY] = 0;
663 
664     costMap = allocGrid();
665     populateGenericCostMap(costMap);
666     for(i=0; i<DCOLS; i++) {
667         for(j=0; j<DROWS; j++) {
668             if (costMap[i][j] == 1 && (pmap[i][j].flags & IS_IN_MACHINE)) { //pmap[i][j].machineNumber) {
669                 costMap[i][j] = PDS_FORBIDDEN;
670             }
671         }
672     }
673     costMap[originX][originY] = 1;
674     dijkstraScan(distanceMap, costMap, false);
675     freeGrid(costMap);
676 
677     qualifyingTileCount = 0; // Keeps track of how many interior cells we've added.
678     totalFreq = rand_range(blueprintCatalog[bp].roomSize[0], blueprintCatalog[bp].roomSize[1]); // Keeps track of the goal size.
679 
680     fillSequentialList(sCols, DCOLS);
681     shuffleList(sCols, DCOLS);
682     fillSequentialList(sRows, DROWS);
683     shuffleList(sRows, DROWS);
684 
685     for (k=0; k<1000 && qualifyingTileCount < totalFreq; k++) {
686         for(i=0; i<DCOLS && qualifyingTileCount < totalFreq; i++) {
687             for(j=0; j<DROWS && qualifyingTileCount < totalFreq; j++) {
688                 if (distanceMap[sCols[i]][sRows[j]] == k) {
689                     interior[sCols[i]][sRows[j]] = true;
690                     qualifyingTileCount++;
691 
692                     if (pmap[sCols[i]][sRows[j]].flags & HAS_ITEM) {
693                         // Abort if we've engulfed another machine's item.
694                         success = false;
695                         qualifyingTileCount = totalFreq; // This is a hack to drop out of these three for-loops.
696                     }
697                 }
698             }
699         }
700     }
701 
702     // Now make sure the interior map satisfies the machine's qualifications.
703     if ((blueprintCatalog[bp].flags & BP_TREAT_AS_BLOCKING)
704         && levelIsDisconnectedWithBlockingMap(interior, false)) {
705         success = false;
706     } else if ((blueprintCatalog[bp].flags & BP_REQUIRE_BLOCKING)
707                && levelIsDisconnectedWithBlockingMap(interior, true) < 100) {
708         success = false;
709     }
710     freeGrid(distanceMap);
711     return success;
712 }
713 
redesignInterior(char interior[DCOLS][DROWS],short originX,short originY,short theProfileIndex)714 void redesignInterior(char interior[DCOLS][DROWS], short originX, short originY, short theProfileIndex) {
715     short i, j, n, newX, newY;
716     enum directions dir;
717     short orphanList[20][2];
718     short orphanCount = 0;
719     short **grid, **pathingGrid, **costGrid;
720     grid = allocGrid();
721 
722     for (i=0; i<DCOLS; i++) {
723         for (j=0; j<DROWS; j++) {
724             if (interior[i][j]) {
725                 if (i == originX && j == originY) {
726                     grid[i][j] = 1; // All rooms must grow from this space.
727                 } else {
728                     grid[i][j] = 0; // Other interior squares are fair game for placing rooms.
729                 }
730             } else if (cellIsPassableOrDoor(i, j)) {
731                 grid[i][j] = 1; // Treat existing level as already built (though shielded by a film of -1s).
732                 for (dir = 0; dir < 4; dir++) {
733                     newX = i + nbDirs[dir][0];
734                     newY = j + nbDirs[dir][1];
735                     if (coordinatesAreInMap(newX, newY)
736                         && interior[newX][newY]
737                         && (newX != originX || newY != originY)) {
738 
739                         orphanList[orphanCount][0] = newX;
740                         orphanList[orphanCount][1] = newY;
741                         orphanCount++;
742                         grid[i][j] = -1; // Treat the orphaned door as off limits.
743 
744                         break;
745                     }
746                 }
747             } else {
748                 grid[i][j] = -1; // Exterior spaces are off limits.
749             }
750         }
751     }
752     attachRooms(grid, &dungeonProfileCatalog[theProfileIndex], 40, 40);
753 
754     // Connect to preexisting rooms that were orphaned (mostly preexisting machine rooms).
755     if (orphanCount > 0) {
756         pathingGrid = allocGrid();
757         costGrid = allocGrid();
758         for (n = 0; n < orphanCount; n++) {
759 
760             if (D_INSPECT_MACHINES) {
761                 dumpLevelToScreen();
762                 copyGrid(pathingGrid, grid);
763                 findReplaceGrid(pathingGrid, -1, -1, 0);
764                 hiliteGrid(pathingGrid, &green, 50);
765                 plotCharWithColor('X', mapToWindowX(orphanList[n][0]), mapToWindowY(orphanList[n][1]), &black, &orange);
766                 temporaryMessage("Orphan detected:", REQUIRE_ACKNOWLEDGMENT);
767             }
768 
769             for (i=0; i<DCOLS; i++) {
770                 for (j=0; j<DROWS; j++) {
771                     if (interior[i][j]) {
772                         if (grid[i][j] > 0) {
773                             pathingGrid[i][j] = 0;
774                             costGrid[i][j] = 1;
775                         } else {
776                             pathingGrid[i][j] = 30000;
777                             costGrid[i][j] = 1;
778                         }
779                     } else {
780                         pathingGrid[i][j] = 30000;
781                         costGrid[i][j] = PDS_OBSTRUCTION;
782                     }
783                 }
784             }
785             dijkstraScan(pathingGrid, costGrid, false);
786 
787             i = orphanList[n][0];
788             j = orphanList[n][1];
789             while (pathingGrid[i][j] > 0) {
790                 for (dir = 0; dir < 4; dir++) {
791                     newX = i + nbDirs[dir][0];
792                     newY = j + nbDirs[dir][1];
793 
794                     if (coordinatesAreInMap(newX, newY)
795                         && pathingGrid[newX][newY] < pathingGrid[i][j]) {
796 
797                         grid[i][j] = 1;
798                         i = newX;
799                         j = newY;
800                         break;
801                     }
802                 }
803                 brogueAssert(dir < 4);
804                 if (D_INSPECT_MACHINES) {
805                     dumpLevelToScreen();
806                     displayGrid(pathingGrid);
807                     plotCharWithColor('X', mapToWindowX(i), mapToWindowY(j), &black, &orange);
808                     temporaryMessage("Orphan connecting:", REQUIRE_ACKNOWLEDGMENT);
809                 }
810             }
811         }
812         freeGrid(pathingGrid);
813         freeGrid(costGrid);
814     }
815 
816     addLoops(grid, 10);
817     for(i=0; i<DCOLS; i++) {
818         for(j=0; j<DROWS; j++) {
819             if (interior[i][j]) {
820                 if (grid[i][j] >= 0) {
821                     pmap[i][j].layers[SURFACE] = pmap[i][j].layers[GAS] = NOTHING;
822                 }
823                 if (grid[i][j] == 0) {
824                     pmap[i][j].layers[DUNGEON] = GRANITE;
825                     interior[i][j] = false;
826                 }
827                 if (grid[i][j] >= 1) {
828                     pmap[i][j].layers[DUNGEON] = FLOOR;
829                 }
830             }
831         }
832     }
833     freeGrid(grid);
834 }
835 
prepareInteriorWithMachineFlags(char interior[DCOLS][DROWS],short originX,short originY,unsigned long flags,short dungeonProfileIndex)836 void prepareInteriorWithMachineFlags(char interior[DCOLS][DROWS], short originX, short originY, unsigned long flags, short dungeonProfileIndex) {
837     short i, j, newX, newY;
838     enum dungeonLayers layer;
839     enum directions dir;
840 
841     // If requested, clear and expand the room as far as possible until either it's convex or it bumps into surrounding rooms
842     if (flags & BP_MAXIMIZE_INTERIOR) {
843         expandMachineInterior(interior, 1);
844     } else if (flags & BP_OPEN_INTERIOR) {
845         expandMachineInterior(interior, 4);
846     }
847 
848     // If requested, cleanse the interior -- no interesting terrain allowed.
849     if (flags & BP_PURGE_INTERIOR) {
850         for(i=0; i<DCOLS; i++) {
851             for(j=0; j<DROWS; j++) {
852                 if (interior[i][j]) {
853                     for (layer=0; layer<NUMBER_TERRAIN_LAYERS; layer++) {
854                         pmap[i][j].layers[layer] = (layer == DUNGEON ? FLOOR : NOTHING);
855                     }
856                 }
857             }
858         }
859     }
860 
861     // If requested, purge pathing blockers -- no traps allowed.
862     if (flags & BP_PURGE_PATHING_BLOCKERS) {
863         for(i=0; i<DCOLS; i++) {
864             for(j=0; j<DROWS; j++) {
865                 if (interior[i][j]) {
866                     for (layer=0; layer<NUMBER_TERRAIN_LAYERS; layer++) {
867                         if (tileCatalog[pmap[i][j].layers[layer]].flags & T_PATHING_BLOCKER) {
868                             pmap[i][j].layers[layer] = (layer == DUNGEON ? FLOOR : NOTHING);
869                         }
870                     }
871                 }
872             }
873         }
874     }
875 
876     // If requested, purge the liquid layer in the interior -- no liquids allowed.
877     if (flags & BP_PURGE_LIQUIDS) {
878         for(i=0; i<DCOLS; i++) {
879             for(j=0; j<DROWS; j++) {
880                 if (interior[i][j]) {
881                     pmap[i][j].layers[LIQUID] = NOTHING;
882                 }
883             }
884         }
885     }
886 
887     // Surround with walls if requested.
888     if (flags & BP_SURROUND_WITH_WALLS) {
889         for(i=0; i<DCOLS; i++) {
890             for(j=0; j<DROWS; j++) {
891                 if (interior[i][j] && !(pmap[i][j].flags & IS_GATE_SITE)) {
892                     for (dir=0; dir< DIRECTION_COUNT; dir++) {
893                         newX = i + nbDirs[dir][0];
894                         newY = j + nbDirs[dir][1];
895                         if (coordinatesAreInMap(newX, newY)
896                             && !interior[newX][newY]
897                             && !cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)
898                             && !(pmap[newX][newY].flags & IS_GATE_SITE)
899                             && !pmap[newX][newY].machineNumber
900                             && cellHasTerrainFlag(newX, newY, T_PATHING_BLOCKER)) {
901                             for (layer=0; layer<NUMBER_TERRAIN_LAYERS; layer++) {
902                                 pmap[newX][newY].layers[layer] = (layer == DUNGEON ? WALL : 0);
903                             }
904                         }
905                     }
906                 }
907             }
908         }
909     }
910 
911     // Completely clear the interior, fill with granite, and cut entirely new rooms into it from the gate site.
912     // Then zero out any portion of the interior that is still wall.
913     if (flags & BP_REDESIGN_INTERIOR) {
914         redesignInterior(interior, originX, originY, dungeonProfileIndex);
915     }
916 
917     // Reinforce surrounding tiles and interior tiles if requested to prevent tunneling in or through.
918     if (flags & BP_IMPREGNABLE) {
919         for(i=0; i<DCOLS; i++) {
920             for(j=0; j<DROWS; j++) {
921                 if (interior[i][j]
922                     && !(pmap[i][j].flags & IS_GATE_SITE)) {
923 
924                     pmap[i][j].flags |= IMPREGNABLE;
925                     for (dir=0; dir< DIRECTION_COUNT; dir++) {
926                         newX = i + nbDirs[dir][0];
927                         newY = j + nbDirs[dir][1];
928                         if (coordinatesAreInMap(newX, newY)
929                             && !interior[newX][newY]
930                             && !(pmap[newX][newY].flags & IS_GATE_SITE)) {
931 
932                             pmap[newX][newY].flags |= IMPREGNABLE;
933                         }
934                     }
935                 }
936             }
937         }
938     }
939 }
940 
941 typedef struct machineData {
942     // Our boolean grids:
943     char interior[DCOLS][DROWS];    // This is the master grid for the machine. All area inside the machine are set to true.
944     char occupied[DCOLS][DROWS];    // This keeps track of what is within the personal space of a previously built feature in the same machine.
945     char candidates[DCOLS][DROWS];  // This is calculated at the start of each feature, and is true where that feature is eligible for building.
946     char blockingMap[DCOLS][DROWS]; // Used during terrain/DF placement in features that are flagged not to tolerate blocking, to see if they block.
947     char viewMap[DCOLS][DROWS];     // Used for features with MF_IN_VIEW_OF_ORIGIN, to calculate which cells are in view of the origin.
948 
949     pcell levelBackup[DCOLS][DROWS];
950 
951     item *spawnedItems[MACHINES_BUFFER_LENGTH];
952     item *spawnedItemsSub[MACHINES_BUFFER_LENGTH];
953     creature *spawnedMonsters[MACHINES_BUFFER_LENGTH];
954     creature *spawnedMonstersSub[MACHINES_BUFFER_LENGTH];
955 
956     short gateCandidates[50][2];
957     short distances[100];
958     short sRows[DROWS];
959     short sCols[DCOLS];
960 } machineData;
961 
962 // Returns true if the machine got built; false if it was aborted.
963 // If empty array parentSpawnedItems or parentSpawnedMonsters is given, will pass those back for deletion if necessary.
buildAMachine(enum machineTypes bp,short originX,short originY,unsigned long requiredMachineFlags,item * adoptiveItem,item * parentSpawnedItems[MACHINES_BUFFER_LENGTH],creature * parentSpawnedMonsters[MACHINES_BUFFER_LENGTH])964 boolean buildAMachine(enum machineTypes bp,
965                       short originX, short originY,
966                       unsigned long requiredMachineFlags,
967                       item *adoptiveItem,
968                       item *parentSpawnedItems[MACHINES_BUFFER_LENGTH],
969                       creature *parentSpawnedMonsters[MACHINES_BUFFER_LENGTH]) {
970 
971     short i, j, k, layer, feat, randIndex, totalFreq, instance, instanceCount = 0,
972         featX, featY, itemCount, monsterCount, qualifyingTileCount,
973         **distanceMap = NULL, distance25, distance75, distanceBound[2],
974         personalSpace, failsafe, locationFailsafe,
975         machineNumber;
976 
977     const unsigned long alternativeFlags[2] = {MF_ALTERNATIVE, MF_ALTERNATIVE_2};
978 
979     boolean DFSucceeded, terrainSucceeded, generateEverywhere, chooseBP,
980         chooseLocation, tryAgain, success = false, skipFeature[20];
981 
982     creature *monst = NULL, *torchBearer = NULL, *leader = NULL;
983 
984     item *theItem = NULL, *torch = NULL;
985 
986     const machineFeature *feature;
987 
988     machineData *p = malloc(sizeof(machineData));
989 
990     memset(p, 0, sizeof(machineData));
991 
992     chooseBP = (((signed short) bp) <= 0 ? true : false);
993 
994     chooseLocation = (originX <= 0 || originY <= 0 ? true : false);
995 
996     failsafe = 10;
997     do {
998         tryAgain = false;
999         if (--failsafe <= 0) {
1000             if (distanceMap) {
1001                 freeGrid(distanceMap);
1002             }
1003             if (D_MESSAGE_MACHINE_GENERATION) {
1004                 if (chooseBP || chooseLocation) {
1005                     printf("\nDepth %i: Failed to build a machine; gave up after 10 unsuccessful attempts to find a suitable blueprint and/or location.",
1006                            rogue.depthLevel);
1007                 } else {
1008                     printf("\nDepth %i: Failed to build a machine; requested blueprint and location did not work.",
1009                            rogue.depthLevel);
1010                 }
1011             }
1012             free(p);
1013             return false;
1014         }
1015 
1016         if (chooseBP) { // If no blueprint is given, then pick one:
1017 
1018             // First, choose the blueprint. We choose from among blueprints
1019             // that have the required blueprint flags and that satisfy the depth requirements.
1020             totalFreq = 0;
1021             for (i=1; i<NUMBER_BLUEPRINTS; i++) {
1022                 if (blueprintQualifies(i, requiredMachineFlags)) {
1023                     totalFreq += blueprintCatalog[i].frequency;
1024                 }
1025             }
1026 
1027             if (!totalFreq) { // If no suitable blueprints are in the library, fail.
1028                 if (distanceMap) {
1029                     freeGrid(distanceMap);
1030                 }
1031                 if (D_MESSAGE_MACHINE_GENERATION) printf("\nDepth %i: Failed to build a machine because no suitable blueprints were available.",
1032                              rogue.depthLevel);
1033                 free(p);
1034                 return false;
1035             }
1036 
1037             // Pick from among the suitable blueprints.
1038             randIndex = rand_range(1, totalFreq);
1039             for (i=1; i<NUMBER_BLUEPRINTS; i++) {
1040                 if (blueprintQualifies(i, requiredMachineFlags)) {
1041                     if (randIndex <= blueprintCatalog[i].frequency) {
1042                         bp = i;
1043                         break;
1044                     } else {
1045                         randIndex -= blueprintCatalog[i].frequency;
1046                     }
1047                 }
1048             }
1049 
1050             // If we don't have a blueprint yet, something went wrong.
1051             brogueAssert(bp>0);
1052         }
1053 
1054         // Find a location and map out the machine interior.
1055         if (blueprintCatalog[bp].flags & BP_ROOM) {
1056             // If it's a room machine, count up the gates of appropriate
1057             // choke size and remember where they are. The origin of the room will be the gate location.
1058             zeroOutGrid(p->interior);
1059 
1060             if (chooseLocation) {
1061                 analyzeMap(true); // Make sure the chokeMap is up to date.
1062                 totalFreq = 0;
1063                 for(i=0; i<DCOLS; i++) {
1064                     for(j=0; j<DROWS && totalFreq < 50; j++) {
1065                         if ((pmap[i][j].flags & IS_GATE_SITE)
1066                             && !(pmap[i][j].flags & IS_IN_MACHINE)
1067                             && chokeMap[i][j] >= blueprintCatalog[bp].roomSize[0]
1068                             && chokeMap[i][j] <= blueprintCatalog[bp].roomSize[1]) {
1069 
1070                             //DEBUG printf("\nDepth %i: Gate site qualified with interior size of %i.", rogue.depthLevel, chokeMap[i][j]);
1071                             p->gateCandidates[totalFreq][0] = i;
1072                             p->gateCandidates[totalFreq][1] = j;
1073                             totalFreq++;
1074                         }
1075                     }
1076                 }
1077 
1078                 if (totalFreq) {
1079                     // Choose the gate.
1080                     randIndex = rand_range(0, totalFreq - 1);
1081                     originX = p->gateCandidates[randIndex][0];
1082                     originY = p->gateCandidates[randIndex][1];
1083                 } else {
1084                     // If no suitable sites, abort.
1085                     if (distanceMap) {
1086                         freeGrid(distanceMap);
1087                     }
1088                     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.",
1089                                  rogue.depthLevel,
1090                                  bp);
1091                     free(p);
1092                     return false;
1093                 }
1094             }
1095 
1096             // Now map out the interior into interior[][].
1097             // Start at the gate location and do a depth-first floodfill to grab all adjoining tiles with the
1098             // same or lower choke value, ignoring any tiles that are already part of a machine.
1099             // If we get false from this, try again. If we've tried too many times already, abort.
1100             tryAgain = !addTileToMachineInteriorAndIterate(p->interior, originX, originY);
1101         } else if (blueprintCatalog[bp].flags & BP_VESTIBULE) {
1102             if (chooseLocation) {
1103                 // Door machines must have locations passed in. We can't pick one ourselves.
1104                 if (distanceMap) {
1105                     freeGrid(distanceMap);
1106                 }
1107                 if (D_MESSAGE_MACHINE_GENERATION) printf("\nDepth %i: ERROR: Attempted to build a door machine from blueprint %i without a location being provided.",
1108                              rogue.depthLevel,
1109                              bp);
1110                 free(p);
1111                 return false;
1112             }
1113             if (!fillInteriorForVestibuleMachine(p->interior, bp, originX, originY)) {
1114                 if (distanceMap) {
1115                     freeGrid(distanceMap);
1116                 }
1117                 if (D_MESSAGE_MACHINE_GENERATION) printf("\nDepth %i: Failed to build a door machine from blueprint %i; not enough room.",
1118                              rogue.depthLevel,
1119                              bp);
1120                 free(p);
1121                 return false;
1122             }
1123         } else {
1124             // Find a location and map out the interior for a non-room machine.
1125             // The strategy here is simply to pick a random location on the map,
1126             // expand it along a pathing map by one space in all directions until the size reaches
1127             // the chosen size, and then make sure the resulting space qualifies.
1128             // If not, try again. If we've tried too many times already, abort.
1129 
1130             locationFailsafe = 10;
1131             do {
1132                 zeroOutGrid(p->interior);
1133                 tryAgain = false;
1134 
1135                 if (chooseLocation) {
1136                     // Pick a random origin location.
1137                     randomMatchingLocation(&originX, &originY, FLOOR, NOTHING, -1);
1138                 }
1139 
1140                 if (!distanceMap) {
1141                     distanceMap = allocGrid();
1142                 }
1143                 fillGrid(distanceMap, 0);
1144                 calculateDistances(distanceMap, originX, originY, T_PATHING_BLOCKER, NULL, true, false);
1145                 qualifyingTileCount = 0; // Keeps track of how many interior cells we've added.
1146                 totalFreq = rand_range(blueprintCatalog[bp].roomSize[0], blueprintCatalog[bp].roomSize[1]); // Keeps track of the goal size.
1147 
1148                 fillSequentialList(p->sCols, DCOLS);
1149                 shuffleList(p->sCols, DCOLS);
1150                 fillSequentialList(p->sRows, DROWS);
1151                 shuffleList(p->sRows, DROWS);
1152 
1153                 for (k=0; k<1000 && qualifyingTileCount < totalFreq; k++) {
1154                     for(i=0; i<DCOLS && qualifyingTileCount < totalFreq; i++) {
1155                         for(j=0; j<DROWS && qualifyingTileCount < totalFreq; j++) {
1156                             if (distanceMap[p->sCols[i]][p->sRows[j]] == k) {
1157                                 p->interior[p->sCols[i]][p->sRows[j]] = true;
1158                                 qualifyingTileCount++;
1159 
1160                                 if (pmap[p->sCols[i]][p->sRows[j]].flags & (HAS_ITEM | HAS_MONSTER | IS_IN_MACHINE)) {
1161                                     // Abort if we've entered another machine or engulfed another machine's item or monster.
1162                                     tryAgain = true;
1163                                     qualifyingTileCount = totalFreq; // This is a hack to drop out of these three for-loops.
1164                                 }
1165                             }
1166                         }
1167                     }
1168                 }
1169 
1170                 // Now make sure the interior map satisfies the machine's qualifications.
1171                 if ((blueprintCatalog[bp].flags & BP_TREAT_AS_BLOCKING)
1172                     && levelIsDisconnectedWithBlockingMap(p->interior, false)) {
1173                     tryAgain = true;
1174                 } else if ((blueprintCatalog[bp].flags & BP_REQUIRE_BLOCKING)
1175                            && levelIsDisconnectedWithBlockingMap(p->interior, true) < 100) {
1176                     tryAgain = true; // BP_REQUIRE_BLOCKING needs some work to make sure the disconnect is interesting.
1177                 }
1178                 // If locationFailsafe runs out, tryAgain will still be true, and we'll try a different machine.
1179                 // If we're not choosing the blueprint, then don't bother with the locationFailsafe; just use the higher-level failsafe.
1180             } while (chooseBP && tryAgain && --locationFailsafe);
1181         }
1182 
1183         // If something went wrong, but we haven't been charged with choosing blueprint OR location,
1184         // then there is nothing to try again, so just fail.
1185         if (tryAgain && !chooseBP && !chooseLocation) {
1186             if (distanceMap) {
1187                 freeGrid(distanceMap);
1188             }
1189             free(p);
1190             return false;
1191         }
1192 
1193         // Now loop if necessary.
1194     } while (tryAgain);
1195 
1196     // 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.
1197     copyMap(pmap, p->levelBackup);
1198 
1199     // Perform any transformations to the interior indicated by the blueprint flags, including expanding the interior if requested.
1200     prepareInteriorWithMachineFlags(p->interior, originX, originY, blueprintCatalog[bp].flags, blueprintCatalog[bp].dungeonProfileType);
1201 
1202     // If necessary, label the interior as IS_IN_AREA_MACHINE or IS_IN_ROOM_MACHINE and mark down the number.
1203     machineNumber = ++rogue.machineNumber; // Reserve this machine number, starting with 1.
1204     for(i=0; i<DCOLS; i++) {
1205         for(j=0; j<DROWS; j++) {
1206             if (p->interior[i][j]) {
1207                 pmap[i][j].flags |= ((blueprintCatalog[bp].flags & BP_ROOM) ? IS_IN_ROOM_MACHINE : IS_IN_AREA_MACHINE);
1208                 pmap[i][j].machineNumber = machineNumber;
1209                 // also clear any secret doors, since they screw up distance mapping and aren't fun inside machines
1210                 if (pmap[i][j].layers[DUNGEON] == SECRET_DOOR) {
1211                     pmap[i][j].layers[DUNGEON] = DOOR;
1212                 }
1213                 // Clear wired tiles in case we stole them from another machine.
1214                 for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
1215                     if (tileCatalog[pmap[i][j].layers[layer]].mechFlags & (TM_IS_WIRED | TM_IS_CIRCUIT_BREAKER)) {
1216                         pmap[i][j].layers[layer] = (layer == DUNGEON ? FLOOR : NOTHING);
1217                     }
1218                 }
1219             }
1220         }
1221     }
1222 
1223 //  DEBUG printf("\n\nWorking on blueprint %i, with origin at (%i, %i). Here's the initial interior map:", bp, originX, originY);
1224 //  DEBUG logBuffer(interior);
1225 
1226     // Calculate the distance map (so that features that want to be close to or far from the origin can be placed accordingly)
1227     // and figure out the 33rd and 67th percentiles for features that want to be near or far from the origin.
1228     if (!distanceMap) {
1229         distanceMap = allocGrid();
1230     }
1231     fillGrid(distanceMap, 0);
1232     calculateDistances(distanceMap, originX, originY, T_PATHING_BLOCKER, NULL, true, true);
1233     qualifyingTileCount = 0;
1234     for (i=0; i<100; i++) {
1235         p->distances[i] = 0;
1236     }
1237     for(i=0; i<DCOLS; i++) {
1238         for(j=0; j<DROWS; j++) {
1239             if (p->interior[i][j]
1240                 && distanceMap[i][j] < 100) {
1241                 p->distances[distanceMap[i][j]]++; // create a histogram of distances -- poor man's sort function
1242                 qualifyingTileCount++;
1243             }
1244         }
1245     }
1246     distance25 = (int) (qualifyingTileCount / 4);
1247     distance75 = (int) (3 * qualifyingTileCount / 4);
1248     for (i=0; i<100; i++) {
1249         if (distance25 <= p->distances[i]) {
1250             distance25 = i;
1251             break;
1252         } else {
1253             distance25 -= p->distances[i];
1254         }
1255     }
1256     for (i=0; i<100; i++) {
1257         if (distance75 <= p->distances[i]) {
1258             distance75 = i;
1259             break;
1260         } else {
1261             distance75 -= p->distances[i];
1262         }
1263     }
1264     //DEBUG printf("\nDistances calculated: 33rd percentile of distance is %i, and 67th is %i.", distance25, distance75);
1265 
1266     // Now decide which features will be skipped -- of the features marked MF_ALTERNATIVE, skip all but one, chosen randomly.
1267     // Then repeat and do the same with respect to MF_ALTERNATIVE_2, to provide up to two independent sets of alternative features per machine.
1268 
1269     for (i=0; i<blueprintCatalog[bp].featureCount; i++) {
1270         skipFeature[i] = false;
1271     }
1272     for (j = 0; j <= 1; j++) {
1273         totalFreq = 0;
1274         for (i=0; i<blueprintCatalog[bp].featureCount; i++) {
1275             if (blueprintCatalog[bp].feature[i].flags & alternativeFlags[j]) {
1276                 skipFeature[i] = true;
1277                 totalFreq++;
1278             }
1279         }
1280         if (totalFreq > 0) {
1281             randIndex = rand_range(1, totalFreq);
1282             for (i=0; i<blueprintCatalog[bp].featureCount; i++) {
1283                 if (blueprintCatalog[bp].feature[i].flags & alternativeFlags[j]) {
1284                     if (randIndex == 1) {
1285                         skipFeature[i] = false; // This is the alternative that gets built. The rest do not.
1286                         break;
1287                     } else {
1288                         randIndex--;
1289                     }
1290                 }
1291             }
1292         }
1293     }
1294 
1295     // Keep track of all monsters and items that we spawn -- if we abort, we have to go back and delete them all.
1296     itemCount = monsterCount = 0;
1297 
1298     // Zero out occupied[][], and use it to keep track of the personal space around each feature that gets placed.
1299     zeroOutGrid(p->occupied);
1300 
1301     // Now tick through the features and build them.
1302     for (feat = 0; feat < blueprintCatalog[bp].featureCount; feat++) {
1303 
1304         if (skipFeature[feat]) {
1305             continue; // Skip the alternative features that were not selected for building.
1306         }
1307 
1308         feature = &(blueprintCatalog[bp].feature[feat]);
1309 
1310         // Figure out the distance bounds.
1311         distanceBound[0] = 0;
1312         distanceBound[1] = 10000;
1313         if (feature->flags & MF_NEAR_ORIGIN) {
1314             distanceBound[1] = distance25;
1315         }
1316         if (feature->flags & MF_FAR_FROM_ORIGIN) {
1317             distanceBound[0] = distance75;
1318         }
1319 
1320         if (feature->flags & (MF_IN_VIEW_OF_ORIGIN | MF_IN_PASSABLE_VIEW_OF_ORIGIN)) {
1321             zeroOutGrid(p->viewMap);
1322             if (feature->flags & MF_IN_PASSABLE_VIEW_OF_ORIGIN) {
1323                 getFOVMask(p->viewMap, originX, originY, max(DCOLS, DROWS) * FP_FACTOR, T_PATHING_BLOCKER, 0, false);
1324             } else {
1325                 getFOVMask(p->viewMap, originX, originY, max(DCOLS, DROWS) * FP_FACTOR, (T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_VISION), 0, false);
1326             }
1327             p->viewMap[originX][originY] = true;
1328 
1329             if (D_INSPECT_MACHINES) {
1330                 dumpLevelToScreen();
1331                 hiliteCharGrid(p->viewMap, &omniscienceColor, 75);
1332                 temporaryMessage("Showing visibility.", REQUIRE_ACKNOWLEDGMENT);
1333             }
1334         }
1335 
1336         do { // If the MF_REPEAT_UNTIL_NO_PROGRESS flag is set, repeat until we fail to build the required number of instances.
1337 
1338             // Make a master map of candidate locations for this feature.
1339             qualifyingTileCount = 0;
1340             for(i=0; i<DCOLS; i++) {
1341                 for(j=0; j<DROWS; j++) {
1342                     if (cellIsFeatureCandidate(i, j,
1343                                                originX, originY,
1344                                                distanceBound,
1345                                                p->interior, p->occupied, p->viewMap, distanceMap,
1346                                                machineNumber, feature->flags, blueprintCatalog[bp].flags)) {
1347                         qualifyingTileCount++;
1348                         p->candidates[i][j] = true;
1349                     } else {
1350                         p->candidates[i][j] = false;
1351                     }
1352                 }
1353             }
1354 
1355             if (D_INSPECT_MACHINES) {
1356                 dumpLevelToScreen();
1357                 hiliteCharGrid(p->occupied, &red, 75);
1358                 hiliteCharGrid(p->candidates, &green, 75);
1359                 hiliteCharGrid(p->interior, &blue, 75);
1360                 temporaryMessage("Indicating: Occupied (red); Candidates (green); Interior (blue).", REQUIRE_ACKNOWLEDGMENT);
1361             }
1362 
1363             if (feature->flags & MF_EVERYWHERE & ~MF_BUILD_AT_ORIGIN) {
1364                 // Generate everywhere that qualifies -- instead of randomly picking tiles, keep spawning until we run out of eligible tiles.
1365                 generateEverywhere = true;
1366             } else {
1367                 // build as many instances as required
1368                 generateEverywhere = false;
1369                 instanceCount = rand_range(feature->instanceCountRange[0], feature->instanceCountRange[1]);
1370             }
1371 
1372             // Cache the personal space constant.
1373             personalSpace = feature->personalSpace;
1374 
1375             for (instance = 0; (generateEverywhere || instance < instanceCount) && qualifyingTileCount > 0;) {
1376 
1377                 // Find a location for the feature.
1378                 if (feature->flags & MF_BUILD_AT_ORIGIN) {
1379                     // Does the feature want to be at the origin? If so, put it there. (Just an optimization.)
1380                     featX = originX;
1381                     featY = originY;
1382                 } else {
1383                     // Pick our candidate location randomly, and also strike it from
1384                     // the candidates map so that subsequent instances of this same feature can't choose it.
1385                     featX = -1;
1386                     featY = -1;
1387                     randIndex = rand_range(1, qualifyingTileCount);
1388                     for(i=0; i<DCOLS && featX < 0; i++) {
1389                         for(j=0; j<DROWS && featX < 0; j++) {
1390                             if (p->candidates[i][j]) {
1391                                 if (randIndex == 1) {
1392                                     // This is the place!
1393                                     featX = i;
1394                                     featY = j;
1395                                     i = DCOLS;  // break out of the loops
1396                                     j = DROWS;
1397                                 } else {
1398                                     randIndex--;
1399                                 }
1400                             }
1401                         }
1402                     }
1403                 }
1404                 // Don't waste time trying the same place again whether or not this attempt succeeds.
1405                 p->candidates[featX][featY] = false;
1406                 qualifyingTileCount--;
1407 
1408                 DFSucceeded = terrainSucceeded = true;
1409 
1410                 // Try to build the DF first, if any, since we don't want it to be disrupted by subsequently placed terrain.
1411                 if (feature->featureDF) {
1412                     DFSucceeded = spawnDungeonFeature(featX, featY, &dungeonFeatureCatalog[feature->featureDF], false,
1413                                                       !(feature->flags & MF_PERMIT_BLOCKING));
1414                 }
1415 
1416                 // Now try to place the terrain tile, if any.
1417                 if (DFSucceeded && feature->terrain) {
1418                     // Must we check for blocking?
1419                     if (!(feature->flags & MF_PERMIT_BLOCKING)
1420                         && ((tileCatalog[feature->terrain].flags & T_PATHING_BLOCKER) || (feature->flags & MF_TREAT_AS_BLOCKING))) {
1421                         // Yes, check for blocking.
1422 
1423                         zeroOutGrid(p->blockingMap);
1424                         p->blockingMap[featX][featY] = true;
1425                         terrainSucceeded = !levelIsDisconnectedWithBlockingMap(p->blockingMap, false);
1426                     }
1427                     if (terrainSucceeded) {
1428                         pmap[featX][featY].layers[feature->layer] = feature->terrain;
1429                     }
1430                 }
1431 
1432                 // OK, if placement was successful, clear some personal space around the feature so subsequent features can't be generated too close.
1433                 // 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.
1434 
1435                 if (DFSucceeded && terrainSucceeded) {
1436                     for (i = featX - personalSpace + 1;
1437                          i <= featX + personalSpace - 1;
1438                          i++) {
1439                         for (j = featY - personalSpace + 1;
1440                              j <= featY + personalSpace - 1;
1441                              j++) {
1442                             if (coordinatesAreInMap(i, j)) {
1443                                 if (p->candidates[i][j]) {
1444                                     brogueAssert(!p->occupied[i][j] || (i == originX && j == originY)); // Candidates[][] should never be true where occupied[][] is true.
1445                                     p->candidates[i][j] = false;
1446                                     qualifyingTileCount--;
1447                                 }
1448                                 p->occupied[i][j] = true;
1449                             }
1450                         }
1451                     }
1452                     instance++; // we've placed an instance
1453                     //DEBUG printf("\nPlaced instance #%i of feature %i at (%i, %i).", instance, feat, featX, featY);
1454                 }
1455 
1456                 if (DFSucceeded && terrainSucceeded) { // Proceed only if the terrain stuff for this instance succeeded.
1457 
1458                     theItem = NULL;
1459 
1460                     // Mark the feature location as part of the machine, in case it is not already inside of it.
1461                     pmap[featX][featY].flags |= ((blueprintCatalog[bp].flags & BP_ROOM) ? IS_IN_ROOM_MACHINE : IS_IN_AREA_MACHINE);
1462                     pmap[featX][featY].machineNumber = machineNumber;
1463 
1464                     // Mark the feature location as impregnable if requested.
1465                     if (feature->flags & MF_IMPREGNABLE) {
1466                         pmap[featX][featY].flags |= IMPREGNABLE;
1467                     }
1468 
1469                     // Generate an item as necessary.
1470                     if ((feature->flags & MF_GENERATE_ITEM)
1471                         || (adoptiveItem && (feature->flags & MF_ADOPT_ITEM) && (blueprintCatalog[bp].flags & BP_ADOPT_ITEM))) {
1472                         // Are we adopting an item instead of generating one?
1473                         if (adoptiveItem && (feature->flags & MF_ADOPT_ITEM) && (blueprintCatalog[bp].flags & BP_ADOPT_ITEM)) {
1474                             theItem = adoptiveItem;
1475                             adoptiveItem = NULL; // can be adopted only once
1476                         } else {
1477                             // Have to create an item ourselves.
1478                             theItem = generateItem(feature->itemCategory, feature->itemKind);
1479                             failsafe = 1000;
1480                             while ((theItem->flags & ITEM_CURSED)
1481                                    || ((feature->flags & MF_REQUIRE_GOOD_RUNIC) && (!(theItem->flags & ITEM_RUNIC))) // runic if requested
1482                                    || ((feature->flags & MF_NO_THROWING_WEAPONS) && theItem->category == WEAPON && theItem->quantity > 1) // no throwing weapons if prohibited
1483                                    || itemIsADuplicate(theItem, p->spawnedItems, itemCount)) { // don't want to duplicates of rings, staffs, etc.
1484                                 deleteItem(theItem);
1485                                 theItem = generateItem(feature->itemCategory, feature->itemKind);
1486                                 if (failsafe <= 0) {
1487                                     break;
1488                                 }
1489                                 failsafe--;
1490                             }
1491                             p->spawnedItems[itemCount] = theItem; // Keep a list of generated items so that we can delete them all if construction fails.
1492                             itemCount++;
1493                         }
1494                         theItem->flags |= feature->itemFlags;
1495 
1496                         addLocationToKey(theItem, featX, featY, (feature->flags & MF_KEY_DISPOSABLE) ? true : false);
1497                         theItem->originDepth = rogue.depthLevel;
1498                         if (feature->flags & MF_SKELETON_KEY) {
1499                             addMachineNumberToKey(theItem, machineNumber, (feature->flags & MF_KEY_DISPOSABLE) ? true : false);
1500                         }
1501                         if (!(feature->flags & MF_OUTSOURCE_ITEM_TO_MACHINE)
1502                             && !(feature->flags & MF_MONSTER_TAKE_ITEM)) {
1503                             // Place the item at the feature location.
1504                             placeItem(theItem, featX, featY);
1505                         }
1506                     }
1507 
1508                     if (feature->flags & (MF_OUTSOURCE_ITEM_TO_MACHINE | MF_BUILD_VESTIBULE)) {
1509                         // Put this item up for adoption, or generate a door guard machine.
1510                         // Try to create a sub-machine that qualifies.
1511                         // If we fail 10 times, abort the entire machine (including any sub-machines already built).
1512                         // Also, if we build a sub-machine, and it succeeds, but this (its parent machine) fails,
1513                         // we pass the monsters and items that it spawned back to the parent,
1514                         // so that if the parent fails, they can all be freed.
1515                         for (i=10; i > 0; i--) {
1516                             // First make sure our adopted item, if any, is not on the floor or in the pack already.
1517                             // Otherwise, a previous attempt to place it may have put it on the floor in a different
1518                             // machine, only to have that machine fail and be deleted, leaving the item remaining on
1519                             // the floor where placed.
1520                             if ((feature->flags & MF_OUTSOURCE_ITEM_TO_MACHINE) && theItem) {
1521                                 removeItemFromChain(theItem, floorItems);
1522                                 removeItemFromChain(theItem, packItems);
1523                                 theItem->nextItem = NULL;
1524                                 success = buildAMachine(-1, -1, -1, BP_ADOPT_ITEM, theItem, p->spawnedItemsSub, p->spawnedMonstersSub);
1525                             } else if (feature->flags & MF_BUILD_VESTIBULE) {
1526                                 success = buildAMachine(-1, featX, featY, BP_VESTIBULE, NULL, p->spawnedItemsSub, p->spawnedMonstersSub);
1527                             }
1528 
1529                             // Now put the item up for adoption.
1530                             if (success) {
1531                                 // Success! Now we have to add that machine's items and monsters to our own list, so they
1532                                 // all get deleted if this machine or its parent fails.
1533                                 for (j=0; j<MACHINES_BUFFER_LENGTH && p->spawnedItemsSub[j]; j++) {
1534                                     p->spawnedItems[itemCount] = p->spawnedItemsSub[j];
1535                                     itemCount++;
1536                                     p->spawnedItemsSub[j] = NULL;
1537                                 }
1538                                 for (j=0; j<MACHINES_BUFFER_LENGTH && p->spawnedMonstersSub[j]; j++) {
1539                                     p->spawnedMonsters[monsterCount] = p->spawnedMonstersSub[j];
1540                                     monsterCount++;
1541                                     p->spawnedMonstersSub[j] = NULL;
1542                                 }
1543                                 break;
1544                             }
1545                         }
1546 
1547                         if (!i) {
1548                             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);
1549                             // failure! abort!
1550                             copyMap(p->levelBackup, pmap);
1551                             abortItemsAndMonsters(p->spawnedItems, p->spawnedMonsters);
1552                             freeGrid(distanceMap);
1553                             free(p);
1554                             return false;
1555                         }
1556                         theItem = NULL;
1557                     }
1558 
1559                     // Generate a horde as necessary.
1560                     if ((feature->flags & MF_GENERATE_HORDE)
1561                         || feature->monsterID) {
1562 
1563                         if (feature->flags & MF_GENERATE_HORDE) {
1564                             monst = spawnHorde(0,
1565                                                featX,
1566                                                featY,
1567                                                ((HORDE_IS_SUMMONED | HORDE_LEADER_CAPTIVE) & ~(feature->hordeFlags)),
1568                                                feature->hordeFlags);
1569                             if (monst) {
1570                                 monst->bookkeepingFlags |= MB_JUST_SUMMONED;
1571                             }
1572                         }
1573 
1574                         if (feature->monsterID) {
1575                             monst = monsterAtLoc(featX, featY);
1576                             if (monst) {
1577                                 killCreature(monst, true); // If there's already a monster here, quietly bury the body.
1578                             }
1579                             monst = generateMonster(feature->monsterID, true, true);
1580                             if (monst) {
1581                                 monst->xLoc = featX;
1582                                 monst->yLoc = featY;
1583                                 pmap[monst->xLoc][monst->yLoc].flags |= HAS_MONSTER;
1584                                 monst->bookkeepingFlags |= MB_JUST_SUMMONED;
1585                             }
1586                         }
1587 
1588                         if (monst) {
1589                             if (!leader) {
1590                                 leader = monst;
1591                             }
1592 
1593                             // Give our item to the monster leader if appropriate.
1594                             // Actually just remember that we have to give it to this monster; the actual
1595                             // hand-off happens after we're sure that the machine will succeed.
1596                             if (theItem && (feature->flags & MF_MONSTER_TAKE_ITEM)) {
1597                                 torchBearer = monst;
1598                                 torch = theItem;
1599                             }
1600                         }
1601 
1602                         for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) {
1603                             creature *monst = nextCreature(&it);
1604                             if (monst->bookkeepingFlags & MB_JUST_SUMMONED) {
1605 
1606                                 // All monsters spawned by a machine are tribemates.
1607                                 // Assign leader/follower roles if they are not yet assigned.
1608                                 if (!(monst->bookkeepingFlags & (MB_LEADER | MB_FOLLOWER))) {
1609                                     if (leader && leader != monst) {
1610                                         monst->leader = leader;
1611                                         monst->bookkeepingFlags &= ~MB_LEADER;
1612                                         monst->bookkeepingFlags |= MB_FOLLOWER;
1613                                         leader->bookkeepingFlags |= MB_LEADER;
1614                                     } else {
1615                                         leader = monst;
1616                                     }
1617                                 }
1618 
1619                                 monst->bookkeepingFlags &= ~MB_JUST_SUMMONED;
1620                                 p->spawnedMonsters[monsterCount] = monst;
1621                                 monsterCount++;
1622                                 if (feature->flags & MF_MONSTER_SLEEPING) {
1623                                     monst->creatureState = MONSTER_SLEEPING;
1624                                 }
1625                                 if (feature->flags & MF_MONSTER_FLEEING) {
1626                                     monst->creatureState = MONSTER_FLEEING;
1627                                     monst->creatureMode = MODE_PERM_FLEEING;
1628                                 }
1629                                 if (feature->flags & MF_MONSTERS_DORMANT) {
1630                                     toggleMonsterDormancy(monst);
1631                                     if (!(feature->flags & MF_MONSTER_SLEEPING) && monst->creatureState != MONSTER_ALLY) {
1632                                         monst->creatureState = MONSTER_TRACKING_SCENT;
1633                                     }
1634                                 }
1635                                 monst->machineHome = machineNumber; // Monster remembers the machine that spawned it.
1636                             }
1637                         }
1638                     }
1639                 }
1640                 theItem = NULL;
1641 
1642                 // Finished with this instance!
1643             }
1644         } while ((feature->flags & MF_REPEAT_UNTIL_NO_PROGRESS) && instance >= feature->minimumInstanceCount);
1645 
1646         //DEBUG printf("\nFinished feature %i. Here's the candidates map:", feat);
1647         //DEBUG logBuffer(candidates);
1648 
1649         if (instance < feature->minimumInstanceCount && !(feature->flags & MF_REPEAT_UNTIL_NO_PROGRESS)) {
1650             // failure! abort!
1651 
1652             if (D_MESSAGE_MACHINE_GENERATION) printf("\nDepth %i: Failed to place blueprint %i because of feature %i; needed %i instances but got only %i.",
1653                          rogue.depthLevel, bp, feat, feature->minimumInstanceCount, instance);
1654 
1655             // Restore the map to how it was before we touched it.
1656             copyMap(p->levelBackup, pmap);
1657             abortItemsAndMonsters(p->spawnedItems, p->spawnedMonsters);
1658             freeGrid(distanceMap);
1659             free(p);
1660             return false;
1661         }
1662     }
1663 
1664     // Clear out the interior flag for all non-wired cells, if requested.
1665     if (blueprintCatalog[bp].flags & BP_NO_INTERIOR_FLAG) {
1666         for(i=0; i<DCOLS; i++) {
1667             for(j=0; j<DROWS; j++) {
1668                 if (pmap[i][j].machineNumber == machineNumber
1669                     && !cellHasTMFlag(i, j, (TM_IS_WIRED | TM_IS_CIRCUIT_BREAKER))) {
1670 
1671                     pmap[i][j].flags &= ~IS_IN_MACHINE;
1672                     pmap[i][j].machineNumber = 0;
1673                 }
1674             }
1675         }
1676     }
1677 
1678     if (torchBearer && torch) {
1679         if (torchBearer->carriedItem) {
1680             deleteItem(torchBearer->carriedItem);
1681         }
1682         removeItemFromChain(torch, floorItems);
1683         torchBearer->carriedItem = torch;
1684     }
1685 
1686     freeGrid(distanceMap);
1687     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);
1688 
1689     //Pass created items and monsters to parent where they will be deleted on failure to place parent machine
1690     if (parentSpawnedItems) {
1691         for (i=0; i<itemCount; i++) {
1692             parentSpawnedItems[i] = p->spawnedItems[i];
1693         }
1694     }
1695     if (parentSpawnedMonsters) {
1696         for (i=0; i<monsterCount; i++) {
1697             parentSpawnedMonsters[i] = p->spawnedMonsters[i];
1698         }
1699     }
1700 
1701     free(p);
1702     return true;
1703 }
1704 
1705 // add machines to the dungeon.
addMachines()1706 void addMachines() {
1707     short machineCount, failsafe;
1708     short randomMachineFactor;
1709 
1710     analyzeMap(true);
1711 
1712     // Add the amulet holder if it's depth 26:
1713     if (rogue.depthLevel == AMULET_LEVEL) {
1714         for (failsafe = 50; failsafe; failsafe--) {
1715             if (buildAMachine(MT_AMULET_AREA, -1, -1, 0, NULL, NULL, NULL)) {
1716                 break;
1717             }
1718         }
1719     }
1720 
1721     // Add reward rooms, if any:
1722     machineCount = 0;
1723     while (rogue.depthLevel <= AMULET_LEVEL
1724         && (rogue.rewardRoomsGenerated + machineCount) * 4 + 2 < rogue.depthLevel * MACHINES_FACTOR / FP_FACTOR) {
1725         // try to build at least one every four levels on average
1726         machineCount++;
1727     }
1728     randomMachineFactor = (rogue.depthLevel < 3 && (rogue.rewardRoomsGenerated + machineCount) == 0 ? 40 : 15);
1729     while (rand_percent(max(randomMachineFactor, 15 * MACHINES_FACTOR / FP_FACTOR)) && machineCount < 100) {
1730         randomMachineFactor = 15;
1731         machineCount++;
1732     }
1733 
1734     for (failsafe = 50; machineCount && failsafe; failsafe--) {
1735         if (buildAMachine(-1, -1, -1, BP_REWARD, NULL, NULL, NULL)) {
1736             machineCount--;
1737             rogue.rewardRoomsGenerated++;
1738         }
1739     }
1740 }
1741 
1742 // Add terrain, DFs and flavor machines. Includes traps, torches, funguses, flavor machines, etc.
1743 // If buildAreaMachines is true, build ONLY the autogenerators that include machines.
1744 // If false, build all EXCEPT the autogenerators that include machines.
runAutogenerators(boolean buildAreaMachines)1745 void runAutogenerators(boolean buildAreaMachines) {
1746     short AG, count, x, y, i;
1747     const autoGenerator *gen;
1748     char grid[DCOLS][DROWS];
1749 
1750     // Cycle through the autoGenerators.
1751     for (AG=1; AG<NUMBER_AUTOGENERATORS; AG++) {
1752 
1753         // Shortcut:
1754         gen = &(autoGeneratorCatalog[AG]);
1755 
1756         if (gen->machine > 0 == buildAreaMachines) {
1757 
1758             // Enforce depth constraints.
1759             if (rogue.depthLevel < gen->minDepth || rogue.depthLevel > gen->maxDepth) {
1760                 continue;
1761             }
1762 
1763             // Decide how many of this AG to build.
1764             count = min((gen->minNumberIntercept + rogue.depthLevel * gen->minNumberSlope) / 100, gen->maxNumber);
1765             while (rand_percent(gen->frequency) && count < gen->maxNumber) {
1766                 count++;
1767             }
1768 
1769             // Build that many instances.
1770             for (i = 0; i < count; i++) {
1771 
1772                 // Find a location for DFs and terrain generations.
1773                 //if (randomMatchingLocation(&x, &y, gen->requiredDungeonFoundationType, NOTHING, -1)) {
1774                 //if (randomMatchingLocation(&x, &y, -1, -1, gen->requiredDungeonFoundationType)) {
1775                 if (randomMatchingLocation(&x, &y, gen->requiredDungeonFoundationType, gen->requiredLiquidFoundationType, -1)) {
1776 
1777                     // Spawn the DF.
1778                     if (gen->DFType) {
1779                         spawnDungeonFeature(x, y, &(dungeonFeatureCatalog[gen->DFType]), false, true);
1780 
1781                         if (D_INSPECT_LEVELGEN) {
1782                             dumpLevelToScreen();
1783                             hiliteCell(x, y, &yellow, 50, true);
1784                             temporaryMessage("Dungeon feature added.", REQUIRE_ACKNOWLEDGMENT);
1785                         }
1786                     }
1787 
1788                     // Spawn the terrain if it's got the priority to spawn there and won't disrupt connectivity.
1789                     if (gen->terrain
1790                         && tileCatalog[pmap[x][y].layers[gen->layer]].drawPriority >= tileCatalog[gen->terrain].drawPriority) {
1791 
1792                         // Check connectivity.
1793                         zeroOutGrid(grid);
1794                         grid[x][y] = true;
1795                         if (!(tileCatalog[gen->terrain].flags & T_PATHING_BLOCKER)
1796                             || !levelIsDisconnectedWithBlockingMap(grid, false)) {
1797 
1798                             // Build!
1799                             pmap[x][y].layers[gen->layer] = gen->terrain;
1800 
1801                             if (D_INSPECT_LEVELGEN) {
1802                                 dumpLevelToScreen();
1803                                 hiliteCell(x, y, &yellow, 50, true);
1804                                 temporaryMessage("Terrain added.", REQUIRE_ACKNOWLEDGMENT);
1805                             }
1806                         }
1807                     }
1808                 }
1809 
1810                 // Attempt to build the machine if requested.
1811                 // Machines will find their own locations, so it will not be at the same place as terrain and DF.
1812                 if (gen->machine > 0) {
1813                     buildAMachine(gen->machine, -1, -1, 0, NULL, NULL, NULL);
1814                 }
1815             }
1816         }
1817     }
1818 }
1819 
1820 // Knock down the boundaries between similar lakes where possible.
cleanUpLakeBoundaries()1821 void cleanUpLakeBoundaries() {
1822     short i, j, x, y, failsafe, layer;
1823     boolean reverse, madeChange;
1824     unsigned long subjectFlags;
1825 
1826     reverse = true;
1827 
1828     failsafe = 100;
1829     do {
1830         madeChange = false;
1831         reverse = !reverse;
1832         failsafe--;
1833 
1834         for (i = (reverse ? DCOLS - 2 : 1);
1835              (reverse ? i > 0 : i < DCOLS - 1);
1836              (reverse ? i-- : i++)) {
1837 
1838             for (j = (reverse ? DROWS - 2 : 1);
1839                  (reverse ? j > 0 : j < DROWS - 1);
1840                  (reverse ? j-- : j++)) {
1841 
1842                 //assert(i >= 1 && i <= DCOLS - 2 && j >= 1 && j <= DROWS - 2);
1843 
1844                 //if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY)
1845                 if (cellHasTerrainFlag(i, j, T_LAKE_PATHING_BLOCKER | T_OBSTRUCTS_PASSABILITY)
1846                     && !cellHasTMFlag(i, j, TM_IS_SECRET)
1847                     && !(pmap[i][j].flags & IMPREGNABLE)) {
1848 
1849                     subjectFlags = terrainFlags(i, j) & (T_LAKE_PATHING_BLOCKER | T_OBSTRUCTS_PASSABILITY);
1850 
1851                     x = y = 0;
1852                     if ((terrainFlags(i - 1, j) & T_LAKE_PATHING_BLOCKER & ~subjectFlags)
1853                         && !cellHasTMFlag(i - 1, j, TM_IS_SECRET)
1854                         && !cellHasTMFlag(i + 1, j, TM_IS_SECRET)
1855                         && (terrainFlags(i - 1, j) & T_LAKE_PATHING_BLOCKER & ~subjectFlags) == (terrainFlags(i + 1, j) & T_LAKE_PATHING_BLOCKER & ~subjectFlags)) {
1856                         x = i + 1;
1857                         y = j;
1858                     } else if ((terrainFlags(i, j - 1) & T_LAKE_PATHING_BLOCKER & ~subjectFlags)
1859                                && !cellHasTMFlag(i, j - 1, TM_IS_SECRET)
1860                                && !cellHasTMFlag(i, j + 1, TM_IS_SECRET)
1861                                && (terrainFlags(i, j - 1) & T_LAKE_PATHING_BLOCKER & ~subjectFlags) == (terrainFlags(i, j + 1) & T_LAKE_PATHING_BLOCKER & ~subjectFlags)) {
1862                         x = i;
1863                         y = j + 1;
1864                     }
1865                     if (x) {
1866                         madeChange = true;
1867                         for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
1868                             pmap[i][j].layers[layer] = pmap[x][y].layers[layer];
1869                         }
1870                         //pmap[i][j].layers[DUNGEON] = CRYSTAL_WALL;
1871                     }
1872                 }
1873             }
1874         }
1875     } while (madeChange && failsafe > 0);
1876 }
1877 
removeDiagonalOpenings()1878 void removeDiagonalOpenings() {
1879     short i, j, k, x1, y1, x2, layer;
1880     boolean diagonalCornerRemoved;
1881 
1882     do {
1883         diagonalCornerRemoved = false;
1884         for (i=0; i<DCOLS-1; i++) {
1885             for (j=0; j<DROWS-1; j++) {
1886                 for (k=0; k<=1; k++) {
1887                     if (!(tileCatalog[pmap[i + k][j].layers[DUNGEON]].flags & T_OBSTRUCTS_PASSABILITY)
1888                         && (tileCatalog[pmap[i + (1-k)][j].layers[DUNGEON]].flags & T_OBSTRUCTS_PASSABILITY)
1889                         && (tileCatalog[pmap[i + (1-k)][j].layers[DUNGEON]].flags & T_OBSTRUCTS_DIAGONAL_MOVEMENT)
1890                         && (tileCatalog[pmap[i + k][j+1].layers[DUNGEON]].flags & T_OBSTRUCTS_PASSABILITY)
1891                         && (tileCatalog[pmap[i + k][j+1].layers[DUNGEON]].flags & T_OBSTRUCTS_DIAGONAL_MOVEMENT)
1892                         && !(tileCatalog[pmap[i + (1-k)][j+1].layers[DUNGEON]].flags & T_OBSTRUCTS_PASSABILITY)) {
1893 
1894                         if (rand_percent(50)) {
1895                             x1 = i + (1-k);
1896                             x2 = i + k;
1897                             y1 = j;
1898                         } else {
1899                             x1 = i + k;
1900                             x2 = i + (1-k);
1901                             y1 = j + 1;
1902                         }
1903                         if (!(pmap[x1][y1].flags & HAS_MONSTER) && pmap[x1][y1].machineNumber == 0) {
1904                             diagonalCornerRemoved = true;
1905                             for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
1906                                 pmap[x1][y1].layers[layer] = pmap[x2][y1].layers[layer];
1907                             }
1908                         }
1909                     }
1910                 }
1911             }
1912         }
1913     } while (diagonalCornerRemoved == true);
1914 }
1915 
insertRoomAt(short ** dungeonMap,short ** roomMap,const short roomToDungeonX,const short roomToDungeonY,const short xRoom,const short yRoom)1916 void insertRoomAt(short **dungeonMap, short **roomMap, const short roomToDungeonX, const short roomToDungeonY, const short xRoom, const short yRoom) {
1917     short newX, newY;
1918     enum directions dir;
1919 
1920     brogueAssert(coordinatesAreInMap(xRoom + roomToDungeonX, yRoom + roomToDungeonY));
1921 
1922     dungeonMap[xRoom + roomToDungeonX][yRoom + roomToDungeonY] = 1;
1923     for (dir = 0; dir < 4; dir++) {
1924         newX = xRoom + nbDirs[dir][0];
1925         newY = yRoom + nbDirs[dir][1];
1926         if (coordinatesAreInMap(newX, newY)
1927             && roomMap[newX][newY]
1928             && coordinatesAreInMap(newX + roomToDungeonX, newY + roomToDungeonY)
1929             && dungeonMap[newX + roomToDungeonX][newY + roomToDungeonY] == 0) {
1930 
1931             insertRoomAt(dungeonMap, roomMap, roomToDungeonX, roomToDungeonY, newX, newY);
1932         }
1933     }
1934 }
1935 
designCavern(short ** grid,short minWidth,short maxWidth,short minHeight,short maxHeight)1936 void designCavern(short **grid, short minWidth, short maxWidth, short minHeight, short maxHeight) {
1937     short destX, destY;
1938     short caveX, caveY, caveWidth, caveHeight;
1939     short fillX = 0, fillY = 0;
1940     boolean foundFillPoint = false;
1941     short **blobGrid;
1942     blobGrid = allocGrid();
1943 
1944     fillGrid(grid, 0);
1945     createBlobOnGrid(blobGrid,
1946                      &caveX, &caveY, &caveWidth, &caveHeight,
1947                      5, minWidth, minHeight, maxWidth, maxHeight, 55, "ffffffttt", "ffffttttt");
1948 
1949 //    colorOverDungeon(&darkGray);
1950 //    hiliteGrid(blobGrid, &tanColor, 80);
1951 //    temporaryMessage("Here's the cave:", REQUIRE_ACKNOWLEDGMENT);
1952 
1953     // Position the new cave in the middle of the grid...
1954     destX = (DCOLS - caveWidth) / 2;
1955     destY = (DROWS - caveHeight) / 2;
1956     // ...pick a floodfill insertion point...
1957     for (fillX = 0; fillX < DCOLS && !foundFillPoint; fillX++) {
1958         for (fillY = 0; fillY < DROWS && !foundFillPoint; fillY++) {
1959             if (blobGrid[fillX][fillY]) {
1960                 foundFillPoint = true;
1961             }
1962         }
1963     }
1964     // ...and copy it to the master grid.
1965     insertRoomAt(grid, blobGrid, destX - caveX, destY - caveY, fillX, fillY);
1966     freeGrid(blobGrid);
1967 }
1968 
1969 // This is a special room that appears at the entrance to the dungeon on depth 1.
designEntranceRoom(short ** grid)1970 void designEntranceRoom(short **grid) {
1971     short roomWidth, roomHeight, roomWidth2, roomHeight2, roomX, roomY, roomX2, roomY2;
1972 
1973     fillGrid(grid, 0);
1974 
1975     roomWidth = 8;
1976     roomHeight = 10;
1977     roomWidth2 = 20;
1978     roomHeight2 = 5;
1979     roomX = DCOLS/2 - roomWidth/2 - 1;
1980     roomY = DROWS - roomHeight - 2;
1981     roomX2 = DCOLS/2 - roomWidth2/2 - 1;
1982     roomY2 = DROWS - roomHeight2 - 2;
1983 
1984     drawRectangleOnGrid(grid, roomX, roomY, roomWidth, roomHeight, 1);
1985     drawRectangleOnGrid(grid, roomX2, roomY2, roomWidth2, roomHeight2, 1);
1986 }
1987 
designCrossRoom(short ** grid)1988 void designCrossRoom(short **grid) {
1989     short roomWidth, roomHeight, roomWidth2, roomHeight2, roomX, roomY, roomX2, roomY2;
1990 
1991     fillGrid(grid, 0);
1992 
1993     roomWidth = rand_range(3, 12);
1994     roomX = rand_range(max(0, DCOLS/2 - (roomWidth - 1)), min(DCOLS, DCOLS/2));
1995     roomWidth2 = rand_range(4, 20);
1996     roomX2 = (roomX + (roomWidth / 2) + rand_range(0, 2) + rand_range(0, 2) - 3) - (roomWidth2 / 2);
1997 
1998     roomHeight = rand_range(3, 7);
1999     roomY = (DROWS/2 - roomHeight);
2000 
2001     roomHeight2 = rand_range(2, 5);
2002     roomY2 = (DROWS/2 - roomHeight2 - (rand_range(0, 2) + rand_range(0, 1)));
2003 
2004     drawRectangleOnGrid(grid, roomX - 5, roomY + 5, roomWidth, roomHeight, 1);
2005     drawRectangleOnGrid(grid, roomX2 - 5, roomY2 + 5, roomWidth2, roomHeight2, 1);
2006 }
2007 
designSymmetricalCrossRoom(short ** grid)2008 void designSymmetricalCrossRoom(short **grid) {
2009     short majorWidth, majorHeight, minorWidth, minorHeight;
2010 
2011     fillGrid(grid, 0);
2012 
2013     majorWidth = rand_range(4, 8);
2014     majorHeight = rand_range(4, 5);
2015 
2016     minorWidth = rand_range(3, 4);
2017     if (majorHeight % 2 == 0) {
2018         minorWidth -= 1;
2019     }
2020     minorHeight = 3;//rand_range(2, 3);
2021     if (majorWidth % 2 == 0) {
2022         minorHeight -= 1;
2023     }
2024 
2025     drawRectangleOnGrid(grid, (DCOLS - majorWidth)/2, (DROWS - minorHeight)/2, majorWidth, minorHeight, 1);
2026     drawRectangleOnGrid(grid, (DCOLS - minorWidth)/2, (DROWS - majorHeight)/2, minorWidth, majorHeight, 1);
2027 }
2028 
designSmallRoom(short ** grid)2029 void designSmallRoom(short **grid) {
2030     short width, height;
2031 
2032     fillGrid(grid, 0);
2033     width = rand_range(3, 6);
2034     height = rand_range(2, 4);
2035     drawRectangleOnGrid(grid, (DCOLS - width) / 2, (DROWS - height) / 2, width, height, 1);
2036 }
2037 
designCircularRoom(short ** grid)2038 void designCircularRoom(short **grid) {
2039     short radius;
2040 
2041     if (rand_percent(5)) {
2042         radius = rand_range(4, 10);
2043     } else {
2044         radius = rand_range(2, 4);
2045     }
2046 
2047     fillGrid(grid, 0);
2048     drawCircleOnGrid(grid, DCOLS/2, DROWS/2, radius, 1);
2049 
2050     if (radius > 6
2051         && rand_percent(50)) {
2052         drawCircleOnGrid(grid, DCOLS/2, DROWS/2, rand_range(3, radius - 3), 0);
2053     }
2054 }
2055 
designChunkyRoom(short ** grid)2056 void designChunkyRoom(short **grid) {
2057     short i, x, y;
2058     short minX, maxX, minY, maxY;
2059     short chunkCount = rand_range(2, 8);
2060 
2061     fillGrid(grid, 0);
2062     drawCircleOnGrid(grid, DCOLS/2, DROWS/2, 2, 1);
2063     minX = DCOLS/2 - 3;
2064     maxX = DCOLS/2 + 3;
2065     minY = DROWS/2 - 3;
2066     maxY = DROWS/2 + 3;
2067 
2068     for (i=0; i<chunkCount;) {
2069         x = rand_range(minX, maxX);
2070         y = rand_range(minY, maxY);
2071         if (grid[x][y]) {
2072 //            colorOverDungeon(&darkGray);
2073 //            hiliteGrid(grid, &white, 100);
2074 
2075             drawCircleOnGrid(grid, x, y, 2, 1);
2076             i++;
2077             minX = max(1, min(x - 3, minX));
2078             maxX = min(DCOLS - 2, max(x + 3, maxX));
2079             minY = max(1, min(y - 3, minY));
2080             maxY = min(DROWS - 2, max(y + 3, maxY));
2081 
2082 //            hiliteGrid(grid, &green, 50);
2083 //            temporaryMessage("Added a chunk:", REQUIRE_ACKNOWLEDGMENT);
2084         }
2085     }
2086 }
2087 
2088 // If the indicated tile is a wall on the room stored in grid, and it could be the site of
2089 // a door out of that room, then return the outbound direction that the door faces.
2090 // Otherwise, return NO_DIRECTION.
directionOfDoorSite(short ** grid,short x,short y)2091 enum directions directionOfDoorSite(short **grid, short x, short y) {
2092     enum directions dir, solutionDir;
2093     short newX, newY, oppX, oppY;
2094 
2095     if (grid[x][y]) { // Already occupied
2096         return NO_DIRECTION;
2097     }
2098 
2099     solutionDir = NO_DIRECTION;
2100     for (dir=0; dir<4; dir++) {
2101         newX = x + nbDirs[dir][0];
2102         newY = y + nbDirs[dir][1];
2103         oppX = x - nbDirs[dir][0];
2104         oppY = y - nbDirs[dir][1];
2105         if (coordinatesAreInMap(oppX, oppY)
2106             && coordinatesAreInMap(newX, newY)
2107             && grid[oppX][oppY] == 1) {
2108 
2109             // This grid cell would be a valid tile on which to place a door that, facing outward, points dir.
2110             if (solutionDir != NO_DIRECTION) {
2111                 // Already claimed by another direction; no doors here!
2112                 return NO_DIRECTION;
2113             }
2114             solutionDir = dir;
2115         }
2116     }
2117     return solutionDir;
2118 }
2119 
chooseRandomDoorSites(short ** roomMap,short doorSites[4][2])2120 void chooseRandomDoorSites(short **roomMap, short doorSites[4][2]) {
2121     short i, j, k, newX, newY;
2122     enum directions dir;
2123     short **grid;
2124     boolean doorSiteFailed;
2125 
2126     grid = allocGrid();
2127     copyGrid(grid, roomMap);
2128 
2129 //    colorOverDungeon(&darkGray);
2130 //    hiliteGrid(grid, &blue, 100);
2131 //    temporaryMessage("Generating this room:", REQUIRE_ACKNOWLEDGMENT);
2132 //    const char dirChars[] = "^v<>";
2133 
2134     for (i=0; i<DCOLS; i++) {
2135         for (j=0; j<DROWS; j++) {
2136             if (!grid[i][j]) {
2137                 dir = directionOfDoorSite(roomMap, i, j);
2138                 if (dir != NO_DIRECTION) {
2139                     // Trace a ray 10 spaces outward from the door site to make sure it doesn't intersect the room.
2140                     // If it does, it's not a valid door site.
2141                     newX = i + nbDirs[dir][0];
2142                     newY = j + nbDirs[dir][1];
2143                     doorSiteFailed = false;
2144                     for (k=0; k<10 && coordinatesAreInMap(newX, newY) && !doorSiteFailed; k++) {
2145                         if (grid[newX][newY]) {
2146                             doorSiteFailed = true;
2147                         }
2148                         newX += nbDirs[dir][0];
2149                         newY += nbDirs[dir][1];
2150                     }
2151                     if (!doorSiteFailed) {
2152 //                        plotCharWithColor(dirChars[dir], mapToWindowX(i), mapToWindowY(j), &black, &green);
2153                         grid[i][j] = dir + 2; // So as not to conflict with 0 or 1, which are used to indicate exterior/interior.
2154                     }
2155                 }
2156             }
2157         }
2158     }
2159 
2160 //    temporaryMessage("Door candidates:", REQUIRE_ACKNOWLEDGMENT);
2161 
2162     // Pick four doors, one in each direction, and store them in doorSites[dir].
2163     for (dir=0; dir<4; dir++) {
2164         randomLocationInGrid(grid, &(doorSites[dir][0]), &(doorSites[dir][1]), dir + 2);
2165     }
2166 
2167     freeGrid(grid);
2168 }
2169 
attachHallwayTo(short ** grid,short doorSites[4][2])2170 void attachHallwayTo(short **grid, short doorSites[4][2]) {
2171     short i, x, y, newX, newY, dirs[4];
2172     short length;
2173     enum directions dir, dir2;
2174     boolean allowObliqueHallwayExit;
2175 
2176     // Pick a direction.
2177     fillSequentialList(dirs, 4);
2178     shuffleList(dirs, 4);
2179     for (i=0; i<4; i++) {
2180         dir = dirs[i];
2181         if (doorSites[dir][0] != -1
2182             && doorSites[dir][1] != -1
2183             && coordinatesAreInMap(doorSites[dir][0] + nbDirs[dir][0] * HORIZONTAL_CORRIDOR_MAX_LENGTH,
2184                                    doorSites[dir][1] + nbDirs[dir][1] * VERTICAL_CORRIDOR_MAX_LENGTH)) {
2185                 break; // That's our direction!
2186         }
2187     }
2188     if (i==4) {
2189         return; // No valid direction for hallways.
2190     }
2191 
2192     if (dir == UP || dir == DOWN) {
2193         length = rand_range(VERTICAL_CORRIDOR_MIN_LENGTH, VERTICAL_CORRIDOR_MAX_LENGTH);
2194     } else {
2195         length = rand_range(HORIZONTAL_CORRIDOR_MIN_LENGTH, HORIZONTAL_CORRIDOR_MAX_LENGTH);
2196     }
2197 
2198     x = doorSites[dir][0];
2199     y = doorSites[dir][1];
2200     for (i = 0; i < length; i++) {
2201         if (coordinatesAreInMap(x, y)) {
2202             grid[x][y] = true;
2203         }
2204         x += nbDirs[dir][0];
2205         y += nbDirs[dir][1];
2206     }
2207     x = clamp(x - nbDirs[dir][0], 0, DCOLS - 1);
2208     y = clamp(y - nbDirs[dir][1], 0, DROWS - 1); // Now (x, y) points at the last interior cell of the hallway.
2209     allowObliqueHallwayExit = rand_percent(15);
2210     for (dir2 = 0; dir2 < 4; dir2++) {
2211         newX = x + nbDirs[dir2][0];
2212         newY = y + nbDirs[dir2][1];
2213 
2214         if ((dir2 != dir && !allowObliqueHallwayExit)
2215             || !coordinatesAreInMap(newX, newY)
2216             || grid[newX][newY]) {
2217 
2218             doorSites[dir2][0] = -1;
2219             doorSites[dir2][1] = -1;
2220         } else {
2221             doorSites[dir2][0] = newX;
2222             doorSites[dir2][1] = newY;
2223         }
2224     }
2225 }
2226 
2227 // Put a random room shape somewhere on the binary grid,
2228 // and optionally record the coordinates of up to four door sites in doorSites.
2229 // If attachHallway is true, then it will bolt a perpendicular hallway onto the room at one of the four standard door sites,
2230 // and then relocate three of the door sites to radiate from the end of the hallway. (The fourth is defunct.)
2231 // RoomTypeFrequencies specifies the probability of each room type, in the following order:
2232 //      0. Cross room
2233 //      1. Small symmetrical cross room
2234 //      2. Small room
2235 //      3. Circular room
2236 //      4. Chunky room
2237 //      5. Cave
2238 //      6. Cavern (the kind that fills a level)
2239 //      7. Entrance room (the big upside-down T room at the start of depth 1)
2240 
designRandomRoom(short ** grid,boolean attachHallway,short doorSites[4][2],const short roomTypeFrequencies[ROOM_TYPE_COUNT])2241 void designRandomRoom(short **grid, boolean attachHallway, short doorSites[4][2],
2242                       const short roomTypeFrequencies[ROOM_TYPE_COUNT]) {
2243     short randIndex, i, sum;
2244     enum directions dir;
2245 
2246     sum = 0;
2247     for (i=0; i<ROOM_TYPE_COUNT; i++) {
2248         sum += roomTypeFrequencies[i];
2249     }
2250     randIndex = rand_range(0, sum - 1);
2251     for (i=0; i<ROOM_TYPE_COUNT; i++) {
2252         if (randIndex < roomTypeFrequencies[i]) {
2253             break; // "i" is our room type.
2254         } else {
2255             randIndex -= roomTypeFrequencies[i];
2256         }
2257     }
2258     switch (i) {
2259         case 0:
2260             designCrossRoom(grid);
2261             break;
2262         case 1:
2263             designSymmetricalCrossRoom(grid);
2264             break;
2265         case 2:
2266             designSmallRoom(grid);
2267             break;
2268         case 3:
2269             designCircularRoom(grid);
2270             break;
2271         case 4:
2272             designChunkyRoom(grid);
2273             break;
2274         case 5:
2275             switch (rand_range(0, 2)) {
2276                 case 0:
2277                     designCavern(grid, 3, 12, 4, 8); // Compact cave room.
2278                     break;
2279                 case 1:
2280                     designCavern(grid, 3, 12, 15, DROWS-2); // Large north-south cave room.
2281                     break;
2282                 case 2:
2283                     designCavern(grid, 20, DROWS-2, 4, 8); // Large east-west cave room.
2284                     break;
2285                 default:
2286                     break;
2287             }
2288             break;
2289         case 6:
2290             designCavern(grid, CAVE_MIN_WIDTH, DCOLS - 2, CAVE_MIN_HEIGHT, DROWS - 2);
2291             break;
2292         case 7:
2293             designEntranceRoom(grid);
2294             break;
2295         default:
2296             break;
2297     }
2298 
2299     if (doorSites) {
2300         chooseRandomDoorSites(grid, doorSites);
2301         if (attachHallway) {
2302             dir = rand_range(0, 3);
2303             for (i=0; doorSites[dir][0] == -1 && i < 3; i++) {
2304                 dir = (dir + 1) % 4; // Each room will have at least 2 valid directions for doors.
2305             }
2306             attachHallwayTo(grid, doorSites);
2307         }
2308     }
2309 }
2310 
roomFitsAt(short ** dungeonMap,short ** roomMap,short roomToDungeonX,short roomToDungeonY)2311 boolean roomFitsAt(short **dungeonMap, short **roomMap, short roomToDungeonX, short roomToDungeonY) {
2312     short xRoom, yRoom, xDungeon, yDungeon, i, j;
2313 
2314     for (xRoom = 0; xRoom < DCOLS; xRoom++) {
2315         for (yRoom = 0; yRoom < DROWS; yRoom++) {
2316             if (roomMap[xRoom][yRoom]) {
2317                 xDungeon = xRoom + roomToDungeonX;
2318                 yDungeon = yRoom + roomToDungeonY;
2319 
2320                 for (i = xDungeon - 1; i <= xDungeon + 1; i++) {
2321                     for (j = yDungeon - 1; j <= yDungeon + 1; j++) {
2322                         if (!coordinatesAreInMap(i, j)
2323                             || dungeonMap[i][j] > 0) {
2324                             return false;
2325                         }
2326                     }
2327                 }
2328             }
2329         }
2330     }
2331     return true;
2332 }
2333 
attachRooms(short ** grid,const dungeonProfile * theDP,short attempts,short maxRoomCount)2334 void attachRooms(short **grid, const dungeonProfile *theDP, short attempts, short maxRoomCount) {
2335     short roomsBuilt, roomsAttempted;
2336     short **roomMap;
2337     short doorSites[4][2];
2338     short i, x, y, sCoord[DCOLS*DROWS];
2339     enum directions dir, oppDir;
2340 
2341     fillSequentialList(sCoord, DCOLS*DROWS);
2342     shuffleList(sCoord, DCOLS*DROWS);
2343 
2344     roomMap = allocGrid();
2345     for (roomsBuilt = roomsAttempted = 0; roomsBuilt < maxRoomCount && roomsAttempted < attempts; roomsAttempted++) {
2346         // Build a room in hyperspace.
2347         fillGrid(roomMap, 0);
2348         designRandomRoom(roomMap, roomsAttempted <= attempts - 5 && rand_percent(theDP->corridorChance),
2349                          doorSites, theDP->roomFrequencies);
2350 
2351         if (D_INSPECT_LEVELGEN) {
2352             colorOverDungeon(&darkGray);
2353             hiliteGrid(roomMap, &blue, 100);
2354             if (doorSites[0][0] != -1) plotCharWithColor('^', mapToWindowX(doorSites[0][0]), mapToWindowY(doorSites[0][1]), &black, &green);
2355             if (doorSites[1][0] != -1) plotCharWithColor('v', mapToWindowX(doorSites[1][0]), mapToWindowY(doorSites[1][1]), &black, &green);
2356             if (doorSites[2][0] != -1) plotCharWithColor('<', mapToWindowX(doorSites[2][0]), mapToWindowY(doorSites[2][1]), &black, &green);
2357             if (doorSites[3][0] != -1) plotCharWithColor('>', mapToWindowX(doorSites[3][0]), mapToWindowY(doorSites[3][1]), &black, &green);
2358             temporaryMessage("Generating this room:", REQUIRE_ACKNOWLEDGMENT);
2359         }
2360 
2361         // Slide hyperspace across real space, in a random but predetermined order, until the room matches up with a wall.
2362         for (i = 0; i < DCOLS*DROWS; i++) {
2363             x = sCoord[i] / DROWS;
2364             y = sCoord[i] % DROWS;
2365 
2366             dir = directionOfDoorSite(grid, x, y);
2367             oppDir = oppositeDirection(dir);
2368             if (dir != NO_DIRECTION
2369                 && doorSites[oppDir][0] != -1
2370                 && roomFitsAt(grid, roomMap, x - doorSites[oppDir][0], y - doorSites[oppDir][1])) {
2371 
2372                 // Room fits here.
2373                 if (D_INSPECT_LEVELGEN) {
2374                     colorOverDungeon(&darkGray);
2375                     hiliteGrid(grid, &white, 100);
2376                 }
2377                 insertRoomAt(grid, roomMap, x - doorSites[oppDir][0], y - doorSites[oppDir][1], doorSites[oppDir][0], doorSites[oppDir][1]);
2378                 grid[x][y] = 2; // Door site.
2379                 if (D_INSPECT_LEVELGEN) {
2380                     hiliteGrid(grid, &green, 50);
2381                     temporaryMessage("Added room.", REQUIRE_ACKNOWLEDGMENT);
2382                 }
2383                 roomsBuilt++;
2384                 break;
2385             }
2386         }
2387     }
2388 
2389     freeGrid(roomMap);
2390 }
2391 
adjustDungeonProfileForDepth(dungeonProfile * theProfile)2392 void adjustDungeonProfileForDepth(dungeonProfile *theProfile) {
2393     const short descentPercent = clamp(100 * (rogue.depthLevel - 1) / (AMULET_LEVEL - 1), 0, 100);
2394 
2395     theProfile->roomFrequencies[0] += 20 * (100 - descentPercent) / 100;
2396     theProfile->roomFrequencies[1] += 10 * (100 - descentPercent) / 100;
2397     theProfile->roomFrequencies[3] +=  7 * (100 - descentPercent) / 100;
2398     theProfile->roomFrequencies[5] += 10 * descentPercent / 100;
2399 
2400     theProfile->corridorChance += 80 * (100 - descentPercent) / 100;
2401 }
2402 
adjustDungeonFirstRoomProfileForDepth(dungeonProfile * theProfile)2403 void adjustDungeonFirstRoomProfileForDepth(dungeonProfile *theProfile) {
2404     short i;
2405     const short descentPercent = clamp(100 * (rogue.depthLevel - 1) / (AMULET_LEVEL - 1), 0, 100);
2406 
2407     if (rogue.depthLevel == 1) {
2408         // All dungeons start with the entrance room on depth 1.
2409         for (i = 0; i < ROOM_TYPE_COUNT; i++) {
2410             theProfile->roomFrequencies[i] = 0;
2411         }
2412         theProfile->roomFrequencies[7] = 1;
2413     } else {
2414         theProfile->roomFrequencies[6] += 50 * descentPercent / 100;
2415     }
2416 }
2417 
2418 // Called by digDungeon().
2419 // Slaps a bunch of rooms and hallways into the grid.
2420 // On the grid, a 0 denotes granite, a 1 denotes floor, and a 2 denotes a possible door site.
2421 // -1 denotes off-limits areas -- rooms can't be placed there and also can't sprout off of there.
2422 // Parent function will translate this grid into pmap[][] to make floors, walls, doors, etc.
carveDungeon(short ** grid)2423 void carveDungeon(short **grid) {
2424     dungeonProfile theDP, theFirstRoomDP;
2425 
2426     theDP = dungeonProfileCatalog[DP_BASIC];
2427     adjustDungeonProfileForDepth(&theDP);
2428 
2429     theFirstRoomDP = dungeonProfileCatalog[DP_BASIC_FIRST_ROOM];
2430     adjustDungeonFirstRoomProfileForDepth(&theFirstRoomDP);
2431 
2432     designRandomRoom(grid, false, NULL, theFirstRoomDP.roomFrequencies);
2433 
2434     if (D_INSPECT_LEVELGEN) {
2435         colorOverDungeon(&darkGray);
2436         hiliteGrid(grid, &white, 100);
2437         temporaryMessage("First room placed:", REQUIRE_ACKNOWLEDGMENT);
2438     }
2439 
2440     attachRooms(grid, &theDP, 35, 35);
2441 
2442 //    colorOverDungeon(&darkGray);
2443 //    hiliteGrid(grid, &white, 100);
2444 //    temporaryMessage("How does this finished level look?", REQUIRE_ACKNOWLEDGMENT);
2445 }
2446 
finishWalls(boolean includingDiagonals)2447 void finishWalls(boolean includingDiagonals) {
2448     short i, j, x1, y1;
2449     boolean foundExposure;
2450     enum directions dir;
2451 
2452     for (i=0; i<DCOLS; i++) {
2453         for (j=0; j<DROWS; j++) {
2454             if (pmap[i][j].layers[DUNGEON] == GRANITE) {
2455                 foundExposure = false;
2456                 for (dir = 0; dir < (includingDiagonals ? 8 : 4) && !foundExposure; dir++) {
2457                     x1 = i + nbDirs[dir][0];
2458                     y1 = j + nbDirs[dir][1];
2459                     if (coordinatesAreInMap(x1, y1)
2460                         && (!cellHasTerrainFlag(x1, y1, T_OBSTRUCTS_VISION) || !cellHasTerrainFlag(x1, y1, T_OBSTRUCTS_PASSABILITY))) {
2461 
2462                         pmap[i][j].layers[DUNGEON] = WALL;
2463                         foundExposure = true;
2464                     }
2465                 }
2466             } else if (pmap[i][j].layers[DUNGEON] == WALL) {
2467                 foundExposure = false;
2468                 for (dir = 0; dir < (includingDiagonals ? 8 : 4) && !foundExposure; dir++) {
2469                     x1 = i + nbDirs[dir][0];
2470                     y1 = j + nbDirs[dir][1];
2471                     if (coordinatesAreInMap(x1, y1)
2472                         && (!cellHasTerrainFlag(x1, y1, T_OBSTRUCTS_VISION) || !cellHasTerrainFlag(x1, y1, T_OBSTRUCTS_PASSABILITY))) {
2473 
2474                         foundExposure = true;
2475                     }
2476                 }
2477                 if (foundExposure == false) {
2478                     pmap[i][j].layers[DUNGEON] = GRANITE;
2479                 }
2480             }
2481         }
2482     }
2483 }
2484 
liquidType(short * deep,short * shallow,short * shallowWidth)2485 void liquidType(short *deep, short *shallow, short *shallowWidth) {
2486     short randMin, randMax, rand;
2487 
2488     randMin = (rogue.depthLevel < 4 ? 1 : 0); // no lava before level 4
2489     randMax = (rogue.depthLevel < 17 ? 2 : 3); // no brimstone before level 18
2490     rand = rand_range(randMin, randMax);
2491     if (rogue.depthLevel == DEEPEST_LEVEL) {
2492         rand = 1;
2493     }
2494 
2495     switch(rand) {
2496         case 0:
2497             *deep = LAVA;
2498             *shallow = NOTHING;
2499             *shallowWidth = 0;
2500             break;
2501         case 1:
2502             *deep = DEEP_WATER;
2503             *shallow = SHALLOW_WATER;
2504             *shallowWidth = 2;
2505             break;
2506         case 2:
2507             *deep = CHASM;
2508             *shallow = CHASM_EDGE;
2509             *shallowWidth = 1;
2510             break;
2511         case 3:
2512             *deep = INERT_BRIMSTONE;
2513             *shallow = OBSIDIAN;
2514             *shallowWidth = 2;
2515             break;
2516     }
2517 }
2518 
2519 // Fills a lake marked in unfilledLakeMap with the specified liquid type, scanning outward to reach other lakes within scanWidth.
2520 // Any wreath of shallow liquid must be done elsewhere.
fillLake(short x,short y,short liquid,short scanWidth,char wreathMap[DCOLS][DROWS],short ** unfilledLakeMap)2521 void fillLake(short x, short y, short liquid, short scanWidth, char wreathMap[DCOLS][DROWS], short **unfilledLakeMap) {
2522     short i, j;
2523 
2524     for (i = x - scanWidth; i <= x + scanWidth; i++) {
2525         for (j = y - scanWidth; j <= y + scanWidth; j++) {
2526             if (coordinatesAreInMap(i, j) && unfilledLakeMap[i][j]) {
2527                 unfilledLakeMap[i][j] = false;
2528                 pmap[i][j].layers[LIQUID] = liquid;
2529                 wreathMap[i][j] = 1;
2530                 fillLake(i, j, liquid, scanWidth, wreathMap, unfilledLakeMap);  // recursive
2531             }
2532         }
2533     }
2534 }
2535 
lakeFloodFill(short x,short y,short ** floodMap,short ** grid,short ** lakeMap,short dungeonToGridX,short dungeonToGridY)2536 void lakeFloodFill(short x, short y, short **floodMap, short **grid, short **lakeMap, short dungeonToGridX, short dungeonToGridY) {
2537     short newX, newY;
2538     enum directions dir;
2539 
2540     floodMap[x][y] = true;
2541     for (dir=0; dir<4; dir++) {
2542         newX = x + nbDirs[dir][0];
2543         newY = y + nbDirs[dir][1];
2544         if (coordinatesAreInMap(newX, newY)
2545             && !floodMap[newX][newY]
2546             && (!cellHasTerrainFlag(newX, newY, T_PATHING_BLOCKER) || cellHasTMFlag(newX, newY, TM_CONNECTS_LEVEL))
2547             && !lakeMap[newX][newY]
2548             && (!coordinatesAreInMap(newX+dungeonToGridX, newY+dungeonToGridY) || !grid[newX+dungeonToGridX][newY+dungeonToGridY])) {
2549 
2550             lakeFloodFill(newX, newY, floodMap, grid, lakeMap, dungeonToGridX, dungeonToGridY);
2551         }
2552     }
2553 }
2554 
lakeDisruptsPassability(short ** grid,short ** lakeMap,short dungeonToGridX,short dungeonToGridY)2555 boolean lakeDisruptsPassability(short **grid, short **lakeMap, short dungeonToGridX, short dungeonToGridY) {
2556     boolean result;
2557     short i, j, x, y;
2558     short **floodMap;
2559 
2560     floodMap = allocGrid();
2561     fillGrid(floodMap, 0);
2562     x = y = -1;
2563     // Get starting location for the fill.
2564     for (i=0; i<DCOLS && x == -1; i++) {
2565         for (j=0; j<DROWS && x == -1; j++) {
2566             if (!cellHasTerrainFlag(i, j, T_PATHING_BLOCKER)
2567                 && !lakeMap[i][j]
2568                 && (!coordinatesAreInMap(i+dungeonToGridX, j+dungeonToGridY) || !grid[i+dungeonToGridX][j+dungeonToGridY])) {
2569 
2570                 x = i;
2571                 y = j;
2572             }
2573         }
2574     }
2575     brogueAssert(x != -1);
2576     // Do the flood fill.
2577     lakeFloodFill(x, y, floodMap, grid, lakeMap, dungeonToGridX, dungeonToGridY);
2578 
2579     // See if any dry tiles weren't reached by the flood fill.
2580     result = false;
2581     for (i=0; i<DCOLS && result == false; i++) {
2582         for (j=0; j<DROWS && result == false; j++) {
2583             if (!cellHasTerrainFlag(i, j, T_PATHING_BLOCKER)
2584                 && !lakeMap[i][j]
2585                 && !floodMap[i][j]
2586                 && (!coordinatesAreInMap(i+dungeonToGridX, j+dungeonToGridY) || !grid[i+dungeonToGridX][j+dungeonToGridY])) {
2587 
2588 //                if (D_INSPECT_LEVELGEN) {
2589 //                    dumpLevelToScreen();
2590 //                    hiliteGrid(lakeMap, &darkBlue, 75);
2591 //                    hiliteGrid(floodMap, &white, 20);
2592 //                    plotCharWithColor('X', mapToWindowX(i), mapToWindowY(j), &black, &red);
2593 //                    temporaryMessage("Failed here.", REQUIRE_ACKNOWLEDGMENT);
2594 //                }
2595 
2596                 result = true;
2597             }
2598         }
2599     }
2600 
2601     freeGrid(floodMap);
2602     return result;
2603 }
2604 
designLakes(short ** lakeMap)2605 void designLakes(short **lakeMap) {
2606     short i, j, k;
2607     short x, y;
2608     short lakeMaxHeight, lakeMaxWidth;
2609     short lakeX, lakeY, lakeWidth, lakeHeight;
2610 
2611     short **grid; // Holds the current lake.
2612 
2613     grid = allocGrid();
2614     fillGrid(lakeMap, 0);
2615     for (lakeMaxHeight = 15, lakeMaxWidth = 30; lakeMaxHeight >=10; lakeMaxHeight--, lakeMaxWidth -= 2) { // lake generations
2616 
2617         fillGrid(grid, 0);
2618         createBlobOnGrid(grid, &lakeX, &lakeY, &lakeWidth, &lakeHeight, 5, 4, 4, lakeMaxWidth, lakeMaxHeight, 55, "ffffftttt", "ffffttttt");
2619 
2620 //        if (D_INSPECT_LEVELGEN) {
2621 //            colorOverDungeon(&darkGray);
2622 //            hiliteGrid(grid, &white, 100);
2623 //            temporaryMessage("Generated a lake.", REQUIRE_ACKNOWLEDGMENT);
2624 //        }
2625 
2626         for (k=0; k<20; k++) { // placement attempts
2627             // propose a position for the top-left of the grid in the dungeon
2628             x = rand_range(1 - lakeX, DCOLS - lakeWidth - lakeX - 2);
2629             y = rand_range(1 - lakeY, DROWS - lakeHeight - lakeY - 2);
2630 
2631             if (!lakeDisruptsPassability(grid, lakeMap, -x, -y)) { // level with lake is completely connected
2632                 //printf("Placed a lake!");
2633 
2634                 // copy in lake
2635                 for (i = 0; i < lakeWidth; i++) {
2636                     for (j = 0; j < lakeHeight; j++) {
2637                         if (grid[i + lakeX][j + lakeY]) {
2638                             lakeMap[i + lakeX + x][j + lakeY + y] = true;
2639                             pmap[i + lakeX + x][j + lakeY + y].layers[DUNGEON] = FLOOR;
2640                         }
2641                     }
2642                 }
2643 
2644                 if (D_INSPECT_LEVELGEN) {
2645                     dumpLevelToScreen();
2646                     hiliteGrid(lakeMap, &white, 50);
2647                     temporaryMessage("Added a lake location.", REQUIRE_ACKNOWLEDGMENT);
2648                 }
2649                 break;
2650             }
2651         }
2652     }
2653     freeGrid(grid);
2654 }
2655 
createWreath(short shallowLiquid,short wreathWidth,char wreathMap[DCOLS][DROWS])2656 void createWreath(short shallowLiquid, short wreathWidth, char wreathMap[DCOLS][DROWS]) {
2657     short i, j, k, l;
2658     for (i=0; i<DCOLS; i++) {
2659         for (j=0; j<DROWS; j++) {
2660             if (wreathMap[i][j]) {
2661                 for (k = i-wreathWidth; k<= i+wreathWidth; k++) {
2662                     for (l = j-wreathWidth; l <= j+wreathWidth; l++) {
2663                         if (coordinatesAreInMap(k, l) && pmap[k][l].layers[LIQUID] == NOTHING
2664                             && (i-k)*(i-k) + (j-l)*(j-l) <= wreathWidth*wreathWidth) {
2665                             pmap[k][l].layers[LIQUID] = shallowLiquid;
2666                             if (pmap[k][l].layers[DUNGEON] == DOOR) {
2667                                 pmap[k][l].layers[DUNGEON] = FLOOR;
2668                             }
2669                         }
2670                     }
2671                 }
2672             }
2673         }
2674     }
2675 }
2676 
fillLakes(short ** lakeMap)2677 void fillLakes(short **lakeMap) {
2678     short deepLiquid = CRYSTAL_WALL, shallowLiquid = CRYSTAL_WALL, shallowLiquidWidth = 0;
2679     char wreathMap[DCOLS][DROWS];
2680     short i, j;
2681 
2682     for (i=0; i<DCOLS; i++) {
2683         for (j=0; j<DROWS; j++) {
2684             if (lakeMap[i][j]) {
2685                 liquidType(&deepLiquid, &shallowLiquid, &shallowLiquidWidth);
2686                 zeroOutGrid(wreathMap);
2687                 fillLake(i, j, deepLiquid, 4, wreathMap, lakeMap);
2688                 createWreath(shallowLiquid, shallowLiquidWidth, wreathMap);
2689 
2690                 if (D_INSPECT_LEVELGEN) {
2691                     dumpLevelToScreen();
2692                     hiliteGrid(lakeMap, &white, 75);
2693                     temporaryMessage("Lake filled.", REQUIRE_ACKNOWLEDGMENT);
2694                 }
2695             }
2696         }
2697     }
2698 }
2699 
finishDoors()2700 void finishDoors() {
2701     short i, j;
2702     const short secretDoorChance = clamp((rogue.depthLevel - 1) * 67 / 25, 0, 67);
2703     for (i=1; i<DCOLS-1; i++) {
2704         for (j=1; j<DROWS-1; j++) {
2705             if (pmap[i][j].layers[DUNGEON] == DOOR
2706                 && pmap[i][j].machineNumber == 0) {
2707                 if ((!cellHasTerrainFlag(i+1, j, T_OBSTRUCTS_PASSABILITY) || !cellHasTerrainFlag(i-1, j, T_OBSTRUCTS_PASSABILITY))
2708                     && (!cellHasTerrainFlag(i, j+1, T_OBSTRUCTS_PASSABILITY) || !cellHasTerrainFlag(i, j-1, T_OBSTRUCTS_PASSABILITY))) {
2709                     // If there's passable terrain to the left or right, and there's passable terrain
2710                     // above or below, then the door is orphaned and must be removed.
2711                     pmap[i][j].layers[DUNGEON] = FLOOR;
2712                 } else if ((cellHasTerrainFlag(i+1, j, T_PATHING_BLOCKER) ? 1 : 0)
2713                            + (cellHasTerrainFlag(i-1, j, T_PATHING_BLOCKER) ? 1 : 0)
2714                            + (cellHasTerrainFlag(i, j+1, T_PATHING_BLOCKER) ? 1 : 0)
2715                            + (cellHasTerrainFlag(i, j-1, T_PATHING_BLOCKER) ? 1 : 0) >= 3) {
2716                     // If the door has three or more pathing blocker neighbors in the four cardinal directions,
2717                     // then the door is orphaned and must be removed.
2718                     pmap[i][j].layers[DUNGEON] = FLOOR;
2719                 } else if (rand_percent(secretDoorChance)) {
2720                     pmap[i][j].layers[DUNGEON] = SECRET_DOOR;
2721                 }
2722             }
2723         }
2724     }
2725 }
2726 
clearLevel()2727 void clearLevel() {
2728     short i, j;
2729 
2730     for( i=0; i<DCOLS; i++ ) {
2731         for( j=0; j<DROWS; j++ ) {
2732             pmap[i][j].layers[DUNGEON] = GRANITE;
2733             pmap[i][j].layers[LIQUID] = NOTHING;
2734             pmap[i][j].layers[GAS] = NOTHING;
2735             pmap[i][j].layers[SURFACE] = NOTHING;
2736             pmap[i][j].machineNumber = 0;
2737             pmap[i][j].rememberedTerrain = NOTHING;
2738             pmap[i][j].rememberedTerrainFlags = (T_OBSTRUCTS_EVERYTHING);
2739             pmap[i][j].rememberedTMFlags = 0;
2740             pmap[i][j].rememberedCellFlags = 0;
2741             pmap[i][j].rememberedItemCategory = 0;
2742             pmap[i][j].rememberedItemKind = 0;
2743             pmap[i][j].rememberedItemQuantity = 0;
2744             pmap[i][j].rememberedItemOriginDepth = 0;
2745             pmap[i][j].flags = 0;
2746             pmap[i][j].volume = 0;
2747         }
2748     }
2749 }
2750 
2751 // Scans the map in random order looking for a good place to build a bridge.
2752 // If it finds one, it builds a bridge there, halts and returns true.
buildABridge()2753 boolean buildABridge() {
2754     short i, j, k, l, i2, j2, nCols[DCOLS], nRows[DROWS];
2755     short bridgeRatioX, bridgeRatioY;
2756     boolean foundExposure;
2757 
2758     bridgeRatioX = (short) (100 + (100 + 100 * rogue.depthLevel / 9) * rand_range(10, 20) / 10);
2759     bridgeRatioY = (short) (100 + (400 + 100 * rogue.depthLevel / 18) * rand_range(10, 20) / 10);
2760 
2761     fillSequentialList(nCols, DCOLS);
2762     shuffleList(nCols, DCOLS);
2763     fillSequentialList(nRows, DROWS);
2764     shuffleList(nRows, DROWS);
2765 
2766     for (i2=1; i2<DCOLS-1; i2++) {
2767         i = nCols[i2];
2768         for (j2=1; j2<DROWS-1; j2++) {
2769             j = nRows[j2];
2770             if (!cellHasTerrainFlag(i, j, (T_CAN_BE_BRIDGED | T_PATHING_BLOCKER))
2771                 && !pmap[i][j].machineNumber) {
2772 
2773                 // try a horizontal bridge
2774                 foundExposure = false;
2775                 for (k = i + 1;
2776                      k < DCOLS // Iterate across the prospective length of the bridge.
2777                      && !pmap[k][j].machineNumber // No bridges in machines.
2778                      && cellHasTerrainFlag(k, j, T_CAN_BE_BRIDGED)  // Candidate tile must be chasm.
2779                      && !cellHasTMFlag(k, j, TM_IS_SECRET) // Can't bridge over secret trapdoors.
2780                      && !cellHasTerrainFlag(k, j, T_OBSTRUCTS_PASSABILITY)  // Candidate tile cannot be a wall.
2781                      && cellHasTerrainFlag(k, j-1, (T_CAN_BE_BRIDGED | T_OBSTRUCTS_PASSABILITY))    // Only chasms or walls are permitted next to the length of the bridge.
2782                      && cellHasTerrainFlag(k, j+1, (T_CAN_BE_BRIDGED | T_OBSTRUCTS_PASSABILITY));
2783                      k++) {
2784 
2785                     if (!cellHasTerrainFlag(k, j-1, T_OBSTRUCTS_PASSABILITY) // Can't run against a wall the whole way.
2786                         && !cellHasTerrainFlag(k, j+1, T_OBSTRUCTS_PASSABILITY)) {
2787                         foundExposure = true;
2788                     }
2789                 }
2790                 if (k < DCOLS
2791                     && (k - i > 3) // Can't have bridges shorter than 3 spaces.
2792                     && foundExposure
2793                     && !cellHasTerrainFlag(k, j, T_PATHING_BLOCKER | T_CAN_BE_BRIDGED) // Must end on an unobstructed land tile.
2794                     && !pmap[k][j].machineNumber // Cannot end in a machine.
2795                     && 100 * pathingDistance(i, j, k, j, T_PATHING_BLOCKER) / (k - i) > bridgeRatioX) { // Must shorten the pathing distance enough.
2796 
2797                     for (l=i+1; l < k; l++) {
2798                         pmap[l][j].layers[LIQUID] = BRIDGE;
2799                     }
2800                     pmap[i][j].layers[SURFACE] = BRIDGE_EDGE;
2801                     pmap[k][j].layers[SURFACE] = BRIDGE_EDGE;
2802                     return true;
2803                 }
2804 
2805                 // try a vertical bridge
2806                 foundExposure = false;
2807                 for (k = j + 1;
2808                      k < DROWS
2809                      && !pmap[i][k].machineNumber
2810                      && cellHasTerrainFlag(i, k, T_CAN_BE_BRIDGED)
2811                      && !cellHasTMFlag(i, k, TM_IS_SECRET)
2812                      && !cellHasTerrainFlag(i, k, T_OBSTRUCTS_PASSABILITY)
2813                      && cellHasTerrainFlag(i-1, k, (T_CAN_BE_BRIDGED | T_OBSTRUCTS_PASSABILITY))
2814                      && cellHasTerrainFlag(i+1, k, (T_CAN_BE_BRIDGED | T_OBSTRUCTS_PASSABILITY));
2815                      k++) {
2816 
2817                     if (!cellHasTerrainFlag(i-1, k, T_OBSTRUCTS_PASSABILITY)
2818                         && !cellHasTerrainFlag(i+1, k, T_OBSTRUCTS_PASSABILITY)) {
2819                         foundExposure = true;
2820                     }
2821                 }
2822                 if (k < DROWS
2823                     && (k - j > 3)
2824                     && foundExposure
2825                     && !cellHasTerrainFlag(i, k, T_PATHING_BLOCKER | T_CAN_BE_BRIDGED)
2826                     && !pmap[i][k].machineNumber // Cannot end in a machine.
2827                     && 100 * pathingDistance(i, j, i, k, T_PATHING_BLOCKER) / (k - j) > bridgeRatioY) {
2828 
2829                     for (l=j+1; l < k; l++) {
2830                         pmap[i][l].layers[LIQUID] = BRIDGE;
2831                     }
2832                     pmap[i][j].layers[SURFACE] = BRIDGE_EDGE;
2833                     pmap[i][k].layers[SURFACE] = BRIDGE_EDGE;
2834                     return true;
2835                 }
2836             }
2837         }
2838     }
2839     return false;
2840 }
2841 
2842 // This is the master function for digging out a dungeon level.
2843 // Finishing touches -- items, monsters, staircases, etc. -- are handled elsewhere.
digDungeon()2844 void digDungeon() {
2845     short i, j;
2846 
2847     short **grid;
2848 
2849     rogue.machineNumber = 0;
2850 
2851     topBlobMinX = topBlobMinY = blobWidth = blobHeight = 0;
2852 
2853 #ifdef AUDIT_RNG
2854     char RNGMessage[100];
2855     sprintf(RNGMessage, "\n\n\nDigging dungeon level %i:\n", rogue.depthLevel);
2856     RNGLog(RNGMessage);
2857 #endif
2858 
2859     // Clear level and fill with granite
2860     clearLevel();
2861 
2862     grid = allocGrid();
2863     carveDungeon(grid);
2864     addLoops(grid, 20);
2865     for (i=0; i<DCOLS; i++) {
2866         for (j=0; j<DROWS; j++) {
2867             if (grid[i][j] == 1) {
2868                 pmap[i][j].layers[DUNGEON] = FLOOR;
2869             } else if (grid[i][j] == 2) {
2870                 pmap[i][j].layers[DUNGEON] = (rand_percent(60) && rogue.depthLevel < DEEPEST_LEVEL ? DOOR : FLOOR);
2871             }
2872         }
2873     }
2874     freeGrid(grid);
2875 
2876     finishWalls(false);
2877 
2878     if (D_INSPECT_LEVELGEN) {
2879         dumpLevelToScreen();
2880         temporaryMessage("Carved into the granite:", REQUIRE_ACKNOWLEDGMENT);
2881     }
2882     //DEBUG printf("\n%i loops created.", numLoops);
2883     //DEBUG logLevel();
2884 
2885     // Time to add lakes and chasms. Strategy is to generate a series of blob lakes of decreasing size. For each lake,
2886     // propose a position, and then check via a flood fill that the level would remain connected with that placement (i.e. that
2887     // each passable tile can still be reached). If not, make 9 more placement attempts before abandoning that lake
2888     // and proceeding to generate the next smaller one.
2889     // Canvas sizes start at 30x15 and decrease by 2x1 at a time down to a minimum of 20x10. Min generated size is always 4x4.
2890 
2891     // DEBUG logLevel();
2892 
2893     // Now design the lakes and then fill them with various liquids (lava, water, chasm, brimstone).
2894     short **lakeMap = allocGrid();
2895     designLakes(lakeMap);
2896     fillLakes(lakeMap);
2897     freeGrid(lakeMap);
2898 
2899     // Run the non-machine autoGenerators.
2900     runAutogenerators(false);
2901 
2902     // Remove diagonal openings.
2903     removeDiagonalOpenings();
2904 
2905     if (D_INSPECT_LEVELGEN) {
2906         dumpLevelToScreen();
2907         temporaryMessage("Diagonal openings removed.", REQUIRE_ACKNOWLEDGMENT);
2908     }
2909 
2910     // Now add some treasure machines.
2911     addMachines();
2912 
2913     if (D_INSPECT_LEVELGEN) {
2914         dumpLevelToScreen();
2915         temporaryMessage("Machines added.", REQUIRE_ACKNOWLEDGMENT);
2916     }
2917 
2918     // Run the machine autoGenerators.
2919     runAutogenerators(true);
2920 
2921     // Now knock down the boundaries between similar lakes where possible.
2922     cleanUpLakeBoundaries();
2923 
2924     if (D_INSPECT_LEVELGEN) {
2925         dumpLevelToScreen();
2926         temporaryMessage("Lake boundaries cleaned up.", REQUIRE_ACKNOWLEDGMENT);
2927     }
2928 
2929     // Now add some bridges.
2930     while (buildABridge());
2931 
2932     if (D_INSPECT_LEVELGEN) {
2933         dumpLevelToScreen();
2934         temporaryMessage("Bridges added.", REQUIRE_ACKNOWLEDGMENT);
2935     }
2936 
2937     // Now remove orphaned doors and upgrade some doors to secret doors
2938     finishDoors();
2939 
2940     // Now finish any exposed granite with walls and revert any unexposed walls to granite
2941     finishWalls(true);
2942 
2943     if (D_INSPECT_LEVELGEN) {
2944         dumpLevelToScreen();
2945         temporaryMessage("Finishing touches added. Level has been generated.", REQUIRE_ACKNOWLEDGMENT);
2946     }
2947 }
2948 
updateMapToShore()2949 void updateMapToShore() {
2950     short i, j;
2951     short **costMap;
2952 
2953     rogue.updatedMapToShoreThisTurn = true;
2954 
2955     costMap = allocGrid();
2956 
2957     // Calculate the map to shore for this level
2958     if (!rogue.mapToShore) {
2959         rogue.mapToShore = allocGrid();
2960         fillGrid(rogue.mapToShore, 0);
2961     }
2962     for (i=0; i<DCOLS; i++) {
2963         for (j=0; j<DROWS; j++) {
2964             if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY)) {
2965                 costMap[i][j] = cellHasTerrainFlag(i, j, T_OBSTRUCTS_DIAGONAL_MOVEMENT) ? PDS_OBSTRUCTION : PDS_FORBIDDEN;
2966                 rogue.mapToShore[i][j] = 30000;
2967             } else {
2968                 costMap[i][j] = 1;
2969                 rogue.mapToShore[i][j] = (cellHasTerrainFlag(i, j, T_LAVA_INSTA_DEATH | T_IS_DEEP_WATER | T_AUTO_DESCENT)
2970                                           && !cellHasTMFlag(i, j, TM_IS_SECRET)) ? 30000 : 0;
2971             }
2972         }
2973     }
2974     dijkstraScan(rogue.mapToShore, costMap, true);
2975     freeGrid(costMap);
2976 }
2977 
2978 // Calculates the distance map for the given waypoint.
2979 // This is called on all waypoints during setUpWaypoints(),
2980 // and then one waypoint is recalculated per turn thereafter.
refreshWaypoint(short wpIndex)2981 void refreshWaypoint(short wpIndex) {
2982     short **costMap;
2983 
2984     costMap = allocGrid();
2985     populateGenericCostMap(costMap);
2986     for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) {
2987         creature* monst = nextCreature(&it);
2988         if ((monst->creatureState == MONSTER_SLEEPING || (monst->info.flags & MONST_IMMOBILE) || (monst->bookkeepingFlags & MB_CAPTIVE))
2989             && costMap[monst->xLoc][monst->yLoc] >= 0) {
2990 
2991             costMap[monst->xLoc][monst->yLoc] = PDS_FORBIDDEN;
2992         }
2993     }
2994     fillGrid(rogue.wpDistance[wpIndex], 30000);
2995     rogue.wpDistance[wpIndex][rogue.wpCoordinates[wpIndex][0]][rogue.wpCoordinates[wpIndex][1]] = 0;
2996     dijkstraScan(rogue.wpDistance[wpIndex], costMap, true);
2997     freeGrid(costMap);
2998 }
2999 
setUpWaypoints()3000 void setUpWaypoints() {
3001     short i, j, sCoord[DCOLS * DROWS], x, y;
3002     char grid[DCOLS][DROWS];
3003 
3004     zeroOutGrid(grid);
3005     for (i=0; i<DCOLS; i++) {
3006         for (j=0; j<DROWS; j++) {
3007             if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_SCENT)) {
3008                 grid[i][j] = 1;
3009             }
3010         }
3011     }
3012     rogue.wpCount = 0;
3013     rogue.wpRefreshTicker = 0;
3014     fillSequentialList(sCoord, DCOLS*DROWS);
3015     shuffleList(sCoord, DCOLS*DROWS);
3016     for (i = 0; i < DCOLS*DROWS && rogue.wpCount < MAX_WAYPOINT_COUNT; i++) {
3017         x = sCoord[i]/DROWS;
3018         y = sCoord[i] % DROWS;
3019         if (!grid[x][y]) {
3020             getFOVMask(grid, x, y, WAYPOINT_SIGHT_RADIUS * FP_FACTOR, T_OBSTRUCTS_SCENT, 0, false);
3021             grid[x][y] = true;
3022             rogue.wpCoordinates[rogue.wpCount][0] = x;
3023             rogue.wpCoordinates[rogue.wpCount][1] = y;
3024             rogue.wpCount++;
3025 //            blackOutScreen();
3026 //            dumpLevelToScreen();
3027 //            hiliteCharGrid(grid, &yellow, 50);
3028 //            temporaryMessage("Waypoint coverage so far:", REQUIRE_ACKNOWLEDGMENT);
3029         }
3030     }
3031 
3032     for (i=0; i<rogue.wpCount; i++) {
3033         refreshWaypoint(i);
3034 //        blackOutScreen();
3035 //        dumpLevelToScreen();
3036 //        displayGrid(rogue.wpDistance[i]);
3037 //        temporaryMessage("Waypoint distance map:", REQUIRE_ACKNOWLEDGMENT);
3038     }
3039 }
3040 
zeroOutGrid(char grid[DCOLS][DROWS])3041 void zeroOutGrid(char grid[DCOLS][DROWS]) {
3042     short i, j;
3043     for (i=0; i<DCOLS; i++) {
3044         for (j=0; j<DROWS; j++) {
3045             grid[i][j] = 0;
3046         }
3047     }
3048 }
3049 
oppositeDirection(short theDir)3050 short oppositeDirection(short theDir) {
3051     switch (theDir) {
3052         case UP:
3053             return DOWN;
3054         case DOWN:
3055             return UP;
3056         case LEFT:
3057             return RIGHT;
3058         case RIGHT:
3059             return LEFT;
3060         case UPRIGHT:
3061             return DOWNLEFT;
3062         case DOWNLEFT:
3063             return UPRIGHT;
3064         case UPLEFT:
3065             return DOWNRIGHT;
3066         case DOWNRIGHT:
3067             return UPLEFT;
3068         case NO_DIRECTION:
3069             return NO_DIRECTION;
3070         default:
3071             return -1;
3072     }
3073 }
3074 
3075 // blockingMap is optional.
3076 // Returns the size of the connected zone, and marks visited[][] with the zoneLabel.
connectCell(short x,short y,short zoneLabel,char blockingMap[DCOLS][DROWS],char zoneMap[DCOLS][DROWS])3077 short connectCell(short x, short y, short zoneLabel, char blockingMap[DCOLS][DROWS], char zoneMap[DCOLS][DROWS]) {
3078     enum directions dir;
3079     short newX, newY, size;
3080 
3081     zoneMap[x][y] = zoneLabel;
3082     size = 1;
3083 
3084     for (dir = 0; dir < 4; dir++) {
3085         newX = x + nbDirs[dir][0];
3086         newY = y + nbDirs[dir][1];
3087 
3088         if (coordinatesAreInMap(newX, newY)
3089             && zoneMap[newX][newY] == 0
3090             && (!blockingMap || !blockingMap[newX][newY])
3091             && cellIsPassableOrDoor(newX, newY)) {
3092 
3093             size += connectCell(newX, newY, zoneLabel, blockingMap, zoneMap);
3094         }
3095     }
3096     return size;
3097 }
3098 
3099 // Make a zone map of connected passable regions that include at least one passable
3100 // cell that borders the blockingMap if blockingMap blocks. Keep track of the size of each zone.
3101 // Then pretend that the blockingMap no longer blocks, and grow these zones into the resulting area
3102 // (without changing the stored zone sizes). If two or more zones now touch, then we block.
3103 // At that point, return the size in cells of the smallest of all of the touching regions
3104 // (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.
levelIsDisconnectedWithBlockingMap(char blockingMap[DCOLS][DROWS],boolean countRegionSize)3105 short levelIsDisconnectedWithBlockingMap(char blockingMap[DCOLS][DROWS], boolean countRegionSize) {
3106     char zoneMap[DCOLS][DROWS];
3107     short i, j, dir, zoneSizes[200], zoneCount, smallestQualifyingZoneSize, borderingZone;
3108 
3109     zoneCount = 0;
3110     smallestQualifyingZoneSize = 10000;
3111     zeroOutGrid(zoneMap);
3112 
3113 //  dumpLevelToScreen();
3114 //  hiliteCharGrid(blockingMap, &omniscienceColor, 100);
3115 //  temporaryMessage("Blocking map:", REQUIRE_ACKNOWLEDGMENT);
3116 
3117     // Map out the zones with the blocking area blocked.
3118     for (i=1; i<DCOLS-1; i++) {
3119         for (j=1; j<DROWS-1; j++) {
3120             if (cellIsPassableOrDoor(i, j) && zoneMap[i][j] == 0 && !blockingMap[i][j]) {
3121                 for (dir=0; dir<4; dir++) {
3122                     if (blockingMap[i + nbDirs[dir][0]][j + nbDirs[dir][1]]) {
3123                         zoneCount++;
3124                         zoneSizes[zoneCount - 1] = connectCell(i, j, zoneCount, blockingMap, zoneMap);
3125                         break;
3126                     }
3127                 }
3128             }
3129         }
3130     }
3131 
3132     // Expand the zones into the blocking area.
3133     for (i=1; i<DCOLS-1; i++) {
3134         for (j=1; j<DROWS-1; j++) {
3135             if (blockingMap[i][j] && zoneMap[i][j] == 0 && cellIsPassableOrDoor(i, j)) {
3136                 for (dir=0; dir<4; dir++) {
3137                     borderingZone = zoneMap[i + nbDirs[dir][0]][j + nbDirs[dir][1]];
3138                     if (borderingZone != 0) {
3139                         connectCell(i, j, borderingZone, NULL, zoneMap);
3140                         break;
3141                     }
3142                 }
3143             }
3144         }
3145     }
3146 
3147     // Figure out which zones touch.
3148     for (i=1; i<DCOLS-1; i++) {
3149         for (j=1; j<DROWS-1; j++) {
3150             if (zoneMap[i][j] != 0) {
3151                 for (dir=0; dir<4; dir++) {
3152                     borderingZone = zoneMap[i + nbDirs[dir][0]][j + nbDirs[dir][1]];
3153                     if (zoneMap[i][j] != borderingZone && borderingZone != 0) {
3154                         if (!countRegionSize) {
3155                             return true;
3156                         }
3157                         smallestQualifyingZoneSize = min(smallestQualifyingZoneSize, zoneSizes[zoneMap[i][j] - 1]);
3158                         smallestQualifyingZoneSize = min(smallestQualifyingZoneSize, zoneSizes[borderingZone - 1]);
3159                         break;
3160                     }
3161                 }
3162             }
3163         }
3164     }
3165     return (smallestQualifyingZoneSize < 10000 ? smallestQualifyingZoneSize : 0);
3166 }
3167 
resetDFMessageEligibility()3168 void resetDFMessageEligibility() {
3169     short i;
3170 
3171     for (i=0; i<NUMBER_DUNGEON_FEATURES; i++) {
3172         dungeonFeatureCatalog[i].messageDisplayed = false;
3173     }
3174 }
3175 
fillSpawnMap(enum dungeonLayers layer,enum tileType surfaceTileType,char spawnMap[DCOLS][DROWS],boolean blockedByOtherLayers,boolean refresh,boolean superpriority)3176 boolean fillSpawnMap(enum dungeonLayers layer,
3177                      enum tileType surfaceTileType,
3178                      char spawnMap[DCOLS][DROWS],
3179                      boolean blockedByOtherLayers,
3180                      boolean refresh,
3181                      boolean superpriority) {
3182     short i, j;
3183     creature *monst;
3184     item *theItem;
3185     boolean accomplishedSomething;
3186 
3187     accomplishedSomething = false;
3188 
3189     for (i=0; i<DCOLS; i++) {
3190         for (j=0; j<DROWS; j++) {
3191             if (    // If it's flagged for building in the spawn map,
3192                 spawnMap[i][j]
3193                     // and the new cell doesn't already contain the fill terrain,
3194                 && pmap[i][j].layers[layer] != surfaceTileType
3195                     // and the terrain in the layer to be overwritten has a higher priority number (unless superpriority),
3196                 && (superpriority || tileCatalog[pmap[i][j].layers[layer]].drawPriority >= tileCatalog[surfaceTileType].drawPriority)
3197                     // and we won't be painting into the surface layer when that cell forbids it,
3198                 && !(layer == SURFACE && cellHasTerrainFlag(i, j, T_OBSTRUCTS_SURFACE_EFFECTS))
3199                     // and, if requested, the fill won't violate the priority of the most important terrain in this cell:
3200                 && (!blockedByOtherLayers || tileCatalog[pmap[i][j].layers[highestPriorityLayer(i, j, true)]].drawPriority >= tileCatalog[surfaceTileType].drawPriority)
3201                 ) {
3202 
3203                 if ((tileCatalog[surfaceTileType].flags & T_IS_FIRE)
3204                     && !(tileCatalog[pmap[i][j].layers[layer]].flags & T_IS_FIRE)) {
3205                     pmap[i][j].flags |= CAUGHT_FIRE_THIS_TURN;
3206                 }
3207 
3208                 if ((tileCatalog[pmap[i][j].layers[layer]].flags & T_PATHING_BLOCKER)
3209                     != (tileCatalog[surfaceTileType].flags & T_PATHING_BLOCKER)) {
3210 
3211                     rogue.staleLoopMap = true;
3212                 }
3213 
3214                 pmap[i][j].layers[layer] = surfaceTileType; // Place the terrain!
3215                 accomplishedSomething = true;
3216 
3217                 if (refresh) {
3218                     refreshDungeonCell(i, j);
3219                     if (player.xLoc == i && player.yLoc == j && !player.status[STATUS_LEVITATING] && refresh) {
3220                         flavorMessage(tileFlavor(player.xLoc, player.yLoc));
3221                     }
3222                     if (pmap[i][j].flags & (HAS_MONSTER)) {
3223                         monst = monsterAtLoc(i, j);
3224                         applyInstantTileEffectsToCreature(monst);
3225                         if (rogue.gameHasEnded) {
3226                             return true;
3227                         }
3228                     }
3229                     if (tileCatalog[surfaceTileType].flags & T_IS_FIRE) {
3230                         if (pmap[i][j].flags & HAS_ITEM) {
3231                             theItem = itemAtLoc(i, j);
3232                             if (theItem->flags & ITEM_FLAMMABLE) {
3233                                 burnItem(theItem);
3234                             }
3235                         }
3236                     }
3237                 }
3238             } else {
3239                 spawnMap[i][j] = false; // so that the spawnmap reflects what actually got built
3240             }
3241         }
3242     }
3243     return accomplishedSomething;
3244 }
3245 
spawnMapDF(short x,short y,enum tileType propagationTerrain,boolean requirePropTerrain,short startProb,short probDec,char spawnMap[DCOLS][DROWS])3246 void spawnMapDF(short x, short y,
3247                 enum tileType propagationTerrain,
3248                 boolean requirePropTerrain,
3249                 short startProb,
3250                 short probDec,
3251                 char spawnMap[DCOLS][DROWS]) {
3252 
3253     short i, j, dir, t, x2, y2;
3254     boolean madeChange;
3255 
3256     spawnMap[x][y] = t = 1; // incremented before anything else happens
3257 
3258     madeChange = true;
3259 
3260     while (madeChange && startProb > 0) {
3261         madeChange = false;
3262         t++;
3263         for (i = 0; i < DCOLS; i++) {
3264             for (j=0; j < DROWS; j++) {
3265                 if (spawnMap[i][j] == t - 1) {
3266                     for (dir = 0; dir < 4; dir++) {
3267                         x2 = i + nbDirs[dir][0];
3268                         y2 = j + nbDirs[dir][1];
3269                         if (coordinatesAreInMap(x2, y2)
3270                             && (!requirePropTerrain || (propagationTerrain > 0 && cellHasTerrainType(x2, y2, propagationTerrain)))
3271                             && (!cellHasTerrainFlag(x2, y2, T_OBSTRUCTS_SURFACE_EFFECTS) || (propagationTerrain > 0 && cellHasTerrainType(x2, y2, propagationTerrain)))
3272                             && rand_percent(startProb)) {
3273 
3274                             spawnMap[x2][y2] = t;
3275                             madeChange = true;
3276                         }
3277                     }
3278                 }
3279             }
3280         }
3281         startProb -= probDec;
3282         if (t > 100) {
3283             for (i = 0; i < DCOLS; i++) {
3284                 for (j=0; j < DROWS; j++) {
3285                     if (spawnMap[i][j] == t) {
3286                         spawnMap[i][j] = 2;
3287                     } else if (spawnMap[i][j] > 0) {
3288                         spawnMap[i][j] = 1;
3289                     }
3290                 }
3291             }
3292             t = 2;
3293         }
3294     }
3295     if (requirePropTerrain && !cellHasTerrainType(x, y, propagationTerrain)) {
3296         spawnMap[x][y] = 0;
3297     }
3298 }
3299 
evacuateCreatures(char blockingMap[DCOLS][DROWS])3300 void evacuateCreatures(char blockingMap[DCOLS][DROWS]) {
3301     short i, j, newLoc[2];
3302     creature *monst;
3303 
3304     for (i=0; i<DCOLS; i++) {
3305         for (j=0; j<DROWS; j++) {
3306             if (blockingMap[i][j]
3307                 && (pmap[i][j].flags & (HAS_MONSTER | HAS_PLAYER))) {
3308 
3309                 monst = monsterAtLoc(i, j);
3310                 getQualifyingLocNear(newLoc,
3311                                      i, j,
3312                                      true,
3313                                      blockingMap,
3314                                      forbiddenFlagsForMonster(&(monst->info)),
3315                                      (HAS_MONSTER | HAS_PLAYER),
3316                                      false,
3317                                      false);
3318                 monst->xLoc = newLoc[0];
3319                 monst->yLoc = newLoc[1];
3320                 pmap[i][j].flags &= ~(HAS_MONSTER | HAS_PLAYER);
3321                 pmap[newLoc[0]][newLoc[1]].flags |= (monst == &player ? HAS_PLAYER : HAS_MONSTER);
3322             }
3323         }
3324     }
3325 }
3326 
3327 // returns whether the feature was successfully generated (false if we aborted because of blocking)
spawnDungeonFeature(short x,short y,dungeonFeature * feat,boolean refreshCell,boolean abortIfBlocking)3328 boolean spawnDungeonFeature(short x, short y, dungeonFeature *feat, boolean refreshCell, boolean abortIfBlocking) {
3329     short i, j, layer;
3330     char blockingMap[DCOLS][DROWS];
3331     boolean blocking;
3332     boolean succeeded;
3333 
3334     if ((feat->flags & DFF_RESURRECT_ALLY)
3335         && !resurrectAlly(x, y)) {
3336         return false;
3337     }
3338 
3339     if (feat->description[0] && !feat->messageDisplayed && playerCanSee(x, y)) {
3340         feat->messageDisplayed = true;
3341         message(feat->description, 0);
3342     }
3343 
3344     zeroOutGrid(blockingMap);
3345 
3346     // Blocking keeps track of whether to abort if it turns out that the DF would obstruct the level.
3347     blocking = ((abortIfBlocking
3348                  && !(feat->flags & DFF_PERMIT_BLOCKING)
3349                  && ((tileCatalog[feat->tile].flags & (T_PATHING_BLOCKER))
3350                      || (feat->flags & DFF_TREAT_AS_BLOCKING))) ? true : false);
3351 
3352     if (feat->tile) {
3353         if (feat->layer == GAS) {
3354             pmap[x][y].volume += feat->startProbability;
3355             pmap[x][y].layers[GAS] = feat->tile;
3356             if (refreshCell) {
3357                 refreshDungeonCell(x, y);
3358             }
3359             succeeded = true;
3360         } else {
3361             spawnMapDF(x, y,
3362                        feat->propagationTerrain,
3363                        (feat->propagationTerrain ? true : false),
3364                        feat->startProbability,
3365                        feat->probabilityDecrement,
3366                        blockingMap);
3367             if (!blocking || !levelIsDisconnectedWithBlockingMap(blockingMap, false)) {
3368                 if (feat->flags & DFF_EVACUATE_CREATURES_FIRST) { // first, evacuate creatures if necessary, so that they do not re-trigger the tile.
3369                     evacuateCreatures(blockingMap);
3370                 }
3371 
3372                 //succeeded = fillSpawnMap(feat->layer, feat->tile, blockingMap, (feat->flags & DFF_BLOCKED_BY_OTHER_LAYERS), refreshCell, (feat->flags & DFF_SUPERPRIORITY));
3373                 fillSpawnMap(feat->layer,
3374                              feat->tile,
3375                              blockingMap,
3376                              (feat->flags & DFF_BLOCKED_BY_OTHER_LAYERS),
3377                              refreshCell,
3378                              (feat->flags & DFF_SUPERPRIORITY)); // this can tweak the spawn map too
3379                 succeeded = true; // fail ONLY if we blocked the level. We succeed even if, thanks to priority, nothing gets built.
3380             } else {
3381                 succeeded = false;
3382             }
3383         }
3384     } else {
3385         blockingMap[x][y] = true;
3386         succeeded = true; // Automatically succeed if there is no terrain to place.
3387         if (feat->flags & DFF_EVACUATE_CREATURES_FIRST) { // first, evacuate creatures if necessary, so that they do not re-trigger the tile.
3388             evacuateCreatures(blockingMap);
3389         }
3390     }
3391 
3392     if (succeeded && (feat->flags & DFF_CLEAR_OTHER_TERRAIN)) {
3393         for (i=0; i<DCOLS; i++) {
3394             for (j=0; j<DROWS; j++) {
3395                 if (blockingMap[i][j]) {
3396                     for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
3397                         if (layer != feat->layer && layer != GAS) {
3398                             pmap[i][j].layers[layer] = (layer == DUNGEON ? FLOOR : NOTHING);
3399                         }
3400                     }
3401                 }
3402             }
3403         }
3404     }
3405 
3406     if (succeeded) {
3407         if ((feat->flags & DFF_AGGRAVATES_MONSTERS) && feat->effectRadius) {
3408             aggravateMonsters(feat->effectRadius, x, y, &gray);
3409         }
3410         if (refreshCell && feat->flashColor && feat->effectRadius) {
3411             colorFlash(feat->flashColor, 0, (IN_FIELD_OF_VIEW | CLAIRVOYANT_VISIBLE), 4, feat->effectRadius, x, y);
3412         }
3413         if (refreshCell && feat->lightFlare) {
3414             createFlare(x, y, feat->lightFlare);
3415         }
3416     }
3417 
3418     if (refreshCell
3419         && (tileCatalog[feat->tile].flags & (T_IS_FIRE | T_AUTO_DESCENT))
3420         && cellHasTerrainFlag(player.xLoc, player.yLoc, (T_IS_FIRE | T_AUTO_DESCENT))) {
3421 
3422         applyInstantTileEffectsToCreature(&player);
3423     }
3424     if (rogue.gameHasEnded) {
3425         return succeeded;
3426     }
3427     //  if (succeeded && feat->description[0] && !feat->messageDisplayed && playerCanSee(x, y)) {
3428     //      feat->messageDisplayed = true;
3429     //      message(feat->description, 0);
3430     //  }
3431     if (succeeded) {
3432         if (feat->subsequentDF) {
3433             if (feat->flags & DFF_SUBSEQ_EVERYWHERE) {
3434                 for (i=0; i<DCOLS; i++) {
3435                     for (j=0; j<DROWS; j++) {
3436                         if (blockingMap[i][j]) {
3437                             spawnDungeonFeature(i, j, &dungeonFeatureCatalog[feat->subsequentDF], refreshCell, abortIfBlocking);
3438                         }
3439                     }
3440                 }
3441             } else {
3442                 spawnDungeonFeature(x, y, &dungeonFeatureCatalog[feat->subsequentDF], refreshCell, abortIfBlocking);
3443             }
3444         }
3445         if (feat->tile
3446             && (tileCatalog[feat->tile].flags & (T_IS_DEEP_WATER | T_LAVA_INSTA_DEATH | T_AUTO_DESCENT))) {
3447 
3448             rogue.updatedMapToShoreThisTurn = false;
3449         }
3450 
3451         // awaken dormant creatures?
3452         if (feat->flags & DFF_ACTIVATE_DORMANT_MONSTER) {
3453             for (creatureIterator it = iterateCreatures(dormantMonsters); hasNextCreature(it);) {
3454                 creature *monst = nextCreature(&it);
3455                 if (monst->xLoc == x && monst->yLoc == y || blockingMap[monst->xLoc][monst->yLoc]) {
3456                     // found it!
3457                     toggleMonsterDormancy(monst);
3458                     restartIterator(&it);
3459                 }
3460             }
3461         }
3462     }
3463     return succeeded;
3464 }
3465 
restoreMonster(creature * monst,short ** mapToStairs,short ** mapToPit)3466 void restoreMonster(creature *monst, short **mapToStairs, short **mapToPit) {
3467     short i, *x, *y, turnCount;//, loc[2];
3468     boolean foundLeader = false;
3469     short **theMap;
3470     enum directions dir;
3471 
3472     x = &(monst->xLoc);
3473     y = &(monst->yLoc);
3474 
3475     if (monst->status[STATUS_ENTERS_LEVEL_IN] > 0) {
3476         if (monst->bookkeepingFlags & (MB_APPROACHING_PIT)) {
3477             theMap = mapToPit;
3478         } else {
3479             theMap = mapToStairs;
3480         }
3481 
3482         pmap[*x][*y].flags &= ~HAS_MONSTER;
3483         if (theMap) {
3484             // STATUS_ENTERS_LEVEL_IN accounts for monster speed; convert back to map distance and subtract from distance to stairs
3485             turnCount = (theMap[monst->xLoc][monst->yLoc] - (monst->status[STATUS_ENTERS_LEVEL_IN] * 100 / monst->movementSpeed));
3486             for (i=0; i < turnCount; i++) {
3487                 if ((dir = nextStep(theMap, monst->xLoc, monst->yLoc, NULL, true)) != NO_DIRECTION) {
3488                     monst->xLoc += nbDirs[dir][0];
3489                     monst->yLoc += nbDirs[dir][1];
3490                 } else {
3491                     break;
3492                 }
3493             }
3494         }
3495         monst->bookkeepingFlags |= MB_PREPLACED;
3496     }
3497 
3498     if ((pmap[*x][*y].flags & (HAS_PLAYER | HAS_STAIRS))
3499         || (monst->bookkeepingFlags & MB_PREPLACED)) {
3500 
3501         if (!(monst->bookkeepingFlags & MB_PREPLACED)) {
3502             // (If if it's preplaced, it won't have set the HAS_MONSTER flag in the first place,
3503             // so clearing it might screw up an existing monster.)
3504             pmap[*x][*y].flags &= ~HAS_MONSTER;
3505         }
3506         getQualifyingPathLocNear(x, y, *x, *y, true, T_DIVIDES_LEVEL & avoidedFlagsForMonster(&(monst->info)), 0,
3507                                  avoidedFlagsForMonster(&(monst->info)), (HAS_MONSTER | HAS_PLAYER | HAS_STAIRS), true);
3508     }
3509     pmap[*x][*y].flags |= HAS_MONSTER;
3510     monst->bookkeepingFlags &= ~(MB_PREPLACED | MB_APPROACHING_DOWNSTAIRS | MB_APPROACHING_UPSTAIRS | MB_APPROACHING_PIT | MB_ABSORBING);
3511     monst->status[STATUS_ENTERS_LEVEL_IN] = 0;
3512     monst->corpseAbsorptionCounter = 0;
3513 
3514     if ((monst->bookkeepingFlags & MB_SUBMERGED) && !cellHasTMFlag(*x, *y, TM_ALLOWS_SUBMERGING)) {
3515         monst->bookkeepingFlags &= ~MB_SUBMERGED;
3516     }
3517 
3518     if (monst->bookkeepingFlags & MB_FOLLOWER) {
3519         // is the leader on the same level?
3520         for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) {
3521             creature *leader = nextCreature(&it);
3522             if (leader == monst->leader) {
3523                 foundLeader = true;
3524                 break;
3525             }
3526         }
3527         // if not, it is time to spread your wings and fly solo
3528         if (!foundLeader) {
3529             monst->bookkeepingFlags &= ~MB_FOLLOWER;
3530             monst->leader = NULL;
3531         }
3532     }
3533 }
3534 
restoreItem(item * theItem)3535 void restoreItem(item *theItem) {
3536     short *x, *y, loc[2];
3537     x = &(theItem->xLoc);
3538     y = &(theItem->yLoc);
3539 
3540     if (theItem->flags & ITEM_PREPLACED) {
3541         theItem->flags &= ~ITEM_PREPLACED;
3542 
3543         // Items can fall into deep water, enclaved lakes, another chasm, even lava!
3544         getQualifyingLocNear(loc, *x, *y, true, 0,
3545                             (T_OBSTRUCTS_ITEMS),
3546                             (HAS_MONSTER | HAS_ITEM | HAS_STAIRS), false, false);
3547 
3548         *x = loc[0];
3549         *y = loc[1];
3550     }
3551     pmap[*x][*y].flags |= HAS_ITEM;
3552     if (theItem->flags & ITEM_MAGIC_DETECTED && itemMagicPolarity(theItem)) {
3553         pmap[*x][*y].flags |= ITEM_DETECTED;
3554     }
3555 }
3556 
3557 // Returns true iff the location is a plain wall, three of the four cardinal neighbors are walls, the remaining cardinal neighbor
3558 // 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.
validStairLoc(short x,short y)3559 boolean validStairLoc(short x, short y) {
3560     short newX, newY, dir, neighborWallCount;
3561 
3562     if (x < 1 || x >= DCOLS - 1 || y < 1 || y >= DROWS - 1 || pmap[x][y].layers[DUNGEON] != WALL) {
3563         return false;
3564     }
3565 
3566     for (dir=0; dir< DIRECTION_COUNT; dir++) {
3567         newX = x + nbDirs[dir][0];
3568         newY = y + nbDirs[dir][1];
3569         if (pmap[newX][newY].flags & IS_IN_MACHINE) {
3570             return false;
3571         }
3572     }
3573 
3574     neighborWallCount = 0;
3575     for (dir=0; dir<4; dir++) {
3576         newX = x + nbDirs[dir][0];
3577         newY = y + nbDirs[dir][1];
3578 
3579         if (cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)) {
3580             // neighbor is a wall
3581             neighborWallCount++;
3582         } else {
3583             // neighbor is not a wall
3584             if (cellHasTerrainFlag(newX, newY, T_PATHING_BLOCKER)
3585                 || passableArcCount(newX, newY) >= 2) {
3586                 return false;
3587             }
3588             // now check the two diagonals between the walls
3589 
3590             newX = x - nbDirs[dir][0] + nbDirs[dir][1];
3591             newY = y - nbDirs[dir][1] + nbDirs[dir][0];
3592             if (!cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)) {
3593                 return false;
3594             }
3595 
3596             newX = x - nbDirs[dir][0] - nbDirs[dir][1];
3597             newY = y - nbDirs[dir][1] - nbDirs[dir][0];
3598             if (!cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)) {
3599                 return false;
3600             }
3601         }
3602     }
3603     if (neighborWallCount != 3) {
3604         return false;
3605     }
3606     return true;
3607 }
3608 
3609 // The walls on either side become torches. Any adjacent granite then becomes top_wall. All wall neighbors are un-tunnelable.
3610 // Grid is zeroed out within 5 spaces in all directions.
prepareForStairs(short x,short y,char grid[DCOLS][DROWS])3611 void prepareForStairs(short x, short y, char grid[DCOLS][DROWS]) {
3612     short newX, newY, dir;
3613 
3614     // Add torches to either side.
3615     for (dir=0; dir<4; dir++) {
3616         if (!cellHasTerrainFlag(x + nbDirs[dir][0], y + nbDirs[dir][1], T_OBSTRUCTS_PASSABILITY)) {
3617             newX = x - nbDirs[dir][1];
3618             newY = y - nbDirs[dir][0];
3619             pmap[newX][newY].layers[DUNGEON] = TORCH_WALL;
3620             newX = x + nbDirs[dir][1];
3621             newY = y + nbDirs[dir][0];
3622             pmap[newX][newY].layers[DUNGEON] = TORCH_WALL;
3623             break;
3624         }
3625     }
3626     // Expose granite.
3627     for (dir=0; dir< DIRECTION_COUNT; dir++) {
3628         newX = x + nbDirs[dir][0];
3629         newY = y + nbDirs[dir][1];
3630         if (pmap[newX][newY].layers[DUNGEON] == GRANITE) {
3631             pmap[newX][newY].layers[DUNGEON] = WALL;
3632         }
3633         if (cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)) {
3634             pmap[newX][newY].flags |= IMPREGNABLE;
3635         }
3636     }
3637     // Zero out grid in the vicinity.
3638     for (newX = max(0, x - 5); newX < min(DCOLS, x + 5); newX++) {
3639         for (newY = max(0, y - 5); newY < min(DROWS, y + 5); newY++) {
3640             grid[newX][newY] = false;
3641         }
3642     }
3643 }
3644 
3645 // Places the player, monsters, items and stairs.
initializeLevel()3646 void initializeLevel() {
3647     short i, j, dir;
3648     short upLoc[2], downLoc[2], **mapToStairs, **mapToPit;
3649     item *theItem;
3650     char grid[DCOLS][DROWS];
3651     short n = rogue.depthLevel - 1;
3652 
3653     // Place the stairs.
3654 
3655     for (i=0; i < DCOLS; i++) {
3656         for (j=0; j < DROWS; j++) {
3657             grid[i][j] = validStairLoc(i, j);
3658         }
3659     }
3660 
3661     if (D_INSPECT_LEVELGEN) {
3662         dumpLevelToScreen();
3663         hiliteCharGrid(grid, &teal, 100);
3664         temporaryMessage("Stair location candidates:", REQUIRE_ACKNOWLEDGMENT);
3665     }
3666 
3667     if (getQualifyingGridLocNear(downLoc, levels[n].downStairsLoc[0], levels[n].downStairsLoc[1], grid, false)) {
3668         prepareForStairs(downLoc[0], downLoc[1], grid);
3669     } else {
3670         getQualifyingLocNear(downLoc, levels[n].downStairsLoc[0], levels[n].downStairsLoc[1], false, 0,
3671                              (T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_ITEMS | T_AUTO_DESCENT | T_IS_DEEP_WATER | T_LAVA_INSTA_DEATH | T_IS_DF_TRAP),
3672                              (HAS_MONSTER | HAS_ITEM | HAS_STAIRS | IS_IN_MACHINE), true, false);
3673     }
3674 
3675     if (rogue.depthLevel == DEEPEST_LEVEL) {
3676         pmap[downLoc[0]][downLoc[1]].layers[DUNGEON] = DUNGEON_PORTAL;
3677     } else {
3678         pmap[downLoc[0]][downLoc[1]].layers[DUNGEON] = DOWN_STAIRS;
3679     }
3680     pmap[downLoc[0]][downLoc[1]].layers[LIQUID]     = NOTHING;
3681     pmap[downLoc[0]][downLoc[1]].layers[SURFACE]    = NOTHING;
3682 
3683     if (!levels[n+1].visited) {
3684         levels[n+1].upStairsLoc[0] = downLoc[0];
3685         levels[n+1].upStairsLoc[1] = downLoc[1];
3686     }
3687     levels[n].downStairsLoc[0] = downLoc[0];
3688     levels[n].downStairsLoc[1] = downLoc[1];
3689 
3690     if (getQualifyingGridLocNear(upLoc, levels[n].upStairsLoc[0], levels[n].upStairsLoc[1], grid, false)) {
3691         prepareForStairs(upLoc[0], upLoc[1], grid);
3692     } else { // Hopefully this never happens.
3693         getQualifyingLocNear(upLoc, levels[n].upStairsLoc[0], levels[n].upStairsLoc[1], false, 0,
3694                              (T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_ITEMS | T_AUTO_DESCENT | T_IS_DEEP_WATER | T_LAVA_INSTA_DEATH | T_IS_DF_TRAP),
3695                              (HAS_MONSTER | HAS_ITEM | HAS_STAIRS | IS_IN_MACHINE), true, false);
3696     }
3697 
3698     levels[n].upStairsLoc[0] = upLoc[0];
3699     levels[n].upStairsLoc[1] = upLoc[1];
3700 
3701     if (rogue.depthLevel == 1) {
3702         pmap[upLoc[0]][upLoc[1]].layers[DUNGEON] = DUNGEON_EXIT;
3703     } else {
3704         pmap[upLoc[0]][upLoc[1]].layers[DUNGEON] = UP_STAIRS;
3705     }
3706     pmap[upLoc[0]][upLoc[1]].layers[LIQUID] = NOTHING;
3707     pmap[upLoc[0]][upLoc[1]].layers[SURFACE] = NOTHING;
3708 
3709     rogue.downLoc[0] = downLoc[0];
3710     rogue.downLoc[1] = downLoc[1];
3711     pmap[downLoc[0]][downLoc[1]].flags |= HAS_STAIRS;
3712     rogue.upLoc[0] = upLoc[0];
3713     rogue.upLoc[1] = upLoc[1];
3714     pmap[upLoc[0]][upLoc[1]].flags |= HAS_STAIRS;
3715 
3716     if (!levels[rogue.depthLevel-1].visited) {
3717 
3718         // Run a field of view check from up stairs so that monsters do not spawn within sight of it.
3719         for (dir=0; dir<4; dir++) {
3720             if (coordinatesAreInMap(upLoc[0] + nbDirs[dir][0], upLoc[1] + nbDirs[dir][1])
3721                 && !cellHasTerrainFlag(upLoc[0] + nbDirs[dir][0], upLoc[1] + nbDirs[dir][1], T_OBSTRUCTS_PASSABILITY)) {
3722 
3723                 upLoc[0] += nbDirs[dir][0];
3724                 upLoc[1] += nbDirs[dir][1];
3725                 break;
3726             }
3727         }
3728         zeroOutGrid(grid);
3729         getFOVMask(grid, upLoc[0], upLoc[1], max(DCOLS, DROWS) * FP_FACTOR, (T_OBSTRUCTS_VISION), 0, false);
3730         for (i=0; i<DCOLS; i++) {
3731             for (j=0; j<DROWS; j++) {
3732                 if (grid[i][j]) {
3733                     pmap[i][j].flags |= IN_FIELD_OF_VIEW;
3734                 }
3735             }
3736         }
3737         populateItems(upLoc[0], upLoc[1]);
3738         populateMonsters();
3739     }
3740 
3741     // Restore items that fell from the previous depth.
3742     for (theItem = floorItems->nextItem; theItem != NULL; theItem = theItem->nextItem) {
3743         restoreItem(theItem);
3744     }
3745 
3746     // Restore creatures that fell from the previous depth or that have been pathing toward the stairs.
3747     mapToStairs = allocGrid();
3748     fillGrid(mapToStairs, 0);
3749     mapToPit = allocGrid();
3750     fillGrid(mapToPit, 0);
3751     calculateDistances(mapToStairs, player.xLoc, player.yLoc, T_PATHING_BLOCKER, NULL, true, true);
3752     calculateDistances(mapToPit,
3753                        levels[rogue.depthLevel - 1].playerExitedVia[0],
3754                        levels[rogue.depthLevel - 1].playerExitedVia[1],
3755                        T_PATHING_BLOCKER,
3756                        NULL,
3757                        true,
3758                        true);
3759     for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) {
3760         creature *monst = nextCreature(&it);
3761         restoreMonster(monst, mapToStairs, mapToPit);
3762     }
3763     freeGrid(mapToStairs);
3764     freeGrid(mapToPit);
3765 }
3766 
3767 // fills (*x, *y) with the coordinates of a random cell with
3768 // no creatures, items or stairs and with either a matching liquid and dungeon type
3769 // or at least one layer of type terrainType.
3770 // A dungeon, liquid type of -1 will match anything.
randomMatchingLocation(short * x,short * y,short dungeonType,short liquidType,short terrainType)3771 boolean randomMatchingLocation(short *x, short *y, short dungeonType, short liquidType, short terrainType) {
3772     short failsafeCount = 0;
3773     do {
3774         failsafeCount++;
3775         *x = rand_range(0, DCOLS - 1);
3776         *y = rand_range(0, DROWS - 1);
3777     } while (failsafeCount < 500 && ((terrainType >= 0 && !cellHasTerrainType(*x, *y, terrainType))
3778                                      || (((dungeonType >= 0 && pmap[*x][*y].layers[DUNGEON] != dungeonType) || (liquidType >= 0 && pmap[*x][*y].layers[LIQUID] != liquidType)) && terrainType < 0)
3779                                      || (pmap[*x][*y].flags & (HAS_PLAYER | HAS_MONSTER | HAS_STAIRS | HAS_ITEM | IS_IN_MACHINE))
3780                                      || (terrainType < 0 && !(tileCatalog[dungeonType].flags & T_OBSTRUCTS_ITEMS)
3781                                          && cellHasTerrainFlag(*x, *y, T_OBSTRUCTS_ITEMS))));
3782     if (failsafeCount >= 500) {
3783         return false;
3784     }
3785     return true;
3786 }
3787