TAS GC Recording/Playing with GUI! Yay! (Bonus: Lag Counter)
Works with frameskipping/throttling and so forth. Only one bug: Every subsequent play plays the same, regardless of framerate, frameskipping and throttling. The only problem is that the recording and the plays act differently, with analog sticks ONLY. git-svn-id: https://dolphin-emu.googlecode.com/svn/trunk@4028 8ced0084-cf51-0410-be5f-012b33b47a6e
This commit is contained in:
parent
fb507ce676
commit
1612c6a2b8
|
@ -148,6 +148,8 @@ CSIDevice_GCController::GetData(u32& _Hi, u32& _Low)
|
|||
}
|
||||
#endif
|
||||
|
||||
Frame::SetPolledDevice();
|
||||
|
||||
if(Frame::IsPlayingInput())
|
||||
Frame::PlayController(&PadStatus, ISIDevice::m_iDeviceNumber);
|
||||
else
|
||||
|
|
|
@ -38,28 +38,43 @@ int g_numPads = 0;
|
|||
ControllerState *g_padStates;
|
||||
FILE *g_recordfd = NULL;
|
||||
|
||||
u64 g_frameCounter = 0;
|
||||
u64 g_frameCounter = 0, g_lagCounter = 0;
|
||||
bool g_bPolled = false;
|
||||
|
||||
void FrameUpdate() {
|
||||
|
||||
g_frameCounter++;
|
||||
|
||||
if(!g_bPolled)
|
||||
g_lagCounter++;
|
||||
|
||||
if (g_bFrameStep)
|
||||
Core::SetState(Core::CORE_PAUSE);
|
||||
|
||||
FrameSkipping();
|
||||
if(g_framesToSkip)
|
||||
FrameSkipping();
|
||||
|
||||
if (g_bAutoFire)
|
||||
g_bFirstKey = !g_bFirstKey;
|
||||
|
||||
if(IsRecordingInput()) {
|
||||
|
||||
// Dump all controllers' states for this frame
|
||||
fwrite(g_padStates, sizeof(ControllerState), g_numPads, g_recordfd);
|
||||
|
||||
} else if(IsPlayingInput()) {
|
||||
// TODO
|
||||
// Dump/Read all controllers' states for this frame
|
||||
if(g_bPolled) {
|
||||
if(IsRecordingInput())
|
||||
fwrite(g_padStates, sizeof(ControllerState), g_numPads, g_recordfd);
|
||||
else if(IsPlayingInput()) {
|
||||
fread(g_padStates, sizeof(ControllerState), g_numPads, g_recordfd);
|
||||
|
||||
// End of recording
|
||||
if(feof(g_recordfd)) {
|
||||
fclose(g_recordfd);
|
||||
g_recordfd = NULL;
|
||||
g_numPads = 0;
|
||||
delete[] g_padStates;
|
||||
g_playMode = MODE_NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_bPolled = false;
|
||||
}
|
||||
|
||||
void SetFrameSkipping(unsigned int framesToSkip) {
|
||||
|
@ -75,6 +90,10 @@ int FrameSkippingFactor() {
|
|||
return g_framesToSkip;
|
||||
}
|
||||
|
||||
void SetPolledDevice() {
|
||||
g_bPolled = true;
|
||||
}
|
||||
|
||||
void SetAutoHold(bool bEnabled, u32 keyToHold)
|
||||
{
|
||||
g_bAutoFire = bEnabled;
|
||||
|
@ -161,18 +180,18 @@ bool IsPlayingInput()
|
|||
}
|
||||
|
||||
// TODO: Add BeginRecordingFromSavestate
|
||||
void BeginRecordingInput(const char *filename, int controllers)
|
||||
bool BeginRecordingInput(const char *filename, int controllers)
|
||||
{
|
||||
if(!filename || g_playMode != MODE_NONE || g_recordfd)
|
||||
return;
|
||||
return false;
|
||||
|
||||
if(File::Exists(filename))
|
||||
File::Delete(filename);
|
||||
|
||||
g_recordfd = fopen(filename, "wb+");
|
||||
g_recordfd = fopen(filename, "wb");
|
||||
if(!g_recordfd) {
|
||||
PanicAlert("Error opening file %s for recording", filename);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write initial empty header
|
||||
|
@ -183,8 +202,11 @@ void BeginRecordingInput(const char *filename, int controllers)
|
|||
g_padStates = new ControllerState[controllers];
|
||||
|
||||
g_frameCounter = 0;
|
||||
g_lagCounter = 0;
|
||||
|
||||
g_playMode = MODE_RECORDING;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EndRecordingInput()
|
||||
|
@ -201,9 +223,9 @@ void EndRecordingInput()
|
|||
|
||||
header.bFromSaveState = false; // TODO: add the case where it's true
|
||||
header.frameCount = g_frameCounter;
|
||||
header.lagCount = g_lagCounter;
|
||||
|
||||
// TODO
|
||||
header.lagCount = 0;
|
||||
header.uniqueID = 0;
|
||||
header.numRerecords = 0;
|
||||
header.author;
|
||||
|
@ -249,18 +271,100 @@ void RecordInput(SPADStatus *PadStatus, int controllerID)
|
|||
g_padStates[controllerID].CStickY = PadStatus->substickY;
|
||||
}
|
||||
|
||||
void PlayInput(const char *filename)
|
||||
bool PlayInput(const char *filename)
|
||||
{
|
||||
// TODO: Implement
|
||||
// TODO: Add the play from savestate case
|
||||
if(!filename || g_playMode != MODE_NONE || g_recordfd)
|
||||
return false;
|
||||
|
||||
if(!File::Exists(filename))
|
||||
return false;
|
||||
|
||||
DTMHeader header;
|
||||
|
||||
g_recordfd = fopen(filename, "rb");
|
||||
if(!g_recordfd)
|
||||
return false;
|
||||
|
||||
fread(&header, sizeof(DTMHeader), 1, g_recordfd);
|
||||
|
||||
// Load savestate (and skip to frame data)
|
||||
if(header.bFromSaveState) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/* TODO: Put this verification somewhere we have the gameID of the played game
|
||||
// TODO: Replace with Unique ID
|
||||
if(header.uniqueID != 0) {
|
||||
PanicAlert("Recording Unique ID Verification Failed");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if(strncmp((char *)header.gameID, Core::g_CoreStartupParameter.GetUniqueID().c_str(), 6)) {
|
||||
PanicAlert("The recorded game (%s) is not the same as the selected game (%s)", header.gameID, Core::g_CoreStartupParameter.GetUniqueID().c_str());
|
||||
goto cleanup;
|
||||
}
|
||||
*/
|
||||
|
||||
g_numPads = header.numControllers;
|
||||
g_padStates = new ControllerState[g_numPads];
|
||||
|
||||
g_playMode = MODE_PLAYING;
|
||||
|
||||
return true;
|
||||
|
||||
cleanup:
|
||||
fclose(g_recordfd);
|
||||
g_recordfd = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
void PlayController(SPADStatus *PadStatus, int controllerID)
|
||||
{
|
||||
// TODO: Implement
|
||||
if(!IsPlayingInput() || controllerID >= g_numPads || controllerID < 0)
|
||||
return;
|
||||
|
||||
memset(PadStatus, 0, sizeof(SPADStatus));
|
||||
|
||||
PadStatus->button |= PAD_USE_ORIGIN;
|
||||
|
||||
if(g_padStates[controllerID].A) {
|
||||
PadStatus->button |= PAD_BUTTON_A;
|
||||
PadStatus->analogA = 0xFF;
|
||||
}
|
||||
if(g_padStates[controllerID].B) {
|
||||
PadStatus->button |= PAD_BUTTON_B;
|
||||
PadStatus->analogB = 0xFF;
|
||||
}
|
||||
if(g_padStates[controllerID].X)
|
||||
PadStatus->button |= PAD_BUTTON_X;
|
||||
if(g_padStates[controllerID].Y)
|
||||
PadStatus->button |= PAD_BUTTON_Y;
|
||||
if(g_padStates[controllerID].Z)
|
||||
PadStatus->button |= PAD_TRIGGER_Z;
|
||||
if(g_padStates[controllerID].Start)
|
||||
PadStatus->button |= PAD_BUTTON_START;
|
||||
|
||||
if(g_padStates[controllerID].DPadUp)
|
||||
PadStatus->button |= PAD_BUTTON_UP;
|
||||
if(g_padStates[controllerID].DPadDown)
|
||||
PadStatus->button |= PAD_BUTTON_DOWN;
|
||||
if(g_padStates[controllerID].DPadLeft)
|
||||
PadStatus->button |= PAD_BUTTON_LEFT;
|
||||
if(g_padStates[controllerID].DPadRight)
|
||||
PadStatus->button |= PAD_BUTTON_RIGHT;
|
||||
|
||||
PadStatus->triggerLeft = g_padStates[controllerID].L;
|
||||
if(PadStatus->triggerLeft > 230)
|
||||
PadStatus->button |= PAD_TRIGGER_L;
|
||||
PadStatus->triggerRight = g_padStates[controllerID].R;
|
||||
if(PadStatus->triggerRight > 230)
|
||||
PadStatus->button |= PAD_TRIGGER_R;
|
||||
|
||||
PadStatus->stickX = g_padStates[controllerID].AnalogStickX;
|
||||
PadStatus->stickY = g_padStates[controllerID].AnalogStickY;
|
||||
|
||||
PadStatus->substickX = g_padStates[controllerID].CStickX;
|
||||
PadStatus->substickY = g_padStates[controllerID].CStickY;
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -69,6 +69,8 @@ typedef struct {
|
|||
|
||||
void FrameUpdate();
|
||||
|
||||
void SetPolledDevice();
|
||||
|
||||
bool IsAutoFiring();
|
||||
bool IsRecordingInput();
|
||||
bool IsPlayingInput();
|
||||
|
@ -84,11 +86,11 @@ void FrameSkipping();
|
|||
|
||||
void ModifyController(SPADStatus *PadStatus, int controllerID);
|
||||
|
||||
void BeginRecordingInput(const char *filename, int controllers);
|
||||
bool BeginRecordingInput(const char *filename, int controllers);
|
||||
void RecordInput(SPADStatus *PadStatus, int controllerID);
|
||||
void EndRecordingInput();
|
||||
|
||||
void PlayInput(const char *filename);
|
||||
bool PlayInput(const char *filename);
|
||||
void PlayController(SPADStatus *PadStatus, int controllerID);
|
||||
|
||||
};
|
||||
|
|
|
@ -236,6 +236,7 @@ EVT_MENU(IDM_HELPABOUT, CFrame::OnHelp)
|
|||
EVT_MENU(wxID_REFRESH, CFrame::OnRefresh)
|
||||
EVT_MENU(IDM_PLAY, CFrame::OnPlay)
|
||||
EVT_MENU(IDM_RECORD, CFrame::OnRecord)
|
||||
EVT_MENU(IDM_PLAYRECORD, CFrame::OnPlayRecording)
|
||||
EVT_MENU(IDM_STOP, CFrame::OnStop)
|
||||
EVT_MENU(IDM_SCREENSHOT, CFrame::OnScreenshot)
|
||||
EVT_MENU(IDM_CONFIG_MAIN, CFrame::OnConfigMain)
|
||||
|
|
|
@ -159,6 +159,7 @@ class CFrame : public wxFrame
|
|||
|
||||
void OnPlay(wxCommandEvent& event); // Emulation
|
||||
void OnRecord(wxCommandEvent& event);
|
||||
void OnPlayRecording(wxCommandEvent& event);
|
||||
void OnChangeDisc(wxCommandEvent& event);
|
||||
void OnStop(wxCommandEvent& event);
|
||||
void OnScreenshot(wxCommandEvent& event);
|
||||
|
|
|
@ -129,9 +129,13 @@ void CFrame::CreateMenu()
|
|||
// Emulation menu
|
||||
wxMenu* emulationMenu = new wxMenu;
|
||||
emulationMenu->Append(IDM_PLAY, _T("&Play\tF10"));
|
||||
emulationMenu->Append(IDM_RECORD, _T("&Start Recording"));
|
||||
emulationMenu->Append(IDM_CHANGEDISC, _T("Change &Disc"));
|
||||
emulationMenu->Append(IDM_STOP, _T("&Stop"));
|
||||
emulationMenu->AppendSeparator();
|
||||
emulationMenu->Append(IDM_RECORD, _T("Start &Recording..."));
|
||||
emulationMenu->Append(IDM_PLAYRECORD, _T("P&lay Recording..."));
|
||||
emulationMenu->AppendSeparator();
|
||||
emulationMenu->Append(IDM_CHANGEDISC, _T("Change &Disc"));
|
||||
|
||||
|
||||
wxMenu *skippingMenu = new wxMenu;
|
||||
m_pSubMenuFrameSkipping = emulationMenu->AppendSubMenu(skippingMenu, _T("&Frame Skipping"));
|
||||
|
@ -505,8 +509,29 @@ void CFrame::OnRecord(wxCommandEvent& WXUNUSED (event))
|
|||
return;
|
||||
|
||||
// TODO: Take controller settings from Gamecube Configuration menu
|
||||
Frame::BeginRecordingInput(path.mb_str(), 1);
|
||||
BootGame();
|
||||
if(Frame::BeginRecordingInput(path.mb_str(), 1))
|
||||
BootGame();
|
||||
}
|
||||
|
||||
void CFrame::OnPlayRecording(wxCommandEvent& WXUNUSED (event))
|
||||
{
|
||||
wxString path = wxFileSelector(
|
||||
_T("Select The Recording File"),
|
||||
wxEmptyString, wxEmptyString, wxEmptyString,
|
||||
wxString::Format
|
||||
(
|
||||
_T("Dolphin TAS Movies (*.dtm)|*.dtm|All files (%s)|%s"),
|
||||
wxFileSelectorDefaultWildcardStr,
|
||||
wxFileSelectorDefaultWildcardStr
|
||||
),
|
||||
wxFD_OPEN | wxFD_PREVIEW | wxFD_FILE_MUST_EXIST,
|
||||
this);
|
||||
|
||||
if(path.IsEmpty())
|
||||
return;
|
||||
|
||||
if(Frame::PlayInput(path.mb_str()))
|
||||
BootGame();
|
||||
}
|
||||
|
||||
void CFrame::OnPlay(wxCommandEvent& WXUNUSED (event))
|
||||
|
@ -858,7 +883,8 @@ void CFrame::UpdateGUI()
|
|||
|
||||
// Emulation
|
||||
GetMenuBar()->FindItem(IDM_STOP)->Enable(running || paused);
|
||||
GetMenuBar()->FindItem(IDM_RECORD)->Enable(!running && !paused);
|
||||
GetMenuBar()->FindItem(IDM_RECORD)->Enable(!initialized);
|
||||
GetMenuBar()->FindItem(IDM_PLAYRECORD)->Enable(!initialized);
|
||||
GetMenuBar()->FindItem(IDM_SCREENSHOT)->Enable(running || paused);
|
||||
m_pSubMenuLoad->Enable(initialized);
|
||||
m_pSubMenuSave->Enable(initialized);
|
||||
|
|
|
@ -63,6 +63,7 @@ enum
|
|||
IDM_FRAMESKIP9,
|
||||
IDM_PLAY,
|
||||
IDM_RECORD,
|
||||
IDM_PLAYRECORD,
|
||||
IDM_STOP,
|
||||
IDM_SCREENSHOT,
|
||||
IDM_BROWSE,
|
||||
|
|
Loading…
Reference in New Issue