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