From b34003b4a5ce1695c44b15ed6666d64676a859b0 Mon Sep 17 00:00:00 2001 From: SoftCoder Date: Sat, 7 Oct 2017 02:40:34 -0700 Subject: [PATCH] - enable using steam statistics to save players network based game end game stats to steam --- source/glest_game/game/game.cpp | 66 ++++++ source/glest_game/game/game_constants.h | 7 +- source/glest_game/global/config.cpp | 1 + source/glest_game/main/main.cpp | 67 ++---- source/glest_game/menu/menu_state_root.cpp | 9 +- source/glest_game/steam/steam.cpp | 214 +++++++++++++++--- source/glest_game/steam/steam.h | 65 ++++++ source/shared_lib/include/util/conversion.h | 1 + source/shared_lib/sources/util/conversion.cpp | 15 ++ 9 files changed, 368 insertions(+), 77 deletions(-) diff --git a/source/glest_game/game/game.cpp b/source/glest_game/game/game.cpp index 83752a146..c74766d9e 100644 --- a/source/glest_game/game/game.cpp +++ b/source/glest_game/game/game.cpp @@ -27,6 +27,8 @@ #include "video_player.h" #include "compression_utils.h" #include "cache_manager.h" +#include "conversion.h" +#include "steam.h" #include "leak_dumper.h" @@ -5170,6 +5172,68 @@ void Game::DumpCRCWorldLogIfRequired(string fileSuffix) { } } +void saveStatsToSteam(Game* game, Stats& endStats) { + if (NetworkManager::getInstance().isNetworkGame()) { + Steam* steamInstance = CacheManager::getCachedItem(GameConstants::steamCacheInstanceKey); + if (steamInstance != NULL) { + printf("\nSTEAM detected, writing out end game stats for player!\n"); + //printf("\nSTEAM Refresh Stats!\n"); + steamInstance->requestRefreshStats(); + for (int factionIndex = 0; + factionIndex < game->getWorld()->getFactionCount(); + ++factionIndex) { + if (factionIndex == game->getWorld()->getThisFactionIndex()) { + //printf("\nWriting out game stats for Faction Index: %d!\n",factionIndex); + if (endStats.getVictory(factionIndex)) { + steamInstance->setStatAsInt("stat_online_wins", + steamInstance->getStatAsInt("stat_online_wins") + + 1); + } else { + steamInstance->setStatAsInt("stat_online_loses", + steamInstance->getStatAsInt("stat_online_loses") + + 1); + } + steamInstance->setStatAsInt("stat_online_kills", + steamInstance->getStatAsInt("stat_online_kills") + + endStats.getKills(factionIndex)); + steamInstance->setStatAsInt("stat_online_kills_enemy", + steamInstance->getStatAsInt( + "stat_online_kills_enemy") + + endStats.getEnemyKills(factionIndex)); + steamInstance->setStatAsInt("stat_online_deaths", + steamInstance->getStatAsInt("stat_online_deaths") + + endStats.getDeaths(factionIndex)); + steamInstance->setStatAsInt("stat_online_units", + steamInstance->getStatAsInt("stat_online_units") + + endStats.getUnitsProduced(factionIndex)); + steamInstance->setStatAsInt( + "stat_online_resources_harvested", + steamInstance->getStatAsInt( + "stat_online_resources_harvested") + + endStats.getResourcesHarvested( + factionIndex)); + if (endStats.getPlayerLeftBeforeEnd(factionIndex)) { + steamInstance->setStatAsInt( + "stat_online_quit_before_end", + steamInstance->getStatAsInt( + "stat_online_quit_before_end") + 1); + } + steamInstance->setStatAsDouble("stat_online_minutes_played", + steamInstance->getStatAsDouble( + "stat_online_minutes_played") + + getTimeDuationMinutes( + endStats.getFramesToCalculatePlaytime(), + GameConstants::updateFps)); + } + } + //printf("\nSTEAM Store Stats!\n"); + steamInstance->storeStats(); + //printf("\nSTEAM Refresh Stats!\n"); + steamInstance->requestRefreshStats(); + } + } +} + void Game::exitGameState(Program *program, Stats &endStats) { if(SystemFlags::getSystemSettingType(SystemFlags::debugSystem).enabled) SystemFlags::OutputDebug(SystemFlags::debugSystem,"In [%s::%s Line: %d]\n",extractFileFromDirectoryPath(__FILE__).c_str(),__FUNCTION__,__LINE__); @@ -5193,6 +5257,8 @@ void Game::exitGameState(Program *program, Stats &endStats) { printf("-----------------------\n"); } + saveStatsToSteam(game, endStats); + ProgramState *newState = new BattleEnd(program, &endStats, game); if(SystemFlags::getSystemSettingType(SystemFlags::debugSystem).enabled) SystemFlags::OutputDebug(SystemFlags::debugSystem,"In [%s::%s Line: %d]\n",extractFileFromDirectoryPath(__FILE__).c_str(),__FUNCTION__,__LINE__); diff --git a/source/glest_game/game/game_constants.h b/source/glest_game/game/game_constants.h index 7e4daeedd..e5c65828f 100644 --- a/source/glest_game/game/game_constants.h +++ b/source/glest_game/game/game_constants.h @@ -47,7 +47,7 @@ public: } static string getString(const T &value) { static EnumParser parser; - for(enumMapTypeIter iValue = parser.enumMap.first(); + for(enumMapTypeIter iValue = parser.enumMap.begin(); iValue != parser.enumMap.end(); ++iValue) { if(iValue->second == value) { return iValue->first; @@ -55,6 +55,10 @@ public: } throw std::runtime_error("unknown enum lookup [" + intToStr(value) + "]"); } + static int getCount() { + static EnumParser parser; + return parser.enumMap.size(); + } }; // ===================================================== @@ -151,6 +155,7 @@ public: static const char *OBSERVER_SLOTNAME; static const char *RANDOMFACTION_SLOTNAME; + static const char *steamCacheInstanceKey; static const char *preCacheThreadCacheLookupKey; static const char *playerTextureCacheLookupKey; static const char *ircClientCacheLookupKey; diff --git a/source/glest_game/global/config.cpp b/source/glest_game/global/config.cpp index cb4db6a0d..a9e586ac2 100644 --- a/source/glest_game/global/config.cpp +++ b/source/glest_game/global/config.cpp @@ -51,6 +51,7 @@ const char *GameConstants::folder_path_screenshots = "screens/"; const char *GameConstants::OBSERVER_SLOTNAME = "*Observer*"; const char *GameConstants::RANDOMFACTION_SLOTNAME = "*Random*"; +const char *GameConstants::steamCacheInstanceKey = "steamInstanceCache"; const char *GameConstants::preCacheThreadCacheLookupKey = "preCacheThreadCache"; const char *GameConstants::ircClientCacheLookupKey = "ircClientCache"; const char *GameConstants::playerTextureCacheLookupKey = "playerTextureCache"; diff --git a/source/glest_game/main/main.cpp b/source/glest_game/main/main.cpp index ae6cd56f2..47e2b7d83 100644 --- a/source/glest_game/main/main.cpp +++ b/source/glest_game/main/main.cpp @@ -138,6 +138,8 @@ static string runtimeErrorMsg = ""; auto_ptr errorHandlerPtr; #endif +//auto_ptr steamInstance; + class NavtiveLanguageNameListCacheGenerator : public SimpleTaskCallbackInterface { virtual void simpleTask(BaseThread *callingThread,void *userdata) { Lang &lang = Lang::getInstance(); @@ -145,34 +147,6 @@ class NavtiveLanguageNameListCacheGenerator : public SimpleTaskCallbackInterface } }; -/* -static void printEvent(const STEAMSHIM_Event *e) -{ - if (!e) return; - - printf("CHILD EVENT: "); - switch (e->type) - { - #define PRINTGOTEVENT(x) case SHIMEVENT_##x: printf("%s(", #x); break - PRINTGOTEVENT(BYE); - PRINTGOTEVENT(STATSRECEIVED); - PRINTGOTEVENT(STATSSTORED); - PRINTGOTEVENT(SETACHIEVEMENT); - PRINTGOTEVENT(GETACHIEVEMENT); - PRINTGOTEVENT(RESETSTATS); - PRINTGOTEVENT(SETSTATI); - PRINTGOTEVENT(GETSTATI); - PRINTGOTEVENT(SETSTATF); - PRINTGOTEVENT(GETSTATF); - #undef PRINTGOTEVENT - default: printf("UNKNOWN("); break; - } - - printf("%sokay, ival=%d, fval=%f, time=%llu, name='%s').\n", - e->okay ? "" : "!", e->ivalue, e->fvalue, e->epochsecs, e->name); -} -*/ - // ===================================================== // class ExceptionHandler // ===================================================== @@ -3326,15 +3300,31 @@ void ShowINISettings(int argc, char **argv,Config &config,Config &configKeys) { } } +Steam & initSteamInstance() { + Steam *&steamInstance = CacheManager::getCachedItem< Steam *>(GameConstants::steamCacheInstanceKey); + if(steamInstance == NULL) { +// Steam &cacheInstance = CacheManager::getCachedItem< Steam * >(GameConstants::steamCacheInstanceKey); +// steamInstance.reset(&cacheInstance); +// //CacheManager::setCachedItem< Steam * >(GameConstants::steamCacheInstanceKey, steamInstance.get()); +// +// cacheInstance = steamInstance.get(); + steamInstance = new Steam(); + } +// return *steamInstance.get(); + return *steamInstance; +} + void setupSteamSettings(bool steamEnabled) { bool needToSaveConfig=false; Config &config = Config::getInstance(); config.setBool("SteamEnabled",steamEnabled,true); if(steamEnabled) { printf("*NOTE: Steam Integration Enabled.\n"); - //string steamPlayerName = safeCharPtrCopy(getenv("SteamAppUser"),100); - //if( steamPlayerName=="") return;// not a steam launch - Steam steam; + Steam &steam = initSteamInstance(); + + // For Debugging purposes: + //steam.resetStats(false); + string steamPlayerName = steam.userName(); string steamLang = steam.lang(); printf("Steam Integration Enabled!\nSteam User Name is [%s] Language is [%s]\n", steamPlayerName.c_str(), steamLang.c_str()); @@ -4240,18 +4230,6 @@ int glestMain(int argc, char** argv) { return 2; } -// if(hasCommandArgument(argc, argv,GAME_ARGS[GAME_ARG_STEAM]) == true) { -// STEAMSHIM_requestStats(); -// while (STEAMSHIM_alive()) { -// const STEAMSHIM_Event *e = STEAMSHIM_pump(); -// printEvent(e); -// if (e && e->type == SHIMEVENT_STATSRECEIVED) { -// break; -// } -// usleep(100 * 1000); -// } // while -// } - if( hasCommandArgument(argc, argv,string(GAME_ARGS[GAME_ARG_MASTERSERVER_MODE])) == true) { //isMasterServerModeEnabled = true; //Window::setMasterserverMode(isMasterServerModeEnabled); @@ -5037,7 +5015,7 @@ int glestMain(int argc, char** argv) { else { if(hasCommandArgument(argc, argv,GAME_ARGS[GAME_ARG_STEAM]) == true) { - Steam steam; + Steam &steam = initSteamInstance(); string steamUser = steam.userName(); string steamLang = steam.lang(); printf("Steam Integration Enabled!\nSteam User Name is [%s] Language is [%s]\n", steamUser.c_str(), steamLang.c_str()); @@ -6141,6 +6119,7 @@ int glestMainWrapper(int argc, char** argv) { int result = glestMainSEHWrapper(argc, argv); if (isSteamMode == true) { + //steamInstance.reset(NULL); STEAMSHIM_deinit(); } diff --git a/source/glest_game/menu/menu_state_root.cpp b/source/glest_game/menu/menu_state_root.cpp index 51c58c051..d711ec846 100644 --- a/source/glest_game/menu/menu_state_root.cpp +++ b/source/glest_game/menu/menu_state_root.cpp @@ -25,6 +25,7 @@ #include "network_message.h" #include "socket.h" #include "auto_test.h" +#include "cache_manager.h" #include "steam.h" #include @@ -80,10 +81,10 @@ MenuStateRoot::MenuStateRoot(Program *program, MainMenu *mainMenu) : labelGreeting.init(labelVersion.getX(), labelVersion.getY()-16); labelGreeting.setText(""); - Config &config = Config::getInstance(); - if(config.getBool("SteamEnabled")) { - Steam steam; - string steamPlayerName = steam.userName(); + + Steam *steamInstance = CacheManager::getCachedItem< Steam *>(GameConstants::steamCacheInstanceKey); + if(steamInstance != NULL) { + string steamPlayerName = steamInstance->userName(); labelGreeting.setText("Welcome Steam Player: " + steamPlayerName); } diff --git a/source/glest_game/steam/steam.cpp b/source/glest_game/steam/steam.cpp index 82b0bfcd3..e4934de60 100644 --- a/source/glest_game/steam/steam.cpp +++ b/source/glest_game/steam/steam.cpp @@ -3,14 +3,19 @@ #include #include #include "steamshim_child.h" +#include "platform_common.h" -/* Achievements */ +namespace Glest{ namespace Game{ + +std::map Steam::SteamStatNameTypes = Steam::create_map(); + +// Achievements //static const char *const achievementNames[] = { // "X", //}; //#define NUM_ACHIEVEMENTS (sizeof(achievementNames) / sizeof(achievementNames[0])) -/* Language map */ +// Language map static inline std::map gen_langToCode() { std::map map; @@ -45,31 +50,51 @@ static inline std::map gen_langToCode() } static const std::map langToCode = gen_langToCode(); -static std::string steamToIsoLang(const char *steamLang) -{ +static std::string steamToIsoLang(const char *steamLang) { //printf("Steam language [%s]\n",steamLang); std::map::const_iterator it = langToCode.find(steamLang); - if (it != langToCode.end()) + if (it != langToCode.end()) { return it->second; + } return "en"; } -/* SteamPrivate */ -struct SteamPrivate -{ +// SteamPrivate +struct SteamPrivate { //std::map achievements; + std::map stats; std::string userName; std::string lang; - SteamPrivate() - { + SteamPrivate() { + //printf("\ncreating private steam state container\n"); + STEAMSHIM_getPersonaName(); STEAMSHIM_getCurrentGameLanguage(); + STEAMSHIM_requestStats(); // for (size_t i = 0; i < NUM_ACHIEVEMENTS; ++i) // STEAMSHIM_getAchievement(achievementNames[i]); - while (!initialized()) - { + + for(int index = 0; index < EnumParser::getCount(); ++index) { + SteamStatName statName = static_cast(index); + string statNameStr = EnumParser::getString(statName); + SteamStatType statType = Steam::getSteamStatNameType(statNameStr); + switch(statType) { + case stat_int: + STEAMSHIM_getStatI(statNameStr.c_str()); + break; + case stat_float: + STEAMSHIM_getStatF(statNameStr.c_str()); + break; + default: + break; + } + } + + Shared::PlatformCommon::Chrono timer; + timer.start(); + while(!initialized() && timer.getMillis() < 2500) { SDL_Delay(100); update(); } @@ -92,11 +117,40 @@ struct SteamPrivate // return achievements[name]; // } - void update() - { + void updateStat(const char *name, double value) { + stats[name] = value; + } + + int getStatAsInt(const char *name) const { + std::map::const_iterator iterFind = stats.find(name); + if(iterFind != stats.end()) { + return iterFind->second; + } + return 0; + } + double getStatAsDouble(const char *name) const { + std::map::const_iterator iterFind = stats.find(name); + if(iterFind != stats.end()) { + return iterFind->second; + } + return 0; + } + + void setStatAsInt(const char *name, int value) { + STEAMSHIM_setStatI(name, value); + update(); + } + void setStatAsFloat(const char *name, float value) { + STEAMSHIM_setStatF(name, value); + update(); + } + void clearLocalStats() { + stats.clear(); + } + + const STEAMSHIM_Event * update(STEAMSHIM_EventType *waitForEvent=NULL) { const STEAMSHIM_Event *e; - while ((e = STEAMSHIM_pump()) != 0) - { + while ((e = STEAMSHIM_pump()) != 0) { /* Handle events */ switch (e->type) { @@ -104,45 +158,146 @@ struct SteamPrivate // updateAchievement(e->name, e->ivalue); // break; case SHIMEVENT_GETPERSONANAME: + //printf("\nGot Shim event SHIMEVENT_GETPERSONANAME isOk = %d\n",e->okay); userName = e->name; break; case SHIMEVENT_GETCURRENTGAMELANGUAGE: + //printf("\nGot Shim event SHIMEVENT_GETCURRENTGAMELANGUAGE isOk = %d\n",e->okay); lang = steamToIsoLang(e->name); + break; + case SHIMEVENT_STATSRECEIVED: + //printf("\nGot Shim event SHIMEVENT_STATSRECEIVED isOk = %d\n",e->okay); + break; + case SHIMEVENT_STATSSTORED: + //printf("\nGot Shim event SHIMEVENT_STATSSTORED isOk = %d\n",e->okay); + break; + + //case SHIMEVENT_SETSTATI: + case SHIMEVENT_GETSTATI: + //printf("\nGot Shim event SHIMEVENT_GETSTATI for stat [%s] value [%d] isOk = %d\n",e->name,e->ivalue,e->okay); + if(e->okay) { + updateStat(e->name, e->ivalue); + } + break; + case SHIMEVENT_GETSTATF: + //printf("\nGot Shim event SHIMEVENT_GETSTATF for stat [%s] value [%f] isOk = %d\n",e->name,e->fvalue,e->okay); + if(e->okay) { + updateStat(e->name, e->fvalue); + } + break; + + case SHIMEVENT_SETSTATI: + //printf("\nGot Shim event SHIMEVENT_SETSTATI for stat [%s] value [%d] isOk = %d\n",e->name,e->ivalue,e->okay); + break; + case SHIMEVENT_SETSTATF: + //printf("\nGot Shim event SHIMEVENT_SETSTATF for stat [%s] value [%f] isOk = %d\n",e->name,e->fvalue,e->okay); + break; + default: + //printf("\nGot Shim event [%d] isOk = %d\n",e->type,e->okay); break; } + if(waitForEvent != NULL && *waitForEvent == e->type) { + return e; + } } + return NULL; } - bool initialized() - { + bool initialized() { return !userName.empty() - && !lang.empty(); + && !lang.empty() + && (int)stats.size() >= EnumParser::getCount(); //&& achievements.size() == NUM_ACHIEVEMENTS; } }; /* Steam */ -Steam::Steam() - : p(new SteamPrivate()) -{ +Steam::Steam() : p(new SteamPrivate()) { } -Steam::~Steam() -{ +Steam::~Steam() { delete p; } -const std::string &Steam::userName() const -{ +const std::string &Steam::userName() const { return p->userName; } -const std::string &Steam::lang() const -{ +const std::string &Steam::lang() const { return p->lang; } +void Steam::resetStats(const int bAlsoAchievements) const { + STEAMSHIM_resetStats(false); + p->update(); +} + +void Steam::storeStats() const { + STEAMSHIM_storeStats(); + STEAMSHIM_EventType statsStored = SHIMEVENT_STATSSTORED; + + Shared::PlatformCommon::Chrono timer; + timer.start(); + while(timer.getMillis() < 2500) { + SDL_Delay(100); + const STEAMSHIM_Event *evt = p->update(&statsStored); + if(evt != NULL && evt->type == statsStored) { + break; + } + } +} + +int Steam::getStatAsInt(const char *name) const { + return p->getStatAsInt(name); +} + +double Steam::getStatAsDouble(const char *name) const { + return p->getStatAsDouble(name); +} + +void Steam::setStatAsInt(const char *name, int value) { + p->setStatAsInt(name, value); +} +void Steam::setStatAsDouble(const char *name, double value) { + p->setStatAsFloat(name, value); +} + +void Steam::requestRefreshStats() { + p->clearLocalStats(); + STEAMSHIM_requestStats(); + STEAMSHIM_EventType statsReceived = SHIMEVENT_STATSRECEIVED; + p->update(&statsReceived); + + for(int index = 0; index < EnumParser::getCount(); ++index) { + SteamStatName statName = static_cast(index); + string statNameStr = EnumParser::getString(statName); + SteamStatType statType = Steam::getSteamStatNameType(statNameStr); + switch(statType) { + case stat_int: + STEAMSHIM_getStatI(statNameStr.c_str()); + break; + case stat_float: + STEAMSHIM_getStatF(statNameStr.c_str()); + break; + default: + break; + } + } + + Shared::PlatformCommon::Chrono timer; + timer.start(); + while(!p->initialized() && timer.getMillis() < 2500) { + SDL_Delay(100); + p->update(); + } + +} + +SteamStatType Steam::getSteamStatNameType(string value) { + return SteamStatNameTypes[value]; +} + //void Steam::unlock(const char *name) //{ // p->setAchievement(name, true); @@ -157,3 +312,6 @@ const std::string &Steam::lang() const //{ // return p->isAchievementSet(name); //} + + +}}//end namespace diff --git a/source/glest_game/steam/steam.h b/source/glest_game/steam/steam.h index 5d39f12b8..33a1a580f 100644 --- a/source/glest_game/steam/steam.h +++ b/source/glest_game/steam/steam.h @@ -2,9 +2,46 @@ #define STEAM_H #include +#include +#include "game_constants.h" + +namespace Glest{ namespace Game{ + struct SteamPrivate; +enum SteamStatName { + stat_online_wins, + stat_online_loses, + stat_online_kills, + stat_online_kills_enemy, + stat_online_deaths, + stat_online_units, + stat_online_resources_harvested, + stat_online_quit_before_end, + stat_online_minutes_played +}; + +enum SteamStatType { + stat_int, + stat_float, + stat_avg +}; + +template <> +inline EnumParser::EnumParser() { + enumMap["stat_online_wins"] = stat_online_wins; + enumMap["stat_online_loses"] = stat_online_loses; + enumMap["stat_online_kills"] = stat_online_kills; + enumMap["stat_online_kills_enemy"] = stat_online_kills_enemy; + enumMap["stat_online_deaths"] = stat_online_deaths; + enumMap["stat_online_units"] = stat_online_units; + enumMap["stat_online_resources_harvested"] = stat_online_resources_harvested; + enumMap["stat_online_quit_before_end"] = stat_online_quit_before_end; + enumMap["stat_online_minutes_played"] = stat_online_minutes_played; +} + + class Steam { public: @@ -12,9 +49,20 @@ public: // void lock(const char *name); // bool isUnlocked(const char *name); + static SteamStatType getSteamStatNameType(string value); + const std::string &userName() const; const std::string &lang() const; + void resetStats(const int bAlsoAchievements) const; + void storeStats() const; + int getStatAsInt(const char *name) const; + double getStatAsDouble(const char *name) const; + void setStatAsInt(const char *name, int value); + void setStatAsDouble(const char *name, double value); + + void requestRefreshStats(); + Steam(); ~Steam(); @@ -22,6 +70,23 @@ private: //friend struct SharedStatePrivate; SteamPrivate *p; + static std::map SteamStatNameTypes; + + static std::map create_map() { + std::map steamStatNameTypes; + steamStatNameTypes["stat_online_wins"] = stat_int; + steamStatNameTypes["stat_online_loses"] = stat_int; + steamStatNameTypes["stat_online_kills"] = stat_int; + steamStatNameTypes["stat_online_kills_enemy"] = stat_int; + steamStatNameTypes["stat_online_deaths"] = stat_int; + steamStatNameTypes["stat_online_units"] = stat_int; + steamStatNameTypes["stat_online_resources_harvested"] = stat_int; + steamStatNameTypes["stat_online_quit_before_end"] = stat_int; + steamStatNameTypes["stat_online_minutes_played"] = stat_float; + return steamStatNameTypes; + } }; +}}//end namespace + #endif // STEAM_H diff --git a/source/shared_lib/include/util/conversion.h b/source/shared_lib/include/util/conversion.h index 836ba997d..deeab9d4d 100644 --- a/source/shared_lib/include/util/conversion.h +++ b/source/shared_lib/include/util/conversion.h @@ -43,6 +43,7 @@ bool IsNumeric(const char *p, bool allowNegative=true); string formatNumber(uint64 f); +double getTimeDuationMinutes(int frames, int updateFps); string getTimeDuationString(int frames, int updateFps); }}//end namespace diff --git a/source/shared_lib/sources/util/conversion.cpp b/source/shared_lib/sources/util/conversion.cpp index 67af10640..f4eff1d88 100644 --- a/source/shared_lib/sources/util/conversion.cpp +++ b/source/shared_lib/sources/util/conversion.cpp @@ -215,6 +215,21 @@ string formatNumber(uint64 f) { return out.str(); } +double getTimeDuationMinutes(int frames, int updateFps) { + int framesleft = frames; + double hours = (int)((int) frames / (float)updateFps / 3600.0f); + framesleft = framesleft - hours * 3600 * updateFps; + double minutes = (int)((int) framesleft / (float)updateFps / 60.0f); + framesleft = framesleft - minutes * 60 * updateFps; + double seconds = (int)((int) framesleft / (float)updateFps); + + double result = (hours * 60.0) + minutes; + if(seconds > 0) { + result += seconds / 60.0; + } + return result; +} + string getTimeDuationString(int frames, int updateFps) { int framesleft = frames; int hours = (int)((int) frames / (float)updateFps / 3600.0f);