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