Clément Wolf 1 месяц назад
Родитель
Сommit
c6b9d37b27

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

@@ -29,7 +29,7 @@ Score parse_score(char *score_line) {
 
     str = strtok_r(NULL, "|", &saveptr);
     if(!str) return score;
-    score.playerName = UString(str);
+    score.playerName = str;
 
     str = strtok_r(NULL, "|", &saveptr);
     if(!str) return score;
@@ -85,7 +85,7 @@ Score parse_score(char *score_line) {
 
     // Set username for given user id, since we now know both
     auto user = get_user_info(score.player_id);
-    user->name = score.playerName;
+    user->name = UString(score.playerName.c_str());
 
     // Mark as a player. Setting this also makes the has_user_info check pass,
     // which unlocks context menu actions such as sending private messages.

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

@@ -195,7 +195,7 @@ void download(const char* url, float* progress, std::vector<uint8_t>& out, int*
         if(result->url == url) {
             *progress = result->progress;
             *response_code = result->response_code;
-            if(result->progress == -1.f || result->progress == 1.f) {
+            if(*response_code != 0) {
                 out = result->data;
                 delete matching_thread->downloads[i];
                 matching_thread->downloads.erase(matching_thread->downloads.begin() + i);

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

@@ -825,7 +825,7 @@ void Osu::update() {
         m_backgroundImageHandler->scheduleFreezeCache();
 
         // scrubbing/seeking
-        if(m_bSeekKey) {
+        if(m_bSeekKey || getSelectedBeatmap()->m_bIsWatchingReplay) {
             if(!bancho.is_playing_a_multi_map()) {
                 m_bSeeking = true;
                 const float mousePosX = (int)engine->getMouse()->getPos().x;
@@ -843,15 +843,17 @@ void Osu::update() {
                         // special case: keep player invulnerable even if scrubbing position does not change
                         getSelectedBeatmap()->resetScore();
                     }
-                } else
+                } else {
                     m_fPrevSeekMousePosX = -1.0f;
+                }
 
-                if(engine->getMouse()->isRightDown())
+                if(engine->getMouse()->isRightDown()) {
                     m_fQuickSaveTime = clamp<float>((float)((getSelectedBeatmap()->getStartTimePlayable() +
                                                              getSelectedBeatmap()->getLengthPlayable()) *
                                                             percent) /
                                                         (float)getSelectedBeatmap()->getLength(),
                                                     0.0f, 1.0f);
+                }
             }
         }
 
@@ -2011,7 +2013,7 @@ void Osu::onFocusGained() {
 
 void Osu::onFocusLost() {
     if(isInPlayMode() && !getSelectedBeatmap()->isPaused() && osu_pause_on_focus_loss.getBool()) {
-        if(!bancho.is_playing_a_multi_map()) {
+        if(!bancho.is_playing_a_multi_map() && !getSelectedBeatmap()->m_bIsWatchingReplay) {
             getSelectedBeatmap()->pause(false);
             m_pauseMenu->setVisible(true);
             m_modSelector->setVisible(false);
@@ -2163,7 +2165,8 @@ void Osu::updateConfineCursor() {
 
     if((osu_confine_cursor_fullscreen.getBool() && env->isFullscreen()) ||
        (osu_confine_cursor_windowed.getBool() && !env->isFullscreen()) ||
-       (isInPlayMode() && !m_pauseMenu->isVisible() && !getModAuto() && !getModAutopilot()))
+       (isInPlayMode() && !m_pauseMenu->isVisible() && !getModAuto() && !getModAutopilot() &&
+        !getSelectedBeatmap()->m_bIsWatchingReplay))
         env->setCursorClip(true, McRect());
     else
         env->setCursorClip(false, McRect());

+ 24 - 10
src/App/Osu/OsuBeatmap.cpp

@@ -603,7 +603,10 @@ void OsuBeatmap::keyPressed1(bool mouse) {
     m_bClick1Held = true;
 
     if((!m_osu->getModAuto() && !m_osu->getModRelax()) || !osu_auto_and_relax_block_user_input.getBool())
-        m_clicks.push_back(m_iCurMusicPosWithOffsets);
+        m_clicks.push_back(Click{
+            .tms = m_iCurMusicPosWithOffsets,
+            .pos = getCursorPos(),
+        });
 
     if(mouse) {
         current_keys = current_keys | OsuReplay::M1;
@@ -635,7 +638,10 @@ void OsuBeatmap::keyPressed2(bool mouse) {
     m_bClick2Held = true;
 
     if((!m_osu->getModAuto() && !m_osu->getModRelax()) || !osu_auto_and_relax_block_user_input.getBool())
-        m_clicks.push_back(m_iCurMusicPosWithOffsets);
+        m_clicks.push_back(Click{
+            .tms = m_iCurMusicPosWithOffsets,
+            .pos = getCursorPos(),
+        });
 
     if(mouse) {
         current_keys = current_keys | OsuReplay::M2;
@@ -1631,7 +1637,7 @@ void OsuBeatmap::unloadObjects() {
     m_hitobjectsSortedByEndTime = std::vector<OsuHitObject *>();
     m_misaimObjects = std::vector<OsuHitObject *>();
     m_breaks = std::vector<OsuDatabaseBeatmap::BREAK>();
-    m_clicks = std::vector<long>();
+    m_clicks = std::vector<Click>();
 }
 
 void OsuBeatmap::resetHitObjects(long curPos) {
@@ -2452,6 +2458,13 @@ void OsuBeatmap::update2() {
             next_frame = spectated_replay[current_frame_idx + 1];
             current_keys = current_frame.key_flags;
 
+            Click click;
+            click.tms = current_frame.cur_music_pos;
+            click.pos.x = current_frame.x;
+            click.pos.y = current_frame.y;
+            click.pos *= OsuGameRules::getPlayfieldScaleFactor(m_osu);
+            click.pos += OsuGameRules::getPlayfieldOffset(m_osu);
+
             // Flag fix to simplify logic (stable sets both K1 and M1 when K1 is pressed)
             if(current_keys & OsuReplay::K1) current_keys &= ~OsuReplay::M1;
             if(current_keys & OsuReplay::K2) current_keys &= ~OsuReplay::M2;
@@ -2475,24 +2488,24 @@ void OsuBeatmap::update2() {
             // Pressed key 1
             if(!(last_keys & OsuReplay::K1) && current_keys & OsuReplay::K1) {
                 m_osu->getHUD()->animateInputoverlay(1, true);
-                m_clicks.push_back(current_frame.cur_music_pos);
+                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_osu->getHUD()->animateInputoverlay(3, true);
-                m_clicks.push_back(current_frame.cur_music_pos);
+                m_clicks.push_back(click);
                 if(!m_bInBreak && !m_bIsInSkippableSection) m_osu->getScore()->addKeyCount(3);
             }
 
             // Pressed key 2
             if(!(last_keys & OsuReplay::K2) && current_keys & OsuReplay::K2) {
                 m_osu->getHUD()->animateInputoverlay(2, true);
-                m_clicks.push_back(current_frame.cur_music_pos);
+                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_osu->getHUD()->animateInputoverlay(4, true);
-                m_clicks.push_back(current_frame.cur_music_pos);
+                m_clicks.push_back(click);
                 if(!m_bInBreak && !m_bIsInSkippableSection) m_osu->getScore()->addKeyCount(4);
             }
         }
@@ -2803,7 +2816,7 @@ void OsuBeatmap::update2() {
                         continue;
 
                     m_misaimObjects[i]->misAimed();
-                    const long delta = m_clicks[c] - (long)m_misaimObjects[i]->getTime();
+                    const long delta = m_clicks[c].tms - (long)m_misaimObjects[i]->getTime();
                     m_osu->getHUD()->addHitError(delta, false, true);
 
                     break;  // the current click has been dealt with (and the hitobject has been misaimed)
@@ -3454,9 +3467,10 @@ void OsuBeatmap::onBeforeStop(bool quit) {
 
     if(bancho.is_online()) {
         score.player_id = bancho.user_id;
-        score.playerName = bancho.username;
+        score.playerName = bancho.username.toUtf8();
     } else {
-        score.playerName = convar->getConVarByName("name")->getString();
+        auto local_name = convar->getConVarByName("name")->getString();
+        score.playerName = local_name.toUtf8();
     }
     score.passed = isComplete && !isZero && !m_osu->getScore()->hasDied();
     score.grade = score.passed ? m_osu->getScore()->getGrade() : Score::Grade::F;

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

@@ -18,6 +18,11 @@ class OsuDatabaseBeatmap;
 class OsuBackgroundStarCacheLoader;
 class OsuBackgroundStarCalcHandler;
 
+struct Click {
+    long tms;
+    Vector2 pos;
+};
+
 class OsuBeatmap {
    public:
     friend class OsuBackgroundStarCacheLoader;
@@ -329,7 +334,7 @@ class OsuBeatmap {
     bool m_bClickedContinue;
     bool m_bPrevKeyWasKey1;
     int m_iAllowAnyNextKeyForFullAlternateUntilHitObjectIndex;
-    std::vector<long> m_clicks;
+    std::vector<Click> m_clicks;
 
     // hitobjects
     std::vector<OsuHitObject *> m_hitobjects;

+ 3 - 4
src/App/Osu/OsuCircle.cpp

@@ -563,11 +563,10 @@ void OsuCircle::miss(long curPos) {
     onHit(OsuScore::HIT::HIT_MISS, delta);
 }
 
-void OsuCircle::onClickEvent(std::vector<long> &clicks) {
+void OsuCircle::onClickEvent(std::vector<Click> &clicks) {
     if(m_bFinished) return;
 
-    const Vector2 cursorPos = m_beatmap->getCursorPos();
-
+    const Vector2 cursorPos = clicks[0].pos;
     const Vector2 pos = m_beatmap->osuCoords2Pixels(m_vRawPos);
     const float cursorDelta = (cursorPos - pos).length();
 
@@ -578,7 +577,7 @@ void OsuCircle::onClickEvent(std::vector<long> &clicks) {
             return;  // ignore click event completely
         }
 
-        const long delta = clicks[0] - (long)m_iTime;
+        const long delta = clicks[0].tms - (long)m_iTime;
 
         OsuScore::HIT result = OsuGameRules::getHitResult(delta, m_beatmap);
         if(result != OsuScore::HIT::HIT_NULL) {

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

@@ -73,7 +73,7 @@ class OsuCircle : public OsuHitObject {
     Vector2 getOriginalRawPosAt(long pos) { return m_vOriginalRawPos; }
     Vector2 getAutoCursorPos(long curPos);
 
-    virtual void onClickEvent(std::vector<long> &clicks);
+    virtual void onClickEvent(std::vector<Click> &clicks);
     virtual void onReset(long curPos);
 
    private:

+ 19 - 19
src/App/Osu/OsuDatabase.cpp

@@ -780,22 +780,21 @@ std::vector<UString> OsuDatabase::getPlayerNamesWithPPScores() {
         keys.push_back(kv.first);
     }
 
-    // bit of a useless double string conversion going on here, but whatever
-
-    std::unordered_set<UString> tempNames;
+    std::unordered_set<std::string> tempNames;
     for(auto &key : keys) {
         for(Score &score : m_scores[key]) {
-            if(!score.isLegacyScore) tempNames.insert(UString(score.playerName.toUtf8()));
+            if(!score.isLegacyScore) tempNames.insert(score.playerName);
         }
     }
 
     // always add local user, even if there were no scores
-    tempNames.insert(UString(m_name_ref->getString().toUtf8()));
+    auto local_name = m_name_ref->getString();
+    tempNames.insert(std::string(local_name.toUtf8()));
 
     std::vector<UString> names;
     names.reserve(tempNames.size());
     for(auto k : tempNames) {
-        if(k.length() > 0) names.push_back(k);
+        if(k.length() > 0) names.push_back(UString(k.c_str()));
     }
 
     return names;
@@ -804,23 +803,22 @@ std::vector<UString> OsuDatabase::getPlayerNamesWithPPScores() {
 std::vector<UString> OsuDatabase::getPlayerNamesWithScoresForUserSwitcher() {
     const bool includeLegacyNames = osu_user_switcher_include_legacy_scores_for_names.getBool();
 
-    // bit of a useless double string conversion going on here, but whatever
-
-    std::unordered_set<UString> tempNames;
+    std::unordered_set<std::string> tempNames;
     for(auto kv : m_scores) {
         const MD5Hash &key = kv.first;
         for(Score &score : m_scores[key]) {
-            if(!score.isLegacyScore || includeLegacyNames) tempNames.insert(UString(score.playerName.toUtf8()));
+            if(!score.isLegacyScore || includeLegacyNames) tempNames.insert(score.playerName);
         }
     }
 
     // always add local user, even if there were no scores
-    tempNames.insert(UString(m_name_ref->getString().toUtf8()));
+    auto local_name = m_name_ref->getString();
+    tempNames.insert(std::string(local_name.toUtf8()));
 
     std::vector<UString> names;
     names.reserve(tempNames.size());
     for(auto k : tempNames) {
-        if(k.length() > 0) names.push_back(k);
+        if(k.length() > 0) names.push_back(UString(k.c_str()));
     }
 
     return names;
@@ -857,11 +855,13 @@ OsuDatabase::PlayerPPScores OsuDatabase::getPlayerPPScores(UString playerName) {
             bool foundValidScore = false;
             float prevPP = -1.0f;
             for(Score &score : m_scores[key]) {
+                UString uName = UString(score.playerName.c_str());
+
                 if(!score.isLegacyScore &&
                    (osu_user_include_relax_and_autopilot_for_stats.getBool()
                         ? true
                         : !((score.modsLegacy & ModFlags::Relax) || (score.modsLegacy & ModFlags::Autopilot))) &&
-                   score.playerName == playerName) {
+                   uName == playerName) {
                     foundValidScore = true;
 
                     totalScore += score.score;
@@ -1851,7 +1851,7 @@ void OsuDatabase::loadScores() {
                         sc.unixTimestamp = read_int64(&db);
 
                         // default
-                        sc.playerName = read_string(&db);
+                        sc.playerName = read_stdstring(&db);
 
                         sc.num300s = read_short(&db);
                         sc.num100s = read_short(&db);
@@ -1973,7 +1973,7 @@ void OsuDatabase::loadScores() {
                     sc.version = read_int32(&db);
                     skip_string(&db);  // beatmap hash (already have it)
 
-                    sc.playerName = read_string(&db);
+                    sc.playerName = read_stdstring(&db);
                     skip_string(&db);  // replay hash (don't use it)
 
                     sc.num300s = read_short(&db);
@@ -2107,7 +2107,7 @@ void OsuDatabase::saveScores() {
             write_int64(&db, score.unixTimestamp);
 
             // default
-            write_string(&db, score.playerName);
+            write_string(&db, score.playerName.c_str());
 
             write_short(&db, score.num300s);
             write_short(&db, score.num100s);
@@ -2561,9 +2561,9 @@ void OsuDatabase::onScoresRename(UString args) {
         for(size_t i = 0; i < kv.second.size(); i++) {
             Score &score = kv.second[i];
 
-            if(!score.isLegacyScore && score.playerName == playerName) {
+            if(!score.isLegacyScore && UString(score.playerName.c_str()) == playerName) {
                 numRenamedScores++;
-                score.playerName = args;
+                score.playerName = args.toUtf8();
             }
         }
     }
@@ -2628,7 +2628,7 @@ void OsuDatabase::onScoresExport() {
                 out << score.unixTimestamp;
                 out << ",";
 
-                out << score.playerName.toUtf8();
+                out << score.playerName;
                 out << ",";
 
                 out << score.num300s;

+ 37 - 4
src/App/Osu/OsuHUD.cpp

@@ -1522,7 +1522,7 @@ std::vector<SCORE_ENTRY> OsuHUD::getCurrentScores() {
             SCORE_ENTRY scoreEntry;
             scoreEntry.entry_id = -(nb_slots + 1);
             scoreEntry.player_id = score.player_id;
-            scoreEntry.name = score.playerName;
+            scoreEntry.name = score.playerName.c_str();
             scoreEntry.combo = score.comboMax;
             scoreEntry.score = score.score;
             scoreEntry.accuracy =
@@ -2236,9 +2236,26 @@ void OsuHUD::drawTargetHeatmap(Graphics *g, float hitcircleDiameter) {
 void OsuHUD::drawScrubbingTimeline(Graphics *g, unsigned long beatmapTime, unsigned long beatmapLength,
                                    unsigned long beatmapLengthPlayable, unsigned long beatmapStartTimePlayable,
                                    float beatmapPercentFinishedPlayable, const std::vector<BREAK> &breaks) {
+    static Vector2 last_cursor_pos = engine->getMouse()->getPos();
+    static double last_cursor_movement = engine->getTime();
+    Vector2 new_cursor_pos = engine->getMouse()->getPos();
+    double new_cursor_movement = engine->getTime();
+    if(last_cursor_pos.x != new_cursor_pos.x || last_cursor_pos.y != new_cursor_pos.y) {
+        last_cursor_pos = new_cursor_pos;
+        last_cursor_movement = new_cursor_movement;
+    }
+
+    // Auto-hide scrubbing timeline when watching a replay
+    double galpha = 1.0f;
+    if(m_osu->getSelectedBeatmap()->m_bIsWatchingReplay) {
+        double time_since_last_move = new_cursor_movement - (last_cursor_movement + 1.0f);
+        galpha = fmax(0.f, fmin(1.0f - time_since_last_move, 1.0f));
+    }
+
     const float dpiScale = Osu::getUIScale(m_osu);
 
-    const Vector2 cursorPos = engine->getMouse()->getPos();
+    Vector2 cursorPos = engine->getMouse()->getPos();
+    cursorPos.y = m_osu->getScreenHeight() * 0.8;
 
     const Color grey = 0xffbbbbbb;
     const Color greyTransparent = 0xbbbbbbbb;
@@ -2303,8 +2320,7 @@ void OsuHUD::drawScrubbingTimeline(Graphics *g, unsigned long beatmapTime, unsig
                 const float offsetX =
                     ((float)startTimeMS / (float)strainStepMS) * strainWidth;  // compensate for startTimeMS
 
-                const float alpha = osu_hud_scrubbing_timeline_strains_alpha.getFloat();
-
+                const float alpha = osu_hud_scrubbing_timeline_strains_alpha.getFloat() * galpha;
                 const Color aimStrainColor =
                     COLORf(alpha, osu_hud_scrubbing_timeline_strains_aim_color_r.getInt() / 255.0f,
                            osu_hud_scrubbing_timeline_strains_aim_color_g.getInt() / 255.0f,
@@ -2361,6 +2377,7 @@ void OsuHUD::drawScrubbingTimeline(Graphics *g, unsigned long beatmapTime, unsig
 
     // breaks
     g->setColor(greyTransparent);
+    g->setAlpha(galpha);
     for(int i = 0; i < breaks.size(); i++) {
         const int width = std::max(
             (int)(m_osu->getScreenWidth() * clamp<float>(breaks[i].endPercent - breaks[i].startPercent, 0.0f, 1.0f)),
@@ -2370,8 +2387,10 @@ void OsuHUD::drawScrubbingTimeline(Graphics *g, unsigned long beatmapTime, unsig
 
     // line
     g->setColor(0xff000000);
+    g->setAlpha(galpha);
     g->drawLine(0, cursorPos.y + 1, m_osu->getScreenWidth(), cursorPos.y + 1);
     g->setColor(grey);
+    g->setAlpha(galpha);
     g->drawLine(0, cursorPos.y, m_osu->getScreenWidth(), cursorPos.y);
 
     // current time triangle
@@ -2380,9 +2399,11 @@ void OsuHUD::drawScrubbingTimeline(Graphics *g, unsigned long beatmapTime, unsig
     {
         g->translate(triangleTip.x + 1, triangleTip.y - m_osu->getSkin()->getSeekTriangle()->getHeight() / 2.0f + 1);
         g->setColor(0xff000000);
+        g->setAlpha(galpha);
         g->drawImage(m_osu->getSkin()->getSeekTriangle());
         g->translate(-1, -1);
         g->setColor(green);
+        g->setAlpha(galpha);
         g->drawImage(m_osu->getSkin()->getSeekTriangle());
     }
     g->popTransform();
@@ -2398,9 +2419,11 @@ void OsuHUD::drawScrubbingTimeline(Graphics *g, unsigned long beatmapTime, unsig
                 1,
             triangleTip.y - m_osu->getSkin()->getSeekTriangle()->getHeight() - currentTimeTopTextOffset + 1);
         g->setColor(0xff000000);
+        g->setAlpha(galpha);
         g->drawString(timeFont, currentTimeText);
         g->translate(-1, -1);
         g->setColor(green);
+        g->setAlpha(galpha);
         g->drawString(timeFont, currentTimeText);
     }
     g->popTransform();
@@ -2412,9 +2435,11 @@ void OsuHUD::drawScrubbingTimeline(Graphics *g, unsigned long beatmapTime, unsig
         g->translate((int)(startAndEndTimeTextOffset + 1),
                      (int)(triangleTip.y + startAndEndTimeTextOffset + timeFont->getHeight() + 1));
         g->setColor(0xff000000);
+        g->setAlpha(galpha);
         g->drawString(timeFont, startTimeText);
         g->translate(-1, -1);
         g->setColor(greyDark);
+        g->setAlpha(galpha);
         g->drawString(timeFont, startTimeText);
     }
     g->popTransform();
@@ -2427,9 +2452,11 @@ void OsuHUD::drawScrubbingTimeline(Graphics *g, unsigned long beatmapTime, unsig
             (int)(m_osu->getScreenWidth() - timeFont->getStringWidth(endTimeText) - startAndEndTimeTextOffset + 1),
             (int)(triangleTip.y + startAndEndTimeTextOffset + timeFont->getHeight() + 1));
         g->setColor(0xff000000);
+        g->setAlpha(galpha);
         g->drawString(timeFont, endTimeText);
         g->translate(-1, -1);
         g->setColor(greyDark);
+        g->setAlpha(galpha);
         g->drawString(timeFont, endTimeText);
     }
     g->popTransform();
@@ -2445,9 +2472,11 @@ void OsuHUD::drawScrubbingTimeline(Graphics *g, unsigned long beatmapTime, unsig
             g->translate(triangleTip.x + 1,
                          triangleTip.y + m_osu->getSkin()->getSeekTriangle()->getHeight() / 2.0f + 1);
             g->setColor(0xff000000);
+            g->setAlpha(galpha);
             g->drawImage(m_osu->getSkin()->getSeekTriangle());
             g->translate(-1, -1);
             g->setColor(grey);
+            g->setAlpha(galpha);
             g->drawImage(m_osu->getSkin()->getSeekTriangle());
         }
         g->popTransform();
@@ -2467,9 +2496,11 @@ void OsuHUD::drawScrubbingTimeline(Graphics *g, unsigned long beatmapTime, unsig
                       currentTimeTopTextOffset * std::max(1.0f, getCursorScaleFactor() * osu_cursor_scale.getFloat()) *
                           osu_hud_scrubbing_timeline_hover_tooltip_offset_multiplier.getFloat()));
             g->setColor(0xff000000);
+            g->setAlpha(galpha);
             g->drawString(timeFont, endTimeText);
             g->translate(-1, -1);
             g->setColor(grey);
+            g->setAlpha(galpha);
             g->drawString(timeFont, endTimeText);
         }
         g->popTransform();
@@ -2492,9 +2523,11 @@ void OsuHUD::drawScrubbingTimeline(Graphics *g, unsigned long beatmapTime, unsig
                       osu_hud_scrubbing_timeline_hover_tooltip_offset_multiplier.getFloat() * 2.0f -
                   1));
         g->setColor(0xff000000);
+        g->setAlpha(galpha);
         g->drawString(timeFont, hoverTimeText);
         g->translate(-1, -1);
         g->setColor(0xff666666);
+        g->setAlpha(galpha);
         g->drawString(timeFont, hoverTimeText);
     }
     g->popTransform();

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

@@ -80,7 +80,7 @@ class OsuHitObject {
     inline bool isBlocked() const { return m_bBlocked; }
     inline bool hasMisAimed() const { return m_bMisAim; }
 
-    virtual void onClickEvent(std::vector<long> &clicks) { ; }
+    virtual void onClickEvent(std::vector<Click> &clicks) { ; }
     virtual void onReset(long curPos);
 
    protected:

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

@@ -505,7 +505,7 @@ void OsuRankingScreen::setScore(Score score, UString dateTime) {
     m_bIsUnranked = false;
 
     m_songInfo->setDate(dateTime);
-    m_songInfo->setPlayer(score.playerName);
+    m_songInfo->setPlayer(UString(score.playerName.c_str()));
 
     m_rankingPanel->setScore(score);
     setGrade(OsuScore::calculateGrade(score.num300s, score.num100s, score.num50s, score.numMisses,

+ 4 - 2
src/App/Osu/OsuReplay.cpp

@@ -242,7 +242,9 @@ void OsuReplay::load_and_watch(Score score) {
                 bancho.osu->m_notificationOverlay->addNotification(msg);
             }
 
-            Score* score_cpy = (Score*)malloc(sizeof(Score));
+            // Need to allocate with calloc since APIRequests free() the .extra
+            void* mem = calloc(1, sizeof(Score));
+            Score* score_cpy = new(mem) Score;
             *score_cpy = score;
 
             APIRequest request;
@@ -266,7 +268,7 @@ void OsuReplay::load_and_watch(Score score) {
 
     bancho.osu->replay_info.diff2_md5 = score.md5hash.hash;
     bancho.osu->replay_info.mod_flags = score.modsLegacy;
-    bancho.osu->replay_info.username = score.playerName;
+    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);

+ 3 - 4
src/App/Osu/OsuSlider.cpp

@@ -1098,19 +1098,18 @@ float OsuSlider::getT(long pos, bool raw) {
     }
 }
 
-void OsuSlider::onClickEvent(std::vector<long> &clicks) {
+void OsuSlider::onClickEvent(std::vector<Click> &clicks) {
     if(m_points.size() == 0 || m_bBlocked)
         return;  // also handle note blocking here (doesn't need fancy shake logic, since sliders don't shake in
                  // osu!stable)
 
     if(!m_bStartFinished) {
-        const Vector2 cursorPos = m_beatmap->getCursorPos();
-
+        const Vector2 cursorPos = clicks[0].pos;
         const Vector2 pos = m_beatmap->osuCoords2Pixels(m_curve->pointAt(0.0f));
         const float cursorDelta = (cursorPos - pos).length();
 
         if(cursorDelta < m_beatmap->getHitcircleDiameter() / 2.0f) {
-            const long delta = clicks[0] - (long)m_iTime;
+            const long delta = clicks[0].tms - (long)m_iTime;
 
             OsuScore::HIT result = OsuGameRules::getHitResult(delta, m_beatmap);
             if(result != OsuScore::HIT::HIT_NULL) {

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

@@ -48,7 +48,7 @@ class OsuSlider : public OsuHitObject {
     Vector2 getOriginalRawPosAt(long pos);
     inline Vector2 getAutoCursorPos(long curPos) { return m_vCurPoint; }
 
-    virtual void onClickEvent(std::vector<long> &clicks);
+    virtual void onClickEvent(std::vector<Click> &clicks);
     virtual void onReset(long curPos);
 
     void rebuildVertexBuffer(bool useRawCoords = false);

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

@@ -306,7 +306,7 @@ void OsuSpinner::update(long curPos) {
             angleDiff = engine->getFrameTime() * 1000.0f * AUTO_MULTIPLIER * m_beatmap->getOsu()->getSpeedMultiplier();
         else  // user spin
         {
-            Vector2 mouseDelta = engine->getMouse()->getPos() - m_beatmap->osuCoords2Pixels(m_vRawPos);
+            Vector2 mouseDelta = m_beatmap->getCursorPos() - m_beatmap->osuCoords2Pixels(m_vRawPos);
             const float currentMouseAngle = (float)std::atan2(mouseDelta.y, mouseDelta.x);
             angleDiff = (currentMouseAngle - m_fLastMouseAngle);
 
@@ -374,7 +374,7 @@ void OsuSpinner::update(long curPos) {
     }
 }
 
-void OsuSpinner::onClickEvent(std::vector<long> &clicks) {
+void OsuSpinner::onClickEvent(std::vector<Click> &clicks) {
     if(m_bFinished) return;
 
     // needed for nightmare mod

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

@@ -27,7 +27,7 @@ class OsuSpinner : public OsuHitObject {
     Vector2 getOriginalRawPosAt(long pos) { return m_vOriginalRawPos; }
     Vector2 getAutoCursorPos(long curPos);
 
-    virtual void onClickEvent(std::vector<long> &clicks);
+    virtual void onClickEvent(std::vector<Click> &clicks);
     virtual void onReset(long curPos);
 
    private:

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

@@ -728,7 +728,7 @@ void OsuUISongBrowserScoreButton::setScore(const Score &score, const OsuDatabase
     // display
     m_scoreGrade =
         OsuScore::calculateGrade(score.num300s, score.num100s, score.num50s, score.numMisses, modHidden, modFlashlight);
-    m_sScoreUsername = score.playerName;
+    m_sScoreUsername = UString(score.playerName.c_str());
     m_sScoreScore = UString::format(
         (score.perfect ? "Score: %llu (%ix PFC)" : (fullCombo ? "Score: %llu (%ix FC)" : "Score: %llu (%ix)")),
         score.score, score.comboMax);

+ 10 - 6
src/App/Osu/OsuUserStatsScreen.cpp

@@ -83,12 +83,13 @@ class OsuUserStatsScreenBackgroundPPRecalculator : public Resource {
         std::unordered_map<MD5Hash, std::vector<Score>> *scores = m_osu->getSongBrowser()->getDatabase()->getScores();
 
         // count number of scores to recalculate for UI
+        std::string sUsername(m_sUserName.toUtf8());
         size_t numScoresToRecalculate = 0;
         for(const auto &kv : *scores) {
             for(size_t i = 0; i < kv.second.size(); i++) {
                 const Score &score = kv.second[i];
 
-                if((!score.isLegacyScore || m_bImportLegacyScores) && score.playerName == m_sUserName)
+                if((!score.isLegacyScore || m_bImportLegacyScores) && score.playerName == sUsername)
                     numScoresToRecalculate++;
             }
         }
@@ -101,7 +102,7 @@ class OsuUserStatsScreenBackgroundPPRecalculator : public Resource {
             for(size_t i = 0; i < kv.second.size(); i++) {
                 Score &score = kv.second[i];
 
-                if((!score.isLegacyScore || m_bImportLegacyScores) && score.playerName == m_sUserName) {
+                if((!score.isLegacyScore || m_bImportLegacyScores) && score.playerName == sUsername) {
                     if(m_bInterrupted.load()) break;
                     if(score.md5hash.hash[0] == 0) continue;
 
@@ -654,8 +655,10 @@ void OsuUserStatsScreen::onCopyAllScoresConfirmed(UString text, int id) {
     if(m_sCopyAllScoresFromUser.length() < 1) return;
 
     const UString &playerNameToCopyInto = m_name_ref->getString();
+    std::string splayerNameToCopyInto(playerNameToCopyInto.toUtf8());
 
     if(playerNameToCopyInto.length() < 1 || m_sCopyAllScoresFromUser == playerNameToCopyInto) return;
+    std::string sCopyAllScoresFromUser(m_sCopyAllScoresFromUser.toUtf8());
 
     debugLog("Copying all scores from \"%s\" into \"%s\"\n", m_sCopyAllScoresFromUser.toUtf8(),
              playerNameToCopyInto.toUtf8());
@@ -672,7 +675,7 @@ void OsuUserStatsScreen::onCopyAllScoresConfirmed(UString text, int id) {
 
             // NOTE: only ever copy McOsu scores
             if(!existingScore.isLegacyScore) {
-                if(existingScore.playerName == m_sCopyAllScoresFromUser) {
+                if(existingScore.playerName == sCopyAllScoresFromUser) {
                     // check if this user already has this exact same score (copied previously) and don't copy if that
                     // is the case
                     bool alreadyCopied = false;
@@ -682,7 +685,7 @@ void OsuUserStatsScreen::onCopyAllScoresConfirmed(UString text, int id) {
                         if(j == i) continue;
 
                         if(!alreadyCopiedScore.isLegacyScore) {
-                            if(alreadyCopiedScore.playerName == playerNameToCopyInto) {
+                            if(alreadyCopiedScore.playerName == splayerNameToCopyInto) {
                                 if(existingScore.isScoreEqualToCopiedScoreIgnoringPlayerName(alreadyCopiedScore)) {
                                     alreadyCopied = true;
                                     break;
@@ -701,7 +704,7 @@ void OsuUserStatsScreen::onCopyAllScoresConfirmed(UString text, int id) {
             if(Osu::debug->getBool()) debugLog("Copying %i for %s\n", (int)tempScoresToCopy.size(), kv.first.toUtf8());
 
             for(size_t i = 0; i < tempScoresToCopy.size(); i++) {
-                tempScoresToCopy[i].playerName = playerNameToCopyInto;  // take ownership of this copied score
+                tempScoresToCopy[i].playerName = splayerNameToCopyInto;  // take ownership of this copied score
                 tempScoresToCopy[i].sortHack =
                     m_osu->getSongBrowser()->getDatabase()->getAndIncrementScoreSortHackCounter();
 
@@ -745,6 +748,7 @@ void OsuUserStatsScreen::onDeleteAllScoresConfirmed(UString text, int id) {
     if(id != 1) return;
 
     const UString &playerName = m_name_ref->getString();
+    std::string splayerName(playerName.toUtf8());
 
     debugLog("Deleting all scores for \"%s\"\n", playerName.toUtf8());
 
@@ -757,7 +761,7 @@ void OsuUserStatsScreen::onDeleteAllScoresConfirmed(UString text, int id) {
 
             // NOTE: only ever delete McOsu scores
             if(!score.isLegacyScore) {
-                if(score.playerName == playerName) {
+                if(score.playerName == splayerName) {
                     kv.second.erase(kv.second.begin() + i);
                     i--;
                 }

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

@@ -26,7 +26,7 @@ struct Score {
     uint64_t unixTimestamp;
 
     uint32_t player_id = 0;
-    UString playerName;
+    std::string playerName;
     bool passed = false;
     bool ragequit = false;
     Grade grade = Grade::N;