add hammer-ons and pull-offs ("hopo")

This commit is contained in:
Chris Danford
2006-11-30 12:29:01 +00:00
parent 319158de1a
commit 6b07fcaed5
8 changed files with 175 additions and 8 deletions
+4
View File
@@ -682,6 +682,10 @@ void GameState::ResetMusicStatistics()
m_fCurBPS = 10; m_fCurBPS = 10;
m_bFreeze = false; m_bFreeze = false;
Actor::SetBGMTime( 0, 0 ); Actor::SetBGMTime( 0, 0 );
FOREACH_PlayerNumber( p )
m_pPlayerState[p]->m_fLastHopoNoteMusicSeconds = -1;
} }
void GameState::ResetStageStatistics() void GameState::ResetStageStatistics()
+1 -1
View File
@@ -159,7 +159,7 @@ public:
// //
// Let a lot of classes access this info here so the don't have to keep their own copies. // Let a lot of classes access this info here so the don't have to keep their own copies.
// //
float m_fMusicSeconds; // time into the current song float m_fMusicSeconds; // time into the current song, not scaled by music rate
float m_fSongBeat; float m_fSongBeat;
float m_fSongBeatNoOffset; float m_fSongBeatNoOffset;
float m_fCurBPS; float m_fCurBPS;
+6
View File
@@ -466,6 +466,12 @@ int NoteData::GetNumTapNotes( int iStartIndex, int iEndIndex ) const
return iNumNotes; return iNumNotes;
} }
int NoteData::GetNumTapNotesInRow( int iRow ) const
{
// Optimization opportunity: There's no need to instantiate row iterators if we're only checking one row.
return GetNumTapNotes( iRow, iRow+1 );
}
int NoteData::GetNumRowsWithTap( int iStartIndex, int iEndIndex ) const int NoteData::GetNumRowsWithTap( int iStartIndex, int iEndIndex ) const
{ {
int iNumNotes = 0; int iNumNotes = 0;
+1
View File
@@ -182,6 +182,7 @@ public:
float GetFirstBeat() const { return NoteRowToBeat( GetFirstRow() ); } float GetFirstBeat() const { return NoteRowToBeat( GetFirstRow() ); }
float GetLastBeat() const { return NoteRowToBeat( GetLastRow() ); } float GetLastBeat() const { return NoteRowToBeat( GetLastRow() ); }
int GetNumTapNotes( int iStartIndex = 0, int iEndIndex = MAX_NOTE_ROW ) const; int GetNumTapNotes( int iStartIndex = 0, int iEndIndex = MAX_NOTE_ROW ) const;
int GetNumTapNotesInRow( int iRow ) const;
int GetNumMines( int iStartIndex = 0, int iEndIndex = MAX_NOTE_ROW ) const; int GetNumMines( int iStartIndex = 0, int iEndIndex = MAX_NOTE_ROW ) const;
int GetNumRowsWithTap( int iStartIndex = 0, int iEndIndex = MAX_NOTE_ROW ) const; int GetNumRowsWithTap( int iStartIndex = 0, int iEndIndex = MAX_NOTE_ROW ) const;
int GetNumRowsWithTapOrHoldHead( int iStartIndex = 0, int iEndIndex = MAX_NOTE_ROW ) const; int GetNumRowsWithTapOrHoldHead( int iStartIndex = 0, int iEndIndex = MAX_NOTE_ROW ) const;
+147 -3
View File
@@ -48,6 +48,7 @@ RString ATTACK_DISPLAY_X_NAME( size_t p, size_t both_sides ) { return "AttackDis
/* Distance to search for a note in Step(), in seconds. */ /* Distance to search for a note in Step(), in seconds. */
static const float StepSearchDistance = 1.0f; static const float StepSearchDistance = 1.0f;
static const float JUMP_WINDOW_SECONDS = 0.25f; static const float JUMP_WINDOW_SECONDS = 0.25f;
static const float HOPO_CHAIN_SECONDS = 0.5f;
void TimingWindowSecondsInit( size_t /*TimingWindow*/ i, RString &sNameOut, float &defaultValueOut ) void TimingWindowSecondsInit( size_t /*TimingWindow*/ i, RString &sNameOut, float &defaultValueOut )
{ {
@@ -1078,13 +1079,77 @@ void Player::Fret( int col, int row, const RageTimer &tm, bool bHeld, bool bRele
DEBUG_ASSERT_M( col >= 0 && col <= m_NoteData.GetNumTracks(), ssprintf("%i, %i", col, m_NoteData.GetNumTracks()) ); DEBUG_ASSERT_M( col >= 0 && col <= m_NoteData.GetNumTracks(), ssprintf("%i, %i", col, m_NoteData.GetNumTracks()) );
m_vbFretIsDown[ col ] = !bRelease; m_vbFretIsDown[ col ] = !bRelease;
// Handle hammer-ons and pull-offs
const float fPositionSeconds = GAMESTATE->m_fMusicSeconds - tm.Ago();
int iHopoCol = -1;
bool bDoHopo =
m_pPlayerState->m_fLastHopoNoteMusicSeconds != -1 &&
fPositionSeconds <= m_pPlayerState->m_fLastHopoNoteMusicSeconds + HOPO_CHAIN_SECONDS;
if( bDoHopo )
{
// do a Hopo if:
// pressed fret if no higher fret is held
// on next lowest pressed fret when the highest held fret is released
bool bHigherFretIsDown = false;
for( int i=col+1; i<m_NoteData.GetNumTracks(); i++ )
{
if( m_vbFretIsDown[i] )
{
bHigherFretIsDown = true;
break;
}
}
if( bHigherFretIsDown )
bDoHopo = false;
}
if( bDoHopo )
{
if( !bRelease )
{
// hammer-on
iHopoCol = col;
}
else
{
// pull-off
// find next lowest fret that is held
for( int i=col-1; i>=0; i-- )
{
if( m_vbFretIsDown[i] )
{
iHopoCol = i;
break;
}
}
if( iHopoCol == -1 )
bDoHopo = false;
}
}
if( bDoHopo )
Hopo( iHopoCol, row, tm, bHeld, bRelease );
} }
void Player::StepOrStrum( 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;
switch( pbt )
{
DEFAULT_FAIL(pbt);
case ButtonType_Strum:
if( bRelease )
return;
break;
case ButtonType_Hopo:
bRelease = false;
break;
case ButtonType_Step:
break;
}
const float fPositionSeconds = GAMESTATE->m_fMusicSeconds - tm.Ago(); const float fPositionSeconds = GAMESTATE->m_fMusicSeconds - tm.Ago();
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;
@@ -1215,6 +1280,7 @@ void Player::StepOrStrum( int col, int row, const RageTimer &tm, bool bHeld, boo
case ButtonType_Strum: case ButtonType_Strum:
iRowOfOverlappingNoteOrRow = GetClosestNonEmptyRow( BeatToNoteRow(fSongBeat), iStepSearchRows, iStepSearchRows, false ); iRowOfOverlappingNoteOrRow = GetClosestNonEmptyRow( BeatToNoteRow(fSongBeat), iStepSearchRows, iStepSearchRows, false );
break; break;
case ButtonType_Hopo:
case ButtonType_Step: case ButtonType_Step:
iRowOfOverlappingNoteOrRow = GetClosestNote( col, BeatToNoteRow(fSongBeat), iStepSearchRows, iStepSearchRows, false ); iRowOfOverlappingNoteOrRow = GetClosestNote( col, BeatToNoteRow(fSongBeat), iStepSearchRows, iStepSearchRows, false );
break; break;
@@ -1261,6 +1327,7 @@ void Player::StepOrStrum( int col, int row, const RageTimer &tm, bool bHeld, boo
case ButtonType_Strum: case ButtonType_Strum:
pTN = &tnDummy; pTN = &tnDummy;
break; break;
case ButtonType_Hopo:
case ButtonType_Step: case ButtonType_Step:
NoteData::iterator iter = m_NoteData.FindTapNote( col, iRowOfOverlappingNoteOrRow ); NoteData::iterator iter = m_NoteData.FindTapNote( col, iRowOfOverlappingNoteOrRow );
DEBUG_ASSERT( iter!= m_NoteData.end(col) ); DEBUG_ASSERT( iter!= m_NoteData.end(col) );
@@ -1366,14 +1433,81 @@ void Player::StepOrStrum( int col, int row, const RageTimer &tm, bool bHeld, boo
case ButtonType_Strum: case ButtonType_Strum:
{ {
bool bNoteRowMatchesFrets = true; bool bNoteRowMatchesFrets = true;
int iFirstNoteCol = -1;
for( int i=0; i<GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer; i++ ) for( int i=0; i<GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer; i++ )
{ {
const TapNote &tn = m_NoteData.GetTapNote( i, iRowOfOverlappingNoteOrRow ); const TapNote &tn = m_NoteData.GetTapNote( i, iRowOfOverlappingNoteOrRow );
bool bNoteMatchesFret = m_vbFretIsDown[i] == (tn.type != TapNote::empty); bool bIsNote = (tn.type != TapNote::empty);
bNoteRowMatchesFrets &= bNoteMatchesFret; if( iFirstNoteCol == -1 && bIsNote )
iFirstNoteCol = i;
// Extra notes to the left (higher up on the string) can be held without penalty. It's necessary to hold
// the extra frets or pull-offs.
if( iFirstNoteCol == -1 )
continue;
bool bNoteMatchesFret = m_vbFretIsDown[i] == bIsNote;
if( !bNoteMatchesFret )
{
bNoteRowMatchesFrets = false;
break;
}
} }
ASSERT( iFirstNoteCol != -1 );
if( !bNoteRowMatchesFrets ) if( !bNoteRowMatchesFrets )
{
score = TNS_None; score = TNS_None;
}
else
{
m_pPlayerState->m_fLastHopoNoteMusicSeconds = fStepSeconds;
}
}
break;
case ButtonType_Hopo:
{
bool bDidHopo = true;
{
// only can hopo on a row with one note
if( m_NoteData.GetNumTapNotesInRow(iRowOfOverlappingNoteOrRow) != 1 )
{
bDidHopo = false;
goto done_checking_hopo;
}
const TapNote &tn = m_NoteData.GetTapNote( col, iRowOfOverlappingNoteOrRow );
if( tn.type == TapNote::empty )
{
bDidHopo = false;
goto done_checking_hopo;
}
int iRowsAgoLastNote = 100000; // TODO: find more reasonable value based on HOPO_CHAIN_SECONDS?
NoteData::all_tracks_reverse_iterator iter = m_NoteData.GetTapNoteRangeAllTracksReverse( iRowsAgoLastNote-iRowsAgoLastNote, iRowOfOverlappingNoteOrRow-1 );
ASSERT( !iter.IsAtEnd() ); // there must have been a note that started the hopo
if( !NoteDataWithScoring::IsRowCompletelyJudged(m_NoteData, iter.Row(), m_pPlayerState->m_PlayerNumber) )
{
bDidHopo = false;
goto done_checking_hopo;
}
const TapNoteResult &lastTNR = NoteDataWithScoring::LastTapNoteWithResult( m_NoteData, iter.Row(), m_pPlayerState->m_PlayerNumber ).result;
if( lastTNR.tns <= TNS_Miss )
{
bDidHopo = false;
goto done_checking_hopo;
}
}
done_checking_hopo:
if( !bDidHopo )
{
score = TNS_None;
}
else
{
m_pPlayerState->m_fLastHopoNoteMusicSeconds = fStepSeconds;
}
} }
break; break;
case ButtonType_Step: case ButtonType_Step:
@@ -1437,6 +1571,7 @@ void Player::StepOrStrum( int col, int row, const RageTimer &tm, bool bHeld, boo
} }
} }
break; break;
case ButtonType_Hopo:
case ButtonType_Step: case ButtonType_Step:
pTN->result.tns = score; pTN->result.tns = score;
pTN->result.fTapNoteOffset = -fNoteOffset; pTN->result.fTapNoteOffset = -fNoteOffset;
@@ -1465,6 +1600,13 @@ void Player::StepOrStrum( int col, int row, const RageTimer &tm, bool bHeld, boo
} }
} }
// check for hopo end
if( score <= TNS_Miss )
{
m_pPlayerState->m_fLastHopoNoteMusicSeconds = -1;
}
if( score != TNS_None ) if( score != TNS_None )
{ {
/* Search for keyed sounds separately. If we can't find a nearby note, search /* Search for keyed sounds separately. If we can't find a nearby note, search
@@ -1492,6 +1634,7 @@ void Player::StepOrStrum( int col, int row, const RageTimer &tm, bool bHeld, boo
} }
break; break;
case ButtonType_Step: case ButtonType_Step:
case ButtonType_Hopo:
const TapNote &tn = m_NoteData.GetTapNote( col, iRowOfOverlappingNoteOrRow ); const TapNote &tn = m_NoteData.GetTapNote( col, iRowOfOverlappingNoteOrRow );
if( tn.iKeysoundIndex >= 0 && tn.iKeysoundIndex < (int) m_vKeysounds.size() ) if( tn.iKeysoundIndex >= 0 && tn.iKeysoundIndex < (int) m_vKeysounds.size() )
m_vKeysounds[tn.iKeysoundIndex].Play(); m_vKeysounds[tn.iKeysoundIndex].Play();
@@ -1515,6 +1658,7 @@ void Player::StepOrStrum( int col, int row, const RageTimer &tm, bool bHeld, boo
} }
break; break;
case ButtonType_Step: case ButtonType_Step:
case ButtonType_Hopo:
m_pNoteField->Step( col, score ); m_pNoteField->Step( col, score );
break; break;
} }
+10 -4
View File
@@ -113,11 +113,17 @@ public:
void CrossedRow( int iNoteRow, const RageTimer &now ); void CrossedRow( int iNoteRow, const RageTimer &now );
void CrossedMineRow( int iNoteRow, const RageTimer &now ); void CrossedMineRow( int iNoteRow, const RageTimer &now );
bool IsOniDead() const; bool IsOniDead() const;
// 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 }; enum ButtonType { ButtonType_Step, ButtonType_Strum, ButtonType_Hopo };
void StepOrStrum( 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 ) { StepOrStrum(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 ) { StepOrStrum(col, row, tm, bHeld, bRelease, ButtonType_Strum); } 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
void Hopo( int col, int row, const RageTimer &tm, bool bHeld, bool bRelease ) { StepStrumHopo(col, row, tm, bHeld, bRelease, ButtonType_Hopo); }
void RandomizeNotes( int iNoteRow ); void RandomizeNotes( int iNoteRow );
void FadeToFail(); void FadeToFail();
void CacheAllUsedNoteSkins(); void CacheAllUsedNoteSkins();
+2
View File
@@ -19,6 +19,8 @@ void PlayerState::Reset()
m_HealthState = ALIVE; m_HealthState = ALIVE;
m_fLastHopoNoteMusicSeconds = -1;
m_PlayerController = PC_HUMAN; m_PlayerController = PC_HUMAN;
m_iCpuSkill = 5; m_iCpuSkill = 5;
+4
View File
@@ -34,6 +34,9 @@ public:
enum HealthState { HOT, ALIVE, DANGER, DEAD }; enum HealthState { HOT, ALIVE, DANGER, DEAD };
HealthState m_HealthState; HealthState m_HealthState;
// Set to the MusicSeconds of the last note successfully strummed or hammered in a hammer chain
float m_fLastHopoNoteMusicSeconds; // if -1, then there is no current hammer chain
PlayerController m_PlayerController; PlayerController m_PlayerController;
// //
@@ -51,6 +54,7 @@ public:
float m_fSecondsUntilAttacksPhasedOut;// positive means PlayerAI is still affected float m_fSecondsUntilAttacksPhasedOut;// positive means PlayerAI is still affected
bool m_bAttackBeganThisUpdate; // flag for other objects to watch (play sounds) bool m_bAttackBeganThisUpdate; // flag for other objects to watch (play sounds)
bool m_bAttackEndedThisUpdate; // flag for other objects to watch (play sounds) bool m_bAttackEndedThisUpdate; // flag for other objects to watch (play sounds)
AttackArray m_ActiveAttacks; AttackArray m_ActiveAttacks;
vector<Attack> m_ModsToApply; vector<Attack> m_ModsToApply;