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, ¤tForeColor, ¤tBackColor, ¤tChar, clamp(thisCellPercent, 0, 100));
1722 plotCharWithColor(currentChar, i, j, ¤tForeColor, ¤tBackColor);
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(¤tFillColor, &black, 75 - 75 * (amtFilled % (amtMax / 20)) / (amtMax / 20));
4521 }
4522 textColor = (dim ? gray : white);
4523 applyColorAverage(&textColor, ¤tFillColor, (dim ? 50 : 33));
4524 plotCharWithColor(barText[i], x + i, y, &textColor, ¤tFillColor);
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