removed debug output for 'wrong animation frames' bug
[rocksndiamonds.git] / src / game.c
index 49acec5bed46a4c70b979dd4fc3f5d795284c645..41add9e6745afd10fb6ad28fafe4e438d6aa7e9e 100644 (file)
@@ -974,12 +974,13 @@ static struct GamePanelControlInfo game_panel_controls[] =
 #define GAME_CTRL_ID_UNDO              3
 #define GAME_CTRL_ID_REDO              4
 #define GAME_CTRL_ID_SAVE              5
-#define GAME_CTRL_ID_LOAD              6
-#define SOUND_CTRL_ID_MUSIC            7
-#define SOUND_CTRL_ID_LOOPS            8
-#define SOUND_CTRL_ID_SIMPLE           9
+#define GAME_CTRL_ID_PAUSE2            6
+#define GAME_CTRL_ID_LOAD              7
+#define SOUND_CTRL_ID_MUSIC            8
+#define SOUND_CTRL_ID_LOOPS            9
+#define SOUND_CTRL_ID_SIMPLE           10
 
-#define NUM_GAME_BUTTONS               10
+#define NUM_GAME_BUTTONS               11
 
 
 /* forward declaration for internal use */
@@ -1948,7 +1949,7 @@ static void InitField(int x, int y, boolean init_game)
     CheckTriggeredElementChange(x, y, element, CE_CREATION_OF_X);
 }
 
-static inline void InitField_WithBug1(int x, int y, boolean init_game)
+inline static void InitField_WithBug1(int x, int y, boolean init_game)
 {
   InitField(x, y, init_game);
 
@@ -1958,7 +1959,7 @@ static inline void InitField_WithBug1(int x, int y, boolean init_game)
     InitMovDir(x, y);
 }
 
-static inline void InitField_WithBug2(int x, int y, boolean init_game)
+inline static void InitField_WithBug2(int x, int y, boolean init_game)
 {
   int old_element = Feld[x][y];
 
@@ -3082,6 +3083,7 @@ void InitGame()
 {
   int full_lev_fieldx = lev_fieldx + (BorderElement != EL_EMPTY ? 2 : 0);
   int full_lev_fieldy = lev_fieldy + (BorderElement != EL_EMPTY ? 2 : 0);
+  int fade_mask = REDRAW_FIELD;
 
   boolean emulate_bd = TRUE;   /* unless non-BOULDERDASH elements found */
   boolean emulate_sb = TRUE;   /* unless non-SOKOBAN     elements found */
@@ -3089,22 +3091,30 @@ void InitGame()
   int initial_move_dir = MV_DOWN;
   int i, j, x, y;
 
-  game_status = GAME_MODE_PLAYING;
+  // required here to update video display before fading (FIX THIS)
+  DrawMaskedBorder(REDRAW_DOOR_2);
 
-  StopAnimation();
+  game_status = GAME_MODE_PLAYING;
 
   if (!game.restart_level)
     CloseDoor(DOOR_CLOSE_1);
 
+  /* needed if different viewport properties defined for playing */
+  ChangeViewportPropertiesIfNeeded();
+
   if (level_editor_test_game)
     FadeSkipNextFadeIn();
   else
     FadeSetEnterScreen();
 
-  FadeOut(REDRAW_FIELD);
+  if (CheckIfGlobalBorderHasChanged())
+    fade_mask = REDRAW_ALL;
 
-  /* needed if different viewport properties defined for playing */
-  ChangeViewportPropertiesIfNeeded();
+  FadeOut(fade_mask);
+
+  OpenDoor(GetDoorState() | DOOR_NO_DELAY | DOOR_FORCE_REDRAW);
+
+  ClearField();
 
   DrawCompleteVideoDisplay();
 
@@ -3916,11 +3926,11 @@ void InitGame()
 
   /* blit playfield from scroll buffer to normal back buffer for fading in */
   BlitScreenToBitmap(backbuffer);
-
-  redraw_mask |= REDRAW_FROM_BACKBUFFER;
   /* !!! FIX THIS (END) !!! */
 
-  FadeIn(REDRAW_FIELD);
+  DrawMaskedBorder(fade_mask);
+
+  FadeIn(fade_mask);
 
 #if 1
   // full screen redraw is required at this point in the following cases:
@@ -3949,9 +3959,14 @@ void InitGame()
   {
     UnmapGameButtons();
     UnmapTapeButtons();
+
+    FreeGameButtons();
+    CreateGameButtons();
+
     game_gadget[SOUND_CTRL_ID_MUSIC]->checked = setup.sound_music;
     game_gadget[SOUND_CTRL_ID_LOOPS]->checked = setup.sound_loops;
     game_gadget[SOUND_CTRL_ID_SIMPLE]->checked = setup.sound_simple;
+
     MapGameButtons();
     MapTapeButtons();
 
@@ -4406,7 +4421,7 @@ void GameEnd()
   {
     game_status = GAME_MODE_MAIN;
 
-    DrawAndFadeInMainMenu(REDRAW_FIELD);
+    DrawMainMenu();
 
     return;
   }
@@ -4417,7 +4432,7 @@ void GameEnd()
 
     game_status = GAME_MODE_MAIN;
 
-    DrawAndFadeInMainMenu(REDRAW_FIELD);
+    DrawMainMenu();
 
     return;
   }
@@ -4436,9 +4451,6 @@ void GameEnd()
   {
     game_status = GAME_MODE_SCORES;
 
-    /* needed if different viewport properties defined for scores */
-    ChangeViewportPropertiesIfNeeded();
-
     DrawHallOfFame(hi_pos);
 
     if (raise_level)
@@ -4459,7 +4471,7 @@ void GameEnd()
       TapeErase();
     }
 
-    DrawAndFadeInMainMenu(REDRAW_FIELD);
+    DrawMainMenu();
   }
 }
 
