fixed compiler warnings (after adding "-Wstrict-prototypes")
[rocksndiamonds.git] / src / tape.c
index ff5f6199ef6f5c482e5d75d8cbb32a644a92b71a..bcec56b049427e1a2a1a0d69415d5844c2509714 100644 (file)
 #define TAPE_CTRL_ID_PAUSE                     3
 #define TAPE_CTRL_ID_RECORD                    4
 #define TAPE_CTRL_ID_PLAY                      5
+#define TAPE_CTRL_ID_INSERT_SOLUTION           6
+#define TAPE_CTRL_ID_PLAY_SOLUTION             7
 
-#define NUM_TAPE_BUTTONS                       6
+#define NUM_TAPE_BUTTONS                       8
 
 /* values for tape handling */
 #define TAPE_PAUSE_SECONDS_BEFORE_DEATH                5
 
 /* forward declaration for internal use */
 static void HandleTapeButtons(struct GadgetInfo *);
-static void TapeStopWarpForward();
+static void TapeStopWarpForward(void);
+static float GetTapeLengthSecondsFloat(void);
 
 static struct GadgetInfo *tape_gadget[NUM_TAPE_BUTTONS];
 
@@ -356,29 +359,38 @@ void DrawVideoDisplaySymbol(unsigned int state)
   DrawVideoDisplay(state, VIDEO_DISPLAY_SYMBOL_ONLY);
 }
 
-void DrawVideoDisplayPlayState()
+void DrawVideoDisplayCurrentState(void)
 {
   int state = 0;
 
   DrawVideoDisplay(VIDEO_STATE_OFF, 0);
 
-  state |= VIDEO_STATE_PLAY_ON;
-
   if (tape.pausing)
-  {
     state |= VIDEO_STATE_PAUSE_ON;
+
+  if (tape.recording)
+  {
+    state |= VIDEO_STATE_REC_ON;
+
+    if (tape.single_step)
+      state |= VIDEO_STATE_1STEP_ON;
   }
-  else
+  else if (tape.playing)
   {
-    if (tape.deactivate_display)
-      state |= VIDEO_STATE_WARP2_ON;
-    else if (tape.warp_forward)
-      state |= VIDEO_STATE_WARP_ON;
-    else if (tape.fast_forward)
-      state |= VIDEO_STATE_FFWD_ON;
+    state |= VIDEO_STATE_PLAY_ON;
 
-    if (tape.pause_before_end)
-      state |= VIDEO_STATE_PBEND_ON;
+    if (!tape.pausing)
+    {
+      if (tape.deactivate_display)
+       state |= VIDEO_STATE_WARP2_ON;
+      else if (tape.warp_forward)
+       state |= VIDEO_STATE_WARP_ON;
+      else if (tape.fast_forward)
+       state |= VIDEO_STATE_FFWD_ON;
+
+      if (tape.pause_before_end)
+       state |= VIDEO_STATE_PBEND_ON;
+    }
   }
 
   // draw labels and symbols separately to prevent labels overlapping symbols
@@ -386,7 +398,7 @@ void DrawVideoDisplayPlayState()
   DrawVideoDisplaySymbol(state);
 }
 
-void DrawCompleteVideoDisplay()
+void DrawCompleteVideoDisplay(void)
 {
   struct GraphicInfo *g_tape = &graphic_info[IMG_BACKGROUND_TAPE];
 
@@ -395,8 +407,7 @@ void DrawCompleteVideoDisplay()
             gfx.vxsize, gfx.vysize, gfx.vx, gfx.vy);
 
   /* draw tape buttons (forced) */
-  UnmapTapeButtons();
-  MapTapeButtons();
+  RedrawOrRemapTapeButtons();
 
   DrawVideoDisplay(VIDEO_ALL_OFF, 0);
 
@@ -431,7 +442,7 @@ void DrawCompleteVideoDisplay()
             0, 0);
 }
 
-void TapeDeactivateDisplayOn()
+void TapeDeactivateDisplayOn(void)
 {
   SetDrawDeactivationMask(REDRAW_FIELD);
   audio.sound_deactivated = TRUE;
@@ -450,6 +461,46 @@ void TapeDeactivateDisplayOff(boolean redraw_display)
 }
 
 
