added function to add top tree node with back link
[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)(void))
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 (DrawingDeactivated(x, y, width, height))
910     return;
911
912   if (!InClippedRectangle(bitmap, &x, &y, &width, &height, TRUE))
913     return;
914
915   sysFillRectangle(bitmap, x, y, width, height, color);
916 }
917
918 void ClearRectangle(Bitmap *bitmap, int x, int y, int width, int height)
919 {
920   FillRectangle(bitmap, x, y, width, height, BLACK_PIXEL);
921 }
922
923 void ClearRectangleOnBackground(Bitmap *bitmap, int x, int y,
924                                 int width, int height)
925 {
926   if (DrawingOnBackground(x, y))
927     BlitBitmap(gfx.background_bitmap, bitmap, x, y, width, height, x, y);
928   else
929     ClearRectangle(bitmap, x, y, width, height);
930 }
931
932 void BlitBitmapMasked(Bitmap *src_bitmap, Bitmap *dst_bitmap,
933                       int src_x, int src_y, int width, int height,
934                       int dst_x, int dst_y)
935 {
936   if (DrawingDeactivated(dst_x, dst_y, width, height))
937     return;
938
939   sysCopyArea(src_bitmap, dst_bitmap, src_x, src_y, width, height,
940               dst_x, dst_y, BLIT_MASKED);
941 }
942
943 void BlitBitmapOnBackground(Bitmap *src_bitmap, Bitmap *dst_bitmap,
944                             int src_x, int src_y, int width, int height,
945                             int dst_x, int dst_y)
946 {
947   if (DrawingOnBackground(dst_x, dst_y))
948   {
949     // draw background
950     BlitBitmap(gfx.background_bitmap, dst_bitmap, dst_x, dst_y, width, height,
951                dst_x, dst_y);
952
953     // draw foreground
954     BlitBitmapMasked(src_bitmap, dst_bitmap, src_x, src_y, width, height,
955                      dst_x, dst_y);
956   }
957   else
958     BlitBitmap(src_bitmap, dst_bitmap, src_x, src_y, width, height,
959                dst_x, dst_y);
960 }
961
962 void BlitTexture(Bitmap *bitmap,
963                 int src_x, int src_y, int width, int height,
964                 int dst_x, int dst_y)
965 {
966   if (bitmap == NULL)
967     return;
968
969   SDLBlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y,
970                  BLIT_OPAQUE);
971 }
972
973 void BlitTextureMasked(Bitmap *bitmap,
974                        int src_x, int src_y, int width, int height,
975                        int dst_x, int dst_y)
976 {
977   if (bitmap == NULL)
978     return;
979
980   SDLBlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y,
981                  BLIT_MASKED);
982 }
983
984 void BlitToScreen(Bitmap *bitmap,
985                   int src_x, int src_y, int width, int height,
986                   int dst_x, int dst_y)
987 {
988   if (bitmap == NULL)
989     return;
990
991   if (video.screen_rendering_mode == SPECIAL_RENDERING_BITMAP)
992     BlitBitmap(bitmap, gfx.final_screen_bitmap, src_x, src_y,
993                width, height, dst_x, dst_y);
994   else
995     BlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y);
996 }
997
998 void BlitToScreenMasked(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   if (video.screen_rendering_mode == SPECIAL_RENDERING_BITMAP)
1006     BlitBitmapMasked(bitmap, gfx.final_screen_bitmap, src_x, src_y,
1007                      width, height, dst_x, dst_y);
1008   else
1009     BlitTextureMasked(bitmap, src_x, src_y, width, height, dst_x, dst_y);
1010 }
1011
1012 void DrawSimpleBlackLine(Bitmap *bitmap, int from_x, int from_y,
1013                          int to_x, int to_y)
1014 {
1015   SDLDrawSimpleLine(bitmap, from_x, from_y, to_x, to_y, BLACK_PIXEL);
1016 }
1017
1018 void DrawSimpleWhiteLine(Bitmap *bitmap, int from_x, int from_y,
1019                          int to_x, int to_y)
1020 {
1021   SDLDrawSimpleLine(bitmap, from_x, from_y, to_x, to_y, WHITE_PIXEL);
1022 }
1023
1024 static void DrawLine(Bitmap *bitmap, int from_x, int from_y,
1025                      int to_x, int to_y, Pixel pixel, int line_width)
1026 {
1027   int x, y;
1028
1029   if (program.headless)
1030     return;
1031
1032   for (x = 0; x < line_width; x++)
1033   {
1034     for (y = 0; y < line_width; y++)
1035     {
1036       int dx = x - line_width / 2;
1037       int dy = y - line_width / 2;
1038
1039       if ((x == 0 && y == 0) ||
1040           (x == 0 && y == line_width - 1) ||
1041           (x == line_width - 1 && y == 0) ||
1042           (x == line_width - 1 && y == line_width - 1))
1043         continue;
1044
1045       SDLDrawLine(bitmap,
1046                   from_x + dx, from_y + dy, to_x + dx, to_y + dy, pixel);
1047     }
1048   }
1049 }
1050
1051 void DrawLines(Bitmap *bitmap, struct XY *points, int num_points, Pixel pixel)
1052 {
1053   int line_width = 4;
1054   int i;
1055
1056   for (i = 0; i < num_points - 1; i++)
1057     DrawLine(bitmap, points[i].x, points[i].y,
1058              points[i + 1].x, points[i + 1].y, pixel, line_width);
1059
1060   /*
1061   SDLDrawLines(bitmap->surface, points, num_points, pixel);
1062   */
1063 }
1064
1065 Pixel GetPixel(Bitmap *bitmap, int x, int y)
1066 {
1067   if (program.headless)
1068     return BLACK_PIXEL;
1069
1070   if (x < 0 || x >= bitmap->width ||
1071       y < 0 || y >= bitmap->height)
1072     return BLACK_PIXEL;
1073
1074   return SDLGetPixel(bitmap, x, y);
1075 }
1076
1077 Pixel GetPixelFromRGB(Bitmap *bitmap, unsigned int color_r,
1078                       unsigned int color_g, unsigned int color_b)
1079 {
1080   if (program.headless)
1081     return BLACK_PIXEL;
1082
1083   return SDL_MapRGB(bitmap->surface->format, color_r, color_g, color_b);
1084 }
1085
1086 Pixel GetPixelFromRGBcompact(Bitmap *bitmap, unsigned int color)
1087 {
1088   unsigned int color_r = (color >> 16) & 0xff;
1089   unsigned int color_g = (color >>  8) & 0xff;
1090   unsigned int color_b = (color >>  0) & 0xff;
1091
1092   return GetPixelFromRGB(bitmap, color_r, color_g, color_b);
1093 }
1094
1095 void KeyboardAutoRepeatOn(void)
1096 {
1097   keyrepeat_status = TRUE;
1098 }
1099
1100 void KeyboardAutoRepeatOff(void)
1101 {
1102   keyrepeat_status = FALSE;
1103 }
1104
1105 boolean SetVideoMode(boolean fullscreen)
1106 {
1107   return SDLSetVideoMode(fullscreen);
1108 }
1109
1110 void SetVideoFrameDelay(unsigned int frame_delay_value)
1111 {
1112   video.frame_delay_value = frame_delay_value;
1113 }
1114
1115 unsigned int GetVideoFrameDelay(void)
1116 {
1117   return video.frame_delay_value;
1118 }
1119
1120 boolean ChangeVideoModeIfNeeded(boolean fullscreen)
1121 {
1122   if ((fullscreen && !video.fullscreen_enabled && video.fullscreen_available)||
1123       (!fullscreen && video.fullscreen_enabled))
1124     fullscreen = SetVideoMode(fullscreen);
1125
1126   return fullscreen;
1127 }
1128
1129 Bitmap *LoadImage(char *filename)
1130 {
1131   Bitmap *new_bitmap;
1132
1133   new_bitmap = SDLLoadImage(filename);
1134
1135   if (new_bitmap)
1136     new_bitmap->source_filename = getStringCopy(filename);
1137
1138   return new_bitmap;
1139 }
1140
1141 Bitmap *LoadCustomImage(char *basename)
1142 {
1143   char *filename = getCustomImageFilename(basename);
1144   Bitmap *new_bitmap;
1145
1146   if (filename == NULL)
1147     Fail("LoadCustomImage(): cannot find file '%s'", basename);
1148
1149   if ((new_bitmap = LoadImage(filename)) == NULL)
1150     Fail("LoadImage('%s') failed", basename);
1151
1152   return new_bitmap;
1153 }
1154
1155 void ReloadCustomImage(Bitmap *bitmap, char *basename)
1156 {
1157   char *filename = getCustomImageFilename(basename);
1158   Bitmap *new_bitmap;
1159
1160   if (filename == NULL)         // (should never happen)
1161   {
1162     Warn("ReloadCustomImage(): cannot find file '%s'", basename);
1163
1164     return;
1165   }
1166
1167   if (strEqual(filename, bitmap->source_filename))
1168   {
1169     // The old and new image are the same (have the same filename and path).
1170     // This usually means that this image does not exist in this graphic set
1171     // and a fallback to the existing image is done.
1172
1173     return;
1174   }
1175
1176   if ((new_bitmap = LoadImage(filename)) == NULL)
1177   {
1178     Warn("LoadImage('%s') failed", basename);
1179
1180     return;
1181   }
1182
1183   if (bitmap->width != new_bitmap->width ||
1184       bitmap->height != new_bitmap->height)
1185   {
1186     Warn("ReloadCustomImage: new image '%s' has wrong dimensions",
1187           filename);
1188
1189     FreeBitmap(new_bitmap);
1190
1191     return;
1192   }
1193
1194   TransferBitmapPointers(new_bitmap, bitmap);
1195   free(new_bitmap);
1196 }
1197
1198 Bitmap *ZoomBitmap(Bitmap *src_bitmap, int zoom_width, int zoom_height)
1199 {
1200   return SDLZoomBitmap(src_bitmap, zoom_width, zoom_height);
1201 }
1202
1203 void ReCreateGameTileSizeBitmap(Bitmap **bitmaps)
1204 {
1205   if (bitmaps[IMG_BITMAP_CUSTOM])
1206   {
1207     // check if original sized bitmap points to custom sized bitmap
1208     if (bitmaps[IMG_BITMAP_PTR_ORIGINAL] == bitmaps[IMG_BITMAP_CUSTOM])
1209     {
1210       SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_PTR_ORIGINAL]);
1211
1212       // keep pointer of previous custom size bitmap
1213       bitmaps[IMG_BITMAP_OTHER] = bitmaps[IMG_BITMAP_CUSTOM];
1214
1215       // set original bitmap pointer to scaled original bitmap of other size
1216       bitmaps[IMG_BITMAP_PTR_ORIGINAL] = bitmaps[IMG_BITMAP_OTHER];
1217
1218       SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_PTR_ORIGINAL]);
1219     }
1220     else
1221     {
1222       FreeBitmap(bitmaps[IMG_BITMAP_CUSTOM]);
1223     }
1224
1225     bitmaps[IMG_BITMAP_CUSTOM] = NULL;
1226   }
1227
1228   if (gfx.game_tile_size == gfx.standard_tile_size)
1229   {
1230     // set game bitmap pointer to standard sized bitmap (already existing)
1231     bitmaps[IMG_BITMAP_PTR_GAME] = bitmaps[IMG_BITMAP_STANDARD];
1232
1233     return;
1234   }
1235
1236   Bitmap *bitmap = bitmaps[IMG_BITMAP_STANDARD];
1237   int width  = bitmap->width  * gfx.game_tile_size / gfx.standard_tile_size;;
1238   int height = bitmap->height * gfx.game_tile_size / gfx.standard_tile_size;;
1239
1240   bitmaps[IMG_BITMAP_CUSTOM] = ZoomBitmap(bitmap, width, height);
1241
1242   // set game bitmap pointer to custom sized bitmap (newly created)
1243   bitmaps[IMG_BITMAP_PTR_GAME] = bitmaps[IMG_BITMAP_CUSTOM];
1244 }
1245
1246 static void CreateScaledBitmaps(Bitmap **bitmaps, int zoom_factor,
1247                                 int tile_size, boolean create_small_bitmaps)
1248 {
1249   Bitmap *old_bitmap = bitmaps[IMG_BITMAP_STANDARD];
1250   Bitmap *tmp_bitmap_final = NULL;
1251   Bitmap *tmp_bitmap_0 = NULL;
1252   Bitmap *tmp_bitmap_1 = NULL;
1253   Bitmap *tmp_bitmap_2 = NULL;
1254   Bitmap *tmp_bitmap_4 = NULL;
1255   Bitmap *tmp_bitmap_8 = NULL;
1256   Bitmap *tmp_bitmap_16 = NULL;
1257   Bitmap *tmp_bitmap_32 = NULL;
1258   int width_final, height_final;
1259   int width_0, height_0;
1260   int width_1, height_1;
1261   int width_2, height_2;
1262   int width_4, height_4;
1263   int width_8, height_8;
1264   int width_16, height_16;
1265   int width_32, height_32;
1266   int old_width, old_height;
1267   int i;
1268
1269   print_timestamp_init("CreateScaledBitmaps");
1270
1271   old_width  = old_bitmap->width;
1272   old_height = old_bitmap->height;
1273
1274   // calculate new image dimensions for final image size
1275   width_final  = old_width  * zoom_factor;
1276   height_final = old_height * zoom_factor;
1277
1278   // get image with final size (this might require scaling up)
1279   // ("final" size may result in non-standard tile size image)
1280   if (zoom_factor != 1)
1281     tmp_bitmap_final = ZoomBitmap(old_bitmap, width_final, height_final);
1282   else
1283     tmp_bitmap_final = old_bitmap;
1284
1285   UPDATE_BUSY_STATE();
1286
1287   width_0  = width_1  = width_final;
1288   height_0 = height_1 = height_final;
1289
1290   tmp_bitmap_0 = tmp_bitmap_1 = tmp_bitmap_final;
1291
1292   if (create_small_bitmaps)
1293   {
1294     // check if we have a non-gameplay tile size image
1295     if (tile_size != gfx.game_tile_size)
1296     {
1297       // get image with gameplay tile size
1298       width_0  = width_final  * gfx.game_tile_size / tile_size;
1299       height_0 = height_final * gfx.game_tile_size / tile_size;
1300
1301       if (width_0 == old_width)
1302         tmp_bitmap_0 = old_bitmap;
1303       else if (width_0 == width_final)
1304         tmp_bitmap_0 = tmp_bitmap_final;
1305       else
1306         tmp_bitmap_0 = ZoomBitmap(old_bitmap, width_0, height_0);
1307
1308       UPDATE_BUSY_STATE();
1309     }
1310
1311     // check if we have a non-standard tile size image
1312     if (tile_size != gfx.standard_tile_size)
1313     {
1314       // get image with standard tile size
1315       width_1  = width_final  * gfx.standard_tile_size / tile_size;
1316       height_1 = height_final * gfx.standard_tile_size / tile_size;
1317
1318       if (width_1 == old_width)
1319         tmp_bitmap_1 = old_bitmap;
1320       else if (width_1 == width_final)
1321         tmp_bitmap_1 = tmp_bitmap_final;
1322       else if (width_1 == width_0)
1323         tmp_bitmap_1 = tmp_bitmap_0;
1324       else
1325         tmp_bitmap_1 = ZoomBitmap(old_bitmap, width_1, height_1);
1326
1327       UPDATE_BUSY_STATE();
1328     }
1329
1330     // calculate new image dimensions for small images
1331     width_2  = width_1  / 2;
1332     height_2 = height_1 / 2;
1333     width_4  = width_1  / 4;
1334     height_4 = height_1 / 4;
1335     width_8  = width_1  / 8;
1336     height_8 = height_1 / 8;
1337     width_16  = width_1  / 16;
1338     height_16 = height_1 / 16;
1339     width_32  = width_1  / 32;
1340     height_32 = height_1 / 32;
1341
1342     // get image with 1/2 of normal size (for use in the level editor)
1343     if (width_2 == old_width)
1344       tmp_bitmap_2 = old_bitmap;
1345     else
1346       tmp_bitmap_2 = ZoomBitmap(tmp_bitmap_1, width_2, height_2);
1347
1348     UPDATE_BUSY_STATE();
1349
1350     // get image with 1/4 of normal size (for use in the level editor)
1351     if (width_4 == old_width)
1352       tmp_bitmap_4 = old_bitmap;
1353     else
1354       tmp_bitmap_4 = ZoomBitmap(tmp_bitmap_2, width_4, height_4);
1355
1356     UPDATE_BUSY_STATE();
1357
1358     // get image with 1/8 of normal size (for use on the preview screen)
1359     if (width_8 == old_width)
1360       tmp_bitmap_8 = old_bitmap;
1361     else
1362       tmp_bitmap_8 = ZoomBitmap(tmp_bitmap_4, width_8, height_8);
1363
1364     UPDATE_BUSY_STATE();
1365
1366     // get image with 1/16 of normal size (for use on the preview screen)
1367     if (width_16 == old_width)
1368       tmp_bitmap_16 = old_bitmap;
1369     else
1370       tmp_bitmap_16 = ZoomBitmap(tmp_bitmap_8, width_16, height_16);
1371
1372     UPDATE_BUSY_STATE();
1373
1374     // get image with 1/32 of normal size (for use on the preview screen)
1375     if (width_32 == old_width)
1376       tmp_bitmap_32 = old_bitmap;
1377     else
1378       tmp_bitmap_32 = ZoomBitmap(tmp_bitmap_16, width_32, height_32);
1379
1380     UPDATE_BUSY_STATE();
1381
1382     bitmaps[IMG_BITMAP_32x32] = tmp_bitmap_1;
1383     bitmaps[IMG_BITMAP_16x16] = tmp_bitmap_2;
1384     bitmaps[IMG_BITMAP_8x8]   = tmp_bitmap_4;
1385     bitmaps[IMG_BITMAP_4x4]   = tmp_bitmap_8;
1386     bitmaps[IMG_BITMAP_2x2]   = tmp_bitmap_16;
1387     bitmaps[IMG_BITMAP_1x1]   = tmp_bitmap_32;
1388
1389     if (width_0 != width_1)
1390       bitmaps[IMG_BITMAP_CUSTOM] = tmp_bitmap_0;
1391
1392     if (bitmaps[IMG_BITMAP_CUSTOM])
1393       bitmaps[IMG_BITMAP_PTR_GAME] = bitmaps[IMG_BITMAP_CUSTOM];
1394     else
1395       bitmaps[IMG_BITMAP_PTR_GAME] = bitmaps[IMG_BITMAP_STANDARD];
1396
1397     // store the "final" (up-scaled) original bitmap, if not already stored
1398
1399     int tmp_bitmap_final_nr = -1;
1400
1401     for (i = 0; i < NUM_IMG_BITMAPS; i++)
1402       if (bitmaps[i] == tmp_bitmap_final)
1403         tmp_bitmap_final_nr = i;
1404
1405     if (tmp_bitmap_final_nr == -1)      // scaled original bitmap not stored
1406     {
1407       // store pointer of scaled original bitmap (not used for any other size)
1408       bitmaps[IMG_BITMAP_OTHER] = tmp_bitmap_final;
1409
1410       // set original bitmap pointer to scaled original bitmap of other size
1411       bitmaps[IMG_BITMAP_PTR_ORIGINAL] = bitmaps[IMG_BITMAP_OTHER];
1412     }
1413     else
1414     {
1415       // set original bitmap pointer to corresponding sized bitmap
1416       bitmaps[IMG_BITMAP_PTR_ORIGINAL] = bitmaps[tmp_bitmap_final_nr];
1417     }
1418
1419     // free the "old" (unscaled) original bitmap, if not already stored
1420
1421     boolean free_old_bitmap = TRUE;
1422
1423     for (i = 0; i < NUM_IMG_BITMAPS; i++)
1424       if (bitmaps[i] == old_bitmap)
1425         free_old_bitmap = FALSE;
1426
1427     if (free_old_bitmap)
1428     {
1429       // copy image filename from old to new standard sized bitmap
1430       bitmaps[IMG_BITMAP_STANDARD]->source_filename =
1431         getStringCopy(old_bitmap->source_filename);
1432
1433       FreeBitmap(old_bitmap);
1434     }
1435   }
1436   else
1437   {
1438     bitmaps[IMG_BITMAP_32x32] = tmp_bitmap_1;
1439
1440     // set original bitmap pointer to corresponding sized bitmap
1441     bitmaps[IMG_BITMAP_PTR_ORIGINAL] = bitmaps[IMG_BITMAP_32x32];
1442
1443     if (old_bitmap != tmp_bitmap_1)
1444       FreeBitmap(old_bitmap);
1445   }
1446
1447   UPDATE_BUSY_STATE();
1448
1449   print_timestamp_done("CreateScaledBitmaps");
1450 }
1451
1452 void CreateBitmapWithSmallBitmaps(Bitmap **bitmaps, int zoom_factor,
1453                                   int tile_size)
1454 {
1455   CreateScaledBitmaps(bitmaps, zoom_factor, tile_size, TRUE);
1456 }
1457
1458 void CreateBitmapTextures(Bitmap **bitmaps)
1459 {
1460   if (bitmaps[IMG_BITMAP_PTR_ORIGINAL] != NULL)
1461     SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_PTR_ORIGINAL]);
1462   else
1463     SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]);
1464 }
1465
1466 void FreeBitmapTextures(Bitmap **bitmaps)
1467 {
1468   if (bitmaps[IMG_BITMAP_PTR_ORIGINAL] != NULL)
1469     SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_PTR_ORIGINAL]);
1470   else
1471     SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]);
1472 }
1473
1474 void ScaleBitmap(Bitmap **bitmaps, int zoom_factor)
1475 {
1476   CreateScaledBitmaps(bitmaps, zoom_factor, 0, FALSE);
1477 }
1478
1479
1480 // ----------------------------------------------------------------------------
1481 // mouse pointer functions
1482 // ----------------------------------------------------------------------------
1483
1484 #define USE_ONE_PIXEL_PLAYFIELD_MOUSEPOINTER            0
1485
1486 // XPM image definitions
1487 static const char *cursor_image_none[] =
1488 {
1489   // width height num_colors chars_per_pixel
1490   "    16    16        3            1",
1491
1492   // colors
1493   "X c #000000",
1494   ". c #ffffff",
1495   "  c None",
1496
1497   // pixels
1498   "                ",
1499   "                ",
1500   "                ",
1501   "                ",
1502   "                ",
1503   "                ",
1504   "                ",
1505   "                ",
1506   "                ",
1507   "                ",
1508   "                ",
1509   "                ",
1510   "                ",
1511   "                ",
1512   "                ",
1513   "                ",
1514
1515   // hot spot
1516   "0,0"
1517 };
1518
1519 #if USE_ONE_PIXEL_PLAYFIELD_MOUSEPOINTER
1520 static const char *cursor_image_dot[] =
1521 {
1522   // width height num_colors chars_per_pixel
1523   "    16    16        3            1",
1524
1525   // colors
1526   "X c #000000",
1527   ". c #ffffff",
1528   "  c None",
1529
1530   // pixels
1531   " X              ",
1532   "X.X             ",
1533   " X              ",
1534   "                ",
1535   "                ",
1536   "                ",
1537   "                ",
1538   "                ",
1539   "                ",
1540   "                ",
1541   "                ",
1542   "                ",
1543   "                ",
1544   "                ",
1545   "                ",
1546   "                ",
1547
1548   // hot spot
1549   "1,1"
1550 };
1551 static const char **cursor_image_playfield = cursor_image_dot;
1552 #else
1553 // some people complained about a "white dot" on the screen and thought it
1554 // was a graphical error... OK, let's just remove the whole pointer :-)
1555 static const char **cursor_image_playfield = cursor_image_none;
1556 #endif
1557
1558 static const int cursor_bit_order = BIT_ORDER_MSB;
1559
1560 static struct MouseCursorInfo *get_cursor_from_image(const char **image)
1561 {
1562   struct MouseCursorInfo *cursor;
1563   boolean bit_order_msb = (cursor_bit_order == BIT_ORDER_MSB);
1564   int header_lines = 4;
1565   int x, y, i;
1566
1567   cursor = checked_calloc(sizeof(struct MouseCursorInfo));
1568
1569   sscanf(image[0], " %d %d ", &cursor->width, &cursor->height);
1570
1571   i = -1;
1572   for (y = 0; y < cursor->width; y++)
1573   {
1574     for (x = 0; x < cursor->height; x++)
1575     {
1576       int bit_nr = x % 8;
1577       int bit_mask = 0x01 << (bit_order_msb ? 7 - bit_nr : bit_nr );
1578
1579       if (bit_nr == 0)
1580       {
1581         i++;
1582         cursor->data[i] = cursor->mask[i] = 0;
1583       }
1584
1585       switch (image[header_lines + y][x])
1586       {
1587         case 'X':
1588           cursor->data[i] |= bit_mask;
1589           cursor->mask[i] |= bit_mask;
1590           break;
1591
1592         case '.':
1593           cursor->mask[i] |= bit_mask;
1594           break;
1595
1596         case ' ':
1597           break;
1598       }
1599     }
1600   }
1601
1602   sscanf(image[header_lines + y], "%d,%d", &cursor->hot_x, &cursor->hot_y);
1603
1604   return cursor;
1605 }
1606
1607 void SetMouseCursor(int mode)
1608 {
1609   static struct MouseCursorInfo *cursor_none = NULL;
1610   static struct MouseCursorInfo *cursor_playfield = NULL;
1611   struct MouseCursorInfo *cursor_new;
1612   int mode_final = mode;
1613
1614   if (cursor_none == NULL)
1615     cursor_none = get_cursor_from_image(cursor_image_none);
1616
1617   if (cursor_playfield == NULL)
1618     cursor_playfield = get_cursor_from_image(cursor_image_playfield);
1619
1620   if (gfx.cursor_mode_override != CURSOR_UNDEFINED)
1621     mode_final = gfx.cursor_mode_override;
1622
1623   cursor_new = (mode_final == CURSOR_DEFAULT   ? NULL :
1624                 mode_final == CURSOR_NONE      ? cursor_none :
1625                 mode_final == CURSOR_PLAYFIELD ? cursor_playfield : NULL);
1626
1627   SDLSetMouseCursor(cursor_new);
1628
1629   gfx.cursor_mode = mode;
1630   gfx.cursor_mode_final = mode_final;
1631 }
1632
1633 void UpdateRawMousePosition(int mouse_x, int mouse_y)
1634 {
1635   // mouse events do not contain logical screen size corrections yet
1636   SDLCorrectRawMousePosition(&mouse_x, &mouse_y);
1637
1638   mouse_x -= video.screen_xoffset;
1639   mouse_y -= video.screen_yoffset;
1640
1641   gfx.mouse_x = mouse_x;
1642   gfx.mouse_y = mouse_y;
1643 }
1644
1645 void UpdateMousePosition(void)
1646 {
1647   int mouse_x, mouse_y;
1648
1649   SDL_PumpEvents();
1650   SDL_GetMouseState(&mouse_x, &mouse_y);
1651
1652   UpdateRawMousePosition(mouse_x, mouse_y);
1653 }
1654
1655
1656 // ============================================================================
1657 // audio functions
1658 // ============================================================================
1659
1660 void OpenAudio(void)
1661 {
1662   // always start with reliable default values
1663   audio.sound_available = FALSE;
1664   audio.music_available = FALSE;
1665   audio.loops_available = FALSE;
1666
1667   audio.sound_enabled = FALSE;
1668   audio.sound_deactivated = FALSE;
1669
1670   audio.mixer_pipe[0] = audio.mixer_pipe[1] = 0;
1671   audio.mixer_pid = 0;
1672   audio.device_name = NULL;
1673   audio.device_fd = -1;
1674
1675   audio.num_channels = 0;
1676   audio.music_channel = 0;
1677   audio.first_sound_channel = 0;
1678
1679   SDLOpenAudio();
1680 }
1681
1682 void CloseAudio(void)
1683 {
1684   SDLCloseAudio();
1685
1686   audio.sound_enabled = FALSE;
1687 }
1688
1689 void SetAudioMode(boolean enabled)
1690 {
1691   if (!audio.sound_available)
1692     return;
1693
1694   audio.sound_enabled = enabled;
1695 }
1696
1697
1698 // ============================================================================
1699 // event functions
1700 // ============================================================================
1701
1702 void InitEventFilter(EventFilter filter_function)
1703 {
1704   SDL_SetEventFilter(filter_function, NULL);
1705 }
1706
1707 boolean PendingEvent(void)
1708 {
1709   return (SDL_PollEvent(NULL) ? TRUE : FALSE);
1710 }
1711
1712 void WaitEvent(Event *event)
1713 {
1714   SDLWaitEvent(event);
1715 }
1716
1717 void PeekEvent(Event *event)
1718 {
1719   SDL_PeepEvents(event, 1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT);
1720 }
1721
1722 void PumpEvents(void)
1723 {
1724   SDL_PumpEvents();
1725 }
1726
1727 void CheckQuitEvent(void)
1728 {
1729   if (SDL_QuitRequested())
1730     program.exit_function(0);
1731 }
1732
1733 Key GetEventKey(KeyEvent *event, boolean with_modifiers)
1734 {
1735   // key up/down events in SDL2 do not return text characters anymore
1736   return event->keysym.sym;
1737 }
1738
1739 KeyMod HandleKeyModState(Key key, int key_status)
1740 {
1741   static KeyMod current_modifiers = KMOD_None;
1742
1743   if (key != KSYM_UNDEFINED)    // new key => check for modifier key change
1744   {
1745     KeyMod new_modifier = KMOD_None;
1746
1747     switch (key)
1748     {
1749       case KSYM_Shift_L:
1750         new_modifier = KMOD_Shift_L;
1751         break;
1752       case KSYM_Shift_R:
1753         new_modifier = KMOD_Shift_R;
1754         break;
1755       case KSYM_Control_L:
1756         new_modifier = KMOD_Control_L;
1757         break;
1758       case KSYM_Control_R:
1759         new_modifier = KMOD_Control_R;
1760         break;
1761       case KSYM_Meta_L:
1762         new_modifier = KMOD_Meta_L;
1763         break;
1764       case KSYM_Meta_R:
1765         new_modifier = KMOD_Meta_R;
1766         break;
1767       case KSYM_Alt_L:
1768         new_modifier = KMOD_Alt_L;
1769         break;
1770       case KSYM_Alt_R:
1771         new_modifier = KMOD_Alt_R;
1772         break;
1773       default:
1774         break;
1775     }
1776
1777     if (key_status == KEY_PRESSED)
1778       current_modifiers |= new_modifier;
1779     else
1780       current_modifiers &= ~new_modifier;
1781   }
1782
1783   return current_modifiers;
1784 }
1785
1786 KeyMod GetKeyModState(void)
1787 {
1788   return (KeyMod)SDL_GetModState();
1789 }
1790
1791 KeyMod GetKeyModStateFromEvents(void)
1792 {
1793   /* always use key modifier state as tracked from key events (this is needed
1794      if the modifier key event was injected into the event queue, but the key
1795      was not really pressed on keyboard -- SDL_GetModState() seems to directly
1796      query the keys as held pressed on the keyboard) -- this case is currently
1797      only used to filter out clipboard insert events from "True X-Mouse" tool */
1798
1799   return HandleKeyModState(KSYM_UNDEFINED, 0);
1800 }
1801
1802 void StartTextInput(int x, int y, int width, int height)
1803 {
1804   textinput_status = TRUE;
1805
1806 #if defined(HAS_SCREEN_KEYBOARD)
1807   SDL_StartTextInput();
1808
1809   if (y + height > SCREEN_KEYBOARD_POS(video.height))
1810   {
1811     video.shifted_up_pos = y + height - SCREEN_KEYBOARD_POS(video.height);
1812     video.shifted_up_delay = SDL_GetTicks();
1813     video.shifted_up = TRUE;
1814   }
1815 #endif
1816 }
1817
1818 void StopTextInput(void)
1819 {
1820   textinput_status = FALSE;
1821
1822 #if defined(HAS_SCREEN_KEYBOARD)
1823   SDL_StopTextInput();
1824
1825   if (video.shifted_up)
1826   {
1827     video.shifted_up_pos = 0;
1828     video.shifted_up_delay = SDL_GetTicks();
1829     video.shifted_up = FALSE;
1830   }
1831 #endif
1832 }
1833
1834 void PushUserEvent(int code, int value1, int value2)
1835 {
1836   UserEvent event;
1837
1838   SDL_memset(&event, 0, sizeof(event));
1839
1840   event.type = EVENT_USER;
1841   event.code = code;
1842   event.value1 = value1;
1843   event.value2 = value2;
1844
1845   SDL_PushEvent((SDL_Event *)&event);
1846 }
1847
1848
1849 // ============================================================================
1850 // joystick functions
1851 // ============================================================================
1852
1853 void InitJoysticks(void)
1854 {
1855   int i;
1856
1857 #if defined(NO_JOYSTICK)
1858   return;       // joysticks generally deactivated by compile-time directive
1859 #endif
1860
1861   // always start with reliable default values
1862   joystick.status = JOYSTICK_NOT_AVAILABLE;
1863   for (i = 0; i < MAX_PLAYERS; i++)
1864     joystick.nr[i] = -1;                // no joystick configured
1865
1866   SDLInitJoysticks();
1867 }
1868
1869 boolean ReadJoystick(int nr, int *x, int *y, boolean *b1, boolean *b2)
1870 {
1871   return SDLReadJoystick(nr, x, y, b1, b2);
1872 }
1873
1874 boolean CheckJoystickOpened(int nr)
1875 {
1876   return SDLCheckJoystickOpened(nr);
1877 }
1878
1879 void ClearJoystickState(void)
1880 {
1881   SDLClearJoystickState();
1882 }
1883
1884
1885 // ============================================================================
1886 // Emscripten functions
1887 // ============================================================================
1888
1889 void InitEmscriptenFilesystem(void)
1890 {
1891 #if defined(PLATFORM_EMSCRIPTEN)
1892   EM_ASM
1893   (
1894     Module.sync_done = 0;
1895
1896     FS.mkdir('/persistent');            // create persistent data directory
1897     FS.mount(IDBFS, {}, '/persistent'); // mount with IDBFS filesystem type
1898     FS.syncfs(true, function(err)       // sync persistent data into memory
1899     {
1900       assert(!err);
1901       Module.sync_done = 1;
1902     });
1903   );
1904
1905   // wait for persistent data to be synchronized to memory
1906   while (emscripten_run_script_int("Module.sync_done") == 0)
1907     Delay(20);
1908 #endif
1909 }
1910
1911 void SyncEmscriptenFilesystem(void)
1912 {
1913 #if defined(PLATFORM_EMSCRIPTEN)
1914   EM_ASM
1915   (
1916     FS.syncfs(function(err)
1917     {
1918       assert(!err);
1919     });
1920   );
1921 #endif
1922 }