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