7 using namespace Replay
;
9 #define REPLAY_MAGICK 0xC322
10 static ReplayRecording rec
;
11 static ReplayPlaying play
;
13 static int next_ffwdto
= 0;
14 static int next_stopat
= 0;
15 static bool next_accel
= false;
16 extern int flipacceltime
;
18 // begin recording a replay into the given file,
19 // creating the save-profile section from the current game state.
20 bool Replay::begin_record(const char *fname
)
26 memset(&rec
, 0, sizeof(rec
));
28 stat("begin_record('%s')", fname
);
30 // grab a savefile record of the game state and write it at the start of the file
31 // just like a regular profile.dat.
32 if (game_save(&profile
)) return 1;
33 if (profile_save(fname
, &profile
)) return 1;
35 fp
= fileopen(fname
, "r+");
38 staterr("begin_record: failed to open file %s", fname
);
42 fseek(fp
, PROFILE_LENGTH
, SEEK_SET
); // seek to end of profile data
44 rec
.hdr
.magick
= REPLAY_MAGICK
;
45 rec
.hdr
.randseed
= getrand();
46 rec
.hdr
.locked
= false;
47 rec
.hdr
.total_frames
= 0;
48 rec
.hdr
.createstamp
= (uint64_t)time(NULL
);
49 rec
.hdr
.stageno
= game
.curmap
;
50 memcpy(&rec
.hdr
.settings
, &normal_settings
, sizeof(Settings
));
52 fwrite(&rec
.hdr
, sizeof(ReplayHeader
), 1, fp
);
55 seedrand(rec
.hdr
.randseed
);
59 rec
.fb
.SetBufferSize(256);
64 bool Replay::end_record()
69 // flush final RLE run
70 write_record(rec
.lastkeys
, rec
.runlength
, &rec
.fb
);
76 fputl('STOP', rec
.fp
);
78 // go back and save the header again so we have total_frames correct.
79 fseek(rec
.fp
, PROFILE_LENGTH
, SEEK_SET
);
80 fwrite(&rec
.hdr
, sizeof(ReplayHeader
), 1, rec
.fp
);
83 stat("end_record(): wrote %d frames", rec
.hdr
.total_frames
);
84 memset(&rec
, 0, sizeof(rec
));
89 void c------------------------------() {}
92 // load the save-game contained with the given replay and begin playback.
93 bool Replay::begin_playback(const char *fname
)
99 memset(&play
, 0, sizeof(play
));
101 stat("begin_playback('%s')", fname
);
103 if (profile_load(fname
, &profile
))
106 fp
= fileopen(fname
, "rb");
109 staterr("begin_playback: failed to open file %s", fname
);
113 fseek(fp
, PROFILE_LENGTH
, SEEK_SET
); // seek to end of profile data
114 fread(&play
.hdr
, sizeof(ReplayHeader
), 1, fp
);
116 if (play
.hdr
.magick
!= REPLAY_MAGICK
)
118 staterr("begin_playback: magick mismatch on file '%s' (%x shouldbe %x)", fname
, play
.hdr
.magick
, REPLAY_MAGICK
);
122 // undo settings we don't want to apply during the replay
123 memcpy(&replay_settings
, &play
.hdr
.settings
, sizeof(Settings
));
124 replay_settings
.resolution
= normal_settings
.resolution
;
125 replay_settings
.sound_enabled
= normal_settings
.sound_enabled
;
126 replay_settings
.music_enabled
= normal_settings
.music_enabled
;
129 seedrand(play
.hdr
.randseed
);
131 if (fgetl(fp
) != 'MARK')
133 console
.Print("Replay fail MARK");
137 // debug stuff for replaying at startup from main.cpp
138 play
.ffwdto
= next_ffwdto
;
141 play
.stopat
= next_stopat
;
144 play
.ffwd_accel
= next_accel
;
152 bool Replay::end_playback()
154 if (!IsPlaying()) return 1;
159 memset(inputs
, 0, sizeof(inputs
));
160 play
.termtimer
= 110;
166 void c------------------------------() {}
169 // run record and playback.
170 // * inputs states can be recorded to the file.
171 // * inputs can be injected into the array from the file.
174 //debug("%d %d", IsPlaying(), IsRecording());
178 settings
= &replay_settings
;
183 settings
= &normal_settings
;
191 // record incoming inputs states to the record_fp.
192 void Replay::run_record()
194 rec
.hdr
.total_frames
++;
195 uint32_t keys
= EncodeBits(inputs
, INPUT_COUNT
);
197 if (keys
!= rec
.lastkeys
)
199 if (rec
.runlength
!= 0)
201 write_record(rec
.lastkeys
, rec
.runlength
, &rec
.fb
);
211 static void Replay::run_playback()
213 play
.elapsed_frames
++;
215 if (play
.stopat
&& play
.elapsed_frames
>= play
.stopat
)
221 if (play
.ffwdto
&& play
.elapsed_frames
< play
.ffwdto
)
225 flipacceltime
= 2; // global variable from main; disables screen->Flip()
229 if (play
.runlength
== 0)
231 if (read_record(&play
.keys
, &play
.runlength
, play
.fp
))
238 play
.elapsed_records
++;
243 debug("keys: %08x", play
.keys
);
244 debug("runlength: %d", play
.runlength
);
245 debug("frame: %d", play
.elapsed_frames
);
246 debug("record: %d", play
.elapsed_records
);
247 //debug("frames left: %d", (play.hdr.total_frames - play.elapsed_frames));
249 bool keys
[INPUT_COUNT
];
250 DecodeBits(play
.keys
, keys
, INPUT_COUNT
);
252 // which recorded inputs should be applied
253 static const int list
[] =
255 LEFTKEY
, RIGHTKEY
, UPKEY
, DOWNKEY
,
256 JUMPKEY
, FIREKEY
, PREVWPNKEY
, NEXTWPNKEY
,
257 INVENTORYKEY
, MAPSYSTEMKEY
,
258 DEBUG_MOVE_KEY
, DEBUG_GOD_KEY
, DEBUG_FLY_KEY
,
262 for(int i
=0;list
[i
] != -1;i
++)
265 inputs
[key
] = keys
[key
];
272 void c------------------------------() {}
275 static void write_record(uint32_t keys
, uint32_t runlength
, FileBuffer
*fb
)
280 fb
->Write32(runlength
);
284 static bool read_record(uint32_t *keys
, uint32_t *runlength
, FILE *fp
)
287 if (ch
== '!') return REC_END
;
291 console
.Print("unexpected end of file");
297 console
.Print("replay field fail [");
303 if (fgetc(fp
) != ':')
305 console
.Print("replay field fail :");
309 *runlength
= fgetl(fp
);
311 if (fgetc(fp
) != ']')
313 console
.Print("replay field fail ]");
321 void c------------------------------() {}
324 // draw the playback status "tape"
325 void Replay::DrawStatus()
327 static const int TAPE_CHARS
= 10;
328 static const int x
= 4;
332 if (!IsPlaying() || game
.paused
)
334 if (play
.termtimer
> 0)
337 const char *str
= ((play
.termtimer
% 40) >= 20) ? "> PLAYBACK TERMINATED <":"> <";
339 int y
= (SCREEN_HEIGHT
- 3) - (GetFontHeight() * 2);
340 font_draw_shaded(x
, y
, str
, 0, &greenfont
);
346 // ask for one more char in length than we actually have, this makes it
347 // so the tape gets all-the-way filled up near the end (otherwise, the only time
348 // it'd be fully filled is on the very last frame, so we wouldn't ever see it).
349 tapepos
= GetPlaybackPosition(TAPE_CHARS
+ 1);
350 if (tapepos
> TAPE_CHARS
) tapepos
= TAPE_CHARS
;
354 buf
[0] = '['; buf
[TAPE_CHARS
+1] = ']'; buf
[TAPE_CHARS
+2] = 0;
355 memset(&buf
[1], ' ', TAPE_CHARS
);
356 memset(&buf
[1], '>', tapepos
);
358 int y
= (SCREEN_HEIGHT
- 3) - GetFontHeight();
359 font_draw_shaded(x
, y
, buf
, 0, &greenfont
);
361 const char *mode
= ((play
.elapsed_frames
% 40) < 20) ? "PLAY" : " ";
362 if (game
.ffwdtime
) mode
= "FFWD";
363 sprintf(buf
, "> %s : %05d", mode
, play
.elapsed_frames
);
365 y
-= GetFontHeight();
366 font_draw_shaded(x
, y
, buf
, 0, &greenfont
);
370 void c------------------------------() {}
373 // called from main after a game is loaded or a new game is begun.
374 void Replay::OnGameStarting()
376 stat("Replay::OnGameStarting()");
383 bool Replay::begin_record_next()
385 int slot
= GetAvailableSlot();
388 stat("begin_record_next: all slots locked; not recording a replay");
392 stat("begin_record_next: starting record to slot %d", slot
);
393 return begin_record(GetReplayName(slot
));
397 static int Replay::GetAvailableSlot(void)
399 ReplaySlotInfo slotinfo
[MAX_REPLAYS
];
400 ReplaySlotInfo
*unlocked
[MAX_REPLAYS
];
403 // get info for all slots.
404 // try to find a slot that isn't used yet, if we can, take the first one.
405 for(i
=MAX_REPLAYS
-1;i
>=0;i
--)
407 GetSlotInfo(i
, &slotinfo
[i
]);
408 //stat("Read status of slot %d: [ %d ]", i, slotinfo[i].status);
410 if (slotinfo
[i
].status
== RS_UNUSED
)
414 // nope, the easy way out didn't work because all the replay slots
415 // have at least something in them. so, get a list of all unlocked files.
417 for(i
=0;i
<MAX_REPLAYS
;i
++)
419 if (slotinfo
[i
].status
== RS_UNLOCKED
)
420 unlocked
[numUnlocked
++] = &slotinfo
[i
];
423 // if the unlocked list has 0 entries then all slots are both in use and locked.
425 if (numUnlocked
== 0)
428 // delete the file in the highest-numbered slot.
429 remove(unlocked
[--numUnlocked
]->filename
);
430 unlocked
[numUnlocked
]->status
= RS_UNUSED
;
432 // assign new filenames to all the files in the list, working downward
433 // from the highest-numbered slot. The going backwards also ensures there
434 // can't be any conflicts while we're in the middle of this.
435 int nextslot
= (MAX_REPLAYS
- 1);
436 for(i
=numUnlocked
-1;i
>=0;i
--)
438 // skip over locked slots
439 while(slotinfo
[nextslot
].status
== RS_LOCKED
)
442 const char *newfilename
= GetReplayName(nextslot
);
443 rename(unlocked
[i
]->filename
, newfilename
);
444 strcpy(unlocked
[i
]->filename
, newfilename
);
449 // now take the first unused slot. we're sure that there is one this time.
450 for(i
=0;i
<MAX_REPLAYS
;i
++)
452 if (!file_exists(GetReplayName(i
)))
456 staterr("GetAvailableSlot: deleted one but still none available???");
462 void c------------------------------() {}
465 // returns locked/unlocked/available status and filename info for the given replay slot.
466 void Replay::GetSlotInfo(int slotno
, ReplaySlotInfo
*slot
)
468 GetReplayName(slotno
, slot
->filename
);
470 if (LoadHeader(slot
->filename
, &slot
->hdr
) || \
471 slot
->hdr
.magick
!= REPLAY_MAGICK
)
473 slot
->status
= RS_UNUSED
;
474 slot
->filename
[0] = 0;
478 slot
->status
= (slot
->hdr
.locked
) ? RS_LOCKED
: RS_UNLOCKED
;
483 const char *GetReplayName(int slotno
, char *buffer
)
485 if (!buffer
) buffer
= GetStaticStr();
486 sprintf(buffer
, "replay/rep%d.dat", slotno
);
490 bool Replay::LoadHeader(const char *fname
, ReplayHeader
*hdr
)
494 fp
= fileopen(fname
, "rb");
497 staterr("LoadHeader: can't open file '%s'", fname
);
501 fseek(fp
, PROFILE_LENGTH
, SEEK_SET
); // seek to end of profile data
502 fread(hdr
, sizeof(ReplayHeader
), 1, fp
);
508 bool Replay::SaveHeader(const char *fname
, ReplayHeader
*hdr
)
512 fp
= fileopen(fname
, "r+");
515 staterr("SaveHeader: can't open file '%s'", fname
);
519 fseek(fp
, PROFILE_LENGTH
, SEEK_SET
); // seek to end of profile data
520 fwrite(hdr
, sizeof(ReplayHeader
), 1, fp
);
527 void c------------------------------() {}
530 // converts a framecount value into a textual total time.
531 void Replay::FramesToTime(int framecount
, char *buffer
)
535 secs
= (framecount
/ GAME_FPS
);
538 if (mins
> 99) mins
= 99;
540 sprintf(buffer
, "[%02d:%02d]", mins
, secs
);
543 // returns a value between 0 and max which is like a percentage
544 // of where we are on the "tape" (only works during playback).
545 int Replay::GetPlaybackPosition(int max
)
547 if (play
.elapsed_frames
>= play
.hdr
.total_frames
)
550 double ratio
= ((double)max
/ (double)play
.hdr
.total_frames
);
551 return (int)((double)play
.elapsed_frames
* ratio
);
555 void c------------------------------() {}
558 bool Replay::IsRecording()
560 return (rec
.fp
!= NULL
);
563 bool Replay::IsPlaying()
565 return (play
.fp
!= NULL
);
575 void c------------------------------() {}
578 void Replay::set_ffwd(int frame
, bool accel
)
583 play
.ffwd_accel
= accel
;
592 void Replay::set_stopat(int frame
)
601 void c------------------------------() {}
604 // shoves an array of bool values up to 32 entries long into a uint32.
605 static uint32_t Replay::EncodeBits(bool *values
, int nvalues
)
610 for(int i
=0;i
<nvalues
;i
++)
612 if (values
[i
]) result
|= mask
;
619 // pulls apart a uint32 created by EncodeBits back into a bool array.
620 void Replay::DecodeBits(uint32_t value
, bool *array
, int len
)
627 array
[i
] = (value
& mask
) ? 1 : 0;
633 void c------------------------------() {}
636 static void dump_replay()
638 stat("=== Header ===");
639 stat("magick: %04x (%s)", play
.hdr
.magick
, (play
.hdr
.magick
== REPLAY_MAGICK
) ? "correct" : "not correct");
640 stat("randseed: %08x", play
.hdr
.randseed
);
641 stat("locked: %d", play
.hdr
.locked
);
642 stat("total_frames: %d (%d secs)", play
.hdr
.total_frames
, play
.hdr
.total_frames
/ 50);
643 stat("stageno: %d (%s)", play
.hdr
.stageno
, map_get_stage_name(play
.hdr
.stageno
));
644 stat("createstamp: %010llx", play
.hdr
.createstamp
);
645 stat("=== End Header ===");
648 stat("resolution: %d", play
.hdr
.settings
.resolution
);
649 stat("last_save_slot: %d", play
.hdr
.settings
.last_save_slot
);
650 stat("multisave: %d", play
.hdr
.settings
.multisave
);
654 bool files_extracted;
658 bool enable_debug_keys;
664 bool no_quake_in_hell;
665 bool inhibit_fullscreen;
672 for(int i=0;i<INPUT_COUNT;i++)
674 stat(" %08x %08x", play.hdr.settings.input_mappings[i], input_get_mapping(i));
677 fseek(play
.fp
, ftell(play
.fp
) - 0x2b, SEEK_SET
); // 0x6a9
678 stat("First Read %08x", fgetl(play
.fp
));
679 stat("Starting read at offset %04x", ftell(play
.fp
));
684 //fseek(play.fp, ftell(play.fp)+offset, SEEK_SET);
686 int total_frames
= 0;
688 uint32_t lastkeys
= 0xffffffff;
689 while(!feof(play
.fp
))
691 uint32_t keys
= fgetl(play
.fp
);
692 uint32_t runlength
= fgetl(play
.fp
);
693 if (runlength
== 0xffffffff || feof(play
.fp
)) break;
694 total_frames
+= runlength
;
696 stat("%04d len %08x: keys %08x offset %08x", record
++, runlength
, keys
, ftell(play
.fp
)-8);
698 if (keys
== lastkeys
)
700 staterr(" -- impossible key repeat");
701 if (runlength
< 0x200000) break;
704 if (runlength
>= 0x200000)
706 staterr(" -- bogus runlength %08x", runlength
);
714 stat("total frames count: %d", total_frames
);
715 stat("frames reported: %d", play
.hdr
.total_frames
);