diff --git a/Docs/Luadoc/Lua.xml b/Docs/Luadoc/Lua.xml index 6b0cde4ffb..9f92d0d66d 100644 --- a/Docs/Luadoc/Lua.xml +++ b/Docs/Luadoc/Lua.xml @@ -2027,6 +2027,7 @@ + diff --git a/Docs/Luadoc/LuaDocumentation.xml b/Docs/Luadoc/LuaDocumentation.xml index acd8167659..92c61f80ae 100644 --- a/Docs/Luadoc/LuaDocumentation.xml +++ b/Docs/Luadoc/LuaDocumentation.xml @@ -6098,6 +6098,11 @@ local spr = Def.Sprite{ Returns true if the Tap Note was judged with a result that would stop it from drawing. + + Returns whether the input for the Tap Note was ever held during + the judgment interval. Useful to distinguish a normal miss from + a held miss. + diff --git a/src/NoteTypes.cpp b/src/NoteTypes.cpp index 851a981dfe..89ff24842e 100644 --- a/src/NoteTypes.cpp +++ b/src/NoteTypes.cpp @@ -210,12 +210,14 @@ public: DEFINE_METHOD(GetTapNoteScore, tns); DEFINE_METHOD(GetTapNoteOffset, fTapNoteOffset); DEFINE_METHOD(GetHidden, bHidden); + DEFINE_METHOD(GetHeld, bHeld); LunaTapNoteResult() { ADD_METHOD( GetTapNoteScore ); ADD_METHOD( GetTapNoteOffset ); ADD_METHOD( GetHidden ); + ADD_METHOD( GetHeld ); } }; LUA_REGISTER_CLASS( TapNoteResult ) diff --git a/src/NoteTypes.h b/src/NoteTypes.h index f966291d41..0ad3bd9dfa 100644 --- a/src/NoteTypes.h +++ b/src/NoteTypes.h @@ -13,7 +13,7 @@ class XNode; struct TapNoteResult { /** @brief Set up the TapNoteResult with default values. */ - TapNoteResult() : tns(TNS_None), fTapNoteOffset(0.f), bHidden(false) { } + TapNoteResult() : tns(TNS_None), fTapNoteOffset(0.f), bHidden(false), bHeld(false) { } /** @brief The TapNoteScore that was achieved by the player. */ TapNoteScore tns; @@ -28,6 +28,9 @@ struct TapNoteResult /** @brief If the whole row has been judged, all taps on the row will be set to hidden. */ bool bHidden; + /** @brief Track if the note was held. Used to track held misses. */ + bool bHeld; + // XML XNode* CreateNode() const; void LoadFromNode( const XNode* pNode ); diff --git a/src/Player.cpp b/src/Player.cpp index 0fe98278aa..145d50d3ad 100644 --- a/src/Player.cpp +++ b/src/Player.cpp @@ -993,6 +993,72 @@ void Player::Update( float fDeltaTime ) } } + // Track held misses + // + // In order to track held misses we have to check whether a note was + // held any time during the judgment window before it is judged a miss. + // Note: at this point we don't actually know yet whether a note will + // be a miss or a hit, so we have to track for all notes whether they + // were held at some point before getting judged. + { + float largestWindow = 0.0f; + largestWindow = max(largestWindow, GetWindowSeconds(TW_W1)); + largestWindow = max(largestWindow, GetWindowSeconds(TW_W2)); + largestWindow = max(largestWindow, GetWindowSeconds(TW_W3)); + largestWindow = max(largestWindow, GetWindowSeconds(TW_W4)); + largestWindow = max(largestWindow, GetWindowSeconds(TW_W5)); + + // We have to check the unjudged notes that are within the + // timing window. Let's find the cutoff point! (lastCheckRow) + const float rate = GAMESTATE->m_SongOptions.GetCurrent().m_fMusicRate; + const SongPosition songPosition = m_pPlayerState->m_Position; + const float musicPosition = songPosition.m_fMusicSeconds + (songPosition.m_LastBeatUpdate.Ago() * rate); + // We have to add 1 here, because GetBeatFromElapsedTime() can round down. + const int lastCheckRow = BeatToNoteRow(m_Timing->GetBeatFromElapsedTime(musicPosition + (largestWindow * rate)) + 1); + + // The button being held only counts for the first unjudged + // note on a track (== column/arrow direction), so we have to + // keep track for which tracks we have already seen an unjudged + // note. + vector seenTracks(m_NoteData.GetNumTracks(), false); + + for(auto iter = *m_pIterNeedsTapJudging; !iter.IsAtEnd() && iter.Row() <= lastCheckRow; ++iter) + { + TapNote &tn = *iter; + const int row = iter.Row(); + const int track = iter.Track(); + + // Skip over warp and fake segments + if (!m_Timing->IsJudgableAtRow(row)) + continue; + + // Held misses only apply to tap notes + if (tn.type != TapNoteType_Tap && tn.type != TapNoteType_HoldHead) + continue; + + const float notePosition = m_Timing->GetElapsedTimeFromBeat(NoteRowToBeat(row)); + const float offset = fabsf((notePosition - musicPosition) / rate); + + // Skip if we are outside of the largest timing window + if (offset > largestWindow) + continue; + + // Skip the note if there is an earlier note on the same track that still awaits judgement + if (seenTracks[track]) + continue; + + seenTracks[track] = true; + + if (!tn.result.bHeld) + { + PlayerNumber pn = m_pPlayerState->m_PlayerNumber; + vector input; + GAMESTATE->GetCurrentStyle(pn)->StyleInputToGameInput(track, pn, input); + + tn.result.bHeld = INPUTMAPPER->IsBeingPressed(input, m_pPlayerState->m_mp); + } + } + } // update HoldNotes logic { @@ -3120,6 +3186,9 @@ void Player::SetJudgment( int iRow, int iTrack, const TapNote &tn, TapNoteScore msg.SetParam( "Early", fTapNoteOffset < 0.0f ); msg.SetParam( "TapNoteOffset", tn.result.fTapNoteOffset ); + if ( tns == TNS_Miss ) + msg.SetParam( "HeldMiss", tn.result.bHeld ); + Lua* L= LUA->Get(); lua_createtable( L, 0, m_NoteData.GetNumTracks() ); // TapNotes this row lua_createtable( L, 0, m_NoteData.GetNumTracks() ); // HoldHeads of tracks held at this row.