Merge pull request #552 from kyzentun/radar_note_count

RadarCategory_Notes, uncapping and fixes to radar calculation.
This commit is contained in:
Kyzentun
2015-04-06 22:49:42 -06:00
11 changed files with 356 additions and 284 deletions
+1
View File
@@ -30,6 +30,7 @@ static const char *RadarCategoryNames[] = {
"Air",
"Freeze",
"Chaos",
"Notes",
"TapsAndHolds",
"Jumps",
"Holds",
+3 -1
View File
@@ -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
};
+1 -1
View File
@@ -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
+83 -77
View File
@@ -951,59 +951,88 @@ void NoteDataUtil::AutogenKickbox(const NoteData& in, NoteData& out, const Timin
out.RevalidateATIs(vector<int>(), 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) {}
};
void NoteDataUtil::CalculateRadarValues( const NoteData &in, float fSongSeconds, RadarValues& out )
// 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
{
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
// that scans for the peak step density. -Kyz
vector<recent_note_t> 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;
bool judgable;
// hold_ends tracks where currently active holds will end, which is used
// to count the number of hands. -Kyz
vector<int> 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;
int num_holds_on_curr_row;
int num_notes_on_curr_row;
crv_state()
:judgable(false), num_holds_on_curr_row(0), num_notes_on_curr_row(0)
{}
};
static void DoRowEndRadarCalc(crv_state& 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;
// 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
// that scans for the peak step density. -Kyz
vector<recent_note> recent_notes;
NoteData::all_tracks_const_iterator curr_note=
in.GetTapNoteRangeAllTracks(0, MAX_NOTE_ROW);
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;
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 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(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
+233 -185
View File
@@ -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<TapNoteScoreJudgeType> LAST_OR_MINIMUM_TNS ("Gameplay","LastOrMinimumTapNoteScore");
static ThemeMetric<TapNoteScore> 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<in.GetNumTracks(); t++ )
{
FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE( in, t, r, iStartIndex, iEndIndex )
{
const TapNote &tn = in.GetTapNote(t, r);
if( tn.result.tns >= 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; t<in.GetNumTracks(); ++t )
{
NoteData::TrackMap::const_iterator begin, end;
in.GetTapNoteRange( t, 0, MAX_NOTE_ROW, begin, end );
for( ; begin != end; ++begin )
{
const TapNote &tn = begin->second;
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; t<in.GetNumTracks(); t++ )
{
const TapNote &tn = in.GetTapNote(t, i);
if( tn.type == TapNoteType_Empty )
continue;
if( tn.type == TapNoteType_Mine ) // mines don't count
continue;
if (tn.type == TapNoteType_Fake ) // fake arrows don't count
continue;
if( tn.result.tns <= TNS_W5 )
Missed = true;
}
if( Missed )
continue;
// Check hold scores.
for( int t=0; t<in.GetNumTracks(); ++t )
{
int iHeadRow;
if( !in.IsHoldNoteAtRow( t, i, &iHeadRow ) )
continue;
const TapNote &tn = in.GetTapNote( t, iHeadRow );
/* If a hold is released *after* a hand containing it, the hand is
* still good. Ignore the judgement and only examine iLastHeldRow
* to be sure that the hold was still held at the point of this row.
* (Note that if the hold head tap was missed, then iLastHeldRow == i
* and this won't fail--but the tap check above will have already failed.) */
if( tn.HoldResult.iLastHeldRow < i )
Missed = true;
}
if( !Missed )
iNum++;
}
return iNum;
}
int GetSuccessfulLifts( const NoteData &in, TapNoteScore tns, int iStartIndex = 0, int iEndIndex = MAX_NOTE_ROW )
{
int iNumSuccessfulLiftNotes = 0;
NoteData::all_tracks_const_iterator iter = in.GetTapNoteRangeAllTracks( iStartIndex, iEndIndex );
for( ; !iter.IsAtEnd(); ++iter )
{
if( iter->type == 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
{
int end_row;
int last_held_row;
hold_status(int e, int l)
:end_row(e), last_held_row(l)
{}
};
struct garv_state
{
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_status> 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()
: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& 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 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(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);
}
}
}
+2 -2
View File
@@ -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
+11 -15
View File
@@ -352,25 +352,21 @@ void SetRadarValues(StepsTagInfo& info)
{
if(info.from_cache || info.for_load_edit)
{
vector<RString> 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<RString> 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<RadarCategory>(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
{
+2
View File
@@ -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.
+1 -1
View File
@@ -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;
+1 -1
View File
@@ -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;
+18 -1
View File
@@ -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;
}