fixed out-of-bounds bug when score was not added to high score list
[rocksndiamonds.git] / src / game.c
index de8384e003562d4a2faea5c49122f701a9a67e2a..4d1d9a858244e4283d7bbbdf7f085ec5c4fc3b95 100644 (file)
@@ -1106,7 +1106,7 @@ void ContinueMoving(int, int);
 void Bang(int, int);
 void InitMovDir(int, int);
 void InitAmoebaNr(int, int);
-int NewHiScore(int);
+void NewHighScore(int, boolean);
 
 void TestIfGoodThingHitsBadThing(int, int, int);
 void TestIfBadThingHitsGoodThing(int, int, int);
@@ -3542,7 +3542,6 @@ void InitGame(void)
   int fade_mask = REDRAW_FIELD;
 
   boolean emulate_bd = TRUE;   // unless non-BOULDERDASH elements found
-  boolean emulate_sb = TRUE;   // unless non-SOKOBAN     elements found
   boolean emulate_sp = TRUE;   // unless non-SUPAPLEX    elements found
   int initial_move_dir = MV_DOWN;
   int i, j, x, y;
@@ -3886,8 +3885,6 @@ void InitGame(void)
   {
     if (emulate_bd && !IS_BD_ELEMENT(Tile[x][y]))
       emulate_bd = FALSE;
-    if (emulate_sb && !IS_SB_ELEMENT(Tile[x][y]))
-      emulate_sb = FALSE;
     if (emulate_sp && !IS_SP_ELEMENT(Tile[x][y]))
       emulate_sp = FALSE;
 
@@ -3912,7 +3909,6 @@ void InitGame(void)
   }
 
   game.emulation = (emulate_bd ? EMU_BOULDERDASH :
-                   emulate_sb ? EMU_SOKOBAN :
                    emulate_sp ? EMU_SUPAPLEX : EMU_NONE);
 
   // initialize type of slippery elements
