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