dc7dba8b2db2c763e19ee8cc274c38d73cc50b06
[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_counter = 0;
574   video.frame_delay = 0;
575   video.frame_delay_value = GAME_FRAME_DELAY;
576
577   video.shifted_up = FALSE;
578   video.shifted_up_pos = 0;
579   video.shifted_up_pos_last = 0;
580   video.shifted_up_delay = 0;
581   video.shifted_up_delay_value = ONE_SECOND_DELAY / 4;
582
583   SDLInitVideoBuffer(fullscreen);
584
585   video.initialized = !program.headless;
586
587   drawto = backbuffer;
588 }
589
590 static void FreeBitmapPointers(Bitmap *bitmap)
591 {
592   if (bitmap == NULL)
593     return;
594
595   SDLFreeBitmapPointers(bitmap);
596
597   checked_free(bitmap->source_filename);
598   bitmap->source_filename = NULL;
599 }
600
601 static void TransferBitmapPointers(Bitmap *src_bitmap,
602                                    Bitmap *dst_bitmap)
603 {
604   if (src_bitmap == NULL || dst_bitmap == NULL)
605     return;
606
607   FreeBitmapPointers(dst_bitmap);
608
609   *dst_bitmap = *src_bitmap;
610 }
611
612 void FreeBitmap(Bitmap *bitmap)
613 {
614   if (bitmap == NULL)
615     return;
616
617   FreeBitmapPointers(bitmap);
618
619   free(bitmap);
620 }
621
622 Bitmap *CreateBitmapStruct(void)
623 {
624   return checked_calloc(sizeof(Bitmap));
625 }
626
627 Bitmap *CreateBitmap(int width, int height, int depth)
628 {
629   Bitmap *new_bitmap = CreateBitmapStruct();
630   int real_width  = MAX(1, width);      // prevent zero bitmap width
631   int real_height = MAX(1, height);     // prevent zero bitmap height
632   int real_depth  = GetRealDepth(depth);
633
634   SDLCreateBitmapContent(new_bitmap, real_width, real_height, real_depth);
635
636   new_bitmap->width  = real_width;
637   new_bitmap->height = real_height;
638
639   return new_bitmap;
640 }
641
642 void ReCreateBitmap(Bitmap **bitmap, int width, int height)
643 {
644   if (*bitmap != NULL)
645   {
646     // if new bitmap size fits into old one, no need to re-create it
647     if (width  <= (*bitmap)->width &&
648         height <= (*bitmap)->height)
649       return;
650
651     // else adjust size so that old and new bitmap size fit into it
652     width  = MAX(width,  (*bitmap)->width);
653     height = MAX(height, (*bitmap)->height);
654   }
655
656   Bitmap *new_bitmap = CreateBitmap(width, height, DEFAULT_DEPTH);
657
658   if (*bitmap == NULL)
659   {
660     *bitmap = new_bitmap;
661   }
662   else
663   {
664     TransferBitmapPointers(new_bitmap, *bitmap);
665     free(new_bitmap);
666   }
667 }
668
669 #if 0
670 static void CloseWindow(DrawWindow *window)
671 {
672 }
673 #endif
674
675 void SetRedrawMaskFromArea(int x, int y, int width, int height)
676 {
677   int x1 = x;
678   int y1 = y;
679   int x2 = x + width - 1;
680   int y2 = y + height - 1;
681
682   if (width == 0 || height == 0)
683     return;
684
685   if (IN_GFX_FIELD_FULL(x1, y1) && IN_GFX_FIELD_FULL(x2, y2))
686     redraw_mask |= REDRAW_FIELD;
687   else if (IN_GFX_DOOR_1(x1, y1) && IN_GFX_DOOR_1(x2, y2))
688     redraw_mask |= REDRAW_DOOR_1;
689   else if (IN_GFX_DOOR_2(x1, y1) && IN_GFX_DOOR_2(x2, y2))
690     redraw_mask |= REDRAW_DOOR_2;
691   else if (IN_GFX_DOOR_3(x1, y1) && IN_GFX_DOOR_3(x2, y2))
692     redraw_mask |= REDRAW_DOOR_3;
693   else
694     redraw_mask = REDRAW_ALL;
695 }
696
697 static boolean CheckDrawingArea(int x, int y, int width, int height,
698                                 int draw_mask)
699 {
700   if (draw_mask == REDRAW_NONE)
701     return FALSE;
702
703   if (draw_mask & REDRAW_ALL)
704     return TRUE;
705
706   if ((draw_mask & REDRAW_FIELD) && IN_GFX_FIELD_FULL(x, y))
707     return TRUE;
708
709   if ((draw_mask & REDRAW_DOOR_1) && IN_GFX_DOOR_1(x, y))
710     return TRUE;
711
712   if ((draw_mask & REDRAW_DOOR_2) && IN_GFX_DOOR_2(x, y))
713     return TRUE;
714
715   if ((draw_mask & REDRAW_DOOR_3) && IN_GFX_DOOR_3(x, y))
716     return TRUE;
717
718   return FALSE;
719 }
720
721 boolean DrawingDeactivatedField(void)
722 {
723   if (program.headless)
724     return TRUE;
725
726   if (gfx.draw_deactivation_mask & REDRAW_FIELD)
727     return TRUE;
728
729   return FALSE;
730 }
731
732 boolean DrawingDeactivated(int x, int y, int width, int height)
733 {
734   return CheckDrawingArea(x, y, width, height, gfx.draw_deactivation_mask);
735 }
736
737 boolean DrawingOnBackground(int x, int y)
738 {
739   return (CheckDrawingArea(x, y, 1, 1, gfx.background_bitmap_mask) &&
740           CheckDrawingArea(x, y, 1, 1, gfx.draw_background_mask));
741 }
742
743 static boolean InClippedRectangle(Bitmap *bitmap, int *x, int *y,
744                                   int *width, int *height, boolean is_dest)
745 {
746   int clip_x, clip_y, clip_width, clip_height;
747
748   if (gfx.clipping_enabled && is_dest)  // only clip destination bitmap
749   {
750     clip_x = MIN(MAX(0, gfx.clip_x), bitmap->width);
751     clip_y = MIN(MAX(0, gfx.clip_y), bitmap->height);
752     clip_width = MIN(MAX(0, gfx.clip_width), bitmap->width - clip_x);
753     clip_height = MIN(MAX(0, gfx.clip_height), bitmap->height - clip_y);
754   }
755   else
756   {
757     clip_x = 0;
758     clip_y = 0;
759     clip_width = bitmap->width;
760     clip_height = bitmap->height;
761   }
762
763   // skip if rectangle completely outside bitmap
764
765   if (*x + *width  <= clip_x ||
766       *y + *height <= clip_y ||
767       *x >= clip_x + clip_width ||
768       *y >= clip_y + clip_height)
769     return FALSE;
770
771   // clip if rectangle overlaps bitmap
772
773   if (*x < clip_x)
774   {
775     *width -= clip_x - *x;
776     *x = clip_x;
777   }
778   else if (*x + *width > clip_x + clip_width)
779   {
780     *width = clip_x + clip_width - *x;
781   }
782
783   if (*y < clip_y)
784   {
785     *height -= clip_y - *y;
786     *y = clip_y;
787   }
788   else if (*y + *height > clip_y + clip_height)
789   {
790     *height = clip_y + clip_height - *y;
791   }
792
793   return TRUE;
794 }
795
796 void BlitBitmap(Bitmap *src_bitmap, Bitmap *dst_bitmap,
797                 int src_x, int src_y, int width, int height,
798                 int dst_x, int dst_y)
799 {
800   int dst_x_unclipped = dst_x;
801   int dst_y_unclipped = dst_y;
802
803   if (program.headless)
804     return;
805
806   if (src_bitmap == NULL || dst_bitmap == NULL)
807     return;
808
809   if (DrawingDeactivated(dst_x, dst_y, width, height))
810     return;
811
812   if (!InClippedRectangle(src_bitmap, &src_x, &src_y, &width, &height, FALSE) ||
813       !InClippedRectangle(dst_bitmap, &dst_x, &dst_y, &width, &height, TRUE))
814     return;
815
816   // source x/y might need adjustment if destination x/y was clipped top/left
817   src_x += dst_x - dst_x_unclipped;
818   src_y += dst_y - dst_y_unclipped;
819
820   // !!! 2013-12-11: An "old friend" is back. Same bug in SDL2 2.0.1 !!!
821   // !!! 2009-03-30: Fixed by using self-compiled, patched SDL.dll !!!
822   /* (This bug still exists in the actual (as of 2009-06-15) version 1.2.13,
823      but is already fixed in SVN and should therefore finally be fixed with
824      the next official SDL release, which is probably version 1.2.14.) */
825   // !!! 2009-03-24: It seems that this problem still exists in 1.2.12 !!!
826
827   if (src_bitmap == dst_bitmap)
828   {
829     // needed when blitting directly to same bitmap -- should not be needed with
830     // recent SDL libraries, but apparently does not work in 1.2.11 directly
831
832     static Bitmap *tmp_bitmap = NULL;
833     static int tmp_bitmap_xsize = 0;
834     static int tmp_bitmap_ysize = 0;
835
836     // start with largest static bitmaps for initial bitmap size ...
837     if (tmp_bitmap_xsize == 0 && tmp_bitmap_ysize == 0)
838     {
839       tmp_bitmap_xsize = MAX(gfx.win_xsize, gfx.scrollbuffer_width);
840       tmp_bitmap_ysize = MAX(gfx.win_ysize, gfx.scrollbuffer_height);
841     }
842
843     // ... and allow for later re-adjustments due to custom artwork bitmaps
844     if (src_bitmap->width > tmp_bitmap_xsize ||
845         src_bitmap->height > tmp_bitmap_ysize)
846     {
847       tmp_bitmap_xsize = MAX(tmp_bitmap_xsize, src_bitmap->width);
848       tmp_bitmap_ysize = MAX(tmp_bitmap_ysize, src_bitmap->height);
849
850       FreeBitmap(tmp_bitmap);
851
852       tmp_bitmap = NULL;
853     }
854
855     if (tmp_bitmap == NULL)
856       tmp_bitmap = CreateBitmap(tmp_bitmap_xsize, tmp_bitmap_ysize,
857                                 DEFAULT_DEPTH);
858
859     sysCopyArea(src_bitmap, tmp_bitmap,
860                 src_x, src_y, width, height, dst_x, dst_y, BLIT_OPAQUE);
861     sysCopyArea(tmp_bitmap, dst_bitmap,
862                 dst_x, dst_y, width, height, dst_x, dst_y, BLIT_OPAQUE);
863
864     return;
865   }
866
867   sysCopyArea(src_bitmap, dst_bitmap,
868               src_x, src_y, width, height, dst_x, dst_y, BLIT_OPAQUE);
869 }
870
871 void BlitBitmapTiled(Bitmap *src_bitmap, Bitmap *dst_bitmap,
872                      int src_x, int src_y, int src_width, int src_height,
873                      int dst_x, int dst_y, int dst_width, int dst_height)
874 {
875   int src_xsize = (src_width  == 0 ? src_bitmap->width  : src_width);
876   int src_ysize = (src_height == 0 ? src_bitmap->height : src_height);
877   int dst_xsize = dst_width;
878   int dst_ysize = dst_height;
879   int src_xsteps = (dst_xsize + src_xsize - 1) / src_xsize;
880   int src_ysteps = (dst_ysize + src_ysize - 1) / src_ysize;
881   int x, y;
882
883   for (y = 0; y < src_ysteps; y++)
884   {
885     for (x = 0; x < src_xsteps; x++)
886     {
887       int draw_x = dst_x + x * src_xsize;
888       int draw_y = dst_y + y * src_ysize;
889       int draw_xsize = MIN(src_xsize, dst_xsize - x * src_xsize);
890       int draw_ysize = MIN(src_ysize, dst_ysize - y * src_ysize);
891
892       BlitBitmap(src_bitmap, dst_bitmap, src_x, src_y, draw_xsize, draw_ysize,
893                  draw_x, draw_y);
894     }
895   }
896 }
897
898 void FadeRectangle(int x, int y, int width, int height,
899                    int fade_mode, int fade_delay, int post_delay,
900                    void (*draw_border_function)(void))
901 {
902   // (use destination bitmap "backbuffer" -- "bitmap_cross" may be undefined)
903   if (!InClippedRectangle(backbuffer, &x, &y, &width, &height, TRUE))
904     return;
905
906   SDLFadeRectangle(x, y, width, height,
907                    fade_mode, fade_delay, post_delay, draw_border_function);
908 }
909
910 void FillRectangle(Bitmap *bitmap, int x, int y, int width, int height,
911                    Pixel color)
912 {
913   if (DrawingDeactivated(x, y, width, height))
914     return;
915
916   if (!InClippedRectangle(bitmap, &x, &y, &width, &height, TRUE))
917     return;
918
919   sysFillRectangle(bitmap, x, y, width, height, color);
920 }
921
922 void ClearRectangle(Bitmap *bitmap, int x, int y, int width, int height)
923 {
924   FillRectangle(bitmap, x, y, width, height, BLACK_PIXEL);
925 }
926
927 void ClearRectangleOnBackground(Bitmap *bitmap, int x, int y,
928                                 int width, int height)
929 {
930   if (DrawingOnBackground(x, y))
931     BlitBitmap(gfx.background_bitmap, bitmap, x, y, width, height, x, y);
932   else
933     ClearRectangle(bitmap, x, y, width, height);
934 }
935
936 void BlitBitmapMasked(Bitmap *src_bitmap, Bitmap *dst_bitmap,
937                       int src_x, int src_y, int width, int height,
938                       int dst_x, int dst_y)
939 {
940   if (DrawingDeactivated(dst_x, dst_y, width, height))
941     return;
942
943   sysCopyArea(src_bitmap, dst_bitmap, src_x, src_y, width, height,
944               dst_x, dst_y, BLIT_MASKED);
945 }
946
947 void BlitBitmapOnBackground(Bitmap *src_bitmap, Bitmap *dst_bitmap,
948                             int src_x, int src_y, int width, int height,
949                             int dst_x, int dst_y)
950 {
951   if (DrawingOnBackground(dst_x, dst_y))
952   {
953     // draw background
954     BlitBitmap(gfx.background_bitmap, dst_bitmap, dst_x, dst_y, width, height,
955                dst_x, dst_y);
956
957     // draw foreground
958     BlitBitmapMasked(src_bitmap, dst_bitmap, src_x, src_y, width, height,
959                      dst_x, dst_y);
960   }
961   else
962     BlitBitmap(src_bitmap, dst_bitmap, src_x, src_y, width, height,
963                dst_x, dst_y);
964 }
965
966 void BlitTexture(Bitmap *bitmap,
967                 int src_x, int src_y, int width, int height,
968                 int dst_x, int dst_y)
969 {
970   if (bitmap == NULL)
971     return;
972
973   SDLBlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y,
974                  BLIT_OPAQUE);
975 }
976
977 void BlitTextureMasked(Bitmap *bitmap,
978                        int src_x, int src_y, int width, int height,
979                        int dst_x, int dst_y)
980 {
981   if (bitmap == NULL)
982     return;
983
984   SDLBlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y,
985                  BLIT_MASKED);
986 }
987
988 void BlitToScreen(Bitmap *bitmap,
989                   int src_x, int src_y, int width, int height,
990                   int dst_x, int dst_y)
991 {
992   if (bitmap == NULL)
993     return;
994
995   if (video.screen_rendering_mode == SPECIAL_RENDERING_BITMAP)
996     BlitBitmap(bitmap, gfx.final_screen_bitmap, src_x, src_y,
997                width, height, dst_x, dst_y);
998   else
999     BlitTexture(bitmap, src_x, src_y, width, height, dst_x, dst_y);
1000 }
1001
1002 void BlitToScreenMasked(Bitmap *bitmap,
1003                         int src_x, int src_y, int width, int height,
1004                         int dst_x, int dst_y)
1005 {
1006   if (bitmap == NULL)
1007     return;
1008
1009   if (video.screen_rendering_mode == SPECIAL_RENDERING_BITMAP)
1010     BlitBitmapMasked(bitmap, gfx.final_screen_bitmap, src_x, src_y,
1011                      width, height, dst_x, dst_y);
1012   else
1013     BlitTextureMasked(bitmap, src_x, src_y, width, height, dst_x, dst_y);
1014 }
1015
1016 void DrawSimpleBlackLine(Bitmap *bitmap, int from_x, int from_y,
1017                          int to_x, int to_y)
1018 {
1019   SDLDrawSimpleLine(bitmap, from_x, from_y, to_x, to_y, BLACK_PIXEL);
1020 }
1021
1022 void DrawSimpleWhiteLine(Bitmap *bitmap, int from_x, int from_y,
1023                          int to_x, int to_y)
1024 {
1025   SDLDrawSimpleLine(bitmap, from_x, from_y, to_x, to_y, WHITE_PIXEL);
1026 }
1027
1028 static void DrawLine(Bitmap *bitmap, int from_x, int from_y,
1029                      int to_x, int to_y, Pixel pixel, int line_width)
1030 {
1031   int x, y;
1032
1033   if (program.headless)
1034     return;
1035
1036   for (x = 0; x < line_width; x++)
1037   {
1038     for (y = 0; y < line_width; y++)
1039     {
1040       int dx = x - line_width / 2;
1041       int dy = y - line_width / 2;
1042
1043       if ((x == 0 && y == 0) ||
1044           (x == 0 && y == line_width - 1) ||
1045           (x == line_width - 1 && y == 0) ||
1046           (x == line_width - 1 && y == line_width - 1))
1047         continue;
1048
1049       SDLDrawLine(bitmap,
1050                   from_x + dx, from_y + dy, to_x + dx, to_y + dy, pixel);
1051     }
1052   }
1053 }
1054
1055 void DrawLines(Bitmap *bitmap, struct XY *points, int num_points, Pixel pixel)
1056 {
1057   int line_width = 4;
1058   int i;
1059
1060   for (i = 0; i < num_points - 1; i++)
1061     DrawLine(bitmap, points[i].x, points[i].y,
1062              points[i + 1].x, points[i + 1].y, pixel, line_width);
1063
1064   /*
1065   SDLDrawLines(bitmap->surface, points, num_points, pixel);
1066   */
1067 }
1068
1069 Pixel GetPixel(Bitmap *bitmap, int x, int y)
1070 {
1071   if (program.headless)
1072     return BLACK_PIXEL;
1073
1074   if (x < 0 || x >= bitmap->width ||
1075       y < 0 || y >= bitmap->height)
1076     return BLACK_PIXEL;
1077
1078   return SDLGetPixel(bitmap, x, y);
1079 }
1080
1081 Pixel GetPixelFromRGB(Bitmap *bitmap, unsigned int color_r,
1082                       unsigned int color_g, unsigned int color_b)
1083 {
1084   if (program.headless)
1085     return BLACK_PIXEL;
1086
1087   return SDL_MapRGB(bitmap->surface->format, color_r, color_g, color_b);
1088 }
1089
1090 Pixel GetPixelFromRGBcompact(Bitmap *bitmap, unsigned int color)
1091 {
1092   unsigned int color_r = (color >> 16) & 0xff;
1093   unsigned int color_g = (color >>  8) & 0xff;
1094   unsigned int color_b = (color >>  0) & 0xff;
1095
1096   return GetPixelFromRGB(bitmap, color_r, color_g, color_b);
1097 }
1098
1099 void KeyboardAutoRepeatOn(void)
1100 {
1101   keyrepeat_status = TRUE;
1102 }
1103
1104 void KeyboardAutoRepeatOff(void)
1105 {
1106   keyrepeat_status = FALSE;
1107 }
1108
1109 boolean SetVideoMode(boolean fullscreen)
1110 {
1111   return SDLSetVideoMode(fullscreen);
1112 }
1113
1114 void SetVideoFrameDelay(unsigned int frame_delay_value)
1115 {
1116   video.frame_delay_value = frame_delay_value;
1117 }
1118
1119 unsigned int GetVideoFrameDelay(void)
1120 {
1121   return video.frame_delay_value;
1122 }
1123
1124 boolean ChangeVideoModeIfNeeded(boolean fullscreen)
1125 {
1126   if ((fullscreen && !video.fullscreen_enabled && video.fullscreen_available)||
1127       (!fullscreen && video.fullscreen_enabled))
1128     fullscreen = SetVideoMode(fullscreen);
1129
1130   return fullscreen;
1131 }
1132
1133 Bitmap *LoadImage(char *filename)
1134 {
1135   Bitmap *new_bitmap;
1136
1137   new_bitmap = SDLLoadImage(filename);
1138
1139   if (new_bitmap)
1140     new_bitmap->source_filename = getStringCopy(filename);
1141
1142   return new_bitmap;
1143 }
1144
1145 Bitmap *LoadCustomImage(char *basename)
1146 {
1147   char *filename = getCustomImageFilename(basename);
1148   Bitmap *new_bitmap;
1149
1150   if (filename == NULL)
1151     Error(ERR_EXIT, "LoadCustomImage(): cannot find file '%s'", basename);
1152
1153   if ((new_bitmap = LoadImage(filename)) == NULL)
1154     Error(ERR_EXIT, "LoadImage('%s') failed: %s", basename, GetError());
1155
1156   return new_bitmap;
1157 }
1158
1159 void ReloadCustomImage(Bitmap *bitmap, char *basename)
1160 {
1161   char *filename = getCustomImageFilename(basename);
1162   Bitmap *new_bitmap;
1163
1164   if (filename == NULL)         // (should never happen)
1165   {
1166     Error(ERR_WARN, "ReloadCustomImage(): cannot find file '%s'", basename);
1167     return;
1168   }
1169
1170   if (strEqual(filename, bitmap->source_filename))
1171   {
1172     // The old and new image are the same (have the same filename and path).
1173     // This usually means that this image does not exist in this graphic set
1174     // and a fallback to the existing image is done.
1175
1176     return;
1177   }
1178
1179   if ((new_bitmap = LoadImage(filename)) == NULL)
1180   {
1181     Error(ERR_WARN, "LoadImage('%s') failed: %s", basename, GetError());
1182     return;
1183   }
1184
1185   if (bitmap->width != new_bitmap->width ||
1186       bitmap->height != new_bitmap->height)
1187   {
1188     Error(ERR_WARN, "ReloadCustomImage: new image '%s' has wrong dimensions",
1189           filename);
1190     FreeBitmap(new_bitmap);
1191     return;
1192   }
1193
1194   TransferBitmapPointers(new_bitmap, bitmap);
1195   free(new_bitmap);
1196 }
1197
1198 static Bitmap *ZoomBitmap(Bitmap *src_bitmap, int zoom_width, int zoom_height)
1199 {
1200   return SDLZoomBitmap(src_bitmap, zoom_width, zoom_height);
1201 }
1202
1203 void ReCreateGameTileSizeBitmap(Bitmap **bitmaps)
1204 {
1205   if (bitmaps[IMG_BITMAP_CUSTOM])
1206   {
1207     FreeBitmap(bitmaps[IMG_BITMAP_CUSTOM]);
1208
1209     bitmaps[IMG_BITMAP_CUSTOM] = NULL;
1210   }
1211
1212   if (gfx.game_tile_size == gfx.standard_tile_size)
1213   {
1214     bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_STANDARD];
1215
1216     return;
1217   }
1218
1219   Bitmap *bitmap = bitmaps[IMG_BITMAP_STANDARD];
1220   int width  = bitmap->width  * gfx.game_tile_size / gfx.standard_tile_size;;
1221   int height = bitmap->height * gfx.game_tile_size / gfx.standard_tile_size;;
1222
1223   Bitmap *bitmap_new = ZoomBitmap(bitmap, width, height);
1224
1225   bitmaps[IMG_BITMAP_CUSTOM] = bitmap_new;
1226   bitmaps[IMG_BITMAP_GAME]   = bitmap_new;
1227 }
1228
1229 static void CreateScaledBitmaps(Bitmap **bitmaps, int zoom_factor,
1230                                 int tile_size, boolean create_small_bitmaps)
1231 {
1232   Bitmap *old_bitmap = bitmaps[IMG_BITMAP_STANDARD];
1233   Bitmap *tmp_bitmap_final = NULL;
1234   Bitmap *tmp_bitmap_0 = NULL;
1235   Bitmap *tmp_bitmap_1 = NULL;
1236   Bitmap *tmp_bitmap_2 = NULL;
1237   Bitmap *tmp_bitmap_4 = NULL;
1238   Bitmap *tmp_bitmap_8 = NULL;
1239   Bitmap *tmp_bitmap_16 = NULL;
1240   Bitmap *tmp_bitmap_32 = NULL;
1241   int width_final, height_final;
1242   int width_0, height_0;
1243   int width_1, height_1;
1244   int width_2, height_2;
1245   int width_4, height_4;
1246   int width_8, height_8;
1247   int width_16, height_16;
1248   int width_32, height_32;
1249   int old_width, old_height;
1250   int i;
1251
1252   print_timestamp_init("CreateScaledBitmaps");
1253
1254   old_width  = old_bitmap->width;
1255   old_height = old_bitmap->height;
1256
1257   // calculate new image dimensions for final image size
1258   width_final  = old_width  * zoom_factor;
1259   height_final = old_height * zoom_factor;
1260
1261   // get image with final size (this might require scaling up)
1262   // ("final" size may result in non-standard tile size image)
1263   if (zoom_factor != 1)
1264     tmp_bitmap_final = ZoomBitmap(old_bitmap, width_final, height_final);
1265   else
1266     tmp_bitmap_final = old_bitmap;
1267
1268   UPDATE_BUSY_STATE();
1269
1270   width_0  = width_1  = width_final;
1271   height_0 = height_1 = height_final;
1272
1273   tmp_bitmap_0 = tmp_bitmap_1 = tmp_bitmap_final;
1274
1275   if (create_small_bitmaps)
1276   {
1277     // check if we have a non-gameplay tile size image
1278     if (tile_size != gfx.game_tile_size)
1279     {
1280       // get image with gameplay tile size
1281       width_0  = width_final  * gfx.game_tile_size / tile_size;
1282       height_0 = height_final * gfx.game_tile_size / tile_size;
1283
1284       if (width_0 == old_width)
1285         tmp_bitmap_0 = old_bitmap;
1286       else if (width_0 == width_final)
1287         tmp_bitmap_0 = tmp_bitmap_final;
1288       else
1289         tmp_bitmap_0 = ZoomBitmap(old_bitmap, width_0, height_0);
1290
1291       UPDATE_BUSY_STATE();
1292     }
1293
1294     // check if we have a non-standard tile size image
1295     if (tile_size != gfx.standard_tile_size)
1296     {
1297       // get image with standard tile size
1298       width_1  = width_final  * gfx.standard_tile_size / tile_size;
1299       height_1 = height_final * gfx.standard_tile_size / tile_size;
1300
1301       if (width_1 == old_width)
1302         tmp_bitmap_1 = old_bitmap;
1303       else if (width_1 == width_final)
1304         tmp_bitmap_1 = tmp_bitmap_final;
1305       else if (width_1 == width_0)
1306         tmp_bitmap_1 = tmp_bitmap_0;
1307       else
1308         tmp_bitmap_1 = ZoomBitmap(old_bitmap, width_1, height_1);
1309
1310       UPDATE_BUSY_STATE();
1311     }
1312
1313     // calculate new image dimensions for small images
1314     width_2  = width_1  / 2;
1315     height_2 = height_1 / 2;
1316     width_4  = width_1  / 4;
1317     height_4 = height_1 / 4;
1318     width_8  = width_1  / 8;
1319     height_8 = height_1 / 8;
1320     width_16  = width_1  / 16;
1321     height_16 = height_1 / 16;
1322     width_32  = width_1  / 32;
1323     height_32 = height_1 / 32;
1324
1325     // get image with 1/2 of normal size (for use in the level editor)
1326     if (width_2 == old_width)
1327       tmp_bitmap_2 = old_bitmap;
1328     else
1329       tmp_bitmap_2 = ZoomBitmap(tmp_bitmap_1, width_2, height_2);
1330
1331     UPDATE_BUSY_STATE();
1332
1333     // get image with 1/4 of normal size (for use in the level editor)
1334     if (width_4 == old_width)
1335       tmp_bitmap_4 = old_bitmap;
1336     else
1337       tmp_bitmap_4 = ZoomBitmap(tmp_bitmap_2, width_4, height_4);
1338
1339     UPDATE_BUSY_STATE();
1340
1341     // get image with 1/8 of normal size (for use on the preview screen)
1342     if (width_8 == old_width)
1343       tmp_bitmap_8 = old_bitmap;
1344     else
1345       tmp_bitmap_8 = ZoomBitmap(tmp_bitmap_4, width_8, height_8);
1346
1347     UPDATE_BUSY_STATE();
1348
1349     // get image with 1/16 of normal size (for use on the preview screen)
1350     if (width_16 == old_width)
1351       tmp_bitmap_16 = old_bitmap;
1352     else
1353       tmp_bitmap_16 = ZoomBitmap(tmp_bitmap_8, width_16, height_16);
1354
1355     UPDATE_BUSY_STATE();
1356
1357     // get image with 1/32 of normal size (for use on the preview screen)
1358     if (width_32 == old_width)
1359       tmp_bitmap_32 = old_bitmap;
1360     else
1361       tmp_bitmap_32 = ZoomBitmap(tmp_bitmap_16, width_32, height_32);
1362
1363     UPDATE_BUSY_STATE();
1364
1365     bitmaps[IMG_BITMAP_32x32] = tmp_bitmap_1;
1366     bitmaps[IMG_BITMAP_16x16] = tmp_bitmap_2;
1367     bitmaps[IMG_BITMAP_8x8]   = tmp_bitmap_4;
1368     bitmaps[IMG_BITMAP_4x4]   = tmp_bitmap_8;
1369     bitmaps[IMG_BITMAP_2x2]   = tmp_bitmap_16;
1370     bitmaps[IMG_BITMAP_1x1]   = tmp_bitmap_32;
1371
1372     if (width_0 != width_1)
1373       bitmaps[IMG_BITMAP_CUSTOM] = tmp_bitmap_0;
1374
1375     if (bitmaps[IMG_BITMAP_CUSTOM])
1376       bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_CUSTOM];
1377     else
1378       bitmaps[IMG_BITMAP_GAME] = bitmaps[IMG_BITMAP_STANDARD];
1379
1380     boolean free_old_bitmap = TRUE;
1381
1382     for (i = 0; i < NUM_IMG_BITMAPS; i++)
1383       if (bitmaps[i] == old_bitmap)
1384         free_old_bitmap = FALSE;
1385
1386     if (free_old_bitmap)
1387     {
1388       // copy image filename from old to new standard sized bitmap
1389       bitmaps[IMG_BITMAP_STANDARD]->source_filename =
1390         getStringCopy(old_bitmap->source_filename);
1391
1392       FreeBitmap(old_bitmap);
1393     }
1394   }
1395   else
1396   {
1397     bitmaps[IMG_BITMAP_32x32] = tmp_bitmap_1;
1398   }
1399
1400   UPDATE_BUSY_STATE();
1401
1402   print_timestamp_done("CreateScaledBitmaps");
1403 }
1404
1405 void CreateBitmapWithSmallBitmaps(Bitmap **bitmaps, int zoom_factor,
1406                                   int tile_size)
1407 {
1408   CreateScaledBitmaps(bitmaps, zoom_factor, tile_size, TRUE);
1409 }
1410
1411 void CreateBitmapTextures(Bitmap **bitmaps)
1412 {
1413   SDLCreateBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]);
1414 }
1415
1416 void FreeBitmapTextures(Bitmap **bitmaps)
1417 {
1418   SDLFreeBitmapTextures(bitmaps[IMG_BITMAP_STANDARD]);
1419 }
1420
1421 void ScaleBitmap(Bitmap **bitmaps, int zoom_factor)
1422 {
1423   CreateScaledBitmaps(bitmaps, zoom_factor, 0, FALSE);
1424 }
1425
1426
1427 // ----------------------------------------------------------------------------
1428 // mouse pointer functions
1429 // ----------------------------------------------------------------------------
1430
1431 #define USE_ONE_PIXEL_PLAYFIELD_MOUSEPOINTER            0
1432
1433 // XPM image definitions
1434 static const char *cursor_image_none[] =
1435 {
1436   // width height num_colors chars_per_pixel
1437   "    16    16        3            1",
1438
1439   // colors
1440   "X c #000000",
1441   ". c #ffffff",
1442   "  c None",
1443
1444   // pixels
1445   "                ",
1446   "                ",
1447   "                ",
1448   "                ",
1449   "                ",
1450   "                ",
1451   "                ",
1452   "                ",
1453   "                ",
1454   "                ",
1455   "                ",
1456   "                ",
1457   "                ",
1458   "                ",
1459   "                ",
1460   "                ",
1461
1462   // hot spot
1463   "0,0"
1464 };
1465
1466 #if USE_ONE_PIXEL_PLAYFIELD_MOUSEPOINTER
1467 static const char *cursor_image_dot[] =
1468 {
1469   // width height num_colors chars_per_pixel
1470   "    16    16        3            1",
1471
1472   // colors
1473   "X c #000000",
1474   ". c #ffffff",
1475   "  c None",
1476
1477   // pixels
1478   " X              ",
1479   "X.X             ",
1480   " X              ",
1481   "                ",
1482   "                ",
1483   "                ",
1484   "                ",
1485   "                ",
1486   "                ",
1487   "                ",
1488   "                ",
1489   "                ",
1490   "                ",
1491   "                ",
1492   "                ",
1493   "                ",
1494
1495   // hot spot
1496   "1,1"
1497 };
1498 static const char **cursor_image_playfield = cursor_image_dot;
1499 #else
1500 // some people complained about a "white dot" on the screen and thought it
1501 // was a graphical error... OK, let's just remove the whole pointer :-)
1502 static const char **cursor_image_playfield = cursor_image_none;
1503 #endif
1504
1505 static const int cursor_bit_order = BIT_ORDER_MSB;
1506
1507 static struct MouseCursorInfo *get_cursor_from_image(const char **image)
1508 {
1509   struct MouseCursorInfo *cursor;
1510   boolean bit_order_msb = (cursor_bit_order == BIT_ORDER_MSB);
1511   int header_lines = 4;
1512   int x, y, i;
1513
1514   cursor = checked_calloc(sizeof(struct MouseCursorInfo));
1515
1516   sscanf(image[0], " %d %d ", &cursor->width, &cursor->height);
1517
1518   i = -1;
1519   for (y = 0; y < cursor->width; y++)
1520   {
1521     for (x = 0; x < cursor->height; x++)
1522     {
1523       int bit_nr = x % 8;
1524       int bit_mask = 0x01 << (bit_order_msb ? 7 - bit_nr : bit_nr );
1525
1526       if (bit_nr == 0)
1527       {
1528         i++;
1529         cursor->data[i] = cursor->mask[i] = 0;
1530       }
1531
1532       switch (image[header_lines + y][x])
1533       {
1534         case 'X':
1535           cursor->data[i] |= bit_mask;
1536           cursor->mask[i] |= bit_mask;
1537           break;
1538
1539         case '.':
1540           cursor->mask[i] |= bit_mask;
1541           break;
1542
1543         case ' ':
1544           break;
1545       }
1546     }
1547   }
1548
1549   sscanf(image[header_lines + y], "%d,%d", &cursor->hot_x, &cursor->hot_y);
1550
1551   return cursor;
1552 }
1553
1554 void SetMouseCursor(int mode)
1555 {
1556   static struct MouseCursorInfo *cursor_none = NULL;
1557   static struct MouseCursorInfo *cursor_playfield = NULL;
1558   struct MouseCursorInfo *cursor_new;
1559   int mode_final = mode;
1560
1561   if (cursor_none == NULL)
1562     cursor_none = get_cursor_from_image(cursor_image_none);
1563
1564   if (cursor_playfield == NULL)
1565     cursor_playfield = get_cursor_from_image(cursor_image_playfield);
1566
1567   if (gfx.cursor_mode_override != CURSOR_UNDEFINED)
1568     mode_final = gfx.cursor_mode_override;
1569
1570   cursor_new = (mode_final == CURSOR_DEFAULT   ? NULL :
1571                 mode_final == CURSOR_NONE      ? cursor_none :
1572                 mode_final == CURSOR_PLAYFIELD ? cursor_playfield : NULL);
1573
1574   SDLSetMouseCursor(cursor_new);
1575
1576   gfx.cursor_mode = mode;
1577   gfx.cursor_mode_final = mode_final;
1578 }
1579
1580 void UpdateRawMousePosition(int mouse_x, int mouse_y)
1581 {
1582   // mouse events do not contain logical screen size corrections yet
1583   SDLCorrectRawMousePosition(&mouse_x, &mouse_y);
1584
1585   mouse_x -= video.screen_xoffset;
1586   mouse_y -= video.screen_yoffset;
1587
1588   gfx.mouse_x = mouse_x;
1589   gfx.mouse_y = mouse_y;
1590 }
1591
1592 void UpdateMousePosition(void)
1593 {
1594   int mouse_x, mouse_y;
1595
1596   SDL_PumpEvents();
1597   SDL_GetMouseState(&mouse_x, &mouse_y);
1598
1599   UpdateRawMousePosition(mouse_x, mouse_y);
1600 }
1601
1602
1603 // ============================================================================
1604 // audio functions
1605 // ============================================================================
1606
1607 void OpenAudio(void)
1608 {
1609   // always start with reliable default values
1610   audio.sound_available = FALSE;
1611   audio.music_available = FALSE;
1612   audio.loops_available = FALSE;
1613
1614   audio.sound_enabled = FALSE;
1615   audio.sound_deactivated = FALSE;
1616
1617   audio.mixer_pipe[0] = audio.mixer_pipe[1] = 0;
1618   audio.mixer_pid = 0;
1619   audio.device_name = NULL;
1620   audio.device_fd = -1;
1621
1622   audio.num_channels = 0;
1623   audio.music_channel = 0;
1624   audio.first_sound_channel = 0;
1625
1626   SDLOpenAudio();
1627 }
1628
1629 void CloseAudio(void)
1630 {
1631   SDLCloseAudio();
1632
1633   audio.sound_enabled = FALSE;
1634 }
1635
1636 void SetAudioMode(boolean enabled)
1637 {
1638   if (!audio.sound_available)
1639     return;
1640
1641   audio.sound_enabled = enabled;
1642 }
1643
1644
1645 // ============================================================================
1646 // event functions
1647 // ============================================================================
1648
1649 void InitEventFilter(EventFilter filter_function)
1650 {
1651   SDL_SetEventFilter(filter_function, NULL);
1652 }
1653
1654 boolean PendingEvent(void)
1655 {
1656   return (SDL_PollEvent(NULL) ? TRUE : FALSE);
1657 }
1658
1659 void WaitEvent(Event *event)
1660 {
1661   SDLWaitEvent(event);
1662 }
1663
1664 void PeekEvent(Event *event)
1665 {
1666   SDL_PeepEvents(event, 1, SDL_PEEKEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT);
1667 }
1668
1669 void PumpEvents(void)
1670 {
1671   SDL_PumpEvents();
1672 }
1673
1674 void CheckQuitEvent(void)
1675 {
1676   if (SDL_QuitRequested())
1677     program.exit_function(0);
1678 }
1679
1680 Key GetEventKey(KeyEvent *event, boolean with_modifiers)
1681 {
1682   // key up/down events in SDL2 do not return text characters anymore
1683   return event->keysym.sym;
1684 }
1685
1686 KeyMod HandleKeyModState(Key key, int key_status)
1687 {
1688   static KeyMod current_modifiers = KMOD_None;
1689
1690   if (key != KSYM_UNDEFINED)    // new key => check for modifier key change
1691   {
1692     KeyMod new_modifier = KMOD_None;
1693
1694     switch(key)
1695     {
1696       case KSYM_Shift_L:
1697         new_modifier = KMOD_Shift_L;
1698         break;
1699       case KSYM_Shift_R:
1700         new_modifier = KMOD_Shift_R;
1701         break;
1702       case KSYM_Control_L:
1703         new_modifier = KMOD_Control_L;
1704         break;
1705       case KSYM_Control_R:
1706         new_modifier = KMOD_Control_R;
1707         break;
1708       case KSYM_Meta_L:
1709         new_modifier = KMOD_Meta_L;
1710         break;
1711       case KSYM_Meta_R:
1712         new_modifier = KMOD_Meta_R;
1713         break;
1714       case KSYM_Alt_L:
1715         new_modifier = KMOD_Alt_L;
1716         break;
1717       case KSYM_Alt_R:
1718         new_modifier = KMOD_Alt_R;
1719         break;
1720       default:
1721         break;
1722     }
1723
1724     if (key_status == KEY_PRESSED)
1725       current_modifiers |= new_modifier;
1726     else
1727       current_modifiers &= ~new_modifier;
1728   }
1729
1730   return current_modifiers;
1731 }
1732
1733 KeyMod GetKeyModState(void)
1734 {
1735   return (KeyMod)SDL_GetModState();
1736 }
1737
1738 KeyMod GetKeyModStateFromEvents(void)
1739 {
1740   /* always use key modifier state as tracked from key events (this is needed
1741      if the modifier key event was injected into the event queue, but the key
1742      was not really pressed on keyboard -- SDL_GetModState() seems to directly
1743      query the keys as held pressed on the keyboard) -- this case is currently
1744      only used to filter out clipboard insert events from "True X-Mouse" tool */
1745
1746   return HandleKeyModState(KSYM_UNDEFINED, 0);
1747 }
1748
1749 void StartTextInput(int x, int y, int width, int height)
1750 {
1751 #if defined(HAS_SCREEN_KEYBOARD)
1752   SDL_StartTextInput();
1753
1754   if (y + height > SCREEN_KEYBOARD_POS(video.height))
1755   {
1756     video.shifted_up_pos = y + height - SCREEN_KEYBOARD_POS(video.height);
1757     video.shifted_up_delay = SDL_GetTicks();
1758     video.shifted_up = TRUE;
1759   }
1760 #endif
1761 }
1762
1763 void StopTextInput(void)
1764 {
1765 #if defined(HAS_SCREEN_KEYBOARD)
1766   SDL_StopTextInput();
1767
1768   if (video.shifted_up)
1769   {
1770     video.shifted_up_pos = 0;
1771     video.shifted_up_delay = SDL_GetTicks();
1772     video.shifted_up = FALSE;
1773   }
1774 #endif
1775 }
1776
1777 void PushUserEvent(int code, int value1, int value2)
1778 {
1779   UserEvent event;
1780
1781   SDL_memset(&event, 0, sizeof(event));
1782
1783   event.type = EVENT_USER;
1784   event.code = code;
1785   event.value1 = value1;
1786   event.value2 = value2;
1787
1788   SDL_PushEvent((SDL_Event *)&event);
1789 }
1790
1791
1792 // ============================================================================
1793 // joystick functions
1794 // ============================================================================
1795
1796 void InitJoysticks(void)
1797 {
1798   int i;
1799
1800 #if defined(NO_JOYSTICK)
1801   return;       // joysticks generally deactivated by compile-time directive
1802 #endif
1803
1804   // always start with reliable default values
1805   joystick.status = JOYSTICK_NOT_AVAILABLE;
1806   for (i = 0; i < MAX_PLAYERS; i++)
1807     joystick.nr[i] = -1;                // no joystick configured
1808
1809   SDLInitJoysticks();
1810 }
1811
1812 boolean ReadJoystick(int nr, int *x, int *y, boolean *b1, boolean *b2)
1813 {
1814   return SDLReadJoystick(nr, x, y, b1, b2);
1815 }
1816
1817 boolean CheckJoystickOpened(int nr)
1818 {
1819   return SDLCheckJoystickOpened(nr);
1820 }
1821
1822 void ClearJoystickState(void)
1823 {
1824   SDLClearJoystickState();
1825 }