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