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.
getPlayerPathOnMap(short path[1000][2],short ** map,short originX,short originY)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 
reversePath(short path[1000][2],short steps)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 
hilitePath(short path[1000][2],short steps,boolean unhilite)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.
clearCursorPath()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 
hideCursor()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 
showCursor()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 
getClosestValidLocationOnMap(short loc[2],short ** map,short x,short y)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 
processSnapMap(short ** map)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.
actionMenu(short x,boolean playingBack)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[%c] Enable graphics  ", yellowColorEscape, whiteColorEscape, " X~"[graphicsMode]);
355             } else {
356                 sprintf(buttons[buttonCount].text, "  [%c] Enable graphics  ",   " X~"[graphicsMode]);
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 
initializeMenuButtons(buttonState * state,brogueButton buttons[5])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.
mainInputLoop()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>)", 0);
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 
considerCautiousMode()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 // previouslyPlottedCells is only accessed by commitDraws and refreshScreen,
875 // as below.
876 static cellDisplayBuffer previouslyPlottedCells[COLS][ROWS];
877 
878 // Only cells which have changed since the previous commitDraws are actually
879 // drawn.
commitDraws()880 void commitDraws() {
881     for (int j = 0; j < ROWS; j++) {
882         for (int i = 0; i < COLS; i++) {
883             cellDisplayBuffer *lastPlotted = &previouslyPlottedCells[i][j];
884             cellDisplayBuffer *curr = &displayBuffer[i][j];
885             boolean needsUpdate =
886                 lastPlotted->character != curr->character
887                 || lastPlotted->foreColorComponents[0] != curr->foreColorComponents[0]
888                 || lastPlotted->foreColorComponents[1] != curr->foreColorComponents[1]
889                 || lastPlotted->foreColorComponents[2] != curr->foreColorComponents[2]
890                 || lastPlotted->backColorComponents[0] != curr->backColorComponents[0]
891                 || lastPlotted->backColorComponents[1] != curr->backColorComponents[1]
892                 || lastPlotted->backColorComponents[2] != curr->backColorComponents[2];
893 
894             if (!needsUpdate) {
895                 continue;
896             }
897 
898             plotChar(curr->character, i, j,
899                      curr->foreColorComponents[0],
900                      curr->foreColorComponents[1],
901                      curr->foreColorComponents[2],
902                      curr->backColorComponents[0],
903                      curr->backColorComponents[1],
904                      curr->backColorComponents[2]
905             );
906             *lastPlotted = *curr;
907         }
908     }
909 }
910 
911 // flags the entire window as needing to be redrawn at next flush.
912 // very low level -- does not interface with the guts of the game.
refreshScreen()913 void refreshScreen() {
914     for (int i = 0; i < COLS; i++) {
915         for (int j = 0; j < ROWS; j++) {
916             cellDisplayBuffer *curr = &displayBuffer[i][j];
917             plotChar(curr->character, i, j,
918                      curr->foreColorComponents[0],
919                      curr->foreColorComponents[1],
920                      curr->foreColorComponents[2],
921                      curr->backColorComponents[0],
922                      curr->backColorComponents[1],
923                      curr->backColorComponents[2]
924             );
925             // Remember that it was previously plotted, so that
926             // commitDraws still knows when it needs updates.
927             previouslyPlottedCells[i][j] = *curr;
928         }
929     }
930 }
931 
932 // higher-level redraw
displayLevel()933 void displayLevel() {
934     short i, j;
935 
936     for( i=0; i<DCOLS; i++ ) {
937         for (j = DROWS-1; j >= 0; j--) {
938             refreshDungeonCell(i, j);
939         }
940     }
941 }
942 
943 // converts colors into components
storeColorComponents(char components[3],const color * theColor)944 void storeColorComponents(char components[3], const color *theColor) {
945     short rand = rand_range(0, theColor->rand);
946     components[0] = max(0, min(100, theColor->red + rand_range(0, theColor->redRand) + rand));
947     components[1] = max(0, min(100, theColor->green + rand_range(0, theColor->greenRand) + rand));
948     components[2] = max(0, min(100, theColor->blue + rand_range(0, theColor->blueRand) + rand));
949 }
950 
bakeTerrainColors(color * foreColor,color * backColor,short x,short y)951 void bakeTerrainColors(color *foreColor, color *backColor, short x, short y) {
952     const short *vals;
953     const short neutralColors[8] = {1000, 1000, 1000, 1000, 0, 0, 0, 0};
954     if (rogue.trueColorMode) {
955         vals = neutralColors;
956     } else {
957         vals = &(terrainRandomValues[x][y][0]);
958     }
959 
960     const short foreRand = foreColor->rand * vals[6] / 1000;
961     const short backRand = backColor->rand * vals[7] / 1000;
962 
963     foreColor->red += foreColor->redRand * vals[0] / 1000 + foreRand;
964     foreColor->green += foreColor->greenRand * vals[1] / 1000 + foreRand;
965     foreColor->blue += foreColor->blueRand * vals[2] / 1000 + foreRand;
966     foreColor->redRand = foreColor->greenRand = foreColor->blueRand = foreColor->rand = 0;
967 
968     backColor->red += backColor->redRand * vals[3] / 1000 + backRand;
969     backColor->green += backColor->greenRand * vals[4] / 1000 + backRand;
970     backColor->blue += backColor->blueRand * vals[5] / 1000 + backRand;
971     backColor->redRand = backColor->greenRand = backColor->blueRand = backColor->rand = 0;
972 
973     if (foreColor->colorDances || backColor->colorDances) {
974         pmap[x][y].flags |= TERRAIN_COLORS_DANCING;
975     } else {
976         pmap[x][y].flags &= ~TERRAIN_COLORS_DANCING;
977     }
978 }
979 
bakeColor(color * theColor)980 void bakeColor(color *theColor) {
981     short rand;
982     rand = rand_range(0, theColor->rand);
983     theColor->red += rand_range(0, theColor->redRand) + rand;
984     theColor->green += rand_range(0, theColor->greenRand) + rand;
985     theColor->blue += rand_range(0, theColor->blueRand) + rand;
986     theColor->redRand = theColor->greenRand = theColor->blueRand = theColor->rand = 0;
987 }
988 
shuffleTerrainColors(short percentOfCells,boolean refreshCells)989 void shuffleTerrainColors(short percentOfCells, boolean refreshCells) {
990     enum directions dir;
991     short i, j;
992 
993     assureCosmeticRNG;
994 
995     for (i=0; i<DCOLS; i++) {
996         for(j=0; j<DROWS; j++) {
997             if (playerCanSeeOrSense(i, j)
998                 && (!rogue.automationActive || !(rogue.playerTurnNumber % 5))
999                 && ((pmap[i][j].flags & TERRAIN_COLORS_DANCING)
1000                     || (player.status[STATUS_HALLUCINATING] && playerCanDirectlySee(i, j)))
1001                 && (i != rogue.cursorLoc[0] || j != rogue.cursorLoc[1])
1002                 && (percentOfCells >= 100 || rand_range(1, 100) <= percentOfCells)) {
1003 
1004                     for (dir=0; dir<DIRECTION_COUNT; dir++) {
1005                         terrainRandomValues[i][j][dir] += rand_range(-600, 600);
1006                         terrainRandomValues[i][j][dir] = clamp(terrainRandomValues[i][j][dir], 0, 1000);
1007                     }
1008 
1009                     if (refreshCells) {
1010                         refreshDungeonCell(i, j);
1011                     }
1012                 }
1013         }
1014     }
1015     restoreRNG;
1016 }
1017 
1018 // if forecolor is too similar to back, darken or lighten it and return true.
1019 // Assumes colors have already been baked (no random components).
separateColors(color * fore,color * back)1020 boolean separateColors(color *fore, color *back) {
1021     color f, b, *modifier;
1022     short failsafe;
1023     boolean madeChange;
1024 
1025     f = *fore;
1026     b = *back;
1027     f.red       = clamp(f.red, 0, 100);
1028     f.green     = clamp(f.green, 0, 100);
1029     f.blue      = clamp(f.blue, 0, 100);
1030     b.red       = clamp(b.red, 0, 100);
1031     b.green     = clamp(b.green, 0, 100);
1032     b.blue      = clamp(b.blue, 0, 100);
1033 
1034     if (f.red + f.blue + f.green > 50 * 3) {
1035         modifier = &black;
1036     } else {
1037         modifier = &white;
1038     }
1039 
1040     madeChange = false;
1041     failsafe = 10;
1042 
1043     while(COLOR_DIFF(f, b) < MIN_COLOR_DIFF && --failsafe) {
1044         applyColorAverage(&f, modifier, 20);
1045         madeChange = true;
1046     }
1047 
1048     if (madeChange) {
1049         *fore = f;
1050         return true;
1051     } else {
1052         return false;
1053     }
1054 }
1055 
normColor(color * baseColor,const short aggregateMultiplier,const short colorTranslation)1056 void normColor(color *baseColor, const short aggregateMultiplier, const short colorTranslation) {
1057 
1058     baseColor->red += colorTranslation;
1059     baseColor->green += colorTranslation;
1060     baseColor->blue += colorTranslation;
1061     const short vectorLength =  baseColor->red + baseColor->green + baseColor->blue;
1062 
1063     if (vectorLength != 0) {
1064         baseColor->red =    baseColor->red * 300    / vectorLength * aggregateMultiplier / 100;
1065         baseColor->green =  baseColor->green * 300  / vectorLength * aggregateMultiplier / 100;
1066         baseColor->blue =   baseColor->blue * 300   / vectorLength * aggregateMultiplier / 100;
1067     }
1068     baseColor->redRand = 0;
1069     baseColor->greenRand = 0;
1070     baseColor->blueRand = 0;
1071     baseColor->rand = 0;
1072 }
1073 
1074 // Used to determine whether to draw a wall top glyph above
glyphIsWallish(enum displayGlyph glyph)1075 static boolean glyphIsWallish(enum displayGlyph glyph) {
1076     switch (glyph) {
1077         case G_WALL:
1078         case G_OPEN_DOOR:
1079         case G_CLOSED_DOOR:
1080         case G_UP_STAIRS:
1081         case G_DOORWAY:
1082         case G_WALL_TOP:
1083         case G_LEVER:
1084         case G_LEVER_PULLED:
1085         case G_CLOSED_IRON_DOOR:
1086         case G_OPEN_IRON_DOOR:
1087         case G_TURRET:
1088         case G_GRANITE:
1089         case G_TORCH:
1090         case G_PORTCULLIS:
1091             return true;
1092 
1093         default:
1094             return false;
1095     }
1096 }
1097 
randomAnimateMonster()1098 static enum monsterTypes randomAnimateMonster() {
1099     /* Randomly pick an animate and vulnerable monster type. Used by
1100     getCellAppearance for hallucination effects. */
1101     static int listLength = 0;
1102     static enum monsterTypes animate[NUMBER_MONSTER_KINDS];
1103 
1104     if (listLength == 0) {
1105         for (int i=0; i < NUMBER_MONSTER_KINDS; i++) {
1106             if (!(monsterCatalog[i].flags & (MONST_INANIMATE | MONST_INVULNERABLE))) {
1107                 animate[listLength++] = i;
1108             }
1109         }
1110     }
1111 
1112     return animate[rand_range(0, listLength - 1)];
1113 }
1114 
1115 // okay, this is kind of a beast...
getCellAppearance(short x,short y,enum displayGlyph * returnChar,color * returnForeColor,color * returnBackColor)1116 void getCellAppearance(short x, short y, enum displayGlyph *returnChar, color *returnForeColor, color *returnBackColor) {
1117     short bestBCPriority, bestFCPriority, bestCharPriority;
1118     short distance;
1119     enum displayGlyph cellChar = 0;
1120     color cellForeColor, cellBackColor, lightMultiplierColor = black, gasAugmentColor;
1121     boolean monsterWithDetectedItem = false, needDistinctness = false;
1122     short gasAugmentWeight = 0;
1123     creature *monst = NULL;
1124     item *theItem = NULL;
1125     enum tileType tile = NOTHING;
1126     const enum displayGlyph itemChars[] = {G_POTION, G_SCROLL, G_FOOD, G_WAND,
1127                         G_STAFF, G_GOLD, G_ARMOR, G_WEAPON, G_RING, G_CHARM};
1128     enum dungeonLayers layer, maxLayer;
1129 
1130     assureCosmeticRNG;
1131 
1132     brogueAssert(coordinatesAreInMap(x, y));
1133 
1134     if (pmap[x][y].flags & HAS_MONSTER) {
1135         monst = monsterAtLoc(x, y);
1136     } else if (pmap[x][y].flags & HAS_DORMANT_MONSTER) {
1137         monst = dormantMonsterAtLoc(x, y);
1138     }
1139     if (monst) {
1140         monsterWithDetectedItem = (monst->carriedItem && (monst->carriedItem->flags & ITEM_MAGIC_DETECTED)
1141                                    && itemMagicPolarity(monst->carriedItem) && !canSeeMonster(monst));
1142     }
1143 
1144     if (monsterWithDetectedItem) {
1145         theItem = monst->carriedItem;
1146     } else {
1147         theItem = itemAtLoc(x, y);
1148     }
1149 
1150     if (!playerCanSeeOrSense(x, y)
1151         && !(pmap[x][y].flags & (ITEM_DETECTED | HAS_PLAYER))
1152         && (!monst || !monsterRevealed(monst))
1153         && !monsterWithDetectedItem
1154         && (pmap[x][y].flags & (DISCOVERED | MAGIC_MAPPED))
1155         && (pmap[x][y].flags & STABLE_MEMORY)) {
1156 
1157         // restore memory
1158         cellChar = pmap[x][y].rememberedAppearance.character;
1159         cellForeColor = colorFromComponents(pmap[x][y].rememberedAppearance.foreColorComponents);
1160         cellBackColor = colorFromComponents(pmap[x][y].rememberedAppearance.backColorComponents);
1161     } else {
1162         // Find the highest-priority fore color, back color and character.
1163         bestFCPriority = bestBCPriority = bestCharPriority = 10000;
1164 
1165         // Default to the appearance of floor.
1166         cellForeColor = *(tileCatalog[FLOOR].foreColor);
1167         cellBackColor = *(tileCatalog[FLOOR].backColor);
1168         cellChar = tileCatalog[FLOOR].displayChar;
1169 
1170         if (!(pmap[x][y].flags & DISCOVERED) && !rogue.playbackOmniscience) {
1171             if (pmap[x][y].flags & MAGIC_MAPPED) {
1172                 maxLayer = LIQUID + 1; // Can see only dungeon and liquid layers with magic mapping.
1173             } else {
1174                 maxLayer = 0; // Terrain shouldn't influence the tile appearance at all if it hasn't been discovered.
1175             }
1176         } else {
1177             maxLayer = NUMBER_TERRAIN_LAYERS;
1178         }
1179 
1180         for (layer = 0; layer < maxLayer; layer++) {
1181             // Gas shows up as a color average, not directly.
1182             if (pmap[x][y].layers[layer] && layer != GAS) {
1183                 tile = pmap[x][y].layers[layer];
1184                 if (rogue.playbackOmniscience && (tileCatalog[tile].mechFlags & TM_IS_SECRET)) {
1185                     tile = dungeonFeatureCatalog[tileCatalog[tile].discoverType].tile;
1186                 }
1187 
1188                 if (tileCatalog[tile].drawPriority < bestFCPriority
1189                     && tileCatalog[tile].foreColor) {
1190 
1191                     cellForeColor = *(tileCatalog[tile].foreColor);
1192                     bestFCPriority = tileCatalog[tile].drawPriority;
1193                 }
1194                 if (tileCatalog[tile].drawPriority < bestBCPriority
1195                     && tileCatalog[tile].backColor) {
1196 
1197                     cellBackColor = *(tileCatalog[tile].backColor);
1198                     bestBCPriority = tileCatalog[tile].drawPriority;
1199                 }
1200                 if (tileCatalog[tile].drawPriority < bestCharPriority
1201                     && tileCatalog[tile].displayChar) {
1202 
1203                     cellChar = tileCatalog[tile].displayChar;
1204                     bestCharPriority = tileCatalog[tile].drawPriority;
1205                     needDistinctness = (tileCatalog[tile].mechFlags & TM_VISUALLY_DISTINCT) ? true : false;
1206                 }
1207             }
1208         }
1209 
1210         if (rogue.trueColorMode) {
1211             lightMultiplierColor = colorMultiplier100;
1212         } else {
1213             colorMultiplierFromDungeonLight(x, y, &lightMultiplierColor);
1214         }
1215 
1216         if (pmap[x][y].layers[GAS]
1217             && tileCatalog[pmap[x][y].layers[GAS]].backColor) {
1218 
1219             gasAugmentColor = *(tileCatalog[pmap[x][y].layers[GAS]].backColor);
1220             if (rogue.trueColorMode) {
1221                 gasAugmentWeight = 30;
1222             } else {
1223                 gasAugmentWeight = min(90, 30 + pmap[x][y].volume);
1224             }
1225         }
1226 
1227         if (D_DISABLE_BACKGROUND_COLORS) {
1228             if (COLOR_DIFF(cellBackColor, black) > COLOR_DIFF(cellForeColor, black)) {
1229                 cellForeColor = cellBackColor;
1230             }
1231             cellBackColor = black;
1232             needDistinctness = true;
1233         }
1234 
1235         if (pmap[x][y].flags & HAS_PLAYER) {
1236             cellChar = player.info.displayChar;
1237             cellForeColor = *(player.info.foreColor);
1238             needDistinctness = true;
1239         } else if (((pmap[x][y].flags & HAS_ITEM) && (pmap[x][y].flags & ITEM_DETECTED)
1240                     && itemMagicPolarity(theItem)
1241                     && !playerCanSeeOrSense(x, y))
1242                    || monsterWithDetectedItem){
1243 
1244             int polarity = itemMagicPolarity(theItem);
1245             if (theItem->category == AMULET) {
1246                 cellChar = G_AMULET;
1247                 cellForeColor = white;
1248             } else if (polarity == -1) {
1249                 cellChar = G_BAD_MAGIC;
1250                 cellForeColor = badMessageColor;
1251             } else if (polarity == 1) {
1252                 cellChar = G_GOOD_MAGIC;
1253                 cellForeColor = goodMessageColor;
1254             } else {
1255                 cellChar = 0;
1256                 cellForeColor = white;
1257             }
1258 
1259             needDistinctness = true;
1260         } else if ((pmap[x][y].flags & HAS_MONSTER)
1261                    && (playerCanSeeOrSense(x, y) || ((monst->info.flags & MONST_IMMOBILE) && (pmap[x][y].flags & DISCOVERED)))
1262                    && (!monsterIsHidden(monst, &player) || rogue.playbackOmniscience)) {
1263             needDistinctness = true;
1264             if (player.status[STATUS_HALLUCINATING] > 0 && !(monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE)) && !rogue.playbackOmniscience) {
1265                 cellChar = monsterCatalog[randomAnimateMonster()].displayChar;
1266                 cellForeColor = *(monsterCatalog[randomAnimateMonster()].foreColor);
1267             } else {
1268                 cellChar = monst->info.displayChar;
1269                 cellForeColor = *(monst->info.foreColor);
1270                 if (monst->status[STATUS_INVISIBLE] || (monst->bookkeepingFlags & MB_SUBMERGED)) {
1271                     // Invisible allies show up on the screen with a transparency effect.
1272                     //cellForeColor = cellBackColor;
1273                     applyColorAverage(&cellForeColor, &cellBackColor, 75);
1274                 } else {
1275                     if (monst->creatureState == MONSTER_ALLY && !(monst->info.flags & MONST_INANIMATE)) {
1276                         if (rogue.trueColorMode) {
1277                             cellForeColor = white;
1278                         } else {
1279                             applyColorAverage(&cellForeColor, &pink, 50);
1280                         }
1281                     }
1282                 }
1283                 //DEBUG if (monst->bookkeepingFlags & MB_LEADER) applyColorAverage(&cellBackColor, &purple, 50);
1284             }
1285         } else if (monst
1286                    && monsterRevealed(monst)
1287                    && !canSeeMonster(monst)) {
1288             if (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience) {
1289                 cellChar = (rand_range(0, 1) ? 'X' : 'x');
1290             } else {
1291                 cellChar = (monst->info.isLarge ? 'X' : 'x');
1292             }
1293             cellForeColor = white;
1294             lightMultiplierColor = white;
1295             if (!(pmap[x][y].flags & DISCOVERED)) {
1296                 cellBackColor = black;
1297                 gasAugmentColor = black;
1298             }
1299         } else if ((pmap[x][y].flags & HAS_ITEM) && !cellHasTerrainFlag(x, y, T_OBSTRUCTS_ITEMS)
1300                    && (playerCanSeeOrSense(x, y) || ((pmap[x][y].flags & DISCOVERED) && !cellHasTerrainFlag(x, y, T_MOVES_ITEMS)))) {
1301             needDistinctness = true;
1302             if (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience) {
1303                 cellChar = itemChars[rand_range(0, 9)];
1304                 cellForeColor = itemColor;
1305             } else {
1306                 theItem = itemAtLoc(x, y);
1307                 cellChar = theItem->displayChar;
1308                 cellForeColor = *(theItem->foreColor);
1309                 // Remember the item was here
1310                 pmap[x][y].rememberedItemCategory = theItem->category;
1311                 pmap[x][y].rememberedItemKind = theItem->kind;
1312                 pmap[x][y].rememberedItemQuantity = theItem->quantity;
1313                 pmap[x][y].rememberedItemOriginDepth = theItem->originDepth;
1314             }
1315         } else if (playerCanSeeOrSense(x, y) || (pmap[x][y].flags & (DISCOVERED | MAGIC_MAPPED))) {
1316             // just don't want these to be plotted as black
1317             // Also, ensure we remember there are no items here
1318             pmap[x][y].rememberedItemCategory = 0;
1319             pmap[x][y].rememberedItemKind = 0;
1320             pmap[x][y].rememberedItemQuantity = 0;
1321             pmap[x][y].rememberedItemOriginDepth = 0;
1322         } else {
1323             *returnChar = ' ';
1324             *returnForeColor = black;
1325             *returnBackColor = undiscoveredColor;
1326 
1327             if (D_DISABLE_BACKGROUND_COLORS) *returnBackColor = black;
1328 
1329             restoreRNG;
1330             return;
1331         }
1332 
1333         if (gasAugmentWeight && ((pmap[x][y].flags & DISCOVERED) || rogue.playbackOmniscience)) {
1334             if (!rogue.trueColorMode || !needDistinctness) {
1335                 applyColorAverage(&cellForeColor, &gasAugmentColor, gasAugmentWeight);
1336             }
1337             // phantoms create sillhouettes in gas clouds
1338             if ((pmap[x][y].flags & HAS_MONSTER)
1339                 && monst->status[STATUS_INVISIBLE]
1340                 && playerCanSeeOrSense(x, y)
1341                 && !monsterRevealed(monst)
1342                 && !monsterHiddenBySubmersion(monst, &player)) {
1343 
1344                 if (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience) {
1345                     cellChar = monsterCatalog[randomAnimateMonster()].displayChar;
1346                 } else {
1347                     cellChar = monst->info.displayChar;
1348                 }
1349                 cellForeColor = cellBackColor;
1350             }
1351             applyColorAverage(&cellBackColor, &gasAugmentColor, gasAugmentWeight);
1352         }
1353 
1354         if (!(pmap[x][y].flags & (ANY_KIND_OF_VISIBLE | ITEM_DETECTED | HAS_PLAYER))
1355             && !playerCanSeeOrSense(x, y)
1356             && (!monst || !monsterRevealed(monst)) && !monsterWithDetectedItem) {
1357 
1358             pmap[x][y].flags |= STABLE_MEMORY;
1359             pmap[x][y].rememberedAppearance.character = cellChar;
1360 
1361             if (rogue.trueColorMode) {
1362                 bakeTerrainColors(&cellForeColor, &cellBackColor, x, y);
1363             }
1364 
1365             // store memory
1366             storeColorComponents(pmap[x][y].rememberedAppearance.foreColorComponents, &cellForeColor);
1367             storeColorComponents(pmap[x][y].rememberedAppearance.backColorComponents, &cellBackColor);
1368 
1369             applyColorAugment(&lightMultiplierColor, &basicLightColor, 100);
1370             if (!rogue.trueColorMode || !needDistinctness) {
1371                 applyColorMultiplier(&cellForeColor, &lightMultiplierColor);
1372             }
1373             applyColorMultiplier(&cellBackColor, &lightMultiplierColor);
1374             bakeTerrainColors(&cellForeColor, &cellBackColor, x, y);
1375 
1376             // Then restore, so that it looks the same on this pass as it will when later refreshed.
1377             cellForeColor = colorFromComponents(pmap[x][y].rememberedAppearance.foreColorComponents);
1378             cellBackColor = colorFromComponents(pmap[x][y].rememberedAppearance.backColorComponents);
1379         }
1380     }
1381 
1382     // Smooth out walls: if there's a "wall-ish" tile drawn below us, just draw the wall top
1383     if ((cellChar == G_WALL || cellChar == G_GRANITE) && coordinatesAreInMap(x, y+1)
1384         && glyphIsWallish(displayBuffer[mapToWindowX(x)][mapToWindowY(y+1)].character)) {
1385         cellChar = G_WALL_TOP;
1386     }
1387 
1388     if (((pmap[x][y].flags & ITEM_DETECTED) || monsterWithDetectedItem
1389          || (monst && monsterRevealed(monst)))
1390         && !playerCanSeeOrSense(x, y)) {
1391         // do nothing
1392     } else if (!(pmap[x][y].flags & VISIBLE) && (pmap[x][y].flags & CLAIRVOYANT_VISIBLE)) {
1393         // can clairvoyantly see it
1394         if (rogue.trueColorMode) {
1395             lightMultiplierColor = basicLightColor;
1396         } else {
1397             applyColorAugment(&lightMultiplierColor, &basicLightColor, 100);
1398         }
1399         if (!rogue.trueColorMode || !needDistinctness) {
1400             applyColorMultiplier(&cellForeColor, &lightMultiplierColor);
1401             applyColorMultiplier(&cellForeColor, &clairvoyanceColor);
1402         }
1403         applyColorMultiplier(&cellBackColor, &lightMultiplierColor);
1404         applyColorMultiplier(&cellBackColor, &clairvoyanceColor);
1405     } else if (!(pmap[x][y].flags & VISIBLE) && (pmap[x][y].flags & TELEPATHIC_VISIBLE)) {
1406         // Can telepathically see it through another creature's eyes.
1407 
1408         applyColorAugment(&lightMultiplierColor, &basicLightColor, 100);
1409 
1410         if (!rogue.trueColorMode || !needDistinctness) {
1411             applyColorMultiplier(&cellForeColor, &lightMultiplierColor);
1412             applyColorMultiplier(&cellForeColor, &telepathyMultiplier);
1413         }
1414         applyColorMultiplier(&cellBackColor, &lightMultiplierColor);
1415         applyColorMultiplier(&cellBackColor, &telepathyMultiplier);
1416     } else if (!(pmap[x][y].flags & DISCOVERED) && (pmap[x][y].flags & MAGIC_MAPPED)) {
1417         // magic mapped only
1418         if (!rogue.playbackOmniscience) {
1419             needDistinctness = false;
1420             if (!rogue.trueColorMode || !needDistinctness) {
1421                 applyColorMultiplier(&cellForeColor, &magicMapColor);
1422             }
1423             applyColorMultiplier(&cellBackColor, &magicMapColor);
1424         }
1425     } else if (!(pmap[x][y].flags & VISIBLE) && !rogue.playbackOmniscience) {
1426         // if it's not visible
1427 
1428         needDistinctness = false;
1429         if (rogue.inWater) {
1430             applyColorAverage(&cellForeColor, &black, 80);
1431             applyColorAverage(&cellBackColor, &black, 80);
1432         } else {
1433             if (!cellHasTMFlag(x, y, TM_BRIGHT_MEMORY)
1434                 && (!rogue.trueColorMode || !needDistinctness)) {
1435 
1436                 applyColorMultiplier(&cellForeColor, &memoryColor);
1437                 applyColorAverage(&cellForeColor, &memoryOverlay, 25);
1438             }
1439             applyColorMultiplier(&cellBackColor, &memoryColor);
1440             applyColorAverage(&cellBackColor, &memoryOverlay, 25);
1441         }
1442     } else if (playerCanSeeOrSense(x, y) && rogue.playbackOmniscience && !(pmap[x][y].flags & ANY_KIND_OF_VISIBLE)) {
1443         // omniscience
1444         applyColorAugment(&lightMultiplierColor, &basicLightColor, 100);
1445         if (!rogue.trueColorMode || !needDistinctness) {
1446             applyColorMultiplier(&cellForeColor, &lightMultiplierColor);
1447             applyColorMultiplier(&cellForeColor, &omniscienceColor);
1448         }
1449         applyColorMultiplier(&cellBackColor, &lightMultiplierColor);
1450         applyColorMultiplier(&cellBackColor, &omniscienceColor);
1451     } else {
1452         if (!rogue.trueColorMode || !needDistinctness) {
1453             applyColorMultiplier(&cellForeColor, &lightMultiplierColor);
1454         }
1455         applyColorMultiplier(&cellBackColor, &lightMultiplierColor);
1456 
1457         if (player.status[STATUS_HALLUCINATING] && !rogue.trueColorMode) {
1458             randomizeColor(&cellForeColor, 40 * player.status[STATUS_HALLUCINATING] / 300 + 20);
1459             randomizeColor(&cellBackColor, 40 * player.status[STATUS_HALLUCINATING] / 300 + 20);
1460         }
1461         if (rogue.inWater) {
1462             applyColorMultiplier(&cellForeColor, &deepWaterLightColor);
1463             applyColorMultiplier(&cellBackColor, &deepWaterLightColor);
1464         }
1465     }
1466 //   DEBUG cellBackColor.red = max(0,((scentMap[x][y] - rogue.scentTurnNumber) * 2) + 100);
1467 //   DEBUG if (pmap[x][y].flags & KNOWN_TO_BE_TRAP_FREE) cellBackColor.red += 20;
1468 //   DEBUG if (cellHasTerrainFlag(x, y, T_IS_FLAMMABLE)) cellBackColor.red += 50;
1469 
1470     if (pmap[x][y].flags & IS_IN_PATH) {
1471         if (cellHasTMFlag(x, y, TM_INVERT_WHEN_HIGHLIGHTED)) {
1472             swapColors(&cellForeColor, &cellBackColor);
1473         } else {
1474             if (!rogue.trueColorMode || !needDistinctness) {
1475                 applyColorAverage(&cellForeColor, &yellow, rogue.cursorPathIntensity);
1476             }
1477             applyColorAverage(&cellBackColor, &yellow, rogue.cursorPathIntensity);
1478         }
1479         needDistinctness = true;
1480     }
1481 
1482     bakeTerrainColors(&cellForeColor, &cellBackColor, x, y);
1483 
1484     if (rogue.displayAggroRangeMode && (pmap[x][y].flags & IN_FIELD_OF_VIEW)) {
1485         distance = min(rogue.scentTurnNumber - scentMap[x][y], scentDistance(x, y, player.xLoc, player.yLoc));
1486         if (distance > rogue.aggroRange * 2) {
1487             applyColorAverage(&cellForeColor, &orange, 12);
1488             applyColorAverage(&cellBackColor, &orange, 12);
1489             applyColorAugment(&cellForeColor, &orange, 12);
1490             applyColorAugment(&cellBackColor, &orange, 12);
1491         }
1492     }
1493 
1494     if (rogue.trueColorMode
1495         && playerCanSeeOrSense(x, y)) {
1496 
1497         if (displayDetail[x][y] == DV_DARK) {
1498             applyColorMultiplier(&cellForeColor, &inDarknessMultiplierColor);
1499             applyColorMultiplier(&cellBackColor, &inDarknessMultiplierColor);
1500 
1501             applyColorAugment(&cellForeColor, &purple, 10);
1502             applyColorAugment(&cellBackColor, &white, -10);
1503             applyColorAverage(&cellBackColor, &purple, 20);
1504         } else if (displayDetail[x][y] == DV_LIT) {
1505 
1506             colorMultiplierFromDungeonLight(x, y, &lightMultiplierColor);
1507             normColor(&lightMultiplierColor, 175, 50);
1508             //applyColorMultiplier(&cellForeColor, &lightMultiplierColor);
1509             //applyColorMultiplier(&cellBackColor, &lightMultiplierColor);
1510             applyColorAugment(&cellForeColor, &lightMultiplierColor, 5);
1511             applyColorAugment(&cellBackColor, &lightMultiplierColor, 5);
1512         }
1513     }
1514 
1515     if (needDistinctness) {
1516         separateColors(&cellForeColor, &cellBackColor);
1517     }
1518 
1519     if (D_SCENT_VISION) {
1520         if (rogue.scentTurnNumber > (unsigned short) scentMap[x][y]) {
1521             cellBackColor.red = rogue.scentTurnNumber - (unsigned short) scentMap[x][y];
1522             cellBackColor.red = clamp(cellBackColor.red, 0, 100);
1523         } else {
1524             cellBackColor.green = abs(rogue.scentTurnNumber - (unsigned short) scentMap[x][y]);
1525             cellBackColor.green = clamp(cellBackColor.green, 0, 100);
1526         }
1527     }
1528 
1529     *returnChar = cellChar;
1530     *returnForeColor = cellForeColor;
1531     *returnBackColor = cellBackColor;
1532 
1533     if (D_DISABLE_BACKGROUND_COLORS) *returnBackColor = black;
1534     restoreRNG;
1535 }
1536 
refreshDungeonCell(short x,short y)1537 void refreshDungeonCell(short x, short y) {
1538     enum displayGlyph cellChar;
1539     color foreColor, backColor;
1540     brogueAssert(coordinatesAreInMap(x, y));
1541 
1542     getCellAppearance(x, y, &cellChar, &foreColor, &backColor);
1543     plotCharWithColor(cellChar, mapToWindowX(x), mapToWindowY(y), &foreColor, &backColor);
1544 }
1545 
applyColorMultiplier(color * baseColor,const color * multiplierColor)1546 void applyColorMultiplier(color *baseColor, const color *multiplierColor) {
1547     baseColor->red = baseColor->red * multiplierColor->red / 100;
1548     baseColor->redRand = baseColor->redRand * multiplierColor->redRand / 100;
1549     baseColor->green = baseColor->green * multiplierColor->green / 100;
1550     baseColor->greenRand = baseColor->greenRand * multiplierColor->greenRand / 100;
1551     baseColor->blue = baseColor->blue * multiplierColor->blue / 100;
1552     baseColor->blueRand = baseColor->blueRand * multiplierColor->blueRand / 100;
1553     baseColor->rand = baseColor->rand * multiplierColor->rand / 100;
1554     //baseColor->colorDances *= multiplierColor->colorDances;
1555     return;
1556 }
1557 
applyColorAverage(color * baseColor,const color * newColor,short averageWeight)1558 void applyColorAverage(color *baseColor, const color *newColor, short averageWeight) {
1559     short weightComplement = 100 - averageWeight;
1560     baseColor->red = (baseColor->red * weightComplement + newColor->red * averageWeight) / 100;
1561     baseColor->redRand = (baseColor->redRand * weightComplement + newColor->redRand * averageWeight) / 100;
1562     baseColor->green = (baseColor->green * weightComplement + newColor->green * averageWeight) / 100;
1563     baseColor->greenRand = (baseColor->greenRand * weightComplement + newColor->greenRand * averageWeight) / 100;
1564     baseColor->blue = (baseColor->blue * weightComplement + newColor->blue * averageWeight) / 100;
1565     baseColor->blueRand = (baseColor->blueRand * weightComplement + newColor->blueRand * averageWeight) / 100;
1566     baseColor->rand = (baseColor->rand * weightComplement + newColor->rand * averageWeight) / 100;
1567     baseColor->colorDances = (baseColor->colorDances || newColor->colorDances);
1568     return;
1569 }
1570 
applyColorAugment(color * baseColor,const color * augmentingColor,short augmentWeight)1571 void applyColorAugment(color *baseColor, const color *augmentingColor, short augmentWeight) {
1572     baseColor->red += (augmentingColor->red * augmentWeight) / 100;
1573     baseColor->redRand += (augmentingColor->redRand * augmentWeight) / 100;
1574     baseColor->green += (augmentingColor->green * augmentWeight) / 100;
1575     baseColor->greenRand += (augmentingColor->greenRand * augmentWeight) / 100;
1576     baseColor->blue += (augmentingColor->blue * augmentWeight) / 100;
1577     baseColor->blueRand += (augmentingColor->blueRand * augmentWeight) / 100;
1578     baseColor->rand += (augmentingColor->rand * augmentWeight) / 100;
1579     return;
1580 }
1581 
applyColorScalar(color * baseColor,short scalar)1582 void applyColorScalar(color *baseColor, short scalar) {
1583     baseColor->red          = baseColor->red        * scalar / 100;
1584     baseColor->redRand      = baseColor->redRand    * scalar / 100;
1585     baseColor->green        = baseColor->green      * scalar / 100;
1586     baseColor->greenRand    = baseColor->greenRand  * scalar / 100;
1587     baseColor->blue         = baseColor->blue       * scalar / 100;
1588     baseColor->blueRand     = baseColor->blueRand   * scalar / 100;
1589     baseColor->rand         = baseColor->rand       * scalar / 100;
1590 }
1591 
applyColorBounds(color * baseColor,short lowerBound,short upperBound)1592 void applyColorBounds(color *baseColor, short lowerBound, short upperBound) {
1593     baseColor->red          = clamp(baseColor->red, lowerBound, upperBound);
1594     baseColor->redRand      = clamp(baseColor->redRand, lowerBound, upperBound);
1595     baseColor->green        = clamp(baseColor->green, lowerBound, upperBound);
1596     baseColor->greenRand    = clamp(baseColor->greenRand, lowerBound, upperBound);
1597     baseColor->blue         = clamp(baseColor->blue, lowerBound, upperBound);
1598     baseColor->blueRand     = clamp(baseColor->blueRand, lowerBound, upperBound);
1599     baseColor->rand         = clamp(baseColor->rand, lowerBound, upperBound);
1600 }
1601 
desaturate(color * baseColor,short weight)1602 void desaturate(color *baseColor, short weight) {
1603     short avg;
1604     avg = (baseColor->red + baseColor->green + baseColor->blue) / 3 + 1;
1605     baseColor->red = baseColor->red * (100 - weight) / 100 + (avg * weight / 100);
1606     baseColor->green = baseColor->green * (100 - weight) / 100 + (avg * weight / 100);
1607     baseColor->blue = baseColor->blue * (100 - weight) / 100 + (avg * weight / 100);
1608 
1609     avg = (baseColor->redRand + baseColor->greenRand + baseColor->blueRand);
1610     baseColor->redRand = baseColor->redRand * (100 - weight) / 100;
1611     baseColor->greenRand = baseColor->greenRand * (100 - weight) / 100;
1612     baseColor->blueRand = baseColor->blueRand * (100 - weight) / 100;
1613 
1614     baseColor->rand += avg * weight / 3 / 100;
1615 }
1616 
randomizeByPercent(short input,short percent)1617 short randomizeByPercent(short input, short percent) {
1618     return (rand_range(input * (100 - percent) / 100, input * (100 + percent) / 100));
1619 }
1620 
randomizeColor(color * baseColor,short randomizePercent)1621 void randomizeColor(color *baseColor, short randomizePercent) {
1622     baseColor->red = randomizeByPercent(baseColor->red, randomizePercent);
1623     baseColor->green = randomizeByPercent(baseColor->green, randomizePercent);
1624     baseColor->blue = randomizeByPercent(baseColor->blue, randomizePercent);
1625 }
1626 
swapColors(color * color1,color * color2)1627 void swapColors(color *color1, color *color2) {
1628     color tempColor = *color1;
1629     *color1 = *color2;
1630     *color2 = tempColor;
1631 }
1632 
1633 // Assumes colors are pre-baked.
blendAppearances(const color * fromForeColor,const color * fromBackColor,const enum displayGlyph fromChar,const color * toForeColor,const color * toBackColor,const enum displayGlyph toChar,color * retForeColor,color * retBackColor,enum displayGlyph * retChar,const short percent)1634 void blendAppearances(const color *fromForeColor, const color *fromBackColor, const enum displayGlyph fromChar,
1635                       const color *toForeColor, const color *toBackColor, const enum displayGlyph toChar,
1636                       color *retForeColor, color *retBackColor, enum displayGlyph *retChar,
1637                       const short percent) {
1638     // Straight average of the back color:
1639     *retBackColor = *fromBackColor;
1640     applyColorAverage(retBackColor, toBackColor, percent);
1641 
1642     // Pick the character:
1643     if (percent >= 50) {
1644         *retChar = toChar;
1645     } else {
1646         *retChar = fromChar;
1647     }
1648 
1649     // Pick the method for blending the fore color.
1650     if (fromChar == toChar) {
1651         // If the character isn't changing, do a straight average.
1652         *retForeColor = *fromForeColor;
1653         applyColorAverage(retForeColor, toForeColor, percent);
1654     } else {
1655         // If it is changing, the first half blends to the current back color, and the second half blends to the final back color.
1656         if (percent >= 50) {
1657             *retForeColor = *retBackColor;
1658             applyColorAverage(retForeColor, toForeColor, (percent - 50) * 2);
1659         } else {
1660             *retForeColor = *fromForeColor;
1661             applyColorAverage(retForeColor, retBackColor, percent * 2);
1662         }
1663     }
1664 }
1665 
irisFadeBetweenBuffers(cellDisplayBuffer fromBuf[COLS][ROWS],cellDisplayBuffer toBuf[COLS][ROWS],short x,short y,short frameCount,boolean outsideIn)1666 void irisFadeBetweenBuffers(cellDisplayBuffer fromBuf[COLS][ROWS],
1667                             cellDisplayBuffer toBuf[COLS][ROWS],
1668                             short x, short y,
1669                             short frameCount,
1670                             boolean outsideIn) {
1671     short i, j, frame, percentBasis, thisCellPercent;
1672     boolean fastForward;
1673     color fromBackColor, toBackColor, fromForeColor, toForeColor, currentForeColor, currentBackColor;
1674     enum displayGlyph fromChar, toChar, currentChar;
1675     short completionMap[COLS][ROWS], maxDistance;
1676 
1677     fastForward = false;
1678     frame = 1;
1679 
1680     // Calculate the square of the maximum distance from (x, y) that the iris will have to spread.
1681     if (x < COLS / 2) {
1682         i = COLS - x;
1683     } else {
1684         i = x;
1685     }
1686     if (y < ROWS / 2) {
1687         j = ROWS - y;
1688     } else {
1689         j = y;
1690     }
1691     maxDistance = i*i + j*j;
1692 
1693     // Generate the initial completion map as a percent of maximum distance.
1694     for (i=0; i<COLS; i++) {
1695         for (j=0; j<ROWS; j++) {
1696             completionMap[i][j] = (i - x)*(i - x) + (j - y)*(j - y); // square of distance
1697             completionMap[i][j] = 100 * completionMap[i][j] / maxDistance; // percent of max distance
1698             if (outsideIn) {
1699                 completionMap[i][j] -= 100; // translate to [-100, 0], with the origin at -100 and the farthest point at 0.
1700             } else {
1701                 completionMap[i][j] *= -1; // translate to [-100, 0], with the origin at 0 and the farthest point at -100.
1702             }
1703         }
1704     }
1705 
1706     do {
1707         percentBasis = 10000 * frame / frameCount;
1708 
1709         for (i=0; i<COLS; i++) {
1710             for (j=0; j<ROWS; j++) {
1711                 thisCellPercent = percentBasis * 3 / 100 + completionMap[i][j];
1712 
1713                 fromBackColor = colorFromComponents(fromBuf[i][j].backColorComponents);
1714                 fromForeColor = colorFromComponents(fromBuf[i][j].foreColorComponents);
1715                 fromChar = fromBuf[i][j].character;
1716 
1717                 toBackColor = colorFromComponents(toBuf[i][j].backColorComponents);
1718                 toForeColor = colorFromComponents(toBuf[i][j].foreColorComponents);
1719                 toChar = toBuf[i][j].character;
1720 
1721                 blendAppearances(&fromForeColor, &fromBackColor, fromChar, &toForeColor, &toBackColor, toChar, &currentForeColor, &currentBackColor, &currentChar, clamp(thisCellPercent, 0, 100));
1722                 plotCharWithColor(currentChar, i, j, &currentForeColor, &currentBackColor);
1723             }
1724         }
1725 
1726         fastForward = pauseBrogue(16);
1727         frame++;
1728     } while (frame <= frameCount && !fastForward);
1729     overlayDisplayBuffer(toBuf, NULL);
1730 }
1731 
1732 // takes dungeon coordinates
colorBlendCell(short x,short y,color * hiliteColor,short hiliteStrength)1733 void colorBlendCell(short x, short y, color *hiliteColor, short hiliteStrength) {
1734     enum displayGlyph displayChar;
1735     color foreColor, backColor;
1736 
1737     getCellAppearance(x, y, &displayChar, &foreColor, &backColor);
1738     applyColorAverage(&foreColor, hiliteColor, hiliteStrength);
1739     applyColorAverage(&backColor, hiliteColor, hiliteStrength);
1740     plotCharWithColor(displayChar, mapToWindowX(x), mapToWindowY(y), &foreColor, &backColor);
1741 }
1742 
1743 // takes dungeon coordinates
hiliteCell(short x,short y,const color * hiliteColor,short hiliteStrength,boolean distinctColors)1744 void hiliteCell(short x, short y, const color *hiliteColor, short hiliteStrength, boolean distinctColors) {
1745     enum displayGlyph displayChar;
1746     color foreColor, backColor;
1747 
1748     assureCosmeticRNG;
1749 
1750     getCellAppearance(x, y, &displayChar, &foreColor, &backColor);
1751     applyColorAugment(&foreColor, hiliteColor, hiliteStrength);
1752     applyColorAugment(&backColor, hiliteColor, hiliteStrength);
1753     if (distinctColors) {
1754         separateColors(&foreColor, &backColor);
1755     }
1756     plotCharWithColor(displayChar, mapToWindowX(x), mapToWindowY(y), &foreColor, &backColor);
1757 
1758     restoreRNG;
1759 }
1760 
adjustedLightValue(short x)1761 short adjustedLightValue(short x) {
1762     if (x <= LIGHT_SMOOTHING_THRESHOLD) {
1763         return x;
1764     } else {
1765         return fp_sqrt(x * FP_FACTOR / LIGHT_SMOOTHING_THRESHOLD) * LIGHT_SMOOTHING_THRESHOLD / FP_FACTOR;
1766     }
1767 }
1768 
colorMultiplierFromDungeonLight(short x,short y,color * editColor)1769 void colorMultiplierFromDungeonLight(short x, short y, color *editColor) {
1770 
1771     editColor->red      = editColor->redRand    = adjustedLightValue(max(0, tmap[x][y].light[0]));
1772     editColor->green    = editColor->greenRand  = adjustedLightValue(max(0, tmap[x][y].light[1]));
1773     editColor->blue     = editColor->blueRand   = adjustedLightValue(max(0, tmap[x][y].light[2]));
1774 
1775     editColor->rand = adjustedLightValue(max(0, tmap[x][y].light[0] + tmap[x][y].light[1] + tmap[x][y].light[2]) / 3);
1776     editColor->colorDances = false;
1777 }
1778 
plotCharWithColor(enum displayGlyph inputChar,short xLoc,short yLoc,const color * cellForeColor,const color * cellBackColor)1779 void plotCharWithColor(enum displayGlyph inputChar, short xLoc, short yLoc, const color *cellForeColor, const color *cellBackColor) {
1780     short oldRNG;
1781 
1782     short foreRed = cellForeColor->red,
1783     foreGreen = cellForeColor->green,
1784     foreBlue = cellForeColor->blue,
1785 
1786     backRed = cellBackColor->red,
1787     backGreen = cellBackColor->green,
1788     backBlue = cellBackColor->blue,
1789 
1790     foreRand, backRand;
1791 
1792     brogueAssert(coordinatesAreInWindow(xLoc, yLoc));
1793 
1794     if (rogue.gameHasEnded || rogue.playbackFastForward) {
1795         return;
1796     }
1797 
1798     //assureCosmeticRNG;
1799     oldRNG = rogue.RNG;
1800     rogue.RNG = RNG_COSMETIC;
1801 
1802     foreRand = rand_range(0, cellForeColor->rand);
1803     backRand = rand_range(0, cellBackColor->rand);
1804     foreRed += rand_range(0, cellForeColor->redRand) + foreRand;
1805     foreGreen += rand_range(0, cellForeColor->greenRand) + foreRand;
1806     foreBlue += rand_range(0, cellForeColor->blueRand) + foreRand;
1807     backRed += rand_range(0, cellBackColor->redRand) + backRand;
1808     backGreen += rand_range(0, cellBackColor->greenRand) + backRand;
1809     backBlue += rand_range(0, cellBackColor->blueRand) + backRand;
1810 
1811     foreRed =       min(100, max(0, foreRed));
1812     foreGreen =     min(100, max(0, foreGreen));
1813     foreBlue =      min(100, max(0, foreBlue));
1814     backRed =       min(100, max(0, backRed));
1815     backGreen =     min(100, max(0, backGreen));
1816     backBlue =      min(100, max(0, backBlue));
1817 
1818     if (inputChar != ' '
1819         && foreRed      == backRed
1820         && foreGreen    == backGreen
1821         && foreBlue     == backBlue) {
1822 
1823         inputChar = ' ';
1824     }
1825 
1826     cellDisplayBuffer *target = &displayBuffer[xLoc][yLoc];
1827     target->character = inputChar;
1828     target->foreColorComponents[0] = foreRed;
1829     target->foreColorComponents[1] = foreGreen;
1830     target->foreColorComponents[2] = foreBlue;
1831     target->backColorComponents[0] = backRed;
1832     target->backColorComponents[1] = backGreen;
1833     target->backColorComponents[2] = backBlue;
1834 
1835     restoreRNG;
1836 }
1837 
plotCharToBuffer(enum displayGlyph inputChar,short x,short y,color * foreColor,color * backColor,cellDisplayBuffer dbuf[COLS][ROWS])1838 void plotCharToBuffer(enum displayGlyph inputChar, short x, short y, color *foreColor, color *backColor, cellDisplayBuffer dbuf[COLS][ROWS]) {
1839     short oldRNG;
1840 
1841     if (!dbuf) {
1842         plotCharWithColor(inputChar, x, y, foreColor, backColor);
1843         return;
1844     }
1845 
1846     brogueAssert(coordinatesAreInWindow(x, y));
1847 
1848     oldRNG = rogue.RNG;
1849     rogue.RNG = RNG_COSMETIC;
1850     //assureCosmeticRNG;
1851     dbuf[x][y].foreColorComponents[0] = foreColor->red + rand_range(0, foreColor->redRand) + rand_range(0, foreColor->rand);
1852     dbuf[x][y].foreColorComponents[1] = foreColor->green + rand_range(0, foreColor->greenRand) + rand_range(0, foreColor->rand);
1853     dbuf[x][y].foreColorComponents[2] = foreColor->blue + rand_range(0, foreColor->blueRand) + rand_range(0, foreColor->rand);
1854     dbuf[x][y].backColorComponents[0] = backColor->red + rand_range(0, backColor->redRand) + rand_range(0, backColor->rand);
1855     dbuf[x][y].backColorComponents[1] = backColor->green + rand_range(0, backColor->greenRand) + rand_range(0, backColor->rand);
1856     dbuf[x][y].backColorComponents[2] = backColor->blue + rand_range(0, backColor->blueRand) + rand_range(0, backColor->rand);
1857     dbuf[x][y].character = inputChar;
1858     dbuf[x][y].opacity = 100;
1859     restoreRNG;
1860 }
1861 
plotForegroundChar(enum displayGlyph inputChar,short x,short y,color * foreColor,boolean affectedByLighting)1862 void plotForegroundChar(enum displayGlyph inputChar, short x, short y, color *foreColor, boolean affectedByLighting) {
1863     color multColor, myColor, backColor, ignoredColor;
1864     enum displayGlyph ignoredChar;
1865 
1866     myColor = *foreColor;
1867     getCellAppearance(x, y, &ignoredChar, &ignoredColor, &backColor);
1868     if (affectedByLighting) {
1869         colorMultiplierFromDungeonLight(x, y, &multColor);
1870         applyColorMultiplier(&myColor, &multColor);
1871     }
1872     plotCharWithColor(inputChar, mapToWindowX(x), mapToWindowY(y), &myColor, &backColor);
1873 }
1874 
1875 // Debug feature: display the level to the screen without regard to lighting, field of view, etc.
dumpLevelToScreen()1876 void dumpLevelToScreen() {
1877     short i, j;
1878     pcell backup;
1879 
1880     assureCosmeticRNG;
1881     for (i=0; i<DCOLS; i++) {
1882         for (j=0; j<DROWS; j++) {
1883             if (pmap[i][j].layers[DUNGEON] != GRANITE
1884                 || (pmap[i][j].flags & DISCOVERED)) {
1885 
1886                 backup = pmap[i][j];
1887                 pmap[i][j].flags |= (VISIBLE | DISCOVERED);
1888                 tmap[i][j].light[0] = 100;
1889                 tmap[i][j].light[1] = 100;
1890                 tmap[i][j].light[2] = 100;
1891                 refreshDungeonCell(i, j);
1892                 pmap[i][j] = backup;
1893             } else {
1894                 plotCharWithColor(' ', mapToWindowX(i), mapToWindowY(j), &white, &black);
1895             }
1896 
1897         }
1898     }
1899     restoreRNG;
1900 }
1901 
1902 // To be used immediately after dumpLevelToScreen() above.
1903 // Highlight the portion indicated by hiliteCharGrid with the hiliteColor at the hiliteStrength -- both latter arguments are optional.
hiliteCharGrid(char hiliteCharGrid[DCOLS][DROWS],color * hiliteColor,short hiliteStrength)1904 void hiliteCharGrid(char hiliteCharGrid[DCOLS][DROWS], color *hiliteColor, short hiliteStrength) {
1905     short i, j, x, y;
1906     color hCol;
1907 
1908     assureCosmeticRNG;
1909 
1910     if (hiliteColor) {
1911         hCol = *hiliteColor;
1912     } else {
1913         hCol = yellow;
1914     }
1915 
1916     bakeColor(&hCol);
1917 
1918     if (!hiliteStrength) {
1919         hiliteStrength = 75;
1920     }
1921 
1922     for (i=0; i<DCOLS; i++) {
1923         for (j=0; j<DROWS; j++) {
1924             if (hiliteCharGrid[i][j]) {
1925                 x = mapToWindowX(i);
1926                 y = mapToWindowY(j);
1927 
1928                 displayBuffer[x][y].backColorComponents[0] = clamp(displayBuffer[x][y].backColorComponents[0] + hCol.red * hiliteStrength / 100, 0, 100);
1929                 displayBuffer[x][y].backColorComponents[1] = clamp(displayBuffer[x][y].backColorComponents[1] + hCol.green * hiliteStrength / 100, 0, 100);
1930                 displayBuffer[x][y].backColorComponents[2] = clamp(displayBuffer[x][y].backColorComponents[2] + hCol.blue * hiliteStrength / 100, 0, 100);
1931                 displayBuffer[x][y].foreColorComponents[0] = clamp(displayBuffer[x][y].foreColorComponents[0] + hCol.red * hiliteStrength / 100, 0, 100);
1932                 displayBuffer[x][y].foreColorComponents[1] = clamp(displayBuffer[x][y].foreColorComponents[1] + hCol.green * hiliteStrength / 100, 0, 100);
1933                 displayBuffer[x][y].foreColorComponents[2] = clamp(displayBuffer[x][y].foreColorComponents[2] + hCol.blue * hiliteStrength / 100, 0, 100);
1934             }
1935         }
1936     }
1937     restoreRNG;
1938 }
1939 
blackOutScreen()1940 void blackOutScreen() {
1941     short i, j;
1942 
1943     for (i=0; i<COLS; i++) {
1944         for (j=0; j<ROWS; j++) {
1945             plotCharWithColor(' ', i, j, &black, &black);
1946         }
1947     }
1948 }
1949 
colorOverDungeon(const color * color)1950 void colorOverDungeon(const color *color) {
1951     short i, j;
1952 
1953     for (i=0; i<DCOLS; i++) {
1954         for (j=0; j<DROWS; j++) {
1955             plotCharWithColor(' ', mapToWindowX(i), mapToWindowY(j), color, color);
1956         }
1957     }
1958 }
1959 
copyDisplayBuffer(cellDisplayBuffer toBuf[COLS][ROWS],cellDisplayBuffer fromBuf[COLS][ROWS])1960 void copyDisplayBuffer(cellDisplayBuffer toBuf[COLS][ROWS], cellDisplayBuffer fromBuf[COLS][ROWS]) {
1961     short i, j;
1962 
1963     for (i=0; i<COLS; i++) {
1964         for (j=0; j<ROWS; j++) {
1965             toBuf[i][j] = fromBuf[i][j];
1966         }
1967     }
1968 }
1969 
clearDisplayBuffer(cellDisplayBuffer dbuf[COLS][ROWS])1970 void clearDisplayBuffer(cellDisplayBuffer dbuf[COLS][ROWS]) {
1971     short i, j, k;
1972 
1973     for (i=0; i<COLS; i++) {
1974         for (j=0; j<ROWS; j++) {
1975             dbuf[i][j].character = ' ';
1976             for (k=0; k<3; k++) {
1977                 dbuf[i][j].foreColorComponents[k] = 0;
1978                 dbuf[i][j].backColorComponents[k] = 0;
1979             }
1980             dbuf[i][j].opacity = 0;
1981         }
1982     }
1983 }
1984 
colorFromComponents(char rgb[3])1985 color colorFromComponents(char rgb[3]) {
1986     color theColor = black;
1987     theColor.red    = rgb[0];
1988     theColor.green  = rgb[1];
1989     theColor.blue   = rgb[2];
1990     return theColor;
1991 }
1992 
1993 // draws overBuf over the current display with per-cell pseudotransparency as specified in overBuf.
1994 // If previousBuf is not null, it gets filled with the preexisting display for reversion purposes.
overlayDisplayBuffer(cellDisplayBuffer overBuf[COLS][ROWS],cellDisplayBuffer previousBuf[COLS][ROWS])1995 void overlayDisplayBuffer(cellDisplayBuffer overBuf[COLS][ROWS], cellDisplayBuffer previousBuf[COLS][ROWS]) {
1996     short i, j;
1997     color foreColor, backColor, tempColor;
1998     enum displayGlyph character;
1999 
2000     if (previousBuf) {
2001         copyDisplayBuffer(previousBuf, displayBuffer);
2002     }
2003 
2004     for (i=0; i<COLS; i++) {
2005         for (j=0; j<ROWS; j++) {
2006 
2007             if (overBuf[i][j].opacity != 0) {
2008                 backColor = colorFromComponents(overBuf[i][j].backColorComponents);
2009 
2010                 // character and fore color:
2011                 if (overBuf[i][j].character == ' ') { // Blank cells in the overbuf take the character from the screen.
2012                     character = displayBuffer[i][j].character;
2013                     foreColor = colorFromComponents(displayBuffer[i][j].foreColorComponents);
2014                     applyColorAverage(&foreColor, &backColor, overBuf[i][j].opacity);
2015                 } else {
2016                     character = overBuf[i][j].character;
2017                     foreColor = colorFromComponents(overBuf[i][j].foreColorComponents);
2018                 }
2019 
2020                 // back color:
2021                 tempColor = colorFromComponents(displayBuffer[i][j].backColorComponents);
2022                 applyColorAverage(&backColor, &tempColor, 100 - overBuf[i][j].opacity);
2023 
2024                 plotCharWithColor(character, i, j, &foreColor, &backColor);
2025             }
2026         }
2027     }
2028 }
2029 
2030 // Takes a list of locations, a color and a list of strengths and flashes the foregrounds of those locations.
2031 // Strengths are percentages measuring how hard the color flashes at its peak.
flashForeground(short * x,short * y,color ** flashColor,short * flashStrength,short count,short frames)2032 void flashForeground(short *x, short *y, color **flashColor, short *flashStrength, short count, short frames) {
2033     short i, j, percent;
2034     enum displayGlyph *displayChar;
2035     color *bColor, *fColor, newColor;
2036     short oldRNG;
2037 
2038     if (count <= 0) {
2039         return;
2040     }
2041 
2042     oldRNG = rogue.RNG;
2043     rogue.RNG = RNG_COSMETIC;
2044     //assureCosmeticRNG;
2045 
2046     displayChar = (enum displayGlyph *) malloc(count * sizeof(enum displayGlyph));
2047     fColor = (color *) malloc(count * sizeof(color));
2048     bColor = (color *) malloc(count * sizeof(color));
2049 
2050     for (i=0; i<count; i++) {
2051         getCellAppearance(x[i], y[i], &displayChar[i], &fColor[i], &bColor[i]);
2052         bakeColor(&fColor[i]);
2053         bakeColor(&bColor[i]);
2054     }
2055 
2056     for (j=frames; j>= 0; j--) {
2057         for (i=0; i<count; i++) {
2058             percent = flashStrength[i] * j / frames;
2059             newColor = fColor[i];
2060             applyColorAverage(&newColor, flashColor[i], percent);
2061             plotCharWithColor(displayChar[i], mapToWindowX(x[i]), mapToWindowY(y[i]), &newColor, &(bColor[i]));
2062         }
2063         if (j) {
2064             if (pauseBrogue(16)) {
2065                 j = 1;
2066             }
2067         }
2068     }
2069 
2070     free(displayChar);
2071     free(fColor);
2072     free(bColor);
2073 
2074     restoreRNG;
2075 }
2076 
flashCell(color * theColor,short frames,short x,short y)2077 void flashCell(color *theColor, short frames, short x, short y) {
2078     short i;
2079     boolean interrupted = false;
2080 
2081     for (i=0; i<frames && !interrupted; i++) {
2082         colorBlendCell(x, y, theColor, 100 - 100 * i / frames);
2083         interrupted = pauseBrogue(50);
2084     }
2085 
2086     refreshDungeonCell(x, y);
2087 }
2088 
2089 // special effect expanding flash of light at dungeon coordinates (x, y) restricted to tiles with matching flags
colorFlash(const color * theColor,unsigned long reqTerrainFlags,unsigned long reqTileFlags,short frames,short maxRadius,short x,short y)2090 void colorFlash(const color *theColor, unsigned long reqTerrainFlags,
2091                 unsigned long reqTileFlags, short frames, short maxRadius, short x, short y) {
2092     short i, j, k, intensity, currentRadius, fadeOut;
2093     short localRadius[DCOLS][DROWS];
2094     boolean tileQualifies[DCOLS][DROWS], aTileQualified, fastForward;
2095 
2096     aTileQualified = false;
2097     fastForward = false;
2098 
2099     for (i = max(x - maxRadius, 0); i <= min(x + maxRadius, DCOLS - 1); i++) {
2100         for (j = max(y - maxRadius, 0); j <= min(y + maxRadius, DROWS - 1); j++) {
2101             if ((!reqTerrainFlags || cellHasTerrainFlag(reqTerrainFlags, i, j))
2102                 && (!reqTileFlags || (pmap[i][j].flags & reqTileFlags))
2103                 && (i-x) * (i-x) + (j-y) * (j-y) <= maxRadius * maxRadius) {
2104 
2105                 tileQualifies[i][j] = true;
2106                 localRadius[i][j] = fp_sqrt(((i-x) * (i-x) + (j-y) * (j-y)) * FP_FACTOR) / FP_FACTOR;
2107                 aTileQualified = true;
2108             } else {
2109                 tileQualifies[i][j] = false;
2110             }
2111         }
2112     }
2113 
2114     if (!aTileQualified) {
2115         return;
2116     }
2117 
2118     for (k = 1; k <= frames; k++) {
2119         currentRadius = max(1, maxRadius * k / frames);
2120         fadeOut = min(100, (frames - k) * 100 * 5 / frames);
2121         for (i = max(x - maxRadius, 0); i <= min(x + maxRadius, DCOLS - 1); i++) {
2122             for (j = max(y - maxRadius, 0); j <= min(y + maxRadius, DROWS - 1); j++) {
2123                 if (tileQualifies[i][j] && (localRadius[i][j] <= currentRadius)) {
2124 
2125                     intensity = 100 - 100 * (currentRadius - localRadius[i][j] - 2) / currentRadius;
2126                     intensity = fadeOut * intensity / 100;
2127 
2128                     hiliteCell(i, j, theColor, intensity, false);
2129                 }
2130             }
2131         }
2132         if (!fastForward && (rogue.playbackFastForward || pauseBrogue(50))) {
2133             k = frames - 1;
2134             fastForward = true;
2135         }
2136     }
2137 }
2138 
2139 #define bCurve(x)   (((x) * (x) + 11) / (10 * ((x) * (x) + 1)) - 0.1)
2140 
2141 // x and y are global coordinates, not within the playing square
funkyFade(cellDisplayBuffer displayBuf[COLS][ROWS],const color * colorStart,const color * colorEnd,short stepCount,short x,short y,boolean invert)2142 void funkyFade(cellDisplayBuffer displayBuf[COLS][ROWS], const color *colorStart,
2143                const color *colorEnd, short stepCount, short x, short y, boolean invert) {
2144     short i, j, n, weight;
2145     double x2, y2, weightGrid[COLS][ROWS][3], percentComplete;
2146     color tempColor, colorMid, foreColor, backColor;
2147     enum displayGlyph tempChar;
2148     short **distanceMap;
2149     boolean fastForward;
2150 
2151     assureCosmeticRNG;
2152 
2153     fastForward = false;
2154     distanceMap = allocGrid();
2155     fillGrid(distanceMap, 0);
2156     calculateDistances(distanceMap, player.xLoc, player.yLoc, T_OBSTRUCTS_PASSABILITY, 0, true, true);
2157 
2158     for (i=0; i<COLS; i++) {
2159         x2 = (double) ((i - x) * 5.0 / COLS);
2160         for (j=0; j<ROWS; j++) {
2161             y2 = (double) ((j - y) * 2.5 / ROWS);
2162 
2163             weightGrid[i][j][0] = bCurve(x2*x2+y2*y2) * (.7 + .3 * cos(5*x2*x2) * cos(5*y2*y2));
2164             weightGrid[i][j][1] = bCurve(x2*x2+y2*y2) * (.7 + .3 * sin(5*x2*x2) * cos(5*y2*y2));
2165             weightGrid[i][j][2] = bCurve(x2*x2+y2*y2);
2166         }
2167     }
2168 
2169     for (n=(invert ? stepCount - 1 : 0); (invert ? n >= 0 : n <= stepCount); n += (invert ? -1 : 1)) {
2170         for (i=0; i<COLS; i++) {
2171             for (j=0; j<ROWS; j++) {
2172 
2173                 percentComplete = (double) (n) * 100 / stepCount;
2174 
2175                 colorMid = *colorStart;
2176                 if (colorEnd) {
2177                     applyColorAverage(&colorMid, colorEnd, n * 100 / stepCount);
2178                 }
2179 
2180                 // the fade color floods the reachable dungeon tiles faster
2181                 if (!invert && coordinatesAreInMap(windowToMapX(i), windowToMapY(j))
2182                     && distanceMap[windowToMapX(i)][windowToMapY(j)] >= 0 && distanceMap[windowToMapX(i)][windowToMapY(j)] < 30000) {
2183                     percentComplete *= 1.0 + (100.0 - min(100, distanceMap[windowToMapX(i)][windowToMapY(j)])) / 100.;
2184                 }
2185 
2186                 weight = (short)(percentComplete + weightGrid[i][j][2] * percentComplete * 10);
2187                 weight = min(100, weight);
2188                 tempColor = black;
2189 
2190                 tempColor.red = (short)(percentComplete + weightGrid[i][j][0] * percentComplete * 10) * colorMid.red / 100;
2191                 tempColor.red = min(colorMid.red, tempColor.red);
2192 
2193                 tempColor.green = (short)(percentComplete + weightGrid[i][j][1] * percentComplete * 10) * colorMid.green / 100;
2194                 tempColor.green = min(colorMid.green, tempColor.green);
2195 
2196                 tempColor.blue = (short)(percentComplete + weightGrid[i][j][2] * percentComplete * 10) * colorMid.blue / 100;
2197                 tempColor.blue = min(colorMid.blue, tempColor.blue);
2198 
2199                 backColor = black;
2200 
2201                 backColor.red = displayBuf[i][j].backColorComponents[0];
2202                 backColor.green = displayBuf[i][j].backColorComponents[1];
2203                 backColor.blue = displayBuf[i][j].backColorComponents[2];
2204 
2205                 foreColor = (invert ? white : black);
2206 
2207                 if (j == (MESSAGE_LINES - 1)
2208                     && i >= mapToWindowX(0)
2209                     && i < mapToWindowX(strLenWithoutEscapes(displayedMessage[MESSAGE_LINES - j - 1]))) {
2210                     tempChar = displayedMessage[MESSAGE_LINES - j - 1][windowToMapX(i)];
2211                 } else {
2212                     tempChar = displayBuf[i][j].character;
2213 
2214                     foreColor.red = displayBuf[i][j].foreColorComponents[0];
2215                     foreColor.green = displayBuf[i][j].foreColorComponents[1];
2216                     foreColor.blue = displayBuf[i][j].foreColorComponents[2];
2217 
2218                     applyColorAverage(&foreColor, &tempColor, weight);
2219                 }
2220                 applyColorAverage(&backColor, &tempColor, weight);
2221                 plotCharWithColor(tempChar, i, j, &foreColor, &backColor);
2222             }
2223         }
2224         if (!fastForward && pauseBrogue(16)) {
2225             // drop the event - skipping the transition should only skip the transition
2226             rogueEvent event;
2227             nextKeyOrMouseEvent(&event, false, false);
2228             fastForward = true;
2229             n = (invert ? 1 : stepCount - 2);
2230         }
2231     }
2232 
2233     freeGrid(distanceMap);
2234 
2235     restoreRNG;
2236 }
2237 
displayWaypoints()2238 void displayWaypoints() {
2239     short i, j, w, lowestDistance;
2240 
2241     for (i=0; i<DCOLS; i++) {
2242         for (j=0; j<DROWS; j++) {
2243             lowestDistance = 30000;
2244             for (w=0; w<rogue.wpCount; w++) {
2245                 if (rogue.wpDistance[w][i][j] < lowestDistance) {
2246                     lowestDistance = rogue.wpDistance[w][i][j];
2247                 }
2248             }
2249             if (lowestDistance < 10) {
2250                 hiliteCell(i, j, &white, clamp(100 - lowestDistance*15, 0, 100), true);
2251             }
2252         }
2253     }
2254     temporaryMessage("Waypoints:", REQUIRE_ACKNOWLEDGMENT);
2255 }
2256 
displayMachines()2257 void displayMachines() {
2258     short i, j;
2259     color foreColor, backColor, machineColors[50];
2260     enum displayGlyph dchar;
2261 
2262     assureCosmeticRNG;
2263 
2264     for (i=0; i<50; i++) {
2265         machineColors[i] = black;
2266         machineColors[i].red = rand_range(0, 100);
2267         machineColors[i].green = rand_range(0, 100);
2268         machineColors[i].blue = rand_range(0, 100);
2269     }
2270 
2271     for (i=0; i<DCOLS; i++) {
2272         for (j=0; j<DROWS; j++) {
2273             if (pmap[i][j].machineNumber) {
2274                 getCellAppearance(i, j, &dchar, &foreColor, &backColor);
2275                 applyColorAugment(&backColor, &(machineColors[pmap[i][j].machineNumber]), 50);
2276                 //plotCharWithColor(dchar, mapToWindowX(i), mapToWindowY(j), &foreColor, &backColor);
2277                 if (pmap[i][j].machineNumber < 10) {
2278                     dchar ='0' + pmap[i][j].machineNumber;
2279                 } else if (pmap[i][j].machineNumber < 10 + 26) {
2280                     dchar = 'a' + pmap[i][j].machineNumber - 10;
2281                 } else {
2282                     dchar = 'A' + pmap[i][j].machineNumber - 10 - 26;
2283                 }
2284                 plotCharWithColor(dchar, mapToWindowX(i), mapToWindowY(j), &foreColor, &backColor);
2285             }
2286         }
2287     }
2288     displayMoreSign();
2289     displayLevel();
2290 
2291     restoreRNG;
2292 }
2293 
2294 #define CHOKEMAP_DISPLAY_CUTOFF 160
displayChokeMap()2295 void displayChokeMap() {
2296     short i, j;
2297     color foreColor, backColor;
2298     enum displayGlyph dchar;
2299 
2300     for (i=0; i<DCOLS; i++) {
2301         for (j=0; j<DROWS; j++) {
2302             if (chokeMap[i][j] < CHOKEMAP_DISPLAY_CUTOFF) {
2303                 if (pmap[i][j].flags & IS_GATE_SITE) {
2304                     getCellAppearance(i, j, &dchar, &foreColor, &backColor);
2305                     applyColorAugment(&backColor, &teal, 50);
2306                     plotCharWithColor(dchar, mapToWindowX(i), mapToWindowY(j), &foreColor, &backColor);
2307                 } else
2308                     if (chokeMap[i][j] < CHOKEMAP_DISPLAY_CUTOFF) {
2309                     getCellAppearance(i, j, &dchar, &foreColor, &backColor);
2310                     applyColorAugment(&backColor, &red, 100 - chokeMap[i][j] * 100 / CHOKEMAP_DISPLAY_CUTOFF);
2311                     plotCharWithColor(dchar, mapToWindowX(i), mapToWindowY(j), &foreColor, &backColor);
2312                 }
2313             }
2314         }
2315     }
2316     displayMoreSign();
2317     displayLevel();
2318 }
2319 
displayLoops()2320 void displayLoops() {
2321     short i, j;
2322     color foreColor, backColor;
2323     enum displayGlyph dchar;
2324 
2325     for (i=0; i<DCOLS; i++) {
2326         for (j=0; j<DROWS; j++) {
2327             if (pmap[i][j].flags & IN_LOOP) {
2328                 getCellAppearance(i, j, &dchar, &foreColor, &backColor);
2329                 applyColorAugment(&backColor, &yellow, 50);
2330                 plotCharWithColor(dchar, mapToWindowX(i), mapToWindowY(j), &foreColor, &backColor);
2331                 //colorBlendCell(i, j, &tempColor, 100);//hiliteCell(i, j, &tempColor, 100, true);
2332             }
2333             if (pmap[i][j].flags & IS_CHOKEPOINT) {
2334                 getCellAppearance(i, j, &dchar, &foreColor, &backColor);
2335                 applyColorAugment(&backColor, &teal, 50);
2336                 plotCharWithColor(dchar, mapToWindowX(i), mapToWindowY(j), &foreColor, &backColor);
2337             }
2338         }
2339     }
2340     waitForAcknowledgment();
2341 }
2342 
exploreKey(const boolean controlKey)2343 void exploreKey(const boolean controlKey) {
2344     short x, y, finalX = 0, finalY = 0;
2345     short **exploreMap;
2346     enum directions dir;
2347     boolean tooDark = false;
2348 
2349     // fight any adjacent enemies first
2350     dir = adjacentFightingDir();
2351     if (dir == NO_DIRECTION) {
2352         for (dir = 0; dir < DIRECTION_COUNT; dir++) {
2353             x = player.xLoc + nbDirs[dir][0];
2354             y = player.yLoc + nbDirs[dir][1];
2355             if (coordinatesAreInMap(x, y)
2356                 && !(pmap[x][y].flags & DISCOVERED)) {
2357 
2358                 tooDark = true;
2359                 break;
2360             }
2361         }
2362         if (!tooDark) {
2363             x = finalX = player.xLoc;
2364             y = finalY = player.yLoc;
2365 
2366             exploreMap = allocGrid();
2367             getExploreMap(exploreMap, false);
2368             do {
2369                 dir = nextStep(exploreMap, x, y, NULL, false);
2370                 if (dir != NO_DIRECTION) {
2371                     x += nbDirs[dir][0];
2372                     y += nbDirs[dir][1];
2373                     if (pmap[x][y].flags & (DISCOVERED | MAGIC_MAPPED)) {
2374                         finalX = x;
2375                         finalY = y;
2376                     }
2377                 }
2378             } while (dir != NO_DIRECTION);
2379             freeGrid(exploreMap);
2380         }
2381     } else {
2382         x = finalX = player.xLoc + nbDirs[dir][0];
2383         y = finalY = player.yLoc + nbDirs[dir][1];
2384     }
2385 
2386     if (tooDark) {
2387         message("It's too dark to explore!", 0);
2388     } else if (x == player.xLoc && y == player.yLoc) {
2389         message("I see no path for further exploration.", 0);
2390     } else if (proposeOrConfirmLocation(finalX, finalY, "I see no path for further exploration.")) {
2391         explore(controlKey ? 1 : 20); // Do the exploring until interrupted.
2392         hideCursor();
2393         exploreKey(controlKey);
2394     }
2395 }
2396 
pauseBrogue(short milliseconds)2397 boolean pauseBrogue(short milliseconds) {
2398     boolean interrupted;
2399 
2400     commitDraws();
2401     if (rogue.playbackMode && rogue.playbackFastForward) {
2402         interrupted = true;
2403     } else {
2404         interrupted = pauseForMilliseconds(milliseconds);
2405     }
2406     return interrupted;
2407 }
2408 
nextBrogueEvent(rogueEvent * returnEvent,boolean textInput,boolean colorsDance,boolean realInputEvenInPlayback)2409 void nextBrogueEvent(rogueEvent *returnEvent, boolean textInput, boolean colorsDance, boolean realInputEvenInPlayback) {
2410     rogueEvent recordingInput;
2411     boolean repeatAgain, interaction;
2412     short pauseDuration;
2413 
2414     returnEvent->eventType = EVENT_ERROR;
2415 
2416     if (rogue.playbackMode && !realInputEvenInPlayback) {
2417         do {
2418             repeatAgain = false;
2419             if ((!rogue.playbackFastForward && rogue.playbackBetweenTurns)
2420                 || rogue.playbackOOS) {
2421 
2422                 pauseDuration = (rogue.playbackPaused ? DEFAULT_PLAYBACK_DELAY : rogue.playbackDelayThisTurn);
2423                 if (pauseDuration && pauseBrogue(pauseDuration)) {
2424                     // if the player did something during playback
2425                     nextBrogueEvent(&recordingInput, false, false, true);
2426                     interaction = executePlaybackInput(&recordingInput);
2427                     repeatAgain = !rogue.playbackPaused && interaction;
2428                 }
2429             }
2430         } while ((repeatAgain || rogue.playbackOOS) && !rogue.gameHasEnded);
2431         rogue.playbackDelayThisTurn = rogue.playbackDelayPerTurn;
2432         recallEvent(returnEvent);
2433     } else {
2434         commitDraws();
2435         if (rogue.creaturesWillFlashThisTurn) {
2436             displayMonsterFlashes(true);
2437         }
2438         do {
2439             nextKeyOrMouseEvent(returnEvent, textInput, colorsDance); // No mouse clicks outside of the window will register.
2440         } while (returnEvent->eventType == MOUSE_UP && !coordinatesAreInWindow(returnEvent->param1, returnEvent->param2));
2441         // recording done elsewhere
2442     }
2443 
2444     if (returnEvent->eventType == EVENT_ERROR) {
2445         rogue.playbackPaused = rogue.playbackMode; // pause if replaying
2446         message("Event error!", REQUIRE_ACKNOWLEDGMENT);
2447     }
2448 }
2449 
executeMouseClick(rogueEvent * theEvent)2450 void executeMouseClick(rogueEvent *theEvent) {
2451     short x, y;
2452     boolean autoConfirm;
2453     x = theEvent->param1;
2454     y = theEvent->param2;
2455     autoConfirm = theEvent->controlKey;
2456 
2457     if (theEvent->eventType == RIGHT_MOUSE_UP) {
2458         displayInventory(ALL_ITEMS, 0, 0, true, true);
2459     } else if (coordinatesAreInMap(windowToMapX(x), windowToMapY(y))) {
2460         if (autoConfirm) {
2461             travel(windowToMapX(x), windowToMapY(y), autoConfirm);
2462         } else {
2463             rogue.cursorLoc[0] = windowToMapX(x);
2464             rogue.cursorLoc[1] = windowToMapY(y);
2465             mainInputLoop();
2466         }
2467 
2468     } else if (windowToMapX(x) >= 0 && windowToMapX(x) < DCOLS && y >= 0 && y < MESSAGE_LINES) {
2469         // If the click location is in the message block, display the message archive.
2470         displayMessageArchive();
2471     }
2472 }
2473 
executeKeystroke(signed long keystroke,boolean controlKey,boolean shiftKey)2474 void executeKeystroke(signed long keystroke, boolean controlKey, boolean shiftKey) {
2475     char path[BROGUE_FILENAME_MAX];
2476     short direction = -1;
2477 
2478     confirmMessages();
2479     stripShiftFromMovementKeystroke(&keystroke);
2480 
2481     switch (keystroke) {
2482         case UP_KEY:
2483         case UP_ARROW:
2484         case NUMPAD_8:
2485             direction = UP;
2486             break;
2487         case DOWN_KEY:
2488         case DOWN_ARROW:
2489         case NUMPAD_2:
2490             direction = DOWN;
2491             break;
2492         case LEFT_KEY:
2493         case LEFT_ARROW:
2494         case NUMPAD_4:
2495             direction = LEFT;
2496             break;
2497         case RIGHT_KEY:
2498         case RIGHT_ARROW:
2499         case NUMPAD_6:
2500             direction = RIGHT;
2501             break;
2502         case NUMPAD_7:
2503         case UPLEFT_KEY:
2504             direction = UPLEFT;
2505             break;
2506         case UPRIGHT_KEY:
2507         case NUMPAD_9:
2508             direction = UPRIGHT;
2509             break;
2510         case DOWNLEFT_KEY:
2511         case NUMPAD_1:
2512             direction = DOWNLEFT;
2513             break;
2514         case DOWNRIGHT_KEY:
2515         case NUMPAD_3:
2516             direction = DOWNRIGHT;
2517             break;
2518         case DESCEND_KEY:
2519             considerCautiousMode();
2520             if (D_WORMHOLING) {
2521                 recordKeystroke(DESCEND_KEY, false, false);
2522                 useStairs(1);
2523             } else if (proposeOrConfirmLocation(rogue.downLoc[0], rogue.downLoc[1], "I see no way down.")) {
2524                 travel(rogue.downLoc[0], rogue.downLoc[1], true);
2525             }
2526             break;
2527         case ASCEND_KEY:
2528             considerCautiousMode();
2529             if (D_WORMHOLING) {
2530                 recordKeystroke(ASCEND_KEY, false, false);
2531                 useStairs(-1);
2532             } else if (proposeOrConfirmLocation(rogue.upLoc[0], rogue.upLoc[1], "I see no way up.")) {
2533                 travel(rogue.upLoc[0], rogue.upLoc[1], true);
2534             }
2535             break;
2536         case RETURN_KEY:
2537             showCursor();
2538             break;
2539         case REST_KEY:
2540         case PERIOD_KEY:
2541         case NUMPAD_5:
2542             considerCautiousMode();
2543             rogue.justRested = true;
2544             recordKeystroke(REST_KEY, false, false);
2545             playerTurnEnded();
2546             break;
2547         case AUTO_REST_KEY:
2548             rogue.justRested = true;
2549             autoRest();
2550             break;
2551         case SEARCH_KEY:
2552             if (controlKey) {
2553                 rogue.disturbed = false;
2554                 rogue.automationActive = true;
2555                 do {
2556                     manualSearch();
2557                     if (pauseBrogue(80)) {
2558                         rogue.disturbed = true;
2559                     }
2560                 } while (player.status[STATUS_SEARCHING] < 5 && !rogue.disturbed);
2561                 rogue.automationActive = false;
2562             } else {
2563                 manualSearch();
2564             }
2565             break;
2566         case INVENTORY_KEY:
2567             displayInventory(ALL_ITEMS, 0, 0, true, true);
2568             break;
2569         case EQUIP_KEY:
2570             equip(NULL);
2571             break;
2572         case UNEQUIP_KEY:
2573             unequip(NULL);
2574             break;
2575         case DROP_KEY:
2576             drop(NULL);
2577             break;
2578         case APPLY_KEY:
2579             apply(NULL, true);
2580             break;
2581         case THROW_KEY:
2582             throwCommand(NULL, false);
2583             break;
2584         case RETHROW_KEY:
2585             if (rogue.lastItemThrown != NULL && itemIsCarried(rogue.lastItemThrown)) {
2586                 throwCommand(rogue.lastItemThrown, true);
2587             }
2588             break;
2589         case RELABEL_KEY:
2590             relabel(NULL);
2591             break;
2592         case SWAP_KEY:
2593             swapLastEquipment();
2594             break;
2595         case TRUE_COLORS_KEY:
2596             rogue.trueColorMode = !rogue.trueColorMode;
2597             displayLevel();
2598             refreshSideBar(-1, -1, false);
2599             if (rogue.trueColorMode) {
2600                 messageWithColor(KEYBOARD_LABELS ? "Color effects disabled. Press '\\' again to enable." : "Color effects disabled.",
2601                                  &teal, 0);
2602             } else {
2603                 messageWithColor(KEYBOARD_LABELS ? "Color effects enabled. Press '\\' again to disable." : "Color effects enabled.",
2604                                  &teal, 0);
2605             }
2606             break;
2607         case AGGRO_DISPLAY_KEY:
2608             rogue.displayAggroRangeMode = !rogue.displayAggroRangeMode;
2609             displayLevel();
2610             refreshSideBar(-1, -1, false);
2611             if (rogue.displayAggroRangeMode) {
2612                 messageWithColor(KEYBOARD_LABELS ? "Stealth range displayed. Press ']' again to hide." : "Stealth range displayed.",
2613                                  &teal, 0);
2614             } else {
2615                 messageWithColor(KEYBOARD_LABELS ? "Stealth range hidden. Press ']' again to display." : "Stealth range hidden.",
2616                                  &teal, 0);
2617             }
2618             break;
2619         case CALL_KEY:
2620             call(NULL);
2621             break;
2622         case EXPLORE_KEY:
2623             considerCautiousMode();
2624             exploreKey(controlKey);
2625             break;
2626         case AUTOPLAY_KEY:
2627             if (confirm("Turn on autopilot?", false)) {
2628                 autoPlayLevel(controlKey);
2629             }
2630             break;
2631         case MESSAGE_ARCHIVE_KEY:
2632             displayMessageArchive();
2633             break;
2634         case BROGUE_HELP_KEY:
2635             printHelpScreen();
2636             break;
2637         case DISCOVERIES_KEY:
2638             printDiscoveriesScreen();
2639             break;
2640         case VIEW_RECORDING_KEY:
2641             if (rogue.playbackMode || serverMode) {
2642                 return;
2643             }
2644             confirmMessages();
2645             if ((rogue.playerTurnNumber < 50 || confirm("End this game and view a recording?", false))
2646                 && dialogChooseFile(path, RECORDING_SUFFIX, "View recording: ")) {
2647                 if (fileExists(path)) {
2648                     strcpy(rogue.nextGamePath, path);
2649                     rogue.nextGame = NG_VIEW_RECORDING;
2650                     rogue.gameHasEnded = true;
2651                 } else {
2652                     message("File not found.", 0);
2653                 }
2654             }
2655             break;
2656         case LOAD_SAVED_GAME_KEY:
2657             if (rogue.playbackMode || serverMode) {
2658                 return;
2659             }
2660             confirmMessages();
2661             if ((rogue.playerTurnNumber < 50 || confirm("End this game and load a saved game?", false))
2662                 && dialogChooseFile(path, GAME_SUFFIX, "Open saved game: ")) {
2663                 if (fileExists(path)) {
2664                     strcpy(rogue.nextGamePath, path);
2665                     rogue.nextGame = NG_OPEN_GAME;
2666                     rogue.gameHasEnded = true;
2667                 } else {
2668                     message("File not found.", 0);
2669                 }
2670             }
2671             break;
2672         case SAVE_GAME_KEY:
2673             if (rogue.playbackMode || serverMode) {
2674                 return;
2675             }
2676             if (confirm("Suspend this game?", false)) {
2677                 saveGame();
2678             }
2679             break;
2680         case NEW_GAME_KEY:
2681             if (rogue.playerTurnNumber < 50 || confirm("End this game and begin a new game?", false)) {
2682                 rogue.nextGame = NG_NEW_GAME;
2683                 rogue.gameHasEnded = true;
2684             }
2685             break;
2686         case QUIT_KEY:
2687             if (confirm("Quit this game without saving?", false)) {
2688                 recordKeystroke(QUIT_KEY, false, false);
2689                 rogue.quit = true;
2690                 gameOver("Quit", true);
2691             }
2692             break;
2693         case GRAPHICS_KEY:
2694             if (hasGraphics) {
2695                 graphicsMode = setGraphicsMode((graphicsMode + 1) % 3);
2696                 switch (graphicsMode) {
2697                     case TEXT_GRAPHICS:
2698                         messageWithColor(KEYBOARD_LABELS
2699                             ? "Switched to text mode. Press 'G' again to enable tiles."
2700                             : "Switched to text mode.", &teal, 0);
2701                         break;
2702                     case TILES_GRAPHICS:
2703                         messageWithColor(KEYBOARD_LABELS
2704                             ? "Switched to graphical tiles. Press 'G' again to enable hybrid mode."
2705                             : "Switched to graphical tiles.", &teal, 0);
2706                         break;
2707                     case HYBRID_GRAPHICS:
2708                         messageWithColor(KEYBOARD_LABELS
2709                             ? "Switched to hybrid mode. Press 'G' again to disable tiles."
2710                             : "Switched to hybrid mode.", &teal, 0);
2711                         break;
2712                 }
2713             }
2714             break;
2715         case SEED_KEY:
2716             /*DEBUG {
2717                 cellDisplayBuffer dbuf[COLS][ROWS];
2718                 copyDisplayBuffer(dbuf, displayBuffer);
2719                 funkyFade(dbuf, &white, 0, 100, mapToWindowX(player.xLoc), mapToWindowY(player.yLoc), false);
2720             }*/
2721             // DEBUG displayLoops();
2722             // DEBUG displayChokeMap();
2723             DEBUG displayMachines();
2724             //DEBUG displayWaypoints();
2725             // DEBUG {displayGrid(safetyMap); displayMoreSign(); displayLevel();}
2726             // parseFile();
2727             // DEBUG spawnDungeonFeature(player.xLoc, player.yLoc, &dungeonFeatureCatalog[DF_METHANE_GAS_ARMAGEDDON], true, false);
2728             printSeed();
2729             break;
2730         case EASY_MODE_KEY:
2731             //if (shiftKey) {
2732                 enableEasyMode();
2733             //}
2734             break;
2735         case PRINTSCREEN_KEY:
2736             if (takeScreenshot()) {
2737                 flashTemporaryAlert(" Screenshot saved in save directory ", 2000);
2738             }
2739             break;
2740         default:
2741             break;
2742     }
2743     if (direction >= 0) { // if it was a movement command
2744         hideCursor();
2745         considerCautiousMode();
2746         if (controlKey || shiftKey) {
2747             playerRuns(direction);
2748         } else {
2749             playerMoves(direction);
2750         }
2751         refreshSideBar(-1, -1, false);
2752     }
2753 
2754     if (D_SAFETY_VISION) {
2755         displayGrid(safetyMap);
2756     }
2757     if (rogue.trueColorMode || D_SCENT_VISION) {
2758         displayLevel();
2759     }
2760 
2761     rogue.cautiousMode = false;
2762 }
2763 
getInputTextString(char * inputText,const char * prompt,short maxLength,const char * defaultEntry,const char * promptSuffix,short textEntryType,boolean useDialogBox)2764 boolean getInputTextString(char *inputText,
2765                            const char *prompt,
2766                            short maxLength,
2767                            const char *defaultEntry,
2768                            const char *promptSuffix,
2769                            short textEntryType,
2770                            boolean useDialogBox) {
2771     short charNum, i, x, y;
2772     char keystroke, suffix[100];
2773     const short textEntryBounds[TEXT_INPUT_TYPES][2] = {{' ', '~'}, {' ', '~'}, {'0', '9'}};
2774     cellDisplayBuffer dbuf[COLS][ROWS], rbuf[COLS][ROWS];
2775 
2776     // x and y mark the origin for text entry.
2777     if (useDialogBox) {
2778         x = (COLS - max(maxLength, strLenWithoutEscapes(prompt))) / 2;
2779         y = ROWS / 2 - 1;
2780         clearDisplayBuffer(dbuf);
2781         rectangularShading(x - 1, y - 2, max(maxLength, strLenWithoutEscapes(prompt)) + 2,
2782                            4, &interfaceBoxColor, INTERFACE_OPACITY, dbuf);
2783         overlayDisplayBuffer(dbuf, rbuf);
2784         printString(prompt, x, y - 1, &white, &interfaceBoxColor, NULL);
2785         for (i=0; i<maxLength; i++) {
2786             plotCharWithColor(' ', x + i, y, &black, &black);
2787         }
2788         printString(defaultEntry, x, y, &white, &black, 0);
2789     } else {
2790         confirmMessages();
2791         x = mapToWindowX(strLenWithoutEscapes(prompt));
2792         y = MESSAGE_LINES - 1;
2793         temporaryMessage(prompt, 0);
2794         printString(defaultEntry, x, y, &white, &black, 0);
2795     }
2796 
2797     maxLength = min(maxLength, COLS - x);
2798 
2799 
2800     strcpy(inputText, defaultEntry);
2801     charNum = strLenWithoutEscapes(inputText);
2802     for (i = charNum; i < maxLength; i++) {
2803         inputText[i] = ' ';
2804     }
2805 
2806     if (promptSuffix[0] == '\0') { // empty suffix
2807         strcpy(suffix, " "); // so that deleting doesn't leave a white trail
2808     } else {
2809         strcpy(suffix, promptSuffix);
2810     }
2811 
2812     do {
2813         printString(suffix, charNum + x, y, &gray, &black, 0);
2814         plotCharWithColor((suffix[0] ? suffix[0] : ' '), x + charNum, y, &black, &white);
2815         keystroke = nextKeyPress(true);
2816         if (keystroke == DELETE_KEY && charNum > 0) {
2817             printString(suffix, charNum + x - 1, y, &gray, &black, 0);
2818             plotCharWithColor(' ', x + charNum + strlen(suffix) - 1, y, &black, &black);
2819             charNum--;
2820             inputText[charNum] = ' ';
2821         } else if (keystroke >= textEntryBounds[textEntryType][0]
2822                    && keystroke <= textEntryBounds[textEntryType][1]) { // allow only permitted input
2823 
2824             if (textEntryType == TEXT_INPUT_FILENAME
2825                 && characterForbiddenInFilename(keystroke)) {
2826 
2827                 keystroke = '-';
2828             }
2829 
2830             inputText[charNum] = keystroke;
2831             plotCharWithColor(keystroke, x + charNum, y, &white, &black);
2832             printString(suffix, charNum + x + 1, y, &gray, &black, 0);
2833             if (charNum < maxLength) {
2834                 charNum++;
2835             }
2836         }
2837 #ifdef USE_CLIPBOARD
2838         else if (keystroke == TAB_KEY) {
2839             char* clipboard = getClipboard();
2840             for (int i=0; i<(int) min(strlen(clipboard), (unsigned long) (maxLength - charNum)); ++i) {
2841 
2842                 char character = clipboard[i];
2843 
2844                 if (character >= textEntryBounds[textEntryType][0]
2845                     && character <= textEntryBounds[textEntryType][1]) { // allow only permitted input
2846                     if (textEntryType == TEXT_INPUT_FILENAME
2847                         && characterForbiddenInFilename(character)) {
2848                         character = '-';
2849                     }
2850                     plotCharWithColor(character, x + charNum, y, &white, &black);
2851                     if (charNum < maxLength) {
2852                         charNum++;
2853                     }
2854                 }
2855             }
2856         }
2857 #endif
2858     } while (keystroke != RETURN_KEY && keystroke != ESCAPE_KEY);
2859 
2860     if (useDialogBox) {
2861         overlayDisplayBuffer(rbuf, NULL);
2862     }
2863 
2864     inputText[charNum] = '\0';
2865 
2866     if (keystroke == ESCAPE_KEY) {
2867         return false;
2868     }
2869     strcat(displayedMessage[0], inputText);
2870     strcat(displayedMessage[0], suffix);
2871     return true;
2872 }
2873 
displayCenteredAlert(char * message)2874 void displayCenteredAlert(char *message) {
2875     printString(message, (COLS - strLenWithoutEscapes(message)) / 2, ROWS / 2, &teal, &black, 0);
2876 }
2877 
2878 // Flashes a message on the screen starting at (x, y) lasting for the given time (in ms) and with the given colors.
flashMessage(char * message,short x,short y,int time,color * fColor,color * bColor)2879 void flashMessage(char *message, short x, short y, int time, color *fColor, color *bColor) {
2880     boolean fastForward;
2881     int     i, j, messageLength, percentComplete, previousPercentComplete;
2882     color backColors[COLS], backColor, foreColor;
2883     cellDisplayBuffer dbufs[COLS];
2884     enum displayGlyph dchar;
2885     short oldRNG;
2886     const int stepInMs = 16;
2887 
2888     if (rogue.playbackFastForward) {
2889         return;
2890     }
2891 
2892     oldRNG = rogue.RNG;
2893     rogue.RNG = RNG_COSMETIC;
2894     //assureCosmeticRNG;
2895 
2896     messageLength = strLenWithoutEscapes(message);
2897     fastForward = false;
2898 
2899     for (j=0; j<messageLength; j++) {
2900         backColors[j] = colorFromComponents(displayBuffer[j + x][y].backColorComponents);
2901         dbufs[j] = displayBuffer[j + x][y];
2902     }
2903 
2904     previousPercentComplete = -1;
2905     for (i=0; i < time && fastForward == false; i += stepInMs) {
2906         percentComplete = 100 * i / time;
2907         percentComplete = percentComplete * percentComplete / 100; // transition is front-loaded
2908         if (previousPercentComplete != percentComplete) {
2909             for (j=0; j<messageLength; j++) {
2910                 if (i==0) {
2911                     backColors[j] = colorFromComponents(displayBuffer[j + x][y].backColorComponents);
2912                     dbufs[j] = displayBuffer[j + x][y];
2913                 }
2914                 backColor = backColors[j];
2915                 applyColorAverage(&backColor, bColor, 100 - percentComplete);
2916                 if (percentComplete < 50) {
2917                     dchar = message[j];
2918                     foreColor = *fColor;
2919                     applyColorAverage(&foreColor, &backColor, percentComplete * 2);
2920                 } else {
2921                     dchar = dbufs[j].character;
2922                     foreColor = colorFromComponents(dbufs[j].foreColorComponents);
2923                     applyColorAverage(&foreColor, &backColor, (100 - percentComplete) * 2);
2924                 }
2925                 plotCharWithColor(dchar, j+x, y, &foreColor, &backColor);
2926             }
2927         }
2928         previousPercentComplete = percentComplete;
2929         fastForward = pauseBrogue(stepInMs);
2930     }
2931     for (j=0; j<messageLength; j++) {
2932         foreColor = colorFromComponents(dbufs[j].foreColorComponents);
2933         plotCharWithColor(dbufs[j].character, j+x, y, &foreColor, &(backColors[j]));
2934     }
2935 
2936     restoreRNG;
2937 }
2938 
flashTemporaryAlert(char * message,int time)2939 void flashTemporaryAlert(char *message, int time) {
2940     flashMessage(message, (COLS - strLenWithoutEscapes(message)) / 2, ROWS / 2, time, &teal, &black);
2941 }
2942 
waitForAcknowledgment()2943 void waitForAcknowledgment() {
2944     rogueEvent theEvent;
2945 
2946     if (rogue.autoPlayingLevel || (rogue.playbackMode && !rogue.playbackOOS)) {
2947         return;
2948     }
2949 
2950     do {
2951         nextBrogueEvent(&theEvent, false, false, false);
2952         if (theEvent.eventType == KEYSTROKE && theEvent.param1 != ACKNOWLEDGE_KEY && theEvent.param1 != ESCAPE_KEY) {
2953             flashTemporaryAlert(" -- Press space or click to continue -- ", 500);
2954         }
2955     } while (!(theEvent.eventType == KEYSTROKE && (theEvent.param1 == ACKNOWLEDGE_KEY || theEvent.param1 == ESCAPE_KEY)
2956                || theEvent.eventType == MOUSE_UP));
2957 }
2958 
waitForKeystrokeOrMouseClick()2959 void waitForKeystrokeOrMouseClick() {
2960     rogueEvent theEvent;
2961     do {
2962         nextBrogueEvent(&theEvent, false, false, false);
2963     } while (theEvent.eventType != KEYSTROKE && theEvent.eventType != MOUSE_UP);
2964 }
2965 
confirm(char * prompt,boolean alsoDuringPlayback)2966 boolean confirm(char *prompt, boolean alsoDuringPlayback) {
2967     short retVal;
2968     brogueButton buttons[2] = {{{0}}};
2969     cellDisplayBuffer rbuf[COLS][ROWS];
2970     char whiteColorEscape[20] = "";
2971     char yellowColorEscape[20] = "";
2972 
2973     if (rogue.autoPlayingLevel || (!alsoDuringPlayback && rogue.playbackMode)) {
2974         return true; // oh yes he did
2975     }
2976 
2977     encodeMessageColor(whiteColorEscape, 0, &white);
2978     encodeMessageColor(yellowColorEscape, 0, KEYBOARD_LABELS ? &yellow : &white);
2979 
2980     initializeButton(&(buttons[0]));
2981     sprintf(buttons[0].text, "     %sY%ses     ", yellowColorEscape, whiteColorEscape);
2982     buttons[0].hotkey[0] = 'y';
2983     buttons[0].hotkey[1] = 'Y';
2984     buttons[0].hotkey[2] = RETURN_KEY;
2985     buttons[0].flags |= (B_WIDE_CLICK_AREA | B_KEYPRESS_HIGHLIGHT);
2986 
2987     initializeButton(&(buttons[1]));
2988     sprintf(buttons[1].text, "     %sN%so      ", yellowColorEscape, whiteColorEscape);
2989     buttons[1].hotkey[0] = 'n';
2990     buttons[1].hotkey[1] = 'N';
2991     buttons[1].hotkey[2] = ACKNOWLEDGE_KEY;
2992     buttons[1].hotkey[3] = ESCAPE_KEY;
2993     buttons[1].flags |= (B_WIDE_CLICK_AREA | B_KEYPRESS_HIGHLIGHT);
2994 
2995     retVal = printTextBox(prompt, COLS/3, ROWS/3, COLS/3, &white, &interfaceBoxColor, rbuf, buttons, 2);
2996     overlayDisplayBuffer(rbuf, NULL);
2997 
2998     if (retVal == -1 || retVal == 1) { // If they canceled or pressed no.
2999         return false;
3000     } else {
3001         return true;
3002     }
3003 
3004     confirmMessages();
3005     return retVal;
3006 }
3007 
clearMonsterFlashes()3008 void clearMonsterFlashes() {
3009 
3010 }
3011 
displayMonsterFlashes(boolean flashingEnabled)3012 void displayMonsterFlashes(boolean flashingEnabled) {
3013     short x[100], y[100], strength[100], count = 0;
3014     color *flashColor[100];
3015 
3016     rogue.creaturesWillFlashThisTurn = false;
3017 
3018     if (rogue.autoPlayingLevel || rogue.blockCombatText) {
3019         return;
3020     }
3021 
3022     short oldRNG = rogue.RNG;
3023     rogue.RNG = RNG_COSMETIC;
3024     //assureCosmeticRNG;
3025 
3026     boolean handledPlayer = false;
3027     for (creatureIterator it = iterateCreatures(monsters); !handledPlayer || hasNextCreature(it);) {
3028         creature *monst = !handledPlayer ? &player : nextCreature(&it);
3029         handledPlayer = true;
3030         if (monst->bookkeepingFlags & MB_WILL_FLASH) {
3031             monst->bookkeepingFlags &= ~MB_WILL_FLASH;
3032             if (flashingEnabled && canSeeMonster(monst) && count < 100) {
3033                 x[count] = monst->xLoc;
3034                 y[count] = monst->yLoc;
3035                 strength[count] = monst->flashStrength;
3036                 flashColor[count] = &(monst->flashColor);
3037                 count++;
3038             }
3039         }
3040     }
3041     flashForeground(x, y, flashColor, strength, count, 20);
3042     restoreRNG;
3043 }
3044 
dequeueEvent()3045 void dequeueEvent() {
3046     rogueEvent returnEvent;
3047     nextBrogueEvent(&returnEvent, false, false, true);
3048 }
3049 
3050 // Empty the message archive
clearMessageArchive()3051 void clearMessageArchive() {
3052     messageArchivePosition = 0;
3053 }
3054 
3055 // Get a pointer to the archivedMessage the given number of entries back in history.
3056 // Pass zero to get the entry under messageArchivePosition.
getArchivedMessage(short back)3057 archivedMessage *getArchivedMessage(short back) {
3058     return &messageArchive[(messageArchivePosition + MESSAGE_ARCHIVE_ENTRIES - back) % MESSAGE_ARCHIVE_ENTRIES];
3059 }
3060 
formatCountedMessage(char * buffer,size_t size,archivedMessage * m)3061 int formatCountedMessage(char *buffer, size_t size, archivedMessage *m) {
3062     int length;
3063 
3064     if (m->count <= 1) {
3065         length = snprintf(buffer, size, "%s", m->message);
3066     } else if (m->count >= MAX_MESSAGE_REPEATS) {
3067         length = snprintf(buffer, size, "%s (many)", m->message);
3068     } else {
3069         length = snprintf(buffer, size, "%s (x%d)", m->message, m->count);
3070     }
3071 
3072     return length;
3073 }
3074 
3075 // Select and write one or more recent messages to the buffer for further
3076 // formatting.  FOLDABLE messages from the same turn are combined, otherwise
3077 // only one message is taken.
3078 // If turnOutput is not null, it is filled with the player turn number shared
3079 // by the chosen messages.
foldMessages(char buffer[COLS * 20],short offset,unsigned long * turnOutput)3080 short foldMessages(char buffer[COLS*20], short offset, unsigned long *turnOutput) {
3081     short i, folded, messageLength, lineLength, length;
3082     unsigned long turn;
3083     char counted[COLS*2];
3084     archivedMessage *m;
3085 
3086     folded = 0;
3087     m = getArchivedMessage(offset + folded + 1);
3088     if (!m->message[0]) {
3089         return folded;
3090     }
3091 
3092     folded++;
3093     turn = m->turn;
3094     if (turnOutput) {
3095         *turnOutput = turn;
3096     }
3097 
3098     if (!(m->flags & FOLDABLE)) {
3099         formatCountedMessage(buffer, COLS*2, m);
3100         return folded;
3101     }
3102 
3103     // Search back for eligible foldable message.  Only fold messages from the same turn
3104     m = getArchivedMessage(offset + folded + 1);
3105     while (folded < MESSAGE_ARCHIVE_ENTRIES && m->message[0] && m->flags & FOLDABLE && turn == m->turn) {
3106         folded++;
3107         m = getArchivedMessage(offset + folded + 1);
3108     }
3109 
3110     lineLength = 0;
3111     length = 0;
3112     buffer[length] = '\0';
3113     for (i = folded; i >= 1; i--) {
3114         m = getArchivedMessage(offset + i);
3115         formatCountedMessage(counted, COLS*2, m);
3116         messageLength = strLenWithoutEscapes(counted);
3117 
3118         if (length == 0) {
3119             length = snprintf(buffer, COLS*20, "%s", counted);
3120             lineLength = messageLength;
3121         } else if (lineLength + 3 + messageLength <= DCOLS) {  // + 3 for semi-colon, space and final period
3122             length += snprintf(&buffer[length], COLS*20 - length, "; %s", counted);
3123             lineLength += 2 + messageLength;
3124         } else {
3125             length += snprintf(&buffer[length], COLS*20 - length, ".\n%s", counted);
3126             lineLength = messageLength;
3127         }
3128     }
3129 
3130     snprintf(&buffer[length], COLS*20 - length, ".");
3131 
3132     return folded;
3133 }
3134 
3135 // Change the given newline-delimited sentences in-place to ensure proper writing.
capitalizeAndPunctuateSentences(char * text,short length)3136 void capitalizeAndPunctuateSentences(char *text, short length) {
3137     short i;
3138     boolean newSentence;
3139 
3140     newSentence = true;
3141 
3142     for (i = 0; i + 1 < length && text[i] != '\0' && text[i+1] != '\0'; i++) {
3143         if (text[i] == COLOR_ESCAPE) {
3144             i += 3; // the loop increment will get the last byte
3145         } else if (text[i] == '"'
3146                    && (text[i+1] == '.' || text[i+1] == ',')) {
3147             // Implement the American quotation mark/period/comma ordering rule.
3148             text[i] = text[i+1];
3149             text[i+1] = '"';
3150         } else if (text[i] == '\n') {
3151             newSentence = true;
3152         } else if (newSentence) {
3153             upperCase(&(text[i]));
3154             newSentence = false;
3155         }
3156     }
3157 }
3158 
3159 // Copy \n-delimited lines to the given buffer.  Carry colors across line breaks.
splitLines(short lines,char wrapped[COLS * 20],char buffer[][COLS * 2],short bufferCursor)3160 void splitLines(short lines, char wrapped[COLS*20], char buffer[][COLS*2], short bufferCursor) {
3161     short linesSeen;
3162     char color[5], line[COLS*2];
3163     char *start, *end;
3164 
3165     start = &(wrapped[0]);
3166     color[0] = '\0';
3167     line[0] = '\0';
3168 
3169     for (end = start, linesSeen = 0; *end; end++) {
3170         if (*end == COLOR_ESCAPE) {
3171             strncpy(color, end, 4);
3172             color[4] = '\0';
3173             end += 4;
3174         } else if (*end == '\n') {
3175             *end = '\0';
3176 
3177             if (bufferCursor + 1 - lines + linesSeen >= 0) {
3178                 strncpy(line, color, 5);
3179                 strncat(line, start, COLS*2 - strlen(line) - 1);
3180                 line[COLS*2-1] = '\0';
3181                 strncpy(buffer[bufferCursor + 1 - lines + linesSeen], line, COLS*2);
3182                 line[0] = '\0';
3183             }
3184 
3185             linesSeen++;
3186             start = end + 1;
3187         }
3188     }
3189 
3190     strncpy(line, color, 5);
3191     strncat(line, start, COLS*2 - strlen(line) - 1);
3192     line[COLS*2-1] = '\0';
3193     strncpy(buffer[bufferCursor], line, COLS*2);
3194 }
3195 
3196 // Fill the buffer of height lines with archived messages.  Fill from the
3197 // bottom, so that the most recent message appears in the last line of buffer.
3198 // linesFormatted, if not null, is filled with the number of formatted lines
3199 // (rows of buffer filled)
3200 // latestMessageLines, if not null, is filled with the number of formatted
3201 // lines generated by events from the current player turn.
formatRecentMessages(char buffer[][COLS * 2],size_t height,short * linesFormatted,short * latestMessageLines)3202 void formatRecentMessages(char buffer[][COLS*2], size_t height, short *linesFormatted, short *latestMessageLines) {
3203     short lines, bufferCursor, messagesFolded, messagesFormatted;
3204     unsigned long turn;
3205     char folded[COLS*20], wrapped[COLS*20];
3206 
3207     bufferCursor = height - 1;
3208     messagesFormatted = 0;
3209 
3210     if (latestMessageLines) {
3211         *latestMessageLines = 0;
3212     }
3213 
3214     while (bufferCursor >= 0 && messagesFormatted < MESSAGE_ARCHIVE_ENTRIES) {
3215         messagesFolded = foldMessages(folded, messagesFormatted, &turn);
3216         if (messagesFolded == 0) {
3217             break;
3218         }
3219 
3220         capitalizeAndPunctuateSentences(folded, COLS*20);
3221         lines = wrapText(wrapped, folded, DCOLS);
3222         splitLines(lines, wrapped, buffer, bufferCursor);
3223 
3224         if (latestMessageLines && turn == rogue.playerTurnNumber) {
3225             *latestMessageLines += lines;
3226         }
3227 
3228         bufferCursor -= lines;
3229         messagesFormatted += messagesFolded;
3230     }
3231 
3232     if (linesFormatted) {
3233         *linesFormatted = height - 1 - bufferCursor;
3234     }
3235 
3236     while (bufferCursor >= 0) {
3237         buffer[bufferCursor--][0] = '\0';
3238     }
3239 }
3240 
3241 // Display recent archived messages after recalculating message confirmations.
displayRecentMessages()3242 void displayRecentMessages() {
3243     short i;
3244     char messageBuffer[MESSAGE_LINES][COLS*2];
3245 
3246     formatRecentMessages(messageBuffer, MESSAGE_LINES, 0, &messagesUnconfirmed);
3247 
3248     for (i = 0; i < MESSAGE_LINES; i++) {
3249         strcpy(displayedMessage[i], messageBuffer[MESSAGE_LINES - i - 1]);
3250     }
3251 
3252     updateMessageDisplay();
3253 }
3254 
3255 // Draw the given formatted messages to the screen:
3256 // messages: the archived messages after all formatting passes
3257 // length: the number of rows in messages, filled from the "bottom", (unused rows have lower indexes)
3258 // offset: index of oldest (visually highest) message to draw
3259 // height: height in rows of the message archive display area
3260 // rbuf: background display buffer to draw against
drawMessageArchive(char messages[MESSAGE_ARCHIVE_LINES][COLS * 2],short length,short offset,short height,cellDisplayBuffer rbuf[COLS][ROWS])3261 void drawMessageArchive(char messages[MESSAGE_ARCHIVE_LINES][COLS*2], short length, short offset, short height, cellDisplayBuffer rbuf[COLS][ROWS]) {
3262     int i, j, k, fadePercent;
3263     cellDisplayBuffer dbuf[COLS][ROWS];
3264 
3265     clearDisplayBuffer(dbuf);
3266 
3267     for (i = 0; (MESSAGE_ARCHIVE_LINES - offset + i) < MESSAGE_ARCHIVE_LINES && i < ROWS && i < height; i++) {
3268         printString(messages[MESSAGE_ARCHIVE_LINES - offset + i], mapToWindowX(0), i, &white, &black, dbuf);
3269 
3270         // Set the dbuf opacity, and do a fade from bottom to top to make it clear that the bottom messages are the most recent.
3271         fadePercent = 50 * (length - offset + i) / length + 50;
3272         for (j = 0; j < DCOLS; j++) {
3273             dbuf[mapToWindowX(j)][i].opacity = INTERFACE_OPACITY;
3274             if (dbuf[mapToWindowX(j)][i].character != ' ') {
3275                 for (k=0; k<3; k++) {
3276                     dbuf[mapToWindowX(j)][i].foreColorComponents[k] = dbuf[mapToWindowX(j)][i].foreColorComponents[k] * fadePercent / 100;
3277                 }
3278             }
3279         }
3280     }
3281 
3282     overlayDisplayBuffer(rbuf, 0);
3283     overlayDisplayBuffer(dbuf, 0);
3284 }
3285 
3286 // Pull-down/pull-up animation.
3287 // open: the desired state of the archived message display.  True when expanding, false when collapsing.
3288 // messages: the archived messages after all formatting passes
3289 // length: the number of rows in messages, filled from the "bottom", (unused rows have lower indexes)
3290 // offset: index of oldest (visually highest) message to draw in the fully expanded state
3291 // height: height in rows of the message archive display area in the fully expanded state
3292 // rbuf: background display buffer to draw against
animateMessageArchive(boolean opening,char messages[MESSAGE_ARCHIVE_LINES][COLS * 2],short length,short offset,short height,cellDisplayBuffer rbuf[COLS][ROWS])3293 void animateMessageArchive(boolean opening, char messages[MESSAGE_ARCHIVE_LINES][COLS*2], short length, short offset, short height, cellDisplayBuffer rbuf[COLS][ROWS]) {
3294     short i;
3295     boolean fastForward;
3296 
3297     fastForward = false;
3298 
3299     for (i = (opening ? MESSAGE_LINES : height);
3300          (opening ? i <= height : i >= MESSAGE_LINES);
3301          i += (opening ? 1 : -1)) {
3302 
3303         drawMessageArchive(messages, length, offset - height + i, i, rbuf);
3304 
3305         if (!fastForward && pauseBrogue(opening ? 2 : 1)) {
3306             fastForward = true;
3307             dequeueEvent();
3308             i = (opening ? height - 1 : MESSAGE_LINES + 1); // skip to the end
3309         }
3310     }
3311 }
3312 
3313 // Accept keyboard input to navigate or dismiss the opened message archive
3314 // messages: the archived messages after all formatting passes
3315 // length: the number of rows in messages, filled from the "bottom", (unused rows have lower indexes)
3316 // offset: index of oldest (visually highest) message to draw
3317 // height: height in rows of the message archive display area
3318 // rbuf: background display buffer to draw against
3319 //
3320 // returns the new offset, which can change if the player scrolled around before closing
scrollMessageArchive(char messages[MESSAGE_ARCHIVE_LINES][COLS * 2],short length,short offset,short height,cellDisplayBuffer rbuf[COLS][ROWS])3321 short scrollMessageArchive(char messages[MESSAGE_ARCHIVE_LINES][COLS*2], short length, short offset, short height, cellDisplayBuffer rbuf[COLS][ROWS]) {
3322     short lastOffset;
3323     boolean exit;
3324     rogueEvent theEvent;
3325     signed long keystroke;
3326 
3327     if (rogue.autoPlayingLevel || (rogue.playbackMode && !rogue.playbackOOS)) {
3328         return offset;
3329     }
3330 
3331     exit = false;
3332     do {
3333         lastOffset = offset;
3334         nextBrogueEvent(&theEvent, false, false, false);
3335 
3336         if (theEvent.eventType == KEYSTROKE) {
3337             keystroke = theEvent.param1;
3338             stripShiftFromMovementKeystroke(&keystroke);
3339 
3340             switch (keystroke) {
3341                 case UP_KEY:
3342                 case UP_ARROW:
3343                 case NUMPAD_8:
3344                     if (theEvent.controlKey) {
3345                         offset = length;
3346                     } else if (theEvent.shiftKey) {
3347                         offset++;
3348                     } else {
3349                         offset += MESSAGE_ARCHIVE_VIEW_LINES / 3;
3350                     }
3351                     break;
3352                 case DOWN_KEY:
3353                 case DOWN_ARROW:
3354                 case NUMPAD_2:
3355                     if (theEvent.controlKey) {
3356                         offset = height;
3357                     } else if (theEvent.shiftKey) {
3358                         offset--;
3359                     } else {
3360                         offset -= MESSAGE_ARCHIVE_VIEW_LINES / 3;
3361                     }
3362                     break;
3363                 case ACKNOWLEDGE_KEY:
3364                 case ESCAPE_KEY:
3365                     exit = true;
3366                     break;
3367                 default:
3368                     flashTemporaryAlert(" -- Press space or click to continue -- ", 500);
3369             }
3370         }
3371 
3372         if (theEvent.eventType == MOUSE_UP) {
3373             exit = true;
3374         }
3375 
3376         offset = max(height, min(offset, length));
3377         if (offset != lastOffset) {
3378             drawMessageArchive(messages, length, offset, height, rbuf);
3379         }
3380     } while (!exit);
3381 
3382     return offset;
3383 }
3384 
displayMessageArchive()3385 void displayMessageArchive() {
3386     short length, offset, height;
3387     cellDisplayBuffer rbuf[COLS][ROWS];
3388     char messageBuffer[MESSAGE_ARCHIVE_LINES][COLS*2];
3389 
3390     formatRecentMessages(messageBuffer, MESSAGE_ARCHIVE_LINES, &length, 0);
3391 
3392     if (length <= MESSAGE_LINES) {
3393         return;
3394     }
3395 
3396     height = min(length, MESSAGE_ARCHIVE_VIEW_LINES);
3397     offset = height;
3398 
3399     copyDisplayBuffer(rbuf, displayBuffer);
3400 
3401     animateMessageArchive(true, messageBuffer, length, offset, height, rbuf);
3402     offset = scrollMessageArchive(messageBuffer, length, offset, height, rbuf);
3403     animateMessageArchive(false, messageBuffer, length, offset, height, rbuf);
3404 
3405     overlayDisplayBuffer(rbuf, 0);
3406     updateFlavorText();
3407     confirmMessages();
3408     updateMessageDisplay();
3409 }
3410 
3411 // Clears the message area and prints the given message in the area.
3412 // It will disappear when messages are refreshed and will not be archived.
3413 // This is primarily used to display prompts.
temporaryMessage(const char * msg,enum messageFlags flags)3414 void temporaryMessage(const char *msg, enum messageFlags flags) {
3415     char message[COLS];
3416     short i, j;
3417 
3418     assureCosmeticRNG;
3419     strcpy(message, msg);
3420 
3421     for (i=0; message[i] == COLOR_ESCAPE; i += 4) {
3422         upperCase(&(message[i]));
3423     }
3424 
3425     if (flags & REFRESH_SIDEBAR) {
3426         refreshSideBar(-1, -1, false);
3427     }
3428 
3429     for (i=0; i<MESSAGE_LINES; i++) {
3430         for (j=0; j<DCOLS; j++) {
3431             plotCharWithColor(' ', mapToWindowX(j), i, &black, &black);
3432         }
3433     }
3434     printString(message, mapToWindowX(0), mapToWindowY(-1), &white, &black, 0);
3435     if (flags & REQUIRE_ACKNOWLEDGMENT) {
3436         waitForAcknowledgment();
3437         updateMessageDisplay();
3438     }
3439     restoreRNG;
3440 }
3441 
messageWithColor(char * msg,color * theColor,enum messageFlags flags)3442 void messageWithColor(char *msg, color *theColor, enum messageFlags flags) {
3443     char buf[COLS*2] = "";
3444     short i;
3445 
3446     i=0;
3447     i = encodeMessageColor(buf, i, theColor);
3448     strcpy(&(buf[i]), msg);
3449     message(buf, flags);
3450 }
3451 
flavorMessage(char * msg)3452 void flavorMessage(char *msg) {
3453     short i;
3454     char text[COLS*20];
3455 
3456     for (i=0; i < COLS*2 && msg[i] != '\0'; i++) {
3457         text[i] = msg[i];
3458     }
3459     text[i] = '\0';
3460 
3461     for(i=0; text[i] == COLOR_ESCAPE; i+=4);
3462     upperCase(&(text[i]));
3463 
3464     printString(text, mapToWindowX(0), ROWS - 2, &flavorTextColor, &black, 0);
3465     for (i = strLenWithoutEscapes(text); i < DCOLS; i++) {
3466         plotCharWithColor(' ', mapToWindowX(i), ROWS - 2, &black, &black);
3467     }
3468 }
3469 
3470 // Insert or collapse a new message into the archive and redraw the recent
3471 // message display.  An incoming message may "collapse" into another (be
3472 // dropped in favor of bumping a repetition count) if the two have identical
3473 // content and one of two other conditions is met.  First, if the two messages
3474 // arrived on the same turn, they may collapse.  Alternately, they may collapse
3475 // if the older message is the latest one in the archive and the new one is not
3476 // semi-colon foldable (such as a combat message.)
message(const char * msg,enum messageFlags flags)3477 void message(const char *msg, enum messageFlags flags) {
3478     short i;
3479     archivedMessage *archiveEntry;
3480     boolean newMessage;
3481 
3482     if (msg == NULL || !msg[0]) {
3483         return;
3484     }
3485 
3486     assureCosmeticRNG;
3487 
3488     rogue.disturbed = true;
3489     if (flags & REQUIRE_ACKNOWLEDGMENT || flags & REFRESH_SIDEBAR) {
3490         refreshSideBar(-1, -1, false);
3491     }
3492     displayCombatText();
3493 
3494     // Add the message to the archive, bumping counts for recent duplicates
3495     newMessage = true;
3496 
3497     // For each at most MESSAGE_ARCHIVE_ENTRIES - 1 past entries..
3498     for (i = 1; i < MESSAGE_ARCHIVE_ENTRIES; i++) {
3499         archiveEntry = getArchivedMessage(i);
3500 
3501         // Consider messages that arrived this turn for collapsing.  Also
3502         // consider the latest entry (which may be from past turns) if the
3503         // incoming message is not semi-colon foldable.
3504         if (!((i == 1 && !(flags & FOLDABLE)) || archiveEntry->turn == rogue.playerTurnNumber)) {
3505             break;
3506         }
3507 
3508         if (strcmp(archiveEntry->message, msg) == 0) {
3509             // We found an suitable older message to collapse into.  So we
3510             // don't need to add another.  Instead consider the older message
3511             // as having happened on the current turn, and bump its count if
3512             // not maxxed out.
3513             newMessage = false;
3514             archiveEntry->turn = rogue.playerTurnNumber;
3515             if (archiveEntry->count < MAX_MESSAGE_REPEATS) {
3516                 archiveEntry->count++;
3517             }
3518             break;
3519         }
3520     }
3521 
3522     // We didn't collapse the new message, so initialize and insert a new
3523     // archive entry for it instead.
3524     if (newMessage) {
3525         archiveEntry = &messageArchive[messageArchivePosition];
3526         strcpy(archiveEntry->message, msg);
3527         archiveEntry->count = 1;
3528         archiveEntry->turn = rogue.playerTurnNumber;
3529         archiveEntry->flags = flags;
3530         messageArchivePosition = (messageArchivePosition + 1) % MESSAGE_ARCHIVE_ENTRIES;
3531     }
3532 
3533     displayRecentMessages();
3534 
3535     if ((flags & REQUIRE_ACKNOWLEDGMENT) || rogue.cautiousMode) {
3536         displayMoreSign();
3537         confirmMessages();
3538         rogue.cautiousMode = false;
3539     }
3540 
3541     if (rogue.playbackMode) {
3542         rogue.playbackDelayThisTurn += rogue.playbackDelayPerTurn * 5;
3543     }
3544 
3545     restoreRNG;
3546 }
3547 
3548 // Only used for the "you die..." message, to enable posthumous inventory viewing.
displayMoreSignWithoutWaitingForAcknowledgment()3549 void displayMoreSignWithoutWaitingForAcknowledgment() {
3550     if (strLenWithoutEscapes(displayedMessage[0]) < DCOLS - 8 || messagesUnconfirmed > 0) {
3551         printString("--MORE--", COLS - 8, MESSAGE_LINES-1, &black, &white, 0);
3552     } else {
3553         printString("--MORE--", COLS - 8, MESSAGE_LINES, &black, &white, 0);
3554     }
3555 }
3556 
displayMoreSign()3557 void displayMoreSign() {
3558     short i;
3559 
3560     if (rogue.autoPlayingLevel) {
3561         return;
3562     }
3563 
3564     if (strLenWithoutEscapes(displayedMessage[0]) < DCOLS - 8 || messagesUnconfirmed > 0) {
3565         printString("--MORE--", COLS - 8, MESSAGE_LINES-1, &black, &white, 0);
3566         waitForAcknowledgment();
3567         printString("        ", COLS - 8, MESSAGE_LINES-1, &black, &black, 0);
3568     } else {
3569         printString("--MORE--", COLS - 8, MESSAGE_LINES, &black, &white, 0);
3570         waitForAcknowledgment();
3571         for (i=1; i<=8; i++) {
3572             refreshDungeonCell(DCOLS - i, 0);
3573         }
3574     }
3575 }
3576 
3577 // Inserts a four-character color escape sequence into a string at the insertion point.
3578 // Does NOT check string lengths, so it could theoretically write over the null terminator.
3579 // Returns the new insertion point.
encodeMessageColor(char * msg,short i,const color * theColor)3580 short encodeMessageColor(char *msg, short i, const color *theColor) {
3581     boolean needTerminator = false;
3582     color col = *theColor;
3583 
3584     assureCosmeticRNG;
3585 
3586     bakeColor(&col);
3587 
3588     col.red     = clamp(col.red, 0, 100);
3589     col.green   = clamp(col.green, 0, 100);
3590     col.blue    = clamp(col.blue, 0, 100);
3591 
3592     needTerminator = !msg[i] || !msg[i + 1] || !msg[i + 2] || !msg[i + 3];
3593 
3594     msg[i++] = COLOR_ESCAPE;
3595     msg[i++] = (char) (COLOR_VALUE_INTERCEPT + col.red);
3596     msg[i++] = (char) (COLOR_VALUE_INTERCEPT + col.green);
3597     msg[i++] = (char) (COLOR_VALUE_INTERCEPT + col.blue);
3598 
3599     if (needTerminator) {
3600         msg[i] = '\0';
3601     }
3602 
3603     restoreRNG;
3604 
3605     return i;
3606 }
3607 
3608 // Call this when the i'th character of msg is COLOR_ESCAPE.
3609 // It will return the encoded color, and will advance i past the color escape sequence.
decodeMessageColor(const char * msg,short i,color * returnColor)3610 short decodeMessageColor(const char *msg, short i, color *returnColor) {
3611 
3612     if (msg[i] != COLOR_ESCAPE) {
3613         printf("\nAsked to decode a color escape that didn't exist!");
3614         *returnColor = white;
3615     } else {
3616         i++;
3617         *returnColor = black;
3618         returnColor->red    = (short) (msg[i++] - COLOR_VALUE_INTERCEPT);
3619         returnColor->green  = (short) (msg[i++] - COLOR_VALUE_INTERCEPT);
3620         returnColor->blue   = (short) (msg[i++] - COLOR_VALUE_INTERCEPT);
3621 
3622         returnColor->red    = clamp(returnColor->red, 0, 100);
3623         returnColor->green  = clamp(returnColor->green, 0, 100);
3624         returnColor->blue   = clamp(returnColor->blue, 0, 100);
3625     }
3626     return i;
3627 }
3628 
3629 // Returns a color for combat text based on the identity of the victim.
messageColorFromVictim(creature * monst)3630 color *messageColorFromVictim(creature *monst) {
3631     if (monst == &player) {
3632         return &badMessageColor;
3633     } else if (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience) {
3634         return &white;
3635     } else if (monst->creatureState == MONSTER_ALLY) {
3636         return &badMessageColor;
3637     } else if (monstersAreEnemies(&player, monst)) {
3638         return &goodMessageColor;
3639     } else {
3640         return &white;
3641     }
3642 }
3643 
updateMessageDisplay()3644 void updateMessageDisplay() {
3645     short i, j, m;
3646     color messageColor;
3647 
3648     for (i=0; i<MESSAGE_LINES; i++) {
3649         messageColor = white;
3650 
3651         if (i >= messagesUnconfirmed) {
3652             applyColorAverage(&messageColor, &black, 50);
3653             applyColorAverage(&messageColor, &black, 75 * i / MESSAGE_LINES);
3654         }
3655 
3656         for (j = m = 0; displayedMessage[i][m] && j < DCOLS; j++, m++) {
3657 
3658             while (displayedMessage[i][m] == COLOR_ESCAPE) {
3659                 m = decodeMessageColor(displayedMessage[i], m, &messageColor); // pulls the message color out and advances m
3660                 if (i >= messagesUnconfirmed) {
3661                     applyColorAverage(&messageColor, &black, 50);
3662                     applyColorAverage(&messageColor, &black, 75 * i / MESSAGE_LINES);
3663                 }
3664             }
3665 
3666             plotCharWithColor(displayedMessage[i][m], mapToWindowX(j), MESSAGE_LINES - i - 1,
3667                               &messageColor,
3668                               &black);
3669         }
3670         for (; j < DCOLS; j++) {
3671             plotCharWithColor(' ', mapToWindowX(j), MESSAGE_LINES - i - 1, &black, &black);
3672         }
3673     }
3674 }
3675 
3676 // Does NOT clear the message archive.
deleteMessages()3677 void deleteMessages() {
3678     short i;
3679     for (i=0; i<MESSAGE_LINES; i++) {
3680         displayedMessage[i][0] = '\0';
3681     }
3682     confirmMessages();
3683 }
3684 
confirmMessages()3685 void confirmMessages() {
3686     messagesUnconfirmed = 0;
3687     updateMessageDisplay();
3688 }
3689 
stripShiftFromMovementKeystroke(signed long * keystroke)3690 void stripShiftFromMovementKeystroke(signed long *keystroke) {
3691     const unsigned short newKey = *keystroke - ('A' - 'a');
3692     if (newKey == LEFT_KEY
3693         || newKey == RIGHT_KEY
3694         || newKey == DOWN_KEY
3695         || newKey == UP_KEY
3696         || newKey == UPLEFT_KEY
3697         || newKey == UPRIGHT_KEY
3698         || newKey == DOWNLEFT_KEY
3699         || newKey == DOWNRIGHT_KEY) {
3700         *keystroke -= 'A' - 'a';
3701     }
3702 }
3703 
upperCase(char * theChar)3704 void upperCase(char *theChar) {
3705     if (*theChar >= 'a' && *theChar <= 'z') {
3706         (*theChar) += ('A' - 'a');
3707     }
3708 }
3709 
3710 enum entityDisplayTypes {
3711     EDT_NOTHING = 0,
3712     EDT_CREATURE,
3713     EDT_ITEM,
3714     EDT_TERRAIN,
3715 };
3716 
3717 // Refreshes the sidebar.
3718 // Progresses from the closest visible monster to the farthest.
3719 // If a monster, item or terrain is focused, then display the sidebar with that monster/item highlighted,
3720 // in the order it would normally appear. If it would normally not fit on the sidebar at all,
3721 // then list it first.
3722 // Also update rogue.sidebarLocationList[ROWS][2] list of locations so that each row of
3723 // the screen is mapped to the corresponding entity, if any.
3724 // FocusedEntityMustGoFirst should usually be false when called externally. This is because
3725 // we won't know if it will fit on the screen in normal order until we try.
3726 // So if we try and fail, this function will call itself again, but with this set to true.
refreshSideBar(short focusX,short focusY,boolean focusedEntityMustGoFirst)3727 void refreshSideBar(short focusX, short focusY, boolean focusedEntityMustGoFirst) {
3728     short printY, oldPrintY, shortestDistance, i, j, k, px, py, x = 0, y = 0, displayEntityCount, indirectVision;
3729     creature *closestMonst = NULL;
3730     item *theItem, *closestItem = NULL;
3731     char buf[COLS];
3732     void *entityList[ROWS] = {0}, *focusEntity = NULL;
3733     enum entityDisplayTypes entityType[ROWS] = {0}, focusEntityType = EDT_NOTHING;
3734     short terrainLocationMap[ROWS][2];
3735     boolean gotFocusedEntityOnScreen = (focusX >= 0 ? false : true);
3736     char addedEntity[DCOLS][DROWS];
3737     short oldRNG;
3738 
3739     if (rogue.gameHasEnded || rogue.playbackFastForward) {
3740         return;
3741     }
3742 
3743     oldRNG = rogue.RNG;
3744     rogue.RNG = RNG_COSMETIC;
3745     //assureCosmeticRNG;
3746 
3747     if (focusX < 0) {
3748         focusedEntityMustGoFirst = false; // just in case!
3749     } else {
3750         if (pmap[focusX][focusY].flags & (HAS_MONSTER | HAS_PLAYER)) {
3751             creature *monst = monsterAtLoc(focusX, focusY);
3752             if (canSeeMonster(monst) || rogue.playbackOmniscience) {
3753                 focusEntity = monst;
3754                 focusEntityType = EDT_CREATURE;
3755             }
3756         }
3757         if (!focusEntity && (pmap[focusX][focusY].flags & HAS_ITEM)) {
3758             theItem = itemAtLoc(focusX, focusY);
3759             if (playerCanSeeOrSense(focusX, focusY)) {
3760                 focusEntity = theItem;
3761                 focusEntityType = EDT_ITEM;
3762             }
3763         }
3764         if (!focusEntity
3765             && cellHasTMFlag(focusX, focusY, TM_LIST_IN_SIDEBAR)
3766             && playerCanSeeOrSense(focusX, focusY)) {
3767 
3768             focusEntity = tileCatalog[pmap[focusX][focusY].layers[layerWithTMFlag(focusX, focusY, TM_LIST_IN_SIDEBAR)]].description;
3769             focusEntityType = EDT_TERRAIN;
3770         }
3771     }
3772 
3773     printY = 0;
3774 
3775     px = player.xLoc;
3776     py = player.yLoc;
3777 
3778     zeroOutGrid(addedEntity);
3779 
3780     // Header information for playback mode.
3781     if (rogue.playbackMode) {
3782         printString("   -- PLAYBACK --   ", 0, printY++, &white, &black, 0);
3783         if (rogue.howManyTurns > 0) {
3784             sprintf(buf, "Turn %li/%li", rogue.playerTurnNumber, rogue.howManyTurns);
3785             printProgressBar(0, printY++, buf, rogue.playerTurnNumber, rogue.howManyTurns, &darkPurple, false);
3786         }
3787         if (rogue.playbackOOS) {
3788             printString("    [OUT OF SYNC]   ", 0, printY++, &badMessageColor, &black, 0);
3789         } else if (rogue.playbackPaused) {
3790             printString("      [PAUSED]      ", 0, printY++, &gray, &black, 0);
3791         }
3792         printString("                    ", 0, printY++, &white, &black, 0);
3793     }
3794 
3795     // Now list the monsters that we'll be displaying in the order of their proximity to player (listing the focused first if required).
3796 
3797     // Initialization.
3798     displayEntityCount = 0;
3799     for (i=0; i<ROWS*2; i++) {
3800         rogue.sidebarLocationList[i][0] = -1;
3801         rogue.sidebarLocationList[i][1] = -1;
3802     }
3803 
3804     // Player always goes first.
3805     entityList[displayEntityCount] = &player;
3806     entityType[displayEntityCount] = EDT_CREATURE;
3807     displayEntityCount++;
3808     addedEntity[player.xLoc][player.yLoc] = true;
3809 
3810     // Focused entity, if it must go first.
3811     if (focusedEntityMustGoFirst && !addedEntity[focusX][focusY]) {
3812         addedEntity[focusX][focusY] = true;
3813         entityList[displayEntityCount] = focusEntity;
3814         entityType[displayEntityCount] = focusEntityType;
3815         terrainLocationMap[displayEntityCount][0] = focusX;
3816         terrainLocationMap[displayEntityCount][1] = focusY;
3817         displayEntityCount++;
3818     }
3819 
3820     for (indirectVision = 0; indirectVision < 2; indirectVision++) {
3821         // Non-focused monsters.
3822         do {
3823             shortestDistance = 10000;
3824             for (creatureIterator it = iterateCreatures(monsters); hasNextCreature(it);) {
3825                 creature *monst = nextCreature(&it);
3826                 if ((canDirectlySeeMonster(monst) || (indirectVision && (canSeeMonster(monst) || rogue.playbackOmniscience)))
3827                     && !addedEntity[monst->xLoc][monst->yLoc]
3828                     && !(monst->info.flags & MONST_NOT_LISTED_IN_SIDEBAR)
3829                     && (px - monst->xLoc) * (px - monst->xLoc) + (py - monst->yLoc) * (py - monst->yLoc) < shortestDistance) {
3830 
3831                     shortestDistance = (px - monst->xLoc) * (px - monst->xLoc) + (py - monst->yLoc) * (py - monst->yLoc);
3832                     closestMonst = monst;
3833                 }
3834             }
3835             if (shortestDistance < 10000) {
3836                 addedEntity[closestMonst->xLoc][closestMonst->yLoc] = true;
3837                 entityList[displayEntityCount] = closestMonst;
3838                 entityType[displayEntityCount] = EDT_CREATURE;
3839                 displayEntityCount++;
3840             }
3841         } while (shortestDistance < 10000 && displayEntityCount * 2 < ROWS); // Because each entity takes at least 2 rows in the sidebar.
3842 
3843         // Non-focused items.
3844         do {
3845             shortestDistance = 10000;
3846             for (theItem = floorItems->nextItem; theItem != NULL; theItem = theItem->nextItem) {
3847                 if ((playerCanDirectlySee(theItem->xLoc, theItem->yLoc) || (indirectVision && (playerCanSeeOrSense(theItem->xLoc, theItem->yLoc) || rogue.playbackOmniscience)))
3848                     && !addedEntity[theItem->xLoc][theItem->yLoc]
3849                     && (px - theItem->xLoc) * (px - theItem->xLoc) + (py - theItem->yLoc) * (py - theItem->yLoc) < shortestDistance) {
3850 
3851                     shortestDistance = (px - theItem->xLoc) * (px - theItem->xLoc) + (py - theItem->yLoc) * (py - theItem->yLoc);
3852                     closestItem = theItem;
3853                 }
3854             }
3855             if (shortestDistance < 10000) {
3856                 addedEntity[closestItem->xLoc][closestItem->yLoc] = true;
3857                 entityList[displayEntityCount] = closestItem;
3858                 entityType[displayEntityCount] = EDT_ITEM;
3859                 displayEntityCount++;
3860             }
3861         } while (shortestDistance < 10000 && displayEntityCount * 2 < ROWS); // Because each entity takes at least 2 rows in the sidebar.
3862 
3863         // Non-focused terrain.
3864 
3865         // count up the number of candidate locations
3866         for (k=0; k<max(DROWS, DCOLS); k++) {
3867             for (i = px-k; i <= px+k; i++) {
3868                 // we are scanning concentric squares. The first and last columns
3869                 // need to be stepped through, but others can be jumped over.
3870                 short step = (i == px-k || i == px+k) ? 1 : 2*k;
3871                 for (j = py-k; j <= py+k; j += step) {
3872                     if (displayEntityCount >= ROWS - 1) goto no_space_for_more_entities;
3873                     if (coordinatesAreInMap(i, j)
3874                         && !addedEntity[i][j]
3875                         && cellHasTMFlag(i, j, TM_LIST_IN_SIDEBAR)
3876                         && (playerCanDirectlySee(i, j) || (indirectVision && (playerCanSeeOrSense(i, j) || rogue.playbackOmniscience)))) {
3877 
3878                         addedEntity[i][j] = true;
3879                         entityList[displayEntityCount] = tileCatalog[pmap[i][j].layers[layerWithTMFlag(i, j, TM_LIST_IN_SIDEBAR)]].description;
3880                         entityType[displayEntityCount] = EDT_TERRAIN;
3881                         terrainLocationMap[displayEntityCount][0] = i;
3882                         terrainLocationMap[displayEntityCount][1] = j;
3883                         displayEntityCount++;
3884                     }
3885                 }
3886             }
3887         }
3888         no_space_for_more_entities:;
3889     }
3890 
3891     // Entities are now listed. Start printing.
3892 
3893     for (i=0; i<displayEntityCount && printY < ROWS - 1; i++) { // Bottom line is reserved for the depth.
3894         oldPrintY = printY;
3895         if (entityType[i] == EDT_CREATURE) {
3896             x = ((creature *) entityList[i])->xLoc;
3897             y = ((creature *) entityList[i])->yLoc;
3898             printY = printMonsterInfo((creature *) entityList[i],
3899                                       printY,
3900                                       (focusEntity && (x != focusX || y != focusY)),
3901                                       (x == focusX && y == focusY));
3902 
3903         } else if (entityType[i] == EDT_ITEM) {
3904             x = ((item *) entityList[i])->xLoc;
3905             y = ((item *) entityList[i])->yLoc;
3906             printY = printItemInfo((item *) entityList[i],
3907                                    printY,
3908                                    (focusEntity && (x != focusX || y != focusY)),
3909                                    (x == focusX && y == focusY));
3910         } else if (entityType[i] == EDT_TERRAIN) {
3911             x = terrainLocationMap[i][0];
3912             y = terrainLocationMap[i][1];
3913             printY = printTerrainInfo(x, y,
3914                                       printY,
3915                                       ((const char *) entityList[i]),
3916                                       (focusEntity && (x != focusX || y != focusY)),
3917                                       (x == focusX && y == focusY));
3918         }
3919         if (focusEntity && (x == focusX && y == focusY) && printY < ROWS) {
3920             gotFocusedEntityOnScreen = true;
3921         }
3922         for (j=oldPrintY; j<printY; j++) {
3923             rogue.sidebarLocationList[j][0] = x;
3924             rogue.sidebarLocationList[j][1] = y;
3925         }
3926     }
3927 
3928     if (gotFocusedEntityOnScreen) {
3929         // Wrap things up.
3930         for (i=printY; i< ROWS - 1; i++) {
3931             printString("                    ", 0, i, &white, &black, 0);
3932         }
3933         sprintf(buf, "  -- Depth: %i --%s   ", rogue.depthLevel, (rogue.depthLevel < 10 ? " " : ""));
3934         printString(buf, 0, ROWS - 1, &white, &black, 0);
3935     } else if (!focusedEntityMustGoFirst) {
3936         // Failed to get the focusMonst printed on the screen. Try again, this time with the focus first.
3937         refreshSideBar(focusX, focusY, true);
3938     }
3939 
3940     restoreRNG;
3941 }
3942 
printString(const char * theString,short x,short y,color * foreColor,color * backColor,cellDisplayBuffer dbuf[COLS][ROWS])3943 void printString(const char *theString, short x, short y, color *foreColor, color *backColor, cellDisplayBuffer dbuf[COLS][ROWS]) {
3944     color fColor;
3945     short i;
3946 
3947     fColor = *foreColor;
3948 
3949     for (i=0; theString[i] != '\0' && x < COLS; i++, x++) {
3950         while (theString[i] == COLOR_ESCAPE) {
3951             i = decodeMessageColor(theString, i, &fColor);
3952             if (!theString[i]) {
3953                 return;
3954             }
3955         }
3956 
3957         if (dbuf) {
3958             plotCharToBuffer(theString[i], x, y, &fColor, backColor, dbuf);
3959         } else {
3960             plotCharWithColor(theString[i], x, y, &fColor, backColor);
3961         }
3962     }
3963 }
3964 
3965 // Inserts line breaks into really long words. Optionally adds a hyphen, but doesn't do anything
3966 // clever regarding hyphen placement. Plays nicely with color escapes.
breakUpLongWordsIn(char * sourceText,short width,boolean useHyphens)3967 void breakUpLongWordsIn(char *sourceText, short width, boolean useHyphens) {
3968     char buf[COLS * ROWS * 2] = "";
3969     short i, m, nextChar, wordWidth;
3970     //const short maxLength = useHyphens ? width - 1 : width;
3971 
3972     // i iterates over characters in sourceText; m keeps track of the length of buf.
3973     wordWidth = 0;
3974     for (i=0, m=0; sourceText[i] != 0;) {
3975         if (sourceText[i] == COLOR_ESCAPE) {
3976             strncpy(&(buf[m]), &(sourceText[i]), 4);
3977             i += 4;
3978             m += 4;
3979         } else if (sourceText[i] == ' ' || sourceText[i] == '\n') {
3980             wordWidth = 0;
3981             buf[m++] = sourceText[i++];
3982         } else {
3983             if (!useHyphens && wordWidth >= width) {
3984                 buf[m++] = '\n';
3985                 wordWidth = 0;
3986             } else if (useHyphens && wordWidth >= width - 1) {
3987                 nextChar = i+1;
3988                 while (sourceText[nextChar] == COLOR_ESCAPE) {
3989                     nextChar += 4;
3990                 }
3991                 if (sourceText[nextChar] && sourceText[nextChar] != ' ' && sourceText[nextChar] != '\n') {
3992                     buf[m++] = '-';
3993                     buf[m++] = '\n';
3994                     wordWidth = 0;
3995                 }
3996             }
3997             buf[m++] = sourceText[i++];
3998             wordWidth++;
3999         }
4000     }
4001     buf[m] = '\0';
4002     strcpy(sourceText, buf);
4003 }
4004 
4005 // Returns the number of lines, including the newlines already in the text.
4006 // Puts the output in "to" only if we receive a "to" -- can make it null and just get a line count.
wrapText(char * to,const char * sourceText,short width)4007 short wrapText(char *to, const char *sourceText, short width) {
4008     short i, w, textLength, lineCount;
4009     char printString[COLS * ROWS * 2];
4010     short spaceLeftOnLine, wordWidth;
4011 
4012     strcpy(printString, sourceText); // a copy we can write on
4013     breakUpLongWordsIn(printString, width, true); // break up any words that are wider than the width.
4014 
4015     textLength = strlen(printString); // do NOT discount escape sequences
4016     lineCount = 1;
4017 
4018     // Now go through and replace spaces with newlines as needed.
4019 
4020     // Fast foward until i points to the first character that is not a color escape.
4021     for (i=0; printString[i] == COLOR_ESCAPE; i+= 4);
4022     spaceLeftOnLine = width;
4023 
4024     while (i < textLength) {
4025         // wordWidth counts the word width of the next word without color escapes.
4026         // w indicates the position of the space or newline or null terminator that terminates the word.
4027         wordWidth = 0;
4028         for (w = i + 1; w < textLength && printString[w] != ' ' && printString[w] != '\n';) {
4029             if (printString[w] == COLOR_ESCAPE) {
4030                 w += 4;
4031             } else {
4032                 w++;
4033                 wordWidth++;
4034             }
4035         }
4036 
4037         if (1 + wordWidth > spaceLeftOnLine || printString[i] == '\n') {
4038             printString[i] = '\n';
4039             lineCount++;
4040             spaceLeftOnLine = width - wordWidth; // line width minus the width of the word we just wrapped
4041             //printf("\n\n%s", printString);
4042         } else {
4043             spaceLeftOnLine -= 1 + wordWidth;
4044         }
4045         i = w; // Advance to the terminator that follows the word.
4046     }
4047     if (to) {
4048         strcpy(to, printString);
4049     }
4050     return lineCount;
4051 }
4052 
4053 // returns the y-coordinate of the last line
printStringWithWrapping(char * theString,short x,short y,short width,color * foreColor,color * backColor,cellDisplayBuffer dbuf[COLS][ROWS])4054 short printStringWithWrapping(char *theString, short x, short y, short width, color *foreColor,
4055                              color*backColor, cellDisplayBuffer dbuf[COLS][ROWS]) {
4056     color fColor;
4057     char printString[COLS * ROWS * 2];
4058     short i, px, py;
4059 
4060     wrapText(printString, theString, width); // inserts newlines as necessary
4061 
4062     // display the string
4063     px = x; //px and py are the print insertion coordinates; x and y remain the top-left of the text box
4064     py = y;
4065     fColor = *foreColor;
4066 
4067     for (i=0; printString[i] != '\0'; i++) {
4068         if (printString[i] == '\n') {
4069             px = x; // back to the leftmost column
4070             if (py < ROWS - 1) { // don't advance below the bottom of the screen
4071                 py++; // next line
4072             } else {
4073                 break; // If we've run out of room, stop.
4074             }
4075             continue;
4076         } else if (printString[i] == COLOR_ESCAPE) {
4077             i = decodeMessageColor(printString, i, &fColor) - 1;
4078             continue;
4079         }
4080 
4081         if (dbuf) {
4082             if (coordinatesAreInWindow(px, py)) {
4083                 plotCharToBuffer(printString[i], px, py, &fColor, backColor, dbuf);
4084             }
4085         } else {
4086             if (coordinatesAreInWindow(px, py)) {
4087                 plotCharWithColor(printString[i], px, py, &fColor, backColor);
4088             }
4089         }
4090 
4091         px++;
4092     }
4093     return py;
4094 }
4095 
nextKeyPress(boolean textInput)4096 char nextKeyPress(boolean textInput) {
4097     rogueEvent theEvent;
4098     do {
4099         nextBrogueEvent(&theEvent, textInput, false, false);
4100     } while (theEvent.eventType != KEYSTROKE);
4101     return theEvent.param1;
4102 }
4103 
4104 #define BROGUE_HELP_LINE_COUNT  33
4105 
printHelpScreen()4106 void printHelpScreen() {
4107     short i, j;
4108     cellDisplayBuffer dbuf[COLS][ROWS], rbuf[COLS][ROWS];
4109     char helpText[BROGUE_HELP_LINE_COUNT][DCOLS*3] = {
4110         "",
4111         "",
4112         "          -- Commands --",
4113         "",
4114         "          mouse  ****move cursor (including to examine monsters and terrain)",
4115         "          click  ****travel",
4116         "  control-click  ****advance one space",
4117         "       <return>  ****enable keyboard cursor control",
4118         "    <space/esc>  ****disable keyboard cursor control",
4119         "hjklyubn, arrow keys, or numpad  ****move or attack (control or shift to run)",
4120         "",
4121         "a/e/r/t/d/c/R/w  ****apply/equip/remove/throw/drop/call/relabel/swap an item",
4122         "              T  ****re-throw last item at last monster",
4123         " i, right-click  ****view inventory",
4124         "              D  ****list discovered items",
4125         "",
4126         "              z  ****rest once",
4127         "              Z  ****rest for 100 turns or until something happens",
4128         "              s  ****search for secrets (control-s: long search)",
4129         "           <, >  ****travel to stairs",
4130         "              x  ****auto-explore (control-x: fast forward)",
4131         "              A  ****autopilot (control-A: fast forward)",
4132         "              M  ****display old messages",
4133         "              G  ****toggle graphical tiles (when available)",
4134         "",
4135         "              S  ****suspend game and quit",
4136         "              Q  ****quit to title screen",
4137         "",
4138         "              \\  ****disable/enable color effects",
4139         "              ]  ****display/hide stealth range",
4140         "    <space/esc>  ****clear message or cancel command",
4141         "",
4142         "        -- press space or click to continue --"
4143     };
4144 
4145     // Replace the "****"s with color escapes.
4146     for (i=0; i<BROGUE_HELP_LINE_COUNT; i++) {
4147         for (j=0; helpText[i][j]; j++) {
4148             if (helpText[i][j] == '*') {
4149                 j = encodeMessageColor(helpText[i], j, &white);
4150             }
4151         }
4152     }
4153 
4154     clearDisplayBuffer(dbuf);
4155 
4156     // Print the text to the dbuf.
4157     for (i=0; i<BROGUE_HELP_LINE_COUNT && i < ROWS; i++) {
4158         printString(helpText[i], mapToWindowX(1), i, &itemMessageColor, &black, dbuf);
4159     }
4160 
4161     // Set the dbuf opacity.
4162     for (i=0; i<DCOLS; i++) {
4163         for (j=0; j<ROWS; j++) {
4164             //plotCharWithColor(' ', mapToWindowX(i), j, &black, &black);
4165             dbuf[mapToWindowX(i)][j].opacity = INTERFACE_OPACITY;
4166         }
4167     }
4168 
4169     // Display.
4170     overlayDisplayBuffer(dbuf, rbuf);
4171     waitForAcknowledgment();
4172     overlayDisplayBuffer(rbuf, 0);
4173     updateFlavorText();
4174     updateMessageDisplay();
4175 }
4176 
printDiscoveries(short category,short count,unsigned short itemCharacter,short x,short y,cellDisplayBuffer dbuf[COLS][ROWS])4177 void printDiscoveries(short category, short count, unsigned short itemCharacter, short x, short y, cellDisplayBuffer dbuf[COLS][ROWS]) {
4178     color *theColor, goodColor, badColor;
4179     char buf[COLS], buf2[COLS];
4180     short i, magic, totalFrequency;
4181     itemTable *theTable = tableForItemCategory(category, NULL);
4182 
4183     goodColor = goodMessageColor;
4184     applyColorAverage(&goodColor, &black, 50);
4185     badColor = badMessageColor;
4186     applyColorAverage(&badColor, &black, 50);
4187 
4188     totalFrequency = 0;
4189     for (i = 0; i < count; i++) {
4190         if (!theTable[i].identified) {
4191             totalFrequency += theTable[i].frequency;
4192         }
4193     }
4194 
4195     for (i = 0; i < count; i++) {
4196         if (theTable[i].identified) {
4197             theColor = &white;
4198             plotCharToBuffer(itemCharacter, x, y + i, &itemColor, &black, dbuf);
4199         } else {
4200             theColor = &darkGray;
4201             magic = magicCharDiscoverySuffix(category, i);
4202             if (magic == 1) {
4203                 plotCharToBuffer(G_GOOD_MAGIC, x, y + i, &goodColor, &black, dbuf);
4204             } else if (magic == -1) {
4205                 plotCharToBuffer(G_BAD_MAGIC, x, y + i, &badColor, &black, dbuf);
4206             }
4207         }
4208         strcpy(buf, theTable[i].name);
4209 
4210         if (!theTable[i].identified
4211             && theTable[i].frequency > 0
4212             && totalFrequency > 0) {
4213 
4214             sprintf(buf2, " (%i%%)", theTable[i].frequency * 100 / totalFrequency);
4215             strcat(buf, buf2);
4216         }
4217 
4218         upperCase(buf);
4219         strcat(buf, " ");
4220         printString(buf, x + 2, y + i, theColor, &black, dbuf);
4221     }
4222 }
4223 
printDiscoveriesScreen()4224 void printDiscoveriesScreen() {
4225     short i, j, y;
4226     cellDisplayBuffer dbuf[COLS][ROWS], rbuf[COLS][ROWS];
4227 
4228     clearDisplayBuffer(dbuf);
4229 
4230     printString("-- SCROLLS --", mapToWindowX(2), y = mapToWindowY(1), &flavorTextColor, &black, dbuf);
4231     printDiscoveries(SCROLL, NUMBER_SCROLL_KINDS, G_SCROLL, mapToWindowX(3), ++y, dbuf);
4232 
4233     printString("-- RINGS --", mapToWindowX(2), y += NUMBER_SCROLL_KINDS + 1, &flavorTextColor, &black, dbuf);
4234     printDiscoveries(RING, NUMBER_RING_KINDS, G_RING, mapToWindowX(3), ++y, dbuf);
4235 
4236     printString("-- POTIONS --", mapToWindowX(29), y = mapToWindowY(1), &flavorTextColor, &black, dbuf);
4237     printDiscoveries(POTION, NUMBER_POTION_KINDS, G_POTION, mapToWindowX(30), ++y, dbuf);
4238 
4239     printString("-- STAFFS --", mapToWindowX(53), y = mapToWindowY(1), &flavorTextColor, &black, dbuf);
4240     printDiscoveries(STAFF, NUMBER_STAFF_KINDS, G_STAFF, mapToWindowX(54), ++y, dbuf);
4241 
4242     printString("-- WANDS --", mapToWindowX(53), y += NUMBER_STAFF_KINDS + 1, &flavorTextColor, &black, dbuf);
4243     printDiscoveries(WAND, NUMBER_WAND_KINDS, G_WAND, mapToWindowX(54), ++y, dbuf);
4244 
4245     printString(KEYBOARD_LABELS ? "-- press any key to continue --" : "-- touch anywhere to continue --",
4246                 mapToWindowX(20), mapToWindowY(DROWS-2), &itemMessageColor, &black, dbuf);
4247 
4248     for (i=0; i<COLS; i++) {
4249         for (j=0; j<ROWS; j++) {
4250             dbuf[i][j].opacity = (i < STAT_BAR_WIDTH ? 0 : INTERFACE_OPACITY);
4251         }
4252     }
4253     overlayDisplayBuffer(dbuf, rbuf);
4254 
4255     waitForKeystrokeOrMouseClick();
4256 
4257     overlayDisplayBuffer(rbuf, NULL);
4258 }
4259 
4260 // Creates buttons for the discoveries screen in the buttons pointer; returns the number of buttons created.
4261 //short createDiscoveriesButtons(short category, short count, unsigned short itemCharacter, short x, short y, brogueButton *buttons) {
4262 //  color goodColor, badColor;
4263 //  char whiteColorEscape[20] = "", darkGrayColorEscape[20] = "", yellowColorEscape[20] = "", goodColorEscape[20] = "", badColorEscape[20] = "";
4264 //  short i, magic, symbolCount;
4265 //  itemTable *theTable = tableForItemCategory(category, NULL);
4266 //    char buf[COLS] = "";
4267 //
4268 //  goodColor = goodMessageColor;
4269 //  applyColorAverage(&goodColor, &black, 50);
4270 //  encodeMessageColor(goodColorEscape, 0, &goodColor);
4271 //  badColor = badMessageColor;
4272 //  applyColorAverage(&badColor, &black, 50);
4273 //  encodeMessageColor(badColorEscape, 0, &badColor);
4274 //  encodeMessageColor(whiteColorEscape, 0, &white);
4275 //    encodeMessageColor(darkGrayColorEscape, 0, &darkGray);
4276 //    encodeMessageColor(yellowColorEscape, 0, &itemColor);
4277 //
4278 //  for (i = 0; i < count; i++) {
4279 //        initializeButton(&(buttons[i]));
4280 //        buttons[i].flags = (B_DRAW | B_HOVER_ENABLED | B_ENABLED); // Clear other flags.
4281 //        buttons[i].buttonColor = black;
4282 //        buttons[i].opacity = 100;
4283 //      buttons[i].x = x;
4284 //      buttons[i].y = y + i;
4285 //      symbolCount = 0;
4286 //      if (theTable[i].identified) {
4287 //          strcat(buttons[i].text, yellowColorEscape);
4288 //          buttons[i].symbol[symbolCount++] = itemCharacter;
4289 //          strcat(buttons[i].text, "*");
4290 //            strcat(buttons[i].text, whiteColorEscape);
4291 //            strcat(buttons[i].text, " ");
4292 //        } else {
4293 //            strcat(buttons[i].text, "  ");
4294 //          strcat(buttons[i].text, darkGrayColorEscape);
4295 //      }
4296 //      strcpy(buf, theTable[i].name);
4297 //      upperCase(buf);
4298 //        strcat(buttons[i].text, buf);
4299 //        strcat(buttons[i].text, "  ");
4300 //        strcat(buttons[i].text, darkGrayColorEscape);
4301 //        magic = magicCharDiscoverySuffix(category, i);
4302 //        strcat(buttons[i].text, "(");
4303 //        if (magic != -1) {
4304 //            strcat(buttons[i].text, goodColorEscape);
4305 //            strcat(buttons[i].text, "*");
4306 //            buttons[i].symbol[symbolCount++] = G_GOOD_MAGIC;
4307 //      }
4308 //        if (magic != 1) {
4309 //            strcat(buttons[i].text, badColorEscape);
4310 //            strcat(buttons[i].text, "*");
4311 //            buttons[i].symbol[symbolCount++] = BAD_MAGIC_CHAR;
4312 //        }
4313 //        strcat(buttons[i].text, darkGrayColorEscape);
4314 //        strcat(buttons[i].text, ")");
4315 //  }
4316 //  return i;
4317 //}
4318 //
4319 //void printDiscoveriesScreen() {
4320 //  short i, j, y, buttonCount;
4321 //  cellDisplayBuffer dbuf[COLS][ROWS], rbuf[COLS][ROWS];
4322 //  brogueButton buttons[NUMBER_SCROLL_KINDS + NUMBER_WAND_KINDS + NUMBER_POTION_KINDS + NUMBER_STAFF_KINDS + NUMBER_RING_KINDS] = {{{0}}};
4323 //    rogueEvent theEvent;
4324 //
4325 //  clearDisplayBuffer(dbuf);
4326 //  buttonCount = 0;
4327 //
4328 //  printString("-- SCROLLS --", mapToWindowX(3), y = mapToWindowY(1), &flavorTextColor, &black, dbuf);
4329 //  buttonCount += createDiscoveriesButtons(SCROLL, NUMBER_SCROLL_KINDS, SCROLL_CHAR, mapToWindowX(3), ++y, &(buttons[buttonCount]));
4330 //
4331 //  printString("-- WANDS --", mapToWindowX(3), y += NUMBER_SCROLL_KINDS + 1, &flavorTextColor, &black, dbuf);
4332 //  buttonCount += createDiscoveriesButtons(WAND, NUMBER_WAND_KINDS, WAND_CHAR, mapToWindowX(3), ++y, &(buttons[buttonCount]));
4333 //
4334 //  printString("-- POTIONS --", mapToWindowX(29), y = mapToWindowY(1), &flavorTextColor, &black, dbuf);
4335 //  buttonCount += createDiscoveriesButtons(POTION, NUMBER_POTION_KINDS, POTION_CHAR, mapToWindowX(29), ++y, &(buttons[buttonCount]));
4336 //
4337 //  printString("-- STAFFS --", mapToWindowX(54), y = mapToWindowY(1), &flavorTextColor, &black, dbuf);
4338 //  buttonCount += createDiscoveriesButtons(STAFF, NUMBER_STAFF_KINDS, STAFF_CHAR, mapToWindowX(54), ++y, &(buttons[buttonCount]));
4339 //
4340 //  printString("-- RINGS --", mapToWindowX(54), y += NUMBER_STAFF_KINDS + 1, &flavorTextColor, &black, dbuf);
4341 //  buttonCount += createDiscoveriesButtons(RING, NUMBER_RING_KINDS, RING_CHAR, mapToWindowX(54), ++y, &(buttons[buttonCount]));
4342 //
4343 //  for (i=0; i<COLS; i++) {
4344 //      for (j=0; j<ROWS; j++) {
4345 //          dbuf[i][j].opacity = (i < STAT_BAR_WIDTH ? 0 : INTERFACE_OPACITY);
4346 //      }
4347 //  }
4348 //    overlayDisplayBuffer(dbuf, rbuf);
4349 //    y = buttonInputLoop(buttons,
4350 //                        buttonCount,
4351 //                        mapToWindowX(3),
4352 //                        mapToWindowY(1),
4353 //                        DCOLS - 6,
4354 //                        DROWS - 2,
4355 //                        &theEvent);
4356 //    overlayDisplayBuffer(rbuf, NULL);
4357 //}
4358 
printHighScores(boolean hiliteMostRecent)4359 void printHighScores(boolean hiliteMostRecent) {
4360     short i, hiliteLineNum, maxLength = 0, leftOffset;
4361     rogueHighScoresEntry list[HIGH_SCORES_COUNT] = {{0}};
4362     char buf[DCOLS*3];
4363     color scoreColor;
4364 
4365     hiliteLineNum = getHighScoresList(list);
4366 
4367     if (!hiliteMostRecent) {
4368         hiliteLineNum = -1;
4369     }
4370 
4371     blackOutScreen();
4372 
4373     for (i = 0; i < HIGH_SCORES_COUNT && list[i].score > 0; i++) {
4374         if (strLenWithoutEscapes(list[i].description) > maxLength) {
4375             maxLength = strLenWithoutEscapes(list[i].description);
4376         }
4377     }
4378 
4379     leftOffset = min(COLS - maxLength - 23 - 1, COLS/5);
4380 
4381     scoreColor = black;
4382     applyColorAverage(&scoreColor, &itemMessageColor, 100);
4383     printString("-- HIGH SCORES --", (COLS - 17 + 1) / 2, 0, &scoreColor, &black, 0);
4384 
4385     for (i = 0; i < HIGH_SCORES_COUNT && list[i].score > 0; i++) {
4386         scoreColor = black;
4387         if (i == hiliteLineNum) {
4388             applyColorAverage(&scoreColor, &itemMessageColor, 100);
4389         } else {
4390             applyColorAverage(&scoreColor, &white, 100);
4391             applyColorAverage(&scoreColor, &black, (i * 50 / 24));
4392         }
4393 
4394         // rank
4395         sprintf(buf, "%s%i)", (i + 1 < 10 ? " " : ""), i + 1);
4396         printString(buf, leftOffset, i + 2, &scoreColor, &black, 0);
4397 
4398         // score
4399         sprintf(buf, "%li", list[i].score);
4400         printString(buf, leftOffset + 5, i + 2, &scoreColor, &black, 0);
4401 
4402         // date
4403         printString(list[i].date, leftOffset + 12, i + 2, &scoreColor, &black, 0);
4404 
4405         // description
4406         printString(list[i].description, leftOffset + 23, i + 2, &scoreColor, &black, 0);
4407     }
4408 
4409     scoreColor = black;
4410     applyColorAverage(&scoreColor, &goodMessageColor, 100);
4411 
4412     printString(KEYBOARD_LABELS ? "Press space to continue." : "Touch anywhere to continue.",
4413                 (COLS - strLenWithoutEscapes(KEYBOARD_LABELS ? "Press space to continue." : "Touch anywhere to continue.")) / 2,
4414                 ROWS - 1, &scoreColor, &black, 0);
4415 
4416     commitDraws();
4417     waitForAcknowledgment();
4418 }
4419 
displayGrid(short ** map)4420 void displayGrid(short **map) {
4421     short i, j, score, topRange, bottomRange;
4422     color tempColor, foreColor, backColor;
4423     enum displayGlyph dchar;
4424 
4425     topRange = -30000;
4426     bottomRange = 30000;
4427     tempColor = black;
4428 
4429     if (map == safetyMap && !rogue.updatedSafetyMapThisTurn) {
4430         updateSafetyMap();
4431     }
4432 
4433     for (i=0; i<DCOLS; i++) {
4434         for (j=0; j<DROWS; j++) {
4435             if (cellHasTerrainFlag(i, j, T_WAYPOINT_BLOCKER) || (map[i][j] == map[0][0]) || (i == player.xLoc && j == player.yLoc)) {
4436                 continue;
4437             }
4438             if (map[i][j] > topRange) {
4439                 topRange = map[i][j];
4440                 //if (topRange == 0) {
4441                     //printf("\ntop is zero at %i,%i", i, j);
4442                 //}
4443             }
4444             if (map[i][j] < bottomRange) {
4445                 bottomRange = map[i][j];
4446             }
4447         }
4448     }
4449 
4450     for (i=0; i<DCOLS; i++) {
4451         for (j=0; j<DROWS; j++) {
4452             if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY | T_LAVA_INSTA_DEATH)
4453                 || (map[i][j] == map[0][0])
4454                 || (i == player.xLoc && j == player.yLoc)) {
4455                 continue;
4456             }
4457             score = 300 - (map[i][j] - bottomRange) * 300 / max(1, (topRange - bottomRange));
4458             tempColor.blue = max(min(score, 100), 0);
4459             score -= 100;
4460             tempColor.red = max(min(score, 100), 0);
4461             score -= 100;
4462             tempColor.green = max(min(score, 100), 0);
4463             getCellAppearance(i, j, &dchar, &foreColor, &backColor);
4464             plotCharWithColor(dchar, mapToWindowX(i), mapToWindowY(j), &foreColor, &tempColor);
4465             //colorBlendCell(i, j, &tempColor, 100);//hiliteCell(i, j, &tempColor, 100, false);
4466         }
4467     }
4468     //printf("\ntop: %i; bottom: %i", topRange, bottomRange);
4469 }
4470 
printSeed()4471 void printSeed() {
4472     char buf[COLS];
4473     snprintf(buf, COLS, "Dungeon seed #%llu; turn #%lu; version %s", (unsigned long long)rogue.seed, rogue.playerTurnNumber, BROGUE_VERSION_STRING);
4474     message(buf, 0);
4475 }
4476 
printProgressBar(short x,short y,const char barLabel[COLS],long amtFilled,long amtMax,color * fillColor,boolean dim)4477 void printProgressBar(short x, short y, const char barLabel[COLS], long amtFilled, long amtMax, color *fillColor, boolean dim) {
4478     char barText[] = "                    "; // string length is 20
4479     short i, labelOffset;
4480     color currentFillColor, textColor, progressBarColor, darkenedBarColor;
4481 
4482     if (y >= ROWS - 1) { // don't write over the depth number
4483         return;
4484     }
4485 
4486     if (amtFilled > amtMax) {
4487         amtFilled = amtMax;
4488     }
4489 
4490     if (amtMax <= 0) {
4491         amtMax = 1;
4492     }
4493 
4494     progressBarColor = *fillColor;
4495     if (!(y % 2)) {
4496         applyColorAverage(&progressBarColor, &black, 25);
4497     }
4498 
4499     if (dim) {
4500         applyColorAverage(&progressBarColor, &black, 50);
4501     }
4502     darkenedBarColor = progressBarColor;
4503     applyColorAverage(&darkenedBarColor, &black, 75);
4504 
4505     labelOffset = (20 - strlen(barLabel)) / 2;
4506     for (i = 0; i < (short) strlen(barLabel); i++) {
4507         barText[i + labelOffset] = barLabel[i];
4508     }
4509 
4510     amtFilled = clamp(amtFilled, 0, amtMax);
4511 
4512     if (amtMax < 10000000) {
4513         amtFilled *= 100;
4514         amtMax *= 100;
4515     }
4516 
4517     for (i=0; i<20; i++) {
4518         currentFillColor = (i <= (20 * amtFilled / amtMax) ? progressBarColor : darkenedBarColor);
4519         if (i == 20 * amtFilled / amtMax) {
4520             applyColorAverage(&currentFillColor, &black, 75 - 75 * (amtFilled % (amtMax / 20)) / (amtMax / 20));
4521         }
4522         textColor = (dim ? gray : white);
4523         applyColorAverage(&textColor, &currentFillColor, (dim ? 50 : 33));
4524         plotCharWithColor(barText[i], x + i, y, &textColor, &currentFillColor);
4525     }
4526 }
4527 
4528 // Very low-level. Changes displayBuffer directly.
highlightScreenCell(short x,short y,color * highlightColor,short strength)4529 void highlightScreenCell(short x, short y, color *highlightColor, short strength) {
4530     color tempColor;
4531 
4532     tempColor = colorFromComponents(displayBuffer[x][y].foreColorComponents);
4533     applyColorAugment(&tempColor, highlightColor, strength);
4534     storeColorComponents(displayBuffer[x][y].foreColorComponents, &tempColor);
4535 
4536     tempColor = colorFromComponents(displayBuffer[x][y].backColorComponents);
4537     applyColorAugment(&tempColor, highlightColor, strength);
4538     storeColorComponents(displayBuffer[x][y].backColorComponents, &tempColor);
4539 }
4540 
estimatedArmorValue()4541 short estimatedArmorValue() {
4542     short retVal;
4543 
4544     retVal = ((armorTable[rogue.armor->kind].range.upperBound + armorTable[rogue.armor->kind].range.lowerBound) / 2) / 10;
4545     retVal += strengthModifier(rogue.armor) / FP_FACTOR;
4546     retVal -= player.status[STATUS_DONNING];
4547 
4548     return max(0, retVal);
4549 }
4550 
creatureHealthChangePercent(creature * monst)4551 short creatureHealthChangePercent(creature *monst) {
4552     if (monst->previousHealthPoints <= 0) {
4553         return 0;
4554     }
4555     // ignore overhealing from tranference
4556     return 100 * (monst->currentHP - min(monst->previousHealthPoints, monst->info.maxHP)) / monst->info.maxHP;
4557 }
4558 
4559 // returns the y-coordinate after the last line printed
printMonsterInfo(creature * monst,short y,boolean dim,boolean highlight)4560 short printMonsterInfo(creature *monst, short y, boolean dim, boolean highlight) {
4561     char buf[COLS * 2], buf2[COLS * 2], monstName[COLS], tempColorEscape[5], grayColorEscape[5];
4562     enum displayGlyph monstChar;
4563     color monstForeColor, monstBackColor, healthBarColor, tempColor;
4564     short initialY, i, j, highlightStrength, displayedArmor, percent;
4565     boolean inPath;
4566     short oldRNG;
4567 
4568     const char hallucinationStrings[16][COLS] = {
4569         "     (Dancing)      ",
4570         "     (Singing)      ",
4571         "  (Pontificating)   ",
4572         "     (Skipping)     ",
4573         "     (Spinning)     ",
4574         "      (Crying)      ",
4575         "     (Laughing)     ",
4576         "     (Humming)      ",
4577         "    (Whistling)     ",
4578         "    (Quivering)     ",
4579         "    (Muttering)     ",
4580         "    (Gibbering)     ",
4581         "     (Giggling)     ",
4582         "     (Moaning)      ",
4583         "    (Shrieking)     ",
4584         "   (Caterwauling)   ",
4585     };
4586     const char statusStrings[NUMBER_OF_STATUS_EFFECTS][COLS] = {
4587         "Searching",
4588         "Donning Armor",
4589         "Weakened: -",
4590         "Telepathic",
4591         "Hallucinating",
4592         "Levitating",
4593         "Slowed",
4594         "Hasted",
4595         "Confused",
4596         "Burning",
4597         "Paralyzed",
4598         "Poisoned",
4599         "Stuck",
4600         "Nauseous",
4601         "Discordant",
4602         "Immune to Fire",
4603         "", // STATUS_EXPLOSION_IMMUNITY,
4604         "", // STATUS_NUTRITION,
4605         "", // STATUS_ENTERS_LEVEL_IN,
4606         "", // STATUS_ENRAGED,
4607         "Frightened",
4608         "Entranced",
4609         "Darkened",
4610         "Lifespan",
4611         "Shielded",
4612         "Invisible",
4613     };
4614 
4615     if (y >= ROWS - 1) {
4616         return ROWS - 1;
4617     }
4618 
4619     initialY = y;
4620 
4621     oldRNG = rogue.RNG;
4622     rogue.RNG = RNG_COSMETIC;
4623     //assureCosmeticRNG;
4624 
4625     if (y < ROWS - 1) {
4626         printString("                    ", 0, y, &white, &black, 0); // Start with a blank line
4627 
4628         // Unhighlight if it's highlighted as part of the path.
4629         inPath = (pmap[monst->xLoc][monst->yLoc].flags & IS_IN_PATH) ? true : false;
4630         pmap[monst->xLoc][monst->yLoc].flags &= ~IS_IN_PATH;
4631         getCellAppearance(monst->xLoc, monst->yLoc, &monstChar, &monstForeColor, &monstBackColor);
4632         applyColorBounds(&monstForeColor, 0, 100);
4633         applyColorBounds(&monstBackColor, 0, 100);
4634         if (inPath) {
4635             pmap[monst->xLoc][monst->yLoc].flags |= IS_IN_PATH;
4636         }
4637 
4638         if (dim) {
4639             applyColorAverage(&monstForeColor, &black, 50);
4640             applyColorAverage(&monstBackColor, &black, 50);
4641         } else if (highlight) {
4642             applyColorAugment(&monstForeColor, &black, 100);
4643             applyColorAugment(&monstBackColor, &black, 100);
4644         }
4645         plotCharWithColor(monstChar, 0, y, &monstForeColor, &monstBackColor);
4646         if(monst->carriedItem) {
4647             plotCharWithColor(monst->carriedItem->displayChar, 1, y, &itemColor, &black);
4648         }
4649         monsterName(monstName, monst, false);
4650         upperCase(monstName);
4651 
4652         if (monst == &player) {
4653             if (player.status[STATUS_INVISIBLE]) {
4654                 strcat(monstName, " xxxx");
4655                 encodeMessageColor(monstName, strlen(monstName) - 4, &monstForeColor);
4656                 strcat(monstName, "(invisible)");
4657             } else if (playerInDarkness()) {
4658                 strcat(monstName, " xxxx");
4659                 //encodeMessageColor(monstName, strlen(monstName) - 4, &playerInDarknessColor);
4660                 encodeMessageColor(monstName, strlen(monstName) - 4, &monstForeColor);
4661                 strcat(monstName, "(dark)");
4662             } else if (!(pmap[player.xLoc][player.yLoc].flags & IS_IN_SHADOW)) {
4663                 strcat(monstName, " xxxx");
4664                 //encodeMessageColor(monstName, strlen(monstName) - 4, &playerInLightColor);
4665                 encodeMessageColor(monstName, strlen(monstName) - 4, &monstForeColor);
4666                 strcat(monstName, "(lit)");
4667             }
4668         }
4669 
4670         sprintf(buf, ": %s", monstName);
4671         printString(buf, monst->carriedItem?2:1, y++, (dim ? &gray : &white), &black, 0);
4672     }
4673 
4674     // mutation, if any
4675     if (y < ROWS - 1
4676         && monst->mutationIndex >= 0
4677         && (!player.status[STATUS_HALLUCINATING] || rogue.playbackOmniscience)) {
4678 
4679         strcpy(buf, "                    ");
4680         sprintf(buf2, "xxxx(%s)", mutationCatalog[monst->mutationIndex].title);
4681         tempColor = *mutationCatalog[monst->mutationIndex].textColor;
4682         if (dim) {
4683             applyColorAverage(&tempColor, &black, 50);
4684         }
4685         encodeMessageColor(buf2, 0, &tempColor);
4686         strcpy(buf + ((strLenWithoutEscapes(buf) - strLenWithoutEscapes(buf2)) / 2), buf2);
4687         for (i = strlen(buf); i < 20 + 4; i++) {
4688             buf[i] = ' ';
4689         }
4690         buf[24] = '\0';
4691         printString(buf, 0, y++, (dim ? &gray : &white), &black, 0);
4692     }
4693 
4694     // hit points
4695     if (monst->info.maxHP > 1
4696         && !(monst->info.flags & MONST_INVULNERABLE)) {
4697 
4698         if (monst == &player) {
4699             healthBarColor = redBar;
4700             applyColorAverage(&healthBarColor, &blueBar, min(100, 100 * player.currentHP / player.info.maxHP));
4701         } else {
4702             healthBarColor = blueBar;
4703         }
4704         percent = creatureHealthChangePercent(monst);
4705         if (monst->currentHP <= 0) {
4706             strcpy(buf, "Dead");
4707         } else if (percent != 0) {
4708             strcpy(buf, "       Health       ");
4709             sprintf(buf2, "(%s%i%%)", percent > 0 ? "+" : "", percent);
4710             strcpy(&(buf[20 - strlen(buf2)]), buf2);
4711         } else {
4712             strcpy(buf, "Health");
4713         }
4714         printProgressBar(0, y++, buf, monst->currentHP, monst->info.maxHP, &healthBarColor, dim);
4715     }
4716 
4717     if (monst == &player) {
4718         // nutrition
4719         if (player.status[STATUS_NUTRITION] > HUNGER_THRESHOLD) {
4720             printProgressBar(0, y++, "Nutrition", player.status[STATUS_NUTRITION], STOMACH_SIZE, &blueBar, dim);
4721         } else if (player.status[STATUS_NUTRITION] > WEAK_THRESHOLD) {
4722             printProgressBar(0, y++, "Nutrition (Hungry)", player.status[STATUS_NUTRITION], STOMACH_SIZE, &blueBar, dim);
4723         } else if (player.status[STATUS_NUTRITION] > FAINT_THRESHOLD) {
4724             printProgressBar(0, y++, "Nutrition (Weak)", player.status[STATUS_NUTRITION], STOMACH_SIZE, &blueBar, dim);
4725         } else if (player.status[STATUS_NUTRITION] > 0) {
4726             printProgressBar(0, y++, "Nutrition (Faint)", player.status[STATUS_NUTRITION], STOMACH_SIZE, &blueBar, dim);
4727         } else if (y < ROWS - 1) {
4728             printString("      STARVING      ", 0, y++, &badMessageColor, &black, NULL);
4729         }
4730     }
4731 
4732     if (!player.status[STATUS_HALLUCINATING] || rogue.playbackOmniscience || monst == &player) {
4733 
4734         for (i=0; i<NUMBER_OF_STATUS_EFFECTS; i++) {
4735             if (i == STATUS_WEAKENED && monst->status[i] > 0) {
4736                 sprintf(buf, "%s%i", statusStrings[STATUS_WEAKENED], monst->weaknessAmount);
4737                 printProgressBar(0, y++, buf, monst->status[i], monst->maxStatus[i], &redBar, dim);
4738             } else if (i == STATUS_LEVITATING && monst->status[i] > 0) {
4739                 printProgressBar(0, y++, (monst == &player ? "Levitating" : "Flying"), monst->status[i], monst->maxStatus[i], &redBar, dim);
4740             } else if (i == STATUS_POISONED
4741                        && monst->status[i] > 0) {
4742 
4743 
4744                 if (monst->status[i] * monst->poisonAmount >= monst->currentHP) {
4745                     strcpy(buf, "Fatal Poison");
4746                 } else {
4747                     strcpy(buf, "Poisoned");
4748                 }
4749                 if (monst->poisonAmount == 1) {
4750                     printProgressBar(0, y++, buf, monst->status[i], monst->maxStatus[i], &redBar, dim);
4751                 } else {
4752                     sprintf(buf2, "%s (x%i)",
4753                             buf,
4754                             monst->poisonAmount);
4755                     printProgressBar(0, y++, buf2, monst->status[i], monst->maxStatus[i], &redBar, dim);
4756                 }
4757             } else if (statusStrings[i][0] && monst->status[i] > 0) {
4758                 printProgressBar(0, y++, statusStrings[i], monst->status[i], monst->maxStatus[i], &redBar, dim);
4759             }
4760         }
4761         if (monst->targetCorpseLoc[0] == monst->xLoc && monst->targetCorpseLoc[1] == monst->yLoc) {
4762             printProgressBar(0, y++,  monsterText[monst->info.monsterID].absorbStatus, monst->corpseAbsorptionCounter, 20, &redBar, dim);
4763         }
4764     }
4765 
4766     if (monst != &player
4767         && (!(monst->info.flags & MONST_INANIMATE)
4768             || monst->creatureState == MONSTER_ALLY)) {
4769 
4770             if (y < ROWS - 1) {
4771                 if (monst->wasNegated
4772                     && monst->newPowerCount == monst->totalPowerCount
4773                     && y < ROWS - 1
4774                     && (!player.status[STATUS_HALLUCINATING] || rogue.playbackOmniscience )) {
4775                     printString("      Negated       ", 0, y++, (dim ? &darkPink : &pink), &black, 0);
4776                 }
4777                 if (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience && y < ROWS - 1) {
4778                     printString(hallucinationStrings[rand_range(0, 9)], 0, y++, (dim ? &darkGray : &gray), &black, 0);
4779                 } else if (monst->bookkeepingFlags & MB_CAPTIVE && y < ROWS - 1) {
4780                     printString("     (Captive)      ", 0, y++, (dim ? &darkGray : &gray), &black, 0);
4781                 } else if ((monst->info.flags & MONST_RESTRICTED_TO_LIQUID)
4782                            && !cellHasTMFlag(monst->xLoc, monst->yLoc, TM_ALLOWS_SUBMERGING)) {
4783                     printString("     (Helpless)     ", 0, y++, (dim ? &darkGray : &gray), &black, 0);
4784                 } else if (monst->creatureState == MONSTER_SLEEPING && y < ROWS - 1) {
4785                     printString("     (Sleeping)     ", 0, y++, (dim ? &darkGray : &gray), &black, 0);
4786                 } else if ((monst->creatureState == MONSTER_ALLY) && y < ROWS - 1) {
4787                     printString("       (Ally)       ", 0, y++, (dim ? &darkGray : &gray), &black, 0);
4788                 } else if (monst->creatureState == MONSTER_FLEEING && y < ROWS - 1) {
4789                     printString("     (Fleeing)      ", 0, y++, (dim ? &darkGray : &gray), &black, 0);
4790                 } else if ((monst->creatureState == MONSTER_WANDERING) && y < ROWS - 1) {
4791                     if ((monst->bookkeepingFlags & MB_FOLLOWER) && monst->leader && (monst->leader->info.flags & MONST_IMMOBILE)) {
4792                         // follower of an immobile leader -- i.e. a totem
4793                         printString("    (Worshiping)    ", 0, y++, (dim ? &darkGray : &gray), &black, 0);
4794                     } else if ((monst->bookkeepingFlags & MB_FOLLOWER) && monst->leader && (monst->leader->bookkeepingFlags & MB_CAPTIVE)) {
4795                         // actually a captor/torturer
4796                         printString("     (Guarding)     ", 0, y++, (dim ? &darkGray : &gray), &black, 0);
4797                     } else {
4798                         printString("    (Wandering)     ", 0, y++, (dim ? &darkGray : &gray), &black, 0);
4799                     }
4800                 } else if (monst->ticksUntilTurn > max(0, player.ticksUntilTurn) + player.movementSpeed) {
4801                     printString("   (Off balance)    ", 0, y++, (dim ? &darkGray : &gray), &black, 0);
4802                 } else if ((monst->creatureState == MONSTER_TRACKING_SCENT) && y < ROWS - 1) {
4803                     printString("     (Hunting)      ", 0, y++, (dim ? &darkGray : &gray), &black, 0);
4804                 }
4805             }
4806         } else if (monst == &player) {
4807             if (y < ROWS - 1) {
4808                 tempColorEscape[0] = '\0';
4809                 grayColorEscape[0] = '\0';
4810                 if (player.status[STATUS_WEAKENED]) {
4811                     tempColor = red;
4812                     if (dim) {
4813                         applyColorAverage(&tempColor, &black, 50);
4814                     }
4815                     encodeMessageColor(tempColorEscape, 0, &tempColor);
4816                     encodeMessageColor(grayColorEscape, 0, (dim ? &darkGray : &gray));
4817                 }
4818 
4819                 displayedArmor = displayedArmorValue();
4820 
4821                 if (!rogue.armor || rogue.armor->flags & ITEM_IDENTIFIED || rogue.playbackOmniscience) {
4822 
4823                     sprintf(buf, "Str: %s%i%s  Armor: %i",
4824                             tempColorEscape,
4825                             rogue.strength - player.weaknessAmount,
4826                             grayColorEscape,
4827                             displayedArmor);
4828                 } else {
4829                     sprintf(buf, "Str: %s%i%s  Armor: %i?",
4830                             tempColorEscape,
4831                             rogue.strength - player.weaknessAmount,
4832                             grayColorEscape,
4833                             estimatedArmorValue());
4834                 }
4835                 //buf[20] = '\0';
4836                 printString("                    ", 0, y, &white, &black, 0);
4837                 printString(buf, (20 - strLenWithoutEscapes(buf)) / 2, y++, (dim ? &darkGray : &gray), &black, 0);
4838             }
4839             if (y < ROWS - 1 && rogue.gold) {
4840                 sprintf(buf, "Gold: %li", rogue.gold);
4841                 buf[20] = '\0';
4842                 printString("                    ", 0, y, &white, &black, 0);
4843                 printString(buf, (20 - strLenWithoutEscapes(buf)) / 2, y++, (dim ? &darkGray : &gray), &black, 0);
4844             }
4845             if (y < ROWS - 1) {
4846                 tempColorEscape[0] = '\0';
4847                 grayColorEscape[0] = '\0';
4848                 tempColor = playerInShadowColor;
4849                 percent = (rogue.aggroRange - 2) * 100 / 28;
4850                 applyColorAverage(&tempColor, &black, percent);
4851                 applyColorAugment(&tempColor, &playerInLightColor, percent);
4852                 if (dim) {
4853                     applyColorAverage(&tempColor, &black, 50);
4854                 }
4855                 encodeMessageColor(tempColorEscape, 0, &tempColor);
4856                 encodeMessageColor(grayColorEscape, 0, (dim ? &darkGray : &gray));
4857                 sprintf(buf, "%sStealth range: %i%s",
4858                         tempColorEscape,
4859                         rogue.aggroRange,
4860                         grayColorEscape);
4861                 printString("                    ", 0, y, &white, &black, 0);
4862                 printString(buf, 1, y++, (dim ? &darkGray : &gray), &black, 0);
4863             }
4864         }
4865 
4866     if (y < ROWS - 1) {
4867         printString("                    ", 0, y++, (dim ? &darkGray : &gray), &black, 0);
4868     }
4869 
4870     if (highlight) {
4871         for (i=0; i<20; i++) {
4872             highlightStrength = smoothHiliteGradient(i, 20-1) / 10;
4873             for (j=initialY; j < (y == ROWS - 1 ? y : min(y - 1, ROWS - 1)); j++) {
4874                 highlightScreenCell(i, j, &white, highlightStrength);
4875             }
4876         }
4877     }
4878 
4879     restoreRNG;
4880     return y;
4881 }
4882 
describeHallucinatedItem(char * buf)4883 void describeHallucinatedItem(char *buf) {
4884     const unsigned short itemCats[10] = {FOOD, WEAPON, ARMOR, POTION, SCROLL, STAFF, WAND, RING, CHARM, GOLD};
4885     short cat, kind, maxKinds;
4886     assureCosmeticRNG;
4887     cat = itemCats[rand_range(0, 9)];
4888     tableForItemCategory(cat, &maxKinds);
4889     kind = rand_range(0, maxKinds - 1);
4890     describedItemBasedOnParameters(cat, kind, 1, 1, buf);
4891     restoreRNG;
4892 }
4893 
4894 // Returns the y-coordinate after the last line printed.
printItemInfo(item * theItem,short y,boolean dim,boolean highlight)4895 short printItemInfo(item *theItem, short y, boolean dim, boolean highlight) {
4896     char name[COLS * 3];
4897     enum displayGlyph itemChar;
4898     color itemForeColor, itemBackColor;
4899     short initialY, i, j, highlightStrength, lineCount;
4900     boolean inPath;
4901     short oldRNG;
4902 
4903     if (y >= ROWS - 1) {
4904         return ROWS - 1;
4905     }
4906 
4907     initialY = y;
4908 
4909     oldRNG = rogue.RNG;
4910     rogue.RNG = RNG_COSMETIC;
4911     //assureCosmeticRNG;
4912 
4913     if (y < ROWS - 1) {
4914         // Unhighlight if it's highlighted as part of the path.
4915         inPath = (pmap[theItem->xLoc][theItem->yLoc].flags & IS_IN_PATH) ? true : false;
4916         pmap[theItem->xLoc][theItem->yLoc].flags &= ~IS_IN_PATH;
4917         getCellAppearance(theItem->xLoc, theItem->yLoc, &itemChar, &itemForeColor, &itemBackColor);
4918         applyColorBounds(&itemForeColor, 0, 100);
4919         applyColorBounds(&itemBackColor, 0, 100);
4920         if (inPath) {
4921             pmap[theItem->xLoc][theItem->yLoc].flags |= IS_IN_PATH;
4922         }
4923         if (dim) {
4924             applyColorAverage(&itemForeColor, &black, 50);
4925             applyColorAverage(&itemBackColor, &black, 50);
4926         }
4927         plotCharWithColor(itemChar, 0, y, &itemForeColor, &itemBackColor);
4928         printString(":                  ", 1, y, (dim ? &gray : &white), &black, 0);
4929         if (rogue.playbackOmniscience || !player.status[STATUS_HALLUCINATING]) {
4930             itemName(theItem, name, true, true, (dim ? &gray : &white));
4931         } else {
4932             describeHallucinatedItem(name);
4933         }
4934         upperCase(name);
4935         lineCount = wrapText(NULL, name, 20-3);
4936         for (i=initialY + 1; i <= initialY + lineCount + 1 && i < ROWS - 1; i++) {
4937             printString("                    ", 0, i, (dim ? &darkGray : &gray), &black, 0);
4938         }
4939         y = printStringWithWrapping(name, 3, y, 20-3, (dim ? &gray : &white), &black, NULL); // Advances y.
4940     }
4941 
4942     if (highlight) {
4943         for (i=0; i<20; i++) {
4944             highlightStrength = smoothHiliteGradient(i, 20-1) / 10;
4945             for (j=initialY; j <= y && j < ROWS - 1; j++) {
4946                 highlightScreenCell(i, j, &white, highlightStrength);
4947             }
4948         }
4949     }
4950     y += 2;
4951 
4952     restoreRNG;
4953     return y;
4954 }
4955 
4956 // Returns the y-coordinate after the last line printed.
printTerrainInfo(short x,short y,short py,const char * description,boolean dim,boolean highlight)4957 short printTerrainInfo(short x, short y, short py, const char *description, boolean dim, boolean highlight) {
4958     enum displayGlyph displayChar;
4959     color foreColor, backColor;
4960     short initialY, i, j, highlightStrength, lineCount;
4961     boolean inPath;
4962     char name[DCOLS*2];
4963     color textColor;
4964     short oldRNG;
4965 
4966     if (py >= ROWS - 1) {
4967         return ROWS - 1;
4968     }
4969 
4970     initialY = py;
4971 
4972     oldRNG = rogue.RNG;
4973     rogue.RNG = RNG_COSMETIC;
4974     //assureCosmeticRNG;
4975 
4976     if (py < ROWS - 1) {
4977         // Unhighlight if it's highlighted as part of the path.
4978         inPath = (pmap[x][y].flags & IS_IN_PATH) ? true : false;
4979         pmap[x][y].flags &= ~IS_IN_PATH;
4980         getCellAppearance(x, y, &displayChar, &foreColor, &backColor);
4981         applyColorBounds(&foreColor, 0, 100);
4982         applyColorBounds(&backColor, 0, 100);
4983         if (inPath) {
4984             pmap[x][y].flags |= IS_IN_PATH;
4985         }
4986         if (dim) {
4987             applyColorAverage(&foreColor, &black, 50);
4988             applyColorAverage(&backColor, &black, 50);
4989         }
4990         plotCharWithColor(displayChar, 0, py, &foreColor, &backColor);
4991         printString(":                  ", 1, py, (dim ? &gray : &white), &black, 0);
4992         strcpy(name, description);
4993         upperCase(name);
4994         lineCount = wrapText(NULL, name, 20-3);
4995         for (i=initialY + 1; i <= initialY + lineCount + 1 && i < ROWS - 1; i++) {
4996             printString("                    ", 0, i, (dim ? &darkGray : &gray), &black, 0);
4997         }
4998         textColor = flavorTextColor;
4999         if (dim) {
5000             applyColorScalar(&textColor, 50);
5001         }
5002         py = printStringWithWrapping(name, 3, py, 20-3, &textColor, &black, NULL); // Advances y.
5003     }
5004 
5005     if (highlight) {
5006         for (i=0; i<20; i++) {
5007             highlightStrength = smoothHiliteGradient(i, 20-1) / 10;
5008             for (j=initialY; j <= py && j < ROWS - 1; j++) {
5009                 highlightScreenCell(i, j, &white, highlightStrength);
5010             }
5011         }
5012     }
5013     py += 2;
5014 
5015     restoreRNG;
5016     return py;
5017 }
5018 
rectangularShading(short x,short y,short width,short height,const color * backColor,short opacity,cellDisplayBuffer dbuf[COLS][ROWS])5019 void rectangularShading(short x, short y, short width, short height,
5020                         const color *backColor, short opacity, cellDisplayBuffer dbuf[COLS][ROWS]) {
5021     short i, j, dist;
5022 
5023     assureCosmeticRNG;
5024     for (i=0; i<COLS; i++) {
5025         for (j=0; j<ROWS; j++) {
5026             storeColorComponents(dbuf[i][j].backColorComponents, backColor);
5027 
5028             if (i >= x && i < x + width
5029                 && j >= y && j < y + height) {
5030                 dbuf[i][j].opacity = min(100, opacity);
5031             } else {
5032                 dist = 0;
5033                 dist += max(0, max(x - i, i - x - width + 1));
5034                 dist += max(0, max(y - j, j - y - height + 1));
5035                 dbuf[i][j].opacity = (int) ((opacity - 10) / max(1, dist));
5036                 if (dbuf[i][j].opacity < 3) {
5037                     dbuf[i][j].opacity = 0;
5038                 }
5039             }
5040         }
5041     }
5042 
5043 //  for (i=0; i<COLS; i++) {
5044 //      for (j=0; j<ROWS; j++) {
5045 //          if (i >= x && i < x + width && j >= y && j < y + height) {
5046 //              plotCharWithColor(' ', i, j, &white, &darkGreen);
5047 //          }
5048 //      }
5049 //  }
5050 //  displayMoreSign();
5051 
5052     restoreRNG;
5053 }
5054 
5055 #define MIN_DEFAULT_INFO_PANEL_WIDTH    33
5056 
5057 // y and width are optional and will be automatically calculated if width <= 0.
5058 // Width will automatically be widened if the text would otherwise fall off the bottom of the
5059 // screen, and x will be adjusted to keep the widening box from spilling off the right of the
5060 // screen.
5061 // If buttons are provided, we'll extend the text box downward, re-position the buttons,
5062 // run a button input loop and return the result.
5063 // (Returns -1 for canceled; otherwise the button index number.)
printTextBox(char * textBuf,short x,short y,short width,color * foreColor,color * backColor,cellDisplayBuffer rbuf[COLS][ROWS],brogueButton * buttons,short buttonCount)5064 short printTextBox(char *textBuf, short x, short y, short width,
5065                    color *foreColor, color *backColor,
5066                    cellDisplayBuffer rbuf[COLS][ROWS],
5067                    brogueButton *buttons, short buttonCount) {
5068     cellDisplayBuffer dbuf[COLS][ROWS];
5069 
5070     short x2, y2, lineCount, i, bx, by, padLines;
5071 
5072     if (width <= 0) {
5073         // autocalculate y and width
5074         if (x < DCOLS / 2 - 1) {
5075             x2 = mapToWindowX(x + 10);
5076             width = (DCOLS - x) - 20;
5077         } else {
5078             x2 = mapToWindowX(10);
5079             width = x - 20;
5080         }
5081         y2 = mapToWindowY(2);
5082 
5083         if (width < MIN_DEFAULT_INFO_PANEL_WIDTH) {
5084             x2 -= (MIN_DEFAULT_INFO_PANEL_WIDTH - width) / 2;
5085             width = MIN_DEFAULT_INFO_PANEL_WIDTH;
5086         }
5087     } else {
5088         y2 = y;
5089         x2 = x;
5090     }
5091 
5092     while (((lineCount = wrapText(NULL, textBuf, width)) + y2) >= ROWS - 2 && width < COLS-5) {
5093         // While the text doesn't fit and the width doesn't fill the screen, increase the width.
5094         width++;
5095         if (x2 + (width / 2) > COLS / 2) {
5096             // If the horizontal midpoint of the text box is on the right half of the screen,
5097             // move the box one space to the left.
5098             x2--;
5099         }
5100     }
5101 
5102     if (buttonCount > 0) {
5103         padLines = 2;
5104         bx = x2 + width;
5105         by = y2 + lineCount + 1;
5106         for (i=0; i<buttonCount; i++) {
5107             if (buttons[i].flags & B_DRAW) {
5108                 bx -= strLenWithoutEscapes(buttons[i].text) + 2;
5109                 buttons[i].x = bx;
5110                 buttons[i].y = by;
5111                 if (bx < x2) {
5112                     // Buttons can wrap to the next line (though are double-spaced).
5113                     bx = x2 + width - (strLenWithoutEscapes(buttons[i].text) + 2);
5114                     by += 2;
5115                     padLines += 2;
5116                     buttons[i].x = bx;
5117                     buttons[i].y = by;
5118                 }
5119             }
5120         }
5121     } else {
5122         padLines = 0;
5123     }
5124 
5125     clearDisplayBuffer(dbuf);
5126     printStringWithWrapping(textBuf, x2, y2, width, foreColor, backColor, dbuf);
5127     rectangularShading(x2, y2, width, lineCount + padLines, backColor, INTERFACE_OPACITY, dbuf);
5128     overlayDisplayBuffer(dbuf, rbuf);
5129 
5130     if (buttonCount > 0) {
5131         return buttonInputLoop(buttons, buttonCount, x2, y2, width, by - y2 + 1 + padLines, NULL);
5132     } else {
5133         return -1;
5134     }
5135 }
5136 
printMonsterDetails(creature * monst,cellDisplayBuffer rbuf[COLS][ROWS])5137 void printMonsterDetails(creature *monst, cellDisplayBuffer rbuf[COLS][ROWS]) {
5138     char textBuf[COLS * 100];
5139 
5140     monsterDetails(textBuf, monst);
5141     printTextBox(textBuf, monst->xLoc, 0, 0, &white, &black, rbuf, NULL, 0);
5142 }
5143 
5144 // Displays the item info box with the dark blue background.
5145 // If includeButtons is true, we include buttons for item actions.
5146 // Returns the key of an action to take, if any; otherwise -1.
printCarriedItemDetails(item * theItem,short x,short y,short width,boolean includeButtons,cellDisplayBuffer rbuf[COLS][ROWS])5147 unsigned long printCarriedItemDetails(item *theItem,
5148                                       short x, short y, short width,
5149                                       boolean includeButtons,
5150                                       cellDisplayBuffer rbuf[COLS][ROWS]) {
5151     char textBuf[COLS * 100], goldColorEscape[5] = "", whiteColorEscape[5] = "";
5152     brogueButton buttons[20] = {{{0}}};
5153     short b;
5154 
5155     itemDetails(textBuf, theItem);
5156 
5157     for (b=0; b<20; b++) {
5158         initializeButton(&(buttons[b]));
5159         buttons[b].flags |= B_WIDE_CLICK_AREA;
5160     }
5161 
5162     b = 0;
5163     if (includeButtons) {
5164         encodeMessageColor(goldColorEscape, 0, KEYBOARD_LABELS ? &yellow : &white);
5165         encodeMessageColor(whiteColorEscape, 0, &white);
5166 
5167         if (theItem->category & (FOOD | SCROLL | POTION | WAND | STAFF | CHARM)) {
5168             sprintf(buttons[b].text, "   %sa%spply   ", goldColorEscape, whiteColorEscape);
5169             buttons[b].hotkey[0] = APPLY_KEY;
5170             b++;
5171         }
5172         if (theItem->category & (ARMOR | WEAPON | RING)) {
5173             if (theItem->flags & ITEM_EQUIPPED) {
5174                 sprintf(buttons[b].text, "  %sr%semove   ", goldColorEscape, whiteColorEscape);
5175                 buttons[b].hotkey[0] = UNEQUIP_KEY;
5176                 b++;
5177             } else {
5178                 sprintf(buttons[b].text, "   %se%squip   ", goldColorEscape, whiteColorEscape);
5179                 buttons[b].hotkey[0] = EQUIP_KEY;
5180                 b++;
5181             }
5182         }
5183         sprintf(buttons[b].text, "   %sd%srop    ", goldColorEscape, whiteColorEscape);
5184         buttons[b].hotkey[0] = DROP_KEY;
5185         b++;
5186 
5187         sprintf(buttons[b].text, "   %st%shrow   ", goldColorEscape, whiteColorEscape);
5188         buttons[b].hotkey[0] = THROW_KEY;
5189         b++;
5190 
5191         if (itemCanBeCalled(theItem)) {
5192             sprintf(buttons[b].text, "   %sc%sall    ", goldColorEscape, whiteColorEscape);
5193             buttons[b].hotkey[0] = CALL_KEY;
5194             b++;
5195         }
5196 
5197         if (KEYBOARD_LABELS) {
5198             sprintf(buttons[b].text, "  %sR%selabel  ", goldColorEscape, whiteColorEscape);
5199             buttons[b].hotkey[0] = RELABEL_KEY;
5200             b++;
5201         }
5202 
5203         // Add invisible previous and next buttons, so up and down arrows can page through items.
5204         // Previous
5205         buttons[b].flags = B_ENABLED; // clear everything else
5206         buttons[b].hotkey[0] = UP_KEY;
5207         buttons[b].hotkey[1] = NUMPAD_8;
5208         buttons[b].hotkey[2] = UP_ARROW;
5209         b++;
5210         // Next
5211         buttons[b].flags = B_ENABLED; // clear everything else
5212         buttons[b].hotkey[0] = DOWN_KEY;
5213         buttons[b].hotkey[1] = NUMPAD_2;
5214         buttons[b].hotkey[2] = DOWN_ARROW;
5215         b++;
5216     }
5217     b = printTextBox(textBuf, x, y, width, &white, &interfaceBoxColor, rbuf, buttons, b);
5218 
5219     if (!includeButtons) {
5220         waitForKeystrokeOrMouseClick();
5221         return -1;
5222     }
5223 
5224     if (b >= 0) {
5225         return buttons[b].hotkey[0];
5226     } else {
5227         return -1;
5228     }
5229 }
5230 
5231 // Returns true if an action was taken.
printFloorItemDetails(item * theItem,cellDisplayBuffer rbuf[COLS][ROWS])5232 void printFloorItemDetails(item *theItem, cellDisplayBuffer rbuf[COLS][ROWS]) {
5233     char textBuf[COLS * 100];
5234 
5235     itemDetails(textBuf, theItem);
5236     printTextBox(textBuf, theItem->xLoc, 0, 0, &white, &black, rbuf, NULL, 0);
5237 }
5238