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