// values for tape handling
#define TAPE_PAUSE_SECONDS_BEFORE_DEATH 5
+#define TAPE_MIN_SECONDS_FOR_UNDO_BUFFER 20
// forward declaration for internal use
static void HandleTapeButtons(struct GadgetInfo *);
static void TapeStopWarpForward(void);
static float GetTapeLengthSecondsFloat(void);
+static void CopyTapeToUndoBuffer(void);
static struct GadgetInfo *tape_gadget[NUM_TAPE_BUTTONS];
+static struct TapeInfo tape_undo_buffer;
// ============================================================================
if (redraw_display)
{
RedrawPlayfield();
+
+ UpdateGameDoorValues();
DrawGameDoorValues();
}
}
{
int i;
+ CopyTapeToUndoBuffer();
+
tape.counter = 0;
tape.length = 0;
tape.length_frames = 0;
tape.length_seconds = 0;
+ tape.score_tape_basename[0] = '\0';
+
if (leveldir_current)
- setString(&tape.level_identifier, leveldir_current->identifier);
+ {
+ strncpy(tape.level_identifier, leveldir_current->identifier,
+ MAX_FILENAME_LEN);
+ tape.level_identifier[MAX_FILENAME_LEN] = '\0';
+ }
tape.level_nr = level_nr;
tape.pos[tape.counter].delay = 0;
action[i] = tape.pos[tape.counter].action[i];
#if DEBUG_TAPE_WHEN_PLAYING
- printf("%05d", FrameCounter);
+ DebugContinued("", "%05d", FrameCounter);
for (i = 0; i < MAX_TAPE_ACTIONS; i++)
- printf(" %08x", action[i]);
- printf("\n");
+ DebugContinued("", " %08x", action[i]);
+ DebugContinued("tape:play", "\n");
#endif
tape.set_centered_player = FALSE;
int i;
if (TAPE_IS_EMPTY(tape))
- return(0);
+ return 0;
for (i = 0; i < tape.length; i++)
tape_length_frames += tape.pos[i].delay;
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 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
};
{
VERSION_IDENT(0,0,0,0),
VERSION_IDENT(3,3,1,0),
+ VERSION_IDENT(0,0,0,0),
-1
};
{
VERSION_IDENT(9,9,9,9),
VERSION_IDENT(4,0,1,1),
+ VERSION_IDENT(4,2,2,0),
-1
};
{
TAPE_PROPERTY_NONE,
TAPE_PROPERTY_EM_RANDOM_BUG,
+ TAPE_PROPERTY_NONE,
-1
};
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);
+
+ 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)
- Error(ERR_EXIT, "no such level identifier: '%s'",
- global.autoplay_leveldir);
+ Fail("no such level identifier: '%s'", global.autoplay_leveldir);
leveldir_current = autoplay_leveldir;
continue;
#endif
- if (options.mytapes)
+ if (tape_filename)
+ LoadTapeFromFilename(tape_filename);
+ else if (options.mytapes)
LoadTape(level_nr);
else
LoadSolutionTape(level_nr);
if (global.autoplay_mode == AUTOPLAY_MODE_FIX)
{
+ boolean skip_patch = FALSE;
+
if (tape.engine_version < patch_version_first[patch_nr] ||
tape.engine_version > patch_version_last[patch_nr])
{
(tape.engine_version / 100 ) % 100,
(tape.engine_version ) % 100);
+ skip_patch = TRUE;
+ }
+
+ if (strEqual(patch_name[patch_nr], "screen_34x34") &&
+ tape.num_participating_players == 1)
+ {
+ Print("Tape %03d %s[%02d:%02d]: (%s) - skipped.\n",
+ level_nr, tape_patch_info,
+ tape.length_seconds / 60, tape.length_seconds % 60,
+ "not suitable for single player tapes");
+
+ skip_patch = TRUE;
+ }
+
+ if (skip_patch)
+ {
if (patch_name[patch_nr + 1] != NULL)
{
// continue with next patch
continue;
}
- tape.property_bits |= patch_property_bit[patch_nr];
+ if (strEqual(patch_name[patch_nr], "screen_34x34"))
+ {
+ tape.scr_fieldx = SCR_FIELDX_DEFAULT * 2;
+ tape.scr_fieldy = SCR_FIELDY_DEFAULT * 2;
+ }
+ else
+ {
+ tape.property_bits |= patch_property_bit[patch_nr];
+ }
}
InitCounter();
PrintLine("-", 79);
Print("Summary (for automatic parsing by scripts):\n");
Print("LEVELDIR [%s] '%s', SOLVED %d/%d (%d%%)",
- (num_levels_played == num_levels_solved ? " OK " : "WARN"),
+ (num_levels_played == num_levels_solved &&
+ num_levels_played > 0 ? " OK " : "WARN"),
autoplay_leveldir->identifier, num_levels_solved, num_levels_played,
(num_levels_played ? num_levels_solved * 100 / num_levels_played : 0));
return FALSE;
}
- byte property_bits = tape->property_bits;
+ boolean unpatch_tape = FALSE;
+ boolean use_property_bit = FALSE;
byte property_bitmask = 0;
- boolean set_property_bit = TRUE;
if (strSuffix(mode, ":0") ||
strSuffix(mode, ":off") ||
strSuffix(mode, ":clear"))
- set_property_bit = FALSE;
+ unpatch_tape = TRUE;
if (strEqual(mode, "em_random_bug") || strPrefix(mode, "em_random_bug:"))
{
}
property_bitmask = TAPE_PROPERTY_EM_RANDOM_BUG;
+
+ use_property_bit = TRUE;
+ }
+ else if (strEqual(mode, "screen_34x34") || strPrefix(mode, "screen_34x34:"))
+ {
+ // this bug only affects team mode tapes
+ if (tape->num_participating_players == 1)
+ {
+ Print("Only team mode tapes can be patched against screen size bug!\n");
+
+ return FALSE;
+ }
+
+ // this bug (that always existed before) was fixed in version 4.2.2.1
+ if (tape->engine_version >= VERSION_IDENT(4,2,2,1))
+ {
+ Print("This tape version cannot be patched against screen size bug!\n");
+
+ return FALSE;
+ }
+
+ int factor = (unpatch_tape ? 1 : 2);
+ int scr_fieldx_new = SCR_FIELDX_DEFAULT * factor;
+ int scr_fieldy_new = SCR_FIELDY_DEFAULT * factor;
+
+ if (scr_fieldx_new == tape->scr_fieldx &&
+ scr_fieldy_new == tape->scr_fieldy)
+ {
+ Print("Tape already patched for '%s'!\n", mode);
+
+ return FALSE;
+ }
+
+ tape->scr_fieldx = scr_fieldx_new;
+ tape->scr_fieldy = scr_fieldy_new;
}
else
{
return FALSE;
}
- if (set_property_bit)
- property_bits |= property_bitmask;
- else
- property_bits &= ~property_bitmask;
-
- if (property_bits == tape->property_bits)
+ // patching tapes using property bits may be used for several patch modes
+ if (use_property_bit)
{
- Print("Tape already patched for '%s'!\n", mode);
+ byte property_bits = tape->property_bits;
+ boolean set_property_bit = (unpatch_tape ? FALSE : TRUE);
- return FALSE;
+ if (set_property_bit)
+ property_bits |= property_bitmask;
+ else
+ property_bits &= ~property_bitmask;
+
+ if (property_bits == tape->property_bits)
+ {
+ Print("Tape already patched for '%s'!\n", mode);
+
+ return FALSE;
+ }
+
+ tape->property_bits = property_bits;
}
Print("Patching for '%s' ... ", mode);
- tape->property_bits = property_bits;
-
return TRUE;
}
PrintLine("=", 79);
Print("Supported patch modes:\n");
Print("- \"em_random_bug\" - use 64-bit random value bug for EM engine\n");
+ Print("- \"screen_34x34\" - force visible playfield size of 34 x 34\n");
PrintLine("-", 79);
Print("Supported modifiers:\n");
Print("- add \":0\", \":off\" or \":clear\" to patch mode to un-patch tape file\n");
global.patchtapes_leveldir);
if (patchtapes_leveldir == NULL)
- Error(ERR_EXIT, "no such level identifier: '%s'",
- global.patchtapes_leveldir);
+ Fail("no such level identifier: '%s'", global.patchtapes_leveldir);
leveldir_current = patchtapes_leveldir;
GDI_END);
if (gi == NULL)
- Error(ERR_EXIT, "cannot create gadget");
+ Fail("cannot create gadget");
tape_gadget[id] = gi;
}