diff --git a/build_linux.sh b/build_linux.sh new file mode 100644 index 0000000..d2ccfcf --- /dev/null +++ b/build_linux.sh @@ -0,0 +1,7 @@ +rm -rf build +mkdir build +meson build +meson configure build -Ddebug=false -Doptimization=2 +cd build +ninja +cp ../assets assets -r diff --git a/make_release.sh b/make_release.sh new file mode 100644 index 0000000..0a9dc9d --- /dev/null +++ b/make_release.sh @@ -0,0 +1,14 @@ +rm -rf build +mkdir build +meson build +meson configure build -Ddebug=false -Doptimization=2 +cd build +ninja +cp ../assets assets -r +../utils/copy_dependencies.py -f jujube.exe + +rm -rf jujube@exe meson* build.ninja .ninja* compile_commands.json test + +cd .. +zip jujube.zip -r build +mv -t build jujube.zip diff --git a/src/Data/GradedNote.cpp b/src/Data/GradedNote.cpp index f444302..9a63160 100644 --- a/src/Data/GradedNote.cpp +++ b/src/Data/GradedNote.cpp @@ -35,11 +35,11 @@ namespace Data { if (delta_abs < sf::Time::Zero) { delta_abs = -delta_abs; } - if (delta_abs < sf::milliseconds(42)) { + if (delta_abs < sf::milliseconds(40)) { return Judgement::Perfect; - } else if (delta_abs < sf::milliseconds(82)) { + } else if (delta_abs < sf::milliseconds(80)) { return Judgement::Great; - } else if (delta_abs < sf::milliseconds(162)) { + } else if (delta_abs < sf::milliseconds(160)) { return Judgement::Good; } else if (delta_abs < sf::milliseconds(533)) { return Judgement::Poor; @@ -47,4 +47,33 @@ namespace Data { return Judgement::Miss; } } + // implement the strange way in which jubeat judges hold note releases + Judgement release_to_judgement(const sf::Time& duration_held, const sf::Time& note_duration, const int tail_length) { + // if we implement hard mode we'll need a cleaner implementation of this since this would be 30. works for now though + int error_margin = 60; + // take the length of the note in ticks on a 300 hz clock and divide by length of tail in pixels + // logic taken from reversing the game's timing code + double milliseconds_to_300hz = 0.3; + auto note_duration_300hz = static_cast(note_duration.asMilliseconds()) * milliseconds_to_300hz; + auto adjusted_note_duration = static_cast( + note_duration_300hz * (1 - (static_cast(error_margin) / (tail_length*195))) + ); + + // held for longer than duration : done + if((duration_held.asMilliseconds() * milliseconds_to_300hz) >= adjusted_note_duration) { + return Judgement::Perfect; + } + + auto percentage_held = static_cast( + duration_held.asMilliseconds() * milliseconds_to_300hz / adjusted_note_duration * 100 + ); + + if (percentage_held < 100 and percentage_held >= 81) { + return Judgement::Great; + } else if (percentage_held < 80 and percentage_held >= 51) { + return Judgement::Good; + } else { + return Judgement::Poor; + } + } } diff --git a/src/Data/GradedNote.hpp b/src/Data/GradedNote.hpp index 59b6827..951d9f5 100644 --- a/src/Data/GradedNote.hpp +++ b/src/Data/GradedNote.hpp @@ -21,11 +21,21 @@ namespace Data { Resources::MarkerAnimation judgement_to_animation(Judgement j); Judgement delta_to_judgement(const sf::Time& delta); + Judgement release_to_judgement(const sf::Time& duration_held, const sf::Time& note_duration, const int tail_length); struct TimedJudgement { TimedJudgement() : delta(sf::Time::Zero), judgement(Judgement::Miss) {}; TimedJudgement(const sf::Time& d, const Judgement& j) : delta(d), judgement(j) {}; explicit TimedJudgement(const sf::Time& t) : delta(t), judgement(delta_to_judgement(t)) {}; + explicit TimedJudgement( + const sf::Time& duration_held, + const sf::Time& t, + const sf::Time& duration, + const int tail_length + ) : + delta(t), + judgement(release_to_judgement(duration_held, duration, tail_length)) + {}; sf::Time delta = sf::Time::Zero; Judgement judgement = Judgement::Miss; }; @@ -38,4 +48,4 @@ namespace Data { std::optional tap_judgement = {}; std::optional long_release = {}; }; -} \ No newline at end of file +} diff --git a/src/Data/Score.cpp b/src/Data/Score.cpp index 3be150f..78c2d09 100644 --- a/src/Data/Score.cpp +++ b/src/Data/Score.cpp @@ -36,6 +36,10 @@ namespace Data { return shutter; } + int ClassicScore::get_judgement_counts(Judgement j) const { + return judgement_counts.at(j); + } + int ClassicScore::get_final_score() const { return get_score() + shutter*100000/1024; } @@ -93,6 +97,8 @@ namespace Data { case Judgement::Miss: shutter_delta = shutter_decrement_4x; break; + default: + break; } shutter = std::clamp(shutter+shutter_delta, 0, 1024); } diff --git a/src/Data/Score.hpp b/src/Data/Score.hpp index 58956bc..eed0184 100644 --- a/src/Data/Score.hpp +++ b/src/Data/Score.hpp @@ -51,6 +51,8 @@ namespace Data { virtual Rating get_rating() const = 0; // Update score according to the recieved judgement virtual void update(Judgement j) = 0; + // Get judgments + virtual int get_judgement_counts(Judgement j) const = 0; }; std::size_t count_classic_scoring_events(const std::set& notes); @@ -64,6 +66,7 @@ namespace Data { int get_score() const override; Rating get_rating() const override; void update(Judgement j) override; + int get_judgement_counts(Judgement j) const; private: int shutter = 0; int shutter_incerment_2x; @@ -71,7 +74,6 @@ namespace Data { int shutter_decrement_4x; const std::size_t tap_event_count; std::unordered_map judgement_counts; - friend class Gameplay::Screen; }; } \ No newline at end of file diff --git a/src/Drawables/GradedDensityGraph.cpp b/src/Drawables/GradedDensityGraph.cpp index 7dbe86d..a472166 100644 --- a/src/Drawables/GradedDensityGraph.cpp +++ b/src/Drawables/GradedDensityGraph.cpp @@ -12,6 +12,7 @@ namespace Drawables { case Data::Judgement::Perfect: return DensityLineGrade::Perfect; case Data::Judgement::Great: + case Data::Judgement::Good: return DensityLineGrade::Great; default: return DensityLineGrade::ComboBreak; diff --git a/src/Screens/Gameplay/Gameplay.cpp b/src/Screens/Gameplay/Gameplay.cpp index 8aecc01..e5ef953 100644 --- a/src/Screens/Gameplay/Gameplay.cpp +++ b/src/Screens/Gameplay/Gameplay.cpp @@ -611,7 +611,12 @@ namespace Gameplay { if (note.long_release) { continue; } - auto timed_judgement = Data::TimedJudgement{music_time-note.timing-note.duration}; + auto timed_judgement = Data::TimedJudgement{ + music_time-note.timing, + music_time-note.timing-note.duration, + note.duration, + note.get_tail_length() + }; note.long_release = timed_judgement; score.update(timed_judgement.judgement); graded_density_graph.update_grades(timed_judgement.judgement, note.timing+note.duration); diff --git a/src/Screens/Results/Results.cpp b/src/Screens/Results/Results.cpp index 29959b6..a49366a 100644 --- a/src/Screens/Results/Results.cpp +++ b/src/Screens/Results/Results.cpp @@ -25,6 +25,7 @@ namespace Results { sf::Clock imgui_clock; auto final_score = score.get_final_score(); auto final_rating = score.get_rating(); + while ((not should_exit) and window.isOpen()) { sf::Event event; while (window.pollEvent(event)) { @@ -54,6 +55,23 @@ namespace Results { ImGui::SFML::Update(window, imgui_clock.restart()); window.clear(sf::Color(7, 23, 53)); + + // Draw song info + // Cover is 40x40 @ (384,20) + if (song_selection.song.cover) { + auto cover = shared.covers.get(*song_selection.song.full_cover_path()); + if (cover) { + sf::Sprite cover_sprite{*cover->texture}; + auto cover_size = 40.f/768.f*get_screen_width(); + Toolkit::set_size_from_local_bounds(cover_sprite, cover_size, cover_size); + cover_sprite.setPosition( + 384.f/768.f*get_screen_width(), + 20.f/768.f*get_screen_width() + ); + window.draw(cover_sprite); + } + } + // White line under the density graph sf::RectangleShape line{{get_screen_width()*1.1f,2.f/768.f*get_screen_width()}}; Toolkit::set_origin_normalized(line, 0.5f, 0.5f); @@ -93,6 +111,27 @@ namespace Results { ); window.draw(rating_text); + // Draw Judgement Breakdown + sf::Text judgements; + judgements.setFont(shared.fallback_font.black); + judgements.setFillColor(sf::Color(29, 98, 226)); + std::string judgement_to_string = ( + "Perfect: " + std::to_string(score.get_judgement_counts(Data::Judgement::Perfect)) + "\n" + + "Great: " + std::to_string(score.get_judgement_counts(Data::Judgement::Great)) + "\n" + + "Good: " + std::to_string(score.get_judgement_counts(Data::Judgement::Good)) + "\n" + + "Poor: " + std::to_string(score.get_judgement_counts(Data::Judgement::Poor)) + "\n" + + "Miss: " + std::to_string(score.get_judgement_counts(Data::Judgement::Miss)) + ); + judgements.setString(judgement_to_string); + judgements.setCharacterSize(static_cast(0.1f*get_panel_size())); + Toolkit::set_local_origin_normalized(judgements, 0.5f, 0.5f); + judgements.setPosition( + get_ribbon_x()+0.f*get_panel_step()+0.5*get_panel_size(), + get_ribbon_y()+2.f*get_panel_step()+0.5*get_panel_size() + ); + window.draw(judgements); + + // Draw song info auto song_title = song_selection.song.title; if (not song_title.empty()) { diff --git a/src/Screens/Results/Results.hpp b/src/Screens/Results/Results.hpp index 1402475..d77d2c1 100644 --- a/src/Screens/Results/Results.hpp +++ b/src/Screens/Results/Results.hpp @@ -25,7 +25,7 @@ namespace Results { private: Drawables::GradedDensityGraph graded_density_graph; const Data::SongDifficulty& song_selection; - const Data::Chart& chart; + const Data::Chart chart; const Data::AbstractScore& score; bool should_exit = false;