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