@@ -4310,7 +4306,7 @@ void InitGame(void)
        {
          // check for player created from custom element as single target
          content = element_info[element].change_page[i].target_element;
-         is_player = ELEM_IS_PLAYER(content);
+         is_player = IS_PLAYER_ELEMENT(content);
 
          if (is_player && (found_rating < 3 ||
                            (found_rating == 3 && element < found_element)))
@@ -4328,7 +4324,7 @@ void InitGame(void)
       {
        // check for player created from custom element as explosion content
        content = element_info[element].content.e[xx][yy];
-       is_player = ELEM_IS_PLAYER(content);
+       is_player = IS_PLAYER_ELEMENT(content);
 
        if (is_player && (found_rating < 2 ||
                          (found_rating == 2 && element < found_element)))
@@ -4349,7 +4345,7 @@ void InitGame(void)
          content =
            element_info[element].change_page[i].target_content.e[xx][yy];
 
-         is_player = ELEM_IS_PLAYER(content);
+         is_player = IS_PLAYER_ELEMENT(content);
 
          if (is_player && (found_rating < 1 ||
                            (found_rating == 1 && element < found_element)))
@@ -4700,6 +4696,40 @@ void InitAmoebaNr(int x, int y)
   AmoebaCnt2[group_nr]++;
 }
 
+static void LevelSolved_SetFinalGameValues(void)
+{
+  game.time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
+  game.score_time_final = (level.use_step_counter ? TimePlayed :
+                          TimePlayed * FRAMES_PER_SECOND + TimeFrames);
+
+  game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
+                     game_em.lev->score :
+                     level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+                     game_mm.score :
+                     game.score);
+
+  game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
+                      MM_HEALTH(game_mm.laser_overload_value) :
+                      game.health);
+
+  game.LevelSolved_CountingTime = game.time_final;
+  game.LevelSolved_CountingScore = game.score_final;
+  game.LevelSolved_CountingHealth = game.health_final;
+}
+
+static void LevelSolved_DisplayFinalGameValues(int time, int score, int health)
+{
+  game.LevelSolved_CountingTime = time;
+  game.LevelSolved_CountingScore = score;
+  game.LevelSolved_CountingHealth = health;
+
+  game_panel_controls[GAME_PANEL_TIME].value = time;
+  game_panel_controls[GAME_PANEL_SCORE].value = score;
+  game_panel_controls[GAME_PANEL_HEALTH].value = health;
+
+  DisplayGameControlValues();
+}
+
 static void LevelSolved(void)
 {
   if (level.game_engine_type == GAME_ENGINE_TYPE_RND &&
@@ -4708,6 +4738,9 @@ static void LevelSolved(void)
 
   game.LevelSolved = TRUE;
   game.GameOver = TRUE;
+
+  // needed here to display correct panel values while player walks into exit
+  LevelSolved_SetFinalGameValues();
 }
 
 void GameWon(void)
@@ -4730,23 +4763,8 @@ void GameWon(void)
     if (local_player->active && local_player->MovPos)
       return;
 
-    game.time_final = (game.no_time_limit ? TimePlayed : TimeLeft);
-    game.score_time_final = (level.use_step_counter ? TimePlayed :
-                            TimePlayed * FRAMES_PER_SECOND + TimeFrames);
-
-    game.score_final = (level.game_engine_type == GAME_ENGINE_TYPE_EM ?
-                       game_em.lev->score :
-                       level.game_engine_type == GAME_ENGINE_TYPE_MM ?
-                       game_mm.score :
-                       game.score);
-
-    game.health_final = (level.game_engine_type == GAME_ENGINE_TYPE_MM ?
-                        MM_HEALTH(game_mm.laser_overload_value) :
-                        game.health);
-
-    game.LevelSolved_CountingTime = game.time_final;
-    game.LevelSolved_CountingScore = game.score_final;
-    game.LevelSolved_CountingHealth = game.health_final;
+    // calculate final game values after player finished walking into exit
+    LevelSolved_SetFinalGameValues();
 
     game.LevelSolved_GameWon = TRUE;
     game.LevelSolved_SaveTape = tape.recording;
@@ -4772,6 +4790,10 @@ void GameWon(void)
     score = score_final = game.score_final;
     health = health_final = game.health_final;
 
+    // update game panel values before (delayed) counting of score (if any)
+    LevelSolved_DisplayFinalGameValues(time, score, health);
+
+    // if level has time score defined, calculate new final game values
     if (time_score > 0)
     {
       int time_final_max = 999;
@@ -4805,18 +4827,13 @@ void GameWon(void)
       game.health_final = health_final;
     }
 
+    // if not counting score after game, immediately update game panel values
     if (level_editor_test_game || !setup.count_score_after_game)
     {
       time = time_final;
       score = score_final;
 
-      game.LevelSolved_CountingTime = time;
-      game.LevelSolved_CountingScore = score;
-
-      game_panel_controls[GAME_PANEL_TIME].value = time;
-      game_panel_controls[GAME_PANEL_SCORE].value = score;
-
-      DisplayGameControlValues();
+      LevelSolved_DisplayFinalGameValues(time, score, health);
     }
 
     if (level.game_engine_type == GAME_ENGINE_TYPE_RND)
@@ -4893,13 +4910,7 @@ void GameWon(void)
       if (time == time_final)
        score = score_final;
 
-      game.LevelSolved_CountingTime = time;
-      game.LevelSolved_CountingScore = score;
-
-      game_panel_controls[GAME_PANEL_TIME].value = time;
-      game_panel_controls[GAME_PANEL_SCORE].value = score;
-
-      DisplayGameControlValues();
+      LevelSolved_DisplayFinalGameValues(time, score, health);
 
       if (time == time_final)
        StopSound(SND_GAME_LEVELTIME_BONUS);
@@ -4925,13 +4936,7 @@ void GameWon(void)
       health += health_count_dir;
       score  += time_score;
 
-      game.LevelSolved_CountingHealth = health;
-      game.LevelSolved_CountingScore = score;
-
-      game_panel_controls[GAME_PANEL_HEALTH].value = health;
-      game_panel_controls[GAME_PANEL_SCORE].value = score;
-
-      DisplayGameControlValues();
+      LevelSolved_DisplayFinalGameValues(time, score, health);
 
       if (health == health_final)
        StopSound(SND_GAME_LEVELTIME_BONUS);
@@ -4960,7 +4965,7 @@ void GameEnd(void)
 {
   // used instead of "level_nr" (needed for network games)
   int last_level_nr = levelset.level_nr;
-  int hi_pos;
+  boolean tape_saved = FALSE;
 
   game.LevelSolved_GameEnd = TRUE;
 
@@ -4970,7 +4975,8 @@ void GameEnd(void)
     if (!global.use_envelope_request)
       CloseDoor(DOOR_CLOSE_1);
 
-    SaveTapeChecked_LevelSolved(tape.level_nr);                // ask to save tape
+    // ask to save tape
+    tape_saved = SaveTapeChecked_LevelSolved(tape.level_nr);
 
     // set unique basename for score tape (also saved in high score table)
     strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
@@ -5004,6 +5010,9 @@ void GameEnd(void)
     SaveLevelSetup_SeriesInfo();
   }
 
+  // save score and score tape before potentially erasing tape below
+  NewHighScore(last_level_nr, tape_saved);
+
   if (setup.increment_levels &&
       level_nr < leveldir_current->last_level &&
       !network_playing)
@@ -5019,13 +5028,11 @@ void GameEnd(void)
     }
   }
 