+/* ========================================================================= */
+/* tape logging functions                                                    */
+/* ========================================================================= */
+
+void PrintTapeReplayProgress(boolean replay_finished)
+{
+  static unsigned int counter_last = -1;
+  unsigned int counter = Counter();
+  unsigned int counter_seconds  = counter / 1000;
+
+  if (!replay_finished)
+  {
+    unsigned int counter_delay = 50;
+
+    if (counter > counter_last + counter_delay)
+    {
+      PrintNoLog("\r");
+      PrintNoLog("Level %03d [%02d:%02d]: [%02d:%02d] - playing tape ... ",
+                level_nr, tape.length_seconds / 60, tape.length_seconds % 60,
+                TapeTime / 60, TapeTime % 60);
+
+      counter_last = counter;
+    }
+  }
+  else
+  {
+    float tape_length_seconds = GetTapeLengthSecondsFloat();
+
+    PrintNoLog("\r");
+    Print("Level %03d [%02d:%02d]: (%02d:%02d.%03d / %.2f %%) - %s.\n",
+         level_nr, tape.length_seconds / 60, tape.length_seconds % 60,
+         counter_seconds / 60, counter_seconds % 60, counter % 1000,
+         (float)counter / tape_length_seconds / 10,
+         tape.auto_play_level_solved ? "solved" : "NOT SOLVED");
+
+    counter_last = -1;
+  }
+}
+
+
 /* ========================================================================= */
 /* tape control functions                                                    */
 /* ========================================================================= */
@@ -461,12 +512,12 @@ void TapeSetDateFromEpochSeconds(time_t epoch_seconds)
   tape.date = 10000 * (lt->tm_year % 100) + 100 * lt->tm_mon + lt->tm_mday;
 }
 
-void TapeSetDateFromNow()
+void TapeSetDateFromNow(void)
 {
   TapeSetDateFromEpochSeconds(time(NULL));
 }
 
-void TapeErase()
+void TapeErase(void)
 {
   int i;
 
@@ -495,9 +546,11 @@ void TapeErase()
 
   tape.centered_player_nr_next = -1;
   tape.set_centered_player = FALSE;
+
+  tape.use_mouse = (level.game_engine_type == GAME_ENGINE_TYPE_MM);
 }
 
-static void TapeRewind()
+static void TapeRewind(void)
 {
   tape.counter = 0;
   tape.delay_played = 0;
@@ -545,23 +598,12 @@ void TapeStartRecording(int random_seed)
   audio.sound_deactivated = FALSE;
 }
 
-static void TapeStartGameRecording()
+static void TapeStartGameRecording(void)
 {
-  TapeStartRecording(level.random_seed);
-
-#if defined(NETWORK_AVALIABLE)
-  if (options.network)
-  {
-    SendToServer_StartPlaying();
-
-    return;
-  }
-#endif
-
-  InitGame();
+  StartGameActions(network.enabled, TRUE, level.random_seed);
 }
 
-static void TapeAppendRecording()
+static void TapeAppendRecording(void)
 {
   if (!tape.playing || !tape.pausing)
     return;
@@ -589,22 +631,23 @@ static void TapeAppendRecording()
   UpdateAndDisplayGameControlValues();
 }
 
-void TapeHaltRecording()
+void TapeHaltRecording(void)
 {
-  if (!tape.recording)
-    return;
-
   tape.counter++;
-  tape.pos[tape.counter].delay = 0;
+
+  // initialize delay for next tape entry (to be able to continue recording)
+  if (tape.counter < MAX_TAPE_LEN)
+    tape.pos[tape.counter].delay = 0;
 
   tape.length = tape.counter;
   tape.length_frames = GetTapeLengthFrames();
   tape.length_seconds = GetTapeLengthSeconds();
 }
 
-void TapeStopRecording()
+void TapeStopRecording(void)
 {
-  TapeHaltRecording();
+  if (tape.recording)
+    TapeHaltRecording();
 
   tape.recording = FALSE;
   tape.pausing = FALSE;
@@ -613,33 +656,10 @@ void TapeStopRecording()
   MapTapeEjectButton();
 }
 
-void TapeRecordAction(byte action_raw[MAX_PLAYERS])
+boolean TapeAddAction(byte action[MAX_PLAYERS])
 {
-  byte action[MAX_PLAYERS];
   int i;
 
-  if (!tape.recording)         /* (record action even when tape is paused) */
-    return;
-
-  if (tape.counter >= MAX_TAPE_LEN - 1)
-  {
-    TapeStopRecording();
-    return;
-  }
-
-  for (i = 0; i < MAX_PLAYERS; i++)
-    action[i] = action_raw[i];
-
-  if (tape.set_centered_player)
-  {
-    for (i = 0; i < MAX_PLAYERS; i++)
-      if (tape.centered_player_nr_next == i ||
-         tape.centered_player_nr_next == -1)
-       action[i] |= KEY_SET_FOCUS;
-
-    tape.set_centered_player = FALSE;
-  }
-
   if (tape.pos[tape.counter].delay > 0)                /* already stored action */
   {
     boolean changed_events = FALSE;
@@ -650,6 +670,9 @@ void TapeRecordAction(byte action_raw[MAX_PLAYERS])
 
     if (changed_events || tape.pos[tape.counter].delay >= 255)
     {
+      if (tape.counter >= MAX_TAPE_LEN - 1)
+       return FALSE;
+
       tape.counter++;
       tape.pos[tape.counter].delay = 0;
     }
@@ -664,47 +687,77 @@ void TapeRecordAction(byte action_raw[MAX_PLAYERS])
 
     tape.pos[tape.counter].delay++;
   }
+
+  return TRUE;
 }
 
