From 064aead73ca78ba3121d8d51d386c44a96ff763f Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Tue, 17 Feb 2015 15:37:49 -0700 Subject: [PATCH] Added autosave to edit mode. --- Themes/_fallback/Languages/en.ini | 1 + src/NotesLoaderSM.cpp | 11 +- src/ScreenEdit.cpp | 179 ++++++++++++++++++------------ src/ScreenEdit.h | 9 +- src/Song.cpp | 32 ++++-- src/Song.h | 7 +- src/Steps.cpp | 3 +- 7 files changed, 150 insertions(+), 92 deletions(-) diff --git a/Themes/_fallback/Languages/en.ini b/Themes/_fallback/Languages/en.ini index dbba7be85d..271ac35ee7 100644 --- a/Themes/_fallback/Languages/en.ini +++ b/Themes/_fallback/Languages/en.ini @@ -1486,6 +1486,7 @@ Zoom In Camera=Zoom In Camera Adding New Attack=Enter the new modifier for this attack.\nThe length can be adjusted later.\nLeave it blank to cancel. Adding New Mod=Please enter the new modifier for this attack.\n\nLeave it blank to cancel. Are you sure you want to clear %d notes?=Are you sure you want to clear %d notes? +Autosave successful.=Autosave successful. Edit Existing Mod=Please adjust the present modifier below.\n\nLeave it blank to erase the mod. Edit Attack Start=Enter the time when this attack starts. Edit Attack Length=Enter how long it takes for this attack to finish. diff --git a/src/NotesLoaderSM.cpp b/src/NotesLoaderSM.cpp index 11d91d5226..18cdd52310 100644 --- a/src/NotesLoaderSM.cpp +++ b/src/NotesLoaderSM.cpp @@ -27,16 +27,6 @@ bool SMLoader::LoadFromDir( const RString &sPath, Song &out ) { vector aFileNames; GetApplicableFiles( sPath, aFileNames ); - - if( aFileNames.size() > 1 ) - { - // Need to break this up first. - RString tmp = "Song " + sPath + " has more than one"; - LOG->UserLog(tmp, this->GetFileExtension(), "file. There can only be one!"); - return false; - } - - ASSERT( aFileNames.size() == 1 ); return LoadFromSimfile( sPath + aFileNames[0], out ); } @@ -1229,6 +1219,7 @@ bool SMLoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath void SMLoader::GetApplicableFiles( const RString &sPath, vector &out ) { + GetDirListing( sPath + RString("*.ats" ), out ); GetDirListing( sPath + RString("*" + this->GetFileExtension() ), out ); } diff --git a/src/ScreenEdit.cpp b/src/ScreenEdit.cpp index d8e7106aaa..19fe294c15 100644 --- a/src/ScreenEdit.cpp +++ b/src/ScreenEdit.cpp @@ -45,6 +45,7 @@ static Preference g_bEditorShowBGChangesPlay( "EditorShowBGChangesPlay", t /** @brief How long must the button be held to generate a hold in record mode? */ const float record_hold_default= 0.3f; float record_hold_seconds = record_hold_default; +const float time_between_autosave= 300.0f; // 5 minutes. -Kyz #define PLAYER_X (SCREEN_CENTER_X) #define PLAYER_Y (SCREEN_CENTER_Y) @@ -110,6 +111,7 @@ AutoScreenMessage( SM_BackFromStepMusicChange ); AutoScreenMessage( SM_DoEraseStepTiming ); AutoScreenMessage( SM_DoSaveAndExit ); AutoScreenMessage( SM_DoExit ); +AutoScreenMessage( SM_AutoSaveSuccessful ); AutoScreenMessage( SM_SaveSuccessful ); AutoScreenMessage( SM_SaveFailed ); @@ -1390,7 +1392,11 @@ void ScreenEdit::Init() m_bHasUndo = false; m_Undo.SetNumTracks( m_NoteDataEdit.GetNumTracks() ); - m_bDirty = m_NoteDataEdit.IsEmpty(); // require the usage of saving if empty. + SetDirty(m_NoteDataEdit.IsEmpty()); // require saving if empty. + if(GAMESTATE->m_pCurSong->WasLoadedFromAutosave()) + { + SetDirty(true); + } m_Player->Init( "Player", GAMESTATE->m_pPlayerState[PLAYER_1], NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL ); m_Player->CacheAllUsedNoteSkins(); @@ -1526,6 +1532,16 @@ void ScreenEdit::Update( float fDeltaTime ) GAMESTATE->UpdateSongPosition( fSeconds, GAMESTATE->m_pCurSong->m_SongTiming, tm ); } + if(m_EditState == STATE_EDITING) + { + if(IsDirty() && m_next_autosave_time > -1.0f && + RageTimer::GetTimeSinceStartFast() > m_next_autosave_time) + { + PerformSave(true); + } + } + + if( m_EditState == STATE_RECORDING ) { // TODO: Find a way to prevent STATE_RECORDING when in Song Timing. @@ -1611,7 +1627,6 @@ void ScreenEdit::Update( float fDeltaTime ) } } - //LOG->Trace( "ScreenEdit::Update(%f)", fDeltaTime ); ScreenWithMenuElements::Update( fDeltaTime ); @@ -3467,6 +3482,7 @@ void ScreenEdit::HandleMessage( const Message &msg ) } static LocalizedString SAVE_SUCCESSFUL ( "ScreenEdit", "Save successful." ); +static LocalizedString AUTOSAVE_SUCCESSFUL ( "ScreenEdit", "Autosave successful." ); static LocalizedString ADD_NEW_MOD ("ScreenEdit", "Adding New Mod"); static LocalizedString ADD_NEW_ATTACK ("ScreenEdit", "Adding New Attack"); @@ -4189,6 +4205,12 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) else SCREENMAN->SystemMessage( SAVE_SUCCESSFUL ); } + else if( SM == SM_AutoSaveSuccessful ) + { + LOG->Trace( "AutoSave successful." ); + m_next_autosave_time= RageTimer::GetTimeSinceStartFast() + time_between_autosave; + SCREENMAN->SystemMessage(AUTOSAVE_SUCCESSFUL); + } else if( SM == SM_SaveFailed ) // save failed; stay in the editor { /* We committed the steps to SongManager. Revert to the last save, and @@ -4258,6 +4280,90 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) ScreenWithMenuElements::HandleScreenMessage( SM ); } +void ScreenEdit::SetDirty(bool dirty) +{ + if(dirty) + { + if(!m_dirty) + { + m_next_autosave_time= RageTimer::GetTimeSinceStartFast() + time_between_autosave; + } + } + else + { + m_next_autosave_time= -1.0f; + } + m_dirty= dirty; +} + +void ScreenEdit::PerformSave(bool autosave) +{ + // copy edit into current Steps + m_pSteps->SetNoteData( m_NoteDataEdit ); + + // don't forget the attacks. + m_pSong->m_Attacks = GAMESTATE->m_pCurSong->m_Attacks; + m_pSong->m_sAttackString = GAMESTATE->m_pCurSong->m_Attacks.ToVectorString(); + m_pSteps->m_Attacks = GAMESTATE->m_pCurSteps[PLAYER_1]->m_Attacks; + m_pSteps->m_sAttackString = GAMESTATE->m_pCurSteps[PLAYER_1]->m_Attacks.ToVectorString(); + + const ScreenMessage save_message= autosave ? SM_AutoSaveSuccessful + : SM_SaveSuccessful; + + switch( EDIT_MODE.GetValue() ) + { + DEFAULT_FAIL( EDIT_MODE.GetValue() ); + case EditMode_Home: + { + ASSERT( m_pSteps->IsAnEdit() ); + + RString sError; + m_pSteps->CalculateRadarValues( m_pSong->m_fMusicLengthSeconds ); + if( !NotesWriterSM::WriteEditFileToMachine(m_pSong, m_pSteps, sError) ) + { + ScreenPrompt::Prompt( SM_None, sError ); + break; + } + + m_pSteps->SetSavedToDisk( true ); + + // HACK: clear undo, so "exit" below knows we don't need to save. + // This only works because important non-steps data can't be changed in + // home mode (BPMs, stops). + ClearUndo(); + + SCREENMAN->ZeroNextUpdate(); + + HandleScreenMessage(save_message); + + /* FIXME + RString s; + switch( c ) + { + case save: s = "ScreenMemcardSaveEditsAfterSave"; break; + case save_on_exit: s = "ScreenMemcardSaveEditsAfterExit"; break; + default: FAIL_M(ssprintf("Invalid menu choice: %i", c)); + } + SCREENMAN->AddNewScreenToTop( s ); + */ + } + break; + case EditMode_Full: + { + // This will recalculate radar values. + m_pSong->Save(autosave); + SCREENMAN->ZeroNextUpdate(); + + HandleScreenMessage(save_message); + } + break; + case EditMode_CourseMods: + case EditMode_Practice: + break; + } + m_soundSave.Play(true); +} + void ScreenEdit::OnSnapModeChange() { m_soundChangeSnap.Play(true); @@ -4659,73 +4765,8 @@ void ScreenEdit::HandleMainMenuChoice( MainMenuChoice c, const vector &iAns } case save: case save_on_exit: - { - m_CurrentAction = c; - - // copy edit into current Steps - m_pSteps->SetNoteData( m_NoteDataEdit ); - - // don't forget the attacks. - m_pSong->m_Attacks = GAMESTATE->m_pCurSong->m_Attacks; - m_pSong->m_sAttackString = GAMESTATE->m_pCurSong->m_Attacks.ToVectorString(); - m_pSteps->m_Attacks = GAMESTATE->m_pCurSteps[PLAYER_1]->m_Attacks; - m_pSteps->m_sAttackString = GAMESTATE->m_pCurSteps[PLAYER_1]->m_Attacks.ToVectorString(); - - switch( EDIT_MODE.GetValue() ) - { - DEFAULT_FAIL( EDIT_MODE.GetValue() ); - case EditMode_Home: - { - ASSERT( m_pSteps->IsAnEdit() ); - - RString sError; - - m_pSteps->CalculateRadarValues( m_pSong->m_fMusicLengthSeconds ); - if( !NotesWriterSM::WriteEditFileToMachine(m_pSong, m_pSteps, sError) ) - { - ScreenPrompt::Prompt( SM_None, sError ); - break; - } - - m_pSteps->SetSavedToDisk( true ); - - // HACK: clear undo, so "exit" below knows we don't need to save. - // This only works because important non-steps data can't be changed in - // home mode (BPMs, stops). - ClearUndo(); - - SCREENMAN->ZeroNextUpdate(); - - HandleScreenMessage( SM_SaveSuccessful ); - - /* FIXME - RString s; - switch( c ) - { - case save: s = "ScreenMemcardSaveEditsAfterSave"; break; - case save_on_exit: s = "ScreenMemcardSaveEditsAfterExit"; break; - default: FAIL_M(ssprintf("Invalid menu choice: %i", c)); - } - SCREENMAN->AddNewScreenToTop( s ); - */ - } - break; - case EditMode_Full: - { - // This will recalculate radar values. - m_pSong->Save(); - SCREENMAN->ZeroNextUpdate(); - - HandleScreenMessage( SM_SaveSuccessful ); - } - break; - case EditMode_CourseMods: - case EditMode_Practice: - break; - } - - m_soundSave.Play(true); - } + m_CurrentAction = c; + PerformSave(false); break; case revert_to_last_save: ScreenPrompt::Prompt( SM_DoRevertToLastSave, REVERT_LAST_SAVE.GetValue() + "\n\n" + DESTROY_ALL_UNSAVED_CHANGES.GetValue(), PROMPT_YES_NO, ANSWER_NO ); diff --git a/src/ScreenEdit.h b/src/ScreenEdit.h index dc828d578b..1946adfdca 100644 --- a/src/ScreenEdit.h +++ b/src/ScreenEdit.h @@ -217,8 +217,10 @@ public: virtual void HandleMessage( const Message &msg ); virtual void HandleScreenMessage( const ScreenMessage SM ); - void SetDirty( bool bDirty ) { m_bDirty = bDirty; } - bool IsDirty() const { return m_bDirty; } + void SetDirty(bool dirty); + bool IsDirty() const { return m_dirty; } + + void PerformSave(bool autosave); EditState GetEditState(){ return m_EditState; } @@ -326,7 +328,8 @@ protected: NoteData m_Undo; /** @brief Has the NoteData been changed such that a user should be prompted to save? */ - bool m_bDirty; + bool m_dirty; + float m_next_autosave_time; /** @brief The sound that is played when a note is added. */ RageSound m_soundAddNote; diff --git a/src/Song.cpp b/src/Song.cpp index ee9071d74c..0e73ab464b 100644 --- a/src/Song.cpp +++ b/src/Song.cpp @@ -991,7 +991,7 @@ bool Song::HasStepsTypeAndDifficulty( StepsType st, Difficulty dc ) const return SongUtil::GetOneSteps( this, st, dc ) != NULL; } -void Song::Save() +void Song::Save(bool autosave) { LOG->Trace( "Song::SaveToSongFile()" ); @@ -999,11 +999,16 @@ void Song::Save() TranslateTitles(); // Save the new files. These calls make backups on their own. - if( !SaveToSSCFile(GetSongFilePath(), false) ) + if( !SaveToSSCFile(GetSongFilePath(), false, autosave) ) return; + SaveToCacheFile(); + // Skip saving the sm and .old files if we are autosaving. -Kyz + if(autosave) + { + return; + } SaveToSMFile(); //SaveToDWIFile(); - SaveToCacheFile(); /* We've safely written our files and created backups. Rename non-SM and * non-DWI files to avoid confusion. */ @@ -1058,16 +1063,20 @@ bool Song::SaveToSMFile() } -bool Song::SaveToSSCFile( RString sPath, bool bSavingCache ) +bool Song::SaveToSSCFile( RString sPath, bool bSavingCache, bool autosave ) { RString path = sPath; if (!bSavingCache) path = SetExtension(sPath, "ssc"); - + if(autosave) + { + path = SetExtension(sPath, "ats"); + } + LOG->Trace( "Song::SaveToSSCFile('%s')", path.c_str() ); // If the file exists, make a backup. - if( !bSavingCache && IsAFile(path) ) + if(!bSavingCache && !autosave && IsAFile(path)) FileCopy( path, path + ".old" ); vector vpStepsToSave; @@ -1090,7 +1099,7 @@ bool Song::SaveToSSCFile( RString sPath, bool bSavingCache ) vpStepsToSave.push_back(*s); } - if (bSavingCache) + if(bSavingCache || autosave) { return NotesWriterSSC::Write(path, *this, vpStepsToSave, bSavingCache); } @@ -1098,6 +1107,15 @@ bool Song::SaveToSSCFile( RString sPath, bool bSavingCache ) if( !NotesWriterSSC::Write(path, *this, vpStepsToSave, bSavingCache) ) return false; + // Remove the autosave file so that edit mode won't auto load it. -Kyz + RString autosave_path= sPath; + SetExtension(autosave_path, "ats"); + if(FILEMAN->DoesFileExist(autosave_path)) + { + FILEMAN->Remove(autosave_path); + } + + if( g_BackUpAllSongSaves.Get() ) { RString sExt = GetExtension( path ); diff --git a/src/Song.h b/src/Song.h index 655f2e584f..b372183ceb 100644 --- a/src/Song.h +++ b/src/Song.h @@ -113,9 +113,9 @@ public: * @param sPath the path where we're saving the file. * @param bSavingCache a flag to determine if we're saving cache data. */ - bool SaveToSSCFile( RString sPath, bool bSavingCache ); + bool SaveToSSCFile(RString sPath, bool bSavingCache, bool autosave= false); /** @brief Save to the SSC and SM files no matter what. */ - void Save(); + void Save(bool autosave= false); /** * @brief Save the current Song to a JSON file. * @return its success or failure. */ @@ -133,6 +133,9 @@ public: * @return its success or failure. */ bool SaveToDWIFile(); + bool WasLoadedFromAutosave() const + { return GetExtension(m_sSongFileName) == "ats"; } + const RString &GetSongFilePath() const; RString GetCacheFilePath() const; diff --git a/src/Steps.cpp b/src/Steps.cpp index d8847deb24..7912f4a603 100644 --- a/src/Steps.cpp +++ b/src/Steps.cpp @@ -105,7 +105,8 @@ bool Steps::GetNoteDataFromSimfile() RString extension = GetExtension(stepFile); extension.MakeLower(); // must do this because the code is expecting lowercase - if (extension.empty() || extension == "ssc") // remember cache files. + if (extension.empty() || extension == "ssc" + || extension == "ats") // remember cache files. { SSCLoader loader; if ( ! loader.LoadNoteDataFromSimfile(stepFile, *this) )