changed "http" to "https" in URLs
[rocksndiamonds.git] / src / anim.c
index 9d1f0b5c734dc75200cc077ff88d826051ceb443..dec9d18ee61d49cf1667e61298111e2c9f9060d0 100644 (file)
@@ -4,7 +4,7 @@
 // (c) 1995-2014 by Artsoft Entertainment
 //                         Holger Schemel
 //                 info@artsoft.org
-//                 http://www.artsoft.org/
+//                 https://www.artsoft.org/
 // ----------------------------------------------------------------------------
 // anim.c
 // ============================================================================
@@ -19,6 +19,7 @@
 #include "screens.h"
 
 
+#define DEBUG_ANIM_DELAY               0
 #define DEBUG_ANIM_EVENTS              0
 
 
@@ -107,6 +108,7 @@ struct GlobalAnimPartControlInfo
   int step_xoffset, step_yoffset;
 
   unsigned int initial_anim_sync_frame;
+  unsigned int anim_random_frame;
   unsigned int step_delay, step_delay_value;
 
   int init_delay_counter;
@@ -209,6 +211,7 @@ struct AnimClassGameMode
 };
 
 // forward declaration for internal use
+static void DoGlobalAnim_DelayAction(struct GlobalAnimPartControlInfo *, int);
 static boolean DoGlobalAnim_EventAction(struct GlobalAnimPartControlInfo *);
 static void HandleGlobalAnim(int, int);
 static void DoAnimationExt(void);
@@ -228,6 +231,8 @@ static int anim_classes_last = ANIM_CLASS_NONE;
 
 static boolean drawing_to_fading_buffer = FALSE;
 
+static boolean handle_click = FALSE;
+
 
 // ============================================================================
 // generic animation frame calculation
@@ -238,6 +243,9 @@ int getAnimationFrame(int num_frames, int delay, int mode, int start_frame,
 {
   int frame = 0;
 
+  if (delay < 1)                       // delay must be at least 1
+    delay = 1;
+
   sync_frame += start_frame * delay;
 
   if (mode & ANIM_LOOP)                        // looping animation
@@ -405,6 +413,7 @@ static void InitToonControls(void)
     part->control_info.y = ARG_UNDEFINED_VALUE;
 
     part->initial_anim_sync_frame = 0;
+    part->anim_random_frame = -1;
 
     part->step_delay = 0;
     part->step_delay_value = graphic_info[control].step_delay;
@@ -509,6 +518,7 @@ static void InitGlobalAnimControls(void)
        part->control_info = graphic_info[control];
 
        part->initial_anim_sync_frame = 0;
+       part->anim_random_frame = -1;
 
        part->step_delay = 0;
        part->step_delay_value = graphic_info[control].step_delay;
@@ -532,6 +542,20 @@ static void InitGlobalAnimControls(void)
          anim->base = *part;
          anim->has_base = TRUE;
        }
+
+       // apply special settings for pointer-style animations
+       if (part->control_info.class == get_hash_from_key("pointer"))
+       {
+         // force animation to be on top (must set anim and part control)
+         if (anim->control_info.draw_order == 0)
+           anim->control_info.draw_order = 1000000;
+         if (part->control_info.draw_order == 0)
+           part->control_info.draw_order = 1000000;
+
+         // force animation to pass-through clicks (must set part control)
+         if (part->control_info.style == STYLE_DEFAULT)
+           part->control_info.style |= STYLE_PASSTHROUGH;
+       }
       }
 
       if (anim->num_parts > 0 || anim->has_base)
@@ -746,6 +770,7 @@ static void DrawGlobalAnimationsExt(int drawing_target, int drawing_stage)
          (g->draw_masked ? BlitBitmapMasked : BlitBitmap);
        void (*blit_screen)(Bitmap *, int, int, int, int, int, int) =
          (g->draw_masked ? BlitToScreenMasked : BlitToScreen);
+       int last_anim_random_frame = gfx.anim_random_frame;
 
        if (!(part->state & ANIM_STATE_RUNNING))
          continue;
@@ -778,10 +803,21 @@ static void DrawGlobalAnimationsExt(int drawing_target, int drawing_stage)
        dst_y += part->viewport_y;
 
        sync_frame = anim_sync_frame - part->initial_anim_sync_frame;