-void TapeTogglePause(boolean toggle_manual)
+void TapeRecordAction(byte action_raw[MAX_PLAYERS])
 {
+  byte action[MAX_PLAYERS];
+  int i;
+
+  if (!tape.recording)         /* (record action even when tape is paused) */
+    return;
+
+  for (i = 0; i < MAX_PLAYERS; i++)
+    action[i] = action_raw[i];
+
+  if (!tape.use_mouse && tape.set_centered_player)
+  {
+    for (i = 0; i < MAX_PLAYERS; i++)
+      if (tape.centered_player_nr_next == i ||
+         tape.centered_player_nr_next == -1)
+       action[i] |= KEY_SET_FOCUS;
+
+    tape.set_centered_player = FALSE;
+  }
+
+  if (!TapeAddAction(action))
+    TapeStopRecording();
+}
+
+void TapeTogglePause(boolean toggle_mode)
+{
+  if (tape.playing && tape.pausing && (toggle_mode & TAPE_TOGGLE_PLAY_PAUSE))
+  {
+    // continue playing in normal mode
+    tape.fast_forward = FALSE;
+    tape.warp_forward = FALSE;
+    tape.deactivate_display = FALSE;
+
+    tape.pause_before_end = FALSE;
+  }
+
   tape.pausing = !tape.pausing;
 
-  if (tape.single_step && toggle_manual)
+  if (tape.single_step && (toggle_mode & TAPE_TOGGLE_MANUAL))
     tape.single_step = FALSE;
 
-  DrawVideoDisplayPlayState();
+  DrawVideoDisplayCurrentState();
 
-  if (tape.warp_forward)
+  if (tape.deactivate_display)
   {
     if (tape.pausing)
-    {
       TapeDeactivateDisplayOff(game_status == GAME_MODE_PLAYING);
-    }
-    else if (tape.deactivate_display)
-    {
+    else
       TapeDeactivateDisplayOn();
+  }
 
-      DrawVideoDisplaySymbol(VIDEO_STATE_WARP_ON);
-    }
-    else
-    {
-      DrawVideoDisplaySymbol(VIDEO_STATE_WARP2_ON);
-    }
+  if (tape.quick_resume)
+  {
+    tape.quick_resume = FALSE;
 
-    if (tape.quick_resume)
-    {
-      tape.quick_resume = FALSE;
+    TapeStopWarpForward();
+    TapeAppendRecording();
 
-      TapeStopWarpForward();
-      TapeAppendRecording();
+    if (!CheckEngineSnapshotSingle())
+      SaveEngineSnapshotSingle();
 
-      if (!CheckEngineSnapshotSingle())
-       SaveEngineSnapshotSingle();
+    // restart step/move snapshots after quick loading tape
+    SaveEngineSnapshotToListInitial();
 
-      // restart step/move snapshots after quick loading tape
-      SaveEngineSnapshotToListInitial();
-    }
+    // do not map undo/redo buttons after quick loading tape
+    return;
   }
 
   if (setup.show_snapshot_buttons &&
@@ -718,7 +771,7 @@ void TapeTogglePause(boolean toggle_manual)
   }
 }
 
-void TapeStartPlaying()
+void TapeStartPlaying(void)
 {
   if (TAPE_IS_EMPTY(tape))
     return;
@@ -741,14 +794,14 @@ void TapeStartPlaying()
   audio.sound_deactivated = FALSE;
 }
 
-static void TapeStartGamePlaying()
+static void TapeStartGamePlaying(void)
 {
   TapeStartPlaying();
 
   InitGame();
 }
 
-void TapeStopPlaying()
+void TapeStopPlaying(void)
 {
   tape.playing = FALSE;
   tape.pausing = FALSE;
@@ -760,7 +813,7 @@ void TapeStopPlaying()
   MapTapeEjectButton();
 }
 
