From a1fe9c8d7f771ded2d903d7341d250b0ed67c037 Mon Sep 17 00:00:00 2001 From: krzys-h Date: Tue, 11 Nov 2014 14:50:44 +0100 Subject: [PATCH] Implemented autosave (#292) --- src/common/event.h | 3 + src/common/resources/resourcemanager.cpp | 119 +++++++++++++++----- src/common/resources/resourcemanager.h | 5 + src/common/restext.cpp | 3 + src/object/robotmain.cpp | 132 ++++++++++++++++++++++- src/object/robotmain.h | 15 +++ src/ui/maindialog.cpp | 110 ++++++++++++++++++- src/ui/maindialog.h | 3 + src/ui/slider.cpp | 15 +-- 9 files changed, 366 insertions(+), 39 deletions(-) diff --git a/src/common/event.h b/src/common/event.h index bb9e123c..ed5d60e6 100644 --- a/src/common/event.h +++ b/src/common/event.h @@ -241,6 +241,9 @@ enum EventType EVENT_INTERFACE_EDITVALUE= 477, EVENT_INTERFACE_SOLUCE4 = 478, EVENT_INTERFACE_BLOOD = 479, + EVENT_INTERFACE_AUTOSAVE_ENABLE = 780, + EVENT_INTERFACE_AUTOSAVE_INTERVAL = 781, + EVENT_INTERFACE_AUTOSAVE_SLOTS = 782, EVENT_INTERFACE_KINFO1 = 500, EVENT_INTERFACE_KINFO2 = 501, diff --git a/src/common/resources/resourcemanager.cpp b/src/common/resources/resourcemanager.cpp index 73298710..28eebfa2 100644 --- a/src/common/resources/resourcemanager.cpp +++ b/src/common/resources/resourcemanager.cpp @@ -154,47 +154,66 @@ CSNDFile* CResourceManager::GetSNDFileHandler(const std::string &filename) bool CResourceManager::Exists(const std::string &filename) { - return PHYSFS_exists(CleanPath(filename).c_str()); + if(PHYSFS_isInit()) + { + return PHYSFS_exists(CleanPath(filename).c_str()); + } + return false; } bool CResourceManager::DirectoryExists(const std::string& directory) { - return PHYSFS_exists(CleanPath(directory).c_str()) && PHYSFS_isDirectory(CleanPath(directory).c_str()); + if(PHYSFS_isInit()) + { + return PHYSFS_exists(CleanPath(directory).c_str()) && PHYSFS_isDirectory(CleanPath(directory).c_str()); + } + return false; } bool CResourceManager::CreateDirectory(const std::string& directory) { - return PHYSFS_mkdir(CleanPath(directory).c_str()); + if(PHYSFS_isInit()) + { + return PHYSFS_mkdir(CleanPath(directory).c_str()); + } + return false; } //TODO: Don't use boost::filesystem here bool CResourceManager::RemoveDirectory(const std::string& directory) { - bool success = true; - std::string writeDir = PHYSFS_getWriteDir(); - try + if(PHYSFS_isInit()) { - fs::remove_all(writeDir + "/" + CleanPath(directory)); + bool success = true; + std::string writeDir = PHYSFS_getWriteDir(); + try + { + fs::remove_all(writeDir + "/" + CleanPath(directory)); + } + catch (std::exception & e) + { + success = false; + } + return success; } - catch (std::exception & e) - { - success = false; - } - return success; + return false; } std::vector CResourceManager::ListFiles(const std::string &directory) { std::vector result; - - char **files = PHYSFS_enumerateFiles(CleanPath(directory).c_str()); - - for (char **i = files; *i != nullptr; i++) + + if(PHYSFS_isInit()) { - result.push_back(*i); - } + char **files = PHYSFS_enumerateFiles(CleanPath(directory).c_str()); - PHYSFS_freeList(files); + for (char **i = files; *i != nullptr; i++) + { + result.push_back(*i); + } + + PHYSFS_freeList(files); + } return result; } @@ -203,18 +222,21 @@ std::vector CResourceManager::ListDirectories(const std::string &di { std::vector result; - char **files = PHYSFS_enumerateFiles(CleanPath(directory).c_str()); - - for (char **i = files; *i != nullptr; i++) + if(PHYSFS_isInit()) { - std::string path = CleanPath(directory) + "/" + (*i); - if (PHYSFS_isDirectory(path.c_str())) - { - result.push_back(*i); - } - } + char **files = PHYSFS_enumerateFiles(CleanPath(directory).c_str()); - PHYSFS_freeList(files); + for (char **i = files; *i != nullptr; i++) + { + std::string path = CleanPath(directory) + "/" + (*i); + if (PHYSFS_isDirectory(path.c_str())) + { + result.push_back(*i); + } + } + + PHYSFS_freeList(files); + } return result; } @@ -241,6 +263,45 @@ long long CResourceManager::GetLastModificationTime(const std::string& filename) return -1; } +//TODO: Don't use boost::filesystem. Why doesn't PHYSFS have this? +bool CResourceManager::Move(const std::string& from, const std::string& to) +{ + if(PHYSFS_isInit()) + { + bool success = true; + std::string writeDir = PHYSFS_getWriteDir(); + try + { + fs::rename(writeDir + "/" + CleanPath(from), writeDir + "/" + CleanPath(to)); + } + catch (std::exception & e) + { + success = false; + } + return success; + } + return false; +} + +//TODO: Don't use boost::filesystem. Why doesn't PHYSFS have this? +bool CResourceManager::Copy(const std::string& from, const std::string& to) +{ + if(PHYSFS_isInit()) + { + bool success = true; + std::string writeDir = PHYSFS_getWriteDir(); + try + { + fs::copy(writeDir + "/" + CleanPath(from), writeDir + "/" + CleanPath(to)); + } + catch (std::exception & e) + { + success = false; + } + return success; + } + return false; +} int CResourceManager::SDLClose(SDL_RWops *context) { diff --git a/src/common/resources/resourcemanager.h b/src/common/resources/resourcemanager.h index e34ea103..84e28fd2 100644 --- a/src/common/resources/resourcemanager.h +++ b/src/common/resources/resourcemanager.h @@ -62,6 +62,11 @@ public: static long long GetFileSize(const std::string &filename); //! Returns last modification date as timestamp static long long GetLastModificationTime(const std::string &filename); + + //! Move file/directory + static bool Move(const std::string &from, const std::string &to); + //! Copy file/directory + static bool Copy(const std::string &from, const std::string &to); private: static int SDLSeek(SDL_RWops *context, int offset, int whence); diff --git a/src/common/restext.cpp b/src/common/restext.cpp index 493f141d..09bf6bd1 100644 --- a/src/common/restext.cpp +++ b/src/common/restext.cpp @@ -206,6 +206,9 @@ void InitializeRestext() stringsEvent[EVENT_INTERFACE_EDITVALUE] = TR("Big indent\\Indent 2 or 4 spaces per level defined by braces"); stringsEvent[EVENT_INTERFACE_SOLUCE4] = TR("Access to solutions\\Show program \"4: Solution\" in the exercises"); stringsEvent[EVENT_INTERFACE_BLOOD] = TR("Blood\\Display blood when the astronaut or the alien queen is hit"); + stringsEvent[EVENT_INTERFACE_AUTOSAVE_ENABLE] = TR("Autosave\\Enables autosave"); + stringsEvent[EVENT_INTERFACE_AUTOSAVE_INTERVAL] = TR("Autosave interval\\How often your game will autosave"); + stringsEvent[EVENT_INTERFACE_AUTOSAVE_SLOTS] = TR("Autosave slots\\How many autosave slots you'll have"); stringsEvent[EVENT_INTERFACE_KDEF] = TR("Standard controls\\Standard key functions"); stringsEvent[EVENT_INTERFACE_KLEFT] = TR("Turn left\\turns the bot to the left"); diff --git a/src/object/robotmain.cpp b/src/object/robotmain.cpp index b44e7042..3e00f933 100644 --- a/src/object/robotmain.cpp +++ b/src/object/robotmain.cpp @@ -708,6 +708,11 @@ CRobotMain::CRobotMain(CApplication* app, bool loadProfile) m_winTerminate = false; m_exitAfterMission = false; + + m_autosave = true; + m_autosaveInterval = 15; + m_autosaveSlots = 3; + m_autosaveLast = 0.0f; m_joystickDeadzone = 0.2f; SetDefaultInputBindings(); @@ -3393,7 +3398,7 @@ void CRobotMain::InitEye() bool CRobotMain::EventFrame(const Event &event) { m_time += event.rTime; - if (!m_movieLock) m_gameTime += event.rTime; + if (!m_movieLock && m_pause->GetPause() == PAUSE_NONE) m_gameTime += event.rTime; if (!m_immediatSatCom && !m_beginSatCom && m_gameTime > 0.1f && m_phase == PHASE_SIMUL) @@ -3404,6 +3409,12 @@ bool CRobotMain::EventFrame(const Event &event) if(!m_movieLock && m_pause->GetPause() == PAUSE_NONE && m_missionTimerStarted) m_missionTimer += event.rTime; + + if(m_pause->GetPause() == PAUSE_NONE && m_autosave && m_gameTime >= m_autosaveLast+(m_autosaveInterval*60) && m_phase == PHASE_SIMUL) { + m_autosaveLast = m_gameTime; + Autosave(); + } + //CLogger::GetInstancePointer()->Debug("%f %f %d\n", m_gameTime, m_autosaveLast, m_autosaveInterval); m_water->EventProcess(event); m_cloud->EventProcess(event); @@ -4742,6 +4753,7 @@ void CRobotMain::CreateScene(bool soluce, bool fixScene, bool resetObject) m_app->ResetKeyStates(); m_time = 0.0f; m_gameTime = 0.0f; + m_autosaveLast = 0.0f; m_infoUsed = 0; m_selectObject = sel; @@ -6930,3 +6942,121 @@ void CRobotMain::StartMissionTimer() m_missionTimerStarted = true; } } + +void CRobotMain::SetAutosave(bool enable) +{ + m_autosave = enable; + m_autosaveLast = m_gameTime; + AutosaveRotate(false); +} + +bool CRobotMain::GetAutosave() +{ + return m_autosave; +} + +void CRobotMain::SetAutosaveInterval(int interval) +{ + m_autosaveInterval = interval; + m_autosaveLast = m_gameTime; +} + +int CRobotMain::GetAutosaveInterval() +{ + return m_autosaveInterval; +} + +void CRobotMain::SetAutosaveSlots(int slots) +{ + m_autosaveSlots = slots; + AutosaveRotate(false); +} + +int CRobotMain::GetAutosaveSlots() +{ + return m_autosaveSlots; +} + +int CRobotMain::AutosaveRotate(bool freeOne) +{ + CLogger::GetInstancePointer()->Debug("Rotate autosaves...\n"); + // Find autosave dirs + auto saveDirs = CResourceManager::ListDirectories(std::string(GetSavegameDir()) + "/" + GetGamerName()); + std::map autosaveDirs; + for(auto& dir : saveDirs) + { + try + { + const std::string autosavePrefix = "autosave"; + if(dir.substr(0, autosavePrefix.length()) == "autosave") + { + int id = boost::lexical_cast(dir.substr(autosavePrefix.length())); + autosaveDirs[id] = std::string(GetSavegameDir()) + "/" + GetGamerName() + "/" + dir; + } + } + catch(...) + { + CLogger::GetInstancePointer()->Debug("bad?\n"); + // skip + } + } + if(autosaveDirs.size() == 0) return 1; + + // Remove all but last m_autosaveSlots + std::map autosavesToKeep; + int last_id = autosaveDirs.rbegin()->first; + int count = 0; + int to_keep = m_autosaveSlots-(freeOne ? 1 : 0); + int new_last_id = Math::Min(autosaveDirs.size(), to_keep); + bool rotate = false; + for(int i = last_id; i > 0; i--) + { + if(autosaveDirs.count(i) > 0) + { + count++; + if(count > m_autosaveSlots-(freeOne ? 1 : 0) || !m_autosave) + { + CLogger::GetInstancePointer()->Trace("Remove %s\n", autosaveDirs[i].c_str()); + CResourceManager::RemoveDirectory(autosaveDirs[i]); + rotate = true; + } + else + { + CLogger::GetInstancePointer()->Trace("Keep %s\n", autosaveDirs[i].c_str()); + autosavesToKeep[new_last_id-count+1] = autosaveDirs[i]; + } + } + } + + // Rename autosaves that we kept + if(rotate) { + for(auto& save : autosavesToKeep) { + std::string newDir = std::string(GetSavegameDir()) + "/" + GetGamerName() + "/autosave" + boost::lexical_cast(save.first); + CLogger::GetInstancePointer()->Trace("Rename %s -> %s\n", save.second.c_str(), newDir.c_str()); + CResourceManager::Move(save.second, newDir); + } + } + + return rotate ? count : count+1; +} + +void CRobotMain::Autosave() +{ + int id = AutosaveRotate(true); + CLogger::GetInstancePointer()->Info("Autosave!\n"); + + std::string dir = std::string(GetSavegameDir()) + "/" + GetGamerName() + "/autosave" + boost::lexical_cast(id); + + if (!CResourceManager::DirectoryExists(dir)) + { + CResourceManager::CreateDirectory(dir); + } + + std::string savegameFileName = dir + "/data.sav"; + std::string fileCBot = CResourceManager::GetSaveLocation() + "/" + dir + "/cbot.run"; + char timestr[100]; + TimeToAscii(time(NULL), timestr); + IOWriteScene(savegameFileName.c_str(), fileCBot.c_str(), const_cast((std::string("[AUTOSAVE] ")+timestr).c_str())); + + m_dialog->MakeSaveScreenshot(dir + "/screen.png"); +} \ No newline at end of file diff --git a/src/object/robotmain.h b/src/object/robotmain.h index 1affd1f6..34059062 100644 --- a/src/object/robotmain.h +++ b/src/object/robotmain.h @@ -396,6 +396,13 @@ public: std::string& GetUserLevelName(int id); void StartMissionTimer(); + + void SetAutosave(bool enable); + bool GetAutosave(); + void SetAutosaveInterval(int interval); + int GetAutosaveInterval(); + void SetAutosaveSlots(int slots); + int GetAutosaveSlots(); protected: bool EventFrame(const Event &event); @@ -429,6 +436,9 @@ protected: void ExecuteCmd(char *cmd); bool TestGadgetQuantity(int rank); void UpdateSpeedLabel(); + + int AutosaveRotate(bool freeOne); + void Autosave(); protected: @@ -589,5 +599,10 @@ protected: bool m_missionTimerEnabled; bool m_missionTimerStarted; float m_missionTimer; + + bool m_autosave; + int m_autosaveInterval; + int m_autosaveSlots; + float m_autosaveLast; }; diff --git a/src/ui/maindialog.cpp b/src/ui/maindialog.cpp index 69a91369..351ccee8 100644 --- a/src/ui/maindialog.cpp +++ b/src/ui/maindialog.cpp @@ -167,6 +167,7 @@ CMainDialog::CMainDialog() m_bCameraInvertY = false; m_bEffect = true; m_bBlood = true; + m_bAutosave = true; m_shotDelay = 0; m_glintMouse = Math::Point(0.0f, 0.0f); @@ -1239,6 +1240,34 @@ void CMainDialog::ChangePhase(Phase phase) pos.y -= 0.048f; pc = pw->CreateCheck(pos, ddim, -1, EVENT_INTERFACE_BLOOD); pc->SetState(STATE_SHADOW); + pos.y -= 0.048f; + pc = pw->CreateCheck(pos, ddim, -1, EVENT_INTERFACE_AUTOSAVE_ENABLE); + pc->SetState(STATE_SHADOW); + pos.y -= 0.048f; + + pos.y -= ddim.y; + ddim.x = dim.x*2.5f; + psl = pw->CreateSlider(pos, ddim, -1, EVENT_INTERFACE_AUTOSAVE_INTERVAL); + psl->SetState(STATE_SHADOW); + psl->SetLimit(1.0f, 30.0f); + psl->SetArrowStep(1.0f); + pos.y += ddim.y/2; + GetResource(RES_EVENT, EVENT_INTERFACE_AUTOSAVE_INTERVAL, name); + pl = pw->CreateLabel(pos, ddim, 0, EVENT_LABEL1, name); + pl->SetTextAlign(Gfx::TEXT_ALIGN_LEFT); + pos.y -= ddim.y/2; + pos.x = ox+sx*3+dim.x*3.5f; + psl = pw->CreateSlider(pos, ddim, -1, EVENT_INTERFACE_AUTOSAVE_SLOTS); + psl->SetState(STATE_SHADOW); + psl->SetLimit(1.0f, 10.0f); + psl->SetArrowStep(1.0f); + pos.y += ddim.y/2; + GetResource(RES_EVENT, EVENT_INTERFACE_AUTOSAVE_SLOTS, name); + pl = pw->CreateLabel(pos, ddim, 0, EVENT_LABEL1, name); + pl->SetTextAlign(Gfx::TEXT_ALIGN_LEFT); + pos.y -= ddim.y/2; + + //? pos.y -= 0.048f; //? pc = pw->CreateCheck(pos, ddim, -1, EVENT_INTERFACE_NICERST); //? pc->SetState(STATE_SHADOW); @@ -2570,6 +2599,23 @@ bool CMainDialog::EventProcess(const Event &event) ChangeSetupButtons(); UpdateSetupButtons(); break; + + case EVENT_INTERFACE_AUTOSAVE_ENABLE: + m_bAutosave = !m_bAutosave; + m_main->SetAutosave(m_bAutosave); + ChangeSetupButtons(); + UpdateSetupButtons(); + break; + + case EVENT_INTERFACE_AUTOSAVE_INTERVAL: + ChangeSetupButtons(); + UpdateSetupButtons(); + break; + + case EVENT_INTERFACE_AUTOSAVE_SLOTS: + ChangeSetupButtons(); + UpdateSetupButtons(); + break; default: break; @@ -4175,12 +4221,17 @@ bool CMainDialog::IOWriteScene() std::string fileCBot = CResourceManager::GetSaveLocation() + "/" + dir + "/cbot.run"; m_main->IOWriteScene(savegameFileName.c_str(), fileCBot.c_str(), info); - m_shotDelay = 3; - m_shotName = CResourceManager::GetSaveLocation() + "/" + dir + "/screen.png"; //TODO: Use PHYSFS? - + MakeSaveScreenshot(dir + "/screen.png"); + return true; } +void CMainDialog::MakeSaveScreenshot(const std::string& name) +{ + m_shotDelay = 3; + m_shotName = CResourceManager::GetSaveLocation() + "/" + name; //TODO: Use PHYSFS? +} + // Reads the scene. bool CMainDialog::IOReadScene() @@ -4726,6 +4777,27 @@ void CMainDialog::UpdateSetupButtons() { pc->SetState(STATE_CHECK, m_bBlood); } + + pc = static_cast(pw->SearchControl(EVENT_INTERFACE_AUTOSAVE_ENABLE)); + if ( pc != 0 ) + { + pc->SetState(STATE_CHECK, m_bAutosave); + } + + ps = static_cast(pw->SearchControl(EVENT_INTERFACE_AUTOSAVE_INTERVAL)); + if ( ps != 0 ) + { + ps->SetState(STATE_ENABLE, m_bAutosave); + ps->SetVisibleValue(m_main->GetAutosaveInterval()); + + } + + ps = static_cast(pw->SearchControl(EVENT_INTERFACE_AUTOSAVE_SLOTS)); + if ( ps != 0 ) + { + ps->SetState(STATE_ENABLE, m_bAutosave); + ps->SetVisibleValue(m_main->GetAutosaveSlots()); + } pc = static_cast(pw->SearchControl(EVENT_INTERFACE_SHADOW)); if ( pc != 0 ) @@ -4891,6 +4963,20 @@ void CMainDialog::ChangeSetupButtons() value = ps->GetVisibleValue(); m_sound->SetMusicVolume(static_cast(value)); } + + ps = static_cast(pw->SearchControl(EVENT_INTERFACE_AUTOSAVE_INTERVAL)); + if ( ps != 0 ) + { + value = ps->GetVisibleValue(); + m_main->SetAutosaveInterval(static_cast(value)); + } + + ps = static_cast(pw->SearchControl(EVENT_INTERFACE_AUTOSAVE_SLOTS)); + if ( ps != 0 ) + { + value = ps->GetVisibleValue(); + m_main->SetAutosaveSlots(static_cast(value)); + } } @@ -4913,6 +4999,9 @@ void CMainDialog::SetupMemorize() GetProfile().SetIntProperty("Setup", "CameraInvertY", m_bCameraInvertY); GetProfile().SetIntProperty("Setup", "InterfaceEffect", m_bEffect); GetProfile().SetIntProperty("Setup", "Blood", m_bBlood); + GetProfile().SetIntProperty("Setup", "Autosave", m_bAutosave); + GetProfile().SetIntProperty("Setup", "AutosaveInterval", m_main->GetAutosaveInterval()); + GetProfile().SetIntProperty("Setup", "AutosaveSlots", m_main->GetAutosaveSlots()); GetProfile().SetIntProperty("Setup", "GroundShadow", m_engine->GetShadow()); GetProfile().SetIntProperty("Setup", "GroundSpot", m_engine->GetGroundSpot()); GetProfile().SetIntProperty("Setup", "ObjectDirty", m_engine->GetDirty()); @@ -5066,6 +5155,21 @@ void CMainDialog::SetupRecall() m_bBlood = iValue; } + if ( GetProfile().GetIntProperty("Setup", "Autosave", iValue) ) + { + m_bAutosave = iValue; + } + + if ( GetProfile().GetIntProperty("Setup", "AutosaveInterval", iValue) ) + { + m_main->SetAutosaveInterval(iValue); + } + + if ( GetProfile().GetIntProperty("Setup", "AutosaveSlots", iValue) ) + { + m_main->SetAutosaveSlots(iValue); + } + if ( GetProfile().GetIntProperty("Setup", "GroundShadow", iValue) ) { m_engine->SetShadow(iValue); diff --git a/src/ui/maindialog.h b/src/ui/maindialog.h index aa98e3ed..67a0bbb4 100644 --- a/src/ui/maindialog.h +++ b/src/ui/maindialog.h @@ -139,6 +139,8 @@ public: void ShowSoluceUpdate(); std::string& GetUserLevelName(int id); + + void MakeSaveScreenshot(const std::string& name); protected: void GlintMove(); @@ -240,6 +242,7 @@ protected: bool m_bCameraInvertY; // for CCamera bool m_bEffect; // for CCamera bool m_bBlood; // for CCamera + bool m_bAutosave; Math::Point m_glintMouse; float m_glintTime; diff --git a/src/ui/slider.cpp b/src/ui/slider.cpp index 58fcc263..70ba7ef3 100644 --- a/src/ui/slider.cpp +++ b/src/ui/slider.cpp @@ -469,15 +469,18 @@ void CSlider::Draw() if ( m_bHoriz ) { - sprintf(text, "%d", static_cast(m_min+m_visibleValue*(m_max-m_min))); - h = m_engine->GetText()->GetHeight(m_fontType, m_fontSize); - pos.x = m_pos.x+m_dim.x+(10.0f/640.0f); - pos.y = m_pos.y+(m_dim.y-h)/2.0f; - m_engine->GetText()->DrawText(text, m_fontType, m_fontSize, pos, m_dim.x, Gfx::TEXT_ALIGN_LEFT, 0); + if ( m_state & STATE_ENABLE ) + { + sprintf(text, "%d", static_cast(m_min+m_visibleValue*(m_max-m_min))); + h = m_engine->GetText()->GetHeight(m_fontType, m_fontSize); + pos.x = m_pos.x+m_dim.x+(10.0f/640.0f); + pos.y = m_pos.y+(m_dim.y-h)/2.0f; + m_engine->GetText()->DrawText(text, m_fontType, m_fontSize, pos, m_dim.x, Gfx::TEXT_ALIGN_LEFT, 0); + } } else { - if ( m_state & STATE_VALUE ) + if ( m_state & STATE_VALUE && m_state & STATE_ENABLE ) { pos.x = m_pos.x+m_dim.x+4.0f/640.0f; h = m_dim.y-m_marginButton*2.0f;