1
0

3 Коммиты 3483c45088 ... 7d68bb5ab7

Автор SHA1 Сообщение Дата
  kiwec 7d68bb5ab7 Nicer UI sound fallback 4 месяцев назад
  kiwec 895f4e57ba Add missing UI sounds 4 месяцев назад
  kiwec 00d68b74cc Handle lobby invite links 4 месяцев назад
38 измененных файлов с 675 добавлено и 306 удалено
  1. 1 1
      Makefile
  2. 10 0
      src/App/Osu/BanchoProtocol.h
  3. 2 0
      src/App/Osu/Beatmap.cpp
  4. 3 1
      src/App/Osu/Changelog.cpp
  5. 45 17
      src/App/Osu/Chat.cpp
  6. 20 1
      src/App/Osu/ChatLink.cpp
  7. 2 5
      src/App/Osu/Database.cpp
  8. 6 1
      src/App/Osu/Lobby.cpp
  9. 38 4
      src/App/Osu/MainMenu.cpp
  10. 1 1
      src/App/Osu/OptionsMenu.cpp
  11. 16 12
      src/App/Osu/PauseMenu.cpp
  12. 1 1
      src/App/Osu/PauseMenu.h
  13. 5 4
      src/App/Osu/RankingScreen.cpp
  14. 40 4
      src/App/Osu/RoomScreen.cpp
  15. 1 1
      src/App/Osu/RoomScreen.h
  16. 0 7
      src/App/Osu/ScreenBackable.cpp
  17. 106 24
      src/App/Osu/Skin.cpp
  18. 76 5
      src/App/Osu/Skin.h
  19. 2 2
      src/App/Osu/SongBrowser/Button.cpp
  20. 15 7
      src/App/Osu/SongBrowser/SongBrowser.cpp
  21. 10 0
      src/App/Osu/SongBrowser/SongDifficultyButton.cpp
  22. 1 0
      src/App/Osu/SongBrowser/SongDifficultyButton.h
  23. 5 1
      src/App/Osu/SpectatorScreen.cpp
  24. 8 0
      src/App/Osu/UIBackButton.cpp
  25. 1 0
      src/App/Osu/UIBackButton.h
  26. 13 1
      src/App/Osu/UIButton.cpp
  27. 15 0
      src/App/Osu/UIContextMenu.cpp
  28. 3 0
      src/App/Osu/UIContextMenu.h
  29. 9 1
      src/App/Osu/UIPauseMenuButton.cpp
  30. 18 0
      src/Engine/Platform/LinuxEnvironment.cpp
  31. 2 10
      src/Engine/Platform/LinuxEnvironment.h
  32. 154 144
      src/Engine/SoundEngine.cpp
  33. 0 7
      src/GUI/CBaseUIButton.cpp
  34. 1 12
      src/GUI/CBaseUIButton.h
  35. 24 9
      src/GUI/CBaseUISlider.cpp
  36. 2 11
      src/GUI/CBaseUISlider.h
  37. 18 0
      src/GUI/CBaseUITextbox.cpp
  38. 1 12
      src/GUI/CBaseUITextbox.h

+ 1 - 1
Makefile

@@ -5,7 +5,7 @@ HEADERS = $(shell find src -type f -name '*.h')
 
 LIBS = blkid freetype2 glew libenet libjpeg liblzma xi zlib
 
-CXXFLAGS = -std=c++17 -fmessage-length=0 -Wno-sign-compare -Wno-unused-local-typedefs -Wno-reorder -Wno-switch -Wall
+CXXFLAGS = -std=c++17 -fmessage-length=0 -fno-exceptions -Wno-sign-compare -Wno-unused-local-typedefs -Wno-reorder -Wno-switch -Wall
 CXXFLAGS += `pkgconf --static --cflags $(LIBS)` `curl-config --cflags`
 CXXFLAGS += -Isrc/App -Isrc/App/Osu -Isrc/Engine -Isrc/GUI -Isrc/GUI/Windows -Isrc/GUI/Windows/VinylScratcher -Isrc/Engine/Input -Isrc/Engine/Platform -Isrc/Engine/Main -Isrc/Engine/Renderer -Isrc/Util
 CXXFLAGS += -Ilibraries/bass/include -Ilibraries/bassasio/include -Ilibraries/bassfx/include -Ilibraries/bassmix/include -Ilibraries/basswasapi/include -Ilibraries/bassloud/include

+ 10 - 0
src/App/Osu/BanchoProtocol.h