-  hi_pos = NewHiScore(last_level_nr);
-
-  if (hi_pos >= 0 && setup.show_scores_after_game)
+  if (scores.last_added >= 0 && setup.show_scores_after_game)
   {
     SetGameStatus(GAME_MODE_SCORES);
 
-    DrawHallOfFame(last_level_nr, hi_pos);
+    DrawHallOfFame(last_level_nr);
   }
   else if (setup.auto_play_next_level && setup.increment_levels &&
           last_level_nr < leveldir_current->last_level &&
@@ -5041,25 +5048,21 @@ void GameEnd(void)
   }
 }
 
-int NewHiScore(int level_nr)
+static int addScoreEntry(struct ScoreInfo *list, struct ScoreEntry *new_entry,
+                        boolean one_score_entry_per_name)
 {
-  int i, l;
-  int position = -1;
-  boolean one_score_entry_per_name = !program.many_scores_per_name;
-
-  LoadScore(level_nr);
+  int i;
 
-  if (strEqual(setup.player_name, EMPTY_PLAYER_NAME) ||
-      game.score_final < scores.entry[MAX_SCORE_ENTRIES - 1].score)
+  if (strEqual(new_entry->name, EMPTY_PLAYER_NAME))
     return -1;
 
   for (i = 0; i < MAX_SCORE_ENTRIES; i++)
   {
-    struct ScoreEntry *entry = &scores.entry[i];
-    boolean score_is_better = (game.score_final      >  entry->score);
-    boolean score_is_equal  = (game.score_final      == entry->score);
-    boolean time_is_better  = (game.score_time_final <  entry->time);
-    boolean time_is_equal   = (game.score_time_final == entry->time);
+    struct ScoreEntry *entry = &list->entry[i];
+    boolean score_is_better = (new_entry->score >  entry->score);
+    boolean score_is_equal  = (new_entry->score == entry->score);
+    boolean time_is_better  = (new_entry->time  <  entry->time);
+    boolean time_is_equal   = (new_entry->time  == entry->time);
     boolean better_by_score = (score_is_better ||
                               (score_is_equal && time_is_better));
     boolean better_by_time  = (time_is_better ||
@@ -5069,6 +5072,12 @@ int NewHiScore(int level_nr)
     boolean entry_is_empty = (entry->score == 0 &&
                              entry->time == 0);
 
+    // prevent adding server score entries if also existing in local score file
+    // (special case: historic score entries have an empty tape basename entry)
+    if (strEqual(new_entry->tape_basename, entry->tape_basename) &&
+       !strEqual(new_entry->tape_basename, UNDEFINED_FILENAME))
+      return -1;
+
     if (is_better || entry_is_empty)
     {
       // player has made it to the hall of fame
@@ -5076,11 +5085,12 @@ int NewHiScore(int level_nr)
       if (i < MAX_SCORE_ENTRIES - 1)
       {
        int m = MAX_SCORE_ENTRIES - 1;
+       int l;
 
        if (one_score_entry_per_name)
        {
          for (l = i; l < MAX_SCORE_ENTRIES; l++)
-           if (strEqual(setup.player_name, scores.entry[l].name))
+           if (strEqual(list->entry[l].name, new_entry->name))
              m = l;
 
          if (m == i)   // player's new highscore overwrites his old one
@@ -5088,36 +5098,81 @@ int NewHiScore(int level_nr)
        }
 
        for (l = m; l > i; l--)
-       {
-         strcpy(scores.entry[l].name, scores.entry[l - 1].name);
-         scores.entry[l].score = scores.entry[l - 1].score;
-         scores.entry[l].time  = scores.entry[l - 1].time;
-       }
+         list->entry[l] = list->entry[l - 1];
       }
 
       put_into_list:
 
-      strcpy(entry->tape_basename, tape.score_tape_basename);
-      strncpy(entry->name, setup.player_name, MAX_PLAYER_NAME_LEN);
-      entry->name[MAX_PLAYER_NAME_LEN] = '\0';
-      entry->score = game.score_final;
-      entry->time = game.score_time_final;
-      position = i;
+      *entry = *new_entry;
 
-      break;
+      return i;
     }
     else if (one_score_entry_per_name &&
-            !strncmp(setup.player_name, entry->name, MAX_PLAYER_NAME_LEN))
-      break;   // player already there with a higher score
+            strEqual(entry->name, new_entry->name))
+    {
+      // player already in high score list with better score or time
+
+      return -1;
+    }
   }
 
-  if (position >= 0)
+  return -1;
+}
+
+void NewHighScore(int level_nr, boolean tape_saved)
+{
+  struct ScoreEntry new_entry = {{ 0 }}; // (prevent warning from GCC bug 53119)
+  boolean one_per_name = FALSE;
+
+  strncpy(new_entry.tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
+  strncpy(new_entry.name, setup.player_name, MAX_PLAYER_NAME_LEN);
+
+  new_entry.score = game.score_final;
+  new_entry.time = game.score_time_final;
+
+  LoadScore(level_nr);
+
+  scores.last_added = addScoreEntry(&scores, &new_entry, one_per_name);
+
+  if (scores.last_added >= 0)
   {
-    SaveScoreTape(level_nr);
     SaveScore(level_nr);
+
+    // store last added local score entry (before merging server scores)
+    scores.last_added_local = scores.last_added;
+
+    if (game.LevelSolved_SaveTape)
+    {
+      SaveScoreTape(level_nr);
+      SaveServerScore(level_nr, tape_saved);
+    }
   }
+}
+
+void MergeServerScore(void)
+{
+  struct ScoreEntry last_added_entry;
+  boolean one_per_name = FALSE;
+  int i;
 
-  return position;
+  if (scores.last_added >= 0)
+    last_added_entry = scores.entry[scores.last_added];
+
+  for (i = 0; i < server_scores.num_entries; i++)
+  {
+    int pos = addScoreEntry(&scores, &server_scores.entry[i], one_per_name);
+
+    if (pos >= 0 && pos <= scores.last_added)
+      scores.last_added++;
+  }
+
+  if (scores.last_added >= MAX_SCORE_ENTRIES)
+  {
+    scores.last_added = MAX_SCORE_ENTRIES - 1;
+    scores.force_last_added = TRUE;
+
+    scores.entry[scores.last_added] = last_added_entry;
+  }
 }
 
 static int getElementMoveStepsizeExt(int x, int y, int direction)
@@ -5640,7 +5695,7 @@ static void RelocatePlayer(int jx, int jy, int el_player_raw)
      possible that the relocation target field did not contain a player element,
      but a walkable element, to which the new player was relocated -- in this
      case, restore that (already initialized!) element on the player field */
-  if (!ELEM_IS_PLAYER(element))        // player may be set on walkable element
+  if (!IS_PLAYER_ELEMENT(element))     // player may be set on walkable element
   {
     Tile[jx][jy] = element;    // restore previously existing element
   }
@@ -5810,7 +5865,7 @@ static void Explode(int ex, int ey, int phase, int mode)
 
       // !!! check this case -- currently needed for rnd_rado_negundo_v,
       // !!! levels 015 018 019 020 021 022 023 026 027 028 !!!
-      else if (ELEM_IS_PLAYER(center_element))
+      else if (IS_PLAYER_ELEMENT(center_element))
        Store[x][y] = EL_EMPTY;
       else if (center_element == EL_YAMYAM)
        Store[x][y] = level.yamyam_content[game.yamyam_content_nr].e[xx][yy];
@@ -5950,7 +6005,7 @@ static void Explode(int ex, int ey, int phase, int mode)
     if (IS_PLAYER(x, y) && !PLAYERINFO(x, y)->present)
       StorePlayer[x][y] = 0;
 
-    if (ELEM_IS_PLAYER(element))
+    if (IS_PLAYER_ELEMENT(element))
       RelocatePlayer(x, y, element);
   }
   else if (IN_SCR_FIELD(SCREENX(x), SCREENY(y)))
