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