NXEngine v1.0.0.5
[NXEngine.git] / replay.cpp
blob6aa238a63e28c49c2dc7fc96cd924f0e51b158a6
2 #include <time.h>
3 #include "nx.h"
4 #include "replay.h"
5 #include "profile.h"
6 #include "replay.fdh"
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)
22 FILE *fp;
23 Profile profile;
25 end_record();
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+");
36 if (!fp)
38 staterr("begin_record: failed to open file %s", fname);
39 return 1;
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);
54 rec.fp = fp;
55 seedrand(rec.hdr.randseed);
57 fputl('MARK', fp);
58 rec.fb.SetFile(fp);
59 rec.fb.SetBufferSize(256);
60 rec.fb.Dump();
61 return 0;
64 bool Replay::end_record()
66 if (!IsRecording())
67 return 1;
69 // flush final RLE run
70 write_record(rec.lastkeys, rec.runlength, &rec.fb);
71 rec.runlength = 0;
72 rec.fb.Flush();
73 rec.fb.SetFile(NULL);
75 fputc('!', rec.fp);
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);
81 fclose(rec.fp);
83 stat("end_record(): wrote %d frames", rec.hdr.total_frames);
84 memset(&rec, 0, sizeof(rec));
85 return 0;
89 void c------------------------------() {}
92 // load the save-game contained with the given replay and begin playback.
93 bool Replay::begin_playback(const char *fname)
95 FILE *fp;
96 Profile profile;
98 end_playback();
99 memset(&play, 0, sizeof(play));
101 stat("begin_playback('%s')", fname);
103 if (profile_load(fname, &profile))
104 return 1;
106 fp = fileopen(fname, "rb");
107 if (!fp)
109 staterr("begin_playback: failed to open file %s", fname);
110 return 1;
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);
119 return 1;
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;
128 game_load(&profile);
129 seedrand(play.hdr.randseed);
131 if (fgetl(fp) != 'MARK')
133 console.Print("Replay fail MARK");
134 return 1;
137 // debug stuff for replaying at startup from main.cpp
138 play.ffwdto = next_ffwdto;
139 next_ffwdto = 0;
141 play.stopat = next_stopat;
142 next_stopat = 0;
144 play.ffwd_accel = next_accel;
145 next_accel = 0;
147 play.fp = fp;
148 // dump_replay();
149 return 0;
152 bool Replay::end_playback()
154 if (!IsPlaying()) return 1;
156 fclose(play.fp);
157 play.fp = NULL;
159 memset(inputs, 0, sizeof(inputs));
160 play.termtimer = 110;
162 return 0;
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.
172 void Replay::run()
174 //debug("%d %d", IsPlaying(), IsRecording());
176 if (IsPlaying())
178 settings = &replay_settings;
179 run_playback();
181 else
183 settings = &normal_settings;
186 if (IsRecording())
187 run_record();
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);
202 rec.runlength = 0;
205 rec.lastkeys = keys;
208 rec.runlength++;
211 static void Replay::run_playback()
213 play.elapsed_frames++;
215 if (play.stopat && play.elapsed_frames >= play.stopat)
217 end_playback();
218 return;
221 if (play.ffwdto && play.elapsed_frames < play.ffwdto)
223 game.ffwdtime = 2;
224 if (play.ffwd_accel)
225 flipacceltime = 2; // global variable from main; disables screen->Flip()
228 // RLE decoding
229 if (play.runlength == 0)
231 if (read_record(&play.keys, &play.runlength, play.fp))
233 end_playback();
234 play.keys = 0;
235 return;
238 play.elapsed_records++;
241 play.runlength--;
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++)
264 int key = list[i];
265 inputs[key] = keys[key];
268 return;
272 void c------------------------------() {}
275 static void write_record(uint32_t keys, uint32_t runlength, FileBuffer *fb)
277 fb->Write8('[');
278 fb->Write32(keys);
279 fb->Write8(':');
280 fb->Write32(runlength);
281 fb->Write8(']');
284 static bool read_record(uint32_t *keys, uint32_t *runlength, FILE *fp)
286 char ch = fgetc(fp);
287 if (ch == '!') return REC_END;
289 if (feof(fp))
291 console.Print("unexpected end of file");
292 return REC_ERR;
295 if (ch != '[')
297 console.Print("replay field fail [");
298 return REC_ERR;
301 *keys = fgetl(fp);
303 if (fgetc(fp) != ':')
305 console.Print("replay field fail :");
306 return REC_ERR;
309 *runlength = fgetl(fp);
311 if (fgetc(fp) != ']')
313 console.Print("replay field fail ]");
314 return REC_ERR;
317 return REC_OK;
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;
329 char buf[40];
330 int tapepos;
332 if (!IsPlaying() || game.paused)
334 if (play.termtimer > 0)
336 play.termtimer--;
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);
343 return;
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;
352 // > PLAY : 00000
353 // [>>>>>>>>>>]
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()");
378 if (!IsPlaying())
379 begin_record_next();
383 bool Replay::begin_record_next()
385 int slot = GetAvailableSlot();
386 if (slot == -1)
388 stat("begin_record_next: all slots locked; not recording a replay");
389 return 1;
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];
401 int i, numUnlocked;
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)
411 return i;
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.
416 numUnlocked = 0;
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.
424 // we fail. return.
425 if (numUnlocked == 0)
426 return -1;
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)
440 nextslot--;
442 const char *newfilename = GetReplayName(nextslot);
443 rename(unlocked[i]->filename, newfilename);
444 strcpy(unlocked[i]->filename, newfilename);
446 nextslot--;
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)))
453 return i;
456 staterr("GetAvailableSlot: deleted one but still none available???");
457 return -1;
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;
476 else
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);
487 return buffer;
490 bool Replay::LoadHeader(const char *fname, ReplayHeader *hdr)
492 FILE *fp;
494 fp = fileopen(fname, "rb");
495 if (!fp)
497 staterr("LoadHeader: can't open file '%s'", fname);
498 return 1;
501 fseek(fp, PROFILE_LENGTH, SEEK_SET); // seek to end of profile data
502 fread(hdr, sizeof(ReplayHeader), 1, fp);
504 fclose(fp);
505 return 0;
508 bool Replay::SaveHeader(const char *fname, ReplayHeader *hdr)
510 FILE *fp;
512 fp = fileopen(fname, "r+");
513 if (!fp)
515 staterr("SaveHeader: can't open file '%s'", fname);
516 return 1;
519 fseek(fp, PROFILE_LENGTH, SEEK_SET); // seek to end of profile data
520 fwrite(hdr, sizeof(ReplayHeader), 1, fp);
522 fclose(fp);
523 return 0;
527 void c------------------------------() {}
530 // converts a framecount value into a textual total time.
531 void Replay::FramesToTime(int framecount, char *buffer)
533 int mins, secs;
535 secs = (framecount / GAME_FPS);
536 mins = (secs / 60);
537 secs = (secs % 60);
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)
548 return max;
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);
568 void Replay::close()
570 end_record();
571 end_playback();
575 void c------------------------------() {}
578 void Replay::set_ffwd(int frame, bool accel)
580 if (IsPlaying())
582 play.ffwdto = frame;
583 play.ffwd_accel = accel;
585 else
587 next_ffwdto = frame;
588 next_accel = accel;
592 void Replay::set_stopat(int frame)
594 if (IsPlaying())
595 play.stopat = frame;
596 else
597 next_stopat = 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)
607 uint32_t result = 0;
608 uint32_t mask = 1;
610 for(int i=0;i<nvalues;i++)
612 if (values[i]) result |= mask;
613 mask <<= 1;
616 return result;
619 // pulls apart a uint32 created by EncodeBits back into a bool array.
620 void Replay::DecodeBits(uint32_t value, bool *array, int len)
622 uint32_t mask = 1;
623 int i;
625 for(i=0;i<len;i++)
627 array[i] = (value & mask) ? 1 : 0;
628 mask <<= 1;
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 ===");
646 stat("");
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);
651 /*int resolution;
652 int last_save_slot;
653 bool multisave;
654 bool files_extracted;
655 bool show_fps;
656 bool displayformat;
658 bool enable_debug_keys;
659 bool sound_enabled;
660 int music_enabled;
662 bool instant_quit;
663 bool emulate_bugs;
664 bool no_quake_in_hell;
665 bool inhibit_fullscreen;
667 bool skip_intro;*/
669 //exit(1);
671 /*stat("Inputs:");
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));
680 //exit(1);
682 //exit(1);
683 //int offset = 1;
684 //fseek(play.fp, ftell(play.fp)+offset, SEEK_SET);
686 int total_frames = 0;
687 int record = 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);
707 break;
710 lastkeys = keys;
713 //total_frames--;
714 stat("total frames count: %d", total_frames);
715 stat("frames reported: %d", play.hdr.total_frames);
716 exit(1);