Merge pull request #210 from kyzentun/HeartCalories

Heart calories
This commit is contained in:
AJ Kelly
2014-07-01 10:58:57 -05:00
5 changed files with 285 additions and 4 deletions
+12
View File
@@ -1147,8 +1147,12 @@
<Function name='SetPreferenceToDefault'/>
</Class>
<Class name='Profile'>
<Function name='AddCaloriesToDailyTotal'/>
<Function name='AddScreenshot'/>
<Function name='CalculateCaloriesFromHeartRate'/>
<Function name='GetAge'/>
<Function name='GetAllUsedHighScoreNames'/>
<Function name='GetBirthYear'/>
<Function name='GetCaloriesBurnedToday'/>
<Function name='GetCategoryHighScoreList'/>
<Function name='GetCharacter'/>
@@ -1163,6 +1167,8 @@
<Function name='GetGoalType'/>
<Function name='GetHighScoreList'/>
<Function name='GetHighScoreListIfExists'/>
<Function name='GetIgnoreStepCountCalories'/>
<Function name='GetIsMale'/>
<Function name='GetLastPlayedCourse'/>
<Function name='GetLastPlayedSong'/>
<Function name='GetLastUsedHighScoreName'/>
@@ -1190,15 +1196,21 @@
<Function name='GetTotalTapsAndHolds'/>
<Function name='GetTotalTrailsWithTopGrade'/>
<Function name='GetUserTable'/>
<Function name='GetVoomax'/>
<Function name='GetWeightPounds'/>
<Function name='HasPassedAnyStepsInSong'/>
<Function name='IsCodeUnlocked'/>
<Function name='SetBirthYear'/>
<Function name='SetCharacter'/>
<Function name='SetDisplayName'/>
<Function name='SetGoalCalories'/>
<Function name='SetGoalSeconds'/>
<Function name='SetGoalType'/>
<Function name='SetIgnoreStepCountCalories'/>
<Function name='SetIsMale'/>
<Function name='SetLastUsedHighScoreName'/>
<Function name='SetWeightPounds'/>
<Function name='SetVoomax'/>
</Class>
<Class name='ProfileManager'>
<Function name='GetLocalProfile'/>
+36
View File
@@ -3284,12 +3284,24 @@ save yourself some time, copy this for undocumented things:
</Function>
</Class>
<Class name='Profile'>
<Function name='AddCaloriesToDailyTotal' return='void' arguments='float cals'>
Adds cals to the daily total.
</Function>
<Function name='AddScreenshot' return='void' arguments='HighScore hs, string filename'>
Adds a screenshot entry to the profile. filename must be the full path of the screenshot, as returned by SaveScreenshot.
</Function>
<Function name='CalculateCaloriesFromHeartRate' return='float' arguments='float heart_bpm, float duration'>
Calculates the number of calories burned based on the heart rate (in beats per minute), the duration (in seconds), and data in the profile.
</Function>
<Function name='GetAge' return='int' arguments=''>
Returns the age.
</Function>
<Function name='GetAllUsedHighScoreNames' return= '{string}' arguments='void'>
Returns a table of all high score names that have been used on this profile.
</Function>
<Function name='GetBirthYear' return='int' arguments=''>
Returns the birth year.
</Function>
<Function name='GetCaloriesBurnedToday' return='float' arguments=''>
Returns the number of calories burned during the current day.
</Function>
@@ -3326,6 +3338,12 @@ save yourself some time, copy this for undocumented things:
<Function name='GetGUID' return='string' arguments=''>
Returns the GUID of this Profile.
</Function>
<Function name='GetIgnoreStepCountCalories' return='bool' arguments=''>
Returns whether this profile ignores the step count based calorie calculation.
</Function>
<Function name='GetIsMale' return='bool' arguments=''>
Returns whether this profile uses the male formula when CalculateCaloriesFromHeartRate is used.
</Function>
<Function name='GetHighScoreList' return='HighScoreList' arguments='Song s, Steps st'>
Gets the profile's HighScoreList for a specified Song and Steps. (Alternate arguments for Courses: <code>Course c, Trail t</code>)
</Function>
@@ -3405,6 +3423,9 @@ save yourself some time, copy this for undocumented things:
<Function name='GetUserTable' return='table' arguments=''>
Returns the user table for this Profile.
</Function>
<Function name='GetVoomax' return='float' arguments=''>
Returns the VO2 max for this profile.
</Function>
<Function name='GetWeightPounds' return='int' arguments=''>
Returns how much the player weighs.
</Function>
@@ -3414,9 +3435,15 @@ save yourself some time, copy this for undocumented things:
<Function name='IsCodeUnlocked' return='bool' arguments='string sUnlockEntryID'>
Returns <code>true</code> if the specified code <code>sUnlockEntryID</code> is unlocked.
</Function>
<Function name='SetBirthYear' return='void' arguments='int year'>
Sets the birth year of the profile.
</Function>
<Function name='SetCharacter' return='void' arguments='string sCharID'>
Sets the current <Link class='Character' /> for the Profile.
</Function>
<Function name='SetDisplayName' return='void' arguments='string name'>
Sets the display name of the profile to name.
</Function>
<Function name='SetGoalCalories' return='void' arguments='int iCals'>
Sets the goal to <code>iCals</code> calories.
</Function>
@@ -3426,9 +3453,18 @@ save yourself some time, copy this for undocumented things:
<Function name='SetGoalType' return='void' arguments='GoalType gt'>
Sets the current goal type to <code>gt</code>.
</Function>
<Function name='SetIgnoreStepCountCalories' return='void' arguments='bool ignore'>
Sets whether this profile ignores the step count based calorie counting.
</Function>
<Function name='SetIsMale' return='void' arguments='bool male'>
Sets whether this profile uses the male formula when CalculateCaloriesFromHeartRate is used.
</Function>
<Function name='SetLastUsedHighScoreName' return='void' arguments='string name'>
Sets last used high score name.
</Function>
<Function name='SetVoomax' return='void' arguments='float voo'>
Sets the VO2 max for the profile. 0 is treated as unset.
</Function>
<Function name='SetWeightPounds' return='void' arguments='int weightPounds'>
Sets how much the player weighs (in pounds) to <code>weightPounds</code>.
</Function>
+50
View File
@@ -0,0 +1,50 @@
SM5 has two systems for calculating the number of calories burned during a song.
The first system simply uses the weight of the player and adds a small amount for every step. This does not require any specific support from the theme and is the default for new profiles.
The second system uses age, gender, weight, heart rate, and song duration to calculate the amount used during a song. This requires a theme to support it, and the relevant parts of Editable.ini to be set for the profile.
Editable.ini fields:
BirthYear, -- defaults to 1995
IgnoreStepCountCalories, -- Must be set to 1 to use the second system.
IsMale, -- Defaults to 1. Set to 0 for females.
Voomax, -- VO2max. Optional. Calorie calculation is more accurate if it is set, but it is not necessary. 0 means unset. http://www.shapesense.com/fitness-exercise/calculators/vo2max-calculator.aspx is a site for estimating V02max, and the source of the equations used.
WeightPounds -- Weight in pounds.
Lua API functions: (names only, details are in Docs/Luadoc/Lua.xml)
GetWeightPounds()
SetWeightPounds()
GetVoomax()
SetVoomax()
GetAge()
GetBirthYear()
SetBirthYear()
GetIgnoreStepCountCalories()
SetIgnoreStepCountCalories()
GetIsMale()
SetIsMale()
AddCaloriesToDailyTotal()
CalculateCaloriesFromHeartRate()
Intended usage:
A theme that supports heart rate calorie calculation should take the following steps:
1. Create a new screen that the player will be able to use to enter their heart rate. It shall be called "ScreenHeartEntry" in this list, though a theme can name it anything.
2. During ScreenGameplay, track the amount of time that the player is hitting steps for. This duration must be in seconds.
3. When proceeding from ScreenGameplay:
3a. Check whether GetIgnoreStepCountCalories returns true for either player's profile.
3b. If it returns true for either player, go to ScreenHeartEntry instead of ScreenEvaluationNormal.
3c. This means changing the NextScreen metric for ScreenGameplay to use a function that checks the profiles and returns the correct screen.
4. On ScreenHeartEntry:
4a. Provide a timer that the players can watch while measuring their pulse.
4b. Provide a way for the players to enter their heart rate in beats per minute.
4c. When a player enters their heart rate, use Profile:CalculateCaloriesFromHeartRate().
4d. Do not add calories to a profile if Profile:IgnoreStepCountCalories() returns false for that profile.
4e. Pass the heart rate and the duration from ScreenGameplay to Profile:CalculateCaloriesFromHeartRate() and store its return value.
4f. Pass the return value of Profile:CalculateCaloriesFromHeartRate() to Profile:AddCaloriesToDailyTotal() to add them to the total for that player's profile.
4g. When all active players have finished entering their heart rate, proceed to ScreenEvaluationNormal.
5. This concludes the special steps required for theme support of heart rate calorie calculation.
Side notes:
CalculateCaloriesFromHeartRate and AddCaloriesToDailyTotal are separate functions so that a theme can display or use the calorie amount in other ways easily.
If IgnoreStepCountCalories is true for a profile, but the theme in use does not provide support using the steps listed above, that profile will not accumulate calories.
+174 -4
View File
@@ -51,7 +51,8 @@ static ThemeMetric<RString> 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<GoalType>(L, 1); return 0; }
static int GetGoalCalories( T* p, lua_State *L ) { lua_pushnumber(L, p->m_iGoalCalories ); return 1; }
@@ -2216,6 +2374,7 @@ public:
{
ADD_METHOD( AddScreenshot );
ADD_METHOD( GetDisplayName );
ADD_METHOD( SetDisplayName );
ADD_METHOD( GetLastUsedHighScoreName );
ADD_METHOD( SetLastUsedHighScoreName );
ADD_METHOD( GetAllUsedHighScoreNames );
@@ -2226,6 +2385,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 );
+13
View File
@@ -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