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