#include "global.h" #include "NoteDataWithScoring.h" #include "NoteData.h" #include "PlayerStageStats.h" #include "GameConstantsAndTypes.h" #include "ThemeMetric.h" #include "RageLog.h" namespace { //ThemeMetric LAST_OR_MINIMUM_TNS ("Gameplay","LastOrMinimumTapNoteScore"); static ThemeMetric MIN_SCORE_TO_MAINTAIN_COMBO( "Gameplay", "MinScoreToMaintainCombo" ); int GetNumTapNotesWithScore( const NoteData &in, TapNoteScore tns, int iStartIndex = 0, int iEndIndex = MAX_NOTE_ROW ) { int iNumSuccessfulTapNotes = 0; for( int t=0; t= tns ) iNumSuccessfulTapNotes++; } } return iNumSuccessfulTapNotes; } int GetNumNWithScore( const NoteData &in, TapNoteScore tns, int MinTaps, int iStartRow = 0, int iEndRow = MAX_NOTE_ROW ) { int iNumSuccessfulDoubles = 0; FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( in, r, iStartRow, iEndRow ) { int iNumNotesInRow = in.GetNumTracksWithTapOrHoldHead( r ); TapNoteScore tnsRow = NoteDataWithScoring::LastTapNoteWithResult( in, r ).result.tns; if( iNumNotesInRow >= MinTaps && tnsRow >= tns ) iNumSuccessfulDoubles++; } return iNumSuccessfulDoubles; } int GetNumHoldNotesWithScore( const NoteData &in, TapNote::SubType subType, HoldNoteScore hns ) { ASSERT( subType != TapNote::SubType_Invalid ); int iNumSuccessfulHolds = 0; for( int t=0; tsecond; if( tn.type != TapNote::hold_head ) continue; if( tn.subType != subType ) continue; if( tn.HoldResult.hns == hns ) ++iNumSuccessfulHolds; } } return iNumSuccessfulHolds; } int GetSuccessfulMines( const NoteData &in, int iStartIndex = 0, int iEndIndex = MAX_NOTE_ROW ) { int iNumSuccessfulMinesNotes = 0; NoteData::all_tracks_const_iterator iter = in.GetTapNoteRangeAllTracks( iStartIndex, iEndIndex ); for( ; !iter.IsAtEnd(); ++iter ) { if( iter->type == TapNote::mine && iter->result.tns == TNS_AvoidMine ) ++iNumSuccessfulMinesNotes; } return iNumSuccessfulMinesNotes; } // See NoteData::GetNumHands(). int GetSuccessfulHands( const NoteData &in, int iStartIndex = 0, int iEndIndex = MAX_NOTE_ROW ) { int iNum = 0; FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( in, i, iStartIndex, iEndIndex ) { if( !in.RowNeedsHands(i) ) continue; bool Missed = false; for( int t=0; ttype == TapNote::lift && iter->result.tns >= tns ) ++iNumSuccessfulLiftNotes; } return iNumSuccessfulLiftNotes; } /* Return the last tap score of a row: the grade of the tap that completed * the row. If the row has no tap notes, return -1. If any tap notes aren't * graded (any tap is TNS_None) or are missed (TNS_Miss), return it. */ int LastTapNoteScoreTrack( const NoteData &in, unsigned iRow, PlayerNumber pn ) { float scoretime = -9999; int best_track = -1; for( int t=0; t scoretime) continue; // huh -aj // enum compare against lowestTNS here //if( tns < lowestTNS ) continue; scoretime = tm; worst_track = t; } return worst_track; } #endif } const TapNote &NoteDataWithScoring::LastTapNoteWithResult( const NoteData &in, unsigned iRow ) { // Allow this to be configurable between LastTapNoteScoreTrack and // MinTapNoteScore; this change inspired by PumpMania (Zmey, et al) -aj /* LOG->Trace( ssprintf("hi i'm NoteDataWithScoring::LastTapNoteWithResult(NoteData in, iRow=%i, PlayerNumber pn)", iRow) ); int iTrack = 0; switch(LAST_OR_MINIMUM_TNS) { case TapNoteScoreJudgeType_MinimumScore: iTrack = MinTapNoteScoreTrack( in, iRow, pn ); LOG->Trace( ssprintf("TapNoteScoreJudgeType_MinimumScore omg iTrack is %i and iRow is %i",iTrack,iRow) ); break; case TapNoteScoreJudgeType_LastScore: default: iTrack = LastTapNoteScoreTrack( in, iRow, pn ); break; } */ int iTrack = LastTapNoteScoreTrack( in, iRow, PLAYER_INVALID ); if( iTrack == -1 ) return TAP_EMPTY; //LOG->Trace( ssprintf("returning in.GetTapNote(iTrack=%i, iRow=%i)", iTrack, iRow) ); return in.GetTapNote( iTrack, iRow ); } /* Return the minimum tap score of a row. If the row isn't complete (not all * taps have been hit), return TNS_None or TNS_Miss. */ TapNoteScore NoteDataWithScoring::MinTapNoteScore( const NoteData &in, unsigned row ) { //LOG->Trace("Hey I'm NoteDataWithScoring::MinTapNoteScore"); TapNoteScore score = TNS_W1; for( int t=0; tTrace( ssprintf("OMG score is?? %s",TapNoteScoreToString(score).c_str()) ); return score; } bool NoteDataWithScoring::IsRowCompletelyJudged( const NoteData &in, unsigned row ) { return MinTapNoteScore( in, row ) >= TNS_Miss; } namespace { // Return the ratio of actual to possible Bs. float GetActualStreamRadarValue( const NoteData &in, float fSongSeconds ) { int iTotalSteps = in.GetNumTapNotes(); if( iTotalSteps == 0 ) return 1.0f; const int iW2s = GetNumTapNotesWithScore( in, TNS_W2 ); return clamp( float(iW2s)/iTotalSteps, 0.0f, 1.0f ); } // Return the ratio of actual combo to max combo. float GetActualVoltageRadarValue( const NoteData &in, float fSongSeconds, const PlayerStageStats &pss ) { /* STATSMAN->m_CurStageStats.iMaxCombo is unrelated to GetNumTapNotes: * m_bComboContinuesBetweenSongs might be on, and the way combo is counted * varies depending on the mode and score keeper. Instead, let's use the * length of the longest recorded combo. This is only subtly different: * it's the percent of the song the longest combo took to get. */ const PlayerStageStats::Combo_t MaxCombo = pss.GetMaxCombo(); float fComboPercent = SCALE( MaxCombo.m_fSizeSeconds, 0, pss.m_fLastSecond-pss.m_fFirstSecond, 0.0f, 1.0f ); return clamp( fComboPercent, 0.0f, 1.0f ); } // Return the ratio of actual to possible W2s on jumps. float GetActualAirRadarValue( const NoteData &in, float fSongSeconds ) { const int iTotalDoubles = in.GetNumJumps(); if( iTotalDoubles == 0 ) return 1.0f; // no jumps in song // number of doubles const int iNumDoubles = GetNumNWithScore( in, TNS_W2, 2 ); return clamp( (float)iNumDoubles / iTotalDoubles, 0.0f, 1.0f ); } // Return the ratio of actual to possible dance points. float GetActualChaosRadarValue( const NoteData &in, float fSongSeconds, const PlayerStageStats &pss ) { const int iPossibleDP = pss.m_iPossibleDancePoints; if ( iPossibleDP == 0 ) return 1; const int ActualDP = pss.m_iActualDancePoints; return clamp( float(ActualDP)/iPossibleDP, 0.0f, 1.0f ); } // Return the ratio of actual to possible successful holds. float GetActualFreezeRadarValue( const NoteData &in, float fSongSeconds ) { // number of hold steps const int iTotalHolds = in.GetNumHoldNotes(); if( iTotalHolds == 0 ) return 1.0f; const int ActualHolds = GetNumHoldNotesWithScore( in, TapNote::hold_head_hold, HNS_Held ) + GetNumHoldNotesWithScore( in, TapNote::hold_head_roll, HNS_Held ); return clamp( float(ActualHolds) / iTotalHolds, 0.0f, 1.0f ); } } void NoteDataWithScoring::GetActualRadarValues( const NoteData &in, const PlayerStageStats &pss, float fSongSeconds, RadarValues& out ) { // The for loop and the assert are used to ensure that all fields of // RadarValue get set in here. FOREACH_ENUM( RadarCategory, rc ) { switch( rc ) { case RadarCategory_Stream: out[rc] = GetActualStreamRadarValue( in, fSongSeconds ); break; case RadarCategory_Voltage: out[rc] = GetActualVoltageRadarValue( in, fSongSeconds, pss ); break; case RadarCategory_Air: out[rc] = GetActualAirRadarValue( in, fSongSeconds ); break; case RadarCategory_Freeze: out[rc] = GetActualFreezeRadarValue( in, fSongSeconds ); break; case RadarCategory_Chaos: out[rc] = GetActualChaosRadarValue( in, fSongSeconds, pss ); break; case RadarCategory_TapsAndHolds: out[rc] = (float) GetNumNWithScore( in, TNS_W4, 1 ); break; case RadarCategory_Jumps: out[rc] = (float) GetNumNWithScore( in, TNS_W4, 2 ); break; case RadarCategory_Holds: out[rc] = (float) GetNumHoldNotesWithScore( in, TapNote::hold_head_hold, HNS_Held ); break; case RadarCategory_Mines: out[rc] = (float) GetSuccessfulMines( in ); break; case RadarCategory_Hands: out[rc] = (float) GetSuccessfulHands( in ); break; case RadarCategory_Rolls: out[rc] = (float) GetNumHoldNotesWithScore( in, TapNote::hold_head_roll, HNS_Held ); break; case RadarCategory_Lifts: out[rc] = (float) GetSuccessfulLifts( in, MIN_SCORE_TO_MAINTAIN_COMBO ); break; case RadarCategory_Fakes: out[rc] = (float) in.GetNumFakes(); break; //case RadarCategory_Minefields: out[rc] = (float) GetNumMinefieldsWithScore( in, TapNote::hold_head_mine, HNS_Held ); break; DEFAULT_FAIL( rc ); } } } /* * (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. */