diff --git a/Minecraft.Client/ClientConnection.cpp b/Minecraft.Client/ClientConnection.cpp index 325e949bb..7a6fa2518 100644 --- a/Minecraft.Client/ClientConnection.cpp +++ b/Minecraft.Client/ClientConnection.cpp @@ -2919,9 +2919,6 @@ void ClientConnection::handleRespawn(shared_ptr packet) gameMode->getTutorial()->showTutorialPopup(false); } - // 4J-JEV: Fix for Durango #156334 - Content: UI: Rich Presence 'In the Nether' message is updating with a 3 to 10 minute delay. - minecraft->localplayers[m_userIndex]->updateRichPresence(); - ConnectionProgressParams *param = new ConnectionProgressParams(); param->iPad = m_userIndex; if( packet->dimension == -1) diff --git a/Minecraft.Client/Extrax64Stubs.cpp b/Minecraft.Client/Extrax64Stubs.cpp index 0147896ca..f0d41843e 100644 --- a/Minecraft.Client/Extrax64Stubs.cpp +++ b/Minecraft.Client/Extrax64Stubs.cpp @@ -35,6 +35,9 @@ #include "Orbis\Sentient\DynamicConfigurations.h" #include #endif +#ifdef _WINDOWS64 +#include "Windows64/RichPresence/discord_rpc.h" +#endif #if !defined(__PS3__) && !defined(__ORBIS__) && !defined(__PSVITA__) #ifdef _WINDOWS64 @@ -729,9 +732,106 @@ eAwardType C_4JProfile::GetAwardType(int iAwardNumber) { return eAwardType_Ach bool C_4JProfile::CanBeAwarded(int iQuadrant, int iAwardNumber) { return false; } void C_4JProfile::Award(int iQuadrant, int iAwardNumber, bool bForce) {} bool C_4JProfile::IsAwardsFlagSet(int iQuadrant, int iAward) { return false; } -void C_4JProfile::RichPresenceInit(int iPresenceCount, int iContextCount) {} -void C_4JProfile::RegisterRichPresenceContext(int iGameConfigContextID) {} -void C_4JProfile::SetRichPresenceContextValue(int iPad, int iContextID, int iVal) {} +void C_4JProfile::RichPresenceInit(int iPresenceCount, int iContextCount) +{ +#ifdef _WINDOWS64 + DiscordEventHandlers discordPresence; + memset(&discordPresence, 0, sizeof(discordPresence)); + + // 1480245069947732159 is on my discord account (acth2) + Discord_Initialize("1480245069947732159", &discordPresence, 1, NULL); +#endif +} +void C_4JProfile::RegisterRichPresenceContext(int iGameConfigContextID) +{ +} + +#ifdef _WINDOWS64 +std::string WStringToUtf8(LPCWSTR wstr) +{ + int size = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL); + std::string result(size - 1, 0); + WideCharToMultiByte(CP_UTF8, 0, wstr, -1, result.data(), size, NULL, NULL); + return result; +} +#endif + +void C_4JProfile::SetRichPresenceContextValue(int iPad, int iContextID, int iVal) +{ +#ifdef _WINDOWS64 + DiscordRichPresence discordPresence; + memset(&discordPresence, 0, sizeof(discordPresence)); + + // std::string = UTF8 + // LPCWSTR = UTF16 + + static std::string s_detailsUtf8; + static std::string s_stateUtf8; + + // i cannot instantly do = because app.getString + s_detailsUtf8 = WStringToUtf8(app.GetString(IDS_RICHPRESENCESTATE_BLANK)); + + if (iContextID == CONTEXT_GAME_STATE) + { + discordPresence.details = "Playing Minecraft: Legacy Console Edition"; + + discordPresence.largeImageKey = "main_icon"; + discordPresence.largeImageText = "Minecraft: LCE"; + + switch (iVal) + { + case CONTEXT_GAME_STATE_NETHER: + s_stateUtf8 = WStringToUtf8(app.GetString(IDS_RICHPRESENCESTATE_NETHER)); + discordPresence.smallImageKey = "nether_icon"; + break; + + case CONTEXT_GAME_STATE_FORGING: + s_stateUtf8 = WStringToUtf8(app.GetString(IDS_RICHPRESENCESTATE_FORGING)); + discordPresence.smallImageKey = "anvil_icon"; + break; + + case CONTEXT_GAME_STATE_BOATING: + s_stateUtf8 = WStringToUtf8(app.GetString(IDS_RICHPRESENCESTATE_BOATING)); + discordPresence.smallImageKey = "boat_icon"; + break; + + case CONTEXT_GAME_STATE_CRAFTING: + s_stateUtf8 = WStringToUtf8(app.GetString(IDS_RICHPRESENCESTATE_CRAFTING)); + discordPresence.smallImageKey = "crafting_icon"; + break; + + case CONTEXT_GAME_STATE_RIDING_MINECART: + s_stateUtf8 = WStringToUtf8(app.GetString(IDS_RICHPRESENCESTATE_RIDING_MINECART)); + discordPresence.smallImageKey = "minecart_icon"; + break; + + case CONTEXT_GAME_STATE_RIDING_PIG: + s_stateUtf8 = WStringToUtf8(app.GetString(IDS_RICHPRESENCESTATE_RIDING_PIG)); + discordPresence.smallImageKey = "pig_icon"; + break; + + case CONTEXT_GAME_STATE_BREWING: + s_stateUtf8 = "Brewing a potion"; + discordPresence.smallImageKey = "brewing_icon"; + break; + + case CONTEXT_GAME_STATE_ENCHANTING: + s_stateUtf8 = "Enchanting"; + discordPresence.smallImageKey = "enchanting_icon"; + break; + + case CONTEXT_GAME_STATE_BLANK: + default: + s_stateUtf8 = WStringToUtf8(app.GetString(IDS_RICHPRESENCESTATE_BLANK)); + discordPresence.smallImageKey = nullptr; + break; + } + + discordPresence.state = s_stateUtf8.c_str(); + Discord_UpdatePresence(&discordPresence); + } +#endif +} void C_4JProfile::SetCurrentGameActivity(int iPad, int iNewPresence, bool bSetOthersToIdle) {} void C_4JProfile::DisplayFullVersionPurchase(bool bRequired, int iQuadrant, int iUpsellParam) {} void C_4JProfile::SetUpsellCallback(void (*Func)(LPVOID lpParam, eUpsellType type, eUpsellResponse response, int iUserData), LPVOID lpParam) {} diff --git a/Minecraft.Client/LevelRenderer.cpp b/Minecraft.Client/LevelRenderer.cpp index cf2937f4f..b155f0c3f 100644 --- a/Minecraft.Client/LevelRenderer.cpp +++ b/Minecraft.Client/LevelRenderer.cpp @@ -3252,7 +3252,6 @@ void LevelRenderer::levelEvent(shared_ptr source, int type, int x, int y level[playerIndex]->playStreamingMusic(L"", x, y, z); // 4J - used to pass nullptr, but using empty string here now instead } } - mc->localplayers[playerIndex]->updateRichPresence(); } break; // 4J - new level event sounds brought forward from 1.2.3 diff --git a/Minecraft.Client/LocalPlayer.cpp b/Minecraft.Client/LocalPlayer.cpp index 7988416ad..06f0b8de9 100644 --- a/Minecraft.Client/LocalPlayer.cpp +++ b/Minecraft.Client/LocalPlayer.cpp @@ -33,6 +33,12 @@ #include "..\Minecraft.World\Random.h" #include "..\Minecraft.World\TileEntity.h" #include "..\Minecraft.World\Mth.h" + +#include "..\AnvilMenu.h" +#include "..\CraftingMenu.h" +#include "..\BrewingStandMenu.h" +#include "..\EnchantmentMenu.h" + #include "AchievementPopup.h" #include "CritParticle.h" @@ -470,42 +476,6 @@ void LocalPlayer::aiStep() yd = 0.0f; onGround = true; } - - // Check if the player is idle and the rich presence needs updated - if( !m_bIsIdle && InputManager.GetIdleSeconds( m_iPad ) > PLAYER_IDLE_TIME ) - { - ProfileManager.SetCurrentGameActivity(m_iPad,CONTEXT_PRESENCE_IDLE,false); - m_bIsIdle = true; - } - else if ( m_bIsIdle && InputManager.GetIdleSeconds( m_iPad ) < PLAYER_IDLE_TIME ) - { - // Are we offline or online, and how many players are there - if(g_NetworkManager.GetPlayerCount()>1) - { - // only do it for this player here - each player will run this code - if(g_NetworkManager.IsLocalGame()) - { - ProfileManager.SetCurrentGameActivity(m_iPad,CONTEXT_PRESENCE_MULTIPLAYEROFFLINE,false); - } - else - { - ProfileManager.SetCurrentGameActivity(m_iPad,CONTEXT_PRESENCE_MULTIPLAYER,false); - } - } - else - { - if(g_NetworkManager.IsLocalGame()) - { - ProfileManager.SetCurrentGameActivity(m_iPad,CONTEXT_PRESENCE_MULTIPLAYER_1POFFLINE,false); - } - else - { - ProfileManager.SetCurrentGameActivity(m_iPad,CONTEXT_PRESENCE_MULTIPLAYER_1P,false); - } - } - updateRichPresence(); - m_bIsIdle = false; - } } void LocalPlayer::changeDimension(int i) @@ -1600,42 +1570,67 @@ bool LocalPlayer::handleMouseClick(int button) void LocalPlayer::updateRichPresence() { - if((m_iPad!=-1)/* && !ui.GetMenuDisplayed(m_iPad)*/ ) - { - shared_ptr selectedItem = inventory->getSelected(); - if(selectedItem != nullptr && selectedItem->id == Item::fishingRod_Id) - { - app.SetRichPresenceContext(m_iPad,CONTEXT_GAME_STATE_FISHING); - } - else if(selectedItem != nullptr && selectedItem->id == Item::map_Id) - { - app.SetRichPresenceContext(m_iPad,CONTEXT_GAME_STATE_MAP); - } - else if ( (riding != nullptr) && riding->instanceof(eTYPE_MINECART) ) - { - app.SetRichPresenceContext(m_iPad,CONTEXT_GAME_STATE_RIDING_MINECART); + if (m_iPad == -1) + { + return; + } + + if (containerMenu != nullptr && containerMenu != inventoryMenu) + { + if (dynamic_cast(containerMenu) != nullptr) + { + app.SetRichPresenceContext(m_iPad, CONTEXT_GAME_STATE_FORGING); + } + else if (dynamic_cast(containerMenu) != nullptr) + { + app.SetRichPresenceContext(m_iPad, CONTEXT_GAME_STATE_CRAFTING); + } + else if (dynamic_cast(containerMenu) != nullptr) + { + app.SetRichPresenceContext(m_iPad, CONTEXT_GAME_STATE_BREWING); } - else if ( (riding != nullptr) && riding->instanceof(eTYPE_BOAT) ) - { - app.SetRichPresenceContext(m_iPad,CONTEXT_GAME_STATE_BOATING); - } - else if ( (riding != nullptr) && riding->instanceof(eTYPE_PIG) ) - { - app.SetRichPresenceContext(m_iPad,CONTEXT_GAME_STATE_RIDING_PIG); - } - else if( this->dimension == -1 ) - { - app.SetRichPresenceContext(m_iPad,CONTEXT_GAME_STATE_NETHER); - } - else if( minecraft->soundEngine->GetIsPlayingStreamingCDMusic() ) - { - app.SetRichPresenceContext(m_iPad,CONTEXT_GAME_STATE_CD); - } - else - { - app.SetRichPresenceContext(m_iPad,CONTEXT_GAME_STATE_BLANK); - } - } + else if (dynamic_cast(containerMenu) != nullptr) + { + app.SetRichPresenceContext(m_iPad, CONTEXT_GAME_STATE_ENCHANTING); + } + return; + } + + // INGAME ACTIVITY + shared_ptr selectedItem = inventory->getSelected(); + if (selectedItem != NULL && selectedItem->id == Item::fishingRod_Id) + { + // TODO + app.SetRichPresenceContext(m_iPad, CONTEXT_GAME_STATE_FISHING); + } + else if (selectedItem != NULL && selectedItem->id == Item::map_Id) + { + app.SetRichPresenceContext(m_iPad, CONTEXT_GAME_STATE_MAP); + } + else if ((riding != NULL) && riding->instanceof(eTYPE_MINECART)) + { + app.SetRichPresenceContext(m_iPad, CONTEXT_GAME_STATE_RIDING_MINECART); + } + else if ((riding != NULL) && riding->instanceof(eTYPE_BOAT)) + { + app.SetRichPresenceContext(m_iPad, CONTEXT_GAME_STATE_BOATING); + } + else if ((riding != NULL) && riding->instanceof(eTYPE_PIG)) + { + app.SetRichPresenceContext(m_iPad, CONTEXT_GAME_STATE_RIDING_PIG); + } + else if (this->dimension == -1) + { + app.SetRichPresenceContext(m_iPad, CONTEXT_GAME_STATE_NETHER); + } + else if (minecraft->soundEngine->GetIsPlayingStreamingCDMusic()) + { + app.SetRichPresenceContext(m_iPad, CONTEXT_GAME_STATE_CD); + } + else + { + app.SetRichPresenceContext(m_iPad, CONTEXT_GAME_STATE_BLANK); + } } // 4J Stu - Added for telemetry diff --git a/Minecraft.Client/Minecraft.cpp b/Minecraft.Client/Minecraft.cpp index 1ba432fd0..79cbc3c54 100644 --- a/Minecraft.Client/Minecraft.cpp +++ b/Minecraft.Client/Minecraft.cpp @@ -430,6 +430,11 @@ void Minecraft::init() progressRenderer = new ProgressRenderer(this); RenderManager.CBuffLockStaticCreations(); + + // Setup discord rich presence + #ifdef _WINDOWS64 + ProfileManager.RichPresenceInit(4, 1); + #endif } void Minecraft::renderLoadingScreen() @@ -2299,6 +2304,15 @@ void Minecraft::tick(bool bFirst, bool bUpdateTextures) // Tick the opacity timer (to display the interface at default opacity for a certain time if the user has been navigating it) app.TickOpacityTimer(iPad); + #ifdef _WINDOWS64 + static int tickRP = 0; + if (++tickRP >= 10) + { + player->updateRichPresence(); + tickRP = 0; + } + #endif + // 4J added if( bFirst ) levelRenderer->destroyedTileManager->tick(); @@ -3562,9 +3576,6 @@ void Minecraft::tick(bool bFirst, bool bUpdateTextures) gameMode->getTutorial()->onSelectedItemChanged(player->inventory->getSelected()); } - // Update presence - player->updateRichPresence(); - if (options->isFlying) { if (wheel > 0) wheel = 1; diff --git a/Minecraft.Client/MultiPlayerLocalPlayer.cpp b/Minecraft.Client/MultiPlayerLocalPlayer.cpp index aef7898f2..0660d25b2 100644 --- a/Minecraft.Client/MultiPlayerLocalPlayer.cpp +++ b/Minecraft.Client/MultiPlayerLocalPlayer.cpp @@ -366,8 +366,6 @@ void MultiplayerLocalPlayer::ride(shared_ptr e) TelemetryManager->RecordEnemyKilledOrOvercome(GetXboxPad(), 0, y, 0, 0, 0, 0, eventType); } - updateRichPresence(); - Minecraft *pMinecraft = Minecraft::GetInstance(); if( pMinecraft->localgameModes[m_iPad] != nullptr ) diff --git a/Minecraft.Client/Windows64/RichPresence/Debug/discord-rpc.lib b/Minecraft.Client/Windows64/RichPresence/Debug/discord-rpc.lib new file mode 100644 index 000000000..84ad3a482 Binary files /dev/null and b/Minecraft.Client/Windows64/RichPresence/Debug/discord-rpc.lib differ diff --git a/Minecraft.Client/Windows64/RichPresence/Release/discord-rpc.lib b/Minecraft.Client/Windows64/RichPresence/Release/discord-rpc.lib new file mode 100644 index 000000000..9851640d6 Binary files /dev/null and b/Minecraft.Client/Windows64/RichPresence/Release/discord-rpc.lib differ diff --git a/Minecraft.Client/Windows64/RichPresence/discord_rpc.h b/Minecraft.Client/Windows64/RichPresence/discord_rpc.h new file mode 100644 index 000000000..3e1441e05 --- /dev/null +++ b/Minecraft.Client/Windows64/RichPresence/discord_rpc.h @@ -0,0 +1,87 @@ +#pragma once +#include + +// clang-format off + +#if defined(DISCORD_DYNAMIC_LIB) +# if defined(_WIN32) +# if defined(DISCORD_BUILDING_SDK) +# define DISCORD_EXPORT __declspec(dllexport) +# else +# define DISCORD_EXPORT __declspec(dllimport) +# endif +# else +# define DISCORD_EXPORT __attribute__((visibility("default"))) +# endif +#else +# define DISCORD_EXPORT +#endif + +// clang-format on + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct DiscordRichPresence { + const char* state; /* max 128 bytes */ + const char* details; /* max 128 bytes */ + int64_t startTimestamp; + int64_t endTimestamp; + const char* largeImageKey; /* max 32 bytes */ + const char* largeImageText; /* max 128 bytes */ + const char* smallImageKey; /* max 32 bytes */ + const char* smallImageText; /* max 128 bytes */ + const char* partyId; /* max 128 bytes */ + int partySize; + int partyMax; + const char* matchSecret; /* max 128 bytes */ + const char* joinSecret; /* max 128 bytes */ + const char* spectateSecret; /* max 128 bytes */ + int8_t instance; +} DiscordRichPresence; + +typedef struct DiscordUser { + const char* userId; + const char* username; + const char* discriminator; + const char* avatar; +} DiscordUser; + +typedef struct DiscordEventHandlers { + void (*ready)(const DiscordUser* request); + void (*disconnected)(int errorCode, const char* message); + void (*errored)(int errorCode, const char* message); + void (*joinGame)(const char* joinSecret); + void (*spectateGame)(const char* spectateSecret); + void (*joinRequest)(const DiscordUser* request); +} DiscordEventHandlers; + +#define DISCORD_REPLY_NO 0 +#define DISCORD_REPLY_YES 1 +#define DISCORD_REPLY_IGNORE 2 + +DISCORD_EXPORT void Discord_Initialize(const char* applicationId, + DiscordEventHandlers* handlers, + int autoRegister, + const char* optionalSteamId); +DISCORD_EXPORT void Discord_Shutdown(void); + +/* checks for incoming messages, dispatches callbacks */ +DISCORD_EXPORT void Discord_RunCallbacks(void); + +/* If you disable the lib starting its own io thread, you'll need to call this from your own */ +#ifdef DISCORD_DISABLE_IO_THREAD +DISCORD_EXPORT void Discord_UpdateConnection(void); +#endif + +DISCORD_EXPORT void Discord_UpdatePresence(const DiscordRichPresence* presence); +DISCORD_EXPORT void Discord_ClearPresence(void); + +DISCORD_EXPORT void Discord_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply); + +DISCORD_EXPORT void Discord_UpdateHandlers(DiscordEventHandlers* handlers); + +#ifdef __cplusplus +} /* extern "C" */ +#endif