9a8d117b85f1f2c7e6f78c3b35d8b582d0e9f311
[rocksndiamonds.git] / src / libgame / system.c
1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // system.c
10 // ============================================================================
11
12 #include <string.h>
13 #include <signal.h>
14
15 #include "platform.h"
16
17 #include "system.h"
18 #include "image.h"
19 #include "sound.h"
20 #include "setup.h"
21 #include "joystick.h"
22 #include "misc.h"
23
24 #define ENABLE_UNUSED_CODE      0       // currently unused functions
25
26
27 // ============================================================================
28 // exported variables
29 // ============================================================================
30
31 struct ProgramInfo      program;
32 struct NetworkInfo      network;
33 struct RuntimeInfo      runtime;
34 struct OptionInfo       options;
35 struct VideoSystemInfo  video;
36 struct AudioSystemInfo  audio;
37 struct GfxInfo          gfx;
38 struct TileCursorInfo   tile_cursor;
39 struct OverlayInfo      overlay;
40 struct ArtworkInfo      artwork;
41 struct JoystickInfo     joystick;
42 struct SetupInfo        setup;
43
44 LevelDirTree           *leveldir_first_all = NULL;
45 LevelDirTree           *leveldir_first = NULL;
46 LevelDirTree           *leveldir_current = NULL;
47 int                     level_nr;
48
49 struct LevelSetInfo     levelset;
50 struct LevelStats       level_stats[MAX_LEVELS];
51
52 DrawWindow             *window = NULL;
53 DrawBuffer             *backbuffer = NULL;
54 DrawBuffer             *drawto = NULL;
55
56 int                     button_status = MB_NOT_PRESSED;
57 boolean                 motion_status = FALSE;
58 int                     wheel_steps = DEFAULT_WHEEL_STEPS;
59 boolean                 keyrepeat_status = TRUE;
60 boolean                 textinput_status = FALSE;
61
62 int                     redraw_mask = REDRAW_NONE;
63
64 int                     FrameCounter = 0;
65
66
67 // ============================================================================
68 // init/close functions
69 // ============================================================================
70
71 void InitProgramInfo(char *argv0, char *config_filename, char *userdata_subdir,
72                      char *program_title, char *icon_title,
73                      char *icon_filename, char *cookie_prefix,
74                      char *program_version_string, int program_version)
75 {
76   program.command_basepath = getBasePath(argv0);
77   program.command_basename = getBaseName(argv0);
78
79   program.config_filename = config_filename;
80
81   program.userdata_subdir = userdata_subdir;
82   program.userdata_path = getUserGameDataDir();
83
84   program.program_title = program_title;
85   program.window_title = "(undefined)";
86   program.icon_title = icon_title;
87
88   program.icon_filename = icon_filename;
89
90   program.cookie_prefix = cookie_prefix;
91
92   program.version_super = VERSION_SUPER(program_version);
93   program.version_major = VERSION_MAJOR(program_version);
94   program.version_minor = VERSION_MINOR(program_version);
95   program.version_patch = VERSION_PATCH(program_version);
96   program.version_ident = program_version;
97
98   program.version_string = program_version_string;
99
100   program.log_filename[LOG_OUT_ID] = getLogFilename(LOG_OUT_BASENAME);
101   program.log_filename[LOG_ERR_ID] = getLogFilename(LOG_ERR_BASENAME);
102   program.log_file[LOG_OUT_ID] = program.log_file_default[LOG_OUT_ID] = stdout;
103   program.log_file[LOG_ERR_ID] = program.log_file_default[LOG_ERR_ID] = stderr;
104
105   program.headless = FALSE;
106 }
107
108 void InitNetworkInfo(boolean enabled, boolean connected, boolean serveronly,
109                      char *server_host, int server_port)
110 {
111   network.enabled = enabled;
112   network.connected = connected;
113   network.serveronly = serveronly;
114
115   network.server_host = server_host;
116   network.server_port = server_port;
117 }
118
119 void InitRuntimeInfo()
120 {
121 #if defined(HAS_TOUCH_DEVICE)
122   runtime.uses_touch_device = TRUE;
123 #else
124   runtime.uses_touch_device = FALSE;
125 #endif
126 }
127
128 void InitScoresInfo(void)
129 {
130   char *global_scores_dir = getPath2(getCommonDataDir(), SCORES_DIRECTORY);
131
132   program.global_scores = directoryExists(global_scores_dir);
133   program.many_scores_per_name = !program.global_scores;
134
135 #if 0
136   if (options.debug)
137   {
138     if (program.global_scores)
139     {
140       Error(ERR_DEBUG, "Using global, multi-user scores directory '%s'.",
141             global_scores_dir);
142       Error(ERR_DEBUG, "Remove to enable single-user scores directory.");
143       Error(ERR_DEBUG, "(This enables multipe score entries per user.)");
144     }
145     else
146     {
147       Error(ERR_DEBUG, "Using private, single-user scores directory.");
148     }
149   }
150 #endif
151
152   free(global_scores_dir);
153 }
154
155 void SetWindowTitle(void)
156 {
157   program.window_title = program.window_title_function();
158
159   SDLSetWindowTitle();
160 }
161
162 void InitWindowTitleFunction(char *(*window_title_function)(void))
163 {
164   program.window_title_function = window_title_function;
165 }
166
167 void InitExitMessageFunction(void (*exit_message_function)(char *, va_list))
168 {
169   program.exit_message_function = exit_message_function;
170 }
171
172 void InitExitFunction(void (*exit_function)(int))
173 {
174   program.exit_function = exit_function;
175
176   // set signal handlers to custom exit function
177   // signal(SIGINT, exit_function);
178   signal(SIGTERM, exit_function);
179
180   // set exit function to automatically cleanup SDL stuff after exit()
181   atexit(SDL_Quit);
182 }
183
184 void InitPlatformDependentStuff(void)
185 {
186   // this is initialized in GetOptions(), but may already be used before
187   options.verbose = TRUE;
188
189   OpenLogFiles();
190
191   int sdl_init_flags = SDL_INIT_EVENTS | SDL_INIT_NOPARACHUTE;
192
193   if (SDL_Init(sdl_init_flags) < 0)
194     Error(ERR_EXIT, "SDL_Init() failed: %s", SDL_GetError());
195
196   SDLNet_Init();
197 }
198
199 void ClosePlatformDependentStuff(void)
200 {
201   CloseLogFiles();
202 }
203
204 void InitGfxFieldInfo(int sx, int sy, int sxsize, int sysize,
205                       int real_sx, int real_sy,
206                       int full_sxsize, int full_sysize,
207                       Bitmap *field_save_buffer)
208 {
209   gfx.sx = sx;
210   gfx.sy = sy;
211   gfx.sxsize = sxsize;
212   gfx.sysize = sysize;
213   gfx.real_sx = real_sx;
214   gfx.real_sy = real_sy;
215   gfx.full_sxsize = full_sxsize;
216   gfx.full_sysize = full_sysize;
217
218   gfx.field_save_buffer = field_save_buffer;
219
220   SetDrawDeactivationMask(REDRAW_NONE);         // do not deactivate drawing
221   SetDrawBackgroundMask(REDRAW_NONE);           // deactivate masked drawing
222 }
223
224 void InitGfxTileSizeInfo(int game_tile_size, int standard_tile_size)
225 {
226   gfx.game_tile_size = game_tile_size;
227   gfx.standard_tile_size = standard_tile_size;
228 }
229
230 void InitGfxDoor1Info(int dx, int dy, int dxsize, int dysize)
231 {
232   gfx.dx = dx;
233   gfx.dy = dy;
234   gfx.dxsize = dxsize;
235   gfx.dysize = dysize;
236 }
237
238 void InitGfxDoor2Info(int vx, int vy, int vxsize, int vysize)
239 {
240   gfx.vx = vx;
241   gfx.vy = vy;
242   gfx.vxsize = vxsize;
243   gfx.vysize = vysize;
244 }
245
246 void InitGfxDoor3Info(int ex, int ey, int exsize, int eysize)
247 {
248   gfx.ex = ex;
249   gfx.ey = ey;
250   gfx.exsize = exsize;
251   gfx.eysize = eysize;
252 }
253
254 void InitGfxWindowInfo(int win_xsize, int win_ysize)
255 {
256   if (win_xsize != gfx.win_xsize || win_ysize != gfx.win_ysize)
257   {
258     ReCreateBitmap(&gfx.background_bitmap, win_xsize, win_ysize);
259
260     ReCreateBitmap(&gfx.final_screen_bitmap, win_xsize, win_ysize);
261
262     ReCreateBitmap(&gfx.fade_bitmap_backup, win_xsize, win_ysize);
263     ReCreateBitmap(&gfx.fade_bitmap_source, win_xsize, win_ysize);
264     ReCreateBitmap(&gfx.fade_bitmap_target, win_xsize, win_ysize);
265     ReCreateBitmap(&gfx.fade_bitmap_black,  win_xsize, win_ysize);
266
267     ClearRectangle(gfx.fade_bitmap_black, 0, 0, win_xsize, win_ysize);
268   }
269
270   gfx.win_xsize = win_xsize;
271   gfx.win_ysize = win_ysize;
272
273   gfx.background_bitmap_mask = REDRAW_NONE;
274 }
275
276 void InitGfxScrollbufferInfo(int scrollbuffer_width, int scrollbuffer_height)
277 {
278   // currently only used by MSDOS code to alloc VRAM buffer, if available
279   // 2009-03-24: also (temporarily?) used for overlapping blit workaround
280   gfx.scrollbuffer_width = scrollbuffer_width;
281   gfx.scrollbuffer_height = scrollbuffer_height;
282 }
283
284 void InitGfxClipRegion(boolean enabled, int x, int y, int width, int height)
285 {
286   gfx.clipping_enabled = enabled;
287   gfx.clip_x = x;
288   gfx.clip_y = y;
289   gfx.clip_width = width;
290   gfx.clip_height = height;
291 }
292
293 void InitGfxDrawBusyAnimFunction(void (*draw_busy_anim_function)(void))
294 {
295   gfx.draw_busy_anim_function = draw_busy_anim_function;
296 }
297
298 void InitGfxDrawGlobalAnimFunction(void (*draw_global_anim_function)(int, int))
299 {
300   gfx.draw_global_anim_function = draw_global_anim_function;
301 }
302
303 void InitGfxDrawGlobalBorderFunction(void (*draw_global_border_function)(int))
304 {
305   gfx.draw_global_border_function = draw_global_border_function;
306 }
307
308 void InitGfxDrawTileCursorFunction(void (*draw_tile_cursor_function)(int))
309 {
310   gfx.draw_tile_cursor_function = draw_tile_cursor_function;
311 }
312
313 void InitGfxCustomArtworkInfo(void)
314 {
315   gfx.override_level_graphics = FALSE;
316   gfx.override_level_sounds = FALSE;
317   gfx.override_level_music = FALSE;
318
319   gfx.draw_init_text = TRUE;
320 }
321
322 void InitGfxOtherSettings(void)
323 {
324   gfx.cursor_mode = CURSOR_DEFAULT;
325   gfx.cursor_mode_override = CURSOR_UNDEFINED;
326   gfx.cursor_mode_final = gfx.cursor_mode;
327
328   // prevent initially displaying custom mouse cursor in upper left corner
329   gfx.mouse_x = POS_OFFSCREEN;
330   gfx.mouse_y = POS_OFFSCREEN;
331 }
332
333 void InitTileCursorInfo(void)
334 {
335   tile_cursor.enabled = FALSE;
336   tile_cursor.active = FALSE;
337   tile_cursor.moving = FALSE;
338
339   tile_cursor.xpos = 0;
340   tile_cursor.ypos = 0;
341   tile_cursor.x = 0;
342   tile_cursor.y = 0;
343   tile_cursor.target_x = 0;
344   tile_cursor.target_y = 0;
345
346   tile_cursor.sx = 0;
347   tile_cursor.sy = 0;
348 }
349
350 void InitOverlayInfo(void)
351 {
352   int nr = GRID_ACTIVE_NR();
353   int x, y;
354
355   overlay.enabled = FALSE;
356   overlay.active = FALSE;
357
358   overlay.show_grid = FALSE;
359
360   overlay.grid_xsize = setup.touch.grid_xsize[nr];
361   overlay.grid_ysize = setup.touch.grid_ysize[nr];
362
363   for (x = 0; x < MAX_GRID_XSIZE; x++)
364     for (y = 0; y < MAX_GRID_YSIZE; y++)
365       overlay.grid_button[x][y] = setup.touch.grid_button[nr][x][y];
366
367   overlay.grid_button_highlight = CHAR_GRID_BUTTON_NONE;
368   overlay.grid_button_action = JOY_NO_ACTION;
369
370 #if defined(USE_TOUCH_INPUT_OVERLAY)
371   if (strEqual(setup.touch.control_type, TOUCH_CONTROL_VIRTUAL_BUTTONS))
372     overlay.enabled = TRUE;
373 #endif
374 }
375
376 void SetTileCursorEnabled(boolean enabled)
377 {
378   tile_cursor.enabled = enabled;
379 }
380
381 void SetTileCursorActive(boolean active)
382 {
383   tile_cursor.active = active;
384 }
385
386 void SetTileCursorTargetXY(int x, int y)
387 {
388   // delayed placement of tile selection cursor at target position
389   // (tile cursor will be moved to target position step by step)
390
391   tile_cursor.xpos = x;
392   tile_cursor.ypos = y;
393   tile_cursor.target_x = tile_cursor.sx + x * gfx.game_tile_size;
394   tile_cursor.target_y = tile_cursor.sy + y * gfx.game_tile_size;
395
396   tile_cursor.moving = TRUE;
397 }
398
399 void SetTileCursorXY(int x, int y)
400 {
401   // immediate placement of tile selection cursor at target position
402
403   SetTileCursorTargetXY(x, y);
404
405   tile_cursor.x = tile_cursor.target_x;
406   tile_cursor.y = tile_cursor.target_y;
407
408   tile_cursor.moving = FALSE;
409 }
410
411 void SetTileCursorSXSY(int sx, int sy)
412 {
413   tile_cursor.sx = sx;
414   tile_cursor.sy = sy;
415 }
416
417 void SetOverlayEnabled(boolean enabled)
418 {
419   overlay.enabled = enabled;
420 }
421
422 void SetOverlayActive(boolean active)
423 {
424   overlay.active = active;
425 }
426
427 void SetOverlayShowGrid(boolean show_grid)
428 {
429   overlay.show_grid = show_grid;
430
431   SetOverlayActive(show_grid);
432
433   if (show_grid)
434     SetOverlayEnabled(TRUE);
435 }
436
437 boolean GetOverlayEnabled(void)
438 {
439   return overlay.enabled;
440 }
441
442 boolean GetOverlayActive(void)
443 {
444   return overlay.active;
445 }
446
447 void SetDrawDeactivationMask(int draw_deactivation_mask)
448 {
449   gfx.draw_deactivation_mask = draw_deactivation_mask;
450 }
451
452 int GetDrawDeactivationMask(void)
453 {
454   return gfx.draw_deactivation_mask;
455 }
456
457 void SetDrawBackgroundMask(int draw_background_mask)
458 {
459   gfx.draw_background_mask = draw_background_mask;
460 }
461
462 static void SetBackgroundBitmap(Bitmap *background_bitmap_tile, int mask)
463 {
464   if (background_bitmap_tile != NULL)
465     gfx.background_bitmap_mask |= mask;
466   else
467     gfx.background_bitmap_mask &= ~mask;
468
469   if (background_bitmap_tile == NULL)   // empty background requested
470     return;
471
472   if (mask == REDRAW_ALL)
473     BlitBitmapTiled(background_bitmap_tile, gfx.background_bitmap, 0, 0, 0, 0,
474                     0, 0, video.width, video.height);
475   else if (mask == REDRAW_FIELD)
476     BlitBitmapTiled(background_bitmap_tile, gfx.background_bitmap, 0, 0, 0, 0,
477                     gfx.real_sx, gfx.real_sy, gfx.full_sxsize, gfx.full_sysize);
478   else if (mask == REDRAW_DOOR_1)
479     BlitBitmapTiled(background_bitmap_tile, gfx.background_bitmap, 0, 0, 0, 0,
480                     gfx.dx, gfx.dy, gfx.dxsize, gfx.dysize);
481 }
482
483 void SetWindowBackgroundBitmap(Bitmap *background_bitmap_tile)
484 {
485   // remove every mask before setting mask for window
486   // (!!! TO BE FIXED: The whole REDRAW_* system really sucks! !!!)
487   SetBackgroundBitmap(NULL, 0xffff);            // !!! FIX THIS !!!
488   SetBackgroundBitmap(background_bitmap_tile, REDRAW_ALL);
489 }
490
491 void SetMainBackgroundBitmap(Bitmap *background_bitmap_tile)
492 {
493   // remove window area mask before setting mask for main area
494   // (!!! TO BE FIXED: The whole REDRAW_* system really sucks! !!!)
495   SetBackgroundBitmap(NULL, REDRAW_ALL);        // !!! FIX THIS !!!
496   SetBackgroundBitmap(background_bitmap_tile, REDRAW_FIELD);
497 }
498
499 void SetDoorBackgroundBitmap(Bitmap *background_bitmap_tile)
500 {
501   // remove window area mask before setting mask for door area
502   // (!!! TO BE FIXED: The whole REDRAW_* system really sucks! !!!)
503   SetBackgroundBitmap(NULL, REDRAW_ALL);        // !!! FIX THIS !!!
504   SetBackgroundBitmap(background_bitmap_tile, REDRAW_DOOR_1);
505 }
506
507
508 // ============================================================================
509 // video functions
510 // ============================================================================
511
512 static int GetRealDepth(int depth)
513 {
514   return (depth == DEFAULT_DEPTH ? video.default_depth : depth);
515 }
516
517 static void sysFillRectangle(Bitmap *bitmap, int x, int y,
518                              int width, int height, Pixel color)
519 {
520   SDLFillRectangle(bitmap, x, y, width, height, color);
521
522   if (bitmap == backbuffer)
523     SetRedrawMaskFromArea(x, y, width, height);
524 }
525
526 static void sysCopyArea(Bitmap *src_bitmap, Bitmap *dst_bitmap,
527                         int src_x, int src_y, int width, int height,
528                         int dst_x, int dst_y, int mask_mode)
529 {
530   SDLCopyArea(src_bitmap, dst_bitmap, src_x, src_y, width, height,
531               dst_x, dst_y, mask_mode);
532
533   if (dst_bitmap == backbuffer)
534     SetRedrawMaskFromArea(dst_x, dst_y, width, height);
535 }
536
537 void LimitScreenUpdates(boolean enable)
538 {
539   SDLLimitScreenUpdates(enable);
540 }
541
542 void InitVideoDefaults(void)
543 {
544   video.default_depth = 32;
545 }
546
547 void InitVideoDisplay(void)
548 {
549   if (program.headless)
550     return;
551
552   SDLInitVideoDisplay();
553   SDLSetDisplaySize();
554 }
555
556 void CloseVideoDisplay(void)
557 {
558   KeyboardAutoRepeatOn();
559
560   SDL_QuitSubSystem(SDL_INIT_VIDEO);
561 }
562
563 void InitVideoBuffer(int width, int height, int depth, boolean fullscreen)
564 {
565   video.width = width;
566   video.height = height;
567   video.depth = GetRealDepth(depth);
568
569   video.screen_width = width;
570   video.screen_height = height;
571   video.screen_xoffset = 0;
572   video.screen_yoffset = 0;
573
574   video.fullscreen_available = FULLSCREEN_STATUS;
575   video.fullscreen_enabled = FALSE;
576
577   video.window_scaling_available = WINDOW_SCALING_STATUS;
578
579   video.frame_counter = 0;
580   video.frame_delay = 0;
581   video.frame_delay_value = GAME_FRAME_DELAY;
582
583   video.shifted_up = FALSE;
584   video.shifted_up_pos = 0;
585   video.shifted_up_pos_last = 0;
586   video.shifted_up_delay = 0;
587   video.shifted_up_delay_value = ONE_SECOND_DELAY / 4;
588
589   SDLInitVideoBuffer(fullscreen);
590
591   video.initialized = !program.headless;
592
593   drawto = backbuffer;
594 }
595
596 static void FreeBitmapPointers(Bitmap *bitmap)
597 {
598   if (bitmap == NULL)
599     return;
600
601   SDLFreeBitmapPointers(bitmap);
602
603   checked_free(bitmap->source_filename);
604   bitmap->source_filename = NULL;
605 }
606
607 static void TransferBitmapPointers(Bitmap *src_bitmap,
608                                    Bitmap *dst_bitmap)
609 {
610   if (src_bitmap == NULL || dst_bitmap == NULL)
611     return;
612
613   FreeBitmapPointers(dst_bitmap);
614
615   *dst_bitmap = *src_bitmap;
616 }
617
618 void FreeBitmap(Bitmap *bitmap)
619 {
620   if (bitmap == NULL)
621     return;
622
623   FreeBitmapPointers(bitmap);
624
625   free(bitmap);
626 }
627
628 Bitmap *CreateBitmapStruct(void)
629 {
630   return checked_calloc(sizeof(Bitmap));
631 }
632
633 Bitmap *CreateBitmap(int width, int height, int depth)
634 {
635   Bitmap *new_bitmap = CreateBitmapStruct();
636   int real_width  = MAX(1, width);      // prevent zero bitmap width
637   int real_height = MAX(1, height);     // prevent zero bitmap height
638   int real_depth  = GetRealDepth(depth);
639
640   SDLCreateBitmapContent(new_bitmap, real_width, real_height, real_depth);
641
642   new_bitmap->width  = real_width;
643   new_bitmap->height = real_height;
644
645   return new_bitmap;
646 }
647
648 void ReCreateBitmap(Bitmap **bitmap, int width, int height)
649 {
650   if (*bitmap != NULL)
651   {
652     // if new bitmap size fits into old one, no need to re-create it
653     if (width  <= (*bitmap)->width &&
654         height <= (*bitmap)->height)
655       return;
656
657     // else adjust size so that old and new bitmap size fit into it
658     width  = MAX(width,  (*bitmap)->width);
659     height = MAX(height, (*bitmap)->height);
660   }
661
662   Bitmap *new_bitmap = CreateBitmap(width, height, DEFAULT_DEPTH);
663
664   if (*bitmap == NULL)
665   {
666     *bitmap = new_bitmap;
667   }
668   else
669   {
670     TransferBitmapPointers(new_bitmap, *bitmap);
671     free(new_bitmap);
672   }
673 }
674
675 #if 0
676 static void CloseWindow(DrawWindow *window)
677 {
678 }
679 #endif
680
681 void SetRedrawMaskFromArea(int x, int y, int width, int height)
682 {
683   int x1 = x;
684   int y1 = y;
685   int x2 = x + width - 1;
686   int y2 = y + height - 1;
687
688   if (width == 0 || height == 0)
689     return;
690
691   if (IN_GFX_FIELD_FULL(x1, y1) && IN_GFX_FIELD_FULL(x2, y2))
692     redraw_mask |= REDRAW_FIELD;
693   else if (IN_GFX_DOOR_1(x1, y1) && IN_GFX_DOOR_1(x2, y2))
694     redraw_mask |= REDRAW_DOOR_1;
695   else if (IN_GFX_DOOR_2(x1, y1) && IN_GFX_DOOR_2(x2, y2))
696     redraw_mask |= REDRAW_DOOR_2;
697   else if (IN_GFX_DOOR_3(x1, y1) && IN_GFX_DOOR_3(x2, y2))
698     redraw_mask |= REDRAW_DOOR_3;
699   else
700     redraw_mask = REDRAW_ALL;
701 }
702
703 static boolean CheckDrawingArea(int x, int y, int width, int height,
704                                 int draw_mask)
705 {
706   if (draw_mask == REDRAW_NONE)
707     return FALSE;
708
709   if (draw_mask & REDRAW_ALL)
710     return TRUE;
711
712   if ((draw_mask & REDRAW_FIELD) && IN_GFX_FIELD_FULL(x, y))
713     return TRUE;
714
715   if ((draw_mask & REDRAW_DOOR_1) && IN_GFX_DOOR_1(x, y))
716     return TRUE;
717
718   if ((draw_mask & REDRAW_DOOR_2) && IN_GFX_DOOR_2(x, y))
719     return TRUE;
720
721   if ((draw_mask & REDRAW_DOOR_3) && IN_GFX_DOOR_3(x, y))
722     return TRUE;
723
724   return FALSE;
725 }
726
727 boolean DrawingDeactivatedField(void)
728 {
729   if (program.headless)
730     return TRUE;
731
732   if (gfx.draw_deactivation_mask & REDRAW_FIELD)
733     return TRUE;
734
735   return FALSE;
736 }
737
738 boolean DrawingDeactivated(int x, int y, int width, int height)
739 {
740   return CheckDrawingArea(x, y, width, height, gfx.draw_deactivation_mask);
741 }
742
743 boolean DrawingOnBackground(int x, int y)
744 {
745   return (CheckDrawingArea(x, y, 1, 1, gfx.background_bitmap_mask) &&
746           CheckDrawingArea(x, y, 1, 1, gfx.draw_background_mask));
747 }
748
749 static boolean InClippedRectangle(Bitmap *bitmap, int *x, int *y,
750                                   int *width, int *height, boolean is_dest)
751 {
752   int clip_x, clip_y, clip_width, clip_height;
753
754   if (gfx.clipping_enabled && is_dest)  // only clip destination bitmap
755   {
756     clip_x = MIN(MAX(0, gfx.clip_x), bitmap->width);
757     clip_y = MIN(MAX(0, gfx.clip_y), bitmap->height);
758     clip_width = MIN(MAX(0, gfx.clip_width), bitmap->width - clip_x);
759     clip_height = MIN(MAX(0, gfx.clip_height), bitmap->height - clip_y);
760   }
761   else
762   {
763     clip_x = 0;
764     clip_y = 0;
765     clip_width = bitmap->width;
766     clip_height = bitmap->height;
767   }
768
769   // skip if rectangle completely outside bitmap
770
771   if (*x + *width  <= clip_x ||
772       *y + *height <= clip_y ||
773       *x >= clip_x + clip_width ||
774       *y >= clip_y + clip_height)
775     return FALSE;
776
777   // clip if rectangle overlaps bitmap
778
779   if (*x < clip_x)
780   {
781     *width -= clip_x - *x;
782     *x = clip_x;
783   }
784   else if (*x + *width > clip_x + clip_width)
785   {
786     *width = clip_x + clip_width - *x;
787   }
788
789   if (*y < clip_y)
790   {
791     *height -= clip_y - *y;
792     *y = clip_y;
793   }
794   else if (*y + *height > clip_y + clip_height)
795   {
796     *height = clip_y + clip_height - *y;
797   }
798
799   return TRUE;
800 }
801
802 void BlitBitmap(Bitmap *src_bitmap, Bitmap *dst_bitmap,
803                 int src_x, int src_y, int width, int height,
804                 int dst_x, int dst_y)
805 {
806   int dst_x_unclipped = dst_x;
807   int dst_y_unclipped = dst_y;
808
809   if (program.headless)
810     return;
811
812   if (src_bitmap == NULL || dst_bitmap == NULL)
813     return;
814
815   if (DrawingDeactivated(dst_x, dst_y, width, height))
816     return;
817
818   if (!InClippedRectangle(src_bitmap, &src_x, &src_y, &width, &height, FALSE) ||
819       !InClippedRectangle(dst_bitmap, &dst_x, &dst_y, &width, &height, TRUE))
820     return;
821
822   // source x/y might need adjustment if destination x/y was clipped top/left
823   src_x += dst_x - dst_x_unclipped;
824   src_y += dst_y - dst_y_unclipped;
825
826   // !!! 2013-12-11: An "old friend" is back. Same bug in SDL2 2.0.1 !!!
827   // !!! 2009-03-30: Fixed by using self-compiled, patched SDL.dll !!!
828   /* (This bug still exists in the actual (as of 2009-06-15) version 1.2.13,
829      but is already fixed in SVN and should therefore finally be fixed with
830      the next official SDL release, which is probably version 1.2.14.) */
831   // !!! 2009-03-24: It seems that this problem still exists in 1.2.12 !!!
832
833   if (src_bitmap == dst_bitmap)
834   {
835     // needed when blitting directly to same bitmap -- should not be needed with
836     // recent SDL libraries, but apparently does not work in 1.2.11 directly
837
838     static Bitmap *tmp_bitmap = NULL;
839     static int tmp_bitmap_xsize = 0;
840     static int tmp_bitmap_ysize = 0;
841
842     // start with largest static bitmaps for initial bitmap size ...
843     if (tmp_bitmap_xsize == 0 && tmp_bitmap_ysize == 0)
844     {
845       tmp_bitmap_xsize = MAX(gfx.win_xsize, gfx.scrollbuffer_width);
846       tmp_bitmap_ysize = MAX(gfx.win_ysize, gfx.scrollbuffer_height);
847     }
848
849     // ... and allow for later re-adjustments due to custom artwork bitmaps
850     if (src_bitmap->width > tmp_bitmap_xsize ||
851         src_bitmap->height > tmp_bitmap_ysize)
852     {
853       tmp_bitmap_xsize = MAX(tmp_bitmap_xsize, src_bitmap->width);
854       tmp_bitmap_ysize = MAX(tmp_bitmap_ysize, src_bitmap->height);
855
856       FreeBitmap(tmp_bitmap);
857
858       tmp_bitmap = NULL;
859     }
860
861     if (tmp_bitmap == NULL)
862       tmp_bitmap = CreateBitmap(tmp_bitmap_xsize, tmp_bitmap_ysize,
863                                 DEFAULT_DEPTH);
864
865     sysCopyArea(src_bitmap, tmp_bitmap,
866                 src_x, src_y, width, height, dst_x, dst_y, BLIT_OPAQUE);
867     sysCopyArea(tmp_bitmap, dst_bitmap,
868                 dst_x, dst_y, width, height, dst_x, dst_y, BLIT_OPAQUE);
869
870     return;
871   }
872
873   sysCopyArea(src_bitmap, dst_bitmap,
874               src_x, src_y, width, height, dst_x, dst_y, BLIT_OPAQUE);
875 }
876
877 void BlitBitmapTiled(Bitmap *src_bitmap, Bitmap *dst_bitmap,
878                      int src_x, int src_y, int src_width, int src_height,
879                      int dst_x, int dst_y, int dst_width, int dst_height)
880 {
881   int src_xsize = (src_width  == 0 ? src_bitmap->width  : src_width);
882   int src_ysize = (src_height == 0 ? src_bitmap->height : src_height);
883   int dst_xsize = dst_width;
884   int dst_ysize = dst_height;
885   int src_xsteps = (dst_xsize + src_xsize - 1) / src_xsize;
886   int src_ysteps = (dst_ysize + src_ysize - 1) / src_ysize;
887   int x, y;
888
889   for (y = 0; y < src_ysteps; y++)
890   {
891     for (x = 0; x < src_xsteps; x++)
892     {
893       int draw_x = dst_x + x * src_xsize;
894       int draw_y = dst_y + y * src_ysize;
895       int draw_xsize = MIN(src_xsize, dst_xsize - x * src_xsize);
896       int draw_ysize = MIN(src_ysize, dst_ysize - y * src_ysize);
897
898       BlitBitmap(src_bitmap, dst_bitmap, src_x, src_y, draw_xsize, draw_ysize,
899                  draw_x, draw_y);
900     }
901   }
902 }
903
904 void FadeRectangle(int x, int y, int width, int height,
905                    int fade_mode, int fade_delay, int post_delay,
906                    void (*draw_border_function)(void))
907 {
908   // (use destination bitmap "backbuffer" -- "bitmap_cross" may be undefined)
909   if (!InClippedRectangle(backbuffer, &x, &y, &width, &height, TRUE))
910     return;
911
912   SDLFadeRectangle(x, y, width, height,
913                    fade_mode, fade_delay, post_delay, draw_border_function);
914 }
915
916 void FillRectangle(Bitmap *bitmap, int x, int y, int width, int height,
917                    Pixel color)
918 {
919   if (DrawingDeactivated(x, y, width, height))
920     return;
921
922   if (!InClippedRectangle(bitmap, &x, &y, &width, &height, TRUE))
923     return;
924
925   sysFillRectangle(bitmap, x, y, width, height, color);
926 }
927
928 void ClearRectangle(Bitmap *bitmap, int x, int y, int width, int height)
929 {
930   FillRectangle(bitmap, x, y, width, height, BLACK_PIXEL);
931 }
932
933 void ClearRectangleOnBackground(Bitmap *bitmap, int x, int y,
934                                 int width, int height)
935 {
936   if (DrawingOnBackground(x, y))
937     BlitBitmap(gfx.background_bitmap, bitmap, x, y, width, height, x, y);
938   else
939     ClearRectangle(bitmap, x, y, width, height);
940 }
941
942 void BlitBitmapMasked(Bitmap *src_bitmap, Bitmap *dst_bitmap,
943                       int src_x, int src_y, int width, int height,
944                       int dst_x, int dst_y)
945 {
946   if (DrawingDeactivated(dst_x, dst_y, width, height))
947     return;
948
949   sysCopyArea(src_bitmap, dst_bitmap, src_x, src_y, width, height,
950               dst_x, dst_y, BLIT_MASKED);
951 }
952
953 void BlitBitmapOnBackground(Bitmap *src_bitmap, Bitmap *dst_bitmap,
954                             int src_x, int src_y, int width, int height,
955                             int dst_x, int dst_y)
956 {
957   if (DrawingOnBackground(dst_x, dst_y))
958   {
959     // draw background
960     BlitBitmap(gfx.background_bitmap, dst_bitmap, dst_x, dst_y, width, height,
961                dst_x, dst_y);
962
963     // draw foreground
964     BlitBitmapMasked(src_bitmap, dst_bitmap, src_x, src_y, width, height,
965                      dst_x, dst_y);
966   }
967   else
968     BlitBitmap(src_bitmap, dst_bitmap, src_x, src_y, width, height,
969                dst_x, dst_y);
970 }
971
972 void BlitTexture(Bitmap *bitmap,
973                 int src_x, int src_y, int width, int height,
974                 int dst_x, int dst_y)
975 {
976   if (bitmap == NULL)
977     return;
978
979   SDLBlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y,
980                  BLIT_OPAQUE);
981 }
982
983 void BlitTextureMasked(Bitmap *bitmap,
984                        int src_x, int src_y, int width, int height,
985                        int dst_x, int dst_y)
986 {
987   if (bitmap == NULL)
988     return;
989
990   SDLBlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y,
991                  BLIT_MASKED);
992 }
993
994 void BlitToScreen(Bitmap *bitmap,
995                   int src_x, int src_y, int width, int height,
996                   int dst_x, int dst_y)
997 {
998   if (bitmap == NULL)
999     return;
1000
1001   if (video.screen_rendering_mode == SPECIAL_RENDERING_BITMAP)
1002     BlitBitmap(bitmap, gfx.final_screen_bitmap, src_x, src_y,
1003                width, height, dst_x, dst_y);
1004   else
1005     BlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y);
1006 }
1007
1008 void BlitToScreenMasked(Bitmap *bitmap,
1009                         int src_x, int src_y, int width, int height,
1010                         int dst_x, int dst_y)
1011 {
1012   if (bitmap == NULL)
1013     return;
1014
1015   if (video.screen_rendering_mode == SPECIAL_RENDERING_BITMAP)
1016     BlitBitmapMasked(bitmap, gfx.final_screen_bitmap, src_x, src_y,
1017                      width, height, dst_x, dst_y);
1018   else
1019     BlitTextureMasked(bitmap, src_x, src_y, width, height, dst_x, dst_y);
1020 }
1021
1022 void DrawSimpleBlackLine(Bitmap *bitmap, int from_x, int from_y,
1023                          int to_x, int to_y)
1024 {
1025   SDLDrawSimpleLine(bitmap, from_x, from_y, to_x, to_y, BLACK_PIXEL);
1026 }
1027
1028 void DrawSimpleWhiteLine(Bitmap *bitmap, int from_x, int from_y,
1029                          int to_x, int to_y)
1030 {
1031   SDLDrawSimpleLine(bitmap, from_x, from_y, to_x, to_y, WHITE_PIXEL);
1032 }
1033
1034 static void DrawLine(Bitmap *bitmap, int from_x, int from_y,
1035                      int to_x, int to_y, Pixel pixel, int line_width)
1036 {
1037   int x, y;
1038
1039   if (program.headless)
1040     return;
1041
1042   for (x = 0; x < line_width; x++)
1043   {
1044     for (y = 0; y < line_width; y++)
1045     {
1046       int dx = x - line_width / 2;
1047       int dy = y - line_width / 2;
1048
1049       if ((x == 0 && y == 0) ||
1050           (x == 0 && y == line_width - 1) ||
1051           (x == line_width - 1 && y == 0) ||
1052           (x == line_width - 1 && y == line_width - 1))
1053         continue;
1054
1055       SDLDrawLine(bitmap,
1056                   from_x + dx, from_y + dy, to_x + dx, to_y + dy, pixel);
1057     }
1058   }
1059 }
1060
1061 void DrawLines(Bitmap *bitmap, struct XY *points, int num_points, Pixel pixel)
1062 {
1063   int line_width = 4;
1064   int i;
1065
1066   for (i = 0; i < num_points - 1; i++)
1067     DrawLine(bitmap, points[i].x, points[i].y,
1068              points[i + 1].x, points[i + 1].y, pixel, line_width);
1069
1070   /*
1071   SDLDrawLines(bitmap->surface, points, num_points, pixel);
1072   */
1073 }
1074
1075 Pixel GetPixel(Bitmap *bitmap, int x, int y)
1076 {
1077   if (program.headless)
1078     return BLACK_PIXEL;
1079
1080   if (x < 0 || x >= bitmap->width ||
1081       y < 0 || y >= bitmap->height)
1082     return BLACK_PIXEL;
1083
1084   return SDLGetPixel(bitmap, x, y);
1085 }
1086
1087 Pixel GetPixelFromRGB(Bitmap *bitmap, unsigned int color_r,
1088                       unsigned int color_g, unsigned int color_b)
1089 {
1090   if (program.headless)
1091     return BLACK_PIXEL;
1092
1093   return SDL_MapRGB(bitmap->surface->format, color_r, color_g, color_b);
1094 }
1095
1096 Pixel GetPixelFromRGBcompact(Bitmap *bitmap, unsigned int color)
1097 {
1098   unsigned int color_r = (color >> 16) & 0xff;
1099   unsigned int color_g = (color >>  8) & 0xff;
1100   unsigned int color_b = (color >>  0) & 0xff;
1101
1102   return GetPixelFromRGB(bitmap, color_r, color_g, color_b);
1103 }
1104
1105 void KeyboardAutoRepeatOn(void)
1106 {
1107   keyrepeat_status = TRUE;
1108 }
1109
1110 void KeyboardAutoRepeatOff(void)
1111 {
1112   keyrepeat_status = FALSE;
1113 }
1114
1115 boolean SetVideoMode(boolean fullscreen)
1116 {
1117   return SDLSetVideoMode(fullscreen);
1118 }
1119
1120 void SetVideoFrameDelay(unsigned int frame_delay_value)
1121 {
1122   video.frame_delay_value = frame_delay_value;
1123 }
1124
1125 unsigned int GetVideoFrameDelay(void)
1126 {
1127   return video.frame_delay_value;
1128 }
1129
1130 boolean ChangeVideoModeIfNeeded(boolean fullscreen)
1131 {
1132   if ((fullscreen && !video.fullscreen_enabled && video.fullscreen_available)||
1133       (!fullscreen && video.fullscreen_enabled))
1134     fullscreen = SetVideoMode(fullscreen);
1135
1136   return fullscreen;
1137 }
1138
1139 Bitmap *LoadImage(char *filename)
1140 {
1141   Bitmap *new_bitmap;
1142
1143   new_bitmap = SDLLoadImage(filename);
1144
1145   if (new_bitmap)
1146     new_bitmap->source_filename = getStringCopy(filename);
1147
1148   return new_bitmap;
1149 }
1150
1151 Bitmap *LoadCustomImage(char *basename)
1152 {
1153   char *filename = getCustomImageFilename(basename);
1154   Bitmap *new_bitmap;
1155
1156   if (filename == NULL)
1157     Error(ERR_EXIT, "LoadCustomImage(): cannot find file '%s'", basename);
1158
1159   if ((new_bitmap = LoadImage(filename)) == NULL)
1160     Error(ERR_EXIT, "LoadImage('%s') failed: %s", basename, GetError());
1161
1162   return new_bitmap;
1163 }
1164
1165 void ReloadCustomImage(Bitmap *bitmap, char *basename)
1166 {
1167   char *filename = getCustomImageFilename(basename);
1168   Bitmap *new_bitmap;
1169
1170   if (filename == NULL)         // (should never happen)
1171   {
1172     Error(ERR_WARN, "ReloadCustomImage(): cannot find file '%s'", basename);
1173     return;
1174   }
1175
1176   if (strEqual(filename, bitmap->source_filename))
1177   {
1178     // The old and new image are the same (have the same filename and path).
1179     // This usually means that this image does not exist in this graphic set
1180     // and a fallback to the existing image is done.
1181
1182     return;
1183   }
1184
1185   if ((new_bitmap = LoadImage(filename)) == NULL)
1186   {
1187     Error(ERR_WARN, "LoadImage('%s') failed: %s", basename, GetError());
1188     return;
1189   }
1190
1191   if (bitmap->width != new_bitmap->width ||
1192       bitmap->height != new_bitmap->height)
1193   {
1194     Error(ERR_WARN, "ReloadCustomImage: new image '%s' has wrong dimensions",
1195           filename);
1196     FreeBitmap(new_bitmap);
1197     return;
1198   }
1199
1200   TransferBitmapPointers(new_bitmap, bitmap);
1201   free(new_bitmap);
1202 }
1203
1204 static Bitmap *ZoomBitmap(Bitmap *src_bitmap, int zoom_width, int zoom_height)
1205 {
1206   return SDLZoomBitmap(src_bitmap, zoom_width, zoom_height);
1207 }
1208
1209 void ReCreateGameTileSizeBitmap(Bitmap **bitmaps)
1210 {
1211   if (bitmaps[IMG_BITMAP_CUSTOM])
1212   {
1213     FreeBitmap(bitmaps[IMG_BITMAP_CUSTOM]);
1214
1215     bitmaps[IMG_BITMAP_CUSTOM] = NULL;
1216   }
1217
1218   if (gfx.game_tile_size == gfx.standard_tile_size)
1219   {
1220     bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_STANDARD];
1221
1222     return;
1223   }
1224
1225   Bitmap *bitmap = bitmaps[IMG_BITMAP_STANDARD];
1226   int width  = bitmap->width  * gfx.game_tile_size / gfx.standard_tile_size;;
1227   int height = bitmap->height * gfx.game_tile_size / gfx.standard_tile_size;;
1228
1229   Bitmap *bitmap_new = ZoomBitmap(bitmap, width, height);
1230
1231   bitmaps[IMG_BITMAP_CUSTOM] = bitmap_new;
1232   bitmaps[IMG_BITMAP_GAME]   = bitmap_new;
1233 }
1234
1235 static void CreateScaledBitmaps(Bitmap **bitmaps, int zoom_factor,
1236                                 int tile_size, boolean create_small_bitmaps)
1237 {
1238   Bitmap *old_bitmap = bitmaps[IMG_BITMAP_STANDARD];
1239   Bitmap *tmp_bitmap_final = NULL;
1240   Bitmap *tmp_bitmap_0 = NULL;
1241   Bitmap *tmp_bitmap_1 = NULL;
1242   Bitmap *tmp_bitmap_2 = NULL;
1243   Bitmap *tmp_bitmap_4 = NULL;
1244   Bitmap *tmp_bitmap_8 = NULL;
1245   Bitmap *tmp_bitmap_16 = NULL;
1246   Bitmap *tmp_bitmap_32 = NULL;
1247   int width_final, height_final;
1248   int width_0, height_0;
1249   int width_1, height_1;
1250   int width_2, height_2;
1251   int width_4, height_4;
1252   int width_8, height_8;
1253   int width_16, height_16;
1254   int width_32, height_32;
1255   int old_width, old_height;
1256   int i;
1257
1258   print_timestamp_init("CreateScaledBitmaps");
1259
1260   old_width  = old_bitmap->width;
1261   old_height = old_bitmap->height;
1262
1263   // calculate new image dimensions for final image size
1264   width_final  = old_width  * zoom_factor;
1265   height_final = old_height * zoom_factor;
1266
1267   // get image with final size (this might require scaling up)
1268   // ("final" size may result in non-standard tile size image)
1269   if (zoom_factor != 1)
1270     tmp_bitmap_final = ZoomBitmap(old_bitmap, width_final, height_final);
1271   else
1272     tmp_bitmap_final = old_bitmap;
1273
1274   UPDATE_BUSY_STATE();
1275
1276   width_0  = width_1  = width_final;
1277   height_0 = height_1 = height_final;
1278
1279   tmp_bitmap_0 = tmp_bitmap_1 = tmp_bitmap_final;
1280
1281   if (create_small_bitmaps)
1282   {
1283     // check if we have a non-gameplay tile size image
1284     if (tile_size != gfx.game_tile_size)
1285     {
1286       // get image with gameplay tile size
1287       width_0  = width_final  * gfx.game_tile_size / tile_size;
1288       height_0 = height_final * gfx.game_tile_size / tile_size;
1289
1290       if (width_0 == old_width)
1291         tmp_bitmap_0 = old_bitmap;
1292       else if (width_0 == width_final)
1293         tmp_bitmap_0 = tmp_bitmap_final;
1294       else
1295         tmp_bitmap_0 = ZoomBitmap(old_bitmap, width_0, height_0);
1296
1297       UPDATE_BUSY_STATE();
1298     }
1299
1300     // check if we have a non-standard tile size image
1301     if (tile_size != gfx.standard_tile_size)
1302     {
1303       // get image with standard tile size
1304       width_1  = width_final  * gfx.standard_tile_size / tile_size;
1305       height_1 = height_final * gfx.standard_tile_size / tile_size;
1306
1307       if (width_1 == old_width)
1308         tmp_bitmap_1 = old_bitmap;
1309       else if (width_1 == width_final)
1310         tmp_bitmap_1 = tmp_bitmap_final;
1311       else if (width_1 == width_0)
1312         tmp_bitmap_1 = tmp_bitmap_0;
1313       else
1314         tmp_bitmap_1 = ZoomBitmap(old_bitmap, width_1, height_1);
1315
1316       UPDATE_BUSY_STATE();
1317     }
1318
1319     // calculate new image dimensions for small images
1320     width_2  = width_1  / 2;
1321     height_2 = height_1 / 2;
1322     width_4  = width_1  / 4;
1323     height_4 = height_1 / 4;
1324     width_8  = width_1  / 8;
1325     height_8 = height_1 / 8;
1326     width_16  = width_1  / 16;
1327     height_16 = height_1 / 16;
1328     width_32  = width_1  / 32;
1329     height_32 = height_1 / 32;
1330
1331     // get image with 1/2 of normal size (for use in the level editor)
1332     if (width_2 == old_width)
1333       tmp_bitmap_2 = old_bitmap;
1334     else
1335       tmp_bitmap_2 = ZoomBitmap(tmp_bitmap_1, width_2, height_2);
1336
1337     UPDATE_BUSY_STATE();
1338
1339     // get image with 1/4 of normal size (for use in the level editor)
1340     if (width_4 == old_width)
1341       tmp_bitmap_4 = old_bitmap;
1342     else
1343       tmp_bitmap_4 = ZoomBitmap(tmp_bitmap_2, width_4, height_4);
1344
1345     UPDATE_BUSY_STATE();
1346
1347     // get image with 1/8 of normal size (for use on the preview screen)
1348     if (width_8 == old_width)
1349       tmp_bitmap_8 = old_bitmap;
1350     else
1351       tmp_bitmap_8 = ZoomBitmap(tmp_bitmap_4, width_8, height_8);
1352
1353     UPDATE_BUSY_STATE();
1354
1355     // get image with 1/16 of normal size (for use on the preview screen)
1356     if (width_16 == old_width)
1357       tmp_bitmap_16 = old_bitmap;
1358     else
1359       tmp_bitmap_16 = ZoomBitmap(tmp_bitmap_8, width_16, height_16);
1360
1361     UPDATE_BUSY_STATE();
1362
1363     // get image with 1/32 of normal size (for use on the preview screen)
1364     if (width_32 == old_width)
1365       tmp_bitmap_32 = old_bitmap;
1366     else
1367       tmp_bitmap_32 = ZoomBitmap(tmp_bitmap_16, width_32, height_32);
1368
1369     UPDATE_BUSY_STATE();
1370
1371     bitmaps[IMG_BITMAP_32x32] = tmp_bitmap_1;
1372     bitmaps[IMG_BITMAP_16x16] = tmp_bitmap_2;
1373     bitmaps[IMG_BITMAP_8x8]   = tmp_bitmap_4;
1374     bitmaps[IMG_BITMAP_4x4]   = tmp_bitmap_8;
1375     bitmaps[IMG_BITMAP_2x2]   = tmp_bitmap_16;
1376     bitmaps[IMG_BITMAP_1x1]   = tmp_bitmap_32;
1377
1378     if (width_0 != width_1)
1379       bitmaps[IMG_BITMAP_CUSTOM] = tmp_bitmap_0;
1380
1381     if (bitmaps[IMG_BITMAP_CUSTOM])
1382       bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_CUSTOM];
1383     else
1384       bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_STANDARD];
1385
1386     boolean free_old_bitmap = TRUE;
1387
1388     for (i = 0; i < NUM_IMG_BITMAPS; i++)
1389       if (bitmaps[i] == old_bitmap)
1390         free_old_bitmap = FALSE;
1391
1392     if (free_old_bitmap)
1393     {
1394       // copy image filename from old to new standard sized bitmap
1395       bitmaps[IMG_BITMAP_STANDARD]->source_filename =
1396         getStringCopy(old_bitmap->source_filename);
1397
1398       FreeBitmap(old_bitmap);
1399     }
1400   }
1401   else
1402   {
1403     bitmaps[IMG_BITMAP_32x32] = tmp_bitmap_1;
1404   }
1405
1406   UPDATE_BUSY_STATE();
1407
1408   print_timestamp_done("CreateScaledBitmaps");
1409 }
1410
1411 void CreateBitmapWithSmallBitmaps(Bitmap **bitmaps, int zoom_factor,
1412                                   int tile_size)
1413 {
1414   CreateScaledBitmaps(bitmaps, zoom_factor, tile_size, TRUE);
1415 }
1416
1417 void CreateBitmapTextures(Bitmap **bitmaps)
1418 {
1419   SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]);
1420 }
1421
1422 void FreeBitmapTextures(Bitmap **bitmaps)
1423 {
1424   SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]);
1425 }
1426
1427 void ScaleBitmap(Bitmap **bitmaps, int zoom_factor)
1428 {
1429   CreateScaledBitmaps(bitmaps, zoom_factor, 0, FALSE);
1430 }
1431
1432
1433 // ----------------------------------------------------------------------------
1434 // mouse pointer functions
1435 // ----------------------------------------------------------------------------
1436
1437 #define USE_ONE_PIXEL_PLAYFIELD_MOUSEPOINTER            0
1438
1439 // XPM image definitions
1440 static const char *cursor_image_none[] =
1441 {
1442   // width height num_colors chars_per_pixel
1443   "    16    16        3            1",
1444
1445   // colors
1446   "X c #000000",
1447   ". c #ffffff",
1448   "  c None",
1449
1450   // pixels
1451   "                ",
1452   "                ",
1453   "                ",
1454   "                ",
1455   "                ",
1456   "                ",
1457   "                ",
1458   "                ",
1459   "                ",
1460   "                ",
1461   "                ",
1462   "                ",
1463   "                ",
1464   "                ",
1465   "                ",
1466   "                ",
1467
1468   // hot spot
1469   "0,0"
1470 };
1471
1472 #if USE_ONE_PIXEL_PLAYFIELD_MOUSEPOINTER
1473 static const char *cursor_image_dot[] =
1474 {
1475   // width height num_colors chars_per_pixel
1476   "    16    16        3            1",
1477
1478   // colors
1479   "X c #000000",
1480   ". c #ffffff",
1481   "  c None",
1482
1483   // pixels
1484   " X              ",
1485   "X.X             ",
1486   " X              ",
1487   "                ",
1488   "                ",
1489   "                ",
1490   "                ",
1491   "                ",
1492   "                ",
1493   "                ",
1494   "                ",
1495   "                ",
1496   "                ",
1497   "                ",
1498   "                ",
1499   "                ",
1500
1501   // hot spot
1502   "1,1"
1503 };
1504 static const char **cursor_image_playfield = cursor_image_dot;
1505 #else
1506 // some people complained about a "white dot" on the screen and thought it
1507 // was a graphical error... OK, let's just remove the whole pointer :-)
1508 static const char **cursor_image_playfield = cursor_image_none;
1509 #endif
1510
1511 static const int cursor_bit_order = BIT_ORDER_MSB;
1512
1513 static struct MouseCursorInfo *get_cursor_from_image(const char **image)
1514 {
1515   struct MouseCursorInfo *cursor;
1516   boolean bit_order_msb = (cursor_bit_order == BIT_ORDER_MSB);
1517   int header_lines = 4;
1518   int x, y, i;
1519
1520   cursor = checked_calloc(sizeof(struct MouseCursorInfo));
1521
1522   sscanf(image[0], " %d %d ", &cursor->width, &cursor->height);
1523
1524   i = -1;
1525   for (y = 0; y < cursor->width; y++)
1526   {
1527     for (x = 0; x < cursor->height; x++)
1528     {
1529       int bit_nr = x % 8;
1530       int bit_mask = 0x01 << (bit_order_msb ? 7 - bit_nr : bit_nr );
1531
1532       if (bit_nr == 0)
1533       {
1534         i++;
1535         cursor->data[i] = cursor->mask[i] = 0;
1536       }
1537
1538       switch (image[header_lines + y][x])
1539       {
1540         case 'X':
1541           cursor->data[i] |= bit_mask;
1542           cursor->mask[i] |= bit_mask;
1543           break;
1544
1545         case '.':
1546           cursor->mask[i] |= bit_mask;
1547           break;
1548
1549         case ' ':
1550           break;
1551       }
1552     }
1553   }
1554
1555   sscanf(image[header_lines + y], "%d,%d", &cursor->hot_x, &cursor->hot_y);
1556
1557   return cursor;
1558 }
1559
1560 void SetMouseCursor(int mode)
1561 {
1562   static struct MouseCursorInfo *cursor_none = NULL;
1563   static struct MouseCursorInfo *cursor_playfield = NULL;
1564   struct MouseCursorInfo *cursor_new;
1565   int mode_final = mode;
1566
1567   if (cursor_none == NULL)
1568     cursor_none = get_cursor_from_image(cursor_image_none);
1569
1570   if (cursor_playfield == NULL)
1571     cursor_playfield = get_cursor_from_image(cursor_image_playfield);
1572
1573   if (gfx.cursor_mode_override != CURSOR_UNDEFINED)
1574     mode_final = gfx.cursor_mode_override;
1575
1576   cursor_new = (mode_final == CURSOR_DEFAULT   ? NULL :
1577                 mode_final == CURSOR_NONE      ? cursor_none :
1578                 mode_final == CURSOR_PLAYFIELD ? cursor_playfield : NULL);
1579
1580   SDLSetMouseCursor(cursor_new);
1581
1582   gfx.cursor_mode = mode;
1583   gfx.cursor_mode_final = mode_final;
1584 }
1585
1586 void UpdateRawMousePosition(int mouse_x, int mouse_y)
1587 {
1588   // mouse events do not contain logical screen size corrections yet
1589   SDLCorrectRawMousePosition(&mouse_x, &mouse_y);
1590
1591   mouse_x -= video.screen_xoffset;
1592   mouse_y -= video.screen_yoffset;
1593
1594   gfx.mouse_x = mouse_x;
1595   gfx.mouse_y = mouse_y;
1596 }
1597
1598 void UpdateMousePosition(void)
1599 {
1600   int mouse_x, mouse_y;
1601
1602   SDL_PumpEvents();
1603   SDL_GetMouseState(&mouse_x, &mouse_y);
1604
1605   UpdateRawMousePosition(mouse_x, mouse_y);
1606 }
1607
1608
1609 // ============================================================================
1610 // audio functions
1611 // ============================================================================
1612
1613 void OpenAudio(void)
1614 {
1615   // always start with reliable default values
1616   audio.sound_available = FALSE;
1617   audio.music_available = FALSE;
1618   audio.loops_available = FALSE;
1619
1620   audio.sound_enabled = FALSE;
1621   audio.sound_deactivated = FALSE;
1622
1623   audio.mixer_pipe[0] = audio.mixer_pipe[1] = 0;
1624   audio.mixer_pid = 0;
1625   audio.device_name = NULL;
1626   audio.device_fd = -1;
1627
1628   audio.num_channels = 0;
1629   audio.music_channel = 0;
1630   audio.first_sound_channel = 0;
1631
1632   SDLOpenAudio();
1633 }
1634
1635 void CloseAudio(void)
1636 {
1637   SDLCloseAudio();
1638
1639   audio.sound_enabled = FALSE;
1640 }
1641
1642 void SetAudioMode(boolean enabled)
1643 {
1644   if (!audio.sound_available)
1645     return;
1646
1647   audio.sound_enabled = enabled;
1648 }
1649
1650
1651 // ============================================================================
1652 // event functions
1653 // ============================================================================
1654
1655 void InitEventFilter(EventFilter filter_function)
1656 {
1657   SDL_SetEventFilter(filter_function, NULL);
1658 }
1659
1660 boolean PendingEvent(void)
1661 {
1662   return (SDL_PollEvent(NULL) ? TRUE : FALSE);
1663 }
1664
1665 void WaitEvent(Event *event)
1666 {
1667   SDLWaitEvent(event);
1668 }
1669
1670 void PeekEvent(Event *event)
1671 {
1672   SDL_PeepEvents(event, 1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT);
1673 }
1674
1675 void PumpEvents(void)
1676 {
1677   SDL_PumpEvents();
1678 }
1679
1680 void CheckQuitEvent(void)
1681 {
1682   if (SDL_QuitRequested())
1683     program.exit_function(0);
1684 }
1685
1686 Key GetEventKey(KeyEvent *event, boolean with_modifiers)
1687 {
1688   // key up/down events in SDL2 do not return text characters anymore
1689   return event->keysym.sym;
1690 }
1691
1692 KeyMod HandleKeyModState(Key key, int key_status)
1693 {
1694   static KeyMod current_modifiers = KMOD_None;
1695
1696   if (key != KSYM_UNDEFINED)    // new key => check for modifier key change
1697   {
1698     KeyMod new_modifier = KMOD_None;
1699
1700     switch (key)
1701     {
1702       case KSYM_Shift_L:
1703         new_modifier = KMOD_Shift_L;
1704         break;
1705       case KSYM_Shift_R:
1706         new_modifier = KMOD_Shift_R;
1707         break;
1708       case KSYM_Control_L:
1709         new_modifier = KMOD_Control_L;
1710         break;
1711       case KSYM_Control_R:
1712         new_modifier = KMOD_Control_R;
1713         break;
1714       case KSYM_Meta_L:
1715         new_modifier = KMOD_Meta_L;
1716         break;
1717       case KSYM_Meta_R:
1718         new_modifier = KMOD_Meta_R;
1719         break;
1720       case KSYM_Alt_L:
1721         new_modifier = KMOD_Alt_L;
1722         break;
1723       case KSYM_Alt_R:
1724         new_modifier = KMOD_Alt_R;
1725         break;
1726       default:
1727         break;
1728     }
1729
1730     if (key_status == KEY_PRESSED)
1731       current_modifiers |= new_modifier;
1732     else
1733       current_modifiers &= ~new_modifier;
1734   }
1735
1736   return current_modifiers;
1737 }
1738
1739 KeyMod GetKeyModState(void)
1740 {
1741   return (KeyMod)SDL_GetModState();
1742 }
1743
1744 KeyMod GetKeyModStateFromEvents(void)
1745 {
1746   /* always use key modifier state as tracked from key events (this is needed
1747      if the modifier key event was injected into the event queue, but the key
1748      was not really pressed on keyboard -- SDL_GetModState() seems to directly
1749      query the keys as held pressed on the keyboard) -- this case is currently
1750      only used to filter out clipboard insert events from "True X-Mouse" tool */
1751
1752   return HandleKeyModState(KSYM_UNDEFINED, 0);
1753 }
1754
1755 void StartTextInput(int x, int y, int width, int height)
1756 {
1757   textinput_status = TRUE;
1758
1759 #if defined(HAS_SCREEN_KEYBOARD)
1760   SDL_StartTextInput();
1761
1762   if (y + height > SCREEN_KEYBOARD_POS(video.height))
1763   {
1764     video.shifted_up_pos = y + height - SCREEN_KEYBOARD_POS(video.height);
1765     video.shifted_up_delay = SDL_GetTicks();
1766     video.shifted_up = TRUE;
1767   }
1768 #endif
1769 }
1770
1771 void StopTextInput(void)
1772 {
1773   textinput_status = FALSE;
1774
1775 #if defined(HAS_SCREEN_KEYBOARD)
1776   SDL_StopTextInput();
1777
1778   if (video.shifted_up)
1779   {
1780     video.shifted_up_pos = 0;
1781     video.shifted_up_delay = SDL_GetTicks();
1782     video.shifted_up = FALSE;
1783   }
1784 #endif
1785 }
1786
1787 void PushUserEvent(int code, int value1, int value2)
1788 {
1789   UserEvent event;
1790
1791   SDL_memset(&event, 0, sizeof(event));
1792
1793   event.type = EVENT_USER;
1794   event.code = code;
1795   event.value1 = value1;
1796   event.value2 = value2;
1797
1798   SDL_PushEvent((SDL_Event *)&event);
1799 }
1800
1801
1802 // ============================================================================
1803 // joystick functions
1804 // ============================================================================
1805
1806 void InitJoysticks(void)
1807 {
1808   int i;
1809
1810 #if defined(NO_JOYSTICK)
1811   return;       // joysticks generally deactivated by compile-time directive
1812 #endif
1813
1814   // always start with reliable default values
1815   joystick.status = JOYSTICK_NOT_AVAILABLE;
1816   for (i = 0; i < MAX_PLAYERS; i++)
1817     joystick.nr[i] = -1;                // no joystick configured
1818
1819   SDLInitJoysticks();
1820 }
1821
1822 boolean ReadJoystick(int nr, int *x, int *y, boolean *b1, boolean *b2)
1823 {
1824   return SDLReadJoystick(nr, x, y, b1, b2);
1825 }
1826
1827 boolean CheckJoystickOpened(int nr)
1828 {
1829   return SDLCheckJoystickOpened(nr);
1830 }
1831
1832 void ClearJoystickState(void)
1833 {
1834   SDLClearJoystickState();
1835 }