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