add hammer-ons and pull-offs ("hopo")
This commit is contained in:
@@ -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()
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user