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