[BACK]Return to IO.c CVS log [TXT][DIR] Up to [contributed] / brogue-ce / src / brogue

Annotation of brogue-ce/src/brogue/IO.c, Revision 1.1.1.1

1.1       rubenllo    1: /*
                      2:  *  IO.c
                      3:  *  Brogue
                      4:  *
                      5:  *  Created by Brian Walker on 1/10/09.
                      6:  *  Copyright 2012. All rights reserved.
                      7:  *
                      8:  *  This file is part of Brogue.
                      9:  *
                     10:  *  This program is free software: you can redistribute it and/or modify
                     11:  *  it under the terms of the GNU Affero General Public License as
                     12:  *  published by the Free Software Foundation, either version 3 of the
                     13:  *  License, or (at your option) any later version.
                     14:  *
                     15:  *  This program is distributed in the hope that it will be useful,
                     16:  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
                     17:  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                     18:  *  GNU Affero General Public License for more details.
                     19:  *
                     20:  *  You should have received a copy of the GNU Affero General Public License
                     21:  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
                     22:  */
                     23:
                     24: #include <math.h>
                     25: #include <time.h>
                     26:
                     27: #include "Rogue.h"
                     28: #include "IncludeGlobals.h"
                     29:
                     30: // Populates path[][] with a list of coordinates starting at origin and traversing down the map. Returns the number of steps in the path.
                     31: short getPlayerPathOnMap(short path[1000][2], short **map, short originX, short originY) {
                     32:     short dir, x, y, steps;
                     33:
                     34:     x = originX;
                     35:     y = originY;
                     36:
                     37:     dir = 0;
                     38:
                     39:     for (steps = 0; dir != -1;) {
                     40:         dir = nextStep(map, x, y, &player, false);
                     41:         if (dir != -1) {
                     42:             x += nbDirs[dir][0];
                     43:             y += nbDirs[dir][1];
                     44:             path[steps][0] = x;
                     45:             path[steps][1] = y;
                     46:             steps++;
                     47:             brogueAssert(coordinatesAreInMap(x, y));
                     48:         }
                     49:     }
                     50:     return steps;
                     51: }
                     52:
                     53: void reversePath(short path[1000][2], short steps) {
                     54:     short i, x, y;
                     55:
                     56:     for (i=0; i<steps / 2; i++) {
                     57:         x = path[steps - i - 1][0];
                     58:         y = path[steps - i - 1][1];
                     59:
                     60:         path[steps - i - 1][0] = path[i][0];
                     61:         path[steps - i - 1][1] = path[i][1];
                     62:
                     63:         path[i][0] = x;
                     64:         path[i][1] = y;
                     65:     }
                     66: }
                     67:
                     68: void hilitePath(short path[1000][2], short steps, boolean unhilite) {
                     69:     short i;
                     70:     if (unhilite) {
                     71:         for (i=0; i<steps; i++) {
                     72:             brogueAssert(coordinatesAreInMap(path[i][0], path[i][1]));
                     73:             pmap[path[i][0]][path[i][1]].flags &= ~IS_IN_PATH;
                     74:             refreshDungeonCell(path[i][0], path[i][1]);
                     75:         }
                     76:     } else {
                     77:         for (i=0; i<steps; i++) {
                     78:             brogueAssert(coordinatesAreInMap(path[i][0], path[i][1]));
                     79:             pmap[path[i][0]][path[i][1]].flags |= IS_IN_PATH;
                     80:             refreshDungeonCell(path[i][0], path[i][1]);
                     81:         }
                     82:     }
                     83: }
                     84:
                     85: // More expensive than hilitePath(__, __, true), but you don't need access to the path itself.
                     86: void clearCursorPath() {
                     87:     short i, j;
                     88:
                     89:     if (!rogue.playbackMode) { // There are no cursor paths during playback.
                     90:         for (i=1; i<DCOLS; i++) {
                     91:             for (j=1; j<DROWS; j++) {
                     92:                 if (pmap[i][j].flags & IS_IN_PATH) {
                     93:                     pmap[i][j].flags &= ~IS_IN_PATH;
                     94:                     refreshDungeonCell(i, j);
                     95:                 }
                     96:             }
                     97:         }
                     98:     }
                     99: }
                    100:
                    101: void hideCursor() {
                    102:     // Drop out of cursor mode if we're in it, and hide the path either way.
                    103:     rogue.cursorMode = false;
                    104:     rogue.cursorPathIntensity = (rogue.cursorMode ? 50 : 20);
                    105:     rogue.cursorLoc[0] = -1;
                    106:     rogue.cursorLoc[1] = -1;
                    107: }
                    108:
                    109: void showCursor() {
                    110:     // Return or enter turns on cursor mode. When the path is hidden, move the cursor to the player.
                    111:     if (!coordinatesAreInMap(rogue.cursorLoc[0], rogue.cursorLoc[1])) {
                    112:         rogue.cursorLoc[0] = player.xLoc;
                    113:         rogue.cursorLoc[1] = player.yLoc;
                    114:         rogue.cursorMode = true;
                    115:         rogue.cursorPathIntensity = (rogue.cursorMode ? 50 : 20);
                    116:     } else {
                    117:         rogue.cursorMode = true;
                    118:         rogue.cursorPathIntensity = (rogue.cursorMode ? 50 : 20);
                    119:     }
                    120: }
                    121:
                    122: void getClosestValidLocationOnMap(short loc[2], short **map, short x, short y) {
                    123:     short i, j, dist, closestDistance, lowestMapScore;
                    124:
                    125:     closestDistance = 10000;
                    126:     lowestMapScore = 10000;
                    127:     for (i=1; i<DCOLS-1; i++) {
                    128:         for (j=1; j<DROWS-1; j++) {
                    129:             if (map[i][j] >= 0
                    130:                 && map[i][j] < 30000) {
                    131:
                    132:                 dist = (i - x)*(i - x) + (j - y)*(j - y);
                    133:                 //hiliteCell(i, j, &purple, min(dist / 2, 100), false);
                    134:                 if (dist < closestDistance
                    135:                     || dist == closestDistance && map[i][j] < lowestMapScore) {
                    136:
                    137:                     loc[0] = i;
                    138:                     loc[1] = j;
                    139:                     closestDistance = dist;
                    140:                     lowestMapScore = map[i][j];
                    141:                 }
                    142:             }
                    143:         }
                    144:     }
                    145: }
                    146:
                    147: void processSnapMap(short **map) {
                    148:     short **costMap;
                    149:     enum directions dir;
                    150:     short i, j, newX, newY;
                    151:
                    152:     costMap = allocGrid();
                    153:
                    154:     populateCreatureCostMap(costMap, &player);
                    155:     fillGrid(map, 30000);
                    156:     map[player.xLoc][player.yLoc] = 0;
                    157:     dijkstraScan(map, costMap, true);
                    158:     for (i = 0; i < DCOLS; i++) {
                    159:         for (j = 0; j < DROWS; j++) {
                    160:             if (cellHasTMFlag(i, j, TM_INVERT_WHEN_HIGHLIGHTED)) {
                    161:                 for (dir = 0; dir < 4; dir++) {
                    162:                     newX = i + nbDirs[dir][0];
                    163:                     newY = j + nbDirs[dir][1];
                    164:                     if (coordinatesAreInMap(newX, newY)
                    165:                         && map[newX][newY] >= 0
                    166:                         && map[newX][newY] < map[i][j]) {
                    167:
                    168:                         map[i][j] = map[newX][newY];
                    169:                     }
                    170:                 }
                    171:             }
                    172:         }
                    173:     }
                    174:
                    175:     freeGrid(costMap);
                    176: }
                    177:
                    178: // Displays a menu of buttons for various commands.
                    179: // Buttons will be disabled if not permitted based on the playback state.
                    180: // Returns the keystroke to effect the button's command, or -1 if canceled.
                    181: // Some buttons take effect in this function instead of returning a value,
                    182: // i.e. true colors mode and display stealth mode.
                    183: short actionMenu(short x, boolean playingBack) {
                    184:     short buttonCount;
                    185:     short y;
                    186:     boolean takeActionOurselves[ROWS] = {false};
                    187:     rogueEvent theEvent;
                    188:
                    189:     brogueButton buttons[ROWS] = {{{0}}};
                    190:     char yellowColorEscape[5] = "", whiteColorEscape[5] = "", darkGrayColorEscape[5] = "";
                    191:     short i, j, longestName = 0, buttonChosen;
                    192:     cellDisplayBuffer dbuf[COLS][ROWS], rbuf[COLS][ROWS];
                    193:
                    194:     encodeMessageColor(yellowColorEscape, 0, &itemMessageColor);
                    195:     encodeMessageColor(whiteColorEscape, 0, &white);
                    196:     encodeMessageColor(darkGrayColorEscape, 0, &black);
                    197:
                    198:     do {
                    199:         for (i=0; i<ROWS; i++) {
                    200:             initializeButton(&(buttons[i]));
                    201:             buttons[i].buttonColor = interfaceBoxColor;
                    202:             buttons[i].opacity = INTERFACE_OPACITY;
                    203:         }
                    204:
                    205:         buttonCount = 0;
                    206:
                    207:         if (playingBack) {
                    208: #ifdef ENABLE_PLAYBACK_SWITCH
                    209:             if (KEYBOARD_LABELS) {
                    210:                 sprintf(buttons[buttonCount].text,  "  %sP: %sPlay from here  ", yellowColorEscape, whiteColorEscape);
                    211:             } else {
                    212:                 strcpy(buttons[buttonCount].text, "  Play from here  ");
                    213:             }
                    214:             buttons[buttonCount].hotkey[0] = SWITCH_TO_PLAYING_KEY;
                    215:             buttonCount++;
                    216:
                    217:             sprintf(buttons[buttonCount].text, "    %s---", darkGrayColorEscape);
                    218:             buttons[buttonCount].flags &= ~B_ENABLED;
                    219:             buttonCount++;
                    220: #endif
                    221:             if (KEYBOARD_LABELS) {
                    222:                 sprintf(buttons[buttonCount].text,  "  %sk: %sFaster playback  ", yellowColorEscape, whiteColorEscape);
                    223:             } else {
                    224:                 strcpy(buttons[buttonCount].text, "  Faster playback  ");
                    225:             }
                    226:             buttons[buttonCount].hotkey[0] = UP_KEY;
                    227:             buttons[buttonCount].hotkey[1] = UP_ARROW;
                    228:             buttons[buttonCount].hotkey[2] = NUMPAD_8;
                    229:             buttonCount++;
                    230:             if (KEYBOARD_LABELS) {
                    231:                 sprintf(buttons[buttonCount].text,  "  %sj: %sSlower playback  ", yellowColorEscape, whiteColorEscape);
                    232:             } else {
                    233:                 strcpy(buttons[buttonCount].text, "  Slower playback  ");
                    234:             }
                    235:             buttons[buttonCount].hotkey[0] = DOWN_KEY;
                    236:             buttons[buttonCount].hotkey[1] = DOWN_ARROW;
                    237:             buttons[buttonCount].hotkey[2] = NUMPAD_2;
                    238:             buttonCount++;
                    239:             sprintf(buttons[buttonCount].text, "    %s---", darkGrayColorEscape);
                    240:             buttons[buttonCount].flags &= ~B_ENABLED;
                    241:             buttonCount++;
                    242:
                    243:             if (KEYBOARD_LABELS) {
                    244:                 sprintf(buttons[buttonCount].text,  "%s0-9: %sFast forward to turn  ", yellowColorEscape, whiteColorEscape);
                    245:             } else {
                    246:                 strcpy(buttons[buttonCount].text, "  Fast forward to turn  ");
                    247:             }
                    248:             buttons[buttonCount].hotkey[0] = '0';
                    249:             buttonCount++;
                    250:             if (KEYBOARD_LABELS) {
                    251:                 sprintf(buttons[buttonCount].text,  "  %s<:%s Previous Level  ", yellowColorEscape, whiteColorEscape);
                    252:             } else {
                    253:                 strcpy(buttons[buttonCount].text, "  Previous Level  ");
                    254:             }
                    255:             buttons[buttonCount].hotkey[0] = ASCEND_KEY;
                    256:             buttonCount++;
                    257:             if (KEYBOARD_LABELS) {
                    258:                 sprintf(buttons[buttonCount].text,  "  %s>:%s Next Level  ", yellowColorEscape, whiteColorEscape);
                    259:             } else {
                    260:                 strcpy(buttons[buttonCount].text, "  Next Level  ");
                    261:             }
                    262:             buttons[buttonCount].hotkey[0] = DESCEND_KEY;
                    263:             buttonCount++;
                    264:             sprintf(buttons[buttonCount].text, "    %s---", darkGrayColorEscape);
                    265:             buttons[buttonCount].flags &= ~B_ENABLED;
                    266:             buttonCount++;
                    267:         } else {
                    268:             if (KEYBOARD_LABELS) {
                    269:                 sprintf(buttons[buttonCount].text, "  %sZ: %sRest until better  ",      yellowColorEscape, whiteColorEscape);
                    270:             } else {
                    271:                 strcpy(buttons[buttonCount].text, "  Rest until better  ");
                    272:             }
                    273:             buttons[buttonCount].hotkey[0] = AUTO_REST_KEY;
                    274:             buttonCount++;
                    275:
                    276:             if (KEYBOARD_LABELS) {
                    277:                 sprintf(buttons[buttonCount].text, "  %sA: %sAutopilot  ",              yellowColorEscape, whiteColorEscape);
                    278:             } else {
                    279:                 strcpy(buttons[buttonCount].text, "  Autopilot  ");
                    280:             }
                    281:             buttons[buttonCount].hotkey[0] = AUTOPLAY_KEY;
                    282:             buttonCount++;
                    283:
                    284:             if (KEYBOARD_LABELS) {
                    285:                 sprintf(buttons[buttonCount].text, "  %sT: %sRe-throw at last monster  ",              yellowColorEscape, whiteColorEscape);
                    286:             } else {
                    287:                 strcpy(buttons[buttonCount].text, "  Re-throw at last monster  ");
                    288:             }
                    289:             buttons[buttonCount].hotkey[0] = RETHROW_KEY;
                    290:             buttonCount++;
                    291:
                    292:             if (!rogue.easyMode) {
                    293:                 if (KEYBOARD_LABELS) {
                    294:                     sprintf(buttons[buttonCount].text, "  %s&: %sEasy mode  ",              yellowColorEscape, whiteColorEscape);
                    295:                 } else {
                    296:                     strcpy(buttons[buttonCount].text, "  Easy mode  ");
                    297:                 }
                    298:                 buttons[buttonCount].hotkey[0] = EASY_MODE_KEY;
                    299:                 buttonCount++;
                    300:             }
                    301:
                    302:             sprintf(buttons[buttonCount].text, "    %s---", darkGrayColorEscape);
                    303:             buttons[buttonCount].flags &= ~B_ENABLED;
                    304:             buttonCount++;
                    305:
                    306:             if(!serverMode) {
                    307:                 if (KEYBOARD_LABELS) {
                    308:                     sprintf(buttons[buttonCount].text, "  %sS: %sSuspend game and quit  ",  yellowColorEscape, whiteColorEscape);
                    309:                 } else {
                    310:                     strcpy(buttons[buttonCount].text, "  Suspend game and quit  ");
                    311:                 }
                    312:                 buttons[buttonCount].hotkey[0] = SAVE_GAME_KEY;
                    313:                 buttonCount++;
                    314:                 if (KEYBOARD_LABELS) {
                    315:                     sprintf(buttons[buttonCount].text, "  %sO: %sOpen suspended game  ",        yellowColorEscape, whiteColorEscape);
                    316:                 } else {
                    317:                     strcpy(buttons[buttonCount].text, "  Open suspended game  ");
                    318:                 }
                    319:                 buttons[buttonCount].hotkey[0] = LOAD_SAVED_GAME_KEY;
                    320:                 buttonCount++;
                    321:                 if (KEYBOARD_LABELS) {
                    322:                     sprintf(buttons[buttonCount].text, "  %sV: %sView saved recording  ",       yellowColorEscape, whiteColorEscape);
                    323:                 } else {
                    324:                     strcpy(buttons[buttonCount].text, "  View saved recording  ");
                    325:                 }
                    326:                 buttons[buttonCount].hotkey[0] = VIEW_RECORDING_KEY;
                    327:                 buttonCount++;
                    328:
                    329:                 sprintf(buttons[buttonCount].text, "    %s---", darkGrayColorEscape);
                    330:                 buttons[buttonCount].flags &= ~B_ENABLED;
                    331:                 buttonCount++;
                    332:             }
                    333:         }
                    334:
                    335:         if (KEYBOARD_LABELS) {
                    336:             sprintf(buttons[buttonCount].text, "  %s\\: %s[%s] Hide color effects  ",   yellowColorEscape, whiteColorEscape, rogue.trueColorMode ? "X" : " ");
                    337:         } else {
                    338:             sprintf(buttons[buttonCount].text, "  [%s] Hide color effects  ",   rogue.trueColorMode ? " " : "X");
                    339:         }
                    340:         buttons[buttonCount].hotkey[0] = TRUE_COLORS_KEY;
                    341:         takeActionOurselves[buttonCount] = true;
                    342:         buttonCount++;
                    343:         if (KEYBOARD_LABELS) {
                    344:             sprintf(buttons[buttonCount].text, "  %s]: %s[%s] Display stealth range  ", yellowColorEscape, whiteColorEscape, rogue.displayAggroRangeMode ? "X" : " ");
                    345:         } else {
                    346:             sprintf(buttons[buttonCount].text, "  [%s] Show stealth range  ",   rogue.displayAggroRangeMode ? "X" : " ");
                    347:         }
                    348:         buttons[buttonCount].hotkey[0] = AGGRO_DISPLAY_KEY;
                    349:         takeActionOurselves[buttonCount] = true;
                    350:         buttonCount++;
                    351:
                    352:         if (hasGraphics) {
                    353:             if (KEYBOARD_LABELS) {
                    354:                 sprintf(buttons[buttonCount].text, "  %sG: %s[%s] Enable graphics  ", yellowColorEscape, whiteColorEscape, graphicsEnabled ? "X" : " ");
                    355:             } else {
                    356:                 sprintf(buttons[buttonCount].text, "  [%s] Enable graphics  ",   graphicsEnabled ? "X" : " ");
                    357:             }
                    358:             buttons[buttonCount].hotkey[0] = GRAPHICS_KEY;
                    359:             takeActionOurselves[buttonCount] = true;
                    360:             buttonCount++;
                    361:         }
                    362:
                    363:         sprintf(buttons[buttonCount].text, "    %s---", darkGrayColorEscape);
                    364:         buttons[buttonCount].flags &= ~B_ENABLED;
                    365:         buttonCount++;
                    366:
                    367:         if (KEYBOARD_LABELS) {
                    368:             sprintf(buttons[buttonCount].text, "  %sD: %sDiscovered items  ",   yellowColorEscape, whiteColorEscape);
                    369:         } else {
                    370:             strcpy(buttons[buttonCount].text, "  Discovered items  ");
                    371:         }
                    372:         buttons[buttonCount].hotkey[0] = DISCOVERIES_KEY;
                    373:         buttonCount++;
                    374:         if (KEYBOARD_LABELS) {
                    375:             sprintf(buttons[buttonCount].text, "  %s~: %sView dungeon seed  ",  yellowColorEscape, whiteColorEscape);
                    376:         } else {
                    377:             strcpy(buttons[buttonCount].text, "  View dungeon seed  ");
                    378:         }
                    379:         buttons[buttonCount].hotkey[0] = SEED_KEY;
                    380:         buttonCount++;
                    381:         if (KEYBOARD_LABELS) { // No help button if we're not in keyboard mode.
                    382:             sprintf(buttons[buttonCount].text, "  %s?: %sHelp  ", yellowColorEscape, whiteColorEscape);
                    383:             buttons[buttonCount].hotkey[0] = BROGUE_HELP_KEY;
                    384:             buttonCount++;
                    385:         }
                    386:         sprintf(buttons[buttonCount].text, "    %s---", darkGrayColorEscape);
                    387:         buttons[buttonCount].flags &= ~B_ENABLED;
                    388:         buttonCount++;
                    389:
                    390:         if (KEYBOARD_LABELS) {
                    391:             sprintf(buttons[buttonCount].text, "  %sQ: %sQuit %s  ",    yellowColorEscape, whiteColorEscape, (playingBack ? "to title screen" : "without saving"));
                    392:         } else {
                    393:             sprintf(buttons[buttonCount].text, "  Quit %s  ",   (playingBack ? "to title screen" : "without saving"));
                    394:         }
                    395:         buttons[buttonCount].hotkey[0] = QUIT_KEY;
                    396:         buttonCount++;
                    397:
                    398:         strcpy(buttons[buttonCount].text, " ");
                    399:         buttons[buttonCount].flags &= ~B_ENABLED;
                    400:         buttonCount++;
                    401:
                    402:         for (i=0; i<buttonCount; i++) {
                    403:             longestName = max(longestName, strLenWithoutEscapes(buttons[i].text));
                    404:         }
                    405:         if (x + longestName >= COLS) {
                    406:             x = COLS - longestName - 1;
                    407:         }
                    408:         y = ROWS - buttonCount;
                    409:         for (i=0; i<buttonCount; i++) {
                    410:             buttons[i].x = x;
                    411:             buttons[i].y = y + i;
                    412:             for (j = strLenWithoutEscapes(buttons[i].text); j < longestName; j++) {
                    413:                 strcat(buttons[i].text, " "); // Schlemiel the Painter, but who cares.
                    414:             }
                    415:         }
                    416:
                    417:         clearDisplayBuffer(dbuf);
                    418:         rectangularShading(x - 1, y, longestName + 2, buttonCount, &black, INTERFACE_OPACITY / 2, dbuf);
                    419:         overlayDisplayBuffer(dbuf, rbuf);
                    420:         buttonChosen = buttonInputLoop(buttons, buttonCount, x - 1, y, longestName + 2, buttonCount, NULL);
                    421:         overlayDisplayBuffer(rbuf, NULL);
                    422:         if (buttonChosen == -1) {
                    423:             return -1;
                    424:         } else if (takeActionOurselves[buttonChosen]) {
                    425:
                    426:             theEvent.eventType = KEYSTROKE;
                    427:             theEvent.param1 = buttons[buttonChosen].hotkey[0];
                    428:             theEvent.param2 = 0;
                    429:             theEvent.shiftKey = theEvent.controlKey = false;
                    430:             executeEvent(&theEvent);
                    431:         } else {
                    432:             return buttons[buttonChosen].hotkey[0];
                    433:         }
                    434:     } while (takeActionOurselves[buttonChosen]);
                    435:     brogueAssert(false);
                    436:     return -1;
                    437: }
                    438:
                    439: #define MAX_MENU_BUTTON_COUNT 5
                    440:
                    441: void initializeMenuButtons(buttonState *state, brogueButton buttons[5]) {
                    442:     short i, x, buttonCount;
                    443:     char goldTextEscape[MAX_MENU_BUTTON_COUNT] = "";
                    444:     char whiteTextEscape[MAX_MENU_BUTTON_COUNT] = "";
                    445:     color tempColor;
                    446:
                    447:     encodeMessageColor(goldTextEscape, 0, KEYBOARD_LABELS ? &yellow : &white);
                    448:     encodeMessageColor(whiteTextEscape, 0, &white);
                    449:
                    450:     for (i=0; i<MAX_MENU_BUTTON_COUNT; i++) {
                    451:         initializeButton(&(buttons[i]));
                    452:         buttons[i].opacity = 75;
                    453:         buttons[i].buttonColor = interfaceButtonColor;
                    454:         buttons[i].y = ROWS - 1;
                    455:         buttons[i].flags |= B_WIDE_CLICK_AREA;
                    456:         buttons[i].flags &= ~B_KEYPRESS_HIGHLIGHT;
                    457:     }
                    458:
                    459:     buttonCount = 0;
                    460:
                    461:     if (rogue.playbackMode) {
                    462:         if (KEYBOARD_LABELS) {
                    463:             sprintf(buttons[buttonCount].text,  " Unpause (%sspace%s) ", goldTextEscape, whiteTextEscape);
                    464:         } else {
                    465:             strcpy(buttons[buttonCount].text,   "     Unpause     ");
                    466:         }
                    467:         buttons[buttonCount].hotkey[0] = ACKNOWLEDGE_KEY;
                    468:         buttonCount++;
                    469:
                    470:         if (KEYBOARD_LABELS) {
                    471:             sprintf(buttons[buttonCount].text,  "Omniscience (%stab%s)", goldTextEscape, whiteTextEscape);
                    472:         } else {
                    473:             strcpy(buttons[buttonCount].text,   "   Omniscience   ");
                    474:         }
                    475:         buttons[buttonCount].hotkey[0] = TAB_KEY;
                    476:         buttonCount++;
                    477:
                    478:         if (KEYBOARD_LABELS) {
                    479:             sprintf(buttons[buttonCount].text,  " Next Turn (%sl%s) ", goldTextEscape, whiteTextEscape);
                    480:         } else {
                    481:             strcpy(buttons[buttonCount].text,   "   Next Turn   ");
                    482:         }
                    483:         buttons[buttonCount].hotkey[0] = RIGHT_KEY;
                    484:         buttons[buttonCount].hotkey[1] = RIGHT_ARROW;
                    485:         buttonCount++;
                    486:
                    487:         strcpy(buttons[buttonCount].text,       "  Menu  ");
                    488:         buttonCount++;
                    489:     } else {
                    490:         sprintf(buttons[buttonCount].text,  "   E%sx%splore   ", goldTextEscape, whiteTextEscape);
                    491:         buttons[buttonCount].hotkey[0] = EXPLORE_KEY;
                    492:         buttons[buttonCount].hotkey[1] = 'X';
                    493:         buttonCount++;
                    494:
                    495:         if (KEYBOARD_LABELS) {
                    496:             sprintf(buttons[buttonCount].text,  "   Rest (%sz%s)   ", goldTextEscape, whiteTextEscape);
                    497:         } else {
                    498:             strcpy(buttons[buttonCount].text,   "     Rest     ");
                    499:         }
                    500:         buttons[buttonCount].hotkey[0] = REST_KEY;
                    501:         buttonCount++;
                    502:
                    503:         if (KEYBOARD_LABELS) {
                    504:             sprintf(buttons[buttonCount].text,  "  Search (%ss%s)  ", goldTextEscape, whiteTextEscape);
                    505:         } else {
                    506:             strcpy(buttons[buttonCount].text,   "    Search    ");
                    507:         }
                    508:         buttons[buttonCount].hotkey[0] = SEARCH_KEY;
                    509:         buttonCount++;
                    510:
                    511:         strcpy(buttons[buttonCount].text,       "    Menu    ");
                    512:         buttonCount++;
                    513:     }
                    514:
                    515:     sprintf(buttons[4].text,    "   %sI%snventory   ", goldTextEscape, whiteTextEscape);
                    516:     buttons[4].hotkey[0] = INVENTORY_KEY;
                    517:     buttons[4].hotkey[1] = 'I';
                    518:
                    519:     x = mapToWindowX(0);
                    520:     for (i=0; i<5; i++) {
                    521:         buttons[i].x = x;
                    522:         x += strLenWithoutEscapes(buttons[i].text) + 2; // Gap between buttons.
                    523:     }
                    524:
                    525:     initializeButtonState(state,
                    526:                           buttons,
                    527:                           5,
                    528:                           mapToWindowX(0),
                    529:                           ROWS - 1,
                    530:                           COLS - mapToWindowX(0),
                    531:                           1);
                    532:
                    533:     for (i=0; i < 5; i++) {
                    534:         drawButton(&(state->buttons[i]), BUTTON_NORMAL, state->rbuf);
                    535:     }
                    536:     for (i=0; i<COLS; i++) { // So the buttons stay (but are dimmed and desaturated) when inactive.
                    537:         tempColor = colorFromComponents(state->rbuf[i][ROWS - 1].backColorComponents);
                    538:         desaturate(&tempColor, 60);
                    539:         applyColorAverage(&tempColor, &black, 50);
                    540:         storeColorComponents(state->rbuf[i][ROWS - 1].backColorComponents, &tempColor);
                    541:         tempColor = colorFromComponents(state->rbuf[i][ROWS - 1].foreColorComponents);
                    542:         desaturate(&tempColor, 60);
                    543:         applyColorAverage(&tempColor, &black, 50);
                    544:         storeColorComponents(state->rbuf[i][ROWS - 1].foreColorComponents, &tempColor);
                    545:     }
                    546: }
                    547:
                    548:
                    549: // This is basically the main loop for the game.
                    550: void mainInputLoop() {
                    551:     short originLoc[2], pathDestination[2], oldTargetLoc[2] = { 0, 0 },
                    552:     path[1000][2], steps, oldRNG, dir, newX, newY;
                    553:     creature *monst;
                    554:     item *theItem;
                    555:     cellDisplayBuffer rbuf[COLS][ROWS];
                    556:
                    557:     boolean canceled, targetConfirmed, tabKey, focusedOnMonster, focusedOnItem, focusedOnTerrain,
                    558:     playingBack, doEvent, textDisplayed;
                    559:
                    560:     rogueEvent theEvent;
                    561:     short **costMap, **playerPathingMap, **cursorSnapMap;
                    562:     brogueButton buttons[5] = {{{0}}};
                    563:     buttonState state;
                    564:     short buttonInput;
                    565:     short backupCost;
                    566:
                    567:     short *cursor = rogue.cursorLoc; // shorthand
                    568:
                    569:     canceled = false;
                    570:     rogue.cursorMode = false; // Controls whether the keyboard moves the cursor or the character.
                    571:     steps = 0;
                    572:
                    573:     rogue.cursorPathIntensity = (rogue.cursorMode ? 50 : 20);
                    574:
                    575:     // Initialize buttons.
                    576:     initializeMenuButtons(&state, buttons);
                    577:
                    578:     playingBack = rogue.playbackMode;
                    579:     rogue.playbackMode = false;
                    580:     costMap = allocGrid();
                    581:     playerPathingMap = allocGrid();
                    582:     cursorSnapMap = allocGrid();
                    583:
                    584:     cursor[0] = cursor[1] = -1;
                    585:
                    586:     while (!rogue.gameHasEnded && (!playingBack || !canceled)) { // repeats until the game ends
                    587:
                    588:         oldRNG = rogue.RNG;
                    589:         rogue.RNG = RNG_COSMETIC;
                    590:
                    591:         focusedOnMonster = focusedOnItem = focusedOnTerrain = false;
                    592:         steps = 0;
                    593:         clearCursorPath();
                    594:
                    595:         originLoc[0] = player.xLoc;
                    596:         originLoc[1] = player.yLoc;
                    597:
                    598:         if (playingBack && rogue.cursorMode) {
                    599:             temporaryMessage("Examine what? (<hjklyubn>, mouse, or <tab>)", false);
                    600:         }
                    601:
                    602:         if (!playingBack
                    603:             && player.xLoc == cursor[0]
                    604:             && player.yLoc == cursor[1]
                    605:             && oldTargetLoc[0] == cursor[0]
                    606:             && oldTargetLoc[1] == cursor[1]) {
                    607:
                    608:             // Path hides when you reach your destination.
                    609:             rogue.cursorMode = false;
                    610:             rogue.cursorPathIntensity = (rogue.cursorMode ? 50 : 20);
                    611:             cursor[0] = -1;
                    612:             cursor[1] = -1;
                    613:         }
                    614:
                    615:         oldTargetLoc[0] = cursor[0];
                    616:         oldTargetLoc[1] = cursor[1];
                    617:
                    618:         populateCreatureCostMap(costMap, &player);
                    619:
                    620:         fillGrid(playerPathingMap, 30000);
                    621:         playerPathingMap[player.xLoc][player.yLoc] = 0;
                    622:         dijkstraScan(playerPathingMap, costMap, true);
                    623:         processSnapMap(cursorSnapMap);
                    624:
                    625:         do {
                    626:             textDisplayed = false;
                    627:
                    628:             // Draw the cursor and path
                    629:             if (coordinatesAreInMap(oldTargetLoc[0], oldTargetLoc[1])) {
                    630:                 refreshDungeonCell(oldTargetLoc[0], oldTargetLoc[1]);               // Remove old cursor.
                    631:             }
                    632:             if (!playingBack) {
                    633:                 if (coordinatesAreInMap(oldTargetLoc[0], oldTargetLoc[1])) {
                    634:                     hilitePath(path, steps, true);                                  // Unhilite old path.
                    635:                 }
                    636:                 if (coordinatesAreInMap(cursor[0], cursor[1])) {
                    637:                     if (cursorSnapMap[cursor[0]][cursor[1]] >= 0
                    638:                         && cursorSnapMap[cursor[0]][cursor[1]] < 30000) {
                    639:
                    640:                         pathDestination[0] = cursor[0];
                    641:                         pathDestination[1] = cursor[1];
                    642:                     } else {
                    643:                         // If the cursor is aimed at an inaccessible area, find the nearest accessible area to path toward.
                    644:                         getClosestValidLocationOnMap(pathDestination, cursorSnapMap, cursor[0], cursor[1]);
                    645:                     }
                    646:
                    647:                     fillGrid(playerPathingMap, 30000);
                    648:                     playerPathingMap[pathDestination[0]][pathDestination[1]] = 0;
                    649:                     backupCost = costMap[pathDestination[0]][pathDestination[1]];
                    650:                     costMap[pathDestination[0]][pathDestination[1]] = 1;
                    651:                     dijkstraScan(playerPathingMap, costMap, true);
                    652:                     costMap[pathDestination[0]][pathDestination[1]] = backupCost;
                    653:                     steps = getPlayerPathOnMap(path, playerPathingMap, player.xLoc, player.yLoc);
                    654:
                    655: //                  steps = getPlayerPathOnMap(path, playerPathingMap, pathDestination[0], pathDestination[1]) - 1; // Get new path.
                    656: //                  reversePath(path, steps);   // Flip it around, back-to-front.
                    657:
                    658:                     if (steps >= 0) {
                    659:                         path[steps][0] = pathDestination[0];
                    660:                         path[steps][1] = pathDestination[1];
                    661:                     }
                    662:                     steps++;
                    663: //                  if (playerPathingMap[cursor[0]][cursor[1]] != 1
                    664:                     if (playerPathingMap[player.xLoc][player.yLoc] != 1
                    665:                         || pathDestination[0] != cursor[0]
                    666:                         || pathDestination[1] != cursor[1]) {
                    667:
                    668:                         hilitePath(path, steps, false);     // Hilite new path.
                    669:                     }
                    670:                 }
                    671:             }
                    672:
                    673:             if (coordinatesAreInMap(cursor[0], cursor[1])) {
                    674:                 hiliteCell(cursor[0],
                    675:                            cursor[1],
                    676:                            &white,
                    677:                            (steps <= 0
                    678:                             || (path[steps-1][0] == cursor[0] && path[steps-1][1] == cursor[1])
                    679:                             || (!playingBack && distanceBetween(player.xLoc, player.yLoc, cursor[0], cursor[1]) <= 1) ? 100 : 25),
                    680:                            true);
                    681:
                    682:                 oldTargetLoc[0] = cursor[0];
                    683:                 oldTargetLoc[1] = cursor[1];
                    684:
                    685:                 monst = monsterAtLoc(cursor[0], cursor[1]);
                    686:                 theItem = itemAtLoc(cursor[0], cursor[1]);
                    687:                 if (monst != NULL && (canSeeMonster(monst) || rogue.playbackOmniscience)) {
                    688:                     rogue.playbackMode = playingBack;
                    689:                     refreshSideBar(cursor[0], cursor[1], false);
                    690:                     rogue.playbackMode = false;
                    691:
                    692:                     focusedOnMonster = true;
                    693:                     if (monst != &player && (!player.status[STATUS_HALLUCINATING] || rogue.playbackOmniscience)) {
                    694:                         printMonsterDetails(monst, rbuf);
                    695:                         textDisplayed = true;
                    696:                     }
                    697:                 } else if (theItem != NULL && playerCanSeeOrSense(cursor[0], cursor[1])) {
                    698:                     rogue.playbackMode = playingBack;
                    699:                     refreshSideBar(cursor[0], cursor[1], false);
                    700:                     rogue.playbackMode = false;
                    701:
                    702:                     focusedOnItem = true;
                    703:                     if (!player.status[STATUS_HALLUCINATING] || rogue.playbackOmniscience) {
                    704:                         printFloorItemDetails(theItem, rbuf);
                    705:                         textDisplayed = true;
                    706:                     }
                    707:                 } else if (cellHasTMFlag(cursor[0], cursor[1], TM_LIST_IN_SIDEBAR) && playerCanSeeOrSense(cursor[0], cursor[1])) {
                    708:                     rogue.playbackMode = playingBack;
                    709:                     refreshSideBar(cursor[0], cursor[1], false);
                    710:                     rogue.playbackMode = false;
                    711:                     focusedOnTerrain = true;
                    712:                 }
                    713:
                    714:                 printLocationDescription(cursor[0], cursor[1]);
                    715:             }
                    716:
                    717:             // Get the input!
                    718:             rogue.playbackMode = playingBack;
                    719:             doEvent = moveCursor(&targetConfirmed, &canceled, &tabKey, cursor, &theEvent, &state, !textDisplayed, rogue.cursorMode, true);
                    720:             rogue.playbackMode = false;
                    721:
                    722:             if (state.buttonChosen == 3) { // Actions menu button.
                    723:                 buttonInput = actionMenu(buttons[3].x - 4, playingBack); // Returns the corresponding keystroke.
                    724:                 if (buttonInput == -1) { // Canceled.
                    725:                     doEvent = false;
                    726:                 } else {
                    727:                     theEvent.eventType = KEYSTROKE;
                    728:                     theEvent.param1 = buttonInput;
                    729:                     theEvent.param2 = 0;
                    730:                     theEvent.shiftKey = theEvent.controlKey = false;
                    731:                     doEvent = true;
                    732:                 }
                    733:             } else if (state.buttonChosen > -1) {
                    734:                 theEvent.eventType = KEYSTROKE;
                    735:                 theEvent.param1 = buttons[state.buttonChosen].hotkey[0];
                    736:                 theEvent.param2 = 0;
                    737:             }
                    738:             state.buttonChosen = -1;
                    739:
                    740:             if (playingBack) {
                    741:                 if (canceled) {
                    742:                     rogue.cursorMode = false;
                    743:                     rogue.cursorPathIntensity = (rogue.cursorMode ? 50 : 20);
                    744:                 }
                    745:
                    746:                 if (theEvent.eventType == KEYSTROKE
                    747:                     && theEvent.param1 == ACKNOWLEDGE_KEY) { // To unpause by button during playback.
                    748:                     canceled = true;
                    749:                 } else {
                    750:                     canceled = false;
                    751:                 }
                    752:             }
                    753:
                    754:             if (focusedOnMonster || focusedOnItem || focusedOnTerrain) {
                    755:                 focusedOnMonster = false;
                    756:                 focusedOnItem = false;
                    757:                 focusedOnTerrain = false;
                    758:                 if (textDisplayed) {
                    759:                     overlayDisplayBuffer(rbuf, 0); // Erase the monster info window.
                    760:                 }
                    761:                 rogue.playbackMode = playingBack;
                    762:                 refreshSideBar(-1, -1, false);
                    763:                 rogue.playbackMode = false;
                    764:             }
                    765:
                    766:             if (tabKey && !playingBack) { // The tab key cycles the cursor through monsters, items and terrain features.
                    767:                 if (nextTargetAfter(&newX, &newY, cursor[0], cursor[1], true, true, true, true, false, theEvent.shiftKey)) {
                    768:                     cursor[0] = newX;
                    769:                     cursor[1] = newY;
                    770:                 }
                    771:             }
                    772:
                    773:             if (theEvent.eventType == KEYSTROKE
                    774:                 && (theEvent.param1 == ASCEND_KEY && cursor[0] == rogue.upLoc[0] && cursor[1] == rogue.upLoc[1]
                    775:                     || theEvent.param1 == DESCEND_KEY && cursor[0] == rogue.downLoc[0] && cursor[1] == rogue.downLoc[1])) {
                    776:
                    777:                     targetConfirmed = true;
                    778:                     doEvent = false;
                    779:                 }
                    780:         } while (!targetConfirmed && !canceled && !doEvent && !rogue.gameHasEnded);
                    781:
                    782:         if (coordinatesAreInMap(oldTargetLoc[0], oldTargetLoc[1])) {
                    783:             refreshDungeonCell(oldTargetLoc[0], oldTargetLoc[1]);                       // Remove old cursor.
                    784:         }
                    785:
                    786:         restoreRNG;
                    787:
                    788:         if (canceled && !playingBack) {
                    789:             hideCursor();
                    790:             confirmMessages();
                    791:         } else if (targetConfirmed && !playingBack && coordinatesAreInMap(cursor[0], cursor[1])) {
                    792:             if (theEvent.eventType == MOUSE_UP
                    793:                 && theEvent.controlKey
                    794:                 && steps > 1) {
                    795:                 // Control-clicking moves the player one step along the path.
                    796:                 for (dir=0;
                    797:                      dir < DIRECTION_COUNT && (player.xLoc + nbDirs[dir][0] != path[0][0] || player.yLoc + nbDirs[dir][1] != path[0][1]);
                    798:                      dir++);
                    799:                 playerMoves(dir);
                    800:             } else if (D_WORMHOLING) {
                    801:                 travel(cursor[0], cursor[1], true);
                    802:             } else {
                    803:                 confirmMessages();
                    804:                 if (originLoc[0] == cursor[0]
                    805:                     && originLoc[1] == cursor[1]) {
                    806:
                    807:                     confirmMessages();
                    808:                 } else if (abs(player.xLoc - cursor[0]) + abs(player.yLoc - cursor[1]) == 1 // horizontal or vertical
                    809:                            || (distanceBetween(player.xLoc, player.yLoc, cursor[0], cursor[1]) == 1 // includes diagonals
                    810:                                && (!diagonalBlocked(player.xLoc, player.yLoc, cursor[0], cursor[1], !rogue.playbackOmniscience)
                    811:                                    || ((pmap[cursor[0]][cursor[1]].flags & HAS_MONSTER) && (monsterAtLoc(cursor[0], cursor[1])->info.flags & MONST_ATTACKABLE_THRU_WALLS)) // there's a turret there
                    812:                                    || ((terrainFlags(cursor[0], cursor[1]) & T_OBSTRUCTS_PASSABILITY) && (terrainMechFlags(cursor[0], cursor[1]) & TM_PROMOTES_ON_PLAYER_ENTRY))))) { // there's a lever there
                    813:                                                                                                                                                                                       // Clicking one space away will cause the player to try to move there directly irrespective of path.
                    814:                                    for (dir=0;
                    815:                                         dir < DIRECTION_COUNT && (player.xLoc + nbDirs[dir][0] != cursor[0] || player.yLoc + nbDirs[dir][1] != cursor[1]);
                    816:                                         dir++);
                    817:                                    playerMoves(dir);
                    818:                                } else if (steps) {
                    819:                                    travelRoute(path, steps);
                    820:                                }
                    821:             }
                    822:         } else if (doEvent) {
                    823:             // If the player entered input during moveCursor() that wasn't a cursor movement command.
                    824:             // Mainly, we want to filter out directional keystrokes when we're in cursor mode, since
                    825:             // those should move the cursor but not the player.
                    826:             brogueAssert(rogue.RNG == RNG_SUBSTANTIVE);
                    827:             if (playingBack) {
                    828:                 rogue.playbackMode = true;
                    829:                 executePlaybackInput(&theEvent);
                    830: #ifdef ENABLE_PLAYBACK_SWITCH
                    831:                 if (!rogue.playbackMode) {
                    832:                     // Playback mode is off, user must have taken control
                    833:                     // Redraw buttons to reflect that
                    834:                     initializeMenuButtons(&state, buttons);
                    835:                 }
                    836: #endif
                    837:                 playingBack = rogue.playbackMode;
                    838:                 rogue.playbackMode = false;
                    839:             } else {
                    840:                 executeEvent(&theEvent);
                    841:                 if (rogue.playbackMode) {
                    842:                     playingBack = true;
                    843:                     rogue.playbackMode = false;
                    844:                     confirmMessages();
                    845:                     break;
                    846:                 }
                    847:             }
                    848:         }
                    849:     }
                    850:
                    851:     rogue.playbackMode = playingBack;
                    852:     refreshSideBar(-1, -1, false);
                    853:     freeGrid(costMap);
                    854:     freeGrid(playerPathingMap);
                    855:     freeGrid(cursorSnapMap);
                    856: }
                    857:
                    858: // accuracy depends on how many clock cycles occur per second
                    859: #define MILLISECONDS    (clock() * 1000 / CLOCKS_PER_SEC)
                    860:
                    861: #define MILLISECONDS_FOR_CAUTION    100
                    862:
                    863: void considerCautiousMode() {
                    864:     /*
                    865:     signed long oldMilliseconds = rogue.milliseconds;
                    866:     rogue.milliseconds = MILLISECONDS;
                    867:     clock_t i = clock();
                    868:     printf("\n%li", i);
                    869:     if (rogue.milliseconds - oldMilliseconds < MILLISECONDS_FOR_CAUTION) {
                    870:         rogue.cautiousMode = true;
                    871:     }*/
                    872: }
                    873:
                    874: // flags the entire window as needing to be redrawn at next flush.
                    875: // very low level -- does not interface with the guts of the game.
                    876: void refreshScreen() {
                    877:     short i, j;
                    878:
                    879:     for( i=0; i<COLS; i++ ) {
                    880:         for( j=0; j<ROWS; j++ ) {
                    881:             displayBuffer[i][j].needsUpdate = true;
                    882:         }
                    883:     }
                    884:     commitDraws();
                    885: }
                    886:
                    887: // higher-level redraw
                    888: void displayLevel() {
                    889:     short i, j;
                    890:
                    891:     for( i=0; i<DCOLS; i++ ) {
                    892:         for (j = DROWS-1; j >= 0; j--) {
                    893:             refreshDungeonCell(i, j);
                    894:         }
                    895:     }
                    896: }
                    897:
                    898: // converts colors into components
                    899: void storeColorComponents(char components[3], const color *theColor) {
                    900:     short rand = rand_range(0, theColor->rand);
                    901:     components[0] = max(0, min(100, theColor->red + rand_range(0, theColor->redRand) + rand));
                    902:     components[1] = max(0, min(100, theColor->green + rand_range(0, theColor->greenRand) + rand));
                    903:     components[2] = max(0, min(100, theColor->blue + rand_range(0, theColor->blueRand) + rand));
                    904: }
                    905:
                    906: void bakeTerrainColors(color *foreColor, color *backColor, short x, short y) {
                    907:     const short *vals;
                    908:     const short neutralColors[8] = {1000, 1000, 1000, 1000, 0, 0, 0, 0};
                    909:     if (rogue.trueColorMode) {
                    910:         vals = neutralColors;
                    911:     } else {
                    912:         vals = &(terrainRandomValues[x][y][0]);
                    913:     }
                    914:
                    915:     const short foreRand = foreColor->rand * vals[6] / 1000;
                    916:     const short backRand = backColor->rand * vals[7] / 1000;
                    917:
                    918:     foreColor->red += foreColor->redRand * vals[0] / 1000 + foreRand;
                    919:     foreColor->green += foreColor->greenRand * vals[1] / 1000 + foreRand;
                    920:     foreColor->blue += foreColor->blueRand * vals[2] / 1000 + foreRand;
                    921:     foreColor->redRand = foreColor->greenRand = foreColor->blueRand = foreColor->rand = 0;
                    922:
                    923:     backColor->red += backColor->redRand * vals[3] / 1000 + backRand;
                    924:     backColor->green += backColor->greenRand * vals[4] / 1000 + backRand;
                    925:     backColor->blue += backColor->blueRand * vals[5] / 1000 + backRand;
                    926:     backColor->redRand = backColor->greenRand = backColor->blueRand = backColor->rand = 0;
                    927:
                    928:     if (foreColor->colorDances || backColor->colorDances) {
                    929:         pmap[x][y].flags |= TERRAIN_COLORS_DANCING;
                    930:     } else {
                    931:         pmap[x][y].flags &= ~TERRAIN_COLORS_DANCING;
                    932:     }
                    933: }
                    934:
                    935: void bakeColor(color *theColor) {
                    936:     short rand;
                    937:     rand = rand_range(0, theColor->rand);
                    938:     theColor->red += rand_range(0, theColor->redRand) + rand;
                    939:     theColor->green += rand_range(0, theColor->greenRand) + rand;
                    940:     theColor->blue += rand_range(0, theColor->blueRand) + rand;
                    941:     theColor->redRand = theColor->greenRand = theColor->blueRand = theColor->rand = 0;
                    942: }
                    943:
                    944: void shuffleTerrainColors(short percentOfCells, boolean refreshCells) {
                    945:     enum directions dir;
                    946:     short i, j;
                    947:
                    948:     assureCosmeticRNG;
                    949:
                    950:     for (i=0; i<DCOLS; i++) {
                    951:         for(j=0; j<DROWS; j++) {
                    952:             if (playerCanSeeOrSense(i, j)
                    953:                 && (!rogue.automationActive || !(rogue.playerTurnNumber % 5))
                    954:                 && ((pmap[i][j].flags & TERRAIN_COLORS_DANCING)
                    955:                     || (player.status[STATUS_HALLUCINATING] && playerCanDirectlySee(i, j)))
                    956:                 && (i != rogue.cursorLoc[0] || j != rogue.cursorLoc[1])
                    957:                 && (percentOfCells >= 100 || rand_range(1, 100) <= percentOfCells)) {
                    958:
                    959:                     for (dir=0; dir<DIRECTION_COUNT; dir++) {
                    960:                         terrainRandomValues[i][j][dir] += rand_range(-600, 600);
                    961:                         terrainRandomValues[i][j][dir] = clamp(terrainRandomValues[i][j][dir], 0, 1000);
                    962:                     }
                    963:
                    964:                     if (refreshCells) {
                    965:                         refreshDungeonCell(i, j);
                    966:                     }
                    967:                 }
                    968:         }
                    969:     }
                    970:     restoreRNG;
                    971: }
                    972:
                    973: // if forecolor is too similar to back, darken or lighten it and return true.
                    974: // Assumes colors have already been baked (no random components).
                    975: boolean separateColors(color *fore, color *back) {
                    976:     color f, b, *modifier;
                    977:     short failsafe;
                    978:     boolean madeChange;
                    979:
                    980:     f = *fore;
                    981:     b = *back;
                    982:     f.red       = clamp(f.red, 0, 100);
                    983:     f.green     = clamp(f.green, 0, 100);
                    984:     f.blue      = clamp(f.blue, 0, 100);
                    985:     b.red       = clamp(b.red, 0, 100);
                    986:     b.green     = clamp(b.green, 0, 100);
                    987:     b.blue      = clamp(b.blue, 0, 100);
                    988:
                    989:     if (f.red + f.blue + f.green > 50 * 3) {
                    990:         modifier = &black;
                    991:     } else {
                    992:         modifier = &white;
                    993:     }
                    994:
                    995:     madeChange = false;
                    996:     failsafe = 10;
                    997:
                    998:     while(COLOR_DIFF(f, b) < MIN_COLOR_DIFF && --failsafe) {
                    999:         applyColorAverage(&f, modifier, 20);
                   1000:         madeChange = true;
                   1001:     }
                   1002:
                   1003:     if (madeChange) {
                   1004:         *fore = f;
                   1005:         return true;
                   1006:     } else {
                   1007:         return false;
                   1008:     }
                   1009: }
                   1010:
                   1011: void normColor(color *baseColor, const short aggregateMultiplier, const short colorTranslation) {
                   1012:
                   1013:     baseColor->red += colorTranslation;
                   1014:     baseColor->green += colorTranslation;
                   1015:     baseColor->blue += colorTranslation;
                   1016:     const short vectorLength =  baseColor->red + baseColor->green + baseColor->blue;
                   1017:
                   1018:     if (vectorLength != 0) {
                   1019:         baseColor->red =    baseColor->red * 300    / vectorLength * aggregateMultiplier / 100;
                   1020:         baseColor->green =  baseColor->green * 300  / vectorLength * aggregateMultiplier / 100;
                   1021:         baseColor->blue =   baseColor->blue * 300   / vectorLength * aggregateMultiplier / 100;
                   1022:     }
                   1023:     baseColor->redRand = 0;
                   1024:     baseColor->greenRand = 0;
                   1025:     baseColor->blueRand = 0;
                   1026:     baseColor->rand = 0;
                   1027: }
                   1028:
                   1029: // Used to determine whether to draw a wall top glyph above
                   1030: static boolean glyphIsWallish(enum displayGlyph glyph) {
                   1031:     switch (glyph) {
                   1032:         case G_WALL:
                   1033:         case G_OPEN_DOOR:
                   1034:         case G_CLOSED_DOOR:
                   1035:         case G_UP_STAIRS:
                   1036:         case G_DOORWAY:
                   1037:         case G_WALL_TOP:
                   1038:         case G_LEVER:
                   1039:         case G_LEVER_PULLED:
                   1040:         case G_CLOSED_IRON_DOOR:
                   1041:         case G_OPEN_IRON_DOOR:
                   1042:         case G_TURRET:
                   1043:         case G_GRANITE:
                   1044:         case G_TORCH:
                   1045:         case G_PORTCULLIS:
                   1046:             return true;
                   1047:
                   1048:         default:
                   1049:             return false;
                   1050:     }
                   1051: }
                   1052:
                   1053: static enum monsterTypes randomAnimateMonster() {
                   1054:     /* Randomly pick an animate and vulnerable monster type. Used by
                   1055:     getCellAppearance for hallucination effects. */
                   1056:     static int listLength = 0;
                   1057:     static enum monsterTypes animate[NUMBER_MONSTER_KINDS];
                   1058:
                   1059:     if (listLength == 0) {
                   1060:         for (int i=0; i < NUMBER_MONSTER_KINDS; i++) {
                   1061:             if (!(monsterCatalog[i].flags & (MONST_INANIMATE | MONST_INVULNERABLE))) {
                   1062:                 animate[listLength++] = i;
                   1063:             }
                   1064:         }
                   1065:     }
                   1066:
                   1067:     return animate[rand_range(0, listLength - 1)];
                   1068: }
                   1069:
                   1070: // okay, this is kind of a beast...
                   1071: void getCellAppearance(short x, short y, enum displayGlyph *returnChar, color *returnForeColor, color *returnBackColor) {
                   1072:     short bestBCPriority, bestFCPriority, bestCharPriority;
                   1073:     short distance;
                   1074:     enum displayGlyph cellChar = 0;
                   1075:     color cellForeColor, cellBackColor, lightMultiplierColor = black, gasAugmentColor;
                   1076:     boolean monsterWithDetectedItem = false, needDistinctness = false;
                   1077:     short gasAugmentWeight = 0;
                   1078:     creature *monst = NULL;
                   1079:     item *theItem = NULL;
                   1080:     enum tileType tile = NOTHING;
                   1081:     const enum displayGlyph itemChars[] = {G_POTION, G_SCROLL, G_FOOD, G_WAND,
                   1082:                         G_STAFF, G_GOLD, G_ARMOR, G_WEAPON, G_RING, G_CHARM};
                   1083:     enum dungeonLayers layer, maxLayer;
                   1084:
                   1085:     assureCosmeticRNG;
                   1086:
                   1087:     brogueAssert(coordinatesAreInMap(x, y));
                   1088:
                   1089:     if (pmap[x][y].flags & HAS_MONSTER) {
                   1090:         monst = monsterAtLoc(x, y);
                   1091:     } else if (pmap[x][y].flags & HAS_DORMANT_MONSTER) {
                   1092:         monst = dormantMonsterAtLoc(x, y);
                   1093:     }
                   1094:     if (monst) {
                   1095:         monsterWithDetectedItem = (monst->carriedItem && (monst->carriedItem->flags & ITEM_MAGIC_DETECTED)
                   1096:                                    && itemMagicPolarity(monst->carriedItem) && !canSeeMonster(monst));
                   1097:     }
                   1098:
                   1099:     if (monsterWithDetectedItem) {
                   1100:         theItem = monst->carriedItem;
                   1101:     } else {
                   1102:         theItem = itemAtLoc(x, y);
                   1103:     }
                   1104:
                   1105:     if (!playerCanSeeOrSense(x, y)
                   1106:         && !(pmap[x][y].flags & (ITEM_DETECTED | HAS_PLAYER))
                   1107:         && (!monst || !monsterRevealed(monst))
                   1108:         && !monsterWithDetectedItem
                   1109:         && (pmap[x][y].flags & (DISCOVERED | MAGIC_MAPPED))
                   1110:         && (pmap[x][y].flags & STABLE_MEMORY)) {
                   1111:
                   1112:         // restore memory
                   1113:         cellChar = pmap[x][y].rememberedAppearance.character;
                   1114:         cellForeColor = colorFromComponents(pmap[x][y].rememberedAppearance.foreColorComponents);
                   1115:         cellBackColor = colorFromComponents(pmap[x][y].rememberedAppearance.backColorComponents);
                   1116:     } else {
                   1117:         // Find the highest-priority fore color, back color and character.
                   1118:         bestFCPriority = bestBCPriority = bestCharPriority = 10000;
                   1119:
                   1120:         // Default to the appearance of floor.
                   1121:         cellForeColor = *(tileCatalog[FLOOR].foreColor);
                   1122:         cellBackColor = *(tileCatalog[FLOOR].backColor);
                   1123:         cellChar = tileCatalog[FLOOR].displayChar;
                   1124:
                   1125:         if (!(pmap[x][y].flags & DISCOVERED) && !rogue.playbackOmniscience) {
                   1126:             if (pmap[x][y].flags & MAGIC_MAPPED) {
                   1127:                 maxLayer = LIQUID + 1; // Can see only dungeon and liquid layers with magic mapping.
                   1128:             } else {
                   1129:                 maxLayer = 0; // Terrain shouldn't influence the tile appearance at all if it hasn't been discovered.
                   1130:             }
                   1131:         } else {
                   1132:             maxLayer = NUMBER_TERRAIN_LAYERS;
                   1133:         }
                   1134:
                   1135:         for (layer = 0; layer < maxLayer; layer++) {
                   1136:             // Gas shows up as a color average, not directly.
                   1137:             if (pmap[x][y].layers[layer] && layer != GAS) {
                   1138:                 tile = pmap[x][y].layers[layer];
                   1139:                 if (rogue.playbackOmniscience && (tileCatalog[tile].mechFlags & TM_IS_SECRET)) {
                   1140:                     tile = dungeonFeatureCatalog[tileCatalog[tile].discoverType].tile;
                   1141:                 }
                   1142:
                   1143:                 if (tileCatalog[tile].drawPriority < bestFCPriority
                   1144:                     && tileCatalog[tile].foreColor) {
                   1145:
                   1146:                     cellForeColor = *(tileCatalog[tile].foreColor);
                   1147:                     bestFCPriority = tileCatalog[tile].drawPriority;
                   1148:                 }
                   1149:                 if (tileCatalog[tile].drawPriority < bestBCPriority
                   1150:                     && tileCatalog[tile].backColor) {
                   1151:
                   1152:                     cellBackColor = *(tileCatalog[tile].backColor);
                   1153:                     bestBCPriority = tileCatalog[tile].drawPriority;
                   1154:                 }
                   1155:                 if (tileCatalog[tile].drawPriority < bestCharPriority
                   1156:                     && tileCatalog[tile].displayChar) {
                   1157:
                   1158:                     cellChar = tileCatalog[tile].displayChar;
                   1159:                     bestCharPriority = tileCatalog[tile].drawPriority;
                   1160:                     needDistinctness = (tileCatalog[tile].mechFlags & TM_VISUALLY_DISTINCT) ? true : false;
                   1161:                 }
                   1162:             }
                   1163:         }
                   1164:
                   1165:         if (rogue.trueColorMode) {
                   1166:             lightMultiplierColor = colorMultiplier100;
                   1167:         } else {
                   1168:             colorMultiplierFromDungeonLight(x, y, &lightMultiplierColor);
                   1169:         }
                   1170:
                   1171:         if (pmap[x][y].layers[GAS]
                   1172:             && tileCatalog[pmap[x][y].layers[GAS]].backColor) {
                   1173:
                   1174:             gasAugmentColor = *(tileCatalog[pmap[x][y].layers[GAS]].backColor);
                   1175:             if (rogue.trueColorMode) {
                   1176:                 gasAugmentWeight = 30;
                   1177:             } else {
                   1178:                 gasAugmentWeight = min(90, 30 + pmap[x][y].volume);
                   1179:             }
                   1180:         }
                   1181:
                   1182:         if (D_DISABLE_BACKGROUND_COLORS) {
                   1183:             if (COLOR_DIFF(cellBackColor, black) > COLOR_DIFF(cellForeColor, black)) {
                   1184:                 cellForeColor = cellBackColor;
                   1185:             }
                   1186:             cellBackColor = black;
                   1187:             needDistinctness = true;
                   1188:         }
                   1189:
                   1190:         if (pmap[x][y].flags & HAS_PLAYER) {
                   1191:             cellChar = player.info.displayChar;
                   1192:             cellForeColor = *(player.info.foreColor);
                   1193:             needDistinctness = true;
                   1194:         } else if (((pmap[x][y].flags & HAS_ITEM) && (pmap[x][y].flags & ITEM_DETECTED)
                   1195:                     && itemMagicPolarity(theItem)
                   1196:                     && !playerCanSeeOrSense(x, y))
                   1197:                    || monsterWithDetectedItem){
                   1198:
                   1199:             int polarity = itemMagicPolarity(theItem);
                   1200:             if (theItem->category == AMULET) {
                   1201:                 cellChar = G_AMULET;
                   1202:                 cellForeColor = white;
                   1203:             } else if (polarity == -1) {
                   1204:                 cellChar = G_BAD_MAGIC;
                   1205:                 cellForeColor = badMessageColor;
                   1206:             } else if (polarity == 1) {
                   1207:                 cellChar = G_GOOD_MAGIC;
                   1208:                 cellForeColor = goodMessageColor;
                   1209:             } else {
                   1210:                 cellChar = 0;
                   1211:                 cellForeColor = white;
                   1212:             }
                   1213:
                   1214:             needDistinctness = true;
                   1215:         } else if ((pmap[x][y].flags & HAS_MONSTER)
                   1216:                    && (playerCanSeeOrSense(x, y) || ((monst->info.flags & MONST_IMMOBILE) && (pmap[x][y].flags & DISCOVERED)))
                   1217:                    && (!monsterIsHidden(monst, &player) || rogue.playbackOmniscience)) {
                   1218:             needDistinctness = true;
                   1219:             if (player.status[STATUS_HALLUCINATING] > 0 && !(monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE)) && !rogue.playbackOmniscience) {
                   1220:                 cellChar = monsterCatalog[randomAnimateMonster()].displayChar;
                   1221:                 cellForeColor = *(monsterCatalog[randomAnimateMonster()].foreColor);
                   1222:             } else {
                   1223:                 cellChar = monst->info.displayChar;
                   1224:                 cellForeColor = *(monst->info.foreColor);
                   1225:                 if (monst->status[STATUS_INVISIBLE] || (monst->bookkeepingFlags & MB_SUBMERGED)) {
                   1226:                     // Invisible allies show up on the screen with a transparency effect.
                   1227:                     //cellForeColor = cellBackColor;
                   1228:                     applyColorAverage(&cellForeColor, &cellBackColor, 75);
                   1229:                 } else {
                   1230:                     if (monst->creatureState == MONSTER_ALLY && !(monst->info.flags & MONST_INANIMATE)) {
                   1231:                         if (rogue.trueColorMode) {
                   1232:                             cellForeColor = white;
                   1233:                         } else {
                   1234:                             applyColorAverage(&cellForeColor, &pink, 50);
                   1235:                         }
                   1236:                     }
                   1237:                 }
                   1238:                 //DEBUG if (monst->bookkeepingFlags & MB_LEADER) applyColorAverage(&cellBackColor, &purple, 50);
                   1239:             }
                   1240:         } else if (monst
                   1241:                    && monsterRevealed(monst)
                   1242:                    && !canSeeMonster(monst)) {
                   1243:             if (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience) {
                   1244:                 cellChar = (rand_range(0, 1) ? 'X' : 'x');
                   1245:             } else {
                   1246:                 cellChar = (monst->info.isLarge ? 'X' : 'x');
                   1247:             }
                   1248:             cellForeColor = white;
                   1249:             lightMultiplierColor = white;
                   1250:             if (!(pmap[x][y].flags & DISCOVERED)) {
                   1251:                 cellBackColor = black;
                   1252:                 gasAugmentColor = black;
                   1253:             }
                   1254:         } else if ((pmap[x][y].flags & HAS_ITEM) && !cellHasTerrainFlag(x, y, T_OBSTRUCTS_ITEMS)
                   1255:                    && (playerCanSeeOrSense(x, y) || ((pmap[x][y].flags & DISCOVERED) && !cellHasTerrainFlag(x, y, T_MOVES_ITEMS)))) {
                   1256:             needDistinctness = true;
                   1257:             if (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience) {
                   1258:                 cellChar = itemChars[rand_range(0, 9)];
                   1259:                 cellForeColor = itemColor;
                   1260:             } else {
                   1261:                 theItem = itemAtLoc(x, y);
                   1262:                 cellChar = theItem->displayChar;
                   1263:                 cellForeColor = *(theItem->foreColor);
                   1264:                 // Remember the item was here
                   1265:                 pmap[x][y].rememberedItemCategory = theItem->category;
                   1266:                 pmap[x][y].rememberedItemKind = theItem->kind;
                   1267:                 pmap[x][y].rememberedItemQuantity = theItem->quantity;
                   1268:                 pmap[x][y].rememberedItemOriginDepth = theItem->originDepth;
                   1269:             }
                   1270:         } else if (playerCanSeeOrSense(x, y) || (pmap[x][y].flags & (DISCOVERED | MAGIC_MAPPED))) {
                   1271:             // just don't want these to be plotted as black
                   1272:             // Also, ensure we remember there are no items here
                   1273:             pmap[x][y].rememberedItemCategory = 0;
                   1274:             pmap[x][y].rememberedItemKind = 0;
                   1275:             pmap[x][y].rememberedItemQuantity = 0;
                   1276:             pmap[x][y].rememberedItemOriginDepth = 0;
                   1277:         } else {
                   1278:             *returnChar = ' ';
                   1279:             *returnForeColor = black;
                   1280:             *returnBackColor = undiscoveredColor;
                   1281:
                   1282:             if (D_DISABLE_BACKGROUND_COLORS) *returnBackColor = black;
                   1283:
                   1284:             restoreRNG;
                   1285:             return;
                   1286:         }
                   1287:
                   1288:         if (gasAugmentWeight && ((pmap[x][y].flags & DISCOVERED) || rogue.playbackOmniscience)) {
                   1289:             if (!rogue.trueColorMode || !needDistinctness) {
                   1290:                 applyColorAverage(&cellForeColor, &gasAugmentColor, gasAugmentWeight);
                   1291:             }
                   1292:             // phantoms create sillhouettes in gas clouds
                   1293:             if ((pmap[x][y].flags & HAS_MONSTER)
                   1294:                 && monst->status[STATUS_INVISIBLE]
                   1295:                 && playerCanSeeOrSense(x, y)
                   1296:                 && !monsterRevealed(monst)
                   1297:                 && !monsterHiddenBySubmersion(monst, &player)) {
                   1298:
                   1299:                 if (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience) {
                   1300:                     cellChar = monsterCatalog[randomAnimateMonster()].displayChar;
                   1301:                 } else {
                   1302:                     cellChar = monst->info.displayChar;
                   1303:                 }
                   1304:                 cellForeColor = cellBackColor;
                   1305:             }
                   1306:             applyColorAverage(&cellBackColor, &gasAugmentColor, gasAugmentWeight);
                   1307:         }
                   1308:
                   1309:         if (!(pmap[x][y].flags & (ANY_KIND_OF_VISIBLE | ITEM_DETECTED | HAS_PLAYER))
                   1310:             && !playerCanSeeOrSense(x, y)
                   1311:             && (!monst || !monsterRevealed(monst)) && !monsterWithDetectedItem) {
                   1312:
                   1313:             pmap[x][y].flags |= STABLE_MEMORY;
                   1314:             pmap[x][y].rememberedAppearance.character = cellChar;
                   1315:
                   1316:             if (rogue.trueColorMode) {
                   1317:                 bakeTerrainColors(&cellForeColor, &cellBackColor, x, y);
                   1318:             }
                   1319:
                   1320:             // store memory
                   1321:             storeColorComponents(pmap[x][y].rememberedAppearance.foreColorComponents, &cellForeColor);
                   1322:             storeColorComponents(pmap[x][y].rememberedAppearance.backColorComponents, &cellBackColor);
                   1323:
                   1324:             applyColorAugment(&lightMultiplierColor, &basicLightColor, 100);
                   1325:             if (!rogue.trueColorMode || !needDistinctness) {
                   1326:                 applyColorMultiplier(&cellForeColor, &lightMultiplierColor);
                   1327:             }
                   1328:             applyColorMultiplier(&cellBackColor, &lightMultiplierColor);
                   1329:             bakeTerrainColors(&cellForeColor, &cellBackColor, x, y);
                   1330:
                   1331:             // Then restore, so that it looks the same on this pass as it will when later refreshed.
                   1332:             cellForeColor = colorFromComponents(pmap[x][y].rememberedAppearance.foreColorComponents);
                   1333:             cellBackColor = colorFromComponents(pmap[x][y].rememberedAppearance.backColorComponents);
                   1334:         }
                   1335:     }
                   1336:
                   1337:     // Smooth out walls: if there's a "wall-ish" tile drawn below us, just draw the wall top
                   1338:     if ((cellChar == G_WALL || cellChar == G_GRANITE) && coordinatesAreInMap(x, y+1)
                   1339:         && glyphIsWallish(displayBuffer[mapToWindowX(x)][mapToWindowY(y+1)].character)) {
                   1340:         cellChar = G_WALL_TOP;
                   1341:     }
                   1342:
                   1343:     if (((pmap[x][y].flags & ITEM_DETECTED) || monsterWithDetectedItem
                   1344:          || (monst && monsterRevealed(monst)))
                   1345:         && !playerCanSeeOrSense(x, y)) {
                   1346:         // do nothing
                   1347:     } else if (!(pmap[x][y].flags & VISIBLE) && (pmap[x][y].flags & CLAIRVOYANT_VISIBLE)) {
                   1348:         // can clairvoyantly see it
                   1349:         if (rogue.trueColorMode) {
                   1350:             lightMultiplierColor = basicLightColor;
                   1351:         } else {
                   1352:             applyColorAugment(&lightMultiplierColor, &basicLightColor, 100);
                   1353:         }
                   1354:         if (!rogue.trueColorMode || !needDistinctness) {
                   1355:             applyColorMultiplier(&cellForeColor, &lightMultiplierColor);
                   1356:             applyColorMultiplier(&cellForeColor, &clairvoyanceColor);
                   1357:         }
                   1358:         applyColorMultiplier(&cellBackColor, &lightMultiplierColor);
                   1359:         applyColorMultiplier(&cellBackColor, &clairvoyanceColor);
                   1360:     } else if (!(pmap[x][y].flags & VISIBLE) && (pmap[x][y].flags & TELEPATHIC_VISIBLE)) {
                   1361:         // Can telepathically see it through another creature's eyes.
                   1362:
                   1363:         applyColorAugment(&lightMultiplierColor, &basicLightColor, 100);
                   1364:
                   1365:         if (!rogue.trueColorMode || !needDistinctness) {
                   1366:             applyColorMultiplier(&cellForeColor, &lightMultiplierColor);
                   1367:             applyColorMultiplier(&cellForeColor, &telepathyMultiplier);
                   1368:         }
                   1369:         applyColorMultiplier(&cellBackColor, &lightMultiplierColor);
                   1370:         applyColorMultiplier(&cellBackColor, &telepathyMultiplier);
                   1371:     } else if (!(pmap[x][y].flags & DISCOVERED) && (pmap[x][y].flags & MAGIC_MAPPED)) {
                   1372:         // magic mapped only
                   1373:         if (!rogue.playbackOmniscience) {
                   1374:             needDistinctness = false;
                   1375:             if (!rogue.trueColorMode || !needDistinctness) {
                   1376:                 applyColorMultiplier(&cellForeColor, &magicMapColor);
                   1377:             }
                   1378:             applyColorMultiplier(&cellBackColor, &magicMapColor);
                   1379:         }
                   1380:     } else if (!(pmap[x][y].flags & VISIBLE) && !rogue.playbackOmniscience) {
                   1381:         // if it's not visible
                   1382:
                   1383:         needDistinctness = false;
                   1384:         if (rogue.inWater) {
                   1385:             applyColorAverage(&cellForeColor, &black, 80);
                   1386:             applyColorAverage(&cellBackColor, &black, 80);
                   1387:         } else {
                   1388:             if (!cellHasTMFlag(x, y, TM_BRIGHT_MEMORY)
                   1389:                 && (!rogue.trueColorMode || !needDistinctness)) {
                   1390:
                   1391:                 applyColorMultiplier(&cellForeColor, &memoryColor);
                   1392:                 applyColorAverage(&cellForeColor, &memoryOverlay, 25);
                   1393:             }
                   1394:             applyColorMultiplier(&cellBackColor, &memoryColor);
                   1395:             applyColorAverage(&cellBackColor, &memoryOverlay, 25);
                   1396:         }
                   1397:     } else if (playerCanSeeOrSense(x, y) && rogue.playbackOmniscience && !(pmap[x][y].flags & ANY_KIND_OF_VISIBLE)) {
                   1398:         // omniscience
                   1399:         applyColorAugment(&lightMultiplierColor, &basicLightColor, 100);
                   1400:         if (!rogue.trueColorMode || !needDistinctness) {
                   1401:             applyColorMultiplier(&cellForeColor, &lightMultiplierColor);
                   1402:             applyColorMultiplier(&cellForeColor, &omniscienceColor);
                   1403:         }
                   1404:         applyColorMultiplier(&cellBackColor, &lightMultiplierColor);
                   1405:         applyColorMultiplier(&cellBackColor, &omniscienceColor);
                   1406:     } else {
                   1407:         if (!rogue.trueColorMode || !needDistinctness) {
                   1408:             applyColorMultiplier(&cellForeColor, &lightMultiplierColor);
                   1409:         }
                   1410:         applyColorMultiplier(&cellBackColor, &lightMultiplierColor);
                   1411:
                   1412:         if (player.status[STATUS_HALLUCINATING] && !rogue.trueColorMode) {
                   1413:             randomizeColor(&cellForeColor, 40 * player.status[STATUS_HALLUCINATING] / 300 + 20);
                   1414:             randomizeColor(&cellBackColor, 40 * player.status[STATUS_HALLUCINATING] / 300 + 20);
                   1415:         }
                   1416:         if (rogue.inWater) {
                   1417:             applyColorMultiplier(&cellForeColor, &deepWaterLightColor);
                   1418:             applyColorMultiplier(&cellBackColor, &deepWaterLightColor);
                   1419:         }
                   1420:     }
                   1421: //   DEBUG cellBackColor.red = max(0,((scentMap[x][y] - rogue.scentTurnNumber) * 2) + 100);
                   1422: //   DEBUG if (pmap[x][y].flags & KNOWN_TO_BE_TRAP_FREE) cellBackColor.red += 20;
                   1423: //   DEBUG if (cellHasTerrainFlag(x, y, T_IS_FLAMMABLE)) cellBackColor.red += 50;
                   1424:
                   1425:     if (pmap[x][y].flags & IS_IN_PATH) {
                   1426:         if (cellHasTMFlag(x, y, TM_INVERT_WHEN_HIGHLIGHTED)) {
                   1427:             swapColors(&cellForeColor, &cellBackColor);
                   1428:         } else {
                   1429:             if (!rogue.trueColorMode || !needDistinctness) {
                   1430:                 applyColorAverage(&cellForeColor, &yellow, rogue.cursorPathIntensity);
                   1431:             }
                   1432:             applyColorAverage(&cellBackColor, &yellow, rogue.cursorPathIntensity);
                   1433:         }
                   1434:         needDistinctness = true;
                   1435:     }
                   1436:
                   1437:     bakeTerrainColors(&cellForeColor, &cellBackColor, x, y);
                   1438:
                   1439:     if (rogue.displayAggroRangeMode && (pmap[x][y].flags & IN_FIELD_OF_VIEW)) {
                   1440:         distance = min(rogue.scentTurnNumber - scentMap[x][y], scentDistance(x, y, player.xLoc, player.yLoc));
                   1441:         if (distance > rogue.aggroRange * 2) {
                   1442:             applyColorAverage(&cellForeColor, &orange, 12);
                   1443:             applyColorAverage(&cellBackColor, &orange, 12);
                   1444:             applyColorAugment(&cellForeColor, &orange, 12);
                   1445:             applyColorAugment(&cellBackColor, &orange, 12);
                   1446:         }
                   1447:     }
                   1448:
                   1449:     if (rogue.trueColorMode
                   1450:         && playerCanSeeOrSense(x, y)) {
                   1451:
                   1452:         if (displayDetail[x][y] == DV_DARK) {
                   1453:             applyColorMultiplier(&cellForeColor, &inDarknessMultiplierColor);
                   1454:             applyColorMultiplier(&cellBackColor, &inDarknessMultiplierColor);
                   1455:
                   1456:             applyColorAugment(&cellForeColor, &purple, 10);
                   1457:             applyColorAugment(&cellBackColor, &white, -10);
                   1458:             applyColorAverage(&cellBackColor, &purple, 20);
                   1459:         } else if (displayDetail[x][y] == DV_LIT) {
                   1460:
                   1461:             colorMultiplierFromDungeonLight(x, y, &lightMultiplierColor);
                   1462:             normColor(&lightMultiplierColor, 175, 50);
                   1463:             //applyColorMultiplier(&cellForeColor, &lightMultiplierColor);
                   1464:             //applyColorMultiplier(&cellBackColor, &lightMultiplierColor);
                   1465:             applyColorAugment(&cellForeColor, &lightMultiplierColor, 5);
                   1466:             applyColorAugment(&cellBackColor, &lightMultiplierColor, 5);
                   1467:         }
                   1468:     }
                   1469:
                   1470:     if (needDistinctness) {
                   1471:         separateColors(&cellForeColor, &cellBackColor);
                   1472:     }
                   1473:
                   1474:     if (D_SCENT_VISION) {
                   1475:         if (rogue.scentTurnNumber > (unsigned short) scentMap[x][y]) {
                   1476:             cellBackColor.red = rogue.scentTurnNumber - (unsigned short) scentMap[x][y];
                   1477:             cellBackColor.red = clamp(cellBackColor.red, 0, 100);
                   1478:         } else {
                   1479:             cellBackColor.green = abs(rogue.scentTurnNumber - (unsigned short) scentMap[x][y]);
                   1480:             cellBackColor.green = clamp(cellBackColor.green, 0, 100);
                   1481:         }
                   1482:     }
                   1483:
                   1484:     *returnChar = cellChar;
                   1485:     *returnForeColor = cellForeColor;
                   1486:     *returnBackColor = cellBackColor;
                   1487:
                   1488:     if (D_DISABLE_BACKGROUND_COLORS) *returnBackColor = black;
                   1489:     restoreRNG;
                   1490: }
                   1491:
                   1492: void refreshDungeonCell(short x, short y) {
                   1493:     enum displayGlyph cellChar;
                   1494:     color foreColor, backColor;
                   1495:     brogueAssert(coordinatesAreInMap(x, y));
                   1496:
                   1497:     getCellAppearance(x, y, &cellChar, &foreColor, &backColor);
                   1498:     plotCharWithColor(cellChar, mapToWindowX(x), mapToWindowY(y), &foreColor, &backColor);
                   1499: }
                   1500:
                   1501: void applyColorMultiplier(color *baseColor, const color *multiplierColor) {
                   1502:     baseColor->red = baseColor->red * multiplierColor->red / 100;
                   1503:     baseColor->redRand = baseColor->redRand * multiplierColor->redRand / 100;
                   1504:     baseColor->green = baseColor->green * multiplierColor->green / 100;
                   1505:     baseColor->greenRand = baseColor->greenRand * multiplierColor->greenRand / 100;
                   1506:     baseColor->blue = baseColor->blue * multiplierColor->blue / 100;
                   1507:     baseColor->blueRand = baseColor->blueRand * multiplierColor->blueRand / 100;
                   1508:     baseColor->rand = baseColor->rand * multiplierColor->rand / 100;
                   1509:     //baseColor->colorDances *= multiplierColor->colorDances;
                   1510:     return;
                   1511: }
                   1512:
                   1513: void applyColorAverage(color *baseColor, const color *newColor, short averageWeight) {
                   1514:     short weightComplement = 100 - averageWeight;
                   1515:     baseColor->red = (baseColor->red * weightComplement + newColor->red * averageWeight) / 100;
                   1516:     baseColor->redRand = (baseColor->redRand * weightComplement + newColor->redRand * averageWeight) / 100;
                   1517:     baseColor->green = (baseColor->green * weightComplement + newColor->green * averageWeight) / 100;
                   1518:     baseColor->greenRand = (baseColor->greenRand * weightComplement + newColor->greenRand * averageWeight) / 100;
                   1519:     baseColor->blue = (baseColor->blue * weightComplement + newColor->blue * averageWeight) / 100;
                   1520:     baseColor->blueRand = (baseColor->blueRand * weightComplement + newColor->blueRand * averageWeight) / 100;
                   1521:     baseColor->rand = (baseColor->rand * weightComplement + newColor->rand * averageWeight) / 100;
                   1522:     baseColor->colorDances = (baseColor->colorDances || newColor->colorDances);
                   1523:     return;
                   1524: }
                   1525:
                   1526: void applyColorAugment(color *baseColor, const color *augmentingColor, short augmentWeight) {
                   1527:     baseColor->red += (augmentingColor->red * augmentWeight) / 100;
                   1528:     baseColor->redRand += (augmentingColor->redRand * augmentWeight) / 100;
                   1529:     baseColor->green += (augmentingColor->green * augmentWeight) / 100;
                   1530:     baseColor->greenRand += (augmentingColor->greenRand * augmentWeight) / 100;
                   1531:     baseColor->blue += (augmentingColor->blue * augmentWeight) / 100;
                   1532:     baseColor->blueRand += (augmentingColor->blueRand * augmentWeight) / 100;
                   1533:     baseColor->rand += (augmentingColor->rand * augmentWeight) / 100;
                   1534:     return;
                   1535: }
                   1536:
                   1537: void applyColorScalar(color *baseColor, short scalar) {
                   1538:     baseColor->red          = baseColor->red        * scalar / 100;
                   1539:     baseColor->redRand      = baseColor->redRand    * scalar / 100;
                   1540:     baseColor->green        = baseColor->green      * scalar / 100;
                   1541:     baseColor->greenRand    = baseColor->greenRand  * scalar / 100;
                   1542:     baseColor->blue         = baseColor->blue       * scalar / 100;
                   1543:     baseColor->blueRand     = baseColor->blueRand   * scalar / 100;
                   1544:     baseColor->rand         = baseColor->rand       * scalar / 100;
                   1545: }
                   1546:
                   1547: void applyColorBounds(color *baseColor, short lowerBound, short upperBound) {
                   1548:     baseColor->red          = clamp(baseColor->red, lowerBound, upperBound);
                   1549:     baseColor->redRand      = clamp(baseColor->redRand, lowerBound, upperBound);
                   1550:     baseColor->green        = clamp(baseColor->green, lowerBound, upperBound);
                   1551:     baseColor->greenRand    = clamp(baseColor->greenRand, lowerBound, upperBound);
                   1552:     baseColor->blue         = clamp(baseColor->blue, lowerBound, upperBound);
                   1553:     baseColor->blueRand     = clamp(baseColor->blueRand, lowerBound, upperBound);
                   1554:     baseColor->rand         = clamp(baseColor->rand, lowerBound, upperBound);
                   1555: }
                   1556:
                   1557: void desaturate(color *baseColor, short weight) {
                   1558:     short avg;
                   1559:     avg = (baseColor->red + baseColor->green + baseColor->blue) / 3 + 1;
                   1560:     baseColor->red = baseColor->red * (100 - weight) / 100 + (avg * weight / 100);
                   1561:     baseColor->green = baseColor->green * (100 - weight) / 100 + (avg * weight / 100);
                   1562:     baseColor->blue = baseColor->blue * (100 - weight) / 100 + (avg * weight / 100);
                   1563:
                   1564:     avg = (baseColor->redRand + baseColor->greenRand + baseColor->blueRand);
                   1565:     baseColor->redRand = baseColor->redRand * (100 - weight) / 100;
                   1566:     baseColor->greenRand = baseColor->greenRand * (100 - weight) / 100;
                   1567:     baseColor->blueRand = baseColor->blueRand * (100 - weight) / 100;
                   1568:
                   1569:     baseColor->rand += avg * weight / 3 / 100;
                   1570: }
                   1571:
                   1572: short randomizeByPercent(short input, short percent) {
                   1573:     return (rand_range(input * (100 - percent) / 100, input * (100 + percent) / 100));
                   1574: }
                   1575:
                   1576: void randomizeColor(color *baseColor, short randomizePercent) {
                   1577:     baseColor->red = randomizeByPercent(baseColor->red, randomizePercent);
                   1578:     baseColor->green = randomizeByPercent(baseColor->green, randomizePercent);
                   1579:     baseColor->blue = randomizeByPercent(baseColor->blue, randomizePercent);
                   1580: }
                   1581:
                   1582: void swapColors(color *color1, color *color2) {
                   1583:     color tempColor = *color1;
                   1584:     *color1 = *color2;
                   1585:     *color2 = tempColor;
                   1586: }
                   1587:
                   1588: // Assumes colors are pre-baked.
                   1589: void blendAppearances(const color *fromForeColor, const color *fromBackColor, const enum displayGlyph fromChar,
                   1590:                       const color *toForeColor, const color *toBackColor, const enum displayGlyph toChar,
                   1591:                       color *retForeColor, color *retBackColor, enum displayGlyph *retChar,
                   1592:                       const short percent) {
                   1593:     // Straight average of the back color:
                   1594:     *retBackColor = *fromBackColor;
                   1595:     applyColorAverage(retBackColor, toBackColor, percent);
                   1596:
                   1597:     // Pick the character:
                   1598:     if (percent >= 50) {
                   1599:         *retChar = toChar;
                   1600:     } else {
                   1601:         *retChar = fromChar;
                   1602:     }
                   1603:
                   1604:     // Pick the method for blending the fore color.
                   1605:     if (fromChar == toChar) {
                   1606:         // If the character isn't changing, do a straight average.
                   1607:         *retForeColor = *fromForeColor;
                   1608:         applyColorAverage(retForeColor, toForeColor, percent);
                   1609:     } else {
                   1610:         // If it is changing, the first half blends to the current back color, and the second half blends to the final back color.
                   1611:         if (percent >= 50) {
                   1612:             *retForeColor = *retBackColor;
                   1613:             applyColorAverage(retForeColor, toForeColor, (percent - 50) * 2);
                   1614:         } else {
                   1615:             *retForeColor = *fromForeColor;
                   1616:             applyColorAverage(retForeColor, retBackColor, percent * 2);
                   1617:         }
                   1618:     }
                   1619: }
                   1620:
                   1621: void irisFadeBetweenBuffers(cellDisplayBuffer fromBuf[COLS][ROWS],
                   1622:                             cellDisplayBuffer toBuf[COLS][ROWS],
                   1623:                             short x, short y,
                   1624:                             short frameCount,
                   1625:                             boolean outsideIn) {
                   1626:     short i, j, frame, percentBasis, thisCellPercent;
                   1627:     boolean fastForward;
                   1628:     color fromBackColor, toBackColor, fromForeColor, toForeColor, currentForeColor, currentBackColor;
                   1629:     enum displayGlyph fromChar, toChar, currentChar;
                   1630:     short completionMap[COLS][ROWS], maxDistance;
                   1631:
                   1632:     fastForward = false;
                   1633:     frame = 1;
                   1634:
                   1635:     // Calculate the square of the maximum distance from (x, y) that the iris will have to spread.
                   1636:     if (x < COLS / 2) {
                   1637:         i = COLS - x;
                   1638:     } else {
                   1639:         i = x;
                   1640:     }
                   1641:     if (y < ROWS / 2) {
                   1642:         j = ROWS - y;
                   1643:     } else {
                   1644:         j = y;
                   1645:     }
                   1646:     maxDistance = i*i + j*j;
                   1647:
                   1648:     // Generate the initial completion map as a percent of maximum distance.
                   1649:     for (i=0; i<COLS; i++) {
                   1650:         for (j=0; j<ROWS; j++) {
                   1651:             completionMap[i][j] = (i - x)*(i - x) + (j - y)*(j - y); // square of distance
                   1652:             completionMap[i][j] = 100 * completionMap[i][j] / maxDistance; // percent of max distance
                   1653:             if (outsideIn) {
                   1654:                 completionMap[i][j] -= 100; // translate to [-100, 0], with the origin at -100 and the farthest point at 0.
                   1655:             } else {
                   1656:                 completionMap[i][j] *= -1; // translate to [-100, 0], with the origin at 0 and the farthest point at -100.
                   1657:             }
                   1658:         }
                   1659:     }
                   1660:
                   1661:     do {
                   1662:         percentBasis = 10000 * frame / frameCount;
                   1663:
                   1664:         for (i=0; i<COLS; i++) {
                   1665:             for (j=0; j<ROWS; j++) {
                   1666:                 thisCellPercent = percentBasis * 3 / 100 + completionMap[i][j];
                   1667:
                   1668:                 fromBackColor = colorFromComponents(fromBuf[i][j].backColorComponents);
                   1669:                 fromForeColor = colorFromComponents(fromBuf[i][j].foreColorComponents);
                   1670:                 fromChar = fromBuf[i][j].character;
                   1671:
                   1672:                 toBackColor = colorFromComponents(toBuf[i][j].backColorComponents);
                   1673:                 toForeColor = colorFromComponents(toBuf[i][j].foreColorComponents);
                   1674:                 toChar = toBuf[i][j].character;
                   1675:
                   1676:                 blendAppearances(&fromForeColor, &fromBackColor, fromChar, &toForeColor, &toBackColor, toChar, &currentForeColor, &currentBackColor, &currentChar, clamp(thisCellPercent, 0, 100));
                   1677:                 plotCharWithColor(currentChar, i, j, &currentForeColor, &currentBackColor);
                   1678:             }
                   1679:         }
                   1680:
                   1681:         fastForward = pauseBrogue(16);
                   1682:         frame++;
                   1683:     } while (frame <= frameCount && !fastForward);
                   1684:     overlayDisplayBuffer(toBuf, NULL);
                   1685: }
                   1686:
                   1687: // takes dungeon coordinates
                   1688: void colorBlendCell(short x, short y, color *hiliteColor, short hiliteStrength) {
                   1689:     enum displayGlyph displayChar;
                   1690:     color foreColor, backColor;
                   1691:
                   1692:     getCellAppearance(x, y, &displayChar, &foreColor, &backColor);
                   1693:     applyColorAverage(&foreColor, hiliteColor, hiliteStrength);
                   1694:     applyColorAverage(&backColor, hiliteColor, hiliteStrength);
                   1695:     plotCharWithColor(displayChar, mapToWindowX(x), mapToWindowY(y), &foreColor, &backColor);
                   1696: }
                   1697:
                   1698: // takes dungeon coordinates
                   1699: void hiliteCell(short x, short y, const color *hiliteColor, short hiliteStrength, boolean distinctColors) {
                   1700:     enum displayGlyph displayChar;
                   1701:     color foreColor, backColor;
                   1702:
                   1703:     assureCosmeticRNG;
                   1704:
                   1705:     getCellAppearance(x, y, &displayChar, &foreColor, &backColor);
                   1706:     applyColorAugment(&foreColor, hiliteColor, hiliteStrength);
                   1707:     applyColorAugment(&backColor, hiliteColor, hiliteStrength);
                   1708:     if (distinctColors) {
                   1709:         separateColors(&foreColor, &backColor);
                   1710:     }
                   1711:     plotCharWithColor(displayChar, mapToWindowX(x), mapToWindowY(y), &foreColor, &backColor);
                   1712:
                   1713:     restoreRNG;
                   1714: }
                   1715:
                   1716: short adjustedLightValue(short x) {
                   1717:     if (x <= LIGHT_SMOOTHING_THRESHOLD) {
                   1718:         return x;
                   1719:     } else {
                   1720:         return fp_sqrt(x * FP_FACTOR / LIGHT_SMOOTHING_THRESHOLD) * LIGHT_SMOOTHING_THRESHOLD / FP_FACTOR;
                   1721:     }
                   1722: }
                   1723:
                   1724: void colorMultiplierFromDungeonLight(short x, short y, color *editColor) {
                   1725:
                   1726:     editColor->red      = editColor->redRand    = adjustedLightValue(max(0, tmap[x][y].light[0]));
                   1727:     editColor->green    = editColor->greenRand  = adjustedLightValue(max(0, tmap[x][y].light[1]));
                   1728:     editColor->blue     = editColor->blueRand   = adjustedLightValue(max(0, tmap[x][y].light[2]));
                   1729:
                   1730:     editColor->rand = adjustedLightValue(max(0, tmap[x][y].light[0] + tmap[x][y].light[1] + tmap[x][y].light[2]) / 3);
                   1731:     editColor->colorDances = false;
                   1732: }
                   1733:
                   1734: void plotCharWithColor(enum displayGlyph inputChar, short xLoc, short yLoc, const color *cellForeColor, const color *cellBackColor) {
                   1735:     short oldRNG;
                   1736:
                   1737:     short foreRed = cellForeColor->red,
                   1738:     foreGreen = cellForeColor->green,
                   1739:     foreBlue = cellForeColor->blue,
                   1740:
                   1741:     backRed = cellBackColor->red,
                   1742:     backGreen = cellBackColor->green,
                   1743:     backBlue = cellBackColor->blue,
                   1744:
                   1745:     foreRand, backRand;
                   1746:
                   1747:     brogueAssert(coordinatesAreInWindow(xLoc, yLoc));
                   1748:
                   1749:     if (rogue.gameHasEnded || rogue.playbackFastForward) {
                   1750:         return;
                   1751:     }
                   1752:
                   1753:     //assureCosmeticRNG;
                   1754:     oldRNG = rogue.RNG;
                   1755:     rogue.RNG = RNG_COSMETIC;
                   1756:
                   1757:     foreRand = rand_range(0, cellForeColor->rand);
                   1758:     backRand = rand_range(0, cellBackColor->rand);
                   1759:     foreRed += rand_range(0, cellForeColor->redRand) + foreRand;
                   1760:     foreGreen += rand_range(0, cellForeColor->greenRand) + foreRand;
                   1761:     foreBlue += rand_range(0, cellForeColor->blueRand) + foreRand;
                   1762:     backRed += rand_range(0, cellBackColor->redRand) + backRand;
                   1763:     backGreen += rand_range(0, cellBackColor->greenRand) + backRand;
                   1764:     backBlue += rand_range(0, cellBackColor->blueRand) + backRand;
                   1765:
                   1766:     foreRed =       min(100, max(0, foreRed));
                   1767:     foreGreen =     min(100, max(0, foreGreen));
                   1768:     foreBlue =      min(100, max(0, foreBlue));
                   1769:     backRed =       min(100, max(0, backRed));
                   1770:     backGreen =     min(100, max(0, backGreen));
                   1771:     backBlue =      min(100, max(0, backBlue));
                   1772:
                   1773:     if (inputChar != ' '
                   1774:         && foreRed      == backRed
                   1775:         && foreGreen    == backGreen
                   1776:         && foreBlue     == backBlue) {
                   1777:
                   1778:         inputChar = ' ';
                   1779:     }
                   1780:
                   1781:     if (inputChar       != displayBuffer[xLoc][yLoc].character
                   1782:         || foreRed      != displayBuffer[xLoc][yLoc].foreColorComponents[0]
                   1783:         || foreGreen    != displayBuffer[xLoc][yLoc].foreColorComponents[1]
                   1784:         || foreBlue     != displayBuffer[xLoc][yLoc].foreColorComponents[2]
                   1785:         || backRed      != displayBuffer[xLoc][yLoc].backColorComponents[0]
                   1786:         || backGreen    != displayBuffer[xLoc][yLoc].backColorComponents[1]
                   1787:         || backBlue     != displayBuffer[xLoc][yLoc].backColorComponents[2]) {
                   1788:
                   1789:         displayBuffer[xLoc][yLoc].needsUpdate = true;
                   1790:
                   1791:         displayBuffer[xLoc][yLoc].character = inputChar;
                   1792:         displayBuffer[xLoc][yLoc].foreColorComponents[0] = foreRed;
                   1793:         displayBuffer[xLoc][yLoc].foreColorComponents[1] = foreGreen;
                   1794:         displayBuffer[xLoc][yLoc].foreColorComponents[2] = foreBlue;
                   1795:         displayBuffer[xLoc][yLoc].backColorComponents[0] = backRed;
                   1796:         displayBuffer[xLoc][yLoc].backColorComponents[1] = backGreen;
                   1797:         displayBuffer[xLoc][yLoc].backColorComponents[2] = backBlue;
                   1798:     }
                   1799:
                   1800:     restoreRNG;
                   1801: }
                   1802:
                   1803: void plotCharToBuffer(enum displayGlyph inputChar, short x, short y, color *foreColor, color *backColor, cellDisplayBuffer dbuf[COLS][ROWS]) {
                   1804:     short oldRNG;
                   1805:
                   1806:     if (!dbuf) {
                   1807:         plotCharWithColor(inputChar, x, y, foreColor, backColor);
                   1808:         return;
                   1809:     }
                   1810:
                   1811:     brogueAssert(coordinatesAreInWindow(x, y));
                   1812:
                   1813:     oldRNG = rogue.RNG;
                   1814:     rogue.RNG = RNG_COSMETIC;
                   1815:     //assureCosmeticRNG;
                   1816:     dbuf[x][y].foreColorComponents[0] = foreColor->red + rand_range(0, foreColor->redRand) + rand_range(0, foreColor->rand);
                   1817:     dbuf[x][y].foreColorComponents[1] = foreColor->green + rand_range(0, foreColor->greenRand) + rand_range(0, foreColor->rand);
                   1818:     dbuf[x][y].foreColorComponents[2] = foreColor->blue + rand_range(0, foreColor->blueRand) + rand_range(0, foreColor->rand);
                   1819:     dbuf[x][y].backColorComponents[0] = backColor->red + rand_range(0, backColor->redRand) + rand_range(0, backColor->rand);
                   1820:     dbuf[x][y].backColorComponents[1] = backColor->green + rand_range(0, backColor->greenRand) + rand_range(0, backColor->rand);
                   1821:     dbuf[x][y].backColorComponents[2] = backColor->blue + rand_range(0, backColor->blueRand) + rand_range(0, backColor->rand);
                   1822:     dbuf[x][y].character = inputChar;
                   1823:     dbuf[x][y].opacity = 100;
                   1824:     restoreRNG;
                   1825: }
                   1826:
                   1827: void plotForegroundChar(enum displayGlyph inputChar, short x, short y, color *foreColor, boolean affectedByLighting) {
                   1828:     color multColor, myColor, backColor, ignoredColor;
                   1829:     enum displayGlyph ignoredChar;
                   1830:
                   1831:     myColor = *foreColor;
                   1832:     getCellAppearance(x, y, &ignoredChar, &ignoredColor, &backColor);
                   1833:     if (affectedByLighting) {
                   1834:         colorMultiplierFromDungeonLight(x, y, &multColor);
                   1835:         applyColorMultiplier(&myColor, &multColor);
                   1836:     }
                   1837:     plotCharWithColor(inputChar, mapToWindowX(x), mapToWindowY(y), &myColor, &backColor);
                   1838: }
                   1839:
                   1840: // Set to false and draws don't take effect, they simply queue up. Set to true and all of the
                   1841: // queued up draws take effect.
                   1842: void commitDraws() {
                   1843:     short i, j;
                   1844:
                   1845:     for (j=0; j<ROWS; j++) {
                   1846:         for (i=0; i<COLS; i++) {
                   1847:             if (displayBuffer[i][j].needsUpdate) {
                   1848:                 plotChar(displayBuffer[i][j].character, i, j,
                   1849:                          displayBuffer[i][j].foreColorComponents[0],
                   1850:                          displayBuffer[i][j].foreColorComponents[1],
                   1851:                          displayBuffer[i][j].foreColorComponents[2],
                   1852:                          displayBuffer[i][j].backColorComponents[0],
                   1853:                          displayBuffer[i][j].backColorComponents[1],
                   1854:                          displayBuffer[i][j].backColorComponents[2]);
                   1855:                 displayBuffer[i][j].needsUpdate = false;
                   1856:             }
                   1857:         }
                   1858:     }
                   1859: }
                   1860:
                   1861: // Debug feature: display the level to the screen without regard to lighting, field of view, etc.
                   1862: void dumpLevelToScreen() {
                   1863:     short i, j;
                   1864:     pcell backup;
                   1865:
                   1866:     assureCosmeticRNG;
                   1867:     for (i=0; i<DCOLS; i++) {
                   1868:         for (j=0; j<DROWS; j++) {
                   1869:             if (pmap[i][j].layers[DUNGEON] != GRANITE
                   1870:                 || (pmap[i][j].flags & DISCOVERED)) {
                   1871:
                   1872:                 backup = pmap[i][j];
                   1873:                 pmap[i][j].flags |= (VISIBLE | DISCOVERED);
                   1874:                 tmap[i][j].light[0] = 100;
                   1875:                 tmap[i][j].light[1] = 100;
                   1876:                 tmap[i][j].light[2] = 100;
                   1877:                 refreshDungeonCell(i, j);
                   1878:                 pmap[i][j] = backup;
                   1879:             } else {
                   1880:                 plotCharWithColor(' ', mapToWindowX(i), mapToWindowY(j), &white, &black);
                   1881:             }
                   1882:
                   1883:         }
                   1884:     }
                   1885:     restoreRNG;
                   1886: }
                   1887:
                   1888: // To be used immediately after dumpLevelToScreen() above.
                   1889: // Highlight the portion indicated by hiliteCharGrid with the hiliteColor at the hiliteStrength -- both latter arguments are optional.
                   1890: void hiliteCharGrid(char hiliteCharGrid[DCOLS][DROWS], color *hiliteColor, short hiliteStrength) {
                   1891:     short i, j, x, y;
                   1892:     color hCol;
                   1893:
                   1894:     assureCosmeticRNG;
                   1895:
                   1896:     if (hiliteColor) {
                   1897:         hCol = *hiliteColor;
                   1898:     } else {
                   1899:         hCol = yellow;
                   1900:     }
                   1901:
                   1902:     bakeColor(&hCol);
                   1903:
                   1904:     if (!hiliteStrength) {
                   1905:         hiliteStrength = 75;
                   1906:     }
                   1907:
                   1908:     for (i=0; i<DCOLS; i++) {
                   1909:         for (j=0; j<DROWS; j++) {
                   1910:             if (hiliteCharGrid[i][j]) {
                   1911:                 x = mapToWindowX(i);
                   1912:                 y = mapToWindowY(j);
                   1913:
                   1914:                 displayBuffer[x][y].needsUpdate = true;
                   1915:                 displayBuffer[x][y].backColorComponents[0] = clamp(displayBuffer[x][y].backColorComponents[0] + hCol.red * hiliteStrength / 100, 0, 100);
                   1916:                 displayBuffer[x][y].backColorComponents[1] = clamp(displayBuffer[x][y].backColorComponents[1] + hCol.green * hiliteStrength / 100, 0, 100);
                   1917:                 displayBuffer[x][y].backColorComponents[2] = clamp(displayBuffer[x][y].backColorComponents[2] + hCol.blue * hiliteStrength / 100, 0, 100);
                   1918:                 displayBuffer[x][y].foreColorComponents[0] = clamp(displayBuffer[x][y].foreColorComponents[0] + hCol.red * hiliteStrength / 100, 0, 100);
                   1919:                 displayBuffer[x][y].foreColorComponents[1] = clamp(displayBuffer[x][y].foreColorComponents[1] + hCol.green * hiliteStrength / 100, 0, 100);
                   1920:                 displayBuffer[x][y].foreColorComponents[2] = clamp(displayBuffer[x][y].foreColorComponents[2] + hCol.blue * hiliteStrength / 100, 0, 100);
                   1921:             }
                   1922:         }
                   1923:     }
                   1924:     restoreRNG;
                   1925: }
                   1926:
                   1927: void blackOutScreen() {
                   1928:     short i, j;
                   1929:
                   1930:     for (i=0; i<COLS; i++) {
                   1931:         for (j=0; j<ROWS; j++) {
                   1932:             plotCharWithColor(' ', i, j, &black, &black);
                   1933:         }
                   1934:     }
                   1935: }
                   1936:
                   1937: void colorOverDungeon(const color *color) {
                   1938:     short i, j;
                   1939:
                   1940:     for (i=0; i<DCOLS; i++) {
                   1941:         for (j=0; j<DROWS; j++) {
                   1942:             plotCharWithColor(' ', mapToWindowX(i), mapToWindowY(j), color, color);
                   1943:         }
                   1944:     }
                   1945: }
                   1946:
                   1947: void copyDisplayBuffer(cellDisplayBuffer toBuf[COLS][ROWS], cellDisplayBuffer fromBuf[COLS][ROWS]) {
                   1948:     short i, j;
                   1949:
                   1950:     for (i=0; i<COLS; i++) {
                   1951:         for (j=0; j<ROWS; j++) {
                   1952:             toBuf[i][j] = fromBuf[i][j];
                   1953:         }
                   1954:     }
                   1955: }
                   1956:
                   1957: void clearDisplayBuffer(cellDisplayBuffer dbuf[COLS][ROWS]) {
                   1958:     short i, j, k;
                   1959:
                   1960:     for (i=0; i<COLS; i++) {
                   1961:         for (j=0; j<ROWS; j++) {
                   1962:             dbuf[i][j].character = ' ';
                   1963:             for (k=0; k<3; k++) {
                   1964:                 dbuf[i][j].foreColorComponents[k] = 0;
                   1965:                 dbuf[i][j].backColorComponents[k] = 0;
                   1966:             }
                   1967:             dbuf[i][j].opacity = 0;
                   1968:         }
                   1969:     }
                   1970: }
                   1971:
                   1972: color colorFromComponents(char rgb[3]) {
                   1973:     color theColor = black;
                   1974:     theColor.red    = rgb[0];
                   1975:     theColor.green  = rgb[1];
                   1976:     theColor.blue   = rgb[2];
                   1977:     return theColor;
                   1978: }
                   1979:
                   1980: // draws overBuf over the current display with per-cell pseudotransparency as specified in overBuf.
                   1981: // If previousBuf is not null, it gets filled with the preexisting display for reversion purposes.
                   1982: void overlayDisplayBuffer(cellDisplayBuffer overBuf[COLS][ROWS], cellDisplayBuffer previousBuf[COLS][ROWS]) {
                   1983:     short i, j;
                   1984:     color foreColor, backColor, tempColor;
                   1985:     enum displayGlyph character;
                   1986:
                   1987:     if (previousBuf) {
                   1988:         copyDisplayBuffer(previousBuf, displayBuffer);
                   1989:     }
                   1990:
                   1991:     for (i=0; i<COLS; i++) {
                   1992:         for (j=0; j<ROWS; j++) {
                   1993:
                   1994:             if (overBuf[i][j].opacity != 0) {
                   1995:                 backColor = colorFromComponents(overBuf[i][j].backColorComponents);
                   1996:
                   1997:                 // character and fore color:
                   1998:                 if (overBuf[i][j].character == ' ') { // Blank cells in the overbuf take the character from the screen.
                   1999:                     character = displayBuffer[i][j].character;
                   2000:                     foreColor = colorFromComponents(displayBuffer[i][j].foreColorComponents);
                   2001:                     applyColorAverage(&foreColor, &backColor, overBuf[i][j].opacity);
                   2002:                 } else {
                   2003:                     character = overBuf[i][j].character;
                   2004:                     foreColor = colorFromComponents(overBuf[i][j].foreColorComponents);
                   2005:                 }
                   2006:
                   2007:                 // back color:
                   2008:                 tempColor = colorFromComponents(displayBuffer[i][j].backColorComponents);
                   2009:                 applyColorAverage(&backColor, &tempColor, 100 - overBuf[i][j].opacity);
                   2010:
                   2011:                 plotCharWithColor(character, i, j, &foreColor, &backColor);
                   2012:             }
                   2013:         }
                   2014:     }
                   2015: }
                   2016:
                   2017: // Takes a list of locations, a color and a list of strengths and flashes the foregrounds of those locations.
                   2018: // Strengths are percentages measuring how hard the color flashes at its peak.
                   2019: void flashForeground(short *x, short *y, color **flashColor, short *flashStrength, short count, short frames) {
                   2020:     short i, j, percent;
                   2021:     enum displayGlyph *displayChar;
                   2022:     color *bColor, *fColor, newColor;
                   2023:     short oldRNG;
                   2024:
                   2025:     if (count <= 0) {
                   2026:         return;
                   2027:     }
                   2028:
                   2029:     oldRNG = rogue.RNG;
                   2030:     rogue.RNG = RNG_COSMETIC;
                   2031:     //assureCosmeticRNG;
                   2032:
                   2033:     displayChar = (enum displayGlyph *) malloc(count * sizeof(enum displayGlyph));
                   2034:     fColor = (color *) malloc(count * sizeof(color));
                   2035:     bColor = (color *) malloc(count * sizeof(color));
                   2036:
                   2037:     for (i=0; i<count; i++) {
                   2038:         getCellAppearance(x[i], y[i], &displayChar[i], &fColor[i], &bColor[i]);
                   2039:         bakeColor(&fColor[i]);
                   2040:         bakeColor(&bColor[i]);
                   2041:     }
                   2042:
                   2043:     for (j=frames; j>= 0; j--) {
                   2044:         for (i=0; i<count; i++) {
                   2045:             percent = flashStrength[i] * j / frames;
                   2046:             newColor = fColor[i];
                   2047:             applyColorAverage(&newColor, flashColor[i], percent);
                   2048:             plotCharWithColor(displayChar[i], mapToWindowX(x[i]), mapToWindowY(y[i]), &newColor, &(bColor[i]));
                   2049:         }
                   2050:         if (j) {
                   2051:             if (pauseBrogue(16)) {
                   2052:                 j = 1;
                   2053:             }
                   2054:         }
                   2055:     }
                   2056:
                   2057:     free(displayChar);
                   2058:     free(fColor);
                   2059:     free(bColor);
                   2060:
                   2061:     restoreRNG;
                   2062: }
                   2063:
                   2064: void flashCell(color *theColor, short frames, short x, short y) {
                   2065:     short i;
                   2066:     boolean interrupted = false;
                   2067:
                   2068:     for (i=0; i<frames && !interrupted; i++) {
                   2069:         colorBlendCell(x, y, theColor, 100 - 100 * i / frames);
                   2070:         interrupted = pauseBrogue(50);
                   2071:     }
                   2072:
                   2073:     refreshDungeonCell(x, y);
                   2074: }
                   2075:
                   2076: // special effect expanding flash of light at dungeon coordinates (x, y) restricted to tiles with matching flags
                   2077: void colorFlash(const color *theColor, unsigned long reqTerrainFlags,
                   2078:                 unsigned long reqTileFlags, short frames, short maxRadius, short x, short y) {
                   2079:     short i, j, k, intensity, currentRadius, fadeOut;
                   2080:     short localRadius[DCOLS][DROWS];
                   2081:     boolean tileQualifies[DCOLS][DROWS], aTileQualified, fastForward;
                   2082:
                   2083:     aTileQualified = false;
                   2084:     fastForward = false;
                   2085:
                   2086:     for (i = max(x - maxRadius, 0); i <= min(x + maxRadius, DCOLS - 1); i++) {
                   2087:         for (j = max(y - maxRadius, 0); j <= min(y + maxRadius, DROWS - 1); j++) {
                   2088:             if ((!reqTerrainFlags || cellHasTerrainFlag(reqTerrainFlags, i, j))
                   2089:                 && (!reqTileFlags || (pmap[i][j].flags & reqTileFlags))
                   2090:                 && (i-x) * (i-x) + (j-y) * (j-y) <= maxRadius * maxRadius) {
                   2091:
                   2092:                 tileQualifies[i][j] = true;
                   2093:                 localRadius[i][j] = fp_sqrt(((i-x) * (i-x) + (j-y) * (j-y)) * FP_FACTOR) / FP_FACTOR;
                   2094:                 aTileQualified = true;
                   2095:             } else {
                   2096:                 tileQualifies[i][j] = false;
                   2097:             }
                   2098:         }
                   2099:     }
                   2100:
                   2101:     if (!aTileQualified) {
                   2102:         return;
                   2103:     }
                   2104:
                   2105:     for (k = 1; k <= frames; k++) {
                   2106:         currentRadius = max(1, maxRadius * k / frames);
                   2107:         fadeOut = min(100, (frames - k) * 100 * 5 / frames);
                   2108:         for (i = max(x - maxRadius, 0); i <= min(x + maxRadius, DCOLS - 1); i++) {
                   2109:             for (j = max(y - maxRadius, 0); j <= min(y + maxRadius, DROWS - 1); j++) {
                   2110:                 if (tileQualifies[i][j] && (localRadius[i][j] <= currentRadius)) {
                   2111:
                   2112:                     intensity = 100 - 100 * (currentRadius - localRadius[i][j] - 2) / currentRadius;
                   2113:                     intensity = fadeOut * intensity / 100;
                   2114:
                   2115:                     hiliteCell(i, j, theColor, intensity, false);
                   2116:                 }
                   2117:             }
                   2118:         }
                   2119:         if (!fastForward && (rogue.playbackFastForward || pauseBrogue(50))) {
                   2120:             k = frames - 1;
                   2121:             fastForward = true;
                   2122:         }
                   2123:     }
                   2124: }
                   2125:
                   2126: #define bCurve(x)   (((x) * (x) + 11) / (10 * ((x) * (x) + 1)) - 0.1)
                   2127:
                   2128: // x and y are global coordinates, not within the playing square
                   2129: void funkyFade(cellDisplayBuffer displayBuf[COLS][ROWS], const color *colorStart,
                   2130:                const color *colorEnd, short stepCount, short x, short y, boolean invert) {
                   2131:     short i, j, n, weight;
                   2132:     double x2, y2, weightGrid[COLS][ROWS][3], percentComplete;
                   2133:     color tempColor, colorMid, foreColor, backColor;
                   2134:     enum displayGlyph tempChar;
                   2135:     short **distanceMap;
                   2136:     boolean fastForward;
                   2137:
                   2138:     assureCosmeticRNG;
                   2139:
                   2140:     fastForward = false;
                   2141:     distanceMap = allocGrid();
                   2142:     fillGrid(distanceMap, 0);
                   2143:     calculateDistances(distanceMap, player.xLoc, player.yLoc, T_OBSTRUCTS_PASSABILITY, 0, true, true);
                   2144:
                   2145:     for (i=0; i<COLS; i++) {
                   2146:         x2 = (double) ((i - x) * 5.0 / COLS);
                   2147:         for (j=0; j<ROWS; j++) {
                   2148:             y2 = (double) ((j - y) * 2.5 / ROWS);
                   2149:
                   2150:             weightGrid[i][j][0] = bCurve(x2*x2+y2*y2) * (.7 + .3 * cos(5*x2*x2) * cos(5*y2*y2));
                   2151:             weightGrid[i][j][1] = bCurve(x2*x2+y2*y2) * (.7 + .3 * sin(5*x2*x2) * cos(5*y2*y2));
                   2152:             weightGrid[i][j][2] = bCurve(x2*x2+y2*y2);
                   2153:         }
                   2154:     }
                   2155:
                   2156:     for (n=(invert ? stepCount - 1 : 0); (invert ? n >= 0 : n <= stepCount); n += (invert ? -1 : 1)) {
                   2157:         for (i=0; i<COLS; i++) {
                   2158:             for (j=0; j<ROWS; j++) {
                   2159:
                   2160:                 percentComplete = (double) (n) * 100 / stepCount;
                   2161:
                   2162:                 colorMid = *colorStart;
                   2163:                 if (colorEnd) {
                   2164:                     applyColorAverage(&colorMid, colorEnd, n * 100 / stepCount);
                   2165:                 }
                   2166:
                   2167:                 // the fade color floods the reachable dungeon tiles faster
                   2168:                 if (!invert && coordinatesAreInMap(windowToMapX(i), windowToMapY(j))
                   2169:                     && distanceMap[windowToMapX(i)][windowToMapY(j)] >= 0 && distanceMap[windowToMapX(i)][windowToMapY(j)] < 30000) {
                   2170:                     percentComplete *= 1.0 + (100.0 - min(100, distanceMap[windowToMapX(i)][windowToMapY(j)])) / 100.;
                   2171:                 }
                   2172:
                   2173:                 weight = (short)(percentComplete + weightGrid[i][j][2] * percentComplete * 10);
                   2174:                 weight = min(100, weight);
                   2175:                 tempColor = black;
                   2176:
                   2177:                 tempColor.red = (short)(percentComplete + weightGrid[i][j][0] * percentComplete * 10) * colorMid.red / 100;
                   2178:                 tempColor.red = min(colorMid.red, tempColor.red);
                   2179:
                   2180:                 tempColor.green = (short)(percentComplete + weightGrid[i][j][1] * percentComplete * 10) * colorMid.green / 100;
                   2181:                 tempColor.green = min(colorMid.green, tempColor.green);
                   2182:
                   2183:                 tempColor.blue = (short)(percentComplete + weightGrid[i][j][2] * percentComplete * 10) * colorMid.blue / 100;
                   2184:                 tempColor.blue = min(colorMid.blue, tempColor.blue);
                   2185:
                   2186:                 backColor = black;
                   2187:
                   2188:                 backColor.red = displayBuf[i][j].backColorComponents[0];
                   2189:                 backColor.green = displayBuf[i][j].backColorComponents[1];
                   2190:                 backColor.blue = displayBuf[i][j].backColorComponents[2];
                   2191:
                   2192:                 foreColor = (invert ? white : black);
                   2193:
                   2194:                 if (j < MESSAGE_LINES
                   2195:                     && i >= mapToWindowX(0)
                   2196:                     && i < mapToWindowX(strLenWithoutEscapes(displayedMessage[MESSAGE_LINES - j - 1]))) {
                   2197:                     tempChar = displayedMessage[MESSAGE_LINES - j - 1][windowToMapX(i)];
                   2198:                 } else {
                   2199:                     tempChar = displayBuf[i][j].character;
                   2200:
                   2201:                     foreColor.red = displayBuf[i][j].foreColorComponents[0];
                   2202:                     foreColor.green = displayBuf[i][j].foreColorComponents[1];
                   2203:                     foreColor.blue = displayBuf[i][j].foreColorComponents[2];
                   2204:
                   2205:                     applyColorAverage(&foreColor, &tempColor, weight);
                   2206:                 }
                   2207:                 applyColorAverage(&backColor, &tempColor, weight);
                   2208:                 plotCharWithColor(tempChar, i, j, &foreColor, &backColor);
                   2209:             }
                   2210:         }
                   2211:         if (!fastForward && pauseBrogue(16)) {
                   2212:             // drop the event - skipping the transition should only skip the transition
                   2213:             rogueEvent event;
                   2214:             nextKeyOrMouseEvent(&event, false, false);
                   2215:             fastForward = true;
                   2216:             n = (invert ? 1 : stepCount - 2);
                   2217:         }
                   2218:     }
                   2219:
                   2220:     freeGrid(distanceMap);
                   2221:
                   2222:     restoreRNG;
                   2223: }
                   2224:
                   2225: void displayWaypoints() {
                   2226:     short i, j, w, lowestDistance;
                   2227:
                   2228:     for (i=0; i<DCOLS; i++) {
                   2229:         for (j=0; j<DROWS; j++) {
                   2230:             lowestDistance = 30000;
                   2231:             for (w=0; w<rogue.wpCount; w++) {
                   2232:                 if (rogue.wpDistance[w][i][j] < lowestDistance) {
                   2233:                     lowestDistance = rogue.wpDistance[w][i][j];
                   2234:                 }
                   2235:             }
                   2236:             if (lowestDistance < 10) {
                   2237:                 hiliteCell(i, j, &white, clamp(100 - lowestDistance*15, 0, 100), true);
                   2238:             }
                   2239:         }
                   2240:     }
                   2241:     temporaryMessage("Waypoints:", true);
                   2242: }
                   2243:
                   2244: void displayMachines() {
                   2245:     short i, j;
                   2246:     color foreColor, backColor, machineColors[50];
                   2247:     enum displayGlyph dchar;
                   2248:
                   2249:     assureCosmeticRNG;
                   2250:
                   2251:     for (i=0; i<50; i++) {
                   2252:         machineColors[i] = black;
                   2253:         machineColors[i].red = rand_range(0, 100);
                   2254:         machineColors[i].green = rand_range(0, 100);
                   2255:         machineColors[i].blue = rand_range(0, 100);
                   2256:     }
                   2257:
                   2258:     for (i=0; i<DCOLS; i++) {
                   2259:         for (j=0; j<DROWS; j++) {
                   2260:             if (pmap[i][j].machineNumber) {
                   2261:                 getCellAppearance(i, j, &dchar, &foreColor, &backColor);
                   2262:                 applyColorAugment(&backColor, &(machineColors[pmap[i][j].machineNumber]), 50);
                   2263:                 //plotCharWithColor(dchar, mapToWindowX(i), mapToWindowY(j), &foreColor, &backColor);
                   2264:                 if (pmap[i][j].machineNumber < 10) {
                   2265:                     dchar ='0' + pmap[i][j].machineNumber;
                   2266:                 } else if (pmap[i][j].machineNumber < 10 + 26) {
                   2267:                     dchar = 'a' + pmap[i][j].machineNumber - 10;
                   2268:                 } else {
                   2269:                     dchar = 'A' + pmap[i][j].machineNumber - 10 - 26;
                   2270:                 }
                   2271:                 plotCharWithColor(dchar, mapToWindowX(i), mapToWindowY(j), &foreColor, &backColor);
                   2272:             }
                   2273:         }
                   2274:     }
                   2275:     displayMoreSign();
                   2276:     displayLevel();
                   2277:
                   2278:     restoreRNG;
                   2279: }
                   2280:
                   2281: #define CHOKEMAP_DISPLAY_CUTOFF 160
                   2282: void displayChokeMap() {
                   2283:     short i, j;
                   2284:     color foreColor, backColor;
                   2285:     enum displayGlyph dchar;
                   2286:
                   2287:     for (i=0; i<DCOLS; i++) {
                   2288:         for (j=0; j<DROWS; j++) {
                   2289:             if (chokeMap[i][j] < CHOKEMAP_DISPLAY_CUTOFF) {
                   2290:                 if (pmap[i][j].flags & IS_GATE_SITE) {
                   2291:                     getCellAppearance(i, j, &dchar, &foreColor, &backColor);
                   2292:                     applyColorAugment(&backColor, &teal, 50);
                   2293:                     plotCharWithColor(dchar, mapToWindowX(i), mapToWindowY(j), &foreColor, &backColor);
                   2294:                 } else
                   2295:                     if (chokeMap[i][j] < CHOKEMAP_DISPLAY_CUTOFF) {
                   2296:                     getCellAppearance(i, j, &dchar, &foreColor, &backColor);
                   2297:                     applyColorAugment(&backColor, &red, 100 - chokeMap[i][j] * 100 / CHOKEMAP_DISPLAY_CUTOFF);
                   2298:                     plotCharWithColor(dchar, mapToWindowX(i), mapToWindowY(j), &foreColor, &backColor);
                   2299:                 }
                   2300:             }
                   2301:         }
                   2302:     }
                   2303:     displayMoreSign();
                   2304:     displayLevel();
                   2305: }
                   2306:
                   2307: void displayLoops() {
                   2308:     short i, j;
                   2309:     color foreColor, backColor;
                   2310:     enum displayGlyph dchar;
                   2311:
                   2312:     for (i=0; i<DCOLS; i++) {
                   2313:         for (j=0; j<DROWS; j++) {
                   2314:             if (pmap[i][j].flags & IN_LOOP) {
                   2315:                 getCellAppearance(i, j, &dchar, &foreColor, &backColor);
                   2316:                 applyColorAugment(&backColor, &yellow, 50);
                   2317:                 plotCharWithColor(dchar, mapToWindowX(i), mapToWindowY(j), &foreColor, &backColor);
                   2318:                 //colorBlendCell(i, j, &tempColor, 100);//hiliteCell(i, j, &tempColor, 100, true);
                   2319:             }
                   2320:             if (pmap[i][j].flags & IS_CHOKEPOINT) {
                   2321:                 getCellAppearance(i, j, &dchar, &foreColor, &backColor);
                   2322:                 applyColorAugment(&backColor, &teal, 50);
                   2323:                 plotCharWithColor(dchar, mapToWindowX(i), mapToWindowY(j), &foreColor, &backColor);
                   2324:             }
                   2325:         }
                   2326:     }
                   2327:     waitForAcknowledgment();
                   2328: }
                   2329:
                   2330: void exploreKey(const boolean controlKey) {
                   2331:     short x, y, finalX = 0, finalY = 0;
                   2332:     short **exploreMap;
                   2333:     enum directions dir;
                   2334:     boolean tooDark = false;
                   2335:
                   2336:     // fight any adjacent enemies first
                   2337:     dir = adjacentFightingDir();
                   2338:     if (dir == NO_DIRECTION) {
                   2339:         for (dir = 0; dir < DIRECTION_COUNT; dir++) {
                   2340:             x = player.xLoc + nbDirs[dir][0];
                   2341:             y = player.yLoc + nbDirs[dir][1];
                   2342:             if (coordinatesAreInMap(x, y)
                   2343:                 && !(pmap[x][y].flags & DISCOVERED)) {
                   2344:
                   2345:                 tooDark = true;
                   2346:                 break;
                   2347:             }
                   2348:         }
                   2349:         if (!tooDark) {
                   2350:             x = finalX = player.xLoc;
                   2351:             y = finalY = player.yLoc;
                   2352:
                   2353:             exploreMap = allocGrid();
                   2354:             getExploreMap(exploreMap, false);
                   2355:             do {
                   2356:                 dir = nextStep(exploreMap, x, y, NULL, false);
                   2357:                 if (dir != NO_DIRECTION) {
                   2358:                     x += nbDirs[dir][0];
                   2359:                     y += nbDirs[dir][1];
                   2360:                     if (pmap[x][y].flags & (DISCOVERED | MAGIC_MAPPED)) {
                   2361:                         finalX = x;
                   2362:                         finalY = y;
                   2363:                     }
                   2364:                 }
                   2365:             } while (dir != NO_DIRECTION);
                   2366:             freeGrid(exploreMap);
                   2367:         }
                   2368:     } else {
                   2369:         x = finalX = player.xLoc + nbDirs[dir][0];
                   2370:         y = finalY = player.yLoc + nbDirs[dir][1];
                   2371:     }
                   2372:
                   2373:     if (tooDark) {
                   2374:         message("It's too dark to explore!", false);
                   2375:     } else if (x == player.xLoc && y == player.yLoc) {
                   2376:         message("I see no path for further exploration.", false);
                   2377:     } else if (proposeOrConfirmLocation(finalX, finalY, "I see no path for further exploration.")) {
                   2378:         explore(controlKey ? 1 : 20); // Do the exploring until interrupted.
                   2379:         hideCursor();
                   2380:         exploreKey(controlKey);
                   2381:     }
                   2382: }
                   2383:
                   2384: boolean pauseBrogue(short milliseconds) {
                   2385:     boolean interrupted;
                   2386:
                   2387:     commitDraws();
                   2388:     if (rogue.playbackMode && rogue.playbackFastForward) {
                   2389:         interrupted = true;
                   2390:     } else {
                   2391:         interrupted = pauseForMilliseconds(milliseconds);
                   2392:     }
                   2393:     return interrupted;
                   2394: }
                   2395:
                   2396: void nextBrogueEvent(rogueEvent *returnEvent, boolean textInput, boolean colorsDance, boolean realInputEvenInPlayback) {
                   2397:     rogueEvent recordingInput;
                   2398:     boolean repeatAgain, interaction;
                   2399:     short pauseDuration;
                   2400:
                   2401:     returnEvent->eventType = EVENT_ERROR;
                   2402:
                   2403:     if (rogue.playbackMode && !realInputEvenInPlayback) {
                   2404:         do {
                   2405:             repeatAgain = false;
                   2406:             if ((!rogue.playbackFastForward && rogue.playbackBetweenTurns)
                   2407:                 || rogue.playbackOOS) {
                   2408:
                   2409:                 pauseDuration = (rogue.playbackPaused ? DEFAULT_PLAYBACK_DELAY : rogue.playbackDelayThisTurn);
                   2410:                 if (pauseDuration && pauseBrogue(pauseDuration)) {
                   2411:                     // if the player did something during playback
                   2412:                     nextBrogueEvent(&recordingInput, false, false, true);
                   2413:                     interaction = executePlaybackInput(&recordingInput);
                   2414:                     repeatAgain = !rogue.playbackPaused && interaction;
                   2415:                 }
                   2416:             }
                   2417:         } while ((repeatAgain || rogue.playbackOOS) && !rogue.gameHasEnded);
                   2418:         rogue.playbackDelayThisTurn = rogue.playbackDelayPerTurn;
                   2419:         recallEvent(returnEvent);
                   2420:     } else {
                   2421:         commitDraws();
                   2422:         if (rogue.creaturesWillFlashThisTurn) {
                   2423:             displayMonsterFlashes(true);
                   2424:         }
                   2425:         do {
                   2426:             nextKeyOrMouseEvent(returnEvent, textInput, colorsDance); // No mouse clicks outside of the window will register.
                   2427:         } while (returnEvent->eventType == MOUSE_UP && !coordinatesAreInWindow(returnEvent->param1, returnEvent->param2));
                   2428:         // recording done elsewhere
                   2429:     }
                   2430:
                   2431:     if (returnEvent->eventType == EVENT_ERROR) {
                   2432:         rogue.playbackPaused = rogue.playbackMode; // pause if replaying
                   2433:         message("Event error!", true);
                   2434:     }
                   2435: }
                   2436:
                   2437: void executeMouseClick(rogueEvent *theEvent) {
                   2438:     short x, y;
                   2439:     boolean autoConfirm;
                   2440:     x = theEvent->param1;
                   2441:     y = theEvent->param2;
                   2442:     autoConfirm = theEvent->controlKey;
                   2443:
                   2444:     if (theEvent->eventType == RIGHT_MOUSE_UP) {
                   2445:         displayInventory(ALL_ITEMS, 0, 0, true, true);
                   2446:     } else if (coordinatesAreInMap(windowToMapX(x), windowToMapY(y))) {
                   2447:         if (autoConfirm) {
                   2448:             travel(windowToMapX(x), windowToMapY(y), autoConfirm);
                   2449:         } else {
                   2450:             rogue.cursorLoc[0] = windowToMapX(x);
                   2451:             rogue.cursorLoc[1] = windowToMapY(y);
                   2452:             mainInputLoop();
                   2453:         }
                   2454:
                   2455:     } else if (windowToMapX(x) >= 0 && windowToMapX(x) < DCOLS && y >= 0 && y < MESSAGE_LINES) {
                   2456:         // If the click location is in the message block, display the message archive.
                   2457:         displayMessageArchive();
                   2458:     }
                   2459: }
                   2460:
                   2461: void executeKeystroke(signed long keystroke, boolean controlKey, boolean shiftKey) {
                   2462:     char path[BROGUE_FILENAME_MAX];
                   2463:     short direction = -1;
                   2464:
                   2465:     confirmMessages();
                   2466:     stripShiftFromMovementKeystroke(&keystroke);
                   2467:
                   2468:     switch (keystroke) {
                   2469:         case UP_KEY:
                   2470:         case UP_ARROW:
                   2471:         case NUMPAD_8:
                   2472:             direction = UP;
                   2473:             break;
                   2474:         case DOWN_KEY:
                   2475:         case DOWN_ARROW:
                   2476:         case NUMPAD_2:
                   2477:             direction = DOWN;
                   2478:             break;
                   2479:         case LEFT_KEY:
                   2480:         case LEFT_ARROW:
                   2481:         case NUMPAD_4:
                   2482:             direction = LEFT;
                   2483:             break;
                   2484:         case RIGHT_KEY:
                   2485:         case RIGHT_ARROW:
                   2486:         case NUMPAD_6:
                   2487:             direction = RIGHT;
                   2488:             break;
                   2489:         case NUMPAD_7:
                   2490:         case UPLEFT_KEY:
                   2491:             direction = UPLEFT;
                   2492:             break;
                   2493:         case UPRIGHT_KEY:
                   2494:         case NUMPAD_9:
                   2495:             direction = UPRIGHT;
                   2496:             break;
                   2497:         case DOWNLEFT_KEY:
                   2498:         case NUMPAD_1:
                   2499:             direction = DOWNLEFT;
                   2500:             break;
                   2501:         case DOWNRIGHT_KEY:
                   2502:         case NUMPAD_3:
                   2503:             direction = DOWNRIGHT;
                   2504:             break;
                   2505:         case DESCEND_KEY:
                   2506:             considerCautiousMode();
                   2507:             if (D_WORMHOLING) {
                   2508:                 recordKeystroke(DESCEND_KEY, false, false);
                   2509:                 useStairs(1);
                   2510:             } else if (proposeOrConfirmLocation(rogue.downLoc[0], rogue.downLoc[1], "I see no way down.")) {
                   2511:                 travel(rogue.downLoc[0], rogue.downLoc[1], true);
                   2512:             }
                   2513:             break;
                   2514:         case ASCEND_KEY:
                   2515:             considerCautiousMode();
                   2516:             if (D_WORMHOLING) {
                   2517:                 recordKeystroke(ASCEND_KEY, false, false);
                   2518:                 useStairs(-1);
                   2519:             } else if (proposeOrConfirmLocation(rogue.upLoc[0], rogue.upLoc[1], "I see no way up.")) {
                   2520:                 travel(rogue.upLoc[0], rogue.upLoc[1], true);
                   2521:             }
                   2522:             break;
                   2523:         case RETURN_KEY:
                   2524:             showCursor();
                   2525:             break;
                   2526:         case REST_KEY:
                   2527:         case PERIOD_KEY:
                   2528:         case NUMPAD_5:
                   2529:             considerCautiousMode();
                   2530:             rogue.justRested = true;
                   2531:             recordKeystroke(REST_KEY, false, false);
                   2532:             playerTurnEnded();
                   2533:             break;
                   2534:         case AUTO_REST_KEY:
                   2535:             rogue.justRested = true;
                   2536:             autoRest();
                   2537:             break;
                   2538:         case SEARCH_KEY:
                   2539:             if (controlKey) {
                   2540:                 rogue.disturbed = false;
                   2541:                 rogue.automationActive = true;
                   2542:                 do {
                   2543:                     manualSearch();
                   2544:                     if (pauseBrogue(80)) {
                   2545:                         rogue.disturbed = true;
                   2546:                     }
                   2547:                 } while (player.status[STATUS_SEARCHING] < 5 && !rogue.disturbed);
                   2548:                 rogue.automationActive = false;
                   2549:             } else {
                   2550:                 manualSearch();
                   2551:             }
                   2552:             break;
                   2553:         case INVENTORY_KEY:
                   2554:             displayInventory(ALL_ITEMS, 0, 0, true, true);
                   2555:             break;
                   2556:         case EQUIP_KEY:
                   2557:             equip(NULL);
                   2558:             break;
                   2559:         case UNEQUIP_KEY:
                   2560:             unequip(NULL);
                   2561:             break;
                   2562:         case DROP_KEY:
                   2563:             drop(NULL);
                   2564:             break;
                   2565:         case APPLY_KEY:
                   2566:             apply(NULL, true);
                   2567:             break;
                   2568:         case THROW_KEY:
                   2569:             throwCommand(NULL, false);
                   2570:             break;
                   2571:         case RETHROW_KEY:
                   2572:             if (rogue.lastItemThrown != NULL && itemIsCarried(rogue.lastItemThrown)) {
                   2573:                 throwCommand(rogue.lastItemThrown, true);
                   2574:             }
                   2575:             break;
                   2576:         case RELABEL_KEY:
                   2577:             relabel(NULL);
                   2578:             break;
                   2579:         case TRUE_COLORS_KEY:
                   2580:             rogue.trueColorMode = !rogue.trueColorMode;
                   2581:             displayLevel();
                   2582:             refreshSideBar(-1, -1, false);
                   2583:             if (rogue.trueColorMode) {
                   2584:                 messageWithColor(KEYBOARD_LABELS ? "Color effects disabled. Press '\\' again to enable." : "Color effects disabled.",
                   2585:                                  &teal, false);
                   2586:             } else {
                   2587:                 messageWithColor(KEYBOARD_LABELS ? "Color effects enabled. Press '\\' again to disable." : "Color effects enabled.",
                   2588:                                  &teal, false);
                   2589:             }
                   2590:             break;
                   2591:         case AGGRO_DISPLAY_KEY:
                   2592:             rogue.displayAggroRangeMode = !rogue.displayAggroRangeMode;
                   2593:             displayLevel();
                   2594:             refreshSideBar(-1, -1, false);
                   2595:             if (rogue.displayAggroRangeMode) {
                   2596:                 messageWithColor(KEYBOARD_LABELS ? "Stealth range displayed. Press ']' again to hide." : "Stealth range displayed.",
                   2597:                                  &teal, false);
                   2598:             } else {
                   2599:                 messageWithColor(KEYBOARD_LABELS ? "Stealth range hidden. Press ']' again to display." : "Stealth range hidden.",
                   2600:                                  &teal, false);
                   2601:             }
                   2602:             break;
                   2603:         case CALL_KEY:
                   2604:             call(NULL);
                   2605:             break;
                   2606:         case EXPLORE_KEY:
                   2607:             considerCautiousMode();
                   2608:             exploreKey(controlKey);
                   2609:             break;
                   2610:         case AUTOPLAY_KEY:
                   2611:             if (confirm("Turn on autopilot?", false)) {
                   2612:                 autoPlayLevel(controlKey);
                   2613:             }
                   2614:             break;
                   2615:         case MESSAGE_ARCHIVE_KEY:
                   2616:             displayMessageArchive();
                   2617:             break;
                   2618:         case BROGUE_HELP_KEY:
                   2619:             printHelpScreen();
                   2620:             break;
                   2621:         case DISCOVERIES_KEY:
                   2622:             printDiscoveriesScreen();
                   2623:             break;
                   2624:         case VIEW_RECORDING_KEY:
                   2625:             if (rogue.playbackMode || serverMode) {
                   2626:                 return;
                   2627:             }
                   2628:             confirmMessages();
                   2629:             if ((rogue.playerTurnNumber < 50 || confirm("End this game and view a recording?", false))
                   2630:                 && dialogChooseFile(path, RECORDING_SUFFIX, "View recording: ")) {
                   2631:                 if (fileExists(path)) {
                   2632:                     strcpy(rogue.nextGamePath, path);
                   2633:                     rogue.nextGame = NG_VIEW_RECORDING;
                   2634:                     rogue.gameHasEnded = true;
                   2635:                 } else {
                   2636:                     message("File not found.", false);
                   2637:                 }
                   2638:             }
                   2639:             break;
                   2640:         case LOAD_SAVED_GAME_KEY:
                   2641:             if (rogue.playbackMode || serverMode) {
                   2642:                 return;
                   2643:             }
                   2644:             confirmMessages();
                   2645:             if ((rogue.playerTurnNumber < 50 || confirm("End this game and load a saved game?", false))
                   2646:                 && dialogChooseFile(path, GAME_SUFFIX, "Open saved game: ")) {
                   2647:                 if (fileExists(path)) {
                   2648:                     strcpy(rogue.nextGamePath, path);
                   2649:                     rogue.nextGame = NG_OPEN_GAME;
                   2650:                     rogue.gameHasEnded = true;
                   2651:                 } else {
                   2652:                     message("File not found.", false);
                   2653:                 }
                   2654:             }
                   2655:             break;
                   2656:         case SAVE_GAME_KEY:
                   2657:             if (rogue.playbackMode || serverMode) {
                   2658:                 return;
                   2659:             }
                   2660:             if (confirm("Suspend this game? (This feature is still in beta.)", false)) {
                   2661:                 saveGame();
                   2662:             }
                   2663:             break;
                   2664:         case NEW_GAME_KEY:
                   2665:             if (rogue.playerTurnNumber < 50 || confirm("End this game and begin a new game?", false)) {
                   2666:                 rogue.nextGame = NG_NEW_GAME;
                   2667:                 rogue.gameHasEnded = true;
                   2668:             }
                   2669:             break;
                   2670:         case QUIT_KEY:
                   2671:             if (confirm("Quit this game without saving?", false)) {
                   2672:                 recordKeystroke(QUIT_KEY, false, false);
                   2673:                 rogue.quit = true;
                   2674:                 gameOver("Quit", true);
                   2675:             }
                   2676:             break;
                   2677:         case GRAPHICS_KEY:
                   2678:             if (hasGraphics) {
                   2679:                 graphicsEnabled = setGraphicsEnabled(!graphicsEnabled);
                   2680:                 if (graphicsEnabled) {
                   2681:                     messageWithColor(KEYBOARD_LABELS ? "Enabled graphical tiles. Press 'G' again to disable." : "Enable graphical tiles.",
                   2682:                                     &teal, false);
                   2683:                 } else {
                   2684:                     messageWithColor(KEYBOARD_LABELS ? "Disabled graphical tiles. Press 'G' again to enable." : "Disabled graphical tiles.",
                   2685:                                     &teal, false);
                   2686:                 }
                   2687:             }
                   2688:             break;
                   2689:         case SEED_KEY:
                   2690:             /*DEBUG {
                   2691:                 cellDisplayBuffer dbuf[COLS][ROWS];
                   2692:                 copyDisplayBuffer(dbuf, displayBuffer);
                   2693:                 funkyFade(dbuf, &white, 0, 100, mapToWindowX(player.xLoc), mapToWindowY(player.yLoc), false);
                   2694:             }*/
                   2695:             // DEBUG displayLoops();
                   2696:             // DEBUG displayChokeMap();
                   2697:             DEBUG displayMachines();
                   2698:             //DEBUG displayWaypoints();
                   2699:             // DEBUG {displayGrid(safetyMap); displayMoreSign(); displayLevel();}
                   2700:             // parseFile();
                   2701:             // DEBUG spawnDungeonFeature(player.xLoc, player.yLoc, &dungeonFeatureCatalog[DF_METHANE_GAS_ARMAGEDDON], true, false);
                   2702:             printSeed();
                   2703:             break;
                   2704:         case EASY_MODE_KEY:
                   2705:             //if (shiftKey) {
                   2706:                 enableEasyMode();
                   2707:             //}
                   2708:             break;
                   2709:         case PRINTSCREEN_KEY:
                   2710:             if (takeScreenshot()) {
                   2711:                 flashTemporaryAlert(" Screenshot saved in save directory ", 2000);
                   2712:             }
                   2713:             break;
                   2714:         default:
                   2715:             break;
                   2716:     }
                   2717:     if (direction >= 0) { // if it was a movement command
                   2718:         hideCursor();
                   2719:         considerCautiousMode();
                   2720:         if (controlKey || shiftKey) {
                   2721:             playerRuns(direction);
                   2722:         } else {
                   2723:             playerMoves(direction);
                   2724:         }
                   2725:         refreshSideBar(-1, -1, false);
                   2726:     }
                   2727:
                   2728:     if (D_SAFETY_VISION) {
                   2729:         displayGrid(safetyMap);
                   2730:     }
                   2731:     if (rogue.trueColorMode || D_SCENT_VISION) {
                   2732:         displayLevel();
                   2733:     }
                   2734:
                   2735:     rogue.cautiousMode = false;
                   2736: }
                   2737:
                   2738: boolean getInputTextString(char *inputText,
                   2739:                            const char *prompt,
                   2740:                            short maxLength,
                   2741:                            const char *defaultEntry,
                   2742:                            const char *promptSuffix,
                   2743:                            short textEntryType,
                   2744:                            boolean useDialogBox) {
                   2745:     short charNum, i, x, y;
                   2746:     char keystroke, suffix[100];
                   2747:     const short textEntryBounds[TEXT_INPUT_TYPES][2] = {{' ', '~'}, {' ', '~'}, {'0', '9'}};
                   2748:     cellDisplayBuffer dbuf[COLS][ROWS], rbuf[COLS][ROWS];
                   2749:
                   2750:     // x and y mark the origin for text entry.
                   2751:     if (useDialogBox) {
                   2752:         x = (COLS - max(maxLength, strLenWithoutEscapes(prompt))) / 2;
                   2753:         y = ROWS / 2 - 1;
                   2754:         clearDisplayBuffer(dbuf);
                   2755:         rectangularShading(x - 1, y - 2, max(maxLength, strLenWithoutEscapes(prompt)) + 2,
                   2756:                            4, &interfaceBoxColor, INTERFACE_OPACITY, dbuf);
                   2757:         overlayDisplayBuffer(dbuf, rbuf);
                   2758:         printString(prompt, x, y - 1, &white, &interfaceBoxColor, NULL);
                   2759:         for (i=0; i<maxLength; i++) {
                   2760:             plotCharWithColor(' ', x + i, y, &black, &black);
                   2761:         }
                   2762:         printString(defaultEntry, x, y, &white, &black, 0);
                   2763:     } else {
                   2764:         confirmMessages();
                   2765:         x = mapToWindowX(strLenWithoutEscapes(prompt));
                   2766:         y = MESSAGE_LINES - 1;
                   2767:         message(prompt, false);
                   2768:         printString(defaultEntry, x, y, &white, &black, 0);
                   2769:     }
                   2770:
                   2771:     maxLength = min(maxLength, COLS - x);
                   2772:
                   2773:
                   2774:     strcpy(inputText, defaultEntry);
                   2775:     charNum = strLenWithoutEscapes(inputText);
                   2776:     for (i = charNum; i < maxLength; i++) {
                   2777:         inputText[i] = ' ';
                   2778:     }
                   2779:
                   2780:     if (promptSuffix[0] == '\0') { // empty suffix
                   2781:         strcpy(suffix, " "); // so that deleting doesn't leave a white trail
                   2782:     } else {
                   2783:         strcpy(suffix, promptSuffix);
                   2784:     }
                   2785:
                   2786:     do {
                   2787:         printString(suffix, charNum + x, y, &gray, &black, 0);
                   2788:         plotCharWithColor((suffix[0] ? suffix[0] : ' '), x + charNum, y, &black, &white);
                   2789:         keystroke = nextKeyPress(true);
                   2790:         if (keystroke == DELETE_KEY && charNum > 0) {
                   2791:             printString(suffix, charNum + x - 1, y, &gray, &black, 0);
                   2792:             plotCharWithColor(' ', x + charNum + strlen(suffix) - 1, y, &black, &black);
                   2793:             charNum--;
                   2794:             inputText[charNum] = ' ';
                   2795:         } else if (keystroke >= textEntryBounds[textEntryType][0]
                   2796:                    && keystroke <= textEntryBounds[textEntryType][1]) { // allow only permitted input
                   2797:
                   2798:             if (textEntryType == TEXT_INPUT_FILENAME
                   2799:                 && characterForbiddenInFilename(keystroke)) {
                   2800:
                   2801:                 keystroke = '-';
                   2802:             }
                   2803:
                   2804:             inputText[charNum] = keystroke;
                   2805:             plotCharWithColor(keystroke, x + charNum, y, &white, &black);
                   2806:             printString(suffix, charNum + x + 1, y, &gray, &black, 0);
                   2807:             if (charNum < maxLength) {
                   2808:                 charNum++;
                   2809:             }
                   2810:         }
                   2811: #ifdef USE_CLIPBOARD
                   2812:         else if (keystroke == TAB_KEY) {
                   2813:             char* clipboard = getClipboard();
                   2814:             for (int i=0; i<(int) min(strlen(clipboard), (unsigned long) (maxLength - charNum)); ++i) {
                   2815:
                   2816:                 char character = clipboard[i];
                   2817:
                   2818:                 if (character >= textEntryBounds[textEntryType][0]
                   2819:                     && character <= textEntryBounds[textEntryType][1]) { // allow only permitted input
                   2820:                     if (textEntryType == TEXT_INPUT_FILENAME
                   2821:                         && characterForbiddenInFilename(character)) {
                   2822:                         character = '-';
                   2823:                     }
                   2824:                     plotCharWithColor(character, x + charNum, y, &white, &black);
                   2825:                     if (charNum < maxLength) {
                   2826:                         charNum++;
                   2827:                     }
                   2828:                 }
                   2829:             }
                   2830:         }
                   2831: #endif
                   2832:     } while (keystroke != RETURN_KEY && keystroke != ESCAPE_KEY);
                   2833:
                   2834:     if (useDialogBox) {
                   2835:         overlayDisplayBuffer(rbuf, NULL);
                   2836:     }
                   2837:
                   2838:     inputText[charNum] = '\0';
                   2839:
                   2840:     if (keystroke == ESCAPE_KEY) {
                   2841:         return false;
                   2842:     }
                   2843:     strcat(displayedMessage[0], inputText);
                   2844:     strcat(displayedMessage[0], suffix);
                   2845:     return true;
                   2846: }
                   2847:
                   2848: void displayCenteredAlert(char *message) {
                   2849:     printString(message, (COLS - strLenWithoutEscapes(message)) / 2, ROWS / 2, &teal, &black, 0);
                   2850: }
                   2851:
                   2852: // Flashes a message on the screen starting at (x, y) lasting for the given time (in ms) and with the given colors.
                   2853: void flashMessage(char *message, short x, short y, int time, color *fColor, color *bColor) {
                   2854:     boolean fastForward;
                   2855:     int     i, j, messageLength, percentComplete, previousPercentComplete;
                   2856:     color backColors[COLS], backColor, foreColor;
                   2857:     cellDisplayBuffer dbufs[COLS];
                   2858:     enum displayGlyph dchar;
                   2859:     short oldRNG;
                   2860:     const int stepInMs = 16;
                   2861:
                   2862:     if (rogue.playbackFastForward) {
                   2863:         return;
                   2864:     }
                   2865:
                   2866:     oldRNG = rogue.RNG;
                   2867:     rogue.RNG = RNG_COSMETIC;
                   2868:     //assureCosmeticRNG;
                   2869:
                   2870:     messageLength = strLenWithoutEscapes(message);
                   2871:     fastForward = false;
                   2872:
                   2873:     for (j=0; j<messageLength; j++) {
                   2874:         backColors[j] = colorFromComponents(displayBuffer[j + x][y].backColorComponents);
                   2875:         dbufs[j] = displayBuffer[j + x][y];
                   2876:     }
                   2877:
                   2878:     previousPercentComplete = -1;
                   2879:     for (i=0; i < time && fastForward == false; i += stepInMs) {
                   2880:         percentComplete = 100 * i / time;
                   2881:         percentComplete = percentComplete * percentComplete / 100; // transition is front-loaded
                   2882:         if (previousPercentComplete != percentComplete) {
                   2883:             for (j=0; j<messageLength; j++) {
                   2884:                 if (i==0) {
                   2885:                     backColors[j] = colorFromComponents(displayBuffer[j + x][y].backColorComponents);
                   2886:                     dbufs[j] = displayBuffer[j + x][y];
                   2887:                 }
                   2888:                 backColor = backColors[j];
                   2889:                 applyColorAverage(&backColor, bColor, 100 - percentComplete);
                   2890:                 if (percentComplete < 50) {
                   2891:                     dchar = message[j];
                   2892:                     foreColor = *fColor;
                   2893:                     applyColorAverage(&foreColor, &backColor, percentComplete * 2);
                   2894:                 } else {
                   2895:                     dchar = dbufs[j].character;
                   2896:                     foreColor = colorFromComponents(dbufs[j].foreColorComponents);
                   2897:                     applyColorAverage(&foreColor, &backColor, (100 - percentComplete) * 2);
                   2898:                 }
                   2899:                 plotCharWithColor(dchar, j+x, y, &foreColor, &backColor);
                   2900:             }
                   2901:         }
                   2902:         previousPercentComplete = percentComplete;
                   2903:         fastForward = pauseBrogue(stepInMs);
                   2904:     }
                   2905:     for (j=0; j<messageLength; j++) {
                   2906:         foreColor = colorFromComponents(dbufs[j].foreColorComponents);
                   2907:         plotCharWithColor(dbufs[j].character, j+x, y, &foreColor, &(backColors[j]));
                   2908:     }
                   2909:
                   2910:     restoreRNG;
                   2911: }
                   2912:
                   2913: void flashTemporaryAlert(char *message, int time) {
                   2914:     flashMessage(message, (COLS - strLenWithoutEscapes(message)) / 2, ROWS / 2, time, &teal, &black);
                   2915: }
                   2916:
                   2917: void waitForAcknowledgment() {
                   2918:     rogueEvent theEvent;
                   2919:
                   2920:     if (rogue.autoPlayingLevel || (rogue.playbackMode && !rogue.playbackOOS)) {
                   2921:         return;
                   2922:     }
                   2923:
                   2924:     do {
                   2925:         nextBrogueEvent(&theEvent, false, false, false);
                   2926:         if (theEvent.eventType == KEYSTROKE && theEvent.param1 != ACKNOWLEDGE_KEY && theEvent.param1 != ESCAPE_KEY) {
                   2927:             flashTemporaryAlert(" -- Press space or click to continue -- ", 500);
                   2928:         }
                   2929:     } while (!(theEvent.eventType == KEYSTROKE && (theEvent.param1 == ACKNOWLEDGE_KEY || theEvent.param1 == ESCAPE_KEY)
                   2930:                || theEvent.eventType == MOUSE_UP));
                   2931: }
                   2932:
                   2933: void waitForKeystrokeOrMouseClick() {
                   2934:     rogueEvent theEvent;
                   2935:     do {
                   2936:         nextBrogueEvent(&theEvent, false, false, false);
                   2937:     } while (theEvent.eventType != KEYSTROKE && theEvent.eventType != MOUSE_UP);
                   2938: }
                   2939:
                   2940: boolean confirm(char *prompt, boolean alsoDuringPlayback) {
                   2941:     short retVal;
                   2942:     brogueButton buttons[2] = {{{0}}};
                   2943:     cellDisplayBuffer rbuf[COLS][ROWS];
                   2944:     char whiteColorEscape[20] = "";
                   2945:     char yellowColorEscape[20] = "";
                   2946:
                   2947:     if (rogue.autoPlayingLevel || (!alsoDuringPlayback && rogue.playbackMode)) {
                   2948:         return true; // oh yes he did
                   2949:     }
                   2950:
                   2951:     encodeMessageColor(whiteColorEscape, 0, &white);
                   2952:     encodeMessageColor(yellowColorEscape, 0, KEYBOARD_LABELS ? &yellow : &white);
                   2953:
                   2954:     initializeButton(&(buttons[0]));
                   2955:     sprintf(buttons[0].text, "     %sY%ses     ", yellowColorEscape, whiteColorEscape);
                   2956:     buttons[0].hotkey[0] = 'y';
                   2957:     buttons[0].hotkey[1] = 'Y';
                   2958:     buttons[0].hotkey[2] = RETURN_KEY;
                   2959:     buttons[0].flags |= (B_WIDE_CLICK_AREA | B_KEYPRESS_HIGHLIGHT);
                   2960:
                   2961:     initializeButton(&(buttons[1]));
                   2962:     sprintf(buttons[1].text, "     %sN%so      ", yellowColorEscape, whiteColorEscape);
                   2963:     buttons[1].hotkey[0] = 'n';
                   2964:     buttons[1].hotkey[1] = 'N';
                   2965:     buttons[1].hotkey[2] = ACKNOWLEDGE_KEY;
                   2966:     buttons[1].hotkey[3] = ESCAPE_KEY;
                   2967:     buttons[1].flags |= (B_WIDE_CLICK_AREA | B_KEYPRESS_HIGHLIGHT);
                   2968:
                   2969:     retVal = printTextBox(prompt, COLS/3, ROWS/3, COLS/3, &white, &interfaceBoxColor, rbuf, buttons, 2);
                   2970:     overlayDisplayBuffer(rbuf, NULL);
                   2971:
                   2972:     if (retVal == -1 || retVal == 1) { // If they canceled or pressed no.
                   2973:         return false;
                   2974:     } else {
                   2975:         return true;
                   2976:     }
                   2977:
                   2978:     confirmMessages();
                   2979:     return retVal;
                   2980: }
                   2981:
                   2982: void clearMonsterFlashes() {
                   2983:
                   2984: }
                   2985:
                   2986: void displayMonsterFlashes(boolean flashingEnabled) {
                   2987:     creature *monst;
                   2988:     short x[100], y[100], strength[100], count = 0;
                   2989:     color *flashColor[100];
                   2990:     short oldRNG;
                   2991:
                   2992:     rogue.creaturesWillFlashThisTurn = false;
                   2993:
                   2994:     if (rogue.autoPlayingLevel || rogue.blockCombatText) {
                   2995:         return;
                   2996:     }
                   2997:
                   2998:     oldRNG = rogue.RNG;
                   2999:     rogue.RNG = RNG_COSMETIC;
                   3000:     //assureCosmeticRNG;
                   3001:
                   3002:     CYCLE_MONSTERS_AND_PLAYERS(monst) {
                   3003:         if (monst->bookkeepingFlags & MB_WILL_FLASH) {
                   3004:             monst->bookkeepingFlags &= ~MB_WILL_FLASH;
                   3005:             if (flashingEnabled && canSeeMonster(monst) && count < 100) {
                   3006:                 x[count] = monst->xLoc;
                   3007:                 y[count] = monst->yLoc;
                   3008:                 strength[count] = monst->flashStrength;
                   3009:                 flashColor[count] = &(monst->flashColor);
                   3010:                 count++;
                   3011:             }
                   3012:         }
                   3013:     }
                   3014:     flashForeground(x, y, flashColor, strength, count, 20);
                   3015:     restoreRNG;
                   3016: }
                   3017:
                   3018: void dequeueEvent() {
                   3019:     rogueEvent returnEvent;
                   3020:     nextBrogueEvent(&returnEvent, false, false, true);
                   3021: }
                   3022:
                   3023: void displayMessageArchive() {
                   3024:     short i, j, k, reverse, fadePercent, totalMessageCount, currentMessageCount;
                   3025:     boolean fastForward;
                   3026:     cellDisplayBuffer dbuf[COLS][ROWS], rbuf[COLS][ROWS];
                   3027:
                   3028:     // Count the number of lines in the archive.
                   3029:     for (totalMessageCount=0;
                   3030:          totalMessageCount < MESSAGE_ARCHIVE_LINES && messageArchive[totalMessageCount][0];
                   3031:          totalMessageCount++);
                   3032:
                   3033:     if (totalMessageCount > MESSAGE_LINES) {
                   3034:
                   3035:         copyDisplayBuffer(rbuf, displayBuffer);
                   3036:
                   3037:         // Pull-down/pull-up animation:
                   3038:         for (reverse = 0; reverse <= 1; reverse++) {
                   3039:             fastForward = false;
                   3040:             for (currentMessageCount = (reverse ? totalMessageCount : MESSAGE_LINES);
                   3041:                  (reverse ? currentMessageCount >= MESSAGE_LINES : currentMessageCount <= totalMessageCount);
                   3042:                  currentMessageCount += (reverse ? -1 : 1)) {
                   3043:
                   3044:                 clearDisplayBuffer(dbuf);
                   3045:
                   3046:                 // Print the message archive text to the dbuf.
                   3047:                 for (j=0; j < currentMessageCount && j < ROWS; j++) {
                   3048:                     printString(messageArchive[(messageArchivePosition - currentMessageCount + MESSAGE_ARCHIVE_LINES + j) % MESSAGE_ARCHIVE_LINES],
                   3049:                                 mapToWindowX(0), j, &white, &black, dbuf);
                   3050:                 }
                   3051:
                   3052:                 // Set the dbuf opacity, and do a fade from bottom to top to make it clear that the bottom messages are the most recent.
                   3053:                 for (j=0; j < currentMessageCount && j<ROWS; j++) {
                   3054:                     fadePercent = 50 * (j + totalMessageCount - currentMessageCount) / totalMessageCount + 50;
                   3055:                     for (i=0; i<DCOLS; i++) {
                   3056:                         dbuf[mapToWindowX(i)][j].opacity = INTERFACE_OPACITY;
                   3057:                         if (dbuf[mapToWindowX(i)][j].character != ' ') {
                   3058:                             for (k=0; k<3; k++) {
                   3059:                                 dbuf[mapToWindowX(i)][j].foreColorComponents[k] = dbuf[mapToWindowX(i)][j].foreColorComponents[k] * fadePercent / 100;
                   3060:                             }
                   3061:                         }
                   3062:                     }
                   3063:                 }
                   3064:
                   3065:                 // Display.
                   3066:                 overlayDisplayBuffer(rbuf, 0);
                   3067:                 overlayDisplayBuffer(dbuf, 0);
                   3068:
                   3069:                 if (!fastForward && pauseBrogue(reverse ? 1 : 2)) {
                   3070:                     fastForward = true;
                   3071:                     dequeueEvent();
                   3072:                     currentMessageCount = (reverse ? MESSAGE_LINES + 1 : totalMessageCount - 1); // skip to the end
                   3073:                 }
                   3074:             }
                   3075:
                   3076:             if (!reverse) {
                   3077:                 displayMoreSign();
                   3078:             }
                   3079:         }
                   3080:         overlayDisplayBuffer(rbuf, 0);
                   3081:         updateFlavorText();
                   3082:         confirmMessages();
                   3083:         updateMessageDisplay();
                   3084:     }
                   3085: }
                   3086:
                   3087: // Clears the message area and prints the given message in the area.
                   3088: // It will disappear when messages are refreshed and will not be archived.
                   3089: // This is primarily used to display prompts.
                   3090: void temporaryMessage(char *msg, boolean requireAcknowledgment) {
                   3091:     char message[COLS];
                   3092:     short i, j;
                   3093:
                   3094:     assureCosmeticRNG;
                   3095:     strcpy(message, msg);
                   3096:
                   3097:     for (i=0; message[i] == COLOR_ESCAPE; i += 4) {
                   3098:         upperCase(&(message[i]));
                   3099:     }
                   3100:
                   3101:     refreshSideBar(-1, -1, false);
                   3102:
                   3103:     for (i=0; i<MESSAGE_LINES; i++) {
                   3104:         for (j=0; j<DCOLS; j++) {
                   3105:             plotCharWithColor(' ', mapToWindowX(j), i, &black, &black);
                   3106:         }
                   3107:     }
                   3108:     printString(message, mapToWindowX(0), mapToWindowY(-1), &white, &black, 0);
                   3109:     if (requireAcknowledgment) {
                   3110:         waitForAcknowledgment();
                   3111:         updateMessageDisplay();
                   3112:     }
                   3113:     restoreRNG;
                   3114: }
                   3115:
                   3116: void messageWithColor(char *msg, color *theColor, boolean requireAcknowledgment) {
                   3117:     char buf[COLS*2] = "";
                   3118:     short i;
                   3119:
                   3120:     i=0;
                   3121:     i = encodeMessageColor(buf, i, theColor);
                   3122:     strcpy(&(buf[i]), msg);
                   3123:     message(buf, requireAcknowledgment);
                   3124: }
                   3125:
                   3126: void flavorMessage(char *msg) {
                   3127:     short i;
                   3128:     char text[COLS*20];
                   3129:
                   3130:     for (i=0; i < COLS*2 && msg[i] != '\0'; i++) {
                   3131:         text[i] = msg[i];
                   3132:     }
                   3133:     text[i] = '\0';
                   3134:
                   3135:     for(i=0; text[i] == COLOR_ESCAPE; i+=4);
                   3136:     upperCase(&(text[i]));
                   3137:
                   3138:     printString(text, mapToWindowX(0), ROWS - 2, &flavorTextColor, &black, 0);
                   3139:     for (i = strLenWithoutEscapes(text); i < DCOLS; i++) {
                   3140:         plotCharWithColor(' ', mapToWindowX(i), ROWS - 2, &black, &black);
                   3141:     }
                   3142: }
                   3143:
                   3144: void messageWithoutCaps(char *msg, boolean requireAcknowledgment) {
                   3145:     short i;
                   3146:     if (!msg[0]) {
                   3147:         return;
                   3148:     }
                   3149:
                   3150:     // need to confirm the oldest message? (Disabled!)
                   3151:     /*if (!messageConfirmed[MESSAGE_LINES - 1]) {
                   3152:         //refreshSideBar(-1, -1, false);
                   3153:         displayMoreSign();
                   3154:         for (i=0; i<MESSAGE_LINES; i++) {
                   3155:             messageConfirmed[i] = true;
                   3156:         }
                   3157:     }*/
                   3158:
                   3159:     for (i = MESSAGE_LINES - 1; i >= 1; i--) {
                   3160:         messageConfirmed[i] = messageConfirmed[i-1];
                   3161:         strcpy(displayedMessage[i], displayedMessage[i-1]);
                   3162:     }
                   3163:     messageConfirmed[0] = false;
                   3164:     strcpy(displayedMessage[0], msg);
                   3165:
                   3166:     // Add the message to the archive.
                   3167:     strcpy(messageArchive[messageArchivePosition], msg);
                   3168:     messageArchivePosition = (messageArchivePosition + 1) % MESSAGE_ARCHIVE_LINES;
                   3169:
                   3170:     // display the message:
                   3171:     updateMessageDisplay();
                   3172:
                   3173:     if (requireAcknowledgment || rogue.cautiousMode) {
                   3174:         displayMoreSign();
                   3175:         confirmMessages();
                   3176:         rogue.cautiousMode = false;
                   3177:     }
                   3178:
                   3179:     if (rogue.playbackMode) {
                   3180:         rogue.playbackDelayThisTurn += rogue.playbackDelayPerTurn * 5;
                   3181:     }
                   3182: }
                   3183:
                   3184:
                   3185: void message(const char *msg, boolean requireAcknowledgment) {
                   3186:     char text[COLS*20], *msgPtr;
                   3187:     short i, lines;
                   3188:
                   3189:     assureCosmeticRNG;
                   3190:
                   3191:     rogue.disturbed = true;
                   3192:     if (requireAcknowledgment) {
                   3193:         refreshSideBar(-1, -1, false);
                   3194:     }
                   3195:     displayCombatText();
                   3196:
                   3197:     lines = wrapText(text, msg, DCOLS);
                   3198:     msgPtr = &(text[0]);
                   3199:
                   3200:     // Implement the American quotation mark/period/comma ordering rule.
                   3201:     for (i=0; text[i] != '\0' && text[i+1] != '\0'; i++) {
                   3202:         if (text[i] == COLOR_ESCAPE) {
                   3203:             i += 4;
                   3204:         } else if (text[i] == '"'
                   3205:                    && (text[i+1] == '.' || text[i+1] == ',')) {
                   3206:
                   3207:             text[i] = text[i+1];
                   3208:             text[i+1] = '"';
                   3209:         }
                   3210:     }
                   3211:
                   3212:     for(i=0; text[i] == COLOR_ESCAPE; i+=4);
                   3213:     upperCase(&(text[i]));
                   3214:
                   3215:     if (lines > 1) {
                   3216:         for (i=0; text[i] != '\0'; i++) {
                   3217:             if (text[i] == '\n') {
                   3218:                 text[i] = '\0';
                   3219:
                   3220:                 messageWithoutCaps(msgPtr, false);
                   3221:                 msgPtr = &(text[i+1]);
                   3222:             }
                   3223:         }
                   3224:     }
                   3225:
                   3226:     messageWithoutCaps(msgPtr, requireAcknowledgment);
                   3227:     restoreRNG;
                   3228: }
                   3229:
                   3230: // Only used for the "you die..." message, to enable posthumous inventory viewing.
                   3231: void displayMoreSignWithoutWaitingForAcknowledgment() {
                   3232:     if (strLenWithoutEscapes(displayedMessage[0]) < DCOLS - 8 || messageConfirmed[0]) {
                   3233:         printString("--MORE--", COLS - 8, MESSAGE_LINES-1, &black, &white, 0);
                   3234:     } else {
                   3235:         printString("--MORE--", COLS - 8, MESSAGE_LINES, &black, &white, 0);
                   3236:     }
                   3237: }
                   3238:
                   3239: void displayMoreSign() {
                   3240:     short i;
                   3241:
                   3242:     if (rogue.autoPlayingLevel) {
                   3243:         return;
                   3244:     }
                   3245:
                   3246:     if (strLenWithoutEscapes(displayedMessage[0]) < DCOLS - 8 || messageConfirmed[0]) {
                   3247:         printString("--MORE--", COLS - 8, MESSAGE_LINES-1, &black, &white, 0);
                   3248:         waitForAcknowledgment();
                   3249:         printString("        ", COLS - 8, MESSAGE_LINES-1, &black, &black, 0);
                   3250:     } else {
                   3251:         printString("--MORE--", COLS - 8, MESSAGE_LINES, &black, &white, 0);
                   3252:         waitForAcknowledgment();
                   3253:         for (i=1; i<=8; i++) {
                   3254:             refreshDungeonCell(DCOLS - i, 0);
                   3255:         }
                   3256:     }
                   3257: }
                   3258:
                   3259: // Inserts a four-character color escape sequence into a string at the insertion point.
                   3260: // Does NOT check string lengths, so it could theoretically write over the null terminator.
                   3261: // Returns the new insertion point.
                   3262: short encodeMessageColor(char *msg, short i, const color *theColor) {
                   3263:     boolean needTerminator = false;
                   3264:     color col = *theColor;
                   3265:
                   3266:     assureCosmeticRNG;
                   3267:
                   3268:     bakeColor(&col);
                   3269:
                   3270:     col.red     = clamp(col.red, 0, 100);
                   3271:     col.green   = clamp(col.green, 0, 100);
                   3272:     col.blue    = clamp(col.blue, 0, 100);
                   3273:
                   3274:     needTerminator = !msg[i] || !msg[i + 1] || !msg[i + 2] || !msg[i + 3];
                   3275:
                   3276:     msg[i++] = COLOR_ESCAPE;
                   3277:     msg[i++] = (char) (COLOR_VALUE_INTERCEPT + col.red);
                   3278:     msg[i++] = (char) (COLOR_VALUE_INTERCEPT + col.green);
                   3279:     msg[i++] = (char) (COLOR_VALUE_INTERCEPT + col.blue);
                   3280:
                   3281:     if (needTerminator) {
                   3282:         msg[i] = '\0';
                   3283:     }
                   3284:
                   3285:     restoreRNG;
                   3286:
                   3287:     return i;
                   3288: }
                   3289:
                   3290: // Call this when the i'th character of msg is COLOR_ESCAPE.
                   3291: // It will return the encoded color, and will advance i past the color escape sequence.
                   3292: short decodeMessageColor(const char *msg, short i, color *returnColor) {
                   3293:
                   3294:     if (msg[i] != COLOR_ESCAPE) {
                   3295:         printf("\nAsked to decode a color escape that didn't exist!");
                   3296:         *returnColor = white;
                   3297:     } else {
                   3298:         i++;
                   3299:         *returnColor = black;
                   3300:         returnColor->red    = (short) (msg[i++] - COLOR_VALUE_INTERCEPT);
                   3301:         returnColor->green  = (short) (msg[i++] - COLOR_VALUE_INTERCEPT);
                   3302:         returnColor->blue   = (short) (msg[i++] - COLOR_VALUE_INTERCEPT);
                   3303:
                   3304:         returnColor->red    = clamp(returnColor->red, 0, 100);
                   3305:         returnColor->green  = clamp(returnColor->green, 0, 100);
                   3306:         returnColor->blue   = clamp(returnColor->blue, 0, 100);
                   3307:     }
                   3308:     return i;
                   3309: }
                   3310:
                   3311: // Returns a color for combat text based on the identity of the victim.
                   3312: color *messageColorFromVictim(creature *monst) {
                   3313:     if (monst == &player) {
                   3314:         return &badMessageColor;
                   3315:     } else if (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience) {
                   3316:         return &white;
                   3317:     } else if (monst->creatureState == MONSTER_ALLY) {
                   3318:         return &badMessageColor;
                   3319:     } else if (monstersAreEnemies(&player, monst)) {
                   3320:         return &goodMessageColor;
                   3321:     } else {
                   3322:         return &white;
                   3323:     }
                   3324: }
                   3325:
                   3326: void updateMessageDisplay() {
                   3327:     short i, j, m;
                   3328:     color messageColor;
                   3329:
                   3330:     for (i=0; i<MESSAGE_LINES; i++) {
                   3331:         messageColor = white;
                   3332:
                   3333:         if (messageConfirmed[i]) {
                   3334:             applyColorAverage(&messageColor, &black, 50);
                   3335:             applyColorAverage(&messageColor, &black, 75 * i / MESSAGE_LINES);
                   3336:         }
                   3337:
                   3338:         for (j = m = 0; displayedMessage[i][m] && j < DCOLS; j++, m++) {
                   3339:
                   3340:             while (displayedMessage[i][m] == COLOR_ESCAPE) {
                   3341:                 m = decodeMessageColor(displayedMessage[i], m, &messageColor); // pulls the message color out and advances m
                   3342:                 if (messageConfirmed[i]) {
                   3343:                     applyColorAverage(&messageColor, &black, 50);
                   3344:                     applyColorAverage(&messageColor, &black, 75 * i / MESSAGE_LINES);
                   3345:                 }
                   3346:             }
                   3347:
                   3348:             plotCharWithColor(displayedMessage[i][m], mapToWindowX(j), MESSAGE_LINES - i - 1,
                   3349:                               &messageColor,
                   3350:                               &black);
                   3351:         }
                   3352:         for (; j < DCOLS; j++) {
                   3353:             plotCharWithColor(' ', mapToWindowX(j), MESSAGE_LINES - i - 1, &black, &black);
                   3354:         }
                   3355:     }
                   3356: }
                   3357:
                   3358: // Does NOT clear the message archive.
                   3359: void deleteMessages() {
                   3360:     short i;
                   3361:     for (i=0; i<MESSAGE_LINES; i++) {
                   3362:         displayedMessage[i][0] = '\0';
                   3363:     }
                   3364:     confirmMessages();
                   3365: }
                   3366:
                   3367: void confirmMessages() {
                   3368:     short i;
                   3369:     for (i=0; i<MESSAGE_LINES; i++) {
                   3370:         messageConfirmed[i] = true;
                   3371:     }
                   3372:     updateMessageDisplay();
                   3373: }
                   3374:
                   3375: void stripShiftFromMovementKeystroke(signed long *keystroke) {
                   3376:     const unsigned short newKey = *keystroke - ('A' - 'a');
                   3377:     if (newKey == LEFT_KEY
                   3378:         || newKey == RIGHT_KEY
                   3379:         || newKey == DOWN_KEY
                   3380:         || newKey == UP_KEY
                   3381:         || newKey == UPLEFT_KEY
                   3382:         || newKey == UPRIGHT_KEY
                   3383:         || newKey == DOWNLEFT_KEY
                   3384:         || newKey == DOWNRIGHT_KEY) {
                   3385:         *keystroke -= 'A' - 'a';
                   3386:     }
                   3387: }
                   3388:
                   3389: void upperCase(char *theChar) {
                   3390:     if (*theChar >= 'a' && *theChar <= 'z') {
                   3391:         (*theChar) += ('A' - 'a');
                   3392:     }
                   3393: }
                   3394:
                   3395: enum entityDisplayTypes {
                   3396:     EDT_NOTHING = 0,
                   3397:     EDT_CREATURE,
                   3398:     EDT_ITEM,
                   3399:     EDT_TERRAIN,
                   3400: };
                   3401:
                   3402: // Refreshes the sidebar.
                   3403: // Progresses from the closest visible monster to the farthest.
                   3404: // If a monster, item or terrain is focused, then display the sidebar with that monster/item highlighted,
                   3405: // in the order it would normally appear. If it would normally not fit on the sidebar at all,
                   3406: // then list it first.
                   3407: // Also update rogue.sidebarLocationList[ROWS][2] list of locations so that each row of
                   3408: // the screen is mapped to the corresponding entity, if any.
                   3409: // FocusedEntityMustGoFirst should usually be false when called externally. This is because
                   3410: // we won't know if it will fit on the screen in normal order until we try.
                   3411: // So if we try and fail, this function will call itself again, but with this set to true.
                   3412: void refreshSideBar(short focusX, short focusY, boolean focusedEntityMustGoFirst) {
                   3413:     short printY, oldPrintY, shortestDistance, i, j, k, px, py, x = 0, y = 0, displayEntityCount, indirectVision;
                   3414:     creature *monst = NULL, *closestMonst = NULL;
                   3415:     item *theItem, *closestItem = NULL;
                   3416:     char buf[COLS];
                   3417:     void *entityList[ROWS] = {0}, *focusEntity = NULL;
                   3418:     enum entityDisplayTypes entityType[ROWS] = {0}, focusEntityType = EDT_NOTHING;
                   3419:     short terrainLocationMap[ROWS][2];
                   3420:     boolean gotFocusedEntityOnScreen = (focusX >= 0 ? false : true);
                   3421:     char addedEntity[DCOLS][DROWS];
                   3422:     short oldRNG;
                   3423:
                   3424:     if (rogue.gameHasEnded || rogue.playbackFastForward) {
                   3425:         return;
                   3426:     }
                   3427:
                   3428:     oldRNG = rogue.RNG;
                   3429:     rogue.RNG = RNG_COSMETIC;
                   3430:     //assureCosmeticRNG;
                   3431:
                   3432:     if (focusX < 0) {
                   3433:         focusedEntityMustGoFirst = false; // just in case!
                   3434:     } else {
                   3435:         if (pmap[focusX][focusY].flags & (HAS_MONSTER | HAS_PLAYER)) {
                   3436:             monst = monsterAtLoc(focusX, focusY);
                   3437:             if (canSeeMonster(monst) || rogue.playbackOmniscience) {
                   3438:                 focusEntity = monst;
                   3439:                 focusEntityType = EDT_CREATURE;
                   3440:             }
                   3441:         }
                   3442:         if (!focusEntity && (pmap[focusX][focusY].flags & HAS_ITEM)) {
                   3443:             theItem = itemAtLoc(focusX, focusY);
                   3444:             if (playerCanSeeOrSense(focusX, focusY)) {
                   3445:                 focusEntity = theItem;
                   3446:                 focusEntityType = EDT_ITEM;
                   3447:             }
                   3448:         }
                   3449:         if (!focusEntity
                   3450:             && cellHasTMFlag(focusX, focusY, TM_LIST_IN_SIDEBAR)
                   3451:             && playerCanSeeOrSense(focusX, focusY)) {
                   3452:
                   3453:             focusEntity = tileCatalog[pmap[focusX][focusY].layers[layerWithTMFlag(focusX, focusY, TM_LIST_IN_SIDEBAR)]].description;
                   3454:             focusEntityType = EDT_TERRAIN;
                   3455:         }
                   3456:     }
                   3457:
                   3458:     printY = 0;
                   3459:
                   3460:     px = player.xLoc;
                   3461:     py = player.yLoc;
                   3462:
                   3463:     zeroOutGrid(addedEntity);
                   3464:
                   3465:     // Header information for playback mode.
                   3466:     if (rogue.playbackMode) {
                   3467:         printString("   -- PLAYBACK --   ", 0, printY++, &white, &black, 0);
                   3468:         if (rogue.howManyTurns > 0) {
                   3469:             sprintf(buf, "Turn %li/%li", rogue.playerTurnNumber, rogue.howManyTurns);
                   3470:             printProgressBar(0, printY++, buf, rogue.playerTurnNumber, rogue.howManyTurns, &darkPurple, false);
                   3471:         }
                   3472:         if (rogue.playbackOOS) {
                   3473:             printString("    [OUT OF SYNC]   ", 0, printY++, &badMessageColor, &black, 0);
                   3474:         } else if (rogue.playbackPaused) {
                   3475:             printString("      [PAUSED]      ", 0, printY++, &gray, &black, 0);
                   3476:         }
                   3477:         printString("                    ", 0, printY++, &white, &black, 0);
                   3478:     }
                   3479:
                   3480:     // Now list the monsters that we'll be displaying in the order of their proximity to player (listing the focused first if required).
                   3481:
                   3482:     // Initialization.
                   3483:     displayEntityCount = 0;
                   3484:     for (i=0; i<ROWS*2; i++) {
                   3485:         rogue.sidebarLocationList[i][0] = -1;
                   3486:         rogue.sidebarLocationList[i][1] = -1;
                   3487:     }
                   3488:
                   3489:     // Player always goes first.
                   3490:     entityList[displayEntityCount] = &player;
                   3491:     entityType[displayEntityCount] = EDT_CREATURE;
                   3492:     displayEntityCount++;
                   3493:     addedEntity[player.xLoc][player.yLoc] = true;
                   3494:
                   3495:     // Focused entity, if it must go first.
                   3496:     if (focusedEntityMustGoFirst && !addedEntity[focusX][focusY]) {
                   3497:         addedEntity[focusX][focusY] = true;
                   3498:         entityList[displayEntityCount] = focusEntity;
                   3499:         entityType[displayEntityCount] = focusEntityType;
                   3500:         terrainLocationMap[displayEntityCount][0] = focusX;
                   3501:         terrainLocationMap[displayEntityCount][1] = focusY;
                   3502:         displayEntityCount++;
                   3503:     }
                   3504:
                   3505:     for (indirectVision = 0; indirectVision < 2; indirectVision++) {
                   3506:         // Non-focused monsters.
                   3507:         do {
                   3508:             shortestDistance = 10000;
                   3509:             for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
                   3510:                 if ((canDirectlySeeMonster(monst) || (indirectVision && (canSeeMonster(monst) || rogue.playbackOmniscience)))
                   3511:                     && !addedEntity[monst->xLoc][monst->yLoc]
                   3512:                     && !(monst->info.flags & MONST_NOT_LISTED_IN_SIDEBAR)
                   3513:                     && (px - monst->xLoc) * (px - monst->xLoc) + (py - monst->yLoc) * (py - monst->yLoc) < shortestDistance) {
                   3514:
                   3515:                     shortestDistance = (px - monst->xLoc) * (px - monst->xLoc) + (py - monst->yLoc) * (py - monst->yLoc);
                   3516:                     closestMonst = monst;
                   3517:                 }
                   3518:             }
                   3519:             if (shortestDistance < 10000) {
                   3520:                 addedEntity[closestMonst->xLoc][closestMonst->yLoc] = true;
                   3521:                 entityList[displayEntityCount] = closestMonst;
                   3522:                 entityType[displayEntityCount] = EDT_CREATURE;
                   3523:                 displayEntityCount++;
                   3524:             }
                   3525:         } while (shortestDistance < 10000 && displayEntityCount * 2 < ROWS); // Because each entity takes at least 2 rows in the sidebar.
                   3526:
                   3527:         // Non-focused items.
                   3528:         do {
                   3529:             shortestDistance = 10000;
                   3530:             for (theItem = floorItems->nextItem; theItem != NULL; theItem = theItem->nextItem) {
                   3531:                 if ((playerCanDirectlySee(theItem->xLoc, theItem->yLoc) || (indirectVision && (playerCanSeeOrSense(theItem->xLoc, theItem->yLoc) || rogue.playbackOmniscience)))
                   3532:                     && !addedEntity[theItem->xLoc][theItem->yLoc]
                   3533:                     && (px - theItem->xLoc) * (px - theItem->xLoc) + (py - theItem->yLoc) * (py - theItem->yLoc) < shortestDistance) {
                   3534:
                   3535:                     shortestDistance = (px - theItem->xLoc) * (px - theItem->xLoc) + (py - theItem->yLoc) * (py - theItem->yLoc);
                   3536:                     closestItem = theItem;
                   3537:                 }
                   3538:             }
                   3539:             if (shortestDistance < 10000) {
                   3540:                 addedEntity[closestItem->xLoc][closestItem->yLoc] = true;
                   3541:                 entityList[displayEntityCount] = closestItem;
                   3542:                 entityType[displayEntityCount] = EDT_ITEM;
                   3543:                 displayEntityCount++;
                   3544:             }
                   3545:         } while (shortestDistance < 10000 && displayEntityCount * 2 < ROWS); // Because each entity takes at least 2 rows in the sidebar.
                   3546:
                   3547:         // Non-focused terrain.
                   3548:
                   3549:         // count up the number of candidate locations
                   3550:         for (k=0; k<max(DROWS, DCOLS); k++) {
                   3551:             for (i = px-k; i <= px+k; i++) {
                   3552:                 // we are scanning concentric squares. The first and last columns
                   3553:                 // need to be stepped through, but others can be jumped over.
                   3554:                 short step = (i == px-k || i == px+k) ? 1 : 2*k;
                   3555:                 for (j = py-k; j <= py+k; j += step) {
                   3556:                     if (displayEntityCount >= ROWS - 1) goto no_space_for_more_entities;
                   3557:                     if (coordinatesAreInMap(i, j)
                   3558:                         && !addedEntity[i][j]
                   3559:                         && cellHasTMFlag(i, j, TM_LIST_IN_SIDEBAR)
                   3560:                         && (playerCanDirectlySee(i, j) || (indirectVision && (playerCanSeeOrSense(i, j) || rogue.playbackOmniscience)))) {
                   3561:
                   3562:                         addedEntity[i][j] = true;
                   3563:                         entityList[displayEntityCount] = tileCatalog[pmap[i][j].layers[layerWithTMFlag(i, j, TM_LIST_IN_SIDEBAR)]].description;
                   3564:                         entityType[displayEntityCount] = EDT_TERRAIN;
                   3565:                         terrainLocationMap[displayEntityCount][0] = i;
                   3566:                         terrainLocationMap[displayEntityCount][1] = j;
                   3567:                         displayEntityCount++;
                   3568:                     }
                   3569:                 }
                   3570:             }
                   3571:         }
                   3572:         no_space_for_more_entities:;
                   3573:     }
                   3574:
                   3575:     // Entities are now listed. Start printing.
                   3576:
                   3577:     for (i=0; i<displayEntityCount && printY < ROWS - 1; i++) { // Bottom line is reserved for the depth.
                   3578:         oldPrintY = printY;
                   3579:         if (entityType[i] == EDT_CREATURE) {
                   3580:             x = ((creature *) entityList[i])->xLoc;
                   3581:             y = ((creature *) entityList[i])->yLoc;
                   3582:             printY = printMonsterInfo((creature *) entityList[i],
                   3583:                                       printY,
                   3584:                                       (focusEntity && (x != focusX || y != focusY)),
                   3585:                                       (x == focusX && y == focusY));
                   3586:
                   3587:         } else if (entityType[i] == EDT_ITEM) {
                   3588:             x = ((item *) entityList[i])->xLoc;
                   3589:             y = ((item *) entityList[i])->yLoc;
                   3590:             printY = printItemInfo((item *) entityList[i],
                   3591:                                    printY,
                   3592:                                    (focusEntity && (x != focusX || y != focusY)),
                   3593:                                    (x == focusX && y == focusY));
                   3594:         } else if (entityType[i] == EDT_TERRAIN) {
                   3595:             x = terrainLocationMap[i][0];
                   3596:             y = terrainLocationMap[i][1];
                   3597:             printY = printTerrainInfo(x, y,
                   3598:                                       printY,
                   3599:                                       ((const char *) entityList[i]),
                   3600:                                       (focusEntity && (x != focusX || y != focusY)),
                   3601:                                       (x == focusX && y == focusY));
                   3602:         }
                   3603:         if (focusEntity && (x == focusX && y == focusY) && printY < ROWS) {
                   3604:             gotFocusedEntityOnScreen = true;
                   3605:         }
                   3606:         for (j=oldPrintY; j<printY; j++) {
                   3607:             rogue.sidebarLocationList[j][0] = x;
                   3608:             rogue.sidebarLocationList[j][1] = y;
                   3609:         }
                   3610:     }
                   3611:
                   3612:     if (gotFocusedEntityOnScreen) {
                   3613:         // Wrap things up.
                   3614:         for (i=printY; i< ROWS - 1; i++) {
                   3615:             printString("                    ", 0, i, &white, &black, 0);
                   3616:         }
                   3617:         sprintf(buf, "  -- Depth: %i --%s   ", rogue.depthLevel, (rogue.depthLevel < 10 ? " " : ""));
                   3618:         printString(buf, 0, ROWS - 1, &white, &black, 0);
                   3619:     } else if (!focusedEntityMustGoFirst) {
                   3620:         // Failed to get the focusMonst printed on the screen. Try again, this time with the focus first.
                   3621:         refreshSideBar(focusX, focusY, true);
                   3622:     }
                   3623:
                   3624:     restoreRNG;
                   3625: }
                   3626:
                   3627: void printString(const char *theString, short x, short y, color *foreColor, color *backColor, cellDisplayBuffer dbuf[COLS][ROWS]) {
                   3628:     color fColor;
                   3629:     short i;
                   3630:
                   3631:     fColor = *foreColor;
                   3632:
                   3633:     for (i=0; theString[i] != '\0' && x < COLS; i++, x++) {
                   3634:         while (theString[i] == COLOR_ESCAPE) {
                   3635:             i = decodeMessageColor(theString, i, &fColor);
                   3636:             if (!theString[i]) {
                   3637:                 return;
                   3638:             }
                   3639:         }
                   3640:
                   3641:         if (dbuf) {
                   3642:             plotCharToBuffer(theString[i], x, y, &fColor, backColor, dbuf);
                   3643:         } else {
                   3644:             plotCharWithColor(theString[i], x, y, &fColor, backColor);
                   3645:         }
                   3646:     }
                   3647: }
                   3648:
                   3649: // Inserts line breaks into really long words. Optionally adds a hyphen, but doesn't do anything
                   3650: // clever regarding hyphen placement. Plays nicely with color escapes.
                   3651: void breakUpLongWordsIn(char *sourceText, short width, boolean useHyphens) {
                   3652:     char buf[COLS * ROWS * 2] = "";
                   3653:     short i, m, nextChar, wordWidth;
                   3654:     //const short maxLength = useHyphens ? width - 1 : width;
                   3655:
                   3656:     // i iterates over characters in sourceText; m keeps track of the length of buf.
                   3657:     wordWidth = 0;
                   3658:     for (i=0, m=0; sourceText[i] != 0;) {
                   3659:         if (sourceText[i] == COLOR_ESCAPE) {
                   3660:             strncpy(&(buf[m]), &(sourceText[i]), 4);
                   3661:             i += 4;
                   3662:             m += 4;
                   3663:         } else if (sourceText[i] == ' ' || sourceText[i] == '\n') {
                   3664:             wordWidth = 0;
                   3665:             buf[m++] = sourceText[i++];
                   3666:         } else {
                   3667:             if (!useHyphens && wordWidth >= width) {
                   3668:                 buf[m++] = '\n';
                   3669:                 wordWidth = 0;
                   3670:             } else if (useHyphens && wordWidth >= width - 1) {
                   3671:                 nextChar = i+1;
                   3672:                 while (sourceText[nextChar] == COLOR_ESCAPE) {
                   3673:                     nextChar += 4;
                   3674:                 }
                   3675:                 if (sourceText[nextChar] && sourceText[nextChar] != ' ' && sourceText[nextChar] != '\n') {
                   3676:                     buf[m++] = '-';
                   3677:                     buf[m++] = '\n';
                   3678:                     wordWidth = 0;
                   3679:                 }
                   3680:             }
                   3681:             buf[m++] = sourceText[i++];
                   3682:             wordWidth++;
                   3683:         }
                   3684:     }
                   3685:     buf[m] = '\0';
                   3686:     strcpy(sourceText, buf);
                   3687: }
                   3688:
                   3689: // Returns the number of lines, including the newlines already in the text.
                   3690: // Puts the output in "to" only if we receive a "to" -- can make it null and just get a line count.
                   3691: short wrapText(char *to, const char *sourceText, short width) {
                   3692:     short i, w, textLength, lineCount;
                   3693:     char printString[COLS * ROWS * 2];
                   3694:     short spaceLeftOnLine, wordWidth;
                   3695:
                   3696:     strcpy(printString, sourceText); // a copy we can write on
                   3697:     breakUpLongWordsIn(printString, width, true); // break up any words that are wider than the width.
                   3698:
                   3699:     textLength = strlen(printString); // do NOT discount escape sequences
                   3700:     lineCount = 1;
                   3701:
                   3702:     // Now go through and replace spaces with newlines as needed.
                   3703:
                   3704:     // Fast foward until i points to the first character that is not a color escape.
                   3705:     for (i=0; printString[i] == COLOR_ESCAPE; i+= 4);
                   3706:     spaceLeftOnLine = width;
                   3707:
                   3708:     while (i < textLength) {
                   3709:         // wordWidth counts the word width of the next word without color escapes.
                   3710:         // w indicates the position of the space or newline or null terminator that terminates the word.
                   3711:         wordWidth = 0;
                   3712:         for (w = i + 1; w < textLength && printString[w] != ' ' && printString[w] != '\n';) {
                   3713:             if (printString[w] == COLOR_ESCAPE) {
                   3714:                 w += 4;
                   3715:             } else {
                   3716:                 w++;
                   3717:                 wordWidth++;
                   3718:             }
                   3719:         }
                   3720:
                   3721:         if (1 + wordWidth > spaceLeftOnLine || printString[i] == '\n') {
                   3722:             printString[i] = '\n';
                   3723:             lineCount++;
                   3724:             spaceLeftOnLine = width - wordWidth; // line width minus the width of the word we just wrapped
                   3725:             //printf("\n\n%s", printString);
                   3726:         } else {
                   3727:             spaceLeftOnLine -= 1 + wordWidth;
                   3728:         }
                   3729:         i = w; // Advance to the terminator that follows the word.
                   3730:     }
                   3731:     if (to) {
                   3732:         strcpy(to, printString);
                   3733:     }
                   3734:     return lineCount;
                   3735: }
                   3736:
                   3737: // returns the y-coordinate of the last line
                   3738: short printStringWithWrapping(char *theString, short x, short y, short width, color *foreColor,
                   3739:                              color*backColor, cellDisplayBuffer dbuf[COLS][ROWS]) {
                   3740:     color fColor;
                   3741:     char printString[COLS * ROWS * 2];
                   3742:     short i, px, py;
                   3743:
                   3744:     wrapText(printString, theString, width); // inserts newlines as necessary
                   3745:
                   3746:     // display the string
                   3747:     px = x; //px and py are the print insertion coordinates; x and y remain the top-left of the text box
                   3748:     py = y;
                   3749:     fColor = *foreColor;
                   3750:
                   3751:     for (i=0; printString[i] != '\0'; i++) {
                   3752:         if (printString[i] == '\n') {
                   3753:             px = x; // back to the leftmost column
                   3754:             if (py < ROWS - 1) { // don't advance below the bottom of the screen
                   3755:                 py++; // next line
                   3756:             } else {
                   3757:                 break; // If we've run out of room, stop.
                   3758:             }
                   3759:             continue;
                   3760:         } else if (printString[i] == COLOR_ESCAPE) {
                   3761:             i = decodeMessageColor(printString, i, &fColor) - 1;
                   3762:             continue;
                   3763:         }
                   3764:
                   3765:         if (dbuf) {
                   3766:             if (coordinatesAreInWindow(px, py)) {
                   3767:                 plotCharToBuffer(printString[i], px, py, &fColor, backColor, dbuf);
                   3768:             }
                   3769:         } else {
                   3770:             if (coordinatesAreInWindow(px, py)) {
                   3771:                 plotCharWithColor(printString[i], px, py, &fColor, backColor);
                   3772:             }
                   3773:         }
                   3774:
                   3775:         px++;
                   3776:     }
                   3777:     return py;
                   3778: }
                   3779:
                   3780: char nextKeyPress(boolean textInput) {
                   3781:     rogueEvent theEvent;
                   3782:     do {
                   3783:         nextBrogueEvent(&theEvent, textInput, false, false);
                   3784:     } while (theEvent.eventType != KEYSTROKE);
                   3785:     return theEvent.param1;
                   3786: }
                   3787:
                   3788: #define BROGUE_HELP_LINE_COUNT  33
                   3789:
                   3790: void printHelpScreen() {
                   3791:     short i, j;
                   3792:     cellDisplayBuffer dbuf[COLS][ROWS], rbuf[COLS][ROWS];
                   3793:     char helpText[BROGUE_HELP_LINE_COUNT][DCOLS*3] = {
                   3794:         "",
                   3795:         "",
                   3796:         "          -- Commands --",
                   3797:         "",
                   3798:         "         mouse  ****move cursor (including to examine monsters and terrain)",
                   3799:         "         click  ****travel",
                   3800:         " control-click  ****advance one space",
                   3801:         "      <return>  ****enable keyboard cursor control",
                   3802:         "   <space/esc>  ****disable keyboard cursor control",
                   3803:         "hjklyubn, arrow keys, or numpad  ****move or attack (control or shift to run)",
                   3804:         "",
                   3805:         " a/e/r/t/d/c/R  ****apply/equip/remove/throw/drop/call/relabel an item",
                   3806:         "             T  ****re-throw last item at last monster",
                   3807:         "i, right-click  ****view inventory",
                   3808:         "             D  ****list discovered items",
                   3809:         "",
                   3810:         "             z  ****rest once",
                   3811:         "             Z  ****rest for 100 turns or until something happens",
                   3812:         "             s  ****search for secrets (control-s: long search)",
                   3813:         "          <, >  ****travel to stairs",
                   3814:         "             x  ****auto-explore (control-x: fast forward)",
                   3815:         "             A  ****autopilot (control-A: fast forward)",
                   3816:         "             M  ****display old messages",
                   3817:         "             G  ****toggle graphical tiles (when available)",
                   3818:         "",
                   3819:         "             S  ****suspend game and quit",
                   3820:         "             Q  ****quit to title screen",
                   3821:         "",
                   3822:         "             \\  ****disable/enable color effects",
                   3823:         "             ]  ****display/hide stealth range",
                   3824:         "   <space/esc>  ****clear message or cancel command",
                   3825:         "",
                   3826:         "        -- press space or click to continue --"
                   3827:     };
                   3828:
                   3829:     // Replace the "****"s with color escapes.
                   3830:     for (i=0; i<BROGUE_HELP_LINE_COUNT; i++) {
                   3831:         for (j=0; helpText[i][j]; j++) {
                   3832:             if (helpText[i][j] == '*') {
                   3833:                 j = encodeMessageColor(helpText[i], j, &white);
                   3834:             }
                   3835:         }
                   3836:     }
                   3837:
                   3838:     clearDisplayBuffer(dbuf);
                   3839:
                   3840:     // Print the text to the dbuf.
                   3841:     for (i=0; i<BROGUE_HELP_LINE_COUNT && i < ROWS; i++) {
                   3842:         printString(helpText[i], mapToWindowX(1), i, &itemMessageColor, &black, dbuf);
                   3843:     }
                   3844:
                   3845:     // Set the dbuf opacity.
                   3846:     for (i=0; i<DCOLS; i++) {
                   3847:         for (j=0; j<ROWS; j++) {
                   3848:             //plotCharWithColor(' ', mapToWindowX(i), j, &black, &black);
                   3849:             dbuf[mapToWindowX(i)][j].opacity = INTERFACE_OPACITY;
                   3850:         }
                   3851:     }
                   3852:
                   3853:     // Display.
                   3854:     overlayDisplayBuffer(dbuf, rbuf);
                   3855:     waitForAcknowledgment();
                   3856:     overlayDisplayBuffer(rbuf, 0);
                   3857:     updateFlavorText();
                   3858:     updateMessageDisplay();
                   3859: }
                   3860:
                   3861: void printDiscoveries(short category, short count, unsigned short itemCharacter, short x, short y, cellDisplayBuffer dbuf[COLS][ROWS]) {
                   3862:     color *theColor, goodColor, badColor;
                   3863:     char buf[COLS], buf2[COLS];
                   3864:     short i, magic, totalFrequency;
                   3865:     itemTable *theTable = tableForItemCategory(category, NULL);
                   3866:
                   3867:     goodColor = goodMessageColor;
                   3868:     applyColorAverage(&goodColor, &black, 50);
                   3869:     badColor = badMessageColor;
                   3870:     applyColorAverage(&badColor, &black, 50);
                   3871:
                   3872:     totalFrequency = 0;
                   3873:     for (i = 0; i < count; i++) {
                   3874:         if (!theTable[i].identified) {
                   3875:             totalFrequency += theTable[i].frequency;
                   3876:         }
                   3877:     }
                   3878:
                   3879:     for (i = 0; i < count; i++) {
                   3880:         if (theTable[i].identified) {
                   3881:             theColor = &white;
                   3882:             plotCharToBuffer(itemCharacter, x, y + i, &itemColor, &black, dbuf);
                   3883:         } else {
                   3884:             theColor = &darkGray;
                   3885:             magic = magicCharDiscoverySuffix(category, i);
                   3886:             if (magic == 1) {
                   3887:                 plotCharToBuffer(G_GOOD_MAGIC, x, y + i, &goodColor, &black, dbuf);
                   3888:             } else if (magic == -1) {
                   3889:                 plotCharToBuffer(G_BAD_MAGIC, x, y + i, &badColor, &black, dbuf);
                   3890:             }
                   3891:         }
                   3892:         strcpy(buf, theTable[i].name);
                   3893:
                   3894:         if (!theTable[i].identified
                   3895:             && theTable[i].frequency > 0
                   3896:             && totalFrequency > 0) {
                   3897:
                   3898:             sprintf(buf2, " (%i%%)", theTable[i].frequency * 100 / totalFrequency);
                   3899:             strcat(buf, buf2);
                   3900:         }
                   3901:
                   3902:         upperCase(buf);
                   3903:         strcat(buf, " ");
                   3904:         printString(buf, x + 2, y + i, theColor, &black, dbuf);
                   3905:     }
                   3906: }
                   3907:
                   3908: void printDiscoveriesScreen() {
                   3909:     short i, j, y;
                   3910:     cellDisplayBuffer dbuf[COLS][ROWS], rbuf[COLS][ROWS];
                   3911:
                   3912:     clearDisplayBuffer(dbuf);
                   3913:
                   3914:     printString("-- SCROLLS --", mapToWindowX(2), y = mapToWindowY(1), &flavorTextColor, &black, dbuf);
                   3915:     printDiscoveries(SCROLL, NUMBER_SCROLL_KINDS, G_SCROLL, mapToWindowX(3), ++y, dbuf);
                   3916:
                   3917:     printString("-- RINGS --", mapToWindowX(2), y += NUMBER_SCROLL_KINDS + 1, &flavorTextColor, &black, dbuf);
                   3918:     printDiscoveries(RING, NUMBER_RING_KINDS, G_RING, mapToWindowX(3), ++y, dbuf);
                   3919:
                   3920:     printString("-- POTIONS --", mapToWindowX(29), y = mapToWindowY(1), &flavorTextColor, &black, dbuf);
                   3921:     printDiscoveries(POTION, NUMBER_POTION_KINDS, G_POTION, mapToWindowX(30), ++y, dbuf);
                   3922:
                   3923:     printString("-- STAFFS --", mapToWindowX(53), y = mapToWindowY(1), &flavorTextColor, &black, dbuf);
                   3924:     printDiscoveries(STAFF, NUMBER_STAFF_KINDS, G_STAFF, mapToWindowX(54), ++y, dbuf);
                   3925:
                   3926:     printString("-- WANDS --", mapToWindowX(53), y += NUMBER_STAFF_KINDS + 1, &flavorTextColor, &black, dbuf);
                   3927:     printDiscoveries(WAND, NUMBER_WAND_KINDS, G_WAND, mapToWindowX(54), ++y, dbuf);
                   3928:
                   3929:     printString(KEYBOARD_LABELS ? "-- press any key to continue --" : "-- touch anywhere to continue --",
                   3930:                 mapToWindowX(20), mapToWindowY(DROWS-2), &itemMessageColor, &black, dbuf);
                   3931:
                   3932:     for (i=0; i<COLS; i++) {
                   3933:         for (j=0; j<ROWS; j++) {
                   3934:             dbuf[i][j].opacity = (i < STAT_BAR_WIDTH ? 0 : INTERFACE_OPACITY);
                   3935:         }
                   3936:     }
                   3937:     overlayDisplayBuffer(dbuf, rbuf);
                   3938:
                   3939:     waitForKeystrokeOrMouseClick();
                   3940:
                   3941:     overlayDisplayBuffer(rbuf, NULL);
                   3942: }
                   3943:
                   3944: // Creates buttons for the discoveries screen in the buttons pointer; returns the number of buttons created.
                   3945: //short createDiscoveriesButtons(short category, short count, unsigned short itemCharacter, short x, short y, brogueButton *buttons) {
                   3946: //  color goodColor, badColor;
                   3947: //  char whiteColorEscape[20] = "", darkGrayColorEscape[20] = "", yellowColorEscape[20] = "", goodColorEscape[20] = "", badColorEscape[20] = "";
                   3948: //  short i, magic, symbolCount;
                   3949: //  itemTable *theTable = tableForItemCategory(category, NULL);
                   3950: //    char buf[COLS] = "";
                   3951: //
                   3952: //  goodColor = goodMessageColor;
                   3953: //  applyColorAverage(&goodColor, &black, 50);
                   3954: //  encodeMessageColor(goodColorEscape, 0, &goodColor);
                   3955: //  badColor = badMessageColor;
                   3956: //  applyColorAverage(&badColor, &black, 50);
                   3957: //  encodeMessageColor(badColorEscape, 0, &badColor);
                   3958: //  encodeMessageColor(whiteColorEscape, 0, &white);
                   3959: //    encodeMessageColor(darkGrayColorEscape, 0, &darkGray);
                   3960: //    encodeMessageColor(yellowColorEscape, 0, &itemColor);
                   3961: //
                   3962: //  for (i = 0; i < count; i++) {
                   3963: //        initializeButton(&(buttons[i]));
                   3964: //        buttons[i].flags = (B_DRAW | B_HOVER_ENABLED | B_ENABLED); // Clear other flags.
                   3965: //        buttons[i].buttonColor = black;
                   3966: //        buttons[i].opacity = 100;
                   3967: //      buttons[i].x = x;
                   3968: //      buttons[i].y = y + i;
                   3969: //      symbolCount = 0;
                   3970: //      if (theTable[i].identified) {
                   3971: //          strcat(buttons[i].text, yellowColorEscape);
                   3972: //          buttons[i].symbol[symbolCount++] = itemCharacter;
                   3973: //          strcat(buttons[i].text, "*");
                   3974: //            strcat(buttons[i].text, whiteColorEscape);
                   3975: //            strcat(buttons[i].text, " ");
                   3976: //        } else {
                   3977: //            strcat(buttons[i].text, "  ");
                   3978: //          strcat(buttons[i].text, darkGrayColorEscape);
                   3979: //      }
                   3980: //      strcpy(buf, theTable[i].name);
                   3981: //      upperCase(buf);
                   3982: //        strcat(buttons[i].text, buf);
                   3983: //        strcat(buttons[i].text, "  ");
                   3984: //        strcat(buttons[i].text, darkGrayColorEscape);
                   3985: //        magic = magicCharDiscoverySuffix(category, i);
                   3986: //        strcat(buttons[i].text, "(");
                   3987: //        if (magic != -1) {
                   3988: //            strcat(buttons[i].text, goodColorEscape);
                   3989: //            strcat(buttons[i].text, "*");
                   3990: //            buttons[i].symbol[symbolCount++] = G_GOOD_MAGIC;
                   3991: //      }
                   3992: //        if (magic != 1) {
                   3993: //            strcat(buttons[i].text, badColorEscape);
                   3994: //            strcat(buttons[i].text, "*");
                   3995: //            buttons[i].symbol[symbolCount++] = BAD_MAGIC_CHAR;
                   3996: //        }
                   3997: //        strcat(buttons[i].text, darkGrayColorEscape);
                   3998: //        strcat(buttons[i].text, ")");
                   3999: //  }
                   4000: //  return i;
                   4001: //}
                   4002: //
                   4003: //void printDiscoveriesScreen() {
                   4004: //  short i, j, y, buttonCount;
                   4005: //  cellDisplayBuffer dbuf[COLS][ROWS], rbuf[COLS][ROWS];
                   4006: //  brogueButton buttons[NUMBER_SCROLL_KINDS + NUMBER_WAND_KINDS + NUMBER_POTION_KINDS + NUMBER_STAFF_KINDS + NUMBER_RING_KINDS] = {{{0}}};
                   4007: //    rogueEvent theEvent;
                   4008: //
                   4009: //  clearDisplayBuffer(dbuf);
                   4010: //  buttonCount = 0;
                   4011: //
                   4012: //  printString("-- SCROLLS --", mapToWindowX(3), y = mapToWindowY(1), &flavorTextColor, &black, dbuf);
                   4013: //  buttonCount += createDiscoveriesButtons(SCROLL, NUMBER_SCROLL_KINDS, SCROLL_CHAR, mapToWindowX(3), ++y, &(buttons[buttonCount]));
                   4014: //
                   4015: //  printString("-- WANDS --", mapToWindowX(3), y += NUMBER_SCROLL_KINDS + 1, &flavorTextColor, &black, dbuf);
                   4016: //  buttonCount += createDiscoveriesButtons(WAND, NUMBER_WAND_KINDS, WAND_CHAR, mapToWindowX(3), ++y, &(buttons[buttonCount]));
                   4017: //
                   4018: //  printString("-- POTIONS --", mapToWindowX(29), y = mapToWindowY(1), &flavorTextColor, &black, dbuf);
                   4019: //  buttonCount += createDiscoveriesButtons(POTION, NUMBER_POTION_KINDS, POTION_CHAR, mapToWindowX(29), ++y, &(buttons[buttonCount]));
                   4020: //
                   4021: //  printString("-- STAFFS --", mapToWindowX(54), y = mapToWindowY(1), &flavorTextColor, &black, dbuf);
                   4022: //  buttonCount += createDiscoveriesButtons(STAFF, NUMBER_STAFF_KINDS, STAFF_CHAR, mapToWindowX(54), ++y, &(buttons[buttonCount]));
                   4023: //
                   4024: //  printString("-- RINGS --", mapToWindowX(54), y += NUMBER_STAFF_KINDS + 1, &flavorTextColor, &black, dbuf);
                   4025: //  buttonCount += createDiscoveriesButtons(RING, NUMBER_RING_KINDS, RING_CHAR, mapToWindowX(54), ++y, &(buttons[buttonCount]));
                   4026: //
                   4027: //  for (i=0; i<COLS; i++) {
                   4028: //      for (j=0; j<ROWS; j++) {
                   4029: //          dbuf[i][j].opacity = (i < STAT_BAR_WIDTH ? 0 : INTERFACE_OPACITY);
                   4030: //      }
                   4031: //  }
                   4032: //    overlayDisplayBuffer(dbuf, rbuf);
                   4033: //    y = buttonInputLoop(buttons,
                   4034: //                        buttonCount,
                   4035: //                        mapToWindowX(3),
                   4036: //                        mapToWindowY(1),
                   4037: //                        DCOLS - 6,
                   4038: //                        DROWS - 2,
                   4039: //                        &theEvent);
                   4040: //    overlayDisplayBuffer(rbuf, NULL);
                   4041: //}
                   4042:
                   4043: void printHighScores(boolean hiliteMostRecent) {
                   4044:     short i, hiliteLineNum, maxLength = 0, leftOffset;
                   4045:     rogueHighScoresEntry list[HIGH_SCORES_COUNT] = {{0}};
                   4046:     char buf[DCOLS*3];
                   4047:     color scoreColor;
                   4048:
                   4049:     hiliteLineNum = getHighScoresList(list);
                   4050:
                   4051:     if (!hiliteMostRecent) {
                   4052:         hiliteLineNum = -1;
                   4053:     }
                   4054:
                   4055:     blackOutScreen();
                   4056:
                   4057:     for (i = 0; i < HIGH_SCORES_COUNT && list[i].score > 0; i++) {
                   4058:         if (strLenWithoutEscapes(list[i].description) > maxLength) {
                   4059:             maxLength = strLenWithoutEscapes(list[i].description);
                   4060:         }
                   4061:     }
                   4062:
                   4063:     leftOffset = min(COLS - maxLength - 23 - 1, COLS/5);
                   4064:
                   4065:     scoreColor = black;
                   4066:     applyColorAverage(&scoreColor, &itemMessageColor, 100);
                   4067:     printString("-- HIGH SCORES --", (COLS - 17 + 1) / 2, 0, &scoreColor, &black, 0);
                   4068:
                   4069:     for (i = 0; i < HIGH_SCORES_COUNT && list[i].score > 0; i++) {
                   4070:         scoreColor = black;
                   4071:         if (i == hiliteLineNum) {
                   4072:             applyColorAverage(&scoreColor, &itemMessageColor, 100);
                   4073:         } else {
                   4074:             applyColorAverage(&scoreColor, &white, 100);
                   4075:             applyColorAverage(&scoreColor, &black, (i * 50 / 24));
                   4076:         }
                   4077:
                   4078:         // rank
                   4079:         sprintf(buf, "%s%i)", (i + 1 < 10 ? " " : ""), i + 1);
                   4080:         printString(buf, leftOffset, i + 2, &scoreColor, &black, 0);
                   4081:
                   4082:         // score
                   4083:         sprintf(buf, "%li", list[i].score);
                   4084:         printString(buf, leftOffset + 5, i + 2, &scoreColor, &black, 0);
                   4085:
                   4086:         // date
                   4087:         printString(list[i].date, leftOffset + 12, i + 2, &scoreColor, &black, 0);
                   4088:
                   4089:         // description
                   4090:         printString(list[i].description, leftOffset + 23, i + 2, &scoreColor, &black, 0);
                   4091:     }
                   4092:
                   4093:     scoreColor = black;
                   4094:     applyColorAverage(&scoreColor, &goodMessageColor, 100);
                   4095:
                   4096:     printString(KEYBOARD_LABELS ? "Press space to continue." : "Touch anywhere to continue.",
                   4097:                 (COLS - strLenWithoutEscapes(KEYBOARD_LABELS ? "Press space to continue." : "Touch anywhere to continue.")) / 2,
                   4098:                 ROWS - 1, &scoreColor, &black, 0);
                   4099:
                   4100:     commitDraws();
                   4101:     waitForAcknowledgment();
                   4102: }
                   4103:
                   4104: void displayGrid(short **map) {
                   4105:     short i, j, score, topRange, bottomRange;
                   4106:     color tempColor, foreColor, backColor;
                   4107:     enum displayGlyph dchar;
                   4108:
                   4109:     topRange = -30000;
                   4110:     bottomRange = 30000;
                   4111:     tempColor = black;
                   4112:
                   4113:     if (map == safetyMap && !rogue.updatedSafetyMapThisTurn) {
                   4114:         updateSafetyMap();
                   4115:     }
                   4116:
                   4117:     for (i=0; i<DCOLS; i++) {
                   4118:         for (j=0; j<DROWS; j++) {
                   4119:             if (cellHasTerrainFlag(i, j, T_WAYPOINT_BLOCKER) || (map[i][j] == map[0][0]) || (i == player.xLoc && j == player.yLoc)) {
                   4120:                 continue;
                   4121:             }
                   4122:             if (map[i][j] > topRange) {
                   4123:                 topRange = map[i][j];
                   4124:                 //if (topRange == 0) {
                   4125:                     //printf("\ntop is zero at %i,%i", i, j);
                   4126:                 //}
                   4127:             }
                   4128:             if (map[i][j] < bottomRange) {
                   4129:                 bottomRange = map[i][j];
                   4130:             }
                   4131:         }
                   4132:     }
                   4133:
                   4134:     for (i=0; i<DCOLS; i++) {
                   4135:         for (j=0; j<DROWS; j++) {
                   4136:             if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY | T_LAVA_INSTA_DEATH)
                   4137:                 || (map[i][j] == map[0][0])
                   4138:                 || (i == player.xLoc && j == player.yLoc)) {
                   4139:                 continue;
                   4140:             }
                   4141:             score = 300 - (map[i][j] - bottomRange) * 300 / max(1, (topRange - bottomRange));
                   4142:             tempColor.blue = max(min(score, 100), 0);
                   4143:             score -= 100;
                   4144:             tempColor.red = max(min(score, 100), 0);
                   4145:             score -= 100;
                   4146:             tempColor.green = max(min(score, 100), 0);
                   4147:             getCellAppearance(i, j, &dchar, &foreColor, &backColor);
                   4148:             plotCharWithColor(dchar, mapToWindowX(i), mapToWindowY(j), &foreColor, &tempColor);
                   4149:             //colorBlendCell(i, j, &tempColor, 100);//hiliteCell(i, j, &tempColor, 100, false);
                   4150:         }
                   4151:     }
                   4152:     //printf("\ntop: %i; bottom: %i", topRange, bottomRange);
                   4153: }
                   4154:
                   4155: void printSeed() {
                   4156:     char buf[COLS];
                   4157:     sprintf(buf, "Dungeon seed #%lu; turn #%lu; version %s", rogue.seed, rogue.playerTurnNumber, BROGUE_VERSION_STRING);
                   4158:     message(buf, false);
                   4159: }
                   4160:
                   4161: void printProgressBar(short x, short y, const char barLabel[COLS], long amtFilled, long amtMax, color *fillColor, boolean dim) {
                   4162:     char barText[] = "                    "; // string length is 20
                   4163:     short i, labelOffset;
                   4164:     color currentFillColor, textColor, progressBarColor, darkenedBarColor;
                   4165:
                   4166:     if (y >= ROWS - 1) { // don't write over the depth number
                   4167:         return;
                   4168:     }
                   4169:
                   4170:     if (amtFilled > amtMax) {
                   4171:         amtFilled = amtMax;
                   4172:     }
                   4173:
                   4174:     if (amtMax <= 0) {
                   4175:         amtMax = 1;
                   4176:     }
                   4177:
                   4178:     progressBarColor = *fillColor;
                   4179:     if (!(y % 2)) {
                   4180:         applyColorAverage(&progressBarColor, &black, 25);
                   4181:     }
                   4182:
                   4183:     if (dim) {
                   4184:         applyColorAverage(&progressBarColor, &black, 50);
                   4185:     }
                   4186:     darkenedBarColor = progressBarColor;
                   4187:     applyColorAverage(&darkenedBarColor, &black, 75);
                   4188:
                   4189:     labelOffset = (20 - strlen(barLabel)) / 2;
                   4190:     for (i = 0; i < (short) strlen(barLabel); i++) {
                   4191:         barText[i + labelOffset] = barLabel[i];
                   4192:     }
                   4193:
                   4194:     amtFilled = clamp(amtFilled, 0, amtMax);
                   4195:
                   4196:     if (amtMax < 10000000) {
                   4197:         amtFilled *= 100;
                   4198:         amtMax *= 100;
                   4199:     }
                   4200:
                   4201:     for (i=0; i<20; i++) {
                   4202:         currentFillColor = (i <= (20 * amtFilled / amtMax) ? progressBarColor : darkenedBarColor);
                   4203:         if (i == 20 * amtFilled / amtMax) {
                   4204:             applyColorAverage(&currentFillColor, &black, 75 - 75 * (amtFilled % (amtMax / 20)) / (amtMax / 20));
                   4205:         }
                   4206:         textColor = (dim ? gray : white);
                   4207:         applyColorAverage(&textColor, &currentFillColor, (dim ? 50 : 33));
                   4208:         plotCharWithColor(barText[i], x + i, y, &textColor, &currentFillColor);
                   4209:     }
                   4210: }
                   4211:
                   4212: // Very low-level. Changes displayBuffer directly.
                   4213: void highlightScreenCell(short x, short y, color *highlightColor, short strength) {
                   4214:     color tempColor;
                   4215:
                   4216:     tempColor = colorFromComponents(displayBuffer[x][y].foreColorComponents);
                   4217:     applyColorAugment(&tempColor, highlightColor, strength);
                   4218:     storeColorComponents(displayBuffer[x][y].foreColorComponents, &tempColor);
                   4219:
                   4220:     tempColor = colorFromComponents(displayBuffer[x][y].backColorComponents);
                   4221:     applyColorAugment(&tempColor, highlightColor, strength);
                   4222:     storeColorComponents(displayBuffer[x][y].backColorComponents, &tempColor);
                   4223:
                   4224:     displayBuffer[x][y].needsUpdate = true;
                   4225: }
                   4226:
                   4227: short estimatedArmorValue() {
                   4228:     short retVal;
                   4229:
                   4230:     retVal = ((armorTable[rogue.armor->kind].range.upperBound + armorTable[rogue.armor->kind].range.lowerBound) / 2) / 10;
                   4231:     retVal += strengthModifier(rogue.armor) / FP_FACTOR;
                   4232:     retVal -= player.status[STATUS_DONNING];
                   4233:
                   4234:     return max(0, retVal);
                   4235: }
                   4236:
                   4237: short creatureHealthChangePercent(creature *monst) {
                   4238:     if (monst->previousHealthPoints <= 0) {
                   4239:         return 0;
                   4240:     }
                   4241:     // ignore overhealing from tranference
                   4242:     return 100 * (monst->currentHP - min(monst->previousHealthPoints, monst->info.maxHP)) / monst->info.maxHP;
                   4243: }
                   4244:
                   4245: // returns the y-coordinate after the last line printed
                   4246: short printMonsterInfo(creature *monst, short y, boolean dim, boolean highlight) {
                   4247:     char buf[COLS * 2], buf2[COLS * 2], monstName[COLS], tempColorEscape[5], grayColorEscape[5];
                   4248:     enum displayGlyph monstChar;
                   4249:     color monstForeColor, monstBackColor, healthBarColor, tempColor;
                   4250:     short initialY, i, j, highlightStrength, displayedArmor, percent;
                   4251:     boolean inPath;
                   4252:     short oldRNG;
                   4253:
                   4254:     const char hallucinationStrings[16][COLS] = {
                   4255:         "     (Dancing)      ",
                   4256:         "     (Singing)      ",
                   4257:         "  (Pontificating)   ",
                   4258:         "     (Skipping)     ",
                   4259:         "     (Spinning)     ",
                   4260:         "      (Crying)      ",
                   4261:         "     (Laughing)     ",
                   4262:         "     (Humming)      ",
                   4263:         "    (Whistling)     ",
                   4264:         "    (Quivering)     ",
                   4265:         "    (Muttering)     ",
                   4266:         "    (Gibbering)     ",
                   4267:         "     (Giggling)     ",
                   4268:         "     (Moaning)      ",
                   4269:         "    (Shrieking)     ",
                   4270:         "   (Caterwauling)   ",
                   4271:     };
                   4272:     const char statusStrings[NUMBER_OF_STATUS_EFFECTS][COLS] = {
                   4273:         "Searching",
                   4274:         "Donning Armor",
                   4275:         "Weakened: -",
                   4276:         "Telepathic",
                   4277:         "Hallucinating",
                   4278:         "Levitating",
                   4279:         "Slowed",
                   4280:         "Hasted",
                   4281:         "Confused",
                   4282:         "Burning",
                   4283:         "Paralyzed",
                   4284:         "Poisoned",
                   4285:         "Stuck",
                   4286:         "Nauseous",
                   4287:         "Discordant",
                   4288:         "Immune to Fire",
                   4289:         "", // STATUS_EXPLOSION_IMMUNITY,
                   4290:         "", // STATUS_NUTRITION,
                   4291:         "", // STATUS_ENTERS_LEVEL_IN,
                   4292:         "Frightened",
                   4293:         "Entranced",
                   4294:         "Darkened",
                   4295:         "Lifespan",
                   4296:         "Shielded",
                   4297:         "Invisible",
                   4298:     };
                   4299:
                   4300:     if (y >= ROWS - 1) {
                   4301:         return ROWS - 1;
                   4302:     }
                   4303:
                   4304:     initialY = y;
                   4305:
                   4306:     oldRNG = rogue.RNG;
                   4307:     rogue.RNG = RNG_COSMETIC;
                   4308:     //assureCosmeticRNG;
                   4309:
                   4310:     if (y < ROWS - 1) {
                   4311:         printString("                    ", 0, y, &white, &black, 0); // Start with a blank line
                   4312:
                   4313:         // Unhighlight if it's highlighted as part of the path.
                   4314:         inPath = (pmap[monst->xLoc][monst->yLoc].flags & IS_IN_PATH) ? true : false;
                   4315:         pmap[monst->xLoc][monst->yLoc].flags &= ~IS_IN_PATH;
                   4316:         getCellAppearance(monst->xLoc, monst->yLoc, &monstChar, &monstForeColor, &monstBackColor);
                   4317:         applyColorBounds(&monstForeColor, 0, 100);
                   4318:         applyColorBounds(&monstBackColor, 0, 100);
                   4319:         if (inPath) {
                   4320:             pmap[monst->xLoc][monst->yLoc].flags |= IS_IN_PATH;
                   4321:         }
                   4322:
                   4323:         if (dim) {
                   4324:             applyColorAverage(&monstForeColor, &black, 50);
                   4325:             applyColorAverage(&monstBackColor, &black, 50);
                   4326:         } else if (highlight) {
                   4327:             applyColorAugment(&monstForeColor, &black, 100);
                   4328:             applyColorAugment(&monstBackColor, &black, 100);
                   4329:         }
                   4330:         plotCharWithColor(monstChar, 0, y, &monstForeColor, &monstBackColor);
                   4331:         if(monst->carriedItem) {
                   4332:             plotCharWithColor(monst->carriedItem->displayChar, 1, y, &itemColor, &black);
                   4333:         }
                   4334:         monsterName(monstName, monst, false);
                   4335:         upperCase(monstName);
                   4336:
                   4337:         if (monst == &player) {
                   4338:             if (player.status[STATUS_INVISIBLE]) {
                   4339:                 strcat(monstName, " xxxx");
                   4340:                 encodeMessageColor(monstName, strlen(monstName) - 4, &monstForeColor);
                   4341:                 strcat(monstName, "(invisible)");
                   4342:             } else if (playerInDarkness()) {
                   4343:                 strcat(monstName, " xxxx");
                   4344:                 //encodeMessageColor(monstName, strlen(monstName) - 4, &playerInDarknessColor);
                   4345:                 encodeMessageColor(monstName, strlen(monstName) - 4, &monstForeColor);
                   4346:                 strcat(monstName, "(dark)");
                   4347:             } else if (!(pmap[player.xLoc][player.yLoc].flags & IS_IN_SHADOW)) {
                   4348:                 strcat(monstName, " xxxx");
                   4349:                 //encodeMessageColor(monstName, strlen(monstName) - 4, &playerInLightColor);
                   4350:                 encodeMessageColor(monstName, strlen(monstName) - 4, &monstForeColor);
                   4351:                 strcat(monstName, "(lit)");
                   4352:             }
                   4353:         }
                   4354:
                   4355:         sprintf(buf, ": %s", monstName);
                   4356:         printString(buf, monst->carriedItem?2:1, y++, (dim ? &gray : &white), &black, 0);
                   4357:     }
                   4358:
                   4359:     // mutation, if any
                   4360:     if (y < ROWS - 1
                   4361:         && monst->mutationIndex >= 0
                   4362:         && (!player.status[STATUS_HALLUCINATING] || rogue.playbackOmniscience)) {
                   4363:
                   4364:         strcpy(buf, "                    ");
                   4365:         sprintf(buf2, "xxxx(%s)", mutationCatalog[monst->mutationIndex].title);
                   4366:         tempColor = *mutationCatalog[monst->mutationIndex].textColor;
                   4367:         if (dim) {
                   4368:             applyColorAverage(&tempColor, &black, 50);
                   4369:         }
                   4370:         encodeMessageColor(buf2, 0, &tempColor);
                   4371:         strcpy(buf + ((strLenWithoutEscapes(buf) - strLenWithoutEscapes(buf2)) / 2), buf2);
                   4372:         for (i = strlen(buf); i < 20 + 4; i++) {
                   4373:             buf[i] = ' ';
                   4374:         }
                   4375:         buf[24] = '\0';
                   4376:         printString(buf, 0, y++, (dim ? &gray : &white), &black, 0);
                   4377:     }
                   4378:
                   4379:     // hit points
                   4380:     if (monst->info.maxHP > 1
                   4381:         && !(monst->info.flags & MONST_INVULNERABLE)) {
                   4382:
                   4383:         if (monst == &player) {
                   4384:             healthBarColor = redBar;
                   4385:             applyColorAverage(&healthBarColor, &blueBar, min(100, 100 * player.currentHP / player.info.maxHP));
                   4386:         } else {
                   4387:             healthBarColor = blueBar;
                   4388:         }
                   4389:         percent = creatureHealthChangePercent(monst);
                   4390:         if (monst->currentHP <= 0) {
                   4391:             strcpy(buf, "Dead");
                   4392:         } else if (percent != 0) {
                   4393:             strcpy(buf, "       Health       ");
                   4394:             sprintf(buf2, "(%s%i%%)", percent > 0 ? "+" : "", percent);
                   4395:             strcpy(&(buf[20 - strlen(buf2)]), buf2);
                   4396:         } else {
                   4397:             strcpy(buf, "Health");
                   4398:         }
                   4399:         printProgressBar(0, y++, buf, monst->currentHP, monst->info.maxHP, &healthBarColor, dim);
                   4400:     }
                   4401:
                   4402:     if (monst == &player) {
                   4403:         // nutrition
                   4404:         if (player.status[STATUS_NUTRITION] > HUNGER_THRESHOLD) {
                   4405:             printProgressBar(0, y++, "Nutrition", player.status[STATUS_NUTRITION], STOMACH_SIZE, &blueBar, dim);
                   4406:         } else if (player.status[STATUS_NUTRITION] > WEAK_THRESHOLD) {
                   4407:             printProgressBar(0, y++, "Nutrition (Hungry)", player.status[STATUS_NUTRITION], STOMACH_SIZE, &blueBar, dim);
                   4408:         } else if (player.status[STATUS_NUTRITION] > FAINT_THRESHOLD) {
                   4409:             printProgressBar(0, y++, "Nutrition (Weak)", player.status[STATUS_NUTRITION], STOMACH_SIZE, &blueBar, dim);
                   4410:         } else if (player.status[STATUS_NUTRITION] > 0) {
                   4411:             printProgressBar(0, y++, "Nutrition (Faint)", player.status[STATUS_NUTRITION], STOMACH_SIZE, &blueBar, dim);
                   4412:         } else if (y < ROWS - 1) {
                   4413:             printString("      STARVING      ", 0, y++, &badMessageColor, &black, NULL);
                   4414:         }
                   4415:     }
                   4416:
                   4417:     if (!player.status[STATUS_HALLUCINATING] || rogue.playbackOmniscience || monst == &player) {
                   4418:
                   4419:         for (i=0; i<NUMBER_OF_STATUS_EFFECTS; i++) {
                   4420:             if (i == STATUS_WEAKENED && monst->status[i] > 0) {
                   4421:                 sprintf(buf, "%s%i", statusStrings[STATUS_WEAKENED], monst->weaknessAmount);
                   4422:                 printProgressBar(0, y++, buf, monst->status[i], monst->maxStatus[i], &redBar, dim);
                   4423:             } else if (i == STATUS_LEVITATING && monst->status[i] > 0) {
                   4424:                 printProgressBar(0, y++, (monst == &player ? "Levitating" : "Flying"), monst->status[i], monst->maxStatus[i], &redBar, dim);
                   4425:             } else if (i == STATUS_POISONED
                   4426:                        && monst->status[i] > 0) {
                   4427:
                   4428:
                   4429:                 if (monst->status[i] * monst->poisonAmount >= monst->currentHP) {
                   4430:                     strcpy(buf, "Fatal Poison");
                   4431:                 } else {
                   4432:                     strcpy(buf, "Poisoned");
                   4433:                 }
                   4434:                 if (monst->poisonAmount == 1) {
                   4435:                     printProgressBar(0, y++, buf, monst->status[i], monst->maxStatus[i], &redBar, dim);
                   4436:                 } else {
                   4437:                     sprintf(buf2, "%s (x%i)",
                   4438:                             buf,
                   4439:                             monst->poisonAmount);
                   4440:                     printProgressBar(0, y++, buf2, monst->status[i], monst->maxStatus[i], &redBar, dim);
                   4441:                 }
                   4442:             } else if (statusStrings[i][0] && monst->status[i] > 0) {
                   4443:                 printProgressBar(0, y++, statusStrings[i], monst->status[i], monst->maxStatus[i], &redBar, dim);
                   4444:             }
                   4445:         }
                   4446:         if (monst->targetCorpseLoc[0] == monst->xLoc && monst->targetCorpseLoc[1] == monst->yLoc) {
                   4447:             printProgressBar(0, y++,  monsterText[monst->info.monsterID].absorbStatus, monst->corpseAbsorptionCounter, 20, &redBar, dim);
                   4448:         }
                   4449:     }
                   4450:
                   4451:     if (monst != &player
                   4452:         && (!(monst->info.flags & MONST_INANIMATE)
                   4453:             || monst->creatureState == MONSTER_ALLY)) {
                   4454:
                   4455:             if (y < ROWS - 1) {
                   4456:                 if (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience && y < ROWS - 1) {
                   4457:                     printString(hallucinationStrings[rand_range(0, 9)], 0, y++, (dim ? &darkGray : &gray), &black, 0);
                   4458:                 } else if (monst->bookkeepingFlags & MB_CAPTIVE && y < ROWS - 1) {
                   4459:                     printString("     (Captive)      ", 0, y++, (dim ? &darkGray : &gray), &black, 0);
                   4460:                 } else if ((monst->info.flags & MONST_RESTRICTED_TO_LIQUID)
                   4461:                            && !cellHasTMFlag(monst->xLoc, monst->yLoc, TM_ALLOWS_SUBMERGING)) {
                   4462:                     printString("     (Helpless)     ", 0, y++, (dim ? &darkGray : &gray), &black, 0);
                   4463:                 } else if (monst->creatureState == MONSTER_SLEEPING && y < ROWS - 1) {
                   4464:                     printString("     (Sleeping)     ", 0, y++, (dim ? &darkGray : &gray), &black, 0);
                   4465:                 } else if ((monst->creatureState == MONSTER_ALLY) && y < ROWS - 1) {
                   4466:                     printString("       (Ally)       ", 0, y++, (dim ? &darkGray : &gray), &black, 0);
                   4467:                 } else if (monst->creatureState == MONSTER_FLEEING && y < ROWS - 1) {
                   4468:                     printString("     (Fleeing)      ", 0, y++, (dim ? &darkGray : &gray), &black, 0);
                   4469:                 } else if ((monst->creatureState == MONSTER_WANDERING) && y < ROWS - 1) {
                   4470:                     if ((monst->bookkeepingFlags & MB_FOLLOWER) && monst->leader && (monst->leader->info.flags & MONST_IMMOBILE)) {
                   4471:                         // follower of an immobile leader -- i.e. a totem
                   4472:                         printString("    (Worshiping)    ", 0, y++, (dim ? &darkGray : &gray), &black, 0);
                   4473:                     } else if ((monst->bookkeepingFlags & MB_FOLLOWER) && monst->leader && (monst->leader->bookkeepingFlags & MB_CAPTIVE)) {
                   4474:                         // actually a captor/torturer
                   4475:                         printString("     (Guarding)     ", 0, y++, (dim ? &darkGray : &gray), &black, 0);
                   4476:                     } else {
                   4477:                         printString("    (Wandering)     ", 0, y++, (dim ? &darkGray : &gray), &black, 0);
                   4478:                     }
                   4479:                 } else if (monst->ticksUntilTurn > max(0, player.ticksUntilTurn) + player.movementSpeed) {
                   4480:                     printString("   (Off balance)    ", 0, y++, (dim ? &darkGray : &gray), &black, 0);
                   4481:                 } else if ((monst->creatureState == MONSTER_TRACKING_SCENT) && y < ROWS - 1) {
                   4482:                     printString("     (Hunting)      ", 0, y++, (dim ? &darkGray : &gray), &black, 0);
                   4483:                 }
                   4484:             }
                   4485:         } else if (monst == &player) {
                   4486:             if (y < ROWS - 1) {
                   4487:                 tempColorEscape[0] = '\0';
                   4488:                 grayColorEscape[0] = '\0';
                   4489:                 if (player.status[STATUS_WEAKENED]) {
                   4490:                     tempColor = red;
                   4491:                     if (dim) {
                   4492:                         applyColorAverage(&tempColor, &black, 50);
                   4493:                     }
                   4494:                     encodeMessageColor(tempColorEscape, 0, &tempColor);
                   4495:                     encodeMessageColor(grayColorEscape, 0, (dim ? &darkGray : &gray));
                   4496:                 }
                   4497:
                   4498:                 displayedArmor = displayedArmorValue();
                   4499:
                   4500:                 if (!rogue.armor || rogue.armor->flags & ITEM_IDENTIFIED || rogue.playbackOmniscience) {
                   4501:
                   4502:                     sprintf(buf, "Str: %s%i%s  Armor: %i",
                   4503:                             tempColorEscape,
                   4504:                             rogue.strength - player.weaknessAmount,
                   4505:                             grayColorEscape,
                   4506:                             displayedArmor);
                   4507:                 } else {
                   4508:                     sprintf(buf, "Str: %s%i%s  Armor: %i?",
                   4509:                             tempColorEscape,
                   4510:                             rogue.strength - player.weaknessAmount,
                   4511:                             grayColorEscape,
                   4512:                             estimatedArmorValue());
                   4513:                 }
                   4514:                 //buf[20] = '\0';
                   4515:                 printString("                    ", 0, y, &white, &black, 0);
                   4516:                 printString(buf, (20 - strLenWithoutEscapes(buf)) / 2, y++, (dim ? &darkGray : &gray), &black, 0);
                   4517:             }
                   4518:             if (y < ROWS - 1 && rogue.gold) {
                   4519:                 sprintf(buf, "Gold: %li", rogue.gold);
                   4520:                 buf[20] = '\0';
                   4521:                 printString("                    ", 0, y, &white, &black, 0);
                   4522:                 printString(buf, (20 - strLenWithoutEscapes(buf)) / 2, y++, (dim ? &darkGray : &gray), &black, 0);
                   4523:             }
                   4524:             if (y < ROWS - 1) {
                   4525:                 tempColorEscape[0] = '\0';
                   4526:                 grayColorEscape[0] = '\0';
                   4527:                 tempColor = playerInShadowColor;
                   4528:                 percent = (rogue.aggroRange - 2) * 100 / 28;
                   4529:                 applyColorAverage(&tempColor, &black, percent);
                   4530:                 applyColorAugment(&tempColor, &playerInLightColor, percent);
                   4531:                 if (dim) {
                   4532:                     applyColorAverage(&tempColor, &black, 50);
                   4533:                 }
                   4534:                 encodeMessageColor(tempColorEscape, 0, &tempColor);
                   4535:                 encodeMessageColor(grayColorEscape, 0, (dim ? &darkGray : &gray));
                   4536:                 sprintf(buf, "%sStealth range: %i%s",
                   4537:                         tempColorEscape,
                   4538:                         rogue.aggroRange,
                   4539:                         grayColorEscape);
                   4540:                 printString("                    ", 0, y, &white, &black, 0);
                   4541:                 printString(buf, 1, y++, (dim ? &darkGray : &gray), &black, 0);
                   4542:             }
                   4543:         }
                   4544:
                   4545:     if (y < ROWS - 1) {
                   4546:         printString("                    ", 0, y++, (dim ? &darkGray : &gray), &black, 0);
                   4547:     }
                   4548:
                   4549:     if (highlight) {
                   4550:         for (i=0; i<20; i++) {
                   4551:             highlightStrength = smoothHiliteGradient(i, 20-1) / 10;
                   4552:             for (j=initialY; j < (y == ROWS - 1 ? y : min(y - 1, ROWS - 1)); j++) {
                   4553:                 highlightScreenCell(i, j, &white, highlightStrength);
                   4554:             }
                   4555:         }
                   4556:     }
                   4557:
                   4558:     restoreRNG;
                   4559:     return y;
                   4560: }
                   4561:
                   4562: void describeHallucinatedItem(char *buf) {
                   4563:     const unsigned short itemCats[10] = {FOOD, WEAPON, ARMOR, POTION, SCROLL, STAFF, WAND, RING, CHARM, GOLD};
                   4564:     short cat, kind, maxKinds;
                   4565:     assureCosmeticRNG;
                   4566:     cat = itemCats[rand_range(0, 9)];
                   4567:     tableForItemCategory(cat, &maxKinds);
                   4568:     kind = rand_range(0, maxKinds - 1);
                   4569:     describedItemBasedOnParameters(cat, kind, 1, 1, buf);
                   4570:     restoreRNG;
                   4571: }
                   4572:
                   4573: // Returns the y-coordinate after the last line printed.
                   4574: short printItemInfo(item *theItem, short y, boolean dim, boolean highlight) {
                   4575:     char name[COLS * 3];
                   4576:     enum displayGlyph itemChar;
                   4577:     color itemForeColor, itemBackColor;
                   4578:     short initialY, i, j, highlightStrength, lineCount;
                   4579:     boolean inPath;
                   4580:     short oldRNG;
                   4581:
                   4582:     if (y >= ROWS - 1) {
                   4583:         return ROWS - 1;
                   4584:     }
                   4585:
                   4586:     initialY = y;
                   4587:
                   4588:     oldRNG = rogue.RNG;
                   4589:     rogue.RNG = RNG_COSMETIC;
                   4590:     //assureCosmeticRNG;
                   4591:
                   4592:     if (y < ROWS - 1) {
                   4593:         // Unhighlight if it's highlighted as part of the path.
                   4594:         inPath = (pmap[theItem->xLoc][theItem->yLoc].flags & IS_IN_PATH) ? true : false;
                   4595:         pmap[theItem->xLoc][theItem->yLoc].flags &= ~IS_IN_PATH;
                   4596:         getCellAppearance(theItem->xLoc, theItem->yLoc, &itemChar, &itemForeColor, &itemBackColor);
                   4597:         applyColorBounds(&itemForeColor, 0, 100);
                   4598:         applyColorBounds(&itemBackColor, 0, 100);
                   4599:         if (inPath) {
                   4600:             pmap[theItem->xLoc][theItem->yLoc].flags |= IS_IN_PATH;
                   4601:         }
                   4602:         if (dim) {
                   4603:             applyColorAverage(&itemForeColor, &black, 50);
                   4604:             applyColorAverage(&itemBackColor, &black, 50);
                   4605:         }
                   4606:         plotCharWithColor(itemChar, 0, y, &itemForeColor, &itemBackColor);
                   4607:         printString(":                  ", 1, y, (dim ? &gray : &white), &black, 0);
                   4608:         if (rogue.playbackOmniscience || !player.status[STATUS_HALLUCINATING]) {
                   4609:             itemName(theItem, name, true, true, (dim ? &gray : &white));
                   4610:         } else {
                   4611:             describeHallucinatedItem(name);
                   4612:         }
                   4613:         upperCase(name);
                   4614:         lineCount = wrapText(NULL, name, 20-3);
                   4615:         for (i=initialY + 1; i <= initialY + lineCount + 1 && i < ROWS - 1; i++) {
                   4616:             printString("                    ", 0, i, (dim ? &darkGray : &gray), &black, 0);
                   4617:         }
                   4618:         y = printStringWithWrapping(name, 3, y, 20-3, (dim ? &gray : &white), &black, NULL); // Advances y.
                   4619:     }
                   4620:
                   4621:     if (highlight) {
                   4622:         for (i=0; i<20; i++) {
                   4623:             highlightStrength = smoothHiliteGradient(i, 20-1) / 10;
                   4624:             for (j=initialY; j <= y && j < ROWS - 1; j++) {
                   4625:                 highlightScreenCell(i, j, &white, highlightStrength);
                   4626:             }
                   4627:         }
                   4628:     }
                   4629:     y += 2;
                   4630:
                   4631:     restoreRNG;
                   4632:     return y;
                   4633: }
                   4634:
                   4635: // Returns the y-coordinate after the last line printed.
                   4636: short printTerrainInfo(short x, short y, short py, const char *description, boolean dim, boolean highlight) {
                   4637:     enum displayGlyph displayChar;
                   4638:     color foreColor, backColor;
                   4639:     short initialY, i, j, highlightStrength, lineCount;
                   4640:     boolean inPath;
                   4641:     char name[DCOLS*2];
                   4642:     color textColor;
                   4643:     short oldRNG;
                   4644:
                   4645:     if (py >= ROWS - 1) {
                   4646:         return ROWS - 1;
                   4647:     }
                   4648:
                   4649:     initialY = py;
                   4650:
                   4651:     oldRNG = rogue.RNG;
                   4652:     rogue.RNG = RNG_COSMETIC;
                   4653:     //assureCosmeticRNG;
                   4654:
                   4655:     if (py < ROWS - 1) {
                   4656:         // Unhighlight if it's highlighted as part of the path.
                   4657:         inPath = (pmap[x][y].flags & IS_IN_PATH) ? true : false;
                   4658:         pmap[x][y].flags &= ~IS_IN_PATH;
                   4659:         getCellAppearance(x, y, &displayChar, &foreColor, &backColor);
                   4660:         applyColorBounds(&foreColor, 0, 100);
                   4661:         applyColorBounds(&backColor, 0, 100);
                   4662:         if (inPath) {
                   4663:             pmap[x][y].flags |= IS_IN_PATH;
                   4664:         }
                   4665:         if (dim) {
                   4666:             applyColorAverage(&foreColor, &black, 50);
                   4667:             applyColorAverage(&backColor, &black, 50);
                   4668:         }
                   4669:         plotCharWithColor(displayChar, 0, py, &foreColor, &backColor);
                   4670:         printString(":                  ", 1, py, (dim ? &gray : &white), &black, 0);
                   4671:         strcpy(name, description);
                   4672:         upperCase(name);
                   4673:         lineCount = wrapText(NULL, name, 20-3);
                   4674:         for (i=initialY + 1; i <= initialY + lineCount + 1 && i < ROWS - 1; i++) {
                   4675:             printString("                    ", 0, i, (dim ? &darkGray : &gray), &black, 0);
                   4676:         }
                   4677:         textColor = flavorTextColor;
                   4678:         if (dim) {
                   4679:             applyColorScalar(&textColor, 50);
                   4680:         }
                   4681:         py = printStringWithWrapping(name, 3, py, 20-3, &textColor, &black, NULL); // Advances y.
                   4682:     }
                   4683:
                   4684:     if (highlight) {
                   4685:         for (i=0; i<20; i++) {
                   4686:             highlightStrength = smoothHiliteGradient(i, 20-1) / 10;
                   4687:             for (j=initialY; j <= py && j < ROWS - 1; j++) {
                   4688:                 highlightScreenCell(i, j, &white, highlightStrength);
                   4689:             }
                   4690:         }
                   4691:     }
                   4692:     py += 2;
                   4693:
                   4694:     restoreRNG;
                   4695:     return py;
                   4696: }
                   4697:
                   4698: void rectangularShading(short x, short y, short width, short height,
                   4699:                         const color *backColor, short opacity, cellDisplayBuffer dbuf[COLS][ROWS]) {
                   4700:     short i, j, dist;
                   4701:
                   4702:     assureCosmeticRNG;
                   4703:     for (i=0; i<COLS; i++) {
                   4704:         for (j=0; j<ROWS; j++) {
                   4705:             storeColorComponents(dbuf[i][j].backColorComponents, backColor);
                   4706:
                   4707:             if (i >= x && i < x + width
                   4708:                 && j >= y && j < y + height) {
                   4709:                 dbuf[i][j].opacity = min(100, opacity);
                   4710:             } else {
                   4711:                 dist = 0;
                   4712:                 dist += max(0, max(x - i, i - x - width + 1));
                   4713:                 dist += max(0, max(y - j, j - y - height + 1));
                   4714:                 dbuf[i][j].opacity = (int) ((opacity - 10) / max(1, dist));
                   4715:                 if (dbuf[i][j].opacity < 3) {
                   4716:                     dbuf[i][j].opacity = 0;
                   4717:                 }
                   4718:             }
                   4719:         }
                   4720:     }
                   4721:
                   4722: //  for (i=0; i<COLS; i++) {
                   4723: //      for (j=0; j<ROWS; j++) {
                   4724: //          if (i >= x && i < x + width && j >= y && j < y + height) {
                   4725: //              plotCharWithColor(' ', i, j, &white, &darkGreen);
                   4726: //          }
                   4727: //      }
                   4728: //  }
                   4729: //  displayMoreSign();
                   4730:
                   4731:     restoreRNG;
                   4732: }
                   4733:
                   4734: #define MIN_DEFAULT_INFO_PANEL_WIDTH    33
                   4735:
                   4736: // y and width are optional and will be automatically calculated if width <= 0.
                   4737: // Width will automatically be widened if the text would otherwise fall off the bottom of the
                   4738: // screen, and x will be adjusted to keep the widening box from spilling off the right of the
                   4739: // screen.
                   4740: // If buttons are provided, we'll extend the text box downward, re-position the buttons,
                   4741: // run a button input loop and return the result.
                   4742: // (Returns -1 for canceled; otherwise the button index number.)
                   4743: short printTextBox(char *textBuf, short x, short y, short width,
                   4744:                    color *foreColor, color *backColor,
                   4745:                    cellDisplayBuffer rbuf[COLS][ROWS],
                   4746:                    brogueButton *buttons, short buttonCount) {
                   4747:     cellDisplayBuffer dbuf[COLS][ROWS];
                   4748:
                   4749:     short x2, y2, lineCount, i, bx, by, padLines;
                   4750:
                   4751:     if (width <= 0) {
                   4752:         // autocalculate y and width
                   4753:         if (x < DCOLS / 2 - 1) {
                   4754:             x2 = mapToWindowX(x + 10);
                   4755:             width = (DCOLS - x) - 20;
                   4756:         } else {
                   4757:             x2 = mapToWindowX(10);
                   4758:             width = x - 20;
                   4759:         }
                   4760:         y2 = mapToWindowY(2);
                   4761:
                   4762:         if (width < MIN_DEFAULT_INFO_PANEL_WIDTH) {
                   4763:             x2 -= (MIN_DEFAULT_INFO_PANEL_WIDTH - width) / 2;
                   4764:             width = MIN_DEFAULT_INFO_PANEL_WIDTH;
                   4765:         }
                   4766:     } else {
                   4767:         y2 = y;
                   4768:         x2 = x;
                   4769:     }
                   4770:
                   4771:     while (((lineCount = wrapText(NULL, textBuf, width)) + y2) >= ROWS - 2 && width < COLS-5) {
                   4772:         // While the text doesn't fit and the width doesn't fill the screen, increase the width.
                   4773:         width++;
                   4774:         if (x2 + (width / 2) > COLS / 2) {
                   4775:             // If the horizontal midpoint of the text box is on the right half of the screen,
                   4776:             // move the box one space to the left.
                   4777:             x2--;
                   4778:         }
                   4779:     }
                   4780:
                   4781:     if (buttonCount > 0) {
                   4782:         padLines = 2;
                   4783:         bx = x2 + width;
                   4784:         by = y2 + lineCount + 1;
                   4785:         for (i=0; i<buttonCount; i++) {
                   4786:             if (buttons[i].flags & B_DRAW) {
                   4787:                 bx -= strLenWithoutEscapes(buttons[i].text) + 2;
                   4788:                 buttons[i].x = bx;
                   4789:                 buttons[i].y = by;
                   4790:                 if (bx < x2) {
                   4791:                     // Buttons can wrap to the next line (though are double-spaced).
                   4792:                     bx = x2 + width - (strLenWithoutEscapes(buttons[i].text) + 2);
                   4793:                     by += 2;
                   4794:                     padLines += 2;
                   4795:                     buttons[i].x = bx;
                   4796:                     buttons[i].y = by;
                   4797:                 }
                   4798:             }
                   4799:         }
                   4800:     } else {
                   4801:         padLines = 0;
                   4802:     }
                   4803:
                   4804:     clearDisplayBuffer(dbuf);
                   4805:     printStringWithWrapping(textBuf, x2, y2, width, foreColor, backColor, dbuf);
                   4806:     rectangularShading(x2, y2, width, lineCount + padLines, backColor, INTERFACE_OPACITY, dbuf);
                   4807:     overlayDisplayBuffer(dbuf, rbuf);
                   4808:
                   4809:     if (buttonCount > 0) {
                   4810:         return buttonInputLoop(buttons, buttonCount, x2, y2, width, by - y2 + 1 + padLines, NULL);
                   4811:     } else {
                   4812:         return -1;
                   4813:     }
                   4814: }
                   4815:
                   4816: void printMonsterDetails(creature *monst, cellDisplayBuffer rbuf[COLS][ROWS]) {
                   4817:     char textBuf[COLS * 100];
                   4818:
                   4819:     monsterDetails(textBuf, monst);
                   4820:     printTextBox(textBuf, monst->xLoc, 0, 0, &white, &black, rbuf, NULL, 0);
                   4821: }
                   4822:
                   4823: // Displays the item info box with the dark blue background.
                   4824: // If includeButtons is true, we include buttons for item actions.
                   4825: // Returns the key of an action to take, if any; otherwise -1.
                   4826: unsigned long printCarriedItemDetails(item *theItem,
                   4827:                                       short x, short y, short width,
                   4828:                                       boolean includeButtons,
                   4829:                                       cellDisplayBuffer rbuf[COLS][ROWS]) {
                   4830:     char textBuf[COLS * 100], goldColorEscape[5] = "", whiteColorEscape[5] = "";
                   4831:     brogueButton buttons[20] = {{{0}}};
                   4832:     short b;
                   4833:
                   4834:     itemDetails(textBuf, theItem);
                   4835:
                   4836:     for (b=0; b<20; b++) {
                   4837:         initializeButton(&(buttons[b]));
                   4838:         buttons[b].flags |= B_WIDE_CLICK_AREA;
                   4839:     }
                   4840:
                   4841:     b = 0;
                   4842:     if (includeButtons) {
                   4843:         encodeMessageColor(goldColorEscape, 0, KEYBOARD_LABELS ? &yellow : &white);
                   4844:         encodeMessageColor(whiteColorEscape, 0, &white);
                   4845:
                   4846:         if (theItem->category & (FOOD | SCROLL | POTION | WAND | STAFF | CHARM)) {
                   4847:             sprintf(buttons[b].text, "   %sa%spply   ", goldColorEscape, whiteColorEscape);
                   4848:             buttons[b].hotkey[0] = APPLY_KEY;
                   4849:             b++;
                   4850:         }
                   4851:         if (theItem->category & (ARMOR | WEAPON | RING)) {
                   4852:             if (theItem->flags & ITEM_EQUIPPED) {
                   4853:                 sprintf(buttons[b].text, "  %sr%semove   ", goldColorEscape, whiteColorEscape);
                   4854:                 buttons[b].hotkey[0] = UNEQUIP_KEY;
                   4855:                 b++;
                   4856:             } else {
                   4857:                 sprintf(buttons[b].text, "   %se%squip   ", goldColorEscape, whiteColorEscape);
                   4858:                 buttons[b].hotkey[0] = EQUIP_KEY;
                   4859:                 b++;
                   4860:             }
                   4861:         }
                   4862:         sprintf(buttons[b].text, "   %sd%srop    ", goldColorEscape, whiteColorEscape);
                   4863:         buttons[b].hotkey[0] = DROP_KEY;
                   4864:         b++;
                   4865:
                   4866:         sprintf(buttons[b].text, "   %st%shrow   ", goldColorEscape, whiteColorEscape);
                   4867:         buttons[b].hotkey[0] = THROW_KEY;
                   4868:         b++;
                   4869:
                   4870:         if (itemCanBeCalled(theItem)) {
                   4871:             sprintf(buttons[b].text, "   %sc%sall    ", goldColorEscape, whiteColorEscape);
                   4872:             buttons[b].hotkey[0] = CALL_KEY;
                   4873:             b++;
                   4874:         }
                   4875:
                   4876:         if (KEYBOARD_LABELS) {
                   4877:             sprintf(buttons[b].text, "  %sR%selabel  ", goldColorEscape, whiteColorEscape);
                   4878:             buttons[b].hotkey[0] = RELABEL_KEY;
                   4879:             b++;
                   4880:         }
                   4881:
                   4882:         // Add invisible previous and next buttons, so up and down arrows can page through items.
                   4883:         // Previous
                   4884:         buttons[b].flags = B_ENABLED; // clear everything else
                   4885:         buttons[b].hotkey[0] = UP_KEY;
                   4886:         buttons[b].hotkey[1] = NUMPAD_8;
                   4887:         buttons[b].hotkey[2] = UP_ARROW;
                   4888:         b++;
                   4889:         // Next
                   4890:         buttons[b].flags = B_ENABLED; // clear everything else
                   4891:         buttons[b].hotkey[0] = DOWN_KEY;
                   4892:         buttons[b].hotkey[1] = NUMPAD_2;
                   4893:         buttons[b].hotkey[2] = DOWN_ARROW;
                   4894:         b++;
                   4895:     }
                   4896:     b = printTextBox(textBuf, x, y, width, &white, &interfaceBoxColor, rbuf, buttons, b);
                   4897:
                   4898:     if (!includeButtons) {
                   4899:         waitForKeystrokeOrMouseClick();
                   4900:         return -1;
                   4901:     }
                   4902:
                   4903:     if (b >= 0) {
                   4904:         return buttons[b].hotkey[0];
                   4905:     } else {
                   4906:         return -1;
                   4907:     }
                   4908: }
                   4909:
                   4910: // Returns true if an action was taken.
                   4911: void printFloorItemDetails(item *theItem, cellDisplayBuffer rbuf[COLS][ROWS]) {
                   4912:     char textBuf[COLS * 100];
                   4913:
                   4914:     itemDetails(textBuf, theItem);
                   4915:     printTextBox(textBuf, theItem->xLoc, 0, 0, &white, &black, rbuf, NULL, 0);
                   4916: }

CVSweb