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