diff --git a/video/sdl_v.c b/video/sdl_v.c new file mode 100644 --- /dev/null +++ b/video/sdl_v.c @@ -0,0 +1,488 @@ +#include "stdafx.h" +#include "openttd.h" +#include "debug.h" +#include "functions.h" +#include "gfx.h" +#include "macros.h" +#include "sdl.h" +#include "window.h" +#include "video/sdl_v.h" +#include +#include "network.h" +#include "variables.h" + +static SDL_Surface *_sdl_screen; +static bool _all_modes; + +#define MAX_DIRTY_RECTS 100 +static SDL_Rect _dirty_rects[MAX_DIRTY_RECTS]; +static int _num_dirty_rects; + +static void SdlVideoMakeDirty(int left, int top, int width, int height) +{ +// printf("(%d,%d)-(%d,%d)\n", left, top, width, height); +// _pixels_redrawn += width*height; + if (_num_dirty_rects < MAX_DIRTY_RECTS) { + _dirty_rects[_num_dirty_rects].x = left; + _dirty_rects[_num_dirty_rects].y = top; + _dirty_rects[_num_dirty_rects].w = width; + _dirty_rects[_num_dirty_rects].h = height; + } + _num_dirty_rects++; +} + +static SDL_Color pal[256]; + +static void UpdatePalette(uint start, uint end) +{ + uint i; + + for (i = start; i != end; i++) { + pal[i].r = _cur_palette[i].r; + pal[i].g = _cur_palette[i].g; + pal[i].b = _cur_palette[i].b; + pal[i].unused = 0; + } + + SDL_CALL SDL_SetColors(_sdl_screen, pal, start, end); +} + +static void InitPalette(void) +{ + UpdatePalette(0, 256); +} + +static void CheckPaletteAnim(void) +{ + if(_pal_last_dirty != -1) { + UpdatePalette(_pal_first_dirty, _pal_last_dirty + 1); + _pal_last_dirty = -1; + } +} + +static void DrawSurfaceToScreen(void) +{ + int n = _num_dirty_rects; + if (n != 0) { + _num_dirty_rects = 0; + if (n > MAX_DIRTY_RECTS) + SDL_CALL SDL_UpdateRect(_sdl_screen, 0, 0, 0, 0); + else + SDL_CALL SDL_UpdateRects(_sdl_screen, n, _dirty_rects); + } +} + +static const uint16 default_resolutions[][2] = { + { 640, 480}, + { 800, 600}, + {1024, 768}, + {1152, 864}, + {1280, 800}, + {1280, 960}, + {1280, 1024}, + {1400, 1050}, + {1600, 1200}, + {1680, 1050}, + {1920, 1200} +}; + +static void GetVideoModes(void) +{ + int i; + SDL_Rect **modes; + + modes = SDL_CALL SDL_ListModes(NULL, SDL_SWSURFACE + (_fullscreen ? SDL_FULLSCREEN : 0)); + + if (modes == NULL) + error("sdl: no modes available"); + + _all_modes = (modes == (void*)-1); + + if (_all_modes) { + // all modes available, put some default ones here + memcpy(_resolutions, default_resolutions, sizeof(default_resolutions)); + _num_resolutions = lengthof(default_resolutions); + } else { + int n = 0; + for (i = 0; modes[i]; i++) { + int w = modes[i]->w; + int h = modes[i]->h; + if (IS_INT_INSIDE(w, 640, MAX_SCREEN_WIDTH + 1) && + IS_INT_INSIDE(h, 480, MAX_SCREEN_HEIGHT + 1)) { + int j; + for (j = 0; j < n; j++) { + if (_resolutions[j][0] == w && _resolutions[j][1] == h) break; + } + + if (j == n) { + _resolutions[j][0] = w; + _resolutions[j][1] = h; + if (++n == lengthof(_resolutions)) break; + } + } + } + _num_resolutions = n; + SortResolutions(_num_resolutions); + } +} + +static int GetAvailableVideoMode(int *w, int *h) +{ + int i; + int best; + uint delta; + + // all modes available? + if (_all_modes) + return 1; + + // is the wanted mode among the available modes? + for (i = 0; i != _num_resolutions; i++) { + if(*w == _resolutions[i][0] && *h == _resolutions[i][1]) + return 1; + } + + // use the closest possible resolution + best = 0; + delta = abs((_resolutions[0][0] - *w) * (_resolutions[0][1] - *h)); + for (i = 1; i != _num_resolutions; ++i) { + uint newdelta = abs((_resolutions[i][0] - *w) * (_resolutions[i][1] - *h)); + if (newdelta < delta) { + best = i; + delta = newdelta; + } + } + *w = _resolutions[best][0]; + *h = _resolutions[best][1]; + return 2; +} + +extern const char _openttd_revision[]; + +static bool CreateMainSurface(int w, int h) +{ + SDL_Surface *newscreen; + char caption[50]; + + GetAvailableVideoMode(&w, &h); + + DEBUG(misc, 1) ("sdl: using mode %dx%d", w, h); + + // DO NOT CHANGE TO HWSURFACE, IT DOES NOT WORK + newscreen = SDL_CALL SDL_SetVideoMode(w, h, 8, SDL_SWSURFACE | SDL_HWPALETTE | (_fullscreen ? SDL_FULLSCREEN : SDL_RESIZABLE)); + if (newscreen == NULL) + return false; + + _screen.width = newscreen->w; + _screen.height = newscreen->h; + _screen.pitch = newscreen->pitch; + + _sdl_screen = newscreen; + InitPalette(); + + snprintf(caption, sizeof(caption), "OpenTTD %s", _openttd_revision); + SDL_CALL SDL_WM_SetCaption(caption, caption); + SDL_CALL SDL_ShowCursor(0); + + GameSizeChanged(); + + return true; +} + +typedef struct VkMapping { + uint16 vk_from; + byte vk_count; + byte map_to; +} VkMapping; + +#define AS(x, z) {x, 0, z} +#define AM(x, y, z, w) {x, y - x, z} + +static const VkMapping _vk_mapping[] = { + // Pageup stuff + up/down + AM(SDLK_PAGEUP, SDLK_PAGEDOWN, WKC_PAGEUP, WKC_PAGEDOWN), + AS(SDLK_UP, WKC_UP), + AS(SDLK_DOWN, WKC_DOWN), + AS(SDLK_LEFT, WKC_LEFT), + AS(SDLK_RIGHT, WKC_RIGHT), + + AS(SDLK_HOME, WKC_HOME), + AS(SDLK_END, WKC_END), + + AS(SDLK_INSERT, WKC_INSERT), + AS(SDLK_DELETE, WKC_DELETE), + + // Map letters & digits + AM(SDLK_a, SDLK_z, 'A', 'Z'), + AM(SDLK_0, SDLK_9, '0', '9'), + + AS(SDLK_ESCAPE, WKC_ESC), + AS(SDLK_PAUSE, WKC_PAUSE), + AS(SDLK_BACKSPACE, WKC_BACKSPACE), + + AS(SDLK_SPACE, WKC_SPACE), + AS(SDLK_RETURN, WKC_RETURN), + AS(SDLK_TAB, WKC_TAB), + + // Function keys + AM(SDLK_F1, SDLK_F12, WKC_F1, WKC_F12), + + // Numeric part. + // What is the virtual keycode for numeric enter?? + AM(SDLK_KP0, SDLK_KP9, WKC_NUM_0, WKC_NUM_9), + AS(SDLK_KP_DIVIDE, WKC_NUM_DIV), + AS(SDLK_KP_MULTIPLY, WKC_NUM_MUL), + AS(SDLK_KP_MINUS, WKC_NUM_MINUS), + AS(SDLK_KP_PLUS, WKC_NUM_PLUS), + AS(SDLK_KP_ENTER, WKC_NUM_ENTER), + AS(SDLK_KP_PERIOD, WKC_NUM_DECIMAL) +}; + +static uint32 ConvertSdlKeyIntoMy(SDL_keysym *sym) +{ + const VkMapping *map; + uint key = 0; + for (map = _vk_mapping; map != endof(_vk_mapping); ++map) { + if ((uint)(sym->sym - map->vk_from) <= map->vk_count) { + key = sym->sym - map->vk_from + map->map_to; + break; + } + } + + // check scancode for BACKQUOTE key, because we want the key left of "1", not anything else (on non-US keyboards) +#if defined(WIN32) || defined(__OS2__) + if (sym->scancode == 41) key |= WKC_BACKQUOTE; +#elif defined(__APPLE__) + if (sym->scancode == 10) key |= WKC_BACKQUOTE; +#elif defined(__MORPHOS__) + if (sym->scancode == 0) key |= WKC_BACKQUOTE; // yes, that key is code '0' under MorphOS :) +#elif defined(__BEOS__) + if (sym->scancode == 17) key |= WKC_BACKQUOTE; +#elif defined(__SVR4) && defined(__sun) + if (sym->scancode == 60) key |= WKC_BACKQUOTE; + if (sym->scancode == 49) key |= WKC_BACKSPACE; +#elif defined(__sgi__) + if (sym->scancode == 22) key |= WKC_BACKQUOTE; +#else + if (sym->scancode == 41) key |= WKC_BACKQUOTE; // Linux console + if (sym->scancode == 49) key |= WKC_BACKQUOTE; +#endif + + // META are the command keys on mac + if (sym->mod & KMOD_META) key |= WKC_META; + if (sym->mod & KMOD_SHIFT) key |= WKC_SHIFT; + if (sym->mod & KMOD_CTRL) key |= WKC_CTRL; + if (sym->mod & KMOD_ALT) key |= WKC_ALT; + // these two lines really help porting hotkey combos. Uncomment to use -- Bjarni + //printf("scancode character pressed %d\n", sym->scancode); + //printf("unicode character pressed %d\n", sym->unicode); + return (key << 16) + sym->unicode; +} + +extern void DoExitSave(void); + +static int PollEvent(void) +{ + SDL_Event ev; + + if (!SDL_CALL SDL_PollEvent(&ev)) + return -2; + + switch (ev.type) { + case SDL_MOUSEMOTION: + if (_cursor.fix_at) { + int dx = ev.motion.x - _cursor.pos.x; + int dy = ev.motion.y - _cursor.pos.y; + if (dx != 0 || dy != 0) { + _cursor.delta.x += dx; + _cursor.delta.y += dy; + SDL_CALL SDL_WarpMouse(_cursor.pos.x, _cursor.pos.y); + } + } else { + _cursor.delta.x = ev.motion.x - _cursor.pos.x; + _cursor.delta.y = ev.motion.y - _cursor.pos.y; + _cursor.pos.x = ev.motion.x; + _cursor.pos.y = ev.motion.y; + _cursor.dirty = true; + } + break; + + case SDL_MOUSEBUTTONDOWN: + if (_rightclick_emulate && (SDL_CALL SDL_GetModState() & (KMOD_LCTRL | KMOD_RCTRL))) + ev.button.button = SDL_BUTTON_RIGHT; + + switch (ev.button.button) { + case SDL_BUTTON_LEFT: + _left_button_down = true; + break; + case SDL_BUTTON_RIGHT: + _right_button_down = true; + _right_button_clicked = true; + break; + case SDL_BUTTON_WHEELUP: + _cursor.wheel--; + break; + case SDL_BUTTON_WHEELDOWN: + _cursor.wheel++; + break; + default: + break; + } + break; + + case SDL_MOUSEBUTTONUP: + if (_rightclick_emulate) { + _right_button_down = false; + _left_button_down = false; + _left_button_clicked = false; + } else if (ev.button.button == SDL_BUTTON_LEFT) { + _left_button_down = false; + _left_button_clicked = false; + } else if (ev.button.button == SDL_BUTTON_RIGHT) { + _right_button_down = false; + } + break; + + case SDL_QUIT: + // do not ask to quit on the main screen + if (_game_mode != GM_MENU) { + if(_patches.autosave_on_exit) { + DoExitSave(); + return ML_QUIT; + } else + AskExitGame(); + } else + return ML_QUIT; + break; + + case SDL_KEYDOWN: /* Toggle full-screen on ALT + ENTER/F */ + if ((ev.key.keysym.mod & (KMOD_ALT | KMOD_META)) && + (ev.key.keysym.sym == SDLK_RETURN || ev.key.keysym.sym == SDLK_f)) { + ToggleFullScreen(!_fullscreen); + } else + _pressed_key = ConvertSdlKeyIntoMy(&ev.key.keysym); + + break; + + case SDL_VIDEORESIZE: { + int w = clamp(ev.resize.w, 64, MAX_SCREEN_WIDTH); + int h = clamp(ev.resize.h, 64, MAX_SCREEN_HEIGHT); + ChangeResInGame(w, h); + break; + } + } + return -1; +} + +static const char *SdlVideoStart(const char * const *parm) +{ + char buf[30]; + + const char *s = SdlOpen(SDL_INIT_VIDEO); + if (s != NULL) return s; + + SDL_CALL SDL_VideoDriverName(buf, 30); + DEBUG(misc, 1) ("sdl: using driver '%s'", buf); + + GetVideoModes(); + CreateMainSurface(_cur_resolution[0], _cur_resolution[1]); + MarkWholeScreenDirty(); + + SDL_CALL SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); + SDL_CALL SDL_EnableUNICODE(1); + return NULL; +} + +static void SdlVideoStop(void) +{ + SdlClose(SDL_INIT_VIDEO); +} + +static int SdlVideoMainLoop(void) +{ + uint32 next_tick = SDL_CALL SDL_GetTicks() + 30; + uint32 cur_ticks; + uint32 pal_tick = 0; + int i; + uint32 mod; + int numkeys; + Uint8 *keys; + + for (;;) { + InteractiveRandom(); // randomness + + while ((i = PollEvent()) == -1) {} + if (i >= 0) return i; + + if (_exit_game) return ML_QUIT; + + mod = SDL_CALL SDL_GetModState(); + keys = SDL_CALL SDL_GetKeyState(&numkeys); +#if defined(_DEBUG) + if (_shift_pressed) +#else + if (keys[SDLK_TAB]) +#endif + { + if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2; + } else if (_fast_forward & 2) { + _fast_forward = 0; + } + + cur_ticks = SDL_CALL SDL_GetTicks(); + if ((_fast_forward && !_pause) || cur_ticks > next_tick) + next_tick = cur_ticks; + + if (cur_ticks == next_tick) { + next_tick += 30; + + _ctrl_pressed = !!(mod & (KMOD_LCTRL | KMOD_RCTRL)); + _shift_pressed = !!(mod & (KMOD_LSHIFT | KMOD_RSHIFT)); + _dbg_screen_rect = !!(mod & KMOD_CAPS); + + // determine which directional keys are down + _dirkeys = + (keys[SDLK_LEFT] ? 1 : 0) | + (keys[SDLK_UP] ? 2 : 0) | + (keys[SDLK_RIGHT] ? 4 : 0) | + (keys[SDLK_DOWN] ? 8 : 0); + GameLoop(); + + _screen.dst_ptr = _sdl_screen->pixels; + UpdateWindows(); + if (++pal_tick > 4) { + CheckPaletteAnim(); + pal_tick = 1; + } + DrawSurfaceToScreen(); + } else { + SDL_CALL SDL_Delay(1); + _screen.dst_ptr = _sdl_screen->pixels; + DrawTextMessage(); + DrawMouseCursor(); + DrawSurfaceToScreen(); + } + } +} + +static bool SdlVideoChangeRes(int w, int h) +{ + return CreateMainSurface(w, h); +} + +static void SdlVideoFullScreen(bool full_screen) +{ + _fullscreen = full_screen; + GetVideoModes(); // get the list of available video modes + if (!_video_driver->change_resolution(_cur_resolution[0], _cur_resolution[1])) + _fullscreen ^= true; // switching resolution failed, put back full_screen to original status +} + +const HalVideoDriver _sdl_video_driver = { + SdlVideoStart, + SdlVideoStop, + SdlVideoMakeDirty, + SdlVideoMainLoop, + SdlVideoChangeRes, + SdlVideoFullScreen, +};