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