1
0

4 Коммитууд c6b9d37b27 ... 03e8b13d77

Эзэн SHA1 Мессеж Огноо
  Clément Wolf 03e8b13d77 Add instant replay 1 сар өмнө
  Clément Wolf 0973ae8d5d Place skip button above seek bar 1 сар өмнө
  Clément Wolf 4ca361d6ef Fix slider fat finger logic 1 сар өмнө
  Clément Wolf 1c4d002118 Add cvar to start initial main menu song at preview point 1 сар өмнө

+ 87 - 82
src/App/Osu/Osu.cpp

@@ -140,6 +140,7 @@ ConVar flashlight_radius("flashlight_radius", 100.f, FCVAR_CHEAT);
 ConVar flashlight_follow_delay("flashlight_follow_delay", 0.120f, FCVAR_CHEAT);
 ConVar flashlight_always_hard("flashlight_always_hard", false, FCVAR_NONE, "always use 200+ combo flashlight radius");
 
+ConVar start_first_main_menu_song_at_preview_point("start_first_main_menu_song_at_preview_point", false, FCVAR_NONE);
 ConVar nightcore_enjoyer("nightcore_enjoyer", false, FCVAR_NONE, "automatically select nightcore when speed modifying");
 ConVar scoreboard_animations("scoreboard_animations", true, FCVAR_NONE, "animate in-game scoreboard");
 
