Annotation of brogue-ce/src/brogue/MainMenu.c, Revision 1.1.1.1
1.1 rubenllo 1: /*
2: * Buttons.c
3: * Brogue
4: *
5: * Created by Brian Walker on 1/14/12.
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 "Rogue.h"
25: #include "IncludeGlobals.h"
26: #include <time.h>
27: #include <limits.h>
28:
29: #define MENU_FLAME_PRECISION_FACTOR 10
30: #define MENU_FLAME_RISE_SPEED 50
31: #define MENU_FLAME_SPREAD_SPEED 20
32: #define MENU_FLAME_COLOR_DRIFT_SPEED 500
33: #define MENU_FLAME_FADE_SPEED 20
34: #define MENU_FLAME_UPDATE_DELAY 50
35: #define MENU_FLAME_ROW_PADDING 2
36: #define MENU_TITLE_OFFSET_X (-4)
37: #define MENU_TITLE_OFFSET_Y (-1)
38:
39: #define MENU_FLAME_COLOR_SOURCE_COUNT 1136
40:
41: #define MENU_FLAME_DENOMINATOR (100 + MENU_FLAME_RISE_SPEED + MENU_FLAME_SPREAD_SPEED)
42:
43:
44: void drawMenuFlames(signed short flames[COLS][(ROWS + MENU_FLAME_ROW_PADDING)][3], unsigned char mask[COLS][ROWS]) {
45: short i, j, versionStringLength;
46: color tempColor = {0};
47: const color *maskColor = &black;
48: char dchar;
49:
50: versionStringLength = strLenWithoutEscapes(BROGUE_VERSION_STRING);
51:
52: for (j=0; j<ROWS; j++) {
53: for (i=0; i<COLS; i++) {
54: if (j == ROWS - 1 && i >= COLS - versionStringLength) {
55: dchar = BROGUE_VERSION_STRING[i - (COLS - versionStringLength)];
56: } else {
57: dchar = ' ';
58: }
59:
60: if (mask[i][j] == 100) {
61: plotCharWithColor(dchar, i, j, &veryDarkGray, maskColor);
62: } else {
63: tempColor = black;
64: tempColor.red = flames[i][j][0] / MENU_FLAME_PRECISION_FACTOR;
65: tempColor.green = flames[i][j][1] / MENU_FLAME_PRECISION_FACTOR;
66: tempColor.blue = flames[i][j][2] / MENU_FLAME_PRECISION_FACTOR;
67: if (mask[i][j] > 0) {
68: applyColorAverage(&tempColor, maskColor, mask[i][j]);
69: }
70: plotCharWithColor(dchar, i, j, &veryDarkGray, &tempColor);
71: }
72: }
73: }
74: }
75:
76: void updateMenuFlames(const color *colors[COLS][(ROWS + MENU_FLAME_ROW_PADDING)],
77: signed short colorSources[MENU_FLAME_COLOR_SOURCE_COUNT][4],
78: signed short flames[COLS][(ROWS + MENU_FLAME_ROW_PADDING)][3]) {
79:
80: short i, j, k, l, x, y;
81: signed short tempFlames[COLS][3];
82: short colorSourceNumber, rand;
83:
84: colorSourceNumber = 0;
85: for (j=0; j<(ROWS + MENU_FLAME_ROW_PADDING); j++) {
86: // Make a temp copy of the current row.
87: for (i=0; i<COLS; i++) {
88: for (k=0; k<3; k++) {
89: tempFlames[i][k] = flames[i][j][k];
90: }
91: }
92:
93: for (i=0; i<COLS; i++) {
94: // Each cell is the weighted average of the three color values below and itself.
95: // Weight of itself: 100
96: // Weight of left and right neighbors: MENU_FLAME_SPREAD_SPEED / 2 each
97: // Weight of below cell: MENU_FLAME_RISE_SPEED
98: // Divisor: 100 + MENU_FLAME_SPREAD_SPEED + MENU_FLAME_RISE_SPEED
99:
100: // Itself:
101: for (k=0; k<3; k++) {
102: flames[i][j][k] = 100 * flames[i][j][k] / MENU_FLAME_DENOMINATOR;
103: }
104:
105: // Left and right neighbors:
106: for (l = -1; l <= 1; l += 2) {
107: x = i + l;
108: if (x == -1) {
109: x = COLS - 1;
110: } else if (x == COLS) {
111: x = 0;
112: }
113: for (k=0; k<3; k++) {
114: flames[i][j][k] += MENU_FLAME_SPREAD_SPEED * tempFlames[x][k] / 2 / MENU_FLAME_DENOMINATOR;
115: }
116: }
117:
118: // Below:
119: y = j + 1;
120: if (y < (ROWS + MENU_FLAME_ROW_PADDING)) {
121: for (k=0; k<3; k++) {
122: flames[i][j][k] += MENU_FLAME_RISE_SPEED * flames[i][y][k] / MENU_FLAME_DENOMINATOR;
123: }
124: }
125:
126: // Fade a little:
127: for (k=0; k<3; k++) {
128: flames[i][j][k] = (1000 - MENU_FLAME_FADE_SPEED) * flames[i][j][k] / 1000;
129: }
130:
131: if (colors[i][j]) {
132: // If it's a color source tile:
133:
134: // First, cause the color to drift a little.
135: for (k=0; k<4; k++) {
136: colorSources[colorSourceNumber][k] += rand_range(-MENU_FLAME_COLOR_DRIFT_SPEED, MENU_FLAME_COLOR_DRIFT_SPEED);
137: colorSources[colorSourceNumber][k] = clamp(colorSources[colorSourceNumber][k], 0, 1000);
138: }
139:
140: // Then, add the color to this tile's flames.
141: rand = colors[i][j]->rand * colorSources[colorSourceNumber][0] / 1000;
142: flames[i][j][0] += (colors[i][j]->red + (colors[i][j]->redRand * colorSources[colorSourceNumber][1] / 1000) + rand) * MENU_FLAME_PRECISION_FACTOR;
143: flames[i][j][1] += (colors[i][j]->green + (colors[i][j]->greenRand * colorSources[colorSourceNumber][2] / 1000) + rand) * MENU_FLAME_PRECISION_FACTOR;
144: flames[i][j][2] += (colors[i][j]->blue + (colors[i][j]->blueRand * colorSources[colorSourceNumber][3] / 1000) + rand) * MENU_FLAME_PRECISION_FACTOR;
145:
146: colorSourceNumber++;
147: }
148: }
149: }
150: }
151:
152: // Takes a grid of values, each of which is 0 or 100, and fills in some middle values in the interstices.
153: void antiAlias(unsigned char mask[COLS][ROWS]) {
154: short i, j, x, y, dir, nbCount;
155: const short intensity[5] = {0, 0, 35, 50, 60};
156:
157: for (i=0; i<COLS; i++) {
158: for (j=0; j<ROWS; j++) {
159: if (mask[i][j] < 100) {
160: nbCount = 0;
161: for (dir=0; dir<4; dir++) {
162: x = i + nbDirs[dir][0];
163: y = j + nbDirs[dir][1];
164: if (coordinatesAreInWindow(x, y) && mask[x][y] == 100) {
165: nbCount++;
166: }
167: }
168: mask[i][j] = intensity[nbCount];
169: }
170: }
171: }
172: }
173:
174: #define MENU_TITLE_WIDTH 74
175: #define MENU_TITLE_HEIGHT 19
176:
177: void initializeMenuFlames(boolean includeTitle,
178: const color *colors[COLS][(ROWS + MENU_FLAME_ROW_PADDING)],
179: color colorStorage[COLS],
180: signed short colorSources[MENU_FLAME_COLOR_SOURCE_COUNT][4],
181: signed short flames[COLS][(ROWS + MENU_FLAME_ROW_PADDING)][3],
182: unsigned char mask[COLS][ROWS]) {
183: short i, j, k, colorSourceCount;
184: const char title[MENU_TITLE_HEIGHT][MENU_TITLE_WIDTH+1] = {
185: "######## ######## ###### ####### #### ### #########",
186: " ## ### ## ### ## ### ## ## ## # ## #",
187: " ## ## ## ## ## ### ## # ## # ## #",
188: " ## ## ## ## # # ## # # ## # ## ",
189: " ## ## ## ## ## ## ## ## ## # ## # ",
190: " ## ## ## ## ## ### ## ## ## # ## # ",
191: " ###### ## ### ## #### ## ## ## # ####### ",
192: " ## ## ## ## ## #### ## ## ## # ## # ",
193: " ## ## ## ## ## ### ## ## ##### ## # ## # ",
194: " ## ## ## ## ### ## ## ### ## ## # ## ",
195: " ## ## ## ## ## # # ## ## ## # ## ",
196: " ## ## ## ## ### ## ### ## ### # ## #",
197: " ## ## ## ## ### ## ### ### ### # ## #",
198: "######## #### ### ###### ##### ###### #########",
199: " ## ",
200: " ########## ",
201: " ## ",
202: " ## ",
203: " #### ",
204: };
205:
206: for (i=0; i<COLS; i++) {
207: for (j=0; j<ROWS; j++) {
208: mask[i][j] = 0;
209: }
210: }
211:
212: for (i=0; i<COLS; i++) {
213: for (j=0; j<(ROWS + MENU_FLAME_ROW_PADDING); j++) {
214: colors[i][j] = NULL;
215: for (k=0; k<3; k++) {
216: flames[i][j][k] = 0;
217: }
218: }
219: }
220:
221: // Seed source color random components.
222: for (i=0; i<MENU_FLAME_COLOR_SOURCE_COUNT; i++) {
223: for (k=0; k<4; k++) {
224: colorSources[i][k] = rand_range(0, 1000);
225: }
226: }
227:
228: // Put some flame source along the bottom row.
229: colorSourceCount = 0;
230: for (i=0; i<COLS; i++) {
231: colorStorage[colorSourceCount] = flameSourceColor;
232: applyColorAverage(&(colorStorage[colorSourceCount]), &flameSourceColorSecondary, 100 - (smoothHiliteGradient(i, COLS - 1) + 25));
233:
234: colors[i][(ROWS + MENU_FLAME_ROW_PADDING)-1] = &(colorStorage[colorSourceCount]);
235: colorSourceCount++;
236: }
237:
238: if (includeTitle) {
239: // Wreathe the title in flames, and mask it in black.
240: for (i=0; i<MENU_TITLE_WIDTH; i++) {
241: for (j=0; j<MENU_TITLE_HEIGHT; j++) {
242: if (title[j][i] != ' ') {
243: colors[(COLS - MENU_TITLE_WIDTH)/2 + i + MENU_TITLE_OFFSET_X][(ROWS - MENU_TITLE_HEIGHT)/2 + j + MENU_TITLE_OFFSET_Y] = &flameTitleColor;
244: colorSourceCount++;
245: mask[(COLS - MENU_TITLE_WIDTH)/2 + i + MENU_TITLE_OFFSET_X][(ROWS - MENU_TITLE_HEIGHT)/2 + j + MENU_TITLE_OFFSET_Y] = 100;
246: }
247: }
248: }
249:
250: // Anti-alias the mask.
251: antiAlias(mask);
252: }
253:
254: brogueAssert(colorSourceCount <= MENU_FLAME_COLOR_SOURCE_COUNT);
255:
256: // Simulate the background flames for a while
257: for (i=0; i<100; i++) {
258: updateMenuFlames(colors, colorSources, flames);
259: }
260:
261: }
262:
263: void titleMenu() {
264: signed short flames[COLS][(ROWS + MENU_FLAME_ROW_PADDING)][3]; // red, green and blue
265: signed short colorSources[MENU_FLAME_COLOR_SOURCE_COUNT][4]; // red, green, blue, and rand, one for each color source (no more than MENU_FLAME_COLOR_SOURCE_COUNT).
266: const color *colors[COLS][(ROWS + MENU_FLAME_ROW_PADDING)];
267: color colorStorage[COLS];
268: unsigned char mask[COLS][ROWS];
269: boolean controlKeyWasDown = false;
270:
271: short i, b, x, y, button;
272: buttonState state;
273: brogueButton buttons[6];
274: char whiteColorEscape[10] = "";
275: char goldColorEscape[10] = "";
276: char newGameText[100] = "", customNewGameText[100] = "";
277: rogueEvent theEvent;
278: enum NGCommands buttonCommands[6] = {NG_NEW_GAME, NG_OPEN_GAME, NG_VIEW_RECORDING, NG_HIGH_SCORES, NG_QUIT};
279:
280: cellDisplayBuffer shadowBuf[COLS][ROWS];
281:
282: // Initialize the RNG so the flames aren't always the same.
283:
284: seedRandomGenerator(0);
285:
286: // Empty nextGamePath and nextGameSeed so that the buttons don't try to load an old game path or seed.
287: rogue.nextGamePath[0] = '\0';
288: rogue.nextGameSeed = 0;
289:
290: // Initialize the title menu buttons.
291: encodeMessageColor(whiteColorEscape, 0, &white);
292: encodeMessageColor(goldColorEscape, 0, KEYBOARD_LABELS ? &itemMessageColor : &white);
293: sprintf(newGameText, " %sN%sew Game ", goldColorEscape, whiteColorEscape);
294: sprintf(customNewGameText, " %sN%sew Game (custom) ", goldColorEscape, whiteColorEscape);
295: b = 0;
296: button = -1;
297:
298: initializeButton(&(buttons[b]));
299: strcpy(buttons[b].text, newGameText);
300: buttons[b].hotkey[0] = 'n';
301: buttons[b].hotkey[1] = 'N';
302: b++;
303:
304: initializeButton(&(buttons[b]));
305: sprintf(buttons[b].text, " %sO%spen Game ", goldColorEscape, whiteColorEscape);
306: buttons[b].hotkey[0] = 'o';
307: buttons[b].hotkey[1] = 'O';
308: b++;
309:
310: initializeButton(&(buttons[b]));
311: sprintf(buttons[b].text, " %sV%siew Recording ", goldColorEscape, whiteColorEscape);
312: buttons[b].hotkey[0] = 'v';
313: buttons[b].hotkey[1] = 'V';
314: b++;
315:
316: initializeButton(&(buttons[b]));
317: sprintf(buttons[b].text, " %sH%sigh Scores ", goldColorEscape, whiteColorEscape);
318: buttons[b].hotkey[0] = 'h';
319: buttons[b].hotkey[1] = 'H';
320: b++;
321:
322: initializeButton(&(buttons[b]));
323: sprintf(buttons[b].text, " %sQ%suit ", goldColorEscape, whiteColorEscape);
324: buttons[b].hotkey[0] = 'q';
325: buttons[b].hotkey[1] = 'Q';
326: b++;
327:
328: x = COLS - 1 - 20 - 2;
329: y = ROWS - 1;
330: for (i = b-1; i >= 0; i--) {
331: y -= 2;
332: buttons[i].x = x;
333: buttons[i].y = y;
334: buttons[i].buttonColor = titleButtonColor;
335: buttons[i].flags |= B_WIDE_CLICK_AREA;
336: }
337:
338: blackOutScreen();
339: clearDisplayBuffer(shadowBuf);
340: initializeButtonState(&state, buttons, b, x, y, 20, b*2-1);
341: rectangularShading(x, y, 20, b*2-1, &black, INTERFACE_OPACITY, shadowBuf);
342: drawButtonsInState(&state);
343:
344: initializeMenuFlames(true, colors, colorStorage, colorSources, flames, mask);
345: rogue.creaturesWillFlashThisTurn = false; // total unconscionable hack
346:
347: do {
348: if (isApplicationActive()) {
349: // Revert the display.
350: overlayDisplayBuffer(state.rbuf, NULL);
351:
352: if (!controlKeyWasDown && controlKeyIsDown()) {
353: strcpy(state.buttons[0].text, customNewGameText);
354: drawButtonsInState(&state);
355: buttonCommands[0] = NG_NEW_GAME_WITH_SEED;
356: controlKeyWasDown = true;
357: } else if (controlKeyWasDown && !controlKeyIsDown()) {
358: strcpy(state.buttons[0].text, newGameText);
359: drawButtonsInState(&state);
360: buttonCommands[0] = NG_NEW_GAME;
361: controlKeyWasDown = false;
362: }
363:
364: // Update the display.
365: updateMenuFlames(colors, colorSources, flames);
366: drawMenuFlames(flames, mask);
367: overlayDisplayBuffer(shadowBuf, NULL);
368: overlayDisplayBuffer(state.dbuf, NULL);
369:
370: // Pause briefly.
371: if (pauseBrogue(MENU_FLAME_UPDATE_DELAY)) {
372: // There was input during the pause! Get the input.
373: nextBrogueEvent(&theEvent, true, false, true);
374:
375: // Process the input.
376: button = processButtonInput(&state, NULL, &theEvent);
377: }
378:
379: } else {
380: pauseBrogue(64);
381: }
382: } while (button == -1 && rogue.nextGame == NG_NOTHING);
383: drawMenuFlames(flames, mask);
384: if (button != -1) {
385: if (button == 0 && controlKeyIsDown()) {
386: // Should fix an issue with Linux/Windows ports that require moving the mouse after
387: // pressing control to get the button to change.
388: rogue.nextGame = NG_NEW_GAME_WITH_SEED;
389: } else {
390: rogue.nextGame = buttonCommands[button];
391: }
392: }
393: }
394:
395: // Closes Brogue without any further prompts, animations, or user interaction.
396: void quitImmediately() {
397: // If we are recording a game, save it.
398: if (rogue.recording) {
399: flushBufferToFile();
400: if (rogue.gameInProgress && !rogue.quit && !rogue.gameHasEnded) {
401: // Game isn't over yet, create a savegame.
402: saveGameNoPrompt();
403: } else {
404: // Save it as a recording.
405: char path[BROGUE_FILENAME_MAX];
406: saveRecordingNoPrompt(path);
407: }
408: }
409: exit(0);
410: }
411:
412: void dialogAlert(char *message) {
413: cellDisplayBuffer rbuf[COLS][ROWS];
414:
415: brogueButton OKButton;
416: initializeButton(&OKButton);
417: strcpy(OKButton.text, " OK ");
418: OKButton.hotkey[0] = RETURN_KEY;
419: OKButton.hotkey[1] = ACKNOWLEDGE_KEY;
420: printTextBox(message, COLS/3, ROWS/3, COLS/3, &white, &interfaceBoxColor, rbuf, &OKButton, 1);
421: overlayDisplayBuffer(rbuf, NULL);
422: }
423:
424: boolean stringsExactlyMatch(const char *string1, const char *string2) {
425: short i;
426: for (i=0; string1[i] && string2[i]; i++) {
427: if (string1[i] != string2[i]) {
428: return false;
429: }
430: }
431: return string1[i] == string2[i];
432: }
433:
434: // Used to compare the dates of two fileEntry variables
435: // Returns (int):
436: // < 0 if 'b' date is lesser than 'a' date
437: // = 0 if 'b' date is equal to 'a' date,
438: // > 0 if 'b' date is greater than 'a' date
439: int fileEntryCompareDates(const void *a, const void *b) {
440: fileEntry *f1 = (fileEntry *)a;
441: fileEntry *f2 = (fileEntry *)b;
442: time_t t1, t2;
443: double diff;
444:
445: t1 = mktime(&f1->date);
446: t2 = mktime(&f2->date);
447: diff = difftime(t2, t1);
448:
449: //char date_f1[11];
450: //char date_f2[11];
451: //strftime(date_f1, sizeof(date_f1), DATE_FORMAT, &f1->date);
452: //strftime(date_f2, sizeof(date_f2), DATE_FORMAT, &f2->date);
453: //printf("\nf1: %s\t%s",date_f1,f1->path);
454: //printf("\nf2: %s\t%s",date_f2,f2->path);
455: //printf("\ndiff: %f\n", diff);
456:
457: return (int)diff;
458: }
459:
460: #define FILES_ON_PAGE_MAX (min(26, ROWS - 7)) // Two rows (top and bottom) for flames, two rows for border, one for prompt, one for heading.
461: #define MAX_FILENAME_DISPLAY_LENGTH 53
462: boolean dialogChooseFile(char *path, const char *suffix, const char *prompt) {
463: short i, j, count, x, y, width, height, suffixLength, pathLength, maxPathLength, currentPageStart;
464: brogueButton buttons[FILES_ON_PAGE_MAX + 2];
465: fileEntry *files;
466: boolean retval = false, again;
467: cellDisplayBuffer dbuf[COLS][ROWS], rbuf[COLS][ROWS];
468: color *dialogColor = &interfaceBoxColor;
469: char *membuf;
470: char fileDate [11];
471:
472: suffixLength = strlen(suffix);
473: files = listFiles(&count, &membuf);
474: copyDisplayBuffer(rbuf, displayBuffer);
475: maxPathLength = strLenWithoutEscapes(prompt);
476:
477: // First, we want to filter the list by stripping out any filenames that do not end with suffix.
478: // i is the entry we're testing, and j is the entry that we move it to if it qualifies.
479: for (i=0, j=0; i<count; i++) {
480: pathLength = strlen(files[i].path);
481: //printf("\nString 1: %s", &(files[i].path[(max(0, pathLength - suffixLength))]));
482: if (stringsExactlyMatch(&(files[i].path[(max(0, pathLength - suffixLength))]), suffix)) {
483:
484: // This file counts!
485: if (i > j) {
486: files[j] = files[i];
487: //strftime(fileDate, sizeof(fileDate), DATE_FORMAT, &files[j].date);
488: //printf("\nMatching file: %s\twith date: %s", files[j].path, fileDate);
489: }
490: j++;
491:
492: // Keep track of the longest length.
493: if (min(pathLength, MAX_FILENAME_DISPLAY_LENGTH) + 10 > maxPathLength) {
494: maxPathLength = min(pathLength, MAX_FILENAME_DISPLAY_LENGTH) + 10;
495: }
496: }
497: }
498: count = j;
499:
500: // Once we have all relevant files, we sort them by date descending
501: qsort(files, count, sizeof(fileEntry), &fileEntryCompareDates);
502:
503: currentPageStart = 0;
504:
505: do { // Repeat to permit scrolling.
506: again = false;
507:
508: for (i=0; i<min(count - currentPageStart, FILES_ON_PAGE_MAX); i++) {
509: initializeButton(&(buttons[i]));
510: buttons[i].flags &= ~(B_WIDE_CLICK_AREA | B_GRADIENT);
511: buttons[i].buttonColor = *dialogColor;
512: if (KEYBOARD_LABELS) {
513: sprintf(buttons[i].text, "%c) ", 'a' + i);
514: } else {
515: buttons[i].text[0] = '\0';
516: }
517: strncat(buttons[i].text, files[currentPageStart+i].path, MAX_FILENAME_DISPLAY_LENGTH);
518:
519: // Clip off the file suffix from the button text.
520: buttons[i].text[strlen(buttons[i].text) - suffixLength] = '\0'; // Snip!
521: buttons[i].hotkey[0] = 'a' + i;
522: buttons[i].hotkey[1] = 'A' + i;
523:
524: // Clip the filename length if necessary.
525: if (strlen(buttons[i].text) > MAX_FILENAME_DISPLAY_LENGTH) {
526: strcpy(&(buttons[i].text[MAX_FILENAME_DISPLAY_LENGTH - 3]), "...");
527: }
528:
529: //strftime(fileDate, sizeof(fileDate), DATE_FORMAT, &files[currentPageStart+i].date);
530: //printf("\nFound file: %s, with date: %s", files[currentPageStart+i].path, fileDate);
531: }
532:
533: x = (COLS - maxPathLength) / 2;
534: width = maxPathLength;
535: height = min(count - currentPageStart, FILES_ON_PAGE_MAX) + 2;
536: y = max(4, (ROWS - height) / 2);
537:
538: for (i=0; i<min(count - currentPageStart, FILES_ON_PAGE_MAX); i++) {
539: pathLength = strlen(buttons[i].text);
540: for (j=pathLength; j<(width - 10); j++) {
541: buttons[i].text[j] = ' ';
542: }
543: buttons[i].text[j] = '\0';
544: strftime(fileDate, sizeof(fileDate), DATE_FORMAT, &files[currentPageStart+i].date);
545: strcpy(&(buttons[i].text[j]), fileDate);
546: buttons[i].x = x;
547: buttons[i].y = y + 1 + i;
548: }
549:
550: if (count > FILES_ON_PAGE_MAX) {
551: // Create up and down arrows.
552: initializeButton(&(buttons[i]));
553: strcpy(buttons[i].text, " * ");
554: buttons[i].symbol[0] = G_UP_ARROW;
555: if (currentPageStart <= 0) {
556: buttons[i].flags &= ~(B_ENABLED | B_DRAW);
557: } else {
558: buttons[i].hotkey[0] = UP_ARROW;
559: buttons[i].hotkey[1] = NUMPAD_8;
560: buttons[i].hotkey[2] = PAGE_UP_KEY;
561: }
562: buttons[i].x = x + (width - 11)/2;
563: buttons[i].y = y;
564:
565: i++;
566: initializeButton(&(buttons[i]));
567: strcpy(buttons[i].text, " * ");
568: buttons[i].symbol[0] = G_DOWN_ARROW;
569: if (currentPageStart + FILES_ON_PAGE_MAX >= count) {
570: buttons[i].flags &= ~(B_ENABLED | B_DRAW);
571: } else {
572: buttons[i].hotkey[0] = DOWN_ARROW;
573: buttons[i].hotkey[1] = NUMPAD_2;
574: buttons[i].hotkey[2] = PAGE_DOWN_KEY;
575: }
576: buttons[i].x = x + (width - 11)/2;
577: buttons[i].y = y + i;
578: }
579:
580: if (count) {
581: clearDisplayBuffer(dbuf);
582: printString(prompt, x, y - 1, &itemMessageColor, dialogColor, dbuf);
583: rectangularShading(x - 1, y - 1, width + 1, height + 1, dialogColor, INTERFACE_OPACITY, dbuf);
584: overlayDisplayBuffer(dbuf, NULL);
585:
586: // for (j=0; j<min(count - currentPageStart, FILES_ON_PAGE_MAX); j++) {
587: // strftime(fileDate, sizeof(fileDate), DATE_FORMAT, &files[currentPageStart+j].date);
588: // printf("\nSanity check BEFORE: %s, with date: %s", files[currentPageStart+j].path, fileDate);
589: // printf("\n (button name)Sanity check BEFORE: %s", buttons[j].text);
590: // }
591:
592: i = buttonInputLoop(buttons,
593: min(count - currentPageStart, FILES_ON_PAGE_MAX) + (count > FILES_ON_PAGE_MAX ? 2 : 0),
594: x,
595: y,
596: width,
597: height,
598: NULL);
599:
600: // for (j=0; j<min(count - currentPageStart, FILES_ON_PAGE_MAX); j++) {
601: // strftime(fileDate, sizeof(fileDate), DATE_FORMAT, &files[currentPageStart+j].date);
602: // printf("\nSanity check AFTER: %s, with date: %s", files[currentPageStart+j].path, fileDate);
603: // printf("\n (button name)Sanity check AFTER: %s", buttons[j].text);
604: // }
605:
606: overlayDisplayBuffer(rbuf, NULL);
607:
608: if (i < min(count - currentPageStart, FILES_ON_PAGE_MAX)) {
609: if (i >= 0) {
610: retval = true;
611: strcpy(path, files[currentPageStart+i].path);
612: } else { // i is -1
613: retval = false;
614: }
615: } else if (i == min(count - currentPageStart, FILES_ON_PAGE_MAX)) { // Up arrow
616: again = true;
617: currentPageStart -= FILES_ON_PAGE_MAX;
618: } else if (i == min(count - currentPageStart, FILES_ON_PAGE_MAX) + 1) { // Down arrow
619: again = true;
620: currentPageStart += FILES_ON_PAGE_MAX;
621: }
622: }
623:
624: } while (again);
625:
626: free(files);
627: free(membuf);
628:
629: if (count == 0) {
630: dialogAlert("No applicable files found.");
631: return false;
632: } else {
633: return retval;
634: }
635: }
636:
637: // This is the basic program loop.
638: // When the program launches, or when a game ends, you end up here.
639: // If the player has already said what he wants to do next
640: // (by storing it in rogue.nextGame -- possibilities listed in enum NGCommands),
641: // we'll do it. The path (rogue.nextGamePath) is essentially a parameter for this command, and
642: // tells NG_VIEW_RECORDING and NG_OPEN_GAME which file to open. If there is a command but no
643: // accompanying path, and it's a command that should take a path, then pop up a dialog to have
644: // the player specify a path. If there is no command (i.e. if rogue.nextGame contains NG_NOTHING),
645: // then we'll display the title screen so the player can choose.
646: void mainBrogueJunction() {
647: rogueEvent theEvent;
648: char path[BROGUE_FILENAME_MAX], buf[100], seedDefault[100];
649: char *maxSeed = "4294967295"; // 2^32 - 1
650: short i, j, k;
651: boolean seedTooBig;
652:
653: // clear screen and display buffer
654: for (i=0; i<COLS; i++) {
655: for (j=0; j<ROWS; j++) {
656: displayBuffer[i][j].character = 0;
657: displayBuffer[i][j].needsUpdate = false;
658: displayBuffer[i][j].opacity = 100;
659: for (k=0; k<3; k++) {
660: displayBuffer[i][j].foreColorComponents[k] = 0;
661: displayBuffer[i][j].backColorComponents[k] = 0;
662: }
663: plotCharWithColor(' ', i, j, &black, &black);
664: }
665: }
666:
667: initializeLaunchArguments(&rogue.nextGame, rogue.nextGamePath, &rogue.nextGameSeed);
668:
669: do {
670: rogue.gameHasEnded = false;
671: rogue.playbackFastForward = false;
672: rogue.playbackMode = false;
673: switch (rogue.nextGame) {
674: case NG_NOTHING:
675: // Run the main menu to get a decision out of the player.
676: titleMenu();
677: break;
678: case NG_NEW_GAME:
679: case NG_NEW_GAME_WITH_SEED:
680: rogue.nextGamePath[0] = '\0';
681: randomNumbersGenerated = 0;
682:
683: rogue.playbackMode = false;
684: rogue.playbackFastForward = false;
685: rogue.playbackBetweenTurns = false;
686:
687: getAvailableFilePath(path, LAST_GAME_NAME, GAME_SUFFIX);
688: strcat(path, GAME_SUFFIX);
689: strcpy(currentFilePath, path);
690:
691: if (rogue.nextGame == NG_NEW_GAME_WITH_SEED) {
692: if (rogue.nextGameSeed == 0) { // Prompt for seed; default is the previous game's seed.
693: if (previousGameSeed == 0) {
694: seedDefault[0] = '\0';
695: } else {
696: sprintf(seedDefault, "%lu", previousGameSeed);
697: }
698: if (getInputTextString(buf, "Generate dungeon with seed number:",
699: strlen(maxSeed),
700: seedDefault,
701: "",
702: TEXT_INPUT_NUMBERS,
703: true)
704: && buf[0] != '\0') {
705: seedTooBig = false;
706: if (strlen(buf) == strlen(maxSeed)) {
707: for (i=0; maxSeed[i]; i++) {
708: if (maxSeed[i] > buf[i]) {
709: break; // we're good
710: } else if (maxSeed[i] < buf[i]) {
711: seedTooBig = true;
712: break;
713: }
714: }
715: }
716: sscanf(seedTooBig ? maxSeed : buf, "%lu", &rogue.nextGameSeed);
717: } else {
718: rogue.nextGame = NG_NOTHING;
719: break; // Don't start a new game after all.
720: }
721: }
722: } else {
723: rogue.nextGameSeed = 0; // Seed based on clock.
724: }
725:
726: rogue.nextGame = NG_NOTHING;
727: initializeRogue(rogue.nextGameSeed);
728: startLevel(rogue.depthLevel, 1); // descending into level 1
729:
730: mainInputLoop();
731: if(serverMode) {
732: rogue.nextGame = NG_QUIT;
733: }
734: freeEverything();
735: break;
736: case NG_OPEN_GAME:
737: rogue.nextGame = NG_NOTHING;
738: path[0] = '\0';
739: if (rogue.nextGamePath[0]) {
740: strcpy(path, rogue.nextGamePath);
741: rogue.nextGamePath[0] = '\0';
742: } else {
743: dialogChooseFile(path, GAME_SUFFIX, "Open saved game:");
744: //chooseFile(path, "Open saved game: ", "Saved game", GAME_SUFFIX);
745: }
746:
747: if (openFile(path)) {
748: if (loadSavedGame()) {
749: mainInputLoop();
750: }
751: freeEverything();
752: } else {
753: //dialogAlert("File not found.");
754: }
755: rogue.playbackMode = false;
756: rogue.playbackOOS = false;
757:
758: if(serverMode) {
759: rogue.nextGame = NG_QUIT;
760: }
761: break;
762: case NG_VIEW_RECORDING:
763: rogue.nextGame = NG_NOTHING;
764:
765: path[0] = '\0';
766: if (rogue.nextGamePath[0]) {
767: strcpy(path, rogue.nextGamePath);
768: rogue.nextGamePath[0] = '\0';
769: } else {
770: dialogChooseFile(path, RECORDING_SUFFIX, "View recording:");
771: //chooseFile(path, "View recording: ", "Recording", RECORDING_SUFFIX);
772: }
773:
774: if (openFile(path)) {
775: randomNumbersGenerated = 0;
776: rogue.playbackMode = true;
777: initializeRogue(0); // Seed argument is ignored because we're in playback.
778: if (!rogue.gameHasEnded) {
779: startLevel(rogue.depthLevel, 1);
780: rogue.playbackPaused = true;
781: displayAnnotation(); // in case there's an annotation for turn 0
782: }
783:
784: while(!rogue.gameHasEnded && rogue.playbackMode) {
785: if (rogue.playbackPaused) {
786: rogue.playbackPaused = false;
787: pausePlayback();
788: }
789: #ifdef ENABLE_PLAYBACK_SWITCH
790: // We are coming from the end of a recording the user has taken over.
791: // No more event checks, that has already been handled
792: if (rogue.gameHasEnded) {
793: break;
794: }
795: #endif
796: rogue.RNG = RNG_COSMETIC; // dancing terrain colors can't influence recordings
797: rogue.playbackBetweenTurns = true;
798: nextBrogueEvent(&theEvent, false, true, false);
799: rogue.RNG = RNG_SUBSTANTIVE;
800:
801: executeEvent(&theEvent);
802: }
803:
804: freeEverything();
805: } else {
806: // announce file not found
807: }
808: rogue.playbackMode = false;
809: rogue.playbackOOS = false;
810:
811: if(serverMode) {
812: rogue.nextGame = NG_QUIT;
813: }
814: break;
815: case NG_HIGH_SCORES:
816: rogue.nextGame = NG_NOTHING;
817: printHighScores(false);
818: break;
819: case NG_QUIT:
820: // No need to do anything.
821: break;
822: default:
823: break;
824: }
825: } while (rogue.nextGame != NG_QUIT);
826: }
CVSweb