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