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