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