+boolean PlaySolutionTape(void)
+{
+ if (!InsertSolutionTape())
+ return FALSE;
+
+ TapeStartGamePlaying();
+
+ 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;
+}
+
+
+// ----------------------------------------------------------------------------
+// tape autoplay functions
+// ----------------------------------------------------------------------------
+
+static void AutoPlayTapes_SetScoreEntry(int score, int time)
+{
+ // set unique basename for score tape (for uploading to score server)
+ strcpy(tape.score_tape_basename, getScoreTapeBasename(setup.player_name));
+
+ // store score in first score entry
+ scores.last_added = 0;
+
+ struct ScoreEntry *entry = &scores.entry[scores.last_added];
+
+ strncpy(entry->tape_basename, tape.score_tape_basename, MAX_FILENAME_LEN);
+ strncpy(entry->name, setup.player_name, MAX_PLAYER_NAME_LEN);
+
+ entry->score = score;
+ entry->time = time;
+
+ PrintNoLog("- uploading score tape to score server ... ");
+
+ server_scores.uploaded = FALSE;
+}
+
+static void AutoPlayTapes_WaitForUpload(void)
+{
+ unsigned int upload_delay = 0;
+ unsigned int upload_delay_value = 10000;
+
+ ResetDelayCounter(&upload_delay);
+
+ // wait for score tape to be successfully uploaded (and fail on timeout)
+ while (!server_scores.uploaded)
+ {
+ if (DelayReached(&upload_delay, upload_delay_value))
+ {
+ PrintNoLog("\r");
+ Print("- uploading score tape to score server - TIMEOUT.\n");
+
+ Fail("cannot upload score tape to score server");
+ }
+
+ Delay(20);
+ }
+
+ PrintNoLog("\r");
+ Print("- uploading score tape to score server - uploaded.\n");
+}
+
+void AutoPlayTapes(void)
+{
+ static LevelDirTree *autoplay_leveldir = NULL;
+ static boolean autoplay_initialized = FALSE;
+ static int autoplay_last_level_nr = -1;
+ static int autoplay_level_nr = -1;
+ static int num_levels_played = 0;
+ static int num_levels_solved = 0;
+ static int num_tapes_patched = 0;
+ static int num_tape_missing = 0;
+ static boolean level_failed[MAX_TAPES_PER_SET];
+ static char *tape_filename = NULL;
+ static int patch_nr = 0;
+ static char *patch_name[] =
+ {
+ "original tape",
+ "em_random_bug",
+ "screen_34x34",
+
+ NULL
+ };
+ static int patch_version_first[] =
+ {
+ VERSION_IDENT(0,0,0,0),
+ VERSION_IDENT(3,3,1,0),
+ VERSION_IDENT(0,0,0,0),
+
+ -1
+ };
+ static int patch_version_last[] =
+ {
+ VERSION_IDENT(9,9,9,9),
+ VERSION_IDENT(4,0,1,1),
+ VERSION_IDENT(4,2,2,0),
+
+ -1
+ };
+ static byte patch_property_bit[] =
+ {
+ TAPE_PROPERTY_NONE,
+ TAPE_PROPERTY_EM_RANDOM_BUG,
+ TAPE_PROPERTY_NONE,
+
+ -1
+ };
+ int i;
+
+ if (autoplay_initialized)
+ {
+ if (global.autoplay_mode == AUTOPLAY_MODE_FIX)
+ {
+ if (tape.auto_play_level_solved)
+ {
+ if (patch_nr > 0)
+ {
+ // level solved by patched tape -- save fixed tape
+ char *filename = getTapeFilename(level_nr);
+ char *filename_orig = getStringCat2(filename, ".orig");
+
+ // create backup from old tape, if not yet existing
+ if (!fileExists(filename_orig))
+ rename(filename, filename_orig);
+
+ SaveTapeToFilename(filename);
+
+ tape.auto_play_level_fixed = TRUE;
+ num_tapes_patched++;
+ }
+
+ // continue with next tape
+ patch_nr = 0;
+ }
+ else if (patch_name[patch_nr + 1] != NULL)
+ {
+ // level not solved by patched tape -- continue with next patch
+ patch_nr++;
+ }
+ else
+ {
+ // level not solved by any patched tape -- continue with next tape
+ tape.auto_play_level_not_fixable = TRUE;
+ patch_nr = 0;
+ }
+ }
+
+ // just finished auto-playing tape
+ PrintTapeReplayProgress(TRUE);
+
+ if (options.tape_log_filename != NULL)
+ CloseTapeLogfile();
+
+ if (global.autoplay_mode == AUTOPLAY_MODE_SAVE &&
+ tape.auto_play_level_solved)
+ {
+ AutoPlayTapes_SetScoreEntry(game.score_final, game.score_time_final);
+
+ if (leveldir_current)
+ {
+ // the tape's level set identifier may differ from current level set
+ strncpy(tape.level_identifier, leveldir_current->identifier,
+ MAX_FILENAME_LEN);
+ tape.level_identifier[MAX_FILENAME_LEN] = '\0';
+
+ // the tape's level number may differ from current level number
+ tape.level_nr = level_nr;
+ }
+
+ // save score tape to upload to server; may be required for some reasons:
+ // * level set identifier in solution tapes may differ from level set
+ // * solution tape may have native format (like Supaplex solution files)
+
+ SaveScoreTape(level_nr);
+ SaveServerScore(level_nr);
+
+ AutoPlayTapes_WaitForUpload();
+ }
+
+ if (patch_nr == 0)
+ num_levels_played++;
+
+ if (tape.auto_play_level_solved)
+ num_levels_solved++;
+
+ if (level_nr >= 0 && level_nr < MAX_TAPES_PER_SET)
+ level_failed[level_nr] = !tape.auto_play_level_solved;
+ }
+ else
+ {
+ DrawCompleteVideoDisplay();
+
+ audio.sound_enabled = FALSE;
+ setup.engine_snapshot_mode = getStringCopy(STR_SNAPSHOT_MODE_OFF);
+
+ if (strSuffix(global.autoplay_leveldir, ".tape"))
+ {
+ tape_filename = global.autoplay_leveldir;
+
+ LoadTapeFromFilename(tape_filename);
+
+ if (tape.no_valid_file)
+ {
+ if (!fileExists(tape_filename))
+ Fail("tape file '%s' does not exist", tape_filename);
+ else
+ Fail("cannot load tape file '%s'", tape_filename);
+ }
+
+ global.autoplay_leveldir = tape.level_identifier;
+
+ if (tape.level_nr >= 0 && tape.level_nr < MAX_TAPES_PER_SET)
+ global.autoplay_level[tape.level_nr] = TRUE;
+
+ global.autoplay_all = FALSE;
+ }
+
+ autoplay_leveldir = getTreeInfoFromIdentifier(leveldir_first,
+ global.autoplay_leveldir);
+
+ if (autoplay_leveldir == NULL)
+ Fail("no such level identifier: '%s'", global.autoplay_leveldir);