fixed bug with events defined to start and stop global animations
[rocksndiamonds.git] / src / anim.c
index bbc38dc45903bde745711a925b9ad3bc3983333e..4f1034eb35d43b4251d97332dafced2b574f0823 100644 (file)
@@ -17,6 +17,7 @@
 #include "files.h"
 #include "events.h"
 #include "screens.h"
+#include "tape.h"
 
 
 #define DEBUG_ANIM_DELAY               0
@@ -114,6 +115,8 @@ struct GlobalAnimPartControlInfo
   struct GraphicInfo graphic_info;
   struct GraphicInfo control_info;
 
+  boolean class_playfield_or_door;
+
   int viewport_x;
   int viewport_y;
   int viewport_width;
@@ -122,6 +125,9 @@ struct GlobalAnimPartControlInfo
   int x, y;
   int step_xoffset, step_yoffset;
 
+  int tile_x, tile_y;
+  int tile_xoffset, tile_yoffset;
+
   unsigned int initial_anim_sync_frame;
   unsigned int anim_random_frame;
 
@@ -304,6 +310,12 @@ int getAnimationFrame(int num_frames, int delay, int mode, int start_frame,
     else
       frame = gfx.anim_random_frame % num_frames;
   }
+  else if (mode & ANIM_LEVEL_NR)       // play frames by level number
+  {
+    int level_pos = level_nr - leveldir_current->first_level;
+
+    frame = level_pos % num_frames;
+  }
   else if (mode & (ANIM_CE_VALUE | ANIM_CE_SCORE | ANIM_CE_DELAY))
   {
     frame = sync_frame % num_frames;
@@ -369,6 +381,27 @@ static int compareGlobalAnimMainControlInfo(const void *obj1, const void *obj2)
   return compare_result;
 }
 
+static boolean isPausedOnPlayfieldOrDoor(struct GlobalAnimPartControlInfo *part)
+{
+  // only pause playfield and door animations when playing
+  if (game_status != GAME_MODE_PLAYING)
+    return FALSE;
+
+  // do not pause animations when game ended (and engine is running)
+  if (checkGameEnded())
+    return FALSE;
+
+  // only pause animations on playfield and doors
+  if (!part->class_playfield_or_door)
+    return FALSE;
+
+  // only pause animations when engine is paused or request dialog is open(ing)
+  if (!tape.pausing && !game.request_active_or_moving)
+    return FALSE;
+
+  return TRUE;
+}
+
 static void InitToonControls(void)
 {
   int mode_nr_toons = GAME_MODE_PSEUDO_TOONS;
@@ -854,6 +887,10 @@ static void DrawGlobalAnimationsExt(int drawing_target, int drawing_stage)
     }
   }
 
+  // when restarting global animations, do not redraw them, but stop here
+  if (drawing_stage == DRAW_GLOBAL_ANIM_STAGE_RESTART)
+    return;
+
   if (global.anim_status == GAME_MODE_LOADING)
     return;
 
@@ -914,6 +951,10 @@ static void DrawGlobalAnimationsExt(int drawing_target, int drawing_stage)
        if (part->drawing_stage != drawing_stage)
          continue;
 
+       // if game is paused, also pause playfield and door animations
+       if (isPausedOnPlayfieldOrDoor(part))
+         part->initial_anim_sync_frame++;
+
        sync_frame = anim_sync_frame - part->initial_anim_sync_frame;
 
        // re-initialize random animation frame after animation delay
@@ -967,8 +1008,6 @@ void DrawGlobalAnimations(int drawing_target, int drawing_stage)
     ResetGlobalAnim_Clicked();
   }
 
-  DrawEnvelopeRequestToScreen(drawing_target, drawing_stage);
-
   if (gfx.cursor_mode_override != last_cursor_mode_override)
     SetMouseCursor(gfx.cursor_mode);
 }
