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