@@ -4884,7 +4896,11 @@ void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
   int frame_delay_value = (ffwd_delay ? FfwdFrameDelay : GameFrameDelay);
   int wait_delay_value = (no_delay ? 0 : frame_delay_value);
 
-  if (quick_relocation)
+  if (level.lazy_relocation && IN_VIS_FIELD(SCREENX(x), SCREENY(y)))
+  {
+    RedrawPlayfield();
+  }
+  else if (quick_relocation)
   {
     if (!IN_VIS_FIELD(SCREENX(x), SCREENY(y)) || center_screen)
     {
@@ -4963,7 +4979,7 @@ void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
       }
     }
 
-    RedrawPlayfield(TRUE, 0,0,0,0);
+    RedrawPlayfield();
   }
   else
   {
@@ -5005,7 +5021,6 @@ void DrawRelocateScreen(int old_x, int old_y, int x, int y, int move_dir,
                   offset_y - MIDPOSY);
     }
 
-
     ScrollScreen(NULL, SCROLL_GO_ON);  /* scroll last frame to full tile */
 
     while (scroll_x != scroll_xx || scroll_y != scroll_yy)
@@ -10237,9 +10252,50 @@ static void HandleElementChange(int x, int y, int page)
     {
       /* !!! not clear why graphic animation should be reset at all here !!! */
       /* !!! UPDATE: but is needed for correct Snake Bite tail animation !!! */
+      /* !!! SOLUTION: do not reset if graphics engine set to 4 or above !!! */
+
+      /*
+       GRAPHICAL BUG ADDRESSED BY CHECKING GRAPHICS ENGINE VERSION:
+
+       When using an animation frame delay of 1 (this only happens with
+       "sp_zonk.moving.left/right" in the classic graphics), the default
+       (non-moving) animation shows wrong animation frames (while the
+       moving animation, like "sp_zonk.moving.left/right", is correct,
+       so this graphical bug never shows up with the classic graphics).
+       For an animation with 4 frames, this causes wrong frames 0,0,1,2
+       be drawn instead of the correct frames 0,1,2,3. This is caused by
+       "GfxFrame[][]" being reset *twice* (in two successive frames) after
+       an element change: First when the change delay ("ChangeDelay[][]")
+       counter has reached zero after decrementing, then a second time in
+       the next frame (after "GfxFrame[][]" was already incremented) when
+       "ChangeDelay[][]" is reset to the initial delay value again.
+
+       This causes frame 0 to be drawn twice, while the last frame won't
+       be drawn anymore, resulting in the wrong frame sequence 0,0,1,2.
+
+       As some animations may already be cleverly designed around this bug
+       (at least the "Snake Bite" snake tail animation does this), it cannot
+       simply be fixed here without breaking such existing animations.
+       Unfortunately, it cannot easily be detected if a graphics set was
+       designed "before" or "after" the bug was fixed. As a workaround,
+       a new graphics set option "game.graphics_engine_version" was added
+       to be able to specify the game's major release version for which the
+       graphics set was designed, which can then be used to decide if the
+       bugfix should be used (version 4 and above) or not (version 3 or
+       below, or if no version was specified at all, as with old sets).
+
+       (The wrong/fixed animation frames can be tested with the test level set
+       "test_gfxframe" and level "000", which contains a specially prepared
+       custom element at level position (x/y) == (11/9) which uses the zonk
+       animation mentioned above. Using "game.graphics_engine_version: 4"
+       fixes the wrong animation frames, showing the correct frames 0,1,2,3.
+       This can also be seen from the debug output for this test element.)
+      */
+
       /* when a custom element is about to change (for example by change delay),
         do not reset graphic animation when the custom element is moving */
-      if (!IS_MOVING(x, y))
+      if (game.graphics_engine_version < 4 &&
+         !IS_MOVING(x, y))
       {
        ResetGfxAnimation(x, y);
        ResetRandomAnimationValue(x, y);
@@ -10970,23 +11026,6 @@ void GameActions()
   byte tape_action[MAX_PLAYERS];
   int i;
 
-  for (i = 0; i < MAX_PLAYERS; i++)
-  {
-    struct PlayerInfo *player = &stored_player[i];
-
-    // allow engine snapshot if movement attempt was stopped
-    if ((game.snapshot.last_action[i] & KEY_MOTION) != 0 &&
-       (player->action & KEY_MOTION) == 0)
-      game.snapshot.changed_action = TRUE;
-
-    // allow engine snapshot in case of snapping/dropping attempt
-    if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
-       (player->action & KEY_BUTTON) != 0)
-      game.snapshot.changed_action = TRUE;
-
-    game.snapshot.last_action[i] = player->action;
-  }
-
   /* detect endless loops, caused by custom element programming */
   if (recursion_loop_detected && recursion_loop_depth == 0)
   {
@@ -11058,9 +11097,18 @@ void GameActions()
   if (tape.playing && tape.warp_forward && !tape.pausing)
     game_frame_delay_value = 0;
 
+#if 0
+  /* ---------- main game synchronization point ---------- */
+
+  int skip = WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
+
+  printf("::: skip == %d\n", skip);
+
+#else
   /* ---------- main game synchronization point ---------- */
 
   WaitUntilDelayReached(&game_frame_delay, game_frame_delay_value);
+#endif
 
   if (network_playing && !network_player_action_received)
   {
@@ -11184,6 +11232,21 @@ void GameActions()
 #endif
 #endif
 
+  for (i = 0; i < MAX_PLAYERS; i++)
+  {
+    // allow engine snapshot in case of changed movement attempt
+    if ((game.snapshot.last_action[i] & KEY_MOTION) !=
+       (stored_player[i].effective_action & KEY_MOTION))
+      game.snapshot.changed_action = TRUE;
+
+    // allow engine snapshot in case of snapping/dropping attempt
+    if ((game.snapshot.last_action[i] & KEY_BUTTON) == 0 &&
+       (stored_player[i].effective_action & KEY_BUTTON) != 0)
+      game.snapshot.changed_action = TRUE;
+
+    game.snapshot.last_action[i] = stored_player[i].effective_action;
+  }
+
   if (level.game_engine_type == GAME_ENGINE_TYPE_EM)
   {
     GameActions_EM_Main();
@@ -11202,6 +11265,25 @@ void GameActions()
   CheckLevelTime();
 
   AdvanceFrameAndPlayerCounters(-1);   /* advance counters for all players */
+
+  if (options.debug)                   /* calculate frames per second */
+  {
+    static unsigned int fps_counter = 0;
+    static int fps_frames = 0;
+    unsigned int fps_delay_ms = Counter() - fps_counter;
+
+    fps_frames++;
+
+    if (fps_delay_ms >= 500)   /* calculate fps every 0.5 seconds */
+    {
+      global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
+
+      fps_frames = 0;
+      fps_counter = Counter();
+    }
+
+    redraw_mask |= REDRAW_FPS;
+  }
 }
 
 void GameActions_EM_Main()
@@ -11735,25 +11817,6 @@ void GameActions_RND()
   DrawAllPlayers();
   PlayAllPlayersSound();
 
-  if (options.debug)                   /* calculate frames per second */
-  {
-    static unsigned int fps_counter = 0;
-    static int fps_frames = 0;
-    unsigned int fps_delay_ms = Counter() - fps_counter;
-
-    fps_frames++;
-
-    if (fps_delay_ms >= 500)   /* calculate fps every 0.5 seconds */
-    {
-      global.frames_per_second = 1000 * (float)fps_frames / fps_delay_ms;
-
-      fps_frames = 0;
-      fps_counter = Counter();
-    }
-
-    redraw_mask |= REDRAW_FPS;
-  }
-
   if (local_player->show_envelope != 0 && local_player->MovPos == 0)
   {
     ShowEnvelope(local_player->show_envelope - EL_ENVELOPE_1);
@@ -14462,13 +14525,13 @@ void RequestQuitGameExt(boolean skip_request, boolean quick_quit, char *message)
 
        game_status = GAME_MODE_MAIN;
 
-       DrawAndFadeInMainMenu(REDRAW_FIELD);
+       DrawMainMenu();
       }
       else
       {
        game_status = GAME_MODE_MAIN;
 
-       DrawAndFadeInMainMenu(REDRAW_FIELD);
+       DrawMainMenu();
       }
     }
   }
@@ -14868,6 +14931,10 @@ static struct
     IMG_GAME_BUTTON_GFX_SAVE,          &game.button.save,
     GAME_CTRL_ID_SAVE,                 "save game"
   },
