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
drawMenuFlames(signed short flames[COLS][(ROWS+MENU_FLAME_ROW_PADDING)][3],unsigned char mask[COLS][ROWS])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
updateMenuFlames(const color * colors[COLS][(ROWS+MENU_FLAME_ROW_PADDING)],signed short colorSources[MENU_FLAME_COLOR_SOURCE_COUNT][4],signed short flames[COLS][(ROWS+MENU_FLAME_ROW_PADDING)][3])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.
antiAlias(unsigned char mask[COLS][ROWS])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
initializeMenuFlames(boolean includeTitle,const color * colors[COLS][(ROWS+MENU_FLAME_ROW_PADDING)],color colorStorage[COLS],signed short colorSources[MENU_FLAME_COLOR_SOURCE_COUNT][4],signed short flames[COLS][(ROWS+MENU_FLAME_ROW_PADDING)][3],unsigned char mask[COLS][ROWS])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
titleMenu()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.
quitImmediately()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
dialogAlert(char * message)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
stringsExactlyMatch(const char * string1,const char * string2)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
fileEntryCompareDates(const void * a,const void * b)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
dialogChooseFile(char * path,const char * suffix,const char * prompt)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.
mainBrogueJunction()646 void mainBrogueJunction() {
647 rogueEvent theEvent;
648 char path[BROGUE_FILENAME_MAX], buf[100], seedDefault[100];
649 short i, j, k;
650
651 // clear screen and display buffer
652 for (i=0; i<COLS; i++) {
653 for (j=0; j<ROWS; j++) {
654 displayBuffer[i][j].character = 0;
655 displayBuffer[i][j].opacity = 100;
656 for (k=0; k<3; k++) {
657 displayBuffer[i][j].foreColorComponents[k] = 0;
658 displayBuffer[i][j].backColorComponents[k] = 0;
659 }
660 plotCharWithColor(' ', i, j, &black, &black);
661 }
662 }
663
664 initializeLaunchArguments(&rogue.nextGame, rogue.nextGamePath, &rogue.nextGameSeed);
665
666 do {
667 rogue.gameHasEnded = false;
668 rogue.playbackFastForward = false;
669 rogue.playbackMode = false;
670 switch (rogue.nextGame) {
671 case NG_NOTHING:
672 // Run the main menu to get a decision out of the player.
673 titleMenu();
674 break;
675 case NG_NEW_GAME:
676 case NG_NEW_GAME_WITH_SEED:
677 rogue.nextGamePath[0] = '\0';
678 randomNumbersGenerated = 0;
679
680 rogue.playbackMode = false;
681 rogue.playbackFastForward = false;
682 rogue.playbackBetweenTurns = false;
683
684 getAvailableFilePath(path, LAST_GAME_NAME, GAME_SUFFIX);
685 strcat(path, GAME_SUFFIX);
686 strcpy(currentFilePath, path);
687
688 if (rogue.nextGame == NG_NEW_GAME_WITH_SEED) {
689 if (rogue.nextGameSeed == 0) { // Prompt for seed; default is the previous game's seed.
690 if (previousGameSeed == 0) {
691 seedDefault[0] = '\0';
692 } else {
693 sprintf(seedDefault, "%llu", (unsigned long long)previousGameSeed);
694 }
695 if (getInputTextString(buf, "Generate dungeon with seed number:",
696 20, // length of "18446744073709551615" (2^64 - 1)
697 seedDefault,
698 "",
699 TEXT_INPUT_NUMBERS,
700 true)
701 && buf[0] != '\0') {
702 if (!tryParseUint64(buf, &rogue.nextGameSeed)) {
703 // seed is too large, default to the largest possible seed
704 rogue.nextGameSeed = 18446744073709551615ULL;
705 }
706 } else {
707 rogue.nextGame = NG_NOTHING;
708 break; // Don't start a new game after all.
709 }
710 }
711 } else {
712 rogue.nextGameSeed = 0; // Seed based on clock.
713 }
714
715 rogue.nextGame = NG_NOTHING;
716 initializeRogue(rogue.nextGameSeed);
717 startLevel(rogue.depthLevel, 1); // descending into level 1
718
719 mainInputLoop();
720 if(serverMode) {
721 rogue.nextGame = NG_QUIT;
722 }
723 freeEverything();
724 break;
725 case NG_OPEN_GAME:
726 rogue.nextGame = NG_NOTHING;
727 path[0] = '\0';
728 if (rogue.nextGamePath[0]) {
729 strcpy(path, rogue.nextGamePath);
730 rogue.nextGamePath[0] = '\0';
731 } else {
732 dialogChooseFile(path, GAME_SUFFIX, "Open saved game:");
733 //chooseFile(path, "Open saved game: ", "Saved game", GAME_SUFFIX);
734 }
735
736 if (openFile(path)) {
737 if (loadSavedGame()) {
738 mainInputLoop();
739 }
740 freeEverything();
741 } else {
742 //dialogAlert("File not found.");
743 }
744 rogue.playbackMode = false;
745 rogue.playbackOOS = false;
746
747 if(serverMode) {
748 rogue.nextGame = NG_QUIT;
749 }
750 break;
751 case NG_VIEW_RECORDING:
752 rogue.nextGame = NG_NOTHING;
753
754 path[0] = '\0';
755 if (rogue.nextGamePath[0]) {
756 strcpy(path, rogue.nextGamePath);
757 rogue.nextGamePath[0] = '\0';
758 } else {
759 dialogChooseFile(path, RECORDING_SUFFIX, "View recording:");
760 //chooseFile(path, "View recording: ", "Recording", RECORDING_SUFFIX);
761 }
762
763 if (openFile(path)) {
764 randomNumbersGenerated = 0;
765 rogue.playbackMode = true;
766 initializeRogue(0); // Seed argument is ignored because we're in playback.
767 if (!rogue.gameHasEnded) {
768 startLevel(rogue.depthLevel, 1);
769 rogue.playbackPaused = true;
770 displayAnnotation(); // in case there's an annotation for turn 0
771 }
772
773 while(!rogue.gameHasEnded && rogue.playbackMode) {
774 if (rogue.playbackPaused) {
775 rogue.playbackPaused = false;
776 pausePlayback();
777 }
778 #ifdef ENABLE_PLAYBACK_SWITCH
779 // We are coming from the end of a recording the user has taken over.
780 // No more event checks, that has already been handled
781 if (rogue.gameHasEnded) {
782 break;
783 }
784 #endif
785 rogue.RNG = RNG_COSMETIC; // dancing terrain colors can't influence recordings
786 rogue.playbackBetweenTurns = true;
787 nextBrogueEvent(&theEvent, false, true, false);
788 rogue.RNG = RNG_SUBSTANTIVE;
789
790 executeEvent(&theEvent);
791 }
792
793 freeEverything();
794 } else {
795 // announce file not found
796 }
797 rogue.playbackMode = false;
798 rogue.playbackOOS = false;
799
800 if(serverMode) {
801 rogue.nextGame = NG_QUIT;
802 }
803 break;
804 case NG_HIGH_SCORES:
805 rogue.nextGame = NG_NOTHING;
806 printHighScores(false);
807 break;
808 case NG_QUIT:
809 // No need to do anything.
810 break;
811 default:
812 break;
813 }
814 } while (rogue.nextGame != NG_QUIT);
815 }
816