fixed incorrect drawing of players which are moving next to each others
authorHolger Schemel <info@artsoft.org>
Sat, 26 Jan 2019 12:19:24 +0000 (13:19 +0100)
committerHolger Schemel <info@artsoft.org>
Sat, 26 Jan 2019 12:19:24 +0000 (13:19 +0100)
Before, players which were moving next to each others without a free
tile between them were drawn incorrectly (with (usually black)
background tile graphics partially drawn over one of the players).

This problem is now fixed by drawing all player tiles layer by layer
(first drawing the background tiles for all players, then drawing the
players, then drawing elements in front of the players etc.).

src/tools.c

index 33b6779..2505f40 100644 (file)
@@ -3834,87 +3834,122 @@ static boolean equalGraphics(int graphic1, int graphic2)
          g1->anim_mode   == g2->anim_mode);
 }
 
-void DrawAllPlayers(void)
+#define DRAW_PLAYER_OVER_PUSHED_ELEMENT        1
+
+enum
 {
-  int i;
+  DRAW_PLAYER_STAGE_INIT = 0,
+  DRAW_PLAYER_STAGE_LAST_FIELD,
+  DRAW_PLAYER_STAGE_FIELD_UNDER_PLAYER,
+#if DRAW_PLAYER_OVER_PUSHED_ELEMENT
+  DRAW_PLAYER_STAGE_ELEMENT_PUSHED,
+  DRAW_PLAYER_STAGE_PLAYER,
+#else
+  DRAW_PLAYER_STAGE_PLAYER,
+  DRAW_PLAYER_STAGE_ELEMENT_PUSHED,
+#endif
+  DRAW_PLAYER_STAGE_ELEMENT_OVER_PLAYER,
+  DRAW_PLAYER_STAGE_FIELD_OVER_PLAYER,
 
-  for (i = 0; i < MAX_PLAYERS; i++)
-    if (stored_player[i].active)
-      DrawPlayer(&stored_player[i]);
-}
+  NUM_DRAW_PLAYER_STAGES
+};
 
-void DrawPlayerField(int x, int y)
+static void DrawPlayerExt(struct PlayerInfo *player, int drawing_stage)
 {
-  if (!IS_PLAYER(x, y))
+  static int static_last_player_graphic[MAX_PLAYERS];
+  static int static_last_player_frame[MAX_PLAYERS];
+  static boolean static_player_is_opaque[MAX_PLAYERS];
+  static boolean draw_player[MAX_PLAYERS];
+  int pnr = player->index_nr;
+
+  if (drawing_stage == DRAW_PLAYER_STAGE_INIT)
+  {
+    static_last_player_graphic[pnr] = getPlayerGraphic(player, player->MovDir);
+    static_last_player_frame[pnr] = player->Frame;
+    static_player_is_opaque[pnr] = FALSE;
+
+    draw_player[pnr] = TRUE;
+  }
+
+  if (!draw_player[pnr])
     return;
 
-  DrawPlayer(PLAYERINFO(x, y));
-}
+#if DEBUG
+  if (!IN_LEV_FIELD(player->jx, player->jy))
+  {
+    printf("DrawPlayerField(): x = %d, y = %d\n", player->jx, player->jy);
+    printf("DrawPlayerField(): This should never happen!\n");
 
-#define DRAW_PLAYER_OVER_PUSHED_ELEMENT        1
+    draw_player[pnr] = FALSE;
+
+    return;
+  }
+#endif
+
+  int last_player_graphic  = static_last_player_graphic[pnr];
+  int last_player_frame    = static_last_player_frame[pnr];
+  boolean player_is_opaque = static_player_is_opaque[pnr];
 
