From 5852067f47757f951e8784c014c2f4d94f850a4b Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Mon, 6 Apr 2015 18:45:23 -0600 Subject: [PATCH 1/2] Added RadarCategory_Notes. Rewrote NoteDataWithScoring stuff to only traverse the NoteData once, and match the radar code in NoteDataUtil. Fixed hand and lift counting in NoteDataUtil::CalculateRadarValues. Removed cap from RadarValues so that Stream, Voltage, Air, Freeze, and Chaos can be over 1.0. Fixed Stream and Voltage to not count hold heads twice. Changed SetRadarValues in NotesLoaderSSC to not require the stepfile version number. --- src/GameConstantsAndTypes.cpp | 1 + src/GameConstantsAndTypes.h | 4 +- src/GrooveRadar.cpp | 2 +- src/NoteDataUtil.cpp | 136 +++++------ src/NoteDataWithScoring.cpp | 418 +++++++++++++++++++--------------- src/NoteDataWithScoring.h | 4 +- src/NotesLoaderSSC.cpp | 26 +-- src/NotesLoaderSSC.h | 2 + src/Song.cpp | 2 +- src/Song.h | 2 +- 10 files changed, 326 insertions(+), 271 deletions(-) diff --git a/src/GameConstantsAndTypes.cpp b/src/GameConstantsAndTypes.cpp index 8bfec7c0b9..9871c01f2d 100644 --- a/src/GameConstantsAndTypes.cpp +++ b/src/GameConstantsAndTypes.cpp @@ -30,6 +30,7 @@ static const char *RadarCategoryNames[] = { "Air", "Freeze", "Chaos", + "Notes", "TapsAndHolds", "Jumps", "Holds", diff --git a/src/GameConstantsAndTypes.h b/src/GameConstantsAndTypes.h index 614b6f6c9e..5ffc987e47 100644 --- a/src/GameConstantsAndTypes.h +++ b/src/GameConstantsAndTypes.h @@ -33,6 +33,7 @@ enum RadarCategory RadarCategory_Air, /**< How much air is in the song? */ RadarCategory_Freeze, /**< How much freeze (holds) is in the song? */ RadarCategory_Chaos, /**< How much chaos is in the song? */ + RadarCategory_Notes, /**< How many notes are in the song? */ RadarCategory_TapsAndHolds, /**< How many taps and holds are in the song? */ RadarCategory_Jumps, /**< How many jumps are in the song? */ RadarCategory_Holds, /**< How many holds are in the song? */ @@ -42,7 +43,8 @@ enum RadarCategory RadarCategory_Lifts, /**< How many lifts are in the song? */ RadarCategory_Fakes, /**< How many fakes are in the song? */ // If you add another radar category, make sure you update - // NoteDataUtil::CalculateRadarValues to calculate it. -Kyz + // NoteDataUtil::CalculateRadarValues to calculate it. + // Also update NoteDataWithScoring::GetActualRadarValues. -Kyz NUM_RadarCategory, /**< The number of radar categories. */ RadarCategory_Invalid }; diff --git a/src/GrooveRadar.cpp b/src/GrooveRadar.cpp index 5aafb9873c..93deafef8a 100644 --- a/src/GrooveRadar.cpp +++ b/src/GrooveRadar.cpp @@ -107,7 +107,7 @@ void GrooveRadar::GrooveRadarValueMap::SetFromSteps( const RadarValues &rv ) { const float fValueCurrent = m_fValuesOld[c] * (1-m_PercentTowardNew) + m_fValuesNew[c] * m_PercentTowardNew; m_fValuesOld[c] = fValueCurrent; - m_fValuesNew[c] = rv[c]; + m_fValuesNew[c] = clamp(rv[c], 0.0, 1.0); } if( !m_bValuesVisible ) // the values WERE invisible diff --git a/src/NoteDataUtil.cpp b/src/NoteDataUtil.cpp index a3cc22c405..d48de7a147 100644 --- a/src/NoteDataUtil.cpp +++ b/src/NoteDataUtil.cpp @@ -961,11 +961,47 @@ struct recent_note_t :row(r), track(t) {} }; +// CalculateRadarValues has to delay some stuff until a row ends, but can +// only detect a row ending when it hits the next note. There isn't a note +// after the last row, so it also has to do the delayed stuff after exiting +// its loop. So this state structure exists to be passed to a function that +// can be called from both places to do the work. If this were Lua, +// DoRowEndRadarCalc would be a nested function. -Kyz +struct crv_state_t +{ + bool judgable; + // hold_ends tracks where currently active holds will end, which is used + // to count the number of hands. -Kyz + vector hold_ends; + // num_holds_on_curr_row saves us the work of tracking where holds started + // just to keep a jump of two holds from counting as a hand. + int num_holds_on_curr_row; + int num_notes_on_curr_row; + + crv_state_t() + :judgable(false), num_holds_on_curr_row(0), num_notes_on_curr_row(0) + {} +}; + +static void DoRowEndRadarCalc(crv_state_t& state, RadarValues& out) +{ + if(state.judgable) + { + if(state.num_notes_on_curr_row + (state.hold_ends.size() - + state.num_holds_on_curr_row) >= 3) + { + ++out[RadarCategory_Hands]; + } + } +} + void NoteDataUtil::CalculateRadarValues( const NoteData &in, float fSongSeconds, RadarValues& out ) { + // Anybody editing this function should also examine + // NoteDataWithScoring::GetActualRadarValues to make sure it handles things + // the same way. out.Zero(); int curr_row= -1; - bool judgable= false; // recent_notes is used to calculate the voltage. Each element is the row // and track number of a tap note. When the pair at the beginning is too // old, it's deleted. This provides a way to have a rolling window @@ -973,37 +1009,30 @@ void NoteDataUtil::CalculateRadarValues( const NoteData &in, float fSongSeconds, vector recent_notes; NoteData::all_tracks_const_iterator curr_note= in.GetTapNoteRangeAllTracks(0, MAX_NOTE_ROW); - int num_notes_on_curr_row= 0; TimingData* timing= GAMESTATE->GetProcessedTimingData(); // total_taps exists because the stream calculation needs GetNumTapNotes, // but TapsAndHolds + Jumps + Hands would be inaccurate. -Kyz float total_taps= 0; - // hold_ends tracks where currently active holds will end, which is used - // to count the number of hands. -Kyz - vector hold_ends; - // num_holds_on_curr_row saves us the work of tracking where holds started - // just to keep a jump of two holds from counting as a hand. - int num_holds_on_curr_row= 0; const float voltage_window_beats= 8.0f; const int voltage_window= BeatToNoteRow(voltage_window_beats); size_t max_notes_in_voltage_window= 0; int num_chaos_rows= 0; - - // Steps zeros the RadarValues before calling CalculateRadarValues. -Kyz + crv_state_t state; while(!curr_note.IsAtEnd()) { if(curr_note.Row() != curr_row) { + DoRowEndRadarCalc(state, out); curr_row= curr_note.Row(); - num_notes_on_curr_row= 0; - num_holds_on_curr_row= 0; - judgable= timing->IsJudgableAtRow(curr_row); - for(size_t n= 0; n < hold_ends.size(); ++n) + state.num_notes_on_curr_row= 0; + state.num_holds_on_curr_row= 0; + state.judgable= timing->IsJudgableAtRow(curr_row); + for(size_t n= 0; n < state.hold_ends.size(); ++n) { - if(hold_ends[n] < curr_row) + if(state.hold_ends[n] < curr_row) { - hold_ends.erase(hold_ends.begin() + n); + state.hold_ends.erase(state.hold_ends.begin() + n); --n; } } @@ -1028,45 +1057,29 @@ void NoteDataUtil::CalculateRadarValues( const NoteData &in, float fSongSeconds, ++num_chaos_rows; } } - if(judgable) + if(state.judgable) { switch(curr_note->type) { case TapNoteType_Tap: case TapNoteType_HoldHead: + // Lifts have to be counted with taps for them to be added to max dp + // correctly. -Kyz + case TapNoteType_Lift: // HoldTails and Attacks are counted by IsTap. -Kyz case TapNoteType_HoldTail: case TapNoteType_Attack: - ++num_notes_on_curr_row; - // Rolls are not counted by GetNumHoldNotes, which is what - // GetStreamRadarValue and GetVoltageRadarValue used to use. -Kyz - switch(curr_note->type) - { - case TapNoteType_HoldHead: - case TapNoteType_HoldTail: - if(curr_note->subType != TapNoteSubType_Hold) - { - break; - } - // This is really dumb: GetNumTapNotes counts hold heads, and - // GetNumHoldNotes also counts hold heads. So stream and - // voltage count hold heads twice. -Kyz - ++total_taps; - recent_notes.push_back( - recent_note_t(curr_row, curr_note.Track())); - case TapNoteType_Tap: - case TapNoteType_Attack: - ++total_taps; - recent_notes.push_back( - recent_note_t(curr_row, curr_note.Track())); - max_notes_in_voltage_window= max(recent_notes.size(), - max_notes_in_voltage_window); - break; - } + ++out[RadarCategory_Notes]; + ++state.num_notes_on_curr_row; + ++total_taps; + recent_notes.push_back( + recent_note_t(curr_row, curr_note.Track())); + max_notes_in_voltage_window= max(recent_notes.size(), + max_notes_in_voltage_window); // If there is one hold active, and one tap on this row, it does // not count as a jump. Hands do need to count the number of // holds active though. -Kyz - switch(num_notes_on_curr_row) + switch(state.num_notes_on_curr_row) { case 1: ++out[RadarCategory_TapsAndHolds]; @@ -1077,15 +1090,10 @@ void NoteDataUtil::CalculateRadarValues( const NoteData &in, float fSongSeconds, default: break; } - if(num_notes_on_curr_row + (hold_ends.size() - - num_holds_on_curr_row) >= 3) - { - ++out[RadarCategory_Hands]; - } if(curr_note->type == TapNoteType_HoldHead) { - hold_ends.push_back(curr_row + curr_note->iDuration); - ++num_holds_on_curr_row; + state.hold_ends.push_back(curr_row + curr_note->iDuration); + ++state.num_holds_on_curr_row; switch(curr_note->subType) { case TapNoteSubType_Hold: @@ -1098,13 +1106,14 @@ void NoteDataUtil::CalculateRadarValues( const NoteData &in, float fSongSeconds, break; } } + else if(curr_note->type == TapNoteType_Lift) + { + ++out[RadarCategory_Lifts]; + } break; case TapNoteType_Mine: ++out[RadarCategory_Mines]; break; - case TapNoteType_Lift: - ++out[RadarCategory_Lifts]; - break; case TapNoteType_Fake: ++out[RadarCategory_Fakes]; break; @@ -1116,25 +1125,22 @@ void NoteDataUtil::CalculateRadarValues( const NoteData &in, float fSongSeconds, } ++curr_note; } + DoRowEndRadarCalc(state, out); + // Walking the notes complete, now assign any values that remain. -Kyz -#define CAP_RADAR(n) min(1.0f, (n)) if(fSongSeconds > 0.0f) { - out[RadarCategory_Stream]= CAP_RADAR((total_taps / fSongSeconds) / 7.0f); + out[RadarCategory_Stream]= (total_taps / fSongSeconds) / 7.0f; // As seen in GetVoltageRadarValue: Don't use the timing data, just // pretend the beats are evenly spaced. -Kyz float avg_bps= in.GetLastBeat() / fSongSeconds; - out[RadarCategory_Voltage]= CAP_RADAR( + out[RadarCategory_Voltage]= ((max_notes_in_voltage_window / voltage_window_beats) * avg_bps) / - 10.0f); - out[RadarCategory_Air]= CAP_RADAR( - out[RadarCategory_Jumps] / fSongSeconds); - out[RadarCategory_Freeze]= CAP_RADAR( - out[RadarCategory_Holds] / fSongSeconds); - out[RadarCategory_Chaos]= CAP_RADAR( - num_chaos_rows / fSongSeconds * .5f); + 10.0f; + out[RadarCategory_Air]= out[RadarCategory_Jumps] / fSongSeconds; + out[RadarCategory_Freeze]= out[RadarCategory_Holds] / fSongSeconds; + out[RadarCategory_Chaos]= num_chaos_rows / fSongSeconds * .5f; } -#undef CAP_RADAR // Sorry, there's not an assert here anymore for making sure all fields // are set. There's a comment in the RadarCategory enum to direct // attention here when adding new categories. -Kyz diff --git a/src/NoteDataWithScoring.cpp b/src/NoteDataWithScoring.cpp index c5ebb71684..e5ed1190e3 100644 --- a/src/NoteDataWithScoring.cpp +++ b/src/NoteDataWithScoring.cpp @@ -3,8 +3,10 @@ #include "NoteData.h" #include "PlayerStageStats.h" #include "GameConstantsAndTypes.h" +#include "GameState.h" #include "ThemeMetric.h" #include "RageLog.h" +#include "TimingData.h" namespace { @@ -12,136 +14,6 @@ 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, TapNoteSubType subType, HoldNoteScore hns ) -{ - ASSERT( subType != TapNoteSubType_Invalid ); - - int iNumSuccessfulHolds = 0; - for( int t=0; tsecond; - if( tn.type != TapNoteType_HoldHead ) - 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 == TapNoteType_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 == TapNoteType_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. */ @@ -276,17 +148,6 @@ bool NoteDataWithScoring::IsRowCompletelyJudged( const NoteData &in, unsigned ro 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 ) { @@ -295,23 +156,16 @@ float GetActualVoltageRadarValue( const NoteData &in, float fSongSeconds, const * 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. */ + // FIXME: + // If MaxCombo.m_fSizeSeconds is used, it is wrong (too short) for any song + // where the last second is after the last step. However, calculating the + // max combo possible would require consulting the timing data on every step + // for combo segments and generally be a painful waste of effort. -Kyz 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 ) { @@ -322,47 +176,241 @@ float GetActualChaosRadarValue( const NoteData &in, float fSongSeconds, const Pl 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, TapNoteSubType_Hold, HNS_Held ) + - GetNumHoldNotesWithScore( in, TapNoteSubType_Roll, HNS_Held ); - return clamp( float(ActualHolds) / iTotalHolds, 0.0f, 1.0f ); } +struct hold_status_t +{ + int end_row; + int last_held_row; + hold_status_t(int e, int l) + :end_row(e), last_held_row(l) + {} +}; + +struct garv_state_t +{ + int curr_row; + int notes_hit_for_stream; + int jumps_hit_for_air; + int holds_held; + int rolls_held; + int notes_hit; + int taps_hit; + int jumps_hit; + int hands_hit; + int mines_avoided; + int lifts_hit; + // hold_ends tracks where currently active holds will end, which is used + // to count the number of hands. -Kyz + vector hold_ends; + int num_notes_on_curr_row; + // num_holds_on_curr_row saves us the work of tracking where holds started + // just to keep a jump of two holds from counting as a hand. + int num_holds_on_curr_row; + int num_notes_hit_on_curr_row; + // last_tns_on_row and last_time_on_row are used for deciding whether a jump + // or hand was successfully hit. + TapNoteScore last_tns_on_row; + float last_time_on_row; + // A hand is considered missed if any of the notes is missed. + TapNoteScore worst_tns_on_row; + // TODO? Make these configurable in some way? + TapNoteScore stream_tns; + TapNoteScore air_tns; + TapNoteScore taps_tns; + TapNoteScore jumps_tns; + TapNoteScore hands_tns; + TapNoteScore lifts_tns; + bool judgable; + garv_state_t() + :curr_row(0), notes_hit_for_stream(0), jumps_hit_for_air(0), + holds_held(0), rolls_held(0), notes_hit(0), taps_hit(0), jumps_hit(0), + hands_hit(0), mines_avoided(0), lifts_hit(0), num_notes_on_curr_row(0), + num_holds_on_curr_row(0), num_notes_hit_on_curr_row(0), + last_tns_on_row(TapNoteScore_Invalid), last_time_on_row(-9999), + worst_tns_on_row(TapNoteScore_Invalid), stream_tns(TNS_W2), + air_tns(TNS_W2), taps_tns(TNS_W4), jumps_tns(TNS_W4), hands_tns(TNS_W4), + lifts_tns(MIN_SCORE_TO_MAINTAIN_COMBO), + judgable(false) + {} +}; + +static void DoRowEndRadarActualCalc(garv_state_t& state, RadarValues& out) +{ + if(state.judgable && state.last_tns_on_row != TapNoteScore_Invalid) + { + if(state.num_notes_on_curr_row >= 1) + { + state.taps_hit+= (state.last_tns_on_row >= state.taps_tns); + } + if(state.num_notes_on_curr_row >= 2) + { + state.jumps_hit_for_air+= (state.last_tns_on_row >= state.air_tns); + state.jumps_hit+= (state.last_tns_on_row >= state.jumps_tns); + } + if(state.num_notes_on_curr_row + (state.hold_ends.size() - + state.num_holds_on_curr_row) >= 3) + { + if(state.worst_tns_on_row >= state.hands_tns) + { + size_t holds_down= 0; + for(size_t n= 0; n < state.hold_ends.size(); ++n) + { + holds_down+= (state.curr_row <= state.hold_ends[n].last_held_row); + } + state.hands_hit+= (holds_down == state.hold_ends.size()); + } + } + } } - -void NoteDataWithScoring::GetActualRadarValues( const NoteData &in, const PlayerStageStats &pss, float fSongSeconds, RadarValues& out ) +void NoteDataWithScoring::GetActualRadarValues(const NoteData &in, + const PlayerStageStats &pss, float song_seconds, RadarValues& out) { + // Anybody editing this function should also examine + // NoteDataUtil::CalculateRadarValues to make sure it handles things the + // same way. + // Some of this logic is similar or identical to + // NoteDataUtil::CalculateRadarValues because I couldn't figure out a good + // way to combine them into one. -Kyz + PlayerNumber pn= pss.m_player_number; + garv_state_t state; + + NoteData::all_tracks_const_iterator curr_note= + in.GetTapNoteRangeAllTracks(0, MAX_NOTE_ROW); + TimingData* timing= GAMESTATE->GetProcessedTimingData(); + + while(!curr_note.IsAtEnd()) + { + if(curr_note.Row() != state.curr_row) + { + DoRowEndRadarActualCalc(state, out); + state.curr_row= curr_note.Row(); + state.num_notes_on_curr_row= 0; + state.num_holds_on_curr_row= 0; + state.judgable= timing->IsJudgableAtRow(state.curr_row); + for(size_t n= 0; n < state.hold_ends.size(); ++n) + { + if(state.hold_ends[n].end_row < state.curr_row) + { + state.hold_ends.erase(state.hold_ends.begin() + n); + --n; + } + } + state.last_tns_on_row= TapNoteScore_Invalid; + state.last_time_on_row= -9999; + state.worst_tns_on_row= TapNoteScore_Invalid; + } + bool for_this_player= curr_note->pn == pn || pn == PLAYER_INVALID || + curr_note->pn == PLAYER_INVALID; + if(state.judgable && for_this_player) + { + switch(curr_note->type) + { + case TapNoteType_Tap: + case TapNoteType_HoldHead: + // HoldTails and Attacks are counted by IsTap. -Kyz + case TapNoteType_HoldTail: + case TapNoteType_Attack: + case TapNoteType_Lift: + ++state.num_notes_on_curr_row; + state.notes_hit_for_stream+= (curr_note->result.tns >= state.stream_tns); + state.notes_hit+= (curr_note->result.tns >= state.taps_tns); + if(curr_note->result.tns < state.worst_tns_on_row) + { + state.worst_tns_on_row= curr_note->result.tns; + } + if(curr_note->result.fTapNoteOffset > state.last_time_on_row) + { + state.last_time_on_row= curr_note->result.fTapNoteOffset; + state.last_tns_on_row= curr_note->result.tns; + } + if(curr_note->type == TapNoteType_HoldHead) + { + if(curr_note->subType == TapNoteSubType_Hold) + { + state.holds_held+= (curr_note->HoldResult.hns >= HNS_Held); + } + else if(curr_note->subType == TapNoteSubType_Roll) + { + state.rolls_held+= (curr_note->HoldResult.hns >= HNS_Held); + } + state.hold_ends.push_back( + hold_status_t(state.curr_row + curr_note->iDuration, + curr_note->HoldResult.iLastHeldRow)); + ++state.num_holds_on_curr_row; + } + else if(curr_note->type == TapNoteType_Lift) + { + state.lifts_hit+= (curr_note->result.tns >= state.lifts_tns); + } + break; + case TapNoteType_Mine: + state.mines_avoided+= (curr_note->result.tns == TNS_AvoidMine); + break; + case TapNoteType_Fake: + break; + } + } + ++curr_note; + } + DoRowEndRadarActualCalc(state, out); + + // ScreenGameplay passes in the RadarValues that were calculated by + // NoteDataUtil::CalculateRadarValues, so those are reused here. -Kyz + int note_count= out[RadarCategory_Notes]; + int jump_count= out[RadarCategory_Jumps]; + int hold_count= out[RadarCategory_Holds]; + int tap_count= out[RadarCategory_TapsAndHolds]; // The for loop and the assert are used to ensure that all fields of // RadarValue get set in here. - FOREACH_ENUM( RadarCategory, rc ) + FOREACH_ENUM(RadarCategory, rc) { - switch( 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, TapNoteSubType_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, TapNoteSubType_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, TapNoteSubType_Mine, HNS_Held ); break; - DEFAULT_FAIL( rc ); + case RadarCategory_Stream: + out[rc]= clamp(float(state.notes_hit_for_stream) / note_count, 0.0f, 1.0f); + break; + case RadarCategory_Voltage: + out[rc]= GetActualVoltageRadarValue(in, song_seconds, pss); + break; + case RadarCategory_Air: + out[rc]= clamp(float(state.jumps_hit_for_air) / jump_count, 0.0f, 1.0f); + break; + case RadarCategory_Freeze: + out[rc]= clamp(float(state.holds_held) / hold_count, 0.0f, 1.0f); + break; + case RadarCategory_Chaos: + out[rc]= GetActualChaosRadarValue(in, song_seconds, pss); + break; + case RadarCategory_TapsAndHolds: + out[rc]= state.taps_hit; + break; + case RadarCategory_Jumps: + out[rc]= state.jumps_hit; + break; + case RadarCategory_Holds: + out[rc]= state.holds_held; + break; + case RadarCategory_Mines: + out[rc]= state.mines_avoided; + break; + case RadarCategory_Hands: + out[rc]= state.hands_hit; + break; + case RadarCategory_Rolls: + out[rc]= state.rolls_held; + break; + case RadarCategory_Lifts: + out[rc]= state.lifts_hit; + break; + case RadarCategory_Fakes: + out[rc]= out[rc]; + break; + case RadarCategory_Notes: + out[rc]= state.notes_hit; + break; + DEFAULT_FAIL(rc); } } } diff --git a/src/NoteDataWithScoring.h b/src/NoteDataWithScoring.h index 56f655dbea..38739a50bd 100644 --- a/src/NoteDataWithScoring.h +++ b/src/NoteDataWithScoring.h @@ -22,8 +22,8 @@ namespace NoteDataWithScoring TapNoteScore MinTapNoteScore( const NoteData &in, unsigned iRow, PlayerNumber plnum = PlayerNumber_Invalid ); const TapNote &LastTapNoteWithResult( const NoteData &in, unsigned iRow, PlayerNumber plnum = PlayerNumber_Invalid ); - void GetActualRadarValues( const NoteData &in, const PlayerStageStats &pss, - float fSongSeconds, RadarValues& out ); + void GetActualRadarValues(const NoteData &in, const PlayerStageStats &pss, + float song_seconds, RadarValues& out); }; #endif diff --git a/src/NotesLoaderSSC.cpp b/src/NotesLoaderSSC.cpp index ad10469b67..d44fbccb0e 100644 --- a/src/NotesLoaderSSC.cpp +++ b/src/NotesLoaderSSC.cpp @@ -352,25 +352,21 @@ void SetRadarValues(StepsTagInfo& info) { if(info.from_cache || info.for_load_edit) { - vector saValues; - split((*info.params)[1], ",", saValues, true); - int categories = NUM_RadarCategory; - if(info.song->m_fVersion < VERSION_RADAR_FAKE && !info.for_load_edit) - { categories -= 1; } - if(saValues.size() == (unsigned int)categories * NUM_PLAYERS) + vector values; + split((*info.params)[1], ",", values, true); + // Instead of trying to use the version to figure out how many + // categories to expect, look at the number of values and split them + // evenly. -Kyz + size_t cats_per_player= values.size() / NUM_PlayerNumber; + RadarValues v[NUM_PLAYERS]; + FOREACH_PlayerNumber(pn) { - RadarValues v[NUM_PLAYERS]; - FOREACH_PlayerNumber(pn) + for(size_t i= 0; i < cats_per_player; ++i) { - // Can't use the foreach anymore due to flexible radar lines. - for(RadarCategory rc = (RadarCategory)0; rc < categories; - enum_add(rc, +1)) - { - v[pn][rc] = StringToFloat(saValues[pn*categories + rc]); - } + v[pn][i]= StringToFloat(values[pn * cats_per_player + i]); } - info.steps->SetCachedRadarValues(v); } + info.steps->SetCachedRadarValues(v); } else { diff --git a/src/NotesLoaderSSC.h b/src/NotesLoaderSSC.h index 9de3383061..7a63914dac 100644 --- a/src/NotesLoaderSSC.h +++ b/src/NotesLoaderSSC.h @@ -32,6 +32,8 @@ const float VERSION_OFFSET_BEFORE_ATTACK = 0.72f; const float VERSION_CHART_NAME_TAG = 0.74f; /** @brief The version that introduced the cache switch tag. */ const float VERSION_CACHE_SWITCH_TAG = 0.77f; +/** @brief The version where note count was added as a radar category. */ +const float VERSION_RADAR_NOTECOUNT = 0.83f; /** * @brief The SSCLoader handles all of the parsing needed for .ssc files. diff --git a/src/Song.cpp b/src/Song.cpp index 2c33a4a71a..88db011735 100644 --- a/src/Song.cpp +++ b/src/Song.cpp @@ -45,7 +45,7 @@ * @brief The internal version of the cache for StepMania. * * Increment this value to invalidate the current cache. */ -const int FILE_CACHE_VERSION = 224; +const int FILE_CACHE_VERSION = 225; /** @brief How long does a song sample last by default? */ const float DEFAULT_MUSIC_SAMPLE_LENGTH = 12.f; diff --git a/src/Song.h b/src/Song.h index db9c901c20..7ab14acf88 100644 --- a/src/Song.h +++ b/src/Song.h @@ -20,7 +20,7 @@ void FixupPath( RString &path, const RString &sSongPath ); RString GetSongAssetPath( RString sPath, const RString &sSongPath ); /** @brief The version of the .ssc file format. */ -const static float STEPFILE_VERSION_NUMBER = 0.82f; +const static float STEPFILE_VERSION_NUMBER = 0.83f; /** @brief How many edits for this song can each profile have? */ const int MAX_EDITS_PER_SONG_PER_PROFILE = 15; From 1e668c660f3faefb41e797018ca33577cfe7554f Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Mon, 6 Apr 2015 21:49:46 -0600 Subject: [PATCH 2/2] Removed _t from typenames because of POSIX. Fixed bug that made a warp and stop on the same row skip the marker in TimingData. --- src/NoteDataUtil.cpp | 18 +++++++++--------- src/NoteDataWithScoring.cpp | 16 ++++++++-------- src/TimingData.cpp | 19 ++++++++++++++++++- 3 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/NoteDataUtil.cpp b/src/NoteDataUtil.cpp index d48de7a147..879e38d70a 100644 --- a/src/NoteDataUtil.cpp +++ b/src/NoteDataUtil.cpp @@ -951,13 +951,13 @@ void NoteDataUtil::AutogenKickbox(const NoteData& in, NoteData& out, const Timin out.RevalidateATIs(vector(), false); } -struct recent_note_t +struct recent_note { int row; int track; - recent_note_t() + recent_note() :row(0), track(0) {} - recent_note_t(int r, int t) + recent_note(int r, int t) :row(r), track(t) {} }; @@ -967,7 +967,7 @@ struct recent_note_t // its loop. So this state structure exists to be passed to a function that // can be called from both places to do the work. If this were Lua, // DoRowEndRadarCalc would be a nested function. -Kyz -struct crv_state_t +struct crv_state { bool judgable; // hold_ends tracks where currently active holds will end, which is used @@ -978,12 +978,12 @@ struct crv_state_t int num_holds_on_curr_row; int num_notes_on_curr_row; - crv_state_t() + crv_state() :judgable(false), num_holds_on_curr_row(0), num_notes_on_curr_row(0) {} }; -static void DoRowEndRadarCalc(crv_state_t& state, RadarValues& out) +static void DoRowEndRadarCalc(crv_state& state, RadarValues& out) { if(state.judgable) { @@ -1006,7 +1006,7 @@ void NoteDataUtil::CalculateRadarValues( const NoteData &in, float fSongSeconds, // and track number of a tap note. When the pair at the beginning is too // old, it's deleted. This provides a way to have a rolling window // that scans for the peak step density. -Kyz - vector recent_notes; + vector recent_notes; NoteData::all_tracks_const_iterator curr_note= in.GetTapNoteRangeAllTracks(0, MAX_NOTE_ROW); TimingData* timing= GAMESTATE->GetProcessedTimingData(); @@ -1017,7 +1017,7 @@ void NoteDataUtil::CalculateRadarValues( const NoteData &in, float fSongSeconds, const int voltage_window= BeatToNoteRow(voltage_window_beats); size_t max_notes_in_voltage_window= 0; int num_chaos_rows= 0; - crv_state_t state; + crv_state state; while(!curr_note.IsAtEnd()) { @@ -1073,7 +1073,7 @@ void NoteDataUtil::CalculateRadarValues( const NoteData &in, float fSongSeconds, ++state.num_notes_on_curr_row; ++total_taps; recent_notes.push_back( - recent_note_t(curr_row, curr_note.Track())); + recent_note(curr_row, curr_note.Track())); max_notes_in_voltage_window= max(recent_notes.size(), max_notes_in_voltage_window); // If there is one hold active, and one tap on this row, it does diff --git a/src/NoteDataWithScoring.cpp b/src/NoteDataWithScoring.cpp index e5ed1190e3..b0ac95ee3b 100644 --- a/src/NoteDataWithScoring.cpp +++ b/src/NoteDataWithScoring.cpp @@ -178,16 +178,16 @@ float GetActualChaosRadarValue( const NoteData &in, float fSongSeconds, const Pl } } -struct hold_status_t +struct hold_status { int end_row; int last_held_row; - hold_status_t(int e, int l) + hold_status(int e, int l) :end_row(e), last_held_row(l) {} }; -struct garv_state_t +struct garv_state { int curr_row; int notes_hit_for_stream; @@ -202,7 +202,7 @@ struct garv_state_t int lifts_hit; // hold_ends tracks where currently active holds will end, which is used // to count the number of hands. -Kyz - vector hold_ends; + vector hold_ends; int num_notes_on_curr_row; // num_holds_on_curr_row saves us the work of tracking where holds started // just to keep a jump of two holds from counting as a hand. @@ -222,7 +222,7 @@ struct garv_state_t TapNoteScore hands_tns; TapNoteScore lifts_tns; bool judgable; - garv_state_t() + garv_state() :curr_row(0), notes_hit_for_stream(0), jumps_hit_for_air(0), holds_held(0), rolls_held(0), notes_hit(0), taps_hit(0), jumps_hit(0), hands_hit(0), mines_avoided(0), lifts_hit(0), num_notes_on_curr_row(0), @@ -235,7 +235,7 @@ struct garv_state_t {} }; -static void DoRowEndRadarActualCalc(garv_state_t& state, RadarValues& out) +static void DoRowEndRadarActualCalc(garv_state& state, RadarValues& out) { if(state.judgable && state.last_tns_on_row != TapNoteScore_Invalid) { @@ -274,7 +274,7 @@ void NoteDataWithScoring::GetActualRadarValues(const NoteData &in, // NoteDataUtil::CalculateRadarValues because I couldn't figure out a good // way to combine them into one. -Kyz PlayerNumber pn= pss.m_player_number; - garv_state_t state; + garv_state state; NoteData::all_tracks_const_iterator curr_note= in.GetTapNoteRangeAllTracks(0, MAX_NOTE_ROW); @@ -336,7 +336,7 @@ void NoteDataWithScoring::GetActualRadarValues(const NoteData &in, state.rolls_held+= (curr_note->HoldResult.hns >= HNS_Held); } state.hold_ends.push_back( - hold_status_t(state.curr_row + curr_note->iDuration, + hold_status(state.curr_row + curr_note->iDuration, curr_note->HoldResult.iLastHeldRow)); ++state.num_holds_on_curr_row; } diff --git a/src/TimingData.cpp b/src/TimingData.cpp index c684de6ff6..b0becfecec 100644 --- a/src/TimingData.cpp +++ b/src/TimingData.cpp @@ -780,9 +780,21 @@ float TimingData::GetElapsedTimeInternal(GetBeatStarts& start, float beat, float bps= GetBPMAtRow(start.last_row) / 60.0f; #define INC_INDEX(index) ++curr_segment; ++index; bool find_marker= beat < FLT_MAX; + // Stops require this special kluge to handle the case where a stop and a + // warp are on the same row and the lookup table entry would be between + // the stop and the warp. If that happens, then the last_time in the entry + // is pushed past the marker by the stop, so the marker can't be found and + // the step that is on that row comes up as a miss. + // So this kluge backs up the lookup table entry by one step if the last + // thing found was a stop. -Kyz + bool last_found_was_a_stop= false; + GetBeatStarts pre_stop_state= start; + unsigned int start_segment= curr_segment; - while(curr_segment < max_segment) + while(curr_segment < max_segment || curr_segment - start_segment <= 1) { + last_found_was_a_stop= false; + pre_stop_state= start; int event_row= INT_MAX; int event_type= NOT_FOUND; FindEvent(event_row, event_type, start, beat, find_marker, bpms, warps, stops, @@ -802,6 +814,7 @@ float TimingData::GetElapsedTimeInternal(GetBeatStarts& start, float beat, break; case FOUND_STOP: case FOUND_STOP_DELAY: + last_found_was_a_stop= true; time_to_next_event= ToStop(stops[start.stop])->GetPause(); next_event_time= start.last_time + time_to_next_event; start.last_time= next_event_time; @@ -831,6 +844,10 @@ float TimingData::GetElapsedTimeInternal(GetBeatStarts& start, float beat, start.last_row= event_row; } #undef INC_INDEX + if(last_found_was_a_stop) + { + start= pre_stop_state; + } return start.last_time; }