add a strum window: After pressing the strum button, the player has some time to hit matching frets before being scored TNS_None
This commit is contained in:
+133
-68
@@ -71,7 +71,8 @@ static Preference<float> m_fTimingWindowScale ( "TimingWindowScale", 1.0f );
|
|||||||
static Preference<float> m_fTimingWindowAdd ( "TimingWindowAdd", 0 );
|
static Preference<float> m_fTimingWindowAdd ( "TimingWindowAdd", 0 );
|
||||||
static Preference1D<float> m_fTimingWindowSeconds( TimingWindowSecondsInit, NUM_TimingWindow );
|
static Preference1D<float> m_fTimingWindowSeconds( TimingWindowSecondsInit, NUM_TimingWindow );
|
||||||
static Preference<float> m_fTimingWindowJump ( "TimingWindowJump", 0.25 );
|
static Preference<float> m_fTimingWindowJump ( "TimingWindowJump", 0.25 );
|
||||||
Preference<float> g_fTimingWindowHopo ( "TimingWindowHopo", 0.25 );
|
Preference<float> g_fTimingWindowHopo ( "TimingWindowHopo", 0.25 ); // max time between notes in a hopo chain
|
||||||
|
Preference<float> g_fTimingWindowStrum ( "TimingWindowStrum", 0.1f ); // max time between strum and when the frets must match
|
||||||
ThemeMetric<bool> PENALIZE_TAP_SCORE_NONE ( "Player", "PenalizeTapScoreNone" );
|
ThemeMetric<bool> PENALIZE_TAP_SCORE_NONE ( "Player", "PenalizeTapScoreNone" );
|
||||||
ThemeMetric<bool> JUDGE_HOLD_NOTES_ON_SAME_ROW_TOGETHER ( "Player", "JudgeHoldNotesOnSameRowTogether" );
|
ThemeMetric<bool> JUDGE_HOLD_NOTES_ON_SAME_ROW_TOGETHER ( "Player", "JudgeHoldNotesOnSameRowTogether" );
|
||||||
|
|
||||||
@@ -595,6 +596,15 @@ void Player::Update( float fDeltaTime )
|
|||||||
if( m_bPaused )
|
if( m_bPaused )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Check for a strum miss
|
||||||
|
//
|
||||||
|
if( m_pPlayerState->m_fLastStrumMusicSeconds != -1 &&
|
||||||
|
m_pPlayerState->m_fLastStrumMusicSeconds + g_fTimingWindowStrum < GAMESTATE->m_fMusicSeconds )
|
||||||
|
{
|
||||||
|
DoStrumMiss();
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Check for TapNote misses
|
// Check for TapNote misses
|
||||||
//
|
//
|
||||||
@@ -1213,6 +1223,15 @@ void Player::Fret( int col, int row, const RageTimer &tm, bool bHeld, bool bRele
|
|||||||
|
|
||||||
m_vbFretIsDown[ col ] = !bRelease;
|
m_vbFretIsDown[ col ] = !bRelease;
|
||||||
|
|
||||||
|
|
||||||
|
// Handle changing fret during a strum
|
||||||
|
if( m_pPlayerState->m_fLastStrumMusicSeconds != -1 )
|
||||||
|
{
|
||||||
|
LOG->Trace( "StrumTry" );
|
||||||
|
StepStrumHopo( col, row, tm, bHeld, bRelease, ButtonType_StrumFretsChanged );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Handle hammer-ons and pull-offs
|
// Handle hammer-ons and pull-offs
|
||||||
const float fPositionSeconds = GAMESTATE->m_fMusicSeconds - tm.Ago();
|
const float fPositionSeconds = GAMESTATE->m_fMusicSeconds - tm.Ago();
|
||||||
int iHopoCol = -1;
|
int iHopoCol = -1;
|
||||||
@@ -1265,12 +1284,93 @@ void Player::Fret( int col, int row, const RageTimer &tm, bool bHeld, bool bRele
|
|||||||
Hopo( iHopoCol, row, tm, bHeld, bRelease );
|
Hopo( iHopoCol, row, tm, bHeld, bRelease );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Player::Strum( int col, int row, const RageTimer &tm, bool bHeld, bool bRelease )
|
||||||
|
{
|
||||||
|
if( bRelease )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if( m_pPlayerState->m_fLastStrumMusicSeconds != -1 )
|
||||||
|
{
|
||||||
|
DoStrumMiss();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pPlayerState->m_fLastStrumMusicSeconds = GAMESTATE->m_fMusicSeconds;
|
||||||
|
|
||||||
|
//StepStrumHopo( col, row, tm, bHeld, bRelease, ButtonType_StrumFretsChanged );
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::DoTapScoreNone()
|
||||||
|
{
|
||||||
|
Message msg( "ScoreNone" );
|
||||||
|
MESSAGEMAN->Broadcast( msg );
|
||||||
|
|
||||||
|
const int iOldCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurCombo : 0;
|
||||||
|
const int iOldMissCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurMissCombo : 0;
|
||||||
|
|
||||||
|
/* The only real way to tell if a mine has been scored is if it has disappeared
|
||||||
|
* but this only works for hit mines so update the scores for avoided mines here. */
|
||||||
|
if( m_pPrimaryScoreKeeper )
|
||||||
|
m_pPrimaryScoreKeeper->HandleTapScoreNone();
|
||||||
|
if( m_pSecondaryScoreKeeper )
|
||||||
|
m_pSecondaryScoreKeeper->HandleTapScoreNone();
|
||||||
|
|
||||||
|
SendComboMessages( iOldCombo, iOldMissCombo );
|
||||||
|
|
||||||
|
|
||||||
|
if( m_pLifeMeter )
|
||||||
|
m_pLifeMeter->HandleTapScoreNone();
|
||||||
|
// TODO: Remove use of PlayerNumber
|
||||||
|
PlayerNumber pn = PLAYER_INVALID;
|
||||||
|
if( m_pCombinedLifeMeter )
|
||||||
|
m_pCombinedLifeMeter->HandleTapScoreNone( pn );
|
||||||
|
|
||||||
|
if( PENALIZE_TAP_SCORE_NONE )
|
||||||
|
{
|
||||||
|
SetJudgment( TNS_Miss, false );
|
||||||
|
// the ScoreKeeper will subrtract points later.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Player::DoStrumMiss()
|
||||||
|
{
|
||||||
|
m_pPlayerState->m_fLastStrumMusicSeconds = -1;
|
||||||
|
DoTapScoreNone();
|
||||||
|
|
||||||
|
if( PENALIZE_TAP_SCORE_NONE )
|
||||||
|
{
|
||||||
|
const float fSongBeat = GAMESTATE->m_fSongBeat;
|
||||||
|
const int iSongRow = BeatToNoteRow( fSongBeat );
|
||||||
|
|
||||||
|
// Score all active holds to NotHeld
|
||||||
|
for( int iTrack=0; iTrack<m_NoteData.GetNumTracks(); ++iTrack )
|
||||||
|
{
|
||||||
|
// Since this is being called every frame, let's not check the whole array every time.
|
||||||
|
// Instead, only check 1 beat back. Even 1 is overkill.
|
||||||
|
const int iStartCheckingAt = max( 0, iSongRow-BeatToNoteRow(1) );
|
||||||
|
NoteData::iterator begin, end;
|
||||||
|
m_NoteData.GetTapNoteRangeInclusive( iTrack, iStartCheckingAt, iSongRow+1, begin, end );
|
||||||
|
for( ; begin != end; ++begin )
|
||||||
|
{
|
||||||
|
TapNote &tn = begin->second;
|
||||||
|
if( tn.HoldResult.bActive )
|
||||||
|
{
|
||||||
|
tn.HoldResult.hns = HNS_LetGo;
|
||||||
|
|
||||||
|
HandleHoldScore( tn );
|
||||||
|
SetHoldJudgment( tn.result.tns, tn.HoldResult.hns, iTrack );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, bool bRelease, Player::ButtonType pbt )
|
void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, bool bRelease, Player::ButtonType pbt )
|
||||||
{
|
{
|
||||||
if( IsOniDead() )
|
if( IsOniDead() )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Do everything that depends on a RageTime here and set your breakpoints somewhere after this block
|
// Do everything that depends on a RageTimer here and set your breakpoints somewhere after this block
|
||||||
const float fLastBeatUpdate = GAMESTATE->m_LastBeatUpdate.Ago();
|
const float fLastBeatUpdate = GAMESTATE->m_LastBeatUpdate.Ago();
|
||||||
const float fPositionSeconds = GAMESTATE->m_fMusicSeconds - tm.Ago();
|
const float fPositionSeconds = GAMESTATE->m_fMusicSeconds - tm.Ago();
|
||||||
const float fTimeSinceStep = tm.Ago();
|
const float fTimeSinceStep = tm.Ago();
|
||||||
@@ -1278,15 +1378,13 @@ void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, b
|
|||||||
switch( pbt )
|
switch( pbt )
|
||||||
{
|
{
|
||||||
DEFAULT_FAIL(pbt);
|
DEFAULT_FAIL(pbt);
|
||||||
case ButtonType_Strum:
|
|
||||||
if( bRelease )
|
|
||||||
return;
|
|
||||||
break;
|
|
||||||
case ButtonType_Hopo:
|
|
||||||
bRelease = false;
|
|
||||||
break;
|
|
||||||
case ButtonType_Step:
|
case ButtonType_Step:
|
||||||
break;
|
break;
|
||||||
|
case ButtonType_StrumFretsChanged:
|
||||||
|
case ButtonType_Hopo:
|
||||||
|
// releasing should hit regular notes, not lifts
|
||||||
|
bRelease = false;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const float fSongBeat = GAMESTATE->m_pCurSong ? GAMESTATE->m_pCurSong->GetBeatFromElapsedTime( fPositionSeconds ) : GAMESTATE->m_fSongBeat;
|
const float fSongBeat = GAMESTATE->m_pCurSong ? GAMESTATE->m_pCurSong->GetBeatFromElapsedTime( fPositionSeconds ) : GAMESTATE->m_fSongBeat;
|
||||||
@@ -1357,6 +1455,7 @@ void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, b
|
|||||||
// Count calories for this step, unless we're being called because a button is
|
// Count calories for this step, unless we're being called because a button is
|
||||||
// held over a mine or being released.
|
// held over a mine or being released.
|
||||||
//
|
//
|
||||||
|
// TODO: Move calorie counting into a ScoreKeeper?
|
||||||
if( m_pPlayerStageStats && m_pPlayerState && !bHeld && !bRelease )
|
if( m_pPlayerStageStats && m_pPlayerState && !bHeld && !bRelease )
|
||||||
{
|
{
|
||||||
// TODO: remove use of PlayerNumber
|
// TODO: remove use of PlayerNumber
|
||||||
@@ -1414,7 +1513,7 @@ void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, b
|
|||||||
switch( pbt )
|
switch( pbt )
|
||||||
{
|
{
|
||||||
DEFAULT_FAIL(pbt);
|
DEFAULT_FAIL(pbt);
|
||||||
case ButtonType_Strum:
|
case ButtonType_StrumFretsChanged:
|
||||||
iRowOfOverlappingNoteOrRow = GetClosestNonEmptyRow( BeatToNoteRow(fSongBeat), iStepSearchRows, iStepSearchRows, false );
|
iRowOfOverlappingNoteOrRow = GetClosestNonEmptyRow( BeatToNoteRow(fSongBeat), iStepSearchRows, iStepSearchRows, false );
|
||||||
break;
|
break;
|
||||||
case ButtonType_Hopo:
|
case ButtonType_Hopo:
|
||||||
@@ -1461,7 +1560,7 @@ void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, b
|
|||||||
switch( pbt )
|
switch( pbt )
|
||||||
{
|
{
|
||||||
DEFAULT_FAIL(pbt);
|
DEFAULT_FAIL(pbt);
|
||||||
case ButtonType_Strum:
|
case ButtonType_StrumFretsChanged:
|
||||||
pTN = &tnDummy;
|
pTN = &tnDummy;
|
||||||
break;
|
break;
|
||||||
case ButtonType_Hopo:
|
case ButtonType_Hopo:
|
||||||
@@ -1567,7 +1666,7 @@ void Player::StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, b
|
|||||||
switch( pbt )
|
switch( pbt )
|
||||||
{
|
{
|
||||||
DEFAULT_FAIL(pbt);
|
DEFAULT_FAIL(pbt);
|
||||||
case ButtonType_Strum:
|
case ButtonType_StrumFretsChanged:
|
||||||
{
|
{
|
||||||
bool bNoteRowMatchesFrets = true;
|
bool bNoteRowMatchesFrets = true;
|
||||||
int iFirstNoteCol = -1;
|
int iFirstNoteCol = -1;
|
||||||
@@ -1713,7 +1812,7 @@ done_checking_hopo:
|
|||||||
switch( pbt )
|
switch( pbt )
|
||||||
{
|
{
|
||||||
DEFAULT_FAIL(pbt);
|
DEFAULT_FAIL(pbt);
|
||||||
case ButtonType_Strum:
|
case ButtonType_StrumFretsChanged:
|
||||||
for( int t=0; t<m_NoteData.GetNumTracks(); t++ )
|
for( int t=0; t<m_NoteData.GetNumTracks(); t++ )
|
||||||
{
|
{
|
||||||
TapNote tn = m_NoteData.GetTapNote( t, iRowOfOverlappingNoteOrRow );
|
TapNote tn = m_NoteData.GetTapNote( t, iRowOfOverlappingNoteOrRow );
|
||||||
@@ -1761,69 +1860,35 @@ done_checking_hopo:
|
|||||||
m_pPlayerState->ClearHopoState();
|
m_pPlayerState->ClearHopoState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check for strum end
|
||||||
|
if( score != TNS_None )
|
||||||
|
{
|
||||||
|
switch( pbt )
|
||||||
|
{
|
||||||
|
DEFAULT_FAIL(pbt);
|
||||||
|
case ButtonType_Step:
|
||||||
|
break;
|
||||||
|
case ButtonType_StrumFretsChanged:
|
||||||
|
m_pPlayerState->m_fLastStrumMusicSeconds = -1;
|
||||||
|
break;
|
||||||
|
case ButtonType_Hopo:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if( score == TNS_None )
|
if( score == TNS_None )
|
||||||
{
|
{
|
||||||
switch( pbt )
|
switch( pbt )
|
||||||
{
|
{
|
||||||
DEFAULT_FAIL(pbt);
|
DEFAULT_FAIL(pbt);
|
||||||
case ButtonType_Strum:
|
|
||||||
case ButtonType_Step:
|
case ButtonType_Step:
|
||||||
{
|
DoTapScoreNone();
|
||||||
Message msg( "ScoreNone" );
|
|
||||||
MESSAGEMAN->Broadcast( msg );
|
|
||||||
|
|
||||||
const int iOldCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurCombo : 0;
|
|
||||||
const int iOldMissCombo = m_pPlayerStageStats ? m_pPlayerStageStats->m_iCurMissCombo : 0;
|
|
||||||
|
|
||||||
/* The only real way to tell if a mine has been scored is if it has disappeared
|
|
||||||
* but this only works for hit mines so update the scores for avoided mines here. */
|
|
||||||
if( m_pPrimaryScoreKeeper )
|
|
||||||
m_pPrimaryScoreKeeper->HandleTapScoreNone();
|
|
||||||
if( m_pSecondaryScoreKeeper )
|
|
||||||
m_pSecondaryScoreKeeper->HandleTapScoreNone();
|
|
||||||
|
|
||||||
SendComboMessages( iOldCombo, iOldMissCombo );
|
|
||||||
|
|
||||||
|
|
||||||
if( m_pLifeMeter )
|
|
||||||
m_pLifeMeter->HandleTapScoreNone();
|
|
||||||
// TODO: Remove use of PlayerNumber
|
|
||||||
PlayerNumber pn = PLAYER_INVALID;
|
|
||||||
if( m_pCombinedLifeMeter )
|
|
||||||
m_pCombinedLifeMeter->HandleTapScoreNone( pn );
|
|
||||||
|
|
||||||
if( PENALIZE_TAP_SCORE_NONE )
|
|
||||||
{
|
|
||||||
SetJudgment( TNS_Miss, false );
|
|
||||||
// the ScoreKeeper will subrtract points later.
|
|
||||||
|
|
||||||
// Score all active holds to NotHeld
|
|
||||||
for( int iTrack=0; iTrack<m_NoteData.GetNumTracks(); ++iTrack )
|
|
||||||
{
|
|
||||||
// Since this is being called every frame, let's not check the whole array every time.
|
|
||||||
// Instead, only check 1 beat back. Even 1 is overkill.
|
|
||||||
const int iStartCheckingAt = max( 0, iSongRow-BeatToNoteRow(1) );
|
|
||||||
NoteData::iterator begin, end;
|
|
||||||
m_NoteData.GetTapNoteRangeInclusive( iTrack, iStartCheckingAt, iSongRow+1, begin, end );
|
|
||||||
for( ; begin != end; ++begin )
|
|
||||||
{
|
|
||||||
TapNote &tn = begin->second;
|
|
||||||
if( tn.HoldResult.bActive )
|
|
||||||
{
|
|
||||||
tn.HoldResult.hns = HNS_LetGo;
|
|
||||||
|
|
||||||
HandleHoldScore( tn );
|
|
||||||
SetHoldJudgment( tn.result.tns, tn.HoldResult.hns, iTrack );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
case ButtonType_StrumFretsChanged:
|
||||||
case ButtonType_Hopo:
|
case ButtonType_Hopo:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if( score != TNS_None )
|
if( score != TNS_None )
|
||||||
@@ -1844,7 +1909,7 @@ done_checking_hopo:
|
|||||||
switch( pbt )
|
switch( pbt )
|
||||||
{
|
{
|
||||||
DEFAULT_FAIL(pbt);
|
DEFAULT_FAIL(pbt);
|
||||||
case ButtonType_Strum:
|
case ButtonType_StrumFretsChanged:
|
||||||
for( int i=0; i<m_NoteData.GetNumTracks(); i++ )
|
for( int i=0; i<m_NoteData.GetNumTracks(); i++ )
|
||||||
{
|
{
|
||||||
const TapNote &tn = m_NoteData.GetTapNote( i, iRowOfOverlappingNoteOrRow );
|
const TapNote &tn = m_NoteData.GetTapNote( i, iRowOfOverlappingNoteOrRow );
|
||||||
@@ -1869,7 +1934,7 @@ done_checking_hopo:
|
|||||||
switch( pbt )
|
switch( pbt )
|
||||||
{
|
{
|
||||||
DEFAULT_FAIL(pbt);
|
DEFAULT_FAIL(pbt);
|
||||||
case ButtonType_Strum:
|
case ButtonType_StrumFretsChanged:
|
||||||
{
|
{
|
||||||
// only pulse the shortest string fret
|
// only pulse the shortest string fret
|
||||||
int iLastFret = -1;
|
int iLastFret = -1;
|
||||||
|
|||||||
+10
-2
@@ -122,10 +122,18 @@ public:
|
|||||||
|
|
||||||
// Called when a fret, step, or strum type button changes
|
// Called when a fret, step, or strum type button changes
|
||||||
void Fret( int col, int row, const RageTimer &tm, bool bHeld, bool bRelease );
|
void Fret( int col, int row, const RageTimer &tm, bool bHeld, bool bRelease );
|
||||||
enum ButtonType { ButtonType_Step, ButtonType_Strum, ButtonType_Hopo };
|
|
||||||
|
// Called when the strum bar is pressed down
|
||||||
|
void Strum( int col, int row, const RageTimer &tm, bool bHeld, bool bRelease );
|
||||||
|
|
||||||
|
// Called when the strum window passes without a row being hit
|
||||||
|
void DoStrumMiss();
|
||||||
|
|
||||||
|
void DoTapScoreNone();
|
||||||
|
|
||||||
|
enum ButtonType { ButtonType_Step, ButtonType_StrumFretsChanged, ButtonType_Hopo };
|
||||||
void StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, bool bRelease, ButtonType gbt );
|
void StepStrumHopo( int col, int row, const RageTimer &tm, bool bHeld, bool bRelease, ButtonType gbt );
|
||||||
void Step( int col, int row, const RageTimer &tm, bool bHeld, bool bRelease ) { StepStrumHopo(col, row, tm, bHeld, bRelease, ButtonType_Step); }
|
void Step( int col, int row, const RageTimer &tm, bool bHeld, bool bRelease ) { StepStrumHopo(col, row, tm, bHeld, bRelease, ButtonType_Step); }
|
||||||
void Strum( int col, int row, const RageTimer &tm, bool bHeld, bool bRelease ) { StepStrumHopo(col, row, tm, bHeld, bRelease, ButtonType_Strum); }
|
|
||||||
|
|
||||||
// called by Fret for Hammer-ons and Pull-offs
|
// called by Fret for Hammer-ons and Pull-offs
|
||||||
void Hopo( int col, int row, const RageTimer &tm, bool bHeld, bool bRelease ) { StepStrumHopo(col, row, tm, bHeld, bRelease, ButtonType_Hopo); }
|
void Hopo( int col, int row, const RageTimer &tm, bool bHeld, bool bRelease ) { StepStrumHopo(col, row, tm, bHeld, bRelease, ButtonType_Hopo); }
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ void PlayerState::Reset()
|
|||||||
|
|
||||||
for( int i=0; i<NUM_INVENTORY_SLOTS; i++ )
|
for( int i=0; i<NUM_INVENTORY_SLOTS; i++ )
|
||||||
m_Inventory[i].MakeBlank();
|
m_Inventory[i].MakeBlank();
|
||||||
|
|
||||||
|
m_fLastStrumMusicSeconds = -1;
|
||||||
|
ClearHopoState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerState::Update( float fDelta )
|
void PlayerState::Update( float fDelta )
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ public:
|
|||||||
enum HealthState { HOT, ALIVE, DANGER, DEAD };
|
enum HealthState { HOT, ALIVE, DANGER, DEAD };
|
||||||
HealthState m_HealthState;
|
HealthState m_HealthState;
|
||||||
|
|
||||||
|
float m_fLastStrumMusicSeconds; // Set to the MusicSeconds of when the a strum button was pressed. If -1, the strum window has passed.
|
||||||
float m_fLastHopoNoteMusicSeconds; // Set to the MusicSeconds of the last note successfully strummed or hammered in a hopochain, -1, then there is no current hopo chain
|
float m_fLastHopoNoteMusicSeconds; // Set to the MusicSeconds of the last note successfully strummed or hammered in a hopochain, -1, then there is no current hopo chain
|
||||||
int m_iLastHopoNoteCol; // if -1, then there is no current hopo chain
|
int m_iLastHopoNoteCol; // if -1, then there is no current hopo chain
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user