Annotation of brogue-ce/src/brogue/Architect.c, Revision 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