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