-byte *TapePlayAction()
+byte *TapePlayAction(void)
 {
   int update_delay = FRAMES_PER_SECOND / 2;
   boolean update_video_display = (FrameCounter % update_delay == 0);
@@ -775,9 +828,6 @@ byte *TapePlayAction()
   {
     if (TapeTime > tape.length_seconds - TAPE_PAUSE_SECONDS_BEFORE_DEATH)
     {
-      tape.fast_forward = FALSE;
-      tape.pause_before_end = FALSE;
-
       TapeStopWarpForward();
       TapeTogglePause(TAPE_TOGGLE_MANUAL);
 
@@ -785,27 +835,38 @@ byte *TapePlayAction()
     }
   }
 
-  if (update_video_display && !tape.deactivate_display)
-  {
-    if (tape.pause_before_end)
-      DrawVideoDisplayLabel(VIDEO_STATE_PBEND(update_draw_label_on));
-    else if (tape.fast_forward)
-      DrawVideoDisplayLabel(VIDEO_STATE_FFWD(update_draw_label_on));
-
-    if (tape.warp_forward)
-      DrawVideoDisplaySymbol(VIDEO_STATE_WARP2_ON);
-  }
-
   if (tape.counter >= tape.length)     /* end of tape reached */
   {
     if (tape.warp_forward && !tape.auto_play)
+    {
+      TapeStopWarpForward();
       TapeTogglePause(TAPE_TOGGLE_MANUAL);
+    }
     else
+    {
       TapeStop();
+    }
 
     return NULL;
   }
 
+  if (update_video_display && !tape.deactivate_display)
+  {
+    int state = 0;
+
+    if (tape.warp_forward)
+      state |= VIDEO_STATE_WARP(update_draw_label_on);
+    else if (tape.fast_forward)
+      state |= VIDEO_STATE_FFWD(update_draw_label_on);
+
+    if (tape.pause_before_end)
+      state |= VIDEO_STATE_PBEND(update_draw_label_on);
+
+    // draw labels and symbols separately to prevent labels overlapping symbols
+    DrawVideoDisplayLabel(state);
+    DrawVideoDisplaySymbol(state);
+  }
+
   for (i = 0; i < MAX_PLAYERS; i++)
     action[i] = tape.pos[tape.counter].action[i];
 
@@ -819,16 +880,19 @@ byte *TapePlayAction()
   tape.set_centered_player = FALSE;
   tape.centered_player_nr_next = -999;
 
-  for (i = 0; i < MAX_PLAYERS; i++)
+  if (!tape.use_mouse)
   {
-    if (action[i] & KEY_SET_FOCUS)
+    for (i = 0; i < MAX_PLAYERS; i++)
     {
-      tape.set_centered_player = TRUE;
-      tape.centered_player_nr_next =
-       (tape.centered_player_nr_next == -999 ? i : -1);
-    }
+      if (action[i] & KEY_SET_FOCUS)
+      {
+       tape.set_centered_player = TRUE;
+       tape.centered_player_nr_next =
+         (tape.centered_player_nr_next == -999 ? i : -1);
+      }
 
-    action[i] &= ~KEY_SET_FOCUS;
+      action[i] &= ~KEY_SET_FOCUS;
+    }
   }
 
   tape.delay_played++;
@@ -838,11 +902,17 @@ byte *TapePlayAction()
     tape.delay_played = 0;
   }
 
+  if (tape.auto_play)
+    PrintTapeReplayProgress(FALSE);
+
   return action;
 }
 
-void TapeStop()
+void TapeStop(void)
 {
+  if (tape.pausing)
+    TapeTogglePause(TAPE_TOGGLE_MANUAL);
+
   TapeStopRecording();
   TapeStopPlaying();
 
@@ -856,7 +926,7 @@ void TapeStop()
   }
 }
 
-unsigned int GetTapeLengthFrames()
+unsigned int GetTapeLengthFrames(void)
 {
   unsigned int tape_length_frames = 0;
   int i;
@@ -870,55 +940,46 @@ unsigned int GetTapeLengthFrames()
   return tape_length_frames;
 }
 
-unsigned int GetTapeLengthSeconds()
+unsigned int GetTapeLengthSeconds(void)
 {
   return (GetTapeLengthFrames() * GAME_FRAME_DELAY / 1000);
 }
 
-static void TapeStartWarpForward()
+static float GetTapeLengthSecondsFloat(void)
 {
-  tape.warp_forward = TRUE;
+  return ((float)GetTapeLengthFrames() * GAME_FRAME_DELAY / 1000);
+}
 
