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