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_bFreeze = false;
Actor::SetBGMTime( 0, 0 );
FOREACH_PlayerNumber( p )
m_pPlayerState[p]->m_fLastHopoNoteMusicSeconds = -1;
}
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.
//
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_fSongBeatNoOffset;
float m_fCurBPS;
+6
View File
@@ -466,6 +466,12 @@ int NoteData::GetNumTapNotes( int iStartIndex, int iEndIndex ) const
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 iNumNotes = 0;
+1
View File
@@ -182,6 +182,7 @@ public:
float GetFirstBeat() const { return NoteRowToBeat( GetFirstRow() ); }
float GetLastBeat() const { return NoteRowToBeat( GetLastRow() ); }
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 GetNumRowsWithTap( 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. */
static const float StepSearchDistance = 1.0f;
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 )
{
@@ -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()) );
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() )
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 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:
iRowOfOverlappingNoteOrRow = GetClosestNonEmptyRow( BeatToNoteRow(fSongBeat), iStepSearchRows, iStepSearchRows, false );
break;
case ButtonType_Hopo:
case ButtonType_Step:
iRowOfOverlappingNoteOrRow = GetClosestNote( col, BeatToNoteRow(fSongBeat), iStepSearchRows, iStepSearchRows, false );
break;
@@ -1261,6 +1327,7 @@ void Player::StepOrStrum( int col, int row, const RageTimer &tm, bool bHeld, boo
case ButtonType_Strum:
pTN = &tnDummy;
break;
case ButtonType_Hopo:
case ButtonType_Step:
NoteData::iterator iter = m_NoteData.FindTapNote( col, iRowOfOverlappingNoteOrRow );
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:
{
bool bNoteRowMatchesFrets = true;
int iFirstNoteCol = -1;
for( int i=0; i<GAMESTATE->GetCurrentStyle()->m_iColsPerPlayer; i++ )
{
const TapNote &tn = m_NoteData.GetTapNote( i, iRowOfOverlappingNoteOrRow );
bool bNoteMatchesFret = m_vbFretIsDown[i] == (tn.type != TapNote::empty);
bNoteRowMatchesFrets &= bNoteMatchesFret;
bool bIsNote = (tn.type != TapNote::empty);
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 )
{
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;
case ButtonType_Step:
@@ -1437,6 +1571,7 @@ void Player::StepOrStrum( int col, int row, const RageTimer &tm, bool bHeld, boo
}
}
break;
case ButtonType_Hopo:
case ButtonType_Step:
pTN->result.tns = score;
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 )
{
/* 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;
case ButtonType_Step:
case ButtonType_Hopo:
const TapNote &tn = m_NoteData.GetTapNote( col, iRowOfOverlappingNoteOrRow );
if( tn.iKeysoundIndex >= 0 && tn.iKeysoundIndex < (int) m_vKeysounds.size() )
m_vKeysounds[tn.iKeysoundIndex].Play();
@@ -1515,6 +1658,7 @@ void Player::StepOrStrum( int col, int row, const RageTimer &tm, bool bHeld, boo
}
break;
case ButtonType_Step:
case ButtonType_Hopo:
m_pNoteField->Step( col, score );
break;
}
+10 -4
View File
@@ -113,11 +113,17 @@ public:
void CrossedRow( int iNoteRow, const RageTimer &now );
void CrossedMineRow( int iNoteRow, const RageTimer &now );
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 );
enum ButtonType { ButtonType_Step, ButtonType_Strum };
void StepOrStrum( 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 Strum( int col, int row, const RageTimer &tm, bool bHeld, bool bRelease ) { StepOrStrum(col, row, tm, bHeld, bRelease, ButtonType_Strum); }
enum ButtonType { ButtonType_Step, ButtonType_Strum, ButtonType_Hopo };
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 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 FadeToFail();
void CacheAllUsedNoteSkins();
+2
View File
@@ -19,6 +19,8 @@ void PlayerState::Reset()
m_HealthState = ALIVE;
m_fLastHopoNoteMusicSeconds = -1;
m_PlayerController = PC_HUMAN;
m_iCpuSkill = 5;
+4
View File
@@ -34,6 +34,9 @@ public:
enum HealthState { HOT, ALIVE, DANGER, DEAD };
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;
//
@@ -51,6 +54,7 @@ public:
float m_fSecondsUntilAttacksPhasedOut;// positive means PlayerAI is still affected
bool m_bAttackBeganThisUpdate; // flag for other objects to watch (play sounds)
bool m_bAttackEndedThisUpdate; // flag for other objects to watch (play sounds)
AttackArray m_ActiveAttacks;
vector<Attack> m_ModsToApply;