From 947479f15423e038c38b5a7673f6a82b2d41c8ce Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Tue, 1 Jul 2014 01:36:00 -0600 Subject: [PATCH] Added heart rate based calorie counting method to Profile as an alternative to step count based calorie counting. Removed unused IsWeightSet from Profile::SaveGeneratlDataCreateNode. --- Docs/Luadoc/Lua.xml | 11 ++ Docs/Luadoc/LuaDocumentation.xml | 33 ++++++ src/Profile.cpp | 177 ++++++++++++++++++++++++++++++- src/Profile.h | 13 +++ 4 files changed, 230 insertions(+), 4 deletions(-) diff --git a/Docs/Luadoc/Lua.xml b/Docs/Luadoc/Lua.xml index 6a8a2ef7a8..8aa39a17b0 100644 --- a/Docs/Luadoc/Lua.xml +++ b/Docs/Luadoc/Lua.xml @@ -1147,8 +1147,12 @@ + + + + @@ -1163,6 +1167,8 @@ + + @@ -1190,15 +1196,20 @@ + + + + + diff --git a/Docs/Luadoc/LuaDocumentation.xml b/Docs/Luadoc/LuaDocumentation.xml index 628696206d..4f84103925 100644 --- a/Docs/Luadoc/LuaDocumentation.xml +++ b/Docs/Luadoc/LuaDocumentation.xml @@ -3284,12 +3284,24 @@ save yourself some time, copy this for undocumented things: + + Adds cals to the daily total. + Adds a screenshot entry to the profile. filename must be the full path of the screenshot, as returned by SaveScreenshot. + + Calculates the number of calories burned based on the heart rate (in beats per minute), the duration (in seconds), and data in the profile. + + + Returns the age. + Returns a table of all high score names that have been used on this profile. + + Returns the birth year. + Returns the number of calories burned during the current day. @@ -3326,6 +3338,12 @@ save yourself some time, copy this for undocumented things: Returns the GUID of this Profile. + + Returns whether this profile ignores the step count based calorie calculation. + + + Returns whether this profile uses the male formula when CalculateCaloriesFromHeartRate is used. + Gets the profile's HighScoreList for a specified Song and Steps. (Alternate arguments for Courses: Course c, Trail t) @@ -3405,6 +3423,9 @@ save yourself some time, copy this for undocumented things: Returns the user table for this Profile. + + Returns the VO2 max for this profile. + Returns how much the player weighs. @@ -3414,6 +3435,9 @@ save yourself some time, copy this for undocumented things: Returns true if the specified code sUnlockEntryID is unlocked. + + Sets the birth year of the profile. + Sets the current for the Profile. @@ -3426,9 +3450,18 @@ save yourself some time, copy this for undocumented things: Sets the current goal type to gt. + + Sets whether this profile ignores the step count based calorie counting. + + + Sets whether this profile uses the male formula when CalculateCaloriesFromHeartRate is used. + Sets last used high score name. + + Sets the VO2 max for the profile. 0 is treated as unset. + Sets how much the player weighs (in pounds) to weightPounds. diff --git a/src/Profile.cpp b/src/Profile.cpp index ff0d808392..7d493e5b2a 100644 --- a/src/Profile.cpp +++ b/src/Profile.cpp @@ -51,7 +51,8 @@ static ThemeMetric UNLOCK_AUTH_STRING( "Profile", "UnlockAuthString" ); * 5 /* HighScores per Steps */ \ * 1024 /* size in bytes of a HighScores XNode */ -const unsigned int DEFAULT_WEIGHT_POUNDS = 120; +const int DEFAULT_WEIGHT_POUNDS = 120; +const float DEFAULT_BIRTH_YEAR= 1995; #if defined(_MSC_VER) #pragma warning (disable : 4706) // assignment within conditional expression @@ -85,6 +86,10 @@ void Profile::InitEditableData() m_sCharacterID = ""; m_sLastUsedHighScoreName = ""; m_iWeightPounds = 0; + m_Voomax= 0; + m_BirthYear= 0; + m_IgnoreStepCountCalories= false; + m_IsMale= true; } void Profile::ClearStats() @@ -221,6 +226,15 @@ int Profile::GetCalculatedWeightPounds() const return m_iWeightPounds; } +int Profile::GetAge() const +{ + if(m_BirthYear == 0) + { + return (GetLocalTime().tm_year+1900) - DEFAULT_BIRTH_YEAR; + } + return (GetLocalTime().tm_year+1900) - m_BirthYear; +} + RString Profile::GetDisplayTotalCaloriesBurned() const { return FormatCalories( m_fTotalCaloriesBurned ); @@ -954,6 +968,10 @@ ProfileLoadResult Profile::LoadStatsXmlFromNode( const XNode *xml, bool bIgnoreE RString sCharacterID = m_sCharacterID; RString sLastUsedHighScoreName = m_sLastUsedHighScoreName; int iWeightPounds = m_iWeightPounds; + float Voomax= m_Voomax; + int BirthYear= m_BirthYear; + bool IgnoreStepCountCalories= m_IgnoreStepCountCalories; + bool IsMale= m_IsMale; LOAD_NODE( GeneralData ); LOAD_NODE( SongScores ); @@ -968,6 +986,10 @@ ProfileLoadResult Profile::LoadStatsXmlFromNode( const XNode *xml, bool bIgnoreE m_sCharacterID = sCharacterID; m_sLastUsedHighScoreName = sLastUsedHighScoreName; m_iWeightPounds = iWeightPounds; + m_Voomax= Voomax; + m_BirthYear= BirthYear; + m_IgnoreStepCountCalories= IgnoreStepCountCalories; + m_IsMale= IsMale; } return ProfileLoadResult_Success; @@ -1095,6 +1117,10 @@ void Profile::SaveEditableDataToDir( RString sDir ) const ini.SetValue( "Editable", "CharacterID", m_sCharacterID ); ini.SetValue( "Editable", "LastUsedHighScoreName", m_sLastUsedHighScoreName ); ini.SetValue( "Editable", "WeightPounds", m_iWeightPounds ); + ini.SetValue( "Editable", "Voomax", m_Voomax ); + ini.SetValue( "Editable", "BirthYear", m_BirthYear ); + ini.SetValue( "Editable", "IgnoreStepCountCalories", m_IgnoreStepCountCalories ); + ini.SetValue( "Editable", "IsMale", m_IsMale ); ini.WriteFile( sDir + EDITABLE_INI ); } @@ -1110,8 +1136,12 @@ XNode* Profile::SaveGeneralDataCreateNode() const pGeneralDataNode->AppendChild( "CharacterID", m_sCharacterID ); pGeneralDataNode->AppendChild( "LastUsedHighScoreName", m_sLastUsedHighScoreName ); pGeneralDataNode->AppendChild( "WeightPounds", m_iWeightPounds ); + pGeneralDataNode->AppendChild( "Voomax", m_Voomax ); + pGeneralDataNode->AppendChild( "BirthYear", m_BirthYear ); + pGeneralDataNode->AppendChild( "IgnoreStepCountCalories", m_IgnoreStepCountCalories ); + pGeneralDataNode->AppendChild( "IsMale", m_IsMale ); + pGeneralDataNode->AppendChild( "IsMachine", IsMachine() ); - pGeneralDataNode->AppendChild( "IsWeightSet", m_iWeightPounds != 0 ); pGeneralDataNode->AppendChild( "Guid", m_sGuid ); pGeneralDataNode->AppendChild( "SortOrder", SortOrderToString(m_SortOrder) ); @@ -1274,6 +1304,10 @@ ProfileLoadResult Profile::LoadEditableDataFromDir( RString sDir ) ini.GetValue( "Editable", "CharacterID", m_sCharacterID ); ini.GetValue( "Editable", "LastUsedHighScoreName", m_sLastUsedHighScoreName ); ini.GetValue( "Editable", "WeightPounds", m_iWeightPounds ); + ini.GetValue( "Editable", "Voomax", m_Voomax ); + ini.GetValue( "Editable", "BirthYear", m_BirthYear ); + ini.GetValue( "Editable", "IgnoreStepCountCalories", m_IgnoreStepCountCalories ); + ini.GetValue( "Editable", "IsMale", m_IsMale ); // This is data that the user can change, so we have to validate it. wstring wstr = RStringToWstring(m_sDisplayName); @@ -1298,6 +1332,10 @@ void Profile::LoadGeneralDataFromNode( const XNode* pNode ) pNode->GetChildValue( "CharacterID", m_sCharacterID ); pNode->GetChildValue( "LastUsedHighScoreName", m_sLastUsedHighScoreName ); pNode->GetChildValue( "WeightPounds", m_iWeightPounds ); + pNode->GetChildValue( "Voomax", m_Voomax ); + pNode->GetChildValue( "BirthYear", m_BirthYear ); + pNode->GetChildValue( "IgnoreStepCountCalories", m_IgnoreStepCountCalories ); + pNode->GetChildValue( "IsMale", m_IsMale ); pNode->GetChildValue( "Guid", m_sGuid ); pNode->GetChildValue( "SortOrder", s ); m_SortOrder = StringToSortOrder( s ); pNode->GetChildValue( "LastDifficulty", s ); m_LastDifficulty = StringToDifficulty( s ); @@ -1451,10 +1489,94 @@ void Profile::AddStepTotals( int iTotalTapsAndHolds, int iTotalJumps, int iTotal m_iTotalHands += iTotalHands; m_iTotalLifts += iTotalLifts; - m_fTotalCaloriesBurned += fCaloriesBurned; + if(!m_IgnoreStepCountCalories) + { + m_fTotalCaloriesBurned += fCaloriesBurned; + DateTime date = DateTime::GetNowDate(); + m_mapDayToCaloriesBurned[date].fCals += fCaloriesBurned; + } +} +// It's a bit unclean to have this flag for routing around the old step count +// based calorie calculation, but I can't think of a better way to do it. +// AddStepTotals is called (through a couple layers) by CommitStageStats at +// the end of ScreenGameplay, so it can't be moved to somewhere else. The +// player can't put in their heart rate for calculation until after +// ScreenGameplay +void Profile::AddCaloriesToDailyTotal(float cals) +{ + m_fTotalCaloriesBurned += cals; DateTime date = DateTime::GetNowDate(); - m_mapDayToCaloriesBurned[date].fCals += fCaloriesBurned; + m_mapDayToCaloriesBurned[date].fCals += cals; +} + +float Profile::CalculateCaloriesFromHeartRate(float HeartRate, float Duration) +{ + // Copied from http://www.shapesense.com/fitness-exercise/calculators/heart-rate-based-calorie-burn-calculator.aspx + /* + Male: ((-55.0969 + (0.6309 x HR) + (0.1988 x W) + (0.2017 x A))/4.184) x T + Female: ((-20.4022 + (0.4472 x HR) - (0.1263 x W) + (0.074 x A))/4.184) x T + where + + HR = Heart rate (in beats/minute) + W = Weight (in kilograms) + A = Age (in years) + T = Exercise duration time (in minutes) + + Equations for Determination of Calorie Burn if VO2max is Known + + Male: ((-95.7735 + (0.634 x HR) + (0.404 x VO2max) + (0.394 x W) + (0.271 x A))/4.184) x T + Female: ((-59.3954 + (0.45 x HR) + (0.380 x VO2max) + (0.103 x W) + (0.274 x A))/4.184) x T + where + + HR = Heart rate (in beats/minute) + VO2max = Maximal oxygen consumption (in mL•kg-1•min-1) + W = Weight (in kilograms) + A = Age (in years) + T = Exercise duration time (in minutes) + */ + // Duration passed in is in seconds. Convert it to minutes to make the code + // match the equations from the website. + Duration= Duration / 60.0; + float kilos= GetCalculatedWeightPounds() / 2.205; + float age= GetAge(); + + // Names for the constants in the equations. + // Assumes male and unknown voomax. + float gender_factor= -55.0969; + float heart_factor= 0.6309; + float voo_factor= 0; + float weight_factor= 0.1988; + float age_factor= 0.2017; + if(m_Voomax > 0) + { + if(m_IsMale) + { + gender_factor= -95.7735; + heart_factor= 0.634; + voo_factor= 0.404; + weight_factor= 0.394; + age_factor= 0.271; + } + else + { + gender_factor= -59.3954; + heart_factor= 0.45; + voo_factor= 0.380; + weight_factor= 0.103; + age_factor= 0.274; + } + } + else if(!m_IsMale) + { + gender_factor= -20.4022; + heart_factor= 0.6309; + weight_factor= 0.1988; + age_factor= 0.2017; + } + return ((gender_factor + (heart_factor * HeartRate) + + (voo_factor * m_Voomax) + (weight_factor * kilos) + (age_factor + age)) + / 4.184) * Duration; } XNode* Profile::SaveSongScoresCreateNode() const @@ -2031,6 +2153,11 @@ public: } static int GetDisplayName( T* p, lua_State *L ) { lua_pushstring(L, p->m_sDisplayName ); return 1; } + static int SetDisplayName( T* p, lua_State *L ) + { + p->m_sDisplayName= SArg(1); + return 0; + } static int GetLastUsedHighScoreName( T* p, lua_State *L ) { lua_pushstring(L, p->m_sLastUsedHighScoreName ); return 1; } static int SetLastUsedHighScoreName( T* p, lua_State *L ) { @@ -2127,6 +2254,37 @@ public: static int SetCharacter( T* p, lua_State *L ) { p->SetCharacter(SArg(1)); return 0; } static int GetWeightPounds( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iWeightPounds ); return 1; } static int SetWeightPounds( T* p, lua_State *L ) { p->m_iWeightPounds = IArg(1); return 0; } + DEFINE_METHOD(GetVoomax, m_Voomax); + DEFINE_METHOD(GetAge, GetAge()); + DEFINE_METHOD(GetBirthYear, m_BirthYear); + DEFINE_METHOD(GetIgnoreStepCountCalories, m_IgnoreStepCountCalories); + DEFINE_METHOD(GetIsMale, m_IsMale); + static int SetVoomax( T* p, lua_State *L ) + { + p->m_Voomax= FArg(1); + return 0; + } + static int SetBirthYear( T* p, lua_State *L ) + { + p->m_BirthYear= IArg(1); + return 0; + } + static int SetIgnoreStepCountCalories( T* p, lua_State *L ) + { + p->m_IgnoreStepCountCalories= BArg(1); + return 0; + } + static int SetIsMale( T* p, lua_State *L ) + { + p->m_IsMale= BArg(1); + return 0; + } + static int AddCaloriesToDailyTotal( T* p, lua_State *L ) + { + p->AddCaloriesToDailyTotal(FArg(1)); + return 0; + } + DEFINE_METHOD(CalculateCaloriesFromHeartRate, CalculateCaloriesFromHeartRate(FArg(1), FArg(2))); static int GetGoalType( T* p, lua_State *L ) { lua_pushnumber(L, p->m_GoalType ); return 1; } static int SetGoalType( T* p, lua_State *L ) { p->m_GoalType = Enum::Check(L, 1); return 0; } static int GetGoalCalories( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iGoalCalories ); return 1; } @@ -2226,6 +2384,17 @@ public: ADD_METHOD( SetCharacter ); ADD_METHOD( GetWeightPounds ); ADD_METHOD( SetWeightPounds ); + ADD_METHOD( GetVoomax ); + ADD_METHOD( SetVoomax ); + ADD_METHOD( GetAge ); + ADD_METHOD( GetBirthYear ); + ADD_METHOD( SetBirthYear ); + ADD_METHOD( GetIgnoreStepCountCalories ); + ADD_METHOD( SetIgnoreStepCountCalories ); + ADD_METHOD( GetIsMale ); + ADD_METHOD( SetIsMale ); + ADD_METHOD( AddCaloriesToDailyTotal ); + ADD_METHOD( CalculateCaloriesFromHeartRate ); ADD_METHOD( GetGoalType ); ADD_METHOD( SetGoalType ); ADD_METHOD( GetGoalCalories ); diff --git a/src/Profile.h b/src/Profile.h index ca19b69415..bc3d55932f 100644 --- a/src/Profile.h +++ b/src/Profile.h @@ -73,6 +73,8 @@ public: * Note: there are probably a lot of variables. */ Profile(): m_sDisplayName(""), m_sCharacterID(""), m_sLastUsedHighScoreName(""), m_iWeightPounds(0), + m_Voomax(0), m_BirthYear(0), m_IgnoreStepCountCalories(false), + m_IsMale(true), m_sGuid(MakeGuid()), m_sDefaultModifiers(), m_SortOrder(SortOrder_Invalid), m_LastDifficulty(Difficulty_Invalid), @@ -120,6 +122,7 @@ public: RString GetDisplayTotalCaloriesBurned() const; // remove me and use Lua instead RString GetDisplayTotalCaloriesBurnedToday() const; // remove me and use Lua instead int GetCalculatedWeightPounds() const; // returns a default value if m_iWeightPounds isn't set + int GetAge() const; // returns a default value if m_Age isn't set float GetCaloriesBurnedToday() const; int GetTotalNumSongsPassed() const; int GetTotalStepsWithTopGrade( StepsType st, Difficulty d, Grade g ) const; @@ -139,6 +142,8 @@ public: void AddStepTotals( int iNumTapsAndHolds, int iNumJumps, int iNumHolds, int iNumRolls, int iNumMines, int iNumHands, int iNumLifts, float fCaloriesBurned ); + void AddCaloriesToDailyTotal(float cals); + float CalculateCaloriesFromHeartRate(float HeartRate, float Duration); bool IsMachine() const; @@ -152,6 +157,14 @@ public: * so that it can be ready quickly. */ RString m_sLastUsedHighScoreName; int m_iWeightPounds; // 0 == not set + // Voomax and BirthYear are used for calculating calories from heart rate. + float m_Voomax; // 0 == not set + int m_BirthYear; // 0 == not set + // m_IgnoreStepCountCalories is so that the step count based calorie + // counter can be ignored in favor of calculating calories from heart rate + // and voomax. + bool m_IgnoreStepCountCalories; + bool m_IsMale; // Used solely for calculating calories from heart rate. //RString m_sProfileImageName; // todo: add a default image -aj // General data