From bb7482e722685ea83745ad437db77d482e5d0b77 Mon Sep 17 00:00:00 2001 From: Glenn Maynard Date: Tue, 25 Jan 2005 05:02:35 +0000 Subject: [PATCH] evil big commit (sorry): Remove m_HoldNote array entirely. Remove 2sAnd3s. Store hold note durations in the hold head. hold_tail only still exists when InsertHoldTails is used, to make SM parsing a little easier (may go away). Add helpers for iterating over ranges while including or excluding adjacent and overlapping hold notes. Range operations are now [start,end) instead of [start,end]. (probably more details coming to sm-dev soon) --- stepmania/src/NoteData.cpp | 687 +++++++++++++------------- stepmania/src/NoteData.h | 68 +-- stepmania/src/NoteDataUtil.cpp | 347 +++++++------ stepmania/src/NoteDataWithScoring.cpp | 34 +- stepmania/src/NoteField.cpp | 93 ++-- stepmania/src/NoteTypes.h | 7 +- stepmania/src/NotesLoaderDWI.cpp | 33 +- stepmania/src/NotesLoaderKSF.cpp | 17 +- stepmania/src/NotesWriterDWI.cpp | 1 - stepmania/src/Player.cpp | 192 ++++--- stepmania/src/ScreenEdit.cpp | 44 +- stepmania/src/ScreenGameplay.cpp | 10 +- 12 files changed, 771 insertions(+), 762 deletions(-) diff --git a/stepmania/src/NoteData.cpp b/stepmania/src/NoteData.cpp index 0d237df310..b36f68dfa0 100644 --- a/stepmania/src/NoteData.cpp +++ b/stepmania/src/NoteData.cpp @@ -30,69 +30,82 @@ void NoteData::SetNumTracks( int iNewNumTracks ) ASSERT( iNewNumTracks > 0 ); m_TapNotes.resize( iNewNumTracks ); - - /* Remove all hold notes that are out of bounds. */ - // Iterate backwards so that we can delete. - for( int h = m_HoldNotes.size()-1; h >= 0; --h ) - if( m_HoldNotes[h].iTrack >= iNewNumTracks ) - m_HoldNotes.erase( m_HoldNotes.begin()+h ); } -/* Clear [rowBegin,rowEnd]; that is, including rowEnd. */ +/* Clear [rowBegin,rowEnd). */ void NoteData::ClearRangeForTrack( int rowBegin, int rowEnd, int iTrack ) { /* Optimization: if the range encloses everything, just clear the whole maps. */ if( rowBegin == 0 && rowEnd == MAX_NOTE_ROW ) { m_TapNotes[iTrack].clear(); - - for( int i = GetNumHoldNotes()-1; i >= 0; --i ) - { - HoldNote hn = GetHoldNote(i); - if( hn.iTrack != iTrack ) - continue; - this->RemoveHoldNote( i ); - } + return; } - /* Crop or split hold notes overlapping the range. */ - for( int i = GetNumHoldNotes()-1; i >= 0; --i ) // for each HoldNote + iterator begin, end; + GetTapNoteRangeInclusive( iTrack, rowBegin, rowEnd, begin, end ); + + if( begin != end && begin->first < rowBegin && begin->first + begin->second.iDuration > rowEnd ) { - HoldNote hn = GetHoldNote(i); - if( hn.iTrack != iTrack ) - continue; + /* A hold note overlaps the whole range. Truncate it, and add the remainder to + * the end. */ + TapNote tn1 = begin->second; + TapNote tn2 = tn1; - if( !hn.RangeOverlaps(rowBegin, rowEnd) ) - continue; + int iEndRow = begin->first + tn1.iDuration; + int iRow = begin->first; - this->RemoveHoldNote( i ); + tn1.iDuration = rowBegin - iRow; + tn2.iDuration = iEndRow - rowEnd; - /* If the range encloses the hold note completely, just delete it. */ - if( hn.ContainedByRange(rowBegin, rowEnd) ) - continue; + SetTapNote( iTrack, iRow, tn1 ); + SetTapNote( iTrack, rowEnd, tn2 ); - if( hn.RangeInside(rowBegin, rowEnd) ) - { - /* The hold note encloses the range, so we need to split the hold note. */ - HoldNote hnLater(hn); - hn.iEndRow = rowBegin; - hnLater.iStartRow = rowEnd; - this->AddHoldNote( hn ); - this->AddHoldNote( hnLater ); - continue; - } + /* We may have invalidated our iterators. */ + GetTapNoteRangeInclusive( iTrack, rowBegin, rowEnd, begin, end ); + } + else if( begin != end && begin->first < rowBegin ) + { + /* A hold note overlaps the beginning of the range. Truncate it. */ + TapNote &tn1 = begin->second; + int iRow = begin->first; + tn1.iDuration = rowBegin - iRow; - if( hn.iStartRow < rowBegin ) - hn.iEndRow = min( hn.iEndRow, rowBegin ); - else - hn.iStartRow = max( hn.iStartRow, rowEnd ); - this->AddHoldNote( hn ); + ++begin; } - /* Clear other notes in the region. */ - FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE( *this, iTrack, r, rowBegin, rowEnd ) - SetTapNote( iTrack, r, TAP_EMPTY ); + if( begin != end ) + { + iterator prev = end; + --prev; + TapNote tn = begin->second; + int iRow = prev->first; + if( tn.type == TapNote::hold_head && iRow + tn.iDuration > rowEnd ) + { + /* A hold note overlaps the end of the range. Separate it. */ + SetTapNote( iTrack, iRow, TAP_EMPTY ); + + int iAdd = rowEnd - iRow; + tn.iDuration -= iAdd; + iRow += iAdd; + SetTapNote( iTrack, iRow, tn ); + end = prev; + } + + /* We may have invalidated our iterators. */ + GetTapNoteRangeInclusive( iTrack, rowBegin, rowEnd, begin, end ); + } + + while( begin != end ) + { + iterator next = begin; + ++next; + + RemoveTapNote( iTrack, begin ); + + begin = next; + } } void NoteData::ClearRange( int rowBegin, int rowEnd ) @@ -105,7 +118,6 @@ void NoteData::ClearAll() { for( int t=0; t rowFromEnd ) + return; /* empty range */ + + const int rowToEnd = (rowFromEnd-rowFromBegin) + rowToBegin; + const int iMoveBy = rowToBegin-rowFromBegin; /* Clear the region. */ ClearRange( rowToBegin, rowToEnd ); - /* Copy everything except for hold notes. */ for( int t=0; tsecond; + if( head.type == TapNote::empty ) continue; + + if( head.type == TapNote::hold_head ) + { + int iStartRow = begin->first + iMoveBy; + int iEndRow = iStartRow + head.iDuration; + + iStartRow = clamp( iStartRow, rowToBegin, rowToEnd ); + iEndRow = clamp( iEndRow, rowToBegin, rowToEnd ); + + this->AddHoldNote( t, iStartRow, iEndRow, head ); + } + else + { + int iTo = begin->first + iMoveBy; + if( iTo >= rowToBegin && iTo <= rowToEnd ) + this->SetTapNote( t, iTo, head ); } - - int iTo = rowToBegin + iFrom - rowFromBegin; - this->SetTapNote( t, iTo, tn ); } } - - /* Copy hold notes. */ - for( int i=0; iGetNumHoldNotes(); ++j ) // for each HoldNote - { - const HoldNote &hn2 = this->GetHoldNote(j); - if( hn2.iEndRow == hn.iStartRow ) - iEarlierHoldNote = j; - if( hn2.iStartRow == hn.iEndRow ) - iLaterHoldNote = j; - } - - if( iEarlierHoldNote != -1 && iLaterHoldNote != -1 ) - { - HoldNote &hnEarlier = this->GetHoldNote( iEarlierHoldNote ); - const HoldNote &hnLater = this->GetHoldNote( iLaterHoldNote ); - hnEarlier.iEndRow = hnLater.iEndRow; - - this->RemoveHoldNote( iLaterHoldNote ); - } - else if( iEarlierHoldNote == -1 && iLaterHoldNote == -1 ) - AddHoldNote( hn ); - else if( iEarlierHoldNote != -1 ) - { - HoldNote &hn2 = this->GetHoldNote(iEarlierHoldNote); - hn2.iEndRow = hn.iEndRow; - } - else if( iLaterHoldNote != -1 ) - { - HoldNote &hn2 = this->GetHoldNote(iLaterHoldNote); - hn2.iStartRow = hn.iStartRow; - } - else - FAIL_M(ssprintf("%i,%i", iEarlierHoldNote, iLaterHoldNote)); - } -} - -void NoteData::Config( const NoteData& from ) -{ - SetNumTracks( from.GetNumTracks() ); } void NoteData::CopyAll( const NoteData& from ) { - Config(from); + SetNumTracks( from.GetNumTracks() ); ClearAll(); for( int c=0; c=0 && add.iEndRow>=0 ); + ASSERT( iStartRow>=0 && iEndRow>=0 ); + ASSERT( iEndRow >= iStartRow ); + + tn.iDuration = iEndRow - iStartRow; + /* Include adjacent (non-overlapping) hold notes, since we need to merge with them. */ + iterator begin, end; + GetTapNoteRangeInclusive( iTrack, iStartRow, iStartRow+tn.iDuration, begin, end, true ); - // look for other hold notes that overlap and merge them - for( int i=0; ifirst; + if( it->second.type == TapNote::hold_head ) { - add.iStartRow = min(add.iStartRow, other.iStartRow); - add.iEndRow = max(add.iEndRow, other.iEndRow); - - // delete this HoldNote - RemoveHoldNote( i ); - --i; + if( iRow < iStartRow ) + { + /* extend tn up */ + int iExtendBy = iStartRow - iRow; + tn.iDuration += iExtendBy; + iStartRow -= iExtendBy; + } + else + { + /* extend tn down */ + int iEndRow = iRow + it->second.iDuration; + tn.iDuration = iEndRow - iStartRow; + } } } - int iAddStartIndex = add.iStartRow; - int iAddEndIndex = add.iEndRow; + /* Remove everything in the range. */ + while( begin != end ) + { + iterator next = begin; + ++next; - // delete TapNotes under this HoldNote - FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE( *this, add.iTrack, i, iAddStartIndex+1, iAddEndIndex ) - SetTapNote( add.iTrack, i, TAP_EMPTY ); + RemoveTapNote( iTrack, begin ); + + begin = next; + } + + /* Additionally, if there's a tap note lying at the end of our range, remove it, + * too. */ + SetTapNote( iTrack, iEndRow, TAP_EMPTY ); // add a tap note at the start of this hold - SetTapNote( add.iTrack, iAddStartIndex, TAP_ORIGINAL_HOLD_HEAD ); // Hold begin marker. Don't draw this, but do grade it. - - m_HoldNotes.push_back(add); + SetTapNote( iTrack, iStartRow, tn ); } -void NoteData::RemoveHoldNote( int iHoldIndex ) +/* Return true if a hold note lies on or adjacent to the given spot. */ +bool NoteData::IsHoldNoteAtBeat( int iTrack, int iRow, int *pHeadRow ) const { - ASSERT( iHoldIndex >= 0 && iHoldIndex < GetNumHoldNotes() ); - - HoldNote& hn = GetHoldNote(iHoldIndex); - - const int iHoldStartIndex = hn.iStartRow; - - // delete a tap note at the start of this hold - SetTapNote(hn.iTrack, iHoldStartIndex, TAP_EMPTY); - - // remove from list - m_HoldNotes.erase(m_HoldNotes.begin()+iHoldIndex, m_HoldNotes.begin()+iHoldIndex+1); -} - -/* Return true if a hold note lies on the given spot. Must be in 2sAnd3s. */ -bool NoteData::IsHoldNoteAtBeat( int iTrack, int iRow, int *pHeadRow, int *pTailRow ) const -{ - /* If neither the actual head nor tail row were requested, search for the head to - * determine the return value. */ int iDummy; if( pHeadRow == NULL ) pHeadRow = &iDummy; bool bFoundHead = false; - if( pHeadRow != NULL ) + /* Starting at iRow, search upwards. If we find a TapNote::hold_head, we're within + * a hold. If we find a tap, mine or attack, we're not--those never lie within hold + * notes. Ignore autoKeysound. */ + FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE_REVERSE( *this, iTrack, r, 0, iRow ) { - /* Starting at iRow, search upwards. If we find a TapNote::hold_head, we're within - * a hold. If we find a tap, mine or attack, we're not--those never lie within hold - * notes. Ignore autoKeysound. */ - FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE_REVERSE( *this, iTrack, r, 0, iRow ) + const TapNote &tn = GetTapNote( iTrack, r ); + switch( tn.type ) { - const TapNote &tn = GetTapNote( iTrack, r ); - switch( tn.type ) - { - case TapNote::hold_head: - *pHeadRow = r; - bFoundHead = true; - break; - - case TapNote::tap: - case TapNote::mine: - case TapNote::attack: - case TapNote::hold_tail: + case TapNote::hold_head: +// if( tn.iDuration + r <= iRow ) + if( tn.iDuration + r < iRow ) return false; - case TapNote::empty: - case TapNote::autoKeysound: - /* ignore */ - continue; - default: - FAIL_M( ssprintf("%i", tn.type) ); - } - - if( bFoundHead ) - break; - } - - /* If we didn't find a matching head, we're not within a hold note, so don't bother - * searching for a tail. */ - if( !bFoundHead ) - return false; - } - - bool bFoundTail = false; - if( pTailRow != NULL ) - { - FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE( *this, iTrack, r, iRow, MAX_NOTE_ROW ) - { - const TapNote &tn = GetTapNote( iTrack, r ); - - switch( tn.type ) - { - case TapNote::hold_tail: - *pTailRow = r; - bFoundTail = true; - break; - case TapNote::tap: - case TapNote::mine: - case TapNote::attack: - return false; - case TapNote::hold_head: - /* If iRow is a hold head, we're within a hold note, and need to continue searching; - * if any row after that is a head, we're not in it, so stop. This is different - * than above, since holds are [head,tail); the row of the tail isn't actually - * part of the hold. */ - if( r == iRow ) - continue; - - return false; - - case TapNote::empty: - case TapNote::autoKeysound: - /* ignore */ - continue; - default: - FAIL_M( ssprintf("%i", tn.type) ); - } - - if( bFoundTail ) - break; + *pHeadRow = r; return true; + + case TapNote::tap: + case TapNote::mine: + case TapNote::attack: + return false; + + case TapNote::empty: + case TapNote::autoKeysound: + /* ignore */ + continue; + default: + FAIL_M( ssprintf("%i", tn.type) ); } + + if( bFoundHead ) + break; } - - return bFoundHead || bFoundTail; + + return bFoundHead; } int NoteData::GetFirstRow() const @@ -482,13 +403,6 @@ int NoteData::GetFirstRow() const iEarliestRowFoundSoFar = min( iEarliestRowFoundSoFar, iRow ); } - for( int i=0; i iOldestRowFoundSoFar ) - iOldestRowFoundSoFar = GetHoldNote(i).iEndRow; + /* XXX: We might have a hold note near the end with autoplay sounds + * after it. Do something else with autoplay sounds ... */ + const TapNote &tn = GetTapNote( t, iRow ); + if( tn.type == TapNote::hold_head ) + iRow += tn.iDuration; + + iOldestRowFoundSoFar = max( iOldestRowFoundSoFar, iRow ); } return iOldestRowFoundSoFar; @@ -576,7 +491,7 @@ int NoteData::RowNeedsHands( const int row ) const { case TapNote::mine: case TapNote::empty: - case TapNote::hold_tail: + case TapNote::hold_head: // handled below continue; // skip these types - they don't count } ++iNumNotesThisIndex; @@ -588,11 +503,10 @@ int NoteData::RowNeedsHands( const int row ) const if( iNumNotesThisIndex < 3 ) { - /* We have at least one, but not enough. Count holds. */ - for( int j=0; jsecond.type != TapNote::hold_head ) + continue; + iNumHolds++; } } -} - - -/* "102000301" == - * "104444001" */ -void NoteData::ConvertHoldNotesTo2sAnd3s() -{ - // copy HoldNotes into the new structure, but expand them into 2s and 3s - for( int i=0; i& addTo ) { - for( unsigned i=0; i %i", iStartRow, iEndRow) ); + TrackMap &mapTrack = m_TapNotes[iTrack]; - const TrackMap &mapTrack = m_TapNotes[iTrack]; - begin = mapTrack.lower_bound( iStartRow ); - end = mapTrack.upper_bound( iEndRow ); + if( iStartRow > iEndRow ) + { + begin = end = mapTrack.end(); + return; + } + + if( iStartRow == 0 ) + begin = mapTrack.begin(); /* optimization */ + else if( iStartRow == MAX_NOTE_ROW ) + begin = mapTrack.end(); /* optimization */ + else + begin = mapTrack.lower_bound( iStartRow ); + + if( iEndRow == 0 ) + end = mapTrack.begin(); /* optimization */ + else if( iEndRow == MAX_NOTE_ROW ) + end = mapTrack.end(); /* optimization */ + else + end = mapTrack.lower_bound( iEndRow ); } + +/* Include hold notes that overlap the edges. If a hold note completely surrounds the given + * range, included it, too. If bIncludeAdjacent is true, also include hold notes adjacent to, + * but not overlapping, the edge. */ +void NoteData::GetTapNoteRangeInclusive( int iTrack, int iStartRow, int iEndRow, iterator &begin, iterator &end, bool bIncludeAdjacent ) +{ + GetTapNoteRange( iTrack, iStartRow, iEndRow, begin, end ); + + if( begin != this->begin(iTrack) ) + { + iterator prev = Decrement(begin); + + const TapNote &tn = prev->second; + if( tn.type == TapNote::hold_head ) + { + int iHoldStartRow = prev->first; + int iHoldEndRow = iHoldStartRow + tn.iDuration; + if( iHoldEndRow > iStartRow || (bIncludeAdjacent && iHoldEndRow == iStartRow ) ) + { + /* The previous note is a hold. */ + begin = prev; + } + } + } + + if( bIncludeAdjacent && end != this->end(iTrack) ) + { + /* Include the next note if it's a hold and starts on iEndRow. */ + const TapNote &tn = end->second; + int iHoldStartRow = end->first; + if( tn.type == TapNote::hold_head && iHoldStartRow == iEndRow ) + ++end; + } +} + +void NoteData::GetTapNoteRangeExclusive( int iTrack, int iStartRow, int iEndRow, iterator &begin, iterator &end ) +{ + GetTapNoteRange( iTrack, iStartRow, iEndRow, begin, end ); + + /* If end-1 is a hold_head, and extends beyond iEndRow, exclude it. */ + if( begin != end && end != this->begin(iTrack) ) + { + iterator prev = end; + --prev; + if( prev->second.type == TapNote::hold_head ) + { + int iStartRow = prev->first; + const TapNote &tn = prev->second; + if( iStartRow + tn.iDuration >= iEndRow ) + end = prev; + } + } +} + +void NoteData::GetTapNoteRange( int iTrack, int iStartRow, int iEndRow, TrackMap::const_iterator &begin, TrackMap::const_iterator &end ) const +{ + TrackMap::iterator const_begin, const_end; + const_cast(this)->GetTapNoteRange( iTrack, iStartRow, iEndRow, const_begin, const_end ); + begin = const_begin; + end = const_end; +} + +void NoteData::GetTapNoteRangeInclusive( int iTrack, int iStartRow, int iEndRow, TrackMap::const_iterator &begin, TrackMap::const_iterator &end, bool bIncludeAdjacent ) const +{ + TrackMap::iterator const_begin, const_end; + const_cast(this)->GetTapNoteRangeInclusive( iTrack, iStartRow, iEndRow, const_begin, const_end, bIncludeAdjacent ); + begin = const_begin; + end = const_end; +} + +void NoteData::GetTapNoteRangeExclusive( int iTrack, int iStartRow, int iEndRow, TrackMap::const_iterator &begin, TrackMap::const_iterator &end ) const +{ + TrackMap::iterator const_begin, const_end; + const_cast(this)->GetTapNoteRange( iTrack, iStartRow, iEndRow, const_begin, const_end ); + begin = const_begin; + end = const_end; +} + + + bool NoteData::GetNextTapNoteRowForAllTracks( int &rowInOut ) const { int iClosestNextRow = MAX_NOTE_ROW; @@ -873,6 +810,68 @@ bool NoteData::GetNextTapNoteRowForAllTracks( int &rowInOut ) const } } +void NoteData::InsertHoldTails() +{ + for( int t=0; tfirst; + const TapNote &tn = begin->second; + if( tn.type != TapNote::hold_head ) + continue; + + TapNote tail = tn; + tail.type = TapNote::hold_tail; + SetTapNote( t, iRow + tn.iDuration, tail ); + } + } +} + +void NoteData::RemoveHoldTails() +{ + for( int t=0; tfirst; + const TapNote &tn = begin->second; + if( tn.type != TapNote::hold_head ) + continue; + + /* Search forward until we find a hold_tail. */ + iterator tail = begin; + + while( tail != end && tail->second.type != TapNote::hold_tail ) + ++tail; + + if( tail == end ) + { + /* If we didn't find one, the hold is invalid; delete it. */ + RemoveTapNote( t, begin ); + } + else + { + /* Delete the tail, and update iDuration of the head. */ + if( next == tail ) + ++next; + int iEndRow = tail->first; + begin->second.iDuration = iEndRow - iStartRow; + RemoveTapNote( t, tail ); + } + } + } +} + /* * (c) 2001-2004 Chris Danford, Glenn Maynard * All rights reserved. diff --git a/stepmania/src/NoteData.h b/stepmania/src/NoteData.h index 6aa412d481..0cad53c22b 100644 --- a/stepmania/src/NoteData.h +++ b/stepmania/src/NoteData.h @@ -11,32 +11,27 @@ #define FOREACH_NONEMPTY_ROW_IN_TRACK( nd, track, row ) \ for( int row = -1; (nd).GetNextTapNoteRowForTrack(track,row); ) #define FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE( nd, track, row, start, last ) \ - for( int row = start-1; (nd).GetNextTapNoteRowForTrack(track,row) && row <= (last); ) + for( int row = start-1; (nd).GetNextTapNoteRowForTrack(track,row) && row < (last); ) #define FOREACH_NONEMPTY_ROW_IN_TRACK_RANGE_REVERSE( nd, track, row, start, last ) \ - for( int row = last+1; (nd).GetPrevTapNoteRowForTrack(track,row) && row >= (start); ) + for( int row = last; (nd).GetPrevTapNoteRowForTrack(track,row) && row >= (start); ) #define FOREACH_NONEMPTY_ROW_ALL_TRACKS( nd, row ) \ for( int row = -1; (nd).GetNextTapNoteRowForAllTracks(row); ) #define FOREACH_NONEMPTY_ROW_ALL_TRACKS_RANGE( nd, row, start, last ) \ - for( int row = start-1; (nd).GetNextTapNoteRowForAllTracks(row) && row <= (last); ) + for( int row = start-1; (nd).GetNextTapNoteRowForAllTracks(row) && row < (last); ) class NoteData { public: typedef map TrackMap; + typedef map::iterator iterator; + typedef map::const_iterator const_iterator; private: // There's no point in inserting empty notes into the map. // Any blank space in the map is defined to be empty. vector m_TapNotes; - vector m_HoldNotes; - public: - - /* Set up to hold the data in From; same number of tracks, same - * divisor. Doesn't allocate or copy anything. */ - void Config( const NoteData& from ); - NoteData(); ~NoteData(); void Init(); @@ -56,6 +51,30 @@ public: return TAP_EMPTY; } + iterator begin( int iTrack ) { return m_TapNotes[iTrack].begin(); } + const_iterator begin( int iTrack ) const { return m_TapNotes[iTrack].begin(); } + iterator end( int iTrack ) { return m_TapNotes[iTrack].end(); } + const_iterator end( int iTrack ) const { return m_TapNotes[iTrack].end(); } + + inline iterator FindTapNote( unsigned iTrack, int iRow ) { return m_TapNotes[iTrack].find( iRow ); } + inline const_iterator FindTapNote( unsigned iTrack, int iRow ) const { return m_TapNotes[iTrack].find( iRow ); } + void RemoveTapNote( unsigned iTrack, iterator it ) { m_TapNotes[iTrack].erase( it ); } + + /* Return an iterator range including exactly iStartRow to iEndRow. */ + void GetTapNoteRange( int iTrack, int iStartRow, int iEndRow, const_iterator &begin, const_iterator &end ) const; + void GetTapNoteRange( int iTrack, int iStartRow, int iEndRow, TrackMap::iterator &begin, TrackMap::iterator &end ); + + /* Return an iterator range include iStartRow to iEndRow. Extend the range to include + * hold notes overlapping the boundary. */ + void GetTapNoteRangeInclusive( int iTrack, int iStartRow, int iEndRow, const_iterator &begin, const_iterator &end, bool bIncludeAdjacent=false ) const; + void GetTapNoteRangeInclusive( int iTrack, int iStartRow, int iEndRow, iterator &begin, iterator &end, bool bIncludeAdjacent=false ); + + /* Return an iterator range include iStartRow to iEndRow. Shrink the range to exclude + * hold notes overlapping the boundary. */ + void GetTapNoteRangeExclusive( int iTrack, int iStartRow, int iEndRow, const_iterator &begin, const_iterator &end ) const; + void GetTapNoteRangeExclusive( int iTrack, int iStartRow, int iEndRow, iterator &begin, iterator &end ); + + // Use this to iterate over notes. // Returns the row index of the first TapNote on the track that has a row // index > afterRow. @@ -63,11 +82,10 @@ public: bool GetNextTapNoteRowForAllTracks( int &rowInOut ) const; bool GetPrevTapNoteRowForTrack( int track, int &rowInOut ) const; - void GetTapNoteRange( int iTrack, int iStartRow, int iEndRow, TrackMap::const_iterator &begin, TrackMap::const_iterator &end ) const; - void MoveTapNoteTrack( int dest, int src ); void SetTapNote( int track, int row, const TapNote& tn ); - + void AddHoldNote( int iTrack, int iStartRow, int iEndRow, TapNote tn ); // add note hold note merging overlapping HoldNotes and destroying TapNotes underneath + void ClearRangeForTrack( int rowBegin, int rowEnd, int iTrack ); void ClearRange( int rowBegin, int rowEnd ); void ClearAll(); @@ -97,19 +115,10 @@ public: void GetTracksHeldAtRow( int row, set& addTo ); int GetNumTracksHeldAtRow( int row ); - // - // used in edit/record - // - void AddHoldNote( HoldNote newNote ); // add note hold note merging overlapping HoldNotes and destroying TapNotes underneath - void RemoveHoldNote( int index ); - HoldNote &GetHoldNote( int index ) { ASSERT( index < (int) m_HoldNotes.size() ); return m_HoldNotes[index]; } - const HoldNote &GetHoldNote( int index ) const { ASSERT( index < (int) m_HoldNotes.size() ); return m_HoldNotes[index]; } - /* Determine if a given spot is within a hold note (being on the tail doesn't count). * Return true if so. If pHeadRow is non-NULL, return the row of the head. If pTailRow is - * non-NULL, return the row of the tail. This function is faster if pTailRow is NULL. - * Must be in 2sAnd3s. */ - bool IsHoldNoteAtBeat( int iTrack, int iRow, int *pHeadRow = NULL, int *pTailRow = NULL ) const; + * non-NULL, return the row of the tail. This function is faster if pTailRow is NULL. */ + bool IsHoldNoteAtBeat( int iTrack, int iRow, int *pHeadRow = NULL ) const; // // statistics @@ -127,18 +136,15 @@ public: // should hands also count as a jump? int GetNumDoubles( int iStartIndex = 0, int iEndIndex = MAX_NOTE_ROW ) const { return GetNumN( 2, iStartIndex, iEndIndex ); } /* optimization: for the default of start to end, use the second (faster) */ - int GetNumHoldNotes( const int iStartIndex, const int iEndIndex = MAX_NOTE_ROW ) const; - int GetNumHoldNotes() const { return m_HoldNotes.size(); } + int GetNumHoldNotes( int iStartIndex = 0, int iEndIndex = MAX_NOTE_ROW ) const; int RowNeedsHands( int row ) const; // Transformations void LoadTransformed( const NoteData& original, int iNewNumTracks, const int iOriginalTrackToTakeFrom[] ); // -1 for iOriginalTracksToTakeFrom means no track - // Convert between HoldNote representation and '2' and '3' markers in TapNotes - void Convert2sAnd3sToHoldNotes(); - void ConvertHoldNotesTo2sAnd3s(); - void To2sAnd3s( const NoteData& from ); - void From2sAnd3s( const NoteData& from ); + /* hold_tail is only used to make SM parsing easier. */ + void InsertHoldTails(); + void RemoveHoldTails(); /* adjusts iDuration */ }; diff --git a/stepmania/src/NoteDataUtil.cpp b/stepmania/src/NoteDataUtil.cpp index 0d2445a6a5..6edd914970 100644 --- a/stepmania/src/NoteDataUtil.cpp +++ b/stepmania/src/NoteDataUtil.cpp @@ -181,7 +181,8 @@ void NoteDataUtil::LoadFromSMNoteDataString( NoteData &out, CString sSMNoteData } } } - out.Convert2sAnd3sToHoldNotes(); + + out.RemoveHoldTails(); } void NoteDataUtil::GetSMNoteDataString( const NoteData &in_, CString ¬es_out ) @@ -189,8 +190,8 @@ void NoteDataUtil::GetSMNoteDataString( const NoteData &in_, CString ¬es_out // // Get note data // - NoteData in; - in.To2sAnd3s( in_ ); + NoteData in( in_ ); + in.InsertHoldTails(); float fLastBeat = in.GetLastBeat(); int iLastMeasure = int( fLastBeat/BEATS_PER_MEASURE ); @@ -264,21 +265,17 @@ void NoteDataUtil::LoadTransformedSlidingWindow( const NoteData &in, NoteData &o return; } - NoteData Original; - Original.To2sAnd3s( in ); - - out.Config( in ); out.SetNumTracks( iNewNumTracks ); int iCurTrackOffset = 0; int iTrackOffsetMin = 0; - int iTrackOffsetMax = abs( iNewNumTracks - Original.GetNumTracks() ); + int iTrackOffsetMax = abs( iNewNumTracks - in.GetNumTracks() ); int bOffsetIncreasing = true; int iLastMeasure = 0; int iMeasuresSinceChange = 0; - FOREACH_NONEMPTY_ROW_ALL_TRACKS( Original, r ) + FOREACH_NONEMPTY_ROW_ALL_TRACKS( in, r ) { const int iMeasure = r / ROWS_PER_MEASURE; if( iMeasure != iLastMeasure ) @@ -289,10 +286,10 @@ void NoteDataUtil::LoadTransformedSlidingWindow( const NoteData &in, NoteData &o // See if there is a hold crossing the beginning of this measure bool bHoldCrossesThisMeasure = false; - for( int t=0; t=0; i-- ) // iterate backwards so we can delete + for( int t=0; tsecond.type != TapNote::hold_head ) + continue; + begin->second.type = TapNote::tap; + } } } @@ -579,22 +563,19 @@ void NoteDataUtil::RemoveSimultaneousNotes( NoteData &in, int iMaxSimultaneous, int iTracksToRemove = max( 0, iTotalTracksPressed - iMaxSimultaneous ); for( int t=0; iTracksToRemove>0 && t=0; i-- ) - if( fmodf(inout.GetHoldNote(i).GetStartBeat(),1) != 0 ) // doesn't start on a beat - inout.RemoveHoldNote( i ); + inout.SetTapNote( t, i, TAP_EMPTY ); + } + } } +// Make all all quarter notes into jumps. void NoteDataUtil::Wide( NoteData &inout, int iStartIndex, int iEndIndex ) { - // Make all all quarter notes into jumps. - inout.ConvertHoldNotesTo2sAnd3s(); - /* Start on an even beat. */ iStartIndex = Quantize( iStartIndex, BeatToNoteRow(2.0f) ); - iEndIndex = min( iEndIndex, inout.GetLastRow() ); - - for( int i=iStartIndex; i iEndIndex ) - continue; + int iMineRow = r + tn.iDuration + BeatToNoteRow(0.5f); + if( iMineRow < iStartIndex || iMineRow > iEndIndex ) + continue; - // Only place a mines if there's not another step nearby - int iMineRangeBegin = iMineRow - BeatToNoteRow( 0.5f ) + 1; - int iMineRangeEnd = iMineRow + BeatToNoteRow( 0.5f ) - 1; - if( !inout.IsRangeEmpty(hn.iTrack, iMineRangeBegin, iMineRangeEnd) ) - continue; - - // Add a mine right after the hold end. - inout.SetTapNote( hn.iTrack, iMineRow, TAP_ADDITION_MINE ); + // Only place a mines if there's not another step nearby + int iMineRangeBegin = iMineRow - BeatToNoteRow( 0.5f ) + 1; + int iMineRangeEnd = iMineRow + BeatToNoteRow( 0.5f ) - 1; + if( !inout.IsRangeEmpty(iTrack, iMineRangeBegin, iMineRangeEnd) ) + continue; + + // Add a mine right after the hold end. + inout.SetTapNote( iTrack, iMineRow, TAP_ADDITION_MINE ); - // Convert all notes inout this row to mines. - for( int t=0; t 0 ); ASSERT( iStartIndex < iEndIndex ); ASSERT( iStartIndex >= 0 ); NoteData temp1, temp2; - temp1.Config( nd ); - temp2.Config( nd ); - - const int iFirstRowAtEndOfRegion = min( nd.GetLastRow(), iEndIndex ); - const int iScaledFirstRowAfterRegion = int(iStartIndex + (iEndIndex - iStartIndex) * fScale); + temp1.SetNumTracks( nd.GetNumTracks() ); + temp2.SetNumTracks( nd.GetNumTracks() ); if( iStartIndex != 0 ) temp1.CopyRange( nd, 0, iStartIndex ); - if( nd.GetLastRow() > iFirstRowAtEndOfRegion ) - temp1.CopyRange( nd, iFirstRowAtEndOfRegion, nd.GetLastRow(), iScaledFirstRowAfterRegion); - temp2.CopyRange( nd, iStartIndex, iFirstRowAtEndOfRegion ); + if( iEndIndex != MAX_NOTE_ROW ) + { + const int iScaledFirstRowAfterRegion = int(iStartIndex + (iEndIndex - iStartIndex) * fScale); + temp1.CopyRange( nd, iEndIndex, MAX_NOTE_ROW, iScaledFirstRowAfterRegion ); + } + temp2.CopyRange( nd, iStartIndex, iEndIndex ); nd.ClearAll(); - for( int r=0; r<=temp2.GetLastRow(); r++ ) + for( int t=0; tsecond; + if( tn.type != TapNote::hold_head ) + continue; + if( tn.HoldResult.hns == hns ) + ++iNumSuccessfulHolds; + } } + return iNumSuccessfulHolds; } @@ -89,7 +96,7 @@ int GetSuccessfulHands( const NoteData &in, int iStartIndex = 0, int iEndIndex = bool Missed = false; for( int t=0; tGetNumHoldNotes(); i++ ) + { - const HoldNote &hn = m_pNoteData->GetHoldNote(i); - if( hn.iTrack != c ) // this HoldNote doesn't belong to this column - continue; + NoteData::TrackMap::const_iterator begin, end; + m_pNoteData->GetTapNoteRangeInclusive( c, iFirstIndexToDraw, iLastIndexToDraw, begin, end ); - const HoldNoteResult &Result = hn.result; - if( Result.hns == HNS_OK ) // if this HoldNote was completed - continue; // don't draw anything + for( ; begin != end; ++begin ) + { + const TapNote &tn = begin->second; //m_pNoteData->GetTapNote(c, i); + if( tn.type != TapNote::hold_head ) + continue; // skip - // If no part of this HoldNote is on the screen, skip it - if( !hn.RangeOverlaps(iFirstIndexToDraw, iLastIndexToDraw) ) - continue; // skip + const HoldNoteResult &Result = tn.HoldResult; + if( Result.hns == HNS_OK ) // if this HoldNote was completed + continue; // don't draw anything - // TRICKY: If boomerang is on, then all notes in the range - // [iFirstIndexToDraw,iLastIndexToDraw] aren't necessarily visible. - // Test every note to make sure it's on screen before drawing - float fYStartOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToBeat(hn.iStartRow) ); - float fYEndOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToBeat(hn.iEndRow) ); - if( !( iFirstPixelToDraw <= fYEndOffset && fYEndOffset <= iLastPixelToDraw || - iFirstPixelToDraw <= fYStartOffset && fYStartOffset <= iLastPixelToDraw || - fYStartOffset < iFirstPixelToDraw && fYEndOffset > iLastPixelToDraw ) ) - { - continue; // skip + int iStartRow = begin->first; + int iEndRow = iStartRow + tn.iDuration; + + // TRICKY: If boomerang is on, then all notes in the range + // [iFirstIndexToDraw,iLastIndexToDraw] aren't necessarily visible. + // Test every note to make sure it's on screen before drawing + float fYStartOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToBeat(iStartRow) ); + float fYEndOffset = ArrowEffects::GetYOffset( m_pPlayerState, c, NoteRowToBeat(iEndRow) ); + if( !( iFirstPixelToDraw <= fYEndOffset && fYEndOffset <= iLastPixelToDraw || + iFirstPixelToDraw <= fYStartOffset && fYStartOffset <= iLastPixelToDraw || + fYStartOffset < iFirstPixelToDraw && fYEndOffset > iLastPixelToDraw ) ) + { + continue; // skip + } + + const bool bIsActive = tn.HoldResult.bActive; + const bool bIsHoldingNote = tn.HoldResult.bHeld; + if( bIsActive ) + SearchForSongBeat()->m_GhostArrowRow.SetHoldIsActive( c ); + + ASSERT_M( NoteRowToBeat(iStartRow) > -2000, ssprintf("%i %i %i", iStartRow, iEndRow, c) ); + SearchForBeat( CurDisplay, NextDisplay, NoteRowToBeat(iStartRow) ); + + bool bIsInSelectionRange = false; + if( m_iBeginMarker!=-1 && m_iEndMarker!=-1 ) + bIsInSelectionRange = (m_iBeginMarker <= iStartRow && iEndRow < m_iEndMarker); + + NoteDisplayCols *nd = CurDisplay->second; + HoldNote hn(c, iStartRow, iEndRow); + hn.result = tn.HoldResult; + + nd->display[c].DrawHold( hn, bIsHoldingNote, bIsActive, Result, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail, false, m_fYReverseOffsetPixels ); } - const bool bIsActive = hn.result.bActive; - const bool bIsHoldingNote = hn.result.bHeld; - if( bIsActive ) - SearchForSongBeat()->m_GhostArrowRow.SetHoldIsActive( hn.iTrack ); - - ASSERT_M( NoteRowToBeat(hn.iStartRow) > -2000, ssprintf("%i %i %i", hn.iStartRow, hn.iEndRow, hn.iTrack) ); - SearchForBeat( CurDisplay, NextDisplay, NoteRowToBeat(hn.iStartRow) ); - - bool bIsInSelectionRange = false; - if( m_iBeginMarker!=-1 && m_iEndMarker!=-1 ) - bIsInSelectionRange = hn.ContainedByRange( m_iBeginMarker, m_iEndMarker ); - - NoteDisplayCols *nd = CurDisplay->second; - nd->display[c].DrawHold( hn, bIsHoldingNote, bIsActive, Result, bIsInSelectionRange ? fSelectedRangeGlow : m_fPercentFadeToFail, false, m_fYReverseOffsetPixels ); } @@ -615,11 +624,12 @@ void NoteField::DrawPrimitives() { int i = begin->first; const TapNote &tn = begin->second; //m_pNoteData->GetTapNote(c, i); - if( tn.type == TapNote::empty ) // no note here - continue; // skip - - if( tn.type == TapNote::hold_head ) // this is a HoldNote begin marker. Grade it, but don't draw + switch( tn.type ) + { + case TapNote::empty: // no note here + case TapNote::hold_head: continue; // skip + } /* Don't draw hidden (fully judged) steps. */ if( tn.result.bHidden ) @@ -656,10 +666,7 @@ void NoteField::DrawPrimitives() bool bIsInSelectionRange = false; if( m_iBeginMarker!=-1 && m_iEndMarker!=-1 ) - { - int iBeat = i; - bIsInSelectionRange = m_iBeginMarker<=iBeat && iBeat<=m_iEndMarker; - } + bIsInSelectionRange = m_iBeginMarker<=i && i 0 ); @@ -303,11 +323,8 @@ bool DWILoader::LoadFromDWITokens( return true; } -/* STUPID ALERT! - * This value can be in either "HH:MM:SS.sssss", "MM:SS.sssss", "SSS.sssss" - * or milliseconds. - * What's even more dumb is that the value can contain a ':'. Colon is supposed to be a parameter separator! - */ +/* This value can be in either "HH:MM:SS.sssss", "MM:SS.sssss", "SSS.sssss" + * or milliseconds. */ float DWILoader::ParseBrokenDWITimestamp(const CString &arg1, const CString &arg2, const CString &arg3) { if(arg1.empty()) return 0; diff --git a/stepmania/src/NotesLoaderKSF.cpp b/stepmania/src/NotesLoaderKSF.cpp index 637ac527d0..331e0e4ce0 100644 --- a/stepmania/src/NotesLoaderKSF.cpp +++ b/stepmania/src/NotesLoaderKSF.cpp @@ -28,14 +28,13 @@ void KSFLoader::RemoveHoles( NoteData &out, const Song &song ) // else // LastRow = out.GetLastRow(); NoteData tmp; - tmp.SetNumTracks(out.GetNumTracks()); - tmp.CopyRange( &out, FromRow, out.GetLastRow() ); - out.ClearRange( FromRow, out.GetLastRow() ); - out.CopyRange( &tmp, 0, tmp.GetLastRow(), ToRow ); + tmp.SetNumTracks( out.GetNumTracks() ); + tmp.CopyRange( &out, FromRow, MAX_NOTE_ROW ); + out.ClearRange( FromRow, MAX_NOTE_ROW ); + out.CopyRange( &tmp, 0, MAX_NOTE_ROW, ToRow ); } /* - out.ConvertHoldNotesTo2sAnd3s(); for( t = 0; t < notedata.GetNumTracks(); ++t ) { const float CurBPM = song.GetBPMAtBeat( NoteRowToBeat(row) ); @@ -54,7 +53,6 @@ void KSFLoader::RemoveHoles( NoteData &out, const Song &song ) notedata.SetTapNote( t, row, TAP_EMPTY ); } } - out.Convert2sAnd3sToHoldNotes(); */ } #endif @@ -188,12 +186,7 @@ bool KSFLoader::LoadFromKSFFile( const CString &sPath, Steps &out, const Song &s if( iHoldStartRow[t] != -1 ) // this ends the hold { - HoldNote hn ( - t, /* button */ - BeatToNoteRow(iHoldStartRow[t]/(float)iTickCount), /* start */ - BeatToNoteRow((r-1)/(float)iTickCount) /* end */ - ); - notedata.AddHoldNote( hn ); + notedata.AddHoldNote( t, BeatToNoteRow(iHoldStartRow[t]/(float)iTickCount), BeatToNoteRow((r-1)/(float)iTickCount), TAP_ORIGINAL_HOLD_HEAD ); iHoldStartRow[t] = -1; } diff --git a/stepmania/src/NotesWriterDWI.cpp b/stepmania/src/NotesWriterDWI.cpp index 1ad7e7d653..50866820a3 100644 --- a/stepmania/src/NotesWriterDWI.cpp +++ b/stepmania/src/NotesWriterDWI.cpp @@ -190,7 +190,6 @@ void NotesWriterDWI::WriteDWINotesField( RageFile &f, const Steps &out, int star { NoteData notedata; out.GetNoteData( notedata ); - notedata.ConvertHoldNotesTo2sAnd3s(); const int iLastMeasure = int( notedata.GetLastBeat()/BEATS_PER_MEASURE ); for( int m=0; m<=iLastMeasure; m++ ) // foreach measure diff --git a/stepmania/src/Player.cpp b/stepmania/src/Player.cpp index c5641e4f00..9ab3ac89a8 100644 --- a/stepmania/src/Player.cpp +++ b/stepmania/src/Player.cpp @@ -338,106 +338,112 @@ void Player::Update( float fDeltaTime ) // // update HoldNotes logic // - for( int i=0; i < m_NoteData.GetNumHoldNotes(); i++ ) // for each HoldNote + for( int iTrack=0; iTrackm_PlayerNumber; - - const StyleInput StyleI( pn, hn.iTrack ); - const GameInput GameI = GAMESTATE->GetCurrentStyle()->StyleInputToGameInput( StyleI ); - - // if they got a bad score or haven't stepped on the corresponding tap yet - const TapNoteScore tns = m_NoteData.GetTapNote( hn.iTrack, hn.iStartRow ).result.tns; - const bool bSteppedOnTapNote = tns != TNS_NONE && tns != TNS_MISS; // did they step on the start of this hold? - - float fLife = hn.result.fLife; - - bool bIsHoldingButton = INPUTMAPPER->IsButtonDown( GameI ); - // TODO: Make the CPU miss sometimes. - if( m_pPlayerState->m_PlayerController != PC_HUMAN ) - bIsHoldingButton = true; - - if( bSteppedOnTapNote && fLife != 0 ) + // 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, begin, end ); + for( ; begin != end; ++begin ) { - /* This hold note is not judged and we stepped on its head. Update iLastHeldRow. - * Do this even if we're a little beyond the end of the hold note, to make sure - * iLastHeldRow is clamped to iEndRow if the hold note is held all the way. */ - hn.result.iLastHeldRow = min( iSongRow, hn.iEndRow ); - } + TapNote &tn = begin->second; + if( tn.type != TapNote::hold_head ) + continue; + int iRow = begin->first; - // If the song beat is in the range of this hold: - if( hn.RowIsInRange(iSongRow) ) - { - // set hold flag so NoteField can do intelligent drawing - hn.result.bHeld = bIsHoldingButton && bSteppedOnTapNote; - hn.result.bActive = bSteppedOnTapNote; + // set hold flags so NoteField can do intelligent drawing + tn.HoldResult.bHeld = false; + tn.HoldResult.bActive = false; - if( bSteppedOnTapNote && bIsHoldingButton ) + HoldNoteScore hns = tn.HoldResult.hns; + if( hns != HNS_NONE ) // if this HoldNote already has a result + continue; // we don't need to update the logic for this one + + // TODO: Remove use of PlayerNumber. + PlayerNumber pn = m_pPlayerState->m_PlayerNumber; + + // if they got a bad score or haven't stepped on the corresponding tap yet + const TapNoteScore tns = tn.result.tns; + const bool bSteppedOnTapNote = tns != TNS_NONE && tns != TNS_MISS; // did they step on the start of this hold? + + bool bIsHoldingButton; + if( m_pPlayerState->m_PlayerController != PC_HUMAN ) { - // Increase life - fLife = 1; - - if( m_pNoteField ) - m_pNoteField->DidHoldNote( hn.iTrack ); // update the "electric ghost" effect + // TODO: Make the CPU miss sometimes. + bIsHoldingButton = true; } else { - /* What is this conditional for? It causes a problem: if a hold note - * begins on a freeze, you can tap it and then release it for the - * duration of the freeze; life doesn't count down until we're - * past the first beat. */ -// if( fSongBeat-hn.fStartBeat > GAMESTATE->m_fCurBPS * GetMaxStepDistanceSeconds() ) -// { + const StyleInput StyleI( pn, iTrack ); + const GameInput GameI = GAMESTATE->GetCurrentStyle()->StyleInputToGameInput( StyleI ); + bIsHoldingButton = INPUTMAPPER->IsButtonDown( GameI ); + } + + int iEndRow = iRow + tn.iDuration; + + float fLife = tn.HoldResult.fLife; + if( bSteppedOnTapNote && fLife != 0 ) + { + /* This hold note is not judged and we stepped on its head. Update iLastHeldRow. + * Do this even if we're a little beyond the end of the hold note, to make sure + * iLastHeldRow is clamped to iEndRow if the hold note is held all the way. */ + tn.HoldResult.iLastHeldRow = min( iSongRow, iEndRow ); + } + + // If the song beat is in the range of this hold: + if( iRow <= iSongRow && iRow <= iEndRow ) + { + // set hold flag so NoteField can do intelligent drawing + tn.HoldResult.bHeld = bIsHoldingButton && bSteppedOnTapNote; + tn.HoldResult.bActive = bSteppedOnTapNote; + + if( bSteppedOnTapNote && bIsHoldingButton ) + { + // Increase life + fLife = 1; + + if( m_pNoteField ) + m_pNoteField->DidHoldNote( iTrack ); // update the "electric ghost" effect + } + else + { // Decrease life fLife -= fDeltaTime/ADJUSTED_WINDOW(OK); fLife = max( fLife, 0 ); // clamp -// } + } } + + /* check for NG. If the head was missed completely, don't count an NG. */ + if( bSteppedOnTapNote && fLife == 0 ) // the player has not pressed the button for a long time! + hns = HNS_NG; + + // check for OK + if( iSongRow >= iEndRow && bSteppedOnTapNote && fLife > 0 ) // if this HoldNote is in the past + { + fLife = 1; + hns = HNS_OK; + if( m_pNoteField ) + m_pNoteField->DidTapNote( iTrack, TNS_PERFECT, true ); // bright ghost flash + } + + if( hns != HNS_NONE ) + { + /* this note has been judged */ + HandleHoldScore( hns, tns ); + m_HoldJudgment[iTrack].SetHoldJudgment( hns ); + + int ms_error = (hns == HNS_OK)? 0:MAX_PRO_TIMING_ERROR; + + if( m_pPlayerStageStats ) + m_pPlayerStageStats->iTotalError += ms_error; + if( hns == HNS_NG ) /* don't show a 0 for an OK */ + m_ProTimingDisplay.SetJudgment( ms_error, TNS_MISS ); + } + + tn.HoldResult.fLife = fLife; + tn.HoldResult.hns = hns; } - - /* check for NG. If the head was missed completely, don't count - * an NG. */ - if( bSteppedOnTapNote && fLife == 0 ) // the player has not pressed the button for a long time! - hns = HNS_NG; - - // check for OK - if( iSongRow >= hn.iEndRow && bSteppedOnTapNote && fLife > 0 ) // if this HoldNote is in the past - { - fLife = 1; - hns = HNS_OK; - if( m_pNoteField ) - m_pNoteField->DidTapNote( StyleI.col, TNS_PERFECT, true ); // bright ghost flash - } - - if( hns != HNS_NONE ) - { - /* this note has been judged */ - HandleHoldScore( hns, tns ); - m_HoldJudgment[hn.iTrack].SetHoldJudgment( hns ); - - int ms_error = (hns == HNS_OK)? 0:MAX_PRO_TIMING_ERROR; - - if( m_pPlayerStageStats ) - m_pPlayerStageStats->iTotalError += ms_error; - if( hns == HNS_NG ) /* don't show a 0 for an OK */ - m_ProTimingDisplay.SetJudgment( ms_error, TNS_MISS ); - } - - hn.result.fLife = fLife; - hn.result.hns = hns; } // TODO: Remove use of PlayerNumber. @@ -475,7 +481,6 @@ void Player::Update( float fDeltaTime ) } } - // process transforms that are waiting to be applied ApplyWaitingTransforms(); @@ -1095,7 +1100,7 @@ void Player::UpdateTapNotesMissedOlderThan( float fMissIfOlderThanSeconds ) } // Since this is being called every frame, let's not check the whole array every time. - // Instead, only check 1 beat back. Even 10 is overkill. + // Instead, only check 1 beat back. Even 1 is overkill. const int iStartCheckingAt = max( 0, iMissIfOlderThanThisIndex-BeatToNoteRow(1) ); //LOG->Trace( "iStartCheckingAt: %d iMissIfOlderThanThisIndex: %d", iStartCheckingAt, iMissIfOlderThanThisIndex ); @@ -1217,14 +1222,7 @@ void Player::RandomizeNotes( int iNoteRow ) continue; /* Make sure the destination row isn't in the middle of a hold. */ - bool bSkip = false; - for( int i = 0; !bSkip && i < m_NoteData.GetNumHoldNotes(); ++i ) - { - const HoldNote &hn = m_NoteData.GetHoldNote(i); - if( hn.iTrack == iSwapWith && hn.RowIsInRange(iNewNoteRow) ) - bSkip = true; - } - if( bSkip ) + if( m_NoteData.IsHoldNoteAtBeat(iSwapWith, iNoteRow) ) continue; m_NoteData.SetTapNote( t, iNewNoteRow, t2 ); diff --git a/stepmania/src/ScreenEdit.cpp b/stepmania/src/ScreenEdit.cpp index b813d6b8f2..a10f5698ec 100644 --- a/stepmania/src/ScreenEdit.cpp +++ b/stepmania/src/ScreenEdit.cpp @@ -682,8 +682,7 @@ void ScreenEdit::Update( float fDeltaTime ) fEndBeat = Quantize( fEndBeat, NoteTypeToBeat(m_SnapDisplay.GetNoteType()) ); // create a new hold note - HoldNote newHN( t, BeatToNoteRow(fStartBeat), BeatToNoteRow(fEndBeat) ); - m_NoteDataRecord.AddHoldNote( newHN ); + m_NoteDataRecord.AddHoldNote( t, BeatToNoteRow(fStartBeat), BeatToNoteRow(fEndBeat), TAP_ORIGINAL_HOLD_HEAD ); } } } @@ -910,13 +909,11 @@ void ScreenEdit::InputEdit( const DeviceInput& DeviceI, const InputEventType typ break; // check for to see if the user intended to remove a HoldNote - for( int i=0; im_fSongBeat; - const float fEndBeat = GAMESTATE->m_fSongBeat + fBeatsToMove; + float fEndBeat = max( GAMESTATE->m_fSongBeat + fBeatsToMove, 0 ); + fEndBeat = Quantize( fEndBeat , NoteTypeToBeat(m_SnapDisplay.GetNoteType()) ); + GAMESTATE->m_fSongBeat = fEndBeat; // check to see if they're holding a button for( int n=0; n<=9; n++ ) // for each number key @@ -1022,12 +1021,13 @@ void ScreenEdit::InputEdit( const DeviceInput& DeviceI, const InputEventType typ continue; // create a new hold note - HoldNote newHN( iCol, BeatToNoteRow(min(fStartBeat, fEndBeat)), BeatToNoteRow(max(fStartBeat, fEndBeat)) ); + int iStartRow = BeatToNoteRow( min(fStartBeat, fEndBeat) ); + int iEndRow = BeatToNoteRow( max(fStartBeat, fEndBeat) ); - newHN.iStartRow = max(newHN.iStartRow, 0); - newHN.iEndRow = max(newHN.iEndRow, 0); + iStartRow = max( iStartRow, 0 ); + iEndRow = max( iEndRow, 0 ); - m_NoteDataEdit.AddHoldNote( newHN ); + m_NoteDataEdit.AddHoldNote( iCol, iStartRow, iEndRow, TAP_ORIGINAL_HOLD_HEAD ); } if( EditIsBeingPressed(EDIT_BUTTON_SCROLL_SELECT) ) @@ -1054,9 +1054,6 @@ void ScreenEdit::InputEdit( const DeviceInput& DeviceI, const InputEventType typ } } - GAMESTATE->m_fSongBeat += fBeatsToMove; - GAMESTATE->m_fSongBeat = max( GAMESTATE->m_fSongBeat, 0 ); - GAMESTATE->m_fSongBeat = Quantize( GAMESTATE->m_fSongBeat, NoteTypeToBeat(m_SnapDisplay.GetNoteType()) ); m_soundChangeLine.Play(); } break; @@ -1947,16 +1944,14 @@ void ScreenEdit::HandleAreaMenuChoice( AreaMenuChoice c, int* iAnswers ) break; case paste_at_current_beat: { - int iSrcFirstRow = 0; - int iSrcLastRow = BeatToNoteRow( m_Clipboard.GetLastBeat() ); int iDestFirstRow = BeatToNoteRow( GAMESTATE->m_fSongBeat ); - m_NoteDataEdit.CopyRange( m_Clipboard, iSrcFirstRow, iSrcLastRow, iDestFirstRow ); + m_NoteDataEdit.CopyRange( m_Clipboard, 0, MAX_NOTE_ROW, iDestFirstRow ); } break; case paste_at_begin_marker: { ASSERT( m_NoteFieldEdit.m_iBeginMarker!=-1 ); - m_NoteDataEdit.CopyRange( m_Clipboard, 0, m_Clipboard.GetLastRow(), m_NoteFieldEdit.m_iBeginMarker ); + m_NoteDataEdit.CopyRange( m_Clipboard, 0, MAX_NOTE_ROW, m_NoteFieldEdit.m_iBeginMarker ); } break; case clear: @@ -2069,8 +2064,6 @@ void ScreenEdit::HandleAreaMenuChoice( AreaMenuChoice c, int* iAnswers ) default: ASSERT(0); } - m_Clipboard.ConvertHoldNotesTo2sAnd3s(); - switch( at ) { case compress_2x: NoteDataUtil::Scale( m_Clipboard, fScale ); break; @@ -2082,8 +2075,6 @@ void ScreenEdit::HandleAreaMenuChoice( AreaMenuChoice c, int* iAnswers ) default: ASSERT(0); } - m_Clipboard.Convert2sAnd3sToHoldNotes(); - int iOldClipboardBeats = m_NoteFieldEdit.m_iEndMarker - m_NoteFieldEdit.m_iBeginMarker; int iNewClipboardBeats = lrintf( iOldClipboardBeats * fScale ); int iDeltaBeats = iNewClipboardBeats - iOldClipboardBeats; @@ -2094,7 +2085,6 @@ void ScreenEdit::HandleAreaMenuChoice( AreaMenuChoice c, int* iAnswers ) HandleAreaMenuChoice( paste_at_begin_marker, NULL ); const vector sIter = m_pSong->GetAllSteps(); - NoteData ndTemp; CString sTempStyle, sTempDiff; for( unsigned i = 0; i < sIter.size(); i++ ) { @@ -2106,10 +2096,9 @@ void ScreenEdit::HandleAreaMenuChoice( AreaMenuChoice c, int* iAnswers ) (sIter[i]->GetDifficulty() == GAMESTATE->m_pCurSteps[PLAYER_1]->GetDifficulty()) ) continue; + NoteData ndTemp; sIter[i]->GetNoteData( ndTemp ); - ndTemp.ConvertHoldNotesTo2sAnd3s(); NoteDataUtil::ScaleRegion( ndTemp, fScale, m_NoteFieldEdit.m_iBeginMarker, m_NoteFieldEdit.m_iEndMarker ); - ndTemp.Convert2sAnd3sToHoldNotes(); sIter[i]->SetNoteData( ndTemp ); } @@ -2286,11 +2275,8 @@ void ScreenEdit::HandleAreaMenuChoice( AreaMenuChoice c, int* iAnswers ) fStopLength *= fBPMatPause; fStopLength /= 60; // don't move the step from where it is, just move everything later - m_NoteDataEdit.ConvertHoldNotesTo2sAnd3s(); NoteDataUtil::ShiftRows( m_NoteDataEdit, BeatToNoteRow(GAMESTATE->m_fSongBeat) + 1, BeatToNoteRow(fStopLength) ); m_pSong->m_Timing.ShiftRows( BeatToNoteRow(GAMESTATE->m_fSongBeat) + 1, BeatToNoteRow(fStopLength) ); - m_NoteDataEdit.Convert2sAnd3sToHoldNotes(); - } // Hello and welcome to I FEEL STUPID :-) break; diff --git a/stepmania/src/ScreenGameplay.cpp b/stepmania/src/ScreenGameplay.cpp index 2363d4ce89..9792b7ce1a 100644 --- a/stepmania/src/ScreenGameplay.cpp +++ b/stepmania/src/ScreenGameplay.cpp @@ -1114,9 +1114,6 @@ void ScreenGameplay::LoadNextSong() NoteDataUtil::LoadTransformedLights( TapNoteData, m_CabinetLightsNoteData, GameManager::StepsTypeToNumTracks(STEPS_TYPE_LIGHTS_CABINET) ); } } - - // Convert to 2sAnd3s so that we can check if we're inside a hold with IsHoldNoteAtBeat. - m_CabinetLightsNoteData.ConvertHoldNotesTo2sAnd3s(); } } @@ -1615,12 +1612,11 @@ void ScreenGameplay::UpdateLights() FOREACH_EnabledPlayer( pn ) { // check if a hold should be active - for( int i=0; i < m_Player[pn].m_NoteData.GetNumHoldNotes(); i++ ) // for each HoldNote + for( int t=0; t < m_Player[pn].m_NoteData.GetNumTracks(); ++t ) { - const HoldNote &hn = m_Player[pn].m_NoteData.GetHoldNote(i); - if( hn.iStartRow <= iSongRow && iSongRow <= hn.iEndRow ) + if( m_Player[pn].m_NoteData.IsHoldNoteAtBeat( t, iSongRow ) ) { - StyleInput si( pn, hn.iTrack ); + StyleInput si( pn, t ); GameInput gi = pStyle->StyleInputToGameInput( si ); bBlinkGameButton[gi.controller][gi.button] |= true; }