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