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