@@ -236,6 +236,16 @@ class Room {
     u8 nb_open_slots = 0;
     Slot slots[16];
 
+    bool nb_ready() {
+        u8 nb = 0;
+        for(int i = 0; i < 16; i++) {
+            if(slots[i].has_player() && slots[i].is_ready()) {
+                nb++;
+            }
+        }
+        return nb;
+    }
+
     bool all_players_ready() {
         for(int i = 0; i < 16; i++) {
             if(slots[i].has_player() && !slots[i].is_ready()) {

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

@@ -988,6 +988,8 @@ bool Beatmap::start() {
     osu->updateConfineCursor();
     osu->updateWindowsKeyDisable();
 
+    engine->getSound()->play(osu->getSkin()->m_expand);
+
     // NOTE: loading failures are handled dynamically in update(), so temporarily assume everything has worked in here
     return true;
 }

+ 3 - 1
src/App/Osu/Changelog.cpp

@@ -29,9 +29,11 @@ Changelog::Changelog() : ScreenBackable() {
     CHANGELOG latest;
     latest.title =
         UString::format("%.2f (%s, %s)", convar->getConVarByName("osu_version")->getFloat(), __DATE__, __TIME__);
+    latest.changes.push_back("- Added missing UI sounds");
     latest.changes.push_back("- Chat: added support for /me command");
     latest.changes.push_back("- Chat: added support for links");
     latest.changes.push_back("- Chat: added support for map links (auto-downloads)");
+    latest.changes.push_back("- Chat: added support for multiplayer invite links");
     changelogs.push_back(latest);
 
     CHANGELOG v35_05;
@@ -344,7 +346,7 @@ void Changelog::updateLayout() {
 }
 
 void Changelog::onBack() {
-    engine->getSound()->play(osu->getSkin()->getMenuClick());
+    engine->getSound()->play(osu->getSkin()->m_menuBack);
 
     osu->toggleChangelog();
 }

+ 45 - 17
src/App/Osu/Chat.cpp

@@ -53,7 +53,10 @@ ChatChannel::~ChatChannel() {
     m_chat->m_button_container->deleteBaseUIElement(btn);
 }
 
-void ChatChannel::onChannelButtonClick(CBaseUIButton *btn) { m_chat->switchToChannel(this); }
+void ChatChannel::onChannelButtonClick(CBaseUIButton *btn) {
+    engine->getSound()->play(osu->getSkin()->m_clickButton);
+    m_chat->switchToChannel(this);
+}
 
 void ChatChannel::add_message(ChatMessage msg) {
     const float line_height = 20;
@@ -94,23 +97,25 @@ void ChatChannel::add_message(ChatMessage msg) {
         }
     }
 
-    // regex101 format: (\[\[(.+?)\]\])|(\[(https?:\/\/\S+) (.+?)\])|(https?:\/\/\S+)
-    // example: link1 https://example.com link2 [https://regex101.com label] link3 [[FAQ]]
-    //
-    // Sadly, perl-style (?|(a)|(b)) capture groups are not supported in C++.
-    // So instead of having a nice regex that handles all cases, we have 6 capture groups.
+    // regex101 format: (\[\[(.+?)\]\])|(\[((\S+):\/\/\S+) (.+?)\])|(https?:\/\/\S+)
+    // This matches:
+    // - Raw URLs      https://example.com
+    // - Labeled URLs  [https://regex101.com useful website]
+    // - Lobby invites [osump://0/ join my lobby plz]
+    // - Wiki links    [[Chat Console]]
     //
     // Group 1) [[FAQ]]
     // Group 2) FAQ
     // Group 3) [https://regex101.com label]
     // Group 4) https://regex101.com
-    // Group 5) label
-    // Group 6) https://example.com
+    // Group 5) https
+    // Group 6) label
+    // Group 7) https://example.com
     //
     // Groups 1, 2 only exist for wiki links
-    // Groups 3, 4, 5 only exist for labeled links
-    // Group 6 only exists for raw links
-    std::wregex url_regex(L"(\\[\\[(.+?)\\]\\])|(\\[(https?://\\S+) (.+?)\\])|(https?://\\S+)");
+    // Groups 3, 4, 5, 6 only exist for labeled links
+    // Group 7 only exists for raw links
+    std::wregex url_regex(L"(\\[\\[(.+?)\\]\\])|(\\[((\\S+)://\\S+) (.+?)\\])|(https?://\\S+)");
 
     std::wstring msg_text = msg.text.wc_str();
     std::wsmatch match;
@@ -122,18 +127,28 @@ void ChatChannel::add_message(ChatMessage msg) {
         int match_len;
         UString link_url;
         UString link_label;
-        if(match[6].matched) {
+        if(match[7].matched) {
             // Raw link
-            match_pos = match.position(6);
-            match_len = match.length(6);
-            link_url = match.str(6).c_str();
-            link_label = match.str(6).c_str();
+            match_pos = match.position(7);
+            match_len = match.length(7);
+            link_url = match.str(7).c_str();
+            link_label = match.str(7).c_str();
         } else if(match[3].matched) {
             // Labeled link
             match_pos = match.position(3);
             match_len = match.length(3);
             link_url = match.str(4).c_str();
-            link_label = match.str(5).c_str();
+            link_label = match.str(6).c_str();
+
+            // Normalize invite links to osump://
+            UString link_protocol = match.str(5).c_str();
+            if(link_protocol == UString("osu")) {
+                // osu:// -> osump://
+                link_url.insert(2, "mp");
+            } else if(link_protocol == UString("http://osump")) {
+                // http://osump:// -> osump://
+                link_url.erase(0, 7);
+            }
         } else {
             // Wiki link
             match_pos = match.position(1);
@@ -370,6 +385,8 @@ void Chat::onKeyDown(KeyboardEvent &key) {
                                                      .text = m_input_box->getText(),
                                                  });
 
+            engine->getSound()->play(osu->getSkin()->m_messageSent);
+
             m_input_box->clear();
         }
         return;
@@ -406,6 +423,9 @@ void Chat::onKeyDown(KeyboardEvent &key) {
             auto new_chan = m_channels[(chan_index + 1) % m_channels.size()];
             switchToChannel(new_chan);
         }
+
+        engine->getSound()->play(osu->getSkin()->m_clickButton);
+
         return;
     }
 
@@ -493,6 +513,10 @@ void Chat::addChannel(UString channel_name, bool switch_to) {
     }
 
     updateLayout(osu->getScreenSize());
+
+    if(isVisible()) {
+        engine->getSound()->play(osu->getSkin()->m_expand);
+    }
 }
 
 void Chat::addMessage(UString channel_name, ChatMessage msg, bool mark_unread) {
@@ -661,6 +685,8 @@ void Chat::leave(UString channel_name) {
     }
 
     removeChannel(channel_name);
+
+    engine->getSound()->play(osu->getSkin()->m_closeChatTab);
 }
 
 void Chat::onDisconnect() {
@@ -724,6 +750,8 @@ void Chat::updateVisibility() {
 CBaseUIContainer *Chat::setVisible(bool visible) {
     if(visible == m_bVisible) return this;
 
+    engine->getSound()->play(osu->getSkin()->m_clickButton);
+
     if(visible && bancho.user_id <= 0) {
         osu->m_optionsMenu->askForLoginDetails();
         return this;

+ 20 - 1
src/App/Osu/ChatLink.cpp

@@ -4,8 +4,11 @@
 #include <regex>
 
 #include "Bancho.h"
+#include "Lobby.h"
 #include "MainMenu.h"
+#include "NotificationOverlay.h"
 #include "Osu.h"
+#include "RoomScreen.h"
 #include "SongBrowser/SongBrowser.h"
 #include "TooltipOverlay.h"
 
@@ -32,7 +35,23 @@ void ChatLink::mouse_update(bool *propagate_clicks) {
 }
 
 void ChatLink::onMouseUpInside() {
-    // TODO: Handle lobby invite links, on click join the lobby (even if passworded)
+    if(m_link.startsWith("osump://")) {
+        if(osu->m_room->isVisible()) {
+            osu->getNotificationOverlay()->addNotification("You are already in a multiplayer room.");
+            return;
+        }
+
+        // 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);
+        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;

+ 2 - 5
src/App/Osu/Database.cpp

@@ -1241,11 +1241,8 @@ void Database::loadDB(Packet *db, bool &fallbackToRawLoad) {
             if(pathTokens.size() > 0 && pathTokens[0].length() > 0) {
                 const std::vector<UString> spaceTokens = pathTokens[0].split(" ");
                 if(spaceTokens.size() > 0 && spaceTokens[0].length() > 0) {
-                    try {
-                        beatmapSetID = spaceTokens[0].toInt();
-                    } catch(...) {
-                        beatmapSetID = -1;
-                    }
+                    beatmapSetID = spaceTokens[0].toInt();
+                    if(beatmapSetID == 0) beatmapSetID = -1;
                 }
             }
         }

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

@@ -15,6 +15,8 @@
 #include "PromptScreen.h"
 #include "ResourceManager.h"
 #include "RichPresence.h"
+#include "Skin.h"
+#include "SoundEngine.h"
 #include "UIButton.h"
 
 RoomUIElement::RoomUIElement(Lobby* multi, Room* room, float x, float y, float width, float height)
@@ -98,6 +100,7 @@ void Lobby::onKeyDown(KeyboardEvent& key) {
         key.consume();
         setVisible(false);
         osu->m_mainMenu->setVisible(true);
+        engine->getSound()->play(osu->getSkin()->m_menuBack);
         return;
     }
 
@@ -204,12 +207,14 @@ void Lobby::joinRoom(u32 id, UString password) {
     send_packet(packet);
 
     for(CBaseUIElement* elm : m_list->getContainer()->getElements()) {
-        auto room = (RoomUIElement*)elm;
+        RoomUIElement* room = dynamic_cast<RoomUIElement*>(elm);
+        if(room == NULL) continue;
         if(room->room_id != id) continue;
         room->join_btn->is_loading = true;
         break;
     }
 
+    debugLog("Joining room #%d with password '%s'\n", id, password.toUtf8());
     osu->getNotificationOverlay()->addNotification("Joining room...");
 }
 

+ 38 - 4
src/App/Osu/MainMenu.cpp

@@ -29,6 +29,8 @@
 #include "UpdateHandler.h"
 #include "VertexArrayObject.h"
 
+static float button_sound_cooldown = 0.f;
+
 using namespace std;
 
 UString MainMenu::NEOSU_MAIN_BUTTON_TEXT = UString("neosu");
@@ -54,6 +56,7 @@ class MainMenuButton : public CBaseUIButton {
     MainMenuButton(MainMenu *mainMenu, float xPos, float yPos, float xSize, float ySize, UString name, UString text);
 
     void onMouseDownInside();
+    void onMouseInside();
 
    private:
     MainMenu *m_mainMenu;
@@ -1302,7 +1305,7 @@ MainMenuButton *MainMenu::addMainMenuButton(UString text) {
 }
 
 void MainMenu::onCubePressed() {
-    engine->getSound()->play(osu->getSkin()->getMenuHit());
+    engine->getSound()->play(osu->getSkin()->m_clickMainMenuCube);
 
     anim->moveQuadInOut(&m_fSizeAddAnim, 0.0f, 0.06f, 0.0f, false);
     anim->moveQuadInOut(&m_fSizeAddAnim, 0.12f, 0.06f, 0.07f, false);
@@ -1351,6 +1354,9 @@ void MainMenu::onPlayButtonPressed() {
     m_bMainMenuAnimFriendScheduled = false;
 
     osu->toggleSongBrowser();
+
+    engine->getSound()->play(osu->getSkin()->m_menuHit);
+    engine->getSound()->play(osu->getSkin()->m_clickSingleplayer);
 }
 
 void MainMenu::onMultiplayerButtonPressed() {
@@ -1361,16 +1367,23 @@ void MainMenu::onMultiplayerButtonPressed() {
 
     setVisible(false);
     osu->m_lobby->setVisible(true);
+
+    engine->getSound()->play(osu->getSkin()->m_menuHit);
+    engine->getSound()->play(osu->getSkin()->m_clickMultiplayer);
 }
 
 void MainMenu::onOptionsButtonPressed() {
     if(!osu->getOptionsMenu()->isVisible()) osu->toggleOptionsMenu();
+
+    engine->getSound()->play(osu->getSkin()->m_clickOptions);
 }
 
 void MainMenu::onExitButtonPressed() {
     m_fShutdownScheduledTime = engine->getTime() + 0.3f;
     m_bWasCleanShutdown = true;
     setMenuElementsVisible(false);
+
+    engine->getSound()->play(osu->getSkin()->m_clickExit);
 }
 
 void MainMenu::onPausePressed() {
@@ -1405,13 +1418,17 @@ MainMenuCubeButton::MainMenuCubeButton(MainMenu *mainMenu, float xPos, float yPo
 
 void MainMenuCubeButton::draw(Graphics *g) {
     // draw nothing
-    /// CBaseUIButton::draw(g);
 }
 
 void MainMenuCubeButton::onMouseInside() {
     anim->moveQuadInOut(&m_mainMenu->m_fSizeAddAnim, 0.12f, 0.15f, 0.0f, true);
 
     CBaseUIButton::onMouseInside();
+
+    if(button_sound_cooldown + 0.05f < engine->getTime()) {
+        engine->getSound()->play(osu->getSkin()->m_hoverMainMenuCube);
+        button_sound_cooldown = engine->getTime();
+    }
 }
 
 void MainMenuCubeButton::onMouseOutside() {
@@ -1428,7 +1445,24 @@ MainMenuButton::MainMenuButton(MainMenu *mainMenu, float xPos, float yPos, float
 
 void MainMenuButton::onMouseDownInside() {
     if(m_mainMenu->m_cube->isMouseInside()) return;
-
-    engine->getSound()->play(osu->getSkin()->getMenuHit());
     CBaseUIButton::onMouseDownInside();
 }
+
+void MainMenuButton::onMouseInside() {
+    if(m_mainMenu->m_cube->isMouseInside()) return;
+    CBaseUIButton::onMouseInside();
+
+    if(button_sound_cooldown + 0.05f < engine->getTime()) {
+        if(getText() == UString("Singleplayer")) {
+            engine->getSound()->play(osu->getSkin()->m_hoverSingleplayer);
+        } else if(getText() == UString("Multiplayer")) {
+            engine->getSound()->play(osu->getSkin()->m_hoverMultiplayer);
+        } else if(getText() == UString("Options (CTRL + O)")) {
+            engine->getSound()->play(osu->getSkin()->m_hoverOptions);
+        } else if(getText() == UString("Exit")) {
+            engine->getSound()->play(osu->getSkin()->m_hoverExit);
+        }
+
+        button_sound_cooldown = engine->getTime();
+    }
+}

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

@@ -2215,7 +2215,7 @@ void OptionsMenu::updateLayout() {
 void OptionsMenu::onBack() {
     osu->getNotificationOverlay()->stopWaitingForKey();
 
-    engine->getSound()->play(osu->getSkin()->getMenuClick());
+    engine->getSound()->play(osu->getSkin()->m_menuBack);
     save();
 
     if(m_bFullscreen)

+ 16 - 12
src/App/Osu/PauseMenu.cpp

@@ -44,9 +44,10 @@ PauseMenu::PauseMenu() : OsuScreen() {
 
     setSize(osu->getScreenWidth(), osu->getScreenHeight());
 
-    UIPauseMenuButton *continueButton = addButton([]() -> Image * { return osu->getSkin()->getPauseContinue(); });
-    UIPauseMenuButton *retryButton = addButton([]() -> Image * { return osu->getSkin()->getPauseRetry(); });
-    UIPauseMenuButton *backButton = addButton([]() -> Image * { return osu->getSkin()->getPauseBack(); });
+    UIPauseMenuButton *continueButton =
+        addButton([]() -> Image * { return osu->getSkin()->getPauseContinue(); }, "Resume");
+    UIPauseMenuButton *retryButton = addButton([]() -> Image * { return osu->getSkin()->getPauseRetry(); }, "Retry");
+    UIPauseMenuButton *backButton = addButton([]() -> Image * { return osu->getSkin()->getPauseBack(); }, "Quit");
 
     continueButton->setClickCallback(fastdelegate::MakeDelegate(this, &PauseMenu::onContinueClicked));
     retryButton->setClickCallback(fastdelegate::MakeDelegate(this, &PauseMenu::onRetryClicked));
@@ -134,7 +135,7 @@ void PauseMenu::onContinueClicked() {
     if(!m_bContinueEnabled) return;
     if(anim->isAnimating(&m_fDimAnim)) return;
 
-    engine->getSound()->play(osu->getSkin()->getMenuHit());
+    engine->getSound()->play(osu->getSkin()->m_clickPauseContinue);
     osu->getSelectedBeatmap()->pause();
 
     scheduleVisibilityChange(false);
@@ -143,7 +144,7 @@ void PauseMenu::onContinueClicked() {
 void PauseMenu::onRetryClicked() {
     if(anim->isAnimating(&m_fDimAnim)) return;
 
-    engine->getSound()->play(osu->getSkin()->getMenuHit());
+    engine->getSound()->play(osu->getSkin()->m_clickPauseRetry);
     osu->getSelectedBeatmap()->restart();
 
     scheduleVisibilityChange(false);
@@ -152,7 +153,7 @@ void PauseMenu::onRetryClicked() {
 void PauseMenu::onBackClicked() {
     if(anim->isAnimating(&m_fDimAnim)) return;
 
-    engine->getSound()->play(osu->getSkin()->getMenuHit());
+    engine->getSound()->play(osu->getSkin()->m_clickPauseBack);
     osu->getSelectedBeatmap()->stop();
 
     scheduleVisibilityChange(false);
@@ -172,8 +173,6 @@ void PauseMenu::onSelectionChange() {
             m_fWarningArrowsAnimX = m_selectedButton->getPos().x;
 
         anim->moveQuadOut(&m_fWarningArrowsAnimY, m_selectedButton->getPos().y, 0.1f);
-
-        engine->getSound()->play(osu->getSkin()->getMenuClick());
     }
 }
 
@@ -337,12 +336,15 @@ void PauseMenu::onResolutionChange(Vector2 newResolution) {
 CBaseUIContainer *PauseMenu::setVisible(bool visible) {
     m_bVisible = visible;
 
-    if(osu->isInPlayMode())
+    if(osu->isInPlayMode()) {
         setContinueEnabled(!osu->getSelectedBeatmap()->hasFailed());
-    else
+    } else {
         setContinueEnabled(true);
+    }
 
     if(visible) {
+        engine->getSound()->play(osu->getSkin()->m_pauseLoop);
+
         if(m_bContinueEnabled) {
             RichPresence::setStatus("Paused");
             RichPresence::setBanchoStatus("Taking a break", PAUSED);
@@ -361,6 +363,8 @@ CBaseUIContainer *PauseMenu::setVisible(bool visible) {
             RichPresence::setBanchoStatus("Failed", SUBMITTING);
         }
     } else {
+        engine->getSound()->stop(osu->getSkin()->m_pauseLoop);
+
         RichPresence::onPlayStart();
 
         if(!bancho.spectators.empty()) {
@@ -401,8 +405,8 @@ void PauseMenu::setContinueEnabled(bool continueEnabled) {
     if(m_buttons.size() > 0) m_buttons[0]->setVisible(m_bContinueEnabled);
 }
 
-UIPauseMenuButton *PauseMenu::addButton(std::function<Image *()> getImageFunc) {
-    UIPauseMenuButton *button = new UIPauseMenuButton(getImageFunc, 0, 0, 0, 0, "");
+UIPauseMenuButton *PauseMenu::addButton(std::function<Image *()> getImageFunc, UString btn_name) {
+    UIPauseMenuButton *button = new UIPauseMenuButton(getImageFunc, 0, 0, 0, 0, btn_name);
     addBaseUIElement(button);
     m_buttons.push_back(button);
     return button;

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

@@ -33,7 +33,7 @@ class PauseMenu : public OsuScreen {
 
     void scheduleVisibilityChange(bool visible);
 
-    UIPauseMenuButton *addButton(std::function<Image *()> getImageFunc);
+    UIPauseMenuButton *addButton(std::function<Image *()> getImageFunc, UString name);
 
     bool m_bScheduledVisibilityChange;
     bool m_bScheduledVisibility;

+ 5 - 4
src/App/Osu/RankingScreen.cpp

@@ -442,6 +442,7 @@ CBaseUIContainer *RankingScreen::setVisible(bool visible) {
         // We backed out of the ranking screen, display the room again
         osu->m_room->setVisible(true);
         osu->m_chat->updateVisibility();
+        engine->getSound()->play(osu->getSkin()->m_menuBack);
 
         // Since we prevented on_map_change() from running while the ranking screen was visible, run it now.
         osu->m_room->on_map_change();
@@ -600,7 +601,7 @@ void RankingScreen::updateLayout() {
 
     m_songInfo->setSize(osu->getScreenWidth(),
                         max(m_songInfo->getMinimumHeight(),
-                                 m_rankingTitle->getSize().y * osu_rankingscreen_topbar_height_percent.getFloat()));
+                            m_rankingTitle->getSize().y * osu_rankingscreen_topbar_height_percent.getFloat()));
 
     m_rankings->setSize(osu->getScreenSize().x + 2, osu->getScreenSize().y - m_songInfo->getSize().y + 3);
     m_rankings->setRelPosY(m_songInfo->getSize().y - 1);
@@ -613,9 +614,9 @@ void RankingScreen::updateLayout() {
     m_rankingPanel->setScale(Osu::getImageScale(hardcodedOsuRankingPanelImageSize, 317.0f),
                              Osu::getImageScale(hardcodedOsuRankingPanelImageSize, 317.0f));
     m_rankingPanel->setSize(max(hardcodedOsuRankingPanelImageSize.x * m_rankingPanel->getScale().x,
-                                     m_rankingPanel->getImage()->getWidth() * m_rankingPanel->getScale().x),
+                                m_rankingPanel->getImage()->getWidth() * m_rankingPanel->getScale().x),
                             max(hardcodedOsuRankingPanelImageSize.y * m_rankingPanel->getScale().y,
-                                     m_rankingPanel->getImage()->getHeight() * m_rankingPanel->getScale().y));
+                                m_rankingPanel->getImage()->getHeight() * m_rankingPanel->getScale().y));
 
     m_rankingIndex->setSize(m_rankings->getSize().x + 2, osu->getScreenHeight() * 0.07f * uiScale);
     m_rankingIndex->setBackgroundColor(0xff745e13);
@@ -637,7 +638,7 @@ void RankingScreen::updateLayout() {
 }
 
 void RankingScreen::onBack() {
-    engine->getSound()->play(osu->getSkin()->getMenuClick());
+    engine->getSound()->play(osu->getSkin()->m_menuBack);
 
     // stop applause sound
     if(osu->getSkin()->getApplause() != NULL && osu->getSkin()->getApplause()->isPlaying())

+ 40 - 4
src/App/Osu/RoomScreen.cpp

@@ -11,6 +11,7 @@
 #include "CBaseUIContainer.h"
 #include "CBaseUILabel.h"
 #include "CBaseUITextbox.h"
+#include "Changelog.h"
 #include "Chat.h"
 #include "Database.h"
 #include "Downloader.h"
@@ -32,6 +33,7 @@
 #include "SongBrowser/SongBrowser.h"
 #include "SongBrowser/SongButton.h"
 #include "SoundEngine.h"
+#include "SpectatorScreen.h"
 #include "UIAvatar.h"
 #include "UIButton.h"
 #include "UICheckbox.h"
@@ -314,8 +316,15 @@ void RoomScreen::onChar(KeyboardEvent &key) {
 void RoomScreen::onResolutionChange(Vector2 newResolution) { updateLayout(newResolution); }
 
 CBaseUIContainer *RoomScreen::setVisible(bool visible) {
+    if(m_bVisible == visible) return this;
+
     // NOTE: Calling setVisible(false) does not quit the room! Call ragequit() instead.
     m_bVisible = visible;
+
+    if(visible) {
+        engine->getSound()->play(osu->getSkin()->m_menuBack);
+    }
+
     return this;
 }
 
@@ -480,7 +489,7 @@ void RoomScreen::updateLayout(Vector2 newResolution) {
 }
 
 // Exit to main menu
-void RoomScreen::ragequit() {
+void RoomScreen::ragequit(bool play_sound) {
     m_bVisible = false;
     bancho.match_started = false;
 
@@ -498,6 +507,10 @@ void RoomScreen::ragequit() {
 
     osu->m_modSelector->resetMods();
     osu->m_modSelector->enableModsFromFlags(osu->previous_mod_flags);
+
+    if(play_sound) {
+        engine->getSound()->play(osu->getSkin()->m_menuBack);
+    }
 }
 
 void RoomScreen::on_map_change() {
@@ -561,10 +574,17 @@ void RoomScreen::on_room_joined(Room room) {
 
     on_map_change();
 
-    // Currently we can only join rooms from the lobby.
-    // If we add ability to join from links, you would need to hide all other
-    // screens, kick the player out of the song they're currently playing, etc.
+    // Close all screens and stop any activity the player is in
+    stop_spectating();
+    if(osu->getSelectedBeatmap()->isPlaying()) {
+        osu->getSelectedBeatmap()->stop(true);
+    }
+    osu->m_rankingScreen->setVisible(false);
+    osu->m_songBrowser2->setVisible(false);
+    osu->m_changelog->setVisible(false);
+    osu->m_mainMenu->setVisible(false);
     osu->m_lobby->setVisible(false);
+
     updateLayout(osu->getScreenSize());
     m_bVisible = true;
 
@@ -581,6 +601,20 @@ void RoomScreen::on_room_joined(Room room) {
 void RoomScreen::on_room_updated(Room room) {
     if(bancho.is_playing_a_multi_map() || !bancho.is_in_a_multi_room()) return;
 
+    if(bancho.room.nb_players < room.nb_players) {
+        engine->getSound()->play(osu->getSkin()->m_roomJoined);
+    } else if(bancho.room.nb_players > room.nb_players) {
+        engine->getSound()->play(osu->getSkin()->m_roomQuit);
+    }
+    if(bancho.room.nb_ready() < room.nb_ready()) {
+        engine->getSound()->play(osu->getSkin()->m_roomReady);
+    } else if(bancho.room.nb_ready() > room.nb_ready()) {
+        engine->getSound()->play(osu->getSkin()->m_roomNotReady);
+    }
+    if(!bancho.room.all_players_ready() && room.all_players_ready()) {
+        engine->getSound()->play(osu->getSkin()->m_matchConfirm);
+    }
+
     bool was_host = bancho.room.is_host();
     bool map_changed = bancho.room.map_id != room.map_id;
     bancho.room = room;
@@ -627,6 +661,8 @@ void RoomScreen::on_match_started(Room room) {
         bancho.match_started = true;
         osu->m_songBrowser2->m_bHasSelectedAndIsPlaying = true;
         osu->m_chat->updateVisibility();
+
+        engine->getSound()->play(osu->getSkin()->m_matchStart);
     } else {
         ragequit();  // map failed to load
     }

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

@@ -41,7 +41,7 @@ class RoomScreen : public OsuScreen {
 
     void updateLayout(Vector2 newResolution);
     void updateSettingsLayout(Vector2 newResolution);
-    void ragequit();
+    void ragequit(bool play_sound = true);
 
     void on_map_change();
     void on_room_joined(Room room);

+ 0 - 7
src/App/Osu/ScreenBackable.cpp

@@ -1,10 +1,3 @@
-//================ Copyright (c) 2016, PG, All rights reserved. =================//
-//
-// Purpose:		screen + back button
-//
-// $NoKeywords: $
-//===============================================================================//
-
 #include "ScreenBackable.h"
 
 #include "KeyBindings.h"

+ 106 - 24
src/App/Osu/Skin.cpp

@@ -8,6 +8,7 @@
 #include "Environment.h"
 #include "File.h"
 #include "GameRules.h"
+#include "LinuxEnvironment.h"
 #include "NotificationOverlay.h"
 #include "Osu.h"
 #include "ResourceManager.h"
@@ -225,7 +226,7 @@ Skin::Skin(UString name, std::string filepath, bool isDefaultSkin) {
     m_failsound = NULL;
     m_applause = NULL;
     m_menuHit = NULL;
-    m_menuClick = NULL;
+    m_menuHover = NULL;
     m_checkOn = NULL;
     m_checkOff = NULL;
     m_shutter = NULL;
@@ -758,7 +759,7 @@ void Skin::load() {
     checkLoadImage(&m_buttonMiddle, "button-middle", "OSU_SKIN_BUTTON_MIDDLE");
     checkLoadImage(&m_buttonRight, "button-right", "OSU_SKIN_BUTTON_RIGHT");
     randomizeFilePath();
-    m_menuBack = createSkinImage("menu-back", Vector2(225, 87), 54);
+    m_menuBackImg = createSkinImage("menu-back", Vector2(225, 87), 54);
     randomizeFilePath();
     m_selectionMode = createSkinImage("selection-mode", Vector2(90, 90),
                                       38);  // NOTE: should actually be Vector2(88, 90), but slightly overscale to
@@ -829,28 +830,30 @@ void Skin::load() {
     // sounds
 
     // samples
-    checkLoadSound(&m_normalHitNormal, "normal-hitnormal", "OSU_SKIN_NORMALHITNORMAL_SND", true, true, false, 0.8f);
-    checkLoadSound(&m_normalHitWhistle, "normal-hitwhistle", "OSU_SKIN_NORMALHITWHISTLE_SND", true, true, false, 0.85f);
+    checkLoadSound(&m_normalHitNormal, "normal-hitnormal", "OSU_SKIN_NORMALHITNORMAL_SND", true, true, false, true,
+                   0.8f);
+    checkLoadSound(&m_normalHitWhistle, "normal-hitwhistle", "OSU_SKIN_NORMALHITWHISTLE_SND", true, true, false, true,
+                   0.85f);
     checkLoadSound(&m_normalHitFinish, "normal-hitfinish", "OSU_SKIN_NORMALHITFINISH_SND", true, true);
-    checkLoadSound(&m_normalHitClap, "normal-hitclap", "OSU_SKIN_NORMALHITCLAP_SND", true, true, false, 0.85f);
+    checkLoadSound(&m_normalHitClap, "normal-hitclap", "OSU_SKIN_NORMALHITCLAP_SND", true, true, false, true, 0.85f);
 
     checkLoadSound(&m_normalSliderTick, "normal-slidertick", "OSU_SKIN_NORMALSLIDERTICK_SND", true, true);
     checkLoadSound(&m_normalSliderSlide, "normal-sliderslide", "OSU_SKIN_NORMALSLIDERSLIDE_SND", false, true, true);
     checkLoadSound(&m_normalSliderWhistle, "normal-sliderwhistle", "OSU_SKIN_NORMALSLIDERWHISTLE_SND", true, true);
 
-    checkLoadSound(&m_softHitNormal, "soft-hitnormal", "OSU_SKIN_SOFTHITNORMAL_SND", true, true, false, 0.8f);
-    checkLoadSound(&m_softHitWhistle, "soft-hitwhistle", "OSU_SKIN_SOFTHITWHISTLE_SND", true, true, false, 0.85f);
+    checkLoadSound(&m_softHitNormal, "soft-hitnormal", "OSU_SKIN_SOFTHITNORMAL_SND", true, true, false, true, 0.8f);
+    checkLoadSound(&m_softHitWhistle, "soft-hitwhistle", "OSU_SKIN_SOFTHITWHISTLE_SND", true, true, false, true, 0.85f);
     checkLoadSound(&m_softHitFinish, "soft-hitfinish", "OSU_SKIN_SOFTHITFINISH_SND", true, true);
-    checkLoadSound(&m_softHitClap, "soft-hitclap", "OSU_SKIN_SOFTHITCLAP_SND", true, true, false, 0.85f);
+    checkLoadSound(&m_softHitClap, "soft-hitclap", "OSU_SKIN_SOFTHITCLAP_SND", true, true, false, true, 0.85f);
 
     checkLoadSound(&m_softSliderTick, "soft-slidertick", "OSU_SKIN_SOFTSLIDERTICK_SND", true, true);
     checkLoadSound(&m_softSliderSlide, "soft-sliderslide", "OSU_SKIN_SOFTSLIDERSLIDE_SND", false, true, true);
     checkLoadSound(&m_softSliderWhistle, "soft-sliderwhistle", "OSU_SKIN_SOFTSLIDERWHISTLE_SND", true, true);
 
-    checkLoadSound(&m_drumHitNormal, "drum-hitnormal", "OSU_SKIN_DRUMHITNORMAL_SND", true, true, false, 0.8f);
-    checkLoadSound(&m_drumHitWhistle, "drum-hitwhistle", "OSU_SKIN_DRUMHITWHISTLE_SND", true, true, false, 0.85f);
+    checkLoadSound(&m_drumHitNormal, "drum-hitnormal", "OSU_SKIN_DRUMHITNORMAL_SND", true, true, false, true, 0.8f);
+    checkLoadSound(&m_drumHitWhistle, "drum-hitwhistle", "OSU_SKIN_DRUMHITWHISTLE_SND", true, true, false, true, 0.85f);
     checkLoadSound(&m_drumHitFinish, "drum-hitfinish", "OSU_SKIN_DRUMHITFINISH_SND", true, true);
-    checkLoadSound(&m_drumHitClap, "drum-hitclap", "OSU_SKIN_DRUMHITCLAP_SND", true, true, false, 0.85f);
+    checkLoadSound(&m_drumHitClap, "drum-hitclap", "OSU_SKIN_DRUMHITCLAP_SND", true, true, false, true, 0.85f);
 
     checkLoadSound(&m_drumSliderTick, "drum-slidertick", "OSU_SKIN_DRUMSLIDERTICK_SND", true, true);
     checkLoadSound(&m_drumSliderSlide, "drum-sliderslide", "OSU_SKIN_DRUMSLIDERSLIDE_SND", false, true, true);
@@ -864,13 +867,88 @@ void Skin::load() {
     checkLoadSound(&m_failsound, "failsound", "OSU_SKIN_FAILSOUND_SND");
     checkLoadSound(&m_applause, "applause", "OSU_SKIN_APPLAUSE_SND");
     checkLoadSound(&m_menuHit, "menuhit", "OSU_SKIN_MENUHIT_SND", true, true);
-    checkLoadSound(&m_menuClick, "menuclick", "OSU_SKIN_MENUCLICK_SND", true, true);
+    checkLoadSound(&m_menuHover, "menuclick", "OSU_SKIN_MENUCLICK_SND", true, true);
     checkLoadSound(&m_checkOn, "check-on", "OSU_SKIN_CHECKON_SND", true, true);
     checkLoadSound(&m_checkOff, "check-off", "OSU_SKIN_CHECKOFF_SND", true, true);
     checkLoadSound(&m_shutter, "shutter", "OSU_SKIN_SHUTTER_SND", true, true);
     checkLoadSound(&m_sectionPassSound, "sectionpass", "OSU_SKIN_SECTIONPASS_SND");
     checkLoadSound(&m_sectionFailSound, "sectionfail", "OSU_SKIN_SECTIONFAIL_SND");
 
+    // UI feedback
+    checkLoadSound(&m_messageSent, "key-confirm", "OSU_SKIN_MESSAGE_SENT_SND", true, true, false);
+    checkLoadSound(&m_deletingText, "key-delete", "OSU_SKIN_DELETING_TEXT_SND", true, true, false);
+    checkLoadSound(&m_movingTextCursor, "key-movement", "OSU_MOVING_TEXT_CURSOR_SND", true, true, false);
+    checkLoadSound(&m_typing1, "key-press-1", "OSU_TYPING_1_SND", true, true, false);
+    checkLoadSound(&m_typing2, "key-press-2", "OSU_TYPING_2_SND", true, true, false, false);
+    checkLoadSound(&m_typing3, "key-press-3", "OSU_TYPING_3_SND", true, true, false, false);
+    checkLoadSound(&m_typing4, "key-press-4", "OSU_TYPING_4_SND", true, true, false, false);
+    checkLoadSound(&m_menuBack, "menuback", "OSU_MENU_BACK_SND", true, true, false, false);
+    checkLoadSound(&m_closeChatTab, "click-close", "OSU_CLOSE_CHAT_TAB_SND", true, true, false, false);
+    checkLoadSound(&m_clickButton, "click-short-confirm", "OSU_CLICK_BUTTON_SND", true, true, false, false);
+    checkLoadSound(&m_hoverButton, "click-short", "OSU_HOVER_BUTTON_SND", true, true, false, false);
+    checkLoadSound(&m_backButtonClick, "back-button-click", "OSU_BACK_BUTTON_CLICK_SND", true, true, false, false);
+    checkLoadSound(&m_backButtonHover, "back-button-hover", "OSU_BACK_BUTTON_HOVER_SND", true, true, false, false);
+    checkLoadSound(&m_clickMainMenuCube, "menu-play-click", "OSU_CLICK_MAIN_MENU_CUBE_SND", true, true, false, false);
+    checkLoadSound(&m_hoverMainMenuCube, "menu-play-hover", "OSU_HOVER_MAIN_MENU_CUBE_SND", true, true, false, false);
+    checkLoadSound(&m_clickSingleplayer, "menu-freeplay-click", "OSU_CLICK_SINGLEPLAYER_SND", true, true, false, false);
+    checkLoadSound(&m_hoverSingleplayer, "menu-freeplay-hover", "OSU_HOVER_SINGLEPLAYER_SND", true, true, false, false);
+    checkLoadSound(&m_clickMultiplayer, "menu-multiplayer-click", "OSU_CLICK_MULTIPLAYER_SND", true, true, false,
+                   false);
+    checkLoadSound(&m_hoverMultiplayer, "menu-multiplayer-hover", "OSU_HOVER_MULTIPLAYER_SND", true, true, false,
+                   false);
+    checkLoadSound(&m_clickOptions, "menu-options-click", "OSU_CLICK_OPTIONS_SND", true, true, false, false);
+    checkLoadSound(&m_hoverOptions, "menu-options-hover", "OSU_HOVER_OPTIONS_SND", true, true, false, false);
+    checkLoadSound(&m_clickExit, "menu-exit-click", "OSU_CLICK_EXIT_SND", true, true, false, false);
+    checkLoadSound(&m_hoverExit, "menu-exit-hover", "OSU_HOVER_EXIT_SND", true, true, false, false);
+    checkLoadSound(&m_expand, "select-expand", "OSU_EXPAND_SND", true, true, false);
+    checkLoadSound(&m_selectDifficulty, "select-difficulty", "OSU_SELECT_DIFFICULTY_SND", true, true, false, false);
+    checkLoadSound(&m_sliderbar, "sliderbar", "OSU_DRAG_SLIDER_SND", true, true, false);
+    checkLoadSound(&m_matchConfirm, "match-confirm", "OSU_ALL_PLAYERS_READY_SND", true, true, false);
+    checkLoadSound(&m_roomJoined, "match-join", "OSU_ROOM_JOINED_SND", true, true, false);
+    checkLoadSound(&m_roomQuit, "match-leave", "OSU_ROOM_QUIT_SND", true, true, false);
+    checkLoadSound(&m_roomNotReady, "match-notready", "OSU_ROOM_NOT_READY_SND", true, true, false);
+    checkLoadSound(&m_roomReady, "match-ready", "OSU_ROOM_READY_SND", true, true, false);
+    checkLoadSound(&m_matchStart, "match-start", "OSU_MATCH_START_SND", true, true, false);
+
+    checkLoadSound(&m_pauseLoop, "pause-loop", "OSU_PAUSE_LOOP_SND", false, false, true, true);
+    checkLoadSound(&m_pauseHover, "pause-hover", "OSU_PAUSE_HOVER_SND", true, true, false, false);
+    checkLoadSound(&m_clickPauseBack, "pause-back-click", "OSU_CLICK_QUIT_SONG_SND", true, true, false, false);
+    checkLoadSound(&m_hoverPauseBack, "pause-back-hover", "OSU_HOVER_QUIT_SONG_SND", true, true, false, false);
+    checkLoadSound(&m_clickPauseContinue, "pause-continue-click", "OSU_CLICK_RESUME_SONG_SND", true, true, false,
+                   false);
+    checkLoadSound(&m_hoverPauseContinue, "pause-continue-hover", "OSU_HOVER_RESUME_SONG_SND", true, true, false,
+                   false);
+    checkLoadSound(&m_clickPauseRetry, "pause-retry-click", "OSU_CLICK_RETRY_SONG_SND", true, true, false, false);
+    checkLoadSound(&m_hoverPauseRetry, "pause-retry-hover", "OSU_HOVER_RETRY_SONG_SND", true, true, false, false);
+
+    if(m_clickButton == NULL) m_clickButton = m_menuHit;
+    if(m_hoverButton == NULL) m_hoverButton = m_menuHover;
+    if(m_pauseHover == NULL) m_pauseHover = m_hoverButton;
+    if(m_selectDifficulty == NULL) m_selectDifficulty = m_clickButton;
+    if(m_typing2 == NULL) m_typing2 = m_typing1;
+    if(m_typing3 == NULL) m_typing3 = m_typing2;
+    if(m_typing4 == NULL) m_typing4 = m_typing3;
+    if(m_backButtonClick == NULL) m_backButtonClick = m_clickButton;
+    if(m_backButtonHover == NULL) m_backButtonHover = m_hoverButton;
+    if(m_menuBack == NULL) m_menuBack = m_clickButton;
+    if(m_closeChatTab == NULL) m_closeChatTab = m_clickButton;
+    if(m_clickMainMenuCube == NULL) m_clickMainMenuCube = m_clickButton;
+    if(m_hoverMainMenuCube == NULL) m_hoverMainMenuCube = m_menuHover;
+    if(m_clickSingleplayer == NULL) m_clickSingleplayer = m_clickButton;
+    if(m_hoverSingleplayer == NULL) m_hoverSingleplayer = m_menuHover;
+    if(m_clickMultiplayer == NULL) m_clickMultiplayer = m_clickButton;
+    if(m_hoverMultiplayer == NULL) m_hoverMultiplayer = m_menuHover;
+    if(m_clickOptions == NULL) m_clickOptions = m_clickButton;
+    if(m_hoverOptions == NULL) m_hoverOptions = m_menuHover;
+    if(m_clickExit == NULL) m_clickExit = m_clickButton;
+    if(m_hoverExit == NULL) m_hoverExit = m_menuHover;
+    if(m_clickPauseBack == NULL) m_clickPauseBack = m_clickButton;
+    if(m_hoverPauseBack == NULL) m_hoverPauseBack = m_pauseHover;
+    if(m_clickPauseContinue == NULL) m_clickPauseContinue = m_clickButton;
+    if(m_hoverPauseContinue == NULL) m_hoverPauseContinue = m_pauseHover;
+    if(m_clickPauseRetry == NULL) m_clickPauseRetry = m_clickButton;
+    if(m_hoverPauseRetry == NULL) m_hoverPauseRetry = m_pauseHover;
+
     // scaling
     // HACKHACK: this is pure cancer
     if(m_cursor != NULL && m_cursor->getFilePath().find("@2x") != -1) m_bCursor2x = true;
@@ -1420,7 +1498,8 @@ void Skin::checkLoadImage(Image **addressOfPointer, std::string skinElementName,
 }
 
 void Skin::checkLoadSound(Sound **addressOfPointer, std::string skinElementName, std::string resourceName,
-                          bool isOverlayable, bool isSample, bool loop, float hardcodedVolumeMultiplier) {
+                          bool isOverlayable, bool isSample, bool loop, bool fallback_to_default,
+                          float hardcodedVolumeMultiplier) {
     if(*addressOfPointer != NULL) return;  // we are already loaded
 
     // NOTE: only the default skin is loaded with a resource name (it must never be unloaded by other instances), and it
@@ -1429,11 +1508,15 @@ void Skin::checkLoadSound(Sound **addressOfPointer, std::string skinElementName,
     // random skin support
     randomizeFilePath();
 
-    auto try_load_sound = [=](std::string base_path, std::string resource_name, bool loop) {
+    auto try_load_sound = [=](std::string base_path, std::string filename, std::string resource_name, bool loop) {
         const char *extensions[] = {".wav", ".mp3", ".ogg"};
         for(int i = 0; i < 3; i++) {
+            std::string fn = filename;
+            fn.append(extensions[i]);
+            fn = fix_filename_casing(base_path, fn);
+
             std::string path = base_path;
-            path.append(extensions[i]);
+            path.append(fn);
 
             if(env->fileExists(path)) {
                 if(osu_skin_async.getBool()) {
@@ -1447,19 +1530,18 @@ void Skin::checkLoadSound(Sound **addressOfPointer, std::string skinElementName,
     };
 
     // load default skin
-    std::string defaultpath = MCENGINE_DATA_DIR "./materials/";
-    defaultpath.append(OSUSKIN_DEFAULT_SKIN_PATH);
-    defaultpath.append(skinElementName);
-    std::string defaultResourceName = resourceName;
-    defaultResourceName.append("_DEFAULT");
-    *addressOfPointer = try_load_sound(defaultpath, defaultResourceName, loop);
+    if(fallback_to_default) {
+        std::string defaultpath = MCENGINE_DATA_DIR "./materials/";
+        defaultpath.append(OSUSKIN_DEFAULT_SKIN_PATH);
+        std::string defaultResourceName = resourceName;
+        defaultResourceName.append("_DEFAULT");
+        *addressOfPointer = try_load_sound(defaultpath, skinElementName, defaultResourceName, loop);
+    }
 
     // load user skin
     Sound *skin_sound = NULL;
     if(osu_skin_use_skin_hitsounds.getBool() || !isSample) {
-        std::string filepath = m_sFilePath;
-        filepath.append(skinElementName);
-        skin_sound = try_load_sound(filepath, "", loop);
+        skin_sound = try_load_sound(m_sFilePath, skinElementName, "", loop);
         if(skin_sound != NULL) {
             *addressOfPointer = skin_sound;
         }

+ 76 - 5
src/App/Osu/Skin.h

@@ -188,7 +188,7 @@ class Skin {
     inline Image *getDefaultButtonLeft() { return m_defaultButtonLeft; }
     inline Image *getDefaultButtonMiddle() { return m_defaultButtonMiddle; }
     inline Image *getDefaultButtonRight() { return m_defaultButtonRight; }
-    inline SkinImage *getMenuBack2() { return m_menuBack; }
+    inline SkinImage *getMenuBack2() { return m_menuBackImg; }
     inline SkinImage *getSelectionMode() { return m_selectionMode; }
     inline SkinImage *getSelectionModeOver() { return m_selectionModeOver; }
     inline SkinImage *getSelectionMods() { return m_selectionMods; }
@@ -243,7 +243,7 @@ class Skin {
     inline Sound *getFailsound() { return m_failsound; }
     inline Sound *getApplause() { return m_applause; }
     inline Sound *getMenuHit() { return m_menuHit; }
-    inline Sound *getMenuClick() { return m_menuClick; }
+    inline Sound *getMenuHover() { return m_menuHover; }
     inline Sound *getCheckOn() { return m_checkOn; }
     inline Sound *getCheckOff() { return m_checkOff; }
     inline Sound *getShutter() { return m_shutter; }
@@ -350,7 +350,7 @@ class Skin {
 
     void checkLoadSound(Sound **addressOfPointer, std::string skinElementName, std::string resourceName,
                         bool isOverlayable = false, bool isSample = false, bool loop = false,
-                        float hardcodedVolumeMultiplier = -1.0f);
+                        bool fallback_to_default = true, float hardcodedVolumeMultiplier = -1.0f);
 
     void onEffectVolumeChange(UString oldValue, UString newValue);
     void onIgnoreBeatmapSampleVolumeChange(UString oldValue, UString newValue);
@@ -504,7 +504,7 @@ class Skin {
     Image *m_defaultButtonLeft;
     Image *m_defaultButtonMiddle;
     Image *m_defaultButtonRight;
-    SkinImage *m_menuBack;
+    SkinImage *m_menuBackImg;
     SkinImage *m_selectionMode;
     SkinImage *m_selectionModeOver;
     SkinImage *m_selectionMods;
@@ -583,11 +583,82 @@ class Skin {
     Sound *m_spinnerBonus;
     Sound *m_spinnerSpinSound;
 
+    // Plays when sending a message in chat
+    Sound *m_messageSent = NULL;
+
+    // Plays when deleting text in a message in chat
+    Sound *m_deletingText = NULL;
+
+    // Plays when changing the text cursor position
+    Sound *m_movingTextCursor = NULL;
+
+    // Plays when pressing a key for chat, search, edit, etc
+    Sound *m_typing1 = NULL;
+    Sound *m_typing2 = NULL;
+    Sound *m_typing3 = NULL;
+    Sound *m_typing4 = NULL;
+
+    // Plays when returning to the previous screen
+    Sound *m_menuBack = NULL;
+
+    // Plays when closing a chat tab
+    Sound *m_closeChatTab = NULL;
+
+    // Plays when hovering above all selectable boxes except beatmaps or main screen buttons
+    Sound *m_hoverButton = NULL;
+
+    // Plays when clicking to confirm a button or dropdown option, opening or
+    // closing chat, switching between chat tabs, or switching groups
+    Sound *m_clickButton = NULL;
+
+    // Main menu sounds
+    Sound *m_clickMainMenuCube = NULL;
+    Sound *m_hoverMainMenuCube = NULL;
+    Sound *m_clickSingleplayer = NULL;
+    Sound *m_hoverSingleplayer = NULL;
+    Sound *m_clickMultiplayer = NULL;
+    Sound *m_hoverMultiplayer = NULL;
+    Sound *m_clickOptions = NULL;
+    Sound *m_hoverOptions = NULL;
+    Sound *m_clickExit = NULL;
+    Sound *m_hoverExit = NULL;
+
+    // Pause menu sounds
+    Sound *m_pauseLoop = NULL;
+    Sound *m_pauseHover = NULL;
+    Sound *m_clickPauseBack = NULL;
+    Sound *m_hoverPauseBack = NULL;
+    Sound *m_clickPauseContinue = NULL;
+    Sound *m_hoverPauseContinue = NULL;
+    Sound *m_clickPauseRetry = NULL;
+    Sound *m_hoverPauseRetry = NULL;
+
+    // Back button sounds
+    Sound *m_backButtonClick = NULL;
+    Sound *m_backButtonHover = NULL;
+
+    // Plays when switching into song selection, selecting a beatmap, opening dropdown boxes, opening chat tabs
+    Sound *m_expand = NULL;
+
+    // Plays when selecting a difficulty of a beatmap
+    Sound *m_selectDifficulty = NULL;
+
+    // Plays when changing the options via a slider
+    Sound *m_sliderbar = NULL;
+
+    // Multiplayer sounds
+    Sound *m_matchConfirm = NULL;  // all players are ready
+    Sound *m_roomJoined = NULL;    // a player joined
+    Sound *m_roomQuit = NULL;      // a player left
+    Sound *m_roomNotReady = NULL;  // a player is no longer ready
+    Sound *m_roomReady = NULL;     // a player is now ready
+    Sound *m_matchStart = NULL;    // match started
+
     Sound *m_combobreak;
     Sound *m_failsound;
     Sound *m_applause;
     Sound *m_menuHit;
-    Sound *m_menuClick;
+    Sound *m_menuHover;
     Sound *m_checkOn;
     Sound *m_checkOff;
     Sound *m_shutter;

+ 2 - 2
src/App/Osu/SongBrowser/Button.cpp

@@ -249,7 +249,7 @@ void Button::deselect() { m_bSelected = false; }
 void Button::resetAnimations() { setMoveAwayState(MOVE_AWAY_STATE::MOVE_CENTER, false); }
 
 void Button::onClicked() {
-    engine->getSound()->play(osu->getSkin()->getMenuClick());
+    engine->getSound()->play(osu->getSkin()->m_selectDifficulty);
 
     CBaseUIButton::onClicked();
 
@@ -262,7 +262,7 @@ void Button::onMouseInside() {
     // hover sound
     if(engine->getTime() > lastHoverSoundTime + 0.05f)  // to avoid earraep
     {
-        if(engine->hasFocus()) engine->getSound()->play(osu->getSkin()->getMenuClick());
+        if(engine->hasFocus()) engine->getSound()->play(osu->getSkin()->getMenuHover());
 
         lastHoverSoundTime = engine->getTime();
     }

+ 15 - 7
src/App/Osu/SongBrowser/SongBrowser.cpp

@@ -426,6 +426,8 @@ SongBrowser::SongBrowser() : ScreenBackable() {
     m_groupButton->setClickCallback(fastdelegate::MakeDelegate(this, &SongBrowser::onGroupClicked));
 
     {
+        // TODO: Add hover sounds
+
         // "hardcoded" grouping tabs
         m_collectionsButton = addTopBarRightTabButton("Collections");
         m_collectionsButton->setClickCallback(fastdelegate::MakeDelegate(this, &SongBrowser::onGroupTabButtonClicked));
@@ -1321,10 +1323,13 @@ void SongBrowser::onChar(KeyboardEvent &e) {
 void SongBrowser::onResolutionChange(Vector2 newResolution) { ScreenBackable::onResolutionChange(newResolution); }
 
 CBaseUIContainer *SongBrowser::setVisible(bool visible) {
+    if(visible == m_bVisible) return this;
+
     m_bVisible = visible;
     m_bShiftPressed = false;  // seems to get stuck sometimes otherwise
 
     if(m_bVisible) {
+        engine->getSound()->play(osu->getSkin()->m_expand);
         RichPresence::onSongBrowser();
 
         updateLayout();
@@ -2477,7 +2482,7 @@ void SongBrowser::updateLayout() {
 }
 
 void SongBrowser::onBack() {
-    engine->getSound()->play(osu->getSkin()->getMenuClick());
+    engine->getSound()->play(osu->getSkin()->m_menuBack);
     osu->toggleSongBrowser();
 }
 
@@ -3344,7 +3349,10 @@ void SongBrowser::onSortChangeInt(UString text, bool autoScroll) {
     onAfterSortingOrGroupChange(autoScroll);
 }
 
-void SongBrowser::onGroupTabButtonClicked(CBaseUIButton *groupTabButton) { onGroupChange(groupTabButton->getText()); }
+void SongBrowser::onGroupTabButtonClicked(CBaseUIButton *groupTabButton) {
+    onGroupChange(groupTabButton->getText());
+    engine->getSound()->play(osu->getSkin()->m_clickButton);
+}
 
 void SongBrowser::onGroupNoGrouping() {
     m_group = GROUP::GROUP_NO_GROUPING;
@@ -3473,8 +3481,6 @@ void SongBrowser::onAfterSortingOrGroupChangeUpdateInt(bool autoScroll) {
 }
 
 void SongBrowser::onSelectionMode() {
-    engine->getSound()->play(osu->getSkin()->getMenuClick());
-
     m_contextMenu->setPos(m_bottombarNavButtons[0]->getPos());
     m_contextMenu->setRelPos(m_bottombarNavButtons[0]->getRelPos());
     m_contextMenu->begin(0, true);
@@ -3508,12 +3514,12 @@ void SongBrowser::onSelectionMode() {
 }
 
 void SongBrowser::onSelectionMods() {
-    engine->getSound()->play(osu->getSkin()->getMenuClick());
+    engine->getSound()->play(osu->getSkin()->m_expand);
     osu->toggleModSelection(m_bF1Pressed);
 }
 
 void SongBrowser::onSelectionRandom() {
-    engine->getSound()->play(osu->getSkin()->getMenuClick());
+    engine->getSound()->play(osu->getSkin()->m_clickButton);
     if(m_bShiftPressed)
         m_bPreviousRandomBeatmapScheduled = true;
     else
@@ -3521,7 +3527,7 @@ void SongBrowser::onSelectionRandom() {
 }
 
 void SongBrowser::onSelectionOptions() {
-    engine->getSound()->play(osu->getSkin()->getMenuClick());
+    engine->getSound()->play(osu->getSkin()->m_clickButton);
 
     Button *currentlySelectedSongButton = findCurrentlySelectedSongButton();
     if(currentlySelectedSongButton != NULL) {
@@ -3561,6 +3567,8 @@ void SongBrowser::onScoreClicked(CBaseUIButton *button) {
 
     osu->getSongBrowser()->setVisible(false);
     osu->getRankingScreen()->setVisible(true);
+
+    engine->getSound()->play(osu->getSkin()->m_menuHit);
 }
 
 void SongBrowser::onScoreContextMenu(ScoreButton *scoreButton, int id) {

+ 10 - 0
src/App/Osu/SongBrowser/SongDifficultyButton.cpp

@@ -15,6 +15,7 @@
 #include "Replay.h"
 #include "ResourceManager.h"
 #include "Skin.h"
+#include "SoundEngine.h"
 #include "Timer.h"
 
 using namespace std;
@@ -172,6 +173,15 @@ void SongDifficultyButton::mouse_update(bool *propagate_clicks) {
     }
 }
 
+void SongDifficultyButton::onClicked() {
+    engine->getSound()->play(osu->getSkin()->m_selectDifficulty);
+
+    // NOTE: Intentionally not calling Button::onClicked(), since that one plays another sound
+    CBaseUIButton::onClicked();
+
+    select(true, true);
+}
+
 void SongDifficultyButton::onSelected(bool wasSelected, bool autoSelectBottomMostChild, bool wasParentSelected) {
     Button::onSelected(wasSelected, autoSelectBottomMostChild, wasParentSelected);
 

+ 1 - 0
src/App/Osu/SongBrowser/SongDifficultyButton.h

@@ -12,6 +12,7 @@ class SongDifficultyButton : public SongButton {
 
     virtual void draw(Graphics *g);
     virtual void mouse_update(bool *propagate_clicks);
+    virtual void onClicked();
 
     virtual void updateGrade();
 

+ 5 - 1
src/App/Osu/SpectatorScreen.cpp

@@ -20,7 +20,9 @@
 #include "PromptScreen.h"
 #include "ResourceManager.h"
 #include "RoomScreen.h"
+#include "Skin.h"
 #include "SongBrowser/SongBrowser.h"
+#include "SoundEngine.h"
 #include "UIButton.h"
 #include "UserCard.h"
 
@@ -70,9 +72,10 @@ void start_spectating(i32 user_id) {
     osu->m_lobby->setVisible(false);
     osu->m_changelog->setVisible(false);
     osu->m_mainMenu->setVisible(false);
-    if(osu->m_room->isVisible()) osu->m_room->ragequit();
+    if(osu->m_room->isVisible()) osu->m_room->ragequit(false);
 
     osu->m_spectatorScreen->setVisible(true);
+    engine->getSound()->play(osu->getSkin()->m_menuHit);
 }
 
 void stop_spectating() {
@@ -95,6 +98,7 @@ void stop_spectating() {
 
     osu->m_spectatorScreen->setVisible(false);
     osu->m_mainMenu->setVisible(true);
+    engine->getSound()->play(osu->getSkin()->m_menuBack);
 }
 
 SpectatorScreen::SpectatorScreen() {

+ 8 - 0
src/App/Osu/UIBackButton.cpp

@@ -6,6 +6,7 @@
 #include "Osu.h"
 #include "Skin.h"
 #include "SkinImage.h"
+#include "SoundEngine.h"
 
 using namespace std;
 
@@ -53,10 +54,17 @@ void UIBackButton::mouse_update(bool *propagate_clicks) {
     CBaseUIButton::mouse_update(propagate_clicks);
 }
 
+void UIBackButton::onMouseDownInside() {
+    CBaseUIButton::onMouseDownInside();
+
+    engine->getSound()->play(osu->getSkin()->m_backButtonClick);
+}
+
 void UIBackButton::onMouseInside() {
     CBaseUIButton::onMouseInside();
 
     anim->moveQuadOut(&m_fAnimation, 1.0f, 0.1f, 0.0f, true);
+    engine->getSound()->play(osu->getSkin()->m_backButtonHover);
 }
 
 void UIBackButton::onMouseOutside() {

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

@@ -8,6 +8,7 @@ class UIBackButton : public CBaseUIButton {
     virtual void draw(Graphics *g);
     virtual void mouse_update(bool *propagate_clicks);
 
+    virtual void onMouseDownInside();
     virtual void onMouseInside();
     virtual void onMouseOutside();
 

+ 13 - 1
src/App/Osu/UIButton.cpp

@@ -5,10 +5,13 @@
 #include "Osu.h"
 #include "ResourceManager.h"
 #include "Skin.h"
+#include "SoundEngine.h"
 #include "TooltipOverlay.h"
 
 using namespace std;
 
+static float button_sound_cooldown = 0.f;
+
 UIButton::UIButton(float xPos, float yPos, float xSize, float ySize, UString name, UString text)
     : CBaseUIButton(xPos, yPos, xSize, ySize, name, text) {
     m_bDefaultSkin = false;
@@ -87,7 +90,14 @@ void UIButton::mouse_update(bool *propagate_clicks) {
     m_bFocusStolenDelay = false;
 }
 
-void UIButton::onMouseInside() { m_fBrightness = 1.0f; }
+void UIButton::onMouseInside() {
+    m_fBrightness = 1.0f;
+
+    if(button_sound_cooldown + 0.05f < engine->getTime()) {
+        engine->getSound()->play(osu->getSkin()->m_hoverButton);
+        button_sound_cooldown = engine->getTime();
+    }
+}
 
 void UIButton::onMouseOutside() { m_fBrightness = 0.85f; }
 
@@ -97,6 +107,8 @@ void UIButton::onClicked() {
     CBaseUIButton::onClicked();
 
     animateClickColor();
+
+    engine->getSound()->play(osu->getSkin()->m_clickButton);
 }
 
 void UIButton::onFocusStolen() {

+ 15 - 0
src/App/Osu/UIContextMenu.cpp

@@ -8,8 +8,12 @@
 #include "Keyboard.h"
 #include "Mouse.h"
 #include "Osu.h"
+#include "Skin.h"
+#include "SoundEngine.h"
 #include "TooltipOverlay.h"
 
+static float button_sound_cooldown = 0.f;
+
 UIContextMenuButton::UIContextMenuButton(float xPos, float yPos, float xSize, float ySize, UString name, UString text,
                                          int id)
     : CBaseUIButton(xPos, yPos, xSize, ySize, name, text) {
@@ -31,6 +35,15 @@ void UIContextMenuButton::mouse_update(bool *propagate_clicks) {
     }
 }
 
+void UIContextMenuButton::onMouseInside() {
+    if(button_sound_cooldown + 0.05f < engine->getTime()) {
+        engine->getSound()->play(osu->getSkin()->m_hoverButton);
+        button_sound_cooldown = engine->getTime();
+    }
+}
+
+void UIContextMenuButton::onMouseDownInside() { engine->getSound()->play(osu->getSkin()->m_clickButton); }
+
 void UIContextMenuButton::setTooltipText(UString text) { m_tooltipTextLines = text.split("\n"); }
 
 UIContextMenuTextbox::UIContextMenuTextbox(float xPos, float yPos, float xSize, float ySize, UString name, int id)
@@ -267,6 +280,8 @@ void UIContextMenu::end(bool invertAnimation, bool clampUnderflowAndOverflowAndE
 
     m_fAnimation = 0.001f;
     anim->moveQuartOut(&m_fAnimation, 1.0f, 0.15f, true);
+
+    engine->getSound()->play(osu->getSkin()->m_expand);
 }
 
 void UIContextMenu::setVisible2(bool visible2) {

+ 3 - 0
src/App/Osu/UIContextMenu.h

@@ -71,6 +71,9 @@ class UIContextMenuButton : public CBaseUIButton {
 
     virtual void mouse_update(bool *propagate_clicks);
 
+    virtual void onMouseInside();
+    virtual void onMouseDownInside();
+
     inline int getID() const { return m_iID; }
 
     void setTooltipText(UString text);

+ 9 - 1
src/App/Osu/UIPauseMenuButton.cpp

@@ -49,11 +49,19 @@ void UIPauseMenuButton::setBaseScale(float xScale, float yScale) {
 void UIPauseMenuButton::onMouseInside() {
     CBaseUIButton::onMouseInside();
 
-    if(engine->hasFocus()) engine->getSound()->play(osu->getSkin()->getMenuClick());
+    if(engine->hasFocus()) engine->getSound()->play(osu->getSkin()->getMenuHover());
 
     const float animationDuration = 0.09f;
     anim->moveLinear(&m_vScale.x, m_vBaseScale.x * m_fScaleMultiplier, animationDuration, true);
     anim->moveLinear(&m_vScale.y, m_vBaseScale.y * m_fScaleMultiplier, animationDuration, true);
+
+    if(getName() == UString("Resume")) {
+        engine->getSound()->play(osu->getSkin()->m_hoverPauseContinue);
+    } else if(getName() == UString("Retry")) {
+        engine->getSound()->play(osu->getSkin()->m_hoverPauseRetry);
+    } else if(getName() == UString("Quit")) {
+        engine->getSound()->play(osu->getSkin()->m_hoverPauseBack);
+    }
 }
 
 void UIPauseMenuButton::onMouseOutside() {

+ 18 - 0
src/Engine/Platform/LinuxEnvironment.cpp

@@ -875,3 +875,21 @@ UString LinuxEnvironment::getClipboardTextInt() {
 }
 
 #endif
+
+#include <filesystem>
+
+std::string fix_filename_casing(std::string directory, std::string filename) {
+#ifdef _WIN32
+    return filename;
+#else
+    if(!std::filesystem::exists(directory) || !std::filesystem::is_directory(directory)) return filename;
+
+    std::filesystem::path dir = directory;
+    for(auto &entry : std::filesystem::directory_iterator(dir)) {
+        if(entry.is_regular_file() && !strcasecmp(entry.path().filename().string().c_str(), filename.c_str())) {
+            return entry.path().filename().string();
+        }
+    }
+    return filename;
+#endif
+}

+ 2 - 10
src/Engine/Platform/LinuxEnvironment.h

@@ -1,15 +1,7 @@
-//================ Copyright (c) 2015, PG, All rights reserved. =================//
-//
-// Purpose:		linux
-//
-// $NoKeywords: $linuxenv
-//===============================================================================//
+#pragma once
 
 #ifdef __linux__
 
-#ifndef LINUXENVIRONMENT_H
-#define LINUXENVIRONMENT_H
-
 #include <X11/X.h>
 #include <X11/Xlib.h>
 
@@ -167,4 +159,4 @@ class LinuxEnvironment : public Environment {
 
 #endif
 
-#endif
+std::string fix_filename_casing(std::string directory, std::string filename);

+ 154 - 144
src/Engine/SoundEngine.cpp

@@ -26,16 +26,20 @@ void _volume(UString oldValue, UString newValue) {
 
 ConVar _volume_("volume", 1.0f, FCVAR_DEFAULT | FCVAR_PRIVATE, _volume);
 
-ConVar snd_ready_delay("snd_ready_delay", 0.0f, FCVAR_DEFAULT | FCVAR_PRIVATE, "after a sound engine restart, wait this many seconds before marking it as ready");
+ConVar snd_ready_delay("snd_ready_delay", 0.0f, FCVAR_DEFAULT | FCVAR_PRIVATE,
+                       "after a sound engine restart, wait this many seconds before marking it as ready");
 ConVar snd_output_device("snd_output_device", "Default", FCVAR_DEFAULT | FCVAR_PRIVATE);
 ConVar snd_restart("snd_restart");
 
 ConVar snd_freq("snd_freq", 44100, FCVAR_DEFAULT | FCVAR_PRIVATE, "output sampling rate in Hz");
-ConVar snd_updateperiod("snd_updateperiod", 10, FCVAR_DEFAULT | FCVAR_PRIVATE, "BASS_CONFIG_UPDATEPERIOD length in milliseconds");
+ConVar snd_updateperiod("snd_updateperiod", 10, FCVAR_DEFAULT | FCVAR_PRIVATE,
+                        "BASS_CONFIG_UPDATEPERIOD length in milliseconds");
 ConVar snd_dev_period("snd_dev_period", 10, FCVAR_DEFAULT | FCVAR_PRIVATE,
                       "BASS_CONFIG_DEV_PERIOD length in milliseconds, or if negative then in samples");
-ConVar snd_dev_buffer("snd_dev_buffer", 30, FCVAR_DEFAULT | FCVAR_PRIVATE, "BASS_CONFIG_DEV_BUFFER length in milliseconds");
-ConVar snd_async_buffer("snd_async_buffer", 65536, FCVAR_DEFAULT | FCVAR_PRIVATE, "BASS_CONFIG_ASYNCFILE_BUFFER length in bytes. Set to 0 to disable.");
+ConVar snd_dev_buffer("snd_dev_buffer", 30, FCVAR_DEFAULT | FCVAR_PRIVATE,
+                      "BASS_CONFIG_DEV_BUFFER length in milliseconds");
+ConVar snd_async_buffer("snd_async_buffer", 65536, FCVAR_DEFAULT | FCVAR_PRIVATE,
+                        "BASS_CONFIG_ASYNCFILE_BUFFER length in bytes. Set to 0 to disable.");
 
 ConVar snd_restrict_play_frame(
     "snd_restrict_play_frame", true, FCVAR_DEFAULT | FCVAR_PRIVATE,
@@ -310,144 +314,144 @@ void SoundEngine::updateOutputDevices(bool printInfo) {
 void display_bass_error() {
     auto code = BASS_ErrorGetCode();
     switch(code) {
-    case BASS_OK:
-        break;
-    case BASS_ERROR_MEM:
-        osu->getNotificationOverlay()->addNotification("BASS error: Memory error");
-        break;
-    case BASS_ERROR_FILEOPEN:
-        osu->getNotificationOverlay()->addNotification("BASS error: Can't open the file");
-        break;
-    case BASS_ERROR_DRIVER:
-        osu->getNotificationOverlay()->addNotification("BASS error: Can't find an available driver");
-        break;
-    case BASS_ERROR_BUFLOST:
-        osu->getNotificationOverlay()->addNotification("BASS error: The sample buffer was lost");
-        break;
-    case BASS_ERROR_HANDLE:
-        osu->getNotificationOverlay()->addNotification("BASS error: Invalid handle");
-        break;
-    case BASS_ERROR_FORMAT:
-        osu->getNotificationOverlay()->addNotification("BASS error: Unsupported sample format");
-        break;
-    case BASS_ERROR_POSITION:
-        osu->getNotificationOverlay()->addNotification("BASS error: Invalid position");
-        break;
-    case BASS_ERROR_INIT:
-        osu->getNotificationOverlay()->addNotification("BASS error: BASS_Init has not been successfully called");
-        break;
-    case BASS_ERROR_START:
-        osu->getNotificationOverlay()->addNotification("BASS error: BASS_Start has not been successfully called");
-        break;
-    case BASS_ERROR_SSL:
-        osu->getNotificationOverlay()->addNotification("BASS error: SSL/HTTPS support isn't available");
-        break;
-    case BASS_ERROR_REINIT:
-        osu->getNotificationOverlay()->addNotification("BASS error: Device needs to be reinitialized");
-        break;
-    case BASS_ERROR_ALREADY:
-        osu->getNotificationOverlay()->addNotification("BASS error: Already initialized");
-        break;
-    case BASS_ERROR_NOTAUDIO:
-        osu->getNotificationOverlay()->addNotification("BASS error: File does not contain audio");
-        break;
-    case BASS_ERROR_NOCHAN:
-        osu->getNotificationOverlay()->addNotification("BASS error: Can't get a free channel");
-        break;
-    case BASS_ERROR_ILLTYPE:
-        osu->getNotificationOverlay()->addNotification("BASS error: An illegal type was specified");
-        break;
-    case BASS_ERROR_ILLPARAM:
-        osu->getNotificationOverlay()->addNotification("BASS error: An illegal parameter was specified");
-        break;
-    case BASS_ERROR_NO3D:
-        osu->getNotificationOverlay()->addNotification("BASS error: No 3D support");
-        break;
-    case BASS_ERROR_NOEAX:
-        osu->getNotificationOverlay()->addNotification("BASS error: No EAX support");
-        break;
-    case BASS_ERROR_DEVICE:
-        osu->getNotificationOverlay()->addNotification("BASS error: Illegal device number");
-        break;
-    case BASS_ERROR_NOPLAY:
-        osu->getNotificationOverlay()->addNotification("BASS error: Not playing");
-        break;
-    case BASS_ERROR_FREQ:
-        osu->getNotificationOverlay()->addNotification("BASS error: Illegal sample rate");
-        break;
-    case BASS_ERROR_NOTFILE:
-        osu->getNotificationOverlay()->addNotification("BASS error: The stream is not a file stream");
-        break;
-    case BASS_ERROR_NOHW:
-        osu->getNotificationOverlay()->addNotification("BASS error: No hardware voices available");
-        break;
-    case BASS_ERROR_EMPTY:
-        osu->getNotificationOverlay()->addNotification("BASS error: The file has no sample data");
-        break;
-    case BASS_ERROR_NONET:
-        osu->getNotificationOverlay()->addNotification("BASS error: No internet connection could be opened");
-        break;
-    case BASS_ERROR_CREATE:
-        osu->getNotificationOverlay()->addNotification("BASS error: Couldn't create the file");
-        break;
-    case BASS_ERROR_NOFX:
-        osu->getNotificationOverlay()->addNotification("BASS error: Effects are not available");
-        break;
-    case BASS_ERROR_NOTAVAIL:
-        osu->getNotificationOverlay()->addNotification("BASS error: Requested data/action is not available");
-        break;
-    case BASS_ERROR_DECODE:
-        osu->getNotificationOverlay()->addNotification("BASS error: The channel is/isn't a decoding channel");
-        break;
-    case BASS_ERROR_DX:
-        osu->getNotificationOverlay()->addNotification("BASS error: A sufficient DirectX version is not installed");
-        break;
-    case BASS_ERROR_TIMEOUT:
-        osu->getNotificationOverlay()->addNotification("BASS error: Connection timeout");
-        break;
-    case BASS_ERROR_FILEFORM:
-        osu->getNotificationOverlay()->addNotification("BASS error: Unsupported file format");
-        break;
-    case BASS_ERROR_SPEAKER:
-        osu->getNotificationOverlay()->addNotification("BASS error: Unavailable speaker");
-        break;
-    case BASS_ERROR_VERSION:
-        osu->getNotificationOverlay()->addNotification("BASS error: Invalid BASS version");
-        break;
-    case BASS_ERROR_CODEC:
-        osu->getNotificationOverlay()->addNotification("BASS error: Codec is not available/supported");
-        break;
-    case BASS_ERROR_ENDED:
-        osu->getNotificationOverlay()->addNotification("BASS error: The channel/file has ended");
-        break;
-    case BASS_ERROR_BUSY:
-        osu->getNotificationOverlay()->addNotification("BASS error: The device is busy");
-        break;
-    case BASS_ERROR_UNSTREAMABLE:
-        osu->getNotificationOverlay()->addNotification("BASS error: Unstreamable file");
-        break;
-    case BASS_ERROR_PROTOCOL:
-        osu->getNotificationOverlay()->addNotification("BASS error: Unsupported protocol");
-        break;
-    case BASS_ERROR_DENIED:
-        osu->getNotificationOverlay()->addNotification("BASS error: Access Denied");
-        break;
-    case BASS_ERROR_WASAPI:
-        osu->getNotificationOverlay()->addNotification("WASAPI error: No WASAPI");
-        break;
-    case BASS_ERROR_WASAPI_BUFFER:
-        osu->getNotificationOverlay()->addNotification("WASAPI error: Invalid buffer size");
-        break;
-    case BASS_ERROR_WASAPI_CATEGORY:
-        osu->getNotificationOverlay()->addNotification("WASAPI error: Can't set category");
-        break;
-    case BASS_ERROR_WASAPI_DENIED:
-        osu->getNotificationOverlay()->addNotification("WASAPI error: Access denied");
-        break;
-    case BASS_ERROR_UNKNOWN:  // fallthrough
-    default:
-        osu->getNotificationOverlay()->addNotification("Unknown BASS error (%i)!", code);
-        break;
+        case BASS_OK:
+            break;
+        case BASS_ERROR_MEM:
+            osu->getNotificationOverlay()->addNotification("BASS error: Memory error");
+            break;
+        case BASS_ERROR_FILEOPEN:
+            osu->getNotificationOverlay()->addNotification("BASS error: Can't open the file");
+            break;
+        case BASS_ERROR_DRIVER:
+            osu->getNotificationOverlay()->addNotification("BASS error: Can't find an available driver");
+            break;
+        case BASS_ERROR_BUFLOST:
+            osu->getNotificationOverlay()->addNotification("BASS error: The sample buffer was lost");
+            break;
+        case BASS_ERROR_HANDLE:
+            osu->getNotificationOverlay()->addNotification("BASS error: Invalid handle");
+            break;
+        case BASS_ERROR_FORMAT:
+            osu->getNotificationOverlay()->addNotification("BASS error: Unsupported sample format");
+            break;
+        case BASS_ERROR_POSITION:
+            osu->getNotificationOverlay()->addNotification("BASS error: Invalid position");
+            break;
+        case BASS_ERROR_INIT:
+            osu->getNotificationOverlay()->addNotification("BASS error: BASS_Init has not been successfully called");
+            break;
+        case BASS_ERROR_START:
+            osu->getNotificationOverlay()->addNotification("BASS error: BASS_Start has not been successfully called");
+            break;
+        case BASS_ERROR_SSL:
+            osu->getNotificationOverlay()->addNotification("BASS error: SSL/HTTPS support isn't available");
+            break;
+        case BASS_ERROR_REINIT:
+            osu->getNotificationOverlay()->addNotification("BASS error: Device needs to be reinitialized");
+            break;
+        case BASS_ERROR_ALREADY:
+            osu->getNotificationOverlay()->addNotification("BASS error: Already initialized");
+            break;
+        case BASS_ERROR_NOTAUDIO:
+            osu->getNotificationOverlay()->addNotification("BASS error: File does not contain audio");
+            break;
+        case BASS_ERROR_NOCHAN:
+            osu->getNotificationOverlay()->addNotification("BASS error: Can't get a free channel");
+            break;
+        case BASS_ERROR_ILLTYPE:
+            osu->getNotificationOverlay()->addNotification("BASS error: An illegal type was specified");
+            break;
+        case BASS_ERROR_ILLPARAM:
+            osu->getNotificationOverlay()->addNotification("BASS error: An illegal parameter was specified");
+            break;
+        case BASS_ERROR_NO3D:
+            osu->getNotificationOverlay()->addNotification("BASS error: No 3D support");
+            break;
+        case BASS_ERROR_NOEAX:
+            osu->getNotificationOverlay()->addNotification("BASS error: No EAX support");
+            break;
+        case BASS_ERROR_DEVICE:
+            osu->getNotificationOverlay()->addNotification("BASS error: Illegal device number");
+            break;
+        case BASS_ERROR_NOPLAY:
+            osu->getNotificationOverlay()->addNotification("BASS error: Not playing");
+            break;
+        case BASS_ERROR_FREQ:
+            osu->getNotificationOverlay()->addNotification("BASS error: Illegal sample rate");
+            break;
+        case BASS_ERROR_NOTFILE:
+            osu->getNotificationOverlay()->addNotification("BASS error: The stream is not a file stream");
+            break;
+        case BASS_ERROR_NOHW:
+            osu->getNotificationOverlay()->addNotification("BASS error: No hardware voices available");
+            break;
+        case BASS_ERROR_EMPTY:
+            osu->getNotificationOverlay()->addNotification("BASS error: The file has no sample data");
+            break;
+        case BASS_ERROR_NONET:
+            osu->getNotificationOverlay()->addNotification("BASS error: No internet connection could be opened");
+            break;
+        case BASS_ERROR_CREATE:
+            osu->getNotificationOverlay()->addNotification("BASS error: Couldn't create the file");
+            break;
+        case BASS_ERROR_NOFX:
+            osu->getNotificationOverlay()->addNotification("BASS error: Effects are not available");
+            break;
+        case BASS_ERROR_NOTAVAIL:
+            osu->getNotificationOverlay()->addNotification("BASS error: Requested data/action is not available");
+            break;
+        case BASS_ERROR_DECODE:
+            osu->getNotificationOverlay()->addNotification("BASS error: The channel is/isn't a decoding channel");
+            break;
+        case BASS_ERROR_DX:
+            osu->getNotificationOverlay()->addNotification("BASS error: A sufficient DirectX version is not installed");
+            break;
+        case BASS_ERROR_TIMEOUT:
+            osu->getNotificationOverlay()->addNotification("BASS error: Connection timeout");
+            break;
+        case BASS_ERROR_FILEFORM:
+            osu->getNotificationOverlay()->addNotification("BASS error: Unsupported file format");
+            break;
+        case BASS_ERROR_SPEAKER:
+            osu->getNotificationOverlay()->addNotification("BASS error: Unavailable speaker");
+            break;
+        case BASS_ERROR_VERSION:
+            osu->getNotificationOverlay()->addNotification("BASS error: Invalid BASS version");
+            break;
+        case BASS_ERROR_CODEC:
+            osu->getNotificationOverlay()->addNotification("BASS error: Codec is not available/supported");
+            break;
+        case BASS_ERROR_ENDED:
+            osu->getNotificationOverlay()->addNotification("BASS error: The channel/file has ended");
+            break;
+        case BASS_ERROR_BUSY:
+            osu->getNotificationOverlay()->addNotification("BASS error: The device is busy");
+            break;
+        case BASS_ERROR_UNSTREAMABLE:
+            osu->getNotificationOverlay()->addNotification("BASS error: Unstreamable file");
+            break;
+        case BASS_ERROR_PROTOCOL:
+            osu->getNotificationOverlay()->addNotification("BASS error: Unsupported protocol");
+            break;
+        case BASS_ERROR_DENIED:
+            osu->getNotificationOverlay()->addNotification("BASS error: Access Denied");
+            break;
+        case BASS_ERROR_WASAPI:
+            osu->getNotificationOverlay()->addNotification("WASAPI error: No WASAPI");
+            break;
+        case BASS_ERROR_WASAPI_BUFFER:
+            osu->getNotificationOverlay()->addNotification("WASAPI error: Invalid buffer size");
+            break;
+        case BASS_ERROR_WASAPI_CATEGORY:
+            osu->getNotificationOverlay()->addNotification("WASAPI error: Can't set category");
+            break;
+        case BASS_ERROR_WASAPI_DENIED:
+            osu->getNotificationOverlay()->addNotification("WASAPI error: Access denied");
+            break;
+        case BASS_ERROR_UNKNOWN:  // fallthrough
+        default:
+            osu->getNotificationOverlay()->addNotification("Unknown BASS error (%i)!", code);
+            break;
     }
 }
 
@@ -619,11 +623,13 @@ bool SoundEngine::initializeOutputDevice(OUTPUT_DEVICE device) {
         auto flags = BASS_WASAPI_RAW | BASS_MIXER_NONSTOP | BASS_WASAPI_RAW;
         if(convar->getConVarByName("win_snd_wasapi_exclusive")->getBool()) {
             // BASS_WASAPI_EXCLUSIVE makes neosu have exclusive output to the sound card
-            // BASS_WASAPI_AUTOFORMAT chooses the best matching sample format, BASSWASAPI doesn't resample in exclusive mode
+            // BASS_WASAPI_AUTOFORMAT chooses the best matching sample format, BASSWASAPI doesn't resample in exclusive
+            // mode
             flags |= BASS_WASAPI_EXCLUSIVE | BASS_WASAPI_AUTOFORMAT;
         }
 
-        if(!BASS_WASAPI_Init(device.id, 0, 0, flags, bufferSize, updatePeriod, WASAPIPROC_BASS, (void *)g_bassOutputMixer)) {
+        if(!BASS_WASAPI_Init(device.id, 0, 0, flags, bufferSize, updatePeriod, WASAPIPROC_BASS,
+                             (void *)g_bassOutputMixer)) {
             const int errorCode = BASS_ErrorGetCode();
             ready_since = -1.0;
             debugLog("BASS_WASAPI_Init() failed.\n");
@@ -738,6 +744,10 @@ bool SoundEngine::play(Sound *snd, float pan, float pitch) {
         snd->m_fLastPlayTime = snd->m_fChannelCreationTime;
     }
 
+    if(Osu::debug->getBool()) {
+        debugLog("Playing %s\n", snd->getFilePath().c_str());
+    }
+
     return true;
 }
 

+ 0 - 7
src/GUI/CBaseUIButton.cpp

@@ -1,10 +1,3 @@
-//================ Copyright (c) 2013, PG, All rights reserved. =================//
-//
-// Purpose:		a simple button
-//
-// $NoKeywords: $button
-//===============================================================================//
-
 #include "CBaseUIButton.h"
 
 #include "Engine.h"

+ 1 - 12
src/GUI/CBaseUIButton.h

@@ -1,13 +1,4 @@
-//================ Copyright (c) 2013, PG, All rights reserved. =================//
-//
-// Purpose:		a simple button
-//
-// $NoKeywords: $button
-//===============================================================================//
-
-#ifndef CBASEUIBUTTON_H
-#define CBASEUIBUTTON_H
-
+#pragma once
 #include "CBaseUIElement.h"
 
 class McFont;
@@ -133,5 +124,3 @@ class CBaseUIButton : public CBaseUIElement {
     ButtonClickVoidCallback m_clickVoidCallback;
     ButtonClickCallback m_clickCallback;
 };
-
-#endif

+ 24 - 9
src/GUI/CBaseUISlider.cpp

@@ -4,6 +4,9 @@
 #include "Engine.h"
 #include "Keyboard.h"
 #include "Mouse.h"
+#include "Osu.h"
+#include "Skin.h"
+#include "SoundEngine.h"
 
 using namespace std;
 
@@ -86,33 +89,37 @@ void CBaseUISlider::mouse_update(bool *propagate_clicks) {
     if(m_bActive) {
         // calculate new values
         if(!m_bHorizontal) {
-            if(m_bAnimated)
+            if(m_bAnimated) {
                 anim->moveQuadOut(&m_vBlockPos.y,
                                   clamp<float>(mousepos.y - m_vGrabBackup.y, 0.0f, m_vSize.y - m_vBlockSize.y), 0.10f,
                                   0, true);
-            else
+            } else {
                 m_vBlockPos.y = clamp<float>(mousepos.y - m_vGrabBackup.y, 0.0f, m_vSize.y - m_vBlockSize.y);
+            }
 
             m_fCurPercent = clamp<float>(1.0f - (std::round(m_vBlockPos.y) / (m_vSize.y - m_vBlockSize.y)), 0.0f, 1.0f);
         } else {
-            if(m_bAnimated)
+            if(m_bAnimated) {
                 anim->moveQuadOut(&m_vBlockPos.x,
                                   clamp<float>(mousepos.x - m_vGrabBackup.x, 0.0f, m_vSize.x - m_vBlockSize.x), 0.10f,
                                   0, true);
-            else
+            } else {
                 m_vBlockPos.x = clamp<float>(mousepos.x - m_vGrabBackup.x, 0.0f, m_vSize.x - m_vBlockSize.x);
+            }
 
             m_fCurPercent = clamp<float>(std::round(m_vBlockPos.x) / (m_vSize.x - m_vBlockSize.x), 0.0f, 1.0f);
         }
 
         // set new value
         if(m_bAnimated) {
-            if(m_bLiveUpdate)
+            if(m_bLiveUpdate) {
                 setValue(lerp<float>(m_fMinValue, m_fMaxValue, m_fCurPercent), false);
-            else
+            } else {
                 m_fCurValue = lerp<float>(m_fMinValue, m_fMaxValue, m_fCurPercent);
-        } else
+            }
+        } else {
             setValue(lerp<float>(m_fMinValue, m_fMaxValue, m_fCurPercent), false);
+        }
 
         m_bHasChanged = true;
     } else {
@@ -122,10 +129,11 @@ void CBaseUISlider::mouse_update(bool *propagate_clicks) {
             if(wheelDelta != 0) {
                 const int multiplier = max(1, std::abs(wheelDelta) / 120);
 
-                if(wheelDelta > 0)
+                if(wheelDelta > 0) {
                     setValue(m_fCurValue + m_fKeyDelta * multiplier, m_bAnimated);
-                else
+                } else {
                     setValue(m_fCurValue - m_fKeyDelta * multiplier, m_bAnimated);
+                }
             }
         }
     }
@@ -210,6 +218,13 @@ CBaseUISlider *CBaseUISlider::setValue(float value, bool animate, bool call_call
 
     if(call_callback && changeCallbackCheck && m_sliderChangeCallback != NULL) {
         m_sliderChangeCallback(this);
+
+        if(m_bHasChanged) {
+            if(m_fLastSoundPlayTime + 0.05f < engine->getTime()) {
+                engine->getSound()->play(osu->getSkin()->m_sliderbar, 0.f, 1.f + 0.05f * percent);
+                m_fLastSoundPlayTime = engine->getTime();
+            }
+        }
     }
 
     updateBlockPos();

+ 2 - 11
src/GUI/CBaseUISlider.h

@@ -1,16 +1,8 @@
-//================ Copyright (c) 2012, PG, All rights reserved. =================//
-//
-// Purpose:		a simple slider
-//
-// $NoKeywords: $
-//===============================================================================//
+#pragma once
 
 // TODO: fix vertical sliders
 // TODO: this entire class is a mess
 
-#ifndef CBASEUISLIDER_H
-#define CBASEUISLIDER_H
-
 #include "CBaseUIElement.h"
 
 class CBaseUISlider : public CBaseUIElement {
@@ -103,8 +95,7 @@ class CBaseUISlider : public CBaseUIElement {
     float m_fPrevValue;
 
     float m_fKeyDelta;
+    float m_fLastSoundPlayTime = 0.f;
 
     SliderChangeCallback m_sliderChangeCallback;
 };
-
-#endif

+ 18 - 0
src/GUI/CBaseUITextbox.cpp

@@ -12,7 +12,10 @@
 #include "Engine.h"
 #include "Keyboard.h"
 #include "Mouse.h"
+#include "Osu.h"
 #include "ResourceManager.h"
+#include "Skin.h"
+#include "SoundEngine.h"
 
 using namespace std;
 
@@ -322,6 +325,7 @@ void CBaseUITextbox::onKeyDown(KeyboardEvent &e) {
 
     switch(e.getKeyCode()) {
         case KEY_DELETE:
+            engine->getSound()->play(osu->getSkin()->m_deletingText);
             if(m_sText.length() > 0) {
                 if(hasSelectedText())
                     handleDeleteSelectedText();
@@ -344,6 +348,7 @@ void CBaseUITextbox::onKeyDown(KeyboardEvent &e) {
             break;
 
         case KEY_BACKSPACE:
+            engine->getSound()->play(osu->getSkin()->m_deletingText);
             if(m_sText.length() > 0) {
                 if(hasSelectedText())
                     handleDeleteSelectedText();
@@ -392,6 +397,8 @@ void CBaseUITextbox::onKeyDown(KeyboardEvent &e) {
             tickCaret();
             handleCaretKeyboardMove();
             updateCaretX();
+
+            engine->getSound()->play(osu->getSkin()->m_movingTextCursor);
         } break;
 
         case KEY_RIGHT: {
@@ -408,6 +415,8 @@ void CBaseUITextbox::onKeyDown(KeyboardEvent &e) {
             tickCaret();
             handleCaretKeyboardMove();
             updateCaretX();
+
+            engine->getSound()->play(osu->getSkin()->m_movingTextCursor);
         } break;
 
         case KEY_C:
@@ -434,6 +443,7 @@ void CBaseUITextbox::onKeyDown(KeyboardEvent &e) {
 
         case KEY_X:
             if(engine->getKeyboard()->isControlDown() && hasSelectedText()) {
+                engine->getSound()->play(osu->getSkin()->m_deletingText);
                 env->setClipBoardText(getSelectedText());
                 handleDeleteSelectedText();
             }
@@ -445,6 +455,8 @@ void CBaseUITextbox::onKeyDown(KeyboardEvent &e) {
             tickCaret();
             handleCaretKeyboardMove();
             updateCaretX();
+
+            engine->getSound()->play(osu->getSkin()->m_movingTextCursor);
             break;
 
         case KEY_END:
@@ -453,6 +465,8 @@ void CBaseUITextbox::onKeyDown(KeyboardEvent &e) {
             tickCaret();
             handleCaretKeyboardMove();
             updateCaretX();
+
+            engine->getSound()->play(osu->getSkin()->m_movingTextCursor);
             break;
     }
 }
@@ -484,6 +498,10 @@ void CBaseUITextbox::onChar(KeyboardEvent &e) {
     }
 
     tickCaret();
+
+    Sound *sounds[] = {osu->getSkin()->m_typing1, osu->getSkin()->m_typing2, osu->getSkin()->m_typing3,
+                       osu->getSkin()->m_typing4};
+    engine->getSound()->play(sounds[rand() % 4]);
 }
 
 void CBaseUITextbox::handleCaretKeyboardMove() {

+ 1 - 12
src/GUI/CBaseUITextbox.h

@@ -1,13 +1,4 @@
-//================ Copyright (c) 2015, PG, All rights reserved. =================//
-//
-// Purpose:		a not so simple textbox, revision 4
-//
-// $NoKeywords: $
-//===============================================================================//
-
-#ifndef CBASEUITEXTBOX_H
-#define CBASEUITEXTBOX_H
-
+#pragma once
 #include "CBaseUIElement.h"
 
 class McFont;
@@ -148,5 +139,3 @@ class CBaseUITextbox : public CBaseUIElement {
     int m_iSelectEnd;
     int m_iSelectX;
 };
-
-#endif