/*
* Buttons.c
* Brogue
*
* Created by Brian Walker on 1/14/12.
* Copyright 2012. All rights reserved.
*
* This file is part of Brogue.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
#include "Rogue.h"
#include "IncludeGlobals.h"
#include
#include
#define MENU_FLAME_PRECISION_FACTOR 10
#define MENU_FLAME_RISE_SPEED 50
#define MENU_FLAME_SPREAD_SPEED 20
#define MENU_FLAME_COLOR_DRIFT_SPEED 500
#define MENU_FLAME_FADE_SPEED 20
#define MENU_FLAME_UPDATE_DELAY 50
#define MENU_FLAME_ROW_PADDING 2
#define MENU_TITLE_OFFSET_X (-4)
#define MENU_TITLE_OFFSET_Y (-1)
#define MENU_FLAME_COLOR_SOURCE_COUNT 1136
#define MENU_FLAME_DENOMINATOR (100 + MENU_FLAME_RISE_SPEED + MENU_FLAME_SPREAD_SPEED)
void drawMenuFlames(signed short flames[COLS][(ROWS + MENU_FLAME_ROW_PADDING)][3], unsigned char mask[COLS][ROWS]) {
short i, j, versionStringLength;
color tempColor = {0};
const color *maskColor = &black;
char dchar;
versionStringLength = strLenWithoutEscapes(BROGUE_VERSION_STRING);
for (j=0; j= COLS - versionStringLength) {
dchar = BROGUE_VERSION_STRING[i - (COLS - versionStringLength)];
} else {
dchar = ' ';
}
if (mask[i][j] == 100) {
plotCharWithColor(dchar, i, j, &veryDarkGray, maskColor);
} else {
tempColor = black;
tempColor.red = flames[i][j][0] / MENU_FLAME_PRECISION_FACTOR;
tempColor.green = flames[i][j][1] / MENU_FLAME_PRECISION_FACTOR;
tempColor.blue = flames[i][j][2] / MENU_FLAME_PRECISION_FACTOR;
if (mask[i][j] > 0) {
applyColorAverage(&tempColor, maskColor, mask[i][j]);
}
plotCharWithColor(dchar, i, j, &veryDarkGray, &tempColor);
}
}
}
}
void 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]) {
short i, j, k, l, x, y;
signed short tempFlames[COLS][3];
short colorSourceNumber, rand;
colorSourceNumber = 0;
for (j=0; j<(ROWS + MENU_FLAME_ROW_PADDING); j++) {
// Make a temp copy of the current row.
for (i=0; irand * colorSources[colorSourceNumber][0] / 1000;
flames[i][j][0] += (colors[i][j]->red + (colors[i][j]->redRand * colorSources[colorSourceNumber][1] / 1000) + rand) * MENU_FLAME_PRECISION_FACTOR;
flames[i][j][1] += (colors[i][j]->green + (colors[i][j]->greenRand * colorSources[colorSourceNumber][2] / 1000) + rand) * MENU_FLAME_PRECISION_FACTOR;
flames[i][j][2] += (colors[i][j]->blue + (colors[i][j]->blueRand * colorSources[colorSourceNumber][3] / 1000) + rand) * MENU_FLAME_PRECISION_FACTOR;
colorSourceNumber++;
}
}
}
}
// Takes a grid of values, each of which is 0 or 100, and fills in some middle values in the interstices.
void antiAlias(unsigned char mask[COLS][ROWS]) {
short i, j, x, y, dir, nbCount;
const short intensity[5] = {0, 0, 35, 50, 60};
for (i=0; i= 0; i--) {
y -= 2;
buttons[i].x = x;
buttons[i].y = y;
buttons[i].buttonColor = titleButtonColor;
buttons[i].flags |= B_WIDE_CLICK_AREA;
}
blackOutScreen();
clearDisplayBuffer(shadowBuf);
initializeButtonState(&state, buttons, b, x, y, 20, b*2-1);
rectangularShading(x, y, 20, b*2-1, &black, INTERFACE_OPACITY, shadowBuf);
drawButtonsInState(&state);
initializeMenuFlames(true, colors, colorStorage, colorSources, flames, mask);
rogue.creaturesWillFlashThisTurn = false; // total unconscionable hack
do {
if (isApplicationActive()) {
// Revert the display.
overlayDisplayBuffer(state.rbuf, NULL);
if (!controlKeyWasDown && controlKeyIsDown()) {
strcpy(state.buttons[0].text, customNewGameText);
drawButtonsInState(&state);
buttonCommands[0] = NG_NEW_GAME_WITH_SEED;
controlKeyWasDown = true;
} else if (controlKeyWasDown && !controlKeyIsDown()) {
strcpy(state.buttons[0].text, newGameText);
drawButtonsInState(&state);
buttonCommands[0] = NG_NEW_GAME;
controlKeyWasDown = false;
}
// Update the display.
updateMenuFlames(colors, colorSources, flames);
drawMenuFlames(flames, mask);
overlayDisplayBuffer(shadowBuf, NULL);
overlayDisplayBuffer(state.dbuf, NULL);
// Pause briefly.
if (pauseBrogue(MENU_FLAME_UPDATE_DELAY)) {
// There was input during the pause! Get the input.
nextBrogueEvent(&theEvent, true, false, true);
// Process the input.
button = processButtonInput(&state, NULL, &theEvent);
}
} else {
pauseBrogue(64);
}
} while (button == -1 && rogue.nextGame == NG_NOTHING);
drawMenuFlames(flames, mask);
if (button != -1) {
if (button == 0 && controlKeyIsDown()) {
// Should fix an issue with Linux/Windows ports that require moving the mouse after
// pressing control to get the button to change.
rogue.nextGame = NG_NEW_GAME_WITH_SEED;
} else {
rogue.nextGame = buttonCommands[button];
}
}
}
// Closes Brogue without any further prompts, animations, or user interaction.
void quitImmediately() {
// If we are recording a game, save it.
if (rogue.recording) {
flushBufferToFile();
if (rogue.gameInProgress && !rogue.quit && !rogue.gameHasEnded) {
// Game isn't over yet, create a savegame.
saveGameNoPrompt();
} else {
// Save it as a recording.
char path[BROGUE_FILENAME_MAX];
saveRecordingNoPrompt(path);
}
}
exit(0);
}
void dialogAlert(char *message) {
cellDisplayBuffer rbuf[COLS][ROWS];
brogueButton OKButton;
initializeButton(&OKButton);
strcpy(OKButton.text, " OK ");
OKButton.hotkey[0] = RETURN_KEY;
OKButton.hotkey[1] = ACKNOWLEDGE_KEY;
printTextBox(message, COLS/3, ROWS/3, COLS/3, &white, &interfaceBoxColor, rbuf, &OKButton, 1);
overlayDisplayBuffer(rbuf, NULL);
}
boolean stringsExactlyMatch(const char *string1, const char *string2) {
short i;
for (i=0; string1[i] && string2[i]; i++) {
if (string1[i] != string2[i]) {
return false;
}
}
return string1[i] == string2[i];
}
// Used to compare the dates of two fileEntry variables
// Returns (int):
// < 0 if 'b' date is lesser than 'a' date
// = 0 if 'b' date is equal to 'a' date,
// > 0 if 'b' date is greater than 'a' date
int fileEntryCompareDates(const void *a, const void *b) {
fileEntry *f1 = (fileEntry *)a;
fileEntry *f2 = (fileEntry *)b;
time_t t1, t2;
double diff;
t1 = mktime(&f1->date);
t2 = mktime(&f2->date);
diff = difftime(t2, t1);
//char date_f1[11];
//char date_f2[11];
//strftime(date_f1, sizeof(date_f1), DATE_FORMAT, &f1->date);
//strftime(date_f2, sizeof(date_f2), DATE_FORMAT, &f2->date);
//printf("\nf1: %s\t%s",date_f1,f1->path);
//printf("\nf2: %s\t%s",date_f2,f2->path);
//printf("\ndiff: %f\n", diff);
return (int)diff;
}
#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.
#define MAX_FILENAME_DISPLAY_LENGTH 53
boolean dialogChooseFile(char *path, const char *suffix, const char *prompt) {
short i, j, count, x, y, width, height, suffixLength, pathLength, maxPathLength, currentPageStart;
brogueButton buttons[FILES_ON_PAGE_MAX + 2];
fileEntry *files;
boolean retval = false, again;
cellDisplayBuffer dbuf[COLS][ROWS], rbuf[COLS][ROWS];
color *dialogColor = &interfaceBoxColor;
char *membuf;
char fileDate [11];
suffixLength = strlen(suffix);
files = listFiles(&count, &membuf);
copyDisplayBuffer(rbuf, displayBuffer);
maxPathLength = strLenWithoutEscapes(prompt);
// First, we want to filter the list by stripping out any filenames that do not end with suffix.
// i is the entry we're testing, and j is the entry that we move it to if it qualifies.
for (i=0, j=0; i j) {
files[j] = files[i];
//strftime(fileDate, sizeof(fileDate), DATE_FORMAT, &files[j].date);
//printf("\nMatching file: %s\twith date: %s", files[j].path, fileDate);
}
j++;
// Keep track of the longest length.
if (min(pathLength, MAX_FILENAME_DISPLAY_LENGTH) + 10 > maxPathLength) {
maxPathLength = min(pathLength, MAX_FILENAME_DISPLAY_LENGTH) + 10;
}
}
}
count = j;
// Once we have all relevant files, we sort them by date descending
qsort(files, count, sizeof(fileEntry), &fileEntryCompareDates);
currentPageStart = 0;
do { // Repeat to permit scrolling.
again = false;
for (i=0; i MAX_FILENAME_DISPLAY_LENGTH) {
strcpy(&(buttons[i].text[MAX_FILENAME_DISPLAY_LENGTH - 3]), "...");
}
//strftime(fileDate, sizeof(fileDate), DATE_FORMAT, &files[currentPageStart+i].date);
//printf("\nFound file: %s, with date: %s", files[currentPageStart+i].path, fileDate);
}
x = (COLS - maxPathLength) / 2;
width = maxPathLength;
height = min(count - currentPageStart, FILES_ON_PAGE_MAX) + 2;
y = max(4, (ROWS - height) / 2);
for (i=0; i FILES_ON_PAGE_MAX) {
// Create up and down arrows.
initializeButton(&(buttons[i]));
strcpy(buttons[i].text, " * ");
buttons[i].symbol[0] = G_UP_ARROW;
if (currentPageStart <= 0) {
buttons[i].flags &= ~(B_ENABLED | B_DRAW);
} else {
buttons[i].hotkey[0] = UP_ARROW;
buttons[i].hotkey[1] = NUMPAD_8;
buttons[i].hotkey[2] = PAGE_UP_KEY;
}
buttons[i].x = x + (width - 11)/2;
buttons[i].y = y;
i++;
initializeButton(&(buttons[i]));
strcpy(buttons[i].text, " * ");
buttons[i].symbol[0] = G_DOWN_ARROW;
if (currentPageStart + FILES_ON_PAGE_MAX >= count) {
buttons[i].flags &= ~(B_ENABLED | B_DRAW);
} else {
buttons[i].hotkey[0] = DOWN_ARROW;
buttons[i].hotkey[1] = NUMPAD_2;
buttons[i].hotkey[2] = PAGE_DOWN_KEY;
}
buttons[i].x = x + (width - 11)/2;
buttons[i].y = y + i;
}
if (count) {
clearDisplayBuffer(dbuf);
printString(prompt, x, y - 1, &itemMessageColor, dialogColor, dbuf);
rectangularShading(x - 1, y - 1, width + 1, height + 1, dialogColor, INTERFACE_OPACITY, dbuf);
overlayDisplayBuffer(dbuf, NULL);
// for (j=0; j FILES_ON_PAGE_MAX ? 2 : 0),
x,
y,
width,
height,
NULL);
// for (j=0; j= 0) {
retval = true;
strcpy(path, files[currentPageStart+i].path);
} else { // i is -1
retval = false;
}
} else if (i == min(count - currentPageStart, FILES_ON_PAGE_MAX)) { // Up arrow
again = true;
currentPageStart -= FILES_ON_PAGE_MAX;
} else if (i == min(count - currentPageStart, FILES_ON_PAGE_MAX) + 1) { // Down arrow
again = true;
currentPageStart += FILES_ON_PAGE_MAX;
}
}
} while (again);
free(files);
free(membuf);
if (count == 0) {
dialogAlert("No applicable files found.");
return false;
} else {
return retval;
}
}
// This is the basic program loop.
// When the program launches, or when a game ends, you end up here.
// If the player has already said what he wants to do next
// (by storing it in rogue.nextGame -- possibilities listed in enum NGCommands),
// we'll do it. The path (rogue.nextGamePath) is essentially a parameter for this command, and
// tells NG_VIEW_RECORDING and NG_OPEN_GAME which file to open. If there is a command but no
// accompanying path, and it's a command that should take a path, then pop up a dialog to have
// the player specify a path. If there is no command (i.e. if rogue.nextGame contains NG_NOTHING),
// then we'll display the title screen so the player can choose.
void mainBrogueJunction() {
rogueEvent theEvent;
char path[BROGUE_FILENAME_MAX], buf[100], seedDefault[100];
char *maxSeed = "4294967295"; // 2^32 - 1
short i, j, k;
boolean seedTooBig;
// clear screen and display buffer
for (i=0; i buf[i]) {
break; // we're good
} else if (maxSeed[i] < buf[i]) {
seedTooBig = true;
break;
}
}
}
sscanf(seedTooBig ? maxSeed : buf, "%lu", &rogue.nextGameSeed);
} else {
rogue.nextGame = NG_NOTHING;
break; // Don't start a new game after all.
}
}
} else {
rogue.nextGameSeed = 0; // Seed based on clock.
}
rogue.nextGame = NG_NOTHING;
initializeRogue(rogue.nextGameSeed);
startLevel(rogue.depthLevel, 1); // descending into level 1
mainInputLoop();
if(serverMode) {
rogue.nextGame = NG_QUIT;
}
freeEverything();
break;
case NG_OPEN_GAME:
rogue.nextGame = NG_NOTHING;
path[0] = '\0';
if (rogue.nextGamePath[0]) {
strcpy(path, rogue.nextGamePath);
rogue.nextGamePath[0] = '\0';
} else {
dialogChooseFile(path, GAME_SUFFIX, "Open saved game:");
//chooseFile(path, "Open saved game: ", "Saved game", GAME_SUFFIX);
}
if (openFile(path)) {
if (loadSavedGame()) {
mainInputLoop();
}
freeEverything();
} else {
//dialogAlert("File not found.");
}
rogue.playbackMode = false;
rogue.playbackOOS = false;
if(serverMode) {
rogue.nextGame = NG_QUIT;
}
break;
case NG_VIEW_RECORDING:
rogue.nextGame = NG_NOTHING;
path[0] = '\0';
if (rogue.nextGamePath[0]) {
strcpy(path, rogue.nextGamePath);
rogue.nextGamePath[0] = '\0';
} else {
dialogChooseFile(path, RECORDING_SUFFIX, "View recording:");
//chooseFile(path, "View recording: ", "Recording", RECORDING_SUFFIX);
}
if (openFile(path)) {
randomNumbersGenerated = 0;
rogue.playbackMode = true;
initializeRogue(0); // Seed argument is ignored because we're in playback.
if (!rogue.gameHasEnded) {
startLevel(rogue.depthLevel, 1);
rogue.playbackPaused = true;
displayAnnotation(); // in case there's an annotation for turn 0
}
while(!rogue.gameHasEnded && rogue.playbackMode) {
if (rogue.playbackPaused) {
rogue.playbackPaused = false;
pausePlayback();
}
#ifdef ENABLE_PLAYBACK_SWITCH
// We are coming from the end of a recording the user has taken over.
// No more event checks, that has already been handled
if (rogue.gameHasEnded) {
break;
}
#endif
rogue.RNG = RNG_COSMETIC; // dancing terrain colors can't influence recordings
rogue.playbackBetweenTurns = true;
nextBrogueEvent(&theEvent, false, true, false);
rogue.RNG = RNG_SUBSTANTIVE;
executeEvent(&theEvent);
}
freeEverything();
} else {
// announce file not found
}
rogue.playbackMode = false;
rogue.playbackOOS = false;
if(serverMode) {
rogue.nextGame = NG_QUIT;
}
break;
case NG_HIGH_SCORES:
rogue.nextGame = NG_NOTHING;
printHighScores(false);
break;
case NG_QUIT:
// No need to do anything.
break;
default:
break;
}
} while (rogue.nextGame != NG_QUIT);
}