#include "files.h"
#include "network.h"
#include "anim.h"
+#include "api.h"
+
#define DEBUG_TAPE_WHEN_PLAYING FALSE
// tape control functions
// ============================================================================
+void TapeSetDateFromIsoDateString(char *date)
+{
+ int i;
+
+ // check ISO date string for correct length
+ if (strlen(date) != 10)
+ return;
+
+ // check ISO date string for correct format
+ for (i = 0; i < strlen(date); i++)
+ if (((i != 4 && i != 7) && (date[i] < '0' || date[i] > '9')) ||
+ ((i == 4 || i == 7) && (date[i] != '-')))
+ return;
+
+ int yy = (date[2] - '0') * 10 + (date[3] - '0');
+ int mm = (date[5] - '0') * 10 + (date[6] - '0');
+ int dd = (date[8] - '0') * 10 + (date[9] - '0');
+
+ if (mm < 1 || mm > 12 || dd < 1 || dd > 31)
+ return;
+
+ tape.date = 10000 * yy + 100 * (mm - 1) + dd;
+}
+
void TapeSetDateFromEpochSeconds(time_t epoch_seconds)
{
struct tm *lt = localtime(&epoch_seconds);
tape.level_nr = level_nr;
tape.pos[tape.counter].delay = 0;
tape.changed = TRUE;
+ tape.solved = FALSE;
tape.random_seed = InitRND(level.random_seed);
tape.property_bits = TAPE_PROPERTY_NONE;
+ tape.bd_replay = FALSE;
+
TapeSetDateFromNow();
for (i = 0; i < MAX_PLAYERS; i++)
// start recording
tape.recording = TRUE;
tape.changed = TRUE;
+ tape.solved = FALSE;
// set current delay (for last played move)
tape.pos[tape.counter].delay = tape.delay_played;
void TapeHaltRecording(void)
{
- tape.counter++;
+ // only advance tape counter if any input events have been recorded
+ if (tape.pos[tape.counter].delay > 0)
+ 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++;
}
+ tape.changed = TRUE;
+
return TRUE;
}
if (!tape.recording) // (record action even when tape is paused)
return;
+ if (!checkGameRunning())
+ return;
+
for (i = 0; i < MAX_TAPE_ACTIONS; i++)
action[i] = action_raw[i];
ModifyPauseButtons();
}
+
+ // stop tape when leaving auto-pause after completely replaying tape
+ if (tape.playing && !tape.pausing && tape.counter >= tape.length)
+ TapeStop();
}
void TapeStartPlaying(void)
MapTapeEjectButton();
}
-byte *TapePlayAction(void)
+byte *TapePlayActionExt(boolean bd_replay)
{
int update_delay = FRAMES_PER_SECOND / 2;
boolean update_video_display = (FrameCounter % update_delay == 0);
if (!tape.playing || tape.pausing)
return NULL;
+ if (!checkGameRunning())
+ return NULL;
+
+ if (tape.bd_replay && !bd_replay)
+ return NULL;
+
if (tape.pause_before_end) // stop some seconds before end of tape
{
if (TapeTime > (int)tape.length_seconds - TAPE_PAUSE_SECONDS_BEFORE_DEATH)
TapeStopWarpForward();
TapeTogglePause(TAPE_TOGGLE_MANUAL);
+ if (setup.autorecord_after_replay)
+ TapeAppendRecording();
+
return NULL;
}
}
if (tape.counter >= tape.length) // end of tape reached
{
- if (tape.warp_forward && !tape.auto_play)
+ if (!tape.auto_play)
{
TapeStopWarpForward();
TapeTogglePause(TAPE_TOGGLE_MANUAL);
+
+ if (setup.autorecord_after_replay)
+ TapeAppendRecording();
}
else
{
}
tape.delay_played++;
- if (tape.delay_played >= tape.pos[tape.counter].delay)
+ if (tape.delay_played >= tape.pos[tape.counter].delay || tape.bd_replay)
{
tape.counter++;
tape.delay_played = 0;
return action;
}
+byte *TapePlayAction_BD(void)
+{
+ return TapePlayActionExt(TRUE);
+}
+
+byte *TapePlayAction(void)
+{
+ return TapePlayActionExt(FALSE);
+}
+
+byte *TapeCorrectAction_BD(byte *action)
+{
+ if (tape.playing)
+ {
+ // only read next tape action if not playing native BD replay
+ if (!TapeIsPlaying_ReplayBD())
+ action = TapePlayAction();
+ }
+ else if (tape.recording)
+ {
+ byte tape_action[MAX_TAPE_ACTIONS] = { action[0] };
+
+ TapeRecordAction(tape_action);
+ }
+
+ return action;
+}
+
void TapeStop(void)
{
if (tape.pausing)
}
}
+static void TapeStopGameOrTape(boolean stop_game)
+{
+ if (score_info_tape_play || (!tape.playing && stop_game))
+ RequestQuitGame(FALSE);
+ else
+ TapeStop();
+}
+
+void TapeStopGame(void)
+{
+ if (game_status == GAME_MODE_MAIN)
+ return;
+
+ TapeStopGameOrTape(TRUE);
+}
+
+void TapeStopTape(void)
+{
+ TapeStopGameOrTape(FALSE);
+}
+
unsigned int GetTapeLengthFrames(void)
{
unsigned int tape_length_frames = 0;
void TapeQuickSave(void)
{
- if (game_status == GAME_MODE_MAIN)
+ if (game_status != GAME_MODE_PLAYING)
{
- Request("No game that can be saved!", REQ_CONFIRM);
+ Request("No game that could be saved!", REQ_CONFIRM);
return;
}
- if (game_status != GAME_MODE_PLAYING)
+ if (!tape.recording)
+ {
+ Request("No recording that could be saved!", REQ_CONFIRM);
+
return;
+ }
- if (tape.recording)
- TapeHaltRecording(); // prepare tape for saving on-the-fly
+ TapeHaltRecording(); // prepare tape for saving on-the-fly
if (TAPE_IS_EMPTY(tape))
{
- Request("No tape that can be saved!", REQ_CONFIRM);
+ Request("No tape that could be saved!", REQ_CONFIRM);
return;
}
}
}
+static boolean checkRestartGame(char *message)
+{
+ if (game_status == GAME_MODE_MAIN)
+ return TRUE;
+
+ if (!hasStartedNetworkGame())
+ return FALSE;
+
+ if (level_editor_test_game)
+ return TRUE;
+
+ if (game.all_players_gone)
+ return TRUE;
+
+ if (!setup.ask_on_quit_game)
+ return TRUE;
+
+ if (Request(message, REQ_ASK | REQ_STAY_CLOSED))
+ return TRUE;
+
+ OpenDoor(DOOR_OPEN_1 | DOOR_COPY_BACK);
+
+ return FALSE;
+}
+
+void TapeRestartGame(void)
+{
+ if (score_info_tape_play)
+ {
+ TapeStartGamePlaying();
+
+ return;
+ }
+
+ if (!checkRestartGame("Restart game?"))
+ return;
+
+ // when using BD game engine, cover screen before fading out
+ if (level.game_engine_type == GAME_ENGINE_TYPE_BD)
+ game_bd.cover_screen = TRUE;
+
+ StartGameActions(network.enabled, setup.autorecord, level.random_seed);
+}
+
+void TapeReplayAndPauseBeforeEnd(void)
+{
+ if (score_info_tape_play)
+ return;
+
+ if (TAPE_IS_EMPTY(tape) && !tape.recording)
+ {
+ Request("No tape for this level!", REQ_CONFIRM);
+
+ return;
+ }
+
+ if (!checkRestartGame("Replay game and pause before end?"))
+ return;
+
+ TapeStop();
+ TapeStartGamePlaying();
+ TapeStartWarpForward(AUTOPLAY_MODE_WARP_NO_DISPLAY);
+
+ tape.pause_before_end = TRUE;
+ tape.quick_resume = TRUE;
+}
+
+boolean TapeIsPlaying_ReplayBD(void)
+{
+ return (tape.playing && tape.bd_replay);
+}
+
boolean hasSolutionTape(void)
{
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);
+ boolean level_has_tape = ((level.game_engine_type == GAME_ENGINE_TYPE_BD &&
+ level.native_bd_level->replay != NULL) ||
+ (level.game_engine_type == GAME_ENGINE_TYPE_SP &&
+ level.native_sp_level->demo.is_available));
return (tape_file_exists || level_has_tape);
}
return TRUE;
}
+static boolean PlayScoreTape_WaitForDownload(void)
+{
+ DelayCounter download_delay = { 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))
+ 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 =
+ (entry->id == -1 ?
+ getScoreTapeFilename(entry->tape_basename, level_nr) :
+ getScoreCacheTapeFilename(entry->tape_basename, level_nr));
+ boolean download_tape = (!fileExists(tape_filename));
+
+ if (download_tape && entry->id == -1)
+ {
+ FadeSkipNextFadeIn();
+
+ Request("Cannot find score tape!", REQ_CONFIRM);
+
+ 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();
+
+ if (entry->id == -1)
+ LoadScoreTape(entry->tape_basename, level_nr);
+ else
+ LoadScoreCacheTape(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) &&
static boolean AutoPlayTapes_WaitForUpload(void)
{
- unsigned int upload_delay = 0;
- unsigned int upload_delay_value = 10000;
+ DelayCounter upload_delay = { 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))
+ if (DelayReached(&upload_delay))
{
PrintNoLog("\r");
Print("- uploading score tape to score server - TIMEOUT.\n");
break;
case TAPE_CTRL_ID_STOP:
- TapeStop();
+ TapeStopTape();
break;