diff --git a/TODO.md b/TODO.md index a72f069..9a0c7aa 100644 --- a/TODO.md +++ b/TODO.md @@ -13,6 +13,8 @@ - Density Graph - White line - Cursor + - Fix some columns not being hit +- mouse clicks highlights don't fade away ## Results Screen diff --git a/meson.build b/meson.build index ae610db..4c38501 100644 --- a/meson.build +++ b/meson.build @@ -76,6 +76,8 @@ sources = [ 'src/Screens/MusicSelect/Resources.cpp', 'src/Screens/MusicSelect/SongInfo.hpp', 'src/Screens/MusicSelect/SongInfo.cpp', + 'src/Screens/Gameplay/Drawables/Cursor.hpp', + 'src/Screens/Gameplay/Drawables/Cursor.cpp', 'src/Screens/Gameplay/Gameplay.hpp', 'src/Screens/Gameplay/Gameplay.cpp', 'src/Screens/Gameplay/PreciseMusic.hpp', diff --git a/src/Data/Chart.cpp b/src/Data/Chart.cpp index 795f92d..08ca6e8 100644 --- a/src/Data/Chart.cpp +++ b/src/Data/Chart.cpp @@ -76,18 +76,7 @@ namespace Data { } sf::Time Chart::get_last_event_timing() const { - if (notes.empty()) { - return sf::Time::Zero; - } else { - const auto& last_note = *std::max_element( - notes.begin(), - notes.end(), - [](const Data::Note& a, const Data::Note& b) -> bool { - return a.timing+a.duration < b.timing+b.duration; - } - ); - return last_note.timing + last_note.duration; - } + return get_time_bounds_from_notes().end; } TimeBounds Data::Chart::get_time_bounds_from_notes() const { diff --git a/src/Data/Chart.hpp b/src/Data/Chart.hpp index 8c820fa..b9614fd 100644 --- a/src/Data/Chart.hpp +++ b/src/Data/Chart.hpp @@ -17,7 +17,7 @@ namespace Data { std::set notes; std::size_t resolution; // get the time at which the very last scorable event happens - // (i.e. including long note releases) + // (i.e. note tap or long note release) sf::Time get_last_event_timing() const; // get the time interval covered by the notes, including offset zero TimeBounds get_time_bounds_from_notes() const; diff --git a/src/Data/GradedNote.cpp b/src/Data/GradedNote.cpp index e1273f1..f444302 100644 --- a/src/Data/GradedNote.cpp +++ b/src/Data/GradedNote.cpp @@ -41,8 +41,10 @@ namespace Data { return Judgement::Great; } else if (delta_abs < sf::milliseconds(162)) { return Judgement::Good; - } else { + } else if (delta_abs < sf::milliseconds(533)) { return Judgement::Poor; + } else { + return Judgement::Miss; } } } diff --git a/src/Data/Song.cpp b/src/Data/Song.cpp index 2de19e1..917c3e2 100644 --- a/src/Data/Song.cpp +++ b/src/Data/Song.cpp @@ -108,6 +108,7 @@ namespace Data { if (m.openFromFile(*song.full_audio_path())) { time_bounds += {sf::Time::Zero, m.getDuration()}; } + time_bounds.end += sf::seconds(1); return time_bounds; } diff --git a/src/Data/Song.hpp b/src/Data/Song.hpp index 8bb6526..11b4eaf 100644 --- a/src/Data/Song.hpp +++ b/src/Data/Song.hpp @@ -55,11 +55,11 @@ namespace Data { const Data::Song& song; const std::string& difficulty; - // get the total play time for this chart : - // if there's no audio and no notes, one second starting at sf::Time::Zero - // else the start bounds is either zero or the first note if for some reason it happens before 0s + // Get the total play interval for this chart : + // if there's no audio and no notes, returns a one second inteval starting at sf::Time::Zero + // otherwise the start bound is either zero or the first note if for some reason it happens before 0s // and the end bound is either the end of the song of the last timing event if a note or the - // release of a long note happen after the audio ends + // release of a long note happens after the audio ends TimeBounds get_time_bounds() const; bool operator==(const SongDifficulty &other) const { diff --git a/src/Drawables/BlackFrame.cpp b/src/Drawables/BlackFrame.cpp index 2acb9fc..3de8b97 100644 --- a/src/Drawables/BlackFrame.cpp +++ b/src/Drawables/BlackFrame.cpp @@ -36,7 +36,7 @@ namespace Drawables { ); m_vertex_array.insert(m_vertex_array.end(), bar.begin(), bar.end()); } - target.draw(m_vertex_array.data(), m_vertex_array.size(), sf::Quads); + target.draw(m_vertex_array.data(), m_vertex_array.size(), sf::Quads, states); } std::array make_solid_quad(const sf::FloatRect& rect, const sf::Color& color) { diff --git a/src/Drawables/DensityGraph.cpp b/src/Drawables/DensityGraph.cpp index 67ae5c8..c47106d 100644 --- a/src/Drawables/DensityGraph.cpp +++ b/src/Drawables/DensityGraph.cpp @@ -56,8 +56,14 @@ namespace Drawables { 115.f }; for (auto &¬e : chart.notes) { - auto index = static_cast(ticks_to_column.transform(note.timing.asSeconds())); + auto index = static_cast(ticks_to_column.transform(note.timing.asSeconds())); d.at(index) += 1; + if (note.duration > sf::Time::Zero) { + auto long_note_end_index = static_cast( + ticks_to_column.transform((note.timing+note.duration).asSeconds()) + ); + d.at(long_note_end_index) += 1; + } } std::replace_if( d.begin(), diff --git a/src/Drawables/GradedDensityGraph.cpp b/src/Drawables/GradedDensityGraph.cpp index f45ee5b..f8fb85a 100644 --- a/src/Drawables/GradedDensityGraph.cpp +++ b/src/Drawables/GradedDensityGraph.cpp @@ -2,10 +2,11 @@ #include +#include +#include #include namespace Drawables { - DensityLineGrade judgement_to_density_line_grade(Data::Judgement judge) { switch (judge) { case Data::Judgement::Perfect: @@ -52,56 +53,11 @@ namespace Drawables { } } - Toolkit::AffineTransform get_seconds_to_column_transform_from_notes(const Data::Chart& chart) { - auto time_bounds = chart.get_time_bounds_from_notes(); - // only one timing point - if (time_bounds.start == time_bounds.end) { - // give one extra second - return Toolkit::AffineTransform( - time_bounds.start.asSeconds(), - (time_bounds.start + sf::seconds(1)).asSeconds(), - 0.f, - 115.f - ); - } else { - // at least two timing points - return Toolkit::AffineTransform( - time_bounds.start.asSeconds(), - time_bounds.end.asSeconds(), - 0.f, - 115.f - ); - } - } - Toolkit::AffineTransform get_seconds_to_column_transform(const Data::SongDifficulty& sd) { - auto chart = sd.song.get_chart(sd.difficulty); - if (not chart) { - throw std::invalid_argument("Song "+sd.song.title+" has no '"+sd.difficulty+"' chart"); - } - // no notes, no need to return something useful - if (chart->notes.empty()) { - return Toolkit::AffineTransform(0.f, 1.f, 0.f, 115.f); - } - // no audio - if (not sd.song.audio) { - return get_seconds_to_column_transform_from_notes(*chart); - } - sf::Music m; - // can't open audio - if (not m.openFromFile(*sd.song.full_audio_path())) { - return get_seconds_to_column_transform_from_notes(*chart); - } - // both notes and audio exist - auto time_bounds = chart->get_time_bounds_from_notes(); + auto bounds = sd.get_time_bounds(); return Toolkit::AffineTransform( - time_bounds.start.asSeconds(), - sf::microseconds( - std::max( - time_bounds.end.asMicroseconds(), - m.getDuration().asMicroseconds() - ) - ).asSeconds(), + bounds.start.asSeconds(), + bounds.end.asSeconds(), 0.f, 115.f ); @@ -148,19 +104,21 @@ namespace Drawables { for (size_t i = 0; i < index_of_first_vertex_of_each_column.size(); i++) { m_densities.at(i).first_vertex = std::next(it, index_of_first_vertex_of_each_column.at(i)); } - first_non_played_density = m_densities.begin(); } void GradedDensityGraph::draw(sf::RenderTarget& target, sf::RenderStates states) const { states.transform *= getTransform(); target.draw(&m_vertex_array[0], m_vertex_array.size(), sf::Quads, states); + if (debug) { + draw_debug(); + } } void GradedDensityGraph::update(const sf::Time& music_time) { const auto float_column = m_seconds_to_column.transform(music_time.asSeconds()); const auto current_column = static_cast(float_column); const auto current_column_it = std::next(m_densities.begin(), current_column); - for (auto it = first_non_played_density; it != current_column_it; ++it) { + for (auto it = m_densities.begin(); it != current_column_it; ++it) { auto color = grade_to_color(it->grade); auto next = std::next(it); std::vector::iterator next_vertex_it; @@ -173,7 +131,6 @@ namespace Drawables { vertex_it->color = color; } } - first_non_played_density = current_column_it; } void GradedDensityGraph::update_grades(const Data::Judgement& judge, const sf::Time& timing) { @@ -183,11 +140,31 @@ namespace Drawables { current_grade = merge_grades(current_grade, judgement_to_density_line_grade(judge)); } -} + sf::FloatRect GradedDensityGraph::getLocalBounds() const { + return sf::FloatRect({0, 0}, {(115*5)-1, 39}); + } -namespace Toolkit { - template<> - void set_origin_normalized(Drawables::GradedDensityGraph& s, float x, float y) { - s.setOrigin(x*574.f, y*39.f); + sf::FloatRect GradedDensityGraph::getGlobalBounds() const { + return getTransform().transformRect(getLocalBounds()); + } + + void Drawables::GradedDensityGraph::draw_debug() const { + if (ImGui::Begin("GradedDensityGraph")) { + auto orig_x = ImGui::GetCursorPosX(); + auto pos = ImGui::GetCursorPos(); + for (size_t line = 0; line < m_densities.size(); line++) { + const auto& graded_density = m_densities.at(line); + auto color = grade_to_color(graded_density.grade); + for (size_t column = 0; column < graded_density.density; column++) { + ImGui::SetCursorPos(pos); + ImGui::DrawRectFilled({{0,0}, {4,4}}, color); + pos.x += 5; + } + pos.x = orig_x; + pos.y += 5; + } + ImGui::Dummy({8*5, 115*5}); + } + ImGui::End(); } } diff --git a/src/Drawables/GradedDensityGraph.hpp b/src/Drawables/GradedDensityGraph.hpp index fa3f00c..9576550 100644 --- a/src/Drawables/GradedDensityGraph.hpp +++ b/src/Drawables/GradedDensityGraph.hpp @@ -9,7 +9,7 @@ #include "../Data/GradedNote.hpp" #include "../Data/Song.hpp" #include "../Toolkit/AffineTransform.hpp" -#include "../Toolkit/Cache.hpp" +#include "../Toolkit/Debuggable.hpp" #include "../Toolkit/SFMLHelpers.hpp" #include "DensityGraph.hpp" @@ -20,7 +20,7 @@ namespace Drawables { ComboBreak, NonGraded }; - + DensityLineGrade judgement_to_density_line_grade(Data::Judgement judge); DensityLineGrade merge_grades(DensityLineGrade current, DensityLineGrade _new); sf::Color grade_to_color(DensityLineGrade grade); @@ -35,27 +35,24 @@ namespace Drawables { std::vector::iterator first_vertex; }; - // helper functions for GradedDensityGraph - Toolkit::AffineTransform get_seconds_to_column_transform_from_notes(const Data::Chart& chart); + // helper function for GradedDensityGraph Toolkit::AffineTransform get_seconds_to_column_transform(const Data::SongDifficulty& sd); - class GradedDensityGraph : public sf::Drawable, public sf::Transformable { + class GradedDensityGraph : public sf::Drawable, public sf::Transformable, public Toolkit::Debuggable { public: explicit GradedDensityGraph(const DensityGraph& density_graph, const Data::SongDifficulty& sd); // Set verticies colors for density columns that have already been played void update(const sf::Time& music_time); // Update stored grades according to the recieved judgement void update_grades(const Data::Judgement& judge, const sf::Time& timing); + sf::FloatRect getLocalBounds() const; + sf::FloatRect getGlobalBounds() const; + + void draw_debug() const; private: void draw(sf::RenderTarget& target, sf::RenderStates states) const override; std::array m_densities; std::vector m_vertex_array; - std::array::iterator first_non_played_density; Toolkit::AffineTransform m_seconds_to_column; }; -} - -namespace Toolkit { - template<> - void set_origin_normalized(Drawables::GradedDensityGraph& s, float x, float y); } \ No newline at end of file diff --git a/src/Screens/Gameplay/Drawables/Cursor.cpp b/src/Screens/Gameplay/Drawables/Cursor.cpp new file mode 100644 index 0000000..7e6c9d5 --- /dev/null +++ b/src/Screens/Gameplay/Drawables/Cursor.cpp @@ -0,0 +1,37 @@ +#include "Cursor.hpp" + +namespace Drawables { + Cursor::Cursor() { + m_vertex_array = { + sf::Vertex{ + sf::Vector2f(1.f, 0.f), + sf::Color::White + }, + sf::Vertex{ + sf::Vector2f(1.f, 1.f), + sf::Color::White + }, + sf::Vertex{ + sf::Vector2f(0.f, 1.f), + sf::Color::Transparent + }, + sf::Vertex{ + sf::Vector2f(0.f, 0.f), + sf::Color::Transparent + } + }; + } + + sf::FloatRect Cursor::getLocalBounds() const { + return sf::FloatRect{{0.f,0.f}, {1.f,1.f}}; + } + + sf::FloatRect Cursor::getGlobalBounds() const { + return getTransform().transformRect(getLocalBounds()); + } + + void Cursor::draw(sf::RenderTarget& target, sf::RenderStates states) const { + states.transform *= getTransform(); + target.draw(&m_vertex_array[0], m_vertex_array.size(), sf::Quads, states); + } +} diff --git a/src/Screens/Gameplay/Drawables/Cursor.hpp b/src/Screens/Gameplay/Drawables/Cursor.hpp new file mode 100644 index 0000000..3c9b6ac --- /dev/null +++ b/src/Screens/Gameplay/Drawables/Cursor.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include + +namespace Drawables { + // Small thingy that scrolls atop the graded density graph to indicate how far we are in the song + class Cursor : public sf::Drawable, public sf::Transformable { + public: + Cursor(); + sf::FloatRect getLocalBounds() const; + sf::FloatRect getGlobalBounds() const; + private: + void draw(sf::RenderTarget& target, sf::RenderStates states) const override; + std::array m_vertex_array; + }; +} \ No newline at end of file diff --git a/src/Screens/Gameplay/Gameplay.cpp b/src/Screens/Gameplay/Gameplay.cpp index fca2e4d..003290f 100644 --- a/src/Screens/Gameplay/Gameplay.cpp +++ b/src/Screens/Gameplay/Gameplay.cpp @@ -154,13 +154,20 @@ namespace Gameplay { window.draw(line); // Density Graph - Toolkit::set_origin_normalized(graded_density_graph, 0.5f, 1.f); + Toolkit::set_local_origin_normalized(graded_density_graph, 0.5f, 1.f); graded_density_graph.setScale(get_screen_width()/768.f, get_screen_width()/768.f); graded_density_graph.setPosition(get_screen_width()*0.5f,423.f/768.f*get_screen_width()); window.draw(graded_density_graph); // Cursor on the density graph - + Toolkit::set_local_origin_normalized(cursor, 1.f, 0.f); + auto bounds = graded_density_graph.getGlobalBounds(); + cursor.setScale(bounds.height, bounds.height); + cursor.setPosition( + bounds.left+music_time_to_progression.transform(music_time.asSeconds())*bounds.width, + bounds.top + ); + window.draw(cursor); // Draw Combo @@ -448,6 +455,7 @@ namespace Gameplay { switch (*key) { case sf::Keyboard::F12: debug = not debug; + graded_density_graph.debug = not graded_density_graph.debug; break; default: break; @@ -487,6 +495,12 @@ namespace Gameplay { score.update(judgement); graded_density_graph.update_grades(judgement, note.timing); if (Data::judgement_breaks_combo(judgement)) { + // If we've broken combo at the begining of a long we also missed the end + if (note.duration > sf::Time::Zero) { + note.long_release = Data::TimedJudgement{sf::Time::Zero, Data::Judgement::Miss}; + score.update(Data::Judgement::Miss); + graded_density_graph.update_grades(Data::Judgement::Miss, note.timing+note.duration); + } combo = 0; } else { combo++; @@ -538,6 +552,11 @@ namespace Gameplay { note.tap_judgement = timed_judgement; score.update(Data::Judgement::Miss); graded_density_graph.update_grades(Data::Judgement::Miss, note.timing); + if (note.duration > sf::Time::Zero) { + note.long_release = timed_judgement; + score.update(Data::Judgement::Miss); + graded_density_graph.update_grades(Data::Judgement::Miss, note.timing+note.duration); + } combo = 0; } } @@ -550,7 +569,7 @@ namespace Gameplay { (note.duration > sf::Time::Zero) and (note.timing + note.duration < music_time) and note.tap_judgement.has_value() - and note.tap_judgement->judgement != Data::Judgement::Miss + and not Data::judgement_breaks_combo(note.tap_judgement->judgement) and (not note.long_release.has_value()) ) { auto timed_judgement = Data::TimedJudgement{sf::Time::Zero, Data::Judgement::Perfect}; diff --git a/src/Screens/Gameplay/Gameplay.hpp b/src/Screens/Gameplay/Gameplay.hpp index 5b38df6..73ef207 100644 --- a/src/Screens/Gameplay/Gameplay.hpp +++ b/src/Screens/Gameplay/Gameplay.hpp @@ -22,6 +22,7 @@ #include "AbstractMusic.hpp" #include "Resources.hpp" #include "TimedEventsQueue.hpp" +#include "Drawables/Cursor.hpp" namespace Gameplay { class Screen : public Toolkit::Debuggable, public HoldsResources { @@ -49,7 +50,7 @@ namespace Gameplay { const Resources::LNMarker& ln_marker; std::unique_ptr music; Drawables::GradedDensityGraph graded_density_graph; - std::array cursor; + Drawables::Cursor cursor; // maps music time to [0, 1] Toolkit::AffineTransform music_time_to_progression; diff --git a/test/imgui-sfml-demo.cpp b/test/imgui-sfml-demo.cpp new file mode 100644 index 0000000..b7f3cbb --- /dev/null +++ b/test/imgui-sfml-demo.cpp @@ -0,0 +1,29 @@ +#include +#include + +#include +#include +#include + +int main() { + sf::RenderWindow window(sf::VideoMode(640, 480), "imgui-sfml-demo"); + window.setVerticalSyncEnabled(true); + ImGui::SFML::Init(window); + window.resetGLStates(); // call it if you only draw ImGui. Otherwise not needed. + sf::Clock deltaClock; + while (window.isOpen()) { + sf::Event event; + while (window.pollEvent(event)) { + ImGui::SFML::ProcessEvent(event); + if (event.type == sf::Event::Closed) { + window.close(); + } + } + ImGui::SFML::Update(window, deltaClock.restart()); + window.clear(sf::Color::Transparent); // fill background with color + ImGui::ShowDemoWindow(); + ImGui::SFML::Render(window); + window.display(); + } + ImGui::SFML::Shutdown(); +} \ No newline at end of file diff --git a/test/meson.build b/test/meson.build index c534cf0..a772603 100644 --- a/test/meson.build +++ b/test/meson.build @@ -1,5 +1,21 @@ test_files = [] +imgui_demo = executable( + 'imgui_demo.out', + [ + 'imgui-sfml-demo.cpp', + '../include/imgui/imgui.cpp', + '../include/imgui/imgui_demo.cpp', + '../include/imgui/imgui_draw.cpp', + '../include/imgui/imgui_widgets.cpp', + '../include/imgui-sfml/imgui-SFML.cpp' + ], + dependencies : dependencies, + include_directories: inc +) + +test('Able to build imgui demo', imgui_demo) + foreach test_file : test_files test_executable = executable( test_file+'.out',