+  {
+    IMG_GAME_BUTTON_GFX_PAUSE2,                &game.button.pause2,
+    GAME_CTRL_ID_PAUSE2,               "pause game"
+  },
   {
     IMG_GAME_BUTTON_GFX_LOAD,          &game.button.load,
     GAME_CTRL_ID_LOAD,                 "load game"
@@ -14918,7 +14985,6 @@ void CreateGameButtons()
     }
 
     if (id == GAME_CTRL_ID_STOP ||
-       id == GAME_CTRL_ID_PAUSE ||
        id == GAME_CTRL_ID_PLAY ||
        id == GAME_CTRL_ID_SAVE ||
        id == GAME_CTRL_ID_LOAD)
@@ -15003,22 +15069,18 @@ void MapUndoRedoButtons()
 {
   UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
   UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
-  UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
 
   MapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
   MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
-  MapGadget(game_gadget[GAME_CTRL_ID_PLAY]);
 }
 
 void UnmapUndoRedoButtons()
 {
   UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
   UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
-  UnmapGadget(game_gadget[GAME_CTRL_ID_PLAY]);
 
   MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
   MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
-  MapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
 }
 
 void MapGameButtons()
@@ -15027,9 +15089,23 @@ void MapGameButtons()
 
   for (i = 0; i < NUM_GAME_BUTTONS; i++)
     if (i != GAME_CTRL_ID_UNDO &&
-       i != GAME_CTRL_ID_REDO &&
-       i != GAME_CTRL_ID_PLAY)
+       i != GAME_CTRL_ID_REDO)
       MapGadget(game_gadget[i]);
+
+  if (setup.show_snapshot_buttons)
+  {
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
+  }
+  else
+  {
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE);
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PLAY);
+  }
+
+  RedrawGameButtons();
 }
 
 void UnmapGameButtons()
@@ -15113,6 +15189,7 @@ static void HandleGameButtonsExt(int id, int button)
       break;
 
     case GAME_CTRL_ID_PAUSE:
+    case GAME_CTRL_ID_PAUSE2:
       if (options.network && game_status == GAME_MODE_PLAYING)
       {
 #if defined(NETWORK_AVALIABLE)