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