From 8827fb3aea6ba58cae6d54130e89755625a1f2a0 Mon Sep 17 00:00:00 2001 From: flend Date: Wed, 2 Aug 2023 07:21:18 +0100 Subject: [PATCH 1/7] Adding a script to format source code and a github action to check --- .clang-format | 138 +++++++++++++++++++++++ .github/workflows/clang-format-check.yml | 21 ++++ Makefile | 6 + formatter.sh | 31 +++++ 4 files changed, 196 insertions(+) create mode 100644 .clang-format create mode 100644 .github/workflows/clang-format-check.yml create mode 100755 formatter.sh diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..651b4102 --- /dev/null +++ b/.clang-format @@ -0,0 +1,138 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: false +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: All +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + - Regex: '.*' + Priority: 1 + SortPriority: 0 +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentCaseLabels: false +IndentGotoLabels: true +IndentPPDirectives: None +IndentWidth: 2 +IndentWrappedFunctionNames: false +# InsertNewlineAtEOF: true # clang 16 which is not widely installed +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: true +SortIncludes: false +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +Standard: Latest +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseCRLF: false +UseTab: Never +... + diff --git a/.github/workflows/clang-format-check.yml b/.github/workflows/clang-format-check.yml new file mode 100644 index 00000000..d8546509 --- /dev/null +++ b/.github/workflows/clang-format-check.yml @@ -0,0 +1,21 @@ +name: clang-format Check +on: [push, pull_request] +jobs: + formatting-check: + name: Formatting Check + runs-on: ubuntu-latest + strategy: + matrix: + path: + - check: 'src' + exclude: '(Globals.c|GlobalsBrogue.c|GlobalsRapidBrogue.c)' + + steps: + - uses: actions/checkout@v3 + - name: Run clang-format style check for C/C++/Protobuf programs. + uses: jidicula/clang-format-action@v4.11.0 + with: + clang-format-version: '10' + check-path: ${{ matrix.path['check'] }} + exclude-regex: ${{ matrix.path['exclude'] }} + fallback-style: 'Mozilla' # optional \ No newline at end of file diff --git a/Makefile b/Makefile index a942041a..6646fb61 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,7 @@ include config.mk +FORMATTER_EXCLUSION_LIST := "src/brogue/Globals.c" "src/variants/GlobalsRapidBrogue.c" "src/variants/GlobalsBrogue.c" + cflags := -Isrc/brogue -Isrc/platform -Isrc/variants -std=c99 \ -Wall -Wpedantic -Werror=implicit -Wno-parentheses -Wno-unused-result \ -Wformat -Werror=format-security -Wformat-overflow=0 @@ -78,6 +80,9 @@ include make/*.mk clean: $(warning 'make clean' is no longer needed in many situations, so is not supported. Use 'make -B' to force rebuild something.) +format: + @./formatter.sh src $(FORMATTER_EXCLUSION_LIST) + escape = $(subst ','\'',$(1)) vars: mkdir -p vars @@ -86,5 +91,6 @@ vars/%: vars FORCE @echo '$(call escape,$($*))' > vars/$*.tmp @if cmp --quiet vars/$*.tmp vars/$*; then :; else cp vars/$*.tmp vars/$*; fi +.PHONY: format FORCE: diff --git a/formatter.sh b/formatter.sh new file mode 100755 index 00000000..3befe8c4 --- /dev/null +++ b/formatter.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# Function to recursively find source files and run clang-format +function format_source_files() { + local root_dir="$1" + local exclude_list=("$@") + + find "$root_dir" -type f \( -name "*.c" -o -name "*.cpp" -o -name "*.h" \) | while read -r file; do + # Check if the file should be excluded + if [[ ! " ${exclude_list[@]} " =~ " ${file} " ]]; then + echo "Formatting: $file" + clang-format -i "$file" + # sed command to ensure files end with a newline + sed -i -e '$!b' -e '/^$/!a\' "$file" + else + echo "Excluding: $file" + fi + done +} + +# Check if the correct number of arguments is provided +if [ "$#" -lt 2 ]; then + echo "Usage: $0 ... " + exit 1 +fi + +directory_root="$1" +shift # Remove the first argument (directory_root) from the arguments list + +# Call the format_source_files function with the provided arguments +format_source_files "$directory_root" "$@" From 9b1965f6974da06c9d186d0d19c9ff62eee95530 Mon Sep 17 00:00:00 2001 From: flend Date: Wed, 2 Aug 2023 09:02:55 +0100 Subject: [PATCH 2/7] Running `make format` to reformat all files --- src/brogue/Architect.c | 6250 +++++++------ src/brogue/Buttons.c | 477 +- src/brogue/Combat.c | 2864 +++--- src/brogue/Dijkstra.c | 387 +- src/brogue/Globals.h | 2 +- src/brogue/GlobalsBase.c | 98 +- src/brogue/GlobalsBase.h | 19 +- src/brogue/Grid.c | 851 +- src/brogue/IO.c | 8350 +++++++++--------- src/brogue/Items.c | 13542 ++++++++++++++--------------- src/brogue/Light.c | 594 +- src/brogue/MainMenu.c | 1354 +-- src/brogue/Math.c | 350 +- src/brogue/Monsters.c | 7378 ++++++++-------- src/brogue/Movement.c | 3646 ++++---- src/brogue/PowerTables.c | 612 +- src/brogue/Recordings.c | 2441 +++--- src/brogue/Rogue.h | 5894 +++++++------ src/brogue/RogueMain.c | 2302 +++-- src/brogue/SeedCatalog.c | 450 +- src/brogue/Time.c | 4437 +++++----- src/brogue/Utilities.c | 17 +- src/brogue/Wizard.c | 821 +- src/platform/PlatformDefines.h | 3 +- src/platform/curses-platform.c | 381 +- src/platform/main.c | 476 +- src/platform/null-platform.c | 44 +- src/platform/platform.h | 141 +- src/platform/platformdependent.c | 1065 ++- src/platform/sdl2-platform.c | 655 +- src/platform/term.c | 1180 ++- src/platform/term.h | 49 +- src/platform/tiles.c | 1252 +-- src/platform/tiles.h | 5 +- src/platform/web-platform.c | 422 +- 35 files changed, 34192 insertions(+), 34617 deletions(-) diff --git a/src/brogue/Architect.c b/src/brogue/Architect.c index cf4a9b1e..13320df1 100644 --- a/src/brogue/Architect.c +++ b/src/brogue/Architect.c @@ -29,116 +29,111 @@ short topBlobMinX, topBlobMinY, blobWidth, blobHeight; #ifdef BROGUE_ASSERTS // otherwise handled as a macro in rogue.h boolean cellHasTerrainFlag(short x, short y, unsigned long flagMask) { - assert(coordinatesAreInMap(x, y)); - return ((flagMask) & terrainFlags((x), (y)) ? true : false); + assert(coordinatesAreInMap(x, y)); + return ((flagMask)&terrainFlags((x), (y)) ? true : false); } #endif boolean checkLoopiness(short x, short y) { - short newX, newY, dir, sdir; - short numStrings, maxStringLength, currentStringLength; + short newX, newY, dir, sdir; + short numStrings, maxStringLength, currentStringLength; - if (!(pmap[x][y].flags & IN_LOOP)) { - return false; - } - - // find an unloopy neighbor to start on - for (sdir = 0; sdir < DIRECTION_COUNT; sdir++) { - newX = x + cDirs[sdir][0]; - newY = y + cDirs[sdir][1]; - if (!coordinatesAreInMap(newX, newY) - || !(pmap[newX][newY].flags & IN_LOOP)) { - break; - } - } - if (sdir == 8) { // no unloopy neighbors - return false; // leave cell loopy - } - - // starting on this unloopy neighbor, work clockwise and count up (a) the number of strings - // of loopy neighbors, and (b) the length of the longest such string. - numStrings = maxStringLength = currentStringLength = 0; - boolean inString = false; - for (dir = sdir; dir < sdir + 8; dir++) { - newX = x + cDirs[dir % 8][0]; - newY = y + cDirs[dir % 8][1]; - if (coordinatesAreInMap(newX, newY) && (pmap[newX][newY].flags & IN_LOOP)) { - currentStringLength++; - if (!inString) { - if (numStrings > 0) { - return false; // more than one string here; leave loopy - } - numStrings++; - inString = true; - } - } else if (inString) { - if (currentStringLength > maxStringLength) { - maxStringLength = currentStringLength; - } - currentStringLength = 0; - inString = false; - } - } - if (inString && currentStringLength > maxStringLength) { + if (!(pmap[x][y].flags & IN_LOOP)) { + return false; + } + + // find an unloopy neighbor to start on + for (sdir = 0; sdir < DIRECTION_COUNT; sdir++) { + newX = x + cDirs[sdir][0]; + newY = y + cDirs[sdir][1]; + if (!coordinatesAreInMap(newX, newY) || !(pmap[newX][newY].flags & IN_LOOP)) { + break; + } + } + if (sdir == 8) { // no unloopy neighbors + return false; // leave cell loopy + } + + // starting on this unloopy neighbor, work clockwise and count up (a) the number of strings + // of loopy neighbors, and (b) the length of the longest such string. + numStrings = maxStringLength = currentStringLength = 0; + boolean inString = false; + for (dir = sdir; dir < sdir + 8; dir++) { + newX = x + cDirs[dir % 8][0]; + newY = y + cDirs[dir % 8][1]; + if (coordinatesAreInMap(newX, newY) && (pmap[newX][newY].flags & IN_LOOP)) { + currentStringLength++; + if (!inString) { + if (numStrings > 0) { + return false; // more than one string here; leave loopy + } + numStrings++; + inString = true; + } + } else if (inString) { + if (currentStringLength > maxStringLength) { maxStringLength = currentStringLength; + } + currentStringLength = 0; + inString = false; } - if (numStrings == 1 && maxStringLength <= 4) { - pmap[x][y].flags &= ~IN_LOOP; + } + if (inString && currentStringLength > maxStringLength) { + maxStringLength = currentStringLength; + } + if (numStrings == 1 && maxStringLength <= 4) { + pmap[x][y].flags &= ~IN_LOOP; - for (dir = 0; dir < DIRECTION_COUNT; dir++) { - newX = x + cDirs[dir][0]; - newY = y + cDirs[dir][1]; - if (coordinatesAreInMap(newX, newY)) { - checkLoopiness(newX, newY); - } - } - return true; - } else { - return false; + for (dir = 0; dir < DIRECTION_COUNT; dir++) { + newX = x + cDirs[dir][0]; + newY = y + cDirs[dir][1]; + if (coordinatesAreInMap(newX, newY)) { + checkLoopiness(newX, newY); + } } + return true; + } else { + return false; + } } void auditLoop(short x, short y, char grid[DCOLS][DROWS]) { - short dir, newX, newY; - if (coordinatesAreInMap(x, y) - && !grid[x][y] - && !(pmap[x][y].flags & IN_LOOP)) { + short dir, newX, newY; + if (coordinatesAreInMap(x, y) && !grid[x][y] && !(pmap[x][y].flags & IN_LOOP)) { - grid[x][y] = true; - for (dir = 0; dir < DIRECTION_COUNT; dir++) { - newX = x + nbDirs[dir][0]; - newY = y + nbDirs[dir][1]; - if (coordinatesAreInMap(newX, newY)) { - auditLoop(newX, newY, grid); - } - } + grid[x][y] = true; + for (dir = 0; dir < DIRECTION_COUNT; dir++) { + newX = x + nbDirs[dir][0]; + newY = y + nbDirs[dir][1]; + if (coordinatesAreInMap(newX, newY)) { + auditLoop(newX, newY, grid); + } } + } } -// Assumes it is called with respect to a passable (startX, startY), and that the same is not already included in results. -// Returns 10000 if the area included an area machine. +// Assumes it is called with respect to a passable (startX, startY), and that the same is not already included in +// results. Returns 10000 if the area included an area machine. short floodFillCount(char results[DCOLS][DROWS], char passMap[DCOLS][DROWS], short startX, short startY) { - short dir, newX, newY, count; + short dir, newX, newY, count; - count = (passMap[startX][startY] == 2 ? 5000 : 1); + count = (passMap[startX][startY] == 2 ? 5000 : 1); - if (pmap[startX][startY].flags & IS_IN_AREA_MACHINE) { - count = 10000; - } + if (pmap[startX][startY].flags & IS_IN_AREA_MACHINE) { + count = 10000; + } - results[startX][startY] = true; + results[startX][startY] = true; - for(dir=0; dir<4; dir++) { - newX = startX + nbDirs[dir][0]; - newY = startY + nbDirs[dir][1]; - if (coordinatesAreInMap(newX, newY) - && passMap[newX][newY] - && !results[newX][newY]) { + for (dir = 0; dir < 4; dir++) { + newX = startX + nbDirs[dir][0]; + newY = startY + nbDirs[dir][1]; + if (coordinatesAreInMap(newX, newY) && passMap[newX][newY] && !results[newX][newY]) { - count += floodFillCount(results, passMap, newX, newY); - } + count += floodFillCount(results, passMap, newX, newY); } - return min(count, 10000); + } + return min(count, 10000); } // Rotates around the cell, counting up the number of distinct strings of passable neighbors in a single revolution. @@ -149,2082 +144,2067 @@ short floodFillCount(char results[DCOLS][DROWS], char passMap[DCOLS][DROWS], sho // Four means it is in the intersection of two hallways. // Five or more means there is a bug. short passableArcCount(short x, short y) { - short arcCount, dir, oldX, oldY, newX, newY; + short arcCount, dir, oldX, oldY, newX, newY; - brogueAssert(coordinatesAreInMap(x, y)); + brogueAssert(coordinatesAreInMap(x, y)); - arcCount = 0; - for (dir = 0; dir < DIRECTION_COUNT; dir++) { - oldX = x + cDirs[(dir + 7) % 8][0]; - oldY = y + cDirs[(dir + 7) % 8][1]; - newX = x + cDirs[dir][0]; - newY = y + cDirs[dir][1]; - // Counts every transition from passable to impassable or vice-versa on the way around the cell: - if ((coordinatesAreInMap(newX, newY) && cellIsPassableOrDoor(newX, newY)) - != (coordinatesAreInMap(oldX, oldY) && cellIsPassableOrDoor(oldX, oldY))) { - arcCount++; - } + arcCount = 0; + for (dir = 0; dir < DIRECTION_COUNT; dir++) { + oldX = x + cDirs[(dir + 7) % 8][0]; + oldY = y + cDirs[(dir + 7) % 8][1]; + newX = x + cDirs[dir][0]; + newY = y + cDirs[dir][1]; + // Counts every transition from passable to impassable or vice-versa on the way around the cell: + if ((coordinatesAreInMap(newX, newY) && cellIsPassableOrDoor(newX, newY)) + != (coordinatesAreInMap(oldX, oldY) && cellIsPassableOrDoor(oldX, oldY))) { + arcCount++; } - return arcCount / 2; // Since we added one when we entered a wall and another when we left. + } + return arcCount / 2; // Since we added one when we entered a wall and another when we left. } // locates all loops and chokepoints void analyzeMap(boolean calculateChokeMap) { - short i, j, i2, j2, dir, newX, newY, oldX, oldY, passableArcCount, cellCount; - char grid[DCOLS][DROWS], passMap[DCOLS][DROWS]; - boolean designationSurvives; + short i, j, i2, j2, dir, newX, newY, oldX, oldY, passableArcCount, cellCount; + char grid[DCOLS][DROWS], passMap[DCOLS][DROWS]; + boolean designationSurvives; - // first find all of the loops - rogue.staleLoopMap = false; + // first find all of the loops + rogue.staleLoopMap = false; - for(i=0; i 2) { - if (!passMap[i-1][j] && !passMap[i+1][j] || !passMap[i][j-1] && !passMap[i][j+1]) { - pmap[i][j].flags |= IS_CHOKEPOINT; - } - break; - } - } - } - } + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + if (pmap[i][j].flags & IN_LOOP) { + designationSurvives = false; + for (dir = 0; dir < DIRECTION_COUNT; dir++) { + newX = i + nbDirs[dir][0]; + newY = j + nbDirs[dir][1]; + if (coordinatesAreInMap(newX, newY) && !grid[newX][newY] && !(pmap[newX][newY].flags & IN_LOOP)) { + designationSurvives = true; + break; + } + } + if (!designationSurvives) { + grid[i][j] = true; + pmap[i][j].flags &= ~IN_LOOP; } + } } + } - if (calculateChokeMap) { - - // Done finding chokepoints; now create a chokepoint map. - - // The chokepoint map is a number for each passable tile. If the tile is a chokepoint, - // then the number indicates the number of tiles that would be rendered unreachable if the - // chokepoint were blocked. If the tile is not a chokepoint, then the number indicates - // the number of tiles that would be rendered unreachable if the nearest exit chokepoint - // were blocked. - // The cost of all of this is one depth-first flood-fill per open point that is adjacent to a chokepoint. - - // Start by setting the chokepoint values really high, and roping off room machines. - for(i=0; i 2) { + if (!passMap[i - 1][j] && !passMap[i + 1][j] || !passMap[i][j - 1] && !passMap[i][j + 1]) { + pmap[i][j].flags |= IS_CHOKEPOINT; + } + break; + } + } + } + } + } + } + + if (calculateChokeMap) { + + // Done finding chokepoints; now create a chokepoint map. + + // The chokepoint map is a number for each passable tile. If the tile is a chokepoint, + // then the number indicates the number of tiles that would be rendered unreachable if the + // chokepoint were blocked. If the tile is not a chokepoint, then the number indicates + // the number of tiles that would be rendered unreachable if the nearest exit chokepoint + // were blocked. + // The cost of all of this is one depth-first flood-fill per open point that is adjacent to a chokepoint. + + // Start by setting the chokepoint values really high, and roping off room machines. + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + chokeMap[i][j] = 30000; + if (pmap[i][j].flags & IS_IN_ROOM_MACHINE) { + passMap[i][j] = false; + } + } + } + + // Scan through and find a chokepoint next to an open point. + + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + if (passMap[i][j] && (pmap[i][j].flags & IS_CHOKEPOINT)) { + for (dir = 0; dir < 4; dir++) { + newX = i + nbDirs[dir][0]; + newY = j + nbDirs[dir][1]; + if (coordinatesAreInMap(newX, newY) && passMap[newX][newY] && !(pmap[newX][newY].flags & IS_CHOKEPOINT)) { + // OK, (newX, newY) is an open point and (i, j) is a chokepoint. + // Pretend (i, j) is blocked by changing passMap, and run a flood-fill cell count starting on (newX, + // newY). Keep track of the flooded region in grid[][]. + zeroOutGrid(grid); + passMap[i][j] = false; + cellCount = floodFillCount(grid, passMap, newX, newY); + passMap[i][j] = true; + + // CellCount is the size of the region that would be obstructed if the chokepoint were blocked. + // CellCounts less than 4 are not useful, so we skip those cases. + + if (cellCount >= 4) { + // Now, on the chokemap, all of those flooded cells should take the lesser of their current value or + // this resultant number. + for (i2 = 0; i2 < DCOLS; i2++) { + for (j2 = 0; j2 < DROWS; j2++) { + if (grid[i2][j2] && cellCount < chokeMap[i2][j2]) { + chokeMap[i2][j2] = cellCount; + pmap[i2][j2].flags &= ~IS_GATE_SITE; + } + } } - } - } - // Scan through and find a chokepoint next to an open point. - - for(i=0; i= 4) { - // Now, on the chokemap, all of those flooded cells should take the lesser of their current value or this resultant number. - for(i2=0; i2 minimumPathingDistance) { // and if the pathing distance between the two flanking floor tiles exceeds minimumPathingDistance, - grid[x][y] = 2; // then turn the tile into a doorway. - costMap[x][y] = 1; // (Cost map also needs updating.) - if (D_INSPECT_LEVELGEN) { - pos p = { x, y }; - plotCharWithColor(G_CLOSED_DOOR, mapToWindow(p), &black, &green); - } - break; - } - } + short newX, newY, oppX, oppY; + short **pathMap, **costMap; + short i, d, x, y, sCoord[DCOLS * DROWS]; + const short dirCoords[2][2] = {{1, 0}, {0, 1}}; + + fillSequentialList(sCoord, DCOLS * DROWS); + shuffleList(sCoord, DCOLS * DROWS); + + if (D_INSPECT_LEVELGEN) { + colorOverDungeon(&darkGray); + hiliteGrid(grid, &white, 100); + } + + pathMap = allocGrid(); + costMap = allocGrid(); + copyGrid(costMap, grid); + findReplaceGrid(costMap, 0, 0, PDS_OBSTRUCTION); + findReplaceGrid(costMap, 1, 30000, 1); + + for (i = 0; i < DCOLS * DROWS; i++) { + x = sCoord[i] / DROWS; + y = sCoord[i] % DROWS; + if (!grid[x][y]) { + for (d = 0; d <= 1; d++) { // Try a horizontal door, and then a vertical door. + newX = x + dirCoords[d][0]; + oppX = x - dirCoords[d][0]; + newY = y + dirCoords[d][1]; + oppY = y - dirCoords[d][1]; + if (coordinatesAreInMap(newX, newY) && coordinatesAreInMap(oppX, oppY) && grid[newX][newY] == 1 + && grid[oppX][oppY] == 1) { // If the tile being inspected has floor on both sides, + + fillGrid(pathMap, 30000); + pathMap[newX][newY] = 0; + dijkstraScan(pathMap, costMap, false); + if (pathMap[oppX][oppY] > minimumPathingDistance) { // and if the pathing distance between the two flanking + // floor tiles exceeds minimumPathingDistance, + grid[x][y] = 2; // then turn the tile into a doorway. + costMap[x][y] = 1; // (Cost map also needs updating.) + if (D_INSPECT_LEVELGEN) { + pos p = {x, y}; + plotCharWithColor(G_CLOSED_DOOR, mapToWindow(p), &black, &green); } + break; + } } + } } - if (D_INSPECT_LEVELGEN) { - temporaryMessage("Added secondary connections:", REQUIRE_ACKNOWLEDGMENT); - } - freeGrid(pathMap); - freeGrid(costMap); + } + if (D_INSPECT_LEVELGEN) { + temporaryMessage("Added secondary connections:", REQUIRE_ACKNOWLEDGMENT); + } + freeGrid(pathMap); + freeGrid(costMap); } // Assumes (startX, startY) is in the machine. // Returns true if everything went well, and false if we ran into a machine component // that was already there, as we don't want to build a machine around it. boolean addTileToMachineInteriorAndIterate(char interior[DCOLS][DROWS], short startX, short startY) { - short dir, newX, newY; - boolean goodSoFar = true; - - interior[startX][startY] = true; - - for (dir = 0; dir < 4 && goodSoFar; dir++) { - newX = startX + nbDirs[dir][0]; - newY = startY + nbDirs[dir][1]; - if (coordinatesAreInMap(newX, newY)) { - if ((pmap[newX][newY].flags & HAS_ITEM) - || ((pmap[newX][newY].flags & IS_IN_MACHINE) && !(pmap[newX][newY].flags & IS_GATE_SITE))) { - // Abort if there's an item in the room. - // Items haven't been populated yet, so the only way this could happen is if another machine - // previously placed an item here. - // Also abort if we're touching another machine at any point other than a gate tile. - return false; - } - if (!interior[newX][newY] - && chokeMap[newX][newY] <= chokeMap[startX][startY] // don't have to worry about walls since they're all 30000 - && !(pmap[newX][newY].flags & IS_IN_MACHINE)) { - //goodSoFar = goodSoFar && addTileToMachineInteriorAndIterate(interior, newX, newY); - if (goodSoFar) { - goodSoFar = addTileToMachineInteriorAndIterate(interior, newX, newY); - } - } + short dir, newX, newY; + boolean goodSoFar = true; + + interior[startX][startY] = true; + + for (dir = 0; dir < 4 && goodSoFar; dir++) { + newX = startX + nbDirs[dir][0]; + newY = startY + nbDirs[dir][1]; + if (coordinatesAreInMap(newX, newY)) { + if ((pmap[newX][newY].flags & HAS_ITEM) + || ((pmap[newX][newY].flags & IS_IN_MACHINE) && !(pmap[newX][newY].flags & IS_GATE_SITE))) { + // Abort if there's an item in the room. + // Items haven't been populated yet, so the only way this could happen is if another machine + // previously placed an item here. + // Also abort if we're touching another machine at any point other than a gate tile. + return false; + } + if (!interior[newX][newY] + && chokeMap[newX][newY] <= chokeMap[startX][startY] // don't have to worry about walls since they're all 30000 + && !(pmap[newX][newY].flags & IS_IN_MACHINE)) { + // goodSoFar = goodSoFar && addTileToMachineInteriorAndIterate(interior, newX, newY); + if (goodSoFar) { + goodSoFar = addTileToMachineInteriorAndIterate(interior, newX, newY); } + } } - return goodSoFar; + } + return goodSoFar; } void copyMap(pcell from[DCOLS][DROWS], pcell to[DCOLS][DROWS]) { - short i, j; + short i, j; - for(i=0; icategory & (STAFF | WAND | POTION | SCROLL | RING | WEAPON | ARMOR | CHARM)) { - for (i = 0; i < itemCount; i++) { - if (spawnedItems[i]->category == theItem->category - && spawnedItems[i]->kind == theItem->kind) { + short i; + if (theItem->category & (STAFF | WAND | POTION | SCROLL | RING | WEAPON | ARMOR | CHARM)) { + for (i = 0; i < itemCount; i++) { + if (spawnedItems[i]->category == theItem->category && spawnedItems[i]->kind == theItem->kind) { - return true; - } - } + return true; + } } - return false; + } + return false; } boolean blueprintQualifies(short i, unsigned long requiredMachineFlags) { - if (blueprintCatalog[i].depthRange[0] > rogue.depthLevel - || blueprintCatalog[i].depthRange[1] < rogue.depthLevel - // Must have the required flags: - || (~(blueprintCatalog[i].flags) & requiredMachineFlags) - // May NOT have BP_ADOPT_ITEM unless that flag is required: - || (blueprintCatalog[i].flags & BP_ADOPT_ITEM & ~requiredMachineFlags) - // May NOT have BP_VESTIBULE unless that flag is required: - || (blueprintCatalog[i].flags & BP_VESTIBULE & ~requiredMachineFlags)) { - - return false; - } - return true; -} - -void abortItemsAndMonsters(item *spawnedItems[MACHINES_BUFFER_LENGTH], creature *spawnedMonsters[MACHINES_BUFFER_LENGTH]) { - short i, j; - - for (i=0; icarriedItem == spawnedItems[i]) { - spawnedMonsters[j]->carriedItem = NULL; - break; - } - } - deleteItem(spawnedItems[i]); - spawnedItems[i] = NULL; - } - for (i=0; i rogue.depthLevel + || blueprintCatalog[i].depthRange[1] < rogue.depthLevel + // Must have the required flags: + || (~(blueprintCatalog[i].flags) & requiredMachineFlags) + // May NOT have BP_ADOPT_ITEM unless that flag is required: + || (blueprintCatalog[i].flags & BP_ADOPT_ITEM & ~requiredMachineFlags) + // May NOT have BP_VESTIBULE unless that flag is required: + || (blueprintCatalog[i].flags & BP_VESTIBULE & ~requiredMachineFlags)) { -boolean cellIsFeatureCandidate(short x, short y, - short originX, short originY, - short distanceBound[2], - char interior[DCOLS][DROWS], - char occupied[DCOLS][DROWS], - char viewMap[DCOLS][DROWS], - short **distanceMap, - short machineNumber, - unsigned long featureFlags, + return false; + } + return true; +} + +void abortItemsAndMonsters(item *spawnedItems[MACHINES_BUFFER_LENGTH], + creature *spawnedMonsters[MACHINES_BUFFER_LENGTH]) { + short i, j; + + for (i = 0; i < MACHINES_BUFFER_LENGTH && spawnedItems[i]; i++) { + removeItemFromChain(spawnedItems[i], floorItems); + removeItemFromChain(spawnedItems[i], packItems); // just in case; can't imagine why this would arise. + for (j = 0; j < MACHINES_BUFFER_LENGTH && spawnedMonsters[j]; j++) { + // Remove the item from spawned monsters, so it doesn't get double-freed when the creature is killed below. + if (spawnedMonsters[j]->carriedItem == spawnedItems[i]) { + spawnedMonsters[j]->carriedItem = NULL; + break; + } + } + deleteItem(spawnedItems[i]); + spawnedItems[i] = NULL; + } + for (i = 0; i < MACHINES_BUFFER_LENGTH && spawnedMonsters[i]; i++) { + killCreature(spawnedMonsters[i], true); + spawnedMonsters[i] = NULL; + } +} + +boolean cellIsFeatureCandidate(short x, short y, short originX, short originY, short distanceBound[2], + char interior[DCOLS][DROWS], char occupied[DCOLS][DROWS], char viewMap[DCOLS][DROWS], + short **distanceMap, short machineNumber, unsigned long featureFlags, unsigned long bpFlags) { - short newX, newY, dir, distance; + short newX, newY, dir, distance; - // No building in the hallway if it's prohibited. - // This check comes before the origin check, so an area machine will fail altogether - // if its origin is in a hallway and the feature that must be built there does not permit as much. - if ((featureFlags & MF_NOT_IN_HALLWAY) - && passableArcCount(x, y) > 1) { - return false; - } + // No building in the hallway if it's prohibited. + // This check comes before the origin check, so an area machine will fail altogether + // if its origin is in a hallway and the feature that must be built there does not permit as much. + if ((featureFlags & MF_NOT_IN_HALLWAY) && passableArcCount(x, y) > 1) { + return false; + } - // No building along the perimeter of the level if it's prohibited. - if ((featureFlags & MF_NOT_ON_LEVEL_PERIMETER) - && (x == 0 || x == DCOLS - 1 || y == 0 || y == DROWS - 1)) { - return false; - } + // No building along the perimeter of the level if it's prohibited. + if ((featureFlags & MF_NOT_ON_LEVEL_PERIMETER) && (x == 0 || x == DCOLS - 1 || y == 0 || y == DROWS - 1)) { + return false; + } - // The origin is a candidate if the feature is flagged to be built at the origin. - // If it's a room, the origin (i.e. doorway) is otherwise NOT a candidate. - if (featureFlags & MF_BUILD_AT_ORIGIN) { - return ((x == originX && y == originY) ? true : false); - } else if ((bpFlags & BP_ROOM) && x == originX && y == originY) { - return false; - } + // The origin is a candidate if the feature is flagged to be built at the origin. + // If it's a room, the origin (i.e. doorway) is otherwise NOT a candidate. + if (featureFlags & MF_BUILD_AT_ORIGIN) { + return ((x == originX && y == originY) ? true : false); + } else if ((bpFlags & BP_ROOM) && x == originX && y == originY) { + return false; + } - // No building in another feature's personal space! - if (occupied[x][y]) { - return false; - } + // No building in another feature's personal space! + if (occupied[x][y]) { + return false; + } - // Must be in the viewmap if the appropriate flag is set. - if ((featureFlags & (MF_IN_VIEW_OF_ORIGIN | MF_IN_PASSABLE_VIEW_OF_ORIGIN)) - && !viewMap[x][y]) { - return false; - } + // Must be in the viewmap if the appropriate flag is set. + if ((featureFlags & (MF_IN_VIEW_OF_ORIGIN | MF_IN_PASSABLE_VIEW_OF_ORIGIN)) && !viewMap[x][y]) { + return false; + } - // Do a distance check if the feature requests it. - if (cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY)) { // Distance is calculated for walls too. - distance = 10000; - for (dir = 0; dir < 4; dir++) { - newX = x + nbDirs[dir][0]; - newY = y + nbDirs[dir][1]; - if (coordinatesAreInMap(newX, newY) - && !cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY) - && distance > distanceMap[newX][newY] + 1) { + // Do a distance check if the feature requests it. + if (cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY)) { // Distance is calculated for walls too. + distance = 10000; + for (dir = 0; dir < 4; dir++) { + newX = x + nbDirs[dir][0]; + newY = y + nbDirs[dir][1]; + if (coordinatesAreInMap(newX, newY) && !cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY) + && distance > distanceMap[newX][newY] + 1) { - distance = distanceMap[newX][newY] + 1; - } - } - } else { - distance = distanceMap[x][y]; + distance = distanceMap[newX][newY] + 1; + } } + } else { + distance = distanceMap[x][y]; + } - if (distance > distanceBound[1] // distance exceeds max - || distance < distanceBound[0]) { // distance falls short of min - return false; - } - if (featureFlags & MF_BUILD_IN_WALLS) { // If we're supposed to build in a wall... - if (!interior[x][y] - && (pmap[x][y].machineNumber == 0 || pmap[x][y].machineNumber == machineNumber) - && cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY)) { // ...and this location is a wall that's not already machined... - for (dir=0; dir<4; dir++) { - newX = x + nbDirs[dir][0]; - newY = y + nbDirs[dir][1]; - if (coordinatesAreInMap(newX, newY) // ...and it's next to an interior spot or permitted elsewhere and next to passable spot... - && ((interior[newX][newY] && !(newX==originX && newY==originY)) - || ((featureFlags & MF_BUILD_ANYWHERE_ON_LEVEL) - && !cellHasTerrainFlag(newX, newY, T_PATHING_BLOCKER) - && pmap[newX][newY].machineNumber == 0))) { - return true; // ...then we're golden! - } - } - } - return false; // Otherwise, no can do. - } else if (cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY)) { // Can't build in a wall unless instructed to do so. - return false; - } else if (featureFlags & MF_BUILD_ANYWHERE_ON_LEVEL) { - if ((featureFlags & MF_GENERATE_ITEM) - && (cellHasTerrainFlag(x, y, T_OBSTRUCTS_ITEMS | T_PATHING_BLOCKER) || (pmap[x][y].flags & (IS_CHOKEPOINT | IN_LOOP | IS_IN_MACHINE)))) { - return false; - } else { - return !(pmap[x][y].flags & IS_IN_MACHINE); + if (distance > distanceBound[1] // distance exceeds max + || distance < distanceBound[0]) { // distance falls short of min + return false; + } + if (featureFlags & MF_BUILD_IN_WALLS) { // If we're supposed to build in a wall... + if (!interior[x][y] && (pmap[x][y].machineNumber == 0 || pmap[x][y].machineNumber == machineNumber) + && cellHasTerrainFlag( + x, y, T_OBSTRUCTS_PASSABILITY)) { // ...and this location is a wall that's not already machined... + for (dir = 0; dir < 4; dir++) { + newX = x + nbDirs[dir][0]; + newY = y + nbDirs[dir][1]; + if (coordinatesAreInMap( + newX, newY) // ...and it's next to an interior spot or permitted elsewhere and next to passable spot... + && ((interior[newX][newY] && !(newX == originX && newY == originY)) + || ((featureFlags & MF_BUILD_ANYWHERE_ON_LEVEL) && !cellHasTerrainFlag(newX, newY, T_PATHING_BLOCKER) + && pmap[newX][newY].machineNumber == 0))) { + return true; // ...then we're golden! } - } else if (interior[x][y]) { - return true; + } } + return false; // Otherwise, no can do. + } else if (cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY)) { // Can't build in a wall unless instructed to do so. return false; + } else if (featureFlags & MF_BUILD_ANYWHERE_ON_LEVEL) { + if ((featureFlags & MF_GENERATE_ITEM) + && (cellHasTerrainFlag(x, y, T_OBSTRUCTS_ITEMS | T_PATHING_BLOCKER) + || (pmap[x][y].flags & (IS_CHOKEPOINT | IN_LOOP | IS_IN_MACHINE)))) { + return false; + } else { + return !(pmap[x][y].flags & IS_IN_MACHINE); + } + } else if (interior[x][y]) { + return true; + } + return false; } - void addLocationToKey(item *theItem, short x, short y, boolean disposableHere) { - short i; + short i; - for (i=0; i < KEY_ID_MAXIMUM && (theItem->keyLoc[i].x || theItem->keyLoc[i].machine); i++); - theItem->keyLoc[i].x = x; - theItem->keyLoc[i].y = y; - theItem->keyLoc[i].disposableHere = disposableHere; + for (i = 0; i < KEY_ID_MAXIMUM && (theItem->keyLoc[i].x || theItem->keyLoc[i].machine); i++) + ; + theItem->keyLoc[i].x = x; + theItem->keyLoc[i].y = y; + theItem->keyLoc[i].disposableHere = disposableHere; } void addMachineNumberToKey(item *theItem, short machineNumber, boolean disposableHere) { - short i; + short i; - for (i=0; i < KEY_ID_MAXIMUM && (theItem->keyLoc[i].x || theItem->keyLoc[i].machine); i++); - theItem->keyLoc[i].machine = machineNumber; - theItem->keyLoc[i].disposableHere = disposableHere; + for (i = 0; i < KEY_ID_MAXIMUM && (theItem->keyLoc[i].x || theItem->keyLoc[i].machine); i++) + ; + theItem->keyLoc[i].machine = machineNumber; + theItem->keyLoc[i].disposableHere = disposableHere; } void expandMachineInterior(char interior[DCOLS][DROWS], short minimumInteriorNeighbors) { - boolean madeChange; - short nbcount, newX, newY, i, j, layer; - enum directions dir; - - do { - madeChange = false; - for(i=1; i= minimumInteriorNeighbors) { - // Make sure zero exterior open/machine neighbors out of eight: - for (nbcount = dir = 0; dir < DIRECTION_COUNT; dir++) { - newX = i + nbDirs[dir][0]; - newY = j + nbDirs[dir][1]; - if (!interior[newX][newY] - && (!cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY) || pmap[newX][newY].machineNumber != 0)) { - nbcount++; - break; - } - } - if (!nbcount) { - // Eliminate this obstruction; welcome its location into the machine. - madeChange = true; - interior[i][j] = true; - for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) { - if (tileCatalog[pmap[i][j].layers[layer]].flags & T_PATHING_BLOCKER) { - pmap[i][j].layers[layer] = (layer == DUNGEON ? FLOOR : NOTHING); - } - } - for (dir = 0; dir < DIRECTION_COUNT; dir++) { - newX = i + nbDirs[dir][0]; - newY = j + nbDirs[dir][1]; - if (pmap[newX][newY].layers[DUNGEON] == GRANITE) { - pmap[newX][newY].layers[DUNGEON] = WALL; - } - } - } - } + boolean madeChange; + short nbcount, newX, newY, i, j, layer; + enum directions dir; + + do { + madeChange = false; + for (i = 1; i < DCOLS - 1; i++) { + for (j = 1; j < DROWS - 1; j++) { + if (cellHasTerrainFlag(i, j, T_PATHING_BLOCKER) && pmap[i][j].machineNumber == 0) { + + // Count up the number of interior open neighbors out of eight: + for (nbcount = dir = 0; dir < DIRECTION_COUNT; dir++) { + newX = i + nbDirs[dir][0]; + newY = j + nbDirs[dir][1]; + if (interior[newX][newY] && !cellHasTerrainFlag(newX, newY, T_PATHING_BLOCKER)) { + nbcount++; + } + } + if (nbcount >= minimumInteriorNeighbors) { + // Make sure zero exterior open/machine neighbors out of eight: + for (nbcount = dir = 0; dir < DIRECTION_COUNT; dir++) { + newX = i + nbDirs[dir][0]; + newY = j + nbDirs[dir][1]; + if (!interior[newX][newY] + && (!cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY) + || pmap[newX][newY].machineNumber != 0)) { + nbcount++; + break; + } + } + if (!nbcount) { + // Eliminate this obstruction; welcome its location into the machine. + madeChange = true; + interior[i][j] = true; + for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) { + if (tileCatalog[pmap[i][j].layers[layer]].flags & T_PATHING_BLOCKER) { + pmap[i][j].layers[layer] = (layer == DUNGEON ? FLOOR : NOTHING); } + } + for (dir = 0; dir < DIRECTION_COUNT; dir++) { + newX = i + nbDirs[dir][0]; + newY = j + nbDirs[dir][1]; + if (pmap[newX][newY].layers[DUNGEON] == GRANITE) { + pmap[newX][newY].layers[DUNGEON] = WALL; + } + } } + } } - } while (madeChange); + } + } + } while (madeChange); - // Clear doors and secret doors out of the interior of the machine. - for(i=1; i 0) { + pathingGrid = allocGrid(); + costGrid = allocGrid(); + for (n = 0; n < orphanCount; n++) { -void redesignInterior(char interior[DCOLS][DROWS], short originX, short originY, short theProfileIndex) { - short i, j, n, newX, newY; - enum directions dir; - pos orphanList[20]; - short orphanCount = 0; - short **grid, **pathingGrid, **costGrid; - grid = allocGrid(); - - for (i=0; i 0) { + pathingGrid[i][j] = 0; + costGrid[i][j] = 1; } else { - grid[i][j] = -1; // Exterior spaces are off limits. + pathingGrid[i][j] = 30000; + costGrid[i][j] = 1; } + } else { + pathingGrid[i][j] = 30000; + costGrid[i][j] = PDS_OBSTRUCTION; + } } - } - attachRooms(grid, &dungeonProfileCatalog[theProfileIndex], 40, 40); - - // Connect to preexisting rooms that were orphaned (mostly preexisting machine rooms). - if (orphanCount > 0) { - pathingGrid = allocGrid(); - costGrid = allocGrid(); - for (n = 0; n < orphanCount; n++) { - - if (D_INSPECT_MACHINES) { - dumpLevelToScreen(); - copyGrid(pathingGrid, grid); - findReplaceGrid(pathingGrid, -1, -1, 0); - hiliteGrid(pathingGrid, &green, 50); - plotCharWithColor('X', mapToWindow(orphanList[n]), &black, &orange); - temporaryMessage("Orphan detected:", REQUIRE_ACKNOWLEDGMENT); - } + } + dijkstraScan(pathingGrid, costGrid, false); - for (i=0; i 0) { - pathingGrid[i][j] = 0; - costGrid[i][j] = 1; - } else { - pathingGrid[i][j] = 30000; - costGrid[i][j] = 1; - } - } else { - pathingGrid[i][j] = 30000; - costGrid[i][j] = PDS_OBSTRUCTION; - } - } - } - dijkstraScan(pathingGrid, costGrid, false); - - i = orphanList[n].x; - j = orphanList[n].y; - while (pathingGrid[i][j] > 0) { - for (dir = 0; dir < 4; dir++) { - newX = i + nbDirs[dir][0]; - newY = j + nbDirs[dir][1]; - - if (coordinatesAreInMap(newX, newY) - && pathingGrid[newX][newY] < pathingGrid[i][j]) { - - grid[i][j] = 1; - i = newX; - j = newY; - break; - } - } - brogueAssert(dir < 4); - if (D_INSPECT_MACHINES) { - dumpLevelToScreen(); - displayGrid(pathingGrid); - pos p = { i, j }; - plotCharWithColor('X', mapToWindow(p), &black, &orange); - temporaryMessage("Orphan connecting:", REQUIRE_ACKNOWLEDGMENT); - } - } - } - freeGrid(pathingGrid); - freeGrid(costGrid); - } + i = orphanList[n].x; + j = orphanList[n].y; + while (pathingGrid[i][j] > 0) { + for (dir = 0; dir < 4; dir++) { + newX = i + nbDirs[dir][0]; + newY = j + nbDirs[dir][1]; - addLoops(grid, 10); - for(i=0; i= 0) { - pmap[i][j].layers[SURFACE] = pmap[i][j].layers[GAS] = NOTHING; - } - if (grid[i][j] == 0) { - pmap[i][j].layers[DUNGEON] = GRANITE; - interior[i][j] = false; - } - if (grid[i][j] >= 1) { - pmap[i][j].layers[DUNGEON] = FLOOR; - } - } - } - } - freeGrid(grid); -} + if (coordinatesAreInMap(newX, newY) && pathingGrid[newX][newY] < pathingGrid[i][j]) { -void prepareInteriorWithMachineFlags(char interior[DCOLS][DROWS], short originX, short originY, unsigned long flags, short dungeonProfileIndex) { - short i, j, newX, newY; - enum dungeonLayers layer; - enum directions dir; + grid[i][j] = 1; + i = newX; + j = newY; + break; + } + } + brogueAssert(dir < 4); + if (D_INSPECT_MACHINES) { + dumpLevelToScreen(); + displayGrid(pathingGrid); + pos p = {i, j}; + plotCharWithColor('X', mapToWindow(p), &black, &orange); + temporaryMessage("Orphan connecting:", REQUIRE_ACKNOWLEDGMENT); + } + } + } + freeGrid(pathingGrid); + freeGrid(costGrid); + } + + addLoops(grid, 10); + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + if (interior[i][j]) { + if (grid[i][j] >= 0) { + pmap[i][j].layers[SURFACE] = pmap[i][j].layers[GAS] = NOTHING; + } + if (grid[i][j] == 0) { + pmap[i][j].layers[DUNGEON] = GRANITE; + interior[i][j] = false; + } + if (grid[i][j] >= 1) { + pmap[i][j].layers[DUNGEON] = FLOOR; + } + } + } + } + freeGrid(grid); +} + +void prepareInteriorWithMachineFlags(char interior[DCOLS][DROWS], short originX, short originY, unsigned long flags, + short dungeonProfileIndex) { + short i, j, newX, newY; + enum dungeonLayers layer; + enum directions dir; - // If requested, clear and expand the room as far as possible until either it's convex or it bumps into surrounding rooms - if (flags & BP_MAXIMIZE_INTERIOR) { - expandMachineInterior(interior, 1); - } else if (flags & BP_OPEN_INTERIOR) { - expandMachineInterior(interior, 4); - } + // If requested, clear and expand the room as far as possible until either it's convex or it bumps into surrounding + // rooms + if (flags & BP_MAXIMIZE_INTERIOR) { + expandMachineInterior(interior, 1); + } else if (flags & BP_OPEN_INTERIOR) { + expandMachineInterior(interior, 4); + } - // If requested, cleanse the interior -- no interesting terrain allowed. - if (flags & BP_PURGE_INTERIOR) { - for(i=0; inumberBlueprints; i++) { - if (blueprintQualifies(i, requiredMachineFlags)) { - totalFreq += blueprintCatalog[i].frequency; - } - } + // First, choose the blueprint. We choose from among blueprints + // that have the required blueprint flags and that satisfy the depth requirements. + totalFreq = 0; + for (i = 1; i < gameConst->numberBlueprints; i++) { + if (blueprintQualifies(i, requiredMachineFlags)) { + totalFreq += blueprintCatalog[i].frequency; + } + } - if (!totalFreq) { // If no suitable blueprints are in the library, fail. - if (distanceMap) { - freeGrid(distanceMap); - } - if (D_MESSAGE_MACHINE_GENERATION) printf("\nDepth %i: Failed to build a machine because no suitable blueprints were available.", - rogue.depthLevel); - free(p); - return false; - } + if (!totalFreq) { // If no suitable blueprints are in the library, fail. + if (distanceMap) { + freeGrid(distanceMap); + } + if (D_MESSAGE_MACHINE_GENERATION) + printf("\nDepth %i: Failed to build a machine because no suitable blueprints were available.", + rogue.depthLevel); + free(p); + return false; + } + + // Pick from among the suitable blueprints. + randIndex = rand_range(1, totalFreq); + for (i = 1; i < gameConst->numberBlueprints; i++) { + if (blueprintQualifies(i, requiredMachineFlags)) { + if (randIndex <= blueprintCatalog[i].frequency) { + bp = i; + break; + } else { + randIndex -= blueprintCatalog[i].frequency; + } + } + } - // Pick from among the suitable blueprints. - randIndex = rand_range(1, totalFreq); - for (i=1; inumberBlueprints; i++) { - if (blueprintQualifies(i, requiredMachineFlags)) { - if (randIndex <= blueprintCatalog[i].frequency) { - bp = i; - break; - } else { - randIndex -= blueprintCatalog[i].frequency; - } - } - } + // If we don't have a blueprint yet, something went wrong. + brogueAssert(bp > 0); + } - // If we don't have a blueprint yet, something went wrong. - brogueAssert(bp>0); - } - - // Find a location and map out the machine interior. - if (blueprintCatalog[bp].flags & BP_ROOM) { - // If it's a room machine, count up the gates of appropriate - // choke size and remember where they are. The origin of the room will be the gate location. - zeroOutGrid(p->interior); - - if (chooseLocation) { - analyzeMap(true); // Make sure the chokeMap is up to date. - totalFreq = 0; - for(i=0; i= blueprintCatalog[bp].roomSize[0] - && chokeMap[i][j] <= blueprintCatalog[bp].roomSize[1]) { - - //DEBUG printf("\nDepth %i: Gate site qualified with interior size of %i.", rogue.depthLevel, chokeMap[i][j]); - p->gateCandidates[totalFreq] = (pos){ .x = i, .y = j }; - totalFreq++; - } - } - } + // Find a location and map out the machine interior. + if (blueprintCatalog[bp].flags & BP_ROOM) { + // If it's a room machine, count up the gates of appropriate + // choke size and remember where they are. The origin of the room will be the gate location. + zeroOutGrid(p->interior); - if (totalFreq) { - // Choose the gate. - randIndex = rand_range(0, totalFreq - 1); - originX = p->gateCandidates[randIndex].x; - originY = p->gateCandidates[randIndex].y; - } else { - // If no suitable sites, abort. - if (distanceMap) { - freeGrid(distanceMap); - } - if (D_MESSAGE_MACHINE_GENERATION) printf("\nDepth %i: Failed to build a machine; there was no eligible door candidate for the chosen room machine from blueprint %i.", - rogue.depthLevel, - bp); - free(p); - return false; - } - } + if (chooseLocation) { + analyzeMap(true); // Make sure the chokeMap is up to date. + totalFreq = 0; + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS && totalFreq < 50; j++) { + if ((pmap[i][j].flags & IS_GATE_SITE) && !(pmap[i][j].flags & IS_IN_MACHINE) + && chokeMap[i][j] >= blueprintCatalog[bp].roomSize[0] + && chokeMap[i][j] <= blueprintCatalog[bp].roomSize[1]) { - // Now map out the interior into interior[][]. - // Start at the gate location and do a depth-first floodfill to grab all adjoining tiles with the - // same or lower choke value, ignoring any tiles that are already part of a machine. - // If we get false from this, try again. If we've tried too many times already, abort. - tryAgain = !addTileToMachineInteriorAndIterate(p->interior, originX, originY); - } else if (blueprintCatalog[bp].flags & BP_VESTIBULE) { - if (chooseLocation) { - // Door machines must have locations passed in. We can't pick one ourselves. - if (distanceMap) { - freeGrid(distanceMap); - } - if (D_MESSAGE_MACHINE_GENERATION) printf("\nDepth %i: ERROR: Attempted to build a door machine from blueprint %i without a location being provided.", - rogue.depthLevel, - bp); - free(p); - return false; - } - if (!fillInteriorForVestibuleMachine(p->interior, bp, originX, originY)) { - if (distanceMap) { - freeGrid(distanceMap); - } - if (D_MESSAGE_MACHINE_GENERATION) printf("\nDepth %i: Failed to build a door machine from blueprint %i; not enough room.", - rogue.depthLevel, - bp); - free(p); - return false; + // DEBUG printf("\nDepth %i: Gate site qualified with interior size of %i.", rogue.depthLevel, + // chokeMap[i][j]); + p->gateCandidates[totalFreq] = (pos){.x = i, .y = j}; + totalFreq++; } - } else { - // Find a location and map out the interior for a non-room machine. - // The strategy here is simply to pick a random location on the map, - // expand it along a pathing map by one space in all directions until the size reaches - // the chosen size, and then make sure the resulting space qualifies. - // If not, try again. If we've tried too many times already, abort. - - locationFailsafe = 10; - do { - zeroOutGrid(p->interior); - tryAgain = false; - - if (chooseLocation) { - // Pick a random origin location. - randomMatchingLocation(&originX, &originY, FLOOR, NOTHING, -1); - } + } + } - if (!distanceMap) { - distanceMap = allocGrid(); - } - fillGrid(distanceMap, 0); - calculateDistances(distanceMap, originX, originY, T_PATHING_BLOCKER, NULL, true, false); - qualifyingTileCount = 0; // Keeps track of how many interior cells we've added. - totalFreq = rand_range(blueprintCatalog[bp].roomSize[0], blueprintCatalog[bp].roomSize[1]); // Keeps track of the goal size. - - fillSequentialList(p->sCols, DCOLS); - shuffleList(p->sCols, DCOLS); - fillSequentialList(p->sRows, DROWS); - shuffleList(p->sRows, DROWS); - - for (k=0; k<1000 && qualifyingTileCount < totalFreq; k++) { - for(i=0; isCols[i]][p->sRows[j]] == k) { - p->interior[p->sCols[i]][p->sRows[j]] = true; - qualifyingTileCount++; - - if (pmap[p->sCols[i]][p->sRows[j]].flags & (HAS_ITEM | HAS_MONSTER | IS_IN_MACHINE)) { - // Abort if we've entered another machine or engulfed another machine's item or monster. - tryAgain = true; - qualifyingTileCount = totalFreq; // This is a hack to drop out of these three for-loops. - } - } - } - } - } + if (totalFreq) { + // Choose the gate. + randIndex = rand_range(0, totalFreq - 1); + originX = p->gateCandidates[randIndex].x; + originY = p->gateCandidates[randIndex].y; + } else { + // If no suitable sites, abort. + if (distanceMap) { + freeGrid(distanceMap); + } + if (D_MESSAGE_MACHINE_GENERATION) + printf("\nDepth %i: Failed to build a machine; there was no eligible door candidate for the chosen room " + "machine from blueprint %i.", + rogue.depthLevel, bp); + free(p); + return false; + } + } + + // Now map out the interior into interior[][]. + // Start at the gate location and do a depth-first floodfill to grab all adjoining tiles with the + // same or lower choke value, ignoring any tiles that are already part of a machine. + // If we get false from this, try again. If we've tried too many times already, abort. + tryAgain = !addTileToMachineInteriorAndIterate(p->interior, originX, originY); + } else if (blueprintCatalog[bp].flags & BP_VESTIBULE) { + if (chooseLocation) { + // Door machines must have locations passed in. We can't pick one ourselves. + if (distanceMap) { + freeGrid(distanceMap); + } + if (D_MESSAGE_MACHINE_GENERATION) + printf("\nDepth %i: ERROR: Attempted to build a door machine from blueprint %i without a location being " + "provided.", + rogue.depthLevel, bp); + free(p); + return false; + } + if (!fillInteriorForVestibuleMachine(p->interior, bp, originX, originY)) { + if (distanceMap) { + freeGrid(distanceMap); + } + if (D_MESSAGE_MACHINE_GENERATION) + printf("\nDepth %i: Failed to build a door machine from blueprint %i; not enough room.", rogue.depthLevel, + bp); + free(p); + return false; + } + } else { + // Find a location and map out the interior for a non-room machine. + // The strategy here is simply to pick a random location on the map, + // expand it along a pathing map by one space in all directions until the size reaches + // the chosen size, and then make sure the resulting space qualifies. + // If not, try again. If we've tried too many times already, abort. + + locationFailsafe = 10; + do { + zeroOutGrid(p->interior); + tryAgain = false; - // Now make sure the interior map satisfies the machine's qualifications. - if ((blueprintCatalog[bp].flags & BP_TREAT_AS_BLOCKING) - && levelIsDisconnectedWithBlockingMap(p->interior, false)) { - tryAgain = true; - } else if ((blueprintCatalog[bp].flags & BP_REQUIRE_BLOCKING) - && levelIsDisconnectedWithBlockingMap(p->interior, true) < 100) { - tryAgain = true; // BP_REQUIRE_BLOCKING needs some work to make sure the disconnect is interesting. - } - // If locationFailsafe runs out, tryAgain will still be true, and we'll try a different machine. - // If we're not choosing the blueprint, then don't bother with the locationFailsafe; just use the higher-level failsafe. - } while (chooseBP && tryAgain && --locationFailsafe); + if (chooseLocation) { + // Pick a random origin location. + randomMatchingLocation(&originX, &originY, FLOOR, NOTHING, -1); } - // If something went wrong, but we haven't been charged with choosing blueprint OR location, - // then there is nothing to try again, so just fail. - if (tryAgain && !chooseBP && !chooseLocation) { - if (distanceMap) { - freeGrid(distanceMap); - } - free(p); - return false; + if (!distanceMap) { + distanceMap = allocGrid(); } + fillGrid(distanceMap, 0); + calculateDistances(distanceMap, originX, originY, T_PATHING_BLOCKER, NULL, true, false); + qualifyingTileCount = 0; // Keeps track of how many interior cells we've added. + totalFreq = rand_range(blueprintCatalog[bp].roomSize[0], + blueprintCatalog[bp].roomSize[1]); // Keeps track of the goal size. - // Now loop if necessary. - } while (tryAgain); - - // This is the point of no return. Back up the level so it can be restored if we have to abort this machine after this point. - copyMap(pmap, p->levelBackup); + fillSequentialList(p->sCols, DCOLS); + shuffleList(p->sCols, DCOLS); + fillSequentialList(p->sRows, DROWS); + shuffleList(p->sRows, DROWS); - // Perform any transformations to the interior indicated by the blueprint flags, including expanding the interior if requested. - prepareInteriorWithMachineFlags(p->interior, originX, originY, blueprintCatalog[bp].flags, blueprintCatalog[bp].dungeonProfileType); + for (k = 0; k < 1000 && qualifyingTileCount < totalFreq; k++) { + for (i = 0; i < DCOLS && qualifyingTileCount < totalFreq; i++) { + for (j = 0; j < DROWS && qualifyingTileCount < totalFreq; j++) { + if (distanceMap[p->sCols[i]][p->sRows[j]] == k) { + p->interior[p->sCols[i]][p->sRows[j]] = true; + qualifyingTileCount++; - // If necessary, label the interior as IS_IN_AREA_MACHINE or IS_IN_ROOM_MACHINE and mark down the number. - machineNumber = ++rogue.machineNumber; // Reserve this machine number, starting with 1. - for(i=0; iinterior[i][j]) { - pmap[i][j].flags |= ((blueprintCatalog[bp].flags & BP_ROOM) ? IS_IN_ROOM_MACHINE : IS_IN_AREA_MACHINE); - pmap[i][j].machineNumber = machineNumber; - // also clear any secret doors, since they screw up distance mapping and aren't fun inside machines - if (pmap[i][j].layers[DUNGEON] == SECRET_DOOR) { - pmap[i][j].layers[DUNGEON] = DOOR; - } - // Clear wired tiles in case we stole them from another machine. - for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) { - if (tileCatalog[pmap[i][j].layers[layer]].mechFlags & (TM_IS_WIRED | TM_IS_CIRCUIT_BREAKER)) { - pmap[i][j].layers[layer] = (layer == DUNGEON ? FLOOR : NOTHING); - } + if (pmap[p->sCols[i]][p->sRows[j]].flags & (HAS_ITEM | HAS_MONSTER | IS_IN_MACHINE)) { + // Abort if we've entered another machine or engulfed another machine's item or monster. + tryAgain = true; + qualifyingTileCount = totalFreq; // This is a hack to drop out of these three for-loops. } + } } + } } - } -// DEBUG printf("\n\nWorking on blueprint %i, with origin at (%i, %i). Here's the initial interior map:", bp, originX, originY); -// DEBUG logBuffer(interior); - - // Calculate the distance map (so that features that want to be close to or far from the origin can be placed accordingly) - // and figure out the 33rd and 67th percentiles for features that want to be near or far from the origin. - if (!distanceMap) { - distanceMap = allocGrid(); - } - fillGrid(distanceMap, 0); - calculateDistances(distanceMap, originX, originY, T_PATHING_BLOCKER, NULL, true, true); - qualifyingTileCount = 0; - for (i=0; i<100; i++) { - p->distances[i] = 0; - } - for(i=0; iinterior[i][j] - && distanceMap[i][j] < 100) { - p->distances[distanceMap[i][j]]++; // create a histogram of distances -- poor man's sort function - qualifyingTileCount++; - } + // Now make sure the interior map satisfies the machine's qualifications. + if ((blueprintCatalog[bp].flags & BP_TREAT_AS_BLOCKING) + && levelIsDisconnectedWithBlockingMap(p->interior, false)) { + tryAgain = true; + } else if ((blueprintCatalog[bp].flags & BP_REQUIRE_BLOCKING) + && levelIsDisconnectedWithBlockingMap(p->interior, true) < 100) { + tryAgain = true; // BP_REQUIRE_BLOCKING needs some work to make sure the disconnect is interesting. } + // If locationFailsafe runs out, tryAgain will still be true, and we'll try a different machine. + // If we're not choosing the blueprint, then don't bother with the locationFailsafe; just use the higher-level + // failsafe. + } while (chooseBP && tryAgain && --locationFailsafe); } - distance25 = (int) (qualifyingTileCount / 4); - distance75 = (int) (3 * qualifyingTileCount / 4); - for (i=0; i<100; i++) { - if (distance25 <= p->distances[i]) { - distance25 = i; - break; - } else { - distance25 -= p->distances[i]; - } + + // If something went wrong, but we haven't been charged with choosing blueprint OR location, + // then there is nothing to try again, so just fail. + if (tryAgain && !chooseBP && !chooseLocation) { + if (distanceMap) { + freeGrid(distanceMap); + } + free(p); + return false; } - for (i=0; i<100; i++) { - if (distance75 <= p->distances[i]) { - distance75 = i; - break; - } else { - distance75 -= p->distances[i]; + + // Now loop if necessary. + } while (tryAgain); + + // This is the point of no return. Back up the level so it can be restored if we have to abort this machine after this + // point. + copyMap(pmap, p->levelBackup); + + // Perform any transformations to the interior indicated by the blueprint flags, including expanding the interior if + // requested. + prepareInteriorWithMachineFlags(p->interior, originX, originY, blueprintCatalog[bp].flags, + blueprintCatalog[bp].dungeonProfileType); + + // If necessary, label the interior as IS_IN_AREA_MACHINE or IS_IN_ROOM_MACHINE and mark down the number. + machineNumber = ++rogue.machineNumber; // Reserve this machine number, starting with 1. + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + if (p->interior[i][j]) { + pmap[i][j].flags |= ((blueprintCatalog[bp].flags & BP_ROOM) ? IS_IN_ROOM_MACHINE : IS_IN_AREA_MACHINE); + pmap[i][j].machineNumber = machineNumber; + // also clear any secret doors, since they screw up distance mapping and aren't fun inside machines + if (pmap[i][j].layers[DUNGEON] == SECRET_DOOR) { + pmap[i][j].layers[DUNGEON] = DOOR; + } + // Clear wired tiles in case we stole them from another machine. + for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) { + if (tileCatalog[pmap[i][j].layers[layer]].mechFlags & (TM_IS_WIRED | TM_IS_CIRCUIT_BREAKER)) { + pmap[i][j].layers[layer] = (layer == DUNGEON ? FLOOR : NOTHING); + } } + } } - //DEBUG printf("\nDistances calculated: 33rd percentile of distance is %i, and 67th is %i.", distance25, distance75); + } - // Now decide which features will be skipped -- of the features marked MF_ALTERNATIVE, skip all but one, chosen randomly. - // Then repeat and do the same with respect to MF_ALTERNATIVE_2, to provide up to two independent sets of alternative features per machine. + // DEBUG printf("\n\nWorking on blueprint %i, with origin at (%i, %i). Here's the initial interior map:", bp, + // originX, originY); DEBUG logBuffer(interior); - for (i=0; idistances[i] = 0; + } + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + if (p->interior[i][j] && distanceMap[i][j] < 100) { + p->distances[distanceMap[i][j]]++; // create a histogram of distances -- poor man's sort function + qualifyingTileCount++; + } + } + } + distance25 = (int)(qualifyingTileCount / 4); + distance75 = (int)(3 * qualifyingTileCount / 4); + for (i = 0; i < 100; i++) { + if (distance25 <= p->distances[i]) { + distance25 = i; + break; + } else { + distance25 -= p->distances[i]; } - for (j = 0; j <= 1; j++) { - totalFreq = 0; - for (i=0; i 0) { - randIndex = rand_range(1, totalFreq); - for (i=0; idistances[i]) { + distance75 = i; + break; + } else { + distance75 -= p->distances[i]; + } + } + // DEBUG printf("\nDistances calculated: 33rd percentile of distance is %i, and 67th is %i.", distance25, distance75); + + // Now decide which features will be skipped -- of the features marked MF_ALTERNATIVE, skip all but one, chosen + // randomly. Then repeat and do the same with respect to MF_ALTERNATIVE_2, to provide up to two independent sets of + // alternative features per machine. + + for (i = 0; i < blueprintCatalog[bp].featureCount; i++) { + skipFeature[i] = false; + } + for (j = 0; j <= 1; j++) { + totalFreq = 0; + for (i = 0; i < blueprintCatalog[bp].featureCount; i++) { + if (blueprintCatalog[bp].feature[i].flags & alternativeFlags[j]) { + skipFeature[i] = true; + totalFreq++; + } + } + if (totalFreq > 0) { + randIndex = rand_range(1, totalFreq); + for (i = 0; i < blueprintCatalog[bp].featureCount; i++) { + if (blueprintCatalog[bp].feature[i].flags & alternativeFlags[j]) { + if (randIndex == 1) { + skipFeature[i] = false; // This is the alternative that gets built. The rest do not. + break; + } else { + randIndex--; + } } + } } + } - // Keep track of all monsters and items that we spawn -- if we abort, we have to go back and delete them all. - itemCount = monsterCount = 0; + // Keep track of all monsters and items that we spawn -- if we abort, we have to go back and delete them all. + itemCount = monsterCount = 0; - // Zero out occupied[][], and use it to keep track of the personal space around each feature that gets placed. - zeroOutGrid(p->occupied); + // Zero out occupied[][], and use it to keep track of the personal space around each feature that gets placed. + zeroOutGrid(p->occupied); - // Now tick through the features and build them. - for (feat = 0; feat < blueprintCatalog[bp].featureCount; feat++) { + // Now tick through the features and build them. + for (feat = 0; feat < blueprintCatalog[bp].featureCount; feat++) { - if (skipFeature[feat]) { - continue; // Skip the alternative features that were not selected for building. - } - - feature = &(blueprintCatalog[bp].feature[feat]); - - // Figure out the distance bounds. - distanceBound[0] = 0; - distanceBound[1] = 10000; - if (feature->flags & MF_NEAR_ORIGIN) { - distanceBound[1] = distance25; - } - if (feature->flags & MF_FAR_FROM_ORIGIN) { - distanceBound[0] = distance75; - } - - if (feature->flags & (MF_IN_VIEW_OF_ORIGIN | MF_IN_PASSABLE_VIEW_OF_ORIGIN)) { - zeroOutGrid(p->viewMap); - if (feature->flags & MF_IN_PASSABLE_VIEW_OF_ORIGIN) { - getFOVMask(p->viewMap, originX, originY, max(DCOLS, DROWS) * FP_FACTOR, T_PATHING_BLOCKER, 0, false); - } else { - getFOVMask(p->viewMap, originX, originY, max(DCOLS, DROWS) * FP_FACTOR, (T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_VISION), 0, false); - } - p->viewMap[originX][originY] = true; + if (skipFeature[feat]) { + continue; // Skip the alternative features that were not selected for building. + } - if (D_INSPECT_MACHINES) { - dumpLevelToScreen(); - hiliteCharGrid(p->viewMap, &omniscienceColor, 75); - temporaryMessage("Showing visibility.", REQUIRE_ACKNOWLEDGMENT); - } - } + feature = &(blueprintCatalog[bp].feature[feat]); - do { // If the MF_REPEAT_UNTIL_NO_PROGRESS flag is set, repeat until we fail to build the required number of instances. - - // Make a master map of candidate locations for this feature. - qualifyingTileCount = 0; - for(i=0; iinterior, p->occupied, p->viewMap, distanceMap, - machineNumber, feature->flags, blueprintCatalog[bp].flags)) { - qualifyingTileCount++; - p->candidates[i][j] = true; - } else { - p->candidates[i][j] = false; - } - } - } + // Figure out the distance bounds. + distanceBound[0] = 0; + distanceBound[1] = 10000; + if (feature->flags & MF_NEAR_ORIGIN) { + distanceBound[1] = distance25; + } + if (feature->flags & MF_FAR_FROM_ORIGIN) { + distanceBound[0] = distance75; + } - if (D_INSPECT_MACHINES) { - dumpLevelToScreen(); - hiliteCharGrid(p->occupied, &red, 75); - hiliteCharGrid(p->candidates, &green, 75); - hiliteCharGrid(p->interior, &blue, 75); - temporaryMessage("Indicating: Occupied (red); Candidates (green); Interior (blue).", REQUIRE_ACKNOWLEDGMENT); - } + if (feature->flags & (MF_IN_VIEW_OF_ORIGIN | MF_IN_PASSABLE_VIEW_OF_ORIGIN)) { + zeroOutGrid(p->viewMap); + if (feature->flags & MF_IN_PASSABLE_VIEW_OF_ORIGIN) { + getFOVMask(p->viewMap, originX, originY, max(DCOLS, DROWS) * FP_FACTOR, T_PATHING_BLOCKER, 0, false); + } else { + getFOVMask(p->viewMap, originX, originY, max(DCOLS, DROWS) * FP_FACTOR, + (T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_VISION), 0, false); + } + p->viewMap[originX][originY] = true; - if (feature->flags & MF_EVERYWHERE & ~MF_BUILD_AT_ORIGIN) { - // Generate everywhere that qualifies -- instead of randomly picking tiles, keep spawning until we run out of eligible tiles. - generateEverywhere = true; - } else { - // build as many instances as required - generateEverywhere = false; - instanceCount = rand_range(feature->instanceCountRange[0], feature->instanceCountRange[1]); - } + if (D_INSPECT_MACHINES) { + dumpLevelToScreen(); + hiliteCharGrid(p->viewMap, &omniscienceColor, 75); + temporaryMessage("Showing visibility.", REQUIRE_ACKNOWLEDGMENT); + } + } - // Cache the personal space constant. - personalSpace = feature->personalSpace; + do { // If the MF_REPEAT_UNTIL_NO_PROGRESS flag is set, repeat until we fail to build the required number of + // instances. - for (instance = 0; (generateEverywhere || instance < instanceCount) && qualifyingTileCount > 0;) { + // Make a master map of candidate locations for this feature. + qualifyingTileCount = 0; + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + if (cellIsFeatureCandidate(i, j, originX, originY, distanceBound, p->interior, p->occupied, p->viewMap, + distanceMap, machineNumber, feature->flags, blueprintCatalog[bp].flags)) { + qualifyingTileCount++; + p->candidates[i][j] = true; + } else { + p->candidates[i][j] = false; + } + } + } - // Find a location for the feature. - if (feature->flags & MF_BUILD_AT_ORIGIN) { - // Does the feature want to be at the origin? If so, put it there. (Just an optimization.) - featX = originX; - featY = originY; + if (D_INSPECT_MACHINES) { + dumpLevelToScreen(); + hiliteCharGrid(p->occupied, &red, 75); + hiliteCharGrid(p->candidates, &green, 75); + hiliteCharGrid(p->interior, &blue, 75); + temporaryMessage("Indicating: Occupied (red); Candidates (green); Interior (blue).", REQUIRE_ACKNOWLEDGMENT); + } + + if (feature->flags & MF_EVERYWHERE & ~MF_BUILD_AT_ORIGIN) { + // Generate everywhere that qualifies -- instead of randomly picking tiles, keep spawning until we run out of + // eligible tiles. + generateEverywhere = true; + } else { + // build as many instances as required + generateEverywhere = false; + instanceCount = rand_range(feature->instanceCountRange[0], feature->instanceCountRange[1]); + } + + // Cache the personal space constant. + personalSpace = feature->personalSpace; + + for (instance = 0; (generateEverywhere || instance < instanceCount) && qualifyingTileCount > 0;) { + + // Find a location for the feature. + if (feature->flags & MF_BUILD_AT_ORIGIN) { + // Does the feature want to be at the origin? If so, put it there. (Just an optimization.) + featX = originX; + featY = originY; + } else { + // Pick our candidate location randomly, and also strike it from + // the candidates map so that subsequent instances of this same feature can't choose it. + featX = -1; + featY = -1; + randIndex = rand_range(1, qualifyingTileCount); + for (i = 0; i < DCOLS && featX < 0; i++) { + for (j = 0; j < DROWS && featX < 0; j++) { + if (p->candidates[i][j]) { + if (randIndex == 1) { + // This is the place! + featX = i; + featY = j; + i = DCOLS; // break out of the loops + j = DROWS; } else { - // Pick our candidate location randomly, and also strike it from - // the candidates map so that subsequent instances of this same feature can't choose it. - featX = -1; - featY = -1; - randIndex = rand_range(1, qualifyingTileCount); - for(i=0; icandidates[i][j]) { - if (randIndex == 1) { - // This is the place! - featX = i; - featY = j; - i = DCOLS; // break out of the loops - j = DROWS; - } else { - randIndex--; - } - } - } - } - } - // Don't waste time trying the same place again whether or not this attempt succeeds. - p->candidates[featX][featY] = false; - qualifyingTileCount--; - - DFSucceeded = terrainSucceeded = true; - - // Try to build the DF first, if any, since we don't want it to be disrupted by subsequently placed terrain. - if (feature->featureDF) { - DFSucceeded = spawnDungeonFeature(featX, featY, &dungeonFeatureCatalog[feature->featureDF], false, - !(feature->flags & MF_PERMIT_BLOCKING)); - } - - // Now try to place the terrain tile, if any. - if (DFSucceeded && feature->terrain) { - // Must we check for blocking? - if (!(feature->flags & MF_PERMIT_BLOCKING) - && ((tileCatalog[feature->terrain].flags & T_PATHING_BLOCKER) || (feature->flags & MF_TREAT_AS_BLOCKING))) { - // Yes, check for blocking. - - zeroOutGrid(p->blockingMap); - p->blockingMap[featX][featY] = true; - terrainSucceeded = !levelIsDisconnectedWithBlockingMap(p->blockingMap, false); - } - if (terrainSucceeded) { - pmap[featX][featY].layers[feature->layer] = feature->terrain; - } + randIndex--; } + } + } + } + } + // Don't waste time trying the same place again whether or not this attempt succeeds. + p->candidates[featX][featY] = false; + qualifyingTileCount--; - // OK, if placement was successful, clear some personal space around the feature so subsequent features can't be generated too close. - // Personal space of 0 means nothing gets cleared, 1 means that only the tile itself gets cleared, and 2 means the 3x3 grid centered on it. - - if (DFSucceeded && terrainSucceeded) { - for (i = featX - personalSpace + 1; - i <= featX + personalSpace - 1; - i++) { - for (j = featY - personalSpace + 1; - j <= featY + personalSpace - 1; - j++) { - if (coordinatesAreInMap(i, j)) { - if (p->candidates[i][j]) { - brogueAssert(!p->occupied[i][j] || (i == originX && j == originY)); // Candidates[][] should never be true where occupied[][] is true. - p->candidates[i][j] = false; - qualifyingTileCount--; - } - p->occupied[i][j] = true; - } - } - } - instance++; // we've placed an instance - //DEBUG printf("\nPlaced instance #%i of feature %i at (%i, %i).", instance, feat, featX, featY); - } - - if (DFSucceeded && terrainSucceeded) { // Proceed only if the terrain stuff for this instance succeeded. - - theItem = NULL; + DFSucceeded = terrainSucceeded = true; - // Mark the feature location as part of the machine, in case it is not already inside of it. - pmap[featX][featY].flags |= ((blueprintCatalog[bp].flags & BP_ROOM) ? IS_IN_ROOM_MACHINE : IS_IN_AREA_MACHINE); - pmap[featX][featY].machineNumber = machineNumber; + // Try to build the DF first, if any, since we don't want it to be disrupted by subsequently placed terrain. + if (feature->featureDF) { + DFSucceeded = spawnDungeonFeature(featX, featY, &dungeonFeatureCatalog[feature->featureDF], false, + !(feature->flags & MF_PERMIT_BLOCKING)); + } - // Mark the feature location as impregnable if requested. - if (feature->flags & MF_IMPREGNABLE) { - pmap[featX][featY].flags |= IMPREGNABLE; - } + // Now try to place the terrain tile, if any. + if (DFSucceeded && feature->terrain) { + // Must we check for blocking? + if (!(feature->flags & MF_PERMIT_BLOCKING) + && ((tileCatalog[feature->terrain].flags & T_PATHING_BLOCKER) + || (feature->flags & MF_TREAT_AS_BLOCKING))) { + // Yes, check for blocking. - // Generate an item as necessary. - if ((feature->flags & MF_GENERATE_ITEM) - || (adoptiveItem && (feature->flags & MF_ADOPT_ITEM) && (blueprintCatalog[bp].flags & BP_ADOPT_ITEM))) { - // Are we adopting an item instead of generating one? - if (adoptiveItem && (feature->flags & MF_ADOPT_ITEM) && (blueprintCatalog[bp].flags & BP_ADOPT_ITEM)) { - theItem = adoptiveItem; - adoptiveItem = NULL; // can be adopted only once - } else { - // Have to create an item ourselves. - theItem = generateItem(feature->itemCategory, feature->itemKind); - failsafe = 1000; - while ((theItem->flags & ITEM_CURSED) - || ((feature->flags & MF_REQUIRE_GOOD_RUNIC) && (!(theItem->flags & ITEM_RUNIC))) // runic if requested - || ((feature->flags & MF_NO_THROWING_WEAPONS) && theItem->category == WEAPON && theItem->quantity > 1) // no throwing weapons if prohibited - || itemIsADuplicate(theItem, p->spawnedItems, itemCount)) { // don't want to duplicates of rings, staffs, etc. - deleteItem(theItem); - theItem = generateItem(feature->itemCategory, feature->itemKind); - if (failsafe <= 0) { - break; - } - failsafe--; - } - p->spawnedItems[itemCount] = theItem; // Keep a list of generated items so that we can delete them all if construction fails. - itemCount++; - } - theItem->flags |= feature->itemFlags; - - addLocationToKey(theItem, featX, featY, (feature->flags & MF_KEY_DISPOSABLE) ? true : false); - theItem->originDepth = rogue.depthLevel; - if (feature->flags & MF_SKELETON_KEY) { - addMachineNumberToKey(theItem, machineNumber, (feature->flags & MF_KEY_DISPOSABLE) ? true : false); - } - if (!(feature->flags & MF_OUTSOURCE_ITEM_TO_MACHINE) - && !(feature->flags & MF_MONSTER_TAKE_ITEM)) { - // Place the item at the feature location. - placeItem(theItem, featX, featY); - } - } + zeroOutGrid(p->blockingMap); + p->blockingMap[featX][featY] = true; + terrainSucceeded = !levelIsDisconnectedWithBlockingMap(p->blockingMap, false); + } + if (terrainSucceeded) { + pmap[featX][featY].layers[feature->layer] = feature->terrain; + } + } - if (feature->flags & (MF_OUTSOURCE_ITEM_TO_MACHINE | MF_BUILD_VESTIBULE)) { - // Put this item up for adoption, or generate a door guard machine. - // Try to create a sub-machine that qualifies. - // If we fail 10 times, abort the entire machine (including any sub-machines already built). - // Also, if we build a sub-machine, and it succeeds, but this (its parent machine) fails, - // we pass the monsters and items that it spawned back to the parent, - // so that if the parent fails, they can all be freed. - for (i=10; i > 0; i--) { - // First make sure our adopted item, if any, is not on the floor or in the pack already. - // Otherwise, a previous attempt to place it may have put it on the floor in a different - // machine, only to have that machine fail and be deleted, leaving the item remaining on - // the floor where placed. - if ((feature->flags & MF_OUTSOURCE_ITEM_TO_MACHINE) && theItem) { - removeItemFromChain(theItem, floorItems); - removeItemFromChain(theItem, packItems); - theItem->nextItem = NULL; - success = buildAMachine(-1, -1, -1, BP_ADOPT_ITEM, theItem, p->spawnedItemsSub, p->spawnedMonstersSub); - } else if (feature->flags & MF_BUILD_VESTIBULE) { - success = buildAMachine(-1, featX, featY, BP_VESTIBULE, NULL, p->spawnedItemsSub, p->spawnedMonstersSub); - } - - // Now put the item up for adoption. - if (success) { - // Success! Now we have to add that machine's items and monsters to our own list, so they - // all get deleted if this machine or its parent fails. - for (j=0; jspawnedItemsSub[j]; j++) { - p->spawnedItems[itemCount] = p->spawnedItemsSub[j]; - itemCount++; - p->spawnedItemsSub[j] = NULL; - } - for (j=0; jspawnedMonstersSub[j]; j++) { - p->spawnedMonsters[monsterCount] = p->spawnedMonstersSub[j]; - monsterCount++; - p->spawnedMonstersSub[j] = NULL; - } - break; - } - } - - if (!i) { - if (D_MESSAGE_MACHINE_GENERATION) printf("\nDepth %i: Failed to place blueprint %i because it requires an adoptive machine and we couldn't place one.", rogue.depthLevel, bp); - // failure! abort! - copyMap(p->levelBackup, pmap); - abortItemsAndMonsters(p->spawnedItems, p->spawnedMonsters); - freeGrid(distanceMap); - free(p); - return false; - } - theItem = NULL; - } + // OK, if placement was successful, clear some personal space around the feature so subsequent features can't be + // generated too close. Personal space of 0 means nothing gets cleared, 1 means that only the tile itself gets + // cleared, and 2 means the 3x3 grid centered on it. - // Generate a horde as necessary. - if ((feature->flags & MF_GENERATE_HORDE) - || feature->monsterID) { - - if (feature->flags & MF_GENERATE_HORDE) { - monst = spawnHorde(0, - featX, - featY, - ((HORDE_IS_SUMMONED | HORDE_LEADER_CAPTIVE) & ~(feature->hordeFlags)), - feature->hordeFlags); - if (monst) { - monst->bookkeepingFlags |= MB_JUST_SUMMONED; - } - } - - if (feature->monsterID) { - monst = monsterAtLoc((pos){ featX, featY }); - if (monst) { - killCreature(monst, true); // If there's already a monster here, quietly bury the body. - } - monst = generateMonster(feature->monsterID, true, true); - if (monst) { - monst->loc = (pos){ .x = featX, .y = featY }; - pmapAt(monst->loc)->flags |= HAS_MONSTER; - monst->bookkeepingFlags |= MB_JUST_SUMMONED; - } - } - - if (monst) { - if (!leader) { - leader = monst; - } - - // Give our item to the monster leader if appropriate. - // Actually just remember that we have to give it to this monster; the actual - // hand-off happens after we're sure that the machine will succeed. - if (theItem && (feature->flags & MF_MONSTER_TAKE_ITEM)) { - torchBearer = monst; - torch = theItem; - } - } - - for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) { - creature *monst = nextCreature(&it); - if (monst->bookkeepingFlags & MB_JUST_SUMMONED) { - - // All monsters spawned by a machine are tribemates. - // Assign leader/follower roles if they are not yet assigned. - if (!(monst->bookkeepingFlags & (MB_LEADER | MB_FOLLOWER))) { - if (leader && leader != monst) { - monst->leader = leader; - monst->bookkeepingFlags &= ~MB_LEADER; - monst->bookkeepingFlags |= MB_FOLLOWER; - leader->bookkeepingFlags |= MB_LEADER; - } else { - leader = monst; - } - } - - monst->bookkeepingFlags &= ~MB_JUST_SUMMONED; - p->spawnedMonsters[monsterCount] = monst; - monsterCount++; - if (feature->flags & MF_MONSTER_SLEEPING) { - monst->creatureState = MONSTER_SLEEPING; - } - if (feature->flags & MF_MONSTER_FLEEING) { - monst->creatureState = MONSTER_FLEEING; - monst->creatureMode = MODE_PERM_FLEEING; - } - if (feature->flags & MF_MONSTERS_DORMANT) { - toggleMonsterDormancy(monst); - if (!(feature->flags & MF_MONSTER_SLEEPING) && monst->creatureState != MONSTER_ALLY) { - monst->creatureState = MONSTER_TRACKING_SCENT; - } - } - monst->machineHome = machineNumber; // Monster remembers the machine that spawned it. - } - } - } + if (DFSucceeded && terrainSucceeded) { + for (i = featX - personalSpace + 1; i <= featX + personalSpace - 1; i++) { + for (j = featY - personalSpace + 1; j <= featY + personalSpace - 1; j++) { + if (coordinatesAreInMap(i, j)) { + if (p->candidates[i][j]) { + brogueAssert(!p->occupied[i][j] + || (i == originX + && j == originY)); // Candidates[][] should never be true where occupied[][] is true. + p->candidates[i][j] = false; + qualifyingTileCount--; } - theItem = NULL; - - // Finished with this instance! + p->occupied[i][j] = true; + } } - } while ((feature->flags & MF_REPEAT_UNTIL_NO_PROGRESS) && instance >= feature->minimumInstanceCount); + } + instance++; // we've placed an instance + // DEBUG printf("\nPlaced instance #%i of feature %i at (%i, %i).", instance, feat, featX, featY); + } - //DEBUG printf("\nFinished feature %i. Here's the candidates map:", feat); - //DEBUG logBuffer(candidates); + if (DFSucceeded && terrainSucceeded) { // Proceed only if the terrain stuff for this instance succeeded. - if (instance < feature->minimumInstanceCount && !(feature->flags & MF_REPEAT_UNTIL_NO_PROGRESS)) { - // failure! abort! + theItem = NULL; - if (D_MESSAGE_MACHINE_GENERATION) printf("\nDepth %i: Failed to place blueprint %i because of feature %i; needed %i instances but got only %i.", - rogue.depthLevel, bp, feat, feature->minimumInstanceCount, instance); + // Mark the feature location as part of the machine, in case it is not already inside of it. + pmap[featX][featY].flags + |= ((blueprintCatalog[bp].flags & BP_ROOM) ? IS_IN_ROOM_MACHINE : IS_IN_AREA_MACHINE); + pmap[featX][featY].machineNumber = machineNumber; - // Restore the map to how it was before we touched it. - copyMap(p->levelBackup, pmap); - abortItemsAndMonsters(p->spawnedItems, p->spawnedMonsters); - freeGrid(distanceMap); - free(p); - return false; - } - } - - // Clear out the interior flag for all non-wired cells, if requested. - if (blueprintCatalog[bp].flags & BP_NO_INTERIOR_FLAG) { - for(i=0; iflags & MF_IMPREGNABLE) { + pmap[featX][featY].flags |= IMPREGNABLE; + } - pmap[i][j].flags &= ~IS_IN_MACHINE; - pmap[i][j].machineNumber = 0; + // Generate an item as necessary. + if ((feature->flags & MF_GENERATE_ITEM) + || (adoptiveItem && (feature->flags & MF_ADOPT_ITEM) && (blueprintCatalog[bp].flags & BP_ADOPT_ITEM))) { + // Are we adopting an item instead of generating one? + if (adoptiveItem && (feature->flags & MF_ADOPT_ITEM) && (blueprintCatalog[bp].flags & BP_ADOPT_ITEM)) { + theItem = adoptiveItem; + adoptiveItem = NULL; // can be adopted only once + } else { + // Have to create an item ourselves. + theItem = generateItem(feature->itemCategory, feature->itemKind); + failsafe = 1000; + while ((theItem->flags & ITEM_CURSED) + || ((feature->flags & MF_REQUIRE_GOOD_RUNIC) + && (!(theItem->flags & ITEM_RUNIC))) // runic if requested + || ((feature->flags & MF_NO_THROWING_WEAPONS) && theItem->category == WEAPON + && theItem->quantity > 1) // no throwing weapons if prohibited + || itemIsADuplicate(theItem, p->spawnedItems, + itemCount)) { // don't want to duplicates of rings, staffs, etc. + deleteItem(theItem); + theItem = generateItem(feature->itemCategory, feature->itemKind); + if (failsafe <= 0) { + break; + } + failsafe--; + } + p->spawnedItems[itemCount] + = theItem; // Keep a list of generated items so that we can delete them all if construction fails. + itemCount++; + } + theItem->flags |= feature->itemFlags; + + addLocationToKey(theItem, featX, featY, (feature->flags & MF_KEY_DISPOSABLE) ? true : false); + theItem->originDepth = rogue.depthLevel; + if (feature->flags & MF_SKELETON_KEY) { + addMachineNumberToKey(theItem, machineNumber, (feature->flags & MF_KEY_DISPOSABLE) ? true : false); + } + if (!(feature->flags & MF_OUTSOURCE_ITEM_TO_MACHINE) && !(feature->flags & MF_MONSTER_TAKE_ITEM)) { + // Place the item at the feature location. + placeItem(theItem, featX, featY); + } + } + + if (feature->flags & (MF_OUTSOURCE_ITEM_TO_MACHINE | MF_BUILD_VESTIBULE)) { + // Put this item up for adoption, or generate a door guard machine. + // Try to create a sub-machine that qualifies. + // If we fail 10 times, abort the entire machine (including any sub-machines already built). + // Also, if we build a sub-machine, and it succeeds, but this (its parent machine) fails, + // we pass the monsters and items that it spawned back to the parent, + // so that if the parent fails, they can all be freed. + for (i = 10; i > 0; i--) { + // First make sure our adopted item, if any, is not on the floor or in the pack already. + // Otherwise, a previous attempt to place it may have put it on the floor in a different + // machine, only to have that machine fail and be deleted, leaving the item remaining on + // the floor where placed. + if ((feature->flags & MF_OUTSOURCE_ITEM_TO_MACHINE) && theItem) { + removeItemFromChain(theItem, floorItems); + removeItemFromChain(theItem, packItems); + theItem->nextItem = NULL; + success = buildAMachine(-1, -1, -1, BP_ADOPT_ITEM, theItem, p->spawnedItemsSub, p->spawnedMonstersSub); + } else if (feature->flags & MF_BUILD_VESTIBULE) { + success + = buildAMachine(-1, featX, featY, BP_VESTIBULE, NULL, p->spawnedItemsSub, p->spawnedMonstersSub); + } + + // Now put the item up for adoption. + if (success) { + // Success! Now we have to add that machine's items and monsters to our own list, so they + // all get deleted if this machine or its parent fails. + for (j = 0; j < MACHINES_BUFFER_LENGTH && p->spawnedItemsSub[j]; j++) { + p->spawnedItems[itemCount] = p->spawnedItemsSub[j]; + itemCount++; + p->spawnedItemsSub[j] = NULL; + } + for (j = 0; j < MACHINES_BUFFER_LENGTH && p->spawnedMonstersSub[j]; j++) { + p->spawnedMonsters[monsterCount] = p->spawnedMonstersSub[j]; + monsterCount++; + p->spawnedMonstersSub[j] = NULL; } - } - } + break; + } + } + + if (!i) { + if (D_MESSAGE_MACHINE_GENERATION) + printf("\nDepth %i: Failed to place blueprint %i because it requires an adoptive machine and we " + "couldn't place one.", + rogue.depthLevel, bp); + // failure! abort! + copyMap(p->levelBackup, pmap); + abortItemsAndMonsters(p->spawnedItems, p->spawnedMonsters); + freeGrid(distanceMap); + free(p); + return false; + } + theItem = NULL; + } + + // Generate a horde as necessary. + if ((feature->flags & MF_GENERATE_HORDE) || feature->monsterID) { + + if (feature->flags & MF_GENERATE_HORDE) { + monst = spawnHorde(0, featX, featY, ((HORDE_IS_SUMMONED | HORDE_LEADER_CAPTIVE) & ~(feature->hordeFlags)), + feature->hordeFlags); + if (monst) { + monst->bookkeepingFlags |= MB_JUST_SUMMONED; + } + } + + if (feature->monsterID) { + monst = monsterAtLoc((pos){featX, featY}); + if (monst) { + killCreature(monst, true); // If there's already a monster here, quietly bury the body. + } + monst = generateMonster(feature->monsterID, true, true); + if (monst) { + monst->loc = (pos){.x = featX, .y = featY}; + pmapAt(monst->loc)->flags |= HAS_MONSTER; + monst->bookkeepingFlags |= MB_JUST_SUMMONED; + } + } + + if (monst) { + if (!leader) { + leader = monst; + } + + // Give our item to the monster leader if appropriate. + // Actually just remember that we have to give it to this monster; the actual + // hand-off happens after we're sure that the machine will succeed. + if (theItem && (feature->flags & MF_MONSTER_TAKE_ITEM)) { + torchBearer = monst; + torch = theItem; + } + } + + for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) { + creature *monst = nextCreature(&it); + if (monst->bookkeepingFlags & MB_JUST_SUMMONED) { + + // All monsters spawned by a machine are tribemates. + // Assign leader/follower roles if they are not yet assigned. + if (!(monst->bookkeepingFlags & (MB_LEADER | MB_FOLLOWER))) { + if (leader && leader != monst) { + monst->leader = leader; + monst->bookkeepingFlags &= ~MB_LEADER; + monst->bookkeepingFlags |= MB_FOLLOWER; + leader->bookkeepingFlags |= MB_LEADER; + } else { + leader = monst; + } + } + + monst->bookkeepingFlags &= ~MB_JUST_SUMMONED; + p->spawnedMonsters[monsterCount] = monst; + monsterCount++; + if (feature->flags & MF_MONSTER_SLEEPING) { + monst->creatureState = MONSTER_SLEEPING; + } + if (feature->flags & MF_MONSTER_FLEEING) { + monst->creatureState = MONSTER_FLEEING; + monst->creatureMode = MODE_PERM_FLEEING; + } + if (feature->flags & MF_MONSTERS_DORMANT) { + toggleMonsterDormancy(monst); + if (!(feature->flags & MF_MONSTER_SLEEPING) && monst->creatureState != MONSTER_ALLY) { + monst->creatureState = MONSTER_TRACKING_SCENT; + } + } + monst->machineHome = machineNumber; // Monster remembers the machine that spawned it. + } + } + } + } + theItem = NULL; + + // Finished with this instance! + } + } while ((feature->flags & MF_REPEAT_UNTIL_NO_PROGRESS) && instance >= feature->minimumInstanceCount); + + // DEBUG printf("\nFinished feature %i. Here's the candidates map:", feat); + // DEBUG logBuffer(candidates); + + if (instance < feature->minimumInstanceCount && !(feature->flags & MF_REPEAT_UNTIL_NO_PROGRESS)) { + // failure! abort! + + if (D_MESSAGE_MACHINE_GENERATION) + printf("\nDepth %i: Failed to place blueprint %i because of feature %i; needed %i instances but got only %i.", + rogue.depthLevel, bp, feat, feature->minimumInstanceCount, instance); + + // Restore the map to how it was before we touched it. + copyMap(p->levelBackup, pmap); + abortItemsAndMonsters(p->spawnedItems, p->spawnedMonsters); + freeGrid(distanceMap); + free(p); + return false; } + } + + // Clear out the interior flag for all non-wired cells, if requested. + if (blueprintCatalog[bp].flags & BP_NO_INTERIOR_FLAG) { + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + if (pmap[i][j].machineNumber == machineNumber && !cellHasTMFlag(i, j, (TM_IS_WIRED | TM_IS_CIRCUIT_BREAKER))) { - if (torchBearer && torch) { - if (torchBearer->carriedItem) { - deleteItem(torchBearer->carriedItem); + pmap[i][j].flags &= ~IS_IN_MACHINE; + pmap[i][j].machineNumber = 0; } - removeItemFromChain(torch, floorItems); - torchBearer->carriedItem = torch; + } + } + } + + if (torchBearer && torch) { + if (torchBearer->carriedItem) { + deleteItem(torchBearer->carriedItem); } + removeItemFromChain(torch, floorItems); + torchBearer->carriedItem = torch; + } - freeGrid(distanceMap); - if (D_MESSAGE_MACHINE_GENERATION) printf("\nDepth %i: Built a machine from blueprint %i with an origin at (%i, %i).", rogue.depthLevel, bp, originX, originY); + freeGrid(distanceMap); + if (D_MESSAGE_MACHINE_GENERATION) + printf("\nDepth %i: Built a machine from blueprint %i with an origin at (%i, %i).", rogue.depthLevel, bp, originX, + originY); - //Pass created items and monsters to parent where they will be deleted on failure to place parent machine - if (parentSpawnedItems) { - for (i=0; ispawnedItems[i]; - } + // Pass created items and monsters to parent where they will be deleted on failure to place parent machine + if (parentSpawnedItems) { + for (i = 0; i < itemCount; i++) { + parentSpawnedItems[i] = p->spawnedItems[i]; } - if (parentSpawnedMonsters) { - for (i=0; ispawnedMonsters[i]; - } + } + if (parentSpawnedMonsters) { + for (i = 0; i < monsterCount; i++) { + parentSpawnedMonsters[i] = p->spawnedMonsters[i]; } + } - free(p); - return true; + free(p); + return true; } // add machines to the dungeon. void addMachines() { - short machineCount, failsafe; - short randomMachineFactor; - - analyzeMap(true); - - // Add the amulet holder if it's depth 26: - if (rogue.depthLevel == gameConst->amuletLevel) { - for (failsafe = 50; failsafe; failsafe--) { - if (buildAMachine(MT_AMULET_AREA, -1, -1, 0, NULL, NULL, NULL)) { - break; - } - } - } - - // Add reward rooms, if any: - machineCount = 0; - while (rogue.depthLevel <= gameConst->amuletLevel - && (rogue.rewardRoomsGenerated + machineCount) * gameConst->machinesPerLevelSuppressionMultiplier + gameConst->machinesPerLevelSuppressionOffset < rogue.depthLevel * gameConst->machinesPerLevelIncreaseFactor) { - // try to build at least one every four levels on average - machineCount++; - } - randomMachineFactor = (rogue.depthLevel <= gameConst->maxLevelForBonusMachines && (rogue.rewardRoomsGenerated + machineCount) == 0 ? 40 : 15); - while (rand_percent(max(randomMachineFactor, 15 * gameConst->machinesPerLevelIncreaseFactor)) && machineCount < 100) { - randomMachineFactor = 15; - machineCount++; - } - - for (failsafe = 50; machineCount && failsafe; failsafe--) { - if (buildAMachine(-1, -1, -1, BP_REWARD, NULL, NULL, NULL)) { - machineCount--; - rogue.rewardRoomsGenerated++; - } - } + short machineCount, failsafe; + short randomMachineFactor; + + analyzeMap(true); + + // Add the amulet holder if it's depth 26: + if (rogue.depthLevel == gameConst->amuletLevel) { + for (failsafe = 50; failsafe; failsafe--) { + if (buildAMachine(MT_AMULET_AREA, -1, -1, 0, NULL, NULL, NULL)) { + break; + } + } + } + + // Add reward rooms, if any: + machineCount = 0; + while (rogue.depthLevel <= gameConst->amuletLevel + && (rogue.rewardRoomsGenerated + machineCount) * gameConst->machinesPerLevelSuppressionMultiplier + + gameConst->machinesPerLevelSuppressionOffset + < rogue.depthLevel * gameConst->machinesPerLevelIncreaseFactor) { + // try to build at least one every four levels on average + machineCount++; + } + randomMachineFactor + = (rogue.depthLevel <= gameConst->maxLevelForBonusMachines && (rogue.rewardRoomsGenerated + machineCount) == 0 + ? 40 + : 15); + while (rand_percent(max(randomMachineFactor, 15 * gameConst->machinesPerLevelIncreaseFactor)) && machineCount < 100) { + randomMachineFactor = 15; + machineCount++; + } + + for (failsafe = 50; machineCount && failsafe; failsafe--) { + if (buildAMachine(-1, -1, -1, BP_REWARD, NULL, NULL, NULL)) { + machineCount--; + rogue.rewardRoomsGenerated++; + } + } } // Add terrain, DFs and flavor machines. Includes traps, torches, funguses, flavor machines, etc. // If buildAreaMachines is true, build ONLY the autogenerators that include machines. // If false, build all EXCEPT the autogenerators that include machines. void runAutogenerators(boolean buildAreaMachines) { - short AG, count, x, y, i; - const autoGenerator *gen; - char grid[DCOLS][DROWS]; + short AG, count, x, y, i; + const autoGenerator *gen; + char grid[DCOLS][DROWS]; - // Cycle through the autoGenerators. - for (AG=1; AGnumberAutogenerators; AG++) { + // Cycle through the autoGenerators. + for (AG = 1; AG < gameConst->numberAutogenerators; AG++) { - // Shortcut: - gen = &(autoGeneratorCatalog[AG]); + // Shortcut: + gen = &(autoGeneratorCatalog[AG]); - if (gen->machine > 0 == buildAreaMachines) { + if (gen->machine > 0 == buildAreaMachines) { - // Enforce depth constraints. - if (rogue.depthLevel < gen->minDepth || rogue.depthLevel > gen->maxDepth) { - continue; - } + // Enforce depth constraints. + if (rogue.depthLevel < gen->minDepth || rogue.depthLevel > gen->maxDepth) { + continue; + } - // Decide how many of this AG to build. - count = min((gen->minNumberIntercept + rogue.depthLevel * gen->minNumberSlope) / 100, gen->maxNumber); - while (rand_percent(gen->frequency) && count < gen->maxNumber) { - count++; - } + // Decide how many of this AG to build. + count = min((gen->minNumberIntercept + rogue.depthLevel * gen->minNumberSlope) / 100, gen->maxNumber); + while (rand_percent(gen->frequency) && count < gen->maxNumber) { + count++; + } - // Build that many instances. - for (i = 0; i < count; i++) { + // Build that many instances. + for (i = 0; i < count; i++) { - // Find a location for DFs and terrain generations. - //if (randomMatchingLocation(&x, &y, gen->requiredDungeonFoundationType, NOTHING, -1)) { - //if (randomMatchingLocation(&x, &y, -1, -1, gen->requiredDungeonFoundationType)) { - if (randomMatchingLocation(&x, &y, gen->requiredDungeonFoundationType, gen->requiredLiquidFoundationType, -1)) { + // Find a location for DFs and terrain generations. + // if (randomMatchingLocation(&x, &y, gen->requiredDungeonFoundationType, NOTHING, -1)) { + // if (randomMatchingLocation(&x, &y, -1, -1, gen->requiredDungeonFoundationType)) { + if (randomMatchingLocation(&x, &y, gen->requiredDungeonFoundationType, gen->requiredLiquidFoundationType, -1)) { - // Spawn the DF. - if (gen->DFType) { - spawnDungeonFeature(x, y, &(dungeonFeatureCatalog[gen->DFType]), false, true); + // Spawn the DF. + if (gen->DFType) { + spawnDungeonFeature(x, y, &(dungeonFeatureCatalog[gen->DFType]), false, true); - if (D_INSPECT_LEVELGEN) { - dumpLevelToScreen(); - hiliteCell(x, y, &yellow, 50, true); - temporaryMessage("Dungeon feature added.", REQUIRE_ACKNOWLEDGMENT); - } - } + if (D_INSPECT_LEVELGEN) { + dumpLevelToScreen(); + hiliteCell(x, y, &yellow, 50, true); + temporaryMessage("Dungeon feature added.", REQUIRE_ACKNOWLEDGMENT); + } + } - // Spawn the terrain if it's got the priority to spawn there and won't disrupt connectivity. - if (gen->terrain - && tileCatalog[pmap[x][y].layers[gen->layer]].drawPriority >= tileCatalog[gen->terrain].drawPriority) { - - // Check connectivity. - zeroOutGrid(grid); - grid[x][y] = true; - if (!(tileCatalog[gen->terrain].flags & T_PATHING_BLOCKER) - || !levelIsDisconnectedWithBlockingMap(grid, false)) { - - // Build! - pmap[x][y].layers[gen->layer] = gen->terrain; - - if (D_INSPECT_LEVELGEN) { - dumpLevelToScreen(); - hiliteCell(x, y, &yellow, 50, true); - temporaryMessage("Terrain added.", REQUIRE_ACKNOWLEDGMENT); - } - } - } - } + // Spawn the terrain if it's got the priority to spawn there and won't disrupt connectivity. + if (gen->terrain + && tileCatalog[pmap[x][y].layers[gen->layer]].drawPriority >= tileCatalog[gen->terrain].drawPriority) { - // Attempt to build the machine if requested. - // Machines will find their own locations, so it will not be at the same place as terrain and DF. - if (gen->machine > 0) { - buildAMachine(gen->machine, -1, -1, 0, NULL, NULL, NULL); - } + // Check connectivity. + zeroOutGrid(grid); + grid[x][y] = true; + if (!(tileCatalog[gen->terrain].flags & T_PATHING_BLOCKER) + || !levelIsDisconnectedWithBlockingMap(grid, false)) { + + // Build! + pmap[x][y].layers[gen->layer] = gen->terrain; + + if (D_INSPECT_LEVELGEN) { + dumpLevelToScreen(); + hiliteCell(x, y, &yellow, 50, true); + temporaryMessage("Terrain added.", REQUIRE_ACKNOWLEDGMENT); + } } + } } + + // Attempt to build the machine if requested. + // Machines will find their own locations, so it will not be at the same place as terrain and DF. + if (gen->machine > 0) { + buildAMachine(gen->machine, -1, -1, 0, NULL, NULL, NULL); + } + } } + } } // Knock down the boundaries between similar lakes where possible. void cleanUpLakeBoundaries() { - short i, j, x, y, failsafe, layer; - boolean reverse, madeChange; - unsigned long subjectFlags; - - reverse = true; - - failsafe = 100; - do { - madeChange = false; - reverse = !reverse; - failsafe--; - - for (i = (reverse ? DCOLS - 2 : 1); - (reverse ? i > 0 : i < DCOLS - 1); - (reverse ? i-- : i++)) { - - for (j = (reverse ? DROWS - 2 : 1); - (reverse ? j > 0 : j < DROWS - 1); - (reverse ? j-- : j++)) { - - //assert(i >= 1 && i <= DCOLS - 2 && j >= 1 && j <= DROWS - 2); - - //if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY) - if (cellHasTerrainFlag(i, j, T_LAKE_PATHING_BLOCKER | T_OBSTRUCTS_PASSABILITY) - && !cellHasTMFlag(i, j, TM_IS_SECRET) - && !(pmap[i][j].flags & IMPREGNABLE)) { - - subjectFlags = terrainFlags(i, j) & (T_LAKE_PATHING_BLOCKER | T_OBSTRUCTS_PASSABILITY); - - x = y = 0; - if ((terrainFlags(i - 1, j) & T_LAKE_PATHING_BLOCKER & ~subjectFlags) - && !cellHasTMFlag(i - 1, j, TM_IS_SECRET) - && !cellHasTMFlag(i + 1, j, TM_IS_SECRET) - && (terrainFlags(i - 1, j) & T_LAKE_PATHING_BLOCKER & ~subjectFlags) == (terrainFlags(i + 1, j) & T_LAKE_PATHING_BLOCKER & ~subjectFlags)) { - x = i + 1; - y = j; - } else if ((terrainFlags(i, j - 1) & T_LAKE_PATHING_BLOCKER & ~subjectFlags) - && !cellHasTMFlag(i, j - 1, TM_IS_SECRET) - && !cellHasTMFlag(i, j + 1, TM_IS_SECRET) - && (terrainFlags(i, j - 1) & T_LAKE_PATHING_BLOCKER & ~subjectFlags) == (terrainFlags(i, j + 1) & T_LAKE_PATHING_BLOCKER & ~subjectFlags)) { - x = i; - y = j + 1; - } - if (x) { - madeChange = true; - for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) { - pmap[i][j].layers[layer] = pmap[x][y].layers[layer]; - } - //pmap[i][j].layers[DUNGEON] = CRYSTAL_WALL; - } - } + short i, j, x, y, failsafe, layer; + boolean reverse, madeChange; + unsigned long subjectFlags; + + reverse = true; + + failsafe = 100; + do { + madeChange = false; + reverse = !reverse; + failsafe--; + + for (i = (reverse ? DCOLS - 2 : 1); (reverse ? i > 0 : i < DCOLS - 1); (reverse ? i-- : i++)) { + + for (j = (reverse ? DROWS - 2 : 1); (reverse ? j > 0 : j < DROWS - 1); (reverse ? j-- : j++)) { + + // assert(i >= 1 && i <= DCOLS - 2 && j >= 1 && j <= DROWS - 2); + + // if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY) + if (cellHasTerrainFlag(i, j, T_LAKE_PATHING_BLOCKER | T_OBSTRUCTS_PASSABILITY) + && !cellHasTMFlag(i, j, TM_IS_SECRET) && !(pmap[i][j].flags & IMPREGNABLE)) { + + subjectFlags = terrainFlags(i, j) & (T_LAKE_PATHING_BLOCKER | T_OBSTRUCTS_PASSABILITY); + + x = y = 0; + if ((terrainFlags(i - 1, j) & T_LAKE_PATHING_BLOCKER & ~subjectFlags) + && !cellHasTMFlag(i - 1, j, TM_IS_SECRET) && !cellHasTMFlag(i + 1, j, TM_IS_SECRET) + && (terrainFlags(i - 1, j) & T_LAKE_PATHING_BLOCKER & ~subjectFlags) + == (terrainFlags(i + 1, j) & T_LAKE_PATHING_BLOCKER & ~subjectFlags)) { + x = i + 1; + y = j; + } else if ((terrainFlags(i, j - 1) & T_LAKE_PATHING_BLOCKER & ~subjectFlags) + && !cellHasTMFlag(i, j - 1, TM_IS_SECRET) && !cellHasTMFlag(i, j + 1, TM_IS_SECRET) + && (terrainFlags(i, j - 1) & T_LAKE_PATHING_BLOCKER & ~subjectFlags) + == (terrainFlags(i, j + 1) & T_LAKE_PATHING_BLOCKER & ~subjectFlags)) { + x = i; + y = j + 1; + } + if (x) { + madeChange = true; + for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) { + pmap[i][j].layers[layer] = pmap[x][y].layers[layer]; } + // pmap[i][j].layers[DUNGEON] = CRYSTAL_WALL; + } } - } while (madeChange && failsafe > 0); + } + } + } while (madeChange && failsafe > 0); } void removeDiagonalOpenings() { - short i, j, k, x1, y1, x2, layer; - boolean diagonalCornerRemoved; - - do { - diagonalCornerRemoved = false; - for (i=0; i 6 - && rand_percent(50)) { - drawCircleOnGrid(grid, DCOLS/2, DROWS/2, rand_range(3, radius - 3), 0); - } + if (radius > 6 && rand_percent(50)) { + drawCircleOnGrid(grid, DCOLS / 2, DROWS / 2, rand_range(3, radius - 3), 0); + } } void designChunkyRoom(short **grid) { - short i, x, y; - short minX, maxX, minY, maxY; - short chunkCount = rand_range(2, 8); + short i, x, y; + short minX, maxX, minY, maxY; + short chunkCount = rand_range(2, 8); - fillGrid(grid, 0); - drawCircleOnGrid(grid, DCOLS/2, DROWS/2, 2, 1); - minX = DCOLS/2 - 3; - maxX = DCOLS/2 + 3; - minY = DROWS/2 - 3; - maxY = DROWS/2 + 3; + fillGrid(grid, 0); + drawCircleOnGrid(grid, DCOLS / 2, DROWS / 2, 2, 1); + minX = DCOLS / 2 - 3; + maxX = DCOLS / 2 + 3; + minY = DROWS / 2 - 3; + maxY = DROWS / 2 + 3; - for (i=0; i 0) { - return false; - } - } - } + short xRoom, yRoom, xDungeon, yDungeon, i, j; + + for (xRoom = 0; xRoom < DCOLS; xRoom++) { + for (yRoom = 0; yRoom < DROWS; yRoom++) { + if (roomMap[xRoom][yRoom]) { + xDungeon = xRoom + roomToDungeonX; + yDungeon = yRoom + roomToDungeonY; + + for (i = xDungeon - 1; i <= xDungeon + 1; i++) { + for (j = yDungeon - 1; j <= yDungeon + 1; j++) { + if (!coordinatesAreInMap(i, j) || dungeonMap[i][j] > 0) { + return false; } + } } + } } - return true; + } + return true; } void attachRooms(short **grid, const dungeonProfile *theDP, short attempts, short maxRoomCount) { - short roomsBuilt, roomsAttempted; - short **roomMap; - pos doorSites[4]; - short i, x, y, sCoord[DCOLS*DROWS]; - enum directions dir, oppDir; - - fillSequentialList(sCoord, DCOLS*DROWS); - shuffleList(sCoord, DCOLS*DROWS); - - roomMap = allocGrid(); - for (roomsBuilt = roomsAttempted = 0; roomsBuilt < maxRoomCount && roomsAttempted < attempts; roomsAttempted++) { - // Build a room in hyperspace. - fillGrid(roomMap, 0); - designRandomRoom(roomMap, roomsAttempted <= attempts - 5 && rand_percent(theDP->corridorChance), - doorSites, theDP->roomFrequencies); + short roomsBuilt, roomsAttempted; + short **roomMap; + pos doorSites[4]; + short i, x, y, sCoord[DCOLS * DROWS]; + enum directions dir, oppDir; + + fillSequentialList(sCoord, DCOLS * DROWS); + shuffleList(sCoord, DCOLS * DROWS); + + roomMap = allocGrid(); + for (roomsBuilt = roomsAttempted = 0; roomsBuilt < maxRoomCount && roomsAttempted < attempts; roomsAttempted++) { + // Build a room in hyperspace. + fillGrid(roomMap, 0); + designRandomRoom(roomMap, roomsAttempted <= attempts - 5 && rand_percent(theDP->corridorChance), doorSites, + theDP->roomFrequencies); + if (D_INSPECT_LEVELGEN) { + colorOverDungeon(&darkGray); + hiliteGrid(roomMap, &blue, 100); + if (doorSites[0].x != -1) + plotCharWithColor('^', mapToWindow(doorSites[0]), &black, &green); + if (doorSites[1].x != -1) + plotCharWithColor('v', mapToWindow(doorSites[1]), &black, &green); + if (doorSites[2].x != -1) + plotCharWithColor('<', mapToWindow(doorSites[2]), &black, &green); + if (doorSites[3].x != -1) + plotCharWithColor('>', mapToWindow(doorSites[3]), &black, &green); + temporaryMessage("Generating this room:", REQUIRE_ACKNOWLEDGMENT); + } + + // Slide hyperspace across real space, in a random but predetermined order, until the room matches up with a wall. + for (i = 0; i < DCOLS * DROWS; i++) { + x = sCoord[i] / DROWS; + y = sCoord[i] % DROWS; + + dir = directionOfDoorSite(grid, x, y); + oppDir = oppositeDirection(dir); + if (dir != NO_DIRECTION && doorSites[oppDir].x != -1 + && roomFitsAt(grid, roomMap, x - doorSites[oppDir].x, y - doorSites[oppDir].y)) { + + // Room fits here. if (D_INSPECT_LEVELGEN) { - colorOverDungeon(&darkGray); - hiliteGrid(roomMap, &blue, 100); - if (doorSites[0].x != -1) plotCharWithColor('^', mapToWindow(doorSites[0]), &black, &green); - if (doorSites[1].x != -1) plotCharWithColor('v', mapToWindow(doorSites[1]), &black, &green); - if (doorSites[2].x != -1) plotCharWithColor('<', mapToWindow(doorSites[2]), &black, &green); - if (doorSites[3].x != -1) plotCharWithColor('>', mapToWindow(doorSites[3]), &black, &green); - temporaryMessage("Generating this room:", REQUIRE_ACKNOWLEDGMENT); - } - - // Slide hyperspace across real space, in a random but predetermined order, until the room matches up with a wall. - for (i = 0; i < DCOLS*DROWS; i++) { - x = sCoord[i] / DROWS; - y = sCoord[i] % DROWS; - - dir = directionOfDoorSite(grid, x, y); - oppDir = oppositeDirection(dir); - if (dir != NO_DIRECTION - && doorSites[oppDir].x != -1 - && roomFitsAt(grid, roomMap, x - doorSites[oppDir].x, y - doorSites[oppDir].y)) { - - // Room fits here. - if (D_INSPECT_LEVELGEN) { - colorOverDungeon(&darkGray); - hiliteGrid(grid, &white, 100); - } - insertRoomAt(grid, roomMap, x - doorSites[oppDir].x, y - doorSites[oppDir].y, doorSites[oppDir].x, doorSites[oppDir].y); - grid[x][y] = 2; // Door site. - if (D_INSPECT_LEVELGEN) { - hiliteGrid(grid, &green, 50); - temporaryMessage("Added room.", REQUIRE_ACKNOWLEDGMENT); - } - roomsBuilt++; - break; - } + colorOverDungeon(&darkGray); + hiliteGrid(grid, &white, 100); + } + insertRoomAt(grid, roomMap, x - doorSites[oppDir].x, y - doorSites[oppDir].y, doorSites[oppDir].x, + doorSites[oppDir].y); + grid[x][y] = 2; // Door site. + if (D_INSPECT_LEVELGEN) { + hiliteGrid(grid, &green, 50); + temporaryMessage("Added room.", REQUIRE_ACKNOWLEDGMENT); } + roomsBuilt++; + break; + } } + } - freeGrid(roomMap); + freeGrid(roomMap); } void adjustDungeonProfileForDepth(dungeonProfile *theProfile) { - const short descentPercent = clamp(100 * (rogue.depthLevel - 1) / (gameConst->amuletLevel - 1), 0, 100); + const short descentPercent = clamp(100 * (rogue.depthLevel - 1) / (gameConst->amuletLevel - 1), 0, 100); - theProfile->roomFrequencies[0] += 20 * (100 - descentPercent) / 100; - theProfile->roomFrequencies[1] += 10 * (100 - descentPercent) / 100; - theProfile->roomFrequencies[3] += 7 * (100 - descentPercent) / 100; - theProfile->roomFrequencies[5] += 10 * descentPercent / 100; + theProfile->roomFrequencies[0] += 20 * (100 - descentPercent) / 100; + theProfile->roomFrequencies[1] += 10 * (100 - descentPercent) / 100; + theProfile->roomFrequencies[3] += 7 * (100 - descentPercent) / 100; + theProfile->roomFrequencies[5] += 10 * descentPercent / 100; - theProfile->corridorChance += 80 * (100 - descentPercent) / 100; + theProfile->corridorChance += 80 * (100 - descentPercent) / 100; } void adjustDungeonFirstRoomProfileForDepth(dungeonProfile *theProfile) { - short i; - const short descentPercent = clamp(100 * (rogue.depthLevel - 1) / (gameConst->amuletLevel - 1), 0, 100); + short i; + const short descentPercent = clamp(100 * (rogue.depthLevel - 1) / (gameConst->amuletLevel - 1), 0, 100); - if (rogue.depthLevel == 1) { - // All dungeons start with the entrance room on depth 1. - for (i = 0; i < ROOM_TYPE_COUNT; i++) { - theProfile->roomFrequencies[i] = 0; - } - theProfile->roomFrequencies[7] = 1; - } else { - theProfile->roomFrequencies[6] += 50 * descentPercent / 100; + if (rogue.depthLevel == 1) { + // All dungeons start with the entrance room on depth 1. + for (i = 0; i < ROOM_TYPE_COUNT; i++) { + theProfile->roomFrequencies[i] = 0; } + theProfile->roomFrequencies[7] = 1; + } else { + theProfile->roomFrequencies[6] += 50 * descentPercent / 100; + } } // Called by digDungeon(). @@ -2418,678 +2401,684 @@ void adjustDungeonFirstRoomProfileForDepth(dungeonProfile *theProfile) { // -1 denotes off-limits areas -- rooms can't be placed there and also can't sprout off of there. // Parent function will translate this grid into pmap[][] to make floors, walls, doors, etc. void carveDungeon(short **grid) { - dungeonProfile theDP, theFirstRoomDP; + dungeonProfile theDP, theFirstRoomDP; - theDP = dungeonProfileCatalog[DP_BASIC]; - adjustDungeonProfileForDepth(&theDP); + theDP = dungeonProfileCatalog[DP_BASIC]; + adjustDungeonProfileForDepth(&theDP); - theFirstRoomDP = dungeonProfileCatalog[DP_BASIC_FIRST_ROOM]; - adjustDungeonFirstRoomProfileForDepth(&theFirstRoomDP); + theFirstRoomDP = dungeonProfileCatalog[DP_BASIC_FIRST_ROOM]; + adjustDungeonFirstRoomProfileForDepth(&theFirstRoomDP); - designRandomRoom(grid, false, NULL, theFirstRoomDP.roomFrequencies); + designRandomRoom(grid, false, NULL, theFirstRoomDP.roomFrequencies); - if (D_INSPECT_LEVELGEN) { - colorOverDungeon(&darkGray); - hiliteGrid(grid, &white, 100); - temporaryMessage("First room placed:", REQUIRE_ACKNOWLEDGMENT); - } + if (D_INSPECT_LEVELGEN) { + colorOverDungeon(&darkGray); + hiliteGrid(grid, &white, 100); + temporaryMessage("First room placed:", REQUIRE_ACKNOWLEDGMENT); + } - attachRooms(grid, &theDP, 35, 35); + attachRooms(grid, &theDP, 35, 35); -// colorOverDungeon(&darkGray); -// hiliteGrid(grid, &white, 100); -// temporaryMessage("How does this finished level look?", REQUIRE_ACKNOWLEDGMENT); + // colorOverDungeon(&darkGray); + // hiliteGrid(grid, &white, 100); + // temporaryMessage("How does this finished level look?", REQUIRE_ACKNOWLEDGMENT); } void finishWalls(boolean includingDiagonals) { - short i, j, x1, y1; - boolean foundExposure; - enum directions dir; - - for (i=0; iminimumLavaLevel ? 1 : 0); - randMax = (rogue.depthLevel < gameConst->minimumBrimstoneLevel ? 2 : 3); - rand = rand_range(randMin, randMax); - if (rogue.depthLevel == gameConst->deepestLevel) { - rand = 1; - } + pmap[i][j].layers[DUNGEON] = WALL; + foundExposure = true; + } + } + } else if (pmap[i][j].layers[DUNGEON] == WALL) { + foundExposure = false; + for (dir = 0; dir < (includingDiagonals ? 8 : 4) && !foundExposure; dir++) { + x1 = i + nbDirs[dir][0]; + y1 = j + nbDirs[dir][1]; + if (coordinatesAreInMap(x1, y1) + && (!cellHasTerrainFlag(x1, y1, T_OBSTRUCTS_VISION) + || !cellHasTerrainFlag(x1, y1, T_OBSTRUCTS_PASSABILITY))) { - switch(rand) { - case 0: - *deep = LAVA; - *shallow = NOTHING; - *shallowWidth = 0; - break; - case 1: - *deep = DEEP_WATER; - *shallow = SHALLOW_WATER; - *shallowWidth = 2; - break; - case 2: - *deep = CHASM; - *shallow = CHASM_EDGE; - *shallowWidth = 1; - break; - case 3: - *deep = INERT_BRIMSTONE; - *shallow = OBSIDIAN; - *shallowWidth = 2; - break; + foundExposure = true; + } + } + if (foundExposure == false) { + pmap[i][j].layers[DUNGEON] = GRANITE; + } + } } + } } -// Fills a lake marked in unfilledLakeMap with the specified liquid type, scanning outward to reach other lakes within scanWidth. -// Any wreath of shallow liquid must be done elsewhere. +void liquidType(short *deep, short *shallow, short *shallowWidth) { + short randMin, randMax, rand; + + randMin = (rogue.depthLevel < gameConst->minimumLavaLevel ? 1 : 0); + randMax = (rogue.depthLevel < gameConst->minimumBrimstoneLevel ? 2 : 3); + rand = rand_range(randMin, randMax); + if (rogue.depthLevel == gameConst->deepestLevel) { + rand = 1; + } + + switch (rand) { + case 0: + *deep = LAVA; + *shallow = NOTHING; + *shallowWidth = 0; + break; + case 1: + *deep = DEEP_WATER; + *shallow = SHALLOW_WATER; + *shallowWidth = 2; + break; + case 2: + *deep = CHASM; + *shallow = CHASM_EDGE; + *shallowWidth = 1; + break; + case 3: + *deep = INERT_BRIMSTONE; + *shallow = OBSIDIAN; + *shallowWidth = 2; + break; + } +} + +// Fills a lake marked in unfilledLakeMap with the specified liquid type, scanning outward to reach other lakes within +// scanWidth. Any wreath of shallow liquid must be done elsewhere. void fillLake(short x, short y, short liquid, short scanWidth, char wreathMap[DCOLS][DROWS], short **unfilledLakeMap) { - short i, j; - - for (i = x - scanWidth; i <= x + scanWidth; i++) { - for (j = y - scanWidth; j <= y + scanWidth; j++) { - if (coordinatesAreInMap(i, j) && unfilledLakeMap[i][j]) { - unfilledLakeMap[i][j] = false; - pmap[i][j].layers[LIQUID] = liquid; - wreathMap[i][j] = 1; - fillLake(i, j, liquid, scanWidth, wreathMap, unfilledLakeMap); // recursive - } - } + short i, j; + + for (i = x - scanWidth; i <= x + scanWidth; i++) { + for (j = y - scanWidth; j <= y + scanWidth; j++) { + if (coordinatesAreInMap(i, j) && unfilledLakeMap[i][j]) { + unfilledLakeMap[i][j] = false; + pmap[i][j].layers[LIQUID] = liquid; + wreathMap[i][j] = 1; + fillLake(i, j, liquid, scanWidth, wreathMap, unfilledLakeMap); // recursive + } } + } } -void lakeFloodFill(short x, short y, short **floodMap, short **grid, short **lakeMap, short dungeonToGridX, short dungeonToGridY) { - short newX, newY; - enum directions dir; +void lakeFloodFill(short x, short y, short **floodMap, short **grid, short **lakeMap, short dungeonToGridX, + short dungeonToGridY) { + short newX, newY; + enum directions dir; - floodMap[x][y] = true; - for (dir=0; dir<4; dir++) { - newX = x + nbDirs[dir][0]; - newY = y + nbDirs[dir][1]; - if (coordinatesAreInMap(newX, newY) - && !floodMap[newX][newY] - && (!cellHasTerrainFlag(newX, newY, T_PATHING_BLOCKER) || cellHasTMFlag(newX, newY, TM_CONNECTS_LEVEL)) - && !lakeMap[newX][newY] - && (!coordinatesAreInMap(newX+dungeonToGridX, newY+dungeonToGridY) || !grid[newX+dungeonToGridX][newY+dungeonToGridY])) { + floodMap[x][y] = true; + for (dir = 0; dir < 4; dir++) { + newX = x + nbDirs[dir][0]; + newY = y + nbDirs[dir][1]; + if (coordinatesAreInMap(newX, newY) && !floodMap[newX][newY] + && (!cellHasTerrainFlag(newX, newY, T_PATHING_BLOCKER) || cellHasTMFlag(newX, newY, TM_CONNECTS_LEVEL)) + && !lakeMap[newX][newY] + && (!coordinatesAreInMap(newX + dungeonToGridX, newY + dungeonToGridY) + || !grid[newX + dungeonToGridX][newY + dungeonToGridY])) { - lakeFloodFill(newX, newY, floodMap, grid, lakeMap, dungeonToGridX, dungeonToGridY); - } + lakeFloodFill(newX, newY, floodMap, grid, lakeMap, dungeonToGridX, dungeonToGridY); } + } } boolean lakeDisruptsPassability(short **grid, short **lakeMap, short dungeonToGridX, short dungeonToGridY) { - boolean result; - short i, j, x, y; - short **floodMap; - - floodMap = allocGrid(); - fillGrid(floodMap, 0); - x = y = -1; - // Get starting location for the fill. - for (i=0; i=10; lakeMaxHeight--, lakeMaxWidth -= 2) { // lake generations - - fillGrid(grid, 0); - createBlobOnGrid(grid, &lakeX, &lakeY, &lakeWidth, &lakeHeight, 5, 4, 4, lakeMaxWidth, lakeMaxHeight, 55, "ffffftttt", "ffffttttt"); - -// if (D_INSPECT_LEVELGEN) { -// colorOverDungeon(&darkGray); -// hiliteGrid(grid, &white, 100); -// temporaryMessage("Generated a lake.", REQUIRE_ACKNOWLEDGMENT); -// } - - for (k=0; k<20; k++) { // placement attempts - // propose a position for the top-left of the grid in the dungeon - x = rand_range(1 - lakeX, DCOLS - lakeWidth - lakeX - 2); - y = rand_range(1 - lakeY, DROWS - lakeHeight - lakeY - 2); - - if (!lakeDisruptsPassability(grid, lakeMap, -x, -y)) { // level with lake is completely connected - //printf("Placed a lake!"); - - // copy in lake - for (i = 0; i < lakeWidth; i++) { - for (j = 0; j < lakeHeight; j++) { - if (grid[i + lakeX][j + lakeY]) { - lakeMap[i + lakeX + x][j + lakeY + y] = true; - pmap[i + lakeX + x][j + lakeY + y].layers[DUNGEON] = FLOOR; - } - } - } + short i, j, k; + short x, y; + short lakeMaxHeight, lakeMaxWidth; + short lakeX, lakeY, lakeWidth, lakeHeight; - if (D_INSPECT_LEVELGEN) { - dumpLevelToScreen(); - hiliteGrid(lakeMap, &white, 50); - temporaryMessage("Added a lake location.", REQUIRE_ACKNOWLEDGMENT); - } - break; + short **grid; // Holds the current lake. + + grid = allocGrid(); + fillGrid(lakeMap, 0); + for (lakeMaxHeight = 15, lakeMaxWidth = 30; lakeMaxHeight >= 10; + lakeMaxHeight--, lakeMaxWidth -= 2) { // lake generations + + fillGrid(grid, 0); + createBlobOnGrid(grid, &lakeX, &lakeY, &lakeWidth, &lakeHeight, 5, 4, 4, lakeMaxWidth, lakeMaxHeight, 55, + "ffffftttt", "ffffttttt"); + + // if (D_INSPECT_LEVELGEN) { + // colorOverDungeon(&darkGray); + // hiliteGrid(grid, &white, 100); + // temporaryMessage("Generated a lake.", REQUIRE_ACKNOWLEDGMENT); + // } + + for (k = 0; k < 20; k++) { // placement attempts + // propose a position for the top-left of the grid in the dungeon + x = rand_range(1 - lakeX, DCOLS - lakeWidth - lakeX - 2); + y = rand_range(1 - lakeY, DROWS - lakeHeight - lakeY - 2); + + if (!lakeDisruptsPassability(grid, lakeMap, -x, -y)) { // level with lake is completely connected + // printf("Placed a lake!"); + + // copy in lake + for (i = 0; i < lakeWidth; i++) { + for (j = 0; j < lakeHeight; j++) { + if (grid[i + lakeX][j + lakeY]) { + lakeMap[i + lakeX + x][j + lakeY + y] = true; + pmap[i + lakeX + x][j + lakeY + y].layers[DUNGEON] = FLOOR; } + } } + + if (D_INSPECT_LEVELGEN) { + dumpLevelToScreen(); + hiliteGrid(lakeMap, &white, 50); + temporaryMessage("Added a lake location.", REQUIRE_ACKNOWLEDGMENT); + } + break; + } } - freeGrid(grid); + } + freeGrid(grid); } void createWreath(short shallowLiquid, short wreathWidth, char wreathMap[DCOLS][DROWS]) { - short i, j, k, l; - for (i=0; iamuletLevel - 1), 0, 67); - for (i=1; i= 3) { - // If the door has three or more pathing blocker neighbors in the four cardinal directions, - // then the door is orphaned and must be removed. - pmap[i][j].layers[DUNGEON] = FLOOR; - } else if (rand_percent(secretDoorChance)) { - pmap[i][j].layers[DUNGEON] = SECRET_DOOR; - } - } - } - } + short i, j; + const short secretDoorChance = clamp((rogue.depthLevel - 1) * 67 / (gameConst->amuletLevel - 1), 0, 67); + for (i = 1; i < DCOLS - 1; i++) { + for (j = 1; j < DROWS - 1; j++) { + if (pmap[i][j].layers[DUNGEON] == DOOR && pmap[i][j].machineNumber == 0) { + if ((!cellHasTerrainFlag(i + 1, j, T_OBSTRUCTS_PASSABILITY) + || !cellHasTerrainFlag(i - 1, j, T_OBSTRUCTS_PASSABILITY)) + && (!cellHasTerrainFlag(i, j + 1, T_OBSTRUCTS_PASSABILITY) + || !cellHasTerrainFlag(i, j - 1, T_OBSTRUCTS_PASSABILITY))) { + // If there's passable terrain to the left or right, and there's passable terrain + // above or below, then the door is orphaned and must be removed. + pmap[i][j].layers[DUNGEON] = FLOOR; + } else if ((cellHasTerrainFlag(i + 1, j, T_PATHING_BLOCKER) ? 1 : 0) + + (cellHasTerrainFlag(i - 1, j, T_PATHING_BLOCKER) ? 1 : 0) + + (cellHasTerrainFlag(i, j + 1, T_PATHING_BLOCKER) ? 1 : 0) + + (cellHasTerrainFlag(i, j - 1, T_PATHING_BLOCKER) ? 1 : 0) + >= 3) { + // If the door has three or more pathing blocker neighbors in the four cardinal directions, + // then the door is orphaned and must be removed. + pmap[i][j].layers[DUNGEON] = FLOOR; + } else if (rand_percent(secretDoorChance)) { + pmap[i][j].layers[DUNGEON] = SECRET_DOOR; + } + } + } + } } void clearLevel() { - short i, j; - - for( i=0; idepthAccelerator / 9) * rand_range(10, 20) / 10); - bridgeRatioY = (short) (100 + (400 + 100 * rogue.depthLevel * gameConst->depthAccelerator / 18) * rand_range(10, 20) / 10); - - fillSequentialList(nCols, DCOLS); - shuffleList(nCols, DCOLS); - fillSequentialList(nRows, DROWS); - shuffleList(nRows, DROWS); - - for (i2=1; i2 3) // Can't have bridges shorter than 3 spaces. - && foundExposure - && !cellHasTerrainFlag(k, j, T_PATHING_BLOCKER | T_CAN_BE_BRIDGED) // Must end on an unobstructed land tile. - && !pmap[k][j].machineNumber // Cannot end in a machine. - && 100 * pathingDistance(i, j, k, j, T_PATHING_BLOCKER) / (k - i) > bridgeRatioX) { // Must shorten the pathing distance enough. - - for (l=i+1; l < k; l++) { - pmap[l][j].layers[LIQUID] = BRIDGE; - } - pmap[i][j].layers[SURFACE] = BRIDGE_EDGE; - pmap[k][j].layers[SURFACE] = BRIDGE_EDGE; - return true; - } - - // try a vertical bridge - foundExposure = false; - for (k = j + 1; - k < DROWS - && !pmap[i][k].machineNumber - && cellHasTerrainFlag(i, k, T_CAN_BE_BRIDGED) - && !cellHasTMFlag(i, k, TM_IS_SECRET) - && !cellHasTerrainFlag(i, k, T_OBSTRUCTS_PASSABILITY) - && cellHasTerrainFlag(i-1, k, (T_CAN_BE_BRIDGED | T_OBSTRUCTS_PASSABILITY)) - && cellHasTerrainFlag(i+1, k, (T_CAN_BE_BRIDGED | T_OBSTRUCTS_PASSABILITY)); - k++) { - - if (!cellHasTerrainFlag(i-1, k, T_OBSTRUCTS_PASSABILITY) - && !cellHasTerrainFlag(i+1, k, T_OBSTRUCTS_PASSABILITY)) { - foundExposure = true; - } - } - if (k < DROWS - && (k - j > 3) - && foundExposure - && !cellHasTerrainFlag(i, k, T_PATHING_BLOCKER | T_CAN_BE_BRIDGED) - && !pmap[i][k].machineNumber // Cannot end in a machine. - && 100 * pathingDistance(i, j, i, k, T_PATHING_BLOCKER) / (k - j) > bridgeRatioY) { - - for (l=j+1; l < k; l++) { - pmap[i][l].layers[LIQUID] = BRIDGE; - } - pmap[i][j].layers[SURFACE] = BRIDGE_EDGE; - pmap[i][k].layers[SURFACE] = BRIDGE_EDGE; - return true; - } - } - } - } - return false; + short i, j, k, l, i2, j2, nCols[DCOLS], nRows[DROWS]; + short bridgeRatioX, bridgeRatioY; + boolean foundExposure; + + bridgeRatioX + = (short)(100 + (100 + 100 * rogue.depthLevel * gameConst->depthAccelerator / 9) * rand_range(10, 20) / 10); + bridgeRatioY + = (short)(100 + (400 + 100 * rogue.depthLevel * gameConst->depthAccelerator / 18) * rand_range(10, 20) / 10); + + fillSequentialList(nCols, DCOLS); + shuffleList(nCols, DCOLS); + fillSequentialList(nRows, DROWS); + shuffleList(nRows, DROWS); + + for (i2 = 1; i2 < DCOLS - 1; i2++) { + i = nCols[i2]; + for (j2 = 1; j2 < DROWS - 1; j2++) { + j = nRows[j2]; + if (!cellHasTerrainFlag(i, j, (T_CAN_BE_BRIDGED | T_PATHING_BLOCKER)) && !pmap[i][j].machineNumber) { + + // try a horizontal bridge + foundExposure = false; + for (k = i + 1; + k < DCOLS // Iterate across the prospective length of the bridge. + && !pmap[k][j].machineNumber // No bridges in machines. + && cellHasTerrainFlag(k, j, T_CAN_BE_BRIDGED) // Candidate tile must be chasm. + && !cellHasTMFlag(k, j, TM_IS_SECRET) // Can't bridge over secret trapdoors. + && !cellHasTerrainFlag(k, j, T_OBSTRUCTS_PASSABILITY) // Candidate tile cannot be a wall. + && cellHasTerrainFlag( + k, j - 1, + (T_CAN_BE_BRIDGED + | T_OBSTRUCTS_PASSABILITY)) // Only chasms or walls are permitted next to the length of the bridge. + && cellHasTerrainFlag(k, j + 1, (T_CAN_BE_BRIDGED | T_OBSTRUCTS_PASSABILITY)); + k++) { + + if (!cellHasTerrainFlag(k, j - 1, T_OBSTRUCTS_PASSABILITY) // Can't run against a wall the whole way. + && !cellHasTerrainFlag(k, j + 1, T_OBSTRUCTS_PASSABILITY)) { + foundExposure = true; + } + } + if (k < DCOLS && (k - i > 3) // Can't have bridges shorter than 3 spaces. + && foundExposure + && !cellHasTerrainFlag(k, j, T_PATHING_BLOCKER | T_CAN_BE_BRIDGED) // Must end on an unobstructed land tile. + && !pmap[k][j].machineNumber // Cannot end in a machine. + && 100 * pathingDistance(i, j, k, j, T_PATHING_BLOCKER) / (k - i) + > bridgeRatioX) { // Must shorten the pathing distance enough. + + for (l = i + 1; l < k; l++) { + pmap[l][j].layers[LIQUID] = BRIDGE; + } + pmap[i][j].layers[SURFACE] = BRIDGE_EDGE; + pmap[k][j].layers[SURFACE] = BRIDGE_EDGE; + return true; + } + + // try a vertical bridge + foundExposure = false; + for (k = j + 1; k < DROWS && !pmap[i][k].machineNumber && cellHasTerrainFlag(i, k, T_CAN_BE_BRIDGED) + && !cellHasTMFlag(i, k, TM_IS_SECRET) && !cellHasTerrainFlag(i, k, T_OBSTRUCTS_PASSABILITY) + && cellHasTerrainFlag(i - 1, k, (T_CAN_BE_BRIDGED | T_OBSTRUCTS_PASSABILITY)) + && cellHasTerrainFlag(i + 1, k, (T_CAN_BE_BRIDGED | T_OBSTRUCTS_PASSABILITY)); + k++) { + + if (!cellHasTerrainFlag(i - 1, k, T_OBSTRUCTS_PASSABILITY) + && !cellHasTerrainFlag(i + 1, k, T_OBSTRUCTS_PASSABILITY)) { + foundExposure = true; + } + } + if (k < DROWS && (k - j > 3) && foundExposure && !cellHasTerrainFlag(i, k, T_PATHING_BLOCKER | T_CAN_BE_BRIDGED) + && !pmap[i][k].machineNumber // Cannot end in a machine. + && 100 * pathingDistance(i, j, i, k, T_PATHING_BLOCKER) / (k - j) > bridgeRatioY) { + + for (l = j + 1; l < k; l++) { + pmap[i][l].layers[LIQUID] = BRIDGE; + } + pmap[i][j].layers[SURFACE] = BRIDGE_EDGE; + pmap[i][k].layers[SURFACE] = BRIDGE_EDGE; + return true; + } + } + } + } + return false; } // This is the master function for digging out a dungeon level. // Finishing touches -- items, monsters, staircases, etc. -- are handled elsewhere. void digDungeon() { - short i, j; + short i, j; - short **grid; + short **grid; - rogue.machineNumber = 0; + rogue.machineNumber = 0; - topBlobMinX = topBlobMinY = blobWidth = blobHeight = 0; + topBlobMinX = topBlobMinY = blobWidth = blobHeight = 0; #ifdef AUDIT_RNG - char RNGMessage[100]; - sprintf(RNGMessage, "\n\n\nDigging dungeon level %i:\n", rogue.depthLevel); - RNGLog(RNGMessage); + char RNGMessage[100]; + sprintf(RNGMessage, "\n\n\nDigging dungeon level %i:\n", rogue.depthLevel); + RNGLog(RNGMessage); #endif - // Clear level and fill with granite - clearLevel(); - - grid = allocGrid(); - carveDungeon(grid); - addLoops(grid, 20); - for (i=0; ideepestLevel ? DOOR : FLOOR); - } - } + // Clear level and fill with granite + clearLevel(); + + grid = allocGrid(); + carveDungeon(grid); + addLoops(grid, 20); + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + if (grid[i][j] == 1) { + pmap[i][j].layers[DUNGEON] = FLOOR; + } else if (grid[i][j] == 2) { + pmap[i][j].layers[DUNGEON] = (rand_percent(60) && rogue.depthLevel < gameConst->deepestLevel ? DOOR : FLOOR); + } } - freeGrid(grid); + } + freeGrid(grid); - finishWalls(false); + finishWalls(false); - if (D_INSPECT_LEVELGEN) { - dumpLevelToScreen(); - temporaryMessage("Carved into the granite:", REQUIRE_ACKNOWLEDGMENT); - } - //DEBUG printf("\n%i loops created.", numLoops); - //DEBUG logLevel(); + if (D_INSPECT_LEVELGEN) { + dumpLevelToScreen(); + temporaryMessage("Carved into the granite:", REQUIRE_ACKNOWLEDGMENT); + } + // DEBUG printf("\n%i loops created.", numLoops); + // DEBUG logLevel(); - // Time to add lakes and chasms. Strategy is to generate a series of blob lakes of decreasing size. For each lake, - // propose a position, and then check via a flood fill that the level would remain connected with that placement (i.e. that - // each passable tile can still be reached). If not, make 9 more placement attempts before abandoning that lake - // and proceeding to generate the next smaller one. - // Canvas sizes start at 30x15 and decrease by 2x1 at a time down to a minimum of 20x10. Min generated size is always 4x4. + // Time to add lakes and chasms. Strategy is to generate a series of blob lakes of decreasing size. For each lake, + // propose a position, and then check via a flood fill that the level would remain connected with that placement (i.e. + // that each passable tile can still be reached). If not, make 9 more placement attempts before abandoning that lake + // and proceeding to generate the next smaller one. + // Canvas sizes start at 30x15 and decrease by 2x1 at a time down to a minimum of 20x10. Min generated size is always + // 4x4. - // DEBUG logLevel(); + // DEBUG logLevel(); - // Now design the lakes and then fill them with various liquids (lava, water, chasm, brimstone). - short **lakeMap = allocGrid(); - designLakes(lakeMap); - fillLakes(lakeMap); - freeGrid(lakeMap); + // Now design the lakes and then fill them with various liquids (lava, water, chasm, brimstone). + short **lakeMap = allocGrid(); + designLakes(lakeMap); + fillLakes(lakeMap); + freeGrid(lakeMap); - // Run the non-machine autoGenerators. - runAutogenerators(false); + // Run the non-machine autoGenerators. + runAutogenerators(false); - // Remove diagonal openings. - removeDiagonalOpenings(); + // Remove diagonal openings. + removeDiagonalOpenings(); - if (D_INSPECT_LEVELGEN) { - dumpLevelToScreen(); - temporaryMessage("Diagonal openings removed.", REQUIRE_ACKNOWLEDGMENT); - } + if (D_INSPECT_LEVELGEN) { + dumpLevelToScreen(); + temporaryMessage("Diagonal openings removed.", REQUIRE_ACKNOWLEDGMENT); + } - // Now add some treasure machines. - addMachines(); + // Now add some treasure machines. + addMachines(); - if (D_INSPECT_LEVELGEN) { - dumpLevelToScreen(); - temporaryMessage("Machines added.", REQUIRE_ACKNOWLEDGMENT); - } + if (D_INSPECT_LEVELGEN) { + dumpLevelToScreen(); + temporaryMessage("Machines added.", REQUIRE_ACKNOWLEDGMENT); + } - // Run the machine autoGenerators. - runAutogenerators(true); + // Run the machine autoGenerators. + runAutogenerators(true); - // Now knock down the boundaries between similar lakes where possible. - cleanUpLakeBoundaries(); + // Now knock down the boundaries between similar lakes where possible. + cleanUpLakeBoundaries(); - if (D_INSPECT_LEVELGEN) { - dumpLevelToScreen(); - temporaryMessage("Lake boundaries cleaned up.", REQUIRE_ACKNOWLEDGMENT); - } + if (D_INSPECT_LEVELGEN) { + dumpLevelToScreen(); + temporaryMessage("Lake boundaries cleaned up.", REQUIRE_ACKNOWLEDGMENT); + } - // Now add some bridges. - while (buildABridge()); + // Now add some bridges. + while (buildABridge()) + ; - if (D_INSPECT_LEVELGEN) { - dumpLevelToScreen(); - temporaryMessage("Bridges added.", REQUIRE_ACKNOWLEDGMENT); - } + if (D_INSPECT_LEVELGEN) { + dumpLevelToScreen(); + temporaryMessage("Bridges added.", REQUIRE_ACKNOWLEDGMENT); + } - // Now remove orphaned doors and upgrade some doors to secret doors - finishDoors(); + // Now remove orphaned doors and upgrade some doors to secret doors + finishDoors(); - // Now finish any exposed granite with walls and revert any unexposed walls to granite - finishWalls(true); + // Now finish any exposed granite with walls and revert any unexposed walls to granite + finishWalls(true); - if (D_INSPECT_LEVELGEN) { - dumpLevelToScreen(); - temporaryMessage("Finishing touches added. Level has been generated.", REQUIRE_ACKNOWLEDGMENT); - } + if (D_INSPECT_LEVELGEN) { + dumpLevelToScreen(); + temporaryMessage("Finishing touches added. Level has been generated.", REQUIRE_ACKNOWLEDGMENT); + } } void updateMapToShore() { - short i, j; - short **costMap; - - rogue.updatedMapToShoreThisTurn = true; - - costMap = allocGrid(); - - // Calculate the map to shore for this level - if (!rogue.mapToShore) { - rogue.mapToShore = allocGrid(); - fillGrid(rogue.mapToShore, 0); - } - for (i=0; icreatureState == MONSTER_SLEEPING || (monst->info.flags & MONST_IMMOBILE) || (monst->bookkeepingFlags & MB_CAPTIVE)) - && costMap[monst->loc.x][monst->loc.y] >= 0) { + costMap = allocGrid(); + populateGenericCostMap(costMap); + for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) { + creature *monst = nextCreature(&it); + if ((monst->creatureState == MONSTER_SLEEPING || (monst->info.flags & MONST_IMMOBILE) + || (monst->bookkeepingFlags & MB_CAPTIVE)) + && costMap[monst->loc.x][monst->loc.y] >= 0) { - costMap[monst->loc.x][monst->loc.y] = PDS_FORBIDDEN; - } + costMap[monst->loc.x][monst->loc.y] = PDS_FORBIDDEN; } - fillGrid(rogue.wpDistance[wpIndex], 30000); - rogue.wpDistance[wpIndex][rogue.wpCoordinates[wpIndex].x][rogue.wpCoordinates[wpIndex].y] = 0; - dijkstraScan(rogue.wpDistance[wpIndex], costMap, true); - freeGrid(costMap); + } + fillGrid(rogue.wpDistance[wpIndex], 30000); + rogue.wpDistance[wpIndex][rogue.wpCoordinates[wpIndex].x][rogue.wpCoordinates[wpIndex].y] = 0; + dijkstraScan(rogue.wpDistance[wpIndex], costMap, true); + freeGrid(costMap); } void setUpWaypoints() { - short i, j, sCoord[DCOLS * DROWS], x, y; - char grid[DCOLS][DROWS]; - - zeroOutGrid(grid); - for (i=0; i= tileCatalog[surfaceTileType].drawPriority) - // and we won't be painting into the surface layer when that cell forbids it, - && !(layer == SURFACE && cellHasTerrainFlag(i, j, T_OBSTRUCTS_SURFACE_EFFECTS)) - // and, if requested, the fill won't violate the priority of the most important terrain in this cell: - && (!blockedByOtherLayers || tileCatalog[pmap[i][j].layers[highestPriorityLayer(i, j, true)]].drawPriority >= tileCatalog[surfaceTileType].drawPriority) - ) { - - if ((tileCatalog[surfaceTileType].flags & T_IS_FIRE) - && !(tileCatalog[pmap[i][j].layers[layer]].flags & T_IS_FIRE)) { - pmap[i][j].flags |= CAUGHT_FIRE_THIS_TURN; - } - - if ((tileCatalog[pmap[i][j].layers[layer]].flags & T_PATHING_BLOCKER) - != (tileCatalog[surfaceTileType].flags & T_PATHING_BLOCKER)) { - - rogue.staleLoopMap = true; - } - - pmap[i][j].layers[layer] = surfaceTileType; // Place the terrain! - accomplishedSomething = true; + short i; + + for (i = 0; i < NUMBER_DUNGEON_FEATURES; i++) { + dungeonFeatureCatalog[i].messageDisplayed = false; + } +} - if (refresh) { - refreshDungeonCell(i, j); - if (player.loc.x == i && player.loc.y == j && !player.status[STATUS_LEVITATING] && refresh) { - flavorMessage(tileFlavor(player.loc.x, player.loc.y)); - } - if (pmap[i][j].flags & (HAS_MONSTER)) { - monst = monsterAtLoc((pos){ i, j }); - applyInstantTileEffectsToCreature(monst); - if (rogue.gameHasEnded) { - return true; - } - } - if (tileCatalog[surfaceTileType].flags & T_IS_FIRE) { - if (pmap[i][j].flags & HAS_ITEM) { - theItem = itemAtLoc(i, j); - if (theItem->flags & ITEM_FLAMMABLE) { - burnItem(theItem); - } - } - } - } - } else { - spawnMap[i][j] = false; // so that the spawnmap reflects what actually got built - } - } - } - return accomplishedSomething; +boolean fillSpawnMap(enum dungeonLayers layer, enum tileType surfaceTileType, char spawnMap[DCOLS][DROWS], + boolean blockedByOtherLayers, boolean refresh, boolean superpriority) { + short i, j; + creature *monst; + item *theItem; + boolean accomplishedSomething; + + accomplishedSomething = false; + + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + if ( // If it's flagged for building in the spawn map, + spawnMap[i][j] + // and the new cell doesn't already contain the fill terrain, + && pmap[i][j].layers[layer] != surfaceTileType + // and the terrain in the layer to be overwritten has a higher priority number (unless superpriority), + && (superpriority + || tileCatalog[pmap[i][j].layers[layer]].drawPriority >= tileCatalog[surfaceTileType].drawPriority) + // and we won't be painting into the surface layer when that cell forbids it, + && !(layer == SURFACE && cellHasTerrainFlag(i, j, T_OBSTRUCTS_SURFACE_EFFECTS)) + // and, if requested, the fill won't violate the priority of the most important terrain in this cell: + && (!blockedByOtherLayers + || tileCatalog[pmap[i][j].layers[highestPriorityLayer(i, j, true)]].drawPriority + >= tileCatalog[surfaceTileType].drawPriority)) { + + if ((tileCatalog[surfaceTileType].flags & T_IS_FIRE) + && !(tileCatalog[pmap[i][j].layers[layer]].flags & T_IS_FIRE)) { + pmap[i][j].flags |= CAUGHT_FIRE_THIS_TURN; + } + + if ((tileCatalog[pmap[i][j].layers[layer]].flags & T_PATHING_BLOCKER) + != (tileCatalog[surfaceTileType].flags & T_PATHING_BLOCKER)) { + + rogue.staleLoopMap = true; + } + + pmap[i][j].layers[layer] = surfaceTileType; // Place the terrain! + accomplishedSomething = true; + + if (refresh) { + refreshDungeonCell(i, j); + if (player.loc.x == i && player.loc.y == j && !player.status[STATUS_LEVITATING] && refresh) { + flavorMessage(tileFlavor(player.loc.x, player.loc.y)); + } + if (pmap[i][j].flags & (HAS_MONSTER)) { + monst = monsterAtLoc((pos){i, j}); + applyInstantTileEffectsToCreature(monst); + if (rogue.gameHasEnded) { + return true; + } + } + if (tileCatalog[surfaceTileType].flags & T_IS_FIRE) { + if (pmap[i][j].flags & HAS_ITEM) { + theItem = itemAtLoc(i, j); + if (theItem->flags & ITEM_FLAMMABLE) { + burnItem(theItem); + } + } + } + } + } else { + spawnMap[i][j] = false; // so that the spawnmap reflects what actually got built + } + } + } + return accomplishedSomething; +} + +void spawnMapDF(short x, short y, enum tileType propagationTerrain, boolean requirePropTerrain, short startProb, + short probDec, char spawnMap[DCOLS][DROWS]) { + + short i, j, dir, t, x2, y2; + boolean madeChange; + + spawnMap[x][y] = t = 1; // incremented before anything else happens + + madeChange = true; + + while (madeChange && startProb > 0) { + madeChange = false; + t++; + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + if (spawnMap[i][j] == t - 1) { + for (dir = 0; dir < 4; dir++) { + x2 = i + nbDirs[dir][0]; + y2 = j + nbDirs[dir][1]; + if (coordinatesAreInMap(x2, y2) + && (!requirePropTerrain || (propagationTerrain > 0 && cellHasTerrainType(x2, y2, propagationTerrain))) + && (!cellHasTerrainFlag(x2, y2, T_OBSTRUCTS_SURFACE_EFFECTS) + || (propagationTerrain > 0 && cellHasTerrainType(x2, y2, propagationTerrain))) + && rand_percent(startProb)) { + + spawnMap[x2][y2] = t; + madeChange = true; + } + } + } + } + } + startProb -= probDec; + if (t > 100) { + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + if (spawnMap[i][j] == t) { + spawnMap[i][j] = 2; + } else if (spawnMap[i][j] > 0) { + spawnMap[i][j] = 1; + } + } + } + t = 2; + } + } + if (requirePropTerrain && !cellHasTerrainType(x, y, propagationTerrain)) { + spawnMap[x][y] = 0; + } } -void spawnMapDF(short x, short y, - enum tileType propagationTerrain, - boolean requirePropTerrain, - short startProb, - short probDec, - char spawnMap[DCOLS][DROWS]) { - - short i, j, dir, t, x2, y2; - boolean madeChange; - - spawnMap[x][y] = t = 1; // incremented before anything else happens - - madeChange = true; +void evacuateCreatures(char blockingMap[DCOLS][DROWS]) { + creature *monst; - while (madeChange && startProb > 0) { - madeChange = false; - t++; - for (i = 0; i < DCOLS; i++) { - for (j=0; j < DROWS; j++) { - if (spawnMap[i][j] == t - 1) { - for (dir = 0; dir < 4; dir++) { - x2 = i + nbDirs[dir][0]; - y2 = j + nbDirs[dir][1]; - if (coordinatesAreInMap(x2, y2) - && (!requirePropTerrain || (propagationTerrain > 0 && cellHasTerrainType(x2, y2, propagationTerrain))) - && (!cellHasTerrainFlag(x2, y2, T_OBSTRUCTS_SURFACE_EFFECTS) || (propagationTerrain > 0 && cellHasTerrainType(x2, y2, propagationTerrain))) - && rand_percent(startProb)) { - - spawnMap[x2][y2] = t; - madeChange = true; - } - } - } - } - } - startProb -= probDec; - if (t > 100) { - for (i = 0; i < DCOLS; i++) { - for (j=0; j < DROWS; j++) { - if (spawnMap[i][j] == t) { - spawnMap[i][j] = 2; - } else if (spawnMap[i][j] > 0) { - spawnMap[i][j] = 1; - } - } - } - t = 2; - } - } - if (requirePropTerrain && !cellHasTerrainType(x, y, propagationTerrain)) { - spawnMap[x][y] = 0; - } -} + for (int i = 0; i < DCOLS; i++) { + for (int j = 0; j < DROWS; j++) { + if (blockingMap[i][j] && (pmap[i][j].flags & (HAS_MONSTER | HAS_PLAYER))) { -void evacuateCreatures(char blockingMap[DCOLS][DROWS]) { - creature *monst; - - for (int i=0; iinfo)), - (HAS_MONSTER | HAS_PLAYER), - false, - false); - monst->loc = newLoc; - pmap[i][j].flags &= ~(HAS_MONSTER | HAS_PLAYER); - pmapAt(newLoc)->flags |= (monst == &player ? HAS_PLAYER : HAS_MONSTER); - } - } + monst = monsterAtLoc((pos){i, j}); + pos newLoc; + getQualifyingLocNear(&newLoc, i, j, true, blockingMap, forbiddenFlagsForMonster(&(monst->info)), + (HAS_MONSTER | HAS_PLAYER), false, false); + monst->loc = newLoc; + pmap[i][j].flags &= ~(HAS_MONSTER | HAS_PLAYER); + pmapAt(newLoc)->flags |= (monst == &player ? HAS_PLAYER : HAS_MONSTER); + } } + } } // returns whether the feature was successfully generated (false if we aborted because of blocking) boolean spawnDungeonFeature(short x, short y, dungeonFeature *feat, boolean refreshCell, boolean abortIfBlocking) { - short i, j, layer; - char blockingMap[DCOLS][DROWS]; - boolean blocking; - boolean succeeded; - - if ((feat->flags & DFF_RESURRECT_ALLY) - && !resurrectAlly(x, y)) { - return false; - } + short i, j, layer; + char blockingMap[DCOLS][DROWS]; + boolean blocking; + boolean succeeded; - if (feat->description[0] && !feat->messageDisplayed && playerCanSee(x, y)) { - feat->messageDisplayed = true; - message(feat->description, 0); - } - - zeroOutGrid(blockingMap); - - // Blocking keeps track of whether to abort if it turns out that the DF would obstruct the level. - blocking = ((abortIfBlocking - && !(feat->flags & DFF_PERMIT_BLOCKING) - && ((tileCatalog[feat->tile].flags & (T_PATHING_BLOCKER)) - || (feat->flags & DFF_TREAT_AS_BLOCKING))) ? true : false); - - if (feat->tile) { - if (feat->layer == GAS) { - pmap[x][y].volume += feat->startProbability; - pmap[x][y].layers[GAS] = feat->tile; - if (refreshCell) { - refreshDungeonCell(x, y); - } - succeeded = true; - } else { - spawnMapDF(x, y, - feat->propagationTerrain, - (feat->propagationTerrain ? true : false), - feat->startProbability, - feat->probabilityDecrement, - blockingMap); - if (!blocking || !levelIsDisconnectedWithBlockingMap(blockingMap, false)) { - if (feat->flags & DFF_EVACUATE_CREATURES_FIRST) { // first, evacuate creatures if necessary, so that they do not re-trigger the tile. - evacuateCreatures(blockingMap); - } - - //succeeded = fillSpawnMap(feat->layer, feat->tile, blockingMap, (feat->flags & DFF_BLOCKED_BY_OTHER_LAYERS), refreshCell, (feat->flags & DFF_SUPERPRIORITY)); - fillSpawnMap(feat->layer, - feat->tile, - blockingMap, - (feat->flags & DFF_BLOCKED_BY_OTHER_LAYERS), - refreshCell, - (feat->flags & DFF_SUPERPRIORITY)); // this can tweak the spawn map too - succeeded = true; // fail ONLY if we blocked the level. We succeed even if, thanks to priority, nothing gets built. - } else { - succeeded = false; - } - } + if ((feat->flags & DFF_RESURRECT_ALLY) && !resurrectAlly(x, y)) { + return false; + } + + if (feat->description[0] && !feat->messageDisplayed && playerCanSee(x, y)) { + feat->messageDisplayed = true; + message(feat->description, 0); + } + + zeroOutGrid(blockingMap); + + // Blocking keeps track of whether to abort if it turns out that the DF would obstruct the level. + blocking = ((abortIfBlocking && !(feat->flags & DFF_PERMIT_BLOCKING) + && ((tileCatalog[feat->tile].flags & (T_PATHING_BLOCKER)) || (feat->flags & DFF_TREAT_AS_BLOCKING))) + ? true + : false); + + if (feat->tile) { + if (feat->layer == GAS) { + pmap[x][y].volume += feat->startProbability; + pmap[x][y].layers[GAS] = feat->tile; + if (refreshCell) { + refreshDungeonCell(x, y); + } + succeeded = true; } else { - blockingMap[x][y] = true; - succeeded = true; // Automatically succeed if there is no terrain to place. - if (feat->flags & DFF_EVACUATE_CREATURES_FIRST) { // first, evacuate creatures if necessary, so that they do not re-trigger the tile. - evacuateCreatures(blockingMap); + spawnMapDF(x, y, feat->propagationTerrain, (feat->propagationTerrain ? true : false), feat->startProbability, + feat->probabilityDecrement, blockingMap); + if (!blocking || !levelIsDisconnectedWithBlockingMap(blockingMap, false)) { + if (feat->flags & DFF_EVACUATE_CREATURES_FIRST) { // first, evacuate creatures if necessary, so that they do not + // re-trigger the tile. + evacuateCreatures(blockingMap); } + + // succeeded = fillSpawnMap(feat->layer, feat->tile, blockingMap, (feat->flags & DFF_BLOCKED_BY_OTHER_LAYERS), + // refreshCell, (feat->flags & DFF_SUPERPRIORITY)); + fillSpawnMap(feat->layer, feat->tile, blockingMap, (feat->flags & DFF_BLOCKED_BY_OTHER_LAYERS), refreshCell, + (feat->flags & DFF_SUPERPRIORITY)); // this can tweak the spawn map too + succeeded + = true; // fail ONLY if we blocked the level. We succeed even if, thanks to priority, nothing gets built. + } else { + succeeded = false; + } + } + } else { + blockingMap[x][y] = true; + succeeded = true; // Automatically succeed if there is no terrain to place. + if (feat->flags & DFF_EVACUATE_CREATURES_FIRST) { // first, evacuate creatures if necessary, so that they do not + // re-trigger the tile. + evacuateCreatures(blockingMap); } + } - if (succeeded && (feat->flags & DFF_CLEAR_OTHER_TERRAIN)) { - for (i=0; ilayer && layer != GAS) { - pmap[i][j].layers[layer] = (layer == DUNGEON ? FLOOR : NOTHING); - } - } - } + if (succeeded && (feat->flags & DFF_CLEAR_OTHER_TERRAIN)) { + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + if (blockingMap[i][j]) { + for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) { + if (layer != feat->layer && layer != GAS) { + pmap[i][j].layers[layer] = (layer == DUNGEON ? FLOOR : NOTHING); } + } } + } } + } - if (succeeded) { - if ((feat->flags & DFF_AGGRAVATES_MONSTERS) && feat->effectRadius) { - aggravateMonsters(feat->effectRadius, x, y, &gray); - } - if (refreshCell && feat->flashColor && feat->effectRadius) { - colorFlash(feat->flashColor, 0, (IN_FIELD_OF_VIEW | CLAIRVOYANT_VISIBLE), 4, feat->effectRadius, x, y); - } - if (refreshCell && feat->lightFlare) { - createFlare(x, y, feat->lightFlare); - } + if (succeeded) { + if ((feat->flags & DFF_AGGRAVATES_MONSTERS) && feat->effectRadius) { + aggravateMonsters(feat->effectRadius, x, y, &gray); } - - if (refreshCell - && (tileCatalog[feat->tile].flags & (T_IS_FIRE | T_AUTO_DESCENT)) - && cellHasTerrainFlag(player.loc.x, player.loc.y, (T_IS_FIRE | T_AUTO_DESCENT))) { - - applyInstantTileEffectsToCreature(&player); + if (refreshCell && feat->flashColor && feat->effectRadius) { + colorFlash(feat->flashColor, 0, (IN_FIELD_OF_VIEW | CLAIRVOYANT_VISIBLE), 4, feat->effectRadius, x, y); } - if (rogue.gameHasEnded) { - return succeeded; + if (refreshCell && feat->lightFlare) { + createFlare(x, y, feat->lightFlare); } - // if (succeeded && feat->description[0] && !feat->messageDisplayed && playerCanSee(x, y)) { - // feat->messageDisplayed = true; - // message(feat->description, 0); - // } - if (succeeded) { - if (feat->subsequentDF) { - if (feat->flags & DFF_SUBSEQ_EVERYWHERE) { - for (i=0; isubsequentDF], refreshCell, abortIfBlocking); - } - } - } - } else { - spawnDungeonFeature(x, y, &dungeonFeatureCatalog[feat->subsequentDF], refreshCell, abortIfBlocking); + } + + if (refreshCell && (tileCatalog[feat->tile].flags & (T_IS_FIRE | T_AUTO_DESCENT)) + && cellHasTerrainFlag(player.loc.x, player.loc.y, (T_IS_FIRE | T_AUTO_DESCENT))) { + + applyInstantTileEffectsToCreature(&player); + } + if (rogue.gameHasEnded) { + return succeeded; + } + // if (succeeded && feat->description[0] && !feat->messageDisplayed && playerCanSee(x, y)) { + // feat->messageDisplayed = true; + // message(feat->description, 0); + // } + if (succeeded) { + if (feat->subsequentDF) { + if (feat->flags & DFF_SUBSEQ_EVERYWHERE) { + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + if (blockingMap[i][j]) { + spawnDungeonFeature(i, j, &dungeonFeatureCatalog[feat->subsequentDF], refreshCell, abortIfBlocking); } + } } - if (feat->tile - && (tileCatalog[feat->tile].flags & (T_IS_DEEP_WATER | T_LAVA_INSTA_DEATH | T_AUTO_DESCENT))) { + } else { + spawnDungeonFeature(x, y, &dungeonFeatureCatalog[feat->subsequentDF], refreshCell, abortIfBlocking); + } + } + if (feat->tile && (tileCatalog[feat->tile].flags & (T_IS_DEEP_WATER | T_LAVA_INSTA_DEATH | T_AUTO_DESCENT))) { - rogue.updatedMapToShoreThisTurn = false; - } + rogue.updatedMapToShoreThisTurn = false; + } - // awaken dormant creatures? - if (feat->flags & DFF_ACTIVATE_DORMANT_MONSTER) { - for (creatureIterator it = iterateCreatures(dormantMonsters); hasNextCreature(it);) { - creature *monst = nextCreature(&it); - if (monst->loc.x == x && monst->loc.y == y || blockingMap[monst->loc.x][monst->loc.y]) { - // found it! - toggleMonsterDormancy(monst); - } - } + // awaken dormant creatures? + if (feat->flags & DFF_ACTIVATE_DORMANT_MONSTER) { + for (creatureIterator it = iterateCreatures(dormantMonsters); hasNextCreature(it);) { + creature *monst = nextCreature(&it); + if (monst->loc.x == x && monst->loc.y == y || blockingMap[monst->loc.x][monst->loc.y]) { + // found it! + toggleMonsterDormancy(monst); } + } } - return succeeded; + } + return succeeded; } void restoreMonster(creature *monst, short **mapToStairs, short **mapToPit) { - short i, *x, *y, turnCount; - boolean foundLeader = false; - short **theMap; - enum directions dir; + short i, *x, *y, turnCount; + boolean foundLeader = false; + short **theMap; + enum directions dir; - x = &(monst->loc.x); - y = &(monst->loc.y); + x = &(monst->loc.x); + y = &(monst->loc.y); - if (monst->status[STATUS_ENTERS_LEVEL_IN] > 0) { - if (monst->bookkeepingFlags & (MB_APPROACHING_PIT)) { - theMap = mapToPit; + if (monst->status[STATUS_ENTERS_LEVEL_IN] > 0) { + if (monst->bookkeepingFlags & (MB_APPROACHING_PIT)) { + theMap = mapToPit; + } else { + theMap = mapToStairs; + } + + pmap[*x][*y].flags &= ~HAS_MONSTER; + if (theMap) { + // STATUS_ENTERS_LEVEL_IN accounts for monster speed; convert back to map distance and subtract from distance to + // stairs + turnCount + = (theMap[monst->loc.x][monst->loc.y] - (monst->status[STATUS_ENTERS_LEVEL_IN] * 100 / monst->movementSpeed)); + for (i = 0; i < turnCount; i++) { + if ((dir = nextStep(theMap, monst->loc.x, monst->loc.y, NULL, true)) != NO_DIRECTION) { + monst->loc.x += nbDirs[dir][0]; + monst->loc.y += nbDirs[dir][1]; } else { - theMap = mapToStairs; - } - - pmap[*x][*y].flags &= ~HAS_MONSTER; - if (theMap) { - // STATUS_ENTERS_LEVEL_IN accounts for monster speed; convert back to map distance and subtract from distance to stairs - turnCount = (theMap[monst->loc.x][monst->loc.y] - (monst->status[STATUS_ENTERS_LEVEL_IN] * 100 / monst->movementSpeed)); - for (i=0; i < turnCount; i++) { - if ((dir = nextStep(theMap, monst->loc.x, monst->loc.y, NULL, true)) != NO_DIRECTION) { - monst->loc.x += nbDirs[dir][0]; - monst->loc.y += nbDirs[dir][1]; - } else { - break; - } - } + break; } - monst->bookkeepingFlags |= MB_PREPLACED; + } } + monst->bookkeepingFlags |= MB_PREPLACED; + } - if ((pmap[*x][*y].flags & (HAS_PLAYER | HAS_STAIRS)) - || (monst->bookkeepingFlags & MB_PREPLACED)) { + if ((pmap[*x][*y].flags & (HAS_PLAYER | HAS_STAIRS)) || (monst->bookkeepingFlags & MB_PREPLACED)) { - if (!(monst->bookkeepingFlags & MB_PREPLACED)) { - // (If if it's preplaced, it won't have set the HAS_MONSTER flag in the first place, - // so clearing it might screw up an existing monster.) - pmap[*x][*y].flags &= ~HAS_MONSTER; - } - getQualifyingPathLocNear(x, y, *x, *y, true, T_DIVIDES_LEVEL & avoidedFlagsForMonster(&(monst->info)), 0, - avoidedFlagsForMonster(&(monst->info)), (HAS_MONSTER | HAS_PLAYER | HAS_STAIRS), true); + if (!(monst->bookkeepingFlags & MB_PREPLACED)) { + // (If if it's preplaced, it won't have set the HAS_MONSTER flag in the first place, + // so clearing it might screw up an existing monster.) + pmap[*x][*y].flags &= ~HAS_MONSTER; } - pmap[*x][*y].flags |= HAS_MONSTER; - monst->bookkeepingFlags &= ~(MB_PREPLACED | MB_APPROACHING_DOWNSTAIRS | MB_APPROACHING_UPSTAIRS | MB_APPROACHING_PIT | MB_ABSORBING); - monst->status[STATUS_ENTERS_LEVEL_IN] = 0; - monst->corpseAbsorptionCounter = 0; + getQualifyingPathLocNear(x, y, *x, *y, true, T_DIVIDES_LEVEL & avoidedFlagsForMonster(&(monst->info)), 0, + avoidedFlagsForMonster(&(monst->info)), (HAS_MONSTER | HAS_PLAYER | HAS_STAIRS), true); + } + pmap[*x][*y].flags |= HAS_MONSTER; + monst->bookkeepingFlags + &= ~(MB_PREPLACED | MB_APPROACHING_DOWNSTAIRS | MB_APPROACHING_UPSTAIRS | MB_APPROACHING_PIT | MB_ABSORBING); + monst->status[STATUS_ENTERS_LEVEL_IN] = 0; + monst->corpseAbsorptionCounter = 0; - if ((monst->bookkeepingFlags & MB_SUBMERGED) && !cellHasTMFlag(*x, *y, TM_ALLOWS_SUBMERGING)) { - monst->bookkeepingFlags &= ~MB_SUBMERGED; - } + if ((monst->bookkeepingFlags & MB_SUBMERGED) && !cellHasTMFlag(*x, *y, TM_ALLOWS_SUBMERGING)) { + monst->bookkeepingFlags &= ~MB_SUBMERGED; + } - if (monst->bookkeepingFlags & MB_FOLLOWER) { - // is the leader on the same level? - for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) { - creature *leader = nextCreature(&it); - if (leader == monst->leader) { - foundLeader = true; - break; - } - } - // if not, it is time to spread your wings and fly solo - if (!foundLeader) { - monst->bookkeepingFlags &= ~MB_FOLLOWER; - monst->leader = NULL; - } + if (monst->bookkeepingFlags & MB_FOLLOWER) { + // is the leader on the same level? + for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) { + creature *leader = nextCreature(&it); + if (leader == monst->leader) { + foundLeader = true; + break; + } + } + // if not, it is time to spread your wings and fly solo + if (!foundLeader) { + monst->bookkeepingFlags &= ~MB_FOLLOWER; + monst->leader = NULL; } + } } void restoreItem(item *theItem) { - if (theItem->flags & ITEM_PREPLACED) { - theItem->flags &= ~ITEM_PREPLACED; + if (theItem->flags & ITEM_PREPLACED) { + theItem->flags &= ~ITEM_PREPLACED; - pos loc; - // Items can fall into deep water, enclaved lakes, another chasm, even lava! - getQualifyingLocNear(&loc, theItem->loc.x, theItem->loc.y, true, 0, - (T_OBSTRUCTS_ITEMS), - (HAS_MONSTER | HAS_ITEM | HAS_STAIRS), false, false); + pos loc; + // Items can fall into deep water, enclaved lakes, another chasm, even lava! + getQualifyingLocNear(&loc, theItem->loc.x, theItem->loc.y, true, 0, (T_OBSTRUCTS_ITEMS), + (HAS_MONSTER | HAS_ITEM | HAS_STAIRS), false, false); - theItem->loc = loc; - } - pmapAt(theItem->loc)->flags |= HAS_ITEM; - if (theItem->flags & ITEM_MAGIC_DETECTED && itemMagicPolarity(theItem)) { - pmapAt(theItem->loc)->flags |= ITEM_DETECTED; - } + theItem->loc = loc; + } + pmapAt(theItem->loc)->flags |= HAS_ITEM; + if (theItem->flags & ITEM_MAGIC_DETECTED && itemMagicPolarity(theItem)) { + pmapAt(theItem->loc)->flags |= ITEM_DETECTED; + } } -// Returns true iff the location is a plain wall, three of the four cardinal neighbors are walls, the remaining cardinal neighbor -// is not a pathing blocker, the two diagonals between the three cardinal walls are also walls, and none of the eight neighbors are in machines. +// Returns true iff the location is a plain wall, three of the four cardinal neighbors are walls, the remaining cardinal +// neighbor is not a pathing blocker, the two diagonals between the three cardinal walls are also walls, and none of the +// eight neighbors are in machines. boolean validStairLoc(short x, short y) { - short newX, newY, dir, neighborWallCount; + short newX, newY, dir, neighborWallCount; - if (x < 1 || x >= DCOLS - 1 || y < 1 || y >= DROWS - 1 || pmap[x][y].layers[DUNGEON] != WALL) { - return false; - } + if (x < 1 || x >= DCOLS - 1 || y < 1 || y >= DROWS - 1 || pmap[x][y].layers[DUNGEON] != WALL) { + return false; + } - for (dir=0; dir< DIRECTION_COUNT; dir++) { - newX = x + nbDirs[dir][0]; - newY = y + nbDirs[dir][1]; - if (pmap[newX][newY].flags & IS_IN_MACHINE) { - return false; - } + for (dir = 0; dir < DIRECTION_COUNT; dir++) { + newX = x + nbDirs[dir][0]; + newY = y + nbDirs[dir][1]; + if (pmap[newX][newY].flags & IS_IN_MACHINE) { + return false; } + } - neighborWallCount = 0; - for (dir=0; dir<4; dir++) { - newX = x + nbDirs[dir][0]; - newY = y + nbDirs[dir][1]; + neighborWallCount = 0; + for (dir = 0; dir < 4; dir++) { + newX = x + nbDirs[dir][0]; + newY = y + nbDirs[dir][1]; - if (cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)) { - // neighbor is a wall - neighborWallCount++; - } else { - // neighbor is not a wall - if (cellHasTerrainFlag(newX, newY, T_PATHING_BLOCKER) - || passableArcCount(newX, newY) >= 2) { - return false; - } - // now check the two diagonals between the walls + if (cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)) { + // neighbor is a wall + neighborWallCount++; + } else { + // neighbor is not a wall + if (cellHasTerrainFlag(newX, newY, T_PATHING_BLOCKER) || passableArcCount(newX, newY) >= 2) { + return false; + } + // now check the two diagonals between the walls - newX = x - nbDirs[dir][0] + nbDirs[dir][1]; - newY = y - nbDirs[dir][1] + nbDirs[dir][0]; - if (!cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)) { - return false; - } + newX = x - nbDirs[dir][0] + nbDirs[dir][1]; + newY = y - nbDirs[dir][1] + nbDirs[dir][0]; + if (!cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)) { + return false; + } - newX = x - nbDirs[dir][0] - nbDirs[dir][1]; - newY = y - nbDirs[dir][1] - nbDirs[dir][0]; - if (!cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)) { - return false; - } - } - } - if (neighborWallCount != 3) { + newX = x - nbDirs[dir][0] - nbDirs[dir][1]; + newY = y - nbDirs[dir][1] - nbDirs[dir][0]; + if (!cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)) { return false; + } } - return true; + } + if (neighborWallCount != 3) { + return false; + } + return true; } -// The walls on either side become torches. Any adjacent granite then becomes top_wall. All wall neighbors are un-tunnelable. -// Grid is zeroed out within 5 spaces in all directions. +// The walls on either side become torches. Any adjacent granite then becomes top_wall. All wall neighbors are +// un-tunnelable. Grid is zeroed out within 5 spaces in all directions. void prepareForStairs(short x, short y, char grid[DCOLS][DROWS]) { - short newX, newY, dir; - - // Add torches to either side. - for (dir=0; dir<4; dir++) { - if (!cellHasTerrainFlag(x + nbDirs[dir][0], y + nbDirs[dir][1], T_OBSTRUCTS_PASSABILITY)) { - newX = x - nbDirs[dir][1]; - newY = y - nbDirs[dir][0]; - pmap[newX][newY].layers[DUNGEON] = TORCH_WALL; - newX = x + nbDirs[dir][1]; - newY = y + nbDirs[dir][0]; - pmap[newX][newY].layers[DUNGEON] = TORCH_WALL; - break; - } - } - // Expose granite. - for (dir=0; dir< DIRECTION_COUNT; dir++) { - newX = x + nbDirs[dir][0]; - newY = y + nbDirs[dir][1]; - if (pmap[newX][newY].layers[DUNGEON] == GRANITE) { - pmap[newX][newY].layers[DUNGEON] = WALL; - } - if (cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)) { - pmap[newX][newY].flags |= IMPREGNABLE; - } - } - // Zero out grid in the vicinity. - for (newX = max(0, x - 5); newX < min(DCOLS, x + 5); newX++) { - for (newY = max(0, y - 5); newY < min(DROWS, y + 5); newY++) { - grid[newX][newY] = false; - } - } + short newX, newY, dir; + + // Add torches to either side. + for (dir = 0; dir < 4; dir++) { + if (!cellHasTerrainFlag(x + nbDirs[dir][0], y + nbDirs[dir][1], T_OBSTRUCTS_PASSABILITY)) { + newX = x - nbDirs[dir][1]; + newY = y - nbDirs[dir][0]; + pmap[newX][newY].layers[DUNGEON] = TORCH_WALL; + newX = x + nbDirs[dir][1]; + newY = y + nbDirs[dir][0]; + pmap[newX][newY].layers[DUNGEON] = TORCH_WALL; + break; + } + } + // Expose granite. + for (dir = 0; dir < DIRECTION_COUNT; dir++) { + newX = x + nbDirs[dir][0]; + newY = y + nbDirs[dir][1]; + if (pmap[newX][newY].layers[DUNGEON] == GRANITE) { + pmap[newX][newY].layers[DUNGEON] = WALL; + } + if (cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)) { + pmap[newX][newY].flags |= IMPREGNABLE; + } + } + // Zero out grid in the vicinity. + for (newX = max(0, x - 5); newX < min(DCOLS, x + 5); newX++) { + for (newY = max(0, y - 5); newY < min(DROWS, y + 5); newY++) { + grid[newX][newY] = false; + } + } } // Places the player, monsters, items and stairs. void initializeLevel() { - short i, j, dir; - short **mapToStairs, **mapToPit; - item *theItem; - char grid[DCOLS][DROWS]; - short n = rogue.depthLevel - 1; - - // Place the stairs. - - for (int i=0; i < DCOLS; i++) { - for (int j=0; j < DROWS; j++) { - grid[i][j] = validStairLoc(i, j); - } - } - - if (D_INSPECT_LEVELGEN) { - dumpLevelToScreen(); - hiliteCharGrid(grid, &teal, 100); - temporaryMessage("Stair location candidates:", REQUIRE_ACKNOWLEDGMENT); - } - - pos downLoc; - if (getQualifyingGridLocNear(&downLoc, levels[n].downStairsLoc.x, levels[n].downStairsLoc.y, grid, false)) { - prepareForStairs(downLoc.x, downLoc.y, grid); - } else { - getQualifyingLocNear(&downLoc, levels[n].downStairsLoc.x, levels[n].downStairsLoc.y, false, 0, - (T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_ITEMS | T_AUTO_DESCENT | T_IS_DEEP_WATER | T_LAVA_INSTA_DEATH | T_IS_DF_TRAP), - (HAS_MONSTER | HAS_ITEM | HAS_STAIRS | IS_IN_MACHINE), true, false); - } - - if (rogue.depthLevel == gameConst->deepestLevel) { - pmapAt(downLoc)->layers[DUNGEON] = DUNGEON_PORTAL; - } else { - pmapAt(downLoc)->layers[DUNGEON] = DOWN_STAIRS; - } - pmapAt(downLoc)->layers[LIQUID] = NOTHING; - pmapAt(downLoc)->layers[SURFACE] = NOTHING; - - if (!levels[n+1].visited) { - levels[n+1].upStairsLoc = downLoc; - } - levels[n].downStairsLoc = downLoc; - - pos upLoc; - if (getQualifyingGridLocNear(&upLoc, levels[n].upStairsLoc.x, levels[n].upStairsLoc.y, grid, false)) { - prepareForStairs(upLoc.x, upLoc.y, grid); - } else { // Hopefully this never happens. - getQualifyingLocNear(&upLoc, levels[n].upStairsLoc.x, levels[n].upStairsLoc.y, false, 0, - (T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_ITEMS | T_AUTO_DESCENT | T_IS_DEEP_WATER | T_LAVA_INSTA_DEATH | T_IS_DF_TRAP), - (HAS_MONSTER | HAS_ITEM | HAS_STAIRS | IS_IN_MACHINE), true, false); - } - - levels[n].upStairsLoc = upLoc; - - if (rogue.depthLevel == 1) { - pmapAt(upLoc)->layers[DUNGEON] = DUNGEON_EXIT; - } else { - pmapAt(upLoc)->layers[DUNGEON] = UP_STAIRS; - } - pmapAt(upLoc)->layers[LIQUID] = NOTHING; - pmapAt(upLoc)->layers[SURFACE] = NOTHING; - - rogue.downLoc = downLoc; - pmapAt(downLoc)->flags |= HAS_STAIRS; - rogue.upLoc = upLoc; - pmapAt(upLoc)->flags |= HAS_STAIRS; - - if (!levels[rogue.depthLevel-1].visited) { - - // Run a field of view check from up stairs so that monsters do not spawn within sight of it. - for (dir=0; dir<4; dir++) { - pos nextLoc = posNeighborInDirection(upLoc, dir); - if (isPosInMap(nextLoc) && !cellHasTerrainFlag(nextLoc.x, nextLoc.y, T_OBSTRUCTS_PASSABILITY)) { - upLoc = nextLoc; - break; - } - } - zeroOutGrid(grid); - getFOVMask(grid, upLoc.x, upLoc.y, max(DCOLS, DROWS) * FP_FACTOR, (T_OBSTRUCTS_VISION), 0, false); - for (i=0; inextItem; theItem != NULL; theItem = theItem->nextItem) { - restoreItem(theItem); - } - - // Restore creatures that fell from the previous depth or that have been pathing toward the stairs. - mapToStairs = allocGrid(); - fillGrid(mapToStairs, 0); - mapToPit = allocGrid(); - fillGrid(mapToPit, 0); - calculateDistances(mapToStairs, player.loc.x, player.loc.y, T_PATHING_BLOCKER, NULL, true, true); - calculateDistances(mapToPit, - levels[rogue.depthLevel - 1].playerExitedVia.x, - levels[rogue.depthLevel - 1].playerExitedVia.y, - T_PATHING_BLOCKER, - NULL, - true, - true); - for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) { - creature *monst = nextCreature(&it); - restoreMonster(monst, mapToStairs, mapToPit); + short i, j, dir; + short **mapToStairs, **mapToPit; + item *theItem; + char grid[DCOLS][DROWS]; + short n = rogue.depthLevel - 1; + + // Place the stairs. + + for (int i = 0; i < DCOLS; i++) { + for (int j = 0; j < DROWS; j++) { + grid[i][j] = validStairLoc(i, j); + } + } + + if (D_INSPECT_LEVELGEN) { + dumpLevelToScreen(); + hiliteCharGrid(grid, &teal, 100); + temporaryMessage("Stair location candidates:", REQUIRE_ACKNOWLEDGMENT); + } + + pos downLoc; + if (getQualifyingGridLocNear(&downLoc, levels[n].downStairsLoc.x, levels[n].downStairsLoc.y, grid, false)) { + prepareForStairs(downLoc.x, downLoc.y, grid); + } else { + getQualifyingLocNear(&downLoc, levels[n].downStairsLoc.x, levels[n].downStairsLoc.y, false, 0, + (T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_ITEMS | T_AUTO_DESCENT | T_IS_DEEP_WATER + | T_LAVA_INSTA_DEATH | T_IS_DF_TRAP), + (HAS_MONSTER | HAS_ITEM | HAS_STAIRS | IS_IN_MACHINE), true, false); + } + + if (rogue.depthLevel == gameConst->deepestLevel) { + pmapAt(downLoc)->layers[DUNGEON] = DUNGEON_PORTAL; + } else { + pmapAt(downLoc)->layers[DUNGEON] = DOWN_STAIRS; + } + pmapAt(downLoc)->layers[LIQUID] = NOTHING; + pmapAt(downLoc)->layers[SURFACE] = NOTHING; + + if (!levels[n + 1].visited) { + levels[n + 1].upStairsLoc = downLoc; + } + levels[n].downStairsLoc = downLoc; + + pos upLoc; + if (getQualifyingGridLocNear(&upLoc, levels[n].upStairsLoc.x, levels[n].upStairsLoc.y, grid, false)) { + prepareForStairs(upLoc.x, upLoc.y, grid); + } else { // Hopefully this never happens. + getQualifyingLocNear(&upLoc, levels[n].upStairsLoc.x, levels[n].upStairsLoc.y, false, 0, + (T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_ITEMS | T_AUTO_DESCENT | T_IS_DEEP_WATER + | T_LAVA_INSTA_DEATH | T_IS_DF_TRAP), + (HAS_MONSTER | HAS_ITEM | HAS_STAIRS | IS_IN_MACHINE), true, false); + } + + levels[n].upStairsLoc = upLoc; + + if (rogue.depthLevel == 1) { + pmapAt(upLoc)->layers[DUNGEON] = DUNGEON_EXIT; + } else { + pmapAt(upLoc)->layers[DUNGEON] = UP_STAIRS; + } + pmapAt(upLoc)->layers[LIQUID] = NOTHING; + pmapAt(upLoc)->layers[SURFACE] = NOTHING; + + rogue.downLoc = downLoc; + pmapAt(downLoc)->flags |= HAS_STAIRS; + rogue.upLoc = upLoc; + pmapAt(upLoc)->flags |= HAS_STAIRS; + + if (!levels[rogue.depthLevel - 1].visited) { + + // Run a field of view check from up stairs so that monsters do not spawn within sight of it. + for (dir = 0; dir < 4; dir++) { + pos nextLoc = posNeighborInDirection(upLoc, dir); + if (isPosInMap(nextLoc) && !cellHasTerrainFlag(nextLoc.x, nextLoc.y, T_OBSTRUCTS_PASSABILITY)) { + upLoc = nextLoc; + break; + } } - freeGrid(mapToStairs); - freeGrid(mapToPit); + zeroOutGrid(grid); + getFOVMask(grid, upLoc.x, upLoc.y, max(DCOLS, DROWS) * FP_FACTOR, (T_OBSTRUCTS_VISION), 0, false); + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + if (grid[i][j]) { + pmap[i][j].flags |= IN_FIELD_OF_VIEW; + } + } + } + populateItems(upLoc.x, upLoc.y); + populateMonsters(); + } + + // Restore items that fell from the previous depth. + for (theItem = floorItems->nextItem; theItem != NULL; theItem = theItem->nextItem) { + restoreItem(theItem); + } + + // Restore creatures that fell from the previous depth or that have been pathing toward the stairs. + mapToStairs = allocGrid(); + fillGrid(mapToStairs, 0); + mapToPit = allocGrid(); + fillGrid(mapToPit, 0); + calculateDistances(mapToStairs, player.loc.x, player.loc.y, T_PATHING_BLOCKER, NULL, true, true); + calculateDistances(mapToPit, levels[rogue.depthLevel - 1].playerExitedVia.x, + levels[rogue.depthLevel - 1].playerExitedVia.y, T_PATHING_BLOCKER, NULL, true, true); + for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) { + creature *monst = nextCreature(&it); + restoreMonster(monst, mapToStairs, mapToPit); + } + freeGrid(mapToStairs); + freeGrid(mapToPit); } // fills (*x, *y) with the coordinates of a random cell with @@ -3754,18 +3723,21 @@ void initializeLevel() { // or at least one layer of type terrainType. // A dungeon, liquid type of -1 will match anything. boolean randomMatchingLocation(short *x, short *y, short dungeonType, short liquidType, short terrainType) { - short failsafeCount = 0; - do { - failsafeCount++; - *x = rand_range(0, DCOLS - 1); - *y = rand_range(0, DROWS - 1); - } while (failsafeCount < 500 && ((terrainType >= 0 && !cellHasTerrainType(*x, *y, terrainType)) - || (((dungeonType >= 0 && pmap[*x][*y].layers[DUNGEON] != dungeonType) || (liquidType >= 0 && pmap[*x][*y].layers[LIQUID] != liquidType)) && terrainType < 0) - || (pmap[*x][*y].flags & (HAS_PLAYER | HAS_MONSTER | HAS_STAIRS | HAS_ITEM | IS_IN_MACHINE)) - || (terrainType < 0 && !(tileCatalog[dungeonType].flags & T_OBSTRUCTS_ITEMS) - && cellHasTerrainFlag(*x, *y, T_OBSTRUCTS_ITEMS)))); - if (failsafeCount >= 500) { - return false; - } - return true; + short failsafeCount = 0; + do { + failsafeCount++; + *x = rand_range(0, DCOLS - 1); + *y = rand_range(0, DROWS - 1); + } while (failsafeCount < 500 + && ((terrainType >= 0 && !cellHasTerrainType(*x, *y, terrainType)) + || (((dungeonType >= 0 && pmap[*x][*y].layers[DUNGEON] != dungeonType) + || (liquidType >= 0 && pmap[*x][*y].layers[LIQUID] != liquidType)) + && terrainType < 0) + || (pmap[*x][*y].flags & (HAS_PLAYER | HAS_MONSTER | HAS_STAIRS | HAS_ITEM | IS_IN_MACHINE)) + || (terrainType < 0 && !(tileCatalog[dungeonType].flags & T_OBSTRUCTS_ITEMS) + && cellHasTerrainFlag(*x, *y, T_OBSTRUCTS_ITEMS)))); + if (failsafeCount >= 500) { + return false; + } + return true; } diff --git a/src/brogue/Buttons.c b/src/brogue/Buttons.c index 35b49ffe..839fa98f 100644 --- a/src/brogue/Buttons.c +++ b/src/brogue/Buttons.c @@ -30,7 +30,7 @@ // Draws the smooth gradient that appears on a button when you hover over or depress it. // Returns the percentage by which the current tile should be averaged toward a hilite color. short smoothHiliteGradient(const short currentXValue, const short maxXValue) { - return (short) (100 * sin(3.14159265 * currentXValue / maxXValue)); + return (short)(100 * sin(3.14159265 * currentXValue / maxXValue)); } // Draws the button to the screen, or to a display buffer if one is given. @@ -39,133 +39,128 @@ short smoothHiliteGradient(const short currentXValue, const short maxXValue) { // Hovering highlight augments fore and back colors with buttonHoverColor by 20%. // Pressed darkens the middle color (or turns it the hover color if the button is black). void drawButton(brogueButton *button, enum buttonDrawStates highlight, cellDisplayBuffer dbuf[COLS][ROWS]) { - if (!(button->flags & B_DRAW)) { - return; - } - //assureCosmeticRNG; - short oldRNG = rogue.RNG; - rogue.RNG = RNG_COSMETIC; - - const int width = strLenWithoutEscapes(button->text); - color bColorBase = button->buttonColor; - color fColorBase = ((button->flags & B_ENABLED) ? white : gray); - - if (highlight == BUTTON_HOVER && (button->flags & B_HOVER_ENABLED)) { - //applyColorAugment(&fColorBase, &buttonHoverColor, 20); - //applyColorAugment(&bColorBase, &buttonHoverColor, 20); - applyColorAverage(&fColorBase, &buttonHoverColor, 25); - applyColorAverage(&bColorBase, &buttonHoverColor, 25); + if (!(button->flags & B_DRAW)) { + return; + } + // assureCosmeticRNG; + short oldRNG = rogue.RNG; + rogue.RNG = RNG_COSMETIC; + + const int width = strLenWithoutEscapes(button->text); + color bColorBase = button->buttonColor; + color fColorBase = ((button->flags & B_ENABLED) ? white : gray); + + if (highlight == BUTTON_HOVER && (button->flags & B_HOVER_ENABLED)) { + // applyColorAugment(&fColorBase, &buttonHoverColor, 20); + // applyColorAugment(&bColorBase, &buttonHoverColor, 20); + applyColorAverage(&fColorBase, &buttonHoverColor, 25); + applyColorAverage(&bColorBase, &buttonHoverColor, 25); + } + + color bColorEdge = bColorBase; + color bColorMid = bColorBase; + applyColorAverage(&bColorEdge, &black, 50); + + if (highlight == BUTTON_PRESSED) { + applyColorAverage(&bColorMid, &black, 75); + if (COLOR_DIFF(bColorMid, bColorBase) < 50) { + bColorMid = bColorBase; + applyColorAverage(&bColorMid, &buttonHoverColor, 50); } + } + color bColor = bColorMid; - color bColorEdge = bColorBase; - color bColorMid = bColorBase; - applyColorAverage(&bColorEdge, &black, 50); + short opacity = button->opacity; + if (highlight == BUTTON_HOVER || highlight == BUTTON_PRESSED) { + opacity = 100 - ((100 - opacity) * opacity / 100); // Apply the opacity twice. + } - if (highlight == BUTTON_PRESSED) { - applyColorAverage(&bColorMid, &black, 75); - if (COLOR_DIFF(bColorMid, bColorBase) < 50) { - bColorMid = bColorBase; - applyColorAverage(&bColorMid, &buttonHoverColor, 50); - } - } - color bColor = bColorMid; + short symbolNumber = 0; - short opacity = button->opacity; - if (highlight == BUTTON_HOVER || highlight == BUTTON_PRESSED) { - opacity = 100 - ((100 - opacity) * opacity / 100); // Apply the opacity twice. + for (int i = 0, textLoc = 0; i < width && i + button->x < COLS; i++, textLoc++) { + while (button->text[textLoc] == COLOR_ESCAPE) { + textLoc = decodeMessageColor(button->text, textLoc, &fColorBase); } - short symbolNumber = 0; - - for (int i = 0, textLoc = 0; i < width && i + button->x < COLS; i++, textLoc++) { - while (button->text[textLoc] == COLOR_ESCAPE) { - textLoc = decodeMessageColor(button->text, textLoc, &fColorBase); - } + color fColor = fColorBase; - color fColor = fColorBase; - - if (button->flags & B_GRADIENT) { - const int midPercent = smoothHiliteGradient(i, width - 1); - bColor = bColorEdge; - applyColorAverage(&bColor, &bColorMid, midPercent); - } + if (button->flags & B_GRADIENT) { + const int midPercent = smoothHiliteGradient(i, width - 1); + bColor = bColorEdge; + applyColorAverage(&bColor, &bColorMid, midPercent); + } - if (highlight == BUTTON_PRESSED) { - applyColorAverage(&fColor, &bColor, 30); - } + if (highlight == BUTTON_PRESSED) { + applyColorAverage(&fColor, &bColor, 30); + } - if (button->opacity < 100) { - applyColorAverage(&fColor, &bColor, 100 - opacity); - } + if (button->opacity < 100) { + applyColorAverage(&fColor, &bColor, 100 - opacity); + } - bakeColor(&fColor); - bakeColor(&bColor); - separateColors(&fColor, &bColor); + bakeColor(&fColor); + bakeColor(&bColor); + separateColors(&fColor, &bColor); - enum displayGlyph displayCharacter = button->text[textLoc]; - if (button->text[textLoc] == '*') { - if (button->symbol[symbolNumber]) { - displayCharacter = button->symbol[symbolNumber]; - } - symbolNumber++; - } + enum displayGlyph displayCharacter = button->text[textLoc]; + if (button->text[textLoc] == '*') { + if (button->symbol[symbolNumber]) { + displayCharacter = button->symbol[symbolNumber]; + } + symbolNumber++; + } - if (locIsInWindow((windowpos){ button->x + i, button->y })) { - plotCharToBuffer(displayCharacter, (windowpos){ button->x + i, button->y }, &fColor, &bColor, dbuf); - if (dbuf) { - // Only buffers can have opacity set. - dbuf[button->x + i][button->y].opacity = opacity; - } - } + if (locIsInWindow((windowpos){button->x + i, button->y})) { + plotCharToBuffer(displayCharacter, (windowpos){button->x + i, button->y}, &fColor, &bColor, dbuf); + if (dbuf) { + // Only buffers can have opacity set. + dbuf[button->x + i][button->y].opacity = opacity; + } } - restoreRNG; + } + restoreRNG; } void initializeButton(brogueButton *button) { - memset((void *) button, 0, sizeof( brogueButton )); - button->text[0] = '\0'; - button->flags |= (B_ENABLED | B_GRADIENT | B_HOVER_ENABLED | B_DRAW | B_KEYPRESS_HIGHLIGHT); - button->buttonColor = interfaceButtonColor; - button->opacity = 100; + memset((void *)button, 0, sizeof(brogueButton)); + button->text[0] = '\0'; + button->flags |= (B_ENABLED | B_GRADIENT | B_HOVER_ENABLED | B_DRAW | B_KEYPRESS_HIGHLIGHT); + button->buttonColor = interfaceButtonColor; + button->opacity = 100; } void drawButtonsInState(buttonState *state) { - // Draw the buttons to the dbuf: - for (int i=0; i < state->buttonCount; i++) { - if (state->buttons[i].flags & B_DRAW) { - drawButton(&(state->buttons[i]), BUTTON_NORMAL, state->dbuf); - } + // Draw the buttons to the dbuf: + for (int i = 0; i < state->buttonCount; i++) { + if (state->buttons[i].flags & B_DRAW) { + drawButton(&(state->buttons[i]), BUTTON_NORMAL, state->dbuf); } + } } -void initializeButtonState(buttonState *state, - brogueButton *buttons, - short buttonCount, - short winX, - short winY, - short winWidth, - short winHeight) { - // Initialize variables for the state struct: - state->buttonChosen = state->buttonFocused = state->buttonDepressed = -1; - state->buttonCount = buttonCount; - state->winX = winX; - state->winY = winY; - state->winWidth = winWidth; - state->winHeight = winHeight; - for (int i=0; i < state->buttonCount; i++) { - state->buttons[i] = buttons[i]; - } - copyDisplayBuffer(state->rbuf, displayBuffer); - clearDisplayBuffer(state->dbuf); - - drawButtonsInState(state); - - // Clear the rbuf so that it resets only those parts of the screen in which buttons are drawn in the first place: - for (int i=0; irbuf[i][j].opacity = (state->dbuf[i][j].opacity ? 100 : 0); - } +void initializeButtonState(buttonState *state, brogueButton *buttons, short buttonCount, short winX, short winY, + short winWidth, short winHeight) { + // Initialize variables for the state struct: + state->buttonChosen = state->buttonFocused = state->buttonDepressed = -1; + state->buttonCount = buttonCount; + state->winX = winX; + state->winY = winY; + state->winWidth = winWidth; + state->winHeight = winHeight; + for (int i = 0; i < state->buttonCount; i++) { + state->buttons[i] = buttons[i]; + } + copyDisplayBuffer(state->rbuf, displayBuffer); + clearDisplayBuffer(state->dbuf); + + drawButtonsInState(state); + + // Clear the rbuf so that it resets only those parts of the screen in which buttons are drawn in the first place: + for (int i = 0; i < COLS; i++) { + for (int j = 0; j < ROWS; j++) { + state->rbuf[i][j].opacity = (state->dbuf[i][j].opacity ? 100 : 0); } + } } // Processes one round of user input, and bakes the necessary graphical changes into state->dbuf. @@ -177,181 +172,173 @@ void initializeButtonState(buttonState *state, // Otherwise, returns -1. That can be if the user canceled (in which case *canceled is true), // or, more commonly, if the user's input in this particular split-second round was not decisive. short processButtonInput(buttonState *state, boolean *canceled, rogueEvent *event) { - boolean buttonUsed = false; + boolean buttonUsed = false; - // Mouse event: - if (event->eventType == MOUSE_DOWN - || event->eventType == MOUSE_UP - || event->eventType == MOUSE_ENTERED_CELL) { + // Mouse event: + if (event->eventType == MOUSE_DOWN || event->eventType == MOUSE_UP || event->eventType == MOUSE_ENTERED_CELL) { - int x = event->param1; - int y = event->param2; + int x = event->param1; + int y = event->param2; - // Revert the button with old focus, if any. - if (state->buttonFocused >= 0) { - drawButton(&(state->buttons[state->buttonFocused]), BUTTON_NORMAL, state->dbuf); - state->buttonFocused = -1; - } + // Revert the button with old focus, if any. + if (state->buttonFocused >= 0) { + drawButton(&(state->buttons[state->buttonFocused]), BUTTON_NORMAL, state->dbuf); + state->buttonFocused = -1; + } - // Find the button with new focus, if any. - int focusIndex; - for (focusIndex=0; focusIndex < state->buttonCount; focusIndex++) { - if ((state->buttons[focusIndex].flags & B_DRAW) - && (state->buttons[focusIndex].flags & B_ENABLED) - && (state->buttons[focusIndex].y == y || ((state->buttons[focusIndex].flags & B_WIDE_CLICK_AREA) && abs(state->buttons[focusIndex].y - y) <= 1)) - && x >= state->buttons[focusIndex].x - && x < state->buttons[focusIndex].x + strLenWithoutEscapes(state->buttons[focusIndex].text)) { - - state->buttonFocused = focusIndex; - if (event->eventType == MOUSE_DOWN) { - state->buttonDepressed = focusIndex; // Keeps track of which button is down at the moment. Cleared on mouseup. - } - break; - } - } - if (focusIndex == state->buttonCount) { // No focus this round. - state->buttonFocused = -1; + // Find the button with new focus, if any. + int focusIndex; + for (focusIndex = 0; focusIndex < state->buttonCount; focusIndex++) { + if ((state->buttons[focusIndex].flags & B_DRAW) && (state->buttons[focusIndex].flags & B_ENABLED) + && (state->buttons[focusIndex].y == y + || ((state->buttons[focusIndex].flags & B_WIDE_CLICK_AREA) && abs(state->buttons[focusIndex].y - y) <= 1)) + && x >= state->buttons[focusIndex].x + && x < state->buttons[focusIndex].x + strLenWithoutEscapes(state->buttons[focusIndex].text)) { + + state->buttonFocused = focusIndex; + if (event->eventType == MOUSE_DOWN) { + state->buttonDepressed = focusIndex; // Keeps track of which button is down at the moment. Cleared on mouseup. } + break; + } + } + if (focusIndex == state->buttonCount) { // No focus this round. + state->buttonFocused = -1; + } + if (state->buttonDepressed >= 0) { + if (state->buttonDepressed == state->buttonFocused) { + drawButton(&(state->buttons[state->buttonDepressed]), BUTTON_PRESSED, state->dbuf); + } + } else if (state->buttonFocused >= 0) { + // If no button is depressed, then update the appearance of the button with the new focus, if any. + drawButton(&(state->buttons[state->buttonFocused]), BUTTON_HOVER, state->dbuf); + } + + // Mouseup: + if (event->eventType == MOUSE_UP) { + if (state->buttonDepressed == state->buttonFocused && state->buttonFocused >= 0) { + // If a button is depressed, and the mouseup happened on that button, it has been chosen and we're done. + buttonUsed = true; + } else { + // Otherwise, no button is depressed. If one was previously depressed, redraw it. if (state->buttonDepressed >= 0) { - if (state->buttonDepressed == state->buttonFocused) { - drawButton(&(state->buttons[state->buttonDepressed]), BUTTON_PRESSED, state->dbuf); - } - } else if (state->buttonFocused >= 0) { - // If no button is depressed, then update the appearance of the button with the new focus, if any. - drawButton(&(state->buttons[state->buttonFocused]), BUTTON_HOVER, state->dbuf); + drawButton(&(state->buttons[state->buttonDepressed]), BUTTON_NORMAL, state->dbuf); + } else if (!(x >= state->winX && x < state->winX + state->winWidth && y >= state->winY + && y < state->winY + state->winHeight)) { + // Clicking outside of a button means canceling. + if (canceled) { + *canceled = true; + } } - // Mouseup: - if (event->eventType == MOUSE_UP) { - if (state->buttonDepressed == state->buttonFocused && state->buttonFocused >= 0) { - // If a button is depressed, and the mouseup happened on that button, it has been chosen and we're done. - buttonUsed = true; - } else { - // Otherwise, no button is depressed. If one was previously depressed, redraw it. - if (state->buttonDepressed >= 0) { - drawButton(&(state->buttons[state->buttonDepressed]), BUTTON_NORMAL, state->dbuf); - } else if (!(x >= state->winX && x < state->winX + state->winWidth - && y >= state->winY && y < state->winY + state->winHeight)) { - // Clicking outside of a button means canceling. - if (canceled) { - *canceled = true; - } - } - - if (state->buttonFocused >= 0) { - // Buttons don't hover-highlight when one is depressed, so we have to fix that when the mouse is up. - drawButton(&(state->buttons[state->buttonFocused]), BUTTON_HOVER, state->dbuf); - } - state->buttonDepressed = -1; - } + if (state->buttonFocused >= 0) { + // Buttons don't hover-highlight when one is depressed, so we have to fix that when the mouse is up. + drawButton(&(state->buttons[state->buttonFocused]), BUTTON_HOVER, state->dbuf); } + state->buttonDepressed = -1; + } } + } + + // Keystroke: + if (event->eventType == KEYSTROKE) { - // Keystroke: - if (event->eventType == KEYSTROKE) { - - // Cycle through all of the hotkeys of all of the buttons. - for (int i=0; i < state->buttonCount; i++) { - for (int k = 0; k < 10 && state->buttons[i].hotkey[k]; k++) { - if (event->param1 == state->buttons[i].hotkey[k]) { - // This button was chosen. - - if (state->buttons[i].flags & B_DRAW) { - // Restore the depressed and focused buttons. - if (state->buttonDepressed >= 0) { - drawButton(&(state->buttons[state->buttonDepressed]), BUTTON_NORMAL, state->dbuf); - } - if (state->buttonFocused >= 0) { - drawButton(&(state->buttons[state->buttonFocused]), BUTTON_NORMAL, state->dbuf); - } - - // If the button likes to flash when keypressed: - if (state->buttons[i].flags & B_KEYPRESS_HIGHLIGHT) { - // Depress the chosen button. - drawButton(&(state->buttons[i]), BUTTON_PRESSED, state->dbuf); - - // Update the display. - overlayDisplayBuffer(state->rbuf, NULL); - overlayDisplayBuffer(state->dbuf, NULL); - - if (!rogue.playbackMode || rogue.playbackPaused) { - // Wait for a little; then we're done. - pauseBrogue(50); - } else { - // Wait long enough for the viewer to see what was selected. - pauseAnimation(1000); - } - } - } - - state->buttonDepressed = i; - buttonUsed = true; - break; - } + // Cycle through all of the hotkeys of all of the buttons. + for (int i = 0; i < state->buttonCount; i++) { + for (int k = 0; k < 10 && state->buttons[i].hotkey[k]; k++) { + if (event->param1 == state->buttons[i].hotkey[k]) { + // This button was chosen. + + if (state->buttons[i].flags & B_DRAW) { + // Restore the depressed and focused buttons. + if (state->buttonDepressed >= 0) { + drawButton(&(state->buttons[state->buttonDepressed]), BUTTON_NORMAL, state->dbuf); + } + if (state->buttonFocused >= 0) { + drawButton(&(state->buttons[state->buttonFocused]), BUTTON_NORMAL, state->dbuf); } - } - if (!buttonUsed - && (event->param1 == ESCAPE_KEY || event->param1 == ACKNOWLEDGE_KEY)) { - // If the player pressed escape, we're done. - if (canceled) { - *canceled = true; + // If the button likes to flash when keypressed: + if (state->buttons[i].flags & B_KEYPRESS_HIGHLIGHT) { + // Depress the chosen button. + drawButton(&(state->buttons[i]), BUTTON_PRESSED, state->dbuf); + + // Update the display. + overlayDisplayBuffer(state->rbuf, NULL); + overlayDisplayBuffer(state->dbuf, NULL); + + if (!rogue.playbackMode || rogue.playbackPaused) { + // Wait for a little; then we're done. + pauseBrogue(50); + } else { + // Wait long enough for the viewer to see what was selected. + pauseAnimation(1000); + } } + } + + state->buttonDepressed = i; + buttonUsed = true; + break; } + } } - if (buttonUsed) { - state->buttonChosen = state->buttonDepressed; - return state->buttonChosen; - } else { - return -1; + if (!buttonUsed && (event->param1 == ESCAPE_KEY || event->param1 == ACKNOWLEDGE_KEY)) { + // If the player pressed escape, we're done. + if (canceled) { + *canceled = true; + } } + } + + if (buttonUsed) { + state->buttonChosen = state->buttonDepressed; + return state->buttonChosen; + } else { + return -1; + } } // Displays a bunch of buttons and collects user input. // Returns the index number of the chosen button, or -1 if the user cancels. // A window region is described by winX, winY, winWidth and winHeight. // Clicking outside of that region will constitute canceling. -short buttonInputLoop(brogueButton *buttons, - short buttonCount, - short winX, - short winY, - short winWidth, - short winHeight, +short buttonInputLoop(brogueButton *buttons, short buttonCount, short winX, short winY, short winWidth, short winHeight, rogueEvent *returnEvent) { - short button; - boolean canceled; - rogueEvent theEvent; - buttonState state = {0}; + short button; + boolean canceled; + rogueEvent theEvent; + buttonState state = {0}; - assureCosmeticRNG; + assureCosmeticRNG; - canceled = false; - initializeButtonState(&state, buttons, buttonCount, winX, winY, winWidth, winHeight); + canceled = false; + initializeButtonState(&state, buttons, buttonCount, winX, winY, winWidth, winHeight); - do { - // Update the display. - overlayDisplayBuffer(state.dbuf, NULL); + do { + // Update the display. + overlayDisplayBuffer(state.dbuf, NULL); - // Get input. - nextBrogueEvent(&theEvent, true, false, false); + // Get input. + nextBrogueEvent(&theEvent, true, false, false); - // Process the input. - button = processButtonInput(&state, &canceled, &theEvent); + // Process the input. + button = processButtonInput(&state, &canceled, &theEvent); - // Revert the display. - overlayDisplayBuffer(state.rbuf, NULL); + // Revert the display. + overlayDisplayBuffer(state.rbuf, NULL); - } while (button == -1 && !canceled); + } while (button == -1 && !canceled); - if (returnEvent) { - *returnEvent = theEvent; - } + if (returnEvent) { + *returnEvent = theEvent; + } - //overlayDisplayBuffer(dbuf, NULL); // hangs around + // overlayDisplayBuffer(dbuf, NULL); // hangs around - restoreRNG; + restoreRNG; - return button; + return button; } diff --git a/src/brogue/Combat.c b/src/brogue/Combat.c index 4383729a..1d37ecfa 100644 --- a/src/brogue/Combat.c +++ b/src/brogue/Combat.c @@ -25,7 +25,6 @@ #include "GlobalsBase.h" #include "Globals.h" - /* Combat rules: * Each combatant has an accuracy rating. This is the percentage of their attacks that will ordinarily hit; * higher numbers are better for them. Numbers over 100 are permitted. @@ -50,118 +49,115 @@ * 0-10 with CF 4 would be 2d3 + 2d2. By playing with the numbers, one can approximate a gaussian * distribution of any mean and standard deviation. * - * Player combatants take their base defense value of their actual armor. Their accuracy is a combination of weapon, armor - * and strength. + * Player combatants take their base defense value of their actual armor. Their accuracy is a combination of weapon, + * armor and strength. * * Players have a base accuracy value of 100 throughout the game. Each point of weapon enchantment (net of * strength penalty/benefit) increases */ fixpt strengthModifier(item *theItem) { - int difference = (rogue.strength - player.weaknessAmount) - theItem->strengthRequired; - if (difference > 0) { - return difference * FP_FACTOR / 4; // 0.25x - } else { - return difference * FP_FACTOR * 5/2; // 2.5x - } + int difference = (rogue.strength - player.weaknessAmount) - theItem->strengthRequired; + if (difference > 0) { + return difference * FP_FACTOR / 4; // 0.25x + } else { + return difference * FP_FACTOR * 5 / 2; // 2.5x + } } fixpt netEnchant(item *theItem) { - fixpt retval = theItem->enchant1 * FP_FACTOR; - if (theItem->category & (WEAPON | ARMOR)) { - retval += strengthModifier(theItem); - } - // Clamp all net enchantment values to [-20, 50]. - return clamp(retval, -20 * FP_FACTOR, 50 * FP_FACTOR); + fixpt retval = theItem->enchant1 * FP_FACTOR; + if (theItem->category & (WEAPON | ARMOR)) { + retval += strengthModifier(theItem); + } + // Clamp all net enchantment values to [-20, 50]. + return clamp(retval, -20 * FP_FACTOR, 50 * FP_FACTOR); } fixpt monsterDamageAdjustmentAmount(const creature *monst) { - if (monst == &player) { - // Handled through player strength routines elsewhere. - return FP_FACTOR; - } else { - return damageFraction(monst->weaknessAmount * FP_FACTOR * -3/2); - } + if (monst == &player) { + // Handled through player strength routines elsewhere. + return FP_FACTOR; + } else { + return damageFraction(monst->weaknessAmount * FP_FACTOR * -3 / 2); + } } short monsterDefenseAdjusted(const creature *monst) { - short retval; - if (monst == &player) { - // Weakness is already taken into account in recalculateEquipmentBonuses() for the player. - retval = monst->info.defense; - } else { - retval = monst->info.defense - 25 * monst->weaknessAmount; - } - return max(retval, 0); + short retval; + if (monst == &player) { + // Weakness is already taken into account in recalculateEquipmentBonuses() for the player. + retval = monst->info.defense; + } else { + retval = monst->info.defense - 25 * monst->weaknessAmount; + } + return max(retval, 0); } short monsterAccuracyAdjusted(const creature *monst) { - short retval = monst->info.accuracy * accuracyFraction(monst->weaknessAmount * FP_FACTOR * -3/2) / FP_FACTOR; - return max(retval, 0); + short retval = monst->info.accuracy * accuracyFraction(monst->weaknessAmount * FP_FACTOR * -3 / 2) / FP_FACTOR; + return max(retval, 0); } // does NOT account for auto-hit from sleeping or unaware defenders; does account for auto-hit from // stuck or captive defenders and from weapons of slaying. short hitProbability(creature *attacker, creature *defender) { - short accuracy = monsterAccuracyAdjusted(attacker); - short defense = monsterDefenseAdjusted(defender); - short hitProbability; - - if (defender->status[STATUS_STUCK] || (defender->bookkeepingFlags & MB_CAPTIVE)) { - return 100; - } - if ((defender->bookkeepingFlags & MB_SEIZED) - && (attacker->bookkeepingFlags & MB_SEIZING)) { - - return 100; - } - if (attacker == &player && rogue.weapon) { - if ((rogue.weapon->flags & ITEM_RUNIC) - && rogue.weapon->enchant2 == W_SLAYING - && monsterIsInClass(defender, rogue.weapon->vorpalEnemy)) { - - return 100; - } - accuracy = player.info.accuracy * accuracyFraction(netEnchant(rogue.weapon)) / FP_FACTOR; - } - hitProbability = accuracy * defenseFraction(defense * FP_FACTOR) / FP_FACTOR; - if (hitProbability > 100) { - hitProbability = 100; - } else if (hitProbability < 0) { - hitProbability = 0; - } - return hitProbability; + short accuracy = monsterAccuracyAdjusted(attacker); + short defense = monsterDefenseAdjusted(defender); + short hitProbability; + + if (defender->status[STATUS_STUCK] || (defender->bookkeepingFlags & MB_CAPTIVE)) { + return 100; + } + if ((defender->bookkeepingFlags & MB_SEIZED) && (attacker->bookkeepingFlags & MB_SEIZING)) { + + return 100; + } + if (attacker == &player && rogue.weapon) { + if ((rogue.weapon->flags & ITEM_RUNIC) && rogue.weapon->enchant2 == W_SLAYING + && monsterIsInClass(defender, rogue.weapon->vorpalEnemy)) { + + return 100; + } + accuracy = player.info.accuracy * accuracyFraction(netEnchant(rogue.weapon)) / FP_FACTOR; + } + hitProbability = accuracy * defenseFraction(defense * FP_FACTOR) / FP_FACTOR; + if (hitProbability > 100) { + hitProbability = 100; + } else if (hitProbability < 0) { + hitProbability = 0; + } + return hitProbability; } boolean attackHit(creature *attacker, creature *defender) { - // automatically hit if the monster is sleeping or captive or stuck in a web - if (defender->status[STATUS_STUCK] - || defender->status[STATUS_PARALYZED] - || (defender->bookkeepingFlags & MB_CAPTIVE)) { + // automatically hit if the monster is sleeping or captive or stuck in a web + if (defender->status[STATUS_STUCK] || defender->status[STATUS_PARALYZED] + || (defender->bookkeepingFlags & MB_CAPTIVE)) { - return true; - } + return true; + } - return rand_percent(hitProbability(attacker, defender)); + return rand_percent(hitProbability(attacker, defender)); } void addMonsterToContiguousMonsterGrid(short x, short y, creature *monst, char grid[DCOLS][DROWS]) { - short newX, newY; - enum directions dir; - creature *tempMonst; - - grid[x][y] = true; - for (dir=0; dir<4; dir++) { - newX = x + nbDirs[dir][0]; - newY = y + nbDirs[dir][1]; - - if (coordinatesAreInMap(newX, newY) && !grid[newX][newY]) { - tempMonst = monsterAtLoc((pos){ newX, newY }); - if (tempMonst && monstersAreTeammates(monst, tempMonst)) { - addMonsterToContiguousMonsterGrid(newX, newY, monst, grid); - } - } - } + short newX, newY; + enum directions dir; + creature *tempMonst; + + grid[x][y] = true; + for (dir = 0; dir < 4; dir++) { + newX = x + nbDirs[dir][0]; + newY = y + nbDirs[dir][1]; + + if (coordinatesAreInMap(newX, newY) && !grid[newX][newY]) { + tempMonst = monsterAtLoc((pos){newX, newY}); + if (tempMonst && monstersAreTeammates(monst, tempMonst)) { + addMonsterToContiguousMonsterGrid(newX, newY, monst, grid); + } + } + } } // Splits a monster in half. @@ -170,1130 +166,1093 @@ void addMonsterToContiguousMonsterGrid(short x, short y, creature *monst, char g // The contiguous group is supplemented with the given (x, y) coordinates, if any; // this is so that jellies et al. can spawn behind the player in a hallway. void splitMonster(creature *monst, pos loc) { - char buf[DCOLS * 3]; - char monstName[DCOLS]; - char monsterGrid[DCOLS][DROWS], eligibleGrid[DCOLS][DROWS]; - creature *clone; - - zeroOutGrid(monsterGrid); - zeroOutGrid(eligibleGrid); - int eligibleLocationCount = 0; - - // Add the (x, y) location to the contiguous group, if any. - if (isPosInMap(loc)) { - monsterGrid[loc.x][loc.y] = true; - } - - // Find the contiguous group of monsters. - addMonsterToContiguousMonsterGrid(monst->loc.x, monst->loc.y, monst, monsterGrid); - - // Find the eligible edges around the group of monsters. - for (int i=0; iloc.x, monst->loc.y, monst, monsterGrid); + + // Find the eligible edges around the group of monsters. + for (int i = 0; i < DCOLS; i++) { + for (int j = 0; j < DROWS; j++) { + if (monsterGrid[i][j]) { + for (int dir = 0; dir < 4; dir++) { + const int newX = i + nbDirs[dir][0]; + const int newY = j + nbDirs[dir][1]; + if (coordinatesAreInMap(newX, newY) && !eligibleGrid[newX][newY] && !monsterGrid[newX][newY] + && !(pmap[newX][newY].flags & (HAS_PLAYER | HAS_MONSTER)) && !monsterAvoids(monst, (pos){newX, newY})) { + + eligibleGrid[newX][newY] = true; + eligibleLocationCount++; + } } - } -// DEBUG { -// hiliteCharGrid(eligibleGrid, &green, 75); -// hiliteCharGrid(monsterGrid, &blue, 75); -// temporaryMessage("Jelly spawn possibilities (green = eligible, blue = monster):", REQUIRE_ACKNOWLEDGMENT); -// displayLevel(); -// } - - // Pick a random location on the eligibleGrid and add the clone there. - if (eligibleLocationCount) { - int randIndex = rand_range(1, eligibleLocationCount); - for (int i=0; icurrentHP = (monst->currentHP + 1) / 2; - clone = cloneMonster(monst, false, false); - - // Split monsters don't inherit the learnings of their parents. - // Sorry, but self-healing jelly armies are too much. - // Mutation effects can be inherited, however; they're not learned abilities. - if (monst->mutationIndex >= 0) { - clone->info.flags &= (monsterCatalog[clone->info.monsterID].flags | mutationCatalog[monst->mutationIndex].monsterFlags); - clone->info.abilityFlags &= (monsterCatalog[clone->info.monsterID].abilityFlags | mutationCatalog[monst->mutationIndex].monsterAbilityFlags); - } else { - clone->info.flags &= monsterCatalog[clone->info.monsterID].flags; - clone->info.abilityFlags &= monsterCatalog[clone->info.monsterID].abilityFlags; - } - for (int b = 0; b < 20; b++) { - clone->info.bolts[b] = monsterCatalog[clone->info.monsterID].bolts[b]; - } - - if (!(clone->info.flags & MONST_FLIES) - && clone->status[STATUS_LEVITATING] == 1000) { - - clone->status[STATUS_LEVITATING] = 0; - } - - clone->loc = (pos){.x = i, .y = j}; - pmap[i][j].flags |= HAS_MONSTER; - clone->ticksUntilTurn = max(clone->ticksUntilTurn, 101); - fadeInMonster(clone); - refreshSideBar(-1, -1, false); - - if (canDirectlySeeMonster(monst)) { - sprintf(buf, "%s splits in two!", monstName); - message(buf, 0); - } - - return; - } - } + } + } + } + // DEBUG { + // hiliteCharGrid(eligibleGrid, &green, 75); + // hiliteCharGrid(monsterGrid, &blue, 75); + // temporaryMessage("Jelly spawn possibilities (green = eligible, blue = monster):", REQUIRE_ACKNOWLEDGMENT); + // displayLevel(); + // } + + // Pick a random location on the eligibleGrid and add the clone there. + if (eligibleLocationCount) { + int randIndex = rand_range(1, eligibleLocationCount); + for (int i = 0; i < DCOLS; i++) { + for (int j = 0; j < DROWS; j++) { + if (eligibleGrid[i][j] && !--randIndex) { + // Found the spot! + + monsterName(monstName, monst, true); + monst->currentHP = (monst->currentHP + 1) / 2; + clone = cloneMonster(monst, false, false); + + // Split monsters don't inherit the learnings of their parents. + // Sorry, but self-healing jelly armies are too much. + // Mutation effects can be inherited, however; they're not learned abilities. + if (monst->mutationIndex >= 0) { + clone->info.flags + &= (monsterCatalog[clone->info.monsterID].flags | mutationCatalog[monst->mutationIndex].monsterFlags); + clone->info.abilityFlags &= (monsterCatalog[clone->info.monsterID].abilityFlags + | mutationCatalog[monst->mutationIndex].monsterAbilityFlags); + } else { + clone->info.flags &= monsterCatalog[clone->info.monsterID].flags; + clone->info.abilityFlags &= monsterCatalog[clone->info.monsterID].abilityFlags; + } + for (int b = 0; b < 20; b++) { + clone->info.bolts[b] = monsterCatalog[clone->info.monsterID].bolts[b]; + } + + if (!(clone->info.flags & MONST_FLIES) && clone->status[STATUS_LEVITATING] == 1000) { + + clone->status[STATUS_LEVITATING] = 0; + } + + clone->loc = (pos){.x = i, .y = j}; + pmap[i][j].flags |= HAS_MONSTER; + clone->ticksUntilTurn = max(clone->ticksUntilTurn, 101); + fadeInMonster(clone); + refreshSideBar(-1, -1, false); + + if (canDirectlySeeMonster(monst)) { + sprintf(buf, "%s splits in two!", monstName); + message(buf, 0); + } + + return; } + } } + } } short alliedCloneCount(creature *monst) { - short count = 0; - for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) { - creature *temp = nextCreature(&it); - if (temp != monst - && temp->info.monsterID == monst->info.monsterID - && monstersAreTeammates(temp, monst)) { + short count = 0; + for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) { + creature *temp = nextCreature(&it); + if (temp != monst && temp->info.monsterID == monst->info.monsterID && monstersAreTeammates(temp, monst)) { - count++; - } + count++; } - if (rogue.depthLevel > 1) { - for (creatureIterator it = iterateCreatures(&levels[rogue.depthLevel - 2].monsters); hasNextCreature(it);) { - creature *temp = nextCreature(&it); - if (temp != monst - && temp->info.monsterID == monst->info.monsterID - && monstersAreTeammates(temp, monst)) { + } + if (rogue.depthLevel > 1) { + for (creatureIterator it = iterateCreatures(&levels[rogue.depthLevel - 2].monsters); hasNextCreature(it);) { + creature *temp = nextCreature(&it); + if (temp != monst && temp->info.monsterID == monst->info.monsterID && monstersAreTeammates(temp, monst)) { - count++; - } - } + count++; + } } - if (rogue.depthLevel < gameConst->deepestLevel) { - for (creatureIterator it = iterateCreatures(&levels[rogue.depthLevel].monsters); hasNextCreature(it);) { - creature *temp = nextCreature(&it); - if (temp != monst - && temp->info.monsterID == monst->info.monsterID - && monstersAreTeammates(temp, monst)) { + } + if (rogue.depthLevel < gameConst->deepestLevel) { + for (creatureIterator it = iterateCreatures(&levels[rogue.depthLevel].monsters); hasNextCreature(it);) { + creature *temp = nextCreature(&it); + if (temp != monst && temp->info.monsterID == monst->info.monsterID && monstersAreTeammates(temp, monst)) { - count++; - } - } + count++; + } } - return count; + } + return count; } // This function is called whenever one creature acts aggressively against another in a way that directly causes damage. // This can be things like melee attacks, fire/lightning attacks or throwing a weapon. void moralAttack(creature *attacker, creature *defender) { - if (attacker == &player && canSeeMonster(defender)) { - rogue.featRecord[FEAT_PACIFIST] = false; - if (defender->creatureState != MONSTER_TRACKING_SCENT) { - rogue.featRecord[FEAT_PALADIN] = false; - } + if (attacker == &player && canSeeMonster(defender)) { + rogue.featRecord[FEAT_PACIFIST] = false; + if (defender->creatureState != MONSTER_TRACKING_SCENT) { + rogue.featRecord[FEAT_PALADIN] = false; } + } - if (defender->currentHP > 0 - && !(defender->bookkeepingFlags & MB_IS_DYING)) { + if (defender->currentHP > 0 && !(defender->bookkeepingFlags & MB_IS_DYING)) { - if (defender->status[STATUS_PARALYZED]) { - defender->status[STATUS_PARALYZED] = 0; - // Paralyzed creature gets a turn to react before the attacker moves again. - defender->ticksUntilTurn = min(attacker->attackSpeed, 100) - 1; - } - if (defender->status[STATUS_MAGICAL_FEAR]) { - defender->status[STATUS_MAGICAL_FEAR] = 1; - } - defender->status[STATUS_ENTRANCED] = 0; + if (defender->status[STATUS_PARALYZED]) { + defender->status[STATUS_PARALYZED] = 0; + // Paralyzed creature gets a turn to react before the attacker moves again. + defender->ticksUntilTurn = min(attacker->attackSpeed, 100) - 1; + } + if (defender->status[STATUS_MAGICAL_FEAR]) { + defender->status[STATUS_MAGICAL_FEAR] = 1; + } + defender->status[STATUS_ENTRANCED] = 0; - if ((defender->info.abilityFlags & MA_AVOID_CORRIDORS)) { - defender->status[STATUS_ENRAGED] = defender->maxStatus[STATUS_ENRAGED] = 4; - } + if ((defender->info.abilityFlags & MA_AVOID_CORRIDORS)) { + defender->status[STATUS_ENRAGED] = defender->maxStatus[STATUS_ENRAGED] = 4; + } - if (attacker == &player - && defender->creatureState == MONSTER_ALLY - && !defender->status[STATUS_DISCORDANT] - && !attacker->status[STATUS_CONFUSED] - && !(attacker->bookkeepingFlags & MB_IS_DYING)) { + if (attacker == &player && defender->creatureState == MONSTER_ALLY && !defender->status[STATUS_DISCORDANT] + && !attacker->status[STATUS_CONFUSED] && !(attacker->bookkeepingFlags & MB_IS_DYING)) { - unAlly(defender); - } + unAlly(defender); + } - if ((attacker == &player || attacker->creatureState == MONSTER_ALLY) - && defender != &player - && defender->creatureState != MONSTER_ALLY) { + if ((attacker == &player || attacker->creatureState == MONSTER_ALLY) && defender != &player + && defender->creatureState != MONSTER_ALLY) { - alertMonster(defender); // this alerts the monster that you're nearby - } + alertMonster(defender); // this alerts the monster that you're nearby + } - if ((defender->info.abilityFlags & MA_CLONE_SELF_ON_DEFEND) && alliedCloneCount(defender) < 100) { - if (distanceBetween(defender->loc, attacker->loc) <= 1) { - splitMonster(defender, attacker->loc); - } else { - splitMonster(defender, INVALID_POS); - } - } + if ((defender->info.abilityFlags & MA_CLONE_SELF_ON_DEFEND) && alliedCloneCount(defender) < 100) { + if (distanceBetween(defender->loc, attacker->loc) <= 1) { + splitMonster(defender, attacker->loc); + } else { + splitMonster(defender, INVALID_POS); + } } + } } boolean playerImmuneToMonster(creature *monst) { - if (monst != &player - && rogue.armor - && (rogue.armor->flags & ITEM_RUNIC) - && (rogue.armor->enchant2 == A_IMMUNITY) - && monsterIsInClass(monst, rogue.armor->vorpalEnemy)) { + if (monst != &player && rogue.armor && (rogue.armor->flags & ITEM_RUNIC) && (rogue.armor->enchant2 == A_IMMUNITY) + && monsterIsInClass(monst, rogue.armor->vorpalEnemy)) { - return true; - } else { - return false; - } + return true; + } else { + return false; + } } void specialHit(creature *attacker, creature *defender, short damage) { - short itemCandidates, randItemIndex, stolenQuantity; - item *theItem = NULL, *itemFromTopOfStack; - char buf[COLS], buf2[COLS], buf3[COLS]; - - if (!(attacker->info.abilityFlags & SPECIAL_HIT)) { - return; - } - - // Special hits that can affect only the player: - if (defender == &player) { - if (playerImmuneToMonster(attacker)) { - return; - } - - if (attacker->info.abilityFlags & MA_HIT_DEGRADE_ARMOR - && defender == &player - && rogue.armor - && !(rogue.armor->flags & ITEM_PROTECTED) - && (rogue.armor->enchant1 + rogue.armor->armor/10 > -10)) { - - rogue.armor->enchant1--; - equipItem(rogue.armor, true, NULL); - itemName(rogue.armor, buf2, false, false, NULL); - sprintf(buf, "your %s weakens!", buf2); - messageWithColor(buf, &itemMessageColor, 0); - checkForDisenchantment(rogue.armor); - } - if (attacker->info.abilityFlags & MA_HIT_HALLUCINATE) { - if (!player.status[STATUS_HALLUCINATING]) { - combatMessage("you begin to hallucinate", 0); - } - if (!player.status[STATUS_HALLUCINATING]) { - player.maxStatus[STATUS_HALLUCINATING] = 0; + short itemCandidates, randItemIndex, stolenQuantity; + item *theItem = NULL, *itemFromTopOfStack; + char buf[COLS], buf2[COLS], buf3[COLS]; + + if (!(attacker->info.abilityFlags & SPECIAL_HIT)) { + return; + } + + // Special hits that can affect only the player: + if (defender == &player) { + if (playerImmuneToMonster(attacker)) { + return; + } + + if (attacker->info.abilityFlags & MA_HIT_DEGRADE_ARMOR && defender == &player && rogue.armor + && !(rogue.armor->flags & ITEM_PROTECTED) && (rogue.armor->enchant1 + rogue.armor->armor / 10 > -10)) { + + rogue.armor->enchant1--; + equipItem(rogue.armor, true, NULL); + itemName(rogue.armor, buf2, false, false, NULL); + sprintf(buf, "your %s weakens!", buf2); + messageWithColor(buf, &itemMessageColor, 0); + checkForDisenchantment(rogue.armor); + } + if (attacker->info.abilityFlags & MA_HIT_HALLUCINATE) { + if (!player.status[STATUS_HALLUCINATING]) { + combatMessage("you begin to hallucinate", 0); + } + if (!player.status[STATUS_HALLUCINATING]) { + player.maxStatus[STATUS_HALLUCINATING] = 0; + } + player.status[STATUS_HALLUCINATING] += gameConst->onHitHallucinateDuration; + player.maxStatus[STATUS_HALLUCINATING] + = max(player.maxStatus[STATUS_HALLUCINATING], player.status[STATUS_HALLUCINATING]); + } + if (attacker->info.abilityFlags & MA_HIT_BURN && !defender->status[STATUS_IMMUNE_TO_FIRE]) { + + exposeCreatureToFire(defender); + } + + if (attacker->info.abilityFlags & MA_HIT_STEAL_FLEE && !(attacker->carriedItem) && (packItems->nextItem) + && attacker->currentHP > 0 + && !attacker->status[STATUS_CONFUSED] // No stealing from the player if you bump him while confused. + && attackHit(attacker, defender)) { + + itemCandidates = numberOfMatchingPackItems(ALL_ITEMS, 0, (ITEM_EQUIPPED), false); + if (itemCandidates) { + randItemIndex = rand_range(1, itemCandidates); + for (theItem = packItems->nextItem; theItem != NULL; theItem = theItem->nextItem) { + if (!(theItem->flags & (ITEM_EQUIPPED))) { + if (randItemIndex == 1) { + break; + } else { + randItemIndex--; } - player.status[STATUS_HALLUCINATING] += gameConst->onHitHallucinateDuration; - player.maxStatus[STATUS_HALLUCINATING] = max(player.maxStatus[STATUS_HALLUCINATING], player.status[STATUS_HALLUCINATING]); - } - if (attacker->info.abilityFlags & MA_HIT_BURN - && !defender->status[STATUS_IMMUNE_TO_FIRE]) { - - exposeCreatureToFire(defender); + } } - - if (attacker->info.abilityFlags & MA_HIT_STEAL_FLEE - && !(attacker->carriedItem) - && (packItems->nextItem) - && attacker->currentHP > 0 - && !attacker->status[STATUS_CONFUSED] // No stealing from the player if you bump him while confused. - && attackHit(attacker, defender)) { - - itemCandidates = numberOfMatchingPackItems(ALL_ITEMS, 0, (ITEM_EQUIPPED), false); - if (itemCandidates) { - randItemIndex = rand_range(1, itemCandidates); - for (theItem = packItems->nextItem; theItem != NULL; theItem = theItem->nextItem) { - if (!(theItem->flags & (ITEM_EQUIPPED))) { - if (randItemIndex == 1) { - break; - } else { - randItemIndex--; - } - } - } - if (theItem) { - if (theItem->category & WEAPON) { // Monkeys will steal half of a stack of weapons, and one of any other stack. - if (theItem->quantity > 3) { - stolenQuantity = (theItem->quantity + 1) / 2; - } else { - stolenQuantity = theItem->quantity; - } - } else { - stolenQuantity = 1; - } - if (stolenQuantity < theItem->quantity) { // Peel off stolen item(s). - itemFromTopOfStack = generateItem(ALL_ITEMS, -1); - *itemFromTopOfStack = *theItem; // Clone the item. - theItem->quantity -= stolenQuantity; - itemFromTopOfStack->quantity = stolenQuantity; - theItem = itemFromTopOfStack; // Redirect pointer. - } else { - if (rogue.swappedIn == theItem || rogue.swappedOut == theItem) { - rogue.swappedIn = NULL; - rogue.swappedOut = NULL; - } - removeItemFromChain(theItem, packItems); - } - theItem->flags &= ~ITEM_PLAYER_AVOIDS; // Explore will seek the item out if it ends up on the floor again. - attacker->carriedItem = theItem; - attacker->creatureMode = MODE_PERM_FLEEING; - attacker->creatureState = MONSTER_FLEEING; - monsterName(buf2, attacker, true); - itemName(theItem, buf3, false, true, NULL); - sprintf(buf, "%s stole %s!", buf2, buf3); - messageWithColor(buf, &badMessageColor, 0); - rogue.autoPlayingLevel = false; - } + if (theItem) { + if (theItem->category + & WEAPON) { // Monkeys will steal half of a stack of weapons, and one of any other stack. + if (theItem->quantity > 3) { + stolenQuantity = (theItem->quantity + 1) / 2; + } else { + stolenQuantity = theItem->quantity; } + } else { + stolenQuantity = 1; + } + if (stolenQuantity < theItem->quantity) { // Peel off stolen item(s). + itemFromTopOfStack = generateItem(ALL_ITEMS, -1); + *itemFromTopOfStack = *theItem; // Clone the item. + theItem->quantity -= stolenQuantity; + itemFromTopOfStack->quantity = stolenQuantity; + theItem = itemFromTopOfStack; // Redirect pointer. + } else { + if (rogue.swappedIn == theItem || rogue.swappedOut == theItem) { + rogue.swappedIn = NULL; + rogue.swappedOut = NULL; + } + removeItemFromChain(theItem, packItems); + } + theItem->flags &= ~ITEM_PLAYER_AVOIDS; // Explore will seek the item out if it ends up on the floor again. + attacker->carriedItem = theItem; + attacker->creatureMode = MODE_PERM_FLEEING; + attacker->creatureState = MONSTER_FLEEING; + monsterName(buf2, attacker, true); + itemName(theItem, buf3, false, true, NULL); + sprintf(buf, "%s stole %s!", buf2, buf3); + messageWithColor(buf, &badMessageColor, 0); + rogue.autoPlayingLevel = false; } - } - if ((attacker->info.abilityFlags & MA_POISONS) - && damage > 0 - && !(defender->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) { - - addPoison(defender, damage, 1); - } - if ((attacker->info.abilityFlags & MA_CAUSES_WEAKNESS) - && damage > 0 - && !(defender->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) { - - weaken(defender, gameConst->onHitWeakenDuration); - } - if (attacker->info.abilityFlags & MA_ATTACKS_STAGGER) { - processStaggerHit(attacker, defender); - } + } + } + } + if ((attacker->info.abilityFlags & MA_POISONS) && damage > 0 + && !(defender->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) { + + addPoison(defender, damage, 1); + } + if ((attacker->info.abilityFlags & MA_CAUSES_WEAKNESS) && damage > 0 + && !(defender->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) { + + weaken(defender, gameConst->onHitWeakenDuration); + } + if (attacker->info.abilityFlags & MA_ATTACKS_STAGGER) { + processStaggerHit(attacker, defender); + } } boolean forceWeaponHit(creature *defender, item *theItem) { - short forceDamage; - char buf[DCOLS*3], buf2[COLS], monstName[DCOLS]; - creature *otherMonster = NULL; - boolean knowFirstMonsterDied = false, autoID = false; - bolt theBolt; + short forceDamage; + char buf[DCOLS * 3], buf2[COLS], monstName[DCOLS]; + creature *otherMonster = NULL; + boolean knowFirstMonsterDied = false, autoID = false; + bolt theBolt; + + monsterName(monstName, defender, true); + + pos oldLoc = defender->loc; + pos newLoc = (pos){.x = defender->loc.x + clamp(defender->loc.x - player.loc.x, -1, 1), + .y = defender->loc.y + clamp(defender->loc.y - player.loc.y, -1, 1)}; + if (canDirectlySeeMonster(defender) + && !cellHasTerrainFlag(newLoc.x, newLoc.y, T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_VISION) + && !(pmapAt(newLoc)->flags & (HAS_MONSTER | HAS_PLAYER))) { + sprintf(buf, "you launch %s backward with the force of your blow", monstName); + buf[DCOLS] = '\0'; + combatMessage(buf, messageColorFromVictim(defender)); + autoID = true; + } + theBolt = boltCatalog[BOLT_BLINKING]; + theBolt.magnitude = max(1, netEnchant(theItem) / FP_FACTOR); + zap(oldLoc, newLoc, &theBolt, false, false); + if (!(defender->bookkeepingFlags & MB_IS_DYING) && distanceBetween(oldLoc, defender->loc) > 0 + && distanceBetween(oldLoc, defender->loc) < weaponForceDistance(netEnchant(theItem))) { + + if (pmap[defender->loc.x + newLoc.x - oldLoc.x][defender->loc.y + newLoc.y - oldLoc.y].flags + & (HAS_MONSTER | HAS_PLAYER)) { + otherMonster = monsterAtLoc((pos){defender->loc.x + newLoc.x - oldLoc.x, defender->loc.y + newLoc.y - oldLoc.y}); + monsterName(buf2, otherMonster, true); + } else { + otherMonster = NULL; + strcpy(buf2, tileCatalog[pmap[defender->loc.x + newLoc.x - oldLoc.x][defender->loc.y + newLoc.y - oldLoc.y] + .layers[highestPriorityLayer(defender->loc.x + newLoc.x - oldLoc.x, + defender->loc.y + newLoc.y - oldLoc.y, true)]] + .description); + } - monsterName(monstName, defender, true); + forceDamage = distanceBetween(oldLoc, defender->loc); + + if (!(defender->info.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE)) + && inflictDamage(NULL, defender, forceDamage, &white, false)) { - pos oldLoc = defender->loc; - pos newLoc = (pos){ - .x = defender->loc.x + clamp(defender->loc.x - player.loc.x, -1, 1), - .y = defender->loc.y + clamp(defender->loc.y - player.loc.y, -1, 1) - }; - if (canDirectlySeeMonster(defender) - && !cellHasTerrainFlag(newLoc.x, newLoc.y, T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_VISION) - && !(pmapAt(newLoc)->flags & (HAS_MONSTER | HAS_PLAYER))) { - sprintf(buf, "you launch %s backward with the force of your blow", monstName); + if (canDirectlySeeMonster(defender)) { + knowFirstMonsterDied = true; + sprintf(buf, "%s %s on impact with %s", monstName, + (defender->info.flags & MONST_INANIMATE) ? "is destroyed" : "dies", buf2); buf[DCOLS] = '\0'; combatMessage(buf, messageColorFromVictim(defender)); autoID = true; + } + killCreature(defender, false); + } else { + if (canDirectlySeeMonster(defender)) { + sprintf(buf, "%s slams against %s", monstName, buf2); + buf[DCOLS] = '\0'; + combatMessage(buf, messageColorFromVictim(defender)); + autoID = true; + } } - theBolt = boltCatalog[BOLT_BLINKING]; - theBolt.magnitude = max(1, netEnchant(theItem) / FP_FACTOR); - zap(oldLoc, newLoc, &theBolt, false, false); - if (!(defender->bookkeepingFlags & MB_IS_DYING) - && distanceBetween(oldLoc, defender->loc) > 0 - && distanceBetween(oldLoc, defender->loc) < weaponForceDistance(netEnchant(theItem))) { - - if (pmap[defender->loc.x + newLoc.x - oldLoc.x][defender->loc.y + newLoc.y - oldLoc.y].flags & (HAS_MONSTER | HAS_PLAYER)) { - otherMonster = monsterAtLoc((pos){ defender->loc.x + newLoc.x - oldLoc.x, defender->loc.y + newLoc.y - oldLoc.y }); - monsterName(buf2, otherMonster, true); - } else { - otherMonster = NULL; - strcpy(buf2, tileCatalog[pmap[defender->loc.x + newLoc.x - oldLoc.x][defender->loc.y + newLoc.y - oldLoc.y].layers[highestPriorityLayer(defender->loc.x + newLoc.x - oldLoc.x, defender->loc.y + newLoc.y - oldLoc.y, true)]].description); - } - - forceDamage = distanceBetween(oldLoc, defender->loc); + moralAttack(&player, defender); - if (!(defender->info.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE)) - && inflictDamage(NULL, defender, forceDamage, &white, false)) { + if (otherMonster && !(defender->info.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE))) { - if (canDirectlySeeMonster(defender)) { - knowFirstMonsterDied = true; - sprintf(buf, "%s %s on impact with %s", - monstName, - (defender->info.flags & MONST_INANIMATE) ? "is destroyed" : "dies", - buf2); - buf[DCOLS] = '\0'; - combatMessage(buf, messageColorFromVictim(defender)); - autoID = true; - } - killCreature(defender, false); - } else { - if (canDirectlySeeMonster(defender)) { - sprintf(buf, "%s slams against %s", - monstName, - buf2); - buf[DCOLS] = '\0'; - combatMessage(buf, messageColorFromVictim(defender)); - autoID = true; - } - } - moralAttack(&player, defender); - - if (otherMonster - && !(defender->info.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE))) { - - if (inflictDamage(NULL, otherMonster, forceDamage, &white, false)) { - if (canDirectlySeeMonster(otherMonster)) { - sprintf(buf, "%s %s%s when %s slams into $HIMHER", - buf2, - (knowFirstMonsterDied ? "also " : ""), - (defender->info.flags & MONST_INANIMATE) ? "is destroyed" : "dies", - monstName); - resolvePronounEscapes(buf, otherMonster); - buf[DCOLS] = '\0'; - combatMessage(buf, messageColorFromVictim(otherMonster)); - autoID = true; - } - killCreature(otherMonster, false); - } - if (otherMonster->creatureState != MONSTER_ALLY) { - // Allies won't defect if you throw another monster at them, even though it hurts. - moralAttack(&player, otherMonster); - } + if (inflictDamage(NULL, otherMonster, forceDamage, &white, false)) { + if (canDirectlySeeMonster(otherMonster)) { + sprintf(buf, "%s %s%s when %s slams into $HIMHER", buf2, (knowFirstMonsterDied ? "also " : ""), + (defender->info.flags & MONST_INANIMATE) ? "is destroyed" : "dies", monstName); + resolvePronounEscapes(buf, otherMonster); + buf[DCOLS] = '\0'; + combatMessage(buf, messageColorFromVictim(otherMonster)); + autoID = true; } - } - return autoID; + killCreature(otherMonster, false); + } + if (otherMonster->creatureState != MONSTER_ALLY) { + // Allies won't defect if you throw another monster at them, even though it hurts. + moralAttack(&player, otherMonster); + } + } + } + return autoID; } void magicWeaponHit(creature *defender, item *theItem, boolean backstabbed) { - char buf[DCOLS*3], monstName[DCOLS], theItemName[DCOLS]; - - const color *effectColors[NUMBER_WEAPON_RUNIC_KINDS] = {&white, &black, - &yellow, &pink, &green, &confusionGasColor, NULL, NULL, &darkRed, &rainbow}; - // W_SPEED, W_QUIETUS, W_PARALYSIS, W_MULTIPLICITY, W_SLOWING, W_CONFUSION, W_FORCE, W_SLAYING, W_MERCY, W_PLENTY - short chance, i; - fixpt enchant; - enum weaponEnchants enchantType = theItem->enchant2; - creature *newMonst; - boolean autoID = false; - - // If the defender is already dead, proceed only if the runic is speed or multiplicity. - // (Everything else acts on the victim, which would literally be overkill.) - if ((defender->bookkeepingFlags & MB_IS_DYING) - && theItem->enchant2 != W_SPEED - && theItem->enchant2 != W_MULTIPLICITY) { - return; - } - - enchant = netEnchant(theItem); - - if (theItem->enchant2 == W_SLAYING) { - chance = (monsterIsInClass(defender, theItem->vorpalEnemy) ? 100 : 0); - } else if (defender->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE)) { - chance = 0; - } else { - chance = runicWeaponChance(theItem, false, 0); - if (backstabbed && chance < 100) { - chance = min(chance * 2, (chance + 100) / 2); + char buf[DCOLS * 3], monstName[DCOLS], theItemName[DCOLS]; + + const color *effectColors[NUMBER_WEAPON_RUNIC_KINDS] + = {&white, &black, &yellow, &pink, &green, &confusionGasColor, NULL, NULL, &darkRed, &rainbow}; + // W_SPEED, W_QUIETUS, W_PARALYSIS, W_MULTIPLICITY, W_SLOWING, W_CONFUSION, W_FORCE, W_SLAYING, W_MERCY, W_PLENTY + short chance, i; + fixpt enchant; + enum weaponEnchants enchantType = theItem->enchant2; + creature *newMonst; + boolean autoID = false; + + // If the defender is already dead, proceed only if the runic is speed or multiplicity. + // (Everything else acts on the victim, which would literally be overkill.) + if ((defender->bookkeepingFlags & MB_IS_DYING) && theItem->enchant2 != W_SPEED + && theItem->enchant2 != W_MULTIPLICITY) { + return; + } + + enchant = netEnchant(theItem); + + if (theItem->enchant2 == W_SLAYING) { + chance = (monsterIsInClass(defender, theItem->vorpalEnemy) ? 100 : 0); + } else if (defender->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE)) { + chance = 0; + } else { + chance = runicWeaponChance(theItem, false, 0); + if (backstabbed && chance < 100) { + chance = min(chance * 2, (chance + 100) / 2); + } + } + if (chance > 0 && rand_percent(chance)) { + if (!(defender->bookkeepingFlags & MB_SUBMERGED)) { + switch (enchantType) { + case W_SPEED: + createFlare(player.loc.x, player.loc.y, SCROLL_ENCHANTMENT_LIGHT); + break; + case W_QUIETUS: + createFlare(defender->loc.x, defender->loc.y, QUIETUS_FLARE_LIGHT); + break; + case W_SLAYING: + createFlare(defender->loc.x, defender->loc.y, SLAYING_FLARE_LIGHT); + break; + default: + flashMonster(defender, effectColors[enchantType], 100); + break; + } + autoID = true; + } + rogue.disturbed = true; + monsterName(monstName, defender, true); + itemName(theItem, theItemName, false, false, NULL); + + switch (enchantType) { + case W_SPEED: + if (player.ticksUntilTurn != -1) { + sprintf(buf, "your %s trembles and time freezes for a moment", theItemName); + buf[DCOLS] = '\0'; + combatMessage(buf, 0); + player.ticksUntilTurn = -1; // free turn! + autoID = true; + } + break; + case W_SLAYING: + case W_QUIETUS: + inflictLethalDamage(&player, defender); + sprintf(buf, "%s suddenly %s", monstName, (defender->info.flags & MONST_INANIMATE) ? "shatters" : "dies"); + buf[DCOLS] = '\0'; + combatMessage(buf, messageColorFromVictim(defender)); + killCreature(defender, false); + autoID = true; + break; + case W_PARALYSIS: + defender->status[STATUS_PARALYZED] = max(defender->status[STATUS_PARALYZED], weaponParalysisDuration(enchant)); + defender->maxStatus[STATUS_PARALYZED] = defender->status[STATUS_PARALYZED]; + if (canDirectlySeeMonster(defender)) { + sprintf(buf, "%s is frozen in place", monstName); + buf[DCOLS] = '\0'; + combatMessage(buf, messageColorFromVictim(defender)); + autoID = true; + } + break; + case W_MULTIPLICITY: + sprintf(buf, "Your %s emits a flash of light, and %sspectral duplicate%s appear%s!", theItemName, + (weaponImageCount(enchant) == 1 ? "a " : ""), (weaponImageCount(enchant) == 1 ? "" : "s"), + (weaponImageCount(enchant) == 1 ? "s" : "")); + buf[DCOLS] = '\0'; + + for (i = 0; i < (weaponImageCount(enchant)); i++) { + newMonst = generateMonster(MK_SPECTRAL_IMAGE, true, false); + getQualifyingPathLocNear(&(newMonst->loc.x), &(newMonst->loc.y), defender->loc.x, defender->loc.y, true, + T_DIVIDES_LEVEL & avoidedFlagsForMonster(&(newMonst->info)), HAS_PLAYER, + avoidedFlagsForMonster(&(newMonst->info)), (HAS_PLAYER | HAS_MONSTER | HAS_STAIRS), + false); + newMonst->bookkeepingFlags + |= (MB_FOLLOWER | MB_BOUND_TO_LEADER | MB_DOES_NOT_TRACK_LEADER | MB_TELEPATHICALLY_REVEALED); + newMonst->bookkeepingFlags &= ~MB_JUST_SUMMONED; + newMonst->leader = &player; + newMonst->creatureState = MONSTER_ALLY; + if (theItem->flags & ITEM_ATTACKS_STAGGER) { + newMonst->info.attackSpeed *= 2; + newMonst->info.abilityFlags |= MA_ATTACKS_STAGGER; } - } - if (chance > 0 && rand_percent(chance)) { - if (!(defender->bookkeepingFlags & MB_SUBMERGED)) { - switch (enchantType) { - case W_SPEED: - createFlare(player.loc.x, player.loc.y, SCROLL_ENCHANTMENT_LIGHT); - break; - case W_QUIETUS: - createFlare(defender->loc.x, defender->loc.y, QUIETUS_FLARE_LIGHT); - break; - case W_SLAYING: - createFlare(defender->loc.x, defender->loc.y, SLAYING_FLARE_LIGHT); - break; - default: - flashMonster(defender, effectColors[enchantType], 100); - break; - } - autoID = true; + if (theItem->flags & ITEM_ATTACKS_QUICKLY) { + newMonst->info.attackSpeed /= 2; } - rogue.disturbed = true; - monsterName(monstName, defender, true); - itemName(theItem, theItemName, false, false, NULL); - - switch (enchantType) { - case W_SPEED: - if (player.ticksUntilTurn != -1) { - sprintf(buf, "your %s trembles and time freezes for a moment", theItemName); - buf[DCOLS] = '\0'; - combatMessage(buf, 0); - player.ticksUntilTurn = -1; // free turn! - autoID = true; - } - break; - case W_SLAYING: - case W_QUIETUS: - inflictLethalDamage(&player, defender); - sprintf(buf, "%s suddenly %s", - monstName, - (defender->info.flags & MONST_INANIMATE) ? "shatters" : "dies"); - buf[DCOLS] = '\0'; - combatMessage(buf, messageColorFromVictim(defender)); - killCreature(defender, false); - autoID = true; - break; - case W_PARALYSIS: - defender->status[STATUS_PARALYZED] = max(defender->status[STATUS_PARALYZED], weaponParalysisDuration(enchant)); - defender->maxStatus[STATUS_PARALYZED] = defender->status[STATUS_PARALYZED]; - if (canDirectlySeeMonster(defender)) { - sprintf(buf, "%s is frozen in place", monstName); - buf[DCOLS] = '\0'; - combatMessage(buf, messageColorFromVictim(defender)); - autoID = true; - } - break; - case W_MULTIPLICITY: - sprintf(buf, "Your %s emits a flash of light, and %sspectral duplicate%s appear%s!", - theItemName, - (weaponImageCount(enchant) == 1 ? "a " : ""), - (weaponImageCount(enchant) == 1 ? "" : "s"), - (weaponImageCount(enchant) == 1 ? "s" : "")); - buf[DCOLS] = '\0'; - - for (i = 0; i < (weaponImageCount(enchant)); i++) { - newMonst = generateMonster(MK_SPECTRAL_IMAGE, true, false); - getQualifyingPathLocNear(&(newMonst->loc.x), &(newMonst->loc.y), defender->loc.x, defender->loc.y, true, - T_DIVIDES_LEVEL & avoidedFlagsForMonster(&(newMonst->info)), HAS_PLAYER, - avoidedFlagsForMonster(&(newMonst->info)), (HAS_PLAYER | HAS_MONSTER | HAS_STAIRS), false); - newMonst->bookkeepingFlags |= (MB_FOLLOWER | MB_BOUND_TO_LEADER | MB_DOES_NOT_TRACK_LEADER | MB_TELEPATHICALLY_REVEALED); - newMonst->bookkeepingFlags &= ~MB_JUST_SUMMONED; - newMonst->leader = &player; - newMonst->creatureState = MONSTER_ALLY; - if (theItem->flags & ITEM_ATTACKS_STAGGER) { - newMonst->info.attackSpeed *= 2; - newMonst->info.abilityFlags |= MA_ATTACKS_STAGGER; - } - if (theItem->flags & ITEM_ATTACKS_QUICKLY) { - newMonst->info.attackSpeed /= 2; - } - if (theItem->flags & ITEM_ATTACKS_PENETRATE) { - newMonst->info.abilityFlags |= MA_ATTACKS_PENETRATE; - } - if (theItem->flags & ITEM_ATTACKS_ALL_ADJACENT) { - newMonst->info.abilityFlags |= MA_ATTACKS_ALL_ADJACENT; - } - if (theItem->flags & ITEM_ATTACKS_EXTEND) { - newMonst->info.abilityFlags |= MA_ATTACKS_EXTEND; - } - newMonst->ticksUntilTurn = 100; - newMonst->info.accuracy = player.info.accuracy + (5 * netEnchant(theItem) / FP_FACTOR); - newMonst->info.damage = player.info.damage; - newMonst->status[STATUS_LIFESPAN_REMAINING] = newMonst->maxStatus[STATUS_LIFESPAN_REMAINING] = weaponImageDuration(enchant); - if (strLenWithoutEscapes(theItemName) <= 8) { - sprintf(newMonst->info.monsterName, "spectral %s", theItemName); - } else { - switch (rogue.weapon->kind) { - case BROADSWORD: - strcpy(newMonst->info.monsterName, "spectral sword"); - break; - case HAMMER: - strcpy(newMonst->info.monsterName, "spectral hammer"); - break; - case PIKE: - strcpy(newMonst->info.monsterName, "spectral pike"); - break; - case WAR_AXE: - strcpy(newMonst->info.monsterName, "spectral axe"); - break; - default: - strcpy(newMonst->info.monsterName, "spectral weapon"); - break; - } - } - pmapAt(newMonst->loc)->flags |= HAS_MONSTER; - fadeInMonster(newMonst); - } - updateVision(true); - - message(buf, 0); - autoID = true; - break; - case W_SLOWING: - slow(defender, weaponSlowDuration(enchant)); - if (canDirectlySeeMonster(defender)) { - sprintf(buf, "%s slows down", monstName); - buf[DCOLS] = '\0'; - combatMessage(buf, messageColorFromVictim(defender)); - autoID = true; - } - break; - case W_CONFUSION: - defender->status[STATUS_CONFUSED] = max(defender->status[STATUS_CONFUSED], weaponConfusionDuration(enchant)); - defender->maxStatus[STATUS_CONFUSED] = defender->status[STATUS_CONFUSED]; - if (canDirectlySeeMonster(defender)) { - sprintf(buf, "%s looks very confused", monstName); - buf[DCOLS] = '\0'; - combatMessage(buf, messageColorFromVictim(defender)); - autoID = true; - } - break; - case W_FORCE: - autoID = forceWeaponHit(defender, theItem); - break; - case W_MERCY: - heal(defender, gameConst->onHitMercyHealPercent, false); - if (canSeeMonster(defender)) { - autoID = true; - } - break; - case W_PLENTY: - newMonst = cloneMonster(defender, true, true); - if (newMonst) { - flashMonster(newMonst, effectColors[enchantType], 100); - if (canSeeMonster(newMonst)) { - autoID = true; - } - } - break; - default: - break; + if (theItem->flags & ITEM_ATTACKS_PENETRATE) { + newMonst->info.abilityFlags |= MA_ATTACKS_PENETRATE; } - } - if (autoID) { - autoIdentify(theItem); - } + if (theItem->flags & ITEM_ATTACKS_ALL_ADJACENT) { + newMonst->info.abilityFlags |= MA_ATTACKS_ALL_ADJACENT; + } + if (theItem->flags & ITEM_ATTACKS_EXTEND) { + newMonst->info.abilityFlags |= MA_ATTACKS_EXTEND; + } + newMonst->ticksUntilTurn = 100; + newMonst->info.accuracy = player.info.accuracy + (5 * netEnchant(theItem) / FP_FACTOR); + newMonst->info.damage = player.info.damage; + newMonst->status[STATUS_LIFESPAN_REMAINING] = newMonst->maxStatus[STATUS_LIFESPAN_REMAINING] + = weaponImageDuration(enchant); + if (strLenWithoutEscapes(theItemName) <= 8) { + sprintf(newMonst->info.monsterName, "spectral %s", theItemName); + } else { + switch (rogue.weapon->kind) { + case BROADSWORD: + strcpy(newMonst->info.monsterName, "spectral sword"); + break; + case HAMMER: + strcpy(newMonst->info.monsterName, "spectral hammer"); + break; + case PIKE: + strcpy(newMonst->info.monsterName, "spectral pike"); + break; + case WAR_AXE: + strcpy(newMonst->info.monsterName, "spectral axe"); + break; + default: + strcpy(newMonst->info.monsterName, "spectral weapon"); + break; + } + } + pmapAt(newMonst->loc)->flags |= HAS_MONSTER; + fadeInMonster(newMonst); + } + updateVision(true); + + message(buf, 0); + autoID = true; + break; + case W_SLOWING: + slow(defender, weaponSlowDuration(enchant)); + if (canDirectlySeeMonster(defender)) { + sprintf(buf, "%s slows down", monstName); + buf[DCOLS] = '\0'; + combatMessage(buf, messageColorFromVictim(defender)); + autoID = true; + } + break; + case W_CONFUSION: + defender->status[STATUS_CONFUSED] = max(defender->status[STATUS_CONFUSED], weaponConfusionDuration(enchant)); + defender->maxStatus[STATUS_CONFUSED] = defender->status[STATUS_CONFUSED]; + if (canDirectlySeeMonster(defender)) { + sprintf(buf, "%s looks very confused", monstName); + buf[DCOLS] = '\0'; + combatMessage(buf, messageColorFromVictim(defender)); + autoID = true; + } + break; + case W_FORCE: + autoID = forceWeaponHit(defender, theItem); + break; + case W_MERCY: + heal(defender, gameConst->onHitMercyHealPercent, false); + if (canSeeMonster(defender)) { + autoID = true; + } + break; + case W_PLENTY: + newMonst = cloneMonster(defender, true, true); + if (newMonst) { + flashMonster(newMonst, effectColors[enchantType], 100); + if (canSeeMonster(newMonst)) { + autoID = true; + } + } + break; + default: + break; + } + } + if (autoID) { + autoIdentify(theItem); + } } void attackVerb(char returnString[DCOLS], creature *attacker, short hitPercentile) { - short verbCount, increment; + short verbCount, increment; + + if (attacker != &player && (player.status[STATUS_HALLUCINATING] || !canSeeMonster(attacker))) { + strcpy(returnString, "hits"); + return; + } + + if (attacker == &player && !rogue.weapon) { + strcpy(returnString, "punch"); + return; + } + + for (verbCount = 0; verbCount < 4 && monsterText[attacker->info.monsterID].attack[verbCount + 1][0] != '\0'; + verbCount++) + ; + increment = (100 / (verbCount + 1)); + hitPercentile = max(0, min(hitPercentile, increment * (verbCount + 1) - 1)); + strcpy(returnString, monsterText[attacker->info.monsterID].attack[hitPercentile / increment]); + resolvePronounEscapes(returnString, attacker); +} - if (attacker != &player && (player.status[STATUS_HALLUCINATING] || !canSeeMonster(attacker))) { - strcpy(returnString, "hits"); - return; - } +void applyArmorRunicEffect(char returnString[DCOLS], creature *attacker, short *damage, boolean melee) { + char armorName[DCOLS], attackerName[DCOLS], monstName[DCOLS], buf[DCOLS * 3]; + boolean runicKnown; + boolean runicDiscovered; + short newDamage, dir, newX, newY, count, i; + fixpt enchant; + creature *monst, *hitList[8]; + + returnString[0] = '\0'; + + if (!(rogue.armor && rogue.armor->flags & ITEM_RUNIC)) { + return; // just in case + } + + enchant = netEnchant(rogue.armor); + + runicKnown = rogue.armor->flags & ITEM_RUNIC_IDENTIFIED; + runicDiscovered = false; + + itemName(rogue.armor, armorName, false, false, NULL); + + monsterName(attackerName, attacker, true); + + switch (rogue.armor->enchant2) { + case A_MULTIPLICITY: + if (melee && !(attacker->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE)) && rand_percent(33)) { + for (i = 0; i < armorImageCount(enchant); i++) { + monst = cloneMonster(attacker, false, true); + monst->bookkeepingFlags + |= (MB_FOLLOWER | MB_BOUND_TO_LEADER | MB_DOES_NOT_TRACK_LEADER | MB_TELEPATHICALLY_REVEALED); + monst->info.flags |= MONST_DIES_IF_NEGATED; + monst->bookkeepingFlags &= ~(MB_JUST_SUMMONED | MB_SEIZED | MB_SEIZING); + monst->info.abilityFlags + &= ~(MA_CAST_SUMMON | MA_DF_ON_DEATH); // No summoning by spectral images. Gotta draw the line! + // Also no exploding or infecting by spectral clones. + monst->leader = &player; + monst->creatureState = MONSTER_ALLY; + monst->status[STATUS_DISCORDANT] = 0; // Otherwise things can get out of control... + monst->ticksUntilTurn = 100; + monst->info.monsterID = MK_SPECTRAL_IMAGE; + if (monst->carriedMonster) { + creature *carried = monst->carriedMonster; + monst->carriedMonster = NULL; + killCreature(carried, true); // Otherwise you can get infinite phoenices from a discordant phoenix. + } - if (attacker == &player && !rogue.weapon) { - strcpy(returnString, "punch"); - return; - } + // Give it the glowy red light and color. + monst->info.intrinsicLightType = SPECTRAL_IMAGE_LIGHT; + monst->info.foreColor = &spectralImageColor; - for (verbCount = 0; verbCount < 4 && monsterText[attacker->info.monsterID].attack[verbCount + 1][0] != '\0'; verbCount++); - increment = (100 / (verbCount + 1)); - hitPercentile = max(0, min(hitPercentile, increment * (verbCount + 1) - 1)); - strcpy(returnString, monsterText[attacker->info.monsterID].attack[hitPercentile / increment]); - resolvePronounEscapes(returnString, attacker); -} + // Temporary guest! + monst->status[STATUS_LIFESPAN_REMAINING] = monst->maxStatus[STATUS_LIFESPAN_REMAINING] = 3; + monst->currentHP = monst->info.maxHP = 1; + monst->info.defense = 0; -void applyArmorRunicEffect(char returnString[DCOLS], creature *attacker, short *damage, boolean melee) { - char armorName[DCOLS], attackerName[DCOLS], monstName[DCOLS], buf[DCOLS * 3]; - boolean runicKnown; - boolean runicDiscovered; - short newDamage, dir, newX, newY, count, i; - fixpt enchant; - creature *monst, *hitList[8]; - - returnString[0] = '\0'; - - if (!(rogue.armor && rogue.armor->flags & ITEM_RUNIC)) { - return; // just in case - } - - enchant = netEnchant(rogue.armor); - - runicKnown = rogue.armor->flags & ITEM_RUNIC_IDENTIFIED; - runicDiscovered = false; - - itemName(rogue.armor, armorName, false, false, NULL); - - monsterName(attackerName, attacker, true); - - switch (rogue.armor->enchant2) { - case A_MULTIPLICITY: - if (melee && !(attacker->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE)) && rand_percent(33)) { - for (i = 0; i < armorImageCount(enchant); i++) { - monst = cloneMonster(attacker, false, true); - monst->bookkeepingFlags |= (MB_FOLLOWER | MB_BOUND_TO_LEADER | MB_DOES_NOT_TRACK_LEADER | MB_TELEPATHICALLY_REVEALED); - monst->info.flags |= MONST_DIES_IF_NEGATED; - monst->bookkeepingFlags &= ~(MB_JUST_SUMMONED | MB_SEIZED | MB_SEIZING); - monst->info.abilityFlags &= ~(MA_CAST_SUMMON | MA_DF_ON_DEATH); // No summoning by spectral images. Gotta draw the line! - // Also no exploding or infecting by spectral clones. - monst->leader = &player; - monst->creatureState = MONSTER_ALLY; - monst->status[STATUS_DISCORDANT] = 0; // Otherwise things can get out of control... - monst->ticksUntilTurn = 100; - monst->info.monsterID = MK_SPECTRAL_IMAGE; - if (monst->carriedMonster) { - creature *carried = monst->carriedMonster; - monst->carriedMonster = NULL; - killCreature(carried, true); // Otherwise you can get infinite phoenices from a discordant phoenix. - } - - // Give it the glowy red light and color. - monst->info.intrinsicLightType = SPECTRAL_IMAGE_LIGHT; - monst->info.foreColor = &spectralImageColor; - - // Temporary guest! - monst->status[STATUS_LIFESPAN_REMAINING] = monst->maxStatus[STATUS_LIFESPAN_REMAINING] = 3; - monst->currentHP = monst->info.maxHP = 1; - monst->info.defense = 0; - - if (strLenWithoutEscapes(attacker->info.monsterName) <= 6) { - sprintf(monst->info.monsterName, "spectral %s", attacker->info.monsterName); - } else { - strcpy(monst->info.monsterName, "spectral clone"); - } - fadeInMonster(monst); - } - updateVision(true); - - runicDiscovered = true; - sprintf(returnString, "Your %s flashes, and spectral images of %s appear!", armorName, attackerName); - } - break; - case A_MUTUALITY: - if (*damage > 0) { - count = 0; - for (i=0; i<8; i++) { - hitList[i] = NULL; - dir = i % 8; - newX = player.loc.x + nbDirs[dir][0]; - newY = player.loc.y + nbDirs[dir][1]; - if (coordinatesAreInMap(newX, newY) && (pmap[newX][newY].flags & HAS_MONSTER)) { - monst = monsterAtLoc((pos){ newX, newY }); - if (monst - && monst != attacker - && monstersAreEnemies(&player, monst) - && !(monst->info.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE)) - && !(monst->bookkeepingFlags & MB_IS_DYING)) { - - hitList[i] = monst; - count++; - } - } - } - if (count) { - for (i=0; i<8; i++) { - if (hitList[i] && !(hitList[i]->bookkeepingFlags & MB_IS_DYING)) { - monsterName(monstName, hitList[i], true); - if (inflictDamage(&player, hitList[i], (*damage + count) / (count + 1), &blue, true)) { - if (canSeeMonster(hitList[i])) { - sprintf(buf, "%s %s", monstName, ((hitList[i]->info.flags & MONST_INANIMATE) ? "is destroyed" : "dies")); - combatMessage(buf, messageColorFromVictim(hitList[i])); - } - killCreature(hitList[i], false); - } - } - } - runicDiscovered = true; - if (!runicKnown) { - sprintf(returnString, "Your %s pulses, and the damage is shared with %s!", - armorName, - (count == 1 ? monstName : "the other adjacent enemies")); - } - *damage = (*damage + count) / (count + 1); - } - } - break; - case A_ABSORPTION: - *damage -= rand_range(1, armorAbsorptionMax(enchant)); - if (*damage <= 0) { - *damage = 0; - runicDiscovered = true; - if (!runicKnown) { - sprintf(returnString, "your %s pulses and absorbs the blow!", armorName); - } - } - break; - case A_REPRISAL: - if (melee && !(attacker->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) { - newDamage = max(1, armorReprisalPercent(enchant) * (*damage) / 100); // 5% reprisal per armor level - if (inflictDamage(&player, attacker, newDamage, &blue, true)) { - if (canSeeMonster(attacker)) { - sprintf(returnString, "your %s pulses and %s drops dead!", armorName, attackerName); - runicDiscovered = true; - } - killCreature(attacker, false); - } else if (!runicKnown) { - if (canSeeMonster(attacker)) { - sprintf(returnString, "your %s pulses and %s shudders in pain!", armorName, attackerName); - runicDiscovered = true; - } - } - } - break; - case A_IMMUNITY: - if (monsterIsInClass(attacker, rogue.armor->vorpalEnemy)) { - *damage = 0; - runicDiscovered = true; - } - break; - case A_BURDEN: - if (rand_percent(10)) { - rogue.armor->strengthRequired++; - sprintf(returnString, "your %s suddenly feels heavier!", armorName); - equipItem(rogue.armor, true, NULL); - runicDiscovered = true; - } - break; - case A_VULNERABILITY: - *damage *= 2; - if (!runicKnown) { - sprintf(returnString, "your %s pulses and you are wracked with pain!", armorName); - runicDiscovered = true; - } - break; - case A_IMMOLATION: - if (rand_percent(10)) { - sprintf(returnString, "flames suddenly explode out of your %s!", armorName); - message(returnString, runicKnown ? 0 : REQUIRE_ACKNOWLEDGMENT); - returnString[0] = '\0'; - spawnDungeonFeature(player.loc.x, player.loc.y, &(dungeonFeatureCatalog[DF_ARMOR_IMMOLATION]), true, false); - runicDiscovered = true; + if (strLenWithoutEscapes(attacker->info.monsterName) <= 6) { + sprintf(monst->info.monsterName, "spectral %s", attacker->info.monsterName); + } else { + strcpy(monst->info.monsterName, "spectral clone"); + } + fadeInMonster(monst); + } + updateVision(true); + + runicDiscovered = true; + sprintf(returnString, "Your %s flashes, and spectral images of %s appear!", armorName, attackerName); + } + break; + case A_MUTUALITY: + if (*damage > 0) { + count = 0; + for (i = 0; i < 8; i++) { + hitList[i] = NULL; + dir = i % 8; + newX = player.loc.x + nbDirs[dir][0]; + newY = player.loc.y + nbDirs[dir][1]; + if (coordinatesAreInMap(newX, newY) && (pmap[newX][newY].flags & HAS_MONSTER)) { + monst = monsterAtLoc((pos){newX, newY}); + if (monst && monst != attacker && monstersAreEnemies(&player, monst) + && !(monst->info.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE)) + && !(monst->bookkeepingFlags & MB_IS_DYING)) { + + hitList[i] = monst; + count++; + } + } + } + if (count) { + for (i = 0; i < 8; i++) { + if (hitList[i] && !(hitList[i]->bookkeepingFlags & MB_IS_DYING)) { + monsterName(monstName, hitList[i], true); + if (inflictDamage(&player, hitList[i], (*damage + count) / (count + 1), &blue, true)) { + if (canSeeMonster(hitList[i])) { + sprintf(buf, "%s %s", monstName, + ((hitList[i]->info.flags & MONST_INANIMATE) ? "is destroyed" : "dies")); + combatMessage(buf, messageColorFromVictim(hitList[i])); + } + killCreature(hitList[i], false); } - default: - break; - } - - if (runicDiscovered && !runicKnown) { - autoIdentify(rogue.armor); - } + } + } + runicDiscovered = true; + if (!runicKnown) { + sprintf(returnString, "Your %s pulses, and the damage is shared with %s!", armorName, + (count == 1 ? monstName : "the other adjacent enemies")); + } + *damage = (*damage + count) / (count + 1); + } + } + break; + case A_ABSORPTION: + *damage -= rand_range(1, armorAbsorptionMax(enchant)); + if (*damage <= 0) { + *damage = 0; + runicDiscovered = true; + if (!runicKnown) { + sprintf(returnString, "your %s pulses and absorbs the blow!", armorName); + } + } + break; + case A_REPRISAL: + if (melee && !(attacker->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) { + newDamage = max(1, armorReprisalPercent(enchant) * (*damage) / 100); // 5% reprisal per armor level + if (inflictDamage(&player, attacker, newDamage, &blue, true)) { + if (canSeeMonster(attacker)) { + sprintf(returnString, "your %s pulses and %s drops dead!", armorName, attackerName); + runicDiscovered = true; + } + killCreature(attacker, false); + } else if (!runicKnown) { + if (canSeeMonster(attacker)) { + sprintf(returnString, "your %s pulses and %s shudders in pain!", armorName, attackerName); + runicDiscovered = true; + } + } + } + break; + case A_IMMUNITY: + if (monsterIsInClass(attacker, rogue.armor->vorpalEnemy)) { + *damage = 0; + runicDiscovered = true; + } + break; + case A_BURDEN: + if (rand_percent(10)) { + rogue.armor->strengthRequired++; + sprintf(returnString, "your %s suddenly feels heavier!", armorName); + equipItem(rogue.armor, true, NULL); + runicDiscovered = true; + } + break; + case A_VULNERABILITY: + *damage *= 2; + if (!runicKnown) { + sprintf(returnString, "your %s pulses and you are wracked with pain!", armorName); + runicDiscovered = true; + } + break; + case A_IMMOLATION: + if (rand_percent(10)) { + sprintf(returnString, "flames suddenly explode out of your %s!", armorName); + message(returnString, runicKnown ? 0 : REQUIRE_ACKNOWLEDGMENT); + returnString[0] = '\0'; + spawnDungeonFeature(player.loc.x, player.loc.y, &(dungeonFeatureCatalog[DF_ARMOR_IMMOLATION]), true, false); + runicDiscovered = true; + } + default: + break; + } + + if (runicDiscovered && !runicKnown) { + autoIdentify(rogue.armor); + } } void decrementWeaponAutoIDTimer() { - char buf[COLS*3], buf2[COLS*3]; + char buf[COLS * 3], buf2[COLS * 3]; - if (rogue.weapon - && !(rogue.weapon->flags & ITEM_IDENTIFIED) - && !--rogue.weapon->charges) { + if (rogue.weapon && !(rogue.weapon->flags & ITEM_IDENTIFIED) && !--rogue.weapon->charges) { - rogue.weapon->flags |= ITEM_IDENTIFIED; - updateIdentifiableItems(); - messageWithColor("you are now familiar enough with your weapon to identify it.", &itemMessageColor, 0); - itemName(rogue.weapon, buf2, true, true, NULL); - sprintf(buf, "%s %s.", (rogue.weapon->quantity > 1 ? "they are" : "it is"), buf2); - messageWithColor(buf, &itemMessageColor, 0); - } + rogue.weapon->flags |= ITEM_IDENTIFIED; + updateIdentifiableItems(); + messageWithColor("you are now familiar enough with your weapon to identify it.", &itemMessageColor, 0); + itemName(rogue.weapon, buf2, true, true, NULL); + sprintf(buf, "%s %s.", (rogue.weapon->quantity > 1 ? "they are" : "it is"), buf2); + messageWithColor(buf, &itemMessageColor, 0); + } } void processStaggerHit(creature *attacker, creature *defender) { - if ((defender->info.flags & (MONST_INVULNERABLE | MONST_IMMOBILE | MONST_INANIMATE)) - || (defender->bookkeepingFlags & MB_CAPTIVE) - || cellHasTerrainFlag(defender->loc.x, defender->loc.y, T_OBSTRUCTS_PASSABILITY)) { - - return; - } - short newX = clamp(defender->loc.x - attacker->loc.x, -1, 1) + defender->loc.x; - short newY = clamp(defender->loc.y - attacker->loc.y, -1, 1) + defender->loc.y; - if (coordinatesAreInMap(newX, newY) - && !cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY) - && !(pmap[newX][newY].flags & (HAS_MONSTER | HAS_PLAYER))) { - - setMonsterLocation(defender, newX, newY); - } + if ((defender->info.flags & (MONST_INVULNERABLE | MONST_IMMOBILE | MONST_INANIMATE)) + || (defender->bookkeepingFlags & MB_CAPTIVE) + || cellHasTerrainFlag(defender->loc.x, defender->loc.y, T_OBSTRUCTS_PASSABILITY)) { + + return; + } + short newX = clamp(defender->loc.x - attacker->loc.x, -1, 1) + defender->loc.x; + short newY = clamp(defender->loc.y - attacker->loc.y, -1, 1) + defender->loc.y; + if (coordinatesAreInMap(newX, newY) && !cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY) + && !(pmap[newX][newY].flags & (HAS_MONSTER | HAS_PLAYER))) { + + setMonsterLocation(defender, newX, newY); + } } // returns whether the attack hit boolean attack(creature *attacker, creature *defender, boolean lungeAttack) { - short damage, specialDamage, poisonDamage; - char buf[COLS*2], buf2[COLS*2], attackerName[COLS], defenderName[COLS], verb[DCOLS], explicationClause[DCOLS] = "", armorRunicString[DCOLS*3]; - boolean sneakAttack, defenderWasAsleep, defenderWasParalyzed, degradesAttackerWeapon, sightUnseen; + short damage, specialDamage, poisonDamage; + char buf[COLS * 2], buf2[COLS * 2], attackerName[COLS], defenderName[COLS], verb[DCOLS], + explicationClause[DCOLS] = "", armorRunicString[DCOLS * 3]; + boolean sneakAttack, defenderWasAsleep, defenderWasParalyzed, degradesAttackerWeapon, sightUnseen; - if (attacker == &player && canSeeMonster(defender)) { - rogue.featRecord[FEAT_PURE_MAGE] = false; - } + if (attacker == &player && canSeeMonster(defender)) { + rogue.featRecord[FEAT_PURE_MAGE] = false; + } - if (attacker->info.abilityFlags & MA_KAMIKAZE) { - killCreature(attacker, false); - return true; - } + if (attacker->info.abilityFlags & MA_KAMIKAZE) { + killCreature(attacker, false); + return true; + } - armorRunicString[0] = '\0'; + armorRunicString[0] = '\0'; - poisonDamage = 0; + poisonDamage = 0; - degradesAttackerWeapon = (defender->info.flags & MONST_DEFEND_DEGRADE_WEAPON ? true : false); + degradesAttackerWeapon = (defender->info.flags & MONST_DEFEND_DEGRADE_WEAPON ? true : false); - sightUnseen = !canSeeMonster(attacker) && !canSeeMonster(defender); + sightUnseen = !canSeeMonster(attacker) && !canSeeMonster(defender); - if (defender->status[STATUS_LEVITATING] && (attacker->info.flags & MONST_RESTRICTED_TO_LIQUID)) { - return false; // aquatic or other liquid-bound monsters cannot attack flying opponents - } + if (defender->status[STATUS_LEVITATING] && (attacker->info.flags & MONST_RESTRICTED_TO_LIQUID)) { + return false; // aquatic or other liquid-bound monsters cannot attack flying opponents + } - if ((attacker == &player || defender == &player) && !rogue.blockCombatText) { - rogue.disturbed = true; - } + if ((attacker == &player || defender == &player) && !rogue.blockCombatText) { + rogue.disturbed = true; + } - defender->status[STATUS_ENTRANCED] = 0; - if (defender->status[STATUS_MAGICAL_FEAR]) { - defender->status[STATUS_MAGICAL_FEAR] = 1; - } + defender->status[STATUS_ENTRANCED] = 0; + if (defender->status[STATUS_MAGICAL_FEAR]) { + defender->status[STATUS_MAGICAL_FEAR] = 1; + } - if (attacker == &player - && defender->creatureState != MONSTER_TRACKING_SCENT) { + if (attacker == &player && defender->creatureState != MONSTER_TRACKING_SCENT) { - rogue.featRecord[FEAT_PALADIN] = false; - } + rogue.featRecord[FEAT_PALADIN] = false; + } - if (attacker != &player && defender == &player && attacker->creatureState == MONSTER_WANDERING) { - attacker->creatureState = MONSTER_TRACKING_SCENT; - } + if (attacker != &player && defender == &player && attacker->creatureState == MONSTER_WANDERING) { + attacker->creatureState = MONSTER_TRACKING_SCENT; + } - if (defender->info.flags & MONST_INANIMATE) { - sneakAttack = false; - defenderWasAsleep = false; - defenderWasParalyzed = false; - } else { - sneakAttack = (defender != &player && attacker == &player && (defender->creatureState == MONSTER_WANDERING) ? true : false); - defenderWasAsleep = (defender != &player && (defender->creatureState == MONSTER_SLEEPING) ? true : false); - defenderWasParalyzed = defender->status[STATUS_PARALYZED] > 0; - } + if (defender->info.flags & MONST_INANIMATE) { + sneakAttack = false; + defenderWasAsleep = false; + defenderWasParalyzed = false; + } else { + sneakAttack + = (defender != &player && attacker == &player && (defender->creatureState == MONSTER_WANDERING) ? true : false); + defenderWasAsleep = (defender != &player && (defender->creatureState == MONSTER_SLEEPING) ? true : false); + defenderWasParalyzed = defender->status[STATUS_PARALYZED] > 0; + } - monsterName(attackerName, attacker, true); - monsterName(defenderName, defender, true); + monsterName(attackerName, attacker, true); + monsterName(defenderName, defender, true); - if ((attacker->info.abilityFlags & MA_SEIZES) - && (!(attacker->bookkeepingFlags & MB_SEIZING) || !(defender->bookkeepingFlags & MB_SEIZED)) - && (distanceBetween(attacker->loc, defender->loc) == 1 - && !diagonalBlocked(attacker->loc.x, attacker->loc.y, defender->loc.x, defender->loc.y, false))) { + if ((attacker->info.abilityFlags & MA_SEIZES) + && (!(attacker->bookkeepingFlags & MB_SEIZING) || !(defender->bookkeepingFlags & MB_SEIZED)) + && (distanceBetween(attacker->loc, defender->loc) == 1 + && !diagonalBlocked(attacker->loc.x, attacker->loc.y, defender->loc.x, defender->loc.y, false))) { - attacker->bookkeepingFlags |= MB_SEIZING; - defender->bookkeepingFlags |= MB_SEIZED; - if (canSeeMonster(attacker) || canSeeMonster(defender)) { - sprintf(buf, "%s seizes %s!", attackerName, (defender == &player ? "your legs" : defenderName)); - messageWithColor(buf, &white, 0); + attacker->bookkeepingFlags |= MB_SEIZING; + defender->bookkeepingFlags |= MB_SEIZED; + if (canSeeMonster(attacker) || canSeeMonster(defender)) { + sprintf(buf, "%s seizes %s!", attackerName, (defender == &player ? "your legs" : defenderName)); + messageWithColor(buf, &white, 0); + } + return false; + } + + if (sneakAttack || defenderWasAsleep || defenderWasParalyzed || lungeAttack || attackHit(attacker, defender)) { + // If the attack hit: + damage = (defender->info.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE) + ? 0 + : randClump(attacker->info.damage) * monsterDamageAdjustmentAmount(attacker) / FP_FACTOR); + + if (sneakAttack || defenderWasAsleep || defenderWasParalyzed) { + if (defender != &player) { + // The non-player defender doesn't hit back this turn because it's still flat-footed. + defender->ticksUntilTurn += max(defender->movementSpeed, defender->attackSpeed); + if (defender->creatureState != MONSTER_ALLY) { + defender->creatureState = MONSTER_TRACKING_SCENT; // Wake up! } - return false; + } } + if (sneakAttack || defenderWasAsleep || defenderWasParalyzed || lungeAttack) { + if (attacker == &player && rogue.weapon && (rogue.weapon->flags & ITEM_SNEAK_ATTACK_BONUS)) { - if (sneakAttack || defenderWasAsleep || defenderWasParalyzed || lungeAttack || attackHit(attacker, defender)) { - // If the attack hit: - damage = (defender->info.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE) - ? 0 : randClump(attacker->info.damage) * monsterDamageAdjustmentAmount(attacker) / FP_FACTOR); - - if (sneakAttack || defenderWasAsleep || defenderWasParalyzed) { - if (defender != &player) { - // The non-player defender doesn't hit back this turn because it's still flat-footed. - defender->ticksUntilTurn += max(defender->movementSpeed, defender->attackSpeed); - if (defender->creatureState != MONSTER_ALLY) { - defender->creatureState = MONSTER_TRACKING_SCENT; // Wake up! - } - } - } - if (sneakAttack || defenderWasAsleep || defenderWasParalyzed || lungeAttack) { - if (attacker == &player - && rogue.weapon - && (rogue.weapon->flags & ITEM_SNEAK_ATTACK_BONUS)) { + damage *= 5; // 5x damage for dagger sneak attacks. + } else { + damage *= 3; // Triple damage for general sneak attacks. + } + } - damage *= 5; // 5x damage for dagger sneak attacks. - } else { - damage *= 3; // Triple damage for general sneak attacks. - } - } + if (defender == &player && rogue.armor && (rogue.armor->flags & ITEM_RUNIC)) { + applyArmorRunicEffect(armorRunicString, attacker, &damage, true); + } - if (defender == &player && rogue.armor && (rogue.armor->flags & ITEM_RUNIC)) { - applyArmorRunicEffect(armorRunicString, attacker, &damage, true); - } + if (attacker == &player && rogue.reaping && !(defender->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) { - if (attacker == &player - && rogue.reaping - && !(defender->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) { + specialDamage = min(damage, defender->currentHP) + * rogue.reaping; // Maximum reaped damage can't exceed the victim's remaining health. + if (rogue.reaping > 0) { + specialDamage = rand_range(0, specialDamage); + } else { + specialDamage = rand_range(specialDamage, 0); + } + if (specialDamage) { + rechargeItemsIncrementally(specialDamage); + } + } - specialDamage = min(damage, defender->currentHP) * rogue.reaping; // Maximum reaped damage can't exceed the victim's remaining health. - if (rogue.reaping > 0) { - specialDamage = rand_range(0, specialDamage); - } else { - specialDamage = rand_range(specialDamage, 0); - } - if (specialDamage) { - rechargeItemsIncrementally(specialDamage); - } + if (damage == 0) { + sprintf(explicationClause, " but %s no damage", (attacker == &player ? "do" : "does")); + if (attacker == &player) { + rogue.disturbed = true; + } + } else if (lungeAttack) { + strcpy(explicationClause, " with a vicious lunge attack"); + } else if (defenderWasParalyzed) { + sprintf(explicationClause, " while $HESHE %s paralyzed", (defender == &player ? "are" : "is")); + } else if (defenderWasAsleep) { + strcpy(explicationClause, " in $HISHER sleep"); + } else if (sneakAttack) { + strcpy(explicationClause, ", catching $HIMHER unaware"); + } else if (defender->status[STATUS_STUCK] || defender->bookkeepingFlags & MB_CAPTIVE) { + sprintf(explicationClause, " while %s dangle%s helplessly", (canSeeMonster(defender) ? "$HESHE" : "it"), + (defender == &player ? "" : "s")); + } + resolvePronounEscapes(explicationClause, defender); + + if ((attacker->info.abilityFlags & MA_POISONS) && damage > 0) { + poisonDamage = damage; + damage = 1; + } + + if (inflictDamage(attacker, defender, damage, &red, false)) { // if the attack killed the defender + if (defenderWasAsleep || sneakAttack || defenderWasParalyzed || lungeAttack) { + sprintf(buf, "%s %s %s%s", attackerName, + ((defender->info.flags & MONST_INANIMATE) ? "destroyed" : "dispatched"), defenderName, + explicationClause); + } else { + sprintf(buf, "%s %s %s%s", attackerName, ((defender->info.flags & MONST_INANIMATE) ? "destroyed" : "defeated"), + defenderName, explicationClause); + } + if (sightUnseen) { + if (defender->info.flags & MONST_INANIMATE) { + combatMessage("you hear something get destroyed in combat", 0); + } else { + combatMessage("you hear something die in combat", 0); } - - if (damage == 0) { - sprintf(explicationClause, " but %s no damage", (attacker == &player ? "do" : "does")); - if (attacker == &player) { - rogue.disturbed = true; - } - } else if (lungeAttack) { - strcpy(explicationClause, " with a vicious lunge attack"); - } else if (defenderWasParalyzed) { - sprintf(explicationClause, " while $HESHE %s paralyzed", (defender == &player ? "are" : "is")); - } else if (defenderWasAsleep) { - strcpy(explicationClause, " in $HISHER sleep"); - } else if (sneakAttack) { - strcpy(explicationClause, ", catching $HIMHER unaware"); - } else if (defender->status[STATUS_STUCK] || defender->bookkeepingFlags & MB_CAPTIVE) { - sprintf(explicationClause, " while %s dangle%s helplessly", - (canSeeMonster(defender) ? "$HESHE" : "it"), - (defender == &player ? "" : "s")); + } else { + combatMessage(buf, (damage > 0 ? messageColorFromVictim(defender) : &white)); + } + killCreature(defender, false); + if (&player == defender) { + gameOver(attacker->info.monsterName, false); + return true; + } else if (&player == attacker && defender->info.monsterID == MK_DRAGON) { + + rogue.featRecord[FEAT_DRAGONSLAYER] = true; + } + } else { // if the defender survived + if (!rogue.blockCombatText && (canSeeMonster(attacker) || canSeeMonster(defender))) { + attackVerb( + verb, attacker, + max(damage - (attacker->info.damage.lowerBound * monsterDamageAdjustmentAmount(attacker) / FP_FACTOR), 0) + * 100 + / max(1, (attacker->info.damage.upperBound - attacker->info.damage.lowerBound) + * monsterDamageAdjustmentAmount(attacker) / FP_FACTOR)); + sprintf(buf, "%s %s %s%s", attackerName, verb, defenderName, explicationClause); + if (sightUnseen) { + if (!rogue.heardCombatThisTurn) { + rogue.heardCombatThisTurn = true; + combatMessage("you hear combat in the distance", 0); + } + } else { + combatMessage(buf, messageColorFromVictim(defender)); } - resolvePronounEscapes(explicationClause, defender); - - if ((attacker->info.abilityFlags & MA_POISONS) && damage > 0) { - poisonDamage = damage; - damage = 1; + } + if (attacker == &player && rogue.weapon && (rogue.weapon->flags & ITEM_ATTACKS_STAGGER)) { + processStaggerHit(attacker, defender); + } + if (attacker->info.abilityFlags & SPECIAL_HIT) { + specialHit(attacker, defender, (attacker->info.abilityFlags & MA_POISONS) ? poisonDamage : damage); + } + if (armorRunicString[0]) { + message(armorRunicString, 0); + if (rogue.armor && (rogue.armor->flags & ITEM_RUNIC) && rogue.armor->enchant2 == A_BURDEN) { + strengthCheck(rogue.armor, true); } + } + } - if (inflictDamage(attacker, defender, damage, &red, false)) { // if the attack killed the defender - if (defenderWasAsleep || sneakAttack || defenderWasParalyzed || lungeAttack) { - sprintf(buf, "%s %s %s%s", attackerName, - ((defender->info.flags & MONST_INANIMATE) ? "destroyed" : "dispatched"), - defenderName, - explicationClause); - } else { - sprintf(buf, "%s %s %s%s", - attackerName, - ((defender->info.flags & MONST_INANIMATE) ? "destroyed" : "defeated"), - defenderName, - explicationClause); - } - if (sightUnseen) { - if (defender->info.flags & MONST_INANIMATE) { - combatMessage("you hear something get destroyed in combat", 0); - } else { - combatMessage("you hear something die in combat", 0); - } - } else { - combatMessage(buf, (damage > 0 ? messageColorFromVictim(defender) : &white)); - } - killCreature(defender, false); - if (&player == defender) { - gameOver(attacker->info.monsterName, false); - return true; - } else if (&player == attacker - && defender->info.monsterID == MK_DRAGON) { - - rogue.featRecord[FEAT_DRAGONSLAYER] = true; - } - } else { // if the defender survived - if (!rogue.blockCombatText && (canSeeMonster(attacker) || canSeeMonster(defender))) { - attackVerb(verb, attacker, max(damage - (attacker->info.damage.lowerBound * monsterDamageAdjustmentAmount(attacker) / FP_FACTOR), 0) * 100 - / max(1, (attacker->info.damage.upperBound - attacker->info.damage.lowerBound) * monsterDamageAdjustmentAmount(attacker) / FP_FACTOR)); - sprintf(buf, "%s %s %s%s", attackerName, verb, defenderName, explicationClause); - if (sightUnseen) { - if (!rogue.heardCombatThisTurn) { - rogue.heardCombatThisTurn = true; - combatMessage("you hear combat in the distance", 0); - } - } else { - combatMessage(buf, messageColorFromVictim(defender)); - } - } - if (attacker == &player && rogue.weapon && (rogue.weapon->flags & ITEM_ATTACKS_STAGGER)) { - processStaggerHit(attacker, defender); - } - if (attacker->info.abilityFlags & SPECIAL_HIT) { - specialHit(attacker, defender, (attacker->info.abilityFlags & MA_POISONS) ? poisonDamage : damage); - } - if (armorRunicString[0]) { - message(armorRunicString, 0); - if (rogue.armor && (rogue.armor->flags & ITEM_RUNIC) && rogue.armor->enchant2 == A_BURDEN) { - strengthCheck(rogue.armor, true); - } - } - } + moralAttack(attacker, defender); - moralAttack(attacker, defender); + if (attacker == &player && rogue.weapon && (rogue.weapon->flags & ITEM_RUNIC)) { + magicWeaponHit(defender, rogue.weapon, sneakAttack || defenderWasAsleep || defenderWasParalyzed); + } - if (attacker == &player && rogue.weapon && (rogue.weapon->flags & ITEM_RUNIC)) { - magicWeaponHit(defender, rogue.weapon, sneakAttack || defenderWasAsleep || defenderWasParalyzed); - } + if (attacker == &player && (defender->bookkeepingFlags & MB_IS_DYING) + && (defender->bookkeepingFlags & MB_HAS_SOUL)) { - if (attacker == &player - && (defender->bookkeepingFlags & MB_IS_DYING) - && (defender->bookkeepingFlags & MB_HAS_SOUL)) { + decrementWeaponAutoIDTimer(); + } - decrementWeaponAutoIDTimer(); - } + if (degradesAttackerWeapon && attacker == &player && rogue.weapon + && !(rogue.weapon->flags & ITEM_PROTECTED) + // Can't damage a Weapon of Acid Mound Slaying by attacking an acid mound... just ain't right! + && !((rogue.weapon->flags & ITEM_RUNIC) && rogue.weapon->enchant2 == W_SLAYING + && monsterIsInClass(defender, rogue.weapon->vorpalEnemy)) + && rogue.weapon->enchant1 >= -10) { - if (degradesAttackerWeapon - && attacker == &player - && rogue.weapon - && !(rogue.weapon->flags & ITEM_PROTECTED) - // Can't damage a Weapon of Acid Mound Slaying by attacking an acid mound... just ain't right! - && !((rogue.weapon->flags & ITEM_RUNIC) && rogue.weapon->enchant2 == W_SLAYING && monsterIsInClass(defender, rogue.weapon->vorpalEnemy)) - && rogue.weapon->enchant1 >= -10) { - - rogue.weapon->enchant1--; - if (rogue.weapon->quiverNumber) { - rogue.weapon->quiverNumber = rand_range(1, 60000); - } - equipItem(rogue.weapon, true, NULL); - itemName(rogue.weapon, buf2, false, false, NULL); - sprintf(buf, "your %s weakens!", buf2); - messageWithColor(buf, &itemMessageColor, 0); - checkForDisenchantment(rogue.weapon); - } + rogue.weapon->enchant1--; + if (rogue.weapon->quiverNumber) { + rogue.weapon->quiverNumber = rand_range(1, 60000); + } + equipItem(rogue.weapon, true, NULL); + itemName(rogue.weapon, buf2, false, false, NULL); + sprintf(buf, "your %s weakens!", buf2); + messageWithColor(buf, &itemMessageColor, 0); + checkForDisenchantment(rogue.weapon); + } - return true; - } else { // if the attack missed - if (!rogue.blockCombatText) { - if (sightUnseen) { - if (!rogue.heardCombatThisTurn) { - rogue.heardCombatThisTurn = true; - combatMessage("you hear combat in the distance", 0); - } - } else { - sprintf(buf, "%s missed %s", attackerName, defenderName); - combatMessage(buf, 0); - } + return true; + } else { // if the attack missed + if (!rogue.blockCombatText) { + if (sightUnseen) { + if (!rogue.heardCombatThisTurn) { + rogue.heardCombatThisTurn = true; + combatMessage("you hear combat in the distance", 0); } - return false; + } else { + sprintf(buf, "%s missed %s", attackerName, defenderName); + combatMessage(buf, 0); + } } + return false; + } } // Gets the length of a string without the four-character color escape sequences, since those aren't displayed. short strLenWithoutEscapes(const char *str) { - short i, count; - - count = 0; - for (i=0; str[i];) { - if (str[i] == COLOR_ESCAPE) { - i += 4; - continue; - } - count++; - i++; - } - return count; + short i, count; + + count = 0; + for (i = 0; str[i];) { + if (str[i] == COLOR_ESCAPE) { + i += 4; + continue; + } + count++; + i++; + } + return count; } // Buffer messages generated by combat until flushed by displayCombatText(). // Messages in the buffer are delimited by newlines. void combatMessage(char *theMsg, const color *theColor) { - short length; - char newMsg[COLS * 2 - 1]; // -1 for the newline when appending later - - if (theColor == 0) { - theColor = &white; - } - - newMsg[0] = '\0'; - encodeMessageColor(newMsg, 0, theColor); - length = strlen(newMsg); - strncat(&newMsg[length], theMsg, (COLS * 2 - 1) - length - 1); - - length = strlen(combatText); - - // Buffer combat messages here just for timing; otherwise player combat - // messages appear after monsters, rather than before. The -2 is for the - // newline and terminator. - if (length + strlen(newMsg) > COLS * 2 - 2) { - displayCombatText(); - } - - if (combatText[0]) { - snprintf(&combatText[length], COLS * 2 - length, "\n%s", newMsg); - } else { - strcpy(combatText, newMsg); - } + short length; + char newMsg[COLS * 2 - 1]; // -1 for the newline when appending later + + if (theColor == 0) { + theColor = &white; + } + + newMsg[0] = '\0'; + encodeMessageColor(newMsg, 0, theColor); + length = strlen(newMsg); + strncat(&newMsg[length], theMsg, (COLS * 2 - 1) - length - 1); + + length = strlen(combatText); + + // Buffer combat messages here just for timing; otherwise player combat + // messages appear after monsters, rather than before. The -2 is for the + // newline and terminator. + if (length + strlen(newMsg) > COLS * 2 - 2) { + displayCombatText(); + } + + if (combatText[0]) { + snprintf(&combatText[length], COLS * 2 - length, "\n%s", newMsg); + } else { + strcpy(combatText, newMsg); + } } // Flush any buffered, newline-delimited combat messages, passing each to @@ -1302,459 +1261,444 @@ void combatMessage(char *theMsg, const color *theColor) { // be flushed by a number of different callers. One is message() itself // creating a recursion, which this function is responsible for terminating. void displayCombatText() { - char buf[COLS * 2]; - char *start, *end; + char buf[COLS * 2]; + char *start, *end; - // message itself will call displayCombatText. For this guard to terminate - // the recursion, we need to copy combatText out and empty it before - // calling message. - if (combatText[0] == '\0') { - return; - } + // message itself will call displayCombatText. For this guard to terminate + // the recursion, we need to copy combatText out and empty it before + // calling message. + if (combatText[0] == '\0') { + return; + } - strcpy(buf, combatText); - combatText[0] = '\0'; + strcpy(buf, combatText); + combatText[0] = '\0'; - start = buf; - for (end = start; *end != '\0'; end++) { - if (*end == '\n') { - *end = '\0'; - message(start, FOLDABLE | (rogue.cautiousMode ? REQUIRE_ACKNOWLEDGMENT : 0)); - start = end + 1; - } + start = buf; + for (end = start; *end != '\0'; end++) { + if (*end == '\n') { + *end = '\0'; + message(start, FOLDABLE | (rogue.cautiousMode ? REQUIRE_ACKNOWLEDGMENT : 0)); + start = end + 1; } + } - message(start, FOLDABLE | (rogue.cautiousMode ? REQUIRE_ACKNOWLEDGMENT : 0)); + message(start, FOLDABLE | (rogue.cautiousMode ? REQUIRE_ACKNOWLEDGMENT : 0)); - rogue.cautiousMode = false; + rogue.cautiousMode = false; } void flashMonster(creature *monst, const color *theColor, short strength) { - if (!theColor) { - return; - } - if (!(monst->bookkeepingFlags & MB_WILL_FLASH) || monst->flashStrength < strength) { - monst->bookkeepingFlags |= MB_WILL_FLASH; - monst->flashStrength = strength; - monst->flashColor = *theColor; - rogue.creaturesWillFlashThisTurn = true; - } + if (!theColor) { + return; + } + if (!(monst->bookkeepingFlags & MB_WILL_FLASH) || monst->flashStrength < strength) { + monst->bookkeepingFlags |= MB_WILL_FLASH; + monst->flashStrength = strength; + monst->flashColor = *theColor; + rogue.creaturesWillFlashThisTurn = true; + } } boolean canAbsorb(creature *ally, boolean ourBolts[], creature *prey, short **grid) { - short i; - - if (ally->creatureState == MONSTER_ALLY - && ally->newPowerCount > 0 - && (!isPosInMap(ally->targetCorpseLoc)) - && !((ally->info.flags | prey->info.flags) & (MONST_INANIMATE | MONST_IMMOBILE)) - && !monsterAvoids(ally, prey->loc) - && grid[ally->loc.x][ally->loc.y] <= 10) { - - if (~(ally->info.abilityFlags) & prey->info.abilityFlags & LEARNABLE_ABILITIES) { - return true; - } else if (~(ally->info.flags) & prey->info.flags & LEARNABLE_BEHAVIORS) { - return true; - } else { - for (i = 0; i < gameConst->numberBoltKinds; i++) { - ourBolts[i] = false; - } - for (i = 0; ally->info.bolts[i] != BOLT_NONE; i++) { - ourBolts[ally->info.bolts[i]] = true; - } + short i; - for (i=0; prey->info.bolts[i] != BOLT_NONE; i++) { - if (!(boltCatalog[prey->info.bolts[i]].flags & BF_NOT_LEARNABLE) - && !ourBolts[prey->info.bolts[i]]) { + if (ally->creatureState == MONSTER_ALLY && ally->newPowerCount > 0 && (!isPosInMap(ally->targetCorpseLoc)) + && !((ally->info.flags | prey->info.flags) & (MONST_INANIMATE | MONST_IMMOBILE)) + && !monsterAvoids(ally, prey->loc) && grid[ally->loc.x][ally->loc.y] <= 10) { - return true; - } - } + if (~(ally->info.abilityFlags) & prey->info.abilityFlags & LEARNABLE_ABILITIES) { + return true; + } else if (~(ally->info.flags) & prey->info.flags & LEARNABLE_BEHAVIORS) { + return true; + } else { + for (i = 0; i < gameConst->numberBoltKinds; i++) { + ourBolts[i] = false; + } + for (i = 0; ally->info.bolts[i] != BOLT_NONE; i++) { + ourBolts[ally->info.bolts[i]] = true; + } + + for (i = 0; prey->info.bolts[i] != BOLT_NONE; i++) { + if (!(boltCatalog[prey->info.bolts[i]].flags & BF_NOT_LEARNABLE) && !ourBolts[prey->info.bolts[i]]) { + + return true; } + } } - return false; + } + return false; } boolean anyoneWantABite(creature *decedent) { - short candidates, randIndex, i; - short **grid; - boolean success = false; - boolean *ourBolts; - - ourBolts = (boolean *)calloc(gameConst->numberBoltKinds, sizeof(boolean)); - - candidates = 0; - if ((!(decedent->info.abilityFlags & LEARNABLE_ABILITIES) - && !(decedent->info.flags & LEARNABLE_BEHAVIORS) - && decedent->info.bolts[0] == BOLT_NONE) - || (cellHasTerrainFlag(decedent->loc.x, decedent->loc.y, T_PATHING_BLOCKER)) - || decedent->info.monsterID == MK_SPECTRAL_IMAGE - || (decedent->info.flags & (MONST_INANIMATE | MONST_IMMOBILE))) { - - return false; - } - - grid = allocGrid(); - fillGrid(grid, 0); - calculateDistances(grid, decedent->loc.x, decedent->loc.y, T_PATHING_BLOCKER, NULL, true, true); + short candidates, randIndex, i; + short **grid; + boolean success = false; + boolean *ourBolts; + + ourBolts = (boolean *)calloc(gameConst->numberBoltKinds, sizeof(boolean)); + + candidates = 0; + if ((!(decedent->info.abilityFlags & LEARNABLE_ABILITIES) && !(decedent->info.flags & LEARNABLE_BEHAVIORS) + && decedent->info.bolts[0] == BOLT_NONE) + || (cellHasTerrainFlag(decedent->loc.x, decedent->loc.y, T_PATHING_BLOCKER)) + || decedent->info.monsterID == MK_SPECTRAL_IMAGE || (decedent->info.flags & (MONST_INANIMATE | MONST_IMMOBILE))) { + + return false; + } + + grid = allocGrid(); + fillGrid(grid, 0); + calculateDistances(grid, decedent->loc.x, decedent->loc.y, T_PATHING_BLOCKER, NULL, true, true); + for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) { + creature *ally = nextCreature(&it); + if (canAbsorb(ally, ourBolts, decedent, grid)) { + candidates++; + } + } + if (candidates > 0) { + randIndex = rand_range(1, candidates); + creature *firstAlly = NULL; for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) { - creature *ally = nextCreature(&it); - if (canAbsorb(ally, ourBolts, decedent, grid)) { - candidates++; + creature *ally = nextCreature(&it); + // CanAbsorb() populates ourBolts if it returns true and there are no learnable behaviors or flags: + if (canAbsorb(ally, ourBolts, decedent, grid) && !--randIndex) { + firstAlly = ally; + break; + } + } + if (firstAlly) { + firstAlly->targetCorpseLoc = decedent->loc; + strcpy(firstAlly->targetCorpseName, decedent->info.monsterName); + firstAlly->corpseAbsorptionCounter = 20; // 20 turns to get there and start eating before he loses interest + + // Choose a superpower. + // First, select from among learnable ability or behavior flags, if one is available. + candidates = 0; + for (i = 0; i < 32; i++) { + if (Fl(i) & ~(firstAlly->info.abilityFlags) & decedent->info.abilityFlags & LEARNABLE_ABILITIES) { + candidates++; } - } - if (candidates > 0) { + } + for (i = 0; i < 32; i++) { + if (Fl(i) & ~(firstAlly->info.flags) & decedent->info.flags & LEARNABLE_BEHAVIORS) { + candidates++; + } + } + if (candidates > 0) { randIndex = rand_range(1, candidates); - creature *firstAlly = NULL; - for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) { - creature *ally = nextCreature(&it); - // CanAbsorb() populates ourBolts if it returns true and there are no learnable behaviors or flags: - if (canAbsorb(ally, ourBolts, decedent, grid) && !--randIndex) { - firstAlly = ally; - break; - } + for (i = 0; i < 32; i++) { + if ((Fl(i) & ~(firstAlly->info.abilityFlags) & decedent->info.abilityFlags & LEARNABLE_ABILITIES) + && !--randIndex) { + + firstAlly->absorptionFlags = Fl(i); + firstAlly->absorbBehavior = false; + success = true; + break; + } } - if (firstAlly) { - firstAlly->targetCorpseLoc = decedent->loc; - strcpy(firstAlly->targetCorpseName, decedent->info.monsterName); - firstAlly->corpseAbsorptionCounter = 20; // 20 turns to get there and start eating before he loses interest - - // Choose a superpower. - // First, select from among learnable ability or behavior flags, if one is available. - candidates = 0; - for (i=0; i<32; i++) { - if (Fl(i) & ~(firstAlly->info.abilityFlags) & decedent->info.abilityFlags & LEARNABLE_ABILITIES) { - candidates++; - } - } - for (i=0; i<32; i++) { - if (Fl(i) & ~(firstAlly->info.flags) & decedent->info.flags & LEARNABLE_BEHAVIORS) { - candidates++; - } - } - if (candidates > 0) { - randIndex = rand_range(1, candidates); - for (i=0; i<32; i++) { - if ((Fl(i) & ~(firstAlly->info.abilityFlags) & decedent->info.abilityFlags & LEARNABLE_ABILITIES) - && !--randIndex) { - - firstAlly->absorptionFlags = Fl(i); - firstAlly->absorbBehavior = false; - success = true; - break; - } - } - for (i=0; i<32 && !success; i++) { - if ((Fl(i) & ~(firstAlly->info.flags) & decedent->info.flags & LEARNABLE_BEHAVIORS) - && !--randIndex) { - - firstAlly->absorptionFlags = Fl(i); - firstAlly->absorbBehavior = true; - success = true; - break; - } - } - } else if (decedent->info.bolts[0] != BOLT_NONE) { - // If there are no learnable ability or behavior flags, pick a learnable bolt. - candidates = 0; - for (i=0; decedent->info.bolts[i] != BOLT_NONE; i++) { - if (!(boltCatalog[decedent->info.bolts[i]].flags & BF_NOT_LEARNABLE) - && !ourBolts[decedent->info.bolts[i]]) { - - candidates++; - } - } - if (candidates > 0) { - randIndex = rand_range(1, candidates); - for (i=0; decedent->info.bolts[i] != BOLT_NONE; i++) { - if (!(boltCatalog[decedent->info.bolts[i]].flags & BF_NOT_LEARNABLE) - && !ourBolts[decedent->info.bolts[i]] - && !--randIndex) { - - firstAlly->absorptionBolt = decedent->info.bolts[i]; - success = true; - break; - } - } - } + for (i = 0; i < 32 && !success; i++) { + if ((Fl(i) & ~(firstAlly->info.flags) & decedent->info.flags & LEARNABLE_BEHAVIORS) && !--randIndex) { + + firstAlly->absorptionFlags = Fl(i); + firstAlly->absorbBehavior = true; + success = true; + break; + } + } + } else if (decedent->info.bolts[0] != BOLT_NONE) { + // If there are no learnable ability or behavior flags, pick a learnable bolt. + candidates = 0; + for (i = 0; decedent->info.bolts[i] != BOLT_NONE; i++) { + if (!(boltCatalog[decedent->info.bolts[i]].flags & BF_NOT_LEARNABLE) && !ourBolts[decedent->info.bolts[i]]) { + + candidates++; + } + } + if (candidates > 0) { + randIndex = rand_range(1, candidates); + for (i = 0; decedent->info.bolts[i] != BOLT_NONE; i++) { + if (!(boltCatalog[decedent->info.bolts[i]].flags & BF_NOT_LEARNABLE) && !ourBolts[decedent->info.bolts[i]] + && !--randIndex) { + + firstAlly->absorptionBolt = decedent->info.bolts[i]; + success = true; + break; } + } } + } } - freeGrid(grid); - free(ourBolts); - return success; + } + freeGrid(grid); + free(ourBolts); + return success; } -#define MIN_FLASH_STRENGTH 50 +#define MIN_FLASH_STRENGTH 50 void inflictLethalDamage(creature *attacker, creature *defender) { - inflictDamage(attacker, defender, defender->currentHP, NULL, true); + inflictDamage(attacker, defender, defender->currentHP, NULL, true); } // returns true if this was a killing stroke; does NOT call killCreature // flashColor indicates the color that the damage will cause the creature to flash -boolean inflictDamage(creature *attacker, creature *defender, - short damage, const color *flashColor, boolean ignoresProtectionShield) { - dungeonFeature theBlood; - short transferenceAmount; +boolean inflictDamage(creature *attacker, creature *defender, short damage, const color *flashColor, + boolean ignoresProtectionShield) { + dungeonFeature theBlood; + short transferenceAmount; - if (damage == 0 - || (defender->info.flags & MONST_INVULNERABLE)) { + if (damage == 0 || (defender->info.flags & MONST_INVULNERABLE)) { - return false; - } + return false; + } - if (!ignoresProtectionShield - && defender->status[STATUS_SHIELDED]) { + if (!ignoresProtectionShield && defender->status[STATUS_SHIELDED]) { - if (defender->status[STATUS_SHIELDED] > damage * 10) { - defender->status[STATUS_SHIELDED] -= damage * 10; - damage = 0; - } else { - damage -= (defender->status[STATUS_SHIELDED] + 9) / 10; - defender->status[STATUS_SHIELDED] = defender->maxStatus[STATUS_SHIELDED] = 0; - } + if (defender->status[STATUS_SHIELDED] > damage * 10) { + defender->status[STATUS_SHIELDED] -= damage * 10; + damage = 0; + } else { + damage -= (defender->status[STATUS_SHIELDED] + 9) / 10; + defender->status[STATUS_SHIELDED] = defender->maxStatus[STATUS_SHIELDED] = 0; } + } - defender->bookkeepingFlags &= ~MB_ABSORBING; // Stop eating a corpse if you are getting hurt. + defender->bookkeepingFlags &= ~MB_ABSORBING; // Stop eating a corpse if you are getting hurt. - // bleed all over the place, proportionately to damage inflicted: - if (damage > 0 && defender->info.bloodType) { - theBlood = dungeonFeatureCatalog[defender->info.bloodType]; - theBlood.startProbability = (theBlood.startProbability * (15 + min(damage, defender->currentHP) * 3 / 2) / 100); - if (theBlood.layer == GAS) { - theBlood.startProbability *= 100; - } - spawnDungeonFeature(defender->loc.x, defender->loc.y, &theBlood, true, false); + // bleed all over the place, proportionately to damage inflicted: + if (damage > 0 && defender->info.bloodType) { + theBlood = dungeonFeatureCatalog[defender->info.bloodType]; + theBlood.startProbability = (theBlood.startProbability * (15 + min(damage, defender->currentHP) * 3 / 2) / 100); + if (theBlood.layer == GAS) { + theBlood.startProbability *= 100; } + spawnDungeonFeature(defender->loc.x, defender->loc.y, &theBlood, true, false); + } - if (defender != &player && defender->creatureState == MONSTER_SLEEPING) { - wakeUp(defender); - } + if (defender != &player && defender->creatureState == MONSTER_SLEEPING) { + wakeUp(defender); + } - if (defender == &player - && rogue.easyMode - && damage > 0) { - damage = max(1, damage/5); - } + if (defender == &player && rogue.easyMode && damage > 0) { + damage = max(1, damage / 5); + } - if (((attacker == &player && rogue.transference) || (attacker && attacker != &player && (attacker->info.abilityFlags & MA_TRANSFERENCE))) - && !(defender->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) { + if (((attacker == &player && rogue.transference) + || (attacker && attacker != &player && (attacker->info.abilityFlags & MA_TRANSFERENCE))) + && !(defender->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) { - transferenceAmount = min(damage, defender->currentHP); // Maximum transferred damage can't exceed the victim's remaining health. + transferenceAmount + = min(damage, defender->currentHP); // Maximum transferred damage can't exceed the victim's remaining health. - if (attacker == &player) { - transferenceAmount = transferenceAmount * rogue.transference / gameConst->playerTransferenceRatio; - if (transferenceAmount == 0) { - transferenceAmount = ((rogue.transference > 0) ? 1 : -1); - } - } else if (attacker->creatureState == MONSTER_ALLY) { - transferenceAmount = transferenceAmount * 4 / 10; // allies get 40% recovery rate - } else { - transferenceAmount = transferenceAmount * 9 / 10; // enemies get 90% recovery rate, deal with it - } + if (attacker == &player) { + transferenceAmount = transferenceAmount * rogue.transference / gameConst->playerTransferenceRatio; + if (transferenceAmount == 0) { + transferenceAmount = ((rogue.transference > 0) ? 1 : -1); + } + } else if (attacker->creatureState == MONSTER_ALLY) { + transferenceAmount = transferenceAmount * 4 / 10; // allies get 40% recovery rate + } else { + transferenceAmount = transferenceAmount * 9 / 10; // enemies get 90% recovery rate, deal with it + } - attacker->currentHP += transferenceAmount; + attacker->currentHP += transferenceAmount; - if (attacker == &player && player.currentHP <= 0) { - gameOver("Drained by a cursed ring", true); - return false; - } + if (attacker == &player && player.currentHP <= 0) { + gameOver("Drained by a cursed ring", true); + return false; } + } - if (defender->currentHP <= damage) { // killed - return true; - } else { // survived - if (damage < 0 && defender->currentHP - damage > defender->info.maxHP) { - defender->currentHP = max(defender->currentHP, defender->info.maxHP); - } else { - defender->currentHP -= damage; // inflict the damage! - if (defender == &player && damage > 0) { - rogue.featRecord[FEAT_INDOMITABLE] = false; - } - } + if (defender->currentHP <= damage) { // killed + return true; + } else { // survived + if (damage < 0 && defender->currentHP - damage > defender->info.maxHP) { + defender->currentHP = max(defender->currentHP, defender->info.maxHP); + } else { + defender->currentHP -= damage; // inflict the damage! + if (defender == &player && damage > 0) { + rogue.featRecord[FEAT_INDOMITABLE] = false; + } + } - if (defender != &player && defender->creatureState != MONSTER_ALLY - && defender->info.flags & MONST_FLEES_NEAR_DEATH - && defender->info.maxHP / 4 >= defender->currentHP) { + if (defender != &player && defender->creatureState != MONSTER_ALLY && defender->info.flags & MONST_FLEES_NEAR_DEATH + && defender->info.maxHP / 4 >= defender->currentHP) { - defender->creatureState = MONSTER_FLEEING; - } - if (flashColor && damage > 0) { - flashMonster(defender, flashColor, MIN_FLASH_STRENGTH + (100 - MIN_FLASH_STRENGTH) * damage / defender->info.maxHP); - } + defender->creatureState = MONSTER_FLEEING; + } + if (flashColor && damage > 0) { + flashMonster(defender, flashColor, + MIN_FLASH_STRENGTH + (100 - MIN_FLASH_STRENGTH) * damage / defender->info.maxHP); } + } - refreshSideBar(-1, -1, false); - return false; + refreshSideBar(-1, -1, false); + return false; } void addPoison(creature *monst, short durationIncrement, short concentrationIncrement) { - extern const color poisonColor; - if (durationIncrement > 0) { - if (monst == &player && !player.status[STATUS_POISONED]) { - combatMessage("scalding poison fills your veins", &badMessageColor); - } - if (!monst->status[STATUS_POISONED]) { - monst->maxStatus[STATUS_POISONED] = 0; - } - monst->poisonAmount += concentrationIncrement; - if (monst->poisonAmount == 0) { - monst->poisonAmount = 1; - } - monst->status[STATUS_POISONED] += durationIncrement; - monst->maxStatus[STATUS_POISONED] = monst->info.maxHP / monst->poisonAmount; + extern const color poisonColor; + if (durationIncrement > 0) { + if (monst == &player && !player.status[STATUS_POISONED]) { + combatMessage("scalding poison fills your veins", &badMessageColor); + } + if (!monst->status[STATUS_POISONED]) { + monst->maxStatus[STATUS_POISONED] = 0; + } + monst->poisonAmount += concentrationIncrement; + if (monst->poisonAmount == 0) { + monst->poisonAmount = 1; + } + monst->status[STATUS_POISONED] += durationIncrement; + monst->maxStatus[STATUS_POISONED] = monst->info.maxHP / monst->poisonAmount; - if (canSeeMonster(monst)) { - flashMonster(monst, &poisonColor, 100); - } + if (canSeeMonster(monst)) { + flashMonster(monst, &poisonColor, 100); } + } } - // Marks the decedent as dying, but does not remove it from the monster chain to avoid iterator invalidation; // that is done in `removeDeadMonsters`. -// Use "administrativeDeath" if the monster is being deleted for administrative purposes, as opposed to dying as a result of physical actions. -// AdministrativeDeath means the monster simply disappears, with no messages, dropped item, DFs or other effect. +// Use "administrativeDeath" if the monster is being deleted for administrative purposes, as opposed to dying as a +// result of physical actions. AdministrativeDeath means the monster simply disappears, with no messages, dropped item, +// DFs or other effect. void killCreature(creature *decedent, boolean administrativeDeath) { - short x, y; - char monstName[DCOLS], buf[DCOLS * 3]; - - if (decedent->bookkeepingFlags & (MB_IS_DYING | MB_HAS_DIED)) { - // monster has already been killed; let's avoid overkill - return; - } - - if (decedent != &player) { - decedent->bookkeepingFlags |= MB_IS_DYING; + short x, y; + char monstName[DCOLS], buf[DCOLS * 3]; + + if (decedent->bookkeepingFlags & (MB_IS_DYING | MB_HAS_DIED)) { + // monster has already been killed; let's avoid overkill + return; + } + + if (decedent != &player) { + decedent->bookkeepingFlags |= MB_IS_DYING; + } + + if (rogue.lastTarget == decedent) { + rogue.lastTarget = NULL; + } + if (rogue.yendorWarden == decedent) { + rogue.yendorWarden = NULL; + } + + if (decedent->carriedItem) { + if (administrativeDeath) { + deleteItem(decedent->carriedItem); + decedent->carriedItem = NULL; + } else { + makeMonsterDropItem(decedent); } + } - if (rogue.lastTarget == decedent) { - rogue.lastTarget = NULL; - } - if (rogue.yendorWarden == decedent) { - rogue.yendorWarden = NULL; - } + if (!administrativeDeath && (decedent->info.abilityFlags & MA_DF_ON_DEATH) + && !(decedent->bookkeepingFlags & MB_IS_FALLING)) { + spawnDungeonFeature(decedent->loc.x, decedent->loc.y, &dungeonFeatureCatalog[decedent->info.DFType], true, false); - if (decedent->carriedItem) { - if (administrativeDeath) { - deleteItem(decedent->carriedItem); - decedent->carriedItem = NULL; - } else { - makeMonsterDropItem(decedent); - } + if (monsterText[decedent->info.monsterID].DFMessage[0] && canSeeMonster(decedent)) { + monsterName(monstName, decedent, true); + snprintf(buf, DCOLS * 3, "%s %s", monstName, monsterText[decedent->info.monsterID].DFMessage); + resolvePronounEscapes(buf, decedent); + message(buf, 0); } + } - if (!administrativeDeath && (decedent->info.abilityFlags & MA_DF_ON_DEATH) - && !(decedent->bookkeepingFlags & MB_IS_FALLING)) { - spawnDungeonFeature(decedent->loc.x, decedent->loc.y, &dungeonFeatureCatalog[decedent->info.DFType], true, false); + if (decedent == &player) { // the player died + // game over handled elsewhere + } else { + if (!administrativeDeath && decedent->creatureState == MONSTER_ALLY && !canSeeMonster(decedent) + && !(decedent->info.flags & MONST_INANIMATE) && !(decedent->bookkeepingFlags & MB_BOUND_TO_LEADER) + && !decedent->carriedMonster) { - if (monsterText[decedent->info.monsterID].DFMessage[0] && canSeeMonster(decedent)) { - monsterName(monstName, decedent, true); - snprintf(buf, DCOLS * 3, "%s %s", monstName, monsterText[decedent->info.monsterID].DFMessage); - resolvePronounEscapes(buf, decedent); - message(buf, 0); - } + messageWithColor("you feel a sense of loss.", &badMessageColor, 0); } - - if (decedent == &player) { // the player died - // game over handled elsewhere + x = decedent->loc.x; + y = decedent->loc.y; + if (decedent->bookkeepingFlags & MB_IS_DORMANT) { + pmap[x][y].flags &= ~HAS_DORMANT_MONSTER; } else { - if (!administrativeDeath - && decedent->creatureState == MONSTER_ALLY - && !canSeeMonster(decedent) - && !(decedent->info.flags & MONST_INANIMATE) - && !(decedent->bookkeepingFlags & MB_BOUND_TO_LEADER) - && !decedent->carriedMonster) { - - messageWithColor("you feel a sense of loss.", &badMessageColor, 0); - } - x = decedent->loc.x; - y = decedent->loc.y; - if (decedent->bookkeepingFlags & MB_IS_DORMANT) { - pmap[x][y].flags &= ~HAS_DORMANT_MONSTER; - } else { - pmap[x][y].flags &= ~HAS_MONSTER; - } - - // This must be done at the same time as removing the HAS_MONSTER flag, or game state might - // end up inconsistent. - decedent->bookkeepingFlags |= MB_HAS_DIED; - if (administrativeDeath) { - decedent->bookkeepingFlags |= MB_ADMINISTRATIVE_DEATH; + pmap[x][y].flags &= ~HAS_MONSTER; + } + + // This must be done at the same time as removing the HAS_MONSTER flag, or game state might + // end up inconsistent. + decedent->bookkeepingFlags |= MB_HAS_DIED; + if (administrativeDeath) { + decedent->bookkeepingFlags |= MB_ADMINISTRATIVE_DEATH; + } + + if (!administrativeDeath && !(decedent->bookkeepingFlags & MB_IS_DORMANT)) { + // Was there another monster inside? + if (decedent->carriedMonster) { + // Insert it into the chain. + creature *carriedMonster = decedent->carriedMonster; + decedent->carriedMonster = NULL; + prependCreature(monsters, carriedMonster); + + carriedMonster->loc.x = x; + carriedMonster->loc.y = y; + carriedMonster->ticksUntilTurn = 200; + pmap[x][y].flags |= HAS_MONSTER; + fadeInMonster(carriedMonster); + + if (canSeeMonster(carriedMonster)) { + monsterName(monstName, carriedMonster, true); + sprintf(buf, "%s appears", monstName); + combatMessage(buf, NULL); } - if (!administrativeDeath && !(decedent->bookkeepingFlags & MB_IS_DORMANT)) { - // Was there another monster inside? - if (decedent->carriedMonster) { - // Insert it into the chain. - creature *carriedMonster = decedent->carriedMonster; - decedent->carriedMonster = NULL; - prependCreature(monsters, carriedMonster); - - carriedMonster->loc.x = x; - carriedMonster->loc.y = y; - carriedMonster->ticksUntilTurn = 200; - pmap[x][y].flags |= HAS_MONSTER; - fadeInMonster(carriedMonster); - - if (canSeeMonster(carriedMonster)) { - monsterName(monstName, carriedMonster, true); - sprintf(buf, "%s appears", monstName); - combatMessage(buf, NULL); - } - - applyInstantTileEffectsToCreature(carriedMonster); - } - anyoneWantABite(decedent); - refreshDungeonCell(x, y); - } - } - decedent->currentHP = 0; - demoteMonsterFromLeadership(decedent); - if (decedent->leader) { - checkForContinuedLeadership(decedent->leader); - } + applyInstantTileEffectsToCreature(carriedMonster); + } + anyoneWantABite(decedent); + refreshDungeonCell(x, y); + } + } + decedent->currentHP = 0; + demoteMonsterFromLeadership(decedent); + if (decedent->leader) { + checkForContinuedLeadership(decedent->leader); + } } void buildHitList(creature **hitList, const creature *attacker, creature *defender, const boolean sweep) { - short i, x, y, newX, newY, newestX, newestY; - enum directions dir, newDir; - - x = attacker->loc.x; - y = attacker->loc.y; - newX = defender->loc.x; - newY = defender->loc.y; - - dir = NO_DIRECTION; - for (i = 0; i < DIRECTION_COUNT; i++) { - if (nbDirs[i][0] == newX - x - && nbDirs[i][1] == newY - y) { - - dir = i; - break; - } - } - - if (sweep) { - if (dir == NO_DIRECTION) { - dir = UP; // Just pick one. + short i, x, y, newX, newY, newestX, newestY; + enum directions dir, newDir; + + x = attacker->loc.x; + y = attacker->loc.y; + newX = defender->loc.x; + newY = defender->loc.y; + + dir = NO_DIRECTION; + for (i = 0; i < DIRECTION_COUNT; i++) { + if (nbDirs[i][0] == newX - x && nbDirs[i][1] == newY - y) { + + dir = i; + break; + } + } + + if (sweep) { + if (dir == NO_DIRECTION) { + dir = UP; // Just pick one. + } + for (i = 0; i < 8; i++) { + newDir = (dir + i) % DIRECTION_COUNT; + newestX = x + cDirs[newDir][0]; + newestY = y + cDirs[newDir][1]; + if (coordinatesAreInMap(newestX, newestY) && (pmap[newestX][newestY].flags & (HAS_MONSTER | HAS_PLAYER))) { + defender = monsterAtLoc((pos){newestX, newestY}); + if (defender && monsterWillAttackTarget(attacker, defender) + && (!cellHasTerrainFlag(defender->loc.x, defender->loc.y, T_OBSTRUCTS_PASSABILITY) + || (defender->info.flags & MONST_ATTACKABLE_THRU_WALLS))) { + + hitList[i] = defender; } - for (i=0; i<8; i++) { - newDir = (dir + i) % DIRECTION_COUNT; - newestX = x + cDirs[newDir][0]; - newestY = y + cDirs[newDir][1]; - if (coordinatesAreInMap(newestX, newestY) && (pmap[newestX][newestY].flags & (HAS_MONSTER | HAS_PLAYER))) { - defender = monsterAtLoc((pos){ newestX, newestY }); - if (defender - && monsterWillAttackTarget(attacker, defender) - && (!cellHasTerrainFlag(defender->loc.x, defender->loc.y, T_OBSTRUCTS_PASSABILITY) || (defender->info.flags & MONST_ATTACKABLE_THRU_WALLS))) { - - hitList[i] = defender; - } - } - } - } else { - hitList[0] = defender; + } } + } else { + hitList[0] = defender; + } } diff --git a/src/brogue/Dijkstra.c b/src/brogue/Dijkstra.c index a9f07d15..367a410a 100644 --- a/src/brogue/Dijkstra.c +++ b/src/brogue/Dijkstra.c @@ -28,232 +28,239 @@ #include "Globals.h" typedef struct pdsLink { - short distance; - short cost; - struct pdsLink *left; - struct pdsLink *right; + short distance; + short cost; + struct pdsLink *left; + struct pdsLink *right; } pdsLink; typedef struct pdsMap { - pdsLink front; - pdsLink links[DCOLS * DROWS]; + pdsLink front; + pdsLink links[DCOLS * DROWS]; } pdsMap; static void pdsUpdate(pdsMap *map, boolean useDiagonals) { - short dirs = useDiagonals ? 8 : 4; - - pdsLink *head = map->front.right; - map->front.right = NULL; - - while (head != NULL) { - for (short dir = 0; dir < dirs; dir++) { - pdsLink *link = head + (nbDirs[dir][0] + DCOLS * nbDirs[dir][1]); - if (link < map->links || link >= map->links + DCOLS * DROWS) continue; - - // verify passability - if (link->cost < 0) continue; - if (dir >= 4) { - pdsLink *way1 = head + nbDirs[dir][0]; - pdsLink *way2 = head + DCOLS * nbDirs[dir][1]; - if (way1->cost == PDS_OBSTRUCTION || way2->cost == PDS_OBSTRUCTION) continue; - } - - if (head->distance + link->cost < link->distance) { - link->distance = head->distance + link->cost; - - // reinsert the touched cell; it'll be close to the beginning of the list now, so - // this will be very fast. start by removing it. - - if (link->right != NULL) link->right->left = link->left; - if (link->left != NULL) link->left->right = link->right; - - pdsLink *left = head; - pdsLink *right = head->right; - while (right != NULL && right->distance < link->distance) { - left = right; - right = right->right; - } - if (left != NULL) left->right = link; - link->right = right; - link->left = left; - if (right != NULL) right->left = link; - } + short dirs = useDiagonals ? 8 : 4; + + pdsLink *head = map->front.right; + map->front.right = NULL; + + while (head != NULL) { + for (short dir = 0; dir < dirs; dir++) { + pdsLink *link = head + (nbDirs[dir][0] + DCOLS * nbDirs[dir][1]); + if (link < map->links || link >= map->links + DCOLS * DROWS) + continue; + + // verify passability + if (link->cost < 0) + continue; + if (dir >= 4) { + pdsLink *way1 = head + nbDirs[dir][0]; + pdsLink *way2 = head + DCOLS * nbDirs[dir][1]; + if (way1->cost == PDS_OBSTRUCTION || way2->cost == PDS_OBSTRUCTION) + continue; + } + + if (head->distance + link->cost < link->distance) { + link->distance = head->distance + link->cost; + + // reinsert the touched cell; it'll be close to the beginning of the list now, so + // this will be very fast. start by removing it. + + if (link->right != NULL) + link->right->left = link->left; + if (link->left != NULL) + link->left->right = link->right; + + pdsLink *left = head; + pdsLink *right = head->right; + while (right != NULL && right->distance < link->distance) { + left = right; + right = right->right; } + if (left != NULL) + left->right = link; + link->right = right; + link->left = left; + if (right != NULL) + right->left = link; + } + } - pdsLink *right = head->right; + pdsLink *right = head->right; - head->left = NULL; - head->right = NULL; + head->left = NULL; + head->right = NULL; - head = right; - } + head = right; + } } static void pdsClear(pdsMap *map, short maxDistance) { - map->front.right = NULL; + map->front.right = NULL; - for (int i=0; i < DCOLS*DROWS; i++) { - map->links[i].distance = maxDistance; - map->links[i].left = NULL; - map->links[i].right = NULL; - } + for (int i = 0; i < DCOLS * DROWS; i++) { + map->links[i].distance = maxDistance; + map->links[i].left = NULL; + map->links[i].right = NULL; + } } static void pdsSetDistance(pdsMap *map, short x, short y, short distance) { - if (x > 0 && y > 0 && x < DCOLS - 1 && y < DROWS - 1) { - pdsLink *link = PDS_CELL(map, x, y); - if (link->distance > distance) { - link->distance = distance; - - if (link->right != NULL) link->right->left = link->left; - if (link->left != NULL) link->left->right = link->right; - - pdsLink *left = &map->front; - pdsLink *right = map->front.right; - - while (right != NULL && right->distance < link->distance) { - left = right; - right = right->right; - } - - link->right = right; - link->left = left; - left->right = link; - if (right != NULL) right->left = link; - } + if (x > 0 && y > 0 && x < DCOLS - 1 && y < DROWS - 1) { + pdsLink *link = PDS_CELL(map, x, y); + if (link->distance > distance) { + link->distance = distance; + + if (link->right != NULL) + link->right->left = link->left; + if (link->left != NULL) + link->left->right = link->right; + + pdsLink *left = &map->front; + pdsLink *right = map->front.right; + + while (right != NULL && right->distance < link->distance) { + left = right; + right = right->right; + } + + link->right = right; + link->left = left; + left->right = link; + if (right != NULL) + right->left = link; } + } } static void pdsBatchInput(pdsMap *map, short **distanceMap, short **costMap, short maxDistance) { - pdsLink *left = NULL; - pdsLink *right = NULL; - - map->front.right = NULL; - for (int i=0; idistance = distanceMap[i][j]; - } else { - if (costMap != NULL) { - // totally hackish; refactor - link->distance = maxDistance; - } - } - - int cost; - - if (i == 0 || j == 0 || i == DCOLS - 1 || j == DROWS - 1) { - cost = PDS_OBSTRUCTION; - } else if (costMap == NULL) { - if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY) && cellHasTerrainFlag(i, j, T_OBSTRUCTS_DIAGONAL_MOVEMENT)) cost = PDS_OBSTRUCTION; - else cost = PDS_FORBIDDEN; - } else { - cost = costMap[i][j]; - } - - link->cost = cost; - - if (cost > 0) { - if (link->distance < maxDistance) { - if (right == NULL || right->distance > link->distance) { - // left and right are used to traverse the list; if many cells have similar values, - // some time can be saved by not clearing them with each insertion. this time, - // sadly, we have to start from the front. - - left = &map->front; - right = map->front.right; - } - - while (right != NULL && right->distance < link->distance) { - left = right; - right = right->right; - } - - link->right = right; - link->left = left; - left->right = link; - if (right != NULL) right->left = link; - - left = link; - } else { - link->right = NULL; - link->left = NULL; - } - } else { - link->right = NULL; - link->left = NULL; - } + pdsLink *left = NULL; + pdsLink *right = NULL; + + map->front.right = NULL; + for (int i = 0; i < DCOLS; i++) { + for (int j = 0; j < DROWS; j++) { + pdsLink *link = PDS_CELL(map, i, j); + + if (distanceMap != NULL) { + link->distance = distanceMap[i][j]; + } else { + if (costMap != NULL) { + // totally hackish; refactor + link->distance = maxDistance; + } + } + + int cost; + + if (i == 0 || j == 0 || i == DCOLS - 1 || j == DROWS - 1) { + cost = PDS_OBSTRUCTION; + } else if (costMap == NULL) { + if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY) + && cellHasTerrainFlag(i, j, T_OBSTRUCTS_DIAGONAL_MOVEMENT)) + cost = PDS_OBSTRUCTION; + else + cost = PDS_FORBIDDEN; + } else { + cost = costMap[i][j]; + } + + link->cost = cost; + + if (cost > 0) { + if (link->distance < maxDistance) { + if (right == NULL || right->distance > link->distance) { + // left and right are used to traverse the list; if many cells have similar values, + // some time can be saved by not clearing them with each insertion. this time, + // sadly, we have to start from the front. + + left = &map->front; + right = map->front.right; + } + + while (right != NULL && right->distance < link->distance) { + left = right; + right = right->right; + } + + link->right = right; + link->left = left; + left->right = link; + if (right != NULL) + right->left = link; + + left = link; + } else { + link->right = NULL; + link->left = NULL; } + } else { + link->right = NULL; + link->left = NULL; + } } + } } static void pdsBatchOutput(pdsMap *map, short **distanceMap, boolean useDiagonals) { - pdsUpdate(map, useDiagonals); - // transfer results to the distanceMap - for (int i=0; idistance; - } + pdsUpdate(map, useDiagonals); + // transfer results to the distanceMap + for (int i = 0; i < DCOLS; i++) { + for (int j = 0; j < DROWS; j++) { + distanceMap[i][j] = PDS_CELL(map, i, j)->distance; } + } } void dijkstraScan(short **distanceMap, short **costMap, boolean useDiagonals) { - static pdsMap map; + static pdsMap map; - pdsBatchInput(&map, distanceMap, costMap, 30000); - pdsBatchOutput(&map, distanceMap, useDiagonals); + pdsBatchInput(&map, distanceMap, costMap, 30000); + pdsBatchOutput(&map, distanceMap, useDiagonals); } -void calculateDistances(short **distanceMap, - short destinationX, short destinationY, - unsigned long blockingTerrainFlags, - creature *traveler, - boolean canUseSecretDoors, - boolean eightWays) { - static pdsMap map; - - for (int i=0; iinfo.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE)) - && (monst->info.flags & (MONST_IMMOBILE | MONST_GETS_TURN_ON_ACTIVATION))) { - - // Always avoid damage-immune stationary monsters. - cost = PDS_FORBIDDEN; - } else if (canUseSecretDoors - && cellHasTMFlag(i, j, TM_IS_SECRET) - && cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY) - && !(discoveredTerrainFlagsAtLoc(i, j) & T_OBSTRUCTS_PASSABILITY)) { - - cost = 1; - } else if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY) - || (traveler && traveler == &player && !(pmap[i][j].flags & (DISCOVERED | MAGIC_MAPPED)))) { - - cost = cellHasTerrainFlag(i, j, T_OBSTRUCTS_DIAGONAL_MOVEMENT) ? PDS_OBSTRUCTION : PDS_FORBIDDEN; - } else if ((traveler && monsterAvoids(traveler, (pos){i, j})) || cellHasTerrainFlag(i, j, blockingTerrainFlags)) { - cost = PDS_FORBIDDEN; - } else { - cost = 1; - } - - PDS_CELL(&map, i, j)->cost = cost; - } +void calculateDistances(short **distanceMap, short destinationX, short destinationY, unsigned long blockingTerrainFlags, + creature *traveler, boolean canUseSecretDoors, boolean eightWays) { + static pdsMap map; + + for (int i = 0; i < DCOLS; i++) { + for (int j = 0; j < DROWS; j++) { + signed char cost; + creature *monst = monsterAtLoc((pos){i, j}); + if (monst && (monst->info.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE)) + && (monst->info.flags & (MONST_IMMOBILE | MONST_GETS_TURN_ON_ACTIVATION))) { + + // Always avoid damage-immune stationary monsters. + cost = PDS_FORBIDDEN; + } else if (canUseSecretDoors && cellHasTMFlag(i, j, TM_IS_SECRET) + && cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY) + && !(discoveredTerrainFlagsAtLoc(i, j) & T_OBSTRUCTS_PASSABILITY)) { + + cost = 1; + } else if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY) + || (traveler && traveler == &player && !(pmap[i][j].flags & (DISCOVERED | MAGIC_MAPPED)))) { + + cost = cellHasTerrainFlag(i, j, T_OBSTRUCTS_DIAGONAL_MOVEMENT) ? PDS_OBSTRUCTION : PDS_FORBIDDEN; + } else if ((traveler && monsterAvoids(traveler, (pos){i, j})) || cellHasTerrainFlag(i, j, blockingTerrainFlags)) { + cost = PDS_FORBIDDEN; + } else { + cost = 1; + } + + PDS_CELL(&map, i, j)->cost = cost; } + } - pdsClear(&map, 30000); - pdsSetDistance(&map, destinationX, destinationY, 0); - pdsBatchOutput(&map, distanceMap, eightWays); + pdsClear(&map, 30000); + pdsSetDistance(&map, destinationX, destinationY, 0); + pdsBatchOutput(&map, distanceMap, eightWays); } short pathingDistance(short x1, short y1, short x2, short y2, unsigned long blockingTerrainFlags) { - short **distanceMap = allocGrid(); - calculateDistances(distanceMap, x2, y2, blockingTerrainFlags, NULL, true, true); - short retval = distanceMap[x1][y1]; - freeGrid(distanceMap); - return retval; + short **distanceMap = allocGrid(); + calculateDistances(distanceMap, x2, y2, blockingTerrainFlags, NULL, true, true); + short retval = distanceMap[x1][y1]; + freeGrid(distanceMap); + return retval; } - diff --git a/src/brogue/Globals.h b/src/brogue/Globals.h index 8bdf54c9..cc24c56f 100644 --- a/src/brogue/Globals.h +++ b/src/brogue/Globals.h @@ -31,7 +31,7 @@ extern const color deepWaterLightColor; extern const color confusionGasColor; -extern color minersLightColor; //dynamic +extern color minersLightColor; // dynamic extern const color basicLightColor; extern const color torchLightColor; diff --git a/src/brogue/GlobalsBase.c b/src/brogue/GlobalsBase.c index 49a7f2fb..38c9622d 100644 --- a/src/brogue/GlobalsBase.c +++ b/src/brogue/GlobalsBase.c @@ -27,15 +27,15 @@ #include "Rogue.h" -tcell tmap[DCOLS][DROWS]; // grids with info about the map +tcell tmap[DCOLS][DROWS]; // grids with info about the map pcell pmap[DCOLS][DROWS]; short **scentMap; -cellDisplayBuffer displayBuffer[COLS][ROWS]; // used to optimize plotCharWithColor +cellDisplayBuffer displayBuffer[COLS][ROWS]; // used to optimize plotCharWithColor short terrainRandomValues[DCOLS][DROWS][8]; -short **safetyMap; // used to help monsters flee -short **allySafetyMap; // used to help allies flee -short **chokeMap; // used to assess the importance of the map's various chokepoints -const short nbDirs[8][2] = {{0,-1}, {0,1}, {-1,0}, {1,0}, {-1,-1}, {-1,1}, {1,-1}, {1,1}}; +short **safetyMap; // used to help monsters flee +short **allySafetyMap; // used to help allies flee +short **chokeMap; // used to assess the importance of the map's various chokepoints +const short nbDirs[8][2] = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}, {-1, -1}, {-1, 1}, {1, -1}, {1, 1}}; const short cDirs[8][2] = {{0, 1}, {1, 1}, {1, 0}, {1, -1}, {0, -1}, {-1, -1}, {-1, 0}, {-1, 1}}; short numberOfWaypoints; levelData *levels; @@ -50,7 +50,7 @@ item *floorItems; item *packItems; item *monsterItemsHopper; -char displayedMessage[MESSAGE_LINES][COLS*2]; +char displayedMessage[MESSAGE_LINES][COLS * 2]; short messagesUnconfirmed; char combatText[COLS * 2]; short messageArchivePosition; @@ -58,7 +58,8 @@ archivedMessage messageArchive[MESSAGE_ARCHIVE_ENTRIES]; char currentFilePath[BROGUE_FILENAME_MAX]; -char displayDetail[DCOLS][DROWS]; // used to make certain per-cell data accessible to external code (e.g. terminal adaptations) +char displayDetail[DCOLS] + [DROWS]; // used to make certain per-cell data accessible to external code (e.g. terminal adaptations) #ifdef AUDIT_RNG FILE *RNGLogFile; @@ -71,53 +72,58 @@ unsigned long positionInPlaybackFile; unsigned long lengthOfPlaybackFile; unsigned long recordingLocation; unsigned long maxLevelChanges; -char annotationPathname[BROGUE_FILENAME_MAX]; // pathname of annotation file +char annotationPathname[BROGUE_FILENAME_MAX]; // pathname of annotation file uint64_t previousGameSeed; -const windowpos WINDOW_POSITION_DUNGEON_TOP_LEFT = { STAT_BAR_WIDTH + 3, MESSAGE_LINES + 2}; +const windowpos WINDOW_POSITION_DUNGEON_TOP_LEFT = {STAT_BAR_WIDTH + 3, MESSAGE_LINES + 2}; // Red Green Blue RedRand GreenRand BlueRand Rand Dances? // basic colors -const color white = {100, 100, 100, 0, 0, 0, 0, false}; -const color gray = {50, 50, 50, 0, 0, 0, 0, false}; -const color darkGray = {30, 30, 30, 0, 0, 0, 0, false}; -const color veryDarkGray = {15, 15, 15, 0, 0, 0, 0, false}; -const color black = {0, 0, 0, 0, 0, 0, 0, false}; -const color yellow = {100, 100, 0, 0, 0, 0, 0, false}; -const color darkYellow = {50, 50, 0, 0, 0, 0, 0, false}; -const color teal = {30, 100, 100, 0, 0, 0, 0, false}; -const color purple = {100, 0, 100, 0, 0, 0, 0, false}; -const color darkPurple = {50, 0, 50, 0, 0, 0, 0, false}; -const color brown = {60, 40, 0, 0, 0, 0, 0, false}; -const color green = {0, 100, 0, 0, 0, 0, 0, false}; -const color darkGreen = {0, 50, 0, 0, 0, 0, 0, false}; -const color orange = {100, 50, 0, 0, 0, 0, 0, false}; -const color darkOrange = {50, 25, 0, 0, 0, 0, 0, false}; -const color blue = {0, 0, 100, 0, 0, 0, 0, false}; -const color darkBlue = {0, 0, 50, 0, 0, 0, 0, false}; -const color darkTurquoise = {0, 40, 65, 0, 0, 0, 0, false}; -const color lightBlue = {40, 40, 100, 0, 0, 0, 0, false}; -const color pink = {100, 60, 66, 0, 0, 0, 0, false}; -const color darkPink = {50, 30, 33, 0, 0, 0, 0, false}; -const color red = {100, 0, 0, 0, 0, 0, 0, false}; -const color darkRed = {50, 0, 0, 0, 0, 0, 0, false}; -const color tanColor = {80, 67, 15, 0, 0, 0, 0, false}; +const color white = {100, 100, 100, 0, 0, 0, 0, false}; +const color gray = {50, 50, 50, 0, 0, 0, 0, false}; +const color darkGray = {30, 30, 30, 0, 0, 0, 0, false}; +const color veryDarkGray = {15, 15, 15, 0, 0, 0, 0, false}; +const color black = {0, 0, 0, 0, 0, 0, 0, false}; +const color yellow = {100, 100, 0, 0, 0, 0, 0, false}; +const color darkYellow = {50, 50, 0, 0, 0, 0, 0, false}; +const color teal = {30, 100, 100, 0, 0, 0, 0, false}; +const color purple = {100, 0, 100, 0, 0, 0, 0, false}; +const color darkPurple = {50, 0, 50, 0, 0, 0, 0, false}; +const color brown = {60, 40, 0, 0, 0, 0, 0, false}; +const color green = {0, 100, 0, 0, 0, 0, 0, false}; +const color darkGreen = {0, 50, 0, 0, 0, 0, 0, false}; +const color orange = {100, 50, 0, 0, 0, 0, 0, false}; +const color darkOrange = {50, 25, 0, 0, 0, 0, 0, false}; +const color blue = {0, 0, 100, 0, 0, 0, 0, false}; +const color darkBlue = {0, 0, 50, 0, 0, 0, 0, false}; +const color darkTurquoise = {0, 40, 65, 0, 0, 0, 0, false}; +const color lightBlue = {40, 40, 100, 0, 0, 0, 0, false}; +const color pink = {100, 60, 66, 0, 0, 0, 0, false}; +const color darkPink = {50, 30, 33, 0, 0, 0, 0, false}; +const color red = {100, 0, 0, 0, 0, 0, 0, false}; +const color darkRed = {50, 0, 0, 0, 0, 0, 0, false}; +const color tanColor = {80, 67, 15, 0, 0, 0, 0, false}; // tile colors -const color rainbow = {-70, -70, -70, 170, 170, 170, 0, true}; +const color rainbow = {-70, -70, -70, 170, 170, 170, 0, true}; // charm constants const fixpt POW_0_CHARM_INCREMENT[] = { // 1.0 - 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, - 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, - 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536}; + 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, + 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, + 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, + 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536, 65536}; const fixpt POW_120_CHARM_INCREMENT[] = { // 1.20^x fixed point, with x from 1 to 50 in increments of 1: - 78643, 94371, 113246, 135895, 163074, 195689, 234827, 281792, 338151, 405781, 486937, 584325, 701190, 841428, 1009714, 1211657, - 1453988, 1744786, 2093744, 2512492, 3014991, 3617989, 4341587, 5209905, 6251886, 7502263, 9002716, 10803259, 12963911, 15556694, - 18668032, 22401639, 26881967, 32258360, 38710033, 46452039, 55742447, 66890937, 80269124, 96322949, 115587539, 138705047, 166446056, - 199735268, 239682321, 287618785, 345142543, 414171051, 497005262, 596406314, 715687577}; + 78643, 94371, 113246, 135895, 163074, 195689, 234827, 281792, 338151, + 405781, 486937, 584325, 701190, 841428, 1009714, 1211657, 1453988, 1744786, + 2093744, 2512492, 3014991, 3617989, 4341587, 5209905, 6251886, 7502263, 9002716, + 10803259, 12963911, 15556694, 18668032, 22401639, 26881967, 32258360, 38710033, 46452039, + 55742447, 66890937, 80269124, 96322949, 115587539, 138705047, 166446056, 199735268, 239682321, + 287618785, 345142543, 414171051, 497005262, 596406314, 715687577}; const fixpt POW_125_CHARM_INCREMENT[] = { // 1.25^x fixed point, with x from 1 to 50 in increments of 1: - 81920, 102400, 128000, 160000, 200000, 250000, 312500, 390625, 488281, 610351, 762939, 953674, 1192092, 1490116, 1862645, 2328306, - 2910383, 3637978, 4547473, 5684341, 7105427, 8881784, 11102230, 13877787, 17347234, 21684043, 27105054, 33881317, 42351647, 52939559, - 66174449, 82718061, 103397576, 129246970, 161558713, 201948391, 252435489, 315544362, 394430452, 493038065, 616297582, 770371977, - 962964972, 1203706215, 1504632769, 1880790961, 2350988701, 2938735877, 3673419846, 4591774807, 5739718509}; + 81920, 102400, 128000, 160000, 200000, 250000, 312500, 390625, 488281, + 610351, 762939, 953674, 1192092, 1490116, 1862645, 2328306, 2910383, 3637978, + 4547473, 5684341, 7105427, 8881784, 11102230, 13877787, 17347234, 21684043, 27105054, + 33881317, 42351647, 52939559, 66174449, 82718061, 103397576, 129246970, 161558713, 201948391, + 252435489, 315544362, 394430452, 493038065, 616297582, 770371977, 962964972, 1203706215, 1504632769, + 1880790961, 2350988701, 2938735877, 3673419846, 4591774807, 5739718509}; diff --git a/src/brogue/GlobalsBase.h b/src/brogue/GlobalsBase.h index 4fd4915b..94df5a47 100644 --- a/src/brogue/GlobalsBase.h +++ b/src/brogue/GlobalsBase.h @@ -21,17 +21,16 @@ * along with this program. If not, see . */ - -extern tcell tmap[DCOLS][DROWS]; // grids with info about the map -extern pcell pmap[DCOLS][DROWS]; // grids with info about the map +extern tcell tmap[DCOLS][DROWS]; // grids with info about the map +extern pcell pmap[DCOLS][DROWS]; // grids with info about the map // Returns a pointer to the `tcell` at the given position. The position must be in-bounds. -static inline tcell* tmapAt(pos p) { +static inline tcell *tmapAt(pos p) { brogueAssert(p.x >= 0 && p.x < DCOLS && p.y >= 0 && p.y < DROWS); return &tmap[p.x][p.y]; } // Returns a pointer to the `pcell` at the given position. The position must be in-bounds. -static inline pcell* pmapAt(pos p) { +static inline pcell *pmapAt(pos p) { brogueAssert(p.x >= 0 && p.x < DCOLS && p.y >= 0 && p.y < DROWS); return &pmap[p.x][p.y]; } @@ -42,13 +41,13 @@ extern const short nbDirs[8][2]; // The direction must not be `NO_DIRECTION`. static inline pos posNeighborInDirection(pos p, enum directions direction_to_step) { brogueAssert(direction_to_step >= 0 && direction_to_step < 8); - return (pos) { .x = p.x + nbDirs[direction_to_step][0], .y = p.y + nbDirs[direction_to_step][1] }; + return (pos){.x = p.x + nbDirs[direction_to_step][0], .y = p.y + nbDirs[direction_to_step][1]}; } extern short **scentMap; extern cellDisplayBuffer displayBuffer[COLS][ROWS]; extern short terrainRandomValues[DCOLS][DROWS][8]; -extern short **safetyMap; // used to help monsters flee +extern short **safetyMap; // used to help monsters flee extern short **allySafetyMap; extern short **chokeMap; @@ -67,9 +66,9 @@ extern item *packItems; extern item *monsterItemsHopper; extern short numberOfWaypoints; -extern char displayedMessage[MESSAGE_LINES][COLS*2]; +extern char displayedMessage[MESSAGE_LINES][COLS * 2]; extern short messagesUnconfirmed; -extern char combatText[COLS*2]; +extern char combatText[COLS * 2]; extern short messageArchivePosition; extern archivedMessage messageArchive[MESSAGE_ARCHIVE_ENTRIES]; @@ -89,7 +88,7 @@ extern unsigned long positionInPlaybackFile; extern unsigned long lengthOfPlaybackFile; extern unsigned long recordingLocation; extern unsigned long maxLevelChanges; -extern char annotationPathname[BROGUE_FILENAME_MAX]; // pathname of annotation file +extern char annotationPathname[BROGUE_FILENAME_MAX]; // pathname of annotation file extern uint64_t previousGameSeed; // basic colors diff --git a/src/brogue/Grid.c b/src/brogue/Grid.c index accce326..e11b7d66 100644 --- a/src/brogue/Grid.c +++ b/src/brogue/Grid.c @@ -25,558 +25,551 @@ #include "Globals.h" #include "GlobalsBase.h" - // mallocing two-dimensional arrays! dun dun DUN! short **allocGrid() { - short i; - short **array = malloc(DCOLS * sizeof(short *)); - - array[0] = malloc(DROWS * DCOLS * sizeof(short)); - for(i = 1; i < DCOLS; i++) { - array[i] = array[0] + i * DROWS; - } - return array; + short i; + short **array = malloc(DCOLS * sizeof(short *)); + + array[0] = malloc(DROWS * DCOLS * sizeof(short)); + for (i = 1; i < DCOLS; i++) { + array[i] = array[0] + i * DROWS; + } + return array; } void freeGrid(short **array) { - free(array[0]); - free(array); + free(array[0]); + free(array); } void copyGrid(short **to, short **from) { - short i, j; + short i, j; - for(i = 0; i < DCOLS; i++) { - for(j = 0; j < DROWS; j++) { - to[i][j] = from[i][j]; - } + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + to[i][j] = from[i][j]; } + } } void fillGrid(short **grid, short fillValue) { - short i, j; + short i, j; - for(i = 0; i < DCOLS; i++) { - for(j = 0; j < DROWS; j++) { - grid[i][j] = fillValue; - } + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + grid[i][j] = fillValue; } + } } -// Highlight the portion indicated by hiliteCharGrid with the hiliteColor at the hiliteStrength -- both latter arguments are optional. +// Highlight the portion indicated by hiliteCharGrid with the hiliteColor at the hiliteStrength -- both latter arguments +// are optional. void hiliteGrid(short **grid, const color *hiliteColor, short hiliteStrength) { - short i, j, x, y; - color hCol; - - assureCosmeticRNG; - - if (hiliteColor) { - hCol = *hiliteColor; - } else { - hCol = yellow; + short i, j, x, y; + color hCol; + + assureCosmeticRNG; + + if (hiliteColor) { + hCol = *hiliteColor; + } else { + hCol = yellow; + } + + bakeColor(&hCol); + + if (!hiliteStrength) { + hiliteStrength = 75; + } + + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + if (grid[i][j]) { + x = mapToWindowX(i); + y = mapToWindowY(j); + + displayBuffer[x][y].backColorComponents[0] + = clamp(displayBuffer[x][y].backColorComponents[0] + hCol.red * hiliteStrength / 100, 0, 100); + displayBuffer[x][y].backColorComponents[1] + = clamp(displayBuffer[x][y].backColorComponents[1] + hCol.green * hiliteStrength / 100, 0, 100); + displayBuffer[x][y].backColorComponents[2] + = clamp(displayBuffer[x][y].backColorComponents[2] + hCol.blue * hiliteStrength / 100, 0, 100); + displayBuffer[x][y].foreColorComponents[0] + = clamp(displayBuffer[x][y].foreColorComponents[0] + hCol.red * hiliteStrength / 100, 0, 100); + displayBuffer[x][y].foreColorComponents[1] + = clamp(displayBuffer[x][y].foreColorComponents[1] + hCol.green * hiliteStrength / 100, 0, 100); + displayBuffer[x][y].foreColorComponents[2] + = clamp(displayBuffer[x][y].foreColorComponents[2] + hCol.blue * hiliteStrength / 100, 0, 100); + } } - - bakeColor(&hCol); - - if (!hiliteStrength) { - hiliteStrength = 75; - } - - for (i=0; i= findValueMin && grid[i][j] <= findValueMax) { - grid[i][j] = fillValue; - } - } + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + if (grid[i][j] >= findValueMin && grid[i][j] <= findValueMax) { + grid[i][j] = fillValue; + } } + } } // Flood-fills the grid from (x, y) along cells that are within the eligible range. // Returns the total count of filled cells. short floodFillGrid(short **grid, short x, short y, short eligibleValueMin, short eligibleValueMax, short fillValue) { - enum directions dir; - short newX, newY, fillCount = 1; - - brogueAssert(fillValue < eligibleValueMin || fillValue > eligibleValueMax); - - grid[x][y] = fillValue; - for (dir = 0; dir < 4; dir++) { - newX = x + nbDirs[dir][0]; - newY = y + nbDirs[dir][1]; - if (coordinatesAreInMap(newX, newY) - && grid[newX][newY] >= eligibleValueMin - && grid[newX][newY] <= eligibleValueMax) { - fillCount += floodFillGrid(grid, newX, newY, eligibleValueMin, eligibleValueMax, fillValue); - } + enum directions dir; + short newX, newY, fillCount = 1; + + brogueAssert(fillValue < eligibleValueMin || fillValue > eligibleValueMax); + + grid[x][y] = fillValue; + for (dir = 0; dir < 4; dir++) { + newX = x + nbDirs[dir][0]; + newY = y + nbDirs[dir][1]; + if (coordinatesAreInMap(newX, newY) && grid[newX][newY] >= eligibleValueMin + && grid[newX][newY] <= eligibleValueMax) { + fillCount += floodFillGrid(grid, newX, newY, eligibleValueMin, eligibleValueMax, fillValue); } - return fillCount; + } + return fillCount; } void drawRectangleOnGrid(short **grid, short x, short y, short width, short height, short value) { - short i, j; + short i, j; - for (i=x; i < x+width; i++) { - for (j=y; j= minPassableArc && count <= maxPassableArc) { - grid[i][j] = value; - } - } + short i, j, count; + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + if (grid[i][j] != value) { + count = passableArcCount(i, j); + if (count >= minPassableArc && count <= maxPassableArc) { + grid[i][j] = value; } + } } + } } short validLocationCount(short **grid, short validValue) { - short i, j, count; - count = 0; - for(i = 0; i < DCOLS; i++) { - for(j = 0; j < DROWS; j++) { - if (grid[i][j] == validValue) { - count++; - } - } + short i, j, count; + count = 0; + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + if (grid[i][j] == validValue) { + count++; + } } - return count; + } + return count; } short leastPositiveValueInGrid(short **grid) { - short i, j, leastPositiveValue = 0; - for(i = 0; i < DCOLS; i++) { - for(j = 0; j < DROWS; j++) { - if (grid[i][j] > 0 && (leastPositiveValue == 0 || grid[i][j] < leastPositiveValue)) { - leastPositiveValue = grid[i][j]; - } - } + short i, j, leastPositiveValue = 0; + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + if (grid[i][j] > 0 && (leastPositiveValue == 0 || grid[i][j] < leastPositiveValue)) { + leastPositiveValue = grid[i][j]; + } } - return leastPositiveValue; + } + return leastPositiveValue; } // Takes a grid as a mask of valid locations, chooses one randomly and returns it as (x, y). // If there are no valid locations, returns (-1, -1). void randomLocationInGrid(short **grid, short *x, short *y, short validValue) { - const short locationCount = validLocationCount(grid, validValue); - short i, j; + const short locationCount = validLocationCount(grid, validValue); + short i, j; - if (locationCount <= 0) { - *x = *y = -1; - return; - } - short index = rand_range(0, locationCount - 1); - for(i = 0; i < DCOLS && index >= 0; i++) { - for(j = 0; j < DROWS && index >= 0; j++) { - if (grid[i][j] == validValue) { - if (index == 0) { - *x = i; - *y = j; - } - index--; - } + if (locationCount <= 0) { + *x = *y = -1; + return; + } + short index = rand_range(0, locationCount - 1); + for (i = 0; i < DCOLS && index >= 0; i++) { + for (j = 0; j < DROWS && index >= 0; j++) { + if (grid[i][j] == validValue) { + if (index == 0) { + *x = i; + *y = j; } + index--; + } } - return; + } + return; } // Finds the lowest positive number in a grid, chooses one location with that number randomly and returns it as (x, y). // If there are no valid locations, returns (-1, -1). void randomLeastPositiveLocationInGrid(short **grid, short *x, short *y, boolean deterministic) { - const short targetValue = leastPositiveValueInGrid(grid); - short locationCount; - short i, j, index; - - if (targetValue == 0) { - *x = *y = -1; - return; - } - - locationCount = 0; - for(i = 0; i < DCOLS; i++) { - for(j = 0; j < DROWS; j++) { - if (grid[i][j] == targetValue) { - locationCount++; - } - } - } + const short targetValue = leastPositiveValueInGrid(grid); + short locationCount; + short i, j, index; - if (deterministic) { - index = locationCount / 2; - } else { - index = rand_range(0, locationCount - 1); + if (targetValue == 0) { + *x = *y = -1; + return; + } + + locationCount = 0; + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + if (grid[i][j] == targetValue) { + locationCount++; + } } - - for(i = 0; i < DCOLS && index >= 0; i++) { - for(j = 0; j < DROWS && index >= 0; j++) { - if (grid[i][j] == targetValue) { - if (index == 0) { - *x = i; - *y = j; - } - index--; - } + } + + if (deterministic) { + index = locationCount / 2; + } else { + index = rand_range(0, locationCount - 1); + } + + for (i = 0; i < DCOLS && index >= 0; i++) { + for (j = 0; j < DROWS && index >= 0; j++) { + if (grid[i][j] == targetValue) { + if (index == 0) { + *x = i; + *y = j; } + index--; + } } - return; + } + return; } -boolean getQualifyingPathLocNear(short *retValX, short *retValY, - short x, short y, - boolean hallwaysAllowed, - unsigned long blockingTerrainFlags, - unsigned long blockingMapFlags, - unsigned long forbiddenTerrainFlags, - unsigned long forbiddenMapFlags, +boolean getQualifyingPathLocNear(short *retValX, short *retValY, short x, short y, boolean hallwaysAllowed, + unsigned long blockingTerrainFlags, unsigned long blockingMapFlags, + unsigned long forbiddenTerrainFlags, unsigned long forbiddenMapFlags, boolean deterministic) { - short **grid, **costMap; - - // First check the given location to see if it works, as an optimization. - if (!cellHasTerrainFlag(x, y, blockingTerrainFlags | forbiddenTerrainFlags) - && !(pmap[x][y].flags & (blockingMapFlags | forbiddenMapFlags)) - && (hallwaysAllowed || passableArcCount(x, y) <= 1)) { - - *retValX = x; - *retValY = y; - return true; - } - - // Allocate the grids. - grid = allocGrid(); - costMap = allocGrid(); - - // Start with a base of a high number everywhere. - fillGrid(grid, 30000); - fillGrid(costMap, 1); - - // Block off the pathing blockers. - getTerrainGrid(costMap, PDS_FORBIDDEN, blockingTerrainFlags, blockingMapFlags); - if (blockingTerrainFlags & (T_OBSTRUCTS_DIAGONAL_MOVEMENT | T_OBSTRUCTS_PASSABILITY)) { - getTerrainGrid(costMap, PDS_OBSTRUCTION, T_OBSTRUCTS_DIAGONAL_MOVEMENT, 0); - } - - // Run the distance scan. - grid[x][y] = 1; - costMap[x][y] = 1; - dijkstraScan(grid, costMap, true); - findReplaceGrid(grid, 30000, 30000, 0); - - // Block off invalid targets that aren't pathing blockers. - getTerrainGrid(grid, 0, forbiddenTerrainFlags, forbiddenMapFlags); - if (!hallwaysAllowed) { - getPassableArcGrid(grid, 2, 10, 0); - } - - // Get the solution. - randomLeastPositiveLocationInGrid(grid, retValX, retValY, deterministic); - -// dumpLevelToScreen(); -// displayGrid(grid); -// if (coordinatesAreInMap(*retValX, *retValY)) { -// hiliteCell(*retValX, *retValY, &yellow, 100, true); -// } -// temporaryMessage("Qualifying path selected:", REQUIRE_ACKNOWLEDGMENT); - - freeGrid(grid); - freeGrid(costMap); - - // Fall back to a pathing-agnostic alternative if there are no solutions. - if (*retValX == -1 && *retValY == -1) { - pos loc; - if (getQualifyingLocNear(&loc, x, y, hallwaysAllowed, NULL, - (blockingTerrainFlags | forbiddenTerrainFlags), - (blockingMapFlags | forbiddenMapFlags), - false, deterministic)) { - *retValX = loc.x; - *retValY = loc.y; - return true; // Found a fallback solution. - } else { - return false; // No solutions. - } + short **grid, **costMap; + + // First check the given location to see if it works, as an optimization. + if (!cellHasTerrainFlag(x, y, blockingTerrainFlags | forbiddenTerrainFlags) + && !(pmap[x][y].flags & (blockingMapFlags | forbiddenMapFlags)) + && (hallwaysAllowed || passableArcCount(x, y) <= 1)) { + + *retValX = x; + *retValY = y; + return true; + } + + // Allocate the grids. + grid = allocGrid(); + costMap = allocGrid(); + + // Start with a base of a high number everywhere. + fillGrid(grid, 30000); + fillGrid(costMap, 1); + + // Block off the pathing blockers. + getTerrainGrid(costMap, PDS_FORBIDDEN, blockingTerrainFlags, blockingMapFlags); + if (blockingTerrainFlags & (T_OBSTRUCTS_DIAGONAL_MOVEMENT | T_OBSTRUCTS_PASSABILITY)) { + getTerrainGrid(costMap, PDS_OBSTRUCTION, T_OBSTRUCTS_DIAGONAL_MOVEMENT, 0); + } + + // Run the distance scan. + grid[x][y] = 1; + costMap[x][y] = 1; + dijkstraScan(grid, costMap, true); + findReplaceGrid(grid, 30000, 30000, 0); + + // Block off invalid targets that aren't pathing blockers. + getTerrainGrid(grid, 0, forbiddenTerrainFlags, forbiddenMapFlags); + if (!hallwaysAllowed) { + getPassableArcGrid(grid, 2, 10, 0); + } + + // Get the solution. + randomLeastPositiveLocationInGrid(grid, retValX, retValY, deterministic); + + // dumpLevelToScreen(); + // displayGrid(grid); + // if (coordinatesAreInMap(*retValX, *retValY)) { + // hiliteCell(*retValX, *retValY, &yellow, 100, true); + // } + // temporaryMessage("Qualifying path selected:", REQUIRE_ACKNOWLEDGMENT); + + freeGrid(grid); + freeGrid(costMap); + + // Fall back to a pathing-agnostic alternative if there are no solutions. + if (*retValX == -1 && *retValY == -1) { + pos loc; + if (getQualifyingLocNear(&loc, x, y, hallwaysAllowed, NULL, (blockingTerrainFlags | forbiddenTerrainFlags), + (blockingMapFlags | forbiddenMapFlags), false, deterministic)) { + *retValX = loc.x; + *retValY = loc.y; + return true; // Found a fallback solution. } else { - return true; // Found a primary solution. + return false; // No solutions. } + } else { + return true; // Found a primary solution. + } } void cellularAutomataRound(short **grid, char birthParameters[9], char survivalParameters[9]) { - short i, j, nbCount, newX, newY; - enum directions dir; - short **buffer2; - - buffer2 = allocGrid(); - copyGrid(buffer2, grid); // Make a backup of grid in buffer2, so that each generation is isolated. - - for(i=0; i topBlobSize) { // if this blob is a new record - topBlobSize = blobSize; - topBlobNumber = blobNumber; - } - blobNumber++; - } - } + // colorOverDungeon(&darkGray); + // hiliteGrid(grid, &white, 100); + // temporaryMessage("Cellular automata result:", REQUIRE_ACKNOWLEDGMENT); + + // Now to measure the result. These are best-of variables; start them out at worst-case values. + topBlobSize = 0; + topBlobNumber = 0; + topBlobMinX = maxBlobWidth; + topBlobMaxX = 0; + topBlobMinY = maxBlobHeight; + topBlobMaxY = 0; + + // Fill each blob with its own number, starting with 2 (since 1 means floor), and keeping track of the biggest: + blobNumber = 2; + + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + if (grid[i][j] == 1) { // an unmarked blob + // Mark all the cells and returns the total size: + blobSize = fillContiguousRegion(grid, i, j, blobNumber); + if (blobSize > topBlobSize) { // if this blob is a new record + topBlobSize = blobSize; + topBlobNumber = blobNumber; + } + blobNumber++; } + } + } - // Figure out the top blob's height and width: - // First find the max & min x: - for(i=0; i topBlobMaxX) { - topBlobMaxX = i; - } - } + // Figure out the top blob's height and width: + // First find the max & min x: + for (i = 0; i < DCOLS; i++) { + foundACellThisLine = false; + for (j = 0; j < DROWS; j++) { + if (grid[i][j] == topBlobNumber) { + foundACellThisLine = true; + break; } - - // Then the max & min y: - for(j=0; j topBlobMaxY) { - topBlobMaxY = j; - } - } + } + if (foundACellThisLine) { + if (i < topBlobMinX) { + topBlobMinX = i; } + if (i > topBlobMaxX) { + topBlobMaxX = i; + } + } + } - blobWidth = (topBlobMaxX - topBlobMinX) + 1; - blobHeight = (topBlobMaxY - topBlobMinY) + 1; - - } while (blobWidth < minBlobWidth - || blobHeight < minBlobHeight - || topBlobNumber == 0); - - // Replace the winning blob with 1's, and everything else with 0's: - for(i=0; i topBlobMaxY) { + topBlobMaxY = j; } + } + } + + blobWidth = (topBlobMaxX - topBlobMinX) + 1; + blobHeight = (topBlobMaxY - topBlobMinY) + 1; + + } while (blobWidth < minBlobWidth || blobHeight < minBlobHeight || topBlobNumber == 0); + + // Replace the winning blob with 1's, and everything else with 0's: + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + if (grid[i][j] == topBlobNumber) { + grid[i][j] = 1; + } else { + grid[i][j] = 0; + } } + } - // Populate the returned variables. - *retMinX = topBlobMinX; - *retMinY = topBlobMinY; - *retWidth = blobWidth; - *retHeight = blobHeight; + // Populate the returned variables. + *retMinX = topBlobMinX; + *retMinY = topBlobMinY; + *retWidth = blobWidth; + *retHeight = blobHeight; } diff --git a/src/brogue/IO.c b/src/brogue/IO.c index 17b2ebeb..e6a3eb7e 100644 --- a/src/brogue/IO.c +++ b/src/brogue/IO.c @@ -28,136 +28,134 @@ #include "GlobalsBase.h" #include "Globals.h" -// Populates path[][] with a list of coordinates starting at origin and traversing down the map. Returns the number of steps in the path. +// Populates path[][] with a list of coordinates starting at origin and traversing down the map. Returns the number of +// steps in the path. short getPlayerPathOnMap(pos path[1000], short **map, pos origin) { - pos at = origin; + pos at = origin; - int steps; - for (steps = 0; true; steps++) { - const int dir = nextStep(map, at.x, at.y, &player, false); - if (dir == -1) { - break; - } - at = posNeighborInDirection(at, dir); - path[steps] = at; - brogueAssert(coordinatesAreInMap(x, y)); + int steps; + for (steps = 0; true; steps++) { + const int dir = nextStep(map, at.x, at.y, &player, false); + if (dir == -1) { + break; } - return steps; + at = posNeighborInDirection(at, dir); + path[steps] = at; + brogueAssert(coordinatesAreInMap(x, y)); + } + return steps; } void reversePath(pos path[1000], short steps) { - for (int i=0; iflags &= ~IS_IN_PATH; - refreshDungeonCell(path[i].x, path[i].y); - } - } else { - for (int i=0; iflags |= IS_IN_PATH; - refreshDungeonCell(path[i].x, path[i].y); - } - } + if (unhilite) { + for (int i = 0; i < steps; i++) { + brogueAssert(isPosInMap(path[i])); + pmapAt(path[i])->flags &= ~IS_IN_PATH; + refreshDungeonCell(path[i].x, path[i].y); + } + } else { + for (int i = 0; i < steps; i++) { + brogueAssert(isPosInMap(path[i])); + pmapAt(path[i])->flags |= IS_IN_PATH; + refreshDungeonCell(path[i].x, path[i].y); + } + } } // More expensive than hilitePath(__, __, true), but you don't need access to the path itself. void clearCursorPath() { - short i, j; - - if (!rogue.playbackMode) { // There are no cursor paths during playback. - for (i=1; i= 0 && map[i][j] < 30000) { - - const int dist = (i - x)*(i - x) + (j - y)*(j - y); - //hiliteCell(i, j, &purple, min(dist / 2, 100), false); - if (dist < closestDistance - || dist == closestDistance && map[i][j] < lowestMapScore) { - - answer = (pos){ i, j }; - closestDistance = dist; - lowestMapScore = map[i][j]; - } - } + pos answer = INVALID_POS; + + int closestDistance = 10000; + int lowestMapScore = 10000; + for (int i = 1; i < DCOLS - 1; i++) { + for (int j = 1; j < DROWS - 1; j++) { + if (map[i][j] >= 0 && map[i][j] < 30000) { + + const int dist = (i - x) * (i - x) + (j - y) * (j - y); + // hiliteCell(i, j, &purple, min(dist / 2, 100), false); + if (dist < closestDistance || dist == closestDistance && map[i][j] < lowestMapScore) { + + answer = (pos){i, j}; + closestDistance = dist; + lowestMapScore = map[i][j]; } + } } + } - return answer; + return answer; } void processSnapMap(short **map) { - short **costMap; - enum directions dir; - short i, j, newX, newY; + short **costMap; + enum directions dir; + short i, j, newX, newY; - costMap = allocGrid(); + costMap = allocGrid(); - populateCreatureCostMap(costMap, &player); - fillGrid(map, 30000); - map[player.loc.x][player.loc.y] = 0; - dijkstraScan(map, costMap, true); - for (i = 0; i < DCOLS; i++) { - for (j = 0; j < DROWS; j++) { - if (cellHasTMFlag(i, j, TM_INVERT_WHEN_HIGHLIGHTED)) { - for (dir = 0; dir < 4; dir++) { - newX = i + nbDirs[dir][0]; - newY = j + nbDirs[dir][1]; - if (coordinatesAreInMap(newX, newY) - && map[newX][newY] >= 0 - && map[newX][newY] < map[i][j]) { - - map[i][j] = map[newX][newY]; - } - } - } + populateCreatureCostMap(costMap, &player); + fillGrid(map, 30000); + map[player.loc.x][player.loc.y] = 0; + dijkstraScan(map, costMap, true); + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + if (cellHasTMFlag(i, j, TM_INVERT_WHEN_HIGHLIGHTED)) { + for (dir = 0; dir < 4; dir++) { + newX = i + nbDirs[dir][0]; + newY = j + nbDirs[dir][1]; + if (coordinatesAreInMap(newX, newY) && map[newX][newY] >= 0 && map[newX][newY] < map[i][j]) { + + map[i][j] = map[newX][newY]; + } } + } } + } - freeGrid(costMap); + freeGrid(costMap); } // Displays a menu of buttons for various commands. @@ -166,689 +164,693 @@ void processSnapMap(short **map) { // Some buttons take effect in this function instead of returning a value, // i.e. true colors mode and display stealth mode. short actionMenu(short x, boolean playingBack) { - short buttonCount; - short y; - boolean takeActionOurselves[ROWS] = {false}; - rogueEvent theEvent; + short buttonCount; + short y; + boolean takeActionOurselves[ROWS] = {false}; + rogueEvent theEvent; - brogueButton buttons[ROWS] = {{{0}}}; - char yellowColorEscape[5] = "", whiteColorEscape[5] = "", darkGrayColorEscape[5] = ""; - short i, j, longestName = 0, buttonChosen; - cellDisplayBuffer dbuf[COLS][ROWS], rbuf[COLS][ROWS]; + brogueButton buttons[ROWS] = {{{0}}}; + char yellowColorEscape[5] = "", whiteColorEscape[5] = "", darkGrayColorEscape[5] = ""; + short i, j, longestName = 0, buttonChosen; + cellDisplayBuffer dbuf[COLS][ROWS], rbuf[COLS][ROWS]; - encodeMessageColor(yellowColorEscape, 0, &itemMessageColor); - encodeMessageColor(whiteColorEscape, 0, &white); - encodeMessageColor(darkGrayColorEscape, 0, &black); + encodeMessageColor(yellowColorEscape, 0, &itemMessageColor); + encodeMessageColor(whiteColorEscape, 0, &white); + encodeMessageColor(darkGrayColorEscape, 0, &black); - do { - for (i=0; i:%s Next Level ", yellowColorEscape, whiteColorEscape); - } else { - strcpy(buttons[buttonCount].text, " Next Level "); - } - buttons[buttonCount].hotkey[0] = DESCEND_KEY; - buttonCount++; - sprintf(buttons[buttonCount].text, " %s---", darkGrayColorEscape); - buttons[buttonCount].flags &= ~B_ENABLED; - buttonCount++; - } else { - if (KEYBOARD_LABELS) { - sprintf(buttons[buttonCount].text, " %sZ: %sRest until better ", yellowColorEscape, whiteColorEscape); - } else { - strcpy(buttons[buttonCount].text, " Rest until better "); - } - buttons[buttonCount].hotkey[0] = AUTO_REST_KEY; - buttonCount++; - - if (KEYBOARD_LABELS) { - sprintf(buttons[buttonCount].text, " %sA: %sAutopilot ", yellowColorEscape, whiteColorEscape); - } else { - strcpy(buttons[buttonCount].text, " Autopilot "); - } - buttons[buttonCount].hotkey[0] = AUTOPLAY_KEY; - buttonCount++; - - if (KEYBOARD_LABELS) { - sprintf(buttons[buttonCount].text, " %sT: %sRe-throw at last monster ", yellowColorEscape, whiteColorEscape); - } else { - strcpy(buttons[buttonCount].text, " Re-throw at last monster "); - } - buttons[buttonCount].hotkey[0] = RETHROW_KEY; - buttonCount++; - - if (!rogue.easyMode) { - if (KEYBOARD_LABELS) { - sprintf(buttons[buttonCount].text, " %s&: %sEasy mode ", yellowColorEscape, whiteColorEscape); - } else { - strcpy(buttons[buttonCount].text, " Easy mode "); - } - buttons[buttonCount].hotkey[0] = EASY_MODE_KEY; - buttonCount++; - } - - sprintf(buttons[buttonCount].text, " %s---", darkGrayColorEscape); - buttons[buttonCount].flags &= ~B_ENABLED; - buttonCount++; - } - - if (KEYBOARD_LABELS) { - sprintf(buttons[buttonCount].text, " %s\\: %s[%s] Hide color effects ", yellowColorEscape, whiteColorEscape, rogue.trueColorMode ? "X" : " "); - } else { - sprintf(buttons[buttonCount].text, " [%s] Hide color effects ", rogue.trueColorMode ? " " : "X"); - } - buttons[buttonCount].hotkey[0] = TRUE_COLORS_KEY; - takeActionOurselves[buttonCount] = true; - buttonCount++; + if (KEYBOARD_LABELS) { + sprintf(buttons[buttonCount].text, " %sk: %sFaster playback ", yellowColorEscape, whiteColorEscape); + } else { + strcpy(buttons[buttonCount].text, " Faster playback "); + } + buttons[buttonCount].hotkey[0] = UP_KEY; + buttons[buttonCount].hotkey[1] = UP_ARROW; + buttons[buttonCount].hotkey[2] = NUMPAD_8; + buttonCount++; + if (KEYBOARD_LABELS) { + sprintf(buttons[buttonCount].text, " %sj: %sSlower playback ", yellowColorEscape, whiteColorEscape); + } else { + strcpy(buttons[buttonCount].text, " Slower playback "); + } + buttons[buttonCount].hotkey[0] = DOWN_KEY; + buttons[buttonCount].hotkey[1] = DOWN_ARROW; + buttons[buttonCount].hotkey[2] = NUMPAD_2; + buttonCount++; + sprintf(buttons[buttonCount].text, " %s---", darkGrayColorEscape); + buttons[buttonCount].flags &= ~B_ENABLED; + buttonCount++; + + if (KEYBOARD_LABELS) { + sprintf(buttons[buttonCount].text, "%s0-9: %sFast forward to turn ", yellowColorEscape, whiteColorEscape); + } else { + strcpy(buttons[buttonCount].text, " Fast forward to turn "); + } + buttons[buttonCount].hotkey[0] = '0'; + buttonCount++; + if (KEYBOARD_LABELS) { + sprintf(buttons[buttonCount].text, " %s<:%s Previous Level ", yellowColorEscape, whiteColorEscape); + } else { + strcpy(buttons[buttonCount].text, " Previous Level "); + } + buttons[buttonCount].hotkey[0] = ASCEND_KEY; + buttonCount++; + if (KEYBOARD_LABELS) { + sprintf(buttons[buttonCount].text, " %s>:%s Next Level ", yellowColorEscape, whiteColorEscape); + } else { + strcpy(buttons[buttonCount].text, " Next Level "); + } + buttons[buttonCount].hotkey[0] = DESCEND_KEY; + buttonCount++; + sprintf(buttons[buttonCount].text, " %s---", darkGrayColorEscape); + buttons[buttonCount].flags &= ~B_ENABLED; + buttonCount++; + } else { + if (KEYBOARD_LABELS) { + sprintf(buttons[buttonCount].text, " %sZ: %sRest until better ", yellowColorEscape, whiteColorEscape); + } else { + strcpy(buttons[buttonCount].text, " Rest until better "); + } + buttons[buttonCount].hotkey[0] = AUTO_REST_KEY; + buttonCount++; + + if (KEYBOARD_LABELS) { + sprintf(buttons[buttonCount].text, " %sA: %sAutopilot ", yellowColorEscape, whiteColorEscape); + } else { + strcpy(buttons[buttonCount].text, " Autopilot "); + } + buttons[buttonCount].hotkey[0] = AUTOPLAY_KEY; + buttonCount++; + + if (KEYBOARD_LABELS) { + sprintf(buttons[buttonCount].text, " %sT: %sRe-throw at last monster ", yellowColorEscape, whiteColorEscape); + } else { + strcpy(buttons[buttonCount].text, " Re-throw at last monster "); + } + buttons[buttonCount].hotkey[0] = RETHROW_KEY; + buttonCount++; + + if (!rogue.easyMode) { if (KEYBOARD_LABELS) { - sprintf(buttons[buttonCount].text, " %s]: %s[%s] Display stealth range ", yellowColorEscape, whiteColorEscape, rogue.displayStealthRangeMode ? "X" : " "); + sprintf(buttons[buttonCount].text, " %s&: %sEasy mode ", yellowColorEscape, whiteColorEscape); } else { - sprintf(buttons[buttonCount].text, " [%s] Show stealth range ", rogue.displayStealthRangeMode ? "X" : " "); + strcpy(buttons[buttonCount].text, " Easy mode "); } - buttons[buttonCount].hotkey[0] = STEALTH_RANGE_KEY; - takeActionOurselves[buttonCount] = true; + buttons[buttonCount].hotkey[0] = EASY_MODE_KEY; buttonCount++; + } - if (hasGraphics) { - if (KEYBOARD_LABELS) { - sprintf(buttons[buttonCount].text, " %sG: %s[%c] Enable graphics ", yellowColorEscape, whiteColorEscape, " X~"[graphicsMode]); - } else { - sprintf(buttons[buttonCount].text, " [%c] Enable graphics ", " X~"[graphicsMode]); - } - buttons[buttonCount].hotkey[0] = GRAPHICS_KEY; - takeActionOurselves[buttonCount] = true; - buttonCount++; - } + sprintf(buttons[buttonCount].text, " %s---", darkGrayColorEscape); + buttons[buttonCount].flags &= ~B_ENABLED; + buttonCount++; + } - sprintf(buttons[buttonCount].text, " %s---", darkGrayColorEscape); - buttons[buttonCount].flags &= ~B_ENABLED; - buttonCount++; + if (KEYBOARD_LABELS) { + sprintf(buttons[buttonCount].text, " %s\\: %s[%s] Hide color effects ", yellowColorEscape, whiteColorEscape, + rogue.trueColorMode ? "X" : " "); + } else { + sprintf(buttons[buttonCount].text, " [%s] Hide color effects ", rogue.trueColorMode ? " " : "X"); + } + buttons[buttonCount].hotkey[0] = TRUE_COLORS_KEY; + takeActionOurselves[buttonCount] = true; + buttonCount++; + if (KEYBOARD_LABELS) { + sprintf(buttons[buttonCount].text, " %s]: %s[%s] Display stealth range ", yellowColorEscape, whiteColorEscape, + rogue.displayStealthRangeMode ? "X" : " "); + } else { + sprintf(buttons[buttonCount].text, " [%s] Show stealth range ", rogue.displayStealthRangeMode ? "X" : " "); + } + buttons[buttonCount].hotkey[0] = STEALTH_RANGE_KEY; + takeActionOurselves[buttonCount] = true; + buttonCount++; + + if (hasGraphics) { + if (KEYBOARD_LABELS) { + sprintf(buttons[buttonCount].text, " %sG: %s[%c] Enable graphics ", yellowColorEscape, whiteColorEscape, + " X~"[graphicsMode]); + } else { + sprintf(buttons[buttonCount].text, " [%c] Enable graphics ", " X~"[graphicsMode]); + } + buttons[buttonCount].hotkey[0] = GRAPHICS_KEY; + takeActionOurselves[buttonCount] = true; + buttonCount++; + } + + sprintf(buttons[buttonCount].text, " %s---", darkGrayColorEscape); + buttons[buttonCount].flags &= ~B_ENABLED; + buttonCount++; + + if (KEYBOARD_LABELS) { + sprintf(buttons[buttonCount].text, " %sD: %sDiscovered items ", yellowColorEscape, whiteColorEscape); + } else { + strcpy(buttons[buttonCount].text, " Discovered items "); + } + buttons[buttonCount].hotkey[0] = DISCOVERIES_KEY; + DEBUG { + buttonCount++; + if (KEYBOARD_LABELS) { + sprintf(buttons[buttonCount].text, " %sC: %sCreate item or monster ", yellowColorEscape, whiteColorEscape); + } else { + strcpy(buttons[buttonCount].text, " Create item or monster "); + } + buttons[buttonCount].hotkey[0] = CREATE_ITEM_MONSTER_KEY; + } + buttonCount++; + if (KEYBOARD_LABELS) { + sprintf(buttons[buttonCount].text, " %s~: %sView dungeon seed ", yellowColorEscape, whiteColorEscape); + } else { + strcpy(buttons[buttonCount].text, " View dungeon seed "); + } + buttons[buttonCount].hotkey[0] = SEED_KEY; + buttonCount++; + if (KEYBOARD_LABELS) { // No help button if we're not in keyboard mode. + sprintf(buttons[buttonCount].text, " %s?: %sHelp ", yellowColorEscape, whiteColorEscape); + buttons[buttonCount].hotkey[0] = BROGUE_HELP_KEY; + buttonCount++; + } + sprintf(buttons[buttonCount].text, " %s---", darkGrayColorEscape); + buttons[buttonCount].flags &= ~B_ENABLED; + buttonCount++; + if (!serverMode) { + if (playingBack) { if (KEYBOARD_LABELS) { - sprintf(buttons[buttonCount].text, " %sD: %sDiscovered items ", yellowColorEscape, whiteColorEscape); + sprintf(buttons[buttonCount].text, " %sO: %sOpen saved game ", yellowColorEscape, whiteColorEscape); } else { - strcpy(buttons[buttonCount].text, " Discovered items "); - } - buttons[buttonCount].hotkey[0] = DISCOVERIES_KEY; - DEBUG { - buttonCount++; - if (KEYBOARD_LABELS) { - sprintf(buttons[buttonCount].text, " %sC: %sCreate item or monster ", yellowColorEscape, whiteColorEscape); - } else { - strcpy(buttons[buttonCount].text, " Create item or monster "); - } - buttons[buttonCount].hotkey[0] = CREATE_ITEM_MONSTER_KEY; + strcpy(buttons[buttonCount].text, " Open saved game "); } + buttons[buttonCount].hotkey[0] = LOAD_SAVED_GAME_KEY; buttonCount++; if (KEYBOARD_LABELS) { - sprintf(buttons[buttonCount].text, " %s~: %sView dungeon seed ", yellowColorEscape, whiteColorEscape); + sprintf(buttons[buttonCount].text, " %sV: %sView saved recording ", yellowColorEscape, whiteColorEscape); } else { - strcpy(buttons[buttonCount].text, " View dungeon seed "); - } - buttons[buttonCount].hotkey[0] = SEED_KEY; - buttonCount++; - if (KEYBOARD_LABELS) { // No help button if we're not in keyboard mode. - sprintf(buttons[buttonCount].text, " %s?: %sHelp ", yellowColorEscape, whiteColorEscape); - buttons[buttonCount].hotkey[0] = BROGUE_HELP_KEY; - buttonCount++; + strcpy(buttons[buttonCount].text, " View saved recording "); } - sprintf(buttons[buttonCount].text, " %s---", darkGrayColorEscape); - buttons[buttonCount].flags &= ~B_ENABLED; + buttons[buttonCount].hotkey[0] = VIEW_RECORDING_KEY; buttonCount++; - - if (!serverMode) { - if (playingBack) { - if (KEYBOARD_LABELS) { - sprintf(buttons[buttonCount].text, " %sO: %sOpen saved game ", yellowColorEscape, whiteColorEscape); - } else { - strcpy(buttons[buttonCount].text, " Open saved game "); - } - buttons[buttonCount].hotkey[0] = LOAD_SAVED_GAME_KEY; - buttonCount++; - if (KEYBOARD_LABELS) { - sprintf(buttons[buttonCount].text, " %sV: %sView saved recording ", yellowColorEscape, whiteColorEscape); - } else { - strcpy(buttons[buttonCount].text, " View saved recording "); - } - buttons[buttonCount].hotkey[0] = VIEW_RECORDING_KEY; - buttonCount++; - } else { - if (KEYBOARD_LABELS) { - sprintf(buttons[buttonCount].text, " %sS: %sSave and exit ", yellowColorEscape, whiteColorEscape); - } else { - strcpy(buttons[buttonCount].text, " Save and exit "); - } - buttons[buttonCount].hotkey[0] = SAVE_GAME_KEY; - buttonCount++; - } - } + } else { if (KEYBOARD_LABELS) { - sprintf(buttons[buttonCount].text, " %sQ: %sQuit %s ", yellowColorEscape, whiteColorEscape, (playingBack ? "to title screen" : "and abandon game")); + sprintf(buttons[buttonCount].text, " %sS: %sSave and exit ", yellowColorEscape, whiteColorEscape); } else { - sprintf(buttons[buttonCount].text, " Quit %s ", (playingBack ? "to title screen" : "and abandon game")); + strcpy(buttons[buttonCount].text, " Save and exit "); } - buttons[buttonCount].hotkey[0] = QUIT_KEY; + buttons[buttonCount].hotkey[0] = SAVE_GAME_KEY; buttonCount++; + } + } + if (KEYBOARD_LABELS) { + sprintf(buttons[buttonCount].text, " %sQ: %sQuit %s ", yellowColorEscape, whiteColorEscape, + (playingBack ? "to title screen" : "and abandon game")); + } else { + sprintf(buttons[buttonCount].text, " Quit %s ", (playingBack ? "to title screen" : "and abandon game")); + } + buttons[buttonCount].hotkey[0] = QUIT_KEY; + buttonCount++; - strcpy(buttons[buttonCount].text, " "); - buttons[buttonCount].flags &= ~B_ENABLED; - buttonCount++; + strcpy(buttons[buttonCount].text, " "); + buttons[buttonCount].flags &= ~B_ENABLED; + buttonCount++; - for (i=0; i= COLS) { - x = COLS - longestName - 1; - } - y = ROWS - buttonCount; - for (i=0; i= COLS) { + x = COLS - longestName - 1; + } + y = ROWS - buttonCount; + for (i = 0; i < buttonCount; i++) { + buttons[i].x = x; + buttons[i].y = y + i; + for (j = strLenWithoutEscapes(buttons[i].text); j < longestName; j++) { + strcat(buttons[i].text, " "); // Schlemiel the Painter, but who cares. + } + } - clearDisplayBuffer(dbuf); - rectangularShading(x - 1, y, longestName + 2, buttonCount, &black, INTERFACE_OPACITY / 2, dbuf); - overlayDisplayBuffer(dbuf, rbuf); - buttonChosen = buttonInputLoop(buttons, buttonCount, x - 1, y, longestName + 2, buttonCount, NULL); - overlayDisplayBuffer(rbuf, NULL); - if (buttonChosen == -1) { - return -1; - } else if (takeActionOurselves[buttonChosen]) { - - theEvent.eventType = KEYSTROKE; - theEvent.param1 = buttons[buttonChosen].hotkey[0]; - theEvent.param2 = 0; - theEvent.shiftKey = theEvent.controlKey = false; - executeEvent(&theEvent); - } else { - return buttons[buttonChosen].hotkey[0]; - } - } while (takeActionOurselves[buttonChosen]); - brogueAssert(false); - return -1; + clearDisplayBuffer(dbuf); + rectangularShading(x - 1, y, longestName + 2, buttonCount, &black, INTERFACE_OPACITY / 2, dbuf); + overlayDisplayBuffer(dbuf, rbuf); + buttonChosen = buttonInputLoop(buttons, buttonCount, x - 1, y, longestName + 2, buttonCount, NULL); + overlayDisplayBuffer(rbuf, NULL); + if (buttonChosen == -1) { + return -1; + } else if (takeActionOurselves[buttonChosen]) { + + theEvent.eventType = KEYSTROKE; + theEvent.param1 = buttons[buttonChosen].hotkey[0]; + theEvent.param2 = 0; + theEvent.shiftKey = theEvent.controlKey = false; + executeEvent(&theEvent); + } else { + return buttons[buttonChosen].hotkey[0]; + } + } while (takeActionOurselves[buttonChosen]); + brogueAssert(false); + return -1; } #define MAX_MENU_BUTTON_COUNT 5 void initializeMenuButtons(buttonState *state, brogueButton buttons[5]) { - short i, x, buttonCount; - char goldTextEscape[MAX_MENU_BUTTON_COUNT] = ""; - char whiteTextEscape[MAX_MENU_BUTTON_COUNT] = ""; - color tempColor; - - encodeMessageColor(goldTextEscape, 0, KEYBOARD_LABELS ? &yellow : &white); - encodeMessageColor(whiteTextEscape, 0, &white); - - for (i=0; ibuttons[i]), BUTTON_NORMAL, state->rbuf); - } - for (i=0; irbuf[i][ROWS - 1].backColorComponents); - desaturate(&tempColor, 60); - applyColorAverage(&tempColor, &black, 50); - storeColorComponents(state->rbuf[i][ROWS - 1].backColorComponents, &tempColor); - tempColor = colorFromComponents(state->rbuf[i][ROWS - 1].foreColorComponents); - desaturate(&tempColor, 60); - applyColorAverage(&tempColor, &black, 50); - storeColorComponents(state->rbuf[i][ROWS - 1].foreColorComponents, &tempColor); - } + if (KEYBOARD_LABELS) { + sprintf(buttons[buttonCount].text, " Search (%ss%s) ", goldTextEscape, whiteTextEscape); + } else { + strcpy(buttons[buttonCount].text, " Search "); + } + buttons[buttonCount].hotkey[0] = SEARCH_KEY; + buttonCount++; + + strcpy(buttons[buttonCount].text, " Menu "); + buttonCount++; + } + + sprintf(buttons[4].text, " %sI%snventory ", goldTextEscape, whiteTextEscape); + buttons[4].hotkey[0] = INVENTORY_KEY; + buttons[4].hotkey[1] = 'I'; + + x = mapToWindowX(0); + for (i = 0; i < 5; i++) { + buttons[i].x = x; + x += strLenWithoutEscapes(buttons[i].text) + 2; // Gap between buttons. + } + + initializeButtonState(state, buttons, 5, mapToWindowX(0), ROWS - 1, COLS - mapToWindowX(0), 1); + + for (i = 0; i < 5; i++) { + drawButton(&(state->buttons[i]), BUTTON_NORMAL, state->rbuf); + } + for (i = 0; i < COLS; i++) { // So the buttons stay (but are dimmed and desaturated) when inactive. + tempColor = colorFromComponents(state->rbuf[i][ROWS - 1].backColorComponents); + desaturate(&tempColor, 60); + applyColorAverage(&tempColor, &black, 50); + storeColorComponents(state->rbuf[i][ROWS - 1].backColorComponents, &tempColor); + tempColor = colorFromComponents(state->rbuf[i][ROWS - 1].foreColorComponents); + desaturate(&tempColor, 60); + applyColorAverage(&tempColor, &black, 50); + storeColorComponents(state->rbuf[i][ROWS - 1].foreColorComponents, &tempColor); + } } - // This is basically the main loop for the game. void mainInputLoop() { - pos oldTargetLoc = { 0, 0 }; - short steps, oldRNG, dir, newX, newY; - pos path[1000]; - creature *monst; - item *theItem; - cellDisplayBuffer rbuf[COLS][ROWS]; - - boolean canceled, targetConfirmed, tabKey, focusedOnMonster, focusedOnItem, focusedOnTerrain, - playingBack, doEvent, textDisplayed; - - rogueEvent theEvent; - short **costMap, **playerPathingMap, **cursorSnapMap; - brogueButton buttons[5] = {{{0}}}; - buttonState state; - short buttonInput; - short backupCost; - - canceled = false; - rogue.cursorMode = false; // Controls whether the keyboard moves the cursor or the character. - steps = 0; - - rogue.cursorPathIntensity = (rogue.cursorMode ? 50 : 20); + pos oldTargetLoc = {0, 0}; + short steps, oldRNG, dir, newX, newY; + pos path[1000]; + creature *monst; + item *theItem; + cellDisplayBuffer rbuf[COLS][ROWS]; - // Initialize buttons. - initializeMenuButtons(&state, buttons); + boolean canceled, targetConfirmed, tabKey, focusedOnMonster, focusedOnItem, focusedOnTerrain, playingBack, doEvent, + textDisplayed; - playingBack = rogue.playbackMode; - rogue.playbackMode = false; - costMap = allocGrid(); - playerPathingMap = allocGrid(); - cursorSnapMap = allocGrid(); + rogueEvent theEvent; + short **costMap, **playerPathingMap, **cursorSnapMap; + brogueButton buttons[5] = {{{0}}}; + buttonState state; + short buttonInput; + short backupCost; - rogue.cursorLoc = INVALID_POS; + canceled = false; + rogue.cursorMode = false; // Controls whether the keyboard moves the cursor or the character. + steps = 0; - while (!rogue.gameHasEnded && (!playingBack || !canceled)) { // repeats until the game ends + rogue.cursorPathIntensity = (rogue.cursorMode ? 50 : 20); - oldRNG = rogue.RNG; - rogue.RNG = RNG_COSMETIC; + // Initialize buttons. + initializeMenuButtons(&state, buttons); - focusedOnMonster = focusedOnItem = focusedOnTerrain = false; - steps = 0; - clearCursorPath(); + playingBack = rogue.playbackMode; + rogue.playbackMode = false; + costMap = allocGrid(); + playerPathingMap = allocGrid(); + cursorSnapMap = allocGrid(); - const pos originLoc = player.loc; + rogue.cursorLoc = INVALID_POS; - if (playingBack && rogue.cursorMode) { - temporaryMessage("Examine what? (, mouse, or )", 0); - } + while (!rogue.gameHasEnded && (!playingBack || !canceled)) { // repeats until the game ends - if (!playingBack - && posEq(player.loc, rogue.cursorLoc) - && posEq(oldTargetLoc, rogue.cursorLoc)) { + oldRNG = rogue.RNG; + rogue.RNG = RNG_COSMETIC; - // Path hides when you reach your destination. - rogue.cursorMode = false; - rogue.cursorPathIntensity = (rogue.cursorMode ? 50 : 20); - rogue.cursorLoc = INVALID_POS; - } + focusedOnMonster = focusedOnItem = focusedOnTerrain = false; + steps = 0; + clearCursorPath(); - oldTargetLoc = rogue.cursorLoc; + const pos originLoc = player.loc; - populateCreatureCostMap(costMap, &player); + if (playingBack && rogue.cursorMode) { + temporaryMessage("Examine what? (, mouse, or )", 0); + } - fillGrid(playerPathingMap, 30000); - playerPathingMap[player.loc.x][player.loc.y] = 0; - dijkstraScan(playerPathingMap, costMap, true); - processSnapMap(cursorSnapMap); + if (!playingBack && posEq(player.loc, rogue.cursorLoc) && posEq(oldTargetLoc, rogue.cursorLoc)) { - do { - textDisplayed = false; + // Path hides when you reach your destination. + rogue.cursorMode = false; + rogue.cursorPathIntensity = (rogue.cursorMode ? 50 : 20); + rogue.cursorLoc = INVALID_POS; + } - // Draw the cursor and path - if (isPosInMap(oldTargetLoc)) { - refreshDungeonCell(oldTargetLoc.x, oldTargetLoc.y); // Remove old cursor. - } - if (!playingBack) { - if (isPosInMap(oldTargetLoc)) { - hilitePath(path, steps, true); // Unhilite old path. - } - if (isPosInMap(rogue.cursorLoc)) { - pos pathDestination; - if (cursorSnapMap[rogue.cursorLoc.x][rogue.cursorLoc.y] >= 0 - && cursorSnapMap[rogue.cursorLoc.x][rogue.cursorLoc.y] < 30000) { - pathDestination = rogue.cursorLoc; - } else { - // If the cursor is aimed at an inaccessible area, find the nearest accessible area to path toward. - pathDestination = getClosestValidLocationOnMap(cursorSnapMap, rogue.cursorLoc.x, rogue.cursorLoc.y); - } - - fillGrid(playerPathingMap, 30000); - playerPathingMap[pathDestination.x][pathDestination.y] = 0; - backupCost = costMap[pathDestination.x][pathDestination.y]; - costMap[pathDestination.x][pathDestination.y] = 1; - dijkstraScan(playerPathingMap, costMap, true); - costMap[pathDestination.x][pathDestination.y] = backupCost; - steps = getPlayerPathOnMap(path, playerPathingMap, player.loc); - -// steps = getPlayerPathOnMap(path, playerPathingMap, pathDestination[0], pathDestination[1]) - 1; // Get new path. -// reversePath(path, steps); // Flip it around, back-to-front. - - if (steps >= 0) { - path[steps] = pathDestination; - } - steps++; -// if (playerPathingMap[cursor[0]][cursor[1]] != 1 - if (playerPathingMap[player.loc.x][player.loc.y] != 1 - || !posEq(pathDestination, rogue.cursorLoc)) { - - hilitePath(path, steps, false); // Hilite new path. - } - } - } + oldTargetLoc = rogue.cursorLoc; - if (isPosInMap(rogue.cursorLoc)) { - hiliteCell(rogue.cursorLoc.x, - rogue.cursorLoc.y, - &white, - (steps <= 0 - || posEq(path[steps-1], rogue.cursorLoc) - || (!playingBack && distanceBetween(player.loc, rogue.cursorLoc) <= 1) ? 100 : 25), - true); - - oldTargetLoc = rogue.cursorLoc; - - monst = monsterAtLoc(rogue.cursorLoc); - theItem = itemAtLoc(rogue.cursorLoc.x, rogue.cursorLoc.y); - if (monst != NULL && (canSeeMonster(monst) || rogue.playbackOmniscience)) { - rogue.playbackMode = playingBack; - refreshSideBar(rogue.cursorLoc.x, rogue.cursorLoc.y, false); - rogue.playbackMode = false; - - focusedOnMonster = true; - if (monst != &player && (!player.status[STATUS_HALLUCINATING] || rogue.playbackOmniscience || player.status[STATUS_TELEPATHIC])) { - printMonsterDetails(monst, rbuf); - textDisplayed = true; - } - } else if (theItem != NULL && playerCanSeeOrSense(rogue.cursorLoc.x, rogue.cursorLoc.y)) { - rogue.playbackMode = playingBack; - refreshSideBar(rogue.cursorLoc.x, rogue.cursorLoc.y, false); - rogue.playbackMode = false; - - focusedOnItem = true; - if (!player.status[STATUS_HALLUCINATING] || rogue.playbackOmniscience) { - printFloorItemDetails(theItem, rbuf); - textDisplayed = true; - } - } else if (cellHasTMFlag(rogue.cursorLoc.x, rogue.cursorLoc.y, TM_LIST_IN_SIDEBAR) && playerCanSeeOrSense(rogue.cursorLoc.x, rogue.cursorLoc.y)) { - rogue.playbackMode = playingBack; - refreshSideBar(rogue.cursorLoc.x, rogue.cursorLoc.y, false); - rogue.playbackMode = false; - focusedOnTerrain = true; - } - - printLocationDescription(rogue.cursorLoc.x, rogue.cursorLoc.y); - } + populateCreatureCostMap(costMap, &player); - // Get the input! - rogue.playbackMode = playingBack; - doEvent = moveCursor(&targetConfirmed, &canceled, &tabKey, &rogue.cursorLoc, &theEvent, &state, !textDisplayed, rogue.cursorMode, true); - rogue.playbackMode = false; - - if (state.buttonChosen == 3) { // Actions menu button. - buttonInput = actionMenu(buttons[3].x - 4, playingBack); // Returns the corresponding keystroke. - if (buttonInput == -1) { // Canceled. - doEvent = false; - } else { - theEvent.eventType = KEYSTROKE; - theEvent.param1 = buttonInput; - theEvent.param2 = 0; - theEvent.shiftKey = theEvent.controlKey = false; - doEvent = true; - } - } else if (state.buttonChosen > -1) { - theEvent.eventType = KEYSTROKE; - theEvent.param1 = buttons[state.buttonChosen].hotkey[0]; - theEvent.param2 = 0; - } - state.buttonChosen = -1; - - if (playingBack) { - if (canceled) { - rogue.cursorMode = false; - rogue.cursorPathIntensity = (rogue.cursorMode ? 50 : 20); - } - - if (theEvent.eventType == KEYSTROKE - && theEvent.param1 == ACKNOWLEDGE_KEY) { // To unpause by button during playback. - canceled = true; - } else { - canceled = false; - } - } + fillGrid(playerPathingMap, 30000); + playerPathingMap[player.loc.x][player.loc.y] = 0; + dijkstraScan(playerPathingMap, costMap, true); + processSnapMap(cursorSnapMap); - if (focusedOnMonster || focusedOnItem || focusedOnTerrain) { - focusedOnMonster = false; - focusedOnItem = false; - focusedOnTerrain = false; - if (textDisplayed) { - overlayDisplayBuffer(rbuf, 0); // Erase the monster info window. - } - rogue.playbackMode = playingBack; - refreshSideBar(-1, -1, false); - rogue.playbackMode = false; - } + do { + textDisplayed = false; - if (tabKey && !playingBack) { // The tab key cycles the cursor through monsters, items and terrain features. - if (nextTargetAfter(&newX, &newY, rogue.cursorLoc.x, rogue.cursorLoc.y, true, true, true, true, false, theEvent.shiftKey)) { - rogue.cursorLoc.x = newX; - rogue.cursorLoc.y = newY; - } - } + // Draw the cursor and path + if (isPosInMap(oldTargetLoc)) { + refreshDungeonCell(oldTargetLoc.x, oldTargetLoc.y); // Remove old cursor. + } + if (!playingBack) { + if (isPosInMap(oldTargetLoc)) { + hilitePath(path, steps, true); // Unhilite old path. + } + if (isPosInMap(rogue.cursorLoc)) { + pos pathDestination; + if (cursorSnapMap[rogue.cursorLoc.x][rogue.cursorLoc.y] >= 0 + && cursorSnapMap[rogue.cursorLoc.x][rogue.cursorLoc.y] < 30000) { + pathDestination = rogue.cursorLoc; + } else { + // If the cursor is aimed at an inaccessible area, find the nearest accessible area to path toward. + pathDestination = getClosestValidLocationOnMap(cursorSnapMap, rogue.cursorLoc.x, rogue.cursorLoc.y); + } + + fillGrid(playerPathingMap, 30000); + playerPathingMap[pathDestination.x][pathDestination.y] = 0; + backupCost = costMap[pathDestination.x][pathDestination.y]; + costMap[pathDestination.x][pathDestination.y] = 1; + dijkstraScan(playerPathingMap, costMap, true); + costMap[pathDestination.x][pathDestination.y] = backupCost; + steps = getPlayerPathOnMap(path, playerPathingMap, player.loc); + + // steps = getPlayerPathOnMap(path, playerPathingMap, pathDestination[0], pathDestination[1]) + // - 1; // Get new path. reversePath(path, steps); // Flip it around, back-to-front. + + if (steps >= 0) { + path[steps] = pathDestination; + } + steps++; + // if (playerPathingMap[cursor[0]][cursor[1]] != 1 + if (playerPathingMap[player.loc.x][player.loc.y] != 1 || !posEq(pathDestination, rogue.cursorLoc)) { + + hilitePath(path, steps, false); // Hilite new path. + } + } + } + + if (isPosInMap(rogue.cursorLoc)) { + hiliteCell(rogue.cursorLoc.x, rogue.cursorLoc.y, &white, + (steps <= 0 || posEq(path[steps - 1], rogue.cursorLoc) + || (!playingBack && distanceBetween(player.loc, rogue.cursorLoc) <= 1) + ? 100 + : 25), + true); - if (theEvent.eventType == KEYSTROKE - && (theEvent.param1 == ASCEND_KEY && rogue.cursorLoc.x == rogue.upLoc.x && rogue.cursorLoc.y == rogue.upLoc.y - || theEvent.param1 == DESCEND_KEY && rogue.cursorLoc.x == rogue.downLoc.x && rogue.cursorLoc.y == rogue.downLoc.y)) { + oldTargetLoc = rogue.cursorLoc; - targetConfirmed = true; - doEvent = false; - } - } while (!targetConfirmed && !canceled && !doEvent && !rogue.gameHasEnded); + monst = monsterAtLoc(rogue.cursorLoc); + theItem = itemAtLoc(rogue.cursorLoc.x, rogue.cursorLoc.y); + if (monst != NULL && (canSeeMonster(monst) || rogue.playbackOmniscience)) { + rogue.playbackMode = playingBack; + refreshSideBar(rogue.cursorLoc.x, rogue.cursorLoc.y, false); + rogue.playbackMode = false; + + focusedOnMonster = true; + if (monst != &player + && (!player.status[STATUS_HALLUCINATING] || rogue.playbackOmniscience + || player.status[STATUS_TELEPATHIC])) { + printMonsterDetails(monst, rbuf); + textDisplayed = true; + } + } else if (theItem != NULL && playerCanSeeOrSense(rogue.cursorLoc.x, rogue.cursorLoc.y)) { + rogue.playbackMode = playingBack; + refreshSideBar(rogue.cursorLoc.x, rogue.cursorLoc.y, false); + rogue.playbackMode = false; + + focusedOnItem = true; + if (!player.status[STATUS_HALLUCINATING] || rogue.playbackOmniscience) { + printFloorItemDetails(theItem, rbuf); + textDisplayed = true; + } + } else if (cellHasTMFlag(rogue.cursorLoc.x, rogue.cursorLoc.y, TM_LIST_IN_SIDEBAR) + && playerCanSeeOrSense(rogue.cursorLoc.x, rogue.cursorLoc.y)) { + rogue.playbackMode = playingBack; + refreshSideBar(rogue.cursorLoc.x, rogue.cursorLoc.y, false); + rogue.playbackMode = false; + focusedOnTerrain = true; + } + + printLocationDescription(rogue.cursorLoc.x, rogue.cursorLoc.y); + } + + // Get the input! + rogue.playbackMode = playingBack; + doEvent = moveCursor(&targetConfirmed, &canceled, &tabKey, &rogue.cursorLoc, &theEvent, &state, !textDisplayed, + rogue.cursorMode, true); + rogue.playbackMode = false; + + if (state.buttonChosen == 3) { // Actions menu button. + buttonInput = actionMenu(buttons[3].x - 4, playingBack); // Returns the corresponding keystroke. + if (buttonInput == -1) { // Canceled. + doEvent = false; + } else { + theEvent.eventType = KEYSTROKE; + theEvent.param1 = buttonInput; + theEvent.param2 = 0; + theEvent.shiftKey = theEvent.controlKey = false; + doEvent = true; + } + } else if (state.buttonChosen > -1) { + theEvent.eventType = KEYSTROKE; + theEvent.param1 = buttons[state.buttonChosen].hotkey[0]; + theEvent.param2 = 0; + } + state.buttonChosen = -1; + + if (playingBack) { + if (canceled) { + rogue.cursorMode = false; + rogue.cursorPathIntensity = (rogue.cursorMode ? 50 : 20); + } + + if (theEvent.eventType == KEYSTROKE + && theEvent.param1 == ACKNOWLEDGE_KEY) { // To unpause by button during playback. + canceled = true; + } else { + canceled = false; + } + } - if (isPosInMap(oldTargetLoc)) { - refreshDungeonCell(oldTargetLoc.x, oldTargetLoc.y); // Remove old rogue.cursorLoc. + if (focusedOnMonster || focusedOnItem || focusedOnTerrain) { + focusedOnMonster = false; + focusedOnItem = false; + focusedOnTerrain = false; + if (textDisplayed) { + overlayDisplayBuffer(rbuf, 0); // Erase the monster info window. } + rogue.playbackMode = playingBack; + refreshSideBar(-1, -1, false); + rogue.playbackMode = false; + } - restoreRNG; - - if (canceled && !playingBack) { - hideCursor(); - confirmMessages(); - } else if (targetConfirmed && !playingBack && isPosInMap(rogue.cursorLoc)) { - if (theEvent.eventType == MOUSE_UP - && theEvent.controlKey - && steps > 1) { - // Control-clicking moves the player one step along the path. - for (dir=0; - dir < DIRECTION_COUNT && !posEq(posNeighborInDirection(player.loc, dir) , path[0]); - dir++); - playerMoves(dir); - } else if (D_WORMHOLING) { - travel(rogue.cursorLoc.x, rogue.cursorLoc.y, true); - } else { - confirmMessages(); - if (posEq(originLoc, rogue.cursorLoc)) { - confirmMessages(); - } else if (abs(player.loc.x - rogue.cursorLoc.x) + abs(player.loc.y - rogue.cursorLoc.y) == 1 // horizontal or vertical - || (distanceBetween(player.loc, rogue.cursorLoc) == 1 // includes diagonals - && (!diagonalBlocked(player.loc.x, player.loc.y, rogue.cursorLoc.x, rogue.cursorLoc.y, !rogue.playbackOmniscience) - || ((pmapAt(rogue.cursorLoc)->flags & HAS_MONSTER) && (monsterAtLoc(rogue.cursorLoc)->info.flags & MONST_ATTACKABLE_THRU_WALLS)) // there's a turret there - || ((terrainFlags(rogue.cursorLoc.x, rogue.cursorLoc.y) & T_OBSTRUCTS_PASSABILITY) && (terrainMechFlags(rogue.cursorLoc.x, rogue.cursorLoc.y) & TM_PROMOTES_ON_PLAYER_ENTRY))))) { // there's a lever there - // Clicking one space away will cause the player to try to move there directly irrespective of path. - for (dir=0; - dir < DIRECTION_COUNT && (player.loc.x + nbDirs[dir][0] != rogue.cursorLoc.x || player.loc.y + nbDirs[dir][1] != rogue.cursorLoc.y); - dir++); - playerMoves(dir); - } else if (steps) { - travelRoute(path, steps); - } - } - } else if (doEvent) { - // If the player entered input during moveCursor() that wasn't a cursor movement command. - // Mainly, we want to filter out directional keystrokes when we're in cursor mode, since - // those should move the cursor but not the player. - brogueAssert(rogue.RNG == RNG_SUBSTANTIVE); - if (playingBack) { - rogue.playbackMode = true; - executePlaybackInput(&theEvent); -#ifdef ENABLE_PLAYBACK_SWITCH - if (!rogue.playbackMode) { - // Playback mode is off, user must have taken control - // Redraw buttons to reflect that - initializeMenuButtons(&state, buttons); - } -#endif - playingBack = rogue.playbackMode; - rogue.playbackMode = false; - } else { - executeEvent(&theEvent); - if (rogue.playbackMode) { - playingBack = true; - rogue.playbackMode = false; - confirmMessages(); - break; - } - } + if (tabKey && !playingBack) { // The tab key cycles the cursor through monsters, items and terrain features. + if (nextTargetAfter(&newX, &newY, rogue.cursorLoc.x, rogue.cursorLoc.y, true, true, true, true, false, + theEvent.shiftKey)) { + rogue.cursorLoc.x = newX; + rogue.cursorLoc.y = newY; } + } + + if (theEvent.eventType == KEYSTROKE + && (theEvent.param1 == ASCEND_KEY && rogue.cursorLoc.x == rogue.upLoc.x && rogue.cursorLoc.y == rogue.upLoc.y + || theEvent.param1 == DESCEND_KEY && rogue.cursorLoc.x == rogue.downLoc.x + && rogue.cursorLoc.y == rogue.downLoc.y)) { + + targetConfirmed = true; + doEvent = false; + } + } while (!targetConfirmed && !canceled && !doEvent && !rogue.gameHasEnded); + + if (isPosInMap(oldTargetLoc)) { + refreshDungeonCell(oldTargetLoc.x, oldTargetLoc.y); // Remove old rogue.cursorLoc. } - rogue.playbackMode = playingBack; - refreshSideBar(-1, -1, false); - freeGrid(costMap); - freeGrid(playerPathingMap); - freeGrid(cursorSnapMap); + restoreRNG; + + if (canceled && !playingBack) { + hideCursor(); + confirmMessages(); + } else if (targetConfirmed && !playingBack && isPosInMap(rogue.cursorLoc)) { + if (theEvent.eventType == MOUSE_UP && theEvent.controlKey && steps > 1) { + // Control-clicking moves the player one step along the path. + for (dir = 0; dir < DIRECTION_COUNT && !posEq(posNeighborInDirection(player.loc, dir), path[0]); dir++) + ; + playerMoves(dir); + } else if (D_WORMHOLING) { + travel(rogue.cursorLoc.x, rogue.cursorLoc.y, true); + } else { + confirmMessages(); + if (posEq(originLoc, rogue.cursorLoc)) { + confirmMessages(); + } else if (abs(player.loc.x - rogue.cursorLoc.x) + abs(player.loc.y - rogue.cursorLoc.y) + == 1 // horizontal or vertical + || (distanceBetween(player.loc, rogue.cursorLoc) == 1 // includes diagonals + && (!diagonalBlocked(player.loc.x, player.loc.y, rogue.cursorLoc.x, rogue.cursorLoc.y, + !rogue.playbackOmniscience) + || ((pmapAt(rogue.cursorLoc)->flags & HAS_MONSTER) + && (monsterAtLoc(rogue.cursorLoc)->info.flags + & MONST_ATTACKABLE_THRU_WALLS)) // there's a turret there + || ((terrainFlags(rogue.cursorLoc.x, rogue.cursorLoc.y) & T_OBSTRUCTS_PASSABILITY) + && (terrainMechFlags(rogue.cursorLoc.x, rogue.cursorLoc.y) + & TM_PROMOTES_ON_PLAYER_ENTRY))))) { // there's a lever there + // Clicking one space away will cause the player to try to move there directly irrespective of path. + for (dir = 0; dir < DIRECTION_COUNT + && (player.loc.x + nbDirs[dir][0] != rogue.cursorLoc.x + || player.loc.y + nbDirs[dir][1] != rogue.cursorLoc.y); + dir++) + ; + playerMoves(dir); + } else if (steps) { + travelRoute(path, steps); + } + } + } else if (doEvent) { + // If the player entered input during moveCursor() that wasn't a cursor movement command. + // Mainly, we want to filter out directional keystrokes when we're in cursor mode, since + // those should move the cursor but not the player. + brogueAssert(rogue.RNG == RNG_SUBSTANTIVE); + if (playingBack) { + rogue.playbackMode = true; + executePlaybackInput(&theEvent); +#ifdef ENABLE_PLAYBACK_SWITCH + if (!rogue.playbackMode) { + // Playback mode is off, user must have taken control + // Redraw buttons to reflect that + initializeMenuButtons(&state, buttons); + } +#endif + playingBack = rogue.playbackMode; + rogue.playbackMode = false; + } else { + executeEvent(&theEvent); + if (rogue.playbackMode) { + playingBack = true; + rogue.playbackMode = false; + confirmMessages(); + break; + } + } + } + } + + rogue.playbackMode = playingBack; + refreshSideBar(-1, -1, false); + freeGrid(costMap); + freeGrid(playerPathingMap); + freeGrid(cursorSnapMap); } // accuracy depends on how many clock cycles occur per second -#define MILLISECONDS (clock() * 1000 / CLOCKS_PER_SEC) +#define MILLISECONDS (clock() * 1000 / CLOCKS_PER_SEC) -#define MILLISECONDS_FOR_CAUTION 100 +#define MILLISECONDS_FOR_CAUTION 100 void considerCautiousMode() { - /* - signed long oldMilliseconds = rogue.milliseconds; - rogue.milliseconds = MILLISECONDS; - clock_t i = clock(); - printf("\n%li", i); - if (rogue.milliseconds - oldMilliseconds < MILLISECONDS_FOR_CAUTION) { - rogue.cautiousMode = true; - }*/ + /* + signed long oldMilliseconds = rogue.milliseconds; + rogue.milliseconds = MILLISECONDS; + clock_t i = clock(); + printf("\n%li", i); + if (rogue.milliseconds - oldMilliseconds < MILLISECONDS_FOR_CAUTION) { + rogue.cautiousMode = true; + }*/ } // previouslyPlottedCells is only accessed by commitDraws and refreshScreen, @@ -858,2188 +860,2151 @@ static cellDisplayBuffer previouslyPlottedCells[COLS][ROWS]; // Only cells which have changed since the previous commitDraws are actually // drawn. void commitDraws() { - for (int j = 0; j < ROWS; j++) { - for (int i = 0; i < COLS; i++) { - cellDisplayBuffer *lastPlotted = &previouslyPlottedCells[i][j]; - cellDisplayBuffer *curr = &displayBuffer[i][j]; - boolean needsUpdate = - lastPlotted->character != curr->character - || lastPlotted->foreColorComponents[0] != curr->foreColorComponents[0] - || lastPlotted->foreColorComponents[1] != curr->foreColorComponents[1] - || lastPlotted->foreColorComponents[2] != curr->foreColorComponents[2] - || lastPlotted->backColorComponents[0] != curr->backColorComponents[0] - || lastPlotted->backColorComponents[1] != curr->backColorComponents[1] - || lastPlotted->backColorComponents[2] != curr->backColorComponents[2]; - - if (!needsUpdate) { - continue; - } - - plotChar(curr->character, i, j, - curr->foreColorComponents[0], - curr->foreColorComponents[1], - curr->foreColorComponents[2], - curr->backColorComponents[0], - curr->backColorComponents[1], - curr->backColorComponents[2] - ); - *lastPlotted = *curr; - } - } + for (int j = 0; j < ROWS; j++) { + for (int i = 0; i < COLS; i++) { + cellDisplayBuffer *lastPlotted = &previouslyPlottedCells[i][j]; + cellDisplayBuffer *curr = &displayBuffer[i][j]; + boolean needsUpdate = lastPlotted->character != curr->character + || lastPlotted->foreColorComponents[0] != curr->foreColorComponents[0] + || lastPlotted->foreColorComponents[1] != curr->foreColorComponents[1] + || lastPlotted->foreColorComponents[2] != curr->foreColorComponents[2] + || lastPlotted->backColorComponents[0] != curr->backColorComponents[0] + || lastPlotted->backColorComponents[1] != curr->backColorComponents[1] + || lastPlotted->backColorComponents[2] != curr->backColorComponents[2]; + + if (!needsUpdate) { + continue; + } + + plotChar(curr->character, i, j, curr->foreColorComponents[0], curr->foreColorComponents[1], + curr->foreColorComponents[2], curr->backColorComponents[0], curr->backColorComponents[1], + curr->backColorComponents[2]); + *lastPlotted = *curr; + } + } } // flags the entire window as needing to be redrawn at next flush. // very low level -- does not interface with the guts of the game. void refreshScreen() { - for (int i = 0; i < COLS; i++) { - for (int j = 0; j < ROWS; j++) { - cellDisplayBuffer *curr = &displayBuffer[i][j]; - plotChar(curr->character, i, j, - curr->foreColorComponents[0], - curr->foreColorComponents[1], - curr->foreColorComponents[2], - curr->backColorComponents[0], - curr->backColorComponents[1], - curr->backColorComponents[2] - ); - // Remember that it was previously plotted, so that - // commitDraws still knows when it needs updates. - previouslyPlottedCells[i][j] = *curr; - } - } + for (int i = 0; i < COLS; i++) { + for (int j = 0; j < ROWS; j++) { + cellDisplayBuffer *curr = &displayBuffer[i][j]; + plotChar(curr->character, i, j, curr->foreColorComponents[0], curr->foreColorComponents[1], + curr->foreColorComponents[2], curr->backColorComponents[0], curr->backColorComponents[1], + curr->backColorComponents[2]); + // Remember that it was previously plotted, so that + // commitDraws still knows when it needs updates. + previouslyPlottedCells[i][j] = *curr; + } + } } // higher-level redraw void displayLevel() { - short i, j; + short i, j; - for( i=0; i= 0; j--) { - refreshDungeonCell(i, j); - } + for (i = 0; i < DCOLS; i++) { + for (j = DROWS - 1; j >= 0; j--) { + refreshDungeonCell(i, j); } + } } // converts colors into components void storeColorComponents(char components[3], const color *theColor) { - short rand = rand_range(0, theColor->rand); - components[0] = max(0, min(100, theColor->red + rand_range(0, theColor->redRand) + rand)); - components[1] = max(0, min(100, theColor->green + rand_range(0, theColor->greenRand) + rand)); - components[2] = max(0, min(100, theColor->blue + rand_range(0, theColor->blueRand) + rand)); + short rand = rand_range(0, theColor->rand); + components[0] = max(0, min(100, theColor->red + rand_range(0, theColor->redRand) + rand)); + components[1] = max(0, min(100, theColor->green + rand_range(0, theColor->greenRand) + rand)); + components[2] = max(0, min(100, theColor->blue + rand_range(0, theColor->blueRand) + rand)); } void bakeTerrainColors(color *foreColor, color *backColor, short x, short y) { - const short *vals; - const short neutralColors[8] = {1000, 1000, 1000, 1000, 0, 0, 0, 0}; - if (rogue.trueColorMode) { - vals = neutralColors; - } else { - vals = &(terrainRandomValues[x][y][0]); - } - - const short foreRand = foreColor->rand * vals[6] / 1000; - const short backRand = backColor->rand * vals[7] / 1000; - - foreColor->red += foreColor->redRand * vals[0] / 1000 + foreRand; - foreColor->green += foreColor->greenRand * vals[1] / 1000 + foreRand; - foreColor->blue += foreColor->blueRand * vals[2] / 1000 + foreRand; - foreColor->redRand = foreColor->greenRand = foreColor->blueRand = foreColor->rand = 0; - - backColor->red += backColor->redRand * vals[3] / 1000 + backRand; - backColor->green += backColor->greenRand * vals[4] / 1000 + backRand; - backColor->blue += backColor->blueRand * vals[5] / 1000 + backRand; - backColor->redRand = backColor->greenRand = backColor->blueRand = backColor->rand = 0; - - if (foreColor->colorDances || backColor->colorDances) { - pmap[x][y].flags |= TERRAIN_COLORS_DANCING; - } else { - pmap[x][y].flags &= ~TERRAIN_COLORS_DANCING; - } + const short *vals; + const short neutralColors[8] = {1000, 1000, 1000, 1000, 0, 0, 0, 0}; + if (rogue.trueColorMode) { + vals = neutralColors; + } else { + vals = &(terrainRandomValues[x][y][0]); + } + + const short foreRand = foreColor->rand * vals[6] / 1000; + const short backRand = backColor->rand * vals[7] / 1000; + + foreColor->red += foreColor->redRand * vals[0] / 1000 + foreRand; + foreColor->green += foreColor->greenRand * vals[1] / 1000 + foreRand; + foreColor->blue += foreColor->blueRand * vals[2] / 1000 + foreRand; + foreColor->redRand = foreColor->greenRand = foreColor->blueRand = foreColor->rand = 0; + + backColor->red += backColor->redRand * vals[3] / 1000 + backRand; + backColor->green += backColor->greenRand * vals[4] / 1000 + backRand; + backColor->blue += backColor->blueRand * vals[5] / 1000 + backRand; + backColor->redRand = backColor->greenRand = backColor->blueRand = backColor->rand = 0; + + if (foreColor->colorDances || backColor->colorDances) { + pmap[x][y].flags |= TERRAIN_COLORS_DANCING; + } else { + pmap[x][y].flags &= ~TERRAIN_COLORS_DANCING; + } } void bakeColor(color *theColor) { - short rand; - rand = rand_range(0, theColor->rand); - theColor->red += rand_range(0, theColor->redRand) + rand; - theColor->green += rand_range(0, theColor->greenRand) + rand; - theColor->blue += rand_range(0, theColor->blueRand) + rand; - theColor->redRand = theColor->greenRand = theColor->blueRand = theColor->rand = 0; + short rand; + rand = rand_range(0, theColor->rand); + theColor->red += rand_range(0, theColor->redRand) + rand; + theColor->green += rand_range(0, theColor->greenRand) + rand; + theColor->blue += rand_range(0, theColor->blueRand) + rand; + theColor->redRand = theColor->greenRand = theColor->blueRand = theColor->rand = 0; } void shuffleTerrainColors(short percentOfCells, boolean refreshCells) { - enum directions dir; - short i, j; - - assureCosmeticRNG; - - for (i=0; i= 100 || rand_range(1, 100) <= percentOfCells)) { - - for (dir=0; dir= 100 || rand_range(1, 100) <= percentOfCells)) { + + for (dir = 0; dir < DIRECTION_COUNT; dir++) { + terrainRandomValues[i][j][dir] += rand_range(-600, 600); + terrainRandomValues[i][j][dir] = clamp(terrainRandomValues[i][j][dir], 0, 1000); } + + if (refreshCells) { + refreshDungeonCell(i, j); + } + } } - restoreRNG; + } + restoreRNG; } // if forecolor is too similar to back, darken or lighten it and return true. // Assumes colors have already been baked (no random components). boolean separateColors(color *fore, const color *back) { - color f, b; - const color *modifier; - short failsafe; - boolean madeChange; - - f = *fore; - b = *back; - f.red = clamp(f.red, 0, 100); - f.green = clamp(f.green, 0, 100); - f.blue = clamp(f.blue, 0, 100); - b.red = clamp(b.red, 0, 100); - b.green = clamp(b.green, 0, 100); - b.blue = clamp(b.blue, 0, 100); - - if (f.red + f.blue + f.green > 50 * 3) { - modifier = &black; - } else { - modifier = &white; - } - - madeChange = false; - failsafe = 10; - - while(COLOR_DIFF(f, b) < MIN_COLOR_DIFF && --failsafe) { - applyColorAverage(&f, modifier, 20); - madeChange = true; - } - - if (madeChange) { - *fore = f; - return true; - } else { - return false; - } + color f, b; + const color *modifier; + short failsafe; + boolean madeChange; + + f = *fore; + b = *back; + f.red = clamp(f.red, 0, 100); + f.green = clamp(f.green, 0, 100); + f.blue = clamp(f.blue, 0, 100); + b.red = clamp(b.red, 0, 100); + b.green = clamp(b.green, 0, 100); + b.blue = clamp(b.blue, 0, 100); + + if (f.red + f.blue + f.green > 50 * 3) { + modifier = &black; + } else { + modifier = &white; + } + + madeChange = false; + failsafe = 10; + + while (COLOR_DIFF(f, b) < MIN_COLOR_DIFF && --failsafe) { + applyColorAverage(&f, modifier, 20); + madeChange = true; + } + + if (madeChange) { + *fore = f; + return true; + } else { + return false; + } } void normColor(color *baseColor, const short aggregateMultiplier, const short colorTranslation) { - baseColor->red += colorTranslation; - baseColor->green += colorTranslation; - baseColor->blue += colorTranslation; - const short vectorLength = baseColor->red + baseColor->green + baseColor->blue; - - if (vectorLength != 0) { - baseColor->red = baseColor->red * 300 / vectorLength * aggregateMultiplier / 100; - baseColor->green = baseColor->green * 300 / vectorLength * aggregateMultiplier / 100; - baseColor->blue = baseColor->blue * 300 / vectorLength * aggregateMultiplier / 100; - } - baseColor->redRand = 0; - baseColor->greenRand = 0; - baseColor->blueRand = 0; - baseColor->rand = 0; + baseColor->red += colorTranslation; + baseColor->green += colorTranslation; + baseColor->blue += colorTranslation; + const short vectorLength = baseColor->red + baseColor->green + baseColor->blue; + + if (vectorLength != 0) { + baseColor->red = baseColor->red * 300 / vectorLength * aggregateMultiplier / 100; + baseColor->green = baseColor->green * 300 / vectorLength * aggregateMultiplier / 100; + baseColor->blue = baseColor->blue * 300 / vectorLength * aggregateMultiplier / 100; + } + baseColor->redRand = 0; + baseColor->greenRand = 0; + baseColor->blueRand = 0; + baseColor->rand = 0; } // Used to determine whether to draw a wall top glyph above static boolean glyphIsWallish(enum displayGlyph glyph) { - switch (glyph) { - case G_WALL: - case G_OPEN_DOOR: - case G_CLOSED_DOOR: - case G_UP_STAIRS: - case G_DOORWAY: - case G_WALL_TOP: - case G_LEVER: - case G_LEVER_PULLED: - case G_CLOSED_IRON_DOOR: - case G_OPEN_IRON_DOOR: - case G_TURRET: - case G_GRANITE: - case G_TORCH: - case G_PORTCULLIS: - return true; - - default: - return false; - } + switch (glyph) { + case G_WALL: + case G_OPEN_DOOR: + case G_CLOSED_DOOR: + case G_UP_STAIRS: + case G_DOORWAY: + case G_WALL_TOP: + case G_LEVER: + case G_LEVER_PULLED: + case G_CLOSED_IRON_DOOR: + case G_OPEN_IRON_DOOR: + case G_TURRET: + case G_GRANITE: + case G_TORCH: + case G_PORTCULLIS: + return true; + + default: + return false; + } } static enum monsterTypes randomAnimateMonster() { - /* Randomly pick an animate and vulnerable monster type. Used by - getCellAppearance for hallucination effects. */ - static int listLength = 0; - static enum monsterTypes animate[NUMBER_MONSTER_KINDS]; - - if (listLength == 0) { - for (int i=0; i < NUMBER_MONSTER_KINDS; i++) { - if (!(monsterCatalog[i].flags & (MONST_INANIMATE | MONST_INVULNERABLE))) { - animate[listLength++] = i; - } - } + /* Randomly pick an animate and vulnerable monster type. Used by + getCellAppearance for hallucination effects. */ + static int listLength = 0; + static enum monsterTypes animate[NUMBER_MONSTER_KINDS]; + + if (listLength == 0) { + for (int i = 0; i < NUMBER_MONSTER_KINDS; i++) { + if (!(monsterCatalog[i].flags & (MONST_INANIMATE | MONST_INVULNERABLE))) { + animate[listLength++] = i; + } } + } - return animate[rand_range(0, listLength - 1)]; + return animate[rand_range(0, listLength - 1)]; } // okay, this is kind of a beast... -void getCellAppearance(short x, short y, enum displayGlyph *returnChar, color *returnForeColor, color *returnBackColor) { - short bestBCPriority, bestFCPriority, bestCharPriority; - short distance; - enum displayGlyph cellChar = 0; - color cellForeColor, cellBackColor, lightMultiplierColor = black, gasAugmentColor; - boolean monsterWithDetectedItem = false, needDistinctness = false; - short gasAugmentWeight = 0; - creature *monst = NULL; - item *theItem = NULL; - enum tileType tile = NOTHING; - const enum displayGlyph itemChars[] = {G_POTION, G_SCROLL, G_FOOD, G_WAND, - G_STAFF, G_GOLD, G_ARMOR, G_WEAPON, G_RING, G_CHARM}; - enum dungeonLayers layer, maxLayer; - - assureCosmeticRNG; - - brogueAssert(coordinatesAreInMap(x, y)); - - if (pmap[x][y].flags & HAS_MONSTER) { - monst = monsterAtLoc((pos){ x, y }); - } else if (pmap[x][y].flags & HAS_DORMANT_MONSTER) { - monst = dormantMonsterAtLoc((pos){ x, y }); - } - if (monst) { - monsterWithDetectedItem = (monst->carriedItem && (monst->carriedItem->flags & ITEM_MAGIC_DETECTED) - && itemMagicPolarity(monst->carriedItem) && !canSeeMonster(monst)); - } - - if (monsterWithDetectedItem) { - theItem = monst->carriedItem; +void getCellAppearance(short x, short y, enum displayGlyph *returnChar, color *returnForeColor, + color *returnBackColor) { + short bestBCPriority, bestFCPriority, bestCharPriority; + short distance; + enum displayGlyph cellChar = 0; + color cellForeColor, cellBackColor, lightMultiplierColor = black, gasAugmentColor; + boolean monsterWithDetectedItem = false, needDistinctness = false; + short gasAugmentWeight = 0; + creature *monst = NULL; + item *theItem = NULL; + enum tileType tile = NOTHING; + const enum displayGlyph itemChars[] + = {G_POTION, G_SCROLL, G_FOOD, G_WAND, G_STAFF, G_GOLD, G_ARMOR, G_WEAPON, G_RING, G_CHARM}; + enum dungeonLayers layer, maxLayer; + + assureCosmeticRNG; + + brogueAssert(coordinatesAreInMap(x, y)); + + if (pmap[x][y].flags & HAS_MONSTER) { + monst = monsterAtLoc((pos){x, y}); + } else if (pmap[x][y].flags & HAS_DORMANT_MONSTER) { + monst = dormantMonsterAtLoc((pos){x, y}); + } + if (monst) { + monsterWithDetectedItem = (monst->carriedItem && (monst->carriedItem->flags & ITEM_MAGIC_DETECTED) + && itemMagicPolarity(monst->carriedItem) && !canSeeMonster(monst)); + } + + if (monsterWithDetectedItem) { + theItem = monst->carriedItem; + } else { + theItem = itemAtLoc(x, y); + } + + if (!playerCanSeeOrSense(x, y) && !(pmap[x][y].flags & (ITEM_DETECTED | HAS_PLAYER)) + && (!monst || !monsterRevealed(monst)) && !monsterWithDetectedItem + && (pmap[x][y].flags & (DISCOVERED | MAGIC_MAPPED)) && (pmap[x][y].flags & STABLE_MEMORY)) { + + // restore memory + cellChar = pmap[x][y].rememberedAppearance.character; + cellForeColor = colorFromComponents(pmap[x][y].rememberedAppearance.foreColorComponents); + cellBackColor = colorFromComponents(pmap[x][y].rememberedAppearance.backColorComponents); + } else { + // Find the highest-priority fore color, back color and character. + bestFCPriority = bestBCPriority = bestCharPriority = 10000; + + // Default to the appearance of floor. + cellForeColor = *(tileCatalog[FLOOR].foreColor); + cellBackColor = *(tileCatalog[FLOOR].backColor); + cellChar = tileCatalog[FLOOR].displayChar; + + if (!(pmap[x][y].flags & DISCOVERED) && !rogue.playbackOmniscience) { + if (pmap[x][y].flags & MAGIC_MAPPED) { + maxLayer = LIQUID + 1; // Can see only dungeon and liquid layers with magic mapping. + } else { + maxLayer = 0; // Terrain shouldn't influence the tile appearance at all if it hasn't been discovered. + } } else { - theItem = itemAtLoc(x, y); + maxLayer = NUMBER_TERRAIN_LAYERS; } - if (!playerCanSeeOrSense(x, y) - && !(pmap[x][y].flags & (ITEM_DETECTED | HAS_PLAYER)) - && (!monst || !monsterRevealed(monst)) - && !monsterWithDetectedItem - && (pmap[x][y].flags & (DISCOVERED | MAGIC_MAPPED)) - && (pmap[x][y].flags & STABLE_MEMORY)) { - - // restore memory - cellChar = pmap[x][y].rememberedAppearance.character; - cellForeColor = colorFromComponents(pmap[x][y].rememberedAppearance.foreColorComponents); - cellBackColor = colorFromComponents(pmap[x][y].rememberedAppearance.backColorComponents); - } else { - // Find the highest-priority fore color, back color and character. - bestFCPriority = bestBCPriority = bestCharPriority = 10000; + for (layer = 0; layer < maxLayer; layer++) { + // Gas shows up as a color average, not directly. + if (pmap[x][y].layers[layer] && layer != GAS) { + tile = pmap[x][y].layers[layer]; + if (rogue.playbackOmniscience && (tileCatalog[tile].mechFlags & TM_IS_SECRET)) { + tile = dungeonFeatureCatalog[tileCatalog[tile].discoverType].tile; + } - // Default to the appearance of floor. - cellForeColor = *(tileCatalog[FLOOR].foreColor); - cellBackColor = *(tileCatalog[FLOOR].backColor); - cellChar = tileCatalog[FLOOR].displayChar; + if (tileCatalog[tile].drawPriority < bestFCPriority && tileCatalog[tile].foreColor) { - if (!(pmap[x][y].flags & DISCOVERED) && !rogue.playbackOmniscience) { - if (pmap[x][y].flags & MAGIC_MAPPED) { - maxLayer = LIQUID + 1; // Can see only dungeon and liquid layers with magic mapping. - } else { - maxLayer = 0; // Terrain shouldn't influence the tile appearance at all if it hasn't been discovered. - } - } else { - maxLayer = NUMBER_TERRAIN_LAYERS; + cellForeColor = *(tileCatalog[tile].foreColor); + bestFCPriority = tileCatalog[tile].drawPriority; } + if (tileCatalog[tile].drawPriority < bestBCPriority && tileCatalog[tile].backColor) { - for (layer = 0; layer < maxLayer; layer++) { - // Gas shows up as a color average, not directly. - if (pmap[x][y].layers[layer] && layer != GAS) { - tile = pmap[x][y].layers[layer]; - if (rogue.playbackOmniscience && (tileCatalog[tile].mechFlags & TM_IS_SECRET)) { - tile = dungeonFeatureCatalog[tileCatalog[tile].discoverType].tile; - } - - if (tileCatalog[tile].drawPriority < bestFCPriority - && tileCatalog[tile].foreColor) { - - cellForeColor = *(tileCatalog[tile].foreColor); - bestFCPriority = tileCatalog[tile].drawPriority; - } - if (tileCatalog[tile].drawPriority < bestBCPriority - && tileCatalog[tile].backColor) { - - cellBackColor = *(tileCatalog[tile].backColor); - bestBCPriority = tileCatalog[tile].drawPriority; - } - if (tileCatalog[tile].drawPriority < bestCharPriority - && tileCatalog[tile].displayChar) { - - cellChar = tileCatalog[tile].displayChar; - bestCharPriority = tileCatalog[tile].drawPriority; - needDistinctness = (tileCatalog[tile].mechFlags & TM_VISUALLY_DISTINCT) ? true : false; - } - } + cellBackColor = *(tileCatalog[tile].backColor); + bestBCPriority = tileCatalog[tile].drawPriority; } + if (tileCatalog[tile].drawPriority < bestCharPriority && tileCatalog[tile].displayChar) { - if (rogue.trueColorMode) { - lightMultiplierColor = colorMultiplier100; - } else { - colorMultiplierFromDungeonLight(x, y, &lightMultiplierColor); + cellChar = tileCatalog[tile].displayChar; + bestCharPriority = tileCatalog[tile].drawPriority; + needDistinctness = (tileCatalog[tile].mechFlags & TM_VISUALLY_DISTINCT) ? true : false; } + } + } - if (pmap[x][y].layers[GAS] - && tileCatalog[pmap[x][y].layers[GAS]].backColor) { - - gasAugmentColor = *(tileCatalog[pmap[x][y].layers[GAS]].backColor); + if (rogue.trueColorMode) { + lightMultiplierColor = colorMultiplier100; + } else { + colorMultiplierFromDungeonLight(x, y, &lightMultiplierColor); + } + + if (pmap[x][y].layers[GAS] && tileCatalog[pmap[x][y].layers[GAS]].backColor) { + + gasAugmentColor = *(tileCatalog[pmap[x][y].layers[GAS]].backColor); + if (rogue.trueColorMode) { + gasAugmentWeight = 30; + } else { + gasAugmentWeight = min(90, 30 + pmap[x][y].volume); + } + } + + if (D_DISABLE_BACKGROUND_COLORS) { + if (COLOR_DIFF(cellBackColor, black) > COLOR_DIFF(cellForeColor, black)) { + cellForeColor = cellBackColor; + } + cellBackColor = black; + needDistinctness = true; + } + + if (pmap[x][y].flags & HAS_PLAYER) { + cellChar = player.info.displayChar; + cellForeColor = *(player.info.foreColor); + needDistinctness = true; + } else if (((pmap[x][y].flags & HAS_ITEM) && (pmap[x][y].flags & ITEM_DETECTED) && itemMagicPolarity(theItem) + && !playerCanSeeOrSense(x, y)) + || monsterWithDetectedItem) { + + int polarity = itemMagicPolarity(theItem); + if (theItem->category == AMULET) { + cellChar = G_AMULET; + cellForeColor = white; + } else if (polarity == -1) { + cellChar = G_BAD_MAGIC; + cellForeColor = badMessageColor; + } else if (polarity == 1) { + cellChar = G_GOOD_MAGIC; + cellForeColor = goodMessageColor; + } else { + cellChar = 0; + cellForeColor = white; + } + + needDistinctness = true; + } else if ((pmap[x][y].flags & HAS_MONSTER) + && (playerCanSeeOrSense(x, y) + || ((monst->info.flags & MONST_IMMOBILE) && (pmap[x][y].flags & DISCOVERED))) + && (!monsterIsHidden(monst, &player) || rogue.playbackOmniscience)) { + needDistinctness = true; + if (player.status[STATUS_HALLUCINATING] > 0 && !(monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE)) + && !rogue.playbackOmniscience && !player.status[STATUS_TELEPATHIC]) { + cellChar = monsterCatalog[randomAnimateMonster()].displayChar; + cellForeColor = *(monsterCatalog[randomAnimateMonster()].foreColor); + } else { + cellChar = monst->info.displayChar; + cellForeColor = *(monst->info.foreColor); + if (monst->status[STATUS_INVISIBLE] || (monst->bookkeepingFlags & MB_SUBMERGED)) { + // Invisible allies show up on the screen with a transparency effect. + // cellForeColor = cellBackColor; + applyColorAverage(&cellForeColor, &cellBackColor, 75); + } else { + if (monst->creatureState == MONSTER_ALLY && !(monst->info.flags & MONST_INANIMATE)) { if (rogue.trueColorMode) { - gasAugmentWeight = 30; + cellForeColor = white; } else { - gasAugmentWeight = min(90, 30 + pmap[x][y].volume); + applyColorAverage(&cellForeColor, &pink, 50); } - } + } + } + // DEBUG if (monst->bookkeepingFlags & MB_LEADER) applyColorAverage(&cellBackColor, &purple, 50); + } + } else if (monst && monsterRevealed(monst) && !canSeeMonster(monst)) { + if (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience && !player.status[STATUS_TELEPATHIC]) { + cellChar = (rand_range(0, 1) ? 'X' : 'x'); + } else { + cellChar = (monst->info.isLarge ? 'X' : 'x'); + } + cellForeColor = white; + lightMultiplierColor = white; + if (!(pmap[x][y].flags & DISCOVERED)) { + cellBackColor = black; + gasAugmentColor = black; + } + } else if ((pmap[x][y].flags & HAS_ITEM) && !cellHasTerrainFlag(x, y, T_OBSTRUCTS_ITEMS) + && (playerCanSeeOrSense(x, y) + || ((pmap[x][y].flags & DISCOVERED) && !cellHasTerrainFlag(x, y, T_MOVES_ITEMS)))) { + needDistinctness = true; + if (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience) { + cellChar = itemChars[rand_range(0, 9)]; + cellForeColor = itemColor; + } else { + theItem = itemAtLoc(x, y); + cellChar = theItem->displayChar; + cellForeColor = *(theItem->foreColor); + // Remember the item was here + pmap[x][y].rememberedItemCategory = theItem->category; + pmap[x][y].rememberedItemKind = theItem->kind; + pmap[x][y].rememberedItemQuantity = theItem->quantity; + pmap[x][y].rememberedItemOriginDepth = theItem->originDepth; + } + } else if (playerCanSeeOrSense(x, y) || (pmap[x][y].flags & (DISCOVERED | MAGIC_MAPPED))) { + // just don't want these to be plotted as black + // Also, ensure we remember there are no items here + pmap[x][y].rememberedItemCategory = 0; + pmap[x][y].rememberedItemKind = 0; + pmap[x][y].rememberedItemQuantity = 0; + pmap[x][y].rememberedItemOriginDepth = 0; + } else { + *returnChar = ' '; + *returnForeColor = black; + *returnBackColor = undiscoveredColor; - if (D_DISABLE_BACKGROUND_COLORS) { - if (COLOR_DIFF(cellBackColor, black) > COLOR_DIFF(cellForeColor, black)) { - cellForeColor = cellBackColor; - } - cellBackColor = black; - needDistinctness = true; - } + if (D_DISABLE_BACKGROUND_COLORS) + *returnBackColor = black; - if (pmap[x][y].flags & HAS_PLAYER) { - cellChar = player.info.displayChar; - cellForeColor = *(player.info.foreColor); - needDistinctness = true; - } else if (((pmap[x][y].flags & HAS_ITEM) && (pmap[x][y].flags & ITEM_DETECTED) - && itemMagicPolarity(theItem) - && !playerCanSeeOrSense(x, y)) - || monsterWithDetectedItem){ - - int polarity = itemMagicPolarity(theItem); - if (theItem->category == AMULET) { - cellChar = G_AMULET; - cellForeColor = white; - } else if (polarity == -1) { - cellChar = G_BAD_MAGIC; - cellForeColor = badMessageColor; - } else if (polarity == 1) { - cellChar = G_GOOD_MAGIC; - cellForeColor = goodMessageColor; - } else { - cellChar = 0; - cellForeColor = white; - } + restoreRNG; + return; + } - needDistinctness = true; - } else if ((pmap[x][y].flags & HAS_MONSTER) - && (playerCanSeeOrSense(x, y) || ((monst->info.flags & MONST_IMMOBILE) && (pmap[x][y].flags & DISCOVERED))) - && (!monsterIsHidden(monst, &player) || rogue.playbackOmniscience)) { - needDistinctness = true; - if (player.status[STATUS_HALLUCINATING] > 0 - && !(monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE)) - && !rogue.playbackOmniscience - && !player.status[STATUS_TELEPATHIC]) { - cellChar = monsterCatalog[randomAnimateMonster()].displayChar; - cellForeColor = *(monsterCatalog[randomAnimateMonster()].foreColor); - } else { - cellChar = monst->info.displayChar; - cellForeColor = *(monst->info.foreColor); - if (monst->status[STATUS_INVISIBLE] || (monst->bookkeepingFlags & MB_SUBMERGED)) { - // Invisible allies show up on the screen with a transparency effect. - //cellForeColor = cellBackColor; - applyColorAverage(&cellForeColor, &cellBackColor, 75); - } else { - if (monst->creatureState == MONSTER_ALLY && !(monst->info.flags & MONST_INANIMATE)) { - if (rogue.trueColorMode) { - cellForeColor = white; - } else { - applyColorAverage(&cellForeColor, &pink, 50); - } - } - } - //DEBUG if (monst->bookkeepingFlags & MB_LEADER) applyColorAverage(&cellBackColor, &purple, 50); - } - } else if (monst - && monsterRevealed(monst) - && !canSeeMonster(monst)) { - if (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience && !player.status[STATUS_TELEPATHIC]) { - cellChar = (rand_range(0, 1) ? 'X' : 'x'); - } else { - cellChar = (monst->info.isLarge ? 'X' : 'x'); - } - cellForeColor = white; - lightMultiplierColor = white; - if (!(pmap[x][y].flags & DISCOVERED)) { - cellBackColor = black; - gasAugmentColor = black; - } - } else if ((pmap[x][y].flags & HAS_ITEM) && !cellHasTerrainFlag(x, y, T_OBSTRUCTS_ITEMS) - && (playerCanSeeOrSense(x, y) || ((pmap[x][y].flags & DISCOVERED) && !cellHasTerrainFlag(x, y, T_MOVES_ITEMS)))) { - needDistinctness = true; - if (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience) { - cellChar = itemChars[rand_range(0, 9)]; - cellForeColor = itemColor; - } else { - theItem = itemAtLoc(x, y); - cellChar = theItem->displayChar; - cellForeColor = *(theItem->foreColor); - // Remember the item was here - pmap[x][y].rememberedItemCategory = theItem->category; - pmap[x][y].rememberedItemKind = theItem->kind; - pmap[x][y].rememberedItemQuantity = theItem->quantity; - pmap[x][y].rememberedItemOriginDepth = theItem->originDepth; - } - } else if (playerCanSeeOrSense(x, y) || (pmap[x][y].flags & (DISCOVERED | MAGIC_MAPPED))) { - // just don't want these to be plotted as black - // Also, ensure we remember there are no items here - pmap[x][y].rememberedItemCategory = 0; - pmap[x][y].rememberedItemKind = 0; - pmap[x][y].rememberedItemQuantity = 0; - pmap[x][y].rememberedItemOriginDepth = 0; - } else { - *returnChar = ' '; - *returnForeColor = black; - *returnBackColor = undiscoveredColor; - - if (D_DISABLE_BACKGROUND_COLORS) *returnBackColor = black; - - restoreRNG; - return; - } - - if (gasAugmentWeight && ((pmap[x][y].flags & DISCOVERED) || rogue.playbackOmniscience)) { - if (!rogue.trueColorMode || !needDistinctness) { - applyColorAverage(&cellForeColor, &gasAugmentColor, gasAugmentWeight); - } - // phantoms create sillhouettes in gas clouds - if ((pmap[x][y].flags & HAS_MONSTER) - && monst->status[STATUS_INVISIBLE] - && playerCanSeeOrSense(x, y) - && !monsterRevealed(monst) - && !monsterHiddenBySubmersion(monst, &player)) { - - if (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience && !player.status[STATUS_TELEPATHIC]) { - cellChar = monsterCatalog[randomAnimateMonster()].displayChar; - } else { - cellChar = monst->info.displayChar; - } - cellForeColor = cellBackColor; - } - applyColorAverage(&cellBackColor, &gasAugmentColor, gasAugmentWeight); - } - - if (!(pmap[x][y].flags & (ANY_KIND_OF_VISIBLE | ITEM_DETECTED | HAS_PLAYER)) - && !playerCanSeeOrSense(x, y) - && (!monst || !monsterRevealed(monst)) && !monsterWithDetectedItem) { - - pmap[x][y].flags |= STABLE_MEMORY; - pmap[x][y].rememberedAppearance.character = cellChar; - - if (rogue.trueColorMode) { - bakeTerrainColors(&cellForeColor, &cellBackColor, x, y); - } - - // store memory - storeColorComponents(pmap[x][y].rememberedAppearance.foreColorComponents, &cellForeColor); - storeColorComponents(pmap[x][y].rememberedAppearance.backColorComponents, &cellBackColor); - - applyColorAugment(&lightMultiplierColor, &basicLightColor, 100); - if (!rogue.trueColorMode || !needDistinctness) { - applyColorMultiplier(&cellForeColor, &lightMultiplierColor); - } - applyColorMultiplier(&cellBackColor, &lightMultiplierColor); - bakeTerrainColors(&cellForeColor, &cellBackColor, x, y); - - // Then restore, so that it looks the same on this pass as it will when later refreshed. - cellForeColor = colorFromComponents(pmap[x][y].rememberedAppearance.foreColorComponents); - cellBackColor = colorFromComponents(pmap[x][y].rememberedAppearance.backColorComponents); - } - } - - // Smooth out walls: if there's a "wall-ish" tile drawn below us, just draw the wall top - if ((cellChar == G_WALL || cellChar == G_GRANITE) && coordinatesAreInMap(x, y+1) - && glyphIsWallish(displayBuffer[mapToWindowX(x)][mapToWindowY(y+1)].character)) { - cellChar = G_WALL_TOP; - } - - if (((pmap[x][y].flags & ITEM_DETECTED) || monsterWithDetectedItem - || (monst && monsterRevealed(monst))) - && !playerCanSeeOrSense(x, y)) { - // do nothing - } else if (!(pmap[x][y].flags & VISIBLE) && (pmap[x][y].flags & CLAIRVOYANT_VISIBLE)) { - // can clairvoyantly see it - if (rogue.trueColorMode) { - lightMultiplierColor = basicLightColor; - } else { - applyColorAugment(&lightMultiplierColor, &basicLightColor, 100); - } - if (!rogue.trueColorMode || !needDistinctness) { - applyColorMultiplier(&cellForeColor, &lightMultiplierColor); - applyColorMultiplier(&cellForeColor, &clairvoyanceColor); - } - applyColorMultiplier(&cellBackColor, &lightMultiplierColor); - applyColorMultiplier(&cellBackColor, &clairvoyanceColor); - } else if (!(pmap[x][y].flags & VISIBLE) && (pmap[x][y].flags & TELEPATHIC_VISIBLE)) { - // Can telepathically see it through another creature's eyes. + if (gasAugmentWeight && ((pmap[x][y].flags & DISCOVERED) || rogue.playbackOmniscience)) { + if (!rogue.trueColorMode || !needDistinctness) { + applyColorAverage(&cellForeColor, &gasAugmentColor, gasAugmentWeight); + } + // phantoms create sillhouettes in gas clouds + if ((pmap[x][y].flags & HAS_MONSTER) && monst->status[STATUS_INVISIBLE] && playerCanSeeOrSense(x, y) + && !monsterRevealed(monst) && !monsterHiddenBySubmersion(monst, &player)) { - applyColorAugment(&lightMultiplierColor, &basicLightColor, 100); - - if (!rogue.trueColorMode || !needDistinctness) { - applyColorMultiplier(&cellForeColor, &lightMultiplierColor); - applyColorMultiplier(&cellForeColor, &telepathyMultiplier); - } - applyColorMultiplier(&cellBackColor, &lightMultiplierColor); - applyColorMultiplier(&cellBackColor, &telepathyMultiplier); - } else if (!(pmap[x][y].flags & DISCOVERED) && (pmap[x][y].flags & MAGIC_MAPPED)) { - // magic mapped only - if (!rogue.playbackOmniscience) { - needDistinctness = false; - if (!rogue.trueColorMode || !needDistinctness) { - applyColorMultiplier(&cellForeColor, &magicMapColor); - } - applyColorMultiplier(&cellBackColor, &magicMapColor); - } - } else if (!(pmap[x][y].flags & VISIBLE) && !rogue.playbackOmniscience) { - // if it's not visible - - needDistinctness = false; - if (rogue.inWater) { - applyColorAverage(&cellForeColor, &black, 80); - applyColorAverage(&cellBackColor, &black, 80); - } else { - if (!cellHasTMFlag(x, y, TM_BRIGHT_MEMORY) - && (!rogue.trueColorMode || !needDistinctness)) { - - applyColorMultiplier(&cellForeColor, &memoryColor); - applyColorAverage(&cellForeColor, &memoryOverlay, 25); - } - applyColorMultiplier(&cellBackColor, &memoryColor); - applyColorAverage(&cellBackColor, &memoryOverlay, 25); - } - } else if (playerCanSeeOrSense(x, y) && rogue.playbackOmniscience && !(pmap[x][y].flags & ANY_KIND_OF_VISIBLE)) { - // omniscience - applyColorAugment(&lightMultiplierColor, &basicLightColor, 100); - if (!rogue.trueColorMode || !needDistinctness) { - applyColorMultiplier(&cellForeColor, &lightMultiplierColor); - applyColorMultiplier(&cellForeColor, &omniscienceColor); - } - applyColorMultiplier(&cellBackColor, &lightMultiplierColor); - applyColorMultiplier(&cellBackColor, &omniscienceColor); - } else { - if (!rogue.trueColorMode || !needDistinctness) { - applyColorMultiplier(&cellForeColor, &lightMultiplierColor); - } - applyColorMultiplier(&cellBackColor, &lightMultiplierColor); - - if (player.status[STATUS_HALLUCINATING] && !rogue.trueColorMode) { - randomizeColor(&cellForeColor, 40 * player.status[STATUS_HALLUCINATING] / 300 + 20); - randomizeColor(&cellBackColor, 40 * player.status[STATUS_HALLUCINATING] / 300 + 20); - } - if (rogue.inWater) { - applyColorMultiplier(&cellForeColor, &deepWaterLightColor); - applyColorMultiplier(&cellBackColor, &deepWaterLightColor); - } - } -// DEBUG cellBackColor.red = max(0,((scentMap[x][y] - rogue.scentTurnNumber) * 2) + 100); -// DEBUG if (pmap[x][y].flags & KNOWN_TO_BE_TRAP_FREE) cellBackColor.red += 20; -// DEBUG if (cellHasTerrainFlag(x, y, T_IS_FLAMMABLE)) cellBackColor.red += 50; - - if (pmap[x][y].flags & IS_IN_PATH) { - if (cellHasTMFlag(x, y, TM_INVERT_WHEN_HIGHLIGHTED)) { - swapColors(&cellForeColor, &cellBackColor); + if (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience && !player.status[STATUS_TELEPATHIC]) { + cellChar = monsterCatalog[randomAnimateMonster()].displayChar; } else { - if (!rogue.trueColorMode || !needDistinctness) { - applyColorAverage(&cellForeColor, &yellow, rogue.cursorPathIntensity); - } - applyColorAverage(&cellBackColor, &yellow, rogue.cursorPathIntensity); + cellChar = monst->info.displayChar; } - needDistinctness = true; + cellForeColor = cellBackColor; + } + applyColorAverage(&cellBackColor, &gasAugmentColor, gasAugmentWeight); } - bakeTerrainColors(&cellForeColor, &cellBackColor, x, y); + if (!(pmap[x][y].flags & (ANY_KIND_OF_VISIBLE | ITEM_DETECTED | HAS_PLAYER)) && !playerCanSeeOrSense(x, y) + && (!monst || !monsterRevealed(monst)) && !monsterWithDetectedItem) { - if (rogue.displayStealthRangeMode && (pmap[x][y].flags & IN_FIELD_OF_VIEW)) { - distance = min(rogue.scentTurnNumber - scentMap[x][y], scentDistance(x, y, player.loc.x, player.loc.y)); - if (distance > rogue.stealthRange * 2) { - applyColorAverage(&cellForeColor, &orange, 12); - applyColorAverage(&cellBackColor, &orange, 12); - applyColorAugment(&cellForeColor, &orange, 12); - applyColorAugment(&cellBackColor, &orange, 12); - } - } + pmap[x][y].flags |= STABLE_MEMORY; + pmap[x][y].rememberedAppearance.character = cellChar; - if ((rogue.trueColorMode || rogue.displayStealthRangeMode) - && playerCanSeeOrSense(x, y)) { + if (rogue.trueColorMode) { + bakeTerrainColors(&cellForeColor, &cellBackColor, x, y); + } - if (displayDetail[x][y] == DV_DARK) { - applyColorMultiplier(&cellForeColor, &inDarknessMultiplierColor); - applyColorMultiplier(&cellBackColor, &inDarknessMultiplierColor); + // store memory + storeColorComponents(pmap[x][y].rememberedAppearance.foreColorComponents, &cellForeColor); + storeColorComponents(pmap[x][y].rememberedAppearance.backColorComponents, &cellBackColor); - applyColorAugment(&cellForeColor, &purple, 10); - applyColorAugment(&cellBackColor, &white, -10); - applyColorAverage(&cellBackColor, &purple, 20); - } else if (displayDetail[x][y] == DV_LIT) { + applyColorAugment(&lightMultiplierColor, &basicLightColor, 100); + if (!rogue.trueColorMode || !needDistinctness) { + applyColorMultiplier(&cellForeColor, &lightMultiplierColor); + } + applyColorMultiplier(&cellBackColor, &lightMultiplierColor); + bakeTerrainColors(&cellForeColor, &cellBackColor, x, y); - colorMultiplierFromDungeonLight(x, y, &lightMultiplierColor); - normColor(&lightMultiplierColor, 175, 50); - //applyColorMultiplier(&cellForeColor, &lightMultiplierColor); - //applyColorMultiplier(&cellBackColor, &lightMultiplierColor); - applyColorAugment(&cellForeColor, &lightMultiplierColor, 5); - applyColorAugment(&cellBackColor, &lightMultiplierColor, 5); - } + // Then restore, so that it looks the same on this pass as it will when later refreshed. + cellForeColor = colorFromComponents(pmap[x][y].rememberedAppearance.foreColorComponents); + cellBackColor = colorFromComponents(pmap[x][y].rememberedAppearance.backColorComponents); } + } - if (needDistinctness) { - separateColors(&cellForeColor, &cellBackColor); - } + // Smooth out walls: if there's a "wall-ish" tile drawn below us, just draw the wall top + if ((cellChar == G_WALL || cellChar == G_GRANITE) && coordinatesAreInMap(x, y + 1) + && glyphIsWallish(displayBuffer[mapToWindowX(x)][mapToWindowY(y + 1)].character)) { + cellChar = G_WALL_TOP; + } - if (D_SCENT_VISION) { - if (rogue.scentTurnNumber > (unsigned short) scentMap[x][y]) { - cellBackColor.red = rogue.scentTurnNumber - (unsigned short) scentMap[x][y]; - cellBackColor.red = clamp(cellBackColor.red, 0, 100); - } else { - cellBackColor.green = abs(rogue.scentTurnNumber - (unsigned short) scentMap[x][y]); - cellBackColor.green = clamp(cellBackColor.green, 0, 100); - } + if (((pmap[x][y].flags & ITEM_DETECTED) || monsterWithDetectedItem || (monst && monsterRevealed(monst))) + && !playerCanSeeOrSense(x, y)) { + // do nothing + } else if (!(pmap[x][y].flags & VISIBLE) && (pmap[x][y].flags & CLAIRVOYANT_VISIBLE)) { + // can clairvoyantly see it + if (rogue.trueColorMode) { + lightMultiplierColor = basicLightColor; + } else { + applyColorAugment(&lightMultiplierColor, &basicLightColor, 100); + } + if (!rogue.trueColorMode || !needDistinctness) { + applyColorMultiplier(&cellForeColor, &lightMultiplierColor); + applyColorMultiplier(&cellForeColor, &clairvoyanceColor); + } + applyColorMultiplier(&cellBackColor, &lightMultiplierColor); + applyColorMultiplier(&cellBackColor, &clairvoyanceColor); + } else if (!(pmap[x][y].flags & VISIBLE) && (pmap[x][y].flags & TELEPATHIC_VISIBLE)) { + // Can telepathically see it through another creature's eyes. + + applyColorAugment(&lightMultiplierColor, &basicLightColor, 100); + + if (!rogue.trueColorMode || !needDistinctness) { + applyColorMultiplier(&cellForeColor, &lightMultiplierColor); + applyColorMultiplier(&cellForeColor, &telepathyMultiplier); + } + applyColorMultiplier(&cellBackColor, &lightMultiplierColor); + applyColorMultiplier(&cellBackColor, &telepathyMultiplier); + } else if (!(pmap[x][y].flags & DISCOVERED) && (pmap[x][y].flags & MAGIC_MAPPED)) { + // magic mapped only + if (!rogue.playbackOmniscience) { + needDistinctness = false; + if (!rogue.trueColorMode || !needDistinctness) { + applyColorMultiplier(&cellForeColor, &magicMapColor); + } + applyColorMultiplier(&cellBackColor, &magicMapColor); + } + } else if (!(pmap[x][y].flags & VISIBLE) && !rogue.playbackOmniscience) { + // if it's not visible + + needDistinctness = false; + if (rogue.inWater) { + applyColorAverage(&cellForeColor, &black, 80); + applyColorAverage(&cellBackColor, &black, 80); + } else { + if (!cellHasTMFlag(x, y, TM_BRIGHT_MEMORY) && (!rogue.trueColorMode || !needDistinctness)) { + + applyColorMultiplier(&cellForeColor, &memoryColor); + applyColorAverage(&cellForeColor, &memoryOverlay, 25); + } + applyColorMultiplier(&cellBackColor, &memoryColor); + applyColorAverage(&cellBackColor, &memoryOverlay, 25); + } + } else if (playerCanSeeOrSense(x, y) && rogue.playbackOmniscience && !(pmap[x][y].flags & ANY_KIND_OF_VISIBLE)) { + // omniscience + applyColorAugment(&lightMultiplierColor, &basicLightColor, 100); + if (!rogue.trueColorMode || !needDistinctness) { + applyColorMultiplier(&cellForeColor, &lightMultiplierColor); + applyColorMultiplier(&cellForeColor, &omniscienceColor); + } + applyColorMultiplier(&cellBackColor, &lightMultiplierColor); + applyColorMultiplier(&cellBackColor, &omniscienceColor); + } else { + if (!rogue.trueColorMode || !needDistinctness) { + applyColorMultiplier(&cellForeColor, &lightMultiplierColor); + } + applyColorMultiplier(&cellBackColor, &lightMultiplierColor); + + if (player.status[STATUS_HALLUCINATING] && !rogue.trueColorMode) { + randomizeColor(&cellForeColor, 40 * player.status[STATUS_HALLUCINATING] / 300 + 20); + randomizeColor(&cellBackColor, 40 * player.status[STATUS_HALLUCINATING] / 300 + 20); + } + if (rogue.inWater) { + applyColorMultiplier(&cellForeColor, &deepWaterLightColor); + applyColorMultiplier(&cellBackColor, &deepWaterLightColor); + } + } + // DEBUG cellBackColor.red = max(0,((scentMap[x][y] - rogue.scentTurnNumber) * 2) + 100); + // DEBUG if (pmap[x][y].flags & KNOWN_TO_BE_TRAP_FREE) cellBackColor.red += 20; + // DEBUG if (cellHasTerrainFlag(x, y, T_IS_FLAMMABLE)) cellBackColor.red += 50; + + if (pmap[x][y].flags & IS_IN_PATH) { + if (cellHasTMFlag(x, y, TM_INVERT_WHEN_HIGHLIGHTED)) { + swapColors(&cellForeColor, &cellBackColor); + } else { + if (!rogue.trueColorMode || !needDistinctness) { + applyColorAverage(&cellForeColor, &yellow, rogue.cursorPathIntensity); + } + applyColorAverage(&cellBackColor, &yellow, rogue.cursorPathIntensity); + } + needDistinctness = true; + } + + bakeTerrainColors(&cellForeColor, &cellBackColor, x, y); + + if (rogue.displayStealthRangeMode && (pmap[x][y].flags & IN_FIELD_OF_VIEW)) { + distance = min(rogue.scentTurnNumber - scentMap[x][y], scentDistance(x, y, player.loc.x, player.loc.y)); + if (distance > rogue.stealthRange * 2) { + applyColorAverage(&cellForeColor, &orange, 12); + applyColorAverage(&cellBackColor, &orange, 12); + applyColorAugment(&cellForeColor, &orange, 12); + applyColorAugment(&cellBackColor, &orange, 12); + } + } + + if ((rogue.trueColorMode || rogue.displayStealthRangeMode) && playerCanSeeOrSense(x, y)) { + + if (displayDetail[x][y] == DV_DARK) { + applyColorMultiplier(&cellForeColor, &inDarknessMultiplierColor); + applyColorMultiplier(&cellBackColor, &inDarknessMultiplierColor); + + applyColorAugment(&cellForeColor, &purple, 10); + applyColorAugment(&cellBackColor, &white, -10); + applyColorAverage(&cellBackColor, &purple, 20); + } else if (displayDetail[x][y] == DV_LIT) { + + colorMultiplierFromDungeonLight(x, y, &lightMultiplierColor); + normColor(&lightMultiplierColor, 175, 50); + // applyColorMultiplier(&cellForeColor, &lightMultiplierColor); + // applyColorMultiplier(&cellBackColor, &lightMultiplierColor); + applyColorAugment(&cellForeColor, &lightMultiplierColor, 5); + applyColorAugment(&cellBackColor, &lightMultiplierColor, 5); + } + } + + if (needDistinctness) { + separateColors(&cellForeColor, &cellBackColor); + } + + if (D_SCENT_VISION) { + if (rogue.scentTurnNumber > (unsigned short)scentMap[x][y]) { + cellBackColor.red = rogue.scentTurnNumber - (unsigned short)scentMap[x][y]; + cellBackColor.red = clamp(cellBackColor.red, 0, 100); + } else { + cellBackColor.green = abs(rogue.scentTurnNumber - (unsigned short)scentMap[x][y]); + cellBackColor.green = clamp(cellBackColor.green, 0, 100); } + } - *returnChar = cellChar; - *returnForeColor = cellForeColor; - *returnBackColor = cellBackColor; + *returnChar = cellChar; + *returnForeColor = cellForeColor; + *returnBackColor = cellBackColor; - if (D_DISABLE_BACKGROUND_COLORS) *returnBackColor = black; - restoreRNG; + if (D_DISABLE_BACKGROUND_COLORS) + *returnBackColor = black; + restoreRNG; } void refreshDungeonCell(short x, short y) { - enum displayGlyph cellChar; - color foreColor, backColor; - brogueAssert(coordinatesAreInMap(x, y)); + enum displayGlyph cellChar; + color foreColor, backColor; + brogueAssert(coordinatesAreInMap(x, y)); - getCellAppearance(x, y, &cellChar, &foreColor, &backColor); - plotCharWithColor(cellChar, mapToWindow((pos){ x, y }), &foreColor, &backColor); + getCellAppearance(x, y, &cellChar, &foreColor, &backColor); + plotCharWithColor(cellChar, mapToWindow((pos){x, y}), &foreColor, &backColor); } void applyColorMultiplier(color *baseColor, const color *multiplierColor) { - baseColor->red = baseColor->red * multiplierColor->red / 100; - baseColor->redRand = baseColor->redRand * multiplierColor->redRand / 100; - baseColor->green = baseColor->green * multiplierColor->green / 100; - baseColor->greenRand = baseColor->greenRand * multiplierColor->greenRand / 100; - baseColor->blue = baseColor->blue * multiplierColor->blue / 100; - baseColor->blueRand = baseColor->blueRand * multiplierColor->blueRand / 100; - baseColor->rand = baseColor->rand * multiplierColor->rand / 100; - //baseColor->colorDances *= multiplierColor->colorDances; - return; + baseColor->red = baseColor->red * multiplierColor->red / 100; + baseColor->redRand = baseColor->redRand * multiplierColor->redRand / 100; + baseColor->green = baseColor->green * multiplierColor->green / 100; + baseColor->greenRand = baseColor->greenRand * multiplierColor->greenRand / 100; + baseColor->blue = baseColor->blue * multiplierColor->blue / 100; + baseColor->blueRand = baseColor->blueRand * multiplierColor->blueRand / 100; + baseColor->rand = baseColor->rand * multiplierColor->rand / 100; + // baseColor->colorDances *= multiplierColor->colorDances; + return; } void applyColorAverage(color *baseColor, const color *newColor, short averageWeight) { - short weightComplement = 100 - averageWeight; - baseColor->red = (baseColor->red * weightComplement + newColor->red * averageWeight) / 100; - baseColor->redRand = (baseColor->redRand * weightComplement + newColor->redRand * averageWeight) / 100; - baseColor->green = (baseColor->green * weightComplement + newColor->green * averageWeight) / 100; - baseColor->greenRand = (baseColor->greenRand * weightComplement + newColor->greenRand * averageWeight) / 100; - baseColor->blue = (baseColor->blue * weightComplement + newColor->blue * averageWeight) / 100; - baseColor->blueRand = (baseColor->blueRand * weightComplement + newColor->blueRand * averageWeight) / 100; - baseColor->rand = (baseColor->rand * weightComplement + newColor->rand * averageWeight) / 100; - baseColor->colorDances = (baseColor->colorDances || newColor->colorDances); - return; + short weightComplement = 100 - averageWeight; + baseColor->red = (baseColor->red * weightComplement + newColor->red * averageWeight) / 100; + baseColor->redRand = (baseColor->redRand * weightComplement + newColor->redRand * averageWeight) / 100; + baseColor->green = (baseColor->green * weightComplement + newColor->green * averageWeight) / 100; + baseColor->greenRand = (baseColor->greenRand * weightComplement + newColor->greenRand * averageWeight) / 100; + baseColor->blue = (baseColor->blue * weightComplement + newColor->blue * averageWeight) / 100; + baseColor->blueRand = (baseColor->blueRand * weightComplement + newColor->blueRand * averageWeight) / 100; + baseColor->rand = (baseColor->rand * weightComplement + newColor->rand * averageWeight) / 100; + baseColor->colorDances = (baseColor->colorDances || newColor->colorDances); + return; } void applyColorAugment(color *baseColor, const color *augmentingColor, short augmentWeight) { - baseColor->red += (augmentingColor->red * augmentWeight) / 100; - baseColor->redRand += (augmentingColor->redRand * augmentWeight) / 100; - baseColor->green += (augmentingColor->green * augmentWeight) / 100; - baseColor->greenRand += (augmentingColor->greenRand * augmentWeight) / 100; - baseColor->blue += (augmentingColor->blue * augmentWeight) / 100; - baseColor->blueRand += (augmentingColor->blueRand * augmentWeight) / 100; - baseColor->rand += (augmentingColor->rand * augmentWeight) / 100; - return; + baseColor->red += (augmentingColor->red * augmentWeight) / 100; + baseColor->redRand += (augmentingColor->redRand * augmentWeight) / 100; + baseColor->green += (augmentingColor->green * augmentWeight) / 100; + baseColor->greenRand += (augmentingColor->greenRand * augmentWeight) / 100; + baseColor->blue += (augmentingColor->blue * augmentWeight) / 100; + baseColor->blueRand += (augmentingColor->blueRand * augmentWeight) / 100; + baseColor->rand += (augmentingColor->rand * augmentWeight) / 100; + return; } void applyColorScalar(color *baseColor, short scalar) { - baseColor->red = baseColor->red * scalar / 100; - baseColor->redRand = baseColor->redRand * scalar / 100; - baseColor->green = baseColor->green * scalar / 100; - baseColor->greenRand = baseColor->greenRand * scalar / 100; - baseColor->blue = baseColor->blue * scalar / 100; - baseColor->blueRand = baseColor->blueRand * scalar / 100; - baseColor->rand = baseColor->rand * scalar / 100; + baseColor->red = baseColor->red * scalar / 100; + baseColor->redRand = baseColor->redRand * scalar / 100; + baseColor->green = baseColor->green * scalar / 100; + baseColor->greenRand = baseColor->greenRand * scalar / 100; + baseColor->blue = baseColor->blue * scalar / 100; + baseColor->blueRand = baseColor->blueRand * scalar / 100; + baseColor->rand = baseColor->rand * scalar / 100; } void applyColorBounds(color *baseColor, short lowerBound, short upperBound) { - baseColor->red = clamp(baseColor->red, lowerBound, upperBound); - baseColor->redRand = clamp(baseColor->redRand, lowerBound, upperBound); - baseColor->green = clamp(baseColor->green, lowerBound, upperBound); - baseColor->greenRand = clamp(baseColor->greenRand, lowerBound, upperBound); - baseColor->blue = clamp(baseColor->blue, lowerBound, upperBound); - baseColor->blueRand = clamp(baseColor->blueRand, lowerBound, upperBound); - baseColor->rand = clamp(baseColor->rand, lowerBound, upperBound); + baseColor->red = clamp(baseColor->red, lowerBound, upperBound); + baseColor->redRand = clamp(baseColor->redRand, lowerBound, upperBound); + baseColor->green = clamp(baseColor->green, lowerBound, upperBound); + baseColor->greenRand = clamp(baseColor->greenRand, lowerBound, upperBound); + baseColor->blue = clamp(baseColor->blue, lowerBound, upperBound); + baseColor->blueRand = clamp(baseColor->blueRand, lowerBound, upperBound); + baseColor->rand = clamp(baseColor->rand, lowerBound, upperBound); } void desaturate(color *baseColor, short weight) { - short avg; - avg = (baseColor->red + baseColor->green + baseColor->blue) / 3 + 1; - baseColor->red = baseColor->red * (100 - weight) / 100 + (avg * weight / 100); - baseColor->green = baseColor->green * (100 - weight) / 100 + (avg * weight / 100); - baseColor->blue = baseColor->blue * (100 - weight) / 100 + (avg * weight / 100); - - avg = (baseColor->redRand + baseColor->greenRand + baseColor->blueRand); - baseColor->redRand = baseColor->redRand * (100 - weight) / 100; - baseColor->greenRand = baseColor->greenRand * (100 - weight) / 100; - baseColor->blueRand = baseColor->blueRand * (100 - weight) / 100; - - baseColor->rand += avg * weight / 3 / 100; + short avg; + avg = (baseColor->red + baseColor->green + baseColor->blue) / 3 + 1; + baseColor->red = baseColor->red * (100 - weight) / 100 + (avg * weight / 100); + baseColor->green = baseColor->green * (100 - weight) / 100 + (avg * weight / 100); + baseColor->blue = baseColor->blue * (100 - weight) / 100 + (avg * weight / 100); + + avg = (baseColor->redRand + baseColor->greenRand + baseColor->blueRand); + baseColor->redRand = baseColor->redRand * (100 - weight) / 100; + baseColor->greenRand = baseColor->greenRand * (100 - weight) / 100; + baseColor->blueRand = baseColor->blueRand * (100 - weight) / 100; + + baseColor->rand += avg * weight / 3 / 100; } short randomizeByPercent(short input, short percent) { - return (rand_range(input * (100 - percent) / 100, input * (100 + percent) / 100)); + return (rand_range(input * (100 - percent) / 100, input * (100 + percent) / 100)); } void randomizeColor(color *baseColor, short randomizePercent) { - baseColor->red = randomizeByPercent(baseColor->red, randomizePercent); - baseColor->green = randomizeByPercent(baseColor->green, randomizePercent); - baseColor->blue = randomizeByPercent(baseColor->blue, randomizePercent); + baseColor->red = randomizeByPercent(baseColor->red, randomizePercent); + baseColor->green = randomizeByPercent(baseColor->green, randomizePercent); + baseColor->blue = randomizeByPercent(baseColor->blue, randomizePercent); } void swapColors(color *color1, color *color2) { - color tempColor = *color1; - *color1 = *color2; - *color2 = tempColor; + color tempColor = *color1; + *color1 = *color2; + *color2 = tempColor; } // Assumes colors are pre-baked. void blendAppearances(const color *fromForeColor, const color *fromBackColor, const enum displayGlyph fromChar, const color *toForeColor, const color *toBackColor, const enum displayGlyph toChar, - color *retForeColor, color *retBackColor, enum displayGlyph *retChar, - const short percent) { - // Straight average of the back color: - *retBackColor = *fromBackColor; - applyColorAverage(retBackColor, toBackColor, percent); - - // Pick the character: + color *retForeColor, color *retBackColor, enum displayGlyph *retChar, const short percent) { + // Straight average of the back color: + *retBackColor = *fromBackColor; + applyColorAverage(retBackColor, toBackColor, percent); + + // Pick the character: + if (percent >= 50) { + *retChar = toChar; + } else { + *retChar = fromChar; + } + + // Pick the method for blending the fore color. + if (fromChar == toChar) { + // If the character isn't changing, do a straight average. + *retForeColor = *fromForeColor; + applyColorAverage(retForeColor, toForeColor, percent); + } else { + // If it is changing, the first half blends to the current back color, and the second half blends to the final back + // color. if (percent >= 50) { - *retChar = toChar; - } else { - *retChar = fromChar; - } - - // Pick the method for blending the fore color. - if (fromChar == toChar) { - // If the character isn't changing, do a straight average. - *retForeColor = *fromForeColor; - applyColorAverage(retForeColor, toForeColor, percent); + *retForeColor = *retBackColor; + applyColorAverage(retForeColor, toForeColor, (percent - 50) * 2); } else { - // If it is changing, the first half blends to the current back color, and the second half blends to the final back color. - if (percent >= 50) { - *retForeColor = *retBackColor; - applyColorAverage(retForeColor, toForeColor, (percent - 50) * 2); - } else { - *retForeColor = *fromForeColor; - applyColorAverage(retForeColor, retBackColor, percent * 2); - } + *retForeColor = *fromForeColor; + applyColorAverage(retForeColor, retBackColor, percent * 2); } + } } -void irisFadeBetweenBuffers(cellDisplayBuffer fromBuf[COLS][ROWS], - cellDisplayBuffer toBuf[COLS][ROWS], - short x, short y, - short frameCount, - boolean outsideIn) { - short i, j, frame, percentBasis, thisCellPercent; - boolean fastForward; - color fromBackColor, toBackColor, fromForeColor, toForeColor, currentForeColor, currentBackColor; - enum displayGlyph fromChar, toChar, currentChar; - short completionMap[COLS][ROWS], maxDistance; - - fastForward = false; - frame = 1; - - // Calculate the square of the maximum distance from (x, y) that the iris will have to spread. - if (x < COLS / 2) { - i = COLS - x; - } else { - i = x; - } - if (y < ROWS / 2) { - j = ROWS - y; - } else { - j = y; - } - maxDistance = i*i + j*j; - - // Generate the initial completion map as a percent of maximum distance. - for (i=0; ired = editColor->redRand = adjustedLightValue(max(0, tmap[x][y].light[0])); - editColor->green = editColor->greenRand = adjustedLightValue(max(0, tmap[x][y].light[1])); - editColor->blue = editColor->blueRand = adjustedLightValue(max(0, tmap[x][y].light[2])); + editColor->red = editColor->redRand = adjustedLightValue(max(0, tmap[x][y].light[0])); + editColor->green = editColor->greenRand = adjustedLightValue(max(0, tmap[x][y].light[1])); + editColor->blue = editColor->blueRand = adjustedLightValue(max(0, tmap[x][y].light[2])); - editColor->rand = adjustedLightValue(max(0, tmap[x][y].light[0] + tmap[x][y].light[1] + tmap[x][y].light[2]) / 3); - editColor->colorDances = false; + editColor->rand = adjustedLightValue(max(0, tmap[x][y].light[0] + tmap[x][y].light[1] + tmap[x][y].light[2]) / 3); + editColor->colorDances = false; } -void plotCharWithColor(enum displayGlyph inputChar, windowpos loc, const color *cellForeColor, const color *cellBackColor) { - short oldRNG; +void plotCharWithColor(enum displayGlyph inputChar, windowpos loc, const color *cellForeColor, + const color *cellBackColor) { + short oldRNG; - short foreRed = cellForeColor->red, - foreGreen = cellForeColor->green, - foreBlue = cellForeColor->blue, + short foreRed = cellForeColor->red, foreGreen = cellForeColor->green, foreBlue = cellForeColor->blue, - backRed = cellBackColor->red, - backGreen = cellBackColor->green, - backBlue = cellBackColor->blue, + backRed = cellBackColor->red, backGreen = cellBackColor->green, backBlue = cellBackColor->blue, - foreRand, backRand; + foreRand, backRand; - brogueAssert(locIsInWindow(loc)); + brogueAssert(locIsInWindow(loc)); - if (rogue.gameHasEnded || rogue.playbackFastForward) { - return; - } - - //assureCosmeticRNG; - oldRNG = rogue.RNG; - rogue.RNG = RNG_COSMETIC; - - foreRand = rand_range(0, cellForeColor->rand); - backRand = rand_range(0, cellBackColor->rand); - foreRed += rand_range(0, cellForeColor->redRand) + foreRand; - foreGreen += rand_range(0, cellForeColor->greenRand) + foreRand; - foreBlue += rand_range(0, cellForeColor->blueRand) + foreRand; - backRed += rand_range(0, cellBackColor->redRand) + backRand; - backGreen += rand_range(0, cellBackColor->greenRand) + backRand; - backBlue += rand_range(0, cellBackColor->blueRand) + backRand; - - foreRed = min(100, max(0, foreRed)); - foreGreen = min(100, max(0, foreGreen)); - foreBlue = min(100, max(0, foreBlue)); - backRed = min(100, max(0, backRed)); - backGreen = min(100, max(0, backGreen)); - backBlue = min(100, max(0, backBlue)); - - if (inputChar != ' ' - && foreRed == backRed - && foreGreen == backGreen - && foreBlue == backBlue) { - - inputChar = ' '; - } - - cellDisplayBuffer *target = &displayBuffer[loc.window_x][loc.window_y]; - target->character = inputChar; - target->foreColorComponents[0] = foreRed; - target->foreColorComponents[1] = foreGreen; - target->foreColorComponents[2] = foreBlue; - target->backColorComponents[0] = backRed; - target->backColorComponents[1] = backGreen; - target->backColorComponents[2] = backBlue; - - restoreRNG; + if (rogue.gameHasEnded || rogue.playbackFastForward) { + return; + } + + // assureCosmeticRNG; + oldRNG = rogue.RNG; + rogue.RNG = RNG_COSMETIC; + + foreRand = rand_range(0, cellForeColor->rand); + backRand = rand_range(0, cellBackColor->rand); + foreRed += rand_range(0, cellForeColor->redRand) + foreRand; + foreGreen += rand_range(0, cellForeColor->greenRand) + foreRand; + foreBlue += rand_range(0, cellForeColor->blueRand) + foreRand; + backRed += rand_range(0, cellBackColor->redRand) + backRand; + backGreen += rand_range(0, cellBackColor->greenRand) + backRand; + backBlue += rand_range(0, cellBackColor->blueRand) + backRand; + + foreRed = min(100, max(0, foreRed)); + foreGreen = min(100, max(0, foreGreen)); + foreBlue = min(100, max(0, foreBlue)); + backRed = min(100, max(0, backRed)); + backGreen = min(100, max(0, backGreen)); + backBlue = min(100, max(0, backBlue)); + + if (inputChar != ' ' && foreRed == backRed && foreGreen == backGreen && foreBlue == backBlue) { + + inputChar = ' '; + } + + cellDisplayBuffer *target = &displayBuffer[loc.window_x][loc.window_y]; + target->character = inputChar; + target->foreColorComponents[0] = foreRed; + target->foreColorComponents[1] = foreGreen; + target->foreColorComponents[2] = foreBlue; + target->backColorComponents[0] = backRed; + target->backColorComponents[1] = backGreen; + target->backColorComponents[2] = backBlue; + + restoreRNG; } -void plotCharToBuffer(enum displayGlyph inputChar, windowpos loc, const color *foreColor, const color *backColor, cellDisplayBuffer dbuf[COLS][ROWS]) { - short oldRNG; - - if (!dbuf) { - plotCharWithColor(inputChar, loc, foreColor, backColor); - return; - } - - brogueAssert(locIsInWindow(loc)); +void plotCharToBuffer(enum displayGlyph inputChar, windowpos loc, const color *foreColor, const color *backColor, + cellDisplayBuffer dbuf[COLS][ROWS]) { + short oldRNG; - oldRNG = rogue.RNG; - rogue.RNG = RNG_COSMETIC; - //assureCosmeticRNG; - - cellDisplayBuffer* cell = &dbuf[loc.window_x][loc.window_y]; - cell->foreColorComponents[0] = foreColor->red + rand_range(0, foreColor->redRand) + rand_range(0, foreColor->rand); - cell->foreColorComponents[1] = foreColor->green + rand_range(0, foreColor->greenRand) + rand_range(0, foreColor->rand); - cell->foreColorComponents[2] = foreColor->blue + rand_range(0, foreColor->blueRand) + rand_range(0, foreColor->rand); - cell->backColorComponents[0] = backColor->red + rand_range(0, backColor->redRand) + rand_range(0, backColor->rand); - cell->backColorComponents[1] = backColor->green + rand_range(0, backColor->greenRand) + rand_range(0, backColor->rand); - cell->backColorComponents[2] = backColor->blue + rand_range(0, backColor->blueRand) + rand_range(0, backColor->rand); - cell->character = inputChar; - restoreRNG; + if (!dbuf) { + plotCharWithColor(inputChar, loc, foreColor, backColor); + return; + } + + brogueAssert(locIsInWindow(loc)); + + oldRNG = rogue.RNG; + rogue.RNG = RNG_COSMETIC; + // assureCosmeticRNG; + + cellDisplayBuffer *cell = &dbuf[loc.window_x][loc.window_y]; + cell->foreColorComponents[0] = foreColor->red + rand_range(0, foreColor->redRand) + rand_range(0, foreColor->rand); + cell->foreColorComponents[1] + = foreColor->green + rand_range(0, foreColor->greenRand) + rand_range(0, foreColor->rand); + cell->foreColorComponents[2] = foreColor->blue + rand_range(0, foreColor->blueRand) + rand_range(0, foreColor->rand); + cell->backColorComponents[0] = backColor->red + rand_range(0, backColor->redRand) + rand_range(0, backColor->rand); + cell->backColorComponents[1] + = backColor->green + rand_range(0, backColor->greenRand) + rand_range(0, backColor->rand); + cell->backColorComponents[2] = backColor->blue + rand_range(0, backColor->blueRand) + rand_range(0, backColor->rand); + cell->character = inputChar; + restoreRNG; } -void plotForegroundChar(enum displayGlyph inputChar, short x, short y, const color *foreColor, boolean affectedByLighting) { - color multColor, myColor, backColor, ignoredColor; - enum displayGlyph ignoredChar; - - myColor = *foreColor; - getCellAppearance(x, y, &ignoredChar, &ignoredColor, &backColor); - if (affectedByLighting) { - colorMultiplierFromDungeonLight(x, y, &multColor); - applyColorMultiplier(&myColor, &multColor); - } - plotCharWithColor(inputChar, mapToWindow((pos){ x, y }), &myColor, &backColor); +void plotForegroundChar(enum displayGlyph inputChar, short x, short y, const color *foreColor, + boolean affectedByLighting) { + color multColor, myColor, backColor, ignoredColor; + enum displayGlyph ignoredChar; + + myColor = *foreColor; + getCellAppearance(x, y, &ignoredChar, &ignoredColor, &backColor); + if (affectedByLighting) { + colorMultiplierFromDungeonLight(x, y, &multColor); + applyColorMultiplier(&myColor, &multColor); + } + plotCharWithColor(inputChar, mapToWindow((pos){x, y}), &myColor, &backColor); } // Debug feature: display the level to the screen without regard to lighting, field of view, etc. void dumpLevelToScreen() { - short i, j; - pcell backup; - - assureCosmeticRNG; - for (i=0; i= 0; j--) { - for (i=0; i= 0; j--) { + for (i = 0; i < count; i++) { + percent = flashStrength[i] * j / frames; + newColor = fColor[i]; + applyColorAverage(&newColor, flashColor[i], percent); + plotCharWithColor(displayChar[i], mapToWindow((pos){x[i], y[i]}), &newColor, &(bColor[i])); + } + if (j) { + if (pauseAnimation(16)) { + j = 1; + } } + } - free(displayChar); - free(fColor); - free(bColor); + free(displayChar); + free(fColor); + free(bColor); - restoreRNG; + restoreRNG; } void flashCell(const color *theColor, short frames, short x, short y) { - short i; - boolean interrupted = false; + short i; + boolean interrupted = false; - for (i=0; i= 0 : n <= stepCount); n += (invert ? -1 : 1)) { - for (i=0; i= 0 : n <= stepCount); n += (invert ? -1 : 1)) { + for (i = 0; i < COLS; i++) { + for (j = 0; j < ROWS; j++) { - percentComplete = (double) (n) * 100 / stepCount; + percentComplete = (double)(n)*100 / stepCount; - colorMid = *colorStart; - if (colorEnd) { - applyColorAverage(&colorMid, colorEnd, n * 100 / stepCount); - } + colorMid = *colorStart; + if (colorEnd) { + applyColorAverage(&colorMid, colorEnd, n * 100 / stepCount); + } - // the fade color floods the reachable dungeon tiles faster - if (!invert && coordinatesAreInMap(windowToMapX(i), windowToMapY(j)) - && distanceMap[windowToMapX(i)][windowToMapY(j)] >= 0 && distanceMap[windowToMapX(i)][windowToMapY(j)] < 30000) { - percentComplete *= 1.0 + (100.0 - min(100, distanceMap[windowToMapX(i)][windowToMapY(j)])) / 100.; - } + // the fade color floods the reachable dungeon tiles faster + if (!invert && coordinatesAreInMap(windowToMapX(i), windowToMapY(j)) + && distanceMap[windowToMapX(i)][windowToMapY(j)] >= 0 + && distanceMap[windowToMapX(i)][windowToMapY(j)] < 30000) { + percentComplete *= 1.0 + (100.0 - min(100, distanceMap[windowToMapX(i)][windowToMapY(j)])) / 100.; + } - weight = (short)(percentComplete + weightGrid[i][j][2] * percentComplete * 10); - weight = min(100, weight); - tempColor = black; + weight = (short)(percentComplete + weightGrid[i][j][2] * percentComplete * 10); + weight = min(100, weight); + tempColor = black; - tempColor.red = (short)(percentComplete + weightGrid[i][j][0] * percentComplete * 10) * colorMid.red / 100; - tempColor.red = min(colorMid.red, tempColor.red); + tempColor.red = (short)(percentComplete + weightGrid[i][j][0] * percentComplete * 10) * colorMid.red / 100; + tempColor.red = min(colorMid.red, tempColor.red); - tempColor.green = (short)(percentComplete + weightGrid[i][j][1] * percentComplete * 10) * colorMid.green / 100; - tempColor.green = min(colorMid.green, tempColor.green); + tempColor.green = (short)(percentComplete + weightGrid[i][j][1] * percentComplete * 10) * colorMid.green / 100; + tempColor.green = min(colorMid.green, tempColor.green); - tempColor.blue = (short)(percentComplete + weightGrid[i][j][2] * percentComplete * 10) * colorMid.blue / 100; - tempColor.blue = min(colorMid.blue, tempColor.blue); + tempColor.blue = (short)(percentComplete + weightGrid[i][j][2] * percentComplete * 10) * colorMid.blue / 100; + tempColor.blue = min(colorMid.blue, tempColor.blue); - backColor = black; + backColor = black; - backColor.red = displayBuf[i][j].backColorComponents[0]; - backColor.green = displayBuf[i][j].backColorComponents[1]; - backColor.blue = displayBuf[i][j].backColorComponents[2]; + backColor.red = displayBuf[i][j].backColorComponents[0]; + backColor.green = displayBuf[i][j].backColorComponents[1]; + backColor.blue = displayBuf[i][j].backColorComponents[2]; - foreColor = (invert ? white : black); + foreColor = (invert ? white : black); - if (j == (MESSAGE_LINES - 1) - && i >= mapToWindowX(0) - && i < mapToWindowX(strLenWithoutEscapes(displayedMessage[MESSAGE_LINES - j - 1]))) { - tempChar = displayedMessage[MESSAGE_LINES - j - 1][windowToMapX(i)]; - } else { - tempChar = displayBuf[i][j].character; + if (j == (MESSAGE_LINES - 1) && i >= mapToWindowX(0) + && i < mapToWindowX(strLenWithoutEscapes(displayedMessage[MESSAGE_LINES - j - 1]))) { + tempChar = displayedMessage[MESSAGE_LINES - j - 1][windowToMapX(i)]; + } else { + tempChar = displayBuf[i][j].character; - foreColor.red = displayBuf[i][j].foreColorComponents[0]; - foreColor.green = displayBuf[i][j].foreColorComponents[1]; - foreColor.blue = displayBuf[i][j].foreColorComponents[2]; + foreColor.red = displayBuf[i][j].foreColorComponents[0]; + foreColor.green = displayBuf[i][j].foreColorComponents[1]; + foreColor.blue = displayBuf[i][j].foreColorComponents[2]; - applyColorAverage(&foreColor, &tempColor, weight); - } - applyColorAverage(&backColor, &tempColor, weight); - plotCharWithColor(tempChar, (windowpos){ i, j }, &foreColor, &backColor); - } - } - if (!fastForward && pauseAnimation(16)) { - // drop the event - skipping the transition should only skip the transition - rogueEvent event; - nextKeyOrMouseEvent(&event, false, false); - fastForward = true; - n = (invert ? 1 : stepCount - 2); + applyColorAverage(&foreColor, &tempColor, weight); } + applyColorAverage(&backColor, &tempColor, weight); + plotCharWithColor(tempChar, (windowpos){i, j}, &foreColor, &backColor); + } + } + if (!fastForward && pauseAnimation(16)) { + // drop the event - skipping the transition should only skip the transition + rogueEvent event; + nextKeyOrMouseEvent(&event, false, false); + fastForward = true; + n = (invert ? 1 : stepCount - 2); } + } - freeGrid(distanceMap); + freeGrid(distanceMap); - restoreRNG; + restoreRNG; } void displayWaypoints() { - short i, j, w, lowestDistance; - - for (i=0; i 100) { - if (pauseForMilliseconds(50)) return true; - milliseconds -= 50; - } - return pauseForMilliseconds(milliseconds); + commitDraws(); + if (rogue.playbackMode && rogue.playbackFastForward) { + return true; + } + // For long delays, let's pause in small increments so that we can immediately react to user interruptions. + while (milliseconds > 100) { + if (pauseForMilliseconds(50)) + return true; + milliseconds -= 50; + } + return pauseForMilliseconds(milliseconds); } // Same as pauseBrogue, but during playback the delay scales according to playback speed. boolean pauseAnimation(short milliseconds) { - if (rogue.playbackMode && !rogue.playbackPaused && milliseconds > 0) { - double factor = rogue.playbackDelayPerTurn / (double)DEFAULT_PLAYBACK_DELAY; - if (factor > 1.) factor = sqrt(factor); // so that animations don't slow down too much - milliseconds = max(1, lround(milliseconds * factor)); - } - return pauseBrogue(milliseconds); + if (rogue.playbackMode && !rogue.playbackPaused && milliseconds > 0) { + double factor = rogue.playbackDelayPerTurn / (double)DEFAULT_PLAYBACK_DELAY; + if (factor > 1.) + factor = sqrt(factor); // so that animations don't slow down too much + milliseconds = max(1, lround(milliseconds * factor)); + } + return pauseBrogue(milliseconds); } void nextBrogueEvent(rogueEvent *returnEvent, boolean textInput, boolean colorsDance, boolean realInputEvenInPlayback) { - rogueEvent recordingInput; - boolean repeatAgain, interaction; - short pauseDuration; - - returnEvent->eventType = EVENT_ERROR; - - if (rogue.playbackMode && !realInputEvenInPlayback) { - do { - repeatAgain = false; - if ((!rogue.playbackFastForward && rogue.playbackBetweenTurns) - || rogue.playbackOOS) { - - pauseDuration = (rogue.playbackPaused ? DEFAULT_PLAYBACK_DELAY : rogue.playbackDelayThisTurn); - if (pauseDuration && pauseBrogue(pauseDuration)) { - // if the player did something during playback - nextBrogueEvent(&recordingInput, false, false, true); - interaction = executePlaybackInput(&recordingInput); - repeatAgain = !rogue.playbackPaused && interaction; - } - } - } while ((repeatAgain || rogue.playbackOOS) && !rogue.gameHasEnded); - rogue.playbackDelayThisTurn = rogue.playbackDelayPerTurn; - recallEvent(returnEvent); - } else { - commitDraws(); - if (rogue.creaturesWillFlashThisTurn) { - displayMonsterFlashes(true); - } - do { - nextKeyOrMouseEvent(returnEvent, textInput, colorsDance); // No mouse clicks outside of the window will register. - } while (returnEvent->eventType == MOUSE_UP && !locIsInWindow((windowpos){ returnEvent->param1, returnEvent->param2 })); - // recording done elsewhere - } + rogueEvent recordingInput; + boolean repeatAgain, interaction; + short pauseDuration; - if (returnEvent->eventType == EVENT_ERROR) { - rogue.playbackPaused = rogue.playbackMode; // pause if replaying - message("Event error!", REQUIRE_ACKNOWLEDGMENT); + returnEvent->eventType = EVENT_ERROR; + + if (rogue.playbackMode && !realInputEvenInPlayback) { + do { + repeatAgain = false; + if ((!rogue.playbackFastForward && rogue.playbackBetweenTurns) || rogue.playbackOOS) { + + pauseDuration = (rogue.playbackPaused ? DEFAULT_PLAYBACK_DELAY : rogue.playbackDelayThisTurn); + if (pauseDuration && pauseBrogue(pauseDuration)) { + // if the player did something during playback + nextBrogueEvent(&recordingInput, false, false, true); + interaction = executePlaybackInput(&recordingInput); + repeatAgain = !rogue.playbackPaused && interaction; + } + } + } while ((repeatAgain || rogue.playbackOOS) && !rogue.gameHasEnded); + rogue.playbackDelayThisTurn = rogue.playbackDelayPerTurn; + recallEvent(returnEvent); + } else { + commitDraws(); + if (rogue.creaturesWillFlashThisTurn) { + displayMonsterFlashes(true); } + do { + nextKeyOrMouseEvent(returnEvent, textInput, colorsDance); // No mouse clicks outside of the window will register. + } while (returnEvent->eventType == MOUSE_UP + && !locIsInWindow((windowpos){returnEvent->param1, returnEvent->param2})); + // recording done elsewhere + } + + if (returnEvent->eventType == EVENT_ERROR) { + rogue.playbackPaused = rogue.playbackMode; // pause if replaying + message("Event error!", REQUIRE_ACKNOWLEDGMENT); + } } void executeMouseClick(rogueEvent *theEvent) { - short x, y; - boolean autoConfirm; - x = theEvent->param1; - y = theEvent->param2; - autoConfirm = theEvent->controlKey; - - if (theEvent->eventType == RIGHT_MOUSE_UP) { - displayInventory(ALL_ITEMS, 0, 0, true, true); - } else if (coordinatesAreInMap(windowToMapX(x), windowToMapY(y))) { - if (autoConfirm) { - travel(windowToMapX(x), windowToMapY(y), autoConfirm); - } else { - rogue.cursorLoc.x = windowToMapX(x); - rogue.cursorLoc.y = windowToMapY(y); - mainInputLoop(); - } - - } else if (windowToMapX(x) >= 0 && windowToMapX(x) < DCOLS && y >= 0 && y < MESSAGE_LINES) { - // If the click location is in the message block, display the message archive. - displayMessageArchive(); + short x, y; + boolean autoConfirm; + x = theEvent->param1; + y = theEvent->param2; + autoConfirm = theEvent->controlKey; + + if (theEvent->eventType == RIGHT_MOUSE_UP) { + displayInventory(ALL_ITEMS, 0, 0, true, true); + } else if (coordinatesAreInMap(windowToMapX(x), windowToMapY(y))) { + if (autoConfirm) { + travel(windowToMapX(x), windowToMapY(y), autoConfirm); + } else { + rogue.cursorLoc.x = windowToMapX(x); + rogue.cursorLoc.y = windowToMapY(y); + mainInputLoop(); } + + } else if (windowToMapX(x) >= 0 && windowToMapX(x) < DCOLS && y >= 0 && y < MESSAGE_LINES) { + // If the click location is in the message block, display the message archive. + displayMessageArchive(); + } } void executeKeystroke(signed long keystroke, boolean controlKey, boolean shiftKey) { - short direction = -1; + short direction = -1; + + confirmMessages(); + stripShiftFromMovementKeystroke(&keystroke); + + switch (keystroke) { + case UP_KEY: + case UP_ARROW: + case NUMPAD_8: + direction = UP; + break; + case DOWN_KEY: + case DOWN_ARROW: + case NUMPAD_2: + direction = DOWN; + break; + case LEFT_KEY: + case LEFT_ARROW: + case NUMPAD_4: + direction = LEFT; + break; + case RIGHT_KEY: + case RIGHT_ARROW: + case NUMPAD_6: + direction = RIGHT; + break; + case NUMPAD_7: + case UPLEFT_KEY: + direction = UPLEFT; + break; + case UPRIGHT_KEY: + case NUMPAD_9: + direction = UPRIGHT; + break; + case DOWNLEFT_KEY: + case NUMPAD_1: + direction = DOWNLEFT; + break; + case DOWNRIGHT_KEY: + case NUMPAD_3: + direction = DOWNRIGHT; + break; + case DESCEND_KEY: + considerCautiousMode(); + if (D_WORMHOLING) { + recordKeystroke(DESCEND_KEY, false, false); + useStairs(1); + } else if (proposeOrConfirmLocation(rogue.downLoc.x, rogue.downLoc.y, "I see no way down.")) { + travel(rogue.downLoc.x, rogue.downLoc.y, true); + } + break; + case ASCEND_KEY: + considerCautiousMode(); + if (D_WORMHOLING) { + recordKeystroke(ASCEND_KEY, false, false); + useStairs(-1); + } else if (proposeOrConfirmLocation(rogue.upLoc.x, rogue.upLoc.y, "I see no way up.")) { + travel(rogue.upLoc.x, rogue.upLoc.y, true); + } + break; + case RETURN_KEY: + showCursor(); + break; + case REST_KEY: + case PERIOD_KEY: + case NUMPAD_5: + considerCautiousMode(); + rogue.justRested = true; + recordKeystroke(REST_KEY, false, false); + playerTurnEnded(); + break; + case AUTO_REST_KEY: + rogue.justRested = true; + autoRest(); + break; + case SEARCH_KEY: + if (controlKey) { + rogue.disturbed = false; + rogue.automationActive = true; + do { + manualSearch(); + if (pauseAnimation(80)) { + rogue.disturbed = true; + } + } while (player.status[STATUS_SEARCHING] < 5 && !rogue.disturbed); + rogue.automationActive = false; + } else { + manualSearch(); + } + break; + case INVENTORY_KEY: + displayInventory(ALL_ITEMS, 0, 0, true, true); + break; + case EQUIP_KEY: + equip(NULL); + break; + case UNEQUIP_KEY: + unequip(NULL); + break; + case DROP_KEY: + drop(NULL); + break; + case APPLY_KEY: + apply(NULL, true); + break; + case THROW_KEY: + throwCommand(NULL, false); + break; + case RETHROW_KEY: + if (rogue.lastItemThrown != NULL && itemIsCarried(rogue.lastItemThrown)) { + throwCommand(rogue.lastItemThrown, true); + } + break; + case RELABEL_KEY: + relabel(NULL); + break; + case SWAP_KEY: + swapLastEquipment(); + break; + case TRUE_COLORS_KEY: + rogue.trueColorMode = !rogue.trueColorMode; + displayLevel(); + refreshSideBar(-1, -1, false); + if (rogue.trueColorMode) { + messageWithColor(KEYBOARD_LABELS ? "Color effects disabled. Press '\\' again to enable." + : "Color effects disabled.", + &teal, 0); + } else { + messageWithColor( + KEYBOARD_LABELS ? "Color effects enabled. Press '\\' again to disable." : "Color effects enabled.", &teal, 0); + } + break; + case STEALTH_RANGE_KEY: + rogue.displayStealthRangeMode = !rogue.displayStealthRangeMode; + displayLevel(); + refreshSideBar(-1, -1, false); + if (rogue.displayStealthRangeMode) { + messageWithColor( + KEYBOARD_LABELS ? "Stealth range displayed. Press ']' again to hide." : "Stealth range displayed.", &teal, 0); + } else { + messageWithColor(KEYBOARD_LABELS ? "Stealth range hidden. Press ']' again to display." : "Stealth range hidden.", + &teal, 0); + } + break; + case CALL_KEY: + call(NULL); + break; + case EXPLORE_KEY: + considerCautiousMode(); + exploreKey(controlKey); + break; + case AUTOPLAY_KEY: + if (confirm("Turn on autopilot?", false)) { + autoPlayLevel(controlKey); + } + break; + case MESSAGE_ARCHIVE_KEY: + displayMessageArchive(); + break; + case BROGUE_HELP_KEY: + printHelpScreen(); + break; + case DISCOVERIES_KEY: + printDiscoveriesScreen(); + break; + case CREATE_ITEM_MONSTER_KEY: + DEBUG { dialogCreateItemOrMonster(); } + break; + case SAVE_GAME_KEY: + if (rogue.playbackMode || serverMode) { + return; + } + if (confirm("Save this game and exit?", false)) { + saveGame(); + } + break; + case NEW_GAME_KEY: + if (rogue.playerTurnNumber < 50 || confirm("End this game and begin a new game?", false)) { + rogue.nextGame = NG_NEW_GAME; + rogue.gameHasEnded = true; + } + break; + case QUIT_KEY: + if (confirm("Quit and abandon this game? (The save will be deleted.)", false)) { + recordKeystroke(QUIT_KEY, false, false); + rogue.quit = true; + gameOver("Quit", true); + } + break; + case GRAPHICS_KEY: + if (hasGraphics) { + graphicsMode = setGraphicsMode((graphicsMode + 1) % 3); + switch (graphicsMode) { + case TEXT_GRAPHICS: + messageWithColor(KEYBOARD_LABELS ? "Switched to text mode. Press 'G' again to enable tiles." + : "Switched to text mode.", + &teal, 0); + break; + case TILES_GRAPHICS: + messageWithColor(KEYBOARD_LABELS ? "Switched to graphical tiles. Press 'G' again to enable hybrid mode." + : "Switched to graphical tiles.", + &teal, 0); + break; + case HYBRID_GRAPHICS: + messageWithColor(KEYBOARD_LABELS ? "Switched to hybrid mode. Press 'G' again to disable tiles." + : "Switched to hybrid mode.", + &teal, 0); + break; + } + } + break; + case SEED_KEY: + /*DEBUG { + cellDisplayBuffer dbuf[COLS][ROWS]; + copyDisplayBuffer(dbuf, displayBuffer); + funkyFade(dbuf, &white, 0, 100, mapToWindowX(player.loc.x), mapToWindowY(player.loc.y), false); + }*/ + // DEBUG displayLoops(); + // DEBUG displayChokeMap(); + DEBUG displayMachines(); + // DEBUG displayWaypoints(); + // DEBUG {displayGrid(safetyMap); displayMoreSign(); displayLevel();} + // parseFile(); + // DEBUG spawnDungeonFeature(player.loc.x, player.loc.y, &dungeonFeatureCatalog[DF_METHANE_GAS_ARMAGEDDON], true, + // false); + printSeed(); + break; + case EASY_MODE_KEY: + // if (shiftKey) { + enableEasyMode(); + //} + break; + case PRINTSCREEN_KEY: + if (takeScreenshot()) { + flashTemporaryAlert(" Screenshot saved in save directory ", 2000); + } + break; + default: + break; + } + if (direction >= 0) { // if it was a movement command + hideCursor(); + considerCautiousMode(); + if (controlKey || shiftKey) { + playerRuns(direction); + } else { + playerMoves(direction); + } + refreshSideBar(-1, -1, false); + } + + if (D_SAFETY_VISION) { + displayGrid(safetyMap); + } + if (rogue.trueColorMode || D_SCENT_VISION) { + displayLevel(); + } + rogue.cautiousMode = false; +} + +boolean getInputTextString(char *inputText, const char *prompt, short maxLength, const char *defaultEntry, + const char *promptSuffix, short textEntryType, boolean useDialogBox) { + short charNum, i, x, y; + char keystroke, suffix[100]; + const short textEntryBounds[TEXT_INPUT_TYPES][2] = {{' ', '~'}, {' ', '~'}, {'0', '9'}}; + cellDisplayBuffer dbuf[COLS][ROWS], rbuf[COLS][ROWS]; + + // x and y mark the origin for text entry. + if (useDialogBox) { + x = (COLS - max(maxLength, strLenWithoutEscapes(prompt))) / 2; + y = ROWS / 2 - 1; + clearDisplayBuffer(dbuf); + rectangularShading(x - 1, y - 2, max(maxLength, strLenWithoutEscapes(prompt)) + 2, 4, &interfaceBoxColor, + INTERFACE_OPACITY, dbuf); + overlayDisplayBuffer(dbuf, rbuf); + printString(prompt, x, y - 1, &white, &interfaceBoxColor, NULL); + for (i = 0; i < maxLength; i++) { + plotCharWithColor(' ', (windowpos){x + i, y}, &black, &black); + } + printString(defaultEntry, x, y, &white, &black, 0); + } else { confirmMessages(); - stripShiftFromMovementKeystroke(&keystroke); - - switch (keystroke) { - case UP_KEY: - case UP_ARROW: - case NUMPAD_8: - direction = UP; - break; - case DOWN_KEY: - case DOWN_ARROW: - case NUMPAD_2: - direction = DOWN; - break; - case LEFT_KEY: - case LEFT_ARROW: - case NUMPAD_4: - direction = LEFT; - break; - case RIGHT_KEY: - case RIGHT_ARROW: - case NUMPAD_6: - direction = RIGHT; - break; - case NUMPAD_7: - case UPLEFT_KEY: - direction = UPLEFT; - break; - case UPRIGHT_KEY: - case NUMPAD_9: - direction = UPRIGHT; - break; - case DOWNLEFT_KEY: - case NUMPAD_1: - direction = DOWNLEFT; - break; - case DOWNRIGHT_KEY: - case NUMPAD_3: - direction = DOWNRIGHT; - break; - case DESCEND_KEY: - considerCautiousMode(); - if (D_WORMHOLING) { - recordKeystroke(DESCEND_KEY, false, false); - useStairs(1); - } else if (proposeOrConfirmLocation(rogue.downLoc.x, rogue.downLoc.y, "I see no way down.")) { - travel(rogue.downLoc.x, rogue.downLoc.y, true); - } - break; - case ASCEND_KEY: - considerCautiousMode(); - if (D_WORMHOLING) { - recordKeystroke(ASCEND_KEY, false, false); - useStairs(-1); - } else if (proposeOrConfirmLocation(rogue.upLoc.x, rogue.upLoc.y, "I see no way up.")) { - travel(rogue.upLoc.x, rogue.upLoc.y, true); - } - break; - case RETURN_KEY: - showCursor(); - break; - case REST_KEY: - case PERIOD_KEY: - case NUMPAD_5: - considerCautiousMode(); - rogue.justRested = true; - recordKeystroke(REST_KEY, false, false); - playerTurnEnded(); - break; - case AUTO_REST_KEY: - rogue.justRested = true; - autoRest(); - break; - case SEARCH_KEY: - if (controlKey) { - rogue.disturbed = false; - rogue.automationActive = true; - do { - manualSearch(); - if (pauseAnimation(80)) { - rogue.disturbed = true; - } - } while (player.status[STATUS_SEARCHING] < 5 && !rogue.disturbed); - rogue.automationActive = false; - } else { - manualSearch(); - } - break; - case INVENTORY_KEY: - displayInventory(ALL_ITEMS, 0, 0, true, true); - break; - case EQUIP_KEY: - equip(NULL); - break; - case UNEQUIP_KEY: - unequip(NULL); - break; - case DROP_KEY: - drop(NULL); - break; - case APPLY_KEY: - apply(NULL, true); - break; - case THROW_KEY: - throwCommand(NULL, false); - break; - case RETHROW_KEY: - if (rogue.lastItemThrown != NULL && itemIsCarried(rogue.lastItemThrown)) { - throwCommand(rogue.lastItemThrown, true); - } - break; - case RELABEL_KEY: - relabel(NULL); - break; - case SWAP_KEY: - swapLastEquipment(); - break; - case TRUE_COLORS_KEY: - rogue.trueColorMode = !rogue.trueColorMode; - displayLevel(); - refreshSideBar(-1, -1, false); - if (rogue.trueColorMode) { - messageWithColor(KEYBOARD_LABELS ? "Color effects disabled. Press '\\' again to enable." : "Color effects disabled.", - &teal, 0); - } else { - messageWithColor(KEYBOARD_LABELS ? "Color effects enabled. Press '\\' again to disable." : "Color effects enabled.", - &teal, 0); - } - break; - case STEALTH_RANGE_KEY: - rogue.displayStealthRangeMode = !rogue.displayStealthRangeMode; - displayLevel(); - refreshSideBar(-1, -1, false); - if (rogue.displayStealthRangeMode) { - messageWithColor(KEYBOARD_LABELS ? "Stealth range displayed. Press ']' again to hide." : "Stealth range displayed.", - &teal, 0); - } else { - messageWithColor(KEYBOARD_LABELS ? "Stealth range hidden. Press ']' again to display." : "Stealth range hidden.", - &teal, 0); - } - break; - case CALL_KEY: - call(NULL); - break; - case EXPLORE_KEY: - considerCautiousMode(); - exploreKey(controlKey); - break; - case AUTOPLAY_KEY: - if (confirm("Turn on autopilot?", false)) { - autoPlayLevel(controlKey); - } - break; - case MESSAGE_ARCHIVE_KEY: - displayMessageArchive(); - break; - case BROGUE_HELP_KEY: - printHelpScreen(); - break; - case DISCOVERIES_KEY: - printDiscoveriesScreen(); - break; - case CREATE_ITEM_MONSTER_KEY: - DEBUG { - dialogCreateItemOrMonster(); - } - break; - case SAVE_GAME_KEY: - if (rogue.playbackMode || serverMode) { - return; - } - if (confirm("Save this game and exit?", false)) { - saveGame(); - } - break; - case NEW_GAME_KEY: - if (rogue.playerTurnNumber < 50 || confirm("End this game and begin a new game?", false)) { - rogue.nextGame = NG_NEW_GAME; - rogue.gameHasEnded = true; - } - break; - case QUIT_KEY: - if (confirm("Quit and abandon this game? (The save will be deleted.)", false)) { - recordKeystroke(QUIT_KEY, false, false); - rogue.quit = true; - gameOver("Quit", true); - } - break; - case GRAPHICS_KEY: - if (hasGraphics) { - graphicsMode = setGraphicsMode((graphicsMode + 1) % 3); - switch (graphicsMode) { - case TEXT_GRAPHICS: - messageWithColor(KEYBOARD_LABELS - ? "Switched to text mode. Press 'G' again to enable tiles." - : "Switched to text mode.", &teal, 0); - break; - case TILES_GRAPHICS: - messageWithColor(KEYBOARD_LABELS - ? "Switched to graphical tiles. Press 'G' again to enable hybrid mode." - : "Switched to graphical tiles.", &teal, 0); - break; - case HYBRID_GRAPHICS: - messageWithColor(KEYBOARD_LABELS - ? "Switched to hybrid mode. Press 'G' again to disable tiles." - : "Switched to hybrid mode.", &teal, 0); - break; - } - } - break; - case SEED_KEY: - /*DEBUG { - cellDisplayBuffer dbuf[COLS][ROWS]; - copyDisplayBuffer(dbuf, displayBuffer); - funkyFade(dbuf, &white, 0, 100, mapToWindowX(player.loc.x), mapToWindowY(player.loc.y), false); - }*/ - // DEBUG displayLoops(); - // DEBUG displayChokeMap(); - DEBUG displayMachines(); - //DEBUG displayWaypoints(); - // DEBUG {displayGrid(safetyMap); displayMoreSign(); displayLevel();} - // parseFile(); - // DEBUG spawnDungeonFeature(player.loc.x, player.loc.y, &dungeonFeatureCatalog[DF_METHANE_GAS_ARMAGEDDON], true, false); - printSeed(); - break; - case EASY_MODE_KEY: - //if (shiftKey) { - enableEasyMode(); - //} - break; - case PRINTSCREEN_KEY: - if (takeScreenshot()) { - flashTemporaryAlert(" Screenshot saved in save directory ", 2000); - } - break; - default: - break; - } - if (direction >= 0) { // if it was a movement command - hideCursor(); - considerCautiousMode(); - if (controlKey || shiftKey) { - playerRuns(direction); - } else { - playerMoves(direction); - } - refreshSideBar(-1, -1, false); - } - - if (D_SAFETY_VISION) { - displayGrid(safetyMap); - } - if (rogue.trueColorMode || D_SCENT_VISION) { - displayLevel(); + x = mapToWindowX(strLenWithoutEscapes(prompt)); + y = MESSAGE_LINES - 1; + temporaryMessage(prompt, 0); + printString(defaultEntry, x, y, &white, &black, 0); + } + + maxLength = min(maxLength, COLS - x); + + if (inputText != defaultEntry) { + strcpy(inputText, defaultEntry); + } + charNum = strLenWithoutEscapes(inputText); + for (i = charNum; i < maxLength; i++) { + inputText[i] = ' '; + } + + if (promptSuffix[0] == '\0') { // empty suffix + strcpy(suffix, " "); // so that deleting doesn't leave a white trail + } else { + strcpy(suffix, promptSuffix); + } + + do { + printString(suffix, charNum + x, y, &gray, &black, 0); + plotCharWithColor((suffix[0] ? suffix[0] : ' '), (windowpos){x + charNum, y}, &black, &white); + keystroke = nextKeyPress(true); + if (keystroke == DELETE_KEY && charNum > 0) { + printString(suffix, charNum + x - 1, y, &gray, &black, 0); + plotCharWithColor(' ', (windowpos){x + charNum + strlen(suffix) - 1, y}, &black, &black); + charNum--; + inputText[charNum] = ' '; + } else if (keystroke >= textEntryBounds[textEntryType][0] + && keystroke <= textEntryBounds[textEntryType][1]) { // allow only permitted input + + if (textEntryType == TEXT_INPUT_FILENAME && characterForbiddenInFilename(keystroke)) { + + keystroke = '-'; + } + + inputText[charNum] = keystroke; + plotCharWithColor(keystroke, (windowpos){x + charNum, y}, &white, &black); + printString(suffix, charNum + x + 1, y, &gray, &black, 0); + if (charNum < maxLength) { + charNum++; + } } +#ifdef USE_CLIPBOARD + else if (keystroke == TAB_KEY) { + char *clipboard = getClipboard(); + for (int i = 0; i < (int)min(strlen(clipboard), (unsigned long)(maxLength - charNum)); ++i) { - rogue.cautiousMode = false; -} + char character = clipboard[i]; -boolean getInputTextString(char *inputText, - const char *prompt, - short maxLength, - const char *defaultEntry, - const char *promptSuffix, - short textEntryType, - boolean useDialogBox) { - short charNum, i, x, y; - char keystroke, suffix[100]; - const short textEntryBounds[TEXT_INPUT_TYPES][2] = {{' ', '~'}, {' ', '~'}, {'0', '9'}}; - cellDisplayBuffer dbuf[COLS][ROWS], rbuf[COLS][ROWS]; - - // x and y mark the origin for text entry. - if (useDialogBox) { - x = (COLS - max(maxLength, strLenWithoutEscapes(prompt))) / 2; - y = ROWS / 2 - 1; - clearDisplayBuffer(dbuf); - rectangularShading(x - 1, y - 2, max(maxLength, strLenWithoutEscapes(prompt)) + 2, - 4, &interfaceBoxColor, INTERFACE_OPACITY, dbuf); - overlayDisplayBuffer(dbuf, rbuf); - printString(prompt, x, y - 1, &white, &interfaceBoxColor, NULL); - for (i=0; i= textEntryBounds[textEntryType][0] + && character <= textEntryBounds[textEntryType][1]) { // allow only permitted input + if (textEntryType == TEXT_INPUT_FILENAME && characterForbiddenInFilename(character)) { + character = '-'; + } + plotCharWithColor(character, (windowpos){x + charNum, y}, &white, &black); + if (charNum < maxLength) { + charNum++; + } } - printString(defaultEntry, x, y, &white, &black, 0); - } else { - confirmMessages(); - x = mapToWindowX(strLenWithoutEscapes(prompt)); - y = MESSAGE_LINES - 1; - temporaryMessage(prompt, 0); - printString(defaultEntry, x, y, &white, &black, 0); - } - - maxLength = min(maxLength, COLS - x); - - - if (inputText != defaultEntry) { - strcpy(inputText, defaultEntry); - } - charNum = strLenWithoutEscapes(inputText); - for (i = charNum; i < maxLength; i++) { - inputText[i] = ' '; - } - - if (promptSuffix[0] == '\0') { // empty suffix - strcpy(suffix, " "); // so that deleting doesn't leave a white trail - } else { - strcpy(suffix, promptSuffix); + } } - - do { - printString(suffix, charNum + x, y, &gray, &black, 0); - plotCharWithColor((suffix[0] ? suffix[0] : ' '), (windowpos){ x + charNum, y }, &black, &white); - keystroke = nextKeyPress(true); - if (keystroke == DELETE_KEY && charNum > 0) { - printString(suffix, charNum + x - 1, y, &gray, &black, 0); - plotCharWithColor(' ', (windowpos){ x + charNum + strlen(suffix) - 1, y }, &black, &black); - charNum--; - inputText[charNum] = ' '; - } else if (keystroke >= textEntryBounds[textEntryType][0] - && keystroke <= textEntryBounds[textEntryType][1]) { // allow only permitted input - - if (textEntryType == TEXT_INPUT_FILENAME - && characterForbiddenInFilename(keystroke)) { - - keystroke = '-'; - } - - inputText[charNum] = keystroke; - plotCharWithColor(keystroke, (windowpos){ x + charNum, y }, &white, &black); - printString(suffix, charNum + x + 1, y, &gray, &black, 0); - if (charNum < maxLength) { - charNum++; - } - } -#ifdef USE_CLIPBOARD - else if (keystroke == TAB_KEY) { - char* clipboard = getClipboard(); - for (int i=0; i<(int) min(strlen(clipboard), (unsigned long) (maxLength - charNum)); ++i) { - - char character = clipboard[i]; - - if (character >= textEntryBounds[textEntryType][0] - && character <= textEntryBounds[textEntryType][1]) { // allow only permitted input - if (textEntryType == TEXT_INPUT_FILENAME - && characterForbiddenInFilename(character)) { - character = '-'; - } - plotCharWithColor(character, (windowpos){ x + charNum, y }, &white, &black); - if (charNum < maxLength) { - charNum++; - } - } - } - } #endif - } while (keystroke != RETURN_KEY && keystroke != ESCAPE_KEY); + } while (keystroke != RETURN_KEY && keystroke != ESCAPE_KEY); - if (useDialogBox) { - overlayDisplayBuffer(rbuf, NULL); - } + if (useDialogBox) { + overlayDisplayBuffer(rbuf, NULL); + } - inputText[charNum] = '\0'; + inputText[charNum] = '\0'; - if (keystroke == ESCAPE_KEY) { - return false; - } - strcat(displayedMessage[0], inputText); - strcat(displayedMessage[0], suffix); - return true; + if (keystroke == ESCAPE_KEY) { + return false; + } + strcat(displayedMessage[0], inputText); + strcat(displayedMessage[0], suffix); + return true; } void displayCenteredAlert(char *message) { - printString(message, (COLS - strLenWithoutEscapes(message)) / 2, ROWS / 2, &teal, &black, 0); + printString(message, (COLS - strLenWithoutEscapes(message)) / 2, ROWS / 2, &teal, &black, 0); } // Flashes a message on the screen starting at (x, y) lasting for the given time (in ms) and with the given colors. void flashMessage(char *message, short x, short y, int time, const color *fColor, const color *bColor) { - boolean fastForward; - int i, j, messageLength, percentComplete, previousPercentComplete; - color backColors[COLS], backColor, foreColor; - cellDisplayBuffer dbufs[COLS]; - enum displayGlyph dchar; - short oldRNG; - const int stepInMs = 16; - - if (rogue.playbackFastForward) { - return; - } - - oldRNG = rogue.RNG; - rogue.RNG = RNG_COSMETIC; - //assureCosmeticRNG; - - messageLength = strLenWithoutEscapes(message); - fastForward = false; - - for (j=0; jbookkeepingFlags & MB_WILL_FLASH) { - monst->bookkeepingFlags &= ~MB_WILL_FLASH; - if (flashingEnabled && canSeeMonster(monst) && count < 100) { - x[count] = monst->loc.x; - y[count] = monst->loc.y; - strength[count] = monst->flashStrength; - flashColor[count] = &(monst->flashColor); - count++; - } - } - } - flashForeground(x, y, flashColor, strength, count, 20); - restoreRNG; + if (rogue.autoPlayingLevel || rogue.blockCombatText) { + return; + } + + short oldRNG = rogue.RNG; + rogue.RNG = RNG_COSMETIC; + // assureCosmeticRNG; + + boolean handledPlayer = false; + for (creatureIterator it = iterateCreatures(monsters); !handledPlayer || hasNextCreature(it);) { + creature *monst = !handledPlayer ? &player : nextCreature(&it); + handledPlayer = true; + if (monst->bookkeepingFlags & MB_WILL_FLASH) { + monst->bookkeepingFlags &= ~MB_WILL_FLASH; + if (flashingEnabled && canSeeMonster(monst) && count < 100) { + x[count] = monst->loc.x; + y[count] = monst->loc.y; + strength[count] = monst->flashStrength; + flashColor[count] = &(monst->flashColor); + count++; + } + } + } + flashForeground(x, y, flashColor, strength, count, 20); + restoreRNG; } void dequeueEvent() { - rogueEvent returnEvent; - nextBrogueEvent(&returnEvent, false, false, true); + rogueEvent returnEvent; + nextBrogueEvent(&returnEvent, false, false, true); } // Empty the message archive -void clearMessageArchive() { - messageArchivePosition = 0; -} +void clearMessageArchive() { messageArchivePosition = 0; } // Get a pointer to the archivedMessage the given number of entries back in history. // Pass zero to get the entry under messageArchivePosition. archivedMessage *getArchivedMessage(short back) { - return &messageArchive[(messageArchivePosition + MESSAGE_ARCHIVE_ENTRIES - back) % MESSAGE_ARCHIVE_ENTRIES]; + return &messageArchive[(messageArchivePosition + MESSAGE_ARCHIVE_ENTRIES - back) % MESSAGE_ARCHIVE_ENTRIES]; } int formatCountedMessage(char *buffer, size_t size, archivedMessage *m) { - int length; + int length; - if (m->count <= 1) { - length = snprintf(buffer, size, "%s", m->message); - } else if (m->count >= MAX_MESSAGE_REPEATS) { - length = snprintf(buffer, size, "%s (many)", m->message); - } else { - length = snprintf(buffer, size, "%s (x%d)", m->message, m->count); - } + if (m->count <= 1) { + length = snprintf(buffer, size, "%s", m->message); + } else if (m->count >= MAX_MESSAGE_REPEATS) { + length = snprintf(buffer, size, "%s (many)", m->message); + } else { + length = snprintf(buffer, size, "%s (x%d)", m->message, m->count); + } - return length; + return length; } // Select and write one or more recent messages to the buffer for further @@ -3047,120 +3012,119 @@ int formatCountedMessage(char *buffer, size_t size, archivedMessage *m) { // only one message is taken. // If turnOutput is not null, it is filled with the player turn number shared // by the chosen messages. -short foldMessages(char buffer[COLS*20], short offset, unsigned long *turnOutput) { - short i, folded, messageLength, lineLength, length; - unsigned long turn; - char counted[COLS*2]; - archivedMessage *m; - - folded = 0; - m = getArchivedMessage(offset + folded + 1); - if (!m->message[0]) { - return folded; - } +short foldMessages(char buffer[COLS * 20], short offset, unsigned long *turnOutput) { + short i, folded, messageLength, lineLength, length; + unsigned long turn; + char counted[COLS * 2]; + archivedMessage *m; + + folded = 0; + m = getArchivedMessage(offset + folded + 1); + if (!m->message[0]) { + return folded; + } - folded++; - turn = m->turn; - if (turnOutput) { - *turnOutput = turn; - } + folded++; + turn = m->turn; + if (turnOutput) { + *turnOutput = turn; + } - if (!(m->flags & FOLDABLE)) { - formatCountedMessage(buffer, COLS*2, m); - return folded; - } + if (!(m->flags & FOLDABLE)) { + formatCountedMessage(buffer, COLS * 2, m); + return folded; + } - // Search back for eligible foldable message. Only fold messages from the same turn + // Search back for eligible foldable message. Only fold messages from the same turn + m = getArchivedMessage(offset + folded + 1); + while (folded < MESSAGE_ARCHIVE_ENTRIES && m->message[0] && m->flags & FOLDABLE && turn == m->turn) { + folded++; m = getArchivedMessage(offset + folded + 1); - while (folded < MESSAGE_ARCHIVE_ENTRIES && m->message[0] && m->flags & FOLDABLE && turn == m->turn) { - folded++; - m = getArchivedMessage(offset + folded + 1); - } - - lineLength = 0; - length = 0; - buffer[length] = '\0'; - for (i = folded; i >= 1; i--) { - m = getArchivedMessage(offset + i); - formatCountedMessage(counted, COLS*2, m); - messageLength = strLenWithoutEscapes(counted); - - if (length == 0) { - length = snprintf(buffer, COLS*20, "%s", counted); - lineLength = messageLength; - } else if (lineLength + 3 + messageLength <= DCOLS) { // + 3 for semi-colon, space and final period - length += snprintf(&buffer[length], COLS*20 - length, "; %s", counted); - lineLength += 2 + messageLength; - } else { - length += snprintf(&buffer[length], COLS*20 - length, ".\n%s", counted); - lineLength = messageLength; - } + } + + lineLength = 0; + length = 0; + buffer[length] = '\0'; + for (i = folded; i >= 1; i--) { + m = getArchivedMessage(offset + i); + formatCountedMessage(counted, COLS * 2, m); + messageLength = strLenWithoutEscapes(counted); + + if (length == 0) { + length = snprintf(buffer, COLS * 20, "%s", counted); + lineLength = messageLength; + } else if (lineLength + 3 + messageLength <= DCOLS) { // + 3 for semi-colon, space and final period + length += snprintf(&buffer[length], COLS * 20 - length, "; %s", counted); + lineLength += 2 + messageLength; + } else { + length += snprintf(&buffer[length], COLS * 20 - length, ".\n%s", counted); + lineLength = messageLength; } + } - snprintf(&buffer[length], COLS*20 - length, "."); + snprintf(&buffer[length], COLS * 20 - length, "."); - return folded; + return folded; } // Change the given newline-delimited sentences in-place to ensure proper writing. void capitalizeAndPunctuateSentences(char *text, short length) { - short i; - boolean newSentence; - - newSentence = true; - - for (i = 0; i + 1 < length && text[i] != '\0' && text[i+1] != '\0'; i++) { - if (text[i] == COLOR_ESCAPE) { - i += 3; // the loop increment will get the last byte - } else if (text[i] == '"' - && (text[i+1] == '.' || text[i+1] == ',')) { - // Implement the American quotation mark/period/comma ordering rule. - text[i] = text[i+1]; - text[i+1] = '"'; - } else if (text[i] == '\n') { - newSentence = true; - } else if (newSentence) { - upperCase(&(text[i])); - newSentence = false; - } - } + short i; + boolean newSentence; + + newSentence = true; + + for (i = 0; i + 1 < length && text[i] != '\0' && text[i + 1] != '\0'; i++) { + if (text[i] == COLOR_ESCAPE) { + i += 3; // the loop increment will get the last byte + } else if (text[i] == '"' && (text[i + 1] == '.' || text[i + 1] == ',')) { + // Implement the American quotation mark/period/comma ordering rule. + text[i] = text[i + 1]; + text[i + 1] = '"'; + } else if (text[i] == '\n') { + newSentence = true; + } else if (newSentence) { + upperCase(&(text[i])); + newSentence = false; + } + } } // Copy \n-delimited lines to the given buffer. Carry colors across line breaks. -void splitLines(short lines, char wrapped[COLS*20], char buffer[][COLS*2], short bufferCursor) { - short linesSeen; - char color[5], line[COLS*2]; - char *start, *end; - - start = &(wrapped[0]); - color[0] = '\0'; - line[0] = '\0'; - - for (end = start, linesSeen = 0; *end; end++) { - if (*end == COLOR_ESCAPE) { - strncpy(color, end, 4); - color[4] = '\0'; - end += 4; - } else if (*end == '\n') { - *end = '\0'; - - if (bufferCursor + 1 - lines + linesSeen >= 0) { - strncpy(line, color, 5); - strncat(line, start, COLS*2 - strlen(line) - 1); - line[COLS*2-1] = '\0'; - strncpy(buffer[bufferCursor + 1 - lines + linesSeen], line, COLS*2); - line[0] = '\0'; - } - - linesSeen++; - start = end + 1; - } - } - - strncpy(line, color, 5); - strncat(line, start, COLS*2 - strlen(line) - 1); - line[COLS*2-1] = '\0'; - strncpy(buffer[bufferCursor], line, COLS*2); +void splitLines(short lines, char wrapped[COLS * 20], char buffer[][COLS * 2], short bufferCursor) { + short linesSeen; + char color[5], line[COLS * 2]; + char *start, *end; + + start = &(wrapped[0]); + color[0] = '\0'; + line[0] = '\0'; + + for (end = start, linesSeen = 0; *end; end++) { + if (*end == COLOR_ESCAPE) { + strncpy(color, end, 4); + color[4] = '\0'; + end += 4; + } else if (*end == '\n') { + *end = '\0'; + + if (bufferCursor + 1 - lines + linesSeen >= 0) { + strncpy(line, color, 5); + strncat(line, start, COLS * 2 - strlen(line) - 1); + line[COLS * 2 - 1] = '\0'; + strncpy(buffer[bufferCursor + 1 - lines + linesSeen], line, COLS * 2); + line[0] = '\0'; + } + + linesSeen++; + start = end + 1; + } + } + + strncpy(line, color, 5); + strncat(line, start, COLS * 2 - strlen(line) - 1); + line[COLS * 2 - 1] = '\0'; + strncpy(buffer[bufferCursor], line, COLS * 2); } // Fill the buffer of height lines with archived messages. Fill from the @@ -3169,57 +3133,57 @@ void splitLines(short lines, char wrapped[COLS*20], char buffer[][COLS*2], short // (rows of buffer filled) // latestMessageLines, if not null, is filled with the number of formatted // lines generated by events from the current player turn. -void formatRecentMessages(char buffer[][COLS*2], size_t height, short *linesFormatted, short *latestMessageLines) { - short lines, bufferCursor, messagesFolded, messagesFormatted; - unsigned long turn; - char folded[COLS*20], wrapped[COLS*20]; +void formatRecentMessages(char buffer[][COLS * 2], size_t height, short *linesFormatted, short *latestMessageLines) { + short lines, bufferCursor, messagesFolded, messagesFormatted; + unsigned long turn; + char folded[COLS * 20], wrapped[COLS * 20]; - bufferCursor = height - 1; - messagesFormatted = 0; + bufferCursor = height - 1; + messagesFormatted = 0; - if (latestMessageLines) { - *latestMessageLines = 0; - } + if (latestMessageLines) { + *latestMessageLines = 0; + } - while (bufferCursor >= 0 && messagesFormatted < MESSAGE_ARCHIVE_ENTRIES) { - messagesFolded = foldMessages(folded, messagesFormatted, &turn); - if (messagesFolded == 0) { - break; - } + while (bufferCursor >= 0 && messagesFormatted < MESSAGE_ARCHIVE_ENTRIES) { + messagesFolded = foldMessages(folded, messagesFormatted, &turn); + if (messagesFolded == 0) { + break; + } - capitalizeAndPunctuateSentences(folded, COLS*20); - lines = wrapText(wrapped, folded, DCOLS); - splitLines(lines, wrapped, buffer, bufferCursor); + capitalizeAndPunctuateSentences(folded, COLS * 20); + lines = wrapText(wrapped, folded, DCOLS); + splitLines(lines, wrapped, buffer, bufferCursor); - if (latestMessageLines && turn == rogue.playerTurnNumber) { - *latestMessageLines += lines; - } - - bufferCursor -= lines; - messagesFormatted += messagesFolded; + if (latestMessageLines && turn == rogue.playerTurnNumber) { + *latestMessageLines += lines; } - if (linesFormatted) { - *linesFormatted = height - 1 - bufferCursor; - } + bufferCursor -= lines; + messagesFormatted += messagesFolded; + } - while (bufferCursor >= 0) { - buffer[bufferCursor--][0] = '\0'; - } + if (linesFormatted) { + *linesFormatted = height - 1 - bufferCursor; + } + + while (bufferCursor >= 0) { + buffer[bufferCursor--][0] = '\0'; + } } // Display recent archived messages after recalculating message confirmations. void displayRecentMessages() { - short i; - char messageBuffer[MESSAGE_LINES][COLS*2]; + short i; + char messageBuffer[MESSAGE_LINES][COLS * 2]; - formatRecentMessages(messageBuffer, MESSAGE_LINES, 0, &messagesUnconfirmed); + formatRecentMessages(messageBuffer, MESSAGE_LINES, 0, &messagesUnconfirmed); - for (i = 0; i < MESSAGE_LINES; i++) { - strcpy(displayedMessage[i], messageBuffer[MESSAGE_LINES - i - 1]); - } + for (i = 0; i < MESSAGE_LINES; i++) { + strcpy(displayedMessage[i], messageBuffer[MESSAGE_LINES - i - 1]); + } - updateMessageDisplay(); + updateMessageDisplay(); } // Draw the given formatted messages to the screen: @@ -3228,29 +3192,32 @@ void displayRecentMessages() { // offset: index of oldest (visually highest) message to draw // height: height in rows of the message archive display area // rbuf: background display buffer to draw against -void drawMessageArchive(char messages[MESSAGE_ARCHIVE_LINES][COLS*2], short length, short offset, short height, cellDisplayBuffer rbuf[COLS][ROWS]) { - int i, j, k, fadePercent; - cellDisplayBuffer dbuf[COLS][ROWS]; +void drawMessageArchive(char messages[MESSAGE_ARCHIVE_LINES][COLS * 2], short length, short offset, short height, + cellDisplayBuffer rbuf[COLS][ROWS]) { + int i, j, k, fadePercent; + cellDisplayBuffer dbuf[COLS][ROWS]; - clearDisplayBuffer(dbuf); + clearDisplayBuffer(dbuf); - for (i = 0; (MESSAGE_ARCHIVE_LINES - offset + i) < MESSAGE_ARCHIVE_LINES && i < ROWS && i < height; i++) { - printString(messages[MESSAGE_ARCHIVE_LINES - offset + i], mapToWindowX(0), i, &white, &black, dbuf); - - // Set the dbuf opacity, and do a fade from bottom to top to make it clear that the bottom messages are the most recent. - fadePercent = 50 * (length - offset + i) / length + 50; - for (j = 0; j < DCOLS; j++) { - dbuf[mapToWindowX(j)][i].opacity = INTERFACE_OPACITY; - if (dbuf[mapToWindowX(j)][i].character != ' ') { - for (k=0; k<3; k++) { - dbuf[mapToWindowX(j)][i].foreColorComponents[k] = dbuf[mapToWindowX(j)][i].foreColorComponents[k] * fadePercent / 100; - } - } + for (i = 0; (MESSAGE_ARCHIVE_LINES - offset + i) < MESSAGE_ARCHIVE_LINES && i < ROWS && i < height; i++) { + printString(messages[MESSAGE_ARCHIVE_LINES - offset + i], mapToWindowX(0), i, &white, &black, dbuf); + + // Set the dbuf opacity, and do a fade from bottom to top to make it clear that the bottom messages are the most + // recent. + fadePercent = 50 * (length - offset + i) / length + 50; + for (j = 0; j < DCOLS; j++) { + dbuf[mapToWindowX(j)][i].opacity = INTERFACE_OPACITY; + if (dbuf[mapToWindowX(j)][i].character != ' ') { + for (k = 0; k < 3; k++) { + dbuf[mapToWindowX(j)][i].foreColorComponents[k] + = dbuf[mapToWindowX(j)][i].foreColorComponents[k] * fadePercent / 100; } + } } + } - overlayDisplayBuffer(rbuf, 0); - overlayDisplayBuffer(dbuf, 0); + overlayDisplayBuffer(rbuf, 0); + overlayDisplayBuffer(dbuf, 0); } // Pull-down/pull-up animation. @@ -3260,24 +3227,23 @@ void drawMessageArchive(char messages[MESSAGE_ARCHIVE_LINES][COLS*2], short leng // offset: index of oldest (visually highest) message to draw in the fully expanded state // height: height in rows of the message archive display area in the fully expanded state // rbuf: background display buffer to draw against -void animateMessageArchive(boolean opening, char messages[MESSAGE_ARCHIVE_LINES][COLS*2], short length, short offset, short height, cellDisplayBuffer rbuf[COLS][ROWS]) { - short i; - boolean fastForward; +void animateMessageArchive(boolean opening, char messages[MESSAGE_ARCHIVE_LINES][COLS * 2], short length, short offset, + short height, cellDisplayBuffer rbuf[COLS][ROWS]) { + short i; + boolean fastForward; - fastForward = false; + fastForward = false; - for (i = (opening ? MESSAGE_LINES : height); - (opening ? i <= height : i >= MESSAGE_LINES); - i += (opening ? 1 : -1)) { + for (i = (opening ? MESSAGE_LINES : height); (opening ? i <= height : i >= MESSAGE_LINES); i += (opening ? 1 : -1)) { - drawMessageArchive(messages, length, offset - height + i, i, rbuf); + drawMessageArchive(messages, length, offset - height + i, i, rbuf); - if (!fastForward && pauseBrogue(opening ? 2 : 1)) { - fastForward = true; - dequeueEvent(); - i = (opening ? height - 1 : MESSAGE_LINES + 1); // skip to the end - } + if (!fastForward && pauseBrogue(opening ? 2 : 1)) { + fastForward = true; + dequeueEvent(); + i = (opening ? height - 1 : MESSAGE_LINES + 1); // skip to the end } + } } // Accept keyboard input to navigate or dismiss the opened message archive @@ -3288,153 +3254,155 @@ void animateMessageArchive(boolean opening, char messages[MESSAGE_ARCHIVE_LINES] // rbuf: background display buffer to draw against // // returns the new offset, which can change if the player scrolled around before closing -short scrollMessageArchive(char messages[MESSAGE_ARCHIVE_LINES][COLS*2], short length, short offset, short height, cellDisplayBuffer rbuf[COLS][ROWS]) { - short lastOffset; - boolean exit; - rogueEvent theEvent; - signed long keystroke; - - if (rogue.autoPlayingLevel || (rogue.playbackMode && !rogue.playbackOOS)) { - return offset; - } - - exit = false; - do { - lastOffset = offset; - nextBrogueEvent(&theEvent, false, false, false); - - if (theEvent.eventType == KEYSTROKE) { - keystroke = theEvent.param1; - stripShiftFromMovementKeystroke(&keystroke); - - switch (keystroke) { - case UP_KEY: - case UP_ARROW: - case NUMPAD_8: - if (theEvent.controlKey) { - offset = length; - } else if (theEvent.shiftKey) { - offset++; - } else { - offset += MESSAGE_ARCHIVE_VIEW_LINES / 3; - } - break; - case DOWN_KEY: - case DOWN_ARROW: - case NUMPAD_2: - if (theEvent.controlKey) { - offset = height; - } else if (theEvent.shiftKey) { - offset--; - } else { - offset -= MESSAGE_ARCHIVE_VIEW_LINES / 3; - } - break; - case ACKNOWLEDGE_KEY: - case ESCAPE_KEY: - exit = true; - break; - default: - flashTemporaryAlert(" -- Press space or click to continue -- ", 500); - } +short scrollMessageArchive(char messages[MESSAGE_ARCHIVE_LINES][COLS * 2], short length, short offset, short height, + cellDisplayBuffer rbuf[COLS][ROWS]) { + short lastOffset; + boolean exit; + rogueEvent theEvent; + signed long keystroke; + + if (rogue.autoPlayingLevel || (rogue.playbackMode && !rogue.playbackOOS)) { + return offset; + } + + exit = false; + do { + lastOffset = offset; + nextBrogueEvent(&theEvent, false, false, false); + + if (theEvent.eventType == KEYSTROKE) { + keystroke = theEvent.param1; + stripShiftFromMovementKeystroke(&keystroke); + + switch (keystroke) { + case UP_KEY: + case UP_ARROW: + case NUMPAD_8: + if (theEvent.controlKey) { + offset = length; + } else if (theEvent.shiftKey) { + offset++; + } else { + offset += MESSAGE_ARCHIVE_VIEW_LINES / 3; + } + break; + case DOWN_KEY: + case DOWN_ARROW: + case NUMPAD_2: + if (theEvent.controlKey) { + offset = height; + } else if (theEvent.shiftKey) { + offset--; + } else { + offset -= MESSAGE_ARCHIVE_VIEW_LINES / 3; } + break; + case ACKNOWLEDGE_KEY: + case ESCAPE_KEY: + exit = true; + break; + default: + flashTemporaryAlert(" -- Press space or click to continue -- ", 500); + } + } - if (theEvent.eventType == MOUSE_UP) { - exit = true; - } + if (theEvent.eventType == MOUSE_UP) { + exit = true; + } - offset = max(height, min(offset, length)); - if (offset != lastOffset) { - drawMessageArchive(messages, length, offset, height, rbuf); - } - } while (!exit); + offset = max(height, min(offset, length)); + if (offset != lastOffset) { + drawMessageArchive(messages, length, offset, height, rbuf); + } + } while (!exit); - return offset; + return offset; } void displayMessageArchive() { - short length, offset, height; - cellDisplayBuffer rbuf[COLS][ROWS]; - char messageBuffer[MESSAGE_ARCHIVE_LINES][COLS*2]; + short length, offset, height; + cellDisplayBuffer rbuf[COLS][ROWS]; + char messageBuffer[MESSAGE_ARCHIVE_LINES][COLS * 2]; - formatRecentMessages(messageBuffer, MESSAGE_ARCHIVE_LINES, &length, 0); + formatRecentMessages(messageBuffer, MESSAGE_ARCHIVE_LINES, &length, 0); - if (length <= MESSAGE_LINES) { - return; - } + if (length <= MESSAGE_LINES) { + return; + } - height = min(length, MESSAGE_ARCHIVE_VIEW_LINES); - offset = height; + height = min(length, MESSAGE_ARCHIVE_VIEW_LINES); + offset = height; - copyDisplayBuffer(rbuf, displayBuffer); + copyDisplayBuffer(rbuf, displayBuffer); - animateMessageArchive(true, messageBuffer, length, offset, height, rbuf); - offset = scrollMessageArchive(messageBuffer, length, offset, height, rbuf); - animateMessageArchive(false, messageBuffer, length, offset, height, rbuf); + animateMessageArchive(true, messageBuffer, length, offset, height, rbuf); + offset = scrollMessageArchive(messageBuffer, length, offset, height, rbuf); + animateMessageArchive(false, messageBuffer, length, offset, height, rbuf); - overlayDisplayBuffer(rbuf, 0); - updateFlavorText(); - confirmMessages(); - updateMessageDisplay(); + overlayDisplayBuffer(rbuf, 0); + updateFlavorText(); + confirmMessages(); + updateMessageDisplay(); } // Clears the message area and prints the given message in the area. // It will disappear when messages are refreshed and will not be archived. // This is primarily used to display prompts. void temporaryMessage(const char *msg, unsigned long flags) { - char message[COLS]; - short i, j; + char message[COLS]; + short i, j; - assureCosmeticRNG; - strcpy(message, msg); + assureCosmeticRNG; + strcpy(message, msg); - for (i=0; message[i] == COLOR_ESCAPE; i += 4) { - upperCase(&(message[i])); - } + for (i = 0; message[i] == COLOR_ESCAPE; i += 4) { + upperCase(&(message[i])); + } - if (flags & REFRESH_SIDEBAR) { - refreshSideBar(-1, -1, false); - } + if (flags & REFRESH_SIDEBAR) { + refreshSideBar(-1, -1, false); + } - for (i=0; iturn == rogue.playerTurnNumber)) { - break; - } - - if (strcmp(archiveEntry->message, msg) == 0) { - // We found an suitable older message to collapse into. So we - // don't need to add another. Instead consider the older message - // as having happened on the current turn, and bump its count if - // not maxxed out. - newMessage = false; - archiveEntry->turn = rogue.playerTurnNumber; - if (archiveEntry->count < MAX_MESSAGE_REPEATS) { - archiveEntry->count++; - } - break; - } - } - - // We didn't collapse the new message, so initialize and insert a new - // archive entry for it instead. - if (newMessage) { - archiveEntry = &messageArchive[messageArchivePosition]; - strcpy(archiveEntry->message, msg); - archiveEntry->count = 1; - archiveEntry->turn = rogue.playerTurnNumber; - archiveEntry->flags = flags; - messageArchivePosition = (messageArchivePosition + 1) % MESSAGE_ARCHIVE_ENTRIES; - } + if (msg == NULL || !msg[0]) { + return; + } - displayRecentMessages(); + assureCosmeticRNG; - if ((flags & REQUIRE_ACKNOWLEDGMENT) || rogue.cautiousMode) { - displayMoreSign(); - confirmMessages(); - rogue.cautiousMode = false; - } + rogue.disturbed = true; + if (flags & REQUIRE_ACKNOWLEDGMENT || flags & REFRESH_SIDEBAR) { + refreshSideBar(-1, -1, false); + } + displayCombatText(); + + // Add the message to the archive, bumping counts for recent duplicates + newMessage = true; + + // For each at most MESSAGE_ARCHIVE_ENTRIES - 1 past entries.. + for (i = 1; i < MESSAGE_ARCHIVE_ENTRIES; i++) { + archiveEntry = getArchivedMessage(i); + + // Consider messages that arrived this turn for collapsing. Also + // consider the latest entry (which may be from past turns) if the + // incoming message is not semi-colon foldable. + if (!((i == 1 && !(flags & FOLDABLE)) || archiveEntry->turn == rogue.playerTurnNumber)) { + break; + } + + if (strcmp(archiveEntry->message, msg) == 0) { + // We found an suitable older message to collapse into. So we + // don't need to add another. Instead consider the older message + // as having happened on the current turn, and bump its count if + // not maxxed out. + newMessage = false; + archiveEntry->turn = rogue.playerTurnNumber; + if (archiveEntry->count < MAX_MESSAGE_REPEATS) { + archiveEntry->count++; + } + break; + } + } + + // We didn't collapse the new message, so initialize and insert a new + // archive entry for it instead. + if (newMessage) { + archiveEntry = &messageArchive[messageArchivePosition]; + strcpy(archiveEntry->message, msg); + archiveEntry->count = 1; + archiveEntry->turn = rogue.playerTurnNumber; + archiveEntry->flags = flags; + messageArchivePosition = (messageArchivePosition + 1) % MESSAGE_ARCHIVE_ENTRIES; + } + + displayRecentMessages(); + + if ((flags & REQUIRE_ACKNOWLEDGMENT) || rogue.cautiousMode) { + displayMoreSign(); + confirmMessages(); + rogue.cautiousMode = false; + } - if (rogue.playbackMode) { - rogue.playbackDelayThisTurn += min(2000, rogue.playbackDelayPerTurn * 5); - } + if (rogue.playbackMode) { + rogue.playbackDelayThisTurn += min(2000, rogue.playbackDelayPerTurn * 5); + } - restoreRNG; + restoreRNG; } // Only used for the "you die..." message, to enable posthumous inventory viewing. void displayMoreSignWithoutWaitingForAcknowledgment() { - if (strLenWithoutEscapes(displayedMessage[0]) < DCOLS - 8 || messagesUnconfirmed > 0) { - printString("--MORE--", COLS - 8, MESSAGE_LINES-1, &black, &white, 0); - } else { - printString("--MORE--", COLS - 8, MESSAGE_LINES, &black, &white, 0); - } + if (strLenWithoutEscapes(displayedMessage[0]) < DCOLS - 8 || messagesUnconfirmed > 0) { + printString("--MORE--", COLS - 8, MESSAGE_LINES - 1, &black, &white, 0); + } else { + printString("--MORE--", COLS - 8, MESSAGE_LINES, &black, &white, 0); + } } void displayMoreSign() { - short i; + short i; - if (rogue.autoPlayingLevel) { - return; - } + if (rogue.autoPlayingLevel) { + return; + } - if (strLenWithoutEscapes(displayedMessage[0]) < DCOLS - 8 || messagesUnconfirmed > 0) { - printString("--MORE--", COLS - 8, MESSAGE_LINES-1, &black, &white, 0); - waitForAcknowledgment(); - printString(" ", COLS - 8, MESSAGE_LINES-1, &black, &black, 0); - } else { - printString("--MORE--", COLS - 8, MESSAGE_LINES, &black, &white, 0); - waitForAcknowledgment(); - for (i=1; i<=8; i++) { - refreshDungeonCell(DCOLS - i, 0); - } + if (strLenWithoutEscapes(displayedMessage[0]) < DCOLS - 8 || messagesUnconfirmed > 0) { + printString("--MORE--", COLS - 8, MESSAGE_LINES - 1, &black, &white, 0); + waitForAcknowledgment(); + printString(" ", COLS - 8, MESSAGE_LINES - 1, &black, &black, 0); + } else { + printString("--MORE--", COLS - 8, MESSAGE_LINES, &black, &white, 0); + waitForAcknowledgment(); + for (i = 1; i <= 8; i++) { + refreshDungeonCell(DCOLS - i, 0); } + } } // Inserts a four-character color escape sequence into a string at the insertion point. // Does NOT check string lengths, so it could theoretically write over the null terminator. // Returns the new insertion point. short encodeMessageColor(char *msg, short i, const color *theColor) { - boolean needTerminator = false; - color col = *theColor; + boolean needTerminator = false; + color col = *theColor; - assureCosmeticRNG; + assureCosmeticRNG; - bakeColor(&col); + bakeColor(&col); - col.red = clamp(col.red, 0, 100); - col.green = clamp(col.green, 0, 100); - col.blue = clamp(col.blue, 0, 100); + col.red = clamp(col.red, 0, 100); + col.green = clamp(col.green, 0, 100); + col.blue = clamp(col.blue, 0, 100); - needTerminator = !msg[i] || !msg[i + 1] || !msg[i + 2] || !msg[i + 3]; + needTerminator = !msg[i] || !msg[i + 1] || !msg[i + 2] || !msg[i + 3]; - msg[i++] = COLOR_ESCAPE; - msg[i++] = (char) (COLOR_VALUE_INTERCEPT + col.red); - msg[i++] = (char) (COLOR_VALUE_INTERCEPT + col.green); - msg[i++] = (char) (COLOR_VALUE_INTERCEPT + col.blue); + msg[i++] = COLOR_ESCAPE; + msg[i++] = (char)(COLOR_VALUE_INTERCEPT + col.red); + msg[i++] = (char)(COLOR_VALUE_INTERCEPT + col.green); + msg[i++] = (char)(COLOR_VALUE_INTERCEPT + col.blue); - if (needTerminator) { - msg[i] = '\0'; - } + if (needTerminator) { + msg[i] = '\0'; + } - restoreRNG; + restoreRNG; - return i; + return i; } // Call this when the i'th character of msg is COLOR_ESCAPE. // It will return the encoded color, and will advance i past the color escape sequence. short decodeMessageColor(const char *msg, short i, color *returnColor) { - if (msg[i] != COLOR_ESCAPE) { - printf("\nAsked to decode a color escape that didn't exist!"); - *returnColor = white; - } else { - i++; - *returnColor = black; - returnColor->red = (short) (msg[i++] - COLOR_VALUE_INTERCEPT); - returnColor->green = (short) (msg[i++] - COLOR_VALUE_INTERCEPT); - returnColor->blue = (short) (msg[i++] - COLOR_VALUE_INTERCEPT); - - returnColor->red = clamp(returnColor->red, 0, 100); - returnColor->green = clamp(returnColor->green, 0, 100); - returnColor->blue = clamp(returnColor->blue, 0, 100); - } - return i; + if (msg[i] != COLOR_ESCAPE) { + printf("\nAsked to decode a color escape that didn't exist!"); + *returnColor = white; + } else { + i++; + *returnColor = black; + returnColor->red = (short)(msg[i++] - COLOR_VALUE_INTERCEPT); + returnColor->green = (short)(msg[i++] - COLOR_VALUE_INTERCEPT); + returnColor->blue = (short)(msg[i++] - COLOR_VALUE_INTERCEPT); + + returnColor->red = clamp(returnColor->red, 0, 100); + returnColor->green = clamp(returnColor->green, 0, 100); + returnColor->blue = clamp(returnColor->blue, 0, 100); + } + return i; } // Returns a color for combat text based on the identity of the victim. const color *messageColorFromVictim(creature *monst) { - if (monst == &player) { - return &badMessageColor; - } else if (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience) { - return &white; - } else if (monst->creatureState == MONSTER_ALLY) { - return &badMessageColor; - } else if (monstersAreEnemies(&player, monst)) { - return &goodMessageColor; - } else { - return &white; - } + if (monst == &player) { + return &badMessageColor; + } else if (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience) { + return &white; + } else if (monst->creatureState == MONSTER_ALLY) { + return &badMessageColor; + } else if (monstersAreEnemies(&player, monst)) { + return &goodMessageColor; + } else { + return &white; + } } void updateMessageDisplay() { - short i, j, m; - color messageColor; - - for (i=0; i= messagesUnconfirmed) { - applyColorAverage(&messageColor, &black, 50); - applyColorAverage(&messageColor, &black, 75 * i / MESSAGE_LINES); - } + for (i = 0; i < MESSAGE_LINES; i++) { + messageColor = white; - for (j = m = 0; displayedMessage[i][m] && j < DCOLS; j++, m++) { + if (i >= messagesUnconfirmed) { + applyColorAverage(&messageColor, &black, 50); + applyColorAverage(&messageColor, &black, 75 * i / MESSAGE_LINES); + } - while (displayedMessage[i][m] == COLOR_ESCAPE) { - m = decodeMessageColor(displayedMessage[i], m, &messageColor); // pulls the message color out and advances m - if (i >= messagesUnconfirmed) { - applyColorAverage(&messageColor, &black, 50); - applyColorAverage(&messageColor, &black, 75 * i / MESSAGE_LINES); - } - } + for (j = m = 0; displayedMessage[i][m] && j < DCOLS; j++, m++) { - plotCharWithColor(displayedMessage[i][m], (windowpos){ mapToWindowX(j), MESSAGE_LINES - i - 1 }, - &messageColor, - &black); - } - for (; j < DCOLS; j++) { - plotCharWithColor(' ', (windowpos){ mapToWindowX(j), MESSAGE_LINES - i - 1 }, &black, &black); + while (displayedMessage[i][m] == COLOR_ESCAPE) { + m = decodeMessageColor(displayedMessage[i], m, &messageColor); // pulls the message color out and advances m + if (i >= messagesUnconfirmed) { + applyColorAverage(&messageColor, &black, 50); + applyColorAverage(&messageColor, &black, 75 * i / MESSAGE_LINES); } + } + + plotCharWithColor(displayedMessage[i][m], (windowpos){mapToWindowX(j), MESSAGE_LINES - i - 1}, &messageColor, + &black); + } + for (; j < DCOLS; j++) { + plotCharWithColor(' ', (windowpos){mapToWindowX(j), MESSAGE_LINES - i - 1}, &black, &black); } + } } // Does NOT clear the message archive. void deleteMessages() { - short i; - for (i=0; i= 'a' && *theChar <= 'z') { - (*theChar) += ('A' - 'a'); - } + if (*theChar >= 'a' && *theChar <= 'z') { + (*theChar) += ('A' - 'a'); + } } enum entityDisplayTypes { - EDT_NOTHING = 0, - EDT_CREATURE, - EDT_ITEM, - EDT_TERRAIN, + EDT_NOTHING = 0, + EDT_CREATURE, + EDT_ITEM, + EDT_TERRAIN, }; // Refreshes the sidebar. @@ -3695,1217 +3656,1172 @@ enum entityDisplayTypes { // we won't know if it will fit on the screen in normal order until we try. // So if we try and fail, this function will call itself again, but with this set to true. void refreshSideBar(short focusX, short focusY, boolean focusedEntityMustGoFirst) { - short printY, oldPrintY, shortestDistance, i, j, k, px, py, x = 0, y = 0, displayEntityCount, indirectVision; - creature *closestMonst = NULL; - item *theItem, *closestItem = NULL; - char buf[COLS]; - const void *entityList[ROWS] = {0}, *focusEntity = NULL; - enum entityDisplayTypes entityType[ROWS] = {0}, focusEntityType = EDT_NOTHING; - pos terrainLocationMap[ROWS]; - boolean gotFocusedEntityOnScreen = (focusX >= 0 ? false : true); - char addedEntity[DCOLS][DROWS]; - short oldRNG; - - if (rogue.gameHasEnded || rogue.playbackFastForward) { - return; - } - - oldRNG = rogue.RNG; - rogue.RNG = RNG_COSMETIC; - //assureCosmeticRNG; - - if (focusX < 0) { - focusedEntityMustGoFirst = false; // just in case! - } else { - if (pmap[focusX][focusY].flags & (HAS_MONSTER | HAS_PLAYER)) { - creature *monst = monsterAtLoc((pos){ focusX, focusY }); - if (canSeeMonster(monst) || rogue.playbackOmniscience) { - focusEntity = monst; - focusEntityType = EDT_CREATURE; - } - } - if (!focusEntity && (pmap[focusX][focusY].flags & HAS_ITEM)) { - theItem = itemAtLoc(focusX, focusY); - if (playerCanSeeOrSense(focusX, focusY)) { - focusEntity = theItem; - focusEntityType = EDT_ITEM; - } - } - if (!focusEntity - && cellHasTMFlag(focusX, focusY, TM_LIST_IN_SIDEBAR) - && playerCanSeeOrSense(focusX, focusY)) { - - focusEntity = tileCatalog[pmap[focusX][focusY].layers[layerWithTMFlag(focusX, focusY, TM_LIST_IN_SIDEBAR)]].description; - focusEntityType = EDT_TERRAIN; - } - } - - printY = 0; - - px = player.loc.x; - py = player.loc.y; - - zeroOutGrid(addedEntity); - - // Header information for playback mode. - if (rogue.playbackMode) { - printString(" -- PLAYBACK -- ", 0, printY++, &white, &black, 0); - if (rogue.howManyTurns > 0) { - sprintf(buf, "Turn %li/%li", rogue.playerTurnNumber, rogue.howManyTurns); - printProgressBar(0, printY++, buf, rogue.playerTurnNumber, rogue.howManyTurns, &darkPurple, false); - } - if (rogue.playbackOOS) { - printString(" [OUT OF SYNC] ", 0, printY++, &badMessageColor, &black, 0); - } else if (rogue.playbackPaused) { - printString(" [PAUSED] ", 0, printY++, &gray, &black, 0); - } - printString(" ", 0, printY++, &white, &black, 0); - } - - // Now list the monsters that we'll be displaying in the order of their proximity to player (listing the focused first if required). - - // Initialization. - displayEntityCount = 0; - for (i=0; i= 0 ? false : true); + char addedEntity[DCOLS][DROWS]; + short oldRNG; + + if (rogue.gameHasEnded || rogue.playbackFastForward) { + return; + } + + oldRNG = rogue.RNG; + rogue.RNG = RNG_COSMETIC; + // assureCosmeticRNG; + + if (focusX < 0) { + focusedEntityMustGoFirst = false; // just in case! + } else { + if (pmap[focusX][focusY].flags & (HAS_MONSTER | HAS_PLAYER)) { + creature *monst = monsterAtLoc((pos){focusX, focusY}); + if (canSeeMonster(monst) || rogue.playbackOmniscience) { + focusEntity = monst; + focusEntityType = EDT_CREATURE; + } + } + if (!focusEntity && (pmap[focusX][focusY].flags & HAS_ITEM)) { + theItem = itemAtLoc(focusX, focusY); + if (playerCanSeeOrSense(focusX, focusY)) { + focusEntity = theItem; + focusEntityType = EDT_ITEM; + } + } + if (!focusEntity && cellHasTMFlag(focusX, focusY, TM_LIST_IN_SIDEBAR) && playerCanSeeOrSense(focusX, focusY)) { + + focusEntity + = tileCatalog[pmap[focusX][focusY].layers[layerWithTMFlag(focusX, focusY, TM_LIST_IN_SIDEBAR)]].description; + focusEntityType = EDT_TERRAIN; + } + } + + printY = 0; + + px = player.loc.x; + py = player.loc.y; + + zeroOutGrid(addedEntity); + + // Header information for playback mode. + if (rogue.playbackMode) { + printString(" -- PLAYBACK -- ", 0, printY++, &white, &black, 0); + if (rogue.howManyTurns > 0) { + sprintf(buf, "Turn %li/%li", rogue.playerTurnNumber, rogue.howManyTurns); + printProgressBar(0, printY++, buf, rogue.playerTurnNumber, rogue.howManyTurns, &darkPurple, false); + } + if (rogue.playbackOOS) { + printString(" [OUT OF SYNC] ", 0, printY++, &badMessageColor, &black, 0); + } else if (rogue.playbackPaused) { + printString(" [PAUSED] ", 0, printY++, &gray, &black, 0); + } + printString(" ", 0, printY++, &white, &black, 0); + } + + // Now list the monsters that we'll be displaying in the order of their proximity to player (listing the focused first + // if required). + + // Initialization. + displayEntityCount = 0; + for (i = 0; i < ROWS * 2; i++) { + rogue.sidebarLocationList[i] = INVALID_POS; + } + + // Player always goes first. + entityList[displayEntityCount] = &player; + entityType[displayEntityCount] = EDT_CREATURE; + displayEntityCount++; + addedEntity[player.loc.x][player.loc.y] = true; + + // Focused entity, if it must go first. + if (focusedEntityMustGoFirst && !addedEntity[focusX][focusY]) { + addedEntity[focusX][focusY] = true; + entityList[displayEntityCount] = focusEntity; + entityType[displayEntityCount] = focusEntityType; + terrainLocationMap[displayEntityCount] = (pos){focusX, focusY}; displayEntityCount++; - addedEntity[player.loc.x][player.loc.y] = true; - - // Focused entity, if it must go first. - if (focusedEntityMustGoFirst && !addedEntity[focusX][focusY]) { - addedEntity[focusX][focusY] = true; - entityList[displayEntityCount] = focusEntity; - entityType[displayEntityCount] = focusEntityType; - terrainLocationMap[displayEntityCount] = (pos) { focusX, focusY }; - displayEntityCount++; - } + } - for (indirectVision = 0; indirectVision < 2; indirectVision++) { - // Non-focused monsters. - do { - shortestDistance = 10000; - for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) { - creature *monst = nextCreature(&it); - if ((canDirectlySeeMonster(monst) || (indirectVision && (canSeeMonster(monst) || rogue.playbackOmniscience))) - && !addedEntity[monst->loc.x][monst->loc.y] - && !(monst->info.flags & MONST_NOT_LISTED_IN_SIDEBAR) - && (px - monst->loc.x) * (px - monst->loc.x) + (py - monst->loc.y) * (py - monst->loc.y) < shortestDistance) { - - shortestDistance = (px - monst->loc.x) * (px - monst->loc.x) + (py - monst->loc.y) * (py - monst->loc.y); - closestMonst = monst; - } - } - if (shortestDistance < 10000) { - addedEntity[closestMonst->loc.x][closestMonst->loc.y] = true; - entityList[displayEntityCount] = closestMonst; - entityType[displayEntityCount] = EDT_CREATURE; - displayEntityCount++; - } - } while (shortestDistance < 10000 && displayEntityCount * 2 < ROWS); // Because each entity takes at least 2 rows in the sidebar. - - // Non-focused items. - do { - shortestDistance = 10000; - for (theItem = floorItems->nextItem; theItem != NULL; theItem = theItem->nextItem) { - if ((playerCanDirectlySee(theItem->loc.x, theItem->loc.y) || (indirectVision && (playerCanSeeOrSense(theItem->loc.x, theItem->loc.y) || rogue.playbackOmniscience))) - && !addedEntity[theItem->loc.x][theItem->loc.y] - && (px - theItem->loc.x) * (px - theItem->loc.x) + (py - theItem->loc.y) * (py - theItem->loc.y) < shortestDistance) { - - shortestDistance = (px - theItem->loc.x) * (px - theItem->loc.x) + (py - theItem->loc.y) * (py - theItem->loc.y); - closestItem = theItem; - } - } - if (shortestDistance < 10000) { - addedEntity[closestItem->loc.x][closestItem->loc.y] = true; - entityList[displayEntityCount] = closestItem; - entityType[displayEntityCount] = EDT_ITEM; - displayEntityCount++; - } - } while (shortestDistance < 10000 && displayEntityCount * 2 < ROWS); // Because each entity takes at least 2 rows in the sidebar. - - // Non-focused terrain. - - // count up the number of candidate locations - for (k=0; k= ROWS - 1) goto no_space_for_more_entities; - if (coordinatesAreInMap(i, j) - && !addedEntity[i][j] - && cellHasTMFlag(i, j, TM_LIST_IN_SIDEBAR) - && (playerCanDirectlySee(i, j) || (indirectVision && (playerCanSeeOrSense(i, j) || rogue.playbackOmniscience)))) { - - addedEntity[i][j] = true; - entityList[displayEntityCount] = tileCatalog[pmap[i][j].layers[layerWithTMFlag(i, j, TM_LIST_IN_SIDEBAR)]].description; - entityType[displayEntityCount] = EDT_TERRAIN; - terrainLocationMap[displayEntityCount] = (pos) { i, j }; - displayEntityCount++; - } - } - } - } - no_space_for_more_entities:; - } - - // Entities are now listed. Start printing. - - for (i=0; iloc.x; - y = ((creature *) entityList[i])->loc.y; - printY = printMonsterInfo((creature *) entityList[i], - printY, - (focusEntity && (x != focusX || y != focusY)), - (x == focusX && y == focusY)); - - } else if (entityType[i] == EDT_ITEM) { - x = ((item *) entityList[i])->loc.x; - y = ((item *) entityList[i])->loc.y; - printY = printItemInfo((item *) entityList[i], - printY, - (focusEntity && (x != focusX || y != focusY)), - (x == focusX && y == focusY)); - } else if (entityType[i] == EDT_TERRAIN) { - x = terrainLocationMap[i].x; - y = terrainLocationMap[i].y; - printY = printTerrainInfo(x, y, - printY, - ((const char *) entityList[i]), - (focusEntity && (x != focusX || y != focusY)), - (x == focusX && y == focusY)); - } - if (focusEntity && (x == focusX && y == focusY) && printY < ROWS) { - gotFocusedEntityOnScreen = true; - } - for (j=oldPrintY; jloc.x][monst->loc.y] && !(monst->info.flags & MONST_NOT_LISTED_IN_SIDEBAR) + && (px - monst->loc.x) * (px - monst->loc.x) + (py - monst->loc.y) * (py - monst->loc.y) + < shortestDistance) { + + shortestDistance = (px - monst->loc.x) * (px - monst->loc.x) + (py - monst->loc.y) * (py - monst->loc.y); + closestMonst = monst; + } + } + if (shortestDistance < 10000) { + addedEntity[closestMonst->loc.x][closestMonst->loc.y] = true; + entityList[displayEntityCount] = closestMonst; + entityType[displayEntityCount] = EDT_CREATURE; + displayEntityCount++; + } + } while (shortestDistance < 10000 + && displayEntityCount * 2 < ROWS); // Because each entity takes at least 2 rows in the sidebar. - restoreRNG; + // Non-focused items. + do { + shortestDistance = 10000; + for (theItem = floorItems->nextItem; theItem != NULL; theItem = theItem->nextItem) { + if ((playerCanDirectlySee(theItem->loc.x, theItem->loc.y) + || (indirectVision && (playerCanSeeOrSense(theItem->loc.x, theItem->loc.y) || rogue.playbackOmniscience))) + && !addedEntity[theItem->loc.x][theItem->loc.y] + && (px - theItem->loc.x) * (px - theItem->loc.x) + (py - theItem->loc.y) * (py - theItem->loc.y) + < shortestDistance) { + + shortestDistance + = (px - theItem->loc.x) * (px - theItem->loc.x) + (py - theItem->loc.y) * (py - theItem->loc.y); + closestItem = theItem; + } + } + if (shortestDistance < 10000) { + addedEntity[closestItem->loc.x][closestItem->loc.y] = true; + entityList[displayEntityCount] = closestItem; + entityType[displayEntityCount] = EDT_ITEM; + displayEntityCount++; + } + } while (shortestDistance < 10000 + && displayEntityCount * 2 < ROWS); // Because each entity takes at least 2 rows in the sidebar. + + // Non-focused terrain. + + // count up the number of candidate locations + for (k = 0; k < max(DROWS, DCOLS); k++) { + for (i = px - k; i <= px + k; i++) { + // we are scanning concentric squares. The first and last columns + // need to be stepped through, but others can be jumped over. + short step = (i == px - k || i == px + k) ? 1 : 2 * k; + for (j = py - k; j <= py + k; j += step) { + if (displayEntityCount >= ROWS - 1) + goto no_space_for_more_entities; + if (coordinatesAreInMap(i, j) && !addedEntity[i][j] && cellHasTMFlag(i, j, TM_LIST_IN_SIDEBAR) + && (playerCanDirectlySee(i, j) + || (indirectVision && (playerCanSeeOrSense(i, j) || rogue.playbackOmniscience)))) { + + addedEntity[i][j] = true; + entityList[displayEntityCount] + = tileCatalog[pmap[i][j].layers[layerWithTMFlag(i, j, TM_LIST_IN_SIDEBAR)]].description; + entityType[displayEntityCount] = EDT_TERRAIN; + terrainLocationMap[displayEntityCount] = (pos){i, j}; + displayEntityCount++; + } + } + } + } + no_space_for_more_entities:; + } + + // Entities are now listed. Start printing. + + for (i = 0; i < displayEntityCount && printY < ROWS - 1; i++) { // Bottom line is reserved for the depth. + oldPrintY = printY; + if (entityType[i] == EDT_CREATURE) { + x = ((creature *)entityList[i])->loc.x; + y = ((creature *)entityList[i])->loc.y; + printY = printMonsterInfo((creature *)entityList[i], printY, (focusEntity && (x != focusX || y != focusY)), + (x == focusX && y == focusY)); + + } else if (entityType[i] == EDT_ITEM) { + x = ((item *)entityList[i])->loc.x; + y = ((item *)entityList[i])->loc.y; + printY = printItemInfo((item *)entityList[i], printY, (focusEntity && (x != focusX || y != focusY)), + (x == focusX && y == focusY)); + } else if (entityType[i] == EDT_TERRAIN) { + x = terrainLocationMap[i].x; + y = terrainLocationMap[i].y; + printY = printTerrainInfo(x, y, printY, ((const char *)entityList[i]), + (focusEntity && (x != focusX || y != focusY)), (x == focusX && y == focusY)); + } + if (focusEntity && (x == focusX && y == focusY) && printY < ROWS) { + gotFocusedEntityOnScreen = true; + } + for (j = oldPrintY; j < printY; j++) { + rogue.sidebarLocationList[j] = (pos){x, y}; + } + } + + if (gotFocusedEntityOnScreen) { + // Wrap things up. + for (i = printY; i < ROWS - 1; i++) { + printString(" ", 0, i, &white, &black, 0); + } + sprintf(buf, " -- Depth: %i --%s ", rogue.depthLevel, (rogue.depthLevel < 10 ? " " : "")); + printString(buf, 0, ROWS - 1, &white, &black, 0); + } else if (!focusedEntityMustGoFirst) { + // Failed to get the focusMonst printed on the screen. Try again, this time with the focus first. + refreshSideBar(focusX, focusY, true); + } + + restoreRNG; } -void printString(const char *theString, short x, short y, const color *foreColor, const color *backColor, cellDisplayBuffer dbuf[COLS][ROWS]) { - short i; +void printString(const char *theString, short x, short y, const color *foreColor, const color *backColor, + cellDisplayBuffer dbuf[COLS][ROWS]) { + short i; - color fColor = *foreColor; + color fColor = *foreColor; - for (i=0; theString[i] != '\0' && x < COLS; i++, x++) { - while (theString[i] == COLOR_ESCAPE) { - i = decodeMessageColor(theString, i, &fColor); - if (!theString[i]) { - return; - } - } - - plotCharToBuffer(theString[i], (windowpos){ x, y }, foreColor, backColor, dbuf); + for (i = 0; theString[i] != '\0' && x < COLS; i++, x++) { + while (theString[i] == COLOR_ESCAPE) { + i = decodeMessageColor(theString, i, &fColor); + if (!theString[i]) { + return; + } } + + plotCharToBuffer(theString[i], (windowpos){x, y}, foreColor, backColor, dbuf); + } } // Inserts line breaks into really long words. Optionally adds a hyphen, but doesn't do anything // clever regarding hyphen placement. Plays nicely with color escapes. void breakUpLongWordsIn(char *sourceText, short width, boolean useHyphens) { - char buf[COLS * ROWS * 2] = ""; - short i, m, nextChar, wordWidth; - //const short maxLength = useHyphens ? width - 1 : width; - - // i iterates over characters in sourceText; m keeps track of the length of buf. - wordWidth = 0; - for (i=0, m=0; sourceText[i] != 0;) { - if (sourceText[i] == COLOR_ESCAPE) { - strncpy(&(buf[m]), &(sourceText[i]), 4); - i += 4; - m += 4; - } else if (sourceText[i] == ' ' || sourceText[i] == '\n') { - wordWidth = 0; - buf[m++] = sourceText[i++]; - } else { - if (!useHyphens && wordWidth >= width) { - buf[m++] = '\n'; - wordWidth = 0; - } else if (useHyphens && wordWidth >= width - 1) { - nextChar = i+1; - while (sourceText[nextChar] == COLOR_ESCAPE) { - nextChar += 4; - } - if (sourceText[nextChar] && sourceText[nextChar] != ' ' && sourceText[nextChar] != '\n') { - buf[m++] = '-'; - buf[m++] = '\n'; - wordWidth = 0; - } - } - buf[m++] = sourceText[i++]; - wordWidth++; - } - } - buf[m] = '\0'; - strcpy(sourceText, buf); + char buf[COLS * ROWS * 2] = ""; + short i, m, nextChar, wordWidth; + // const short maxLength = useHyphens ? width - 1 : width; + + // i iterates over characters in sourceText; m keeps track of the length of buf. + wordWidth = 0; + for (i = 0, m = 0; sourceText[i] != 0;) { + if (sourceText[i] == COLOR_ESCAPE) { + strncpy(&(buf[m]), &(sourceText[i]), 4); + i += 4; + m += 4; + } else if (sourceText[i] == ' ' || sourceText[i] == '\n') { + wordWidth = 0; + buf[m++] = sourceText[i++]; + } else { + if (!useHyphens && wordWidth >= width) { + buf[m++] = '\n'; + wordWidth = 0; + } else if (useHyphens && wordWidth >= width - 1) { + nextChar = i + 1; + while (sourceText[nextChar] == COLOR_ESCAPE) { + nextChar += 4; + } + if (sourceText[nextChar] && sourceText[nextChar] != ' ' && sourceText[nextChar] != '\n') { + buf[m++] = '-'; + buf[m++] = '\n'; + wordWidth = 0; + } + } + buf[m++] = sourceText[i++]; + wordWidth++; + } + } + buf[m] = '\0'; + strcpy(sourceText, buf); } // Returns the number of lines, including the newlines already in the text. // Puts the output in "to" only if we receive a "to" -- can make it null and just get a line count. short wrapText(char *to, const char *sourceText, short width) { - short i, w, textLength, lineCount; - char printString[COLS * ROWS * 2]; - short spaceLeftOnLine, wordWidth; - - strcpy(printString, sourceText); // a copy we can write on - breakUpLongWordsIn(printString, width, true); // break up any words that are wider than the width. + short i, w, textLength, lineCount; + char printString[COLS * ROWS * 2]; + short spaceLeftOnLine, wordWidth; - textLength = strlen(printString); // do NOT discount escape sequences - lineCount = 1; + strcpy(printString, sourceText); // a copy we can write on + breakUpLongWordsIn(printString, width, true); // break up any words that are wider than the width. - // Now go through and replace spaces with newlines as needed. + textLength = strlen(printString); // do NOT discount escape sequences + lineCount = 1; - // Fast foward until i points to the first character that is not a color escape. - for (i=0; printString[i] == COLOR_ESCAPE; i+= 4); - spaceLeftOnLine = width; + // Now go through and replace spaces with newlines as needed. - while (i < textLength) { - // wordWidth counts the word width of the next word without color escapes. - // w indicates the position of the space or newline or null terminator that terminates the word. - wordWidth = 0; - for (w = i + 1; w < textLength && printString[w] != ' ' && printString[w] != '\n';) { - if (printString[w] == COLOR_ESCAPE) { - w += 4; - } else { - w++; - wordWidth++; - } - } + // Fast foward until i points to the first character that is not a color escape. + for (i = 0; printString[i] == COLOR_ESCAPE; i += 4) + ; + spaceLeftOnLine = width; - if (1 + wordWidth > spaceLeftOnLine || printString[i] == '\n') { - printString[i] = '\n'; - lineCount++; - spaceLeftOnLine = width - wordWidth; // line width minus the width of the word we just wrapped - //printf("\n\n%s", printString); - } else { - spaceLeftOnLine -= 1 + wordWidth; - } - i = w; // Advance to the terminator that follows the word. - } - if (to) { - strcpy(to, printString); - } - return lineCount; + while (i < textLength) { + // wordWidth counts the word width of the next word without color escapes. + // w indicates the position of the space or newline or null terminator that terminates the word. + wordWidth = 0; + for (w = i + 1; w < textLength && printString[w] != ' ' && printString[w] != '\n';) { + if (printString[w] == COLOR_ESCAPE) { + w += 4; + } else { + w++; + wordWidth++; + } + } + + if (1 + wordWidth > spaceLeftOnLine || printString[i] == '\n') { + printString[i] = '\n'; + lineCount++; + spaceLeftOnLine = width - wordWidth; // line width minus the width of the word we just wrapped + // printf("\n\n%s", printString); + } else { + spaceLeftOnLine -= 1 + wordWidth; + } + i = w; // Advance to the terminator that follows the word. + } + if (to) { + strcpy(to, printString); + } + return lineCount; } // returns the y-coordinate of the last line short printStringWithWrapping(const char *theString, short x, short y, short width, const color *foreColor, const color *backColor, cellDisplayBuffer dbuf[COLS][ROWS]) { - color fColor; - char printString[COLS * ROWS * 2]; - short i, px, py; - - wrapText(printString, theString, width); // inserts newlines as necessary - - // display the string - px = x; //px and py are the print insertion coordinates; x and y remain the top-left of the text box - py = y; - fColor = *foreColor; - - for (i=0; printString[i] != '\0'; i++) { - if (printString[i] == '\n') { - px = x; // back to the leftmost column - if (py < ROWS - 1) { // don't advance below the bottom of the screen - py++; // next line - } else { - break; // If we've run out of room, stop. - } - continue; - } else if (printString[i] == COLOR_ESCAPE) { - i = decodeMessageColor(printString, i, &fColor) - 1; - continue; - } - - if (locIsInWindow((windowpos){ px, py })) { - plotCharToBuffer(printString[i], (windowpos){ px, py }, &fColor, backColor, dbuf); - } - - px++; - } - return py; + color fColor; + char printString[COLS * ROWS * 2]; + short i, px, py; + + wrapText(printString, theString, width); // inserts newlines as necessary + + // display the string + px = x; // px and py are the print insertion coordinates; x and y remain the top-left of the text box + py = y; + fColor = *foreColor; + + for (i = 0; printString[i] != '\0'; i++) { + if (printString[i] == '\n') { + px = x; // back to the leftmost column + if (py < ROWS - 1) { // don't advance below the bottom of the screen + py++; // next line + } else { + break; // If we've run out of room, stop. + } + continue; + } else if (printString[i] == COLOR_ESCAPE) { + i = decodeMessageColor(printString, i, &fColor) - 1; + continue; + } + + if (locIsInWindow((windowpos){px, py})) { + plotCharToBuffer(printString[i], (windowpos){px, py}, &fColor, backColor, dbuf); + } + + px++; + } + return py; } char nextKeyPress(boolean textInput) { - rogueEvent theEvent; - do { - nextBrogueEvent(&theEvent, textInput, false, false); - } while (theEvent.eventType != KEYSTROKE); - return theEvent.param1; + rogueEvent theEvent; + do { + nextBrogueEvent(&theEvent, textInput, false, false); + } while (theEvent.eventType != KEYSTROKE); + return theEvent.param1; } -#define BROGUE_HELP_LINE_COUNT 33 +#define BROGUE_HELP_LINE_COUNT 33 void printHelpScreen() { - short i, j; - cellDisplayBuffer dbuf[COLS][ROWS], rbuf[COLS][ROWS]; - char helpText[BROGUE_HELP_LINE_COUNT][DCOLS*3] = { - "", - "", - " -- Commands --", - "", - " mouse ****move cursor (including to examine monsters and terrain)", - " click ****travel", - " control-click ****advance one space", - " ****enable keyboard cursor control", - " ****disable keyboard cursor control", - "hjklyubn, arrow keys, or numpad ****move or attack (control or shift to run)", - "", - "a/e/r/t/d/c/R/w ****apply/equip/remove/throw/drop/call/relabel/swap an item", - " T ****re-throw last item at last monster", - " i, right-click ****view inventory", - " D ****list discovered items", - "", - " z ****rest once", - " Z ****rest for 100 turns or until something happens", - " s ****search for secrets (control-s: long search)", - " <, > ****travel to stairs", - " x ****auto-explore (control-x: fast forward)", - " A ****autopilot (control-A: fast forward)", - " M ****display old messages", - " G ****toggle graphical tiles (when available)", - "", - " S ****save and exit", - " Q ****quit and abandon game", - "", - " \\ ****disable/enable color effects", - " ] ****display/hide stealth range", - " ****clear message or cancel command", - "", - " -- press space or click to continue --" - }; - - // Replace the "****"s with color escapes. - for (i=0; i ****enable keyboard cursor control", + " ****disable keyboard cursor control", + "hjklyubn, arrow keys, or numpad ****move or attack (control or shift to run)", + "", + "a/e/r/t/d/c/R/w ****apply/equip/remove/throw/drop/call/relabel/swap an item", + " T ****re-throw last item at last monster", + " i, right-click ****view inventory", + " D ****list discovered items", + "", + " z ****rest once", + " Z ****rest for 100 turns or until something happens", + " s ****search for secrets (control-s: long search)", + " <, > ****travel to stairs", + " x ****auto-explore (control-x: fast forward)", + " A ****autopilot (control-A: fast forward)", + " M ****display old messages", + " G ****toggle graphical tiles (when available)", + "", + " S ****save and exit", + " Q ****quit and abandon game", + "", + " \\ ****disable/enable color effects", + " ] ****display/hide stealth range", + " ****clear message or cancel command", + "", + " -- press space or click to continue --"}; + + // Replace the "****"s with color escapes. + for (i = 0; i < BROGUE_HELP_LINE_COUNT; i++) { + for (j = 0; helpText[i][j]; j++) { + if (helpText[i][j] == '*') { + j = encodeMessageColor(helpText[i], j, &white); + } + } + } + + clearDisplayBuffer(dbuf); + + // Print the text to the dbuf. + for (i = 0; i < BROGUE_HELP_LINE_COUNT && i < ROWS; i++) { + printString(helpText[i], mapToWindowX(1), i, &itemMessageColor, &black, dbuf); + } + + // Set the dbuf opacity. + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < ROWS; j++) { + // plotCharWithColor(' ', (windowpos) { mapToWindowX(i), j }, &black, &black); + dbuf[mapToWindowX(i)][j].opacity = INTERFACE_OPACITY; + } + } + + // Display. + overlayDisplayBuffer(dbuf, rbuf); + waitForAcknowledgment(); + overlayDisplayBuffer(rbuf, 0); + updateFlavorText(); + updateMessageDisplay(); } -void printDiscoveries(short category, short count, unsigned short itemCharacter, short x, short y, cellDisplayBuffer dbuf[COLS][ROWS]) { - color goodColor, badColor; - const color *theColor; - char buf[COLS], buf2[COLS]; - short i, magic, totalFrequency; - itemTable *theTable = tableForItemCategory(category); - - goodColor = goodMessageColor; - applyColorAverage(&goodColor, &black, 50); - badColor = badMessageColor; - applyColorAverage(&badColor, &black, 50); - - totalFrequency = 0; - for (i = 0; i < count; i++) { - if (!theTable[i].identified) { - totalFrequency += theTable[i].frequency; - } +void printDiscoveries(short category, short count, unsigned short itemCharacter, short x, short y, + cellDisplayBuffer dbuf[COLS][ROWS]) { + color goodColor, badColor; + const color *theColor; + char buf[COLS], buf2[COLS]; + short i, magic, totalFrequency; + itemTable *theTable = tableForItemCategory(category); + + goodColor = goodMessageColor; + applyColorAverage(&goodColor, &black, 50); + badColor = badMessageColor; + applyColorAverage(&badColor, &black, 50); + + totalFrequency = 0; + for (i = 0; i < count; i++) { + if (!theTable[i].identified) { + totalFrequency += theTable[i].frequency; + } + } + + for (i = 0; i < count; i++) { + if (theTable[i].identified) { + theColor = &white; + plotCharToBuffer(itemCharacter, (windowpos){x, y + i}, &itemColor, &black, dbuf); + } else { + theColor = &darkGray; + magic = magicCharDiscoverySuffix(category, i); + if (magic == 1) { + plotCharToBuffer(G_GOOD_MAGIC, (windowpos){x, y + i}, &goodColor, &black, dbuf); + } else if (magic == -1) { + plotCharToBuffer(G_BAD_MAGIC, (windowpos){x, y + i}, &badColor, &black, dbuf); + } } + strcpy(buf, theTable[i].name); - for (i = 0; i < count; i++) { - if (theTable[i].identified) { - theColor = &white; - plotCharToBuffer(itemCharacter, (windowpos){ x, y + i }, &itemColor, &black, dbuf); - } else { - theColor = &darkGray; - magic = magicCharDiscoverySuffix(category, i); - if (magic == 1) { - plotCharToBuffer(G_GOOD_MAGIC, (windowpos){ x, y + i }, &goodColor, &black, dbuf); - } else if (magic == -1) { - plotCharToBuffer(G_BAD_MAGIC, (windowpos){ x, y + i }, &badColor, &black, dbuf); - } - } - strcpy(buf, theTable[i].name); - - if (!theTable[i].identified - && theTable[i].frequency > 0 - && totalFrequency > 0) { + if (!theTable[i].identified && theTable[i].frequency > 0 && totalFrequency > 0) { - sprintf(buf2, " (%i%%)", theTable[i].frequency * 100 / totalFrequency); - strcat(buf, buf2); - } - - upperCase(buf); - strcat(buf, " "); - printString(buf, x + 2, y + i, theColor, &black, dbuf); + sprintf(buf2, " (%i%%)", theTable[i].frequency * 100 / totalFrequency); + strcat(buf, buf2); } + + upperCase(buf); + strcat(buf, " "); + printString(buf, x + 2, y + i, theColor, &black, dbuf); + } } void printDiscoveriesScreen() { - short i, j, y; - cellDisplayBuffer dbuf[COLS][ROWS], rbuf[COLS][ROWS]; + short i, j, y; + cellDisplayBuffer dbuf[COLS][ROWS], rbuf[COLS][ROWS]; - clearDisplayBuffer(dbuf); + clearDisplayBuffer(dbuf); - printString("-- SCROLLS --", mapToWindowX(2), y = mapToWindowY(1), &flavorTextColor, &black, dbuf); - printDiscoveries(SCROLL, gameConst->numberScrollKinds, G_SCROLL, mapToWindowX(3), ++y, dbuf); + printString("-- SCROLLS --", mapToWindowX(2), y = mapToWindowY(1), &flavorTextColor, &black, dbuf); + printDiscoveries(SCROLL, gameConst->numberScrollKinds, G_SCROLL, mapToWindowX(3), ++y, dbuf); - printString("-- RINGS --", mapToWindowX(2), y += gameConst->numberScrollKinds + 1, &flavorTextColor, &black, dbuf); - printDiscoveries(RING, NUMBER_RING_KINDS, G_RING, mapToWindowX(3), ++y, dbuf); + printString("-- RINGS --", mapToWindowX(2), y += gameConst->numberScrollKinds + 1, &flavorTextColor, &black, dbuf); + printDiscoveries(RING, NUMBER_RING_KINDS, G_RING, mapToWindowX(3), ++y, dbuf); - printString("-- POTIONS --", mapToWindowX(29), y = mapToWindowY(1), &flavorTextColor, &black, dbuf); - printDiscoveries(POTION, gameConst->numberPotionKinds, G_POTION, mapToWindowX(30), ++y, dbuf); + printString("-- POTIONS --", mapToWindowX(29), y = mapToWindowY(1), &flavorTextColor, &black, dbuf); + printDiscoveries(POTION, gameConst->numberPotionKinds, G_POTION, mapToWindowX(30), ++y, dbuf); - printString("-- STAFFS --", mapToWindowX(53), y = mapToWindowY(1), &flavorTextColor, &black, dbuf); - printDiscoveries(STAFF, NUMBER_STAFF_KINDS, G_STAFF, mapToWindowX(54), ++y, dbuf); + printString("-- STAFFS --", mapToWindowX(53), y = mapToWindowY(1), &flavorTextColor, &black, dbuf); + printDiscoveries(STAFF, NUMBER_STAFF_KINDS, G_STAFF, mapToWindowX(54), ++y, dbuf); - printString("-- WANDS --", mapToWindowX(53), y += NUMBER_STAFF_KINDS + 1, &flavorTextColor, &black, dbuf); - printDiscoveries(WAND, gameConst->numberWandKinds, G_WAND, mapToWindowX(54), ++y, dbuf); + printString("-- WANDS --", mapToWindowX(53), y += NUMBER_STAFF_KINDS + 1, &flavorTextColor, &black, dbuf); + printDiscoveries(WAND, gameConst->numberWandKinds, G_WAND, mapToWindowX(54), ++y, dbuf); - printString(KEYBOARD_LABELS ? "-- press any key to continue --" : "-- touch anywhere to continue --", - mapToWindowX(20), mapToWindowY(DROWS-2), &itemMessageColor, &black, dbuf); + printString(KEYBOARD_LABELS ? "-- press any key to continue --" : "-- touch anywhere to continue --", + mapToWindowX(20), mapToWindowY(DROWS - 2), &itemMessageColor, &black, dbuf); - for (i=0; i 0; i++) { - if (strLenWithoutEscapes(list[i].description) > maxLength) { - maxLength = strLenWithoutEscapes(list[i].description); - } + for (i = 0; i < HIGH_SCORES_COUNT && list[i].score > 0; i++) { + if (strLenWithoutEscapes(list[i].description) > maxLength) { + maxLength = strLenWithoutEscapes(list[i].description); } + } - leftOffset = min(COLS - maxLength - 23 - 1, COLS/5); + leftOffset = min(COLS - maxLength - 23 - 1, COLS / 5); - scoreColor = black; - applyColorAverage(&scoreColor, &itemMessageColor, 100); - printString("-- HIGH SCORES --", (COLS - 17 + 1) / 2, 0, &scoreColor, &black, 0); + scoreColor = black; + applyColorAverage(&scoreColor, &itemMessageColor, 100); + printString("-- HIGH SCORES --", (COLS - 17 + 1) / 2, 0, &scoreColor, &black, 0); - for (i = 0; i < HIGH_SCORES_COUNT && list[i].score > 0; i++) { - scoreColor = black; - if (i == hiliteLineNum) { - applyColorAverage(&scoreColor, &itemMessageColor, 100); - } else { - applyColorAverage(&scoreColor, &white, 100); - applyColorAverage(&scoreColor, &black, (i * 50 / 24)); - } + for (i = 0; i < HIGH_SCORES_COUNT && list[i].score > 0; i++) { + scoreColor = black; + if (i == hiliteLineNum) { + applyColorAverage(&scoreColor, &itemMessageColor, 100); + } else { + applyColorAverage(&scoreColor, &white, 100); + applyColorAverage(&scoreColor, &black, (i * 50 / 24)); + } - // rank - sprintf(buf, "%s%i)", (i + 1 < 10 ? " " : ""), i + 1); - printString(buf, leftOffset, i + 2, &scoreColor, &black, 0); + // rank + sprintf(buf, "%s%i)", (i + 1 < 10 ? " " : ""), i + 1); + printString(buf, leftOffset, i + 2, &scoreColor, &black, 0); - // score - sprintf(buf, "%li", list[i].score); - printString(buf, leftOffset + 5, i + 2, &scoreColor, &black, 0); + // score + sprintf(buf, "%li", list[i].score); + printString(buf, leftOffset + 5, i + 2, &scoreColor, &black, 0); - // date - printString(list[i].date, leftOffset + 12, i + 2, &scoreColor, &black, 0); + // date + printString(list[i].date, leftOffset + 12, i + 2, &scoreColor, &black, 0); - // description - printString(list[i].description, leftOffset + 23, i + 2, &scoreColor, &black, 0); - } + // description + printString(list[i].description, leftOffset + 23, i + 2, &scoreColor, &black, 0); + } - scoreColor = black; - applyColorAverage(&scoreColor, &goodMessageColor, 100); + scoreColor = black; + applyColorAverage(&scoreColor, &goodMessageColor, 100); - printString(KEYBOARD_LABELS ? "Press space to continue." : "Touch anywhere to continue.", - (COLS - strLenWithoutEscapes(KEYBOARD_LABELS ? "Press space to continue." : "Touch anywhere to continue.")) / 2, - ROWS - 1, &scoreColor, &black, 0); + printString( + KEYBOARD_LABELS ? "Press space to continue." : "Touch anywhere to continue.", + (COLS - strLenWithoutEscapes(KEYBOARD_LABELS ? "Press space to continue." : "Touch anywhere to continue.")) / 2, + ROWS - 1, &scoreColor, &black, 0); - commitDraws(); - waitForAcknowledgment(); + commitDraws(); + waitForAcknowledgment(); } void displayGrid(short **map) { - short i, j, score, topRange, bottomRange; - color tempColor, foreColor, backColor; - enum displayGlyph dchar; - - topRange = -30000; - bottomRange = 30000; - tempColor = black; - - if (map == safetyMap && !rogue.updatedSafetyMapThisTurn) { - updateSafetyMap(); - } - - for (i=0; i topRange) { - topRange = map[i][j]; - //if (topRange == 0) { - //printf("\ntop is zero at %i,%i", i, j); - //} - } - if (map[i][j] < bottomRange) { - bottomRange = map[i][j]; - } - } - } - - for (i=0; i topRange) { + topRange = map[i][j]; + // if (topRange == 0) { + // printf("\ntop is zero at %i,%i", i, j); + //} + } + if (map[i][j] < bottomRange) { + bottomRange = map[i][j]; + } + } + } + + for (i = 0; i < DCOLS; i++) { + for (j = 0; j < DROWS; j++) { + if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY | T_LAVA_INSTA_DEATH) || (map[i][j] == map[0][0]) + || (i == player.loc.x && j == player.loc.y)) { + continue; + } + score = 300 - (map[i][j] - bottomRange) * 300 / max(1, (topRange - bottomRange)); + tempColor.blue = max(min(score, 100), 0); + score -= 100; + tempColor.red = max(min(score, 100), 0); + score -= 100; + tempColor.green = max(min(score, 100), 0); + getCellAppearance(i, j, &dchar, &foreColor, &backColor); + plotCharWithColor(dchar, mapToWindow((pos){i, j}), &foreColor, &tempColor); + // colorBlendCell(i, j, &tempColor, 100);//hiliteCell(i, j, &tempColor, 100, false); + } + } + // printf("\ntop: %i; bottom: %i", topRange, bottomRange); } void printSeed() { - char buf[COLS]; - snprintf(buf, COLS, "Dungeon seed #%llu; turn #%lu; version %s", (unsigned long long)rogue.seed, rogue.playerTurnNumber, gameConst->versionString); - message(buf, 0); + char buf[COLS]; + snprintf(buf, COLS, "Dungeon seed #%llu; turn #%lu; version %s", (unsigned long long)rogue.seed, + rogue.playerTurnNumber, gameConst->versionString); + message(buf, 0); } -void printProgressBar(short x, short y, const char barLabel[COLS], long amtFilled, long amtMax, const color *fillColor, boolean dim) { - char barText[] = " "; // string length is 20 - short i, labelOffset; - color currentFillColor, textColor, progressBarColor, darkenedBarColor; +void printProgressBar(short x, short y, const char barLabel[COLS], long amtFilled, long amtMax, const color *fillColor, + boolean dim) { + char barText[] = " "; // string length is 20 + short i, labelOffset; + color currentFillColor, textColor, progressBarColor, darkenedBarColor; - if (y >= ROWS - 1) { // don't write over the depth number - return; - } - - if (amtFilled > amtMax) { - amtFilled = amtMax; - } - - if (amtMax <= 0) { - amtMax = 1; - } - - progressBarColor = *fillColor; - if (!(y % 2)) { - applyColorAverage(&progressBarColor, &black, 25); - } - - if (dim) { - applyColorAverage(&progressBarColor, &black, 50); - } - darkenedBarColor = progressBarColor; - applyColorAverage(&darkenedBarColor, &black, 75); - - labelOffset = (20 - strlen(barLabel)) / 2; - for (i = 0; i < (short) strlen(barLabel); i++) { - barText[i + labelOffset] = barLabel[i]; - } - - amtFilled = clamp(amtFilled, 0, amtMax); - - if (amtMax < 10000000) { - amtFilled *= 100; - amtMax *= 100; - } - - for (i=0; i<20; i++) { - currentFillColor = (i <= (20 * amtFilled / amtMax) ? progressBarColor : darkenedBarColor); - if (i == 20 * amtFilled / amtMax) { - applyColorAverage(¤tFillColor, &black, 75 - 75 * (amtFilled % (amtMax / 20)) / (amtMax / 20)); - } - textColor = (dim ? gray : white); - applyColorAverage(&textColor, ¤tFillColor, (dim ? 50 : 33)); - plotCharWithColor(barText[i], (windowpos){ x + i, y }, &textColor, ¤tFillColor); - } + if (y >= ROWS - 1) { // don't write over the depth number + return; + } + + if (amtFilled > amtMax) { + amtFilled = amtMax; + } + + if (amtMax <= 0) { + amtMax = 1; + } + + progressBarColor = *fillColor; + if (!(y % 2)) { + applyColorAverage(&progressBarColor, &black, 25); + } + + if (dim) { + applyColorAverage(&progressBarColor, &black, 50); + } + darkenedBarColor = progressBarColor; + applyColorAverage(&darkenedBarColor, &black, 75); + + labelOffset = (20 - strlen(barLabel)) / 2; + for (i = 0; i < (short)strlen(barLabel); i++) { + barText[i + labelOffset] = barLabel[i]; + } + + amtFilled = clamp(amtFilled, 0, amtMax); + + if (amtMax < 10000000) { + amtFilled *= 100; + amtMax *= 100; + } + + for (i = 0; i < 20; i++) { + currentFillColor = (i <= (20 * amtFilled / amtMax) ? progressBarColor : darkenedBarColor); + if (i == 20 * amtFilled / amtMax) { + applyColorAverage(¤tFillColor, &black, 75 - 75 * (amtFilled % (amtMax / 20)) / (amtMax / 20)); + } + textColor = (dim ? gray : white); + applyColorAverage(&textColor, ¤tFillColor, (dim ? 50 : 33)); + plotCharWithColor(barText[i], (windowpos){x + i, y}, &textColor, ¤tFillColor); + } } // Very low-level. Changes displayBuffer directly. void highlightScreenCell(short x, short y, const color *highlightColor, short strength) { - color tempColor; + color tempColor; - tempColor = colorFromComponents(displayBuffer[x][y].foreColorComponents); - applyColorAugment(&tempColor, highlightColor, strength); - storeColorComponents(displayBuffer[x][y].foreColorComponents, &tempColor); + tempColor = colorFromComponents(displayBuffer[x][y].foreColorComponents); + applyColorAugment(&tempColor, highlightColor, strength); + storeColorComponents(displayBuffer[x][y].foreColorComponents, &tempColor); - tempColor = colorFromComponents(displayBuffer[x][y].backColorComponents); - applyColorAugment(&tempColor, highlightColor, strength); - storeColorComponents(displayBuffer[x][y].backColorComponents, &tempColor); + tempColor = colorFromComponents(displayBuffer[x][y].backColorComponents); + applyColorAugment(&tempColor, highlightColor, strength); + storeColorComponents(displayBuffer[x][y].backColorComponents, &tempColor); } // Like `armorValueIfUnenchanted` for the currently-equipped armor, but takes the penalty from // donning into account. static short estimatedArmorValue() { - short retVal = armorValueIfUnenchanted(rogue.armor) - player.status[STATUS_DONNING]; - return max(0, retVal); + short retVal = armorValueIfUnenchanted(rogue.armor) - player.status[STATUS_DONNING]; + return max(0, retVal); } short creatureHealthChangePercent(creature *monst) { - if (monst->previousHealthPoints <= 0) { - return 0; - } - // ignore overhealing from tranference - return 100 * (monst->currentHP - min(monst->previousHealthPoints, monst->info.maxHP)) / monst->info.maxHP; + if (monst->previousHealthPoints <= 0) { + return 0; + } + // ignore overhealing from tranference + return 100 * (monst->currentHP - min(monst->previousHealthPoints, monst->info.maxHP)) / monst->info.maxHP; } // returns the y-coordinate after the last line printed short printMonsterInfo(creature *monst, short y, boolean dim, boolean highlight) { - char buf[COLS * 2], buf2[COLS * 2], monstName[COLS], tempColorEscape[5], grayColorEscape[5]; - enum displayGlyph monstChar; - color monstForeColor, monstBackColor, healthBarColor, tempColor; - short initialY, i, j, highlightStrength, percent; - boolean inPath; - short oldRNG; - - const char hallucinationStrings[16][COLS] = { - " (Dancing) ", - " (Singing) ", - " (Pontificating) ", - " (Skipping) ", - " (Spinning) ", - " (Crying) ", - " (Laughing) ", - " (Humming) ", - " (Whistling) ", - " (Quivering) ", - " (Muttering) ", - " (Gibbering) ", - " (Giggling) ", - " (Moaning) ", - " (Shrieking) ", - " (Caterwauling) ", - }; - const char statusStrings[NUMBER_OF_STATUS_EFFECTS][COLS] = { - "Searching", - "Donning Armor", - "Weakened: -", - "Telepathic", - "Hallucinating", - "Levitating", - "Slowed", - "Hasted", - "Confused", - "Burning", - "Paralyzed", - "Poisoned", - "Stuck", - "Nauseous", - "Discordant", - "Immune to Fire", - "", // STATUS_EXPLOSION_IMMUNITY, - "", // STATUS_NUTRITION, - "", // STATUS_ENTERS_LEVEL_IN, - "", // STATUS_ENRAGED, - "Frightened", - "Entranced", - "Darkened", - "Lifespan", - "Shielded", - "Invisible", - }; - - if (y >= ROWS - 1) { - return ROWS - 1; - } - - initialY = y; - - oldRNG = rogue.RNG; - rogue.RNG = RNG_COSMETIC; - //assureCosmeticRNG; - - if (y < ROWS - 1) { - printString(" ", 0, y, &white, &black, 0); // Start with a blank line - - // Unhighlight if it's highlighted as part of the path. - inPath = (pmapAt(monst->loc)->flags & IS_IN_PATH) ? true : false; - pmapAt(monst->loc)->flags &= ~IS_IN_PATH; - getCellAppearance(monst->loc.x, monst->loc.y, &monstChar, &monstForeColor, &monstBackColor); - applyColorBounds(&monstForeColor, 0, 100); - applyColorBounds(&monstBackColor, 0, 100); - if (inPath) { - pmapAt(monst->loc)->flags |= IS_IN_PATH; - } - - if (dim) { - applyColorAverage(&monstForeColor, &black, 50); - applyColorAverage(&monstBackColor, &black, 50); - } else if (highlight) { - applyColorAugment(&monstForeColor, &black, 100); - applyColorAugment(&monstBackColor, &black, 100); - } - plotCharWithColor(monstChar, (windowpos){ 0, y }, &monstForeColor, &monstBackColor); - if(monst->carriedItem) { - plotCharWithColor(monst->carriedItem->displayChar, (windowpos) { 1, y }, &itemColor, &black); - } - monsterName(monstName, monst, false); - upperCase(monstName); - - if (monst == &player) { - if (player.status[STATUS_INVISIBLE]) { - strcat(monstName, " xxxx"); - encodeMessageColor(monstName, strlen(monstName) - 4, &monstForeColor); - strcat(monstName, "(invisible)"); - } else if (playerInDarkness()) { - strcat(monstName, " xxxx"); - //encodeMessageColor(monstName, strlen(monstName) - 4, &playerInDarknessColor); - encodeMessageColor(monstName, strlen(monstName) - 4, &monstForeColor); - strcat(monstName, "(dark)"); - } else if (!(pmapAt(player.loc)->flags & IS_IN_SHADOW)) { - strcat(monstName, " xxxx"); - //encodeMessageColor(monstName, strlen(monstName) - 4, &playerInLightColor); - encodeMessageColor(monstName, strlen(monstName) - 4, &monstForeColor); - strcat(monstName, "(lit)"); - } - } - - sprintf(buf, ": %s", monstName); - printString(buf, monst->carriedItem?2:1, y++, (dim ? &gray : &white), &black, 0); + char buf[COLS * 2], buf2[COLS * 2], monstName[COLS], tempColorEscape[5], grayColorEscape[5]; + enum displayGlyph monstChar; + color monstForeColor, monstBackColor, healthBarColor, tempColor; + short initialY, i, j, highlightStrength, percent; + boolean inPath; + short oldRNG; + + const char hallucinationStrings[16][COLS] = { + " (Dancing) ", " (Singing) ", " (Pontificating) ", " (Skipping) ", + " (Spinning) ", " (Crying) ", " (Laughing) ", " (Humming) ", + " (Whistling) ", " (Quivering) ", " (Muttering) ", " (Gibbering) ", + " (Giggling) ", " (Moaning) ", " (Shrieking) ", " (Caterwauling) ", + }; + const char statusStrings[NUMBER_OF_STATUS_EFFECTS][COLS] = { + "Searching", "Donning Armor", "Weakened: -", "Telepathic", "Hallucinating", "Levitating", + "Slowed", "Hasted", "Confused", "Burning", "Paralyzed", "Poisoned", + "Stuck", "Nauseous", "Discordant", "Immune to Fire", + "", // STATUS_EXPLOSION_IMMUNITY, + "", // STATUS_NUTRITION, + "", // STATUS_ENTERS_LEVEL_IN, + "", // STATUS_ENRAGED, + "Frightened", "Entranced", "Darkened", "Lifespan", "Shielded", "Invisible", + }; + + if (y >= ROWS - 1) { + return ROWS - 1; + } + + initialY = y; + + oldRNG = rogue.RNG; + rogue.RNG = RNG_COSMETIC; + // assureCosmeticRNG; + + if (y < ROWS - 1) { + printString(" ", 0, y, &white, &black, 0); // Start with a blank line + + // Unhighlight if it's highlighted as part of the path. + inPath = (pmapAt(monst->loc)->flags & IS_IN_PATH) ? true : false; + pmapAt(monst->loc)->flags &= ~IS_IN_PATH; + getCellAppearance(monst->loc.x, monst->loc.y, &monstChar, &monstForeColor, &monstBackColor); + applyColorBounds(&monstForeColor, 0, 100); + applyColorBounds(&monstBackColor, 0, 100); + if (inPath) { + pmapAt(monst->loc)->flags |= IS_IN_PATH; } - // mutation, if any - if (y < ROWS - 1 - && monst->mutationIndex >= 0 - && (!player.status[STATUS_HALLUCINATING] || rogue.playbackOmniscience)) { + if (dim) { + applyColorAverage(&monstForeColor, &black, 50); + applyColorAverage(&monstBackColor, &black, 50); + } else if (highlight) { + applyColorAugment(&monstForeColor, &black, 100); + applyColorAugment(&monstBackColor, &black, 100); + } + plotCharWithColor(monstChar, (windowpos){0, y}, &monstForeColor, &monstBackColor); + if (monst->carriedItem) { + plotCharWithColor(monst->carriedItem->displayChar, (windowpos){1, y}, &itemColor, &black); + } + monsterName(monstName, monst, false); + upperCase(monstName); - strcpy(buf, " "); - sprintf(buf2, "xxxx(%s)", mutationCatalog[monst->mutationIndex].title); - tempColor = *mutationCatalog[monst->mutationIndex].textColor; - if (dim) { - applyColorAverage(&tempColor, &black, 50); - } - encodeMessageColor(buf2, 0, &tempColor); - strcpy(buf + ((strLenWithoutEscapes(buf) - strLenWithoutEscapes(buf2)) / 2), buf2); - for (i = strlen(buf); i < 20 + 4; i++) { - buf[i] = ' '; - } - buf[24] = '\0'; - printString(buf, 0, y++, (dim ? &gray : &white), &black, 0); + if (monst == &player) { + if (player.status[STATUS_INVISIBLE]) { + strcat(monstName, " xxxx"); + encodeMessageColor(monstName, strlen(monstName) - 4, &monstForeColor); + strcat(monstName, "(invisible)"); + } else if (playerInDarkness()) { + strcat(monstName, " xxxx"); + // encodeMessageColor(monstName, strlen(monstName) - 4, &playerInDarknessColor); + encodeMessageColor(monstName, strlen(monstName) - 4, &monstForeColor); + strcat(monstName, "(dark)"); + } else if (!(pmapAt(player.loc)->flags & IS_IN_SHADOW)) { + strcat(monstName, " xxxx"); + // encodeMessageColor(monstName, strlen(monstName) - 4, &playerInLightColor); + encodeMessageColor(monstName, strlen(monstName) - 4, &monstForeColor); + strcat(monstName, "(lit)"); + } + } + + sprintf(buf, ": %s", monstName); + printString(buf, monst->carriedItem ? 2 : 1, y++, (dim ? &gray : &white), &black, 0); + } + + // mutation, if any + if (y < ROWS - 1 && monst->mutationIndex >= 0 + && (!player.status[STATUS_HALLUCINATING] || rogue.playbackOmniscience)) { + + strcpy(buf, " "); + sprintf(buf2, "xxxx(%s)", mutationCatalog[monst->mutationIndex].title); + tempColor = *mutationCatalog[monst->mutationIndex].textColor; + if (dim) { + applyColorAverage(&tempColor, &black, 50); + } + encodeMessageColor(buf2, 0, &tempColor); + strcpy(buf + ((strLenWithoutEscapes(buf) - strLenWithoutEscapes(buf2)) / 2), buf2); + for (i = strlen(buf); i < 20 + 4; i++) { + buf[i] = ' '; } + buf[24] = '\0'; + printString(buf, 0, y++, (dim ? &gray : &white), &black, 0); + } - // hit points - if (monst->info.maxHP > 1 - && !(monst->info.flags & MONST_INVULNERABLE)) { + // hit points + if (monst->info.maxHP > 1 && !(monst->info.flags & MONST_INVULNERABLE)) { - if (monst == &player) { - healthBarColor = redBar; - applyColorAverage(&healthBarColor, &blueBar, min(100, 100 * player.currentHP / player.info.maxHP)); + if (monst == &player) { + healthBarColor = redBar; + applyColorAverage(&healthBarColor, &blueBar, min(100, 100 * player.currentHP / player.info.maxHP)); + } else { + healthBarColor = blueBar; + } + percent = creatureHealthChangePercent(monst); + if (monst->currentHP <= 0) { + strcpy(buf, "Dead"); + } else if (percent != 0) { + strcpy(buf, " Health "); + sprintf(buf2, "(%s%i%%)", percent > 0 ? "+" : "", percent); + strcpy(&(buf[20 - strlen(buf2)]), buf2); + } else { + strcpy(buf, "Health"); + } + printProgressBar(0, y++, buf, monst->currentHP, monst->info.maxHP, &healthBarColor, dim); + } + + if (monst == &player) { + // nutrition + if (player.status[STATUS_NUTRITION] > HUNGER_THRESHOLD) { + printProgressBar(0, y++, "Nutrition", player.status[STATUS_NUTRITION], STOMACH_SIZE, &blueBar, dim); + } else if (player.status[STATUS_NUTRITION] > WEAK_THRESHOLD) { + printProgressBar(0, y++, "Nutrition (Hungry)", player.status[STATUS_NUTRITION], STOMACH_SIZE, &blueBar, dim); + } else if (player.status[STATUS_NUTRITION] > FAINT_THRESHOLD) { + printProgressBar(0, y++, "Nutrition (Weak)", player.status[STATUS_NUTRITION], STOMACH_SIZE, &blueBar, dim); + } else if (player.status[STATUS_NUTRITION] > 0) { + printProgressBar(0, y++, "Nutrition (Faint)", player.status[STATUS_NUTRITION], STOMACH_SIZE, &blueBar, dim); + } else if (y < ROWS - 1) { + printString(" STARVING ", 0, y++, &badMessageColor, &black, NULL); + } + } + + if (!player.status[STATUS_HALLUCINATING] || rogue.playbackOmniscience || monst == &player) { + + for (i = 0; i < NUMBER_OF_STATUS_EFFECTS; i++) { + if (i == STATUS_WEAKENED && monst->status[i] > 0) { + sprintf(buf, "%s%i", statusStrings[STATUS_WEAKENED], monst->weaknessAmount); + printProgressBar(0, y++, buf, monst->status[i], monst->maxStatus[i], &redBar, dim); + } else if (i == STATUS_LEVITATING && monst->status[i] > 0) { + printProgressBar(0, y++, (monst == &player ? "Levitating" : "Flying"), monst->status[i], monst->maxStatus[i], + &redBar, dim); + } else if (i == STATUS_POISONED && monst->status[i] > 0) { + + if (monst->status[i] * monst->poisonAmount >= monst->currentHP) { + strcpy(buf, "Fatal Poison"); } else { - healthBarColor = blueBar; + strcpy(buf, "Poisoned"); } - percent = creatureHealthChangePercent(monst); - if (monst->currentHP <= 0) { - strcpy(buf, "Dead"); - } else if (percent != 0) { - strcpy(buf, " Health "); - sprintf(buf2, "(%s%i%%)", percent > 0 ? "+" : "", percent); - strcpy(&(buf[20 - strlen(buf2)]), buf2); + if (monst->poisonAmount == 1) { + printProgressBar(0, y++, buf, monst->status[i], monst->maxStatus[i], &redBar, dim); } else { - strcpy(buf, "Health"); - } - printProgressBar(0, y++, buf, monst->currentHP, monst->info.maxHP, &healthBarColor, dim); - } - - if (monst == &player) { - // nutrition - if (player.status[STATUS_NUTRITION] > HUNGER_THRESHOLD) { - printProgressBar(0, y++, "Nutrition", player.status[STATUS_NUTRITION], STOMACH_SIZE, &blueBar, dim); - } else if (player.status[STATUS_NUTRITION] > WEAK_THRESHOLD) { - printProgressBar(0, y++, "Nutrition (Hungry)", player.status[STATUS_NUTRITION], STOMACH_SIZE, &blueBar, dim); - } else if (player.status[STATUS_NUTRITION] > FAINT_THRESHOLD) { - printProgressBar(0, y++, "Nutrition (Weak)", player.status[STATUS_NUTRITION], STOMACH_SIZE, &blueBar, dim); - } else if (player.status[STATUS_NUTRITION] > 0) { - printProgressBar(0, y++, "Nutrition (Faint)", player.status[STATUS_NUTRITION], STOMACH_SIZE, &blueBar, dim); - } else if (y < ROWS - 1) { - printString(" STARVING ", 0, y++, &badMessageColor, &black, NULL); + sprintf(buf2, "%s (x%i)", buf, monst->poisonAmount); + printProgressBar(0, y++, buf2, monst->status[i], monst->maxStatus[i], &redBar, dim); } + } else if (statusStrings[i][0] && monst->status[i] > 0) { + printProgressBar(0, y++, statusStrings[i], monst->status[i], monst->maxStatus[i], &redBar, dim); + } } - - if (!player.status[STATUS_HALLUCINATING] || rogue.playbackOmniscience || monst == &player) { - - for (i=0; istatus[i] > 0) { - sprintf(buf, "%s%i", statusStrings[STATUS_WEAKENED], monst->weaknessAmount); - printProgressBar(0, y++, buf, monst->status[i], monst->maxStatus[i], &redBar, dim); - } else if (i == STATUS_LEVITATING && monst->status[i] > 0) { - printProgressBar(0, y++, (monst == &player ? "Levitating" : "Flying"), monst->status[i], monst->maxStatus[i], &redBar, dim); - } else if (i == STATUS_POISONED - && monst->status[i] > 0) { - - - if (monst->status[i] * monst->poisonAmount >= monst->currentHP) { - strcpy(buf, "Fatal Poison"); - } else { - strcpy(buf, "Poisoned"); - } - if (monst->poisonAmount == 1) { - printProgressBar(0, y++, buf, monst->status[i], monst->maxStatus[i], &redBar, dim); - } else { - sprintf(buf2, "%s (x%i)", - buf, - monst->poisonAmount); - printProgressBar(0, y++, buf2, monst->status[i], monst->maxStatus[i], &redBar, dim); - } - } else if (statusStrings[i][0] && monst->status[i] > 0) { - printProgressBar(0, y++, statusStrings[i], monst->status[i], monst->maxStatus[i], &redBar, dim); - } - } - if (posEq(monst->targetCorpseLoc, monst->loc)) { - printProgressBar(0, y++, monsterText[monst->info.monsterID].absorbStatus, monst->corpseAbsorptionCounter, 20, &redBar, dim); - } + if (posEq(monst->targetCorpseLoc, monst->loc)) { + printProgressBar(0, y++, monsterText[monst->info.monsterID].absorbStatus, monst->corpseAbsorptionCounter, 20, + &redBar, dim); } + } - if (monst != &player - && (!(monst->info.flags & MONST_INANIMATE) - || monst->creatureState == MONSTER_ALLY)) { - - if (y < ROWS - 1) { - if (monst->wasNegated - && monst->newPowerCount == monst->totalPowerCount - && y < ROWS - 1 - && (!player.status[STATUS_HALLUCINATING] || rogue.playbackOmniscience )) { - printString(" Negated ", 0, y++, (dim ? &darkPink : &pink), &black, 0); - } - if (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience && y < ROWS - 1) { - printString(hallucinationStrings[rand_range(0, 9)], 0, y++, (dim ? &darkGray : &gray), &black, 0); - } else if (monst->bookkeepingFlags & MB_CAPTIVE && y < ROWS - 1) { - printString(" (Captive) ", 0, y++, (dim ? &darkGray : &gray), &black, 0); - } else if ((monst->info.flags & MONST_RESTRICTED_TO_LIQUID) - && !cellHasTMFlag(monst->loc.x, monst->loc.y, TM_ALLOWS_SUBMERGING)) { - printString(" (Helpless) ", 0, y++, (dim ? &darkGray : &gray), &black, 0); - } else if (monst->creatureState == MONSTER_SLEEPING && y < ROWS - 1) { - printString(" (Sleeping) ", 0, y++, (dim ? &darkGray : &gray), &black, 0); - } else if ((monst->creatureState == MONSTER_ALLY) && y < ROWS - 1) { - printString(" (Ally) ", 0, y++, (dim ? &darkGray : &gray), &black, 0); - } else if (monst->creatureState == MONSTER_FLEEING && y < ROWS - 1) { - printString(" (Fleeing) ", 0, y++, (dim ? &darkGray : &gray), &black, 0); - } else if ((monst->creatureState == MONSTER_WANDERING) && y < ROWS - 1) { - if ((monst->bookkeepingFlags & MB_FOLLOWER) && monst->leader && (monst->leader->info.flags & MONST_IMMOBILE)) { - // follower of an immobile leader -- i.e. a totem - printString(" (Worshiping) ", 0, y++, (dim ? &darkGray : &gray), &black, 0); - } else if ((monst->bookkeepingFlags & MB_FOLLOWER) && monst->leader && (monst->leader->bookkeepingFlags & MB_CAPTIVE)) { - // actually a captor/torturer - printString(" (Guarding) ", 0, y++, (dim ? &darkGray : &gray), &black, 0); - } else { - printString(" (Wandering) ", 0, y++, (dim ? &darkGray : &gray), &black, 0); - } - } else if (monst->ticksUntilTurn > max(0, player.ticksUntilTurn) + player.movementSpeed) { - printString(" (Off balance) ", 0, y++, (dim ? &darkGray : &gray), &black, 0); - } else if ((monst->creatureState == MONSTER_TRACKING_SCENT) && y < ROWS - 1) { - printString(" (Hunting) ", 0, y++, (dim ? &darkGray : &gray), &black, 0); - } - } - } else if (monst == &player) { - if (y < ROWS - 1) { - tempColorEscape[0] = '\0'; - grayColorEscape[0] = '\0'; - if (player.status[STATUS_WEAKENED]) { - tempColor = red; - if (dim) { - applyColorAverage(&tempColor, &black, 50); - } - encodeMessageColor(tempColorEscape, 0, &tempColor); - encodeMessageColor(grayColorEscape, 0, (dim ? &darkGray : &gray)); - } - - if (!rogue.armor || rogue.armor->flags & ITEM_IDENTIFIED || rogue.playbackOmniscience) { - - sprintf(buf, "Str: %s%i%s Armor: %i", - tempColorEscape, - rogue.strength - player.weaknessAmount, - grayColorEscape, - displayedArmorValue()); - } else { - sprintf(buf, "Str: %s%i%s Armor: %i?", - tempColorEscape, - rogue.strength - player.weaknessAmount, - grayColorEscape, - estimatedArmorValue()); - } - //buf[20] = '\0'; - printString(" ", 0, y, &white, &black, 0); - printString(buf, (20 - strLenWithoutEscapes(buf)) / 2, y++, (dim ? &darkGray : &gray), &black, 0); - } - if (y < ROWS - 1 && rogue.gold) { - sprintf(buf, "Gold: %li", rogue.gold); - buf[20] = '\0'; - printString(" ", 0, y, &white, &black, 0); - printString(buf, (20 - strLenWithoutEscapes(buf)) / 2, y++, (dim ? &darkGray : &gray), &black, 0); - } - if (y < ROWS - 1) { - tempColorEscape[0] = '\0'; - grayColorEscape[0] = '\0'; - tempColor = playerInShadowColor; - percent = (rogue.stealthRange - 2) * 100 / 28; - applyColorAverage(&tempColor, &black, percent); - applyColorAugment(&tempColor, &playerInLightColor, percent); - if (dim) { - applyColorAverage(&tempColor, &black, 50); - } - encodeMessageColor(tempColorEscape, 0, &tempColor); - encodeMessageColor(grayColorEscape, 0, (dim ? &darkGray : &gray)); - sprintf(buf, "%sStealth range: %i%s", - tempColorEscape, - rogue.stealthRange, - grayColorEscape); - printString(" ", 0, y, &white, &black, 0); - printString(buf, 1, y++, (dim ? &darkGray : &gray), &black, 0); - } - } + if (monst != &player && (!(monst->info.flags & MONST_INANIMATE) || monst->creatureState == MONSTER_ALLY)) { if (y < ROWS - 1) { - printString(" ", 0, y++, (dim ? &darkGray : &gray), &black, 0); - } - - if (highlight) { - for (i=0; i<20; i++) { - highlightStrength = smoothHiliteGradient(i, 20-1) / 10; - for (j=initialY; j < (y == ROWS - 1 ? y : min(y - 1, ROWS - 1)); j++) { - highlightScreenCell(i, j, &white, highlightStrength); - } + if (monst->wasNegated && monst->newPowerCount == monst->totalPowerCount && y < ROWS - 1 + && (!player.status[STATUS_HALLUCINATING] || rogue.playbackOmniscience)) { + printString(" Negated ", 0, y++, (dim ? &darkPink : &pink), &black, 0); + } + if (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience && y < ROWS - 1) { + printString(hallucinationStrings[rand_range(0, 9)], 0, y++, (dim ? &darkGray : &gray), &black, 0); + } else if (monst->bookkeepingFlags & MB_CAPTIVE && y < ROWS - 1) { + printString(" (Captive) ", 0, y++, (dim ? &darkGray : &gray), &black, 0); + } else if ((monst->info.flags & MONST_RESTRICTED_TO_LIQUID) + && !cellHasTMFlag(monst->loc.x, monst->loc.y, TM_ALLOWS_SUBMERGING)) { + printString(" (Helpless) ", 0, y++, (dim ? &darkGray : &gray), &black, 0); + } else if (monst->creatureState == MONSTER_SLEEPING && y < ROWS - 1) { + printString(" (Sleeping) ", 0, y++, (dim ? &darkGray : &gray), &black, 0); + } else if ((monst->creatureState == MONSTER_ALLY) && y < ROWS - 1) { + printString(" (Ally) ", 0, y++, (dim ? &darkGray : &gray), &black, 0); + } else if (monst->creatureState == MONSTER_FLEEING && y < ROWS - 1) { + printString(" (Fleeing) ", 0, y++, (dim ? &darkGray : &gray), &black, 0); + } else if ((monst->creatureState == MONSTER_WANDERING) && y < ROWS - 1) { + if ((monst->bookkeepingFlags & MB_FOLLOWER) && monst->leader && (monst->leader->info.flags & MONST_IMMOBILE)) { + // follower of an immobile leader -- i.e. a totem + printString(" (Worshiping) ", 0, y++, (dim ? &darkGray : &gray), &black, 0); + } else if ((monst->bookkeepingFlags & MB_FOLLOWER) && monst->leader + && (monst->leader->bookkeepingFlags & MB_CAPTIVE)) { + // actually a captor/torturer + printString(" (Guarding) ", 0, y++, (dim ? &darkGray : &gray), &black, 0); + } else { + printString(" (Wandering) ", 0, y++, (dim ? &darkGray : &gray), &black, 0); } + } else if (monst->ticksUntilTurn > max(0, player.ticksUntilTurn) + player.movementSpeed) { + printString(" (Off balance) ", 0, y++, (dim ? &darkGray : &gray), &black, 0); + } else if ((monst->creatureState == MONSTER_TRACKING_SCENT) && y < ROWS - 1) { + printString(" (Hunting) ", 0, y++, (dim ? &darkGray : &gray), &black, 0); + } } - - restoreRNG; - return y; + } else if (monst == &player) { + if (y < ROWS - 1) { + tempColorEscape[0] = '\0'; + grayColorEscape[0] = '\0'; + if (player.status[STATUS_WEAKENED]) { + tempColor = red; + if (dim) { + applyColorAverage(&tempColor, &black, 50); + } + encodeMessageColor(tempColorEscape, 0, &tempColor); + encodeMessageColor(grayColorEscape, 0, (dim ? &darkGray : &gray)); + } + + if (!rogue.armor || rogue.armor->flags & ITEM_IDENTIFIED || rogue.playbackOmniscience) { + + sprintf(buf, "Str: %s%i%s Armor: %i", tempColorEscape, rogue.strength - player.weaknessAmount, grayColorEscape, + displayedArmorValue()); + } else { + sprintf(buf, "Str: %s%i%s Armor: %i?", tempColorEscape, rogue.strength - player.weaknessAmount, + grayColorEscape, estimatedArmorValue()); + } + // buf[20] = '\0'; + printString(" ", 0, y, &white, &black, 0); + printString(buf, (20 - strLenWithoutEscapes(buf)) / 2, y++, (dim ? &darkGray : &gray), &black, 0); + } + if (y < ROWS - 1 && rogue.gold) { + sprintf(buf, "Gold: %li", rogue.gold); + buf[20] = '\0'; + printString(" ", 0, y, &white, &black, 0); + printString(buf, (20 - strLenWithoutEscapes(buf)) / 2, y++, (dim ? &darkGray : &gray), &black, 0); + } + if (y < ROWS - 1) { + tempColorEscape[0] = '\0'; + grayColorEscape[0] = '\0'; + tempColor = playerInShadowColor; + percent = (rogue.stealthRange - 2) * 100 / 28; + applyColorAverage(&tempColor, &black, percent); + applyColorAugment(&tempColor, &playerInLightColor, percent); + if (dim) { + applyColorAverage(&tempColor, &black, 50); + } + encodeMessageColor(tempColorEscape, 0, &tempColor); + encodeMessageColor(grayColorEscape, 0, (dim ? &darkGray : &gray)); + sprintf(buf, "%sStealth range: %i%s", tempColorEscape, rogue.stealthRange, grayColorEscape); + printString(" ", 0, y, &white, &black, 0); + printString(buf, 1, y++, (dim ? &darkGray : &gray), &black, 0); + } + } + + if (y < ROWS - 1) { + printString(" ", 0, y++, (dim ? &darkGray : &gray), &black, 0); + } + + if (highlight) { + for (i = 0; i < 20; i++) { + highlightStrength = smoothHiliteGradient(i, 20 - 1) / 10; + for (j = initialY; j < (y == ROWS - 1 ? y : min(y - 1, ROWS - 1)); j++) { + highlightScreenCell(i, j, &white, highlightStrength); + } + } + } + + restoreRNG; + return y; } void describeHallucinatedItem(char *buf) { - const unsigned short itemCats[10] = {FOOD, WEAPON, ARMOR, POTION, SCROLL, STAFF, WAND, RING, CHARM, GOLD}; - short cat, kind, maxKinds; - assureCosmeticRNG; - cat = itemCats[rand_range(0, 9)]; - tableForItemCategory(cat); - maxKinds = itemKindCount(cat, 0); - kind = rand_range(0, maxKinds - 1); - describedItemBasedOnParameters(cat, kind, 1, 1, buf); - restoreRNG; + const unsigned short itemCats[10] = {FOOD, WEAPON, ARMOR, POTION, SCROLL, STAFF, WAND, RING, CHARM, GOLD}; + short cat, kind, maxKinds; + assureCosmeticRNG; + cat = itemCats[rand_range(0, 9)]; + tableForItemCategory(cat); + maxKinds = itemKindCount(cat, 0); + kind = rand_range(0, maxKinds - 1); + describedItemBasedOnParameters(cat, kind, 1, 1, buf); + restoreRNG; } // Returns the y-coordinate after the last line printed. short printItemInfo(item *theItem, short y, boolean dim, boolean highlight) { - char name[COLS * 3]; - enum displayGlyph itemChar; - color itemForeColor, itemBackColor; - short initialY, i, j, highlightStrength, lineCount; - boolean inPath; - short oldRNG; - - if (y >= ROWS - 1) { - return ROWS - 1; + char name[COLS * 3]; + enum displayGlyph itemChar; + color itemForeColor, itemBackColor; + short initialY, i, j, highlightStrength, lineCount; + boolean inPath; + short oldRNG; + + if (y >= ROWS - 1) { + return ROWS - 1; + } + + initialY = y; + + oldRNG = rogue.RNG; + rogue.RNG = RNG_COSMETIC; + // assureCosmeticRNG; + + if (y < ROWS - 1) { + // Unhighlight if it's highlighted as part of the path. + inPath = (pmapAt(theItem->loc)->flags & IS_IN_PATH) ? true : false; + pmapAt(theItem->loc)->flags &= ~IS_IN_PATH; + getCellAppearance(theItem->loc.x, theItem->loc.y, &itemChar, &itemForeColor, &itemBackColor); + applyColorBounds(&itemForeColor, 0, 100); + applyColorBounds(&itemBackColor, 0, 100); + if (inPath) { + pmapAt(theItem->loc)->flags |= IS_IN_PATH; } - - initialY = y; - - oldRNG = rogue.RNG; - rogue.RNG = RNG_COSMETIC; - //assureCosmeticRNG; - - if (y < ROWS - 1) { - // Unhighlight if it's highlighted as part of the path. - inPath = (pmapAt(theItem->loc)->flags & IS_IN_PATH) ? true : false; - pmapAt(theItem->loc)->flags &= ~IS_IN_PATH; - getCellAppearance(theItem->loc.x, theItem->loc.y, &itemChar, &itemForeColor, &itemBackColor); - applyColorBounds(&itemForeColor, 0, 100); - applyColorBounds(&itemBackColor, 0, 100); - if (inPath) { - pmapAt(theItem->loc)->flags |= IS_IN_PATH; - } - if (dim) { - applyColorAverage(&itemForeColor, &black, 50); - applyColorAverage(&itemBackColor, &black, 50); - } - plotCharWithColor(itemChar, (windowpos){ 0, y }, &itemForeColor, &itemBackColor); - printString(": ", 1, y, (dim ? &gray : &white), &black, 0); - if (rogue.playbackOmniscience || !player.status[STATUS_HALLUCINATING]) { - itemName(theItem, name, true, true, (dim ? &gray : &white)); - } else { - describeHallucinatedItem(name); - } - upperCase(name); - lineCount = wrapText(NULL, name, 20-3); - for (i=initialY + 1; i <= initialY + lineCount + 1 && i < ROWS - 1; i++) { - printString(" ", 0, i, (dim ? &darkGray : &gray), &black, 0); - } - y = printStringWithWrapping(name, 3, y, 20-3, (dim ? &gray : &white), &black, NULL); // Advances y. + if (dim) { + applyColorAverage(&itemForeColor, &black, 50); + applyColorAverage(&itemBackColor, &black, 50); } + plotCharWithColor(itemChar, (windowpos){0, y}, &itemForeColor, &itemBackColor); + printString(": ", 1, y, (dim ? &gray : &white), &black, 0); + if (rogue.playbackOmniscience || !player.status[STATUS_HALLUCINATING]) { + itemName(theItem, name, true, true, (dim ? &gray : &white)); + } else { + describeHallucinatedItem(name); + } + upperCase(name); + lineCount = wrapText(NULL, name, 20 - 3); + for (i = initialY + 1; i <= initialY + lineCount + 1 && i < ROWS - 1; i++) { + printString(" ", 0, i, (dim ? &darkGray : &gray), &black, 0); + } + y = printStringWithWrapping(name, 3, y, 20 - 3, (dim ? &gray : &white), &black, NULL); // Advances y. + } - if (highlight) { - for (i=0; i<20; i++) { - highlightStrength = smoothHiliteGradient(i, 20-1) / 10; - for (j=initialY; j <= y && j < ROWS - 1; j++) { - highlightScreenCell(i, j, &white, highlightStrength); - } - } + if (highlight) { + for (i = 0; i < 20; i++) { + highlightStrength = smoothHiliteGradient(i, 20 - 1) / 10; + for (j = initialY; j <= y && j < ROWS - 1; j++) { + highlightScreenCell(i, j, &white, highlightStrength); + } } - y += 2; + } + y += 2; - restoreRNG; - return y; + restoreRNG; + return y; } // Returns the y-coordinate after the last line printed. short printTerrainInfo(short x, short y, short py, const char *description, boolean dim, boolean highlight) { - enum displayGlyph displayChar; - color foreColor, backColor; - short initialY, i, j, highlightStrength, lineCount; - boolean inPath; - char name[DCOLS*2]; - color textColor; - short oldRNG; - - if (py >= ROWS - 1) { - return ROWS - 1; + enum displayGlyph displayChar; + color foreColor, backColor; + short initialY, i, j, highlightStrength, lineCount; + boolean inPath; + char name[DCOLS * 2]; + color textColor; + short oldRNG; + + if (py >= ROWS - 1) { + return ROWS - 1; + } + + initialY = py; + + oldRNG = rogue.RNG; + rogue.RNG = RNG_COSMETIC; + // assureCosmeticRNG; + + if (py < ROWS - 1) { + // Unhighlight if it's highlighted as part of the path. + inPath = (pmap[x][y].flags & IS_IN_PATH) ? true : false; + pmap[x][y].flags &= ~IS_IN_PATH; + getCellAppearance(x, y, &displayChar, &foreColor, &backColor); + applyColorBounds(&foreColor, 0, 100); + applyColorBounds(&backColor, 0, 100); + if (inPath) { + pmap[x][y].flags |= IS_IN_PATH; } - - initialY = py; - - oldRNG = rogue.RNG; - rogue.RNG = RNG_COSMETIC; - //assureCosmeticRNG; - - if (py < ROWS - 1) { - // Unhighlight if it's highlighted as part of the path. - inPath = (pmap[x][y].flags & IS_IN_PATH) ? true : false; - pmap[x][y].flags &= ~IS_IN_PATH; - getCellAppearance(x, y, &displayChar, &foreColor, &backColor); - applyColorBounds(&foreColor, 0, 100); - applyColorBounds(&backColor, 0, 100); - if (inPath) { - pmap[x][y].flags |= IS_IN_PATH; - } - if (dim) { - applyColorAverage(&foreColor, &black, 50); - applyColorAverage(&backColor, &black, 50); - } - plotCharWithColor(displayChar, (windowpos){ 0, py }, &foreColor, &backColor); - printString(": ", 1, py, (dim ? &gray : &white), &black, 0); - strcpy(name, description); - upperCase(name); - lineCount = wrapText(NULL, name, 20-3); - for (i=initialY + 1; i <= initialY + lineCount + 1 && i < ROWS - 1; i++) { - printString(" ", 0, i, (dim ? &darkGray : &gray), &black, 0); - } - textColor = flavorTextColor; - if (dim) { - applyColorScalar(&textColor, 50); - } - py = printStringWithWrapping(name, 3, py, 20-3, &textColor, &black, NULL); // Advances y. + if (dim) { + applyColorAverage(&foreColor, &black, 50); + applyColorAverage(&backColor, &black, 50); + } + plotCharWithColor(displayChar, (windowpos){0, py}, &foreColor, &backColor); + printString(": ", 1, py, (dim ? &gray : &white), &black, 0); + strcpy(name, description); + upperCase(name); + lineCount = wrapText(NULL, name, 20 - 3); + for (i = initialY + 1; i <= initialY + lineCount + 1 && i < ROWS - 1; i++) { + printString(" ", 0, i, (dim ? &darkGray : &gray), &black, 0); + } + textColor = flavorTextColor; + if (dim) { + applyColorScalar(&textColor, 50); } + py = printStringWithWrapping(name, 3, py, 20 - 3, &textColor, &black, NULL); // Advances y. + } - if (highlight) { - for (i=0; i<20; i++) { - highlightStrength = smoothHiliteGradient(i, 20-1) / 10; - for (j=initialY; j <= py && j < ROWS - 1; j++) { - highlightScreenCell(i, j, &white, highlightStrength); - } - } + if (highlight) { + for (i = 0; i < 20; i++) { + highlightStrength = smoothHiliteGradient(i, 20 - 1) / 10; + for (j = initialY; j <= py && j < ROWS - 1; j++) { + highlightScreenCell(i, j, &white, highlightStrength); + } } - py += 2; + } + py += 2; - restoreRNG; - return py; + restoreRNG; + return py; } -void rectangularShading(short x, short y, short width, short height, - const color *backColor, short opacity, cellDisplayBuffer dbuf[COLS][ROWS]) { - short i, j, dist; - - assureCosmeticRNG; - for (i=0; i= x && i < x + width - && j >= y && j < y + height) { - dbuf[i][j].opacity = min(100, opacity); - } else { - dist = 0; - dist += max(0, max(x - i, i - x - width + 1)); - dist += max(0, max(y - j, j - y - height + 1)); - dbuf[i][j].opacity = (int) ((opacity - 10) / max(1, dist)); - if (dbuf[i][j].opacity < 3) { - dbuf[i][j].opacity = 0; - } - } - } - } - -// for (i=0; i= x && i < x + width && j >= y && j < y + height) { -// plotCharWithColor(' ', (windowpos){ i, j }, &white, &darkGreen); -// } -// } -// } -// displayMoreSign(); - - restoreRNG; +void rectangularShading(short x, short y, short width, short height, const color *backColor, short opacity, + cellDisplayBuffer dbuf[COLS][ROWS]) { + short i, j, dist; + + assureCosmeticRNG; + for (i = 0; i < COLS; i++) { + for (j = 0; j < ROWS; j++) { + storeColorComponents(dbuf[i][j].backColorComponents, backColor); + + if (i >= x && i < x + width && j >= y && j < y + height) { + dbuf[i][j].opacity = min(100, opacity); + } else { + dist = 0; + dist += max(0, max(x - i, i - x - width + 1)); + dist += max(0, max(y - j, j - y - height + 1)); + dbuf[i][j].opacity = (int)((opacity - 10) / max(1, dist)); + if (dbuf[i][j].opacity < 3) { + dbuf[i][j].opacity = 0; + } + } + } + } + + // for (i=0; i= x && i < x + width && j >= y && j < y + height) { + // plotCharWithColor(' ', (windowpos){ i, j }, &white, &darkGreen); + // } + // } + // } + // displayMoreSign(); + + restoreRNG; } -#define MIN_DEFAULT_INFO_PANEL_WIDTH 33 +#define MIN_DEFAULT_INFO_PANEL_WIDTH 33 // y and width are optional and will be automatically calculated if width <= 0. // Width will automatically be widened if the text would otherwise fall off the bottom of the @@ -4914,177 +4830,173 @@ void rectangularShading(short x, short y, short width, short height, // If buttons are provided, we'll extend the text box downward, re-position the buttons, // run a button input loop and return the result. // (Returns -1 for canceled; otherwise the button index number.) -short printTextBox(char *textBuf, short x, short y, short width, - const color *foreColor, const color *backColor, - cellDisplayBuffer rbuf[COLS][ROWS], - brogueButton *buttons, short buttonCount) { - cellDisplayBuffer dbuf[COLS][ROWS]; - - short x2, y2, lineCount, i, bx, by, padLines; - - if (width <= 0) { - // autocalculate y and width - if (x < DCOLS / 2 - 1) { - x2 = mapToWindowX(x + 10); - width = (DCOLS - x) - 20; - } else { - x2 = mapToWindowX(10); - width = x - 20; - } - y2 = mapToWindowY(2); - - if (width < MIN_DEFAULT_INFO_PANEL_WIDTH) { - x2 -= (MIN_DEFAULT_INFO_PANEL_WIDTH - width) / 2; - width = MIN_DEFAULT_INFO_PANEL_WIDTH; - } - } else { - y2 = y; - x2 = x; - } +short printTextBox(char *textBuf, short x, short y, short width, const color *foreColor, const color *backColor, + cellDisplayBuffer rbuf[COLS][ROWS], brogueButton *buttons, short buttonCount) { + cellDisplayBuffer dbuf[COLS][ROWS]; - while (((lineCount = wrapText(NULL, textBuf, width)) + y2) >= ROWS - 2 && width < COLS-5) { - // While the text doesn't fit and the width doesn't fill the screen, increase the width. - width++; - if (x2 + (width / 2) > COLS / 2) { - // If the horizontal midpoint of the text box is on the right half of the screen, - // move the box one space to the left. - x2--; - } - } + short x2, y2, lineCount, i, bx, by, padLines; - if (buttonCount > 0) { - padLines = 2; - bx = x2 + width; - by = y2 + lineCount + 1; - for (i=0; i 0) { - return buttonInputLoop(buttons, buttonCount, x2, y2, width, by - y2 + 1 + padLines, NULL); - } else { - return -1; - } + x2 = mapToWindowX(10); + width = x - 20; + } + y2 = mapToWindowY(2); + + if (width < MIN_DEFAULT_INFO_PANEL_WIDTH) { + x2 -= (MIN_DEFAULT_INFO_PANEL_WIDTH - width) / 2; + width = MIN_DEFAULT_INFO_PANEL_WIDTH; + } + } else { + y2 = y; + x2 = x; + } + + while (((lineCount = wrapText(NULL, textBuf, width)) + y2) >= ROWS - 2 && width < COLS - 5) { + // While the text doesn't fit and the width doesn't fill the screen, increase the width. + width++; + if (x2 + (width / 2) > COLS / 2) { + // If the horizontal midpoint of the text box is on the right half of the screen, + // move the box one space to the left. + x2--; + } + } + + if (buttonCount > 0) { + padLines = 2; + bx = x2 + width; + by = y2 + lineCount + 1; + for (i = 0; i < buttonCount; i++) { + if (buttons[i].flags & B_DRAW) { + bx -= strLenWithoutEscapes(buttons[i].text) + 2; + buttons[i].x = bx; + buttons[i].y = by; + if (bx < x2) { + // Buttons can wrap to the next line (though are double-spaced). + bx = x2 + width - (strLenWithoutEscapes(buttons[i].text) + 2); + by += 2; + padLines += 2; + buttons[i].x = bx; + buttons[i].y = by; + } + } + } + } else { + padLines = 0; + } + + clearDisplayBuffer(dbuf); + printStringWithWrapping(textBuf, x2, y2, width, foreColor, backColor, dbuf); + rectangularShading(x2, y2, width, lineCount + padLines, backColor, INTERFACE_OPACITY, dbuf); + overlayDisplayBuffer(dbuf, rbuf); + + if (buttonCount > 0) { + return buttonInputLoop(buttons, buttonCount, x2, y2, width, by - y2 + 1 + padLines, NULL); + } else { + return -1; + } } void printMonsterDetails(creature *monst, cellDisplayBuffer rbuf[COLS][ROWS]) { - char textBuf[COLS * 100]; + char textBuf[COLS * 100]; - monsterDetails(textBuf, monst); - printTextBox(textBuf, monst->loc.x, 0, 0, &white, &black, rbuf, NULL, 0); + monsterDetails(textBuf, monst); + printTextBox(textBuf, monst->loc.x, 0, 0, &white, &black, rbuf, NULL, 0); } // Displays the item info box with the dark blue background. // If includeButtons is true, we include buttons for item actions. // Returns the key of an action to take, if any; otherwise -1. -unsigned long printCarriedItemDetails(item *theItem, - short x, short y, short width, - boolean includeButtons, +unsigned long printCarriedItemDetails(item *theItem, short x, short y, short width, boolean includeButtons, cellDisplayBuffer rbuf[COLS][ROWS]) { - char textBuf[COLS * 100], goldColorEscape[5] = "", whiteColorEscape[5] = ""; - brogueButton buttons[20] = {{{0}}}; - short b; - - itemDetails(textBuf, theItem); + char textBuf[COLS * 100], goldColorEscape[5] = "", whiteColorEscape[5] = ""; + brogueButton buttons[20] = {{{0}}}; + short b; - for (b=0; b<20; b++) { - initializeButton(&(buttons[b])); - buttons[b].flags |= B_WIDE_CLICK_AREA; - } - - b = 0; - if (includeButtons) { - encodeMessageColor(goldColorEscape, 0, KEYBOARD_LABELS ? &yellow : &white); - encodeMessageColor(whiteColorEscape, 0, &white); + itemDetails(textBuf, theItem); - if (theItem->category & (FOOD | SCROLL | POTION | WAND | STAFF | CHARM)) { - sprintf(buttons[b].text, " %sa%spply ", goldColorEscape, whiteColorEscape); - buttons[b].hotkey[0] = APPLY_KEY; - b++; - } - if (theItem->category & (ARMOR | WEAPON | RING)) { - if (theItem->flags & ITEM_EQUIPPED) { - sprintf(buttons[b].text, " %sr%semove ", goldColorEscape, whiteColorEscape); - buttons[b].hotkey[0] = UNEQUIP_KEY; - b++; - } else { - sprintf(buttons[b].text, " %se%squip ", goldColorEscape, whiteColorEscape); - buttons[b].hotkey[0] = EQUIP_KEY; - b++; - } - } - sprintf(buttons[b].text, " %sd%srop ", goldColorEscape, whiteColorEscape); - buttons[b].hotkey[0] = DROP_KEY; - b++; + for (b = 0; b < 20; b++) { + initializeButton(&(buttons[b])); + buttons[b].flags |= B_WIDE_CLICK_AREA; + } - sprintf(buttons[b].text, " %st%shrow ", goldColorEscape, whiteColorEscape); - buttons[b].hotkey[0] = THROW_KEY; - b++; - - if (itemCanBeCalled(theItem)) { - sprintf(buttons[b].text, " %sc%sall ", goldColorEscape, whiteColorEscape); - buttons[b].hotkey[0] = CALL_KEY; - b++; - } - - if (KEYBOARD_LABELS) { - sprintf(buttons[b].text, " %sR%selabel ", goldColorEscape, whiteColorEscape); - buttons[b].hotkey[0] = RELABEL_KEY; - b++; - } + b = 0; + if (includeButtons) { + encodeMessageColor(goldColorEscape, 0, KEYBOARD_LABELS ? &yellow : &white); + encodeMessageColor(whiteColorEscape, 0, &white); - // Add invisible previous and next buttons, so up and down arrows can page through items. - // Previous - buttons[b].flags = B_ENABLED; // clear everything else - buttons[b].hotkey[0] = UP_KEY; - buttons[b].hotkey[1] = NUMPAD_8; - buttons[b].hotkey[2] = UP_ARROW; + if (theItem->category & (FOOD | SCROLL | POTION | WAND | STAFF | CHARM)) { + sprintf(buttons[b].text, " %sa%spply ", goldColorEscape, whiteColorEscape); + buttons[b].hotkey[0] = APPLY_KEY; + b++; + } + if (theItem->category & (ARMOR | WEAPON | RING)) { + if (theItem->flags & ITEM_EQUIPPED) { + sprintf(buttons[b].text, " %sr%semove ", goldColorEscape, whiteColorEscape); + buttons[b].hotkey[0] = UNEQUIP_KEY; b++; - // Next - buttons[b].flags = B_ENABLED; // clear everything else - buttons[b].hotkey[0] = DOWN_KEY; - buttons[b].hotkey[1] = NUMPAD_2; - buttons[b].hotkey[2] = DOWN_ARROW; + } else { + sprintf(buttons[b].text, " %se%squip ", goldColorEscape, whiteColorEscape); + buttons[b].hotkey[0] = EQUIP_KEY; b++; - } - b = printTextBox(textBuf, x, y, width, &white, &interfaceBoxColor, rbuf, buttons, b); - - if (!includeButtons) { - waitForKeystrokeOrMouseClick(); - return -1; - } + } + } + sprintf(buttons[b].text, " %sd%srop ", goldColorEscape, whiteColorEscape); + buttons[b].hotkey[0] = DROP_KEY; + b++; + + sprintf(buttons[b].text, " %st%shrow ", goldColorEscape, whiteColorEscape); + buttons[b].hotkey[0] = THROW_KEY; + b++; + + if (itemCanBeCalled(theItem)) { + sprintf(buttons[b].text, " %sc%sall ", goldColorEscape, whiteColorEscape); + buttons[b].hotkey[0] = CALL_KEY; + b++; + } + + if (KEYBOARD_LABELS) { + sprintf(buttons[b].text, " %sR%selabel ", goldColorEscape, whiteColorEscape); + buttons[b].hotkey[0] = RELABEL_KEY; + b++; + } + + // Add invisible previous and next buttons, so up and down arrows can page through items. + // Previous + buttons[b].flags = B_ENABLED; // clear everything else + buttons[b].hotkey[0] = UP_KEY; + buttons[b].hotkey[1] = NUMPAD_8; + buttons[b].hotkey[2] = UP_ARROW; + b++; + // Next + buttons[b].flags = B_ENABLED; // clear everything else + buttons[b].hotkey[0] = DOWN_KEY; + buttons[b].hotkey[1] = NUMPAD_2; + buttons[b].hotkey[2] = DOWN_ARROW; + b++; + } + b = printTextBox(textBuf, x, y, width, &white, &interfaceBoxColor, rbuf, buttons, b); + + if (!includeButtons) { + waitForKeystrokeOrMouseClick(); + return -1; + } - if (b >= 0) { - return buttons[b].hotkey[0]; - } else { - return -1; - } + if (b >= 0) { + return buttons[b].hotkey[0]; + } else { + return -1; + } } // Returns true if an action was taken. void printFloorItemDetails(item *theItem, cellDisplayBuffer rbuf[COLS][ROWS]) { - char textBuf[COLS * 100]; + char textBuf[COLS * 100]; - itemDetails(textBuf, theItem); - printTextBox(textBuf, theItem->loc.x, 0, 0, &white, &black, rbuf, NULL, 0); + itemDetails(textBuf, theItem); + printTextBox(textBuf, theItem->loc.x, 0, 0, &white, &black, rbuf, NULL, 0); } diff --git a/src/brogue/Items.c b/src/brogue/Items.c index 92de7906..976cc59b 100644 --- a/src/brogue/Items.c +++ b/src/brogue/Items.c @@ -21,7 +21,6 @@ * along with this program. If not, see . */ - #include "Rogue.h" #include "GlobalsBase.h" #include "Globals.h" @@ -32,3125 +31,3036 @@ #define MAGIC_POLARITY_ANY 0 item *initializeItem() { - short i; - item *theItem; - - theItem = (item *) malloc(sizeof(item)); - memset(theItem, '\0', sizeof(item) ); - - theItem->category = 0; - theItem->kind = 0; - theItem->flags = 0; - theItem->displayChar = '&'; - theItem->foreColor = &itemColor; - theItem->inventoryColor = &white; - theItem->inventoryLetter = '\0'; - theItem->armor = 0; - theItem->strengthRequired = 0; - theItem->enchant1 = 0; - theItem->enchant2 = 0; - theItem->timesEnchanted = 0; - theItem->vorpalEnemy = 0; - theItem->charges = 0; - theItem->quantity = 1; - theItem->quiverNumber = 0; - theItem->originDepth = 0; - theItem->inscription[0] = '\0'; - theItem->lastUsed[0] = 0; - theItem->lastUsed[1] = 0; - theItem->lastUsed[2] = 0; - theItem->nextItem = NULL; - - for (i=0; i < KEY_ID_MAXIMUM; i++) { - theItem->keyLoc[i].x = 0; - theItem->keyLoc[i].y = 0; - theItem->keyLoc[i].machine = 0; - theItem->keyLoc[i].disposableHere = false; - } - return theItem; + short i; + item *theItem; + + theItem = (item *)malloc(sizeof(item)); + memset(theItem, '\0', sizeof(item)); + + theItem->category = 0; + theItem->kind = 0; + theItem->flags = 0; + theItem->displayChar = '&'; + theItem->foreColor = &itemColor; + theItem->inventoryColor = &white; + theItem->inventoryLetter = '\0'; + theItem->armor = 0; + theItem->strengthRequired = 0; + theItem->enchant1 = 0; + theItem->enchant2 = 0; + theItem->timesEnchanted = 0; + theItem->vorpalEnemy = 0; + theItem->charges = 0; + theItem->quantity = 1; + theItem->quiverNumber = 0; + theItem->originDepth = 0; + theItem->inscription[0] = '\0'; + theItem->lastUsed[0] = 0; + theItem->lastUsed[1] = 0; + theItem->lastUsed[2] = 0; + theItem->nextItem = NULL; + + for (i = 0; i < KEY_ID_MAXIMUM; i++) { + theItem->keyLoc[i].x = 0; + theItem->keyLoc[i].y = 0; + theItem->keyLoc[i].machine = 0; + theItem->keyLoc[i].disposableHere = false; + } + return theItem; } // Allocates space, generates a specified item (or random category/kind if -1) // and returns a pointer to that item. The item is not given a location here // and is not inserted into the item chain! item *generateItem(unsigned short theCategory, short theKind) { - item *theItem = initializeItem(); - makeItemInto(theItem, theCategory, theKind); - return theItem; + item *theItem = initializeItem(); + makeItemInto(theItem, theCategory, theKind); + return theItem; } unsigned long pickItemCategory(unsigned long theCategory) { - short i, sum, randIndex; - unsigned short correspondingCategories[13] = {GOLD, SCROLL, POTION, STAFF, WAND, WEAPON, ARMOR, FOOD, RING, CHARM, AMULET, GEM, KEY}; + short i, sum, randIndex; + unsigned short correspondingCategories[13] + = {GOLD, SCROLL, POTION, STAFF, WAND, WEAPON, ARMOR, FOOD, RING, CHARM, AMULET, GEM, KEY}; - sum = 0; + sum = 0; - for (i=0; i<13; i++) { - if (theCategory <= 0 || theCategory & correspondingCategories[i]) { - sum += itemGenerationProbabilities[i]; - } + for (i = 0; i < 13; i++) { + if (theCategory <= 0 || theCategory & correspondingCategories[i]) { + sum += itemGenerationProbabilities[i]; } + } - if (sum == 0) { - return theCategory; // e.g. when you pass in AMULET or GEM, since they have no frequency - } + if (sum == 0) { + return theCategory; // e.g. when you pass in AMULET or GEM, since they have no frequency + } - randIndex = rand_range(1, sum); + randIndex = rand_range(1, sum); - for (i=0; ; i++) { - if (theCategory <= 0 || theCategory & correspondingCategories[i]) { - if (randIndex <= itemGenerationProbabilities[i]) { - return correspondingCategories[i]; - } - randIndex -= itemGenerationProbabilities[i]; - } + for (i = 0;; i++) { + if (theCategory <= 0 || theCategory & correspondingCategories[i]) { + if (randIndex <= itemGenerationProbabilities[i]) { + return correspondingCategories[i]; + } + randIndex -= itemGenerationProbabilities[i]; } + } } // Sets an item to the given type and category (or chooses randomly if -1) with all other stats item *makeItemInto(item *theItem, unsigned long itemCategory, short itemKind) { - const itemTable *theEntry = NULL; - - if (itemCategory <= 0) { - itemCategory = ALL_ITEMS; - } + const itemTable *theEntry = NULL; - itemCategory = pickItemCategory(itemCategory); + if (itemCategory <= 0) { + itemCategory = ALL_ITEMS; + } - theItem->category = itemCategory; - - switch (itemCategory) { - - case FOOD: - if (itemKind < 0) { - itemKind = chooseKind(foodTable, NUMBER_FOOD_KINDS); - } - theEntry = &foodTable[itemKind]; - theItem->displayChar = G_FOOD; - theItem->flags |= ITEM_IDENTIFIED; - break; + itemCategory = pickItemCategory(itemCategory); - case WEAPON: - if (itemKind < 0) { - itemKind = chooseKind(weaponTable, NUMBER_WEAPON_KINDS); - } - theEntry = &weaponTable[itemKind]; - theItem->damage = weaponTable[itemKind].range; - theItem->strengthRequired = weaponTable[itemKind].strengthRequired; - theItem->displayChar = G_WEAPON; - - switch (itemKind) { - case DAGGER: - theItem->flags |= ITEM_SNEAK_ATTACK_BONUS; - break; - case MACE: - case HAMMER: - theItem->flags |= ITEM_ATTACKS_STAGGER; - break; - case WHIP: - theItem->flags |= ITEM_ATTACKS_EXTEND; - break; - case RAPIER: - theItem->flags |= (ITEM_ATTACKS_QUICKLY | ITEM_LUNGE_ATTACKS); - break; - case FLAIL: - theItem->flags |= ITEM_PASS_ATTACKS; - break; - case SPEAR: - case PIKE: - theItem->flags |= ITEM_ATTACKS_PENETRATE; - break; - case AXE: - case WAR_AXE: - theItem->flags |= ITEM_ATTACKS_ALL_ADJACENT; - break; - default: - break; - } + theItem->category = itemCategory; - if (rand_percent(40)) { - theItem->enchant1 += rand_range(1, 3); - if (rand_percent(50)) { - // cursed - theItem->enchant1 *= -1; - theItem->flags |= ITEM_CURSED; - if (rand_percent(33)) { // give it a bad runic - theItem->enchant2 = rand_range(NUMBER_GOOD_WEAPON_ENCHANT_KINDS, NUMBER_WEAPON_RUNIC_KINDS - 1); - theItem->flags |= ITEM_RUNIC; - } - } else if (rand_range(3, 10) - * ((theItem->flags & ITEM_ATTACKS_STAGGER) ? 2 : 1) - / ((theItem->flags & ITEM_ATTACKS_QUICKLY) ? 2 : 1) - / ((theItem->flags & ITEM_ATTACKS_EXTEND) ? 2 : 1) - > theItem->damage.lowerBound) { - // give it a good runic; lower damage items are more likely to be runic - theItem->enchant2 = rand_range(0, NUMBER_GOOD_WEAPON_ENCHANT_KINDS - 1); - theItem->flags |= ITEM_RUNIC; - if (theItem->enchant2 == W_SLAYING) { - theItem->vorpalEnemy = chooseVorpalEnemy(); - } - } else { - while (rand_percent(10)) { - theItem->enchant1++; - } - } - } - if (itemKind == DART || itemKind == INCENDIARY_DART || itemKind == JAVELIN) { - if (itemKind == INCENDIARY_DART) { - theItem->quantity = rand_range(3, 6); - } else { - theItem->quantity = rand_range(5, 18); - } - theItem->quiverNumber = rand_range(1, 60000); - theItem->flags &= ~(ITEM_CURSED | ITEM_RUNIC); // throwing weapons can't be cursed or runic - theItem->enchant1 = 0; // throwing weapons can't be magical - } - theItem->charges = gameConst->weaponKillsToAutoID; // kill 20 enemies to auto-identify - break; + switch (itemCategory) { - case ARMOR: - if (itemKind < 0) { - itemKind = chooseKind(armorTable, NUMBER_ARMOR_KINDS); - } - theEntry = &armorTable[itemKind]; - theItem->armor = randClump(armorTable[itemKind].range); - theItem->strengthRequired = armorTable[itemKind].strengthRequired; - theItem->displayChar = G_ARMOR; - theItem->charges = gameConst->armorDelayToAutoID; // this many turns until it reveals its enchants and whether runic - if (rand_percent(40)) { - theItem->enchant1 += rand_range(1, 3); - if (rand_percent(50)) { - // cursed - theItem->enchant1 *= -1; - theItem->flags |= ITEM_CURSED; - if (rand_percent(33)) { // give it a bad runic - theItem->enchant2 = rand_range(NUMBER_GOOD_ARMOR_ENCHANT_KINDS, NUMBER_ARMOR_ENCHANT_KINDS - 1); - theItem->flags |= ITEM_RUNIC; - } - } else if (rand_range(0, 95) > theItem->armor) { // give it a good runic - theItem->enchant2 = rand_range(0, NUMBER_GOOD_ARMOR_ENCHANT_KINDS - 1); - theItem->flags |= ITEM_RUNIC; - if (theItem->enchant2 == A_IMMUNITY) { - theItem->vorpalEnemy = chooseVorpalEnemy(); - } - } else { - while (rand_percent(10)) { - theItem->enchant1++; - } - } - } - break; - case SCROLL: - if (itemKind < 0) { - itemKind = chooseKind(scrollTable, gameConst->numberScrollKinds); - } - theEntry = &scrollTable[itemKind]; - theItem->displayChar = G_SCROLL; - theItem->flags |= ITEM_FLAMMABLE; - break; - case POTION: - if (itemKind < 0) { - itemKind = chooseKind(potionTable, gameConst->numberPotionKinds); - } - theEntry = &potionTable[itemKind]; - theItem->displayChar = G_POTION; - break; - case STAFF: - if (itemKind < 0) { - itemKind = chooseKind(staffTable, NUMBER_STAFF_KINDS); - } - theEntry = &staffTable[itemKind]; - theItem->displayChar = G_STAFF; - theItem->charges = 2; - if (rand_percent(50)) { - theItem->charges++; - if (rand_percent(15)) { - theItem->charges++; - while (rand_percent(10)) { - theItem->charges++; - } - } - } - theItem->enchant1 = theItem->charges; - theItem->enchant2 = (itemKind == STAFF_BLINKING || itemKind == STAFF_OBSTRUCTION ? 1000 : 500); // start with no recharging mojo - break; - case WAND: - if (itemKind < 0) { - itemKind = chooseKind(wandTable, gameConst->numberWandKinds); - } - theEntry = &(wandTable[itemKind]); - theItem->displayChar = G_WAND; - theItem->charges = randClump(wandTable[itemKind].range); - break; - case RING: - if (itemKind < 0) { - itemKind = chooseKind(ringTable, NUMBER_RING_KINDS); - } - theEntry = &ringTable[itemKind]; - theItem->displayChar = G_RING; - theItem->enchant1 = randClump(ringTable[itemKind].range); - theItem->charges = gameConst->ringDelayToAutoID; // how many turns of being worn until it auto-identifies - if (rand_percent(16)) { - // cursed - theItem->enchant1 *= -1; - theItem->flags |= ITEM_CURSED; - } else { - while (rand_percent(10)) { - theItem->enchant1++; - } - } - break; - case CHARM: - if (itemKind < 0) { - itemKind = chooseKind(charmTable, gameConst->numberCharmKinds); - } - theItem->displayChar = G_CHARM; - theItem->charges = 0; // Charms are initially ready for use. - theItem->enchant1 = randClump(charmTable[itemKind].range); - while (rand_percent(7)) { - theItem->enchant1++; - } - theItem->flags |= ITEM_IDENTIFIED; - break; - case GOLD: - theEntry = NULL; - theItem->displayChar = G_GOLD; - theItem->quantity = rand_range(50 + rogue.depthLevel * 10 * gameConst->depthAccelerator, 100 + rogue.depthLevel * 15 * gameConst->depthAccelerator); - break; - case AMULET: - theEntry = NULL; - theItem->displayChar = G_AMULET; - itemKind = 0; - theItem->flags |= ITEM_IDENTIFIED; - break; - case GEM: - theEntry = NULL; - theItem->displayChar = G_GEM; - itemKind = 0; - theItem->flags |= ITEM_IDENTIFIED; - break; - case KEY: - theEntry = NULL; - theItem->displayChar = G_KEY; - theItem->flags |= ITEM_IDENTIFIED; - break; - default: - theEntry = NULL; - message("something has gone terribly wrong!", REQUIRE_ACKNOWLEDGMENT); - break; + case FOOD: + if (itemKind < 0) { + itemKind = chooseKind(foodTable, NUMBER_FOOD_KINDS); } - if (theItem - && !(theItem->flags & ITEM_IDENTIFIED) - && (!(theItem->category & (POTION | SCROLL) ) || (theEntry && !theEntry->identified))) { - - theItem->flags |= ITEM_CAN_BE_IDENTIFIED; + theEntry = &foodTable[itemKind]; + theItem->displayChar = G_FOOD; + theItem->flags |= ITEM_IDENTIFIED; + break; + + case WEAPON: + if (itemKind < 0) { + itemKind = chooseKind(weaponTable, NUMBER_WEAPON_KINDS); + } + theEntry = &weaponTable[itemKind]; + theItem->damage = weaponTable[itemKind].range; + theItem->strengthRequired = weaponTable[itemKind].strengthRequired; + theItem->displayChar = G_WEAPON; + + switch (itemKind) { + case DAGGER: + theItem->flags |= ITEM_SNEAK_ATTACK_BONUS; + break; + case MACE: + case HAMMER: + theItem->flags |= ITEM_ATTACKS_STAGGER; + break; + case WHIP: + theItem->flags |= ITEM_ATTACKS_EXTEND; + break; + case RAPIER: + theItem->flags |= (ITEM_ATTACKS_QUICKLY | ITEM_LUNGE_ATTACKS); + break; + case FLAIL: + theItem->flags |= ITEM_PASS_ATTACKS; + break; + case SPEAR: + case PIKE: + theItem->flags |= ITEM_ATTACKS_PENETRATE; + break; + case AXE: + case WAR_AXE: + theItem->flags |= ITEM_ATTACKS_ALL_ADJACENT; + break; + default: + break; + } + + if (rand_percent(40)) { + theItem->enchant1 += rand_range(1, 3); + if (rand_percent(50)) { + // cursed + theItem->enchant1 *= -1; + theItem->flags |= ITEM_CURSED; + if (rand_percent(33)) { // give it a bad runic + theItem->enchant2 = rand_range(NUMBER_GOOD_WEAPON_ENCHANT_KINDS, NUMBER_WEAPON_RUNIC_KINDS - 1); + theItem->flags |= ITEM_RUNIC; + } + } else if (rand_range(3, 10) * ((theItem->flags & ITEM_ATTACKS_STAGGER) ? 2 : 1) + / ((theItem->flags & ITEM_ATTACKS_QUICKLY) ? 2 : 1) + / ((theItem->flags & ITEM_ATTACKS_EXTEND) ? 2 : 1) + > theItem->damage.lowerBound) { + // give it a good runic; lower damage items are more likely to be runic + theItem->enchant2 = rand_range(0, NUMBER_GOOD_WEAPON_ENCHANT_KINDS - 1); + theItem->flags |= ITEM_RUNIC; + if (theItem->enchant2 == W_SLAYING) { + theItem->vorpalEnemy = chooseVorpalEnemy(); + } + } else { + while (rand_percent(10)) { + theItem->enchant1++; + } + } + } + if (itemKind == DART || itemKind == INCENDIARY_DART || itemKind == JAVELIN) { + if (itemKind == INCENDIARY_DART) { + theItem->quantity = rand_range(3, 6); + } else { + theItem->quantity = rand_range(5, 18); + } + theItem->quiverNumber = rand_range(1, 60000); + theItem->flags &= ~(ITEM_CURSED | ITEM_RUNIC); // throwing weapons can't be cursed or runic + theItem->enchant1 = 0; // throwing weapons can't be magical + } + theItem->charges = gameConst->weaponKillsToAutoID; // kill 20 enemies to auto-identify + break; + + case ARMOR: + if (itemKind < 0) { + itemKind = chooseKind(armorTable, NUMBER_ARMOR_KINDS); + } + theEntry = &armorTable[itemKind]; + theItem->armor = randClump(armorTable[itemKind].range); + theItem->strengthRequired = armorTable[itemKind].strengthRequired; + theItem->displayChar = G_ARMOR; + theItem->charges = gameConst->armorDelayToAutoID; // this many turns until it reveals its enchants and whether runic + if (rand_percent(40)) { + theItem->enchant1 += rand_range(1, 3); + if (rand_percent(50)) { + // cursed + theItem->enchant1 *= -1; + theItem->flags |= ITEM_CURSED; + if (rand_percent(33)) { // give it a bad runic + theItem->enchant2 = rand_range(NUMBER_GOOD_ARMOR_ENCHANT_KINDS, NUMBER_ARMOR_ENCHANT_KINDS - 1); + theItem->flags |= ITEM_RUNIC; + } + } else if (rand_range(0, 95) > theItem->armor) { // give it a good runic + theItem->enchant2 = rand_range(0, NUMBER_GOOD_ARMOR_ENCHANT_KINDS - 1); + theItem->flags |= ITEM_RUNIC; + if (theItem->enchant2 == A_IMMUNITY) { + theItem->vorpalEnemy = chooseVorpalEnemy(); + } + } else { + while (rand_percent(10)) { + theItem->enchant1++; + } + } + } + break; + case SCROLL: + if (itemKind < 0) { + itemKind = chooseKind(scrollTable, gameConst->numberScrollKinds); + } + theEntry = &scrollTable[itemKind]; + theItem->displayChar = G_SCROLL; + theItem->flags |= ITEM_FLAMMABLE; + break; + case POTION: + if (itemKind < 0) { + itemKind = chooseKind(potionTable, gameConst->numberPotionKinds); + } + theEntry = &potionTable[itemKind]; + theItem->displayChar = G_POTION; + break; + case STAFF: + if (itemKind < 0) { + itemKind = chooseKind(staffTable, NUMBER_STAFF_KINDS); + } + theEntry = &staffTable[itemKind]; + theItem->displayChar = G_STAFF; + theItem->charges = 2; + if (rand_percent(50)) { + theItem->charges++; + if (rand_percent(15)) { + theItem->charges++; + while (rand_percent(10)) { + theItem->charges++; + } + } + } + theItem->enchant1 = theItem->charges; + theItem->enchant2 + = (itemKind == STAFF_BLINKING || itemKind == STAFF_OBSTRUCTION ? 1000 : 500); // start with no recharging mojo + break; + case WAND: + if (itemKind < 0) { + itemKind = chooseKind(wandTable, gameConst->numberWandKinds); + } + theEntry = &(wandTable[itemKind]); + theItem->displayChar = G_WAND; + theItem->charges = randClump(wandTable[itemKind].range); + break; + case RING: + if (itemKind < 0) { + itemKind = chooseKind(ringTable, NUMBER_RING_KINDS); + } + theEntry = &ringTable[itemKind]; + theItem->displayChar = G_RING; + theItem->enchant1 = randClump(ringTable[itemKind].range); + theItem->charges = gameConst->ringDelayToAutoID; // how many turns of being worn until it auto-identifies + if (rand_percent(16)) { + // cursed + theItem->enchant1 *= -1; + theItem->flags |= ITEM_CURSED; + } else { + while (rand_percent(10)) { + theItem->enchant1++; + } + } + break; + case CHARM: + if (itemKind < 0) { + itemKind = chooseKind(charmTable, gameConst->numberCharmKinds); } - theItem->kind = itemKind; + theItem->displayChar = G_CHARM; + theItem->charges = 0; // Charms are initially ready for use. + theItem->enchant1 = randClump(charmTable[itemKind].range); + while (rand_percent(7)) { + theItem->enchant1++; + } + theItem->flags |= ITEM_IDENTIFIED; + break; + case GOLD: + theEntry = NULL; + theItem->displayChar = G_GOLD; + theItem->quantity = rand_range(50 + rogue.depthLevel * 10 * gameConst->depthAccelerator, + 100 + rogue.depthLevel * 15 * gameConst->depthAccelerator); + break; + case AMULET: + theEntry = NULL; + theItem->displayChar = G_AMULET; + itemKind = 0; + theItem->flags |= ITEM_IDENTIFIED; + break; + case GEM: + theEntry = NULL; + theItem->displayChar = G_GEM; + itemKind = 0; + theItem->flags |= ITEM_IDENTIFIED; + break; + case KEY: + theEntry = NULL; + theItem->displayChar = G_KEY; + theItem->flags |= ITEM_IDENTIFIED; + break; + default: + theEntry = NULL; + message("something has gone terribly wrong!", REQUIRE_ACKNOWLEDGMENT); + break; + } + if (theItem && !(theItem->flags & ITEM_IDENTIFIED) + && (!(theItem->category & (POTION | SCROLL)) || (theEntry && !theEntry->identified))) { - return theItem; + theItem->flags |= ITEM_CAN_BE_IDENTIFIED; + } + theItem->kind = itemKind; + + return theItem; } short chooseKind(const itemTable *theTable, short numKinds) { - short i, totalFrequencies = 0, randomFrequency; - for (i=0; i theTable[i].frequency; i++) { - randomFrequency -= max(0, theTable[i].frequency); - } - return i; + short i, totalFrequencies = 0, randomFrequency; + for (i = 0; i < numKinds; i++) { + totalFrequencies += max(0, theTable[i].frequency); + } + randomFrequency = rand_range(1, totalFrequencies); + for (i = 0; randomFrequency > theTable[i].frequency; i++) { + randomFrequency -= max(0, theTable[i].frequency); + } + return i; } // Places an item at (x,y) if provided or else a random location if they're 0. Inserts item into the floor list. item *placeItem(item *theItem, short x, short y) { - enum dungeonLayers layer; - char theItemName[DCOLS], buf[DCOLS]; - if (x <= 0 || y <= 0) { - pos loc; - randomMatchingLocation(&loc.x, &loc.y, FLOOR, NOTHING, -1); - theItem->loc = loc; - } else { - theItem->loc.x = x; - theItem->loc.y = y; - } - - removeItemFromChain(theItem, floorItems); // just in case; double-placing an item will result in game-crashing loops in the item list - addItemToChain(theItem, floorItems); - pmapAt(theItem->loc)->flags |= HAS_ITEM; - if ((theItem->flags & ITEM_MAGIC_DETECTED) && itemMagicPolarity(theItem)) { - pmapAt(theItem->loc)->flags |= ITEM_DETECTED; + enum dungeonLayers layer; + char theItemName[DCOLS], buf[DCOLS]; + if (x <= 0 || y <= 0) { + pos loc; + randomMatchingLocation(&loc.x, &loc.y, FLOOR, NOTHING, -1); + theItem->loc = loc; + } else { + theItem->loc.x = x; + theItem->loc.y = y; + } + + removeItemFromChain( + theItem, floorItems); // just in case; double-placing an item will result in game-crashing loops in the item list + addItemToChain(theItem, floorItems); + pmapAt(theItem->loc)->flags |= HAS_ITEM; + if ((theItem->flags & ITEM_MAGIC_DETECTED) && itemMagicPolarity(theItem)) { + pmapAt(theItem->loc)->flags |= ITEM_DETECTED; + } + if (cellHasTerrainFlag(x, y, T_IS_DF_TRAP) && !cellHasTerrainFlag(x, y, T_MOVES_ITEMS) + && !(pmap[x][y].flags & PRESSURE_PLATE_DEPRESSED)) { + + pmap[x][y].flags |= PRESSURE_PLATE_DEPRESSED; + if (playerCanSee(x, y)) { + if (cellHasTMFlag(x, y, TM_IS_SECRET)) { + discover(x, y); + refreshDungeonCell(x, y); + } + itemName(theItem, theItemName, false, false, NULL); + sprintf(buf, "a pressure plate clicks underneath the %s!", theItemName); + message(buf, REQUIRE_ACKNOWLEDGMENT); } - if (cellHasTerrainFlag(x, y, T_IS_DF_TRAP) - && !cellHasTerrainFlag(x, y, T_MOVES_ITEMS) - && !(pmap[x][y].flags & PRESSURE_PLATE_DEPRESSED)) { - - pmap[x][y].flags |= PRESSURE_PLATE_DEPRESSED; - if (playerCanSee(x, y)) { - if (cellHasTMFlag(x, y, TM_IS_SECRET)) { - discover(x, y); - refreshDungeonCell(x, y); - } - itemName(theItem, theItemName, false, false, NULL); - sprintf(buf, "a pressure plate clicks underneath the %s!", theItemName); - message(buf, REQUIRE_ACKNOWLEDGMENT); - } - for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) { - if (tileCatalog[pmap[x][y].layers[layer]].flags & T_IS_DF_TRAP) { - spawnDungeonFeature(x, y, &(dungeonFeatureCatalog[tileCatalog[pmap[x][y].layers[layer]].fireType]), true, false); - promoteTile(x, y, layer, false); - } - } + for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) { + if (tileCatalog[pmap[x][y].layers[layer]].flags & T_IS_DF_TRAP) { + spawnDungeonFeature(x, y, &(dungeonFeatureCatalog[tileCatalog[pmap[x][y].layers[layer]].fireType]), true, + false); + promoteTile(x, y, layer, false); + } } - return theItem; + } + return theItem; } void fillItemSpawnHeatMap(unsigned short heatMap[DCOLS][DROWS], unsigned short heatLevel, short x, short y) { - enum directions dir; - short newX, newY; - - if (pmap[x][y].layers[DUNGEON] == DOOR) { - heatLevel += 10; - } else if (pmap[x][y].layers[DUNGEON] == SECRET_DOOR) { - heatLevel += 3000; - } - if (heatMap[x][y] > heatLevel) { - heatMap[x][y] = heatLevel; - } - for (dir = 0; dir < 4; dir++) { - newX = x + nbDirs[dir][0]; - newY = y + nbDirs[dir][1]; - if (coordinatesAreInMap(newX, newY) - && !cellHasTerrainFlag(newX, newY, T_IS_DEEP_WATER | T_LAVA_INSTA_DEATH | T_AUTO_DESCENT) - && isPassableOrSecretDoor(newX, newY) - && heatLevel < heatMap[newX][newY]) { - - fillItemSpawnHeatMap(heatMap, heatLevel, newX, newY); - } + enum directions dir; + short newX, newY; + + if (pmap[x][y].layers[DUNGEON] == DOOR) { + heatLevel += 10; + } else if (pmap[x][y].layers[DUNGEON] == SECRET_DOOR) { + heatLevel += 3000; + } + if (heatMap[x][y] > heatLevel) { + heatMap[x][y] = heatLevel; + } + for (dir = 0; dir < 4; dir++) { + newX = x + nbDirs[dir][0]; + newY = y + nbDirs[dir][1]; + if (coordinatesAreInMap(newX, newY) + && !cellHasTerrainFlag(newX, newY, T_IS_DEEP_WATER | T_LAVA_INSTA_DEATH | T_AUTO_DESCENT) + && isPassableOrSecretDoor(newX, newY) && heatLevel < heatMap[newX][newY]) { + + fillItemSpawnHeatMap(heatMap, heatLevel, newX, newY); } + } } void coolHeatMapAt(unsigned short heatMap[DCOLS][DROWS], short x, short y, unsigned long *totalHeat) { - short k, l; - unsigned short currentHeat; + short k, l; + unsigned short currentHeat; - currentHeat = heatMap[x][y]; - if (currentHeat == 0) { - return; - } - *totalHeat -= heatMap[x][y]; - heatMap[x][y] = 0; + currentHeat = heatMap[x][y]; + if (currentHeat == 0) { + return; + } + *totalHeat -= heatMap[x][y]; + heatMap[x][y] = 0; - // lower the heat near the chosen location - for (k = -5; k <= 5; k++) { - for (l = -5; l <= 5; l++) { - if (coordinatesAreInMap(x+k, y+l) && heatMap[x+k][y+l] == currentHeat) { - heatMap[x+k][y+l] = max(1, heatMap[x+k][y+l]/10); - *totalHeat -= (currentHeat - heatMap[x+k][y+l]); - } - } + // lower the heat near the chosen location + for (k = -5; k <= 5; k++) { + for (l = -5; l <= 5; l++) { + if (coordinatesAreInMap(x + k, y + l) && heatMap[x + k][y + l] == currentHeat) { + heatMap[x + k][y + l] = max(1, heatMap[x + k][y + l] / 10); + *totalHeat -= (currentHeat - heatMap[x + k][y + l]); + } } + } } // Returns false if no place could be found. // That should happen only if the total heat is zero. boolean getItemSpawnLoc(unsigned short heatMap[DCOLS][DROWS], short *x, short *y, unsigned long *totalHeat) { - unsigned long randIndex; - unsigned short currentHeat; - short i, j; + unsigned long randIndex; + unsigned short currentHeat; + short i, j; - if (*totalHeat <= 0) { - return false; - } + if (*totalHeat <= 0) { + return false; + } - randIndex = rand_range(1, *totalHeat); + randIndex = rand_range(1, *totalHeat); - //printf("\nrandIndex: %i", randIndex); + // printf("\nrandIndex: %i", randIndex); - for (i=0; inumberPotionKinds, sizeof(itemTable)); - scrollTableCopy = calloc(gameConst->numberScrollKinds, sizeof(itemTable)); + // Store copy of potion and scroll tables, since they are modified during level item generation. + potionTableCopy = calloc(gameConst->numberPotionKinds, sizeof(itemTable)); + scrollTableCopy = calloc(gameConst->numberScrollKinds, sizeof(itemTable)); - memcpy(potionTableCopy, potionTable, gameConst->numberPotionKinds * sizeof(itemTable)); - memcpy(scrollTableCopy, scrollTable, gameConst->numberScrollKinds * sizeof(itemTable)); + memcpy(potionTableCopy, potionTable, gameConst->numberPotionKinds * sizeof(itemTable)); + memcpy(scrollTableCopy, scrollTable, gameConst->numberScrollKinds * sizeof(itemTable)); - if (rogue.depthLevel > gameConst->amuletLevel) { - numberOfItems = lumenstoneDistribution[rogue.depthLevel - gameConst->amuletLevel - 1]; - numberOfGoldPiles = 0; - } else { - // Add frequency to metered items memory - for (i = 0; i < gameConst->numberMeteredItems; i++) { - rogue.meteredItems[i].frequency += meteredItemsGenerationTable[i].incrementFrequency; - } - numberOfItems = 3; - while (rand_percent(60)) { - numberOfItems++; - } - if (rogue.depthLevel <= 2) { - numberOfItems += 2; // 4 extra items to kickstart your career as a rogue - } else if (rogue.depthLevel <= 4) { - numberOfItems++; // and 2 more here - } + if (rogue.depthLevel > gameConst->amuletLevel) { + numberOfItems = lumenstoneDistribution[rogue.depthLevel - gameConst->amuletLevel - 1]; + numberOfGoldPiles = 0; + } else { + // Add frequency to metered items memory + for (i = 0; i < gameConst->numberMeteredItems; i++) { + rogue.meteredItems[i].frequency += meteredItemsGenerationTable[i].incrementFrequency; + } + numberOfItems = 3; + while (rand_percent(60)) { + numberOfItems++; + } + if (rogue.depthLevel <= 2) { + numberOfItems += 2; // 4 extra items to kickstart your career as a rogue + } else if (rogue.depthLevel <= 4) { + numberOfItems++; // and 2 more here + } - numberOfItems += gameConst->extraItemsPerLevel; + numberOfItems += gameConst->extraItemsPerLevel; - numberOfGoldPiles = min(5, rogue.depthLevel * gameConst->depthAccelerator / 4); - for (goldBonusProbability = 60; - rand_percent(goldBonusProbability) && numberOfGoldPiles <= 10; - goldBonusProbability -= 15) { + numberOfGoldPiles = min(5, rogue.depthLevel * gameConst->depthAccelerator / 4); + for (goldBonusProbability = 60; rand_percent(goldBonusProbability) && numberOfGoldPiles <= 10; + goldBonusProbability -= 15) { - numberOfGoldPiles++; - } - // Adjust the amount of gold if we're past depth 5 and we were below or above - // the production schedule as of the previous depth. - if (rogue.depthLevel >= gameConst->goldAdjustmentStartDepth) { - if (rogue.goldGenerated < aggregateGoldLowerBound(rogue.depthLevel * gameConst->depthAccelerator - 1)) { - numberOfGoldPiles += 2; - } else if (rogue.goldGenerated > aggregateGoldUpperBound(rogue.depthLevel * gameConst->depthAccelerator - 1)) { - numberOfGoldPiles -= 2; - } - } + numberOfGoldPiles++; } + // Adjust the amount of gold if we're past depth 5 and we were below or above + // the production schedule as of the previous depth. + if (rogue.depthLevel >= gameConst->goldAdjustmentStartDepth) { + if (rogue.goldGenerated < aggregateGoldLowerBound(rogue.depthLevel * gameConst->depthAccelerator - 1)) { + numberOfGoldPiles += 2; + } else if (rogue.goldGenerated > aggregateGoldUpperBound(rogue.depthLevel * gameConst->depthAccelerator - 1)) { + numberOfGoldPiles -= 2; + } + } + } - // Create an item spawn heat map to bias item generation behind secret doors (and, to a lesser - // extent, regular doors). This is in terms of the number of secret/regular doors that must be - // passed to reach the area when pathing to it from the upward staircase. - // This is why there are often several items in well hidden secret rooms. Otherwise, - // those rooms are usually empty, which is demoralizing after you take the trouble to find them. - for (i=0; i