added using setup option for displaying overlay touch buttons
[rocksndiamonds.git] / src / tools.c
index 38f5dd7bad62e7b5e731ed006a71ad9fc2ef4af1..79cf0885135681310694fdc4dc562fd40dfa5cf0 100644 (file)
@@ -512,6 +512,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;
 
@@ -1091,6 +1095,13 @@ void FadeSkipNextFadeOut(void)
   FadeExt(0, FADE_MODE_SKIP_FADE_OUT, FADE_TYPE_SKIP);
 }
 
+static int getGlobalGameStatus(int status)
+{
+  return (status == GAME_MODE_PSEUDO_TYPENAME ? GAME_MODE_MAIN :
+         status == GAME_MODE_SCOREINFO       ? GAME_MODE_SCORES :
+         status);
+}
+
 static Bitmap *getBitmapFromGraphicOrDefault(int graphic, int default_graphic)
 {
   if (graphic == IMG_UNDEFINED)
@@ -1113,14 +1124,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);
@@ -1416,39 +1427,42 @@ void SetBorderElement(void)
   }
 }
 
-void FloodFillLevelExt(int from_x, int from_y, int fill_element,
+void FloodFillLevelExt(int start_x, int start_y, int fill_element,
                       int max_array_fieldx, int max_array_fieldy,
                       short field[max_array_fieldx][max_array_fieldy],
                       int max_fieldx, int max_fieldy)
 {
-  int i,x,y;
-  int old_element;
-  static int check[4][2] = { { -1, 0 }, { 0, -1 }, { 1, 0 }, { 0, 1 } };
-  static int safety = 0;
+  static struct XY stack_buffer[MAX_LEV_FIELDX * MAX_LEV_FIELDY];
+  static struct XY check[4] = { { -1, 0 }, { 0, -1 }, { 1, 0 }, { 0, 1 } };
+  int old_element = field[start_x][start_y];
+  int stack_pos = 0;
 
-  // check if starting field still has the desired content
-  if (field[from_x][from_y] == fill_element)
+  // do nothing if start field already has the desired content
+  if (old_element == fill_element)
     return;
 
-  safety++;
+  stack_buffer[stack_pos++] = (struct XY){ start_x, start_y };
 
-  if (safety > max_fieldx * max_fieldy)
-    Fail("Something went wrong in 'FloodFill()'. Please debug.");
+  while (stack_pos > 0)
+  {
+    struct XY current = stack_buffer[--stack_pos];
+    int i;
 
-  old_element = field[from_x][from_y];
-  field[from_x][from_y] = fill_element;
+    field[current.x][current.y] = fill_element;
 
-  for (i = 0; i < 4; i++)
-  {
-    x = from_x + check[i][0];
-    y = from_y + check[i][1];
+    for (i = 0; i < 4; i++)
+    {
+      int x = current.x + check[i].x;
+      int y = current.y + check[i].y;
 
-    if (IN_FIELD(x, y, max_fieldx, max_fieldy) && field[x][y] == old_element)
-      FloodFillLevelExt(x, y, fill_element, max_array_fieldx, max_array_fieldy,
-                       field, max_fieldx, max_fieldy);
-  }
+      // check for stack buffer overflow (should not happen)
+      if (stack_pos >= MAX_LEV_FIELDX * MAX_LEV_FIELDY)
+       Fail("Stack buffer overflow in 'FloodFillLevelExt()'. Please debug.");
 
-  safety--;
+      if (IN_FIELD(x, y, max_fieldx, max_fieldy) && field[x][y] == old_element)
+       stack_buffer[stack_pos++] = (struct XY){ x, y };
+    }
+  }
 }
 
 void FloodFillLevel(int from_x, int from_y, int fill_element,
@@ -1478,6 +1492,40 @@ int getGraphicAnimationFrame(int graphic, int sync_frame)
                           sync_frame);
 }
 
