added new animation mode ".global_anim_sync" for game element graphics
[rocksndiamonds.git] / src / tools.c
index 0872e24bc1fde98a5459eff7dda8fff43ae68293..98e60731333f9add8584999855c3ee65459e3e1f 100644 (file)
@@ -165,6 +165,14 @@ static struct DoorPartControlInfo door_part_controls[] =
   }
 };
 
+static struct XY xy_topdown[] =
+{
+  {  0, -1 },
+  { -1,  0 },
+  { +1,  0 },
+  {  0, +1 }
+};
+
 
 // forward declaration for internal use
 static void UnmapToolButtons(void);
@@ -512,6 +520,10 @@ static void DrawMaskedBorderExt_Rect(int x, int y, int width, int height,
   Bitmap *src_bitmap = getGlobalBorderBitmapFromStatus(global.border_status);
   Bitmap *dst_bitmap = gfx.masked_border_bitmap_ptr;
 
+  // may happen for "border.draw_masked.*" with undefined "global.border.*"
+  if (src_bitmap == NULL)
+    return;
+
   if (x == -1 && y == -1)
     return;
 
@@ -763,7 +775,7 @@ void BackToFront(void)
     DrawFramesPerSecond();
 
   // remove playfield redraw before potentially merging with doors redraw
-  if (DrawingDeactivated(REAL_SX, REAL_SY, FULL_SXSIZE, FULL_SYSIZE))
+  if (DrawingDeactivated(REAL_SX, REAL_SY))
     redraw_mask &= ~REDRAW_FIELD;
 
   // redraw complete window if both playfield and (some) doors need redraw
@@ -1091,7 +1103,14 @@ void FadeSkipNextFadeOut(void)
   FadeExt(0, FADE_MODE_SKIP_FADE_OUT, FADE_TYPE_SKIP);
 }
 
-static Bitmap *getBitmapFromGraphicOrDefault(int graphic, int default_graphic)
+static int getGlobalGameStatus(int status)
+{
+  return (status == GAME_MODE_PSEUDO_TYPENAME ? GAME_MODE_MAIN :
+         status == GAME_MODE_SCOREINFO       ? GAME_MODE_SCORES :
+         status);
+}
+
+Bitmap *getBitmapFromGraphicOrDefault(int graphic, int default_graphic)
 {
   if (graphic == IMG_UNDEFINED)
     return NULL;
@@ -1113,14 +1132,14 @@ static Bitmap *getGlobalBorderBitmap(int graphic)
   return getBitmapFromGraphicOrDefault(graphic, IMG_GLOBAL_BORDER);
 }
 
