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