diff --git a/examples/unrealstatus/Plugins/DiscordRpc/DiscordRpc.uplugin b/examples/unrealstatus/Plugins/DiscordRpc/DiscordRpc.uplugin new file mode 100644 index 0000000..59e49d9 --- /dev/null +++ b/examples/unrealstatus/Plugins/DiscordRpc/DiscordRpc.uplugin @@ -0,0 +1,23 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "Discord RPC", + "Description": "Wrap the Discord RPC library.", + "Category": "Messaging", + "CreatedBy": "Chris Marsh ", + "CreatedByURL": "https://discordapp.com/", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "CanContainContent": true, + "IsBetaVersion": true, + "Installed": false, + "Modules": [ + { + "Name": "DiscordRpc", + "Type": "Runtime", + "LoadingPhase": "PreDefault" + } + ] +} \ No newline at end of file diff --git a/examples/unrealstatus/Plugins/DiscordRpc/Resources/Icon128.png b/examples/unrealstatus/Plugins/DiscordRpc/Resources/Icon128.png new file mode 100644 index 0000000..8b7f8e1 Binary files /dev/null and b/examples/unrealstatus/Plugins/DiscordRpc/Resources/Icon128.png differ diff --git a/examples/unrealstatus/Plugins/DiscordRpc/Resources/discord.png b/examples/unrealstatus/Plugins/DiscordRpc/Resources/discord.png new file mode 100644 index 0000000..8b7f8e1 Binary files /dev/null and b/examples/unrealstatus/Plugins/DiscordRpc/Resources/discord.png differ diff --git a/examples/unrealstatus/Plugins/DiscordRpc/Source/DiscordRpc/DiscordRpc.Build.cs b/examples/unrealstatus/Plugins/DiscordRpc/Source/DiscordRpc/DiscordRpc.Build.cs new file mode 100644 index 0000000..4249e5f --- /dev/null +++ b/examples/unrealstatus/Plugins/DiscordRpc/Source/DiscordRpc/DiscordRpc.Build.cs @@ -0,0 +1,51 @@ +// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class discordrpc : ModuleRules +{ + public discordrpc(ReadOnlyTargetRules Target) : base(Target) + { + Definitions.Add("DISCORD_DYNAMIC_LIB=1"); + + PublicIncludePaths.AddRange( + new string[] { + "DiscordRpc/Public" + } + ); + + PrivateIncludePaths.AddRange( + new string[] { + "DiscordRpc/Private" + } + ); + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "Slate", + "SlateCore", + "Projects" + } + ); + + DynamicallyLoadedModuleNames.AddRange( + new string[] + { + // ... add any modules that your module loads dynamically here ... + } + ); + + string BaseDirectory = Path.GetFullPath(Path.Combine(ModuleDirectory, "..", "..", "ThirdParty", "DiscordRpcLibrary")); + PublicIncludePaths.Add(Path.Combine(BaseDirectory, "Include")); + } +} \ No newline at end of file diff --git a/examples/unrealstatus/Plugins/DiscordRpc/Source/DiscordRpc/Private/DiscordRpc.cpp b/examples/unrealstatus/Plugins/DiscordRpc/Source/DiscordRpc/Private/DiscordRpc.cpp new file mode 100644 index 0000000..d8ff536 --- /dev/null +++ b/examples/unrealstatus/Plugins/DiscordRpc/Source/DiscordRpc/Private/DiscordRpc.cpp @@ -0,0 +1,71 @@ +// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved. + +#include "DiscordRpcPrivatePCH.h" +#include "IPluginManager.h" +#include "ModuleManager.h" + +#define LOCTEXT_NAMESPACE "FDiscordRpcModule" + +void FDiscordRpcModule::StartupModule() +{ +#if !PLATFORM_LINUX +#if defined(DISCORD_DYNAMIC_LIB) + // Get the base directory of this plugin + FString BaseDir = IPluginManager::Get().FindPlugin("DiscordRpc")->GetBaseDir(); + const FString SDKDir = FPaths::Combine(*BaseDir, TEXT("ThirdParty"), TEXT("DiscordRpcLibrary")); +#if PLATFORM_WINDOWS + const FString LibName = TEXT("discord-rpc"); + const FString LibDir = FPaths::Combine(*SDKDir, TEXT("Win64")); + if (!LoadDependency(LibDir, LibName, DiscordRpcLibraryHandle)) { + FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT(LOCTEXT_NAMESPACE, "Failed to load DiscordRpc plugin. Plug-in will not be functional.")); + FreeDependency(DiscordRpcLibraryHandle); + } +#elif PLATFORM_MAC + const FString LibName = TEXT("discord-rpc"); + const FString LibDir = FPaths::Combine(*SDKDir, TEXT("Mac")); + if (!LoadDependency(LibDir, LibName, DiscordRpcLibraryHandle)) { + FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT(LOCTEXT_NAMESPACE, "Failed to load DiscordRpc plugin. Plug-in will not be functional.")); + FreeDependency(DiscordRpcLibraryHandle); + } +#endif +#endif +#endif +} + +void FDiscordRpcModule::ShutdownModule() +{ + // Free the dll handle +#if !PLATFORM_LINUX +#if defined(DISCORD_DYNAMIC_LIB) + FreeDependency(DiscordRpcLibraryHandle); +#endif +#endif +} + +bool FDiscordAPIModule::LoadDependency(const FString& Dir, const FString& Name, void*& Handle) +{ + FString Lib = Name + TEXT(".") + FPlatformProcess::GetModuleExtension(); + FString Path = Dir.IsEmpty() ? *Lib : FPaths::Combine(*Dir, *Lib); + + Handle = FPlatformProcess::GetDllHandle(*Path); + + if (Handle == nullptr) + { + return false; + } + + return true; +} + +void FDiscordAPIModule::FreeDependency(void*& Handle) +{ + if (Handle != nullptr) + { + FPlatformProcess::FreeDllHandle(Handle); + Handle = nullptr; + } +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FDiscordRpcModule, DiscordRpc) \ No newline at end of file diff --git a/examples/unrealstatus/Plugins/DiscordRpc/Source/DiscordRpc/Private/DiscordRpcBlueprint.cpp b/examples/unrealstatus/Plugins/DiscordRpc/Source/DiscordRpc/Private/DiscordRpcBlueprint.cpp new file mode 100644 index 0000000..5ad637b --- /dev/null +++ b/examples/unrealstatus/Plugins/DiscordRpc/Source/DiscordRpc/Private/DiscordRpcBlueprint.cpp @@ -0,0 +1,150 @@ +#include "DicordRpcPrivatePCH" +#include "DiscordRpcBlueprint.h" +#include "discord-rpc.h" + +DEFINE_LOG_CATEGORY(Discord) + +static UDiscordRpc* self = nullptr; + +static void ReadyHandler() +{ + UE_LOG(Discord, Log, TEXT("Discord connected")); + if (self) { + self->IsConnected = true; + self->OnConnected.Broadcast(); + } +} + +static void DisconnectHandler(int errorCode, const char* message) +{ + auto msg = FString(message); + UE_LOG(Discord, Log, TEXT("Discord disconnected (%d): %s"), errorCode, *msg); + if (self) { + self->IsConnected = false; + self->OnDisconnected.Broadcast(errorCode, msg); + } +} + +static void ErroredHandler(int errorCode, const char* message) +{ + auto msg = FString(message); + UE_LOG(Discord, Log, TEXT("Discord error (%d): %s"), errorCode, *msg); + if (self) { + self->OnErrored.Broadcast(errorCode, msg); + } +} + +static void JoinGameHandler(const char* joinSecret) +{ + auto secret = FString(joinSecret); + UE_LOG(Discord, Log, TEXT("Discord join %s"), *secret); + if (self) { + self->OnJoin.Broadcast(secret); + } +} + +static void SpectateGameHandler(const char* spectateSecret) +{ + auto secret = FString(spectateSecret); + UE_LOG(Discord, Log, TEXT("Discord spectate %s"), *secret); + if (self) { + self->OnSpectate.Broadcast(secret); + } +} + +static void JoinRequestHandler(const DiscordJoinRequest* request) +{ + FDiscordJoinRequestData jr; + jr.userId = ANSI_TO_TCHAR(request->userId); + jr.username = ANSI_TO_TCHAR(request->username); + jr.discriminator = ANSI_TO_TCHAR(request->discriminator); + jr.avatar = ANSI_TO_TCHAR(request->avatar); + UE_LOG(Discord, Log, TEXT("Discord join request from %s#%s"), *jr.username, *jr.discriminator); + if (self) { + self->OnJoinRequest.Broadcast(jr); + } +} + +void UDiscordRpc::Initialize(const FString& applicationId, + bool autoRegister, + const FString& optionalSteamId) +{ + self = this; + IsConnected = false; + DiscordEventHandlers handlers{}; + handlers.ready = ReadyHandler; + handlers.disconnected = DisconnectHandler; + handlers.errored = ErroredHandler; + if (OnJoin.IsBound()) { + handlers.joinGame = JoinGameHandler; + } + if (OnSpectate.IsBound()) { + handlers.spectateGame = SpectateGameHandler; + } + if (OnJoinRequest.IsBound()) { + handlers.joinRequest = JoinRequestHandler; + } + auto appId = StringCast(*applicationId); + auto steamId = StringCast(*optionalSteamId); + Discord_Initialize( + (const char*)appId.Get(), &handlers, autoRegister, (const char*)steamId.Get()); +} + +void UDiscordRpc::Shutdown() +{ + Discord_Shutdown(); + self = nullptr; +} + +void UDiscordRpc::RunCallbacks() +{ + Discord_RunCallbacks(); +} + +void UDiscordRpc::UpdatePresence() +{ + DiscordRichPresence rp{}; + + auto state = StringCast(*RichPresence.state); + rp.state = state.Get(); + + auto details = StringCast(*RichPresence.details); + rp.details = details.Get(); + + auto largeImageKey = StringCast(*RichPresence.largeImageKey); + rp.largeImageKey = largeImageKey.Get(); + + auto largeImageText = StringCast(*RichPresence.largeImageText); + rp.largeImageText = largeImageText.Get(); + + auto smallImageKey = StringCast(*RichPresence.smallImageKey); + rp.smallImageKey = smallImageKey.Get(); + + auto smallImageText = StringCast(*RichPresence.smallImageText); + rp.smallImageText = smallImageText.Get(); + + auto partyId = StringCast(*RichPresence.partyId); + rp.partyId = partyId.Get(); + + auto matchSecret = StringCast(*RichPresence.matchSecret); + rp.matchSecret = matchSecret.Get(); + + auto joinSecret = StringCast(*RichPresence.joinSecret); + rp.joinSecret = joinSecret.Get(); + + auto spectateSecret = StringCast(*RichPresence.spectateSecret); + rp.spectateSecret = spectateSecret.Get(); + + rp.startTimestamp = RichPresence.startTimestamp; + rp.endTimestamp = RichPresence.endTimestamp; + rp.partySize = RichPresence.partySize; + rp.partyMax = RichPresence.partyMax; + rp.instance = RichPresence.instance; + + Discord_UpdatePresence(&rp); +} + +void UDiscordRpc::ClearPresence() +{ + Discord_ClearPresence(); +} diff --git a/examples/unrealstatus/Plugins/DiscordRpc/Source/DiscordRpc/Public/DiscordRpc.h b/examples/unrealstatus/Plugins/DiscordRpc/Source/DiscordRpc/Public/DiscordRpc.h new file mode 100644 index 0000000..f5b27ea --- /dev/null +++ b/examples/unrealstatus/Plugins/DiscordRpc/Source/DiscordRpc/Public/DiscordRpc.h @@ -0,0 +1,20 @@ +// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "ModuleManager.h" + +class FDiscordRpcModule : public IModuleInterface { +public: + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + +private: + /** Handle to the test dll we will load */ + void* DiscordLibraryHandle; + + /** StartupModule is covered with defines, these functions are the place to put breakpoints */ + static bool LoadDependency(const FString& Dir, const FString& Name, void*& Handle); + static void FreeDependency(void*& Handle); +}; \ No newline at end of file diff --git a/examples/unrealstatus/Plugins/DiscordRpc/Source/DiscordRpc/Public/DiscordRpcBlueprint.h b/examples/unrealstatus/Plugins/DiscordRpc/Source/DiscordRpc/Public/DiscordRpcBlueprint.h new file mode 100644 index 0000000..7fbad7b --- /dev/null +++ b/examples/unrealstatus/Plugins/DiscordRpc/Source/DiscordRpc/Public/DiscordRpcBlueprint.h @@ -0,0 +1,154 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Engine.h" +#include "DiscordRpcBlueprint.generated.h" + +// unreal's header tool hates clang-format +// clang-format off + +/** +* Ask to join callback data +*/ +USTRUCT(BlueprintType) +struct FDiscordJoinRequestData { + GENERATED_USTRUCT_BODY() + + UPROPERTY(BlueprintReadOnly) + FString userId; + UPROPERTY(BlueprintReadOnly) + FString username; + UPROPERTY(BlueprintReadOnly) + FString discriminator; + UPROPERTY(BlueprintReadOnly) + FString avatar; +}; + + +DECLARE_LOG_CATEGORY_EXTERN(Discord, Log, All); + +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FDiscordConnected); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FDiscordDisconnected, int, errorCode, const FString&, errorMessage); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FDiscordErrored, int, errorCode, const FString&, errorMessage); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordJoin, const FString&, joinSecret); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordSpectate, const FString&, spectateSecret); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDiscordJoinRequest, const FDiscordJoinRequestData&, joinRequest); + +// clang-format on + +/** + * Rich presence data + */ +USTRUCT(BlueprintType) +struct FDiscordRichPresence { + GENERATED_USTRUCT_BODY() + + UPROPERTY(BlueprintReadWrite) + FString state; + UPROPERTY(BlueprintReadWrite) + FString details; + // todo, timestamps are 64bit, does that even matter? + UPROPERTY(BlueprintReadWrite) + int startTimestamp; + UPROPERTY(BlueprintReadWrite) + int endTimestamp; + UPROPERTY(BlueprintReadWrite) + FString largeImageKey; + UPROPERTY(BlueprintReadWrite) + FString largeImageText; + UPROPERTY(BlueprintReadWrite) + FString smallImageKey; + UPROPERTY(BlueprintReadWrite) + FString smallImageText; + UPROPERTY(BlueprintReadWrite) + FString partyId; + UPROPERTY(BlueprintReadWrite) + int partySize; + UPROPERTY(BlueprintReadWrite) + int partyMax; + UPROPERTY(BlueprintReadWrite) + FString matchSecret; + UPROPERTY(BlueprintReadWrite) + FString joinSecret; + UPROPERTY(BlueprintReadWrite) + FString spectateSecret; + UPROPERTY(BlueprintReadWrite) + bool instance; +}; + +/** + * + */ +UCLASS(BlueprintType, meta = (DisplayName = "Discord RPC"), Category = "Discord") +class DISCORDRPC_API UDiscordRpc : public UObject { + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable, + meta = (DisplayName = "Initialize connection", Keywords = "Discord rpc"), + Category = "Discord") + void Initialize(const FString& applicationId, + bool autoRegister, + const FString& optionalSteamId); + + UFUNCTION(BlueprintCallable, + meta = (DisplayName = "Shut down connection", Keywords = "Discord rpc"), + Category = "Discord") + void Shutdown(); + + UFUNCTION(BlueprintCallable, + meta = (DisplayName = "Check for callbacks", Keywords = "Discord rpc"), + Category = "Discord") + void RunCallbacks(); + + UFUNCTION(BlueprintCallable, + meta = (DisplayName = "Send presence", Keywords = "Discord rpc"), + Category = "Discord") + void UpdatePresence(); + + UFUNCTION(BlueprintCallable, + meta = (DisplayName = "Clear presence", Keywords = "Discord rpc"), + Category = "Discord") + void ClearPresence(); + + UPROPERTY(BlueprintReadOnly, + meta = (DisplayName = "Is Discord connected", Keywords = "Discord rpc"), + Category = "Discord") + bool IsConnected; + + UPROPERTY(BlueprintAssignable, + meta = (DisplayName = "On connection", Keywords = "Discord rpc"), + Category = "Discord") + FDiscordConnected OnConnected; + + UPROPERTY(BlueprintAssignable, + meta = (DisplayName = "On disconnection", Keywords = "Discord rpc"), + Category = "Discord") + FDiscordDisconnected OnDisconnected; + + UPROPERTY(BlueprintAssignable, + meta = (DisplayName = "On error message", Keywords = "Discord rpc"), + Category = "Discord") + FDiscordErrored OnErrored; + + UPROPERTY(BlueprintAssignable, + meta = (DisplayName = "When Discord user presses join", Keywords = "Discord rpc"), + Category = "Discord") + FDiscordJoin OnJoin; + + UPROPERTY(BlueprintAssignable, + meta = (DisplayName = "When Discord user presses spectate", Keywords = "Discord rpc"), + Category = "Discord") + FDiscordSpectate OnSpectate; + + UPROPERTY(BlueprintAssignable, + meta = (DisplayName = "When Discord another user sends a join request", + Keywords = "Discord rpc"), + Category = "Discord") + FDiscordJoinRequest OnJoinRequest; + + UPROPERTY(BlueprintReadWrite, + meta = (DisplayName = "Rich presence info", Keywords = "Discord rpc"), + Category = "Discord") + FDiscordRichPresence RichPresence; +}; diff --git a/examples/unrealstatus/Plugins/DiscordRpc/ThirdParty/DiscordRpcLibrary/Linux/x86_64-unknown-linux-gnu/libdiscord-rpc.so b/examples/unrealstatus/Plugins/DiscordRpc/ThirdParty/DiscordRpcLibrary/Linux/x86_64-unknown-linux-gnu/libdiscord-rpc.so new file mode 100644 index 0000000..87055c6 Binary files /dev/null and b/examples/unrealstatus/Plugins/DiscordRpc/ThirdParty/DiscordRpcLibrary/Linux/x86_64-unknown-linux-gnu/libdiscord-rpc.so differ diff --git a/examples/unrealstatus/Plugins/DiscordRpc/ThirdParty/DiscordRpcLibrary/Win64/discord-rpc.dll b/examples/unrealstatus/Plugins/DiscordRpc/ThirdParty/DiscordRpcLibrary/Win64/discord-rpc.dll new file mode 100644 index 0000000..009dc06 Binary files /dev/null and b/examples/unrealstatus/Plugins/DiscordRpc/ThirdParty/DiscordRpcLibrary/Win64/discord-rpc.dll differ diff --git a/examples/unrealstatus/Plugins/DiscordRpc/ThirdParty/DiscordRpcLibrary/Win64/discord-rpc.lib b/examples/unrealstatus/Plugins/DiscordRpc/ThirdParty/DiscordRpcLibrary/Win64/discord-rpc.lib new file mode 100644 index 0000000..4692582 Binary files /dev/null and b/examples/unrealstatus/Plugins/DiscordRpc/ThirdParty/DiscordRpcLibrary/Win64/discord-rpc.lib differ