Prechádzať zdrojové kódy

Support user links and beatmapset links

kiwec 2 mesiacov pred
rodič
commit
4dbf25bd0c

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

@@ -30,6 +30,8 @@ Changelog::Changelog() : ScreenBackable() {
     latest.title =
         UString::format("%.2f (%s, %s)", convar->getConVarByName("osu_version")->getFloat(), __DATE__, __TIME__);
     latest.changes.push_back("- Added setting to prevent servers from replacing the main menu logo");
+    latest.changes.push_back("- Chat: added support for user links");
+    latest.changes.push_back("- Chat: improved map link support");
     changelogs.push_back(latest);
 
     CHANGELOG v35_06;

+ 58 - 35
src/App/Osu/ChatLink.cpp

@@ -11,6 +11,7 @@
 #include "RoomScreen.h"
 #include "SongBrowser/SongBrowser.h"
 #include "TooltipOverlay.h"
+#include "UIUserContextMenu.h"
 
 ChatLink::ChatLink(float xPos, float yPos, float xSize, float ySize, UString link, UString label)
     : CBaseUILabel(xPos, yPos, xSize, ySize, link, label) {
@@ -34,7 +35,34 @@ void ChatLink::mouse_update(bool *propagate_clicks) {
     }
 }
 
+void ChatLink::open_beatmap_link(i32 map_id, i32 set_id) {
+    if(osu->getSongBrowser()->isVisible()) {
+        osu->getSongBrowser()->map_autodl = map_id;
+        osu->getSongBrowser()->set_autodl = set_id;
+    } else if(osu->getMainMenu()->isVisible()) {
+        osu->toggleSongBrowser();
+        osu->getSongBrowser()->map_autodl = map_id;
+        osu->getSongBrowser()->set_autodl = set_id;
+    } else {
+        env->openURLInDefaultBrowser(m_link);
+    }
+}
+
 void ChatLink::onMouseUpInside() {
+    std::string link_str = m_link.toUtf8();
+    std::smatch match;
+
+    // This lazy escaping is only good for endpoint URLs, not anything more serious
+    UString escaped_endpoint;
+    for(int i = 0; i < bancho.endpoint.length(); i++) {
+        if(bancho.endpoint[i] == L'.') {
+            escaped_endpoint.append("\\.");
+        } else {
+            escaped_endpoint.append(bancho.endpoint[i]);
+        }
+    }
+
+    // Detect multiplayer invite links
     if(m_link.startsWith("osump://")) {
         if(osu->m_room->isVisible()) {
             osu->getNotificationOverlay()->addNotification("You are already in a multiplayer room.");
@@ -43,53 +71,48 @@ void ChatLink::onMouseUpInside() {
 
         // If the password has a space in it, parsing will break, but there's no way around it...
         // osu!stable also considers anything after a space to be part of the lobby title :(
-        std::regex password_regex("osump://(\\d+)/(\\S*)");
-        std::string invite_str = m_link.toUtf8();
-        std::smatch match;
-        std::regex_search(invite_str, match, password_regex);
+        std::regex_search(link_str, match, std::regex("osump://(\\d+)/(\\S*)"));
         u32 invite_id = strtoul(match.str(1).c_str(), NULL, 10);
         UString password = match.str(2).c_str();
         osu->m_lobby->joinRoom(invite_id, password);
         return;
     }
 
-    // This lazy escaping is only good for endpoint URLs, not anything more serious
-    UString escaped_endpoint;
-    for(int i = 0; i < bancho.endpoint.length(); i++) {
-        if(bancho.endpoint[i] == L'.') {
-            escaped_endpoint.append("\\.");
-        } else {
-            escaped_endpoint.append(bancho.endpoint[i]);
-        }
+    // Detect user links
+    // https:\/\/(osu\.)?akatsuki\.gg\/u(sers)?\/(\d+)
+    UString user_pattern = "https://(osu\\.)?";
+    user_pattern.append(escaped_endpoint);
+    user_pattern.append("/u(sers)?/(\\d+)");
+    if(std::regex_search(link_str, match, std::regex(user_pattern.toUtf8()))) {
+        i32 user_id = std::stoi(match.str(3));
+        osu->m_user_actions->open(user_id);
+        return;
     }
 
-    // https:\/\/(akatsuki.gg|osu.ppy.sh)\/b(eatmapsets\/\d+#(osu)?)?\/(\d+)
-    UString map_pattern = "https://(osu\\.";
+    // Detect beatmap links
+    // https:\/\/((osu\.)?akatsuki\.gg|osu\.ppy\.sh)\/b(eatmaps)?\/(\d+)
+    UString map_pattern = "https://((osu\\.)?";
     map_pattern.append(escaped_endpoint);
-    map_pattern.append("|osu.ppy.sh)/b(eatmapsets/(\\d+)#(osu)?)?/(\\d+)");
-    std::wregex map_regex(map_pattern.wc_str());
-
-    std::wstring link_str = m_link.wc_str();
-    std::wsmatch match;
-    if(std::regex_search(link_str, match, map_regex)) {
-        i32 map_id = std::stoi(std::wstring_convert<std::codecvt_utf8<wchar_t>>().to_bytes(match.str(5)));
-        i32 set_id = 0;
-        if(match[3].matched) {
-            // Set ID doesn't match if the URL only contains the map ID
-            set_id = std::stoi(std::wstring_convert<std::codecvt_utf8<wchar_t>>().to_bytes(match.str(3)));
-        }
+    map_pattern.append("|osu\\.ppy\\.sh)/b(eatmaps)?/(\\d+)");
+    if(std::regex_search(link_str, match, std::regex(map_pattern.toUtf8()))) {
+        i32 map_id = std::stoi(match.str(4));
+        open_beatmap_link(map_id, 0);
+        return;
+    }
 
-        if(osu->getSongBrowser()->isVisible()) {
-            osu->getSongBrowser()->map_autodl = map_id;
-            osu->getSongBrowser()->set_autodl = set_id;
-        } else if(osu->getMainMenu()->isVisible()) {
-            osu->toggleSongBrowser();
-            osu->getSongBrowser()->map_autodl = map_id;
-            osu->getSongBrowser()->set_autodl = set_id;
-        } else {
-            env->openURLInDefaultBrowser(m_link);
+    // Detect beatmapset links
+    // https:\/\/((osu\.)?akatsuki\.gg|osu\.ppy\.sh)\/beatmapsets\/(\d+)(#osu\/(\d+))?
+    UString set_pattern = "https://((osu\\.)?";
+    set_pattern.append(escaped_endpoint);
+    set_pattern.append("|osu\\.ppy\\.sh)/beatmapsets/(\\d+)(#osu/(\\d+))?");
+    if(std::regex_search(link_str, match, std::regex(set_pattern.toUtf8()))) {
+        i32 set_id = std::stoi(match.str(3));
+        i32 map_id = 0;
+        if(match[5].matched) {
+            map_id = std::stoi(match.str(5));
         }
 
+        open_beatmap_link(map_id, set_id);
         return;
     }
 

+ 2 - 0
src/App/Osu/ChatLink.h

@@ -10,5 +10,7 @@ class ChatLink : public CBaseUILabel {
     virtual void mouse_update(bool *propagate_clicks);
     virtual void onMouseUpInside();
 
+    void open_beatmap_link(i32 map_id, i32 set_id);
+
     UString m_link;
 };

+ 13 - 0
src/App/Osu/Database.cpp

@@ -801,6 +801,19 @@ DatabaseBeatmap *Database::getBeatmapDifficulty(i32 map_id) {
     return NULL;
 }
 
+DatabaseBeatmap *Database::getBeatmapSet(i32 set_id) {
+    if(!isFinished()) return NULL;
+
+    for(size_t i = 0; i < m_databaseBeatmaps.size(); i++) {
+        DatabaseBeatmap *beatmap = m_databaseBeatmaps[i];
+        if(beatmap->getSetID() == set_id) {
+            return beatmap;
+        }
+    }
+
+    return NULL;
+}
+
 std::string Database::parseLegacyCfgBeatmapDirectoryParameter() {
     // get BeatmapDirectory parameter from osu!.<OS_USERNAME>.cfg
     debugLog("Database::parseLegacyCfgBeatmapDirectoryParameter() : username = %s\n", env->getUsername().toUtf8());

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

@@ -89,6 +89,7 @@ class Database {
     inline const std::vector<DatabaseBeatmap *> getDatabaseBeatmaps() const { return m_databaseBeatmaps; }
     DatabaseBeatmap *getBeatmapDifficulty(const MD5Hash &md5hash);
     DatabaseBeatmap *getBeatmapDifficulty(i32 map_id);
+    DatabaseBeatmap *getBeatmapSet(i32 set_id);
 
     inline std::unordered_map<MD5Hash, std::vector<FinishedScore>> *getScores() { return &m_scores; }
     inline const std::vector<SCORE_SORTING_METHOD> &getScoreSortingMethods() const { return m_scoreSortingMethods; }

+ 46 - 0
src/App/Osu/SongBrowser/SongBrowser.cpp

@@ -943,6 +943,52 @@ void SongBrowser::mouse_update(bool *propagate_clicks) {
             osu->getNotificationOverlay()->addNotification(text);
         } else if(beatmap != NULL) {
             osu->m_songBrowser2->onDifficultySelected(beatmap, false);
+            map_autodl = 0;
+            set_autodl = 0;
+        }
+    } else if(set_autodl) {
+        auto beatmapset = getDatabase()->getBeatmapSet(set_autodl);
+        if(beatmapset == NULL) {
+            float progress = -1.f;
+            download_beatmapset(set_autodl, &progress);
+            if(progress == -1.f) {
+                auto error_str = UString::format("Failed to download Beatmapset #%d :(", set_autodl);
+                osu->getNotificationOverlay()->addNotification(error_str);
+                map_autodl = 0;
+                set_autodl = 0;
+            } else if(progress < 1.f) {
+                // TODO @kiwec: this notification format is jank & laggy
+                auto text = UString::format("Downloading... %.2f%%", progress * 100.f);
+                osu->getNotificationOverlay()->addNotification(text);
+            } else {
+                // Pasted from Downloader::download_beatmap
+                auto mapset_path = UString::format(MCENGINE_DATA_DIR "maps/%d/", set_autodl);
+                // XXX: Make a permanent database for auto-downloaded songs, so we can load them like osu!.db's
+                osu->m_songBrowser2->getDatabase()->addBeatmap(mapset_path.toUtf8());
+                osu->m_songBrowser2->updateSongButtonSorting();
+                debugLog("Finished loading beatmapset %d.\n", set_autodl);
+
+                beatmapset = getDatabase()->getBeatmapSet(set_autodl);
+            }
+        }
+
+        if(beatmapset != NULL) {
+            // Just picking the hardest diff for now
+            DatabaseBeatmap *best_diff = NULL;
+            const std::vector<DatabaseBeatmap *> &diffs = beatmapset->getDifficulties();
+            for(size_t d = 0; d < diffs.size(); d++) {
+                DatabaseBeatmap *diff = diffs[d];
+                if(!best_diff || diff->getStarsNomod() > best_diff->getStarsNomod()) {
+                    best_diff = diff;
+                }
+            }
+
+            if(best_diff == NULL) {
+                osu->getNotificationOverlay()->addNotification("Beatmapset has no difficulties :/");
+            } else {
+                osu->m_songBrowser2->onDifficultySelected(best_diff, false);
+            }
+
             map_autodl = 0;
             set_autodl = 0;
         }