@@ -989,6 +1028,8 @@ static boolean SetGlobalAnimPart_Viewport(struct GlobalAnimPartControlInfo *part
 
   part->drawing_stage = DRAW_GLOBAL_ANIM_STAGE_1;
 
+  part->class_playfield_or_door = FALSE;
+
   if (part->control_info.class == get_hash_from_key("window") ||
       part->control_info.class == get_hash_from_key("border"))
   {
@@ -1026,6 +1067,8 @@ static boolean SetGlobalAnimPart_Viewport(struct GlobalAnimPartControlInfo *part
     viewport_y = DY;
     viewport_width  = DXSIZE;
     viewport_height = DYSIZE;
+
+    part->class_playfield_or_door = TRUE;
   }
   else if (part->control_info.class == get_hash_from_key("door_2"))
   {
@@ -1043,6 +1086,8 @@ static boolean SetGlobalAnimPart_Viewport(struct GlobalAnimPartControlInfo *part
       viewport_width  = VXSIZE;
       viewport_height = VYSIZE;
     }
+
+    part->class_playfield_or_door = TRUE;
   }
   else         // default: "playfield"
   {
@@ -1050,6 +1095,8 @@ static boolean SetGlobalAnimPart_Viewport(struct GlobalAnimPartControlInfo *part
     viewport_y = REAL_SY;
     viewport_width  = FULL_SXSIZE;
     viewport_height = FULL_SYSIZE;
+
+    part->class_playfield_or_door = TRUE;
   }
 
   if (viewport_x != part->viewport_x ||
@@ -1191,6 +1238,7 @@ static void PlayGlobalAnimSoundIfLoop(struct GlobalAnimPartControlInfo *part)
 static boolean checkGlobalAnimEvent(int anim_event, int mask)
 {
   int mask_anim_only = mask & ~ANIM_EVENT_PART_MASK;
+  int mask_ce_only   = mask & ~ANIM_EVENT_PAGE_MASK;
 
   if (mask & ANIM_EVENT_ANY)
     return (anim_event & ANIM_EVENT_ANY);
@@ -1199,7 +1247,8 @@ static boolean checkGlobalAnimEvent(int anim_event, int mask)
   else if (mask & ANIM_EVENT_UNCLICK_ANY)
     return (anim_event & ANIM_EVENT_UNCLICK_ANY);
   else if (mask & ANIM_EVENT_CE_CHANGE)
-    return (anim_event == mask);
+    return (anim_event == mask ||
+           anim_event == mask_ce_only);
   else
     return (anim_event == mask ||
            anim_event == mask_anim_only);
@@ -1208,24 +1257,31 @@ static boolean checkGlobalAnimEvent(int anim_event, int mask)
 static boolean isClickablePart(struct GlobalAnimPartControlInfo *part, int mask)
 {
   struct GraphicInfo *c = &part->control_info;
-  int num_init_events = GetGlobalAnimEventValueCount(c->init_event);
-  int num_anim_events = GetGlobalAnimEventValueCount(c->anim_event);
   int i;
 
-  for (i = 0; i < num_init_events; i++)
+  if (part->init_event_state)
   {
-    int init_event = GetGlobalAnimEventValue(c->init_event, i);
+    int num_init_events = GetGlobalAnimEventValueCount(c->init_event);
 
-    if (checkGlobalAnimEvent(init_event, mask))
-      return TRUE;
-  }
+    for (i = 0; i < num_init_events; i++)
+    {
+      int init_event = GetGlobalAnimEventValue(c->init_event, i);
 
-  for (i = 0; i < num_anim_events; i++)
+      if (checkGlobalAnimEvent(init_event, mask))
+       return TRUE;
+    }
+  }
+  else if (part->anim_event_state)
   {
-    int anim_event = GetGlobalAnimEventValue(c->anim_event, i);
+    int num_anim_events = GetGlobalAnimEventValueCount(c->anim_event);
+
+    for (i = 0; i < num_anim_events; i++)
+    {
+      int anim_event = GetGlobalAnimEventValue(c->anim_event, i);
 
-    if (checkGlobalAnimEvent(anim_event, mask))
-      return TRUE;
+      if (checkGlobalAnimEvent(anim_event, mask))
+       return TRUE;
+    }
   }
 
   return FALSE;
@@ -1291,6 +1347,28 @@ static boolean clickConsumed(struct GlobalAnimPartControlInfo *part)
   return ((part->control_info.style & STYLE_PASSTHROUGH) ? FALSE : TRUE);
 }
 
+static void SetGlobalAnimPartTileXY(struct GlobalAnimPartControlInfo *part)
+{
+  // calculate playfield position (with scrolling) for related CE tile
+  // (do not use FX/FY, which are incorrect during envelope requests)
+  int FX0 = 2 * TILEX_VAR;     // same as FX during DRAW_TO_FIELDBUFFER
+  int FY0 = 2 * TILEY_VAR;     // same as FY during DRAW_TO_FIELDBUFFER
+  int fx = getFieldbufferOffsetX_RND(ScreenMovDir, ScreenGfxPos);
+  int fy = getFieldbufferOffsetY_RND(ScreenMovDir, ScreenGfxPos);
+  int sx = FX0 + SCREENX(part->tile_x) * TILEX_VAR;
+  int sy = FY0 + SCREENY(part->tile_y) * TILEY_VAR;
+  int cx = SX - REAL_SX;
+  int cy = SY - REAL_SY;
+  int x = sx - fx + cx;
+  int y = sy - fy + cy;
+
+  part->tile_xoffset += part->step_xoffset;
+  part->tile_yoffset += part->step_yoffset;
+
+  part->x = x + part->tile_xoffset;
+  part->y = y + part->tile_yoffset;
+}
+
 static void InitGlobalAnim_Triggered(struct GlobalAnimPartControlInfo *part,
                                     boolean *click_consumed,
                                     boolean *any_event_action,
@@ -1358,12 +1436,16 @@ static void InitGlobalAnim_Triggered(struct GlobalAnimPartControlInfo *part,
   }
 }
 
-static void InitGlobalAnim_Triggered_ByCustomElement(int nr)
+static void InitGlobalAnim_Triggered_ByCustomElement(int nr, int page,
+                                                    int x, int y,
+                                                    int trigger_x,
+                                                    int trigger_y)
 {
   struct GlobalAnimControlInfo *ctrl = &global_anim_ctrl[GAME_MODE_PLAYING];
 
   int event_value = ANIM_EVENT_CE_CHANGE;
-  int mask = event_value | (nr << ANIM_EVENT_CE_BIT);
+  int event_bits = (nr << ANIM_EVENT_CE_BIT) | (page << ANIM_EVENT_PAGE_BIT);
+  int mask = event_value | event_bits;
   int anim2_nr;
 
   for (anim2_nr = 0; anim2_nr < ctrl->num_anims; anim2_nr++)
@@ -1378,10 +1460,34 @@ static void InitGlobalAnim_Triggered_ByCustomElement(int nr)
       if (!(part2->state & ANIM_STATE_RUNNING))
        continue;
 
-      if (isClickablePart(part2, mask))
+      if (isClickablePart(part2, mask) && !part2->triggered)
       {
+       struct GraphicInfo *c = &part2->control_info;
+
+       if (c->position == POS_CE ||
+           c->position == POS_CE_TRIGGER)
+       {
+         // store CE tile and offset position to handle scrolling
+         part2->tile_x = (c->position == POS_CE_TRIGGER ? trigger_x : x);
+         part2->tile_y = (c->position == POS_CE_TRIGGER ? trigger_y : y);
+         part2->tile_xoffset = c->x;
+         part2->tile_yoffset = c->y;
+
+         // set resulting animation position relative to CE tile position
+         // (but only for ".init_event", not ".anim_event" type events)
+         if (part2->init_event_state)
+           SetGlobalAnimPartTileXY(part2);
+
+         // restart animation (by using current sync frame)
+         part2->initial_anim_sync_frame = anim_sync_frame;
+       }
+
        part2->triggered = TRUE;
 
+       // do not trigger any other animation if CE change event was consumed
+       if (c->style == STYLE_CONSUME_CE_EVENT)
+         return;
+
 #if 0
        Debug("anim:InitGlobalAnim_Triggered_ByCustomElement",
              "%d.%d TRIGGERED BY CE %d", anim2_nr, part2_nr, nr + 1);
@@ -1430,6 +1536,10 @@ static int HandleGlobalAnim_Part(struct GlobalAnimPartControlInfo *part,
   struct GraphicInfo *c = &part->control_info;
   boolean viewport_changed = SetGlobalAnimPart_Viewport(part);
 
+  // if game is paused, also pause playfield and door animations
+  if (isPausedOnPlayfieldOrDoor(part))
+    return state;
+
   if (viewport_changed)
     state |= ANIM_STATE_RESTART;
 
@@ -1563,7 +1673,7 @@ static int HandleGlobalAnim_Part(struct GlobalAnimPartControlInfo *part,
       part->init_event_state)
   {
     if (part->initial_anim_sync_frame > 0)
-      part->initial_anim_sync_frame -= part->init_delay_counter - 1;
+      part->initial_anim_sync_frame = anim_sync_frame;
 
     part->init_delay_counter = 1;
     part->init_event_state = FALSE;
@@ -1592,9 +1702,13 @@ static int HandleGlobalAnim_Part(struct GlobalAnimPartControlInfo *part,
 
       HandleGlobalAnimDelay(part, ANIM_DELAY_INIT,  "START [INIT_DELAY]");
       HandleGlobalAnimEvent(part, ANIM_EVENT_START, "START [ANIM]");
-    }
 
-    return ANIM_STATE_WAITING;
+      // continue with state ANIM_STATE_RUNNING (set below)
+    }
+    else
+    {
+      return ANIM_STATE_WAITING;
+    }
   }
 
   if (part->init_event_state)
@@ -1688,8 +1802,16 @@ static int HandleGlobalAnim_Part(struct GlobalAnimPartControlInfo *part,
   }
 #endif
 
-  part->x += part->step_xoffset;
-  part->y += part->step_yoffset;
+  if (c->position == POS_CE ||
+      c->position == POS_CE_TRIGGER)
+  {
+    SetGlobalAnimPartTileXY(part);
+  }
+  else
+  {
+    part->x += part->step_xoffset;
+    part->y += part->step_yoffset;
+  }
 
   anim->last_x = part->x;
   anim->last_y = part->y;
@@ -2099,11 +2221,16 @@ void RestartGlobalAnimsByStatus(int status)
   global.anim_status = status;
 
   // force restarting global animations by changed global animation status
-  SDLRedrawWindow();
+  DrawGlobalAnimationsExt(DRAW_TO_SCREEN, DRAW_GLOBAL_ANIM_STAGE_RESTART);
 
   global.anim_status = anim_status_last;
 }
 
+void SetAnimStatusBeforeFading(int status)
+{
+  anim_status_last_before_fading = status;
+}
+
 boolean HandleGlobalAnimClicks(int mx, int my, int button, boolean force_click)
 {
   static boolean click_consumed = FALSE;
@@ -2140,10 +2267,13 @@ int getGlobalAnimSyncFrame(void)
   return anim_sync_frame;
 }
 
-void HandleGlobalAnimEventByElementChange(int element)
+void HandleGlobalAnimEventByElementChange(int element, int page, int x, int y,
+                                         int trigger_x, int trigger_y)
 {
   if (!IS_CUSTOM_ELEMENT(element))
     return;
 
-  InitGlobalAnim_Triggered_ByCustomElement(element - EL_CUSTOM_START);
+  // custom element stored as 0 to 255, change page stored as 1 to 32
+  InitGlobalAnim_Triggered_ByCustomElement(element - EL_CUSTOM_START, page + 1,
+                                          x, y, trigger_x, trigger_y);
 }