added support for BD game engine to Makefile for Android
[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(void)
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 void ResetBitmapAlpha(Bitmap *bitmap)
603 {
604   bitmap->alpha[0][0] = -1;
605   bitmap->alpha[0][1] = -1;
606   bitmap->alpha[1][0] = -1;
607   bitmap->alpha[1][1] = -1;
608   bitmap->alpha_next_blit = -1;
609 }
610
611 Bitmap *CreateBitmapStruct(void)
612 {
613   Bitmap *new_bitmap = checked_calloc(sizeof(Bitmap));
614
615   ResetBitmapAlpha(new_bitmap);
616
617   return new_bitmap;
618 }
619
620 Bitmap *CreateBitmap(int width, int height, int depth)
621 {
622   Bitmap *new_bitmap = CreateBitmapStruct();
623   int real_width  = MAX(1, width);      // prevent zero bitmap width
624   int real_height = MAX(1, height);     // prevent zero bitmap height
625   int real_depth  = GetRealDepth(depth);
626
627   new_bitmap->surface = SDLCreateNativeSurface(real_width, real_height, real_depth);
628
629   new_bitmap->width  = real_width;
630   new_bitmap->height = real_height;
631
632   return new_bitmap;
633 }
634
635 void ReCreateBitmap(Bitmap **bitmap, int width, int height)
636 {
637   if (*bitmap != NULL)
638   {
639     // if new bitmap size fits into old one, no need to re-create it
640     if (width  <= (*bitmap)->width &&
641         height <= (*bitmap)->height)
642       return;
643
644     // else adjust size so that old and new bitmap size fit into it
645     width  = MAX(width,  (*bitmap)->width);
646     height = MAX(height, (*bitmap)->height);
647   }
648
649   Bitmap *new_bitmap = CreateBitmap(width, height, DEFAULT_DEPTH);
650
651   if (*bitmap == NULL)
652   {
653     *bitmap = new_bitmap;
654   }
655   else
656   {
657     TransferBitmapPointers(new_bitmap, *bitmap);
658     free(new_bitmap);
659   }
660 }
661
662 #if 0
663 static void CloseWindow(DrawWindow *window)
664 {
665 }
666 #endif
667
668 void SetRedrawMaskFromArea(int x, int y, int width, int height)
669 {
670   int x1 = x;
671   int y1 = y;
672   int x2 = x + width - 1;
673   int y2 = y + height - 1;
674
675   if (width == 0 || height == 0)
676     return;
677
678   if (IN_GFX_FIELD_FULL(x1, y1) && IN_GFX_FIELD_FULL(x2, y2))
679     redraw_mask |= REDRAW_FIELD;
680   else if (IN_GFX_DOOR_1(x1, y1) && IN_GFX_DOOR_1(x2, y2))
681     redraw_mask |= REDRAW_DOOR_1;
682   else if (IN_GFX_DOOR_2(x1, y1) && IN_GFX_DOOR_2(x2, y2))
683     redraw_mask |= REDRAW_DOOR_2;
684   else if (IN_GFX_DOOR_3(x1, y1) && IN_GFX_DOOR_3(x2, y2))
685     redraw_mask |= REDRAW_DOOR_3;
686   else
687     redraw_mask = REDRAW_ALL;
688 }
689
690 static boolean CheckDrawingArea(int x, int y, int draw_mask)
691 {
692   if (draw_mask == REDRAW_NONE)
693     return FALSE;
694
695   if (draw_mask & REDRAW_ALL)
696     return TRUE;
697
698   if ((draw_mask & REDRAW_FIELD) && IN_GFX_FIELD_FULL(x, y))
699     return TRUE;
700
701   if ((draw_mask & REDRAW_DOOR_1) && IN_GFX_DOOR_1(x, y))
702     return TRUE;
703
704   if ((draw_mask & REDRAW_DOOR_2) && IN_GFX_DOOR_2(x, y))
705     return TRUE;
706
707   if ((draw_mask & REDRAW_DOOR_3) && IN_GFX_DOOR_3(x, y))
708     return TRUE;
709
710   return FALSE;
711 }
712
713 boolean DrawingDeactivatedField(void)
714 {
715   if (program.headless)
716     return TRUE;
717
718   if (gfx.draw_deactivation_mask & REDRAW_FIELD)
719     return TRUE;
720
721   return FALSE;
722 }
723
724 boolean DrawingDeactivated(int x, int y)
725 {
726   return CheckDrawingArea(x, y, gfx.draw_deactivation_mask);
727 }
728
729 boolean DrawingOnBackground(int x, int y)
730 {
731   return (CheckDrawingArea(x, y, gfx.background_bitmap_mask) &&
732           CheckDrawingArea(x, y, gfx.draw_background_mask));
733 }
734
735 static boolean InClippedRectangle(Bitmap *bitmap, int *x, int *y,
736                                   int *width, int *height, boolean is_dest)
737 {
738   int clip_x, clip_y, clip_width, clip_height;
739
740   if (gfx.clipping_enabled && is_dest)  // only clip destination bitmap
741   {
742     clip_x = MIN(MAX(0, gfx.clip_x), bitmap->width);
743     clip_y = MIN(MAX(0, gfx.clip_y), bitmap->height);
744     clip_width = MIN(MAX(0, gfx.clip_width), bitmap->width - clip_x);
745     clip_height = MIN(MAX(0, gfx.clip_height), bitmap->height - clip_y);
746   }
747   else
748   {
749     clip_x = 0;
750     clip_y = 0;
751     clip_width = bitmap->width;
752     clip_height = bitmap->height;
753   }
754
755   // skip if rectangle completely outside bitmap
756
757   if (*x + *width  <= clip_x ||
758       *y + *height <= clip_y ||
759       *x >= clip_x + clip_width ||
760       *y >= clip_y + clip_height)
761     return FALSE;
762
763   // clip if rectangle overlaps bitmap
764
765   if (*x < clip_x)
766   {
767     *width -= clip_x - *x;
768     *x = clip_x;
769   }
770   else if (*x + *width > clip_x + clip_width)
771   {
772     *width = clip_x + clip_width - *x;
773   }
774
775   if (*y < clip_y)
776   {
777     *height -= clip_y - *y;
778     *y = clip_y;
779   }
780   else if (*y + *height > clip_y + clip_height)
781   {
782     *height = clip_y + clip_height - *y;
783   }
784
785   return TRUE;
786 }
787
788 void SetBitmapAlphaNextBlit(Bitmap *bitmap, int alpha)
789 {
790   // set alpha value for next blitting of bitmap
791   bitmap->alpha_next_blit = alpha;
792 }
793
794 void BlitBitmap(Bitmap *src_bitmap, Bitmap *dst_bitmap,
795                 int src_x, int src_y, int width, int height,
796                 int dst_x, int dst_y)
797 {
798   int dst_x_unclipped = dst_x;
799   int dst_y_unclipped = dst_y;
800
801   if (program.headless)
802     return;
803
804   if (src_bitmap == NULL || dst_bitmap == NULL)
805     return;
806
807   if (DrawingDeactivated(dst_x, dst_y))
808     return;
809
810   if (!InClippedRectangle(src_bitmap, &src_x, &src_y, &width, &height, FALSE) ||
811       !InClippedRectangle(dst_bitmap, &dst_x, &dst_y, &width, &height, TRUE))
812     return;
813
814   // source x/y might need adjustment if destination x/y was clipped top/left
815   src_x += dst_x - dst_x_unclipped;
816   src_y += dst_y - dst_y_unclipped;
817
818   // !!! 2013-12-11: An "old friend" is back. Same bug in SDL2 2.0.1 !!!
819   // !!! 2009-03-30: Fixed by using self-compiled, patched SDL.dll !!!
820   /* (This bug still exists in the actual (as of 2009-06-15) version 1.2.13,
821      but is already fixed in SVN and should therefore finally be fixed with
822      the next official SDL release, which is probably version 1.2.14.) */
823   // !!! 2009-03-24: It seems that this problem still exists in 1.2.12 !!!
824
825   if (src_bitmap == dst_bitmap)
826   {
827     // needed when blitting directly to same bitmap -- should not be needed with
828     // recent SDL libraries, but apparently does not work in 1.2.11 directly
829
830     static Bitmap *tmp_bitmap = NULL;
831     static int tmp_bitmap_xsize = 0;
832     static int tmp_bitmap_ysize = 0;
833
834     // start with largest static bitmaps for initial bitmap size ...
835     if (tmp_bitmap_xsize == 0 && tmp_bitmap_ysize == 0)
836     {
837       tmp_bitmap_xsize = MAX(gfx.win_xsize, gfx.scrollbuffer_width);
838       tmp_bitmap_ysize = MAX(gfx.win_ysize, gfx.scrollbuffer_height);
839     }
840
841     // ... and allow for later re-adjustments due to custom artwork bitmaps
842     if (src_bitmap->width > tmp_bitmap_xsize ||
843         src_bitmap->height > tmp_bitmap_ysize)
844     {
845       tmp_bitmap_xsize = MAX(tmp_bitmap_xsize, src_bitmap->width);
846       tmp_bitmap_ysize = MAX(tmp_bitmap_ysize, src_bitmap->height);
847
848       FreeBitmap(tmp_bitmap);
849
850       tmp_bitmap = NULL;
851     }
852
853     if (tmp_bitmap == NULL)
854       tmp_bitmap = CreateBitmap(tmp_bitmap_xsize, tmp_bitmap_ysize,
855                                 DEFAULT_DEPTH);
856
857     sysCopyArea(src_bitmap, tmp_bitmap,
858                 src_x, src_y, width, height, dst_x, dst_y, BLIT_OPAQUE);
859     sysCopyArea(tmp_bitmap, dst_bitmap,
860                 dst_x, dst_y, width, height, dst_x, dst_y, BLIT_OPAQUE);
861
862     return;
863   }
864
865   sysCopyArea(src_bitmap, dst_bitmap,
866               src_x, src_y, width, height, dst_x, dst_y, BLIT_OPAQUE);
867 }
868
869 void BlitBitmapTiled(Bitmap *src_bitmap, Bitmap *dst_bitmap,
870                      int src_x, int src_y, int src_width, int src_height,
871                      int dst_x, int dst_y, int dst_width, int dst_height)
872 {
873   int src_xsize = (src_width  == 0 ? src_bitmap->width  : src_width);
874   int src_ysize = (src_height == 0 ? src_bitmap->height : src_height);
875   int dst_xsize = dst_width;
876   int dst_ysize = dst_height;
877   int src_xsteps = (dst_xsize + src_xsize - 1) / src_xsize;
878   int src_ysteps = (dst_ysize + src_ysize - 1) / src_ysize;
879   int x, y;
880
881   for (y = 0; y < src_ysteps; y++)
882   {
883     for (x = 0; x < src_xsteps; x++)
884     {
885       int draw_x = dst_x + x * src_xsize;
886       int draw_y = dst_y + y * src_ysize;
887       int draw_xsize = MIN(src_xsize, dst_xsize - x * src_xsize);
888       int draw_ysize = MIN(src_ysize, dst_ysize - y * src_ysize);
889
890       BlitBitmap(src_bitmap, dst_bitmap, src_x, src_y, draw_xsize, draw_ysize,
891                  draw_x, draw_y);
892     }
893   }
894 }
895
896 void FadeRectangle(int x, int y, int width, int height,
897                    int fade_mode, int fade_delay, int post_delay,
898                    void (*draw_border_function)(void))
899 {
900   // (use destination bitmap "backbuffer" -- "bitmap_cross" may be undefined)
901   if (!InClippedRectangle(backbuffer, &x, &y, &width, &height, TRUE))
902     return;
903
904   SDLFadeRectangle(x, y, width, height,
905                    fade_mode, fade_delay, post_delay, draw_border_function);
906 }
907
908 void FillRectangle(Bitmap *bitmap, int x, int y, int width, int height,
909                    Pixel color)
910 {
911   if (program.headless)
912     return;
913
914   if (DrawingDeactivated(x, y))
915     return;
916
917   if (!InClippedRectangle(bitmap, &x, &y, &width, &height, TRUE))
918     return;
919
920   sysFillRectangle(bitmap, x, y, width, height, color);
921 }
922
923 void ClearRectangle(Bitmap *bitmap, int x, int y, int width, int height)
924 {
925   FillRectangle(bitmap, x, y, width, height, BLACK_PIXEL);
926 }
927
928 void ClearRectangleOnBackground(Bitmap *bitmap, int x, int y,
929                                 int width, int height)
930 {
931   if (DrawingOnBackground(x, y))
932     BlitBitmap(gfx.background_bitmap, bitmap, x, y, width, height, x, y);
933   else
934     ClearRectangle(bitmap, x, y, width, height);
935 }
936
937 void BlitBitmapMasked(Bitmap *src_bitmap, Bitmap *dst_bitmap,
938                       int src_x, int src_y, int width, int height,
939                       int dst_x, int dst_y)
940 {
941   if (program.headless)
942     return;
943
944   if (src_bitmap == NULL || dst_bitmap == NULL)
945     return;
946
947   if (DrawingDeactivated(dst_x, dst_y))
948     return;
949
950   sysCopyArea(src_bitmap, dst_bitmap, src_x, src_y, width, height,
951               dst_x, dst_y, BLIT_MASKED);
952 }
953
954 void BlitBitmapOnBackground(Bitmap *src_bitmap, Bitmap *dst_bitmap,
955                             int src_x, int src_y, int width, int height,
956                             int dst_x, int dst_y)
957 {
958   if (DrawingOnBackground(dst_x, dst_y))
959   {
960     // draw background
961     BlitBitmap(gfx.background_bitmap, dst_bitmap, dst_x, dst_y, width, height,
962                dst_x, dst_y);
963
964     // draw foreground
965     BlitBitmapMasked(src_bitmap, dst_bitmap, src_x, src_y, width, height,
966                      dst_x, dst_y);
967   }
968   else
969     BlitBitmap(src_bitmap, dst_bitmap, src_x, src_y, width, height,
970                dst_x, dst_y);
971 }
972
973 void BlitTexture(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_OPAQUE);
982 }
983
984 void BlitTextureMasked(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   SDLBlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y,
992                  BLIT_MASKED);
993 }
994
995 void BlitToScreen(Bitmap *bitmap,
996                   int src_x, int src_y, int width, int height,
997                   int dst_x, int dst_y)
998 {
999   if (bitmap == NULL)
1000     return;
1001
1002   if (video.screen_rendering_mode == SPECIAL_RENDERING_BITMAP)
1003     BlitBitmap(bitmap, gfx.final_screen_bitmap, src_x, src_y,
1004                width, height, dst_x, dst_y);
1005   else
1006     BlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y);
1007 }
1008
1009 void BlitToScreenMasked(Bitmap *bitmap,
1010                         int src_x, int src_y, int width, int height,
1011                         int dst_x, int dst_y)
1012 {
1013   if (bitmap == NULL)
1014     return;
1015
1016   if (video.screen_rendering_mode == SPECIAL_RENDERING_BITMAP)
1017     BlitBitmapMasked(bitmap, gfx.final_screen_bitmap, src_x, src_y,
1018                      width, height, dst_x, dst_y);
1019   else
1020     BlitTextureMasked(bitmap, src_x, src_y, width, height, dst_x, dst_y);
1021 }
1022
1023 void DrawSimpleWhiteLine(Bitmap *bitmap, int from_x, int from_y,
1024                          int to_x, int to_y)
1025 {
1026   SDLDrawSimpleLine(bitmap, from_x, from_y, to_x, to_y, WHITE_PIXEL);
1027 }
1028
1029 static void DrawLine(Bitmap *bitmap, int from_x, int from_y,
1030                      int to_x, int to_y, Pixel pixel, int line_width)
1031 {
1032   int x, y;
1033
1034   if (program.headless)
1035     return;
1036
1037   for (x = 0; x < line_width; x++)
1038   {
1039     for (y = 0; y < line_width; y++)
1040     {
1041       int dx = x - line_width / 2;
1042       int dy = y - line_width / 2;
1043
1044       if ((x == 0 && y == 0) ||
1045           (x == 0 && y == line_width - 1) ||
1046           (x == line_width - 1 && y == 0) ||
1047           (x == line_width - 1 && y == line_width - 1))
1048         continue;
1049
1050       SDLDrawLine(bitmap,
1051                   from_x + dx, from_y + dy, to_x + dx, to_y + dy, pixel);
1052     }
1053   }
1054 }
1055
1056 void DrawLines(Bitmap *bitmap, struct XY *points, int num_points, Pixel pixel)
1057 {
1058   int line_width = 4;
1059   int i;
1060
1061   for (i = 0; i < num_points - 1; i++)
1062     DrawLine(bitmap, points[i].x, points[i].y,
1063              points[i + 1].x, points[i + 1].y, pixel, line_width);
1064
1065   /*
1066   SDLDrawLines(bitmap->surface, points, num_points, pixel);
1067   */
1068 }
1069
1070 Pixel GetPixel(Bitmap *bitmap, int x, int y)
1071 {
1072   if (program.headless)
1073     return BLACK_PIXEL;
1074
1075   if (x < 0 || x >= bitmap->width ||
1076       y < 0 || y >= bitmap->height)
1077     return BLACK_PIXEL;
1078
1079   return SDLGetPixel(bitmap, x, y);
1080 }
1081
1082 Pixel GetPixelFromRGB(Bitmap *bitmap, unsigned int color_r,
1083                       unsigned int color_g, unsigned int color_b)
1084 {
1085   if (program.headless)
1086     return BLACK_PIXEL;
1087
1088   return SDL_MapRGB(bitmap->surface->format, color_r, color_g, color_b);
1089 }
1090
1091 void KeyboardAutoRepeatOn(void)
1092 {
1093   keyrepeat_status = TRUE;
1094 }
1095
1096 void KeyboardAutoRepeatOff(void)
1097 {
1098   keyrepeat_status = FALSE;
1099 }
1100
1101 boolean SetVideoMode(boolean fullscreen)
1102 {
1103   return SDLSetVideoMode(fullscreen);
1104 }
1105
1106 void SetVideoFrameDelay(unsigned int frame_delay_value)
1107 {
1108   video.frame_delay.value = frame_delay_value;
1109 }
1110
1111 unsigned int GetVideoFrameDelay(void)
1112 {
1113   return video.frame_delay.value;
1114 }
1115
1116 boolean ChangeVideoModeIfNeeded(boolean fullscreen)
1117 {
1118   if ((fullscreen && !video.fullscreen_enabled && video.fullscreen_available)||
1119       (!fullscreen && video.fullscreen_enabled))
1120     fullscreen = SetVideoMode(fullscreen);
1121
1122   return fullscreen;
1123 }
1124
1125 Bitmap *LoadImage(char *filename)
1126 {
1127   Bitmap *new_bitmap;
1128
1129   new_bitmap = SDLLoadImage(filename);
1130
1131   if (new_bitmap)
1132     new_bitmap->source_filename = getStringCopy(filename);
1133
1134   return new_bitmap;
1135 }
1136
1137 Bitmap *LoadCustomImage(char *basename)
1138 {
1139   char *filename = getCustomImageFilename(basename);
1140   Bitmap *new_bitmap;
1141
1142   if (filename == NULL)
1143     Fail("LoadCustomImage(): cannot find file '%s'", basename);
1144
1145   if ((new_bitmap = LoadImage(filename)) == NULL)
1146     Fail("LoadImage('%s') failed", basename);
1147
1148   return new_bitmap;
1149 }
1150
1151 void ReloadCustomImage(Bitmap *bitmap, char *basename)
1152 {
1153   char *filename = getCustomImageFilename(basename);
1154   Bitmap *new_bitmap;
1155
1156   if (filename == NULL)         // (should never happen)
1157   {
1158     Warn("ReloadCustomImage(): cannot find file '%s'", basename);
1159
1160     return;
1161   }
1162
1163   if (strEqual(filename, bitmap->source_filename))
1164   {
1165     // The old and new image are the same (have the same filename and path).
1166     // This usually means that this image does not exist in this graphic set
1167     // and a fallback to the existing image is done.
1168
1169     return;
1170   }
1171
1172   if ((new_bitmap = LoadImage(filename)) == NULL)
1173   {
1174     Warn("LoadImage('%s') failed", basename);
1175
1176     return;
1177   }
1178
1179   if (bitmap->width != new_bitmap->width ||
1180       bitmap->height != new_bitmap->height)
1181   {
1182     Warn("ReloadCustomImage: new image '%s' has wrong dimensions",
1183           filename);
1184
1185     FreeBitmap(new_bitmap);
1186
1187     return;
1188   }
1189
1190   TransferBitmapPointers(new_bitmap, bitmap);
1191   free(new_bitmap);
1192 }
1193
1194 Bitmap *ZoomBitmap(Bitmap *src_bitmap, int zoom_width, int zoom_height)
1195 {
1196   return SDLZoomBitmap(src_bitmap, zoom_width, zoom_height);
1197 }
1198
1199 void ReCreateGameTileSizeBitmap(Bitmap **bitmaps)
1200 {
1201   if (bitmaps[IMG_BITMAP_CUSTOM])
1202   {
1203     // check if original sized bitmap points to custom sized bitmap
1204     if (bitmaps[IMG_BITMAP_PTR_ORIGINAL] == bitmaps[IMG_BITMAP_CUSTOM])
1205     {
1206       SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_PTR_ORIGINAL]);
1207
1208       // keep pointer of previous custom size bitmap
1209       bitmaps[IMG_BITMAP_OTHER] = bitmaps[IMG_BITMAP_CUSTOM];
1210
1211       // set original bitmap pointer to scaled original bitmap of other size
1212       bitmaps[IMG_BITMAP_PTR_ORIGINAL] = bitmaps[IMG_BITMAP_OTHER];
1213
1214       SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_PTR_ORIGINAL]);
1215     }
1216     else
1217     {
1218       FreeBitmap(bitmaps[IMG_BITMAP_CUSTOM]);
1219     }
1220
1221     bitmaps[IMG_BITMAP_CUSTOM] = NULL;
1222   }
1223
1224   if (gfx.game_tile_size == gfx.standard_tile_size)
1225   {
1226     // set game bitmap pointer to standard sized bitmap (already existing)
1227     bitmaps[IMG_BITMAP_PTR_GAME] = bitmaps[IMG_BITMAP_STANDARD];
1228
1229     return;
1230   }
1231
1232   Bitmap *bitmap = bitmaps[IMG_BITMAP_STANDARD];
1233   int width  = bitmap->width  * gfx.game_tile_size / gfx.standard_tile_size;;
1234   int height = bitmap->height * gfx.game_tile_size / gfx.standard_tile_size;;
1235
1236   bitmaps[IMG_BITMAP_CUSTOM] = ZoomBitmap(bitmap, width, height);
1237
1238   // set game bitmap pointer to custom sized bitmap (newly created)
1239   bitmaps[IMG_BITMAP_PTR_GAME] = bitmaps[IMG_BITMAP_CUSTOM];
1240 }
1241
1242 static void CreateScaledBitmaps(Bitmap **bitmaps, int zoom_factor,
1243                                 int tile_size, boolean create_small_bitmaps)
1244 {
1245   Bitmap *old_bitmap = bitmaps[IMG_BITMAP_STANDARD];
1246   Bitmap *tmp_bitmap_final = NULL;
1247   Bitmap *tmp_bitmap_0 = NULL;
1248   Bitmap *tmp_bitmap_1 = NULL;
1249   Bitmap *tmp_bitmap_2 = NULL;
1250   Bitmap *tmp_bitmap_4 = NULL;
1251   Bitmap *tmp_bitmap_8 = NULL;
1252   Bitmap *tmp_bitmap_16 = NULL;
1253   Bitmap *tmp_bitmap_32 = NULL;
1254   int width_final, height_final;
1255   int width_0, height_0;
1256   int width_1, height_1;
1257   int width_2, height_2;
1258   int width_4, height_4;
1259   int width_8, height_8;
1260   int width_16, height_16;
1261   int width_32, height_32;
1262   int old_width, old_height;
1263   int i;
1264
1265   print_timestamp_init("CreateScaledBitmaps");
1266
1267   old_width  = old_bitmap->width;
1268   old_height = old_bitmap->height;
1269
1270   // calculate new image dimensions for final image size
1271   width_final  = old_width  * zoom_factor;
1272   height_final = old_height * zoom_factor;
1273
1274   // get image with final size (this might require scaling up)
1275   // ("final" size may result in non-standard tile size image)
1276   if (zoom_factor != 1)
1277     tmp_bitmap_final = ZoomBitmap(old_bitmap, width_final, height_final);
1278   else
1279     tmp_bitmap_final = old_bitmap;
1280
1281   UPDATE_BUSY_STATE();
1282
1283   width_0  = width_1  = width_final;
1284   height_0 = height_1 = height_final;
1285
1286   tmp_bitmap_0 = tmp_bitmap_1 = tmp_bitmap_final;
1287
1288   if (create_small_bitmaps)
1289   {
1290     // check if we have a non-gameplay tile size image
1291     if (tile_size != gfx.game_tile_size)
1292     {
1293       // get image with gameplay tile size
1294       width_0  = width_final  * gfx.game_tile_size / tile_size;
1295       height_0 = height_final * gfx.game_tile_size / tile_size;
1296
1297       if (width_0 == old_width)
1298         tmp_bitmap_0 = old_bitmap;
1299       else if (width_0 == width_final)
1300         tmp_bitmap_0 = tmp_bitmap_final;
1301       else
1302         tmp_bitmap_0 = ZoomBitmap(old_bitmap, width_0, height_0);
1303
1304       UPDATE_BUSY_STATE();
1305     }
1306
1307     // check if we have a non-standard tile size image
1308     if (tile_size != gfx.standard_tile_size)
1309     {
1310       // get image with standard tile size
1311       width_1  = width_final  * gfx.standard_tile_size / tile_size;
1312       height_1 = height_final * gfx.standard_tile_size / tile_size;
1313
1314       if (width_1 == old_width)
1315         tmp_bitmap_1 = old_bitmap;
1316       else if (width_1 == width_final)
1317         tmp_bitmap_1 = tmp_bitmap_final;
1318       else if (width_1 == width_0)
1319         tmp_bitmap_1 = tmp_bitmap_0;
1320       else
1321         tmp_bitmap_1 = ZoomBitmap(old_bitmap, width_1, height_1);
1322
1323       UPDATE_BUSY_STATE();
1324     }
1325
1326     // calculate new image dimensions for small images
1327     width_2  = width_1  / 2;
1328     height_2 = height_1 / 2;
1329     width_4  = width_1  / 4;
1330     height_4 = height_1 / 4;
1331     width_8  = width_1  / 8;
1332     height_8 = height_1 / 8;
1333     width_16  = width_1  / 16;
1334     height_16 = height_1 / 16;
1335     width_32  = width_1  / 32;
1336     height_32 = height_1 / 32;
1337
1338     // get image with 1/2 of normal size (for use in the level editor)
1339     if (width_2 == old_width)
1340       tmp_bitmap_2 = old_bitmap;
1341     else
1342       tmp_bitmap_2 = ZoomBitmap(tmp_bitmap_1, width_2, height_2);
1343
1344     UPDATE_BUSY_STATE();
1345
1346     // get image with 1/4 of normal size (for use in the level editor)
1347     if (width_4 == old_width)
1348       tmp_bitmap_4 = old_bitmap;
1349     else
1350       tmp_bitmap_4 = ZoomBitmap(tmp_bitmap_2, width_4, height_4);
1351
1352     UPDATE_BUSY_STATE();
1353
1354     // get image with 1/8 of normal size (for use on the preview screen)
1355     if (width_8 == old_width)
1356       tmp_bitmap_8 = old_bitmap;
1357     else
1358       tmp_bitmap_8 = ZoomBitmap(tmp_bitmap_4, width_8, height_8);
1359
1360     UPDATE_BUSY_STATE();
1361
1362     // get image with 1/16 of normal size (for use on the preview screen)
1363     if (width_16 == old_width)
1364       tmp_bitmap_16 = old_bitmap;
1365     else
1366       tmp_bitmap_16 = ZoomBitmap(tmp_bitmap_8, width_16, height_16);
1367
1368     UPDATE_BUSY_STATE();
1369
1370     // get image with 1/32 of normal size (for use on the preview screen)
1371     if (width_32 == old_width)
1372       tmp_bitmap_32 = old_bitmap;
1373     else
1374       tmp_bitmap_32 = ZoomBitmap(tmp_bitmap_16, width_32, height_32);
1375
1376     UPDATE_BUSY_STATE();
1377
1378     bitmaps[IMG_BITMAP_32x32] = tmp_bitmap_1;
1379     bitmaps[IMG_BITMAP_16x16] = tmp_bitmap_2;
1380     bitmaps[IMG_BITMAP_8x8]   = tmp_bitmap_4;
1381     bitmaps[IMG_BITMAP_4x4]   = tmp_bitmap_8;
1382     bitmaps[IMG_BITMAP_2x2]   = tmp_bitmap_16;
1383     bitmaps[IMG_BITMAP_1x1]   = tmp_bitmap_32;
1384
1385     if (width_0 != width_1)
1386       bitmaps[IMG_BITMAP_CUSTOM] = tmp_bitmap_0;
1387
1388     if (bitmaps[IMG_BITMAP_CUSTOM])
1389       bitmaps[IMG_BITMAP_PTR_GAME] = bitmaps[IMG_BITMAP_CUSTOM];
1390     else
1391       bitmaps[IMG_BITMAP_PTR_GAME] = bitmaps[IMG_BITMAP_STANDARD];
1392
1393     // store the "final" (up-scaled) original bitmap, if not already stored
1394
1395     int tmp_bitmap_final_nr = -1;
1396
1397     for (i = 0; i < NUM_IMG_BITMAPS; i++)
1398       if (bitmaps[i] == tmp_bitmap_final)
1399         tmp_bitmap_final_nr = i;
1400
1401     if (tmp_bitmap_final_nr == -1)      // scaled original bitmap not stored
1402     {
1403       // store pointer of scaled original bitmap (not used for any other size)
1404       bitmaps[IMG_BITMAP_OTHER] = tmp_bitmap_final;
1405
1406       // set original bitmap pointer to scaled original bitmap of other size
1407       bitmaps[IMG_BITMAP_PTR_ORIGINAL] = bitmaps[IMG_BITMAP_OTHER];
1408     }
1409     else
1410     {
1411       // set original bitmap pointer to corresponding sized bitmap
1412       bitmaps[IMG_BITMAP_PTR_ORIGINAL] = bitmaps[tmp_bitmap_final_nr];
1413     }
1414
1415     // free the "old" (unscaled) original bitmap, if not already stored
1416
1417     boolean free_old_bitmap = TRUE;
1418
1419     for (i = 0; i < NUM_IMG_BITMAPS; i++)
1420       if (bitmaps[i] == old_bitmap)
1421         free_old_bitmap = FALSE;
1422
1423     if (free_old_bitmap)
1424     {
1425       // copy image filename from old to new standard sized bitmap
1426       bitmaps[IMG_BITMAP_STANDARD]->source_filename =
1427         getStringCopy(old_bitmap->source_filename);
1428
1429       FreeBitmap(old_bitmap);
1430     }
1431   }
1432   else
1433   {
1434     bitmaps[IMG_BITMAP_32x32] = tmp_bitmap_1;
1435
1436     // set original bitmap pointer to corresponding sized bitmap
1437     bitmaps[IMG_BITMAP_PTR_ORIGINAL] = bitmaps[IMG_BITMAP_32x32];
1438
1439     if (old_bitmap != tmp_bitmap_1)
1440       FreeBitmap(old_bitmap);
1441   }
1442
1443   UPDATE_BUSY_STATE();
1444
1445   print_timestamp_done("CreateScaledBitmaps");
1446 }
1447
1448 void CreateBitmapWithSmallBitmaps(Bitmap **bitmaps, int zoom_factor,
1449                                   int tile_size)
1450 {
1451   CreateScaledBitmaps(bitmaps, zoom_factor, tile_size, TRUE);
1452 }
1453
1454 void CreateBitmapTextures(Bitmap **bitmaps)
1455 {
1456   if (bitmaps[IMG_BITMAP_PTR_ORIGINAL] != NULL)
1457     SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_PTR_ORIGINAL]);
1458   else
1459     SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]);
1460 }
1461
1462 void FreeBitmapTextures(Bitmap **bitmaps)
1463 {
1464   if (bitmaps[IMG_BITMAP_PTR_ORIGINAL] != NULL)
1465     SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_PTR_ORIGINAL]);
1466   else
1467     SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]);
1468 }
1469
1470 void ScaleBitmap(Bitmap **bitmaps, int zoom_factor)
1471 {
1472   CreateScaledBitmaps(bitmaps, zoom_factor, 0, FALSE);
1473 }
1474
1475
1476 // ----------------------------------------------------------------------------
1477 // mouse pointer functions
1478 // ----------------------------------------------------------------------------
1479
1480 #define USE_ONE_PIXEL_PLAYFIELD_MOUSEPOINTER            0
1481
1482 // XPM image definitions
1483 static const char *cursor_image_none[] =
1484 {
1485   // width height num_colors chars_per_pixel
1486   "    16    16        3            1",
1487
1488   // colors
1489   "X c #000000",
1490   ". c #ffffff",
1491   "  c None",
1492
1493   // pixels
1494   "                ",
1495   "                ",
1496   "                ",
1497   "                ",
1498   "                ",
1499   "                ",
1500   "                ",
1501   "                ",
1502   "                ",
1503   "                ",
1504   "                ",
1505   "                ",
1506   "                ",
1507   "                ",
1508   "                ",
1509   "                ",
1510
1511   // hot spot
1512   "0,0"
1513 };
1514
1515 #if USE_ONE_PIXEL_PLAYFIELD_MOUSEPOINTER
1516 static const char *cursor_image_dot[] =
1517 {
1518   // width height num_colors chars_per_pixel
1519   "    16    16        3            1",
1520
1521   // colors
1522   "X c #000000",
1523   ". c #ffffff",
1524   "  c None",
1525
1526   // pixels
1527   " X              ",
1528   "X.X             ",
1529   " X              ",
1530   "                ",
1531   "                ",
1532   "                ",
1533   "                ",
1534   "                ",
1535   "                ",
1536   "                ",
1537   "                ",
1538   "                ",
1539   "                ",
1540   "                ",
1541   "                ",
1542   "                ",
1543
1544   // hot spot
1545   "1,1"
1546 };
1547 static const char **cursor_image_playfield = cursor_image_dot;
1548 #else
1549 // some people complained about a "white dot" on the screen and thought it
1550 // was a graphical error... OK, let's just remove the whole pointer :-)
1551 static const char **cursor_image_playfield = cursor_image_none;
1552 #endif
1553
1554 static const int cursor_bit_order = BIT_ORDER_MSB;
1555
1556 static struct MouseCursorInfo *get_cursor_from_image(const char **image)
1557 {
1558   struct MouseCursorInfo *cursor;
1559   boolean bit_order_msb = (cursor_bit_order == BIT_ORDER_MSB);
1560   int header_lines = 4;
1561   int x, y, i;
1562
1563   cursor = checked_calloc(sizeof(struct MouseCursorInfo));
1564
1565   sscanf(image[0], " %d %d ", &cursor->width, &cursor->height);
1566
1567   i = -1;
1568   for (y = 0; y < cursor->width; y++)
1569   {
1570     for (x = 0; x < cursor->height; x++)
1571     {
1572       int bit_nr = x % 8;
1573       int bit_mask = 0x01 << (bit_order_msb ? 7 - bit_nr : bit_nr );
1574
1575       if (bit_nr == 0)
1576       {
1577         i++;
1578         cursor->data[i] = cursor->mask[i] = 0;
1579       }
1580
1581       switch (image[header_lines + y][x])
1582       {
1583         case 'X':
1584           cursor->data[i] |= bit_mask;
1585           cursor->mask[i] |= bit_mask;
1586           break;
1587
1588         case '.':
1589           cursor->mask[i] |= bit_mask;
1590           break;
1591
1592         case ' ':
1593           break;
1594       }
1595     }
1596   }
1597
1598   sscanf(image[header_lines + y], "%d,%d", &cursor->hot_x, &cursor->hot_y);
1599
1600   return cursor;
1601 }
1602
1603 void SetMouseCursor(int mode)
1604 {
1605   static struct MouseCursorInfo *cursor_none = NULL;
1606   static struct MouseCursorInfo *cursor_playfield = NULL;
1607   struct MouseCursorInfo *cursor_new;
1608   int mode_final = mode;
1609
1610   if (cursor_none == NULL)
1611     cursor_none = get_cursor_from_image(cursor_image_none);
1612
1613   if (cursor_playfield == NULL)
1614     cursor_playfield = get_cursor_from_image(cursor_image_playfield);
1615
1616   if (gfx.cursor_mode_override != CURSOR_UNDEFINED)
1617     mode_final = gfx.cursor_mode_override;
1618
1619   cursor_new = (mode_final == CURSOR_DEFAULT   ? NULL :
1620                 mode_final == CURSOR_NONE      ? cursor_none :
1621                 mode_final == CURSOR_PLAYFIELD ? cursor_playfield : NULL);
1622
1623   SDLSetMouseCursor(cursor_new);
1624
1625   gfx.cursor_mode = mode;
1626   gfx.cursor_mode_final = mode_final;
1627 }
1628
1629 void UpdateRawMousePosition(int mouse_x, int mouse_y)
1630 {
1631   // mouse events do not contain logical screen size corrections yet
1632   SDLCorrectRawMousePosition(&mouse_x, &mouse_y);
1633
1634   mouse_x -= video.screen_xoffset;
1635   mouse_y -= video.screen_yoffset;
1636
1637   gfx.mouse_x = mouse_x;
1638   gfx.mouse_y = mouse_y;
1639 }
1640
1641 void UpdateMousePosition(void)
1642 {
1643   int mouse_x, mouse_y;
1644
1645   SDL_PumpEvents();
1646   SDL_GetMouseState(&mouse_x, &mouse_y);
1647
1648   UpdateRawMousePosition(mouse_x, mouse_y);
1649 }
1650
1651
1652 // ============================================================================
1653 // audio functions
1654 // ============================================================================
1655
1656 void OpenAudio(void)
1657 {
1658   // always start with reliable default values
1659   audio.sound_available = FALSE;
1660   audio.music_available = FALSE;
1661   audio.loops_available = FALSE;
1662
1663   audio.sound_enabled = FALSE;
1664   audio.sound_deactivated = FALSE;
1665
1666   audio.mixer_pipe[0] = audio.mixer_pipe[1] = 0;
1667   audio.mixer_pid = 0;
1668   audio.device_name = NULL;
1669   audio.device_fd = -1;
1670
1671   audio.sample_rate = DEFAULT_AUDIO_SAMPLE_RATE;
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)
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.count = 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.count = 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 boolean PendingEscapeKeyEvent(void)
1847 {
1848   if (PendingEvent())
1849   {
1850     Event event;
1851
1852     // check if any key press event is pending
1853     if (SDL_PeepEvents(&event, 1, SDL_PEEKEVENT, SDL_KEYDOWN, SDL_KEYDOWN) != 1)
1854       return FALSE;
1855
1856     // check if pressed key is "Escape" key
1857     if (event.key.keysym.sym == KSYM_Escape)
1858       return TRUE;
1859   }
1860
1861   return FALSE;
1862 }
1863
1864
1865 // ============================================================================
1866 // joystick functions
1867 // ============================================================================
1868
1869 void InitJoysticks(void)
1870 {
1871   int i;
1872
1873 #if defined(NO_JOYSTICK)
1874   return;       // joysticks generally deactivated by compile-time directive
1875 #endif
1876
1877   // always start with reliable default values
1878   joystick.status = JOYSTICK_NOT_AVAILABLE;
1879   for (i = 0; i < MAX_PLAYERS; i++)
1880     joystick.nr[i] = -1;                // no joystick configured
1881
1882   SDLInitJoysticks();
1883 }
1884
1885 boolean ReadJoystick(int nr, int *x, int *y, boolean *b1, boolean *b2)
1886 {
1887   return SDLReadJoystick(nr, x, y, b1, b2);
1888 }
1889
1890 boolean CheckJoystickOpened(int nr)
1891 {
1892   return SDLCheckJoystickOpened(nr);
1893 }
1894
1895 void ClearJoystickState(void)
1896 {
1897   SDLClearJoystickState();
1898 }
1899
1900
1901 // ============================================================================
1902 // Emscripten functions
1903 // ============================================================================
1904
1905 void InitEmscriptenFilesystem(void)
1906 {
1907 #if defined(PLATFORM_EMSCRIPTEN)
1908   EM_ASM
1909   ({
1910     dir = UTF8ToString($0);
1911
1912     Module.sync_done = 0;
1913
1914     FS.mkdir(dir);                      // create persistent data directory
1915     FS.mount(IDBFS, {}, dir);           // mount with IDBFS filesystem type
1916     FS.syncfs(true, function(err)       // sync persistent data into memory
1917     {
1918       assert(!err);
1919       Module.sync_done = 1;
1920     });
1921   }, PERSISTENT_DIRECTORY);
1922
1923   // wait for persistent data to be synchronized to memory
1924   while (emscripten_run_script_int("Module.sync_done") == 0)
1925     Delay(20);
1926 #endif
1927 }
1928
1929 void SyncEmscriptenFilesystem(void)
1930 {
1931 #if defined(PLATFORM_EMSCRIPTEN)
1932   EM_ASM
1933   (
1934     FS.syncfs(function(err)
1935     {
1936       assert(!err);
1937     });
1938   );
1939 #endif
1940 }