-  if (!tape.fast_forward && !tape.pause_before_end)
-  {
-    tape.pausing = FALSE;
-    tape.pause_before_end = TRUE;
-    tape.deactivate_display = TRUE;
+static void TapeStartWarpForward(int mode)
+{
+  tape.fast_forward = (mode & AUTOPLAY_FFWD);
+  tape.warp_forward = (mode & AUTOPLAY_WARP);
+  tape.deactivate_display = (mode & AUTOPLAY_WARP_NO_DISPLAY);
+
+  tape.pausing = FALSE;
 
+  if (tape.deactivate_display)
     TapeDeactivateDisplayOn();
 
-    DrawVideoDisplay(VIDEO_STATE_PBEND_ON, 0);
-    DrawVideoDisplaySymbol(VIDEO_STATE_WARP_ON);
-  }
-  else
-  {
-    DrawVideoDisplaySymbol(VIDEO_STATE_WARP2_ON);
-  }
+  DrawVideoDisplayCurrentState();
 }
 
-static void TapeStopWarpForward()
+static void TapeStopWarpForward(void)
 {
-  int state = VIDEO_STATE_PAUSE(tape.pausing);
-
-  if (tape.deactivate_display)
-    tape.pause_before_end = FALSE;
-
+  tape.fast_forward = FALSE;
   tape.warp_forward = FALSE;
   tape.deactivate_display = FALSE;
 
-  TapeDeactivateDisplayOff(game_status == GAME_MODE_PLAYING);
+  tape.pause_before_end = FALSE;
 
-  state |= VIDEO_STATE_WARP_OFF;
-  state |= (tape.pause_before_end ? VIDEO_STATE_PBEND_ON :
-           tape.fast_forward     ? VIDEO_STATE_FFWD_ON :
-           VIDEO_STATE_PLAY_ON);
+  TapeDeactivateDisplayOff(game_status == GAME_MODE_PLAYING);
 
-  DrawVideoDisplay(state, 0);
+  DrawVideoDisplayCurrentState();
 }
 
-static void TapeSingleStep()
+static void TapeSingleStep(void)
 {
-  if (options.network)
+  if (network.enabled)
     return;
 
   if (!tape.pausing)
@@ -929,7 +990,7 @@ static void TapeSingleStep()
   DrawVideoDisplay(VIDEO_STATE_1STEP(tape.single_step), 0);
 }
 
-void TapeQuickSave()
+void TapeQuickSave(void)
 {
   if (game_status == GAME_MODE_MAIN)
   {
@@ -955,7 +1016,7 @@ void TapeQuickSave()
     SaveEngineSnapshotSingle();
 }
 
-void TapeQuickLoad()
+void TapeQuickLoad(void)
 {
   char *filename = getTapeFilename(level_nr);
 
@@ -1006,7 +1067,7 @@ void TapeQuickLoad()
   if (!TAPE_IS_EMPTY(tape))
   {
     TapeStartGamePlaying();
-    TapeStartWarpForward();
+    TapeStartWarpForward(AUTOPLAY_MODE_WARP_NO_DISPLAY);
 
     tape.quick_resume = TRUE;
   }
@@ -1018,17 +1079,52 @@ void TapeQuickLoad()
   }
 }
 
-void InsertSolutionTape()
+boolean hasSolutionTape(void)
 {
-  if (!TAPE_IS_EMPTY(tape))
-    return;
+  boolean tape_file_exists = fileExists(getSolutionTapeFilename(level_nr));
+  boolean level_has_tape = (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
+                           level.native_sp_level->demo.is_available);
 
-  LoadSolutionTape(level_nr);
+  return (tape_file_exists || level_has_tape);
+}
 
-  if (TAPE_IS_EMPTY(tape))
+boolean InsertSolutionTape(void)
+{
+  if (!hasSolutionTape())
+  {
     Request("No solution tape for this level!", REQ_CONFIRM);
 
+    return FALSE;
+  }
+
+  if (!TAPE_IS_STOPPED(tape))
+    TapeStop();
+
+  // if tape recorder already contains a tape, remove it without asking
+  TapeErase();
+
+  LoadSolutionTape(level_nr);
+
   DrawCompleteVideoDisplay();
+
+  if (TAPE_IS_EMPTY(tape))
+  {
+    Request("Loading solution tape for this level failed!", REQ_CONFIRM);
+
+    return FALSE;
+  }
+
+  return TRUE;
+}
+
+boolean PlaySolutionTape(void)
+{
+  if (!InsertSolutionTape())
+    return FALSE;
+
+  TapeStartGamePlaying();
+
+  return TRUE;
 }
 
 
