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