diff --git a/CMakeLists.txt b/CMakeLists.txt index eb7e62076e..016066d1da 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -338,6 +338,48 @@ if (DUSK_MOVIE_SUPPORT) list(APPEND GAME_COMPILE_DEFS MOVIE_SUPPORT=1) endif () +option(DUSK_ENABLE_DISCORD_RPC "Enable Discord Rich Presence support" ON) +if (DUSK_ENABLE_DISCORD_RPC AND NOT ANDROID AND NOT IOS AND NOT TVOS) + + FetchContent_Populate(discord_rpc + URL https://github.com/discord/discord-rpc/archive/refs/tags/v3.4.0.tar.gz + URL_HASH SHA256=e13427019027acd187352dacba6c65953af66fdf3c35fcf38fc40b454a9d7855 + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + ) + # RapidJSON is a git submodule absent from the discord-rpc tarball; fetch separately. + FetchContent_Populate(rapidjson + URL https://github.com/Tencent/rapidjson/archive/refs/tags/v1.1.0.tar.gz + URL_HASH SHA256=bf7ced29704a1e696fbccf2a2b4ea068e7774fa37f6d7dd4039d0787f8bed98e + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + ) + + if (NOT TARGET discord-rpc) + set(_drpc ${discord_rpc_SOURCE_DIR}/src) + set(_drpc_src + ${_drpc}/discord_rpc.cpp + ${_drpc}/rpc_connection.cpp + ${_drpc}/serialization.cpp + ) + if (WIN32) + list(APPEND _drpc_src ${_drpc}/connection_win.cpp ${_drpc}/discord_register_win.cpp) + elseif (APPLE) + list(APPEND _drpc_src ${_drpc}/connection_unix.cpp ${_drpc}/discord_register_osx.m) + else () + list(APPEND _drpc_src ${_drpc}/connection_unix.cpp ${_drpc}/discord_register_linux.cpp) + endif () + add_library(discord-rpc STATIC ${_drpc_src}) + target_include_directories(discord-rpc PUBLIC + ${discord_rpc_SOURCE_DIR}/include + ${rapidjson_SOURCE_DIR}/include + ) + if (UNIX) + target_link_libraries(discord-rpc PUBLIC pthread) + endif () + endif () + list(APPEND GAME_LIBS discord-rpc) + list(APPEND GAME_COMPILE_DEFS DUSK_DISCORD_RPC=1) +endif () + # Edit & Continue if (MSVC) if ("${CMAKE_MSVC_DEBUG_INFORMATION_FORMAT}" STREQUAL "" AND CMAKE_BUILD_TYPE STREQUAL "Debug") diff --git a/CMakePresets.json b/CMakePresets.json index 89c78818f9..92799249a9 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -358,7 +358,10 @@ "inherits": [ "relwithdebinfo", "ci" - ] + ], + "cacheVariables": { + "AURORA_SDL3_PROVIDER": "vendor" + } }, { "name": "x-linux-ci-gcc", diff --git a/files.cmake b/files.cmake index d315dbcafd..7554c89826 100644 --- a/files.cmake +++ b/files.cmake @@ -1391,4 +1391,5 @@ set(DUSK_FILES src/dusk/mod_loader.cpp src/dusk/imgui/ImGuiMenuMods.cpp src/dusk/gx_helper.cpp + src/dusk/discord_presence.cpp ) diff --git a/include/dusk/discord_presence.hpp b/include/dusk/discord_presence.hpp new file mode 100644 index 0000000000..899b2ad5f9 --- /dev/null +++ b/include/dusk/discord_presence.hpp @@ -0,0 +1,18 @@ +#pragma once + +#ifdef DUSK_DISCORD_RPC + +namespace dusk { +namespace discord { + +void Initialize(); + +void RunCallbacks(); + +void UpdatePresence(); + +void Shutdown(); +} +} + +#endif // DUSK_DISCORD_RPC diff --git a/src/d/actor/d_a_alink_crawl.inc b/src/d/actor/d_a_alink_crawl.inc index 4a657c1de8..2dbf88d3e1 100644 --- a/src/d/actor/d_a_alink_crawl.inc +++ b/src/d/actor/d_a_alink_crawl.inc @@ -37,18 +37,40 @@ void daAlink_c::setCrawlMoveDirectionArrow() { } if (field_0x3198 & 4) { - if (!bvar) { - direction |= data_80452F38; - } else { - direction |= data_80452F39; + #if TARGET_PC + if (dusk::getSettings().game.enableMirrorMode) { + if (!bvar) { + direction |= data_80452F39; + } else { + direction |= data_80452F38; + } + } else + #endif + { + if (!bvar) { + direction |= data_80452F38; + } else { + direction |= data_80452F39; + } } } if (field_0x3198 & 8) { - if (!bvar) { - direction |= data_80452F39; - } else { - direction |= data_80452F38; + #if TARGET_PC + if (dusk::getSettings().game.enableMirrorMode) { + if (!bvar) { + direction |= data_80452F38; + } else { + direction |= data_80452F39; + } + } else + #endif + { + if (!bvar) { + direction |= data_80452F39; + } else { + direction |= data_80452F38; + } } } diff --git a/src/dusk/discord_presence.cpp b/src/dusk/discord_presence.cpp new file mode 100644 index 0000000000..3b12075c8b --- /dev/null +++ b/src/dusk/discord_presence.cpp @@ -0,0 +1,122 @@ +#ifdef DUSK_DISCORD_RPC + +#include "dusk/discord_presence.hpp" +#include "dusk/logging.h" +#include "dusk/main.h" +#include "dusk/map_loader_definitions.h" +#include "d/d_com_inf_game.h" +#include "discord_rpc.h" +#include "fmt/format.h" + +#include +#include +#include + +namespace dusk { +namespace discord { + +static int64_t g_startTime = 0; +static bool g_initialized = false; +static const char* APPLICATION_ID = "1495632471994405035"; + +static void OnReady(const DiscordUser* user) { + DuskLog.info("Discord: Connected as {}", user->username); +} + +static void OnDisconnected(int errorCode, const char* message) { + DuskLog.warn("Discord: Disconnected ({}: {})", errorCode, message); +} + +static void OnError(int errorCode, const char* message) { + DuskLog.warn("Discord: Error ({}: {})", errorCode, message); +} + +static const char* LookupMapName(const char* mapFile) { + if (!mapFile || mapFile[0] == '\0') return nullptr; + for (const auto& region : gameRegions) { + for (const auto& map : region.maps) { + if (map.mapFile && strcmp(mapFile, map.mapFile) == 0) { + return map.mapName; + } + } + } + return nullptr; +} + +void Initialize() { + g_startTime = static_cast( + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch() + ).count() + ); + + DiscordEventHandlers handlers{}; + handlers.ready = OnReady; + handlers.disconnected = OnDisconnected; + handlers.errored = OnError; + Discord_Initialize(APPLICATION_ID, &handlers, 0, nullptr); + g_initialized = true; + + DuskLog.info("Discord Rich Presence initialized"); +} + +void RunCallbacks() { + if (!g_initialized) return; + Discord_RunCallbacks(); +} + +void UpdatePresence() { + if (!g_initialized) return; + + static auto lastUpdate = std::chrono::steady_clock::time_point{}; + const auto now = std::chrono::steady_clock::now(); + if (now - lastUpdate < std::chrono::seconds(15)) return; + lastUpdate = now; + + static std::string detailsBuf; + static std::string stateBuf; + + DiscordRichPresence presence{}; + presence.startTimestamp = g_startTime; + presence.largeImageKey = "icon"; + presence.largeImageText = "Dusk"; + + if (dusk::IsGameLaunched) { + const char* stageName = dComIfGp_getLastPlayStageName(); + + // stageName is empty until a room is actually entered + if (stageName[0] != '\0') { + const char* locationName = LookupMapName(stageName); + + if (locationName) { + detailsBuf = locationName; + } + else { + detailsBuf = "Twilight Princess"; + } + + presence.details = detailsBuf.c_str(); + + stateBuf = fmt::format(FMT_STRING("{}/{} \u2665 | {} Rupees"), + dComIfGs_getLife() / 4, dComIfGs_getMaxLife() / 5, dComIfGs_getRupee()); + + presence.state = stateBuf.c_str(); + } + } + + Discord_UpdatePresence(&presence); + DuskLog.debug("Discord Rich Presence sent"); +} + +void Shutdown() { + if (!g_initialized) return; + Discord_ClearPresence(); + Discord_Shutdown(); + g_initialized = false; + DuskLog.info("Discord Rich Presence shut down"); +} + +} // namespace discord +} // namespace dusk + +#endif // DUSK_DISCORD_RPC diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index b560169445..6488f183a2 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -71,6 +71,7 @@ #include "dusk/config.hpp" #include "dusk/settings.h" #include "dusk/imgui/ImGuiConsole.hpp" +#include "dusk/discord_presence.hpp" #include "tracy/Tracy.hpp" #include "f_pc/f_pc_draw.h" @@ -292,6 +293,11 @@ void main01(void) { aurora_end_frame(); FrameMark; + +#ifdef DUSK_DISCORD_RPC + dusk::discord::RunCallbacks(); + dusk::discord::UpdatePresence(); +#endif } while (dusk::IsRunning); exit:; @@ -536,6 +542,10 @@ int game_main(int argc, char* argv[]) { auroraInfo = aurora_initialize(argc, argv, &config); +#ifdef DUSK_DISCORD_RPC + dusk::discord::Initialize(); +#endif + VISetWindowTitle( fmt::format("Dusk {} [{}]", DUSK_WC_DESCRIBE, dusk::backend_name(auroraInfo.backend)) .c_str()); @@ -569,6 +579,9 @@ int game_main(int argc, char* argv[]) { // pre game launch ui main loop if (!launchUILoop()) { dusk::ShutdownCrashReporting(); +#ifdef DUSK_DISCORD_RPC + dusk::discord::Shutdown(); +#endif aurora_shutdown(); return 0; } @@ -615,6 +628,9 @@ int game_main(int argc, char* argv[]) { // Notifies all CVs and causes threads to exit OSResetSystem(OS_RESET_SHUTDOWN, 0, 0); +#ifdef DUSK_DISCORD_RPC + dusk::discord::Shutdown(); +#endif aurora_shutdown(); return 0;