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