+static boolean PlayScoreTape_WaitForDownload(void)
+{
+ unsigned int download_delay = 0;
+ unsigned int download_delay_value = 10000;
+
+ ResetDelayCounter(&download_delay);
+
+ // wait for score tape to be successfully downloaded (and fail on timeout)
+ while (!server_scores.tape_downloaded)
+ {
+ if (DelayReached(&download_delay, download_delay_value))
+ return FALSE;
+
+ UPDATE_BUSY_STATE_NOT_LOADING();
+
+ Delay(20);
+ }
+
+ return TRUE;
+}
+
+boolean PlayScoreTape(int entry_nr)
+{
+ struct ScoreEntry *entry = &scores.entry[entry_nr];
+ char *tape_filename = getScoreTapeFilename(entry->tape_basename, level_nr);
+ boolean download_tape = (!fileExists(tape_filename));
+
+ if (download_tape && entry->id == -1)
+ return FALSE;
+
+ server_scores.tape_downloaded = FALSE;
+
+ if (download_tape)
+ ApiGetScoreTapeAsThread(level_nr, entry->id, entry->tape_basename);
+
+ SetGameStatus(GAME_MODE_PLAYING);
+
+ FadeOut(REDRAW_FIELD);
+
+ if (download_tape && !PlayScoreTape_WaitForDownload())
+ {
+ SetGameStatus(GAME_MODE_SCOREINFO);
+ ClearField();
+
+ Request("Cannot download score tape from score server!", REQ_CONFIRM);
+
+ return FALSE;
+ }
+
+ if (!TAPE_IS_STOPPED(tape))
+ TapeStop();
+
+ // if tape recorder already contains a tape, remove it without asking
+ TapeErase();
+
+ LoadScoreTape(entry->tape_basename, level_nr);
+
+ if (TAPE_IS_EMPTY(tape))
+ {
+ SetGameStatus(GAME_MODE_SCOREINFO);
+ ClearField();
+
+ Request("Cannot load score tape for this level!", REQ_CONFIRM);
+
+ return FALSE;
+ }
+
+ FadeSkipNextFadeOut();
+
+ TapeStartGamePlaying();
+
+ score_info_tape_play = TRUE;
+
+ return TRUE;
+}
+
+static boolean checkTapesFromSameLevel(struct TapeInfo *t1, struct TapeInfo *t2)
+{
+ return (strEqual(t1->level_identifier, t2->level_identifier) &&
+ t1->level_nr == t2->level_nr);
+}
+
+static void CopyTape(struct TapeInfo *tape_from, struct TapeInfo *tape_to)
+{
+ *tape_to = *tape_from;
+}
+
+static void SwapTapes(struct TapeInfo *t1, struct TapeInfo *t2)
+{
+ struct TapeInfo tmp = *t1;
+
+ *t1 = *t2;
+ *t2 = tmp;
+}
+
+static void CopyTapeToUndoBuffer(void)
+{
+ // copy tapes to undo buffer if large enough (or larger than last undo tape)
+ // or if the last undo tape is from a different level set or level number
+ if (tape.length_seconds >= TAPE_MIN_SECONDS_FOR_UNDO_BUFFER ||
+ tape.length_seconds >= tape_undo_buffer.length_seconds ||
+ !checkTapesFromSameLevel(&tape, &tape_undo_buffer))
+ {
+ CopyTape(&tape, &tape_undo_buffer);
+ }
+}
+
+void UndoTape(void)
+{
+ // only undo tapes from same level set and with same level number
+ if (!checkTapesFromSameLevel(&tape, &tape_undo_buffer))
+ return;
+
+ if (!TAPE_IS_STOPPED(tape))
+ TapeStop();
+
+ // swap last recorded tape with undo buffer, so undo can be reversed
+ SwapTapes(&tape, &tape_undo_buffer);
+
+ DrawCompleteVideoDisplay();
+}
+
+void FixTape_ForceSinglePlayer(void)
+{
+ int i;
+
+ /* fix single-player tapes that contain player input for more than one
+ player (due to a bug in 3.3.1.2 and earlier versions), which results
+ in playing levels with more than one player in multi-player mode,
+ even though the tape was originally recorded in single-player mode */
+
+ // remove player input actions for all players but the first one
+ for (i = 1; i < MAX_PLAYERS; i++)
+ tape.player_participates[i] = FALSE;
+
+ tape.changed = TRUE;
+}
+