diff --git a/src/CMakeData-data.cmake b/src/CMakeData-data.cmake index 6df194959f..6670c0214c 100644 --- a/src/CMakeData-data.cmake +++ b/src/CMakeData-data.cmake @@ -52,12 +52,14 @@ source_group("Data Structures\\\\Courses and Trails" list(APPEND SM_DATA_NOTEDATA_SRC "NoteData.cpp" "NoteDataUtil.cpp" - "NoteDataWithScoring.cpp") + "NoteDataWithScoring.cpp" + "ColumnCues.cpp") list(APPEND SM_DATA_NOTEDATA_HPP "NoteData.h" "NoteDataUtil.h" - "NoteDataWithScoring.h") + "NoteDataWithScoring.h" + "ColumnCues.h") source_group("Data Structures\\\\Note Data" FILES diff --git a/src/ColumnCues.cpp b/src/ColumnCues.cpp new file mode 100644 index 0000000000..ffeacfe4df --- /dev/null +++ b/src/ColumnCues.cpp @@ -0,0 +1,61 @@ +#include "global.h" +#include "ColumnCues.h" +#include "GameState.h" +#include "NoteData.h" +#include "TimingData.h" + +void ColumnCue::CalculateColumnCues(const NoteData &in, std::vector &out, float minDuration) +{ + TimingData *timing = GAMESTATE->GetProcessedTimingData(); + NoteData::all_tracks_const_iterator curr_note = in.GetTapNoteRangeAllTracks(0, MAX_NOTE_ROW); + int curr_row = -1; + + std::vector allColumnCues; + ColumnCue currentCue = ColumnCue(); + + while (!curr_note.IsAtEnd()) + { + if(curr_note.Row() != curr_row) + { + if (currentCue.startTime != -1 ) + { + allColumnCues.push_back(currentCue); + } + currentCue = ColumnCue(); + currentCue.startTime = timing->GetElapsedTimeFromBeat(NoteRowToBeat(curr_note.Row())); + curr_row = curr_note.Row(); + } + + if (curr_note->type == TapNoteType_Tap || curr_note->type == TapNoteType_HoldHead) + { + currentCue.columns.push_back(ColumnCueColumn(curr_note.Track() + 1, false)); + } + else if(curr_note->type == TapNoteType_Mine) + { + currentCue.columns.push_back(ColumnCueColumn(curr_note.Track() + 1, true)); + } + + ++curr_note; + } + + // If there's a remaining columnCue from the last row, add it + if( currentCue.startTime != -1) + { + allColumnCues.push_back(currentCue); + } + + float previousCueTime = 0; + std::vector columnCues; + for( ColumnCue columnCue : allColumnCues ) + { + float duration = columnCue.startTime - previousCueTime; + if( duration > minDuration || previousCueTime == 0) + { + columnCues.push_back(ColumnCue(previousCueTime, duration, columnCue.columns)); + } + previousCueTime = columnCue.startTime; + } + + out.clear(); + out.assign(columnCues.begin(), columnCues.end()); +} diff --git a/src/ColumnCues.h b/src/ColumnCues.h new file mode 100644 index 0000000000..e02c490bc7 --- /dev/null +++ b/src/ColumnCues.h @@ -0,0 +1,51 @@ +#ifndef COLUMN_CUES_H +#define COLUMN_CUES_H + +#include "GameConstantsAndTypes.h" +class NoteData; + +/* ColumnCues are used to indicate to the player which column the next note will occur + after a long gap in the stepchart. + This info is made available to the theme via lua functions in Steps.cpp + */ +struct ColumnCueColumn +{ + int colNum; + bool isMine; + ColumnCueColumn() + { + colNum = 0; + isMine = false; + } + ColumnCueColumn(int c, bool m) + { + colNum = c; + isMine = m; + } +}; + +struct ColumnCue +{ + float startTime; + float duration; + std::vector columns; + + ColumnCue() + { + startTime = -1; + duration = -1; + } + + ColumnCue(float s, float d, std::vector c) + { + startTime = s; + duration = d; + columns.assign(c.begin(), c.end()); + } + /** @brief Calculates the set of ColumnCues for the given NoteData. Each "cue" is for any note that has a + * minimum of minDuration seconds between it and the previous note on that same column. + */ + static void CalculateColumnCues(const NoteData &in, std::vector &out, float minDuration); +}; + +#endif diff --git a/src/Steps.cpp b/src/Steps.cpp index 16c0351719..945b8b7dae 100644 --- a/src/Steps.cpp +++ b/src/Steps.cpp @@ -701,6 +701,20 @@ RString Steps::GenerateChartKey(NoteData &nd, TimingData *td) return o; } +std::vector Steps::GetColumnCues(float minDuration) +{ + // TODO: Should we worry about getting the right steps per player? + // It seems like this is only necessary when dealing with Couples charts + + std::vector cues; + NoteData noteData; + this->GetNoteData( noteData ); + GAMESTATE->SetProcessedTimingData(this->GetTimingData()); + ColumnCue::CalculateColumnCues(noteData, cues, minDuration); + GAMESTATE->SetProcessedTimingData(nullptr); + return cues; +} + // lua start #include "LuaBinding.h" @@ -799,6 +813,50 @@ public: return 1; } + static int GetColumnCues(T *p, lua_State*L) + { + float minDuration = 1.5; + if (lua_isnumber(L, 1)) + { + minDuration = lua_tonumber(L, 1); + } + std::vector cues = p->GetColumnCues(minDuration); + lua_createtable(L, cues.size(), 0); + + for (unsigned i = 0; i < cues.size(); i++) + { + lua_newtable(L); + lua_pushstring(L, "startTime"); + lua_pushnumber(L, cues[i].startTime); + lua_settable(L, -3); + + lua_pushstring(L, "duration"); + lua_pushnumber(L, cues[i].duration); + lua_settable(L, -3); + + lua_pushstring(L, "columns"); + lua_createtable(L, cues[i].columns.size(), 0); + + for (unsigned c = 0; c < cues[i].columns.size(); c++) + { + lua_newtable(L); + lua_pushstring(L, "colNum"); + lua_pushinteger(L, cues[i].columns[c].colNum); + lua_settable(L, -3); + + lua_pushstring(L, "isMine"); + lua_pushboolean(L, cues[i].columns[c].isMine); + lua_settable(L, -3); + + lua_rawseti(L, -2, c + 1); + } + + lua_settable(L, -3); + lua_rawseti(L, -2, i + 1); + } + return 1; + } + LunaSteps() { ADD_METHOD( GetAuthorCredit ); @@ -824,6 +882,7 @@ public: ADD_METHOD( IsDisplayBpmRandom ); ADD_METHOD( PredictMeter ); ADD_METHOD( GetDisplayBPMType ); + ADD_METHOD( GetColumnCues ); } }; diff --git a/src/Steps.h b/src/Steps.h index 57c14cb26c..f4d9072aa5 100644 --- a/src/Steps.h +++ b/src/Steps.h @@ -9,6 +9,7 @@ #include "Difficulty.h" #include "RageUtil_AutoPtr.h" #include "TimingData.h" +#include "ColumnCues.h" #include @@ -210,6 +211,8 @@ public: { return join(":", this->m_sAttackString); } + + std::vector GetColumnCues(float minDuration); private: inline const Steps *Real() const { return parent ? parent : this; }