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