#include "global.h" #include "StatsManager.h" #include "GameState.h" #include "Foreach.h" #include "ProfileManager.h" #include "Profile.h" #include "PrefsManager.h" #include "Steps.h" #include "StyleUtil.h" StatsManager* STATSMAN = NULL; // global object accessable from anywhere in the program StatsManager::StatsManager() { } void StatsManager::Reset() { m_CurStageStats.Init(); m_vPlayedStageStats.clear(); m_AccumStageStats.Init(); CalcAccumStageStats(); } static StageStats AccumStageStats( const vector& vss ) { StageStats ssreturn; if( !vss.empty() ) { ssreturn.pStyle = vss[0].pStyle; ssreturn.playMode = vss[0].playMode; } FOREACH_CONST( StageStats, vss, ss ) ssreturn.AddStats( *ss ); unsigned uNumSongs = ssreturn.vpPlayedSongs.size(); if( uNumSongs == 0 ) return ssreturn; // don't divide by 0 below /* Scale radar percentages back down to roughly 0..1. Don't scale RadarCategory_TapsAndHolds * and the rest, which are counters. */ // FIXME: Weight each song by the number of stages it took to account for // long, marathon. FOREACH_EnabledPlayer( p ) { for( int r = 0; r < RadarCategory_TapsAndHolds; r++) { ssreturn.m_player[p].radarPossible[r] /= uNumSongs; ssreturn.m_player[p].radarActual[r] /= uNumSongs; } } FOREACH_EnabledMultiPlayer( p ) { for( int r = 0; r < RadarCategory_TapsAndHolds; r++) { ssreturn.m_multiPlayer[p].radarPossible[r] /= uNumSongs; ssreturn.m_multiPlayer[p].radarActual[r] /= uNumSongs; } } return ssreturn; } void StatsManager::GetFinalEvalStageStats( StageStats& statsOut ) const { statsOut.Init(); vector vssToCount; // Show stats only for the latest 3 normal songs + passed extra stages int PassedRegularSongsLeft = 3; for( int i = (int)m_vPlayedStageStats.size()-1; i >= 0; --i ) { const StageStats &ss = m_vPlayedStageStats[i]; if( !ss.OnePassed() ) continue; if( ss.StageType == StageStats::STAGE_NORMAL ) { if( PassedRegularSongsLeft == 0 ) break; --PassedRegularSongsLeft; } vssToCount.push_back( ss ); } statsOut = AccumStageStats( vssToCount ); } void StatsManager::CalcAccumStageStats() { m_AccumStageStats = AccumStageStats( m_vPlayedStageStats ); } /* This data is added to each player profile, and to the machine profile per-player. */ void AddPlayerStatsToProfile( Profile *pProfile, const StageStats &ss, PlayerNumber pn ) { ss.AssertValid( pn ); CHECKPOINT; StyleID sID; sID.FromStyle( ss.pStyle ); ASSERT( ss.vpPlayedSongs.size() == ss.m_player[pn].vpPlayedSteps.size() ); for( unsigned i=0; im_iNumSongsPlayedByPlayMode[ss.playMode]++; pProfile->m_iNumSongsPlayedByStyle[sID] ++; pProfile->m_iNumSongsPlayedByDifficulty[pSteps->GetDifficulty()] ++; int iMeter = clamp( pSteps->GetMeter(), 0, MAX_METER ); pProfile->m_iNumSongsPlayedByMeter[iMeter] ++; } pProfile->m_iTotalDancePoints += ss.m_player[pn].iActualDancePoints; if( ss.StageType == StageStats::STAGE_EXTRA || ss.StageType == StageStats::STAGE_EXTRA2 ) { if( ss.m_player[pn].bFailed ) ++pProfile->m_iNumExtraStagesFailed; else ++pProfile->m_iNumExtraStagesPassed; } // If you fail in a course, you passed all but the final song. // FIXME: Not true. If playing with 2 players, one player could have failed earlier. if( !ss.m_player[pn].bFailed ) { pProfile->m_iNumStagesPassedByPlayMode[ss.playMode] ++; pProfile->m_iNumStagesPassedByGrade[ss.m_player[pn].GetGrade()] ++; } } void StatsManager::CommitStatsToProfiles() { // // Add step totals. Use radarActual, since the player might have failed part way // through the song, in which case we don't want to give credit for the rest of the // song. // FOREACH_HumanPlayer( pn ) { int iNumTapsAndHolds = (int) m_CurStageStats.m_player[pn].radarActual[RadarCategory_TapsAndHolds]; int iNumJumps = (int) m_CurStageStats.m_player[pn].radarActual[RadarCategory_Jumps]; int iNumHolds = (int) m_CurStageStats.m_player[pn].radarActual[RadarCategory_Holds]; int iNumRolls = (int) m_CurStageStats.m_player[pn].radarActual[RadarCategory_Rolls]; int iNumMines = (int) m_CurStageStats.m_player[pn].radarActual[RadarCategory_Mines]; int iNumHands = (int) m_CurStageStats.m_player[pn].radarActual[RadarCategory_Hands]; float fCaloriesBurned = m_CurStageStats.m_player[pn].fCaloriesBurned; PROFILEMAN->AddStepTotals( pn, iNumTapsAndHolds, iNumJumps, iNumHolds, iNumRolls, iNumMines, iNumHands, fCaloriesBurned ); } // Update profile stats Profile* pMachineProfile = PROFILEMAN->GetMachineProfile(); int iGameplaySeconds = (int)truncf(m_CurStageStats.fGameplaySeconds); pMachineProfile->m_iTotalGameplaySeconds += iGameplaySeconds; pMachineProfile->m_iCurrentCombo = 0; pMachineProfile->m_iNumTotalSongsPlayed += m_CurStageStats.vpPlayedSongs.size(); CHECKPOINT; FOREACH_HumanPlayer( pn ) { CHECKPOINT; Profile* pPlayerProfile = PROFILEMAN->GetProfile( pn ); if( pPlayerProfile ) { pPlayerProfile->m_iTotalGameplaySeconds += iGameplaySeconds; pPlayerProfile->m_iCurrentCombo = PREFSMAN->m_bComboContinuesBetweenSongs ? m_CurStageStats.m_player[pn].iCurCombo : 0; pPlayerProfile->m_iNumTotalSongsPlayed += m_CurStageStats.vpPlayedSongs.size(); } AddPlayerStatsToProfile( pMachineProfile, m_CurStageStats, pn ); if( pPlayerProfile ) AddPlayerStatsToProfile( pPlayerProfile, m_CurStageStats, pn ); CHECKPOINT; } } // lua start #include "LuaBinding.h" class LunaStatsManager: public Luna { public: LunaStatsManager() { LUA->Register( Register ); } static int GetCurStageStats( T* p, lua_State *L ) { p->m_CurStageStats.PushSelf(L); return 1; } static int GetAccumStageStats( T* p, lua_State *L ) { p->GetAccumStageStats().PushSelf(L); return 1; } static int Reset( T* p, lua_State *L ) { p->Reset(); return 0; } static int GetFinalGrade( T* p, lua_State *L ) { PlayerNumber pn = (PlayerNumber)IArg(1); if( !GAMESTATE->IsHumanPlayer(pn) ) lua_pushnumber( L, Grade_NoData ); else { StageStats stats; p->GetFinalEvalStageStats( stats ); lua_pushnumber( L, stats.m_player[pn].GetGrade() ); } return 1; } static int GetStagesPlayed( T* p, lua_State *L ) { lua_pushnumber( L, p->m_vPlayedStageStats.size() ); return 1; } static int GetBestGrade( T* p, lua_State *L ) { Grade g = NUM_Grade; FOREACH_EnabledPlayer( pn ) g = min( g, STATSMAN->m_CurStageStats.m_player[pn].GetGrade() ); lua_pushnumber( L, g ); return 1; } static int GetWorstGrade( T* p, lua_State *L ) { Grade g = Grade_Tier01; FOREACH_EnabledPlayer( pn ) g = max( g, STATSMAN->m_CurStageStats.m_player[pn].GetGrade() ); lua_pushnumber( L, g ); return 1; } static void Register(lua_State *L) { ADD_METHOD( GetCurStageStats ); ADD_METHOD( GetAccumStageStats ); ADD_METHOD( Reset ); ADD_METHOD( GetFinalGrade ); ADD_METHOD( GetStagesPlayed ); ADD_METHOD( GetBestGrade ); ADD_METHOD( GetWorstGrade ); Luna::Register( L ); // Add global singleton if constructed already. If it's not constructed yet, // then we'll register it later when we reinit Lua just before // initializing the display. if( STATSMAN ) { lua_pushstring(L, "STATSMAN"); STATSMAN->PushSelf( L ); lua_settable(L, LUA_GLOBALSINDEX); } } }; LUA_REGISTER_CLASS( StatsManager ) // lua end // // Old Lua // #include "LuaFunctions.h" LuaFunction_NoArgs( OnePassed, STATSMAN->m_CurStageStats.OnePassed() ); LuaFunction_NoArgs( AllFailed, STATSMAN->m_CurStageStats.AllFailed() ); LuaFunction( Grade, StringToGrade( SArg(1) ) ); const StageStats *GetStageStatsN( int n ) { if( n == (int) STATSMAN->m_vPlayedStageStats.size() ) return &STATSMAN->m_CurStageStats; if( n > (int) STATSMAN->m_vPlayedStageStats.size() ) return NULL; return &STATSMAN->m_vPlayedStageStats[n]; } Grade GetBestFinalGrade() { Grade top_grade = Grade_Failed; StageStats stats; STATSMAN->GetFinalEvalStageStats( stats ); FOREACH_HumanPlayer( p ) { // If this player failed any stage, then their final grade is an F. bool bPlayerFailedOneStage = false; FOREACH_CONST( StageStats, STATSMAN->m_vPlayedStageStats, ss ) { if( ss->m_player[p].bFailed ) { bPlayerFailedOneStage = true; break; } } if( bPlayerFailedOneStage ) continue; top_grade = min( top_grade, stats.m_player[p].GetGrade() ); } return top_grade; } LuaFunction_NoArgs( GetBestFinalGrade, GetBestFinalGrade() ); /* * (c) 2001-2004 Chris Danford * 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. */