+
+       // re-initialize random animation frame after animation delay
+       if (g->anim_mode == ANIM_RANDOM &&
+           sync_frame % g->anim_delay == 0 &&
+           sync_frame > 0)
+         part->anim_random_frame = GetSimpleRandom(g->anim_frames);
+
+       gfx.anim_random_frame = part->anim_random_frame;
+
        frame = getAnimationFrame(g->anim_frames, g->anim_delay,
                                  g->anim_mode, g->anim_start_frame,
                                  sync_frame);
 
+       gfx.anim_random_frame = last_anim_random_frame;
+
        getFixedGraphicSource(part->graphic, frame, &src_bitmap,
                              &src_x, &src_y);
 
@@ -811,13 +847,24 @@ static void DrawGlobalAnimationsExt(int drawing_target, int drawing_stage)
 
 void DrawGlobalAnimations(int drawing_target, int drawing_stage)
 {
+  int last_cursor_mode_override = gfx.cursor_mode_override;
+
   if (drawing_stage == DRAW_GLOBAL_ANIM_STAGE_1)
+  {
     ResetGlobalAnim_Clickable();
 
+    gfx.cursor_mode_override = CURSOR_UNDEFINED;
+  }
+
   DrawGlobalAnimationsExt(drawing_target, drawing_stage);
 
   if (drawing_stage == DRAW_GLOBAL_ANIM_STAGE_2)
+  {
     ResetGlobalAnim_Clicked();
+  }
+
+  if (gfx.cursor_mode_override != last_cursor_mode_override)
+    SetMouseCursor(gfx.cursor_mode);
 }
 
 static boolean SetGlobalAnimPart_Viewport(struct GlobalAnimPartControlInfo *part)
