[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     ! 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