added optional button to restart game (door, panel and touch variants)
[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 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   SDLCreateBitmapContent(new_bitmap, 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 (DrawingDeactivated(dst_x, dst_y))
942     return;
943
944   sysCopyArea(src_bitmap, dst_bitmap, src_x, src_y, width, height,
945               dst_x, dst_y, BLIT_MASKED);
946 }
947
948 void BlitBitmapOnBackground(Bitmap *src_bitmap, Bitmap *dst_bitmap,
949                             int src_x, int src_y, int width, int height,
950                             int dst_x, int dst_y)
951 {
952   if (DrawingOnBackground(dst_x, dst_y))
953   {
954     // draw background
955     BlitBitmap(gfx.background_bitmap, dst_bitmap, dst_x, dst_y, width, height,
956                dst_x, dst_y);
957
958     // draw foreground
959     BlitBitmapMasked(src_bitmap, dst_bitmap, src_x, src_y, width, height,
960                      dst_x, dst_y);
961   }
962   else
963     BlitBitmap(src_bitmap, dst_bitmap, src_x, src_y, width, height,
964                dst_x, dst_y);
965 }
966
967 void BlitTexture(Bitmap *bitmap,
968                 int src_x, int src_y, int width, int height,
969                 int dst_x, int dst_y)
970 {
971   if (bitmap == NULL)
972     return;
973
974   SDLBlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y,
975                  BLIT_OPAQUE);
976 }
977
978 void BlitTextureMasked(Bitmap *bitmap,
979                        int src_x, int src_y, int width, int height,
980                        int dst_x, int dst_y)
981 {
982   if (bitmap == NULL)
983     return;
984
985   SDLBlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y,
986                  BLIT_MASKED);
987 }
988
989 void BlitToScreen(Bitmap *bitmap,
990                   int src_x, int src_y, int width, int height,
991                   int dst_x, int dst_y)
992 {
993   if (bitmap == NULL)
994     return;
995
996   if (video.screen_rendering_mode == SPECIAL_RENDERING_BITMAP)
997     BlitBitmap(bitmap, gfx.final_screen_bitmap, src_x, src_y,
998                width, height, dst_x, dst_y);
999   else
1000     BlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y);
1001 }
1002
1003 void BlitToScreenMasked(Bitmap *bitmap,
1004                         int src_x, int src_y, int width, int height,
1005                         int dst_x, int dst_y)
1006 {
1007   if (bitmap == NULL)
1008     return;
1009
1010   if (video.screen_rendering_mode == SPECIAL_RENDERING_BITMAP)
1011     BlitBitmapMasked(bitmap, gfx.final_screen_bitmap, src_x, src_y,
1012                      width, height, dst_x, dst_y);
1013   else
1014     BlitTextureMasked(bitmap, src_x, src_y, width, height, dst_x, dst_y);
1015 }
1016
1017 void DrawSimpleWhiteLine(Bitmap *bitmap, int from_x, int from_y,
1018                          int to_x, int to_y)
1019 {
1020   SDLDrawSimpleLine(bitmap, from_x, from_y, to_x, to_y, WHITE_PIXEL);
1021 }
1022
1023 static void DrawLine(Bitmap *bitmap, int from_x, int from_y,
1024                      int to_x, int to_y, Pixel pixel, int line_width)
1025 {
1026   int x, y;
1027
1028   if (program.headless)
1029     return;
1030
1031   for (x = 0; x < line_width; x++)
1032   {
1033     for (y = 0; y < line_width; y++)
1034     {
1035       int dx = x - line_width / 2;
1036       int dy = y - line_width / 2;
1037
1038       if ((x == 0 && y == 0) ||
1039           (x == 0 && y == line_width - 1) ||
1040           (x == line_width - 1 && y == 0) ||
1041           (x == line_width - 1 && y == line_width - 1))
1042         continue;
1043
1044       SDLDrawLine(bitmap,
1045                   from_x + dx, from_y + dy, to_x + dx, to_y + dy, pixel);
1046     }
1047   }
1048 }
1049
1050 void DrawLines(Bitmap *bitmap, struct XY *points, int num_points, Pixel pixel)
1051 {
1052   int line_width = 4;
1053   int i;
1054
1055   for (i = 0; i < num_points - 1; i++)
1056     DrawLine(bitmap, points[i].x, points[i].y,
1057              points[i + 1].x, points[i + 1].y, pixel, line_width);
1058
1059   /*
1060   SDLDrawLines(bitmap->surface, points, num_points, pixel);
1061   */
1062 }
1063
1064 Pixel GetPixel(Bitmap *bitmap, int x, int y)
1065 {
1066   if (program.headless)
1067     return BLACK_PIXEL;
1068
1069   if (x < 0 || x >= bitmap->width ||
1070       y < 0 || y >= bitmap->height)
1071     return BLACK_PIXEL;
1072
1073   return SDLGetPixel(bitmap, x, y);
1074 }
1075
1076 Pixel GetPixelFromRGB(Bitmap *bitmap, unsigned int color_r,
1077                       unsigned int color_g, unsigned int color_b)
1078 {
1079   if (program.headless)
1080     return BLACK_PIXEL;
1081
1082   return SDL_MapRGB(bitmap->surface->format, color_r, color_g, color_b);
1083 }
1084
1085 void KeyboardAutoRepeatOn(void)
1086 {
1087   keyrepeat_status = TRUE;
1088 }
1089
1090 void KeyboardAutoRepeatOff(void)
1091 {
1092   keyrepeat_status = FALSE;
1093 }
1094
1095 boolean SetVideoMode(boolean fullscreen)
1096 {
1097   return SDLSetVideoMode(fullscreen);
1098 }
1099
1100 void SetVideoFrameDelay(unsigned int frame_delay_value)
1101 {
1102   video.frame_delay.value = frame_delay_value;
1103 }
1104
1105 unsigned int GetVideoFrameDelay(void)
1106 {
1107   return video.frame_delay.value;
1108 }
1109
1110 boolean ChangeVideoModeIfNeeded(boolean fullscreen)
1111 {
1112   if ((fullscreen && !video.fullscreen_enabled && video.fullscreen_available)||
1113       (!fullscreen && video.fullscreen_enabled))
1114     fullscreen = SetVideoMode(fullscreen);
1115
1116   return fullscreen;
1117 }
1118
1119 Bitmap *LoadImage(char *filename)
1120 {
1121   Bitmap *new_bitmap;
1122
1123   new_bitmap = SDLLoadImage(filename);
1124
1125   if (new_bitmap)
1126     new_bitmap->source_filename = getStringCopy(filename);
1127
1128   return new_bitmap;
1129 }
1130
1131 Bitmap *LoadCustomImage(char *basename)
1132 {
1133   char *filename = getCustomImageFilename(basename);
1134   Bitmap *new_bitmap;
1135
1136   if (filename == NULL)
1137     Fail("LoadCustomImage(): cannot find file '%s'", basename);
1138
1139   if ((new_bitmap = LoadImage(filename)) == NULL)
1140     Fail("LoadImage('%s') failed", basename);
1141
1142   return new_bitmap;
1143 }
1144
1145 void ReloadCustomImage(Bitmap *bitmap, char *basename)
1146 {
1147   char *filename = getCustomImageFilename(basename);
1148   Bitmap *new_bitmap;
1149
1150   if (filename == NULL)         // (should never happen)
1151   {
1152     Warn("ReloadCustomImage(): cannot find file '%s'", basename);
1153
1154     return;
1155   }
1156
1157   if (strEqual(filename, bitmap->source_filename))
1158   {
1159     // The old and new image are the same (have the same filename and path).
1160     // This usually means that this image does not exist in this graphic set
1161     // and a fallback to the existing image is done.
1162
1163     return;
1164   }
1165
1166   if ((new_bitmap = LoadImage(filename)) == NULL)
1167   {
1168     Warn("LoadImage('%s') failed", basename);
1169
1170     return;
1171   }
1172
1173   if (bitmap->width != new_bitmap->width ||
1174       bitmap->height != new_bitmap->height)
1175   {
1176     Warn("ReloadCustomImage: new image '%s' has wrong dimensions",
1177           filename);
1178
1179     FreeBitmap(new_bitmap);
1180
1181     return;
1182   }
1183
1184   TransferBitmapPointers(new_bitmap, bitmap);
1185   free(new_bitmap);
1186 }
1187
1188 Bitmap *ZoomBitmap(Bitmap *src_bitmap, int zoom_width, int zoom_height)
1189 {
1190   return SDLZoomBitmap(src_bitmap, zoom_width, zoom_height);
1191 }
1192
1193 void ReCreateGameTileSizeBitmap(Bitmap **bitmaps)
1194 {
1195   if (bitmaps[IMG_BITMAP_CUSTOM])
1196   {
1197     // check if original sized bitmap points to custom sized bitmap
1198     if (bitmaps[IMG_BITMAP_PTR_ORIGINAL] == bitmaps[IMG_BITMAP_CUSTOM])
1199     {
1200       SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_PTR_ORIGINAL]);
1201
1202       // keep pointer of previous custom size bitmap
1203       bitmaps[IMG_BITMAP_OTHER] = bitmaps[IMG_BITMAP_CUSTOM];
1204
1205       // set original bitmap pointer to scaled original bitmap of other size
1206       bitmaps[IMG_BITMAP_PTR_ORIGINAL] = bitmaps[IMG_BITMAP_OTHER];
1207
1208       SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_PTR_ORIGINAL]);
1209     }
1210     else
1211     {
1212       FreeBitmap(bitmaps[IMG_BITMAP_CUSTOM]);
1213     }
1214
1215     bitmaps[IMG_BITMAP_CUSTOM] = NULL;
1216   }
1217
1218   if (gfx.game_tile_size == gfx.standard_tile_size)
1219   {
1220     // set game bitmap pointer to standard sized bitmap (already existing)
1221     bitmaps[IMG_BITMAP_PTR_GAME] = bitmaps[IMG_BITMAP_STANDARD];
1222
1223     return;
1224   }
1225
1226   Bitmap *bitmap = bitmaps[IMG_BITMAP_STANDARD];
1227   int width  = bitmap->width  * gfx.game_tile_size / gfx.standard_tile_size;;
1228   int height = bitmap->height * gfx.game_tile_size / gfx.standard_tile_size;;
1229
1230   bitmaps[IMG_BITMAP_CUSTOM] = ZoomBitmap(bitmap, width, height);
1231
1232   // set game bitmap pointer to custom sized bitmap (newly created)
1233   bitmaps[IMG_BITMAP_PTR_GAME] = bitmaps[IMG_BITMAP_CUSTOM];
1234 }
1235
1236 static void CreateScaledBitmaps(Bitmap **bitmaps, int zoom_factor,
1237                                 int tile_size, boolean create_small_bitmaps)
1238 {
1239   Bitmap *old_bitmap = bitmaps[IMG_BITMAP_STANDARD];
1240   Bitmap *tmp_bitmap_final = NULL;
1241   Bitmap *tmp_bitmap_0 = NULL;
1242   Bitmap *tmp_bitmap_1 = NULL;
1243   Bitmap *tmp_bitmap_2 = NULL;
1244   Bitmap *tmp_bitmap_4 = NULL;
1245   Bitmap *tmp_bitmap_8 = NULL;
1246   Bitmap *tmp_bitmap_16 = NULL;
1247   Bitmap *tmp_bitmap_32 = NULL;
1248   int width_final, height_final;
1249   int width_0, height_0;
1250   int width_1, height_1;
1251   int width_2, height_2;
1252   int width_4, height_4;
1253   int width_8, height_8;
1254   int width_16, height_16;
1255   int width_32, height_32;
1256   int old_width, old_height;
1257   int i;
1258
1259   print_timestamp_init("CreateScaledBitmaps");
1260
1261   old_width  = old_bitmap->width;
1262   old_height = old_bitmap->height;
1263
1264   // calculate new image dimensions for final image size
1265   width_final  = old_width  * zoom_factor;
1266   height_final = old_height * zoom_factor;
1267
1268   // get image with final size (this might require scaling up)
1269   // ("final" size may result in non-standard tile size image)
1270   if (zoom_factor != 1)
1271     tmp_bitmap_final = ZoomBitmap(old_bitmap, width_final, height_final);
1272   else
1273     tmp_bitmap_final = old_bitmap;
1274
1275   UPDATE_BUSY_STATE();
1276
1277   width_0  = width_1  = width_final;
1278   height_0 = height_1 = height_final;
1279
1280   tmp_bitmap_0 = tmp_bitmap_1 = tmp_bitmap_final;
1281
1282   if (create_small_bitmaps)
1283   {
1284     // check if we have a non-gameplay tile size image
1285     if (tile_size != gfx.game_tile_size)
1286     {
1287       // get image with gameplay tile size
1288       width_0  = width_final  * gfx.game_tile_size / tile_size;
1289       height_0 = height_final * gfx.game_tile_size / tile_size;
1290
1291       if (width_0 == old_width)
1292         tmp_bitmap_0 = old_bitmap;
1293       else if (width_0 == width_final)
1294         tmp_bitmap_0 = tmp_bitmap_final;
1295       else
1296         tmp_bitmap_0 = ZoomBitmap(old_bitmap, width_0, height_0);
1297
1298       UPDATE_BUSY_STATE();
1299     }
1300
1301     // check if we have a non-standard tile size image
1302     if (tile_size != gfx.standard_tile_size)
1303     {
1304       // get image with standard tile size
1305       width_1  = width_final  * gfx.standard_tile_size / tile_size;
1306       height_1 = height_final * gfx.standard_tile_size / tile_size;
1307
1308       if (width_1 == old_width)
1309         tmp_bitmap_1 = old_bitmap;
1310       else if (width_1 == width_final)
1311         tmp_bitmap_1 = tmp_bitmap_final;
1312       else if (width_1 == width_0)
1313         tmp_bitmap_1 = tmp_bitmap_0;
1314       else
1315         tmp_bitmap_1 = ZoomBitmap(old_bitmap, width_1, height_1);
1316
1317       UPDATE_BUSY_STATE();
1318     }
1319
1320     // calculate new image dimensions for small images
1321     width_2  = width_1  / 2;
1322     height_2 = height_1 / 2;
1323     width_4  = width_1  / 4;
1324     height_4 = height_1 / 4;
1325     width_8  = width_1  / 8;
1326     height_8 = height_1 / 8;
1327     width_16  = width_1  / 16;
1328     height_16 = height_1 / 16;
1329     width_32  = width_1  / 32;
1330     height_32 = height_1 / 32;
1331
1332     // get image with 1/2 of normal size (for use in the level editor)
1333     if (width_2 == old_width)
1334       tmp_bitmap_2 = old_bitmap;
1335     else
1336       tmp_bitmap_2 = ZoomBitmap(tmp_bitmap_1, width_2, height_2);
1337
1338     UPDATE_BUSY_STATE();
1339
1340     // get image with 1/4 of normal size (for use in the level editor)
1341     if (width_4 == old_width)
1342       tmp_bitmap_4 = old_bitmap;
1343     else
1344       tmp_bitmap_4 = ZoomBitmap(tmp_bitmap_2, width_4, height_4);
1345
1346     UPDATE_BUSY_STATE();
1347
1348     // get image with 1/8 of normal size (for use on the preview screen)
1349     if (width_8 == old_width)
1350       tmp_bitmap_8 = old_bitmap;
1351     else
1352       tmp_bitmap_8 = ZoomBitmap(tmp_bitmap_4, width_8, height_8);
1353
1354     UPDATE_BUSY_STATE();
1355
1356     // get image with 1/16 of normal size (for use on the preview screen)
1357     if (width_16 == old_width)
1358       tmp_bitmap_16 = old_bitmap;
1359     else
1360       tmp_bitmap_16 = ZoomBitmap(tmp_bitmap_8, width_16, height_16);
1361
1362     UPDATE_BUSY_STATE();
1363
1364     // get image with 1/32 of normal size (for use on the preview screen)
1365     if (width_32 == old_width)
1366       tmp_bitmap_32 = old_bitmap;
1367     else
1368       tmp_bitmap_32 = ZoomBitmap(tmp_bitmap_16, width_32, height_32);
1369
1370     UPDATE_BUSY_STATE();
1371
1372     bitmaps[IMG_BITMAP_32x32] = tmp_bitmap_1;
1373     bitmaps[IMG_BITMAP_16x16] = tmp_bitmap_2;
1374     bitmaps[IMG_BITMAP_8x8]   = tmp_bitmap_4;
1375     bitmaps[IMG_BITMAP_4x4]   = tmp_bitmap_8;
1376     bitmaps[IMG_BITMAP_2x2]   = tmp_bitmap_16;
1377     bitmaps[IMG_BITMAP_1x1]   = tmp_bitmap_32;
1378
1379     if (width_0 != width_1)
1380       bitmaps[IMG_BITMAP_CUSTOM] = tmp_bitmap_0;
1381
1382     if (bitmaps[IMG_BITMAP_CUSTOM])
1383       bitmaps[IMG_BITMAP_PTR_GAME] = bitmaps[IMG_BITMAP_CUSTOM];
1384     else
1385       bitmaps[IMG_BITMAP_PTR_GAME] = bitmaps[IMG_BITMAP_STANDARD];
1386
1387     // store the "final" (up-scaled) original bitmap, if not already stored
1388
1389     int tmp_bitmap_final_nr = -1;
1390
1391     for (i = 0; i < NUM_IMG_BITMAPS; i++)
1392       if (bitmaps[i] == tmp_bitmap_final)
1393         tmp_bitmap_final_nr = i;
1394
1395     if (tmp_bitmap_final_nr == -1)      // scaled original bitmap not stored
1396     {
1397       // store pointer of scaled original bitmap (not used for any other size)
1398       bitmaps[IMG_BITMAP_OTHER] = tmp_bitmap_final;
1399
1400       // set original bitmap pointer to scaled original bitmap of other size
1401       bitmaps[IMG_BITMAP_PTR_ORIGINAL] = bitmaps[IMG_BITMAP_OTHER];
1402     }
1403     else
1404     {
1405       // set original bitmap pointer to corresponding sized bitmap
1406       bitmaps[IMG_BITMAP_PTR_ORIGINAL] = bitmaps[tmp_bitmap_final_nr];
1407     }
1408
1409     // free the "old" (unscaled) original bitmap, if not already stored
1410
1411     boolean free_old_bitmap = TRUE;
1412
1413     for (i = 0; i < NUM_IMG_BITMAPS; i++)
1414       if (bitmaps[i] == old_bitmap)
1415         free_old_bitmap = FALSE;
1416
1417     if (free_old_bitmap)
1418     {
1419       // copy image filename from old to new standard sized bitmap
1420       bitmaps[IMG_BITMAP_STANDARD]->source_filename =
1421         getStringCopy(old_bitmap->source_filename);
1422
1423       FreeBitmap(old_bitmap);
1424     }
1425   }
1426   else
1427   {
1428     bitmaps[IMG_BITMAP_32x32] = tmp_bitmap_1;
1429
1430     // set original bitmap pointer to corresponding sized bitmap
1431     bitmaps[IMG_BITMAP_PTR_ORIGINAL] = bitmaps[IMG_BITMAP_32x32];
1432
1433     if (old_bitmap != tmp_bitmap_1)
1434       FreeBitmap(old_bitmap);
1435   }
1436
1437   UPDATE_BUSY_STATE();
1438
1439   print_timestamp_done("CreateScaledBitmaps");
1440 }
1441
1442 void CreateBitmapWithSmallBitmaps(Bitmap **bitmaps, int zoom_factor,
1443                                   int tile_size)
1444 {
1445   CreateScaledBitmaps(bitmaps, zoom_factor, tile_size, TRUE);
1446 }
1447
1448 void CreateBitmapTextures(Bitmap **bitmaps)
1449 {
1450   if (bitmaps[IMG_BITMAP_PTR_ORIGINAL] != NULL)
1451     SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_PTR_ORIGINAL]);
1452   else
1453     SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]);
1454 }
1455
1456 void FreeBitmapTextures(Bitmap **bitmaps)
1457 {
1458   if (bitmaps[IMG_BITMAP_PTR_ORIGINAL] != NULL)
1459     SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_PTR_ORIGINAL]);
1460   else
1461     SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]);
1462 }
1463
1464 void ScaleBitmap(Bitmap **bitmaps, int zoom_factor)
1465 {
1466   CreateScaledBitmaps(bitmaps, zoom_factor, 0, FALSE);
1467 }
1468
1469
1470 // ----------------------------------------------------------------------------
1471 // mouse pointer functions
1472 // ----------------------------------------------------------------------------
1473
1474 #define USE_ONE_PIXEL_PLAYFIELD_MOUSEPOINTER            0
1475
1476 // XPM image definitions
1477 static const char *cursor_image_none[] =
1478 {
1479   // width height num_colors chars_per_pixel
1480   "    16    16        3            1",
1481
1482   // colors
1483   "X c #000000",
1484   ". c #ffffff",
1485   "  c None",
1486
1487   // pixels
1488   "                ",
1489   "                ",
1490   "                ",
1491   "                ",
1492   "                ",
1493   "                ",
1494   "                ",
1495   "                ",
1496   "                ",
1497   "                ",
1498   "                ",
1499   "                ",
1500   "                ",
1501   "                ",
1502   "                ",
1503   "                ",
1504
1505   // hot spot
1506   "0,0"
1507 };
1508
1509 #if USE_ONE_PIXEL_PLAYFIELD_MOUSEPOINTER
1510 static const char *cursor_image_dot[] =
1511 {
1512   // width height num_colors chars_per_pixel
1513   "    16    16        3            1",
1514
1515   // colors
1516   "X c #000000",
1517   ". c #ffffff",
1518   "  c None",
1519
1520   // pixels
1521   " X              ",
1522   "X.X             ",
1523   " X              ",
1524   "                ",
1525   "                ",
1526   "                ",
1527   "                ",
1528   "                ",
1529   "                ",
1530   "                ",
1531   "                ",
1532   "                ",
1533   "                ",
1534   "                ",
1535   "                ",
1536   "                ",
1537
1538   // hot spot
1539   "1,1"
1540 };
1541 static const char **cursor_image_playfield = cursor_image_dot;
1542 #else
1543 // some people complained about a "white dot" on the screen and thought it
1544 // was a graphical error... OK, let's just remove the whole pointer :-)
1545 static const char **cursor_image_playfield = cursor_image_none;
1546 #endif
1547
1548 static const int cursor_bit_order = BIT_ORDER_MSB;
1549
1550 static struct MouseCursorInfo *get_cursor_from_image(const char **image)
1551 {
1552   struct MouseCursorInfo *cursor;
1553   boolean bit_order_msb = (cursor_bit_order == BIT_ORDER_MSB);
1554   int header_lines = 4;
1555   int x, y, i;
1556
1557   cursor = checked_calloc(sizeof(struct MouseCursorInfo));
1558
1559   sscanf(image[0], " %d %d ", &cursor->width, &cursor->height);
1560
1561   i = -1;
1562   for (y = 0; y < cursor->width; y++)
1563   {
1564     for (x = 0; x < cursor->height; x++)
1565     {
1566       int bit_nr = x % 8;
1567       int bit_mask = 0x01 << (bit_order_msb ? 7 - bit_nr : bit_nr );
1568
1569       if (bit_nr == 0)
1570       {
1571         i++;
1572         cursor->data[i] = cursor->mask[i] = 0;
1573       }
1574
1575       switch (image[header_lines + y][x])
1576       {
1577         case 'X':
1578           cursor->data[i] |= bit_mask;
1579           cursor->mask[i] |= bit_mask;
1580           break;
1581
1582         case '.':
1583           cursor->mask[i] |= bit_mask;
1584           break;
1585
1586         case ' ':
1587           break;
1588       }
1589     }
1590   }
1591
1592   sscanf(image[header_lines + y], "%d,%d", &cursor->hot_x, &cursor->hot_y);
1593
1594   return cursor;
1595 }
1596
1597 void SetMouseCursor(int mode)
1598 {
1599   static struct MouseCursorInfo *cursor_none = NULL;
1600   static struct MouseCursorInfo *cursor_playfield = NULL;
1601   struct MouseCursorInfo *cursor_new;
1602   int mode_final = mode;
1603
1604   if (cursor_none == NULL)
1605     cursor_none = get_cursor_from_image(cursor_image_none);
1606
1607   if (cursor_playfield == NULL)
1608     cursor_playfield = get_cursor_from_image(cursor_image_playfield);
1609
1610   if (gfx.cursor_mode_override != CURSOR_UNDEFINED)
1611     mode_final = gfx.cursor_mode_override;
1612
1613   cursor_new = (mode_final == CURSOR_DEFAULT   ? NULL :
1614                 mode_final == CURSOR_NONE      ? cursor_none :
1615                 mode_final == CURSOR_PLAYFIELD ? cursor_playfield : NULL);
1616
1617   SDLSetMouseCursor(cursor_new);
1618
1619   gfx.cursor_mode = mode;
1620   gfx.cursor_mode_final = mode_final;
1621 }
1622
1623 void UpdateRawMousePosition(int mouse_x, int mouse_y)
1624 {
1625   // mouse events do not contain logical screen size corrections yet
1626   SDLCorrectRawMousePosition(&mouse_x, &mouse_y);
1627
1628   mouse_x -= video.screen_xoffset;
1629   mouse_y -= video.screen_yoffset;
1630
1631   gfx.mouse_x = mouse_x;
1632   gfx.mouse_y = mouse_y;
1633 }
1634
1635 void UpdateMousePosition(void)
1636 {
1637   int mouse_x, mouse_y;
1638
1639   SDL_PumpEvents();
1640   SDL_GetMouseState(&mouse_x, &mouse_y);
1641
1642   UpdateRawMousePosition(mouse_x, mouse_y);
1643 }
1644
1645
1646 // ============================================================================
1647 // audio functions
1648 // ============================================================================
1649
1650 void OpenAudio(void)
1651 {
1652   // always start with reliable default values
1653   audio.sound_available = FALSE;
1654   audio.music_available = FALSE;
1655   audio.loops_available = FALSE;
1656
1657   audio.sound_enabled = FALSE;
1658   audio.sound_deactivated = FALSE;
1659
1660   audio.mixer_pipe[0] = audio.mixer_pipe[1] = 0;
1661   audio.mixer_pid = 0;
1662   audio.device_name = NULL;
1663   audio.device_fd = -1;
1664
1665   audio.num_channels = 0;
1666   audio.music_channel = 0;
1667   audio.first_sound_channel = 0;
1668
1669   SDLOpenAudio();
1670 }
1671
1672 void CloseAudio(void)
1673 {
1674   SDLCloseAudio();
1675
1676   audio.sound_enabled = FALSE;
1677 }
1678
1679 void SetAudioMode(boolean enabled)
1680 {
1681   if (!audio.sound_available)
1682     return;
1683
1684   audio.sound_enabled = enabled;
1685 }
1686
1687
1688 // ============================================================================
1689 // event functions
1690 // ============================================================================
1691
1692 void InitEventFilter(EventFilter filter_function)
1693 {
1694   SDL_SetEventFilter(filter_function, NULL);
1695 }
1696
1697 boolean PendingEvent(void)
1698 {
1699   return (SDL_PollEvent(NULL) ? TRUE : FALSE);
1700 }
1701
1702 void WaitEvent(Event *event)
1703 {
1704   SDLWaitEvent(event);
1705 }
1706
1707 void PeekEvent(Event *event)
1708 {
1709   SDL_PeepEvents(event, 1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT);
1710 }
1711
1712 void PumpEvents(void)
1713 {
1714   SDL_PumpEvents();
1715 }
1716
1717 void CheckQuitEvent(void)
1718 {
1719   if (SDL_QuitRequested())
1720     program.exit_function(0);
1721 }
1722
1723 Key GetEventKey(KeyEvent *event)
1724 {
1725   // key up/down events in SDL2 do not return text characters anymore
1726   return event->keysym.sym;
1727 }
1728
1729 KeyMod HandleKeyModState(Key key, int key_status)
1730 {
1731   static KeyMod current_modifiers = KMOD_None;
1732
1733   if (key != KSYM_UNDEFINED)    // new key => check for modifier key change
1734   {
1735     KeyMod new_modifier = KMOD_None;
1736
1737     switch (key)
1738     {
1739       case KSYM_Shift_L:
1740         new_modifier = KMOD_Shift_L;
1741         break;
1742       case KSYM_Shift_R:
1743         new_modifier = KMOD_Shift_R;
1744         break;
1745       case KSYM_Control_L:
1746         new_modifier = KMOD_Control_L;
1747         break;
1748       case KSYM_Control_R:
1749         new_modifier = KMOD_Control_R;
1750         break;
1751       case KSYM_Meta_L:
1752         new_modifier = KMOD_Meta_L;
1753         break;
1754       case KSYM_Meta_R:
1755         new_modifier = KMOD_Meta_R;
1756         break;
1757       case KSYM_Alt_L:
1758         new_modifier = KMOD_Alt_L;
1759         break;
1760       case KSYM_Alt_R:
1761         new_modifier = KMOD_Alt_R;
1762         break;
1763       default:
1764         break;
1765     }
1766
1767     if (key_status == KEY_PRESSED)
1768       current_modifiers |= new_modifier;
1769     else
1770       current_modifiers &= ~new_modifier;
1771   }
1772
1773   return current_modifiers;
1774 }
1775
1776 KeyMod GetKeyModState(void)
1777 {
1778   return (KeyMod)SDL_GetModState();
1779 }
1780
1781 KeyMod GetKeyModStateFromEvents(void)
1782 {
1783   /* always use key modifier state as tracked from key events (this is needed
1784      if the modifier key event was injected into the event queue, but the key
1785      was not really pressed on keyboard -- SDL_GetModState() seems to directly
1786      query the keys as held pressed on the keyboard) -- this case is currently
1787      only used to filter out clipboard insert events from "True X-Mouse" tool */
1788
1789   return HandleKeyModState(KSYM_UNDEFINED, 0);
1790 }
1791
1792 void StartTextInput(int x, int y, int width, int height)
1793 {
1794   textinput_status = TRUE;
1795
1796 #if defined(HAS_SCREEN_KEYBOARD)
1797   SDL_StartTextInput();
1798
1799   if (y + height > SCREEN_KEYBOARD_POS(video.height))
1800   {
1801     video.shifted_up_pos = y + height - SCREEN_KEYBOARD_POS(video.height);
1802     video.shifted_up_delay.count = SDL_GetTicks();
1803     video.shifted_up = TRUE;
1804   }
1805 #endif
1806 }
1807
1808 void StopTextInput(void)
1809 {
1810   textinput_status = FALSE;
1811
1812 #if defined(HAS_SCREEN_KEYBOARD)
1813   SDL_StopTextInput();
1814
1815   if (video.shifted_up)
1816   {
1817     video.shifted_up_pos = 0;
1818     video.shifted_up_delay.count = SDL_GetTicks();
1819     video.shifted_up = FALSE;
1820   }
1821 #endif
1822 }
1823
1824 void PushUserEvent(int code, int value1, int value2)
1825 {
1826   UserEvent event;
1827
1828   SDL_memset(&event, 0, sizeof(event));
1829
1830   event.type = EVENT_USER;
1831   event.code = code;
1832   event.value1 = value1;
1833   event.value2 = value2;
1834
1835   SDL_PushEvent((SDL_Event *)&event);
1836 }
1837
1838 boolean PendingEscapeKeyEvent(void)
1839 {
1840   if (PendingEvent())
1841   {
1842     Event event;
1843
1844     // check if any key press event is pending
1845     if (SDL_PeepEvents(&event, 1, SDL_PEEKEVENT, SDL_KEYDOWN, SDL_KEYDOWN) != 1)
1846       return FALSE;
1847
1848     // check if pressed key is "Escape" key
1849     if (event.key.keysym.sym == KSYM_Escape)
1850       return TRUE;
1851   }
1852
1853   return FALSE;
1854 }
1855
1856
1857 // ============================================================================
1858 // joystick functions
1859 // ============================================================================
1860
1861 void InitJoysticks(void)
1862 {
1863   int i;
1864
1865 #if defined(NO_JOYSTICK)
1866   return;       // joysticks generally deactivated by compile-time directive
1867 #endif
1868
1869   // always start with reliable default values
1870   joystick.status = JOYSTICK_NOT_AVAILABLE;
1871   for (i = 0; i < MAX_PLAYERS; i++)
1872     joystick.nr[i] = -1;                // no joystick configured
1873
1874   SDLInitJoysticks();
1875 }
1876
1877 boolean ReadJoystick(int nr, int *x, int *y, boolean *b1, boolean *b2)
1878 {
1879   return SDLReadJoystick(nr, x, y, b1, b2);
1880 }
1881
1882 boolean CheckJoystickOpened(int nr)
1883 {
1884   return SDLCheckJoystickOpened(nr);
1885 }
1886
1887 void ClearJoystickState(void)
1888 {
1889   SDLClearJoystickState();
1890 }
1891
1892
1893 // ============================================================================
1894 // Emscripten functions
1895 // ============================================================================
1896
1897 void InitEmscriptenFilesystem(void)
1898 {
1899 #if defined(PLATFORM_EMSCRIPTEN)
1900   EM_ASM
1901   ({
1902     dir = UTF8ToString($0);
1903
1904     Module.sync_done = 0;
1905
1906     FS.mkdir(dir);                      // create persistent data directory
1907     FS.mount(IDBFS, {}, dir);           // mount with IDBFS filesystem type
1908     FS.syncfs(true, function(err)       // sync persistent data into memory
1909     {
1910       assert(!err);
1911       Module.sync_done = 1;
1912     });
1913   }, PERSISTENT_DIRECTORY);
1914
1915   // wait for persistent data to be synchronized to memory
1916   while (emscripten_run_script_int("Module.sync_done") == 0)
1917     Delay(20);
1918 #endif
1919 }
1920
1921 void SyncEmscriptenFilesystem(void)
1922 {
1923 #if defined(PLATFORM_EMSCRIPTEN)
1924   EM_ASM
1925   (
1926     FS.syncfs(function(err)
1927     {
1928       assert(!err);
1929     });
1930   );
1931 #endif
1932 }