@@ -8736,7 +8791,7 @@ void ContinueMoving(int x, int y)
     if (GFX_CRUMBLED(Tile[x][y]))
       TEST_DrawLevelFieldCrumbledNeighbours(x, y);
 
-    if (ELEM_IS_PLAYER(move_leave_element))
+    if (IS_PLAYER_ELEMENT(move_leave_element))
       RelocatePlayer(x, y, move_leave_element);
   }
 
@@ -10546,7 +10601,7 @@ static void CreateFieldExt(int x, int y, int element, boolean is_change)
   int previous_move_direction = MovDir[x][y];
   int last_ce_value = CustomValue[x][y];
   boolean player_explosion_protected = PLAYER_EXPLOSION_PROTECTED(x, y);
-  boolean new_element_is_player = ELEM_IS_PLAYER(new_element);
+  boolean new_element_is_player = IS_PLAYER_ELEMENT(new_element);
   boolean add_player_onto_element = (new_element_is_player &&
                                     new_element != EL_SOKOBAN_FIELD_PLAYER &&
                                     IS_WALKABLE(old_element));
@@ -10722,7 +10777,7 @@ static boolean ChangeElement(int x, int y, int element, int page)
          (change->replace_when == CP_WHEN_COLLECTIBLE  && is_collectible) ||
          (change->replace_when == CP_WHEN_REMOVABLE    && is_removable) ||
          (change->replace_when == CP_WHEN_DESTRUCTIBLE && is_destructible)) &&