-void DrawPlayer(struct PlayerInfo *player)
-{
   int jx = player->jx;
   int jy = player->jy;
-  int move_dir = player->MovDir;
+  int move_dir = (player->is_waiting ? player->dir_waiting : player->MovDir);
   int dx = (move_dir == MV_LEFT ? -1 : move_dir == MV_RIGHT ? +1 : 0);
   int dy = (move_dir == MV_UP   ? -1 : move_dir == MV_DOWN  ? +1 : 0);
   int last_jx = (player->is_moving ? jx - dx : jx);
   int last_jy = (player->is_moving ? jy - dy : jy);
   int next_jx = jx + dx;
   int next_jy = jy + dy;
-  boolean player_is_moving = (player->MovPos ? TRUE : FALSE);
-  boolean player_is_opaque = FALSE;
-  int sx = SCREENX(jx), sy = SCREENY(jy);
-  int sxx = 0, syy = 0;
-  int element = Feld[jx][jy], last_element = Feld[last_jx][last_jy];
-  int graphic;
-  int action = ACTION_DEFAULT;
-  int last_player_graphic = getPlayerGraphic(player, move_dir);
-  int last_player_frame = player->Frame;
-  int frame = 0;
+  boolean player_is_moving = (player->MovPos != 0 ? TRUE : FALSE);
+  int sx = SCREENX(jx);
+  int sy = SCREENY(jy);
+  int sxx = (move_dir == MV_LEFT || move_dir == MV_RIGHT ? player->GfxPos : 0);
+  int syy = (move_dir == MV_UP   || move_dir == MV_DOWN  ? player->GfxPos : 0);
+  int element = Feld[jx][jy];
+  int last_element = Feld[last_jx][last_jy];
+  int action = (player->is_pushing    ? ACTION_PUSHING         :
+               player->is_digging    ? ACTION_DIGGING         :
+               player->is_collecting ? ACTION_COLLECTING      :
+               player->is_moving     ? ACTION_MOVING          :
+               player->is_snapping   ? ACTION_SNAPPING        :
+               player->is_dropping   ? ACTION_DROPPING        :
+               player->is_waiting    ? player->action_waiting :
+               ACTION_DEFAULT);
+
+  if (drawing_stage == DRAW_PLAYER_STAGE_INIT)
+  {
+    // ------------------------------------------------------------------------
+    // initialize drawing the player
+    // ------------------------------------------------------------------------
 
-  // GfxElement[][] is set to the element the player is digging or collecting;
-  // remove also for off-screen player if the player is not moving anymore
-  if (IN_LEV_FIELD(jx, jy) && !player_is_moving)
-    GfxElement[jx][jy] = EL_UNDEFINED;
+    draw_player[pnr] = FALSE;
 
-  if (!player->active || !IN_SCR_FIELD(SCREENX(last_jx), SCREENY(last_jy)))
-    return;
+    // GfxElement[][] is set to the element the player is digging or collecting;
+    // remove also for off-screen player if the player is not moving anymore
+    if (IN_LEV_FIELD(jx, jy) && !player_is_moving)
+      GfxElement[jx][jy] = EL_UNDEFINED;
 
-#if DEBUG
-  if (!IN_LEV_FIELD(jx, jy))
-  {
-    printf("DrawPlayerField(): x = %d, y = %d\n",jx,jy);
-    printf("DrawPlayerField(): sx = %d, sy = %d\n",sx,sy);
-    printf("DrawPlayerField(): This should never happen!\n");
-    return;
-  }
-#endif
+    if (!player->active || !IN_SCR_FIELD(SCREENX(last_jx), SCREENY(last_jy)))
+      return;
 
-  if (element == EL_EXPLOSION)
-    return;
+    if (element == EL_EXPLOSION)
+      return;
 
-  action = (player->is_pushing    ? ACTION_PUSHING         :
-           player->is_digging    ? ACTION_DIGGING         :
-           player->is_collecting ? ACTION_COLLECTING      :
-           player->is_moving     ? ACTION_MOVING          :
-           player->is_snapping   ? ACTION_SNAPPING        :
-           player->is_dropping   ? ACTION_DROPPING        :
-           player->is_waiting    ? player->action_waiting : ACTION_DEFAULT);
+    InitPlayerGfxAnimation(player, action, move_dir);
 
-  if (player->is_waiting)
-    move_dir = player->dir_waiting;
+    draw_player[pnr] = TRUE;
+  }
+  else if (drawing_stage == DRAW_PLAYER_STAGE_LAST_FIELD)
+  {
+    // ------------------------------------------------------------------------
+    // draw things in the field the player is leaving, if needed
+    // ------------------------------------------------------------------------
 
-  InitPlayerGfxAnimation(player, action, move_dir);
+    if (!IN_SCR_FIELD(sx, sy))
+      draw_player[pnr] = FALSE;
 
-  // --------------------------------------------------------------------------
-  // draw things in the field the player is leaving, if needed
-  // --------------------------------------------------------------------------
+    if (!player->is_moving)
+      return;
 
-  if (player->is_moving)
-  {
     if (Back[last_jx][last_jy] && IS_DRAWABLE(last_element))
     {
       DrawLevelElement(last_jx, last_jy, Back[last_jx][last_jy]);
@@ -3930,35 +3965,32 @@ void DrawPlayer(struct PlayerInfo *player)
             last_element == EL_EM_DYNAMITE_ACTIVE ||
             last_element == EL_SP_DISK_RED_ACTIVE)
       DrawDynamite(last_jx, last_jy);
-#if 0
-    /* !!! this is not enough to prevent flickering of players which are
-       moving next to each others without a free tile between them -- this
-       can only be solved by drawing all players layer by layer (first the
-       background, then the foreground etc.) !!! => TODO */
-    else if (!IS_PLAYER(last_jx, last_jy))
-      DrawLevelField(last_jx, last_jy);
-#else
     else
       DrawLevelField(last_jx, last_jy);
-#endif
 
     if (player->is_pushing && IN_SCR_FIELD(SCREENX(next_jx), SCREENY(next_jy)))
       DrawLevelElement(next_jx, next_jy, EL_EMPTY);
   }
+  else if (drawing_stage == DRAW_PLAYER_STAGE_FIELD_UNDER_PLAYER)
+  {
+    // ------------------------------------------------------------------------
+    // draw things behind the player, if needed
+    // ------------------------------------------------------------------------
 
-  if (!IN_SCR_FIELD(sx, sy))
-    return;
+    if (Back[jx][jy])
+    {
+      DrawLevelElement(jx, jy, Back[jx][jy]);
 
-  // --------------------------------------------------------------------------
-  // draw things behind the player, if needed
-  // --------------------------------------------------------------------------
+      return;
+    }
+
+    if (IS_ACTIVE_BOMB(element))
+    {
+      DrawLevelElement(jx, jy, EL_EMPTY);
+
+      return;
+    }
 
-  if (Back[jx][jy])
-    DrawLevelElement(jx, jy, Back[jx][jy]);
-  else if (IS_ACTIVE_BOMB(element))
-    DrawLevelElement(jx, jy, EL_EMPTY);
-  else
-  {
     if (player_is_moving && GfxElement[jx][jy] != EL_UNDEFINED)
     {
       int old_element = GfxElement[jx][jy];
@@ -3971,14 +4003,14 @@ void DrawPlayer(struct PlayerInfo *player)
        DrawGraphic(sx, sy, old_graphic, frame);
 
       if (graphic_info[old_graphic].anim_mode & ANIM_OPAQUE_PLAYER)
-       player_is_opaque = TRUE;
+       static_player_is_opaque[pnr] = TRUE;
     }
     else
     {
       GfxElement[jx][jy] = EL_UNDEFINED;
 
       // make sure that pushed elements are drawn with correct frame rate
-      graphic = el_act_dir2img(element, ACTION_PUSHING, move_dir);
+      int graphic = el_act_dir2img(element, ACTION_PUSHING, move_dir);
 
       if (player->is_pushing && player->is_moving && !IS_ANIM_MODE_CE(graphic))
        GfxFrame[jx][jy] = player->StepFrame;
@@ -3986,79 +4018,26 @@ void DrawPlayer(struct PlayerInfo *player)
       DrawLevelField(jx, jy);
     }
   }
-
-#if !DRAW_PLAYER_OVER_PUSHED_ELEMENT
-  // -----------------------------------------------------------------------
-  // draw player himself
-  // -----------------------------------------------------------------------
-
-  graphic = getPlayerGraphic(player, move_dir);
-
-  // in the case of changed player action or direction, prevent the current
-  // animation frame from being restarted for identical animations
-  if (player->Frame == 0 && equalGraphics(graphic, last_player_graphic))
-    player->Frame = last_player_frame;
-
-  frame = getGraphicAnimationFrame(graphic, player->Frame);
-
-  if (player->GfxPos)
-  {
-    if (move_dir == MV_LEFT || move_dir == MV_RIGHT)
-      sxx = player->GfxPos;
-    else
-      syy = player->GfxPos;
-  }
-
-  if (player_is_opaque)
-    DrawGraphicShifted(sx, sy, sxx, syy, graphic, frame,NO_CUTTING,NO_MASKING);
-  else
-    DrawGraphicShiftedThruMask(sx, sy, sxx, syy, graphic, frame, NO_CUTTING);
-
-  if (SHIELD_ON(player))
-  {
-    int graphic = (player->shield_deadly_time_left ? IMG_SHIELD_DEADLY_ACTIVE :
-                  IMG_SHIELD_NORMAL_ACTIVE);
-    int frame = getGraphicAnimationFrame(graphic, -1);
-
-    DrawGraphicShiftedThruMask(sx, sy, sxx, syy, graphic, frame, NO_CUTTING);
-  }
-#endif
-
-#if DRAW_PLAYER_OVER_PUSHED_ELEMENT
-  if (player->GfxPos)
+  else if (drawing_stage == DRAW_PLAYER_STAGE_ELEMENT_PUSHED)
   {
-    if (move_dir == MV_LEFT || move_dir == MV_RIGHT)
-      sxx = player->GfxPos;
-    else
-      syy = player->GfxPos;
-  }
-#endif
+    // ------------------------------------------------------------------------
+    // draw things the player is pushing, if needed
+    // ------------------------------------------------------------------------
 
-  // --------------------------------------------------------------------------
-  // draw things the player is pushing, if needed
-  // --------------------------------------------------------------------------
+    if (!player->is_pushing || !player->is_moving)
+      return;
 
-  if (player->is_pushing && player->is_moving)
-  {
-    int px = SCREENX(jx), py = SCREENY(jy);
-    int pxx = (TILEX - ABS(sxx)) * dx;
-    int pyy = (TILEY - ABS(syy)) * dy;
     int gfx_frame = GfxFrame[jx][jy];
 
-    int graphic;
-    int sync_frame;
-    int frame;
-
     if (!IS_MOVING(jx, jy))            // push movement already finished
     {
       element = Feld[next_jx][next_jy];
       gfx_frame = GfxFrame[next_jx][next_jy];
     }
 
-    graphic = el_act_dir2img(element, ACTION_PUSHING, move_dir);
-
-    sync_frame = (IS_ANIM_MODE_CE(graphic) ? gfx_frame : player->StepFrame);
-    frame = getGraphicAnimationFrame(graphic, sync_frame);
+    int graphic = el_act_dir2img(element, ACTION_PUSHING, move_dir);
+    int sync_frame = (IS_ANIM_MODE_CE(graphic) ? gfx_frame : player->StepFrame);
+    int frame = getGraphicAnimationFrame(graphic, sync_frame);
 
     // draw background element under pushed element (like the Sokoban field)
     if (game.use_masked_pushing && IS_MOVING(jx, jy))
@@ -4078,6 +4057,10 @@ void DrawPlayer(struct PlayerInfo *player)
     else if (Back[next_jx][next_jy])
       DrawLevelElement(next_jx, next_jy, Back[next_jx][next_jy]);
 
+    int px = SCREENX(jx), py = SCREENY(jy);
+    int pxx = (TILEX - ABS(sxx)) * dx;
+    int pyy = (TILEY - ABS(syy)) * dy;
+
 #if 1
     // do not draw (EM style) pushing animation when pushing is finished
     // (two-tile animations usually do not contain start and end frame)
@@ -4091,96 +4074,118 @@ void DrawPlayer(struct PlayerInfo *player)
     DrawGraphicShiftedThruMask(px, py, pxx, pyy, graphic, frame, NO_CUTTING);
 #endif
   }
+  else if (drawing_stage == DRAW_PLAYER_STAGE_PLAYER)
+  {
+    // ------------------------------------------------------------------------
+    // draw player himself
+    // ------------------------------------------------------------------------
 
-#if DRAW_PLAYER_OVER_PUSHED_ELEMENT
-  // -----------------------------------------------------------------------
-  // draw player himself
-  // -----------------------------------------------------------------------
-
-  graphic = getPlayerGraphic(player, move_dir);
+    int graphic = getPlayerGraphic(player, move_dir);
 
-  // in the case of changed player action or direction, prevent the current
-  // animation frame from being restarted for identical animations
-  if (player->Frame == 0 && equalGraphics(graphic, last_player_graphic))
-    player->Frame = last_player_frame;
+    // in the case of changed player action or direction, prevent the current
+    // animation frame from being restarted for identical animations
+    if (player->Frame == 0 && equalGraphics(graphic, last_player_graphic))
+      player->Frame = last_player_frame;
 
-  frame = getGraphicAnimationFrame(graphic, player->Frame);
+    int frame = getGraphicAnimationFrame(graphic, player->Frame);
 
-  if (player->GfxPos)
-  {
-    if (move_dir == MV_LEFT || move_dir == MV_RIGHT)
-      sxx = player->GfxPos;
+    if (player_is_opaque)
+      DrawGraphicShifted(sx,sy, sxx,syy, graphic, frame, NO_CUTTING,NO_MASKING);
     else
-      syy = player->GfxPos;
-  }
+      DrawGraphicShiftedThruMask(sx, sy, sxx, syy, graphic, frame, NO_CUTTING);
 
-  if (player_is_opaque)
-    DrawGraphicShifted(sx, sy, sxx, syy, graphic, frame,NO_CUTTING,NO_MASKING);
-  else
-    DrawGraphicShiftedThruMask(sx, sy, sxx, syy, graphic, frame, NO_CUTTING);
+    if (SHIELD_ON(player))
+    {
+      graphic = (player->shield_deadly_time_left ? IMG_SHIELD_DEADLY_ACTIVE :
+                IMG_SHIELD_NORMAL_ACTIVE);
+      frame = getGraphicAnimationFrame(graphic, -1);
 
-  if (SHIELD_ON(player))
+      DrawGraphicShiftedThruMask(sx, sy, sxx, syy, graphic, frame, NO_CUTTING);
+    }
+  }
+  else if (drawing_stage == DRAW_PLAYER_STAGE_ELEMENT_OVER_PLAYER)
   {
-    int graphic = (player->shield_deadly_time_left ? IMG_SHIELD_DEADLY_ACTIVE :
-                  IMG_SHIELD_NORMAL_ACTIVE);
-    int frame = getGraphicAnimationFrame(graphic, -1);
+    // ------------------------------------------------------------------------
+    // draw things in front of player (active dynamite or dynabombs)
+    // ------------------------------------------------------------------------
 
-    DrawGraphicShiftedThruMask(sx, sy, sxx, syy, graphic, frame, NO_CUTTING);
-  }
-#endif
+    if (IS_ACTIVE_BOMB(element))
+    {
+      int graphic = el2img(element);
+      int frame = getGraphicAnimationFrame(graphic, GfxFrame[jx][jy]);
 
-  // --------------------------------------------------------------------------
-  // draw things in front of player (active dynamite or dynabombs)
-  // --------------------------------------------------------------------------
+      if (game.emulation == EMU_SUPAPLEX)
+       DrawGraphic(sx, sy, IMG_SP_DISK_RED, frame);
+      else
+       DrawGraphicThruMask(sx, sy, graphic, frame);
+    }
 
-  if (IS_ACTIVE_BOMB(element))
+    if (player_is_moving && last_element == EL_EXPLOSION)
+    {
+      int element = (GfxElement[last_jx][last_jy] != EL_UNDEFINED ?
+                    GfxElement[last_jx][last_jy] :  EL_EMPTY);
+      int graphic = el_act2img(element, ACTION_EXPLODING);
+      int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
+      int phase = ExplodePhase[last_jx][last_jy] - 1;
+      int frame = getGraphicAnimationFrame(graphic, phase - delay);
+
+      if (phase >= delay)
+       DrawGraphicThruMask(SCREENX(last_jx), SCREENY(last_jy), graphic, frame);
+    }
+  }
+  else if (drawing_stage == DRAW_PLAYER_STAGE_FIELD_OVER_PLAYER)
   {
-    graphic = el2img(element);
-    frame = getGraphicAnimationFrame(graphic, GfxFrame[jx][jy]);
+    // ------------------------------------------------------------------------
+    // draw elements the player is just walking/passing through/under
+    // ------------------------------------------------------------------------
 
-    if (game.emulation == EMU_SUPAPLEX)
-      DrawGraphic(sx, sy, IMG_SP_DISK_RED, frame);
-    else
-      DrawGraphicThruMask(sx, sy, graphic, frame);
-  }
+    if (player_is_moving)
+    {
+      // handle the field the player is leaving ...
+      if (IS_ACCESSIBLE_INSIDE(last_element))
+       DrawLevelField(last_jx, last_jy);
+      else if (IS_ACCESSIBLE_UNDER(last_element))
+       DrawLevelFieldThruMask(last_jx, last_jy);
+    }
 
-  if (player_is_moving && last_element == EL_EXPLOSION)
-  {
-    int element = (GfxElement[last_jx][last_jy] != EL_UNDEFINED ?
-                  GfxElement[last_jx][last_jy] :  EL_EMPTY);
-    int graphic = el_act2img(element, ACTION_EXPLODING);
-    int delay = (game.emulation == EMU_SUPAPLEX ? 3 : 2);
-    int phase = ExplodePhase[last_jx][last_jy] - 1;
-    int frame = getGraphicAnimationFrame(graphic, phase - delay);
+    // do not redraw accessible elements if the player is just pushing them
+    if (!player_is_moving || !player->is_pushing)
+    {
+      // ... and the field the player is entering
+      if (IS_ACCESSIBLE_INSIDE(element))
+       DrawLevelField(jx, jy);
+      else if (IS_ACCESSIBLE_UNDER(element))
+       DrawLevelFieldThruMask(jx, jy);
+    }
 
-    if (phase >= delay)
-      DrawGraphicThruMask(SCREENX(last_jx), SCREENY(last_jy), graphic, frame);
+    MarkTileDirty(sx, sy);
   }
+}
+
+void DrawPlayer(struct PlayerInfo *player)
+{
+  int i;
 
-  // --------------------------------------------------------------------------
-  // draw elements the player is just walking/passing through/under
-  // --------------------------------------------------------------------------
+  for (i = 0; i < NUM_DRAW_PLAYER_STAGES; i++)
+    DrawPlayerExt(player, i);
+}
 
-  if (player_is_moving)
-  {
-    // handle the field the player is leaving ...
-    if (IS_ACCESSIBLE_INSIDE(last_element))
-      DrawLevelField(last_jx, last_jy);
-    else if (IS_ACCESSIBLE_UNDER(last_element))
-      DrawLevelFieldThruMask(last_jx, last_jy);
-  }
+void DrawAllPlayers(void)
+{
+  int i, j;
 
-  // do not redraw accessible elements if the player is just pushing them
-  if (!player_is_moving || !player->is_pushing)
-  {
-    // ... and the field the player is entering
-    if (IS_ACCESSIBLE_INSIDE(element))
-      DrawLevelField(jx, jy);
-    else if (IS_ACCESSIBLE_UNDER(element))
-      DrawLevelFieldThruMask(jx, jy);
-  }
+  for (i = 0; i < NUM_DRAW_PLAYER_STAGES; i++)
+    for (j = 0; j < MAX_PLAYERS; j++)
+      if (stored_player[j].active)
+       DrawPlayerExt(&stored_player[j], i);
+}
+
+void DrawPlayerField(int x, int y)
+{
+  if (!IS_PLAYER(x, y))
+    return;
 
-  MarkTileDirty(sx, sy);
+  DrawPlayer(PLAYERINFO(x, y));
 }
 
 // ----------------------------------------------------------------------------