/*
* IO.c
* Brogue
*
* Created by Brian Walker on 1/10/09.
* Copyright 2012. All rights reserved.
*
* This file is part of Brogue.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <math.h>
#include <time.h>
#include "Rogue.h"
#include "IncludeGlobals.h"
// 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(short path[1000][2], short **map, short originX, short originY) {
short dir, x, y, steps;
x = originX;
y = originY;
dir = 0;
for (steps = 0; dir != -1;) {
dir = nextStep(map, x, y, &player, false);
if (dir != -1) {
x += nbDirs[dir][0];
y += nbDirs[dir][1];
path[steps][0] = x;
path[steps][1] = y;
steps++;
brogueAssert(coordinatesAreInMap(x, y));
}
}
return steps;
}
void reversePath(short path[1000][2], short steps) {
short i, x, y;
for (i=0; i<steps / 2; i++) {
x = path[steps - i - 1][0];
y = path[steps - i - 1][1];
path[steps - i - 1][0] = path[i][0];
path[steps - i - 1][1] = path[i][1];
path[i][0] = x;
path[i][1] = y;
}
}
void hilitePath(short path[1000][2], short steps, boolean unhilite) {
short i;
if (unhilite) {
for (i=0; i<steps; i++) {
brogueAssert(coordinatesAreInMap(path[i][0], path[i][1]));
pmap[path[i][0]][path[i][1]].flags &= ~IS_IN_PATH;
refreshDungeonCell(path[i][0], path[i][1]);
}
} else {
for (i=0; i<steps; i++) {
brogueAssert(coordinatesAreInMap(path[i][0], path[i][1]));
pmap[path[i][0]][path[i][1]].flags |= IS_IN_PATH;
refreshDungeonCell(path[i][0], path[i][1]);
}
}
}
// 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<DCOLS; i++) {
for (j=1; j<DROWS; j++) {
if (pmap[i][j].flags & IS_IN_PATH) {
pmap[i][j].flags &= ~IS_IN_PATH;
refreshDungeonCell(i, j);
}
}
}
}
}
void hideCursor() {
// Drop out of cursor mode if we're in it, and hide the path either way.
rogue.cursorMode = false;
rogue.cursorPathIntensity = (rogue.cursorMode ? 50 : 20);
rogue.cursorLoc[0] = -1;
rogue.cursorLoc[1] = -1;
}
void showCursor() {
// Return or enter turns on cursor mode. When the path is hidden, move the cursor to the player.
if (!coordinatesAreInMap(rogue.cursorLoc[0], rogue.cursorLoc[1])) {
rogue.cursorLoc[0] = player.xLoc;
rogue.cursorLoc[1] = player.yLoc;
rogue.cursorMode = true;
rogue.cursorPathIntensity = (rogue.cursorMode ? 50 : 20);
} else {
rogue.cursorMode = true;
rogue.cursorPathIntensity = (rogue.cursorMode ? 50 : 20);
}
}
void getClosestValidLocationOnMap(short loc[2], short **map, short x, short y) {
short i, j, dist, closestDistance, lowestMapScore;
closestDistance = 10000;
lowestMapScore = 10000;
for (i=1; i<DCOLS-1; i++) {
for (j=1; j<DROWS-1; j++) {
if (map[i][j] >= 0
&& map[i][j] < 30000) {
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) {
loc[0] = i;
loc[1] = j;
closestDistance = dist;
lowestMapScore = map[i][j];
}
}
}
}
}
void processSnapMap(short **map) {
short **costMap;
enum directions dir;
short i, j, newX, newY;
costMap = allocGrid();
populateCreatureCostMap(costMap, &player);
fillGrid(map, 30000);
map[player.xLoc][player.yLoc] = 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);
}
// Displays a menu of buttons for various commands.
// Buttons will be disabled if not permitted based on the playback state.
// Returns the keystroke to effect the button's command, or -1 if canceled.
// 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;
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);
do {
for (i=0; i<ROWS; i++) {
initializeButton(&(buttons[i]));
buttons[i].buttonColor = interfaceBoxColor;
buttons[i].opacity = INTERFACE_OPACITY;
}
buttonCount = 0;
if (playingBack) {
#ifdef ENABLE_PLAYBACK_SWITCH
if (KEYBOARD_LABELS) {
sprintf(buttons[buttonCount].text, " %sP: %sPlay from here ", yellowColorEscape, whiteColorEscape);
} else {
strcpy(buttons[buttonCount].text, " Play from here ");
}
buttons[buttonCount].hotkey[0] = SWITCH_TO_PLAYING_KEY;
buttonCount++;
sprintf(buttons[buttonCount].text, " %s---", darkGrayColorEscape);
buttons[buttonCount].flags &= ~B_ENABLED;
buttonCount++;
#endif
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&: %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(!serverMode) {
if (KEYBOARD_LABELS) {
sprintf(buttons[buttonCount].text, " %sS: %sSuspend game and quit ", yellowColorEscape, whiteColorEscape);
} else {
strcpy(buttons[buttonCount].text, " Suspend game and quit ");
}
buttons[buttonCount].hotkey[0] = SAVE_GAME_KEY;
buttonCount++;
if (KEYBOARD_LABELS) {
sprintf(buttons[buttonCount].text, " %sO: %sOpen suspended game ", yellowColorEscape, whiteColorEscape);
} else {
strcpy(buttons[buttonCount].text, " Open suspended 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++;
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.displayAggroRangeMode ? "X" : " ");
} else {
sprintf(buttons[buttonCount].text, " [%s] Show stealth range ", rogue.displayAggroRangeMode ? "X" : " ");
}
buttons[buttonCount].hotkey[0] = AGGRO_DISPLAY_KEY;
takeActionOurselves[buttonCount] = true;
buttonCount++;
if (hasGraphics) {
if (KEYBOARD_LABELS) {
sprintf(buttons[buttonCount].text, " %sG: %s[%s] Enable graphics ", yellowColorEscape, whiteColorEscape, graphicsEnabled ? "X" : " ");
} else {
sprintf(buttons[buttonCount].text, " [%s] Enable graphics ", graphicsEnabled ? "X" : " ");
}
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;
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 (KEYBOARD_LABELS) {
sprintf(buttons[buttonCount].text, " %sQ: %sQuit %s ", yellowColorEscape, whiteColorEscape, (playingBack ? "to title screen" : "without saving"));
} else {
sprintf(buttons[buttonCount].text, " Quit %s ", (playingBack ? "to title screen" : "without saving"));
}
buttons[buttonCount].hotkey[0] = QUIT_KEY;
buttonCount++;
strcpy(buttons[buttonCount].text, " ");
buttons[buttonCount].flags &= ~B_ENABLED;
buttonCount++;
for (i=0; i<buttonCount; i++) {
longestName = max(longestName, strLenWithoutEscapes(buttons[i].text));
}
if (x + longestName >= 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;
}
#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; i<MAX_MENU_BUTTON_COUNT; i++) {
initializeButton(&(buttons[i]));
buttons[i].opacity = 75;
buttons[i].buttonColor = interfaceButtonColor;
buttons[i].y = ROWS - 1;
buttons[i].flags |= B_WIDE_CLICK_AREA;
buttons[i].flags &= ~B_KEYPRESS_HIGHLIGHT;
}
buttonCount = 0;
if (rogue.playbackMode) {
if (KEYBOARD_LABELS) {
sprintf(buttons[buttonCount].text, " Unpause (%sspace%s) ", goldTextEscape, whiteTextEscape);
} else {
strcpy(buttons[buttonCount].text, " Unpause ");
}
buttons[buttonCount].hotkey[0] = ACKNOWLEDGE_KEY;
buttonCount++;
if (KEYBOARD_LABELS) {
sprintf(buttons[buttonCount].text, "Omniscience (%stab%s)", goldTextEscape, whiteTextEscape);
} else {
strcpy(buttons[buttonCount].text, " Omniscience ");
}
buttons[buttonCount].hotkey[0] = TAB_KEY;
buttonCount++;
if (KEYBOARD_LABELS) {
sprintf(buttons[buttonCount].text, " Next Turn (%sl%s) ", goldTextEscape, whiteTextEscape);
} else {
strcpy(buttons[buttonCount].text, " Next Turn ");
}
buttons[buttonCount].hotkey[0] = RIGHT_KEY;
buttons[buttonCount].hotkey[1] = RIGHT_ARROW;
buttonCount++;
strcpy(buttons[buttonCount].text, " Menu ");
buttonCount++;
} else {
sprintf(buttons[buttonCount].text, " E%sx%splore ", goldTextEscape, whiteTextEscape);
buttons[buttonCount].hotkey[0] = EXPLORE_KEY;
buttons[buttonCount].hotkey[1] = 'X';
buttonCount++;
if (KEYBOARD_LABELS) {
sprintf(buttons[buttonCount].text, " Rest (%sz%s) ", goldTextEscape, whiteTextEscape);
} else {
strcpy(buttons[buttonCount].text, " Rest ");
}
buttons[buttonCount].hotkey[0] = REST_KEY;
buttonCount++;
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() {
short originLoc[2], pathDestination[2], oldTargetLoc[2] = { 0, 0 },
path[1000][2], steps, oldRNG, dir, newX, newY;
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;
short *cursor = rogue.cursorLoc; // shorthand
canceled = false;
rogue.cursorMode = false; // Controls whether the keyboard moves the cursor or the character.
steps = 0;
rogue.cursorPathIntensity = (rogue.cursorMode ? 50 : 20);
// Initialize buttons.
initializeMenuButtons(&state, buttons);
playingBack = rogue.playbackMode;
rogue.playbackMode = false;
costMap = allocGrid();
playerPathingMap = allocGrid();
cursorSnapMap = allocGrid();
cursor[0] = cursor[1] = -1;
while (!rogue.gameHasEnded && (!playingBack || !canceled)) { // repeats until the game ends
oldRNG = rogue.RNG;
rogue.RNG = RNG_COSMETIC;
focusedOnMonster = focusedOnItem = focusedOnTerrain = false;
steps = 0;
clearCursorPath();
originLoc[0] = player.xLoc;
originLoc[1] = player.yLoc;
if (playingBack && rogue.cursorMode) {
temporaryMessage("Examine what? (<hjklyubn>, mouse, or <tab>)", false);
}
if (!playingBack
&& player.xLoc == cursor[0]
&& player.yLoc == cursor[1]
&& oldTargetLoc[0] == cursor[0]
&& oldTargetLoc[1] == cursor[1]) {
// Path hides when you reach your destination.
rogue.cursorMode = false;
rogue.cursorPathIntensity = (rogue.cursorMode ? 50 : 20);
cursor[0] = -1;
cursor[1] = -1;
}
oldTargetLoc[0] = cursor[0];
oldTargetLoc[1] = cursor[1];
populateCreatureCostMap(costMap, &player);
fillGrid(playerPathingMap, 30000);
playerPathingMap[player.xLoc][player.yLoc] = 0;
dijkstraScan(playerPathingMap, costMap, true);
processSnapMap(cursorSnapMap);
do {
textDisplayed = false;
// Draw the cursor and path
if (coordinatesAreInMap(oldTargetLoc[0], oldTargetLoc[1])) {
refreshDungeonCell(oldTargetLoc[0], oldTargetLoc[1]); // Remove old cursor.
}
if (!playingBack) {
if (coordinatesAreInMap(oldTargetLoc[0], oldTargetLoc[1])) {
hilitePath(path, steps, true); // Unhilite old path.
}
if (coordinatesAreInMap(cursor[0], cursor[1])) {
if (cursorSnapMap[cursor[0]][cursor[1]] >= 0
&& cursorSnapMap[cursor[0]][cursor[1]] < 30000) {
pathDestination[0] = cursor[0];
pathDestination[1] = cursor[1];
} else {
// If the cursor is aimed at an inaccessible area, find the nearest accessible area to path toward.
getClosestValidLocationOnMap(pathDestination, cursorSnapMap, cursor[0], cursor[1]);
}
fillGrid(playerPathingMap, 30000);
playerPathingMap[pathDestination[0]][pathDestination[1]] = 0;
backupCost = costMap[pathDestination[0]][pathDestination[1]];
costMap[pathDestination[0]][pathDestination[1]] = 1;
dijkstraScan(playerPathingMap, costMap, true);
costMap[pathDestination[0]][pathDestination[1]] = backupCost;
steps = getPlayerPathOnMap(path, playerPathingMap, player.xLoc, player.yLoc);
// 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][0] = pathDestination[0];
path[steps][1] = pathDestination[1];
}
steps++;
// if (playerPathingMap[cursor[0]][cursor[1]] != 1
if (playerPathingMap[player.xLoc][player.yLoc] != 1
|| pathDestination[0] != cursor[0]
|| pathDestination[1] != cursor[1]) {
hilitePath(path, steps, false); // Hilite new path.
}
}
}
if (coordinatesAreInMap(cursor[0], cursor[1])) {
hiliteCell(cursor[0],
cursor[1],
&white,
(steps <= 0
|| (path[steps-1][0] == cursor[0] && path[steps-1][1] == cursor[1])
|| (!playingBack && distanceBetween(player.xLoc, player.yLoc, cursor[0], cursor[1]) <= 1) ? 100 : 25),
true);
oldTargetLoc[0] = cursor[0];
oldTargetLoc[1] = cursor[1];
monst = monsterAtLoc(cursor[0], cursor[1]);
theItem = itemAtLoc(cursor[0], cursor[1]);
if (monst != NULL && (canSeeMonster(monst) || rogue.playbackOmniscience)) {
rogue.playbackMode = playingBack;
refreshSideBar(cursor[0], cursor[1], false);
rogue.playbackMode = false;
focusedOnMonster = true;
if (monst != &player && (!player.status[STATUS_HALLUCINATING] || rogue.playbackOmniscience)) {
printMonsterDetails(monst, rbuf);
textDisplayed = true;
}
} else if (theItem != NULL && playerCanSeeOrSense(cursor[0], cursor[1])) {
rogue.playbackMode = playingBack;
refreshSideBar(cursor[0], cursor[1], false);
rogue.playbackMode = false;
focusedOnItem = true;
if (!player.status[STATUS_HALLUCINATING] || rogue.playbackOmniscience) {
printFloorItemDetails(theItem, rbuf);
textDisplayed = true;
}
} else if (cellHasTMFlag(cursor[0], cursor[1], TM_LIST_IN_SIDEBAR) && playerCanSeeOrSense(cursor[0], cursor[1])) {
rogue.playbackMode = playingBack;
refreshSideBar(cursor[0], cursor[1], false);
rogue.playbackMode = false;
focusedOnTerrain = true;
}
printLocationDescription(cursor[0], cursor[1]);
}
// Get the input!
rogue.playbackMode = playingBack;
doEvent = moveCursor(&targetConfirmed, &canceled, &tabKey, cursor, &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 (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;
}
if (tabKey && !playingBack) { // The tab key cycles the cursor through monsters, items and terrain features.
if (nextTargetAfter(&newX, &newY, cursor[0], cursor[1], true, true, true, true, false, theEvent.shiftKey)) {
cursor[0] = newX;
cursor[1] = newY;
}
}
if (theEvent.eventType == KEYSTROKE
&& (theEvent.param1 == ASCEND_KEY && cursor[0] == rogue.upLoc[0] && cursor[1] == rogue.upLoc[1]
|| theEvent.param1 == DESCEND_KEY && cursor[0] == rogue.downLoc[0] && cursor[1] == rogue.downLoc[1])) {
targetConfirmed = true;
doEvent = false;
}
} while (!targetConfirmed && !canceled && !doEvent && !rogue.gameHasEnded);
if (coordinatesAreInMap(oldTargetLoc[0], oldTargetLoc[1])) {
refreshDungeonCell(oldTargetLoc[0], oldTargetLoc[1]); // Remove old cursor.
}
restoreRNG;
if (canceled && !playingBack) {
hideCursor();
confirmMessages();
} else if (targetConfirmed && !playingBack && coordinatesAreInMap(cursor[0], cursor[1])) {
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 && (player.xLoc + nbDirs[dir][0] != path[0][0] || player.yLoc + nbDirs[dir][1] != path[0][1]);
dir++);
playerMoves(dir);
} else if (D_WORMHOLING) {
travel(cursor[0], cursor[1], true);
} else {
confirmMessages();
if (originLoc[0] == cursor[0]
&& originLoc[1] == cursor[1]) {
confirmMessages();
} else if (abs(player.xLoc - cursor[0]) + abs(player.yLoc - cursor[1]) == 1 // horizontal or vertical
|| (distanceBetween(player.xLoc, player.yLoc, cursor[0], cursor[1]) == 1 // includes diagonals
&& (!diagonalBlocked(player.xLoc, player.yLoc, cursor[0], cursor[1], !rogue.playbackOmniscience)
|| ((pmap[cursor[0]][cursor[1]].flags & HAS_MONSTER) && (monsterAtLoc(cursor[0], cursor[1])->info.flags & MONST_ATTACKABLE_THRU_WALLS)) // there's a turret there
|| ((terrainFlags(cursor[0], cursor[1]) & T_OBSTRUCTS_PASSABILITY) && (terrainMechFlags(cursor[0], cursor[1]) & 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.xLoc + nbDirs[dir][0] != cursor[0] || player.yLoc + nbDirs[dir][1] != cursor[1]);
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_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;
}*/
}
// 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() {
short i, j;
for( i=0; i<COLS; i++ ) {
for( j=0; j<ROWS; j++ ) {
displayBuffer[i][j].needsUpdate = true;
}
}
commitDraws();
}
// higher-level redraw
void displayLevel() {
short 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));
}
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;
}
}
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;
}
void shuffleTerrainColors(short percentOfCells, boolean refreshCells) {
enum directions dir;
short i, j;
assureCosmeticRNG;
for (i=0; i<DCOLS; i++) {
for(j=0; j<DROWS; j++) {
if (playerCanSeeOrSense(i, j)
&& (!rogue.automationActive || !(rogue.playerTurnNumber % 5))
&& ((pmap[i][j].flags & TERRAIN_COLORS_DANCING)
|| (player.status[STATUS_HALLUCINATING] && playerCanDirectlySee(i, j)))
&& (i != rogue.cursorLoc[0] || j != rogue.cursorLoc[1])
&& (percentOfCells >= 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;
}
// 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, color *back) {
color f, b, *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;
}
// 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;
}
}
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;
}
}
}
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(x, y);
} else if (pmap[x][y].flags & HAS_DORMANT_MONSTER) {
monst = dormantMonsterAtLoc(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 {
maxLayer = NUMBER_TERRAIN_LAYERS;
}
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;
}
}
}
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) {
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) {
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) {
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.
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.displayAggroRangeMode && (pmap[x][y].flags & IN_FIELD_OF_VIEW)) {
distance = min(rogue.scentTurnNumber - scentMap[x][y], scentDistance(x, y, player.xLoc, player.yLoc));
if (distance > rogue.aggroRange * 2) {
applyColorAverage(&cellForeColor, &orange, 12);
applyColorAverage(&cellBackColor, &orange, 12);
applyColorAugment(&cellForeColor, &orange, 12);
applyColorAugment(&cellBackColor, &orange, 12);
}
}
if (rogue.trueColorMode
&& 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;
if (D_DISABLE_BACKGROUND_COLORS) *returnBackColor = black;
restoreRNG;
}
void refreshDungeonCell(short x, short y) {
enum displayGlyph cellChar;
color foreColor, backColor;
brogueAssert(coordinatesAreInMap(x, y));
getCellAppearance(x, y, &cellChar, &foreColor, &backColor);
plotCharWithColor(cellChar, mapToWindowX(x), mapToWindowY(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;
}
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;
}
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;
}
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;
}
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);
}
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 randomizeByPercent(short input, short percent) {
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);
}
void swapColors(color *color1, color *color2) {
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:
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) {
*retForeColor = *retBackColor;
applyColorAverage(retForeColor, toForeColor, (percent - 50) * 2);
} else {
*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; i<COLS; i++) {
for (j=0; j<ROWS; j++) {
completionMap[i][j] = (i - x)*(i - x) + (j - y)*(j - y); // square of distance
completionMap[i][j] = 100 * completionMap[i][j] / maxDistance; // percent of max distance
if (outsideIn) {
completionMap[i][j] -= 100; // translate to [-100, 0], with the origin at -100 and the farthest point at 0.
} else {
completionMap[i][j] *= -1; // translate to [-100, 0], with the origin at 0 and the farthest point at -100.
}
}
}
do {
percentBasis = 10000 * frame / frameCount;
for (i=0; i<COLS; i++) {
for (j=0; j<ROWS; j++) {
thisCellPercent = percentBasis * 3 / 100 + completionMap[i][j];
fromBackColor = colorFromComponents(fromBuf[i][j].backColorComponents);
fromForeColor = colorFromComponents(fromBuf[i][j].foreColorComponents);
fromChar = fromBuf[i][j].character;
toBackColor = colorFromComponents(toBuf[i][j].backColorComponents);
toForeColor = colorFromComponents(toBuf[i][j].foreColorComponents);
toChar = toBuf[i][j].character;
blendAppearances(&fromForeColor, &fromBackColor, fromChar, &toForeColor, &toBackColor, toChar, ¤tForeColor, ¤tBackColor, ¤tChar, clamp(thisCellPercent, 0, 100));
plotCharWithColor(currentChar, i, j, ¤tForeColor, ¤tBackColor);
}
}
fastForward = pauseBrogue(16);
frame++;
} while (frame <= frameCount && !fastForward);
overlayDisplayBuffer(toBuf, NULL);
}
// takes dungeon coordinates
void colorBlendCell(short x, short y, color *hiliteColor, short hiliteStrength) {
enum displayGlyph displayChar;
color foreColor, backColor;
getCellAppearance(x, y, &displayChar, &foreColor, &backColor);
applyColorAverage(&foreColor, hiliteColor, hiliteStrength);
applyColorAverage(&backColor, hiliteColor, hiliteStrength);
plotCharWithColor(displayChar, mapToWindowX(x), mapToWindowY(y), &foreColor, &backColor);
}
// takes dungeon coordinates
void hiliteCell(short x, short y, const color *hiliteColor, short hiliteStrength, boolean distinctColors) {
enum displayGlyph displayChar;
color foreColor, backColor;
assureCosmeticRNG;
getCellAppearance(x, y, &displayChar, &foreColor, &backColor);
applyColorAugment(&foreColor, hiliteColor, hiliteStrength);
applyColorAugment(&backColor, hiliteColor, hiliteStrength);
if (distinctColors) {
separateColors(&foreColor, &backColor);
}
plotCharWithColor(displayChar, mapToWindowX(x), mapToWindowY(y), &foreColor, &backColor);
restoreRNG;
}
short adjustedLightValue(short x) {
if (x <= LIGHT_SMOOTHING_THRESHOLD) {
return x;
} else {
return fp_sqrt(x * FP_FACTOR / LIGHT_SMOOTHING_THRESHOLD) * LIGHT_SMOOTHING_THRESHOLD / FP_FACTOR;
}
}
void colorMultiplierFromDungeonLight(short x, short y, color *editColor) {
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;
}
void plotCharWithColor(enum displayGlyph inputChar, short xLoc, short yLoc, const color *cellForeColor, const color *cellBackColor) {
short oldRNG;
short foreRed = cellForeColor->red,
foreGreen = cellForeColor->green,
foreBlue = cellForeColor->blue,
backRed = cellBackColor->red,
backGreen = cellBackColor->green,
backBlue = cellBackColor->blue,
foreRand, backRand;
brogueAssert(coordinatesAreInWindow(xLoc, yLoc));
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 = ' ';
}
if (inputChar != displayBuffer[xLoc][yLoc].character
|| foreRed != displayBuffer[xLoc][yLoc].foreColorComponents[0]
|| foreGreen != displayBuffer[xLoc][yLoc].foreColorComponents[1]
|| foreBlue != displayBuffer[xLoc][yLoc].foreColorComponents[2]
|| backRed != displayBuffer[xLoc][yLoc].backColorComponents[0]
|| backGreen != displayBuffer[xLoc][yLoc].backColorComponents[1]
|| backBlue != displayBuffer[xLoc][yLoc].backColorComponents[2]) {
displayBuffer[xLoc][yLoc].needsUpdate = true;
displayBuffer[xLoc][yLoc].character = inputChar;
displayBuffer[xLoc][yLoc].foreColorComponents[0] = foreRed;
displayBuffer[xLoc][yLoc].foreColorComponents[1] = foreGreen;
displayBuffer[xLoc][yLoc].foreColorComponents[2] = foreBlue;
displayBuffer[xLoc][yLoc].backColorComponents[0] = backRed;
displayBuffer[xLoc][yLoc].backColorComponents[1] = backGreen;
displayBuffer[xLoc][yLoc].backColorComponents[2] = backBlue;
}
restoreRNG;
}
void plotCharToBuffer(enum displayGlyph inputChar, short x, short y, color *foreColor, color *backColor, cellDisplayBuffer dbuf[COLS][ROWS]) {
short oldRNG;
if (!dbuf) {
plotCharWithColor(inputChar, x, y, foreColor, backColor);
return;
}
brogueAssert(coordinatesAreInWindow(x, y));
oldRNG = rogue.RNG;
rogue.RNG = RNG_COSMETIC;
//assureCosmeticRNG;
dbuf[x][y].foreColorComponents[0] = foreColor->red + rand_range(0, foreColor->redRand) + rand_range(0, foreColor->rand);
dbuf[x][y].foreColorComponents[1] = foreColor->green + rand_range(0, foreColor->greenRand) + rand_range(0, foreColor->rand);
dbuf[x][y].foreColorComponents[2] = foreColor->blue + rand_range(0, foreColor->blueRand) + rand_range(0, foreColor->rand);
dbuf[x][y].backColorComponents[0] = backColor->red + rand_range(0, backColor->redRand) + rand_range(0, backColor->rand);
dbuf[x][y].backColorComponents[1] = backColor->green + rand_range(0, backColor->greenRand) + rand_range(0, backColor->rand);
dbuf[x][y].backColorComponents[2] = backColor->blue + rand_range(0, backColor->blueRand) + rand_range(0, backColor->rand);
dbuf[x][y].character = inputChar;
dbuf[x][y].opacity = 100;
restoreRNG;
}
void plotForegroundChar(enum displayGlyph inputChar, short x, short y, 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, mapToWindowX(x), mapToWindowY(y), &myColor, &backColor);
}
// Set to false and draws don't take effect, they simply queue up. Set to true and all of the
// queued up draws take effect.
void commitDraws() {
short i, j;
for (j=0; j<ROWS; j++) {
for (i=0; i<COLS; i++) {
if (displayBuffer[i][j].needsUpdate) {
plotChar(displayBuffer[i][j].character, i, j,
displayBuffer[i][j].foreColorComponents[0],
displayBuffer[i][j].foreColorComponents[1],
displayBuffer[i][j].foreColorComponents[2],
displayBuffer[i][j].backColorComponents[0],
displayBuffer[i][j].backColorComponents[1],
displayBuffer[i][j].backColorComponents[2]);
displayBuffer[i][j].needsUpdate = false;
}
}
}
}
// 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<DCOLS; i++) {
for (j=0; j<DROWS; j++) {
if (pmap[i][j].layers[DUNGEON] != GRANITE
|| (pmap[i][j].flags & DISCOVERED)) {
backup = pmap[i][j];
pmap[i][j].flags |= (VISIBLE | DISCOVERED);
tmap[i][j].light[0] = 100;
tmap[i][j].light[1] = 100;
tmap[i][j].light[2] = 100;
refreshDungeonCell(i, j);
pmap[i][j] = backup;
} else {
plotCharWithColor(' ', mapToWindowX(i), mapToWindowY(j), &white, &black);
}
}
}
restoreRNG;
}
// To be used immediately after dumpLevelToScreen() above.
// Highlight the portion indicated by hiliteCharGrid with the hiliteColor at the hiliteStrength -- both latter arguments are optional.
void hiliteCharGrid(char hiliteCharGrid[DCOLS][DROWS], color *hiliteColor, short hiliteStrength) {
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 (hiliteCharGrid[i][j]) {
x = mapToWindowX(i);
y = mapToWindowY(j);
displayBuffer[x][y].needsUpdate = true;
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);
}
}
}
restoreRNG;
}
void blackOutScreen() {
short i, j;
for (i=0; i<COLS; i++) {
for (j=0; j<ROWS; j++) {
plotCharWithColor(' ', i, j, &black, &black);
}
}
}
void colorOverDungeon(const color *color) {
short i, j;
for (i=0; i<DCOLS; i++) {
for (j=0; j<DROWS; j++) {
plotCharWithColor(' ', mapToWindowX(i), mapToWindowY(j), color, color);
}
}
}
void copyDisplayBuffer(cellDisplayBuffer toBuf[COLS][ROWS], cellDisplayBuffer fromBuf[COLS][ROWS]) {
short i, j;
for (i=0; i<COLS; i++) {
for (j=0; j<ROWS; j++) {
toBuf[i][j] = fromBuf[i][j];
}
}
}
void clearDisplayBuffer(cellDisplayBuffer dbuf[COLS][ROWS]) {
short i, j, k;
for (i=0; i<COLS; i++) {
for (j=0; j<ROWS; j++) {
dbuf[i][j].character = ' ';
for (k=0; k<3; k++) {
dbuf[i][j].foreColorComponents[k] = 0;
dbuf[i][j].backColorComponents[k] = 0;
}
dbuf[i][j].opacity = 0;
}
}
}
color colorFromComponents(char rgb[3]) {
color theColor = black;
theColor.red = rgb[0];
theColor.green = rgb[1];
theColor.blue = rgb[2];
return theColor;
}
// draws overBuf over the current display with per-cell pseudotransparency as specified in overBuf.
// If previousBuf is not null, it gets filled with the preexisting display for reversion purposes.
void overlayDisplayBuffer(cellDisplayBuffer overBuf[COLS][ROWS], cellDisplayBuffer previousBuf[COLS][ROWS]) {
short i, j;
color foreColor, backColor, tempColor;
enum displayGlyph character;
if (previousBuf) {
copyDisplayBuffer(previousBuf, displayBuffer);
}
for (i=0; i<COLS; i++) {
for (j=0; j<ROWS; j++) {
if (overBuf[i][j].opacity != 0) {
backColor = colorFromComponents(overBuf[i][j].backColorComponents);
// character and fore color:
if (overBuf[i][j].character == ' ') { // Blank cells in the overbuf take the character from the screen.
character = displayBuffer[i][j].character;
foreColor = colorFromComponents(displayBuffer[i][j].foreColorComponents);
applyColorAverage(&foreColor, &backColor, overBuf[i][j].opacity);
} else {
character = overBuf[i][j].character;
foreColor = colorFromComponents(overBuf[i][j].foreColorComponents);
}
// back color:
tempColor = colorFromComponents(displayBuffer[i][j].backColorComponents);
applyColorAverage(&backColor, &tempColor, 100 - overBuf[i][j].opacity);
plotCharWithColor(character, i, j, &foreColor, &backColor);
}
}
}
}
// Takes a list of locations, a color and a list of strengths and flashes the foregrounds of those locations.
// Strengths are percentages measuring how hard the color flashes at its peak.
void flashForeground(short *x, short *y, color **flashColor, short *flashStrength, short count, short frames) {
short i, j, percent;
enum displayGlyph *displayChar;
color *bColor, *fColor, newColor;
short oldRNG;
if (count <= 0) {
return;
}
oldRNG = rogue.RNG;
rogue.RNG = RNG_COSMETIC;
//assureCosmeticRNG;
displayChar = (enum displayGlyph *) malloc(count * sizeof(enum displayGlyph));
fColor = (color *) malloc(count * sizeof(color));
bColor = (color *) malloc(count * sizeof(color));
for (i=0; i<count; i++) {
getCellAppearance(x[i], y[i], &displayChar[i], &fColor[i], &bColor[i]);
bakeColor(&fColor[i]);
bakeColor(&bColor[i]);
}
for (j=frames; j>= 0; j--) {
for (i=0; i<count; i++) {
percent = flashStrength[i] * j / frames;
newColor = fColor[i];
applyColorAverage(&newColor, flashColor[i], percent);
plotCharWithColor(displayChar[i], mapToWindowX(x[i]), mapToWindowY(y[i]), &newColor, &(bColor[i]));
}
if (j) {
if (pauseBrogue(16)) {
j = 1;
}
}
}
free(displayChar);
free(fColor);
free(bColor);
restoreRNG;
}
void flashCell(color *theColor, short frames, short x, short y) {
short i;
boolean interrupted = false;
for (i=0; i<frames && !interrupted; i++) {
colorBlendCell(x, y, theColor, 100 - 100 * i / frames);
interrupted = pauseBrogue(50);
}
refreshDungeonCell(x, y);
}
// special effect expanding flash of light at dungeon coordinates (x, y) restricted to tiles with matching flags
void colorFlash(const color *theColor, unsigned long reqTerrainFlags,
unsigned long reqTileFlags, short frames, short maxRadius, short x, short y) {
short i, j, k, intensity, currentRadius, fadeOut;
short localRadius[DCOLS][DROWS];
boolean tileQualifies[DCOLS][DROWS], aTileQualified, fastForward;
aTileQualified = false;
fastForward = false;
for (i = max(x - maxRadius, 0); i <= min(x + maxRadius, DCOLS - 1); i++) {
for (j = max(y - maxRadius, 0); j <= min(y + maxRadius, DROWS - 1); j++) {
if ((!reqTerrainFlags || cellHasTerrainFlag(reqTerrainFlags, i, j))
&& (!reqTileFlags || (pmap[i][j].flags & reqTileFlags))
&& (i-x) * (i-x) + (j-y) * (j-y) <= maxRadius * maxRadius) {
tileQualifies[i][j] = true;
localRadius[i][j] = fp_sqrt(((i-x) * (i-x) + (j-y) * (j-y)) * FP_FACTOR) / FP_FACTOR;
aTileQualified = true;
} else {
tileQualifies[i][j] = false;
}
}
}
if (!aTileQualified) {
return;
}
for (k = 1; k <= frames; k++) {
currentRadius = max(1, maxRadius * k / frames);
fadeOut = min(100, (frames - k) * 100 * 5 / frames);
for (i = max(x - maxRadius, 0); i <= min(x + maxRadius, DCOLS - 1); i++) {
for (j = max(y - maxRadius, 0); j <= min(y + maxRadius, DROWS - 1); j++) {
if (tileQualifies[i][j] && (localRadius[i][j] <= currentRadius)) {
intensity = 100 - 100 * (currentRadius - localRadius[i][j] - 2) / currentRadius;
intensity = fadeOut * intensity / 100;
hiliteCell(i, j, theColor, intensity, false);
}
}
}
if (!fastForward && (rogue.playbackFastForward || pauseBrogue(50))) {
k = frames - 1;
fastForward = true;
}
}
}
#define bCurve(x) (((x) * (x) + 11) / (10 * ((x) * (x) + 1)) - 0.1)
// x and y are global coordinates, not within the playing square
void funkyFade(cellDisplayBuffer displayBuf[COLS][ROWS], const color *colorStart,
const color *colorEnd, short stepCount, short x, short y, boolean invert) {
short i, j, n, weight;
double x2, y2, weightGrid[COLS][ROWS][3], percentComplete;
color tempColor, colorMid, foreColor, backColor;
enum displayGlyph tempChar;
short **distanceMap;
boolean fastForward;
assureCosmeticRNG;
fastForward = false;
distanceMap = allocGrid();
fillGrid(distanceMap, 0);
calculateDistances(distanceMap, player.xLoc, player.yLoc, T_OBSTRUCTS_PASSABILITY, 0, true, true);
for (i=0; i<COLS; i++) {
x2 = (double) ((i - x) * 5.0 / COLS);
for (j=0; j<ROWS; j++) {
y2 = (double) ((j - y) * 2.5 / ROWS);
weightGrid[i][j][0] = bCurve(x2*x2+y2*y2) * (.7 + .3 * cos(5*x2*x2) * cos(5*y2*y2));
weightGrid[i][j][1] = bCurve(x2*x2+y2*y2) * (.7 + .3 * sin(5*x2*x2) * cos(5*y2*y2));
weightGrid[i][j][2] = bCurve(x2*x2+y2*y2);
}
}
for (n=(invert ? stepCount - 1 : 0); (invert ? n >= 0 : n <= stepCount); n += (invert ? -1 : 1)) {
for (i=0; i<COLS; i++) {
for (j=0; j<ROWS; j++) {
percentComplete = (double) (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.;
}
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.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);
backColor = black;
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);
if (j < MESSAGE_LINES
&& 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];
applyColorAverage(&foreColor, &tempColor, weight);
}
applyColorAverage(&backColor, &tempColor, weight);
plotCharWithColor(tempChar, i, j, &foreColor, &backColor);
}
}
if (!fastForward && pauseBrogue(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);
restoreRNG;
}
void displayWaypoints() {
short i, j, w, lowestDistance;
for (i=0; i<DCOLS; i++) {
for (j=0; j<DROWS; j++) {
lowestDistance = 30000;
for (w=0; w<rogue.wpCount; w++) {
if (rogue.wpDistance[w][i][j] < lowestDistance) {
lowestDistance = rogue.wpDistance[w][i][j];
}
}
if (lowestDistance < 10) {
hiliteCell(i, j, &white, clamp(100 - lowestDistance*15, 0, 100), true);
}
}
}
temporaryMessage("Waypoints:", true);
}
void displayMachines() {
short i, j;
color foreColor, backColor, machineColors[50];
enum displayGlyph dchar;
assureCosmeticRNG;
for (i=0; i<50; i++) {
machineColors[i] = black;
machineColors[i].red = rand_range(0, 100);
machineColors[i].green = rand_range(0, 100);
machineColors[i].blue = rand_range(0, 100);
}
for (i=0; i<DCOLS; i++) {
for (j=0; j<DROWS; j++) {
if (pmap[i][j].machineNumber) {
getCellAppearance(i, j, &dchar, &foreColor, &backColor);
applyColorAugment(&backColor, &(machineColors[pmap[i][j].machineNumber]), 50);
//plotCharWithColor(dchar, mapToWindowX(i), mapToWindowY(j), &foreColor, &backColor);
if (pmap[i][j].machineNumber < 10) {
dchar ='0' + pmap[i][j].machineNumber;
} else if (pmap[i][j].machineNumber < 10 + 26) {
dchar = 'a' + pmap[i][j].machineNumber - 10;
} else {
dchar = 'A' + pmap[i][j].machineNumber - 10 - 26;
}
plotCharWithColor(dchar, mapToWindowX(i), mapToWindowY(j), &foreColor, &backColor);
}
}
}
displayMoreSign();
displayLevel();
restoreRNG;
}
#define CHOKEMAP_DISPLAY_CUTOFF 160
void displayChokeMap() {
short i, j;
color foreColor, backColor;
enum displayGlyph dchar;
for (i=0; i<DCOLS; i++) {
for (j=0; j<DROWS; j++) {
if (chokeMap[i][j] < CHOKEMAP_DISPLAY_CUTOFF) {
if (pmap[i][j].flags & IS_GATE_SITE) {
getCellAppearance(i, j, &dchar, &foreColor, &backColor);
applyColorAugment(&backColor, &teal, 50);
plotCharWithColor(dchar, mapToWindowX(i), mapToWindowY(j), &foreColor, &backColor);
} else
if (chokeMap[i][j] < CHOKEMAP_DISPLAY_CUTOFF) {
getCellAppearance(i, j, &dchar, &foreColor, &backColor);
applyColorAugment(&backColor, &red, 100 - chokeMap[i][j] * 100 / CHOKEMAP_DISPLAY_CUTOFF);
plotCharWithColor(dchar, mapToWindowX(i), mapToWindowY(j), &foreColor, &backColor);
}
}
}
}
displayMoreSign();
displayLevel();
}
void displayLoops() {
short i, j;
color foreColor, backColor;
enum displayGlyph dchar;
for (i=0; i<DCOLS; i++) {
for (j=0; j<DROWS; j++) {
if (pmap[i][j].flags & IN_LOOP) {
getCellAppearance(i, j, &dchar, &foreColor, &backColor);
applyColorAugment(&backColor, &yellow, 50);
plotCharWithColor(dchar, mapToWindowX(i), mapToWindowY(j), &foreColor, &backColor);
//colorBlendCell(i, j, &tempColor, 100);//hiliteCell(i, j, &tempColor, 100, true);
}
if (pmap[i][j].flags & IS_CHOKEPOINT) {
getCellAppearance(i, j, &dchar, &foreColor, &backColor);
applyColorAugment(&backColor, &teal, 50);
plotCharWithColor(dchar, mapToWindowX(i), mapToWindowY(j), &foreColor, &backColor);
}
}
}
waitForAcknowledgment();
}
void exploreKey(const boolean controlKey) {
short x, y, finalX = 0, finalY = 0;
short **exploreMap;
enum directions dir;
boolean tooDark = false;
// fight any adjacent enemies first
dir = adjacentFightingDir();
if (dir == NO_DIRECTION) {
for (dir = 0; dir < DIRECTION_COUNT; dir++) {
x = player.xLoc + nbDirs[dir][0];
y = player.yLoc + nbDirs[dir][1];
if (coordinatesAreInMap(x, y)
&& !(pmap[x][y].flags & DISCOVERED)) {
tooDark = true;
break;
}
}
if (!tooDark) {
x = finalX = player.xLoc;
y = finalY = player.yLoc;
exploreMap = allocGrid();
getExploreMap(exploreMap, false);
do {
dir = nextStep(exploreMap, x, y, NULL, false);
if (dir != NO_DIRECTION) {
x += nbDirs[dir][0];
y += nbDirs[dir][1];
if (pmap[x][y].flags & (DISCOVERED | MAGIC_MAPPED)) {
finalX = x;
finalY = y;
}
}
} while (dir != NO_DIRECTION);
freeGrid(exploreMap);
}
} else {
x = finalX = player.xLoc + nbDirs[dir][0];
y = finalY = player.yLoc + nbDirs[dir][1];
}
if (tooDark) {
message("It's too dark to explore!", false);
} else if (x == player.xLoc && y == player.yLoc) {
message("I see no path for further exploration.", false);
} else if (proposeOrConfirmLocation(finalX, finalY, "I see no path for further exploration.")) {
explore(controlKey ? 1 : 20); // Do the exploring until interrupted.
hideCursor();
exploreKey(controlKey);
}
}
boolean pauseBrogue(short milliseconds) {
boolean interrupted;
commitDraws();
if (rogue.playbackMode && rogue.playbackFastForward) {
interrupted = true;
} else {
interrupted = pauseForMilliseconds(milliseconds);
}
return interrupted;
}
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 && !coordinatesAreInWindow(returnEvent->param1, returnEvent->param2));
// recording done elsewhere
}
if (returnEvent->eventType == EVENT_ERROR) {
rogue.playbackPaused = rogue.playbackMode; // pause if replaying
message("Event error!", true);
}
}
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[0] = windowToMapX(x);
rogue.cursorLoc[1] = 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) {
char path[BROGUE_FILENAME_MAX];
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[0], rogue.downLoc[1], "I see no way down.")) {
travel(rogue.downLoc[0], rogue.downLoc[1], true);
}
break;
case ASCEND_KEY:
considerCautiousMode();
if (D_WORMHOLING) {
recordKeystroke(ASCEND_KEY, false, false);
useStairs(-1);
} else if (proposeOrConfirmLocation(rogue.upLoc[0], rogue.upLoc[1], "I see no way up.")) {
travel(rogue.upLoc[0], rogue.upLoc[1], 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 (pauseBrogue(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 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, false);
} else {
messageWithColor(KEYBOARD_LABELS ? "Color effects enabled. Press '\\' again to disable." : "Color effects enabled.",
&teal, false);
}
break;
case AGGRO_DISPLAY_KEY:
rogue.displayAggroRangeMode = !rogue.displayAggroRangeMode;
displayLevel();
refreshSideBar(-1, -1, false);
if (rogue.displayAggroRangeMode) {
messageWithColor(KEYBOARD_LABELS ? "Stealth range displayed. Press ']' again to hide." : "Stealth range displayed.",
&teal, false);
} else {
messageWithColor(KEYBOARD_LABELS ? "Stealth range hidden. Press ']' again to display." : "Stealth range hidden.",
&teal, false);
}
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 VIEW_RECORDING_KEY:
if (rogue.playbackMode || serverMode) {
return;
}
confirmMessages();
if ((rogue.playerTurnNumber < 50 || confirm("End this game and view a recording?", false))
&& dialogChooseFile(path, RECORDING_SUFFIX, "View recording: ")) {
if (fileExists(path)) {
strcpy(rogue.nextGamePath, path);
rogue.nextGame = NG_VIEW_RECORDING;
rogue.gameHasEnded = true;
} else {
message("File not found.", false);
}
}
break;
case LOAD_SAVED_GAME_KEY:
if (rogue.playbackMode || serverMode) {
return;
}
confirmMessages();
if ((rogue.playerTurnNumber < 50 || confirm("End this game and load a saved game?", false))
&& dialogChooseFile(path, GAME_SUFFIX, "Open saved game: ")) {
if (fileExists(path)) {
strcpy(rogue.nextGamePath, path);
rogue.nextGame = NG_OPEN_GAME;
rogue.gameHasEnded = true;
} else {
message("File not found.", false);
}
}
break;
case SAVE_GAME_KEY:
if (rogue.playbackMode || serverMode) {
return;
}
if (confirm("Suspend this game? (This feature is still in beta.)", 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 this game without saving?", false)) {
recordKeystroke(QUIT_KEY, false, false);
rogue.quit = true;
gameOver("Quit", true);
}
break;
case GRAPHICS_KEY:
if (hasGraphics) {
graphicsEnabled = setGraphicsEnabled(!graphicsEnabled);
if (graphicsEnabled) {
messageWithColor(KEYBOARD_LABELS ? "Enabled graphical tiles. Press 'G' again to disable." : "Enable graphical tiles.",
&teal, false);
} else {
messageWithColor(KEYBOARD_LABELS ? "Disabled graphical tiles. Press 'G' again to enable." : "Disabled graphical tiles.",
&teal, false);
}
}
break;
case SEED_KEY:
/*DEBUG {
cellDisplayBuffer dbuf[COLS][ROWS];
copyDisplayBuffer(dbuf, displayBuffer);
funkyFade(dbuf, &white, 0, 100, mapToWindowX(player.xLoc), mapToWindowY(player.yLoc), false);
}*/
// DEBUG displayLoops();
// DEBUG displayChokeMap();
DEBUG displayMachines();
//DEBUG displayWaypoints();
// DEBUG {displayGrid(safetyMap); displayMoreSign(); displayLevel();}
// parseFile();
// DEBUG spawnDungeonFeature(player.xLoc, player.yLoc, &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(' ', x + i, y, &black, &black);
}
printString(defaultEntry, x, y, &white, &black, 0);
} else {
confirmMessages();
x = mapToWindowX(strLenWithoutEscapes(prompt));
y = MESSAGE_LINES - 1;
message(prompt, false);
printString(defaultEntry, x, y, &white, &black, 0);
}
maxLength = min(maxLength, COLS - x);
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] : ' '), x + charNum, y, &black, &white);
keystroke = nextKeyPress(true);
if (keystroke == DELETE_KEY && charNum > 0) {
printString(suffix, charNum + x - 1, y, &gray, &black, 0);
plotCharWithColor(' ', 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, 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, x + charNum, y, &white, &black);
if (charNum < maxLength) {
charNum++;
}
}
}
}
#endif
} while (keystroke != RETURN_KEY && keystroke != ESCAPE_KEY);
if (useDialogBox) {
overlayDisplayBuffer(rbuf, NULL);
}
inputText[charNum] = '\0';
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);
}
// 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, color *fColor, 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; j<messageLength; j++) {
backColors[j] = colorFromComponents(displayBuffer[j + x][y].backColorComponents);
dbufs[j] = displayBuffer[j + x][y];
}
previousPercentComplete = -1;
for (i=0; i < time && fastForward == false; i += stepInMs) {
percentComplete = 100 * i / time;
percentComplete = percentComplete * percentComplete / 100; // transition is front-loaded
if (previousPercentComplete != percentComplete) {
for (j=0; j<messageLength; j++) {
if (i==0) {
backColors[j] = colorFromComponents(displayBuffer[j + x][y].backColorComponents);
dbufs[j] = displayBuffer[j + x][y];
}
backColor = backColors[j];
applyColorAverage(&backColor, bColor, 100 - percentComplete);
if (percentComplete < 50) {
dchar = message[j];
foreColor = *fColor;
applyColorAverage(&foreColor, &backColor, percentComplete * 2);
} else {
dchar = dbufs[j].character;
foreColor = colorFromComponents(dbufs[j].foreColorComponents);
applyColorAverage(&foreColor, &backColor, (100 - percentComplete) * 2);
}
plotCharWithColor(dchar, j+x, y, &foreColor, &backColor);
}
}
previousPercentComplete = percentComplete;
fastForward = pauseBrogue(stepInMs);
}
for (j=0; j<messageLength; j++) {
foreColor = colorFromComponents(dbufs[j].foreColorComponents);
plotCharWithColor(dbufs[j].character, j+x, y, &foreColor, &(backColors[j]));
}
restoreRNG;
}
void flashTemporaryAlert(char *message, int time) {
flashMessage(message, (COLS - strLenWithoutEscapes(message)) / 2, ROWS / 2, time, &teal, &black);
}
void waitForAcknowledgment() {
rogueEvent theEvent;
if (rogue.autoPlayingLevel || (rogue.playbackMode && !rogue.playbackOOS)) {
return;
}
do {
nextBrogueEvent(&theEvent, false, false, false);
if (theEvent.eventType == KEYSTROKE && theEvent.param1 != ACKNOWLEDGE_KEY && theEvent.param1 != ESCAPE_KEY) {
flashTemporaryAlert(" -- Press space or click to continue -- ", 500);
}
} while (!(theEvent.eventType == KEYSTROKE && (theEvent.param1 == ACKNOWLEDGE_KEY || theEvent.param1 == ESCAPE_KEY)
|| theEvent.eventType == MOUSE_UP));
}
void waitForKeystrokeOrMouseClick() {
rogueEvent theEvent;
do {
nextBrogueEvent(&theEvent, false, false, false);
} while (theEvent.eventType != KEYSTROKE && theEvent.eventType != MOUSE_UP);
}
boolean confirm(char *prompt, boolean alsoDuringPlayback) {
short retVal;
brogueButton buttons[2] = {{{0}}};
cellDisplayBuffer rbuf[COLS][ROWS];
char whiteColorEscape[20] = "";
char yellowColorEscape[20] = "";
if (rogue.autoPlayingLevel || (!alsoDuringPlayback && rogue.playbackMode)) {
return true; // oh yes he did
}
encodeMessageColor(whiteColorEscape, 0, &white);
encodeMessageColor(yellowColorEscape, 0, KEYBOARD_LABELS ? &yellow : &white);
initializeButton(&(buttons[0]));
sprintf(buttons[0].text, " %sY%ses ", yellowColorEscape, whiteColorEscape);
buttons[0].hotkey[0] = 'y';
buttons[0].hotkey[1] = 'Y';
buttons[0].hotkey[2] = RETURN_KEY;
buttons[0].flags |= (B_WIDE_CLICK_AREA | B_KEYPRESS_HIGHLIGHT);
initializeButton(&(buttons[1]));
sprintf(buttons[1].text, " %sN%so ", yellowColorEscape, whiteColorEscape);
buttons[1].hotkey[0] = 'n';
buttons[1].hotkey[1] = 'N';
buttons[1].hotkey[2] = ACKNOWLEDGE_KEY;
buttons[1].hotkey[3] = ESCAPE_KEY;
buttons[1].flags |= (B_WIDE_CLICK_AREA | B_KEYPRESS_HIGHLIGHT);
retVal = printTextBox(prompt, COLS/3, ROWS/3, COLS/3, &white, &interfaceBoxColor, rbuf, buttons, 2);
overlayDisplayBuffer(rbuf, NULL);
if (retVal == -1 || retVal == 1) { // If they canceled or pressed no.
return false;
} else {
return true;
}
confirmMessages();
return retVal;
}
void clearMonsterFlashes() {
}
void displayMonsterFlashes(boolean flashingEnabled) {
creature *monst;
short x[100], y[100], strength[100], count = 0;
color *flashColor[100];
short oldRNG;
rogue.creaturesWillFlashThisTurn = false;
if (rogue.autoPlayingLevel || rogue.blockCombatText) {
return;
}
oldRNG = rogue.RNG;
rogue.RNG = RNG_COSMETIC;
//assureCosmeticRNG;
CYCLE_MONSTERS_AND_PLAYERS(monst) {
if (monst->bookkeepingFlags & MB_WILL_FLASH) {
monst->bookkeepingFlags &= ~MB_WILL_FLASH;
if (flashingEnabled && canSeeMonster(monst) && count < 100) {
x[count] = monst->xLoc;
y[count] = monst->yLoc;
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);
}
void displayMessageArchive() {
short i, j, k, reverse, fadePercent, totalMessageCount, currentMessageCount;
boolean fastForward;
cellDisplayBuffer dbuf[COLS][ROWS], rbuf[COLS][ROWS];
// Count the number of lines in the archive.
for (totalMessageCount=0;
totalMessageCount < MESSAGE_ARCHIVE_LINES && messageArchive[totalMessageCount][0];
totalMessageCount++);
if (totalMessageCount > MESSAGE_LINES) {
copyDisplayBuffer(rbuf, displayBuffer);
// Pull-down/pull-up animation:
for (reverse = 0; reverse <= 1; reverse++) {
fastForward = false;
for (currentMessageCount = (reverse ? totalMessageCount : MESSAGE_LINES);
(reverse ? currentMessageCount >= MESSAGE_LINES : currentMessageCount <= totalMessageCount);
currentMessageCount += (reverse ? -1 : 1)) {
clearDisplayBuffer(dbuf);
// Print the message archive text to the dbuf.
for (j=0; j < currentMessageCount && j < ROWS; j++) {
printString(messageArchive[(messageArchivePosition - currentMessageCount + MESSAGE_ARCHIVE_LINES + j) % MESSAGE_ARCHIVE_LINES],
mapToWindowX(0), j, &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.
for (j=0; j < currentMessageCount && j<ROWS; j++) {
fadePercent = 50 * (j + totalMessageCount - currentMessageCount) / totalMessageCount + 50;
for (i=0; i<DCOLS; i++) {
dbuf[mapToWindowX(i)][j].opacity = INTERFACE_OPACITY;
if (dbuf[mapToWindowX(i)][j].character != ' ') {
for (k=0; k<3; k++) {
dbuf[mapToWindowX(i)][j].foreColorComponents[k] = dbuf[mapToWindowX(i)][j].foreColorComponents[k] * fadePercent / 100;
}
}
}
}
// Display.
overlayDisplayBuffer(rbuf, 0);
overlayDisplayBuffer(dbuf, 0);
if (!fastForward && pauseBrogue(reverse ? 1 : 2)) {
fastForward = true;
dequeueEvent();
currentMessageCount = (reverse ? MESSAGE_LINES + 1 : totalMessageCount - 1); // skip to the end
}
}
if (!reverse) {
displayMoreSign();
}
}
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(char *msg, boolean requireAcknowledgment) {
char message[COLS];
short i, j;
assureCosmeticRNG;
strcpy(message, msg);
for (i=0; message[i] == COLOR_ESCAPE; i += 4) {
upperCase(&(message[i]));
}
refreshSideBar(-1, -1, false);
for (i=0; i<MESSAGE_LINES; i++) {
for (j=0; j<DCOLS; j++) {
plotCharWithColor(' ', mapToWindowX(j), i, &black, &black);
}
}
printString(message, mapToWindowX(0), mapToWindowY(-1), &white, &black, 0);
if (requireAcknowledgment) {
waitForAcknowledgment();
updateMessageDisplay();
}
restoreRNG;
}
void messageWithColor(char *msg, color *theColor, boolean requireAcknowledgment) {
char buf[COLS*2] = "";
short i;
i=0;
i = encodeMessageColor(buf, i, theColor);
strcpy(&(buf[i]), msg);
message(buf, requireAcknowledgment);
}
void flavorMessage(char *msg) {
short i;
char text[COLS*20];
for (i=0; i < COLS*2 && msg[i] != '\0'; i++) {
text[i] = msg[i];
}
text[i] = '\0';
for(i=0; text[i] == COLOR_ESCAPE; i+=4);
upperCase(&(text[i]));
printString(text, mapToWindowX(0), ROWS - 2, &flavorTextColor, &black, 0);
for (i = strLenWithoutEscapes(text); i < DCOLS; i++) {
plotCharWithColor(' ', mapToWindowX(i), ROWS - 2, &black, &black);
}
}
void messageWithoutCaps(char *msg, boolean requireAcknowledgment) {
short i;
if (!msg[0]) {
return;
}
// need to confirm the oldest message? (Disabled!)
/*if (!messageConfirmed[MESSAGE_LINES - 1]) {
//refreshSideBar(-1, -1, false);
displayMoreSign();
for (i=0; i<MESSAGE_LINES; i++) {
messageConfirmed[i] = true;
}
}*/
for (i = MESSAGE_LINES - 1; i >= 1; i--) {
messageConfirmed[i] = messageConfirmed[i-1];
strcpy(displayedMessage[i], displayedMessage[i-1]);
}
messageConfirmed[0] = false;
strcpy(displayedMessage[0], msg);
// Add the message to the archive.
strcpy(messageArchive[messageArchivePosition], msg);
messageArchivePosition = (messageArchivePosition + 1) % MESSAGE_ARCHIVE_LINES;
// display the message:
updateMessageDisplay();
if (requireAcknowledgment || rogue.cautiousMode) {
displayMoreSign();
confirmMessages();
rogue.cautiousMode = false;
}
if (rogue.playbackMode) {
rogue.playbackDelayThisTurn += rogue.playbackDelayPerTurn * 5;
}
}
void message(const char *msg, boolean requireAcknowledgment) {
char text[COLS*20], *msgPtr;
short i, lines;
assureCosmeticRNG;
rogue.disturbed = true;
if (requireAcknowledgment) {
refreshSideBar(-1, -1, false);
}
displayCombatText();
lines = wrapText(text, msg, DCOLS);
msgPtr = &(text[0]);
// Implement the American quotation mark/period/comma ordering rule.
for (i=0; text[i] != '\0' && text[i+1] != '\0'; i++) {
if (text[i] == COLOR_ESCAPE) {
i += 4;
} else if (text[i] == '"'
&& (text[i+1] == '.' || text[i+1] == ',')) {
text[i] = text[i+1];
text[i+1] = '"';
}
}
for(i=0; text[i] == COLOR_ESCAPE; i+=4);
upperCase(&(text[i]));
if (lines > 1) {
for (i=0; text[i] != '\0'; i++) {
if (text[i] == '\n') {
text[i] = '\0';
messageWithoutCaps(msgPtr, false);
msgPtr = &(text[i+1]);
}
}
}
messageWithoutCaps(msgPtr, requireAcknowledgment);
restoreRNG;
}
// Only used for the "you die..." message, to enable posthumous inventory viewing.
void displayMoreSignWithoutWaitingForAcknowledgment() {
if (strLenWithoutEscapes(displayedMessage[0]) < DCOLS - 8 || messageConfirmed[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;
if (rogue.autoPlayingLevel) {
return;
}
if (strLenWithoutEscapes(displayedMessage[0]) < DCOLS - 8 || messageConfirmed[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;
assureCosmeticRNG;
bakeColor(&col);
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];
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';
}
restoreRNG;
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;
}
// Returns a color for combat text based on the identity of the victim.
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;
}
}
void updateMessageDisplay() {
short i, j, m;
color messageColor;
for (i=0; i<MESSAGE_LINES; i++) {
messageColor = white;
if (messageConfirmed[i]) {
applyColorAverage(&messageColor, &black, 50);
applyColorAverage(&messageColor, &black, 75 * i / MESSAGE_LINES);
}
for (j = m = 0; displayedMessage[i][m] && j < DCOLS; j++, m++) {
while (displayedMessage[i][m] == COLOR_ESCAPE) {
m = decodeMessageColor(displayedMessage[i], m, &messageColor); // pulls the message color out and advances m
if (messageConfirmed[i]) {
applyColorAverage(&messageColor, &black, 50);
applyColorAverage(&messageColor, &black, 75 * i / MESSAGE_LINES);
}
}
plotCharWithColor(displayedMessage[i][m], mapToWindowX(j), MESSAGE_LINES - i - 1,
&messageColor,
&black);
}
for (; j < DCOLS; j++) {
plotCharWithColor(' ', mapToWindowX(j), MESSAGE_LINES - i - 1, &black, &black);
}
}
}
// Does NOT clear the message archive.
void deleteMessages() {
short i;
for (i=0; i<MESSAGE_LINES; i++) {
displayedMessage[i][0] = '\0';
}
confirmMessages();
}
void confirmMessages() {
short i;
for (i=0; i<MESSAGE_LINES; i++) {
messageConfirmed[i] = true;
}
updateMessageDisplay();
}
void stripShiftFromMovementKeystroke(signed long *keystroke) {
const unsigned short newKey = *keystroke - ('A' - 'a');
if (newKey == LEFT_KEY
|| newKey == RIGHT_KEY
|| newKey == DOWN_KEY
|| newKey == UP_KEY
|| newKey == UPLEFT_KEY
|| newKey == UPRIGHT_KEY
|| newKey == DOWNLEFT_KEY
|| newKey == DOWNRIGHT_KEY) {
*keystroke -= 'A' - 'a';
}
}
void upperCase(char *theChar) {
if (*theChar >= 'a' && *theChar <= 'z') {
(*theChar) += ('A' - 'a');
}
}
enum entityDisplayTypes {
EDT_NOTHING = 0,
EDT_CREATURE,
EDT_ITEM,
EDT_TERRAIN,
};
// Refreshes the sidebar.
// Progresses from the closest visible monster to the farthest.
// If a monster, item or terrain is focused, then display the sidebar with that monster/item highlighted,
// in the order it would normally appear. If it would normally not fit on the sidebar at all,
// then list it first.
// Also update rogue.sidebarLocationList[ROWS][2] list of locations so that each row of
// the screen is mapped to the corresponding entity, if any.
// FocusedEntityMustGoFirst should usually be false when called externally. This is because
// 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 *monst = NULL, *closestMonst = NULL;
item *theItem, *closestItem = NULL;
char buf[COLS];
void *entityList[ROWS] = {0}, *focusEntity = NULL;
enum entityDisplayTypes entityType[ROWS] = {0}, focusEntityType = EDT_NOTHING;
short terrainLocationMap[ROWS][2];
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)) {
monst = monsterAtLoc(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.xLoc;
py = player.yLoc;
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][0] = -1;
rogue.sidebarLocationList[i][1] = -1;
}
// Player always goes first.
entityList[displayEntityCount] = &player;
entityType[displayEntityCount] = EDT_CREATURE;
displayEntityCount++;
addedEntity[player.xLoc][player.yLoc] = 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][0] = focusX;
terrainLocationMap[displayEntityCount][1] = focusY;
displayEntityCount++;
}
for (indirectVision = 0; indirectVision < 2; indirectVision++) {
// Non-focused monsters.
do {
shortestDistance = 10000;
for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
if ((canDirectlySeeMonster(monst) || (indirectVision && (canSeeMonster(monst) || rogue.playbackOmniscience)))
&& !addedEntity[monst->xLoc][monst->yLoc]
&& !(monst->info.flags & MONST_NOT_LISTED_IN_SIDEBAR)
&& (px - monst->xLoc) * (px - monst->xLoc) + (py - monst->yLoc) * (py - monst->yLoc) < shortestDistance) {
shortestDistance = (px - monst->xLoc) * (px - monst->xLoc) + (py - monst->yLoc) * (py - monst->yLoc);
closestMonst = monst;
}
}
if (shortestDistance < 10000) {
addedEntity[closestMonst->xLoc][closestMonst->yLoc] = 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->xLoc, theItem->yLoc) || (indirectVision && (playerCanSeeOrSense(theItem->xLoc, theItem->yLoc) || rogue.playbackOmniscience)))
&& !addedEntity[theItem->xLoc][theItem->yLoc]
&& (px - theItem->xLoc) * (px - theItem->xLoc) + (py - theItem->yLoc) * (py - theItem->yLoc) < shortestDistance) {
shortestDistance = (px - theItem->xLoc) * (px - theItem->xLoc) + (py - theItem->yLoc) * (py - theItem->yLoc);
closestItem = theItem;
}
}
if (shortestDistance < 10000) {
addedEntity[closestItem->xLoc][closestItem->yLoc] = 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][0] = i;
terrainLocationMap[displayEntityCount][1] = 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])->xLoc;
y = ((creature *) entityList[i])->yLoc;
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])->xLoc;
y = ((item *) entityList[i])->yLoc;
printY = printItemInfo((item *) entityList[i],
printY,
(focusEntity && (x != focusX || y != focusY)),
(x == focusX && y == focusY));
} else if (entityType[i] == EDT_TERRAIN) {
x = terrainLocationMap[i][0];
y = terrainLocationMap[i][1];
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][0] = x;
rogue.sidebarLocationList[j][1] = 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, color *foreColor, color *backColor, cellDisplayBuffer dbuf[COLS][ROWS]) {
color fColor;
short i;
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;
}
}
if (dbuf) {
plotCharToBuffer(theString[i], x, y, &fColor, backColor, dbuf);
} else {
plotCharWithColor(theString[i], x, y, &fColor, backColor);
}
}
}
// 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);
}
// 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.
textLength = strlen(printString); // do NOT discount escape sequences
lineCount = 1;
// Now go through and replace spaces with newlines as needed.
// 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;
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(char *theString, short x, short y, short width, color *foreColor,
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 (dbuf) {
if (coordinatesAreInWindow(px, py)) {
plotCharToBuffer(printString[i], px, py, &fColor, backColor, dbuf);
}
} else {
if (coordinatesAreInWindow(px, py)) {
plotCharWithColor(printString[i], px, py, &fColor, backColor);
}
}
px++;
}
return py;
}
char nextKeyPress(boolean textInput) {
rogueEvent theEvent;
do {
nextBrogueEvent(&theEvent, textInput, false, false);
} while (theEvent.eventType != KEYSTROKE);
return theEvent.param1;
}
#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",
" <return> ****enable keyboard cursor control",
" <space/esc> ****disable keyboard cursor control",
"hjklyubn, arrow keys, or numpad ****move or attack (control or shift to run)",
"",
" a/e/r/t/d/c/R ****apply/equip/remove/throw/drop/call/relabel 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 ****suspend game and quit",
" Q ****quit to title screen",
"",
" \\ ****disable/enable color effects",
" ] ****display/hide stealth range",
" <space/esc> ****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(' ', 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 *theColor, goodColor, badColor;
char buf[COLS], buf2[COLS];
short i, magic, totalFrequency;
itemTable *theTable = tableForItemCategory(category, NULL);
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, x, y + i, &itemColor, &black, dbuf);
} else {
theColor = &darkGray;
magic = magicCharDiscoverySuffix(category, i);
if (magic == 1) {
plotCharToBuffer(G_GOOD_MAGIC, x, y + i, &goodColor, &black, dbuf);
} else if (magic == -1) {
plotCharToBuffer(G_BAD_MAGIC, x, y + i, &badColor, &black, dbuf);
}
}
strcpy(buf, theTable[i].name);
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);
}
}
void printDiscoveriesScreen() {
short i, j, y;
cellDisplayBuffer dbuf[COLS][ROWS], rbuf[COLS][ROWS];
clearDisplayBuffer(dbuf);
printString("-- SCROLLS --", mapToWindowX(2), y = mapToWindowY(1), &flavorTextColor, &black, dbuf);
printDiscoveries(SCROLL, NUMBER_SCROLL_KINDS, G_SCROLL, mapToWindowX(3), ++y, dbuf);
printString("-- RINGS --", mapToWindowX(2), y += NUMBER_SCROLL_KINDS + 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, NUMBER_POTION_KINDS, 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("-- WANDS --", mapToWindowX(53), y += NUMBER_STAFF_KINDS + 1, &flavorTextColor, &black, dbuf);
printDiscoveries(WAND, NUMBER_WAND_KINDS, 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);
for (i=0; i<COLS; i++) {
for (j=0; j<ROWS; j++) {
dbuf[i][j].opacity = (i < STAT_BAR_WIDTH ? 0 : INTERFACE_OPACITY);
}
}
overlayDisplayBuffer(dbuf, rbuf);
waitForKeystrokeOrMouseClick();
overlayDisplayBuffer(rbuf, NULL);
}
// Creates buttons for the discoveries screen in the buttons pointer; returns the number of buttons created.
//short createDiscoveriesButtons(short category, short count, unsigned short itemCharacter, short x, short y, brogueButton *buttons) {
// color goodColor, badColor;
// char whiteColorEscape[20] = "", darkGrayColorEscape[20] = "", yellowColorEscape[20] = "", goodColorEscape[20] = "", badColorEscape[20] = "";
// short i, magic, symbolCount;
// itemTable *theTable = tableForItemCategory(category, NULL);
// char buf[COLS] = "";
//
// goodColor = goodMessageColor;
// applyColorAverage(&goodColor, &black, 50);
// encodeMessageColor(goodColorEscape, 0, &goodColor);
// badColor = badMessageColor;
// applyColorAverage(&badColor, &black, 50);
// encodeMessageColor(badColorEscape, 0, &badColor);
// encodeMessageColor(whiteColorEscape, 0, &white);
// encodeMessageColor(darkGrayColorEscape, 0, &darkGray);
// encodeMessageColor(yellowColorEscape, 0, &itemColor);
//
// for (i = 0; i < count; i++) {
// initializeButton(&(buttons[i]));
// buttons[i].flags = (B_DRAW | B_HOVER_ENABLED | B_ENABLED); // Clear other flags.
// buttons[i].buttonColor = black;
// buttons[i].opacity = 100;
// buttons[i].x = x;
// buttons[i].y = y + i;
// symbolCount = 0;
// if (theTable[i].identified) {
// strcat(buttons[i].text, yellowColorEscape);
// buttons[i].symbol[symbolCount++] = itemCharacter;
// strcat(buttons[i].text, "*");
// strcat(buttons[i].text, whiteColorEscape);
// strcat(buttons[i].text, " ");
// } else {
// strcat(buttons[i].text, " ");
// strcat(buttons[i].text, darkGrayColorEscape);
// }
// strcpy(buf, theTable[i].name);
// upperCase(buf);
// strcat(buttons[i].text, buf);
// strcat(buttons[i].text, " ");
// strcat(buttons[i].text, darkGrayColorEscape);
// magic = magicCharDiscoverySuffix(category, i);
// strcat(buttons[i].text, "(");
// if (magic != -1) {
// strcat(buttons[i].text, goodColorEscape);
// strcat(buttons[i].text, "*");
// buttons[i].symbol[symbolCount++] = G_GOOD_MAGIC;
// }
// if (magic != 1) {
// strcat(buttons[i].text, badColorEscape);
// strcat(buttons[i].text, "*");
// buttons[i].symbol[symbolCount++] = BAD_MAGIC_CHAR;
// }
// strcat(buttons[i].text, darkGrayColorEscape);
// strcat(buttons[i].text, ")");
// }
// return i;
//}
//
//void printDiscoveriesScreen() {
// short i, j, y, buttonCount;
// cellDisplayBuffer dbuf[COLS][ROWS], rbuf[COLS][ROWS];
// brogueButton buttons[NUMBER_SCROLL_KINDS + NUMBER_WAND_KINDS + NUMBER_POTION_KINDS + NUMBER_STAFF_KINDS + NUMBER_RING_KINDS] = {{{0}}};
// rogueEvent theEvent;
//
// clearDisplayBuffer(dbuf);
// buttonCount = 0;
//
// printString("-- SCROLLS --", mapToWindowX(3), y = mapToWindowY(1), &flavorTextColor, &black, dbuf);
// buttonCount += createDiscoveriesButtons(SCROLL, NUMBER_SCROLL_KINDS, SCROLL_CHAR, mapToWindowX(3), ++y, &(buttons[buttonCount]));
//
// printString("-- WANDS --", mapToWindowX(3), y += NUMBER_SCROLL_KINDS + 1, &flavorTextColor, &black, dbuf);
// buttonCount += createDiscoveriesButtons(WAND, NUMBER_WAND_KINDS, WAND_CHAR, mapToWindowX(3), ++y, &(buttons[buttonCount]));
//
// printString("-- POTIONS --", mapToWindowX(29), y = mapToWindowY(1), &flavorTextColor, &black, dbuf);
// buttonCount += createDiscoveriesButtons(POTION, NUMBER_POTION_KINDS, POTION_CHAR, mapToWindowX(29), ++y, &(buttons[buttonCount]));
//
// printString("-- STAFFS --", mapToWindowX(54), y = mapToWindowY(1), &flavorTextColor, &black, dbuf);
// buttonCount += createDiscoveriesButtons(STAFF, NUMBER_STAFF_KINDS, STAFF_CHAR, mapToWindowX(54), ++y, &(buttons[buttonCount]));
//
// printString("-- RINGS --", mapToWindowX(54), y += NUMBER_STAFF_KINDS + 1, &flavorTextColor, &black, dbuf);
// buttonCount += createDiscoveriesButtons(RING, NUMBER_RING_KINDS, RING_CHAR, mapToWindowX(54), ++y, &(buttons[buttonCount]));
//
// for (i=0; i<COLS; i++) {
// for (j=0; j<ROWS; j++) {
// dbuf[i][j].opacity = (i < STAT_BAR_WIDTH ? 0 : INTERFACE_OPACITY);
// }
// }
// overlayDisplayBuffer(dbuf, rbuf);
// y = buttonInputLoop(buttons,
// buttonCount,
// mapToWindowX(3),
// mapToWindowY(1),
// DCOLS - 6,
// DROWS - 2,
// &theEvent);
// overlayDisplayBuffer(rbuf, NULL);
//}
void printHighScores(boolean hiliteMostRecent) {
short i, hiliteLineNum, maxLength = 0, leftOffset;
rogueHighScoresEntry list[HIGH_SCORES_COUNT] = {{0}};
char buf[DCOLS*3];
color scoreColor;
hiliteLineNum = getHighScoresList(list);
if (!hiliteMostRecent) {
hiliteLineNum = -1;
}
blackOutScreen();
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);
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));
}
// 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);
// date
printString(list[i].date, leftOffset + 12, i + 2, &scoreColor, &black, 0);
// description
printString(list[i].description, leftOffset + 23, i + 2, &scoreColor, &black, 0);
}
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);
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<DCOLS; i++) {
for (j=0; j<DROWS; j++) {
if (cellHasTerrainFlag(i, j, T_WAYPOINT_BLOCKER) || (map[i][j] == map[0][0]) || (i == player.xLoc && j == player.yLoc)) {
continue;
}
if (map[i][j] > 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.xLoc && j == player.yLoc)) {
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, mapToWindowX(i), mapToWindowY(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];
sprintf(buf, "Dungeon seed #%lu; turn #%lu; version %s", rogue.seed, rogue.playerTurnNumber, BROGUE_VERSION_STRING);
message(buf, false);
}
void printProgressBar(short x, short y, const char barLabel[COLS], long amtFilled, long amtMax, 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], x + i, y, &textColor, ¤tFillColor);
}
}
// Very low-level. Changes displayBuffer directly.
void highlightScreenCell(short x, short y, color *highlightColor, short strength) {
color 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);
displayBuffer[x][y].needsUpdate = true;
}
short estimatedArmorValue() {
short retVal;
retVal = ((armorTable[rogue.armor->kind].range.upperBound + armorTable[rogue.armor->kind].range.lowerBound) / 2) / 10;
retVal += strengthModifier(rogue.armor) / FP_FACTOR;
retVal -= 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;
}
// 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, displayedArmor, 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,
"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 = (pmap[monst->xLoc][monst->yLoc].flags & IS_IN_PATH) ? true : false;
pmap[monst->xLoc][monst->yLoc].flags &= ~IS_IN_PATH;
getCellAppearance(monst->xLoc, monst->yLoc, &monstChar, &monstForeColor, &monstBackColor);
applyColorBounds(&monstForeColor, 0, 100);
applyColorBounds(&monstBackColor, 0, 100);
if (inPath) {
pmap[monst->xLoc][monst->yLoc].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, 0, y, &monstForeColor, &monstBackColor);
if(monst->carriedItem) {
plotCharWithColor(monst->carriedItem->displayChar, 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 (!(pmap[player.xLoc][player.yLoc].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)) {
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 {
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 (monst->targetCorpseLoc[0] == monst->xLoc && monst->targetCorpseLoc[1] == monst->yLoc) {
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 (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->xLoc, monst->yLoc, 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));
}
displayedArmor = displayedArmorValue();
if (!rogue.armor || rogue.armor->flags & ITEM_IDENTIFIED || rogue.playbackOmniscience) {
sprintf(buf, "Str: %s%i%s Armor: %i",
tempColorEscape,
rogue.strength - player.weaknessAmount,
grayColorEscape,
displayedArmor);
} 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.aggroRange - 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.aggroRange,
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);
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;
}
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 = (pmap[theItem->xLoc][theItem->yLoc].flags & IS_IN_PATH) ? true : false;
pmap[theItem->xLoc][theItem->yLoc].flags &= ~IS_IN_PATH;
getCellAppearance(theItem->xLoc, theItem->yLoc, &itemChar, &itemForeColor, &itemBackColor);
applyColorBounds(&itemForeColor, 0, 100);
applyColorBounds(&itemBackColor, 0, 100);
if (inPath) {
pmap[theItem->xLoc][theItem->yLoc].flags |= IS_IN_PATH;
}
if (dim) {
applyColorAverage(&itemForeColor, &black, 50);
applyColorAverage(&itemBackColor, &black, 50);
}
plotCharWithColor(itemChar, 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);
}
}
}
y += 2;
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;
}
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, 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);
}
}
}
py += 2;
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<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<COLS; i++) {
// for (j=0; j<ROWS; j++) {
// if (i >= x && i < x + width && j >= y && j < y + height) {
// plotCharWithColor(' ', i, j, &white, &darkGreen);
// }
// }
// }
// displayMoreSign();
restoreRNG;
}
#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
// screen, and x will be adjusted to keep the widening box from spilling off the right of the
// screen.
// 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,
color *foreColor, 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;
}
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];
monsterDetails(textBuf, monst);
printTextBox(textBuf, monst->xLoc, 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,
cellDisplayBuffer rbuf[COLS][ROWS]) {
char textBuf[COLS * 100], goldColorEscape[5] = "", whiteColorEscape[5] = "";
brogueButton buttons[20] = {{{0}}};
short b;
itemDetails(textBuf, theItem);
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);
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++;
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;
}
}
// Returns true if an action was taken.
void printFloorItemDetails(item *theItem, cellDisplayBuffer rbuf[COLS][ROWS]) {
char textBuf[COLS * 100];
itemDetails(textBuf, theItem);
printTextBox(textBuf, theItem->xLoc, 0, 0, &white, &black, rbuf, NULL, 0);
}