diff --git a/stepmania/src/Makefile.am b/stepmania/src/Makefile.am index 35d3b53eef..de379d4006 100644 --- a/stepmania/src/Makefile.am +++ b/stepmania/src/Makefile.am @@ -75,6 +75,7 @@ NoteFieldPositioning.cpp NoteFieldPositioning.h NoteTypes.cpp NoteTypes.h NotesL NotesLoaderBMS.cpp NotesLoaderBMS.h NotesLoaderDWI.cpp NotesLoaderDWI.h NotesLoaderKSF.cpp NotesLoaderKSF.h \ NotesLoaderSM.cpp NotesLoaderSM.h NotesWriterDWI.cpp NotesWriterDWI.h NotesWriterSM.cpp NotesWriterSM.h \ PlayerAI.cpp PlayerAI.h PlayerNumber.cpp PlayerNumber.h PlayerOptions.cpp PlayerOptions.h \ +PlayerStageStats.cpp PlayerStageStats.h \ PlayerState.cpp PlayerState.h Preference.cpp Preference.h Profile.cpp Profile.h \ RandomSample.cpp RandomSample.h RadarValues.cpp RadarValues.h ScreenDimensions.h ScreenDimensions.cpp \ ScoreKeeper.h ScoreKeeperMAX2.cpp ScoreKeeperMAX2.h \ diff --git a/stepmania/src/PlayerStageStats.cpp b/stepmania/src/PlayerStageStats.cpp new file mode 100644 index 0000000000..cfb9d9f73e --- /dev/null +++ b/stepmania/src/PlayerStageStats.cpp @@ -0,0 +1,403 @@ +#include "global.h" +#include "PlayerStageStats.h" +#include "RageLog.h" +#include "PrefsManager.h" +#include "Foreach.h" + +void PlayerStageStats::Init() +{ + vpSteps.clear(); + fAliveSeconds = 0; + bFailed = bFailedEarlier = false; + iPossibleDancePoints = iActualDancePoints = 0; + iCurCombo = iMaxCombo = iCurMissCombo = iScore = iBonus = iMaxScore = iCurMaxScore = 0; + fSecondsBeforeFail = 0; + iSongsPassed = iSongsPlayed = 0; + iTotalError = 0; + fCaloriesBurned = 0; + + ZERO( iTapNoteScores ); + ZERO( iHoldNoteScores ); + radarPossible.Zero(); + radarActual.Zero(); + + fFirstSecond = 999999; + fLastSecond = 0; +} + +void PlayerStageStats::AddStats( const PlayerStageStats& other ) +{ + FOREACH_CONST( Steps*, other.vpSteps, s ) + vpSteps.push_back( *s ); + fAliveSeconds += other.fAliveSeconds; + bFailed |= other.bFailed; + bFailedEarlier |= other.bFailedEarlier; + iPossibleDancePoints += other.iPossibleDancePoints; + iActualDancePoints += other.iActualDancePoints; + + for( int t=0; t::const_iterator it; + for( it = other.fLifeRecord.begin(); it != other.fLifeRecord.end(); ++it ) + { + const float pos = it->first; + const float life = it->second; + fLifeRecord[fOtherFirstSecond+pos] = life; + } + + for( unsigned i=0; i 0.001 ) + continue; + + /* These are really the same combo. */ + prevcombo.fSizeSeconds += combo.fSizeSeconds; + prevcombo.cnt += combo.cnt; + ComboList.erase( ComboList.begin()+i ); + --i; + } +} + +Grade PlayerStageStats::GetGrade() const +{ + if( bFailedEarlier ) + return GRADE_FAILED; + + /* XXX: This entire calculation should be in ScoreKeeper, but final evaluation + * is tricky since at that point the ScoreKeepers no longer exist. + * + * See http://www.aaroninjapan.com/ddr2.html ("Regular play scoring") */ + float Possible = 0, Actual = 0; + FOREACH_TapNoteScore( tns ) + { + int iTapScoreValue; + switch( tns ) + { + case TNS_NONE: iTapScoreValue = 0; break; + case TNS_HIT_MINE: iTapScoreValue = PREFSMAN->m_iGradeWeightHitMine; break; + case TNS_MISS: iTapScoreValue = PREFSMAN->m_iGradeWeightMiss; break; + case TNS_BOO: iTapScoreValue = PREFSMAN->m_iGradeWeightBoo; break; + case TNS_GOOD: iTapScoreValue = PREFSMAN->m_iGradeWeightGood; break; + case TNS_GREAT: iTapScoreValue = PREFSMAN->m_iGradeWeightGreat; break; + case TNS_PERFECT: iTapScoreValue = PREFSMAN->m_iGradeWeightPerfect; break; + case TNS_MARVELOUS: iTapScoreValue = PREFSMAN->m_iGradeWeightMarvelous; break; + default: FAIL_M( ssprintf("%i", tns) ); break; + } + Actual += iTapNoteScores[tns] * iTapScoreValue; + Possible += iTapNoteScores[tns] * PREFSMAN->m_iGradeWeightMarvelous; + } + + FOREACH_HoldNoteScore( hns ) + { + int iHoldScoreValue; + switch( hns ) + { + case HNS_NONE: iHoldScoreValue = 0; break; + case HNS_NG: iHoldScoreValue = PREFSMAN->m_iGradeWeightNG; break; + case HNS_OK: iHoldScoreValue = PREFSMAN->m_iGradeWeightOK; break; + default: FAIL_M( ssprintf("%i", hns) ); break; + } + Actual += iHoldNoteScores[hns] * iHoldScoreValue; + Possible += iHoldNoteScores[hns] * PREFSMAN->m_iGradeWeightOK; + } + + LOG->Trace( "GetGrade: Actual: %f, Possible: %f", Actual, Possible ); + + Grade grade = GRADE_FAILED; + + float fPercent = (Possible == 0) ? 0 : Actual / Possible; + + FOREACH_Grade(g) + { + if( fPercent >= PREFSMAN->m_fGradePercent[g] ) + { + grade = g; + break; + } + } + + LOG->Trace( "GetGrade: Grade: %s, %i", GradeToString(grade).c_str(), PREFSMAN->m_bGradeTier02IsAllPerfects ); + if( PREFSMAN->m_bGradeTier02IsAllPerfects ) + { + if( iTapNoteScores[TNS_MARVELOUS] > 0 && + iTapNoteScores[TNS_PERFECT] == 0 && + iTapNoteScores[TNS_GREAT] == 0 && + iTapNoteScores[TNS_GOOD] == 0 && + iTapNoteScores[TNS_BOO] == 0 && + iTapNoteScores[TNS_MISS] == 0 && + iTapNoteScores[TNS_HIT_MINE] == 0 && + iHoldNoteScores[HNS_NG] == 0 ) + return GRADE_TIER_1; + + if( iTapNoteScores[TNS_PERFECT] > 0 && + iTapNoteScores[TNS_GREAT] == 0 && + iTapNoteScores[TNS_GOOD] == 0 && + iTapNoteScores[TNS_BOO] == 0 && + iTapNoteScores[TNS_MISS] == 0 && + iTapNoteScores[TNS_HIT_MINE] == 0 && + iHoldNoteScores[HNS_NG] == 0 ) + return GRADE_TIER_2; + + return max( grade, GRADE_TIER_3 ); + } + + return grade; +} + +float PlayerStageStats::GetPercentDancePoints() const +{ + if( iPossibleDancePoints == 0 ) + return 0; // div/0 + + if( iActualDancePoints == iPossibleDancePoints ) + return 1; // correct for rounding error + + /* This can happen in battle, with transform attacks. */ + //ASSERT_M( iActualDancePoints <= iPossibleDancePoints, ssprintf("%i/%i", iActualDancePoints, iPossibleDancePoints) ); + + float fPercentDancePoints = iActualDancePoints / (float)iPossibleDancePoints; + + return fPercentDancePoints; +} + +void PlayerStageStats::SetLifeRecordAt( float fLife, float fSecond ) +{ + if( fSecond < 0 ) + return; + + fFirstSecond = min( fSecond, fFirstSecond ); + fLastSecond = max( fSecond, fLastSecond ); + //LOG->Trace( "fLastSecond = %f", fLastSecond ); + + if( !fLifeRecord.empty() ) + { + const float old = GetLifeRecordAt( fSecond ); + if( fabsf(old-fSecond) < 0.001f ) + return; /* no change */ + } + + fLifeRecord[fSecond] = fLife; +} + +float PlayerStageStats::GetLifeRecordAt( float fSecond ) const +{ + /* Find the first element whose key is not less than k. */ + map::const_iterator it = fLifeRecord.lower_bound( fSecond ); + + /* Find the first element whose key is less than k. */ + if( it != fLifeRecord.begin() ) + --it; + + return it->second; + +} + +float PlayerStageStats::GetLifeRecordLerpAt( float fSecond ) const +{ + /* Find the first element whose key is not less than k. */ + map::const_iterator later = fLifeRecord.lower_bound( fSecond ); + + /* Find the first element whose key is less than k. */ + map::const_iterator earlier = later; + if( earlier != fLifeRecord.begin() ) + --earlier; + + if( earlier->first == later->first ) + return earlier->second; + + /* earlier <= pos <= later */ + const float f = SCALE( fSecond, earlier->first, later->first, 1, 0 ); + return earlier->second * f + later->second * (1-f); +} + + +void PlayerStageStats::GetLifeRecord( float *fLifeOut, int iNumSamples ) const +{ + for( int i = 0; i < iNumSamples; ++i ) + { + float from = SCALE( i, 0, (float)iNumSamples, fFirstSecond, fLastSecond ); + fLifeOut[i] = GetLifeRecordLerpAt( from ); + } +} + +/* If "rollover" is true, we're being called before gameplay begins, so we can record + * the amount of the first combo that comes from the previous song. */ +void PlayerStageStats::UpdateComboList( float fSecond, bool rollover ) +{ + if( fSecond < 0 ) + return; + + if( !rollover ) + { + fFirstSecond = min( fSecond, fFirstSecond ); + fLastSecond = max( fSecond, fLastSecond ); + //LOG->Trace( "fLastSecond = %f", fLastSecond ); + } + + int cnt = iCurCombo; + if( !cnt ) + return; /* no combo */ + + if( ComboList.size() == 0 || ComboList.back().cnt >= cnt ) + { + /* If the previous combo (if any) starts on -9999, then we rolled over some + * combo, but missed the first step. Remove it. */ + if( ComboList.size() && ComboList.back().fStartSecond == -9999 ) + ComboList.erase( ComboList.begin()+ComboList.size()-1, ComboList.end() ); + + /* This is a new combo. */ + Combo_t NewCombo; + /* "start" is the position that the combo started within this song. If we're + * recording rollover, the combo hasn't started yet (within this song), so put + * a placeholder in and set it on the next call. (Otherwise, start will be less + * than fFirstPos.) */ + if( rollover ) + NewCombo.fStartSecond = -9999; + else + NewCombo.fStartSecond = fSecond; + ComboList.push_back( NewCombo ); + } + + Combo_t &combo = ComboList.back(); + combo.fSizeSeconds = fSecond - combo.fStartSecond; + combo.cnt = cnt; + if( !rollover && combo.fStartSecond == -9999 ) + combo.fStartSecond = fSecond; + + if( rollover ) + combo.rollover = cnt; +} + +/* This returns the largest combo contained within the song, as if + * m_bComboContinuesBetweenSongs is turned off. */ +PlayerStageStats::Combo_t PlayerStageStats::GetMaxCombo() const +{ + if( ComboList.size() == 0 ) + return Combo_t(); + + int m = 0; + for( unsigned i = 1; i < ComboList.size(); ++i ) + { + if( ComboList[i].cnt > ComboList[m].cnt ) + m = i; + } + + return ComboList[m]; +} + +int PlayerStageStats::GetComboAtStartOfStage() const +{ + if( ComboList.empty() ) + return 0; + else + return ComboList[0].rollover; +} + +bool PlayerStageStats::FullComboOfScore( TapNoteScore tnsAllGreaterOrEqual ) const +{ + ASSERT( tnsAllGreaterOrEqual >= TNS_GREAT ); + ASSERT( tnsAllGreaterOrEqual <= TNS_MARVELOUS ); + + if( iHoldNoteScores[HNS_NG] > 0 ) + return false; + + for( int i=TNS_MISS; i 0 ) + return false; + } + return true; +} + +bool PlayerStageStats::SingleDigitsOfScore( TapNoteScore tnsAllGreaterOrEqual ) const +{ + return FullComboOfScore( tnsAllGreaterOrEqual ) && + iTapNoteScores[tnsAllGreaterOrEqual] < 10; +} + +bool PlayerStageStats::OneOfScore( TapNoteScore tnsAllGreaterOrEqual ) const +{ + return FullComboOfScore( tnsAllGreaterOrEqual ) && + iTapNoteScores[tnsAllGreaterOrEqual] == 1; +} + +int PlayerStageStats::GetTotalTaps() const +{ + int iTotalTaps = 0; + for( int i=TNS_MISS; i +class Steps; + +struct PlayerStageStats +{ + PlayerStageStats() { Init(); } + void Init(); + + void AddStats( const PlayerStageStats& other ); // accumulate + + Grade GetGrade() const; + float GetPercentDancePoints() const; + vector vpSteps; + float fAliveSeconds; // how far into the music did they last before failing? Updated by Gameplay, scaled by music rate. + + /* Set if the player actually failed at any point during the song. This is always + * false in FAIL_OFF. If recovery is enabled and two players are playing, + * this is only set if both players were failing at the same time. */ + bool bFailed; + + /* This indicates whether the player bottomed out his bar/ran out of lives at some + * point during the song. It's set in all fail modes. */ + bool bFailedEarlier; + int iPossibleDancePoints; + int iActualDancePoints; + int iTapNoteScores[NUM_TAP_NOTE_SCORES]; + int iHoldNoteScores[NUM_HOLD_NOTE_SCORES]; + int iCurCombo; + int iMaxCombo; + int iCurMissCombo; + int iScore; + int iCurMaxScore; + int iMaxScore; + int iBonus; // bonus to be added on screeneval + RadarValues radarPossible; // filled in by ScreenGameplay on start of notes + RadarValues radarActual; + float fSecondsBeforeFail; // -1 means didn't/hasn't failed + /* The number of songs played and passed, respectively. */ + int iSongsPassed; + int iSongsPlayed; + int iTotalError; + + // workout + float fCaloriesBurned; + + map fLifeRecord; + void SetLifeRecordAt( float fLife, float fSecond ); + void GetLifeRecord( float *fLifeOut, int iNumSamples ) const; + float GetLifeRecordAt( float fSecond ) const; + float GetLifeRecordLerpAt( float fSecond ) const; + + struct Combo_t + { + /* Start and size of this combo, in the same scale as the combo list mapping and + * the life record. */ + float fStartSecond, fSizeSeconds; + + /* Combo size, in steps. */ + int cnt; + + /* Size of the combo that didn't come from this stage (rollover from the last song). + * (This is a subset of cnt.) */ + int rollover; + + /* Get the size of the combo that came from this song. */ + int GetStageCnt() const { return cnt - rollover; } + + Combo_t(): fStartSecond(0), fSizeSeconds(0), cnt(0), rollover(0) { } + bool IsZero() const { return fStartSecond < 0; } + }; + vector ComboList; + float fFirstSecond; + float fLastSecond; + + int GetComboAtStartOfStage() const; + bool FullComboOfScore( TapNoteScore tnsAllGreaterOrEqual ) const; + bool FullCombo() const { return FullComboOfScore(TNS_GREAT); } + bool SingleDigitsOfScore( TapNoteScore tnsAllGreaterOrEqual ) const; + bool OneOfScore( TapNoteScore tnsAllGreaterOrEqual ) const; + int GetTotalTaps() const; + float GetPercentageOfTaps( TapNoteScore tns ) const; + void UpdateComboList( float fSecond, bool rollover ); + Combo_t GetMaxCombo() const; +}; + + +#endif + +/* + * (c) 2001-2004 Chris Danford, Glenn Maynard + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, provided that the above + * copyright notice(s) and this permission notice appear in all copies of + * the Software and that both the above copyright notice(s) and this + * permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF + * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS + * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT + * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ diff --git a/stepmania/src/StageStats.cpp b/stepmania/src/StageStats.cpp index 790d0a9c52..e113633128 100644 --- a/stepmania/src/StageStats.cpp +++ b/stepmania/src/StageStats.cpp @@ -1,10 +1,6 @@ #include "global.h" #include "StageStats.h" #include "GameState.h" -#include "RageLog.h" -#include "SongManager.h" -#include "RageUtil.h" -#include "PrefsManager.h" #include "Foreach.h" #include "Steps.h" #include "song.h" @@ -12,182 +8,6 @@ StageStats g_CurStageStats; vector g_vPlayedStageStats; -void PlayerStageStats::Init() -{ - vpSteps.clear(); - fAliveSeconds = 0; - bFailed = bFailedEarlier = false; - iPossibleDancePoints = iActualDancePoints = 0; - iCurCombo = iMaxCombo = iCurMissCombo = iScore = iBonus = iMaxScore = iCurMaxScore = 0; - fSecondsBeforeFail = 0; - iSongsPassed = iSongsPlayed = 0; - iTotalError = 0; - fCaloriesBurned = 0; - - ZERO( iTapNoteScores ); - ZERO( iHoldNoteScores ); - radarPossible.Zero(); - radarActual.Zero(); - - fFirstSecond = 999999; - fLastSecond = 0; -} - -void PlayerStageStats::AddStats( const PlayerStageStats& other ) -{ - FOREACH_CONST( Steps*, other.vpSteps, s ) - vpSteps.push_back( *s ); - fAliveSeconds += other.fAliveSeconds; - bFailed |= other.bFailed; - bFailedEarlier |= other.bFailedEarlier; - iPossibleDancePoints += other.iPossibleDancePoints; - iActualDancePoints += other.iActualDancePoints; - - for( int t=0; t::const_iterator it; - for( it = other.fLifeRecord.begin(); it != other.fLifeRecord.end(); ++it ) - { - const float pos = it->first; - const float life = it->second; - fLifeRecord[fOtherFirstSecond+pos] = life; - } - - for( unsigned i=0; i 0.001 ) - continue; - - /* These are really the same combo. */ - prevcombo.fSizeSeconds += combo.fSizeSeconds; - prevcombo.cnt += combo.cnt; - ComboList.erase( ComboList.begin()+i ); - --i; - } -} - -Grade PlayerStageStats::GetGrade() const -{ - if( bFailedEarlier ) - return GRADE_FAILED; - - /* XXX: This entire calculation should be in ScoreKeeper, but final evaluation - * is tricky since at that point the ScoreKeepers no longer exist. - * - * See http://www.aaroninjapan.com/ddr2.html ("Regular play scoring") */ - float Possible = 0, Actual = 0; - FOREACH_TapNoteScore( tns ) - { - int iTapScoreValue; - switch( tns ) - { - case TNS_NONE: iTapScoreValue = 0; break; - case TNS_HIT_MINE: iTapScoreValue = PREFSMAN->m_iGradeWeightHitMine; break; - case TNS_MISS: iTapScoreValue = PREFSMAN->m_iGradeWeightMiss; break; - case TNS_BOO: iTapScoreValue = PREFSMAN->m_iGradeWeightBoo; break; - case TNS_GOOD: iTapScoreValue = PREFSMAN->m_iGradeWeightGood; break; - case TNS_GREAT: iTapScoreValue = PREFSMAN->m_iGradeWeightGreat; break; - case TNS_PERFECT: iTapScoreValue = PREFSMAN->m_iGradeWeightPerfect; break; - case TNS_MARVELOUS: iTapScoreValue = PREFSMAN->m_iGradeWeightMarvelous; break; - default: FAIL_M( ssprintf("%i", tns) ); break; - } - Actual += iTapNoteScores[tns] * iTapScoreValue; - Possible += iTapNoteScores[tns] * PREFSMAN->m_iGradeWeightMarvelous; - } - - FOREACH_HoldNoteScore( hns ) - { - int iHoldScoreValue; - switch( hns ) - { - case HNS_NONE: iHoldScoreValue = 0; break; - case HNS_NG: iHoldScoreValue = PREFSMAN->m_iGradeWeightNG; break; - case HNS_OK: iHoldScoreValue = PREFSMAN->m_iGradeWeightOK; break; - default: FAIL_M( ssprintf("%i", hns) ); break; - } - Actual += iHoldNoteScores[hns] * iHoldScoreValue; - Possible += iHoldNoteScores[hns] * PREFSMAN->m_iGradeWeightOK; - } - - LOG->Trace( "GetGrade: Actual: %f, Possible: %f", Actual, Possible ); - - Grade grade = GRADE_FAILED; - - float fPercent = (Possible == 0) ? 0 : Actual / Possible; - - FOREACH_Grade(g) - { - if( fPercent >= PREFSMAN->m_fGradePercent[g] ) - { - grade = g; - break; - } - } - - LOG->Trace( "GetGrade: Grade: %s, %i", GradeToString(grade).c_str(), PREFSMAN->m_bGradeTier02IsAllPerfects ); - if( PREFSMAN->m_bGradeTier02IsAllPerfects ) - { - if( iTapNoteScores[TNS_MARVELOUS] > 0 && - iTapNoteScores[TNS_PERFECT] == 0 && - iTapNoteScores[TNS_GREAT] == 0 && - iTapNoteScores[TNS_GOOD] == 0 && - iTapNoteScores[TNS_BOO] == 0 && - iTapNoteScores[TNS_MISS] == 0 && - iTapNoteScores[TNS_HIT_MINE] == 0 && - iHoldNoteScores[HNS_NG] == 0 ) - return GRADE_TIER_1; - - if( iTapNoteScores[TNS_PERFECT] > 0 && - iTapNoteScores[TNS_GREAT] == 0 && - iTapNoteScores[TNS_GOOD] == 0 && - iTapNoteScores[TNS_BOO] == 0 && - iTapNoteScores[TNS_MISS] == 0 && - iTapNoteScores[TNS_HIT_MINE] == 0 && - iHoldNoteScores[HNS_NG] == 0 ) - return GRADE_TIER_2; - - return max( grade, GRADE_TIER_3 ); - } - - return grade; -} - - void StageStats::Init() { playMode = PLAY_MODE_INVALID; @@ -261,203 +81,6 @@ bool StageStats::AllFailedEarlier() const return true; } -float PlayerStageStats::GetPercentDancePoints() const -{ - if( iPossibleDancePoints == 0 ) - return 0; // div/0 - - if( iActualDancePoints == iPossibleDancePoints ) - return 1; // correct for rounding error - - /* This can happen in battle, with transform attacks. */ - //ASSERT_M( iActualDancePoints <= iPossibleDancePoints, ssprintf("%i/%i", iActualDancePoints, iPossibleDancePoints) ); - - float fPercentDancePoints = iActualDancePoints / (float)iPossibleDancePoints; - - return fPercentDancePoints; -} - -void PlayerStageStats::SetLifeRecordAt( float fLife, float fSecond ) -{ - if( fSecond < 0 ) - return; - - fFirstSecond = min( fSecond, fFirstSecond ); - fLastSecond = max( fSecond, fLastSecond ); - //LOG->Trace( "fLastSecond = %f", fLastSecond ); - - if( !fLifeRecord.empty() ) - { - const float old = GetLifeRecordAt( fSecond ); - if( fabsf(old-fSecond) < 0.001f ) - return; /* no change */ - } - - fLifeRecord[fSecond] = fLife; -} - -float PlayerStageStats::GetLifeRecordAt( float fSecond ) const -{ - /* Find the first element whose key is not less than k. */ - map::const_iterator it = fLifeRecord.lower_bound( fSecond ); - - /* Find the first element whose key is less than k. */ - if( it != fLifeRecord.begin() ) - --it; - - return it->second; - -} - -float PlayerStageStats::GetLifeRecordLerpAt( float fSecond ) const -{ - /* Find the first element whose key is not less than k. */ - map::const_iterator later = fLifeRecord.lower_bound( fSecond ); - - /* Find the first element whose key is less than k. */ - map::const_iterator earlier = later; - if( earlier != fLifeRecord.begin() ) - --earlier; - - if( earlier->first == later->first ) - return earlier->second; - - /* earlier <= pos <= later */ - const float f = SCALE( fSecond, earlier->first, later->first, 1, 0 ); - return earlier->second * f + later->second * (1-f); -} - - -void PlayerStageStats::GetLifeRecord( float *fLifeOut, int iNumSamples ) const -{ - for( int i = 0; i < iNumSamples; ++i ) - { - float from = SCALE( i, 0, (float)iNumSamples, fFirstSecond, fLastSecond ); - fLifeOut[i] = GetLifeRecordLerpAt( from ); - } -} - -/* If "rollover" is true, we're being called before gameplay begins, so we can record - * the amount of the first combo that comes from the previous song. */ -void PlayerStageStats::UpdateComboList( float fSecond, bool rollover ) -{ - if( fSecond < 0 ) - return; - - if( !rollover ) - { - fFirstSecond = min( fSecond, fFirstSecond ); - fLastSecond = max( fSecond, fLastSecond ); - //LOG->Trace( "fLastSecond = %f", fLastSecond ); - } - - int cnt = iCurCombo; - if( !cnt ) - return; /* no combo */ - - if( ComboList.size() == 0 || ComboList.back().cnt >= cnt ) - { - /* If the previous combo (if any) starts on -9999, then we rolled over some - * combo, but missed the first step. Remove it. */ - if( ComboList.size() && ComboList.back().fStartSecond == -9999 ) - ComboList.erase( ComboList.begin()+ComboList.size()-1, ComboList.end() ); - - /* This is a new combo. */ - Combo_t NewCombo; - /* "start" is the position that the combo started within this song. If we're - * recording rollover, the combo hasn't started yet (within this song), so put - * a placeholder in and set it on the next call. (Otherwise, start will be less - * than fFirstPos.) */ - if( rollover ) - NewCombo.fStartSecond = -9999; - else - NewCombo.fStartSecond = fSecond; - ComboList.push_back( NewCombo ); - } - - Combo_t &combo = ComboList.back(); - combo.fSizeSeconds = fSecond - combo.fStartSecond; - combo.cnt = cnt; - if( !rollover && combo.fStartSecond == -9999 ) - combo.fStartSecond = fSecond; - - if( rollover ) - combo.rollover = cnt; -} - -/* This returns the largest combo contained within the song, as if - * m_bComboContinuesBetweenSongs is turned off. */ -PlayerStageStats::Combo_t PlayerStageStats::GetMaxCombo() const -{ - if( ComboList.size() == 0 ) - return Combo_t(); - - int m = 0; - for( unsigned i = 1; i < ComboList.size(); ++i ) - { - if( ComboList[i].cnt > ComboList[m].cnt ) - m = i; - } - - return ComboList[m]; -} - -int PlayerStageStats::GetComboAtStartOfStage() const -{ - if( ComboList.empty() ) - return 0; - else - return ComboList[0].rollover; -} - -bool PlayerStageStats::FullComboOfScore( TapNoteScore tnsAllGreaterOrEqual ) const -{ - ASSERT( tnsAllGreaterOrEqual >= TNS_GREAT ); - ASSERT( tnsAllGreaterOrEqual <= TNS_MARVELOUS ); - - if( iHoldNoteScores[HNS_NG] > 0 ) - return false; - - for( int i=TNS_MISS; i 0 ) - return false; - } - return true; -} - -bool PlayerStageStats::SingleDigitsOfScore( TapNoteScore tnsAllGreaterOrEqual ) const -{ - return FullComboOfScore( tnsAllGreaterOrEqual ) && - iTapNoteScores[tnsAllGreaterOrEqual] < 10; -} - -bool PlayerStageStats::OneOfScore( TapNoteScore tnsAllGreaterOrEqual ) const -{ - return FullComboOfScore( tnsAllGreaterOrEqual ) && - iTapNoteScores[tnsAllGreaterOrEqual] == 1; -} - -int PlayerStageStats::GetTotalTaps() const -{ - int iTotalTaps = 0; - for( int i=TNS_MISS; i +#include "PlayerStageStats.h" class Song; -class Steps; class Style; -struct PlayerStageStats -{ - PlayerStageStats() { Init(); } - void Init(); - - void AddStats( const PlayerStageStats& other ); // accumulate - - Grade GetGrade() const; - float GetPercentDancePoints() const; - vector vpSteps; - float fAliveSeconds; // how far into the music did they last before failing? Updated by Gameplay, scaled by music rate. - - /* Set if the player actually failed at any point during the song. This is always - * false in FAIL_OFF. If recovery is enabled and two players are playing, - * this is only set if both players were failing at the same time. */ - bool bFailed; - - /* This indicates whether the player bottomed out his bar/ran out of lives at some - * point during the song. It's set in all fail modes. */ - bool bFailedEarlier; - int iPossibleDancePoints; - int iActualDancePoints; - int iTapNoteScores[NUM_TAP_NOTE_SCORES]; - int iHoldNoteScores[NUM_HOLD_NOTE_SCORES]; - int iCurCombo; - int iMaxCombo; - int iCurMissCombo; - int iScore; - int iCurMaxScore; - int iMaxScore; - int iBonus; // bonus to be added on screeneval - RadarValues radarPossible; // filled in by ScreenGameplay on start of notes - RadarValues radarActual; - float fSecondsBeforeFail; // -1 means didn't/hasn't failed - /* The number of songs played and passed, respectively. */ - int iSongsPassed; - int iSongsPlayed; - int iTotalError; - - // workout - float fCaloriesBurned; - - map fLifeRecord; - void SetLifeRecordAt( float fLife, float fSecond ); - void GetLifeRecord( float *fLifeOut, int iNumSamples ) const; - float GetLifeRecordAt( float fSecond ) const; - float GetLifeRecordLerpAt( float fSecond ) const; - - struct Combo_t - { - /* Start and size of this combo, in the same scale as the combo list mapping and - * the life record. */ - float fStartSecond, fSizeSeconds; - - /* Combo size, in steps. */ - int cnt; - - /* Size of the combo that didn't come from this stage (rollover from the last song). - * (This is a subset of cnt.) */ - int rollover; - - /* Get the size of the combo that came from this song. */ - int GetStageCnt() const { return cnt - rollover; } - - Combo_t(): fStartSecond(0), fSizeSeconds(0), cnt(0), rollover(0) { } - bool IsZero() const { return fStartSecond < 0; } - }; - vector ComboList; - float fFirstSecond; - float fLastSecond; - - int GetComboAtStartOfStage() const; - bool FullComboOfScore( TapNoteScore tnsAllGreaterOrEqual ) const; - bool FullCombo() const { return FullComboOfScore(TNS_GREAT); } - bool SingleDigitsOfScore( TapNoteScore tnsAllGreaterOrEqual ) const; - bool OneOfScore( TapNoteScore tnsAllGreaterOrEqual ) const; - int GetTotalTaps() const; - float GetPercentageOfTaps( TapNoteScore tns ) const; - void UpdateComboList( float fSecond, bool rollover ); - Combo_t GetMaxCombo() const; -}; struct StageStats { diff --git a/stepmania/src/StepMania.vcproj b/stepmania/src/StepMania.vcproj index e9837b56fa..79f35eb8ff 100644 --- a/stepmania/src/StepMania.vcproj +++ b/stepmania/src/StepMania.vcproj @@ -824,6 +824,12 @@ cl /Zl /nologo /c verstub.cpp /Fo"$(IntDir)"\ + + + +