diff --git a/Source/Core/DolphinQt/CMakeLists.txt b/Source/Core/DolphinQt/CMakeLists.txt index eff3bdd840..b54d09ebf6 100644 --- a/Source/Core/DolphinQt/CMakeLists.txt +++ b/Source/Core/DolphinQt/CMakeLists.txt @@ -8,6 +8,7 @@ set(CMAKE_AUTOMOC ON) add_executable(dolphin-emu AboutDialog.cpp CheatsManager.cpp + DiscordHandler.cpp FIFO/FIFOPlayerWindow.cpp FIFO/FIFOAnalyzer.cpp HotkeyScheduler.cpp diff --git a/Source/Core/DolphinQt/DolphinQt.vcxproj b/Source/Core/DolphinQt/DolphinQt.vcxproj index 0228011308..3e5ad13b14 100644 --- a/Source/Core/DolphinQt/DolphinQt.vcxproj +++ b/Source/Core/DolphinQt/DolphinQt.vcxproj @@ -107,6 +107,7 @@ + @@ -175,6 +176,7 @@ + @@ -320,6 +322,7 @@ + diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index c77b08718f..4804b469bc 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -67,6 +67,7 @@ #include "DolphinQt/Debugger/MemoryWidget.h" #include "DolphinQt/Debugger/RegisterWidget.h" #include "DolphinQt/Debugger/WatchWidget.h" +#include "DolphinQt/DiscordHandler.h" #include "DolphinQt/FIFO/FIFOPlayerWindow.h" #include "DolphinQt/GCMemcardManager.h" #include "DolphinQt/GameList/GameList.h" @@ -1043,7 +1044,12 @@ void MainWindow::BootWiiSystemMenu() void MainWindow::NetPlayInit() { m_netplay_setup_dialog = new NetPlaySetupDialog(this); +<<<<<<< HEAD:Source/Core/DolphinQt/MainWindow.cpp m_netplay_dialog = new NetPlayDialog; +======= + m_netplay_dialog = new NetPlayDialog(this); + m_netplay_discord = new DiscordHandler(); +>>>>>>> Add Discord Join Net Play functionally:Source/Core/DolphinQt2/MainWindow.cpp connect(m_netplay_dialog, &NetPlayDialog::Boot, this, [this](const QString& path) { StartGame(path); }); @@ -1051,6 +1057,10 @@ void MainWindow::NetPlayInit() connect(m_netplay_dialog, &NetPlayDialog::rejected, this, &MainWindow::NetPlayQuit); connect(m_netplay_setup_dialog, &NetPlaySetupDialog::Join, this, &MainWindow::NetPlayJoin); connect(m_netplay_setup_dialog, &NetPlaySetupDialog::Host, this, &MainWindow::NetPlayHost); + connect(m_netplay_discord, &DiscordHandler::Join, this, &MainWindow::NetPlayJoin); + + Discord::InitNetPlayFunctionality([this] { m_netplay_discord->DiscordJoin(); }); + m_netplay_discord->Start(); } bool MainWindow::NetPlayJoin() @@ -1169,6 +1179,7 @@ void MainWindow::NetPlayQuit() { Settings::Instance().ResetNetPlayClient(); Settings::Instance().ResetNetPlayServer(); + Discord::UpdateDiscordPresence(); } void MainWindow::EnableScreenSaver(bool enable) diff --git a/Source/Core/DolphinQt/MainWindow.h b/Source/Core/DolphinQt/MainWindow.h index 8c387a5f20..360e7343de 100644 --- a/Source/Core/DolphinQt/MainWindow.h +++ b/Source/Core/DolphinQt/MainWindow.h @@ -19,6 +19,7 @@ struct BootParameters; class CheatsManager; class CodeWidget; class ControllersWindow; +class DiscordHandler; class DragEnterEvent; class FIFOPlayerWindow; class GameList; @@ -183,6 +184,7 @@ private: ControllersWindow* m_controllers_window; SettingsWindow* m_settings_window; NetPlayDialog* m_netplay_dialog; + DiscordHandler* m_netplay_discord; NetPlaySetupDialog* m_netplay_setup_dialog; GraphicsWindow* m_graphics_window; static constexpr int num_gc_controllers = 4; diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp index e5af25d6cf..5224fb4820 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp @@ -29,6 +29,7 @@ #include "Common/CommonPaths.h" #include "Common/Config/Config.h" +#include "Common/HttpRequest.h" #include "Common/TraversalClient.h" #include "Core/Config/GraphicsSettings.h" @@ -49,6 +50,7 @@ #include "DolphinQt/Resources.h" #include "DolphinQt/Settings.h" +#include "UICommon/DiscordPresence.h" #include "UICommon/GameFile.h" #include "VideoCommon/VideoConfig.h" @@ -418,6 +420,7 @@ void NetPlayDialog::show(std::string nickname, bool use_traversal) m_nickname = nickname; m_use_traversal = use_traversal; m_buffer_size = 0; + m_old_player_count = 0; m_room_box->clear(); m_chat_edit->clear(); @@ -565,6 +568,41 @@ void NetPlayDialog::UpdateGUI() m_hostcode_action_button->setText(tr("Copy")); m_hostcode_action_button->setEnabled(true); } + + if (m_old_player_count != player_count) + { + if (m_use_traversal) + { + const auto host_id = g_TraversalClient->GetHostID(); + Discord::UpdateDiscordPresence(player_count, Discord::SecretType::RoomID, + std::string(host_id.begin(), host_id.end())); + } + else + { + // Temporary soluation + // To Do: Don't rely on a service that Dolphin devs aren't in control of. Ask one of the + // project managers about this. + + Common::HttpRequest request; + Common::HttpRequest::Response response = request.Get("https://www.myexternalip.com/raw"); + + if (!response.has_value()) + return; + + // The response ends with a /n and the - 1 removes that + std::string exernalIPAddress = std::string(response->begin(), response->end() - 1); + std::string port = std::to_string(Settings::Instance().GetNetPlayServer()->GetPort()); + std::string secret; + secret.reserve(exernalIPAddress.length() + 1 + port.length()); + secret += exernalIPAddress; + secret += ':'; + secret += port; + + Discord::UpdateDiscordPresence(player_count, Discord::SecretType::IPAddress, secret); + } + + m_old_player_count = player_count; + } } // NetPlayUI methods diff --git a/Source/Core/DolphinQt2/DiscordHandler.cpp b/Source/Core/DolphinQt2/DiscordHandler.cpp new file mode 100644 index 0000000000..bad505f2de --- /dev/null +++ b/Source/Core/DolphinQt2/DiscordHandler.cpp @@ -0,0 +1,45 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt2/DiscordHandler.h" + +#include "Common/Thread.h" + +#include "UICommon/DiscordPresence.h" + +DiscordHandler::DiscordHandler() = default; + +DiscordHandler::~DiscordHandler() +{ + Stop(); +} + +void DiscordHandler::Start() +{ + m_stop_requested.Set(false); + m_thread = std::thread(&DiscordHandler::Run, this); +} + +void DiscordHandler::Stop() +{ + m_stop_requested.Set(true); + + if (m_thread.joinable()) + m_thread.join(); +} + +void DiscordHandler::DiscordJoin() +{ + emit DiscordHandler::Join(); +} + +void DiscordHandler::Run() +{ + while (!m_stop_requested.IsSet()) + { + Common::SleepCurrentThread(1000 * 2); + + Discord::CallPendingCallbacks(); + } +} diff --git a/Source/Core/DolphinQt2/DiscordHandler.h b/Source/Core/DolphinQt2/DiscordHandler.h new file mode 100644 index 0000000000..1cc6787b46 --- /dev/null +++ b/Source/Core/DolphinQt2/DiscordHandler.h @@ -0,0 +1,30 @@ +// Copyright 2018 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +#include + +#include "Common/Flag.h" + +class DiscordHandler : public QObject +{ + Q_OBJECT +public: + explicit DiscordHandler(); + ~DiscordHandler(); + + void Start(); + void Stop(); + void DiscordJoin(); +signals: + void Join(); + +private: + void Run(); + Common::Flag m_stop_requested; + std::thread m_thread; +}; diff --git a/Source/Core/UICommon/DiscordPresence.cpp b/Source/Core/UICommon/DiscordPresence.cpp index dece60dcbc..afd57d87c7 100644 --- a/Source/Core/UICommon/DiscordPresence.cpp +++ b/Source/Core/UICommon/DiscordPresence.cpp @@ -2,10 +2,14 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. -#include "UICommon/DiscordPresence.h" +#include "Common/Hash.h" + +#include "Core/Config/NetplaySettings.h" #include "Core/Config/UISettings.h" #include "Core/ConfigManager.h" +#include "UICommon/DiscordPresence.h" + #ifdef USE_DISCORD_PRESENCE #include @@ -15,6 +19,61 @@ namespace Discord { +#ifdef USE_DISCORD_PRESENCE +static JoinFunction join_function = nullptr; +static const char* username = ""; + +static void HandleDiscordReady(const DiscordUser* user) +{ + username = user->username; +} + +static void HandleDiscordJoin(const char* join_secret) +{ + if (join_function == nullptr) + return; + + if (Config::Get(Config::NETPLAY_NICKNAME) == Config::NETPLAY_NICKNAME.default_value) + Config::SetBaseOrCurrent(Config::NETPLAY_NICKNAME, username); + + std::string secret(join_secret); + + size_t offset = 0; + std::string type = secret.substr(offset, secret.find('\n')); + offset += type.length() + 1; + + switch (static_cast(std::stol(type))) + { + default: + case SecretType::Empty: + return; + + case SecretType::IPAddress: + { + Config::SetBaseOrCurrent(Config::NETPLAY_TRAVERSAL_CHOICE, "direct"); + + std::string host = secret.substr(offset, secret.find_last_of(':') - offset); + Config::SetBaseOrCurrent(Config::NETPLAY_HOST_CODE, host); + + offset += host.length(); + if (secret[offset] == ':') + Config::SetBaseOrCurrent(Config::NETPLAY_CONNECT_PORT, std::stoul(secret.substr(offset + 1))); + } + break; + + case SecretType::RoomID: + { + Config::SetBaseOrCurrent(Config::NETPLAY_TRAVERSAL_CHOICE, "traversal"); + + Config::SetBaseOrCurrent(Config::NETPLAY_HOST_CODE, secret.substr(offset)); + } + break; + } + + join_function(); +} +#endif + void Init() { #ifdef USE_DISCORD_PRESENCE @@ -22,13 +81,34 @@ void Init() return; DiscordEventHandlers handlers = {}; + + handlers.ready = HandleDiscordReady; + handlers.joinGame = HandleDiscordJoin; // The number is the client ID for Dolphin, it's used for images and the appication name Discord_Initialize("455712169795780630", &handlers, 1, nullptr); UpdateDiscordPresence(); #endif } -void UpdateDiscordPresence() +void CallPendingCallbacks() +{ +#ifdef USE_DISCORD_PRESENCE + if (!Config::Get(Config::MAIN_USE_DISCORD_PRESENCE)) + return; + + Discord_RunCallbacks(); + +#endif +} + +void InitNetPlayFunctionality(const JoinFunction& join) +{ +#ifdef USE_DISCORD_PRESENCE + join_function = std::move(join); +#endif +} + +void UpdateDiscordPresence(const int party_size, SecretType type, const std::string& secret) { #ifdef USE_DISCORD_PRESENCE if (!Config::Get(Config::MAIN_USE_DISCORD_PRESENCE)) @@ -41,6 +121,44 @@ void UpdateDiscordPresence() discord_presence.largeImageText = "Dolphin is an emulator for the GameCube and the Wii."; discord_presence.details = title.empty() ? "Not in-game" : title.c_str(); discord_presence.startTimestamp = std::time(nullptr); + + if (0 < party_size) + { + if (party_size < 4) + { + discord_presence.state = "In a party"; + discord_presence.partySize = party_size; + discord_presence.partyMax = 4; + } + else + { + // others can still join to spectate + discord_presence.state = "In a full party"; + discord_presence.partySize = party_size; + // Note: joining still works without partyMax + } + } + + std::string party_ID; + std::string secret_final; + if (type != SecretType::Empty) + { + // Declearing party_ID or secret_final here will deallocate the variable before passing the + // values over to Discord_UpdatePresence. + + const size_t secret_length = secret.length(); + party_ID = std::to_string( + Common::HashAdler32(reinterpret_cast(secret.c_str()), secret_length)); + + const std::string secret_type = std::to_string(static_cast(type)); + secret_final.reserve(secret_type.length() + 1 + secret_length); + secret_final += secret_type; + secret_final += '\n'; + secret_final += secret; + } + discord_presence.partyId = party_ID.c_str(); + discord_presence.joinSecret = secret_final.c_str(); + Discord_UpdatePresence(&discord_presence); #endif } diff --git a/Source/Core/UICommon/DiscordPresence.h b/Source/Core/UICommon/DiscordPresence.h index 9c4673f885..f2e40bb0b6 100644 --- a/Source/Core/UICommon/DiscordPresence.h +++ b/Source/Core/UICommon/DiscordPresence.h @@ -4,10 +4,24 @@ #pragma once +#include + namespace Discord { +using JoinFunction = std::function; + +enum class SecretType : char +{ + Empty, + IPAddress, + RoomID, +}; + void Init(); -void UpdateDiscordPresence(); +void InitNetPlayFunctionality(const JoinFunction& join); +void CallPendingCallbacks(); +void UpdateDiscordPresence(int party_size = 0, SecretType type = SecretType::Empty, + const std::string& secret = {}); void Shutdown(); void SetDiscordPresenceEnabled(bool enabled); } // namespace Discord