@@ -1036,7 +1132,7 @@ void InsertSolutionTape()
  * tape autoplay functions
  * ------------------------------------------------------------------------- */
 
-void AutoPlayTape()
+void AutoPlayTape(void)
 {
   static LevelDirTree *autoplay_leveldir = NULL;
   static boolean autoplay_initialized = FALSE;
@@ -1050,7 +1146,7 @@ void AutoPlayTape()
   if (autoplay_initialized)
   {
     /* just finished auto-playing tape */
-    printf("%s.\n", tape.auto_play_level_solved ? "solved" : "NOT SOLVED");
+    PrintTapeReplayProgress(TRUE);
 
     num_levels_played++;
 
@@ -1082,15 +1178,15 @@ void AutoPlayTape()
 
     autoplay_level_nr = autoplay_leveldir->first_level;
 
-    printf_line("=", 79);
-    printf("Automatically playing level tapes\n");
-    printf_line("-", 79);
-    printf("Level series identifier: '%s'\n", autoplay_leveldir->identifier);
-    printf("Level series name:       '%s'\n", autoplay_leveldir->name);
-    printf("Level series author:     '%s'\n", autoplay_leveldir->author);
-    printf("Number of levels:        %d\n",   autoplay_leveldir->levels);
-    printf_line("=", 79);
-    printf("\n");
+    PrintLine("=", 79);
+    Print("Automatically playing level tapes\n");
+    PrintLine("-", 79);
+    Print("Level series identifier: '%s'\n", autoplay_leveldir->identifier);
+    Print("Level series name:       '%s'\n", autoplay_leveldir->name);
+    Print("Level series author:     '%s'\n", autoplay_leveldir->author);
+    Print("Number of levels:        %d\n",   autoplay_leveldir->levels);
+    PrintLine("=", 79);
+    Print("\n");
 
     for (i = 0; i < MAX_TAPES_PER_SET; i++)
       level_failed[i] = FALSE;
@@ -1107,66 +1203,64 @@ void AutoPlayTape()
 
     TapeErase();
 
-    printf("Level %03d: ", level_nr);
-
     LoadLevel(level_nr);
-    if (level.no_valid_file)
+
+    if (level.no_level_file || level.no_valid_file)
     {
-      printf("(no level)\n");
+      Print("Level %03d: (no level)\n", level_nr);
+
       continue;
     }
 
 #if 0
     /* ACTIVATE THIS FOR LOADING/TESTING OF LEVELS ONLY */
-    printf("(only testing level)\n");
+    Print("Level %03d: (only testing level)\n", level_nr);
     continue;
 #endif
 
-    LoadSolutionTape(level_nr);
+    if (options.mytapes)
+      LoadTape(level_nr);
+    else
+      LoadSolutionTape(level_nr);
 
     if (tape.no_valid_file)
     {
       num_tape_missing++;
 
-      printf("(no tape)\n");
+      Print("Level %03d: (no tape)\n", level_nr);
 
       continue;
     }
 
-    printf("playing tape ... ");
+    InitCounter();
 
     TapeStartGamePlaying();
-
-    if (global.autoplay_mode == AUTOPLAY_FFWD)
-      tape.fast_forward = TRUE;
-
-    if (global.autoplay_mode != AUTOPLAY_PLAY)
-      TapeStartWarpForward();
+    TapeStartWarpForward(global.autoplay_mode);
 
     return;
   }
 
-  printf("\n");
-  printf_line("=", 79);
-  printf("Number of levels played: %d\n", num_levels_played);
-  printf("Number of levels solved: %d (%d%%)\n", num_levels_solved,
+  Print("\n");
+  PrintLine("=", 79);
+  Print("Number of levels played: %d\n", num_levels_played);
+  Print("Number of levels solved: %d (%d%%)\n", num_levels_solved,
         (num_levels_played ? num_levels_solved * 100 / num_levels_played :0));
-  printf_line("-", 79);
-  printf("Summary (for automatic parsing by scripts):\n");
-  printf("LEVELDIR '%s', SOLVED %d/%d (%d%%)",
+  PrintLine("-", 79);
+  Print("Summary (for automatic parsing by scripts):\n");
+  Print("LEVELDIR '%s', SOLVED %d/%d (%d%%)",
         autoplay_leveldir->identifier, num_levels_solved, num_levels_played,
         (num_levels_played ? num_levels_solved * 100 / num_levels_played :0));
 
   if (num_levels_played != num_levels_solved)
   {
-    printf(", FAILED:");
+    Print(", FAILED:");
     for (i = 0; i < MAX_TAPES_PER_SET; i++)
       if (level_failed[i])
-       printf(" %03d", i);
+       Print(" %03d", i);
   }
 
-  printf("\n");
-  printf_line("=", 79);
+  Print("\n");
+  PrintLine("=", 79);
 
   CloseAllAndExit(0);
 }
@@ -1206,16 +1300,25 @@ static struct
   {
     IMG_GFX_TAPE_BUTTON_PLAY,          &tape.button.play,
     TAPE_CTRL_ID_PLAY,                 "play tape"
+  },
+  {
+    IMG_GFX_TAPE_BUTTON_INSERT_SOLUTION,&tape.button.insert_solution,
+    TAPE_CTRL_ID_INSERT_SOLUTION,      "insert solution tape"
+  },
+  {
+    IMG_GFX_TAPE_BUTTON_PLAY_SOLUTION, &tape.button.play_solution,
+    TAPE_CTRL_ID_PLAY_SOLUTION,                "play solution tape"
   }
 };
 
-void CreateTapeButtons()
+void CreateTapeButtons(void)
 {
   int i;
 
   for (i = 0; i < NUM_TAPE_BUTTONS; i++)
   {
-    struct GraphicInfo *gfx = &graphic_info[tapebutton_info[i].graphic];
+    int graphic = tapebutton_info[i].graphic;
+    struct GraphicInfo *gfx = &graphic_info[graphic];
     struct XY *pos = tapebutton_info[i].pos;
     struct GadgetInfo *gi;
     int gd_x = gfx->src_x;
@@ -1225,6 +1328,7 @@ void CreateTapeButtons()
     int id = i;
 
     gi = CreateGadget(GDI_CUSTOM_ID, id,
+                     GDI_IMAGE_ID, graphic,
                      GDI_INFO_TEXT, tapebutton_info[i].infotext,
                      GDI_X, VX + pos->x,
                      GDI_Y, VY + pos->y,
@@ -1246,7 +1350,7 @@ void CreateTapeButtons()
   }
 }
 
-void FreeTapeButtons()
+void FreeTapeButtons(void)
 {
   int i;
 
@@ -1254,19 +1358,19 @@ void FreeTapeButtons()
     FreeGadget(tape_gadget[i]);
 }
 
-void MapTapeEjectButton()
+void MapTapeEjectButton(void)
 {
   UnmapGadget(tape_gadget[TAPE_CTRL_ID_EXTRA]);
   MapGadget(tape_gadget[TAPE_CTRL_ID_EJECT]);
 }
 
-void MapTapeWarpButton()
+void MapTapeWarpButton(void)
 {
   UnmapGadget(tape_gadget[TAPE_CTRL_ID_EJECT]);
   MapGadget(tape_gadget[TAPE_CTRL_ID_EXTRA]);
 }
 
-void MapTapeButtons()
+void MapTapeButtons(void)
 {
   int i;
 
@@ -1278,10 +1382,10 @@ void MapTapeButtons()
     MapTapeWarpButton();
 
   if (tape.show_game_buttons)
-    MapGameButtons();
+    MapGameButtonsOnTape();
 }
 
-void UnmapTapeButtons()
+void UnmapTapeButtons(void)
 {
   int i;
 
@@ -1289,7 +1393,35 @@ void UnmapTapeButtons()
     UnmapGadget(tape_gadget[i]);
 
   if (tape.show_game_buttons)
-    UnmapGameButtons();
+    UnmapGameButtonsOnTape();
+}
+
+void RedrawTapeButtons(void)
+{
+  int i;
+
+  for (i = 0; i < NUM_TAPE_BUTTONS; i++)
+    RedrawGadget(tape_gadget[i]);
+
+  if (tape.show_game_buttons)
+    RedrawGameButtonsOnTape();
+
+  // RedrawGadget() may have set REDRAW_ALL if buttons are defined off-area
+  redraw_mask &= ~REDRAW_ALL;
+}
+
+void RedrawOrRemapTapeButtons(void)
+{
+  if (tape_gadget[TAPE_CTRL_ID_PLAY]->mapped)
+  {
+    // tape buttons already mapped
+    RedrawTapeButtons();
+  }
+  else
+  {
+    UnmapTapeButtons();
+    MapTapeButtons();
+  }
 }
 
 static void HandleTapeButtonsExt(int id)
@@ -1312,7 +1444,7 @@ static void HandleTapeButtonsExt(int id)
       else
       {
        if (tape.changed)
-         SaveTapeChecked(tape.level_nr);
+         SaveTapeChecked(level_nr);
 
        TapeErase();
       }
@@ -1323,35 +1455,32 @@ static void HandleTapeButtonsExt(int id)
     case TAPE_CTRL_ID_EXTRA:
       if (tape.playing)
       {
-       if (!tape.warp_forward)                 /* PLAY -> WARP FORWARD PLAY */
-       {
-         TapeStartWarpForward();
-       }
-       else if (tape.pausing)                  /* PAUSE -> WARP FORWARD PLAY */
-       {
-         TapeTogglePause(TAPE_TOGGLE_MANUAL);
-       }
-       else                                    /* WARP FORWARD PLAY -> PLAY */
-       {
-         TapeStopWarpForward();
-       }
+       tape.pause_before_end = !tape.pause_before_end;
+
+       DrawVideoDisplayCurrentState();
       }
       else if (tape.recording)
+      {
        TapeSingleStep();
+      }
 
       break;
 
     case TAPE_CTRL_ID_STOP:
       TapeStop();
+
       break;
 
     case TAPE_CTRL_ID_PAUSE:
       TapeTogglePause(TAPE_TOGGLE_MANUAL);
+
       break;
 
     case TAPE_CTRL_ID_RECORD:
       if (TAPE_IS_STOPPED(tape))
+      {
        TapeStartGameRecording();
+      }
       else if (tape.pausing)
       {
        if (tape.playing)                       /* PLAY -> PAUSE -> RECORD */
@@ -1359,6 +1488,7 @@ static void HandleTapeButtonsExt(int id)
        else
          TapeTogglePause(TAPE_TOGGLE_MANUAL);
       }
+
       break;
 
     case TAPE_CTRL_ID_PLAY:
@@ -1380,45 +1510,44 @@ static void HandleTapeButtonsExt(int id)
       {
        if (tape.pausing)                       /* PAUSE -> PLAY */
        {
-         // continue playing in normal mode
-         tape.fast_forward = FALSE;
-         tape.warp_forward = FALSE;
-         tape.pause_before_end = FALSE;
-         tape.deactivate_display = FALSE;
-
-         TapeTogglePause(TAPE_TOGGLE_MANUAL);
+         TapeTogglePause(TAPE_TOGGLE_MANUAL | TAPE_TOGGLE_PLAY_PAUSE);
        }
-       else if (tape.warp_forward &&
-                !tape.fast_forward)            /* WARP FORWARD PLAY -> PLAY */
+       else if (!tape.fast_forward)            /* PLAY -> FFWD */
        {
-         TapeStopWarpForward();
+         tape.fast_forward = TRUE;
        }
-       else if (!tape.fast_forward)            /* PLAY -> FAST FORWARD PLAY */
+       else if (!tape.warp_forward)            /* FFWD -> WARP */
        {
-         tape.fast_forward = TRUE;
-
-         DrawVideoDisplay(VIDEO_STATE_FFWD_ON, 0);
+         tape.warp_forward = TRUE;
        }
-       else if (!tape.pause_before_end)        /* FFWD PLAY -> AUTO PAUSE */
+       else if (!tape.deactivate_display)      /* WARP -> WARP BLIND */
        {
-         tape.pause_before_end = TRUE;
+         tape.deactivate_display = TRUE;
 
-         DrawVideoDisplay(VIDEO_STATE_FFWD_OFF | VIDEO_STATE_PBEND_ON, 0);
-
-         if (tape.warp_forward)
-           DrawVideoDisplaySymbol(VIDEO_STATE_WARP2_ON);
+         TapeDeactivateDisplayOn();
        }
-       else                                    /* AUTO PAUSE -> NORMAL PLAY */
+       else                                    /* WARP BLIND -> PLAY */
        {
-         if (tape.warp_forward)
-           TapeStopWarpForward();
-
          tape.fast_forward = FALSE;
-         tape.pause_before_end = FALSE;
+         tape.warp_forward = FALSE;
+         tape.deactivate_display = FALSE;
 
-         DrawVideoDisplay(VIDEO_STATE_PBEND_OFF | VIDEO_STATE_PLAY_ON, 0);
+         TapeDeactivateDisplayOff(game_status == GAME_MODE_PLAYING);
        }
+
+       DrawVideoDisplayCurrentState();
       }
+
+      break;
+
+    case TAPE_CTRL_ID_INSERT_SOLUTION:
+      InsertSolutionTape();
+
+      break;
+
+    case TAPE_CTRL_ID_PLAY_SOLUTION:
+      PlaySolutionTape();
+
       break;
 
     default: