+ // draw labels and symbols separately to prevent labels overlapping symbols
+ DrawVideoDisplayLabel(state);
+ DrawVideoDisplaySymbol(state);
+}
+
+void DrawCompleteVideoDisplay()
+{
+ struct GraphicInfo *g_tape = &graphic_info[IMG_BACKGROUND_TAPE];
+
+ /* draw tape background */
+ BlitBitmap(g_tape->bitmap, drawto, g_tape->src_x, g_tape->src_y,
+ gfx.vxsize, gfx.vysize, gfx.vx, gfx.vy);
+
+ /* draw tape buttons (forced) */
+ RedrawOrRemapTapeButtons();
+
+ DrawVideoDisplay(VIDEO_ALL_OFF, 0);
+
+ if (tape.recording)
+ {
+ DrawVideoDisplay(VIDEO_STATE_REC_ON, 0);
+ DrawVideoDisplay(VIDEO_STATE_DATE_ON, tape.date);
+ DrawVideoDisplay(VIDEO_STATE_TIME_ON, tape.length_seconds);
+ DrawVideoDisplay(VIDEO_STATE_FRAME_ON, tape.length_frames);
+
+ if (tape.pausing)
+ DrawVideoDisplay(VIDEO_STATE_PAUSE_ON, 0);
+ }
+ else if (tape.playing)
+ {
+ DrawVideoDisplay(VIDEO_STATE_PLAY_ON, 0);
+ DrawVideoDisplay(VIDEO_STATE_DATE_ON, tape.date);
+ DrawVideoDisplay(VIDEO_STATE_TIME_ON, 0);
+ DrawVideoDisplay(VIDEO_STATE_FRAME_ON, 0);
+
+ if (tape.pausing)
+ DrawVideoDisplay(VIDEO_STATE_PAUSE_ON, 0);
+ }
+ else if (tape.date && tape.length)
+ {
+ DrawVideoDisplay(VIDEO_STATE_DATE_ON, tape.date);
+ DrawVideoDisplay(VIDEO_STATE_TIME_ON, tape.length_seconds);
+ DrawVideoDisplay(VIDEO_STATE_FRAME_ON, tape.length_frames);
+ }
+
+ BlitBitmap(drawto, bitmap_db_door_2, gfx.vx, gfx.vy, gfx.vxsize, gfx.vysize,
+ 0, 0);
+}
+
+void TapeDeactivateDisplayOn()
+{
+ SetDrawDeactivationMask(REDRAW_FIELD);
+ audio.sound_deactivated = TRUE;
+}
+
+void TapeDeactivateDisplayOff(boolean redraw_display)
+{
+ SetDrawDeactivationMask(REDRAW_NONE);
+ audio.sound_deactivated = FALSE;
+
+ if (redraw_display)
+ {
+ RedrawPlayfield();
+ DrawGameDoorValues();
+ }
+}
+
+
+/* ========================================================================= */
+/* 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 */
+/* ========================================================================= */
+
+void TapeSetDateFromEpochSeconds(time_t epoch_seconds)
+{
+ struct tm *lt = localtime(&epoch_seconds);
+
+ tape.date = 10000 * (lt->tm_year % 100) + 100 * lt->tm_mon + lt->tm_mday;
+}
+
+void TapeSetDateFromNow()
+{
+ TapeSetDateFromEpochSeconds(time(NULL));
+}
+
+void TapeErase()
+{
+ int i;
+
+ tape.counter = 0;
+ tape.length = 0;
+ tape.length_frames = 0;
+ tape.length_seconds = 0;
+
+ if (leveldir_current)
+ setString(&tape.level_identifier, leveldir_current->identifier);
+
+ tape.level_nr = level_nr;
+ tape.pos[tape.counter].delay = 0;
+ tape.changed = TRUE;
+
+ tape.random_seed = InitRND(level.random_seed);
+
+ tape.file_version = FILE_VERSION_ACTUAL;
+ tape.game_version = GAME_VERSION_ACTUAL;
+ tape.engine_version = level.game_version;
+
+ TapeSetDateFromNow();
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ tape.player_participates[i] = FALSE;
+
+ 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()
+{
+ tape.counter = 0;
+ tape.delay_played = 0;
+ tape.pause_before_end = FALSE;
+ tape.recording = FALSE;
+ tape.playing = FALSE;
+ tape.fast_forward = FALSE;
+ tape.warp_forward = FALSE;
+ tape.deactivate_display = FALSE;
+ tape.auto_play = (global.autoplay_leveldir != NULL);
+ tape.auto_play_level_solved = FALSE;
+ tape.quick_resume = FALSE;
+ tape.single_step = FALSE;
+
+ tape.centered_player_nr_next = -1;
+ tape.set_centered_player = FALSE;
+
+ InitRND(tape.random_seed);
+}
+
+static void TapeSetRandomSeed(int random_seed)
+{
+ tape.random_seed = InitRND(random_seed);
+}
+
+void TapeStartRecording(int random_seed)
+{
+ if (!TAPE_IS_STOPPED(tape))
+ TapeStop();
+
+ TapeErase();
+ TapeRewind();
+ TapeSetRandomSeed(random_seed);
+
+ tape.recording = TRUE;
+
+ DrawVideoDisplay(VIDEO_STATE_REC_ON, 0);
+ DrawVideoDisplay(VIDEO_STATE_DATE_ON, tape.date);
+ DrawVideoDisplay(VIDEO_STATE_TIME_ON, 0);
+ DrawVideoDisplay(VIDEO_STATE_FRAME_ON, 0);
+
+ MapTapeWarpButton();
+
+ SetDrawDeactivationMask(REDRAW_NONE);
+ audio.sound_deactivated = FALSE;
+}
+
+static void TapeStartGameRecording()
+{
+ StartGameActions(options.network, TRUE, level.random_seed);
+}
+
+static void TapeAppendRecording()
+{
+ if (!tape.playing || !tape.pausing)
+ return;
+
+ // stop playing
+ tape.playing = FALSE;
+ tape.fast_forward = FALSE;
+ tape.warp_forward = FALSE;
+ tape.pause_before_end = FALSE;
+ tape.deactivate_display = FALSE;
+
+ // start recording
+ tape.recording = TRUE;
+ tape.changed = TRUE;
+
+ // set current delay (for last played move)
+ tape.pos[tape.counter].delay = tape.delay_played;
+
+ // set current date
+ TapeSetDateFromNow();
+
+ DrawVideoDisplay(VIDEO_STATE_DATE_ON, tape.date);
+ DrawVideoDisplay(VIDEO_STATE_PLAY_OFF | VIDEO_STATE_REC_ON, 0);
+
+ UpdateAndDisplayGameControlValues();
+}
+
+void TapeHaltRecording()
+{
+ tape.counter++;
+
+ // 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()
+{
+ if (tape.recording)
+ TapeHaltRecording();
+
+ tape.recording = FALSE;
+ tape.pausing = FALSE;
+
+ DrawVideoDisplay(VIDEO_STATE_REC_OFF, 0);
+ MapTapeEjectButton();
+}
+
+boolean TapeAddAction(byte action[MAX_PLAYERS])
+{
+ int i;
+
+ if (tape.pos[tape.counter].delay > 0) /* already stored action */
+ {
+ boolean changed_events = FALSE;
+
+ for (i = 0; i < MAX_PLAYERS; i++)
+ if (tape.pos[tape.counter].action[i] != action[i])
+ changed_events = TRUE;
+
+ 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;