-Bitmap *getGlobalBorderBitmapFromStatus(int status)
+Bitmap *getGlobalBorderBitmapFromStatus(int status_raw)
 {
+  int status = getGlobalGameStatus(status_raw);
   int graphic =
-    (status == GAME_MODE_MAIN ||
-     status == GAME_MODE_PSEUDO_TYPENAME       ? IMG_GLOBAL_BORDER_MAIN :
-     status == GAME_MODE_SCORES                        ? IMG_GLOBAL_BORDER_SCORES :
-     status == GAME_MODE_EDITOR                        ? IMG_GLOBAL_BORDER_EDITOR :
-     status == GAME_MODE_PLAYING               ? IMG_GLOBAL_BORDER_PLAYING :
+    (status == GAME_MODE_MAIN    ? IMG_GLOBAL_BORDER_MAIN :
+     status == GAME_MODE_SCORES  ? IMG_GLOBAL_BORDER_SCORES :
+     status == GAME_MODE_EDITOR  ? IMG_GLOBAL_BORDER_EDITOR :
+     status == GAME_MODE_PLAYING ? IMG_GLOBAL_BORDER_PLAYING :
      IMG_GLOBAL_BORDER);
 
   return getGlobalBorderBitmap(graphic);
@@ -1422,7 +1441,7 @@ void FloodFillLevelExt(int start_x, int start_y, int fill_element,
                       int max_fieldx, int max_fieldy)
 {
   static struct XY stack_buffer[MAX_LEV_FIELDX * MAX_LEV_FIELDY];
-  static struct XY check[4] = { { -1, 0 }, { 0, -1 }, { 1, 0 }, { 0, 1 } };
+  struct XY *check = xy_topdown;
   int old_element = field[start_x][start_y];
   int stack_pos = 0;
 
@@ -1473,6 +1492,8 @@ int getGraphicAnimationFrame(int graphic, int sync_frame)
   // animation synchronized with global frame counter, not move position
   if (graphic_info[graphic].anim_global_sync || sync_frame < 0)
     sync_frame = FrameCounter;
+  else if (graphic_info[graphic].anim_global_anim_sync)
+    sync_frame = getGlobalAnimSyncFrame();
 
   return getAnimationFrame(graphic_info[graphic].anim_frames,
                           graphic_info[graphic].anim_delay,
@@ -1490,8 +1511,9 @@ int getGraphicAnimationFrameXY(int graphic, int lx, int ly)
     int ysize = MAX(1, g->anim_frames / xsize);
     int xoffset = g->anim_start_frame % xsize;
     int yoffset = g->anim_start_frame % ysize;
-    int x = (lx + xoffset + xsize) % xsize;
-    int y = (ly + yoffset + ysize) % ysize;
+    // may be needed if screen field is significantly larger than playfield
+    int x = (lx + xoffset + SCR_FIELDX * xsize) % xsize;
+    int y = (ly + yoffset + SCR_FIELDY * ysize) % ysize;
     int sync_frame = y * xsize + x;
 
     return sync_frame % g->anim_frames;
@@ -1499,14 +1521,19 @@ int getGraphicAnimationFrameXY(int graphic, int lx, int ly)
   else if (graphic_info[graphic].anim_mode & ANIM_RANDOM_STATIC)
   {
     struct GraphicInfo *g = &graphic_info[graphic];
-    int x = (lx + lev_fieldx) % lev_fieldx;
-    int y = (ly + lev_fieldy) % lev_fieldy;
+    // may be needed if screen field is significantly larger than playfield
+    int x = (lx + SCR_FIELDX * lev_fieldx) % lev_fieldx;
+    int y = (ly + SCR_FIELDY * lev_fieldy) % lev_fieldy;
     int sync_frame = GfxRandomStatic[x][y];
 
     return sync_frame % g->anim_frames;
   }
+  else
+  {
+    int sync_frame = (IN_LEV_FIELD(lx, ly) ? GfxFrame[lx][ly] : -1);
 
-  return getGraphicAnimationFrame(graphic, GfxFrame[lx][ly]);
+    return getGraphicAnimationFrame(graphic, sync_frame);
+  }
 }
 
 void getGraphicSourceBitmap(int graphic, int tilesize, Bitmap **bitmap)
@@ -1996,11 +2023,11 @@ void DrawScreenElementExt(int x, int y, int dx, int dy, int element,
   int graphic;
   int frame;
 
-  if (element == EL_EMPTY)
-    element = GfxElementEmpty[lx][ly];
-
   if (IN_LEV_FIELD(lx, ly))
   {
+    if (element == EL_EMPTY)
+      element = GfxElementEmpty[lx][ly];
+
     SetRandomAnimationValue(lx, ly);
 
     graphic = el_act_dir2img(element, GfxAction[lx][ly], GfxDir[lx][ly]);
@@ -2276,13 +2303,7 @@ static void DrawLevelFieldCrumbledExt(int x, int y, int graphic, int frame)
   int sx = SCREENX(x), sy = SCREENY(y);
   int element;
   int i;
-  static int xy[4][2] =
-  {
-    { 0, -1 },
-    { -1, 0 },
-    { +1, 0 },
-    { 0, +1 }
-  };
+  struct XY *xy = xy_topdown;
 
   if (!IN_LEV_FIELD(x, y))
     return;
@@ -2297,8 +2318,8 @@ static void DrawLevelFieldCrumbledExt(int x, int y, int graphic, int frame)
     // crumble field borders towards direct neighbour fields
     for (i = 0; i < 4; i++)
     {
-      int xx = x + xy[i][0];
-      int yy = y + xy[i][1];
+      int xx = x + xy[i].x;
+      int yy = y + xy[i].y;
 
       element = (IN_LEV_FIELD(xx, yy) ? TILE_GFX_ELEMENT(xx, yy) :
                 BorderElement);
@@ -2332,10 +2353,10 @@ static void DrawLevelFieldCrumbledExt(int x, int y, int graphic, int frame)
     // crumble field borders of direct neighbour fields
     for (i = 0; i < 4; i++)
     {
-      int xx = x + xy[i][0];
-      int yy = y + xy[i][1];
-      int sxx = sx + xy[i][0];
-      int syy = sy + xy[i][1];
+      int xx = x + xy[i].x;
+      int yy = y + xy[i].y;
+      int sxx = sx + xy[i].x;
+      int syy = sy + xy[i].y;
 
       if (!IN_LEV_FIELD(xx, yy) ||
          !IN_SCR_FIELD(sxx, syy))
@@ -2428,22 +2449,16 @@ void DrawLevelFieldCrumbledDigging(int x, int y, int direction,
 void DrawLevelFieldCrumbledNeighbours(int x, int y)
 {
   int sx = SCREENX(x), sy = SCREENY(y);
-  static int xy[4][2] =
-  {
-    { 0, -1 },
-    { -1, 0 },
-    { +1, 0 },
-    { 0, +1 }
-  };
+  struct XY *xy = xy_topdown;
   int i;
 
   // crumble direct neighbour fields (required for field borders)
   for (i = 0; i < 4; i++)
   {
-    int xx = x + xy[i][0];
-    int yy = y + xy[i][1];
-    int sxx = sx + xy[i][0];
-    int syy = sy + xy[i][1];
+    int xx = x + xy[i].x;
+    int yy = y + xy[i].y;
+    int sxx = sx + xy[i].x;
+    int syy = sy + xy[i].y;
 
     if (!IN_LEV_FIELD(xx, yy) ||
        !IN_SCR_FIELD(sxx, syy) ||
@@ -2517,6 +2532,11 @@ void DrawScreenGraphic(int x, int y, int graphic, int frame)
   }
 }
 
+void DrawLevelGraphic(int x, int y, int graphic, int frame)
+{
+  DrawScreenGraphic(SCREENX(x), SCREENY(y), graphic, frame);
+}
+
 void DrawScreenElement(int x, int y, int element)
 {
   int mask_mode = NO_MASKING;
@@ -2864,9 +2884,9 @@ static void AnimateEnvelope(int envelope_nr, int anim_mode, int action)
   int mask_mode = (src_bitmap != NULL ? BLIT_MASKED : BLIT_ON_BACKGROUND);
   boolean ffwd_delay = (tape.playing && tape.fast_forward);
   boolean no_delay = (tape.warp_forward);
-  unsigned int anim_delay = 0;
   int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
   int anim_delay_value = MAX(1, (no_delay ? 0 : frame_delay_value) / 2);
+  DelayCounter anim_delay = { anim_delay_value };
   int font_nr = FONT_ENVELOPE_1 + envelope_nr;
   int font_width = getFontWidth(font_nr);
   int font_height = getFontHeight(font_nr);
@@ -2903,16 +2923,16 @@ static void AnimateEnvelope(int envelope_nr, int anim_mode, int action)
       for (xx = 0; xx < xsize; xx++)
        DrawEnvelopeBackground(graphic, sx, sy, xx, yy, xsize, ysize, font_nr);
 
-    DrawTextBuffer(sx + font_width, sy + font_height,
-                  level.envelope[envelope_nr].text, font_nr, max_xsize,
-                  xsize - 2, ysize - 2, 0, mask_mode,
-                  level.envelope[envelope_nr].autowrap,
-                  level.envelope[envelope_nr].centered, FALSE);
+    DrawTextArea(sx + font_width, sy + font_height,
+                level.envelope[envelope_nr].text, font_nr, max_xsize,
+                xsize - 2, ysize - 2, 0, mask_mode,
+                level.envelope[envelope_nr].autowrap,
+                level.envelope[envelope_nr].centered, FALSE);
 
     redraw_mask |= REDRAW_FIELD;
     BackToFront();
 
-    SkipUntilDelayReached(&anim_delay, anim_delay_value, &i, last_frame);
+    SkipUntilDelayReached(&anim_delay, &i, last_frame);
   }
 
   ClearAutoRepeatKeyEvents();
@@ -3182,7 +3202,7 @@ static void AnimateEnvelopeRequest(int anim_mode, int action)
   boolean no_delay = (tape.warp_forward);
   int delay_value = (ffwd_delay ? delay_value_fast : delay_value_normal);
   int anim_delay_value = MAX(1, (no_delay ? 0 : delay_value + 500 * 0) / 2);
-  unsigned int anim_delay = 0;
+  DelayCounter anim_delay = { anim_delay_value };
 
   int tile_size = MAX(request.step_offset, 1);
   int max_xsize = request.width  / tile_size;
@@ -3255,7 +3275,7 @@ static void AnimateEnvelopeRequest(int anim_mode, int action)
 
     BackToFront();
 
-    SkipUntilDelayReached(&anim_delay, anim_delay_value, &i, last_frame);
+    SkipUntilDelayReached(&anim_delay, &i, last_frame);
   }
 
   ClearAutoRepeatKeyEvents();
@@ -3526,15 +3546,17 @@ static void DrawPreviewLevelInfo(int mode)
 
 static void DrawPreviewLevelExt(boolean restart)
 {
-  static unsigned int scroll_delay = 0;
-  static unsigned int label_delay = 0;
+  static DelayCounter scroll_delay = { 0 };
+  static DelayCounter label_delay = { 0 };
   static int from_x, from_y, scroll_direction;
   static int label_state, label_counter;
-  unsigned int scroll_delay_value = preview.step_delay;
   boolean show_level_border = (BorderElement != EL_EMPTY);
   int level_xsize = lev_fieldx + (show_level_border ? 2 : 0);
   int level_ysize = lev_fieldy + (show_level_border ? 2 : 0);
 
+  scroll_delay.value = preview.step_delay;
+  label_delay.value = MICROLEVEL_LABEL_DELAY;
+
   if (restart)
   {
     from_x = 0;
@@ -3588,7 +3610,7 @@ static void DrawPreviewLevelExt(boolean restart)
   // scroll preview level, if needed
   if (preview.anim_mode != ANIM_NONE &&
       (level_xsize > preview.xsize || level_ysize > preview.ysize) &&
-      DelayReached(&scroll_delay, scroll_delay_value))
+      DelayReached(&scroll_delay))
   {
     switch (scroll_direction)
     {
@@ -3646,7 +3668,7 @@ static void DrawPreviewLevelExt(boolean restart)
   if (!strEqual(level.name, NAMELESS_LEVEL_NAME) &&
       !strEqual(level.author, ANONYMOUS_NAME) &&
       !strEqual(level.author, leveldir_current->name) &&
-      DelayReached(&label_delay, MICROLEVEL_LABEL_DELAY))
+      DelayReached(&label_delay))
   {
     int max_label_counter = 23;
 
@@ -4583,7 +4605,7 @@ static int RequestHandleEvents(unsigned int req_state, int draw_buffer_game)
 
          case EVENT_KEYPRESS:
          {
-           Key key = GetEventKey((KeyEvent *)&event, TRUE);
+           Key key = GetEventKey((KeyEvent *)&event);
 
            switch (key)
            {
@@ -5330,8 +5352,7 @@ unsigned int MoveDoor(unsigned int door_state)
   };
   static int door1 = DOOR_CLOSE_1;
   static int door2 = DOOR_CLOSE_2;
-  unsigned int door_delay = 0;
-  unsigned int door_delay_value;
+  DelayCounter door_delay = { 0 };
   int i;
 
   if (door_state == DOOR_GET_STATE)
@@ -5446,7 +5467,7 @@ unsigned int MoveDoor(unsigned int door_state)
     num_move_steps = max_move_delay / max_step_delay;
     num_move_steps_doors_only = max_move_delay_doors_only / max_step_delay;
 
-    door_delay_value = max_step_delay;
+    door_delay.value = max_step_delay;
 
     if ((door_state & DOOR_NO_DELAY) || setup.quick_doors)
     {
@@ -5529,7 +5550,7 @@ unsigned int MoveDoor(unsigned int door_state)
        {
          int k2_door = (door_opening ? k : num_move_steps_doors_only - k - 1);
          int kk_door = MAX(0, k2_door);
-         int sync_frame = kk_door * door_delay_value;
+         int sync_frame = kk_door * door_delay.value;
          int frame = getGraphicAnimationFrame(dpc->graphic, sync_frame);
 
          getFixedGraphicSource(dpc->graphic, frame, &bitmap,
@@ -5638,7 +5659,7 @@ unsigned int MoveDoor(unsigned int door_state)
       {
        BackToFront();
 
-       SkipUntilDelayReached(&door_delay, door_delay_value, &k, last_frame);
+       SkipUntilDelayReached(&door_delay, &k, last_frame);
 
        // prevent OS (Windows) from complaining about program not responding
        CheckQuitEvent();
@@ -5652,13 +5673,13 @@ unsigned int MoveDoor(unsigned int door_state)
     {
       // wait for specified door action post delay
       if (door_state & DOOR_ACTION_1 && door_state & DOOR_ACTION_2)
-       door_delay_value = MAX(door_1.post_delay, door_2.post_delay);
+       door_delay.value = MAX(door_1.post_delay, door_2.post_delay);
       else if (door_state & DOOR_ACTION_1)
-       door_delay_value = door_1.post_delay;
+       door_delay.value = door_1.post_delay;
       else if (door_state & DOOR_ACTION_2)
-       door_delay_value = door_2.post_delay;
+       door_delay.value = door_2.post_delay;
 
-      while (!DelayReached(&door_delay, door_delay_value))
+      while (!DelayReached(&door_delay))
        BackToFront();
     }
   }
@@ -5835,6 +5856,10 @@ void CreateToolButtons(void)
     int y = pos->y;
     int id = i;
 
+    // do not use touch buttons if overlay touch buttons are disabled
+    if (is_touch_button && !setup.touch.overlay_buttons)
+      continue;
+
     if (global.use_envelope_request && !is_touch_button)
     {
       setRequestPosition(&base_x, &base_y, TRUE);
@@ -8763,6 +8788,8 @@ void SetGfxAnimation_EM(struct GraphicInfo_EM *g_em,
 
   if (graphic_info[graphic].anim_global_sync)
     sync_frame = FrameCounter;
+  else if (graphic_info[graphic].anim_global_anim_sync)
+    sync_frame = getGlobalAnimSyncFrame();
   else if (IN_FIELD(x, y, MAX_LEV_FIELDX, MAX_LEV_FIELDY))
     sync_frame = GfxFrame[x][y];
   else
@@ -8822,6 +8849,8 @@ void getGraphicSourceObjectExt_EM(struct GraphicInfo_EM *g_em,
 
   if (graphic_info[graphic].anim_global_sync)
     sync_frame = FrameCounter;
+  else if (graphic_info[graphic].anim_global_anim_sync)
+    sync_frame = getGlobalAnimSyncFrame();
   else if (IN_FIELD(x, y, MAX_LEV_FIELDX, MAX_LEV_FIELDY))
     sync_frame = GfxFrame[x][y];
   else
@@ -9285,7 +9314,7 @@ void InitGraphicInfo_EM(void)
   }
 }
 
-static void CheckSaveEngineSnapshot_EM(byte action[MAX_PLAYERS], int frame,
+static void CheckSaveEngineSnapshot_EM(int frame,
                                       boolean any_player_moving,
                                       boolean any_player_snapping,
                                       boolean any_player_dropping)
@@ -9342,7 +9371,7 @@ static void CheckSaveEngineSnapshot_MM(boolean element_clicked,
   }
 }
 
-boolean CheckSingleStepMode_EM(byte action[MAX_PLAYERS], int frame,
+boolean CheckSingleStepMode_EM(int frame,
                               boolean any_player_moving,
                               boolean any_player_snapping,
                               boolean any_player_dropping)
@@ -9351,7 +9380,7 @@ boolean CheckSingleStepMode_EM(byte action[MAX_PLAYERS], int frame,
     if (frame == 7 && !any_player_dropping && FrameCounter > 6)
       TapeTogglePause(TAPE_TOGGLE_AUTOMATIC);
 
-  CheckSaveEngineSnapshot_EM(action, frame, any_player_moving,
+  CheckSaveEngineSnapshot_EM(frame, any_player_moving,
                             any_player_snapping, any_player_dropping);
 
   return tape.pausing;
@@ -9385,7 +9414,7 @@ void CheckSingleStepMode_MM(boolean element_clicked,
 }
 
 void getGraphicSource_SP(struct GraphicInfo_SP *g_sp,
-                        int graphic, int sync_frame, int x, int y)
+                        int graphic, int sync_frame)
 {
   int frame = getGraphicAnimationFrame(graphic, sync_frame);
 
@@ -9724,9 +9753,9 @@ void ChangeViewportPropertiesIfNeeded(void)
 {
   boolean use_mini_tilesize = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
                               FALSE : setup.small_game_graphics);
-  int gfx_game_mode = game_status;
-  int gfx_game_mode2 = (game_status == GAME_MODE_EDITOR ? GAME_MODE_DEFAULT :
-                       game_status);
+  int gfx_game_mode = getGlobalGameStatus(game_status);
+  int gfx_game_mode2 = (gfx_game_mode == GAME_MODE_EDITOR ? GAME_MODE_DEFAULT :
+                       gfx_game_mode);
   struct RectWithBorder *vp_window    = &viewport.window[gfx_game_mode];
   struct RectWithBorder *vp_playfield = &viewport.playfield[gfx_game_mode];
   struct RectWithBorder *vp_door_1    = &viewport.door_1[gfx_game_mode];
@@ -9939,12 +9968,28 @@ void ChangeViewportPropertiesIfNeeded(void)
   }
 }
 
+void OpenURL(char *url)
+{
+#if SDL_VERSION_ATLEAST(2,0,14)
+  SDL_OpenURL(url);
+#else
+  Warn("SDL_OpenURL(\"%s\") not supported by SDL %d.%d.%d!",
+       url, SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_PATCHLEVEL);
+  Warn("Please upgrade to at least SDL 2.0.14 for URL support!");
+#endif
+}
+
+void OpenURLFromHash(SetupFileHash *hash, int hash_key)
+{
+  OpenURL(getHashEntry(hash, int2str(hash_key, 0)));
+}
+
 
 // ============================================================================
 // tests
 // ============================================================================
 
-#if defined(PLATFORM_WIN32)
+#if defined(PLATFORM_WINDOWS)
 /* FILETIME of Jan 1 1970 00:00:00. */
 static const unsigned __int64 epoch = ((unsigned __int64) 116444736000000000ULL);
 
@@ -9998,7 +10043,7 @@ static char *test_init_uuid_random_function_better(void)
   return seed_text;
 }
 
-#if defined(PLATFORM_WIN32)
+#if defined(PLATFORM_WINDOWS)
 static char *test_init_uuid_random_function_better_windows(void)
 {
   static char seed_text[100];
@@ -10026,7 +10071,7 @@ static unsigned int test_uuid_random_function_better(int max)
   return (max > 0 ? prng_get_uint() % max : 0);
 }
 
-#if defined(PLATFORM_WIN32)
+#if defined(PLATFORM_WINDOWS)
 #define NUM_UUID_TESTS                 3
 #else
 #define NUM_UUID_TESTS                 2
@@ -10053,7 +10098,7 @@ static void TestGeneratingUUIDs_RunTest(int nr, int always_seed, int num_uuids)
      test_uuid_random_function_better);
   int xpos = 40;
 
-#if defined(PLATFORM_WIN32)
+#if defined(PLATFORM_WINDOWS)
   if (nr == 2)
   {
     random_name = "windows";