-        !(IS_PLAYER(ex, ey) && ELEM_IS_PLAYER(content_element)));
+        !(IS_PLAYER(ex, ey) && IS_PLAYER_ELEMENT(content_element)));
 
       if (!can_replace[xx][yy])
        complete_replace = FALSE;
@@ -10784,6 +10839,10 @@ static boolean ChangeElement(int x, int y, int element, int page)
       Store[x][y] = EL_EMPTY;
     }
 
+    // special case: element changes to player (and may be kept if walkable)
+    if (IS_PLAYER_ELEMENT(target_element) && !level.keep_walkable_ce)
+      CreateElementFromChange(x, y, EL_EMPTY);
+
     CreateElementFromChange(x, y, target_element);
 
     PlayLevelSoundElementAction(x, y, element, ACTION_CHANGING);
@@ -11654,7 +11713,7 @@ static void GameActionsExt(void)
     Warn("element '%s' caused endless loop in game engine",
         EL_NAME(recursion_loop_element));
 
-    RequestQuitGameExt(FALSE, level_editor_test_game, message);
+    RequestQuitGameExt(program.headless, level_editor_test_game, message);
 
     recursion_loop_detected = FALSE;   // if game should be continued
 
@@ -12125,6 +12184,9 @@ void GameActions_RND(void)
        TEST_DrawLevelField(x, y);
 
        TestFieldAfterSnapping(x, y, element, move_direction, player_index_bit);
+
+       if (IS_ENVELOPE(element))
+         local_player->show_envelope = element;
       }
     }
 
