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