@@ -828,7 +875,8 @@ static boolean SetGlobalAnimPart_Viewport(struct GlobalAnimPartControlInfo *part
   int viewport_height;
   boolean changed = FALSE;
 
-  if (part->last_anim_status == global.anim_status)
+  if (part->last_anim_status == global.anim_status &&
+      part->control_info.class != get_hash_from_key("pointer"))
     return FALSE;
 
   part->last_anim_status = global.anim_status;
@@ -845,6 +893,27 @@ static boolean SetGlobalAnimPart_Viewport(struct GlobalAnimPartControlInfo *part
 
     part->drawing_stage = DRAW_GLOBAL_ANIM_STAGE_2;
   }
+  else if (part->control_info.class == get_hash_from_key("pointer"))
+  {
+    int mx = MIN(MAX(0, gfx.mouse_x), WIN_XSIZE - 1);
+    int my = MIN(MAX(0, gfx.mouse_y), WIN_YSIZE - 1);
+
+    // prevent displaying off-screen custom mouse cursor in upper left corner
+    if (gfx.mouse_x == POS_OFFSCREEN &&
+       gfx.mouse_y == POS_OFFSCREEN)
+      mx = my = POS_OFFSCREEN;
+
+    viewport_x = mx - part->control_info.x;
+    viewport_y = my - part->control_info.y;
+    viewport_width  = part->graphic_info.width;
+    viewport_height = part->graphic_info.height;
+
+    part->drawing_stage = DRAW_GLOBAL_ANIM_STAGE_2;
+
+    // do not use global animation mouse pointer when reloading artwork
+    if (global.anim_status != GAME_MODE_LOADING)
+      gfx.cursor_mode_override = CURSOR_NONE;
+  }
   else if (part->control_info.class == get_hash_from_key("door_1"))
   {
     viewport_x = DX;
@@ -887,7 +956,8 @@ static boolean SetGlobalAnimPart_Viewport(struct GlobalAnimPartControlInfo *part
     part->viewport_width  = viewport_width;
     part->viewport_height = viewport_height;
 
-    changed = TRUE;
+    if (part->control_info.class != get_hash_from_key("pointer"))
+      changed = TRUE;
   }
 
   return changed;
@@ -1076,13 +1146,18 @@ static boolean isClickedPart(struct GlobalAnimPartControlInfo *part,
   return TRUE;
 }
 
+static boolean clickBlocked(struct GlobalAnimPartControlInfo *part)
+{
+  return (part->control_info.style & STYLE_BLOCK ? TRUE : FALSE);
+}
+
 static boolean clickConsumed(struct GlobalAnimPartControlInfo *part)
 {
   return (part->control_info.style & STYLE_PASSTHROUGH ? FALSE : TRUE);
 }
 
 static void InitGlobalAnim_Triggered(struct GlobalAnimPartControlInfo *part,
-                                    boolean *anything_clicked,
+                                    boolean *click_consumed,
                                     boolean *any_event_action,
                                     int event_value, char *info_text)
 {
@@ -1106,13 +1181,13 @@ static void InitGlobalAnim_Triggered(struct GlobalAnimPartControlInfo *part,
     {
       struct GlobalAnimPartControlInfo *part2 = &anim2->part[part2_nr];
 
-      if (part2->state != ANIM_STATE_RUNNING)
+      if (!(part2->state & ANIM_STATE_RUNNING))
        continue;
 
       if (isClickablePart(part2, mask))
       {
        part2->triggered = TRUE;
-       *anything_clicked = clickConsumed(part);        // click was on "part"!
+       *click_consumed |= clickConsumed(part);         // click was on "part"!
 
 #if DEBUG_ANIM_EVENTS
        printf("::: => %d.%d TRIGGERED BY %s OF %d.%d\n",
@@ -1145,6 +1220,16 @@ static void InitGlobalAnim_Triggered(struct GlobalAnimPartControlInfo *part,
   }
 }
 
+static void HandleGlobalAnimDelay(struct GlobalAnimPartControlInfo *part,
+                                 int delay_type, char *info_text)
+{
+#if DEBUG_ANIM_DELAY
+  printf("::: %d.%d %s\n", part->old_anim_nr + 1, part->old_nr + 1, info_text);
+#endif
+
+  DoGlobalAnim_DelayAction(part, delay_type);
+}
+
 static void HandleGlobalAnimEvent(struct GlobalAnimPartControlInfo *part,
                                  int event_value, char *info_text)
 {
@@ -1152,17 +1237,20 @@ static void HandleGlobalAnimEvent(struct GlobalAnimPartControlInfo *part,
   printf("::: %d.%d %s\n", part->old_anim_nr + 1, part->old_nr + 1, info_text);
 #endif
 
-  boolean anything_clicked = FALSE;
+  boolean click_consumed = FALSE;
   boolean any_event_action = FALSE;
 
   // check if this event is defined to trigger other animations
-  InitGlobalAnim_Triggered(part, &anything_clicked, &any_event_action,
+  InitGlobalAnim_Triggered(part, &click_consumed, &any_event_action,
                           event_value, info_text);
 }
 
 static int HandleGlobalAnim_Part(struct GlobalAnimPartControlInfo *part,
                                 int state)
 {
+  if (handle_click && !part->clicked)
+    return state;
+
   struct GlobalAnimControlInfo *ctrl = &global_anim_ctrl[part->mode_nr];
   struct GlobalAnimMainControlInfo *anim = &ctrl->anim[part->anim_nr];
   struct GraphicInfo *g = &part->graphic_info;
@@ -1187,12 +1275,18 @@ static int HandleGlobalAnim_Part(struct GlobalAnimPartControlInfo *part,
     part->anim_delay_counter =
       (c->anim_delay_fixed + GetSimpleRandom(c->anim_delay_random));
 
+    part->post_delay_counter = 0;
+
     part->init_event_state = (c->init_event != ANIM_EVENT_UNDEFINED);
     part->anim_event_state = (c->anim_event != ANIM_EVENT_UNDEFINED);
 
     part->initial_anim_sync_frame =
       (g->anim_global_sync ? 0 : anim_sync_frame + part->init_delay_counter);
 
+    // do not re-initialize random animation frame after fade-in
+    if (part->anim_random_frame == -1)
+      part->anim_random_frame = GetSimpleRandom(g->anim_frames);
+
     if (c->direction & MV_HORIZONTAL)
     {
       int pos_bottom = part->viewport_height - g->height;
@@ -1256,10 +1350,13 @@ static int HandleGlobalAnim_Part(struct GlobalAnimPartControlInfo *part,
       part->step_yoffset = 0;
     }
 
-    if (c->x != ARG_UNDEFINED_VALUE)
-      part->x = c->x;
-    if (c->y != ARG_UNDEFINED_VALUE)
-      part->y = c->y;
+    if (part->control_info.class != get_hash_from_key("pointer"))
+    {
+      if (c->x != ARG_UNDEFINED_VALUE)
+       part->x = c->x;
+      if (c->y != ARG_UNDEFINED_VALUE)
+       part->y = c->y;
+    }
 
     if (c->position == POS_LAST &&
        anim->last_x > -g->width  && anim->last_x < part->viewport_width &&
@@ -1279,6 +1376,7 @@ static int HandleGlobalAnim_Part(struct GlobalAnimPartControlInfo *part,
     {
       PlayGlobalAnimSoundAndMusic(part);
 
+      HandleGlobalAnimDelay(part, ANIM_DELAY_INIT,  "START [INIT_DELAY]");
       HandleGlobalAnimEvent(part, ANIM_EVENT_START, "START [ANIM]");
     }
     else
@@ -1318,6 +1416,7 @@ static int HandleGlobalAnim_Part(struct GlobalAnimPartControlInfo *part,
 
       PlayGlobalAnimSoundAndMusic(part);
 
+      HandleGlobalAnimDelay(part, ANIM_DELAY_INIT,  "START [INIT_DELAY]");
       HandleGlobalAnimEvent(part, ANIM_EVENT_START, "START [ANIM]");
     }
 
@@ -1368,7 +1467,8 @@ static int HandleGlobalAnim_Part(struct GlobalAnimPartControlInfo *part,
 
       StopGlobalAnimSoundAndMusic(part);
 
-      HandleGlobalAnimEvent(part, ANIM_EVENT_END, "END [ANIM_DELAY/EVENT]");
+      HandleGlobalAnimDelay(part, ANIM_DELAY_ANIM, "END [ANIM_DELAY]");
+      HandleGlobalAnimEvent(part, ANIM_EVENT_END,  "END [ANIM_DELAY/EVENT]");
 
       part->post_delay_counter =
        (c->post_delay_fixed + GetSimpleRandom(c->post_delay_random));
@@ -1387,6 +1487,7 @@ static int HandleGlobalAnim_Part(struct GlobalAnimPartControlInfo *part,
 
     if (part->post_delay_counter == 0)
     {
+      HandleGlobalAnimDelay(part, ANIM_DELAY_POST, "END [POST_DELAY]");
       HandleGlobalAnimEvent(part, ANIM_EVENT_POST, "END [POST_DELAY]");
 
       return ANIM_STATE_RESTART;
@@ -1618,22 +1719,37 @@ static void DoAnimationExt(void)
 #endif
 }
 
+static void DoGlobalAnim_DelayAction(struct GlobalAnimPartControlInfo *part,
+                                    int delay_type)
+{
+  int delay_action =
+    (delay_type == ANIM_DELAY_INIT ? part->control_info.init_delay_action :
+     delay_type == ANIM_DELAY_ANIM ? part->control_info.anim_delay_action :
+     delay_type == ANIM_DELAY_POST ? part->control_info.post_delay_action :
+     ANIM_DELAY_ACTION_NONE);
+
+  if (delay_action == ANIM_DELAY_ACTION_NONE)
+    return;
+
+  PushUserEvent(USEREVENT_ANIM_DELAY_ACTION, delay_action, 0);
+}
+
 static boolean DoGlobalAnim_EventAction(struct GlobalAnimPartControlInfo *part)
 {
-  int anim_event_action = part->control_info.anim_event_action;
+  int event_action = (part->init_event_state ?
+                     part->control_info.init_event_action :
+                     part->control_info.anim_event_action);
 
-  if (anim_event_action == -1)
+  if (event_action == ANIM_EVENT_ACTION_NONE)
     return FALSE;
 
-  boolean action_executed = (DoGadgetAction(anim_event_action) ||
-                            DoScreenAction(anim_event_action) ||
-                            DoKeysymAction(anim_event_action));
+  PushUserEvent(USEREVENT_ANIM_EVENT_ACTION, event_action, 0);
 
   // check if further actions are allowed to be executed
   if (part->control_info.style & STYLE_MULTIPLE_ACTIONS)
     return FALSE;
 
-  return action_executed;
+  return TRUE;
 }
 
 static void InitGlobalAnim_Clickable(void)
@@ -1670,10 +1786,12 @@ static void InitGlobalAnim_Clickable(void)
 
 static boolean InitGlobalAnim_Clicked(int mx, int my, int clicked_event)
 {
+  boolean click_consumed = FALSE;
   boolean anything_clicked = FALSE;
   boolean any_part_clicked = FALSE;
   boolean any_event_action = FALSE;
   int mode_nr;
+  int i;
 
   // check game modes in reverse draw order (to stop when clicked)
   for (mode_nr = NUM_GAME_MODES - 1; mode_nr >= 0; mode_nr--)
@@ -1702,7 +1820,7 @@ static boolean InitGlobalAnim_Clicked(int mx, int my, int clicked_event)
        if (!part->clickable)
          continue;
 
-       if (part->state != ANIM_STATE_RUNNING)
+       if (!(part->state & ANIM_STATE_RUNNING))
          continue;
 
        // always handle "any" click events (clicking anywhere on screen) ...
@@ -1714,8 +1832,8 @@ static boolean InitGlobalAnim_Clicked(int mx, int my, int clicked_event)
                 part->old_anim_nr + 1, part->old_nr + 1);
 #endif
 
-         part->clicked = TRUE;
-         anything_clicked = clickConsumed(part);
+         anything_clicked = part->clicked = TRUE;
+         click_consumed |= clickConsumed(part);
        }
 
        // always handle "unclick:any" events (releasing anywhere on screen) ...
@@ -1727,8 +1845,8 @@ static boolean InitGlobalAnim_Clicked(int mx, int my, int clicked_event)
                 part->old_anim_nr + 1, part->old_nr + 1);
 #endif
 
-         part->clicked = TRUE;
-         anything_clicked = clickConsumed(part);
+         anything_clicked = part->clicked = TRUE;
+         click_consumed |= clickConsumed(part);
        }
 
        // ... but only handle the first (topmost) clickable animation
@@ -1748,7 +1866,7 @@ static boolean InitGlobalAnim_Clicked(int mx, int my, int clicked_event)
            any_event_action = TRUE;
 
          // determine if mouse clicks should be blocked from other animations
-         any_part_clicked = clickConsumed(part);
+         any_part_clicked |= clickConsumed(part);
 
          if (isClickablePart(part, ANIM_EVENT_SELF))
          {
@@ -1757,19 +1875,35 @@ static boolean InitGlobalAnim_Clicked(int mx, int my, int clicked_event)
                   part->old_anim_nr + 1, part->old_nr + 1);
 #endif
 
-           part->clicked = TRUE;
-           anything_clicked = clickConsumed(part);
+           anything_clicked = part->clicked = TRUE;
+           click_consumed |= clickConsumed(part);
          }
 
+         // determine if mouse clicks should be blocked by this animation
+         click_consumed |= clickBlocked(part);
+
          // check if this click is defined to trigger other animations
-         InitGlobalAnim_Triggered(part, &anything_clicked, &any_event_action,
+         InitGlobalAnim_Triggered(part, &click_consumed, &any_event_action,
                                   ANIM_EVENT_CLICK, "CLICK");
        }
       }
     }
   }
 
-  return (anything_clicked || any_event_action);
+  if (anything_clicked)
+  {
+    handle_click = TRUE;
+
+    for (i = 0; i < NUM_GAME_MODES; i++)
+      HandleGlobalAnim(ANIM_CONTINUE, i);
+
+    handle_click = FALSE;
+
+    // prevent ignoring release event if processed within same game frame
+    StopProcessingEvents();
+  }
+
+  return (click_consumed || any_event_action);
 }
 
 static void ResetGlobalAnim_Clickable(void)
@@ -1782,7 +1916,7 @@ static void ResetGlobalAnim_Clicked(void)
   InitGlobalAnim_Clicked(-1, -1, ANIM_CLICKED_RESET);
 }
 
-boolean HandleGlobalAnimClicks(int mx, int my, int button)
+boolean HandleGlobalAnimClicks(int mx, int my, int button, boolean force_click)
 {
   static boolean click_consumed = FALSE;
   static int last_button = 0;
@@ -1790,6 +1924,9 @@ boolean HandleGlobalAnimClicks(int mx, int my, int button)
   boolean release_event;
   boolean click_consumed_current = click_consumed;
 
+  if (button != 0 && force_click)
+    last_button = 0;
+
   // check if button state has changed since last invocation
   press_event   = (button != 0 && last_button == 0);
   release_event = (button == 0 && last_button != 0);