#ifdef SDL_PATHS #include #endif #include #include #include "platform.h" #include "tiles.h" #define PAUSE_BETWEEN_EVENT_POLLING 36L//17 #define MAX_REMAPS 128 struct keypair { char from; char to; }; static struct keypair remapping[MAX_REMAPS]; static size_t nremaps = 0; static boolean showGraphics = false; static rogueEvent lastEvent; static void sdlfatal(char *file, int line) { fprintf(stderr, "Fatal SDL error (%s:%d): %s\n", file, line, SDL_GetError()); exit(1); } static void imgfatal(char *file, int line) { fprintf(stderr, "Fatal SDL_image error (%s:%d): %s\n", file, line, IMG_GetError()); exit(1); } /* If the key is to be processed, returns true and updates event. False otherwise. This function only listens for keypresses which do not produce corresponding TextInputEvents. */ static boolean eventFromKey(rogueEvent *event, SDL_Keycode key) { event->param1 = -1; switch (key) { case SDLK_ESCAPE: event->param1 = ESCAPE_KEY; return true; case SDLK_UP: event->param1 = UP_ARROW; return true; case SDLK_DOWN: event->param1 = DOWN_ARROW; return true; case SDLK_RIGHT: event->param1 = RIGHT_ARROW; return true; case SDLK_LEFT: event->param1 = LEFT_ARROW; return true; case SDLK_RETURN: case SDLK_KP_ENTER: event->param1 = RETURN_KEY; return true; case SDLK_BACKSPACE: event->param1 = DELETE_KEY; return true; case SDLK_TAB: event->param1 = TAB_KEY; return true; case SDLK_PRINTSCREEN: event->param1 = PRINTSCREEN_KEY; return true; } /* Only process keypad events when we're holding a modifier, as there is no TextInputEvent then. */ if (event->shiftKey || event->controlKey) { switch (key) { case SDLK_KP_0: event->param1 = NUMPAD_0; return true; case SDLK_KP_1: event->param1 = NUMPAD_1; return true; case SDLK_KP_2: event->param1 = NUMPAD_2; return true; case SDLK_KP_3: event->param1 = NUMPAD_3; return true; case SDLK_KP_4: event->param1 = NUMPAD_4; return true; case SDLK_KP_5: event->param1 = NUMPAD_5; return true; case SDLK_KP_6: event->param1 = NUMPAD_6; return true; case SDLK_KP_7: event->param1 = NUMPAD_7; return true; case SDLK_KP_8: event->param1 = NUMPAD_8; return true; case SDLK_KP_9: event->param1 = NUMPAD_9; return true; } } // Ctrl+letter doesn't give a TextInputEvent if (event->controlKey && key >= SDLK_a && key <= SDLK_z) { event->param1 = 'a' + (key - SDLK_a); if (event->shiftKey) event->param1 -= 'a' - 'A'; return true; } return false; } static boolean _modifierHeld(int mod) { SDL_Keymod km = SDL_GetModState(); return mod == 0 && (km & (KMOD_LSHIFT | KMOD_RSHIFT)) || mod == 1 && (km & (KMOD_LCTRL | KMOD_RCTRL)); } static char applyRemaps(char c) { for (size_t i=0; i < nremaps; i++) { if (remapping[i].from == c) return remapping[i].to; } return c; } /* If an event is available, returns true and updates returnEvent. Otherwise it returns false and an error event. This function also processes platform-specific inputs/behaviours. */ static boolean pollBrogueEvent(rogueEvent *returnEvent, boolean textInput) { static int mx = 0, my = 0; returnEvent->eventType = EVENT_ERROR; returnEvent->shiftKey = _modifierHeld(0); returnEvent->controlKey = _modifierHeld(1); SDL_Event event; boolean ret = false; // ~ for (int i=0; i < 100 && SDL_PollEvent(&event); i++) { while (SDL_PollEvent(&event)) { if (event.type == SDL_QUIT) { // the player clicked the X button! SDL_Quit(); quitImmediately(); } else if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) { resizeWindow(event.window.data1, event.window.data2); } else if (event.type == SDL_KEYDOWN) { SDL_Keycode key = event.key.keysym.sym; if (key == SDLK_PAGEUP) { resizeWindow(max(windowWidth * 11/10, windowWidth + 1), max(windowHeight * 11/10, windowHeight + 1)); continue; } else if (key == SDLK_PAGEDOWN) { fullScreen = false; resizeWindow(max(windowWidth * 10/11, 1), max(windowHeight * 10/11, 1)); continue; } else if (key == SDLK_F11 || key == SDLK_F12 || key == SDLK_RETURN && (SDL_GetModState() & KMOD_ALT)) { fullScreen = !fullScreen; resizeWindow(-1, -1); // Reset to starting resolution continue; } if (eventFromKey(returnEvent, key)) { returnEvent->eventType = KEYSTROKE; return true; } } else if (event.type == SDL_TEXTINPUT && (unsigned char)(event.text.text[0]) < 0x80) { /* It's difficult/impossible to check what characters are on the shifts of keys. So to detect '&', '>' etc. reliably we need to listen for text input events as well as keydowns. This results in hybrid keyboard code, where Brogue KEYSTROKEs can come from different SDL events. */ char c = event.text.text[0]; if (!textInput) { c = applyRemaps(c); if (c == '=' || c == '+') { resizeWindow(max(windowWidth * 11/10, windowWidth + 1), max(windowHeight * 11/10, windowHeight + 1)); } else if (c == '-') { fullScreen = false; resizeWindow(max(windowWidth * 10/11, 1), max(windowHeight * 10/11, 1)); } } returnEvent->eventType = KEYSTROKE; returnEvent->param1 = c; // ~ printf("textinput %s\n", event.text.text); return true; } else if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) { if (event.button.button == SDL_BUTTON_LEFT || event.button.button == SDL_BUTTON_RIGHT) { if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) { returnEvent->eventType = MOUSE_DOWN; } else if (event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT) { returnEvent->eventType = MOUSE_UP; } else if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_RIGHT) { returnEvent->eventType = RIGHT_MOUSE_DOWN; } else if (event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_RIGHT) { returnEvent->eventType = RIGHT_MOUSE_UP; } returnEvent->param1 = event.button.x * COLS / windowWidth; returnEvent->param2 = event.button.y * ROWS / windowHeight; return true; } } else if (event.type == SDL_MOUSEMOTION) { // We don't want to return on a mouse motion event, because only the last // in the queue is important. That's why we just set ret=true int xcell = event.motion.x * COLS / windowWidth, ycell = event.motion.y * ROWS / windowHeight; if (xcell != mx || ycell != my) { returnEvent->eventType = MOUSE_ENTERED_CELL; returnEvent->param1 = xcell; returnEvent->param2 = ycell; mx = xcell; my = ycell; ret = true; } } } return ret; } static void _gameLoop() { #ifdef SDL_PATHS char *path = SDL_GetBasePath(); if (path) { path[strlen(path) - 1] = '\0'; // remove trailing separator strcpy(dataDirectory, path); } else { fprintf(stderr, "Failed to find the path to the application\n"); exit(1); } free(path); path = SDL_GetPrefPath("Brogue", "Brogue CE"); if (!path || chdir(path) != 0) { fprintf(stderr, "Failed to find or change to the save directory\n"); exit(1); } free(path); #endif if (SDL_Init(SDL_INIT_VIDEO) < 0) sdlfatal(__FILE__, __LINE__); if (!(IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG)) imgfatal(__FILE__, __LINE__); initTiles(); lastEvent.eventType = EVENT_ERROR; resizeWindow(windowWidth, windowHeight); rogueMain(); SDL_Quit(); } static boolean _pauseForMilliseconds(short ms) { updateScreen(); SDL_Delay(ms); if (lastEvent.eventType != EVENT_ERROR && lastEvent.eventType != MOUSE_ENTERED_CELL) { return true; // SDL already gave us an interrupting event to process } return pollBrogueEvent(&lastEvent, false) // ask SDL for a new event if one is available && lastEvent.eventType != EVENT_ERROR // and check if it is interrupting && lastEvent.eventType != MOUSE_ENTERED_CELL; } static void _nextKeyOrMouseEvent(rogueEvent *returnEvent, boolean textInput, boolean colorsDance) { long tstart, dt; updateScreen(); if (lastEvent.eventType != EVENT_ERROR) { *returnEvent = lastEvent; lastEvent.eventType = EVENT_ERROR; return; } while (true) { tstart = SDL_GetTicks(); if (colorsDance) { shuffleTerrainColors(3, true); commitDraws(); } updateScreen(); if (pollBrogueEvent(returnEvent, textInput)) break; dt = PAUSE_BETWEEN_EVENT_POLLING - (SDL_GetTicks() - tstart); if (dt > 0) { SDL_Delay(dt); } } } /* Returns the index of the sprite representing the given glyph. Sprites <256 are from the text font sheet, 256+ are from the tiles sheet. */ static int fontIndex(enum displayGlyph glyph) { // These are the only non-ASCII glyphs which always come from the font sheet if (glyph == G_UP_ARROW) return 0x90; if (glyph == G_DOWN_ARROW) return 0x91; if (glyph < 128) { // ASCII characters map directly return glyph; } else if (showGraphics && glyph >= 128) { // Tile glyphs have sprite indices starting at 256 // -2 to disregard the up and down arrow glyphs return glyph + 128 - 2; } else { unsigned int code = glyphToUnicode(glyph); switch (code) { case U_MIDDLE_DOT: return 0x80; case U_FOUR_DOTS: return 0x81; case U_DIAMOND: return 0x82; case U_FLIPPED_V: return 0x83; case U_ARIES: return 0x84; case U_ESZETT: return 0xdf; case U_ANKH: return 0x85; case U_MUSIC_NOTE: return 0x86; case U_CIRCLE: return 0x87; case U_LIGHTNING_BOLT: return 0x99; case U_FILLED_CIRCLE: return 0x89; case U_NEUTER: return 0x8a; case U_U_ACUTE: return 0xda; case U_CURRENCY: return 0xa4; case U_UP_ARROW: return 0x90; case U_DOWN_ARROW: return 0x91; case U_LEFT_ARROW: return 0x92; case U_RIGHT_ARROW: return 0x93; case U_OMEGA: return 0x96; case U_CIRCLE_BARS: return 0x8c; case U_FILLED_CIRCLE_BARS: return 0x8d; default: brogueAssert(code < 256); return code; } } } static void _plotChar( enum displayGlyph inputChar, short x, short y, short foreRed, short foreGreen, short foreBlue, short backRed, short backGreen, short backBlue ) { updateTile(y, x, fontIndex(inputChar), foreRed, foreGreen, foreBlue, backRed, backGreen, backBlue); } static void _remap(const char *from, const char *to) { if (nremaps < MAX_REMAPS) { remapping[nremaps].from = from[0]; remapping[nremaps].to = to[0]; nremaps++; } } /* * Take screenshot in current working directory (ScreenshotN.png) */ static boolean _takeScreenshot() { SDL_Surface *screenshot = captureScreen(); if (!screenshot) return false; // choose filename char screenshotFilepath[BROGUE_FILENAME_MAX]; getAvailableFilePath(screenshotFilepath, "Screenshot", SCREENSHOT_SUFFIX); strcat(screenshotFilepath, SCREENSHOT_SUFFIX); // save to PNG IMG_SavePNG(screenshot, screenshotFilepath); SDL_FreeSurface(screenshot); return true; } static boolean _setGraphicsEnabled(boolean state) { showGraphics = state; refreshScreen(); updateScreen(); return state; } struct brogueConsole sdlConsole = { _gameLoop, _pauseForMilliseconds, _nextKeyOrMouseEvent, _plotChar, _remap, _modifierHeld, NULL, _takeScreenshot, _setGraphicsEnabled };