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