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