@@ -13908,7 +13970,11 @@ static void TestFieldAfterSnapping(int x, int y, int element, int direction,
   if (level.finish_dig_collect)
   {
     int dig_side = MV_DIR_OPPOSITE(direction);
+    int change_event = (IS_DIGGABLE(element) ? CE_PLAYER_DIGS_X :
+                       CE_PLAYER_COLLECTS_X);
 
+    CheckTriggeredElementChangeByPlayer(x, y, element, change_event,
+                                       player_index_bit, dig_side);
     CheckTriggeredElementChangeByPlayer(x, y, element, CE_PLAYER_SNAPS_X,
                                        player_index_bit, dig_side);
   }
@@ -14282,7 +14348,10 @@ static int DigField(struct PlayerInfo *player,
     }
     else if (IS_ENVELOPE(element))
     {
-      player->show_envelope = element;
+      boolean wait_for_snapping = (mode == DF_SNAP && level.block_snap_field);
+
+      if (!wait_for_snapping)
+       player->show_envelope = element;
     }
     else if (element == EL_EMC_LENSES)
     {
@@ -14469,7 +14538,7 @@ static int DigField(struct PlayerInfo *player,
       if (sokoban_task_solved &&
          game.sokoban_fields_still_needed == 0 &&
          game.sokoban_objects_still_needed == 0 &&
-         (game.emulation == EMU_SOKOBAN || level.auto_exit_sokoban))
+         level.auto_exit_sokoban)
       {
        game.players_still_needed = 0;
 
@@ -16099,12 +16168,18 @@ static void UnmapGameButtonsAtSamePosition(int id)
 
 static void UnmapGameButtonsAtSamePosition_All(void)
 {
-  if (setup.show_snapshot_buttons)
+  if (setup.show_load_save_buttons)
   {
     UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
     UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
     UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
   }
+  else if (setup.show_undo_redo_buttons)
+  {
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_PAUSE2);
+    UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
+  }
   else
   {
     UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_STOP);
@@ -16117,17 +16192,13 @@ static void UnmapGameButtonsAtSamePosition_All(void)
   }
 }
 
-static void MapGameButtonsAtSamePosition(int id)
+void MapLoadSaveButtons(void)
 {
-  int i;
-
-  for (i = 0; i < NUM_GAME_BUTTONS; i++)
-    if (i != id &&
-       gamebutton_info[i].pos->x == gamebutton_info[id].pos->x &&
-       gamebutton_info[i].pos->y == gamebutton_info[id].pos->y)
-      MapGadget(game_gadget[i]);
+  UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_LOAD);
+  UnmapGameButtonsAtSamePosition(GAME_CTRL_ID_SAVE);
 
-  UnmapGameButtonsAtSamePosition_All();
+  MapGadget(game_gadget[GAME_CTRL_ID_LOAD]);
+  MapGadget(game_gadget[GAME_CTRL_ID_SAVE]);
 }
 
 void MapUndoRedoButtons(void)
@@ -16139,15 +16210,6 @@ void MapUndoRedoButtons(void)
   MapGadget(game_gadget[GAME_CTRL_ID_REDO]);
 }
 
-void UnmapUndoRedoButtons(void)
-{
-  UnmapGadget(game_gadget[GAME_CTRL_ID_UNDO]);
-  UnmapGadget(game_gadget[GAME_CTRL_ID_REDO]);
-
-  MapGameButtonsAtSamePosition(GAME_CTRL_ID_UNDO);
-  MapGameButtonsAtSamePosition(GAME_CTRL_ID_REDO);
-}
-
 void ModifyPauseButtons(void)
 {
   static int ids[] =
@@ -16169,9 +16231,7 @@ static void MapGameButtonsExt(boolean on_tape)
   int i;
 
   for (i = 0; i < NUM_GAME_BUTTONS; i++)
-    if ((!on_tape || gamebutton_info[i].allowed_on_tape) &&
-       i != GAME_CTRL_ID_UNDO &&
-       i != GAME_CTRL_ID_REDO)
+    if (!on_tape || gamebutton_info[i].allowed_on_tape)
       MapGadget(game_gadget[i]);
 
   UnmapGameButtonsAtSamePosition_All();
@@ -16263,6 +16323,8 @@ static void GameUndoRedoExt(void)
   DrawVideoDisplay(VIDEO_STATE_FRAME_ON, FrameCounter);
   DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
 
+  ModifyPauseButtons();
+
   BackToFront();
 }
 
@@ -16271,8 +16333,12 @@ static void GameUndo(int steps)
   if (!CheckEngineSnapshotList())
     return;
 
+  int tape_property_bits = tape.property_bits;
+
   LoadEngineSnapshot_Undo(steps);
 
+  tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
+
   GameUndoRedoExt();
 }
 
@@ -16281,8 +16347,12 @@ static void GameRedo(int steps)
   if (!CheckEngineSnapshotList())
     return;
 
+  int tape_property_bits = tape.property_bits;
+
   LoadEngineSnapshot_Redo(steps);
 
+  tape.property_bits |= tape_property_bits | TAPE_PROPERTY_SNAPSHOT;
+
   GameUndoRedoExt();
 }