+int getGraphicAnimationFrameXY(int graphic, int lx, int ly)
+{
+  if (graphic_info[graphic].anim_mode & ANIM_TILED)
+  {
+    struct GraphicInfo *g = &graphic_info[graphic];
+    int xsize = MAX(1, g->anim_frames_per_line);
+    int ysize = MAX(1, g->anim_frames / xsize);
+    int xoffset = g->anim_start_frame % xsize;
+    int yoffset = g->anim_start_frame % 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;
+  }
+  else if (graphic_info[graphic].anim_mode & ANIM_RANDOM_STATIC)
+  {
+    struct GraphicInfo *g = &graphic_info[graphic];
+    // 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, sync_frame);
+  }
+}
+
 void getGraphicSourceBitmap(int graphic, int tilesize, Bitmap **bitmap)
 {
   struct GraphicInfo *g = &graphic_info[graphic];
@@ -1559,6 +1607,24 @@ void getMiniGraphicSource(int graphic, Bitmap **bitmap, int *x, int *y)
   getSizedGraphicSource(graphic, 0, MINI_TILESIZE, bitmap, x, y);
 }
 
+void getGlobalAnimGraphicSource(int graphic, int frame,
+                               Bitmap **bitmap, int *x, int *y)
+{
+  struct GraphicInfo *g = &graphic_info[graphic];
+
+  // if no graphics defined at all, use fallback graphics
+  if (g->bitmaps == NULL)
+    *g = graphic_info[IMG_CHAR_EXCLAM];
+
+  // use original size graphics, if existing, else use standard size graphics
+  if (g->bitmaps[IMG_BITMAP_PTR_ORIGINAL])
+    *bitmap = g->bitmaps[IMG_BITMAP_PTR_ORIGINAL];
+  else
+    *bitmap = g->bitmaps[IMG_BITMAP_STANDARD];
+
+  getGraphicSourceXY(graphic, frame, x, y, FALSE);
+}
+
 static void getGraphicSourceExt(int graphic, int frame, Bitmap **bitmap,
                                int *x, int *y, boolean get_backside)
 {
@@ -1949,16 +2015,19 @@ void DrawScreenElementExt(int x, int y, int dx, int dy, int element,
 
   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]);
-    frame = getGraphicAnimationFrame(graphic, GfxFrame[lx][ly]);
+    frame = getGraphicAnimationFrameXY(graphic, lx, ly);
 
     // do not use double (EM style) movement graphic when not moving
     if (graphic_info[graphic].double_movement && !dx && !dy)
     {
       graphic = el_act_dir2img(element, ACTION_DEFAULT, GfxDir[lx][ly]);
-      frame = getGraphicAnimationFrame(graphic, GfxFrame[lx][ly]);
+      frame = getGraphicAnimationFrameXY(graphic, lx, ly);
     }
 
     if (game.use_masked_elements && (dx || dy))
@@ -1967,7 +2036,7 @@ void DrawScreenElementExt(int x, int y, int dx, int dy, int element,
   else // border element
   {
     graphic = el2img(element);
-    frame = getGraphicAnimationFrame(graphic, -1);
+    frame = getGraphicAnimationFrameXY(graphic, lx, ly);
   }
 
   if (element == EL_EXPANDABLE_WALL)
@@ -2084,7 +2153,7 @@ static void DrawLevelFieldCrumbledInnerCorners(int x, int y, int dx, int dy,
   if (game.use_masked_elements)
   {
     int graphic0 = el2img(EL_EMPTY);
-    int frame0 = getGraphicAnimationFrame(graphic0, GfxFrame[x][y]);
+    int frame0 = getGraphicAnimationFrameXY(graphic0, x, y);
     Bitmap *src_bitmap0;
     int src_x0, src_y0;
 
@@ -2122,7 +2191,7 @@ static void DrawLevelFieldCrumbledBorders(int x, int y, int graphic, int frame,
 
   // only needed when using masked elements
   int graphic0 = el2img(EL_EMPTY);
-  int frame0 = getGraphicAnimationFrame(graphic0, GfxFrame[x][y]);
+  int frame0 = getGraphicAnimationFrameXY(graphic0, x, y);
   Bitmap *src_bitmap0;
   int src_x0, src_y0;
 
@@ -2465,6 +2534,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;
@@ -2851,11 +2925,11 @@ 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();
@@ -3510,8 +3584,8 @@ static void DrawPreviewLevelExt(boolean restart)
     DrawPreviewLevelInfo(MICROLABEL_LEVEL_AUTHOR);
 
     // initialize delay counters
-    DelayReached(&scroll_delay, 0);
-    DelayReached(&label_delay, 0);
+    ResetDelayCounter(&scroll_delay);
+    ResetDelayCounter(&label_delay);
 
     if (leveldir_current->name)
     {
@@ -3654,7 +3728,7 @@ void DrawPreviewPlayers(void)
     {
       int element = level.field[x][y];
 
-      if (ELEM_IS_PLAYER(element))
+      if (IS_PLAYER_ELEMENT(element))
       {
        int player_nr = GET_PLAYER_NR(element);
 
@@ -3819,10 +3893,10 @@ void ClearNetworkPlayers(void)
 }
 
 static void DrawGraphicAnimationExt(DrawBuffer *dst_bitmap, int x, int y,
-                                   int graphic, int sync_frame,
+                                   int graphic, int lx, int ly,
                                    int mask_mode)
 {
-  int frame = getGraphicAnimationFrame(graphic, sync_frame);
+  int frame = getGraphicAnimationFrameXY(graphic, lx, ly);
 
   if (mask_mode == USE_MASKING)
     DrawGraphicThruMaskExt(dst_bitmap, x, y, graphic, frame);
@@ -3860,7 +3934,7 @@ static void DrawGraphicAnimation(int x, int y, int graphic)
   }
 
   DrawGraphicAnimationExt(drawto_field, FX + x * TILEX_VAR, FY + y * TILEY_VAR,
-                         graphic, GfxFrame[lx][ly], mask_mode);
+                         graphic, lx, ly, mask_mode);
 
   MarkTileDirty(x, y);
 }
@@ -3884,7 +3958,7 @@ void DrawFixedGraphicAnimation(int x, int y, int graphic)
   }
 
   DrawGraphicAnimationExt(drawto_field, FX + x * TILEX, FY + y * TILEY,
-                         graphic, GfxFrame[lx][ly], mask_mode);
+                         graphic, lx, ly, mask_mode);
 
   MarkTileDirty(x, y);
 }
@@ -3908,9 +3982,15 @@ void DrawLevelGraphicAnimationIfNeeded(int x, int y, int graphic)
   if (!IN_LEV_FIELD(x, y) || !IN_SCR_FIELD(sx, sy))
     return;
 
+  if (Tile[x][y] == EL_EMPTY)
+    graphic = el2img(GfxElementEmpty[x][y]);
+
   if (!IS_NEW_FRAME(GfxFrame[x][y], graphic))
     return;
 
+  if (ANIM_MODE(graphic) & (ANIM_TILED | ANIM_RANDOM_STATIC))
+    return;
+
   DrawGraphicAnimation(sx, sy, graphic);
 
 #if 1
@@ -4256,7 +4336,7 @@ static void DrawPlayerExt(struct PlayerInfo *player, int drawing_stage)
     if (IS_ACTIVE_BOMB(element))
     {
       int graphic = el2img(element);
-      int frame = getGraphicAnimationFrame(graphic, GfxFrame[jx][jy]);
+      int frame = getGraphicAnimationFrameXY(graphic, jx, jy);
 
       if (game.emulation == EMU_SUPAPLEX)
        DrawGraphic(sx, sy, IMG_SP_DISK_RED, frame);
@@ -5323,7 +5403,6 @@ unsigned int MoveDoor(unsigned int door_state)
     int num_move_steps = 0;    // number of animation steps for all doors
     int max_move_delay_doors_only = 0; // delay for doors only (no panel)
     int num_move_steps_doors_only = 0; // steps for doors only (no panel)
-    int current_move_delay = 0;
     int start = 0;
     int k;
 
@@ -5583,8 +5662,6 @@ unsigned int MoveDoor(unsigned int door_state)
 
        SkipUntilDelayReached(&door_delay, door_delay_value, &k, last_frame);
 
-       current_move_delay += max_step_delay;
-
        // prevent OS (Windows) from complaining about program not responding
        CheckQuitEvent();
       }
@@ -5780,6 +5857,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);
@@ -9669,9 +9750,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];
@@ -9883,3 +9964,206 @@ void ChangeViewportPropertiesIfNeeded(void)
     InitGraphicInfo_EM();
   }
 }
+
+void OpenURL(char *url)
+{
+  SDL_OpenURL(url);
+}
+
+void OpenURLFromHash(SetupFileHash *hash, int hash_key)
+{
+  OpenURL(getHashEntry(hash, int2str(hash_key, 0)));
+}
+
+
+// ============================================================================
+// tests
+// ============================================================================
+
+#if defined(PLATFORM_WINDOWS)
+/* FILETIME of Jan 1 1970 00:00:00. */
+static const unsigned __int64 epoch = ((unsigned __int64) 116444736000000000ULL);
+
+/*
+ * timezone information is stored outside the kernel so tzp isn't used anymore.
+ *
+ * Note: this function is not for Win32 high precision timing purpose. See
+ * elapsed_time().
+ */
+static int gettimeofday_windows(struct timeval * tp, struct timezone * tzp)
+{
+  FILETIME    file_time;
+  SYSTEMTIME  system_time;
+  ULARGE_INTEGER ularge;
+
+  GetSystemTime(&system_time);
+  SystemTimeToFileTime(&system_time, &file_time);
+  ularge.LowPart = file_time.dwLowDateTime;
+  ularge.HighPart = file_time.dwHighDateTime;
+
+  tp->tv_sec = (long) ((ularge.QuadPart - epoch) / 10000000L);
+  tp->tv_usec = (long) (system_time.wMilliseconds * 1000);
+
+  return 0;
+}
+#endif
+
+static char *test_init_uuid_random_function_simple(void)
+{
+  static char seed_text[100];
+  unsigned int seed = InitSimpleRandom(NEW_RANDOMIZE);
+
+  sprintf(seed_text, "%d", seed);
+
+  return seed_text;
+}
+
+static char *test_init_uuid_random_function_better(void)
+{
+  static char seed_text[100];
+  struct timeval current_time;
+
+  gettimeofday(&current_time, NULL);
+
+  prng_seed_bytes(&current_time, sizeof(current_time));
+
+  sprintf(seed_text, "%ld.%ld",
+         (long)current_time.tv_sec,
+         (long)current_time.tv_usec);
+
+  return seed_text;
+}
+
+#if defined(PLATFORM_WINDOWS)
+static char *test_init_uuid_random_function_better_windows(void)
+{
+  static char seed_text[100];
+  struct timeval current_time;
+
+  gettimeofday_windows(&current_time, NULL);
+
+  prng_seed_bytes(&current_time, sizeof(current_time));
+
+  sprintf(seed_text, "%ld.%ld",
+         (long)current_time.tv_sec,
+         (long)current_time.tv_usec);
+
+  return seed_text;
+}
+#endif
+
+static unsigned int test_uuid_random_function_simple(int max)
+{
+  return GetSimpleRandom(max);
+}
+
+static unsigned int test_uuid_random_function_better(int max)
+{
+  return (max > 0 ? prng_get_uint() % max : 0);
+}
+
+#if defined(PLATFORM_WINDOWS)
+#define NUM_UUID_TESTS                 3
+#else
+#define NUM_UUID_TESTS                 2
+#endif
+
+static void TestGeneratingUUIDs_RunTest(int nr, int always_seed, int num_uuids)
+{
+  struct hashtable *hash_seeds =
+    create_hashtable(16, 0.75, get_hash_from_key, hash_keys_are_equal);
+  struct hashtable *hash_uuids =
+    create_hashtable(16, 0.75, get_hash_from_key, hash_keys_are_equal);
+  static char message[100];
+  int i;
+
+  char *random_name = (nr == 0 ? "simple" : "better");
+  char *random_type = (always_seed ? "always" : "only once");
+  char *(*init_random_function)(void) =
+    (nr == 0 ?
+     test_init_uuid_random_function_simple :
+     test_init_uuid_random_function_better);
+  unsigned int (*random_function)(int) =
+    (nr == 0 ?
+     test_uuid_random_function_simple :
+     test_uuid_random_function_better);
+  int xpos = 40;
+
+#if defined(PLATFORM_WINDOWS)
+  if (nr == 2)
+  {
+    random_name = "windows";
+    init_random_function = test_init_uuid_random_function_better_windows;
+  }
+#endif
+
+  ClearField();
+
+  DrawTextF(xpos, 40, FC_GREEN, "Test: Generating UUIDs");
+  DrawTextF(xpos, 80, FC_YELLOW, "Test %d.%d:", nr + 1, always_seed + 1);
+
+  DrawTextF(xpos, 100, FC_YELLOW, "Random Generator Name: %s", random_name);
+  DrawTextF(xpos, 120, FC_YELLOW, "Seeding Random Generator: %s", random_type);
+  DrawTextF(xpos, 140, FC_YELLOW, "Number of UUIDs generated: %d", num_uuids);
+
+  DrawTextF(xpos, 180, FC_GREEN, "Please wait ...");
+
+  BackToFront();
+
+  // always initialize random number generator at least once
+  init_random_function();
+
+  unsigned int time_start = SDL_GetTicks();
+
+  for (i = 0; i < num_uuids; i++)
+  {
+    if (always_seed)
+    {
+      char *seed = getStringCopy(init_random_function());
+
+      hashtable_remove(hash_seeds, seed);
+      hashtable_insert(hash_seeds, seed, "1");
+    }
+
+    char *uuid = getStringCopy(getUUIDExt(random_function));
+
+    hashtable_remove(hash_uuids, uuid);
+    hashtable_insert(hash_uuids, uuid, "1");
+  }
+
+  int num_unique_seeds = hashtable_count(hash_seeds);
+  int num_unique_uuids = hashtable_count(hash_uuids);
+
+  unsigned int time_needed = SDL_GetTicks() - time_start;
+
+  DrawTextF(xpos, 220, FC_YELLOW, "Time needed: %d ms", time_needed);
+
+  DrawTextF(xpos, 240, FC_YELLOW, "Number of unique UUIDs: %d", num_unique_uuids);
+
+  if (always_seed)
+    DrawTextF(xpos, 260, FC_YELLOW, "Number of unique seeds: %d", num_unique_seeds);
+
+  if (nr == NUM_UUID_TESTS - 1 && always_seed)
+    DrawTextF(xpos, 300, FC_GREEN, "All tests done!");
+  else
+    DrawTextF(xpos, 300, FC_GREEN, "Confirm dialog for next test ...");
+
+  sprintf(message, "Test %d.%d finished!", nr + 1, always_seed + 1);
+
+  Request(message, REQ_CONFIRM);
+
+  hashtable_destroy(hash_seeds, 0);
+  hashtable_destroy(hash_uuids, 0);
+}
+
+void TestGeneratingUUIDs(void)
+{
+  int num_uuids = 1000000;
+  int i, j;
+
+  for (i = 0; i < NUM_UUID_TESTS; i++)
+    for (j = 0; j < 2; j++)
+      TestGeneratingUUIDs_RunTest(i, j, num_uuids);
+
+  CloseAllAndExit(0);
+}