@@ -317,7 +318,6 @@ Osu::Osu(int instanceID) {
     m_bKeyboardKey22Down = false;
     m_bMouseKey1Down = false;
     m_bMouseKey2Down = false;
-    m_bSkipDownCheck = false;
     m_bSkipScheduled = false;
     m_bQuickRetryDown = false;
     m_fQuickRetryTime = 0.0f;
@@ -505,16 +505,6 @@ Osu::Osu(int instanceID) {
     m_screens.push_back(m_mainMenu);
     m_screens.push_back(m_tooltipOverlay);
 
-    // make primary screen visible
-    // m_optionsMenu->setVisible(true);
-    // m_modSelector->setVisible(true);
-    // m_songBrowser2->setVisible(true);
-    // m_pauseMenu->setVisible(true);
-    // m_rankingScreen->setVisible(true);
-    // m_changelog->setVisible(true);
-    // m_editor->setVisible(true);
-    // m_userStatsScreen->setVisible(true);
-
     // Init online functionality (multiplayer/leaderboards/etc)
     bancho.osu = this;
     init_networking_thread();
@@ -527,23 +517,6 @@ Osu::Osu(int instanceID) {
 
     m_updateHandler->checkForUpdates();
 
-    /*
-    // DEBUG: immediately start diff of a beatmap
-    {
-            UString debugFolder = "S:/GAMES/osu!/Songs/41823 The Quick Brown Fox - The Big Black/";
-            UString debugDiffFileName = "The Quick Brown Fox - The Big Black (Blue Dragon) [WHO'S AFRAID OF THE BIG
-    BLACK].osu";
-
-            UString beatmapPath = debugFolder;
-            beatmapPath.append(debugDiffFileName);
-
-            OsuDatabaseBeatmap *debugDiff = new OsuDatabaseBeatmap(this, beatmapPath, debugFolder);
-
-            m_songBrowser2->onDifficultySelected(debugDiff, true);
-            // WARNING: this will leak memory (one OsuDatabaseBeatmap object), but who cares (since debug only)
-    }
-    */
-
     // memory/performance optimization; if osu_mod_mafham is not enabled, reduce the two rendertarget sizes to 64x64,
     m_osu_mod_mafham_ref->setCallback(fastdelegate::MakeDelegate(this, &Osu::onModMafhamChange));
     m_osu_mod_fposu_ref->setCallback(fastdelegate::MakeDelegate(this, &Osu::onModFPoSuChange));
@@ -824,69 +797,29 @@ void Osu::update() {
         // NOTE: force keep loaded background images while playing
         m_backgroundImageHandler->scheduleFreezeCache();
 
-        // scrubbing/seeking
-        if(m_bSeekKey || getSelectedBeatmap()->m_bIsWatchingReplay) {
-            if(!bancho.is_playing_a_multi_map()) {
-                m_bSeeking = true;
-                const float mousePosX = (int)engine->getMouse()->getPos().x;
-                const float percent = clamp<float>(mousePosX / (float)getScreenWidth(), 0.0f, 1.0f);
-
-                if(engine->getMouse()->isLeftDown()) {
-                    if(mousePosX != m_fPrevSeekMousePosX || !osu_scrubbing_smooth.getBool()) {
-                        m_fPrevSeekMousePosX = mousePosX;
-
-                        // special case: allow cancelling the failing animation here
-                        if(getSelectedBeatmap()->hasFailed()) getSelectedBeatmap()->cancelFailing();
-
-                        getSelectedBeatmap()->seekPercentPlayable(percent);
-                    } else {
-                        // special case: keep player invulnerable even if scrubbing position does not change
-                        getSelectedBeatmap()->resetScore();
-                    }
-                } else {
-                    m_fPrevSeekMousePosX = -1.0f;
-                }
-
-                if(engine->getMouse()->isRightDown()) {
-                    m_fQuickSaveTime = clamp<float>((float)((getSelectedBeatmap()->getStartTimePlayable() +
-                                                             getSelectedBeatmap()->getLengthPlayable()) *
-                                                            percent) /
-                                                        (float)getSelectedBeatmap()->getLength(),
-                                                    0.0f, 1.0f);
-                }
-            }
-        }
-
         // skip button clicking
-        if(getSelectedBeatmap()->isInSkippableSection() && !getSelectedBeatmap()->isPaused() && !m_bSeeking &&
-           !m_volumeOverlay->isBusy()) {
+        bool can_skip = getSelectedBeatmap()->isInSkippableSection() && !m_bClickedSkipButton;
+        can_skip &= !getSelectedBeatmap()->isPaused() && !m_volumeOverlay->isBusy();
+        if(can_skip) {
             const bool isAnyOsuKeyDown = (m_bKeyboardKey1Down || m_bKeyboardKey12Down || m_bKeyboardKey2Down ||
                                           m_bKeyboardKey22Down || m_bMouseKey1Down || m_bMouseKey2Down);
             const bool isAnyKeyDown = (isAnyOsuKeyDown || engine->getMouse()->isLeftDown());
 
             if(isAnyKeyDown) {
-                if(!m_bSkipDownCheck) {
-                    m_bSkipDownCheck = true;
-
-                    const bool isCursorInsideSkipButton =
-                        m_hud->getSkipClickRect().contains(engine->getMouse()->getPos());
-
-                    if(isCursorInsideSkipButton) {
-                        if(!m_bSkipScheduled) {
-                            m_bSkipScheduled = true;
-
-                            if(bancho.is_playing_a_multi_map()) {
-                                Packet packet;
-                                packet.id = MATCH_SKIP_REQUEST;
-                                send_packet(packet);
-                            }
+                if(m_hud->getSkipClickRect().contains(engine->getMouse()->getPos())) {
+                    if(!m_bSkipScheduled) {
+                        m_bSkipScheduled = true;
+                        m_bClickedSkipButton = true;
+
+                        if(bancho.is_playing_a_multi_map()) {
+                            Packet packet;
+                            packet.id = MATCH_SKIP_REQUEST;
+                            send_packet(packet);
                         }
                     }
                 }
-            } else
-                m_bSkipDownCheck = false;
-        } else
-            m_bSkipDownCheck = false;
+            }
+        }
 
         // skipping
         if(m_bSkipScheduled) {
@@ -910,6 +843,47 @@ void Osu::update() {
             if(!isLoading) m_bSkipScheduled = false;
         }
 
+        // Reset m_bClickedSkipButton on mouse up
+        // We only use m_bClickedSkipButton to prevent seeking when clicking the skip button
+        if(m_bClickedSkipButton && !getSelectedBeatmap()->isInSkippableSection()) {
+            if(!engine->getMouse()->isLeftDown()) {
+                m_bClickedSkipButton = false;
+            }
+        }
+
+        // scrubbing/seeking
+        m_bSeeking = (m_bSeekKey || getSelectedBeatmap()->m_bIsWatchingReplay);
+        m_bSeeking &= !getSelectedBeatmap()->isPaused() && !m_volumeOverlay->isBusy();
+        m_bSeeking &= !bancho.is_playing_a_multi_map() && !m_bClickedSkipButton;
+        if(m_bSeeking) {
+            const float mousePosX = (int)engine->getMouse()->getPos().x;
+            const float percent = clamp<float>(mousePosX / (float)getScreenWidth(), 0.0f, 1.0f);
+
+            if(engine->getMouse()->isLeftDown()) {
+                if(mousePosX != m_fPrevSeekMousePosX || !osu_scrubbing_smooth.getBool()) {
+                    m_fPrevSeekMousePosX = mousePosX;
+
+                    // special case: allow cancelling the failing animation here
+                    if(getSelectedBeatmap()->hasFailed()) getSelectedBeatmap()->cancelFailing();
+
+                    getSelectedBeatmap()->seekPercentPlayable(percent);
+                } else {
+                    // special case: keep player invulnerable even if scrubbing position does not change
+                    getSelectedBeatmap()->resetScore();
+                }
+            } else {
+                m_fPrevSeekMousePosX = -1.0f;
+            }
+
+            if(engine->getMouse()->isRightDown()) {
+                m_fQuickSaveTime = clamp<float>(
+                    (float)((getSelectedBeatmap()->getStartTimePlayable() + getSelectedBeatmap()->getLengthPlayable()) *
+                            percent) /
+                        (float)getSelectedBeatmap()->getLength(),
+                    0.0f, 1.0f);
+            }
+        }
+
         // quick retry timer
         if(m_bQuickRetryDown && m_fQuickRetryTime != 0.0f && engine->getTime() > m_fQuickRetryTime) {
             m_fQuickRetryTime = 0.0f;
@@ -1270,6 +1244,37 @@ void Osu::onKeyDown(KeyboardEvent &key) {
 
     // while playing (and not in options)
     if(isInPlayMode() && !m_optionsMenu->isVisible() && !m_chat->isVisible()) {
+        auto beatmap = getSelectedBeatmap();
+
+        // instant replay
+        if((beatmap->isPaused() || beatmap->hasFailed())) {
+            if(!key.isConsumed() && key == (KEYCODE)OsuKeyBindings::INSTANT_REPLAY.getInt()) {
+                if(!beatmap->m_bIsWatchingReplay) {
+                    Score score;
+                    score.replay = beatmap->live_replay;
+                    score.md5hash = beatmap->getSelectedDifficulty2()->getMD5Hash();
+                    score.modsLegacy = getScore()->getModsLegacy();
+
+                    // XXX: code reuse from saveAndSubmitScore, also incorrect when using Auto
+                    if(bancho.is_online()) {
+                        score.player_id = bancho.user_id;
+                        score.playerName = bancho.username.toUtf8();
+                    } else {
+                        auto local_name = convar->getConVarByName("name")->getString();
+                        score.player_id = 0;
+                        score.playerName = local_name.toUtf8();
+                    }
+
+                    double percentFinished = beatmap->getPercentFinished();
+                    double offsetPercent = 10000.f / beatmap->getLength();
+                    double seekPoint = fmax(0.f, percentFinished - offsetPercent);
+                    beatmap->cancelFailing();
+                    beatmap->watch(score, seekPoint);
+                    return;
+                }
+            }
+        }
+
         // while playing and not paused
         if(!getSelectedBeatmap()->isPaused()) {
             getSelectedBeatmap()->onKeyDown(key);

+ 1 - 1
src/App/Osu/Osu.h

@@ -325,12 +325,12 @@ class Osu : public App, public MouseListener {
     bool m_bKeyboardKey22Down;
     bool m_bMouseKey1Down;
     bool m_bMouseKey2Down;
-    bool m_bSkipDownCheck;
     bool m_bSkipScheduled;
     bool m_bQuickRetryDown;
     float m_fQuickRetryTime;
     bool m_bSeekKey;
     bool m_bSeeking;
+    bool m_bClickedSkipButton = false;
     float m_fPrevSeekMousePosX;
     float m_fQuickSaveTime;
 

+ 46 - 36
src/App/Osu/OsuBeatmap.cpp

@@ -702,12 +702,23 @@ void OsuBeatmap::deselect() {
     unloadObjects();
 }
 
-bool OsuBeatmap::watch(Score score) {
+bool OsuBeatmap::watch(Score score, double start_percent) {
     // Replay is invalid
     if(score.replay.size() < 3) {
         return false;
     }
 
+    bancho.osu->replay_info.diff2_md5 = score.md5hash.hash;
+    bancho.osu->replay_info.mod_flags = score.modsLegacy;
+    bancho.osu->replay_info.username = UString(score.playerName.c_str());
+    bancho.osu->replay_info.player_id = score.player_id;
+
+    m_bIsPlaying = false;
+    m_bIsPaused = false;
+    m_bContinueScheduled = false;
+    stopStarCacheLoader();
+    unloadObjects();
+
     m_bIsWatchingReplay = true;
     m_osu->onBeforePlayStart();
 
@@ -723,7 +734,9 @@ bool OsuBeatmap::watch(Score score) {
 
     m_osu->m_songBrowser2->m_bHasSelectedAndIsPlaying = true;
     m_osu->m_songBrowser2->setVisible(false);
-    m_osu->onPlayStart();
+
+    m_bIsWaiting = true;  // ensure onPlayStart() will be called by seekPercent()
+    seekPercent(start_percent);
 
     return true;
 }
@@ -997,13 +1010,17 @@ void OsuBeatmap::stop(bool quit) {
 
     if(getSkin()->getFailsound()->isPlaying()) engine->getSound()->stop(getSkin()->getFailsound());
 
-    m_currentHitObject = NULL;
-
     m_bIsPlaying = false;
     m_bIsPaused = false;
     m_bContinueScheduled = false;
 
-    onBeforeStop(quit);
+    // kill any running star cache loader
+    stopStarCacheLoader();
+
+    saveAndSubmitScore(quit);
+
+    m_bIsWatchingReplay = false;
+    spectated_replay.clear();
 
     unloadObjects();
 
@@ -1293,21 +1310,8 @@ bool OsuBeatmap::isKey2Down() {
     }
 }
 
-bool OsuBeatmap::isClickHeld() {
-    if(m_bIsWatchingReplay) {
-        return current_keys & (OsuReplay::M1 | OsuReplay::K1 | OsuReplay::M2 | OsuReplay::K2);
-    } else {
-        return m_bClick1Held || m_bClick2Held;
-    }
-}
-
-bool OsuBeatmap::isLastKeyDownKey1() {
-    if(m_bIsWatchingReplay) {
-        return last_keys & (OsuReplay::M1 | OsuReplay::K1);
-    } else {
-        return m_bPrevKeyWasKey1;
-    }
-}
+bool OsuBeatmap::isClickHeld() { return isKey1Down() || isKey2Down(); }
+bool OsuBeatmap::isLastKeyDownKey1() { return m_bPrevKeyWasKey1; }
 
 UString OsuBeatmap::getTitle() const {
     if(m_selectedDifficulty2 != NULL)
@@ -1562,7 +1566,9 @@ bool OsuBeatmap::canUpdate() {
 }
 
 void OsuBeatmap::handlePreviewPlay() {
-    if(m_music != NULL && (!m_music->isPlaying() || m_music->getPosition() > 0.95f) && m_selectedDifficulty2 != NULL) {
+    if(m_music == nullptr) return;
+
+    if((!m_music->isPlaying() || m_music->getPosition() > 0.95f) && m_selectedDifficulty2 != NULL) {
         // this is an assumption, but should be good enough for most songs
         // reset playback position when the song has nearly reached the end (when the user switches back to the results
         // screen or the songbrowser after playing)
@@ -1574,14 +1580,23 @@ void OsuBeatmap::handlePreviewPlay() {
             if(m_music->getFrequency() < m_fMusicFrequencyBackup)  // player has died, reset frequency
                 m_music->setFrequency(m_fMusicFrequencyBackup);
 
-            if(m_osu->getMainMenu()->isVisible())
+            // When McOsu is initialized, it starts playing a random song in the main menu.
+            // Users can set a convar to make it start at its preview point instead.
+            // The next songs will start at the beginning regardless.
+            static bool should_start_song_at_preview_point =
+                convar->getConVarByName("start_first_main_menu_song_at_preview_point")->getBool();
+            bool start_at_song_beginning = m_osu->getMainMenu()->isVisible() && !should_start_song_at_preview_point;
+            should_start_song_at_preview_point = false;
+
+            if(start_at_song_beginning) {
                 m_music->setPositionMS(0);
-            else if(m_iContinueMusicPos != 0)
+            } else if(m_iContinueMusicPos != 0) {
                 m_music->setPositionMS(m_iContinueMusicPos);
-            else
+            } else {
                 m_music->setPositionMS(m_selectedDifficulty2->getPreviewTime() < 0
                                            ? (unsigned long)(m_music->getLengthMS() * 0.40f)
                                            : m_selectedDifficulty2->getPreviewTime());
+            }
 
             m_music->setVolume(m_osu_volume_music_ref->getFloat());
             m_music->setSpeed(m_osu->getSpeedMultiplier());
@@ -1589,7 +1604,7 @@ void OsuBeatmap::handlePreviewPlay() {
     }
 
     // always loop during preview
-    if(m_music != NULL) m_music->setLoop(osu_beatmap_preview_music_loop.getBool());
+    m_music->setLoop(osu_beatmap_preview_music_loop.getBool());
 }
 
 void OsuBeatmap::loadMusic(bool stream, bool prescan) {
@@ -1630,6 +1645,7 @@ void OsuBeatmap::unloadMusic() {
 }
 
 void OsuBeatmap::unloadObjects() {
+    m_currentHitObject = nullptr;
     for(int i = 0; i < m_hitobjects.size(); i++) {
         delete m_hitobjects[i];
     }
@@ -2487,11 +2503,13 @@ void OsuBeatmap::update2() {
 
             // Pressed key 1
             if(!(last_keys & OsuReplay::K1) && current_keys & OsuReplay::K1) {
+                m_bPrevKeyWasKey1 = true;
                 m_osu->getHUD()->animateInputoverlay(1, true);
                 m_clicks.push_back(click);
                 if(!m_bInBreak && !m_bIsInSkippableSection) m_osu->getScore()->addKeyCount(1);
             }
             if(!(last_keys & OsuReplay::M1) && current_keys & OsuReplay::M1) {
+                m_bPrevKeyWasKey1 = true;
                 m_osu->getHUD()->animateInputoverlay(3, true);
                 m_clicks.push_back(click);
                 if(!m_bInBreak && !m_bIsInSkippableSection) m_osu->getScore()->addKeyCount(3);
@@ -2499,11 +2517,13 @@ void OsuBeatmap::update2() {
 
             // Pressed key 2
             if(!(last_keys & OsuReplay::K2) && current_keys & OsuReplay::K2) {
+                m_bPrevKeyWasKey1 = false;
                 m_osu->getHUD()->animateInputoverlay(2, true);
                 m_clicks.push_back(click);
                 if(!m_bInBreak && !m_bIsInSkippableSection) m_osu->getScore()->addKeyCount(2);
             }
             if(!(last_keys & OsuReplay::M2) && current_keys & OsuReplay::M2) {
+                m_bPrevKeyWasKey1 = false;
                 m_osu->getHUD()->animateInputoverlay(4, true);
                 m_clicks.push_back(click);
                 if(!m_bInBreak && !m_bIsInSkippableSection) m_osu->getScore()->addKeyCount(4);
@@ -3406,12 +3426,7 @@ void OsuBeatmap::onPlayStart() {
     onModUpdate(false, false);
 }
 
-void OsuBeatmap::onBeforeStop(bool quit) {
-    debugLog("OsuBeatmap::onBeforeStop()\n");
-
-    // kill any running star cache loader
-    stopStarCacheLoader();
-
+void OsuBeatmap::saveAndSubmitScore(bool quit) {
     // calculate stars
     double aim = 0.0;
     double aimSliderFactor = 0.0;
@@ -3547,11 +3562,6 @@ void OsuBeatmap::onBeforeStop(bool quit) {
     if(!isComplete) {
         m_osu->getScore()->setPPv2(0.0f);
     }
-
-    m_bIsWatchingReplay = false;
-    spectated_replay.clear();
-
-    debugLog("OsuBeatmap::onBeforeStop() done.\n");
 }
 
 void OsuBeatmap::onPaused(bool first) {

+ 2 - 2
src/App/Osu/OsuBeatmap.h

@@ -109,7 +109,7 @@ class OsuBeatmap {
                     // clicking on a beatmap)
     void selectDifficulty2(OsuDatabaseBeatmap *difficulty2);
     void deselect();  // stops + unloads the currently loaded music and deletes all hitobjects
-    bool watch(Score score);
+    bool watch(Score score, double start_percent);
     bool play();
     void restart(bool quick = false);
     void pause(bool quitIfWaiting = true);
@@ -408,7 +408,7 @@ class OsuBeatmap {
     }
 
     void onPlayStart();
-    void onBeforeStop(bool quit);
+    void saveAndSubmitScore(bool quit);
     void onPaused(bool first);
 
     void drawFollowPoints(Graphics *g);

+ 5 - 5
src/App/Osu/OsuHUD.cpp

@@ -317,11 +317,6 @@ void OsuHUD::draw(Graphics *g) {
 
         drawFancyScoreboard(g);
 
-        if(!m_osu->isSkipScheduled() && beatmap->isInSkippableSection() &&
-           ((m_osu_skip_intro_enabled_ref->getBool() && beatmap->getHitObjectIndexForCurrentTime() < 1) ||
-            (m_osu_skip_breaks_enabled_ref->getBool() && beatmap->getHitObjectIndexForCurrentTime() > 0)))
-            drawSkip(g);
-
         g->pushTransform();
         {
             if(m_osu->getModTarget() && osu_draw_target_heatmap.getBool())
@@ -450,6 +445,11 @@ void OsuHUD::draw(Graphics *g) {
         drawScrubbingTimeline(g, beatmap->getTime(), beatmap->getLength(), beatmap->getLengthPlayable(),
                               beatmap->getStartTimePlayable(), beatmap->getPercentFinishedPlayable(), breaks);
     }
+
+    if(!m_osu->isSkipScheduled() && beatmap->isInSkippableSection() &&
+       ((m_osu_skip_intro_enabled_ref->getBool() && beatmap->getHitObjectIndexForCurrentTime() < 1) ||
+        (m_osu_skip_breaks_enabled_ref->getBool() && beatmap->getHitObjectIndexForCurrentTime() > 0)))
+        drawSkip(g);
 }
 
 void OsuHUD::mouse_update(bool *propagate_clicks) {

+ 2 - 0
src/App/Osu/OsuKeyBindings.cpp

@@ -31,6 +31,7 @@ ConVar OsuKeyBindings::SEEK_TIME_FORWARD("osu_key_seek_time_forward", (int)KEY_R
 ConVar OsuKeyBindings::QUICK_RETRY("osu_key_quick_retry", (int)KEY_BACKSPACE, FCVAR_NONE);
 ConVar OsuKeyBindings::QUICK_SAVE("osu_key_quick_save", (int)KEY_F6, FCVAR_NONE);
 ConVar OsuKeyBindings::QUICK_LOAD("osu_key_quick_load", (int)KEY_F7, FCVAR_NONE);
+ConVar OsuKeyBindings::INSTANT_REPLAY("osu_key_instant_replay", (int)KEY_F1, FCVAR_NONE);
 ConVar OsuKeyBindings::TOGGLE_CHAT("osu_key_toggle_chat", (int)KEY_F8, FCVAR_NONE);
 ConVar OsuKeyBindings::SAVE_SCREENSHOT("osu_key_save_screenshot", (int)KEY_F12, FCVAR_NONE);
 ConVar OsuKeyBindings::DISABLE_MOUSE_BUTTONS("osu_key_disable_mouse_buttons", (int)KEY_F10, FCVAR_NONE);
@@ -75,6 +76,7 @@ std::vector<ConVar*> OsuKeyBindings::ALL = {&OsuKeyBindings::LEFT_CLICK,
                                             &OsuKeyBindings::QUICK_RETRY,
                                             &OsuKeyBindings::QUICK_SAVE,
                                             &OsuKeyBindings::QUICK_LOAD,
+                                            &OsuKeyBindings::INSTANT_REPLAY,
                                             &OsuKeyBindings::TOGGLE_CHAT,
                                             &OsuKeyBindings::SAVE_SCREENSHOT,
                                             &OsuKeyBindings::DISABLE_MOUSE_BUTTONS,

+ 1 - 0
src/App/Osu/OsuKeyBindings.h

@@ -37,6 +37,7 @@ class OsuKeyBindings {
     static ConVar QUICK_RETRY;
     static ConVar QUICK_SAVE;
     static ConVar QUICK_LOAD;
+    static ConVar INSTANT_REPLAY;
     static ConVar TOGGLE_CHAT;
     static ConVar SAVE_SCREENSHOT;
     static ConVar DISABLE_MOUSE_BUTTONS;

+ 2 - 2
src/App/Osu/OsuMainMenu.cpp

@@ -1267,7 +1267,7 @@ void OsuMainMenu::mouse_update(bool *propagate_clicks) {
         anim->moveQuadInOut(&m_fUpdateButtonAnim, 1.0f, 0.5f, true);
     }
 
-    // handle pause button pause detection
+    // Update pause button and shuffle songs
     if(m_osu->getSelectedBeatmap() != NULL) {
         if(m_osu->getSelectedBeatmap()->isPreviewMusicPlaying()) {
             m_osu->getSelectedBeatmap()->getMusic()->setLoop(false);
@@ -1286,7 +1286,7 @@ void OsuMainMenu::selectRandomBeatmap() {
     shuffling = true;
 
     if(m_osu->getSongBrowser()->getDatabase()->isFinished()) {
-        m_osu->getSongBrowser()->selectRandomBeatmap(false);
+        m_osu->getSongBrowser()->selectRandomBeatmap();
     } else {
         // Database is not loaded yet, load a random map and select it
         // XXX: Also pick from McOsu maps/ directory

+ 1 - 0
src/App/Osu/OsuOptionsMenu.cpp

@@ -1025,6 +1025,7 @@ OsuOptionsMenu::OsuOptionsMenu(Osu *osu) : OsuScreenBackable(osu) {
     addKeyBindButton("Quick Retry (hold briefly)", &OsuKeyBindings::QUICK_RETRY);
     addKeyBindButton("Quick Save", &OsuKeyBindings::QUICK_SAVE);
     addKeyBindButton("Quick Load", &OsuKeyBindings::QUICK_LOAD);
+    addKeyBindButton("Instant Replay", &OsuKeyBindings::INSTANT_REPLAY);
     addSubSection("Keys - Universal", keyboardSectionTags);
     addKeyBindButton("Toggle chat", &OsuKeyBindings::TOGGLE_CHAT);
     addKeyBindButton("Save Screenshot", &OsuKeyBindings::SAVE_SCREENSHOT);

+ 1 - 1
src/App/Osu/OsuPauseMenu.cpp

@@ -358,7 +358,7 @@ void OsuPauseMenu::onResolutionChange(Vector2 newResolution) {
 CBaseUIContainer *OsuPauseMenu::setVisible(bool visible) {
     m_bVisible = visible;
 
-    if(m_osu->isInPlayMode() && m_osu->getSelectedBeatmap() != NULL)
+    if(m_osu->isInPlayMode())
         setContinueEnabled(!m_osu->getSelectedBeatmap()->hasFailed());
     else
         setContinueEnabled(true);

+ 1 - 6
src/App/Osu/OsuReplay.cpp

@@ -266,17 +266,12 @@ void OsuReplay::load_and_watch(Score score) {
         return;
     }
 
-    bancho.osu->replay_info.diff2_md5 = score.md5hash.hash;
-    bancho.osu->replay_info.mod_flags = score.modsLegacy;
-    bancho.osu->replay_info.username = UString(score.playerName.c_str());
-    bancho.osu->replay_info.player_id = score.player_id;
-
     auto beatmap = bancho.osu->getSongBrowser()->getDatabase()->getBeatmapDifficulty(score.md5hash.hash);
     if(beatmap == nullptr) {
         // XXX: Auto-download beatmap
         bancho.osu->m_notificationOverlay->addNotification("Missing beatmap for this replay");
     } else {
         bancho.osu->getSongBrowser()->onDifficultySelected(beatmap, false);
-        bancho.osu->getSelectedBeatmap()->watch(score);
+        bancho.osu->getSelectedBeatmap()->watch(score, 0.f);
     }
 }

+ 27 - 20
src/App/Osu/OsuSlider.cpp

@@ -174,7 +174,7 @@ OsuSlider::OsuSlider(char type, int repeat, float pixelLength, std::vector<Vecto
     m_fEndHitAnimation = 0.0f;
     m_fEndSliderBodyFadeAnimation = 0.0f;
     m_iStrictTrackingModLastClickHeldTime = 0;
-    m_iDownKey = 0;
+    m_iFatFingerKey = 0;
     m_iPrevSliderSlideSoundSampleSet = -1;
     m_bCursorLeft = true;
     m_bCursorInside = false;
@@ -735,12 +735,9 @@ void OsuSlider::update(long curPos) {
         m_vCurPoint = m_beatmap->osuCoords2Pixels(m_vCurPointRaw);
     }
 
-    // reset sliding key (opposite), see isClickHeldSlider()
-    if(m_iDownKey > 0) {
-        if((m_iDownKey == 2 && !m_beatmap->isKey1Down()) ||
-           (m_iDownKey == 1 && !m_beatmap->isKey2Down()))  // opposite key!
-            m_iDownKey = 0;
-    }
+    // When fat finger key is released, remove isClickHeldSlider() restrictions
+    if(m_iFatFingerKey == 1 && !m_beatmap->isKey1Down()) m_iFatFingerKey = 0;
+    if(m_iFatFingerKey == 2 && !m_beatmap->isKey2Down()) m_iFatFingerKey = 0;
 
     // handle dynamic followradius
     float followRadius =
@@ -1179,11 +1176,16 @@ void OsuSlider::onHit(OsuScore::HIT result, long delta, bool startOrEnd, float t
 
         m_bStartFinished = true;
 
-        // remember which key this slider was started with
-        if(m_beatmap->isKey2Down() && !m_beatmap->isLastKeyDownKey1())
-            m_iDownKey = 2;
-        else if(m_beatmap->isKey1Down() && m_beatmap->isLastKeyDownKey1())
-            m_iDownKey = 1;
+        // The player entered the slider by fat fingering, so...
+        if(m_beatmap->isKey1Down() && m_beatmap->isKey2Down()) {
+            if(m_beatmap->isLastKeyDownKey1()) {
+                // Player pressed K1 last: "fat finger" key is K2
+                m_iFatFingerKey = 2;
+            } else {
+                // Player pressed K2 last: "fat finger" key is K1
+                m_iFatFingerKey = 1;
+            }
+        }
 
         if(!m_beatmap->getOsu()->getModTarget())
             m_beatmap->addHitResult(
@@ -1358,7 +1360,7 @@ void OsuSlider::onReset(long curPos) {
     m_beatmap->getSkin()->stopSliderSlideSound();
 
     m_iStrictTrackingModLastClickHeldTime = 0;
-    m_iDownKey = 0;
+    m_iFatFingerKey = 0;
     m_iPrevSliderSlideSoundSampleSet = -1;
     m_bCursorLeft = true;
     m_bHeldTillEnd = false;
@@ -1434,11 +1436,16 @@ void OsuSlider::rebuildVertexBuffer(bool useRawCoords) {
 }
 
 bool OsuSlider::isClickHeldSlider() {
-    // m_iDownKey contains the key the sliderstartcircle was clicked with (if it was clicked at all, if not then it is
-    // 0) it is reset back to 0 automatically in update() once the opposite key has been released at least once if
-    // m_iDownKey is less than 1, then any key being held is enough to slide (either the startcircle was missed, or the
-    // opposite key it was clicked with has been released at least once already) otherwise, that specific key is the
-    // only one which counts for sliding
-    const bool mouseDownAcceptable = (m_iDownKey == 1 ? m_beatmap->isKey1Down() : m_beatmap->isKey2Down());
-    return (m_iDownKey < 1 ? m_beatmap->isClickHeld() : mouseDownAcceptable);
+    // osu! has a weird slider quirk, that I'll explain in detail here.
+    // When holding K1 before the slider, tapping K2 on slider head, and releasing K2 later,
+    // the slider is no longer considered being "held" until K2 is pressed again, or K1 is released and pressed again.
+
+    // The reason this exists is to prevent people from holding K1 the whole map and tapping with K2.
+    // Holding is part of the rhythm flow, and this is a rhythm game right?
+
+    if(m_iFatFingerKey == 0) return m_beatmap->isClickHeld();
+    if(m_iFatFingerKey == 1) return m_beatmap->isKey2Down();
+    if(m_iFatFingerKey == 2) return m_beatmap->isKey1Down();
+
+    return false;  // unreachable
 }

+ 1 - 1
src/App/Osu/OsuSlider.h

@@ -128,7 +128,7 @@ class OsuSlider : public OsuHitObject {
     float m_fEndHitAnimation;
     float m_fEndSliderBodyFadeAnimation;
     long m_iStrictTrackingModLastClickHeldTime;
-    int m_iDownKey;
+    int m_iFatFingerKey;
     int m_iPrevSliderSlideSoundSampleSet;
     bool m_bCursorLeft;
     bool m_bCursorInside;

+ 2 - 8
src/App/Osu/OsuSongBrowser.cpp

@@ -1,9 +1,4 @@
-//================ Copyright (c) 2016, PG, All rights reserved. =================//
-//
-// Purpose:		beatmap browser and selector
-//
-// $NoKeywords: $osusb
-//===============================================================================//
+#include "OsuSongBrowser.h"
 
 #include "AnimationHandler.h"
 #include "Bancho.h"
@@ -35,7 +30,6 @@
 #include "OsuRoom.h"
 #include "OsuSkin.h"
 #include "OsuSkinImage.h"
-#include "OsuSongBrowser.h"
 #include "OsuUIBackButton.h"
 #include "OsuUIContextMenu.h"
 #include "OsuUISearchOverlay.h"
@@ -4116,7 +4110,7 @@ void OsuSongBrowser::selectSongButton(OsuUISongBrowserButton *songButton) {
     }
 }
 
-void OsuSongBrowser::selectRandomBeatmap(bool playMusicFromPreviewPoint) {
+void OsuSongBrowser::selectRandomBeatmap() {
     // filter songbuttons or independent diffs
     const std::vector<CBaseUIElement *> &elements = m_songBrowser->getContainer()->getElements();
     std::vector<OsuUISongBrowserSongButton *> songButtons;

+ 2 - 13
src/App/Osu/OsuSongBrowser.h

@@ -1,13 +1,4 @@
-//================ Copyright (c) 2016, PG, All rights reserved. =================//
-//
-// Purpose:		beatmap browser and selector
-//
-// $NoKeywords: $osusb
-//===============================================================================//
-
-#ifndef OSUSONGBROWSER2_H
-#define OSUSONGBROWSER2_H
-
+#pragma once
 #include "MouseListener.h"
 #include "OsuScreenBackable.h"
 
@@ -124,7 +115,7 @@ class OsuSongBrowser : public OsuScreenBackable {
     void onCollectionButtonContextMenu(OsuUISongBrowserCollectionButton *collectionButton, UString text, int id);
 
     void highlightScore(uint64_t unixTimestamp);
-    void selectRandomBeatmap(bool playMusicFromPreviewPoint = true);
+    void selectRandomBeatmap();
     void playNextRandomBeatmap() {
         selectRandomBeatmap();
         playSelectedDifficulty();
@@ -393,5 +384,3 @@ class OsuSongBrowser : public OsuScreenBackable {
     bool m_bBackgroundStarCalcScheduledForce;
     OsuDatabaseBeatmapStarCalculator *m_dynamicStarCalculator;
 };
-
-#endif