F4MP/tiltedcode/Code/client/Services/Generic/DiscordService.cpp

226 lines
6.8 KiB
C++
Raw Normal View History

#include <TiltedOnlinePCH.h>
#include "Services/DiscordService.h"
#include "Events/UpdateEvent.h"
#include <TiltedCore/Filesystem.hpp>
#include <PlayerCharacter.h>
#include <Forms/TESWorldSpace.h>
#include <base/threading/ThreadUtils.h>
#include <Events/LocationChangeEvent.h>
#define DISCORD_OVERLAY_ENABLE 0
IDiscordUserEvents DiscordService::s_mUserEvents = {DiscordService::OnUserUpdate};
DiscordService::DiscordService(entt::dispatcher& aDispatcher)
{
// initialize persistant rich presence data
m_ActivityState.instance = false;
m_ActivityState.type = EDiscordActivityType::DiscordActivityType_Playing;
strcpy_s(m_ActivityState.details, "Loading");
strcpy_s(m_ActivityState.assets.large_image, "logo");
m_cellChangeConnection = aDispatcher.sink<LocationChangeEvent>().connect<&DiscordService::OnLocationChangeEvent>(this);
#if DISCORD_OVERLAY_ENABLE
auto& d3d11 = TiltedPhoques::D3D11Hook::Get();
d3d11.OnCreate.Connect([&](IDXGISwapChain* pSwapchain) { InitOverlay(pSwapchain); });
d3d11.OnPresent.Connect(
[&](IDXGISwapChain*)
{
if (m_bOverlayEnabled)
{
m_pOverlayMgr->on_present(m_pOverlayMgr);
}
});
#endif
}
DiscordService::~DiscordService() = default;
void DiscordService::OnUserUpdate(void* userp)
{
auto* pDiscord = static_cast<DiscordService*>(userp);
pDiscord->m_pUserMgr->get_current_user(pDiscord->m_pUserMgr, &pDiscord->m_userData);
}
void DiscordService::OnLocationChangeEvent() noexcept
{
auto* pPlayer = PlayerCharacter::Get();
// we'll disable this inbuilt location tracker
// in case the user requests a custom discord presence
if (!m_bCustomPresence && pPlayer)
{
// auto *pLocation = pPlayer->GetCurrentLocation();
auto* pLocation = pPlayer->locationForm;
auto* pWorldspace = pPlayer->GetWorldSpace();
bool updateTimestamp = false;
if (pLocation)
strncpy_s(m_ActivityState.details, pLocation->GetName(), sizeof(DiscordActivity::details));
if (pWorldspace)
{
if (pWorldspace->fullName.value.data)
strncpy_s(m_ActivityState.state, pWorldspace->fullName.value.AsAscii(), sizeof(DiscordActivity::state));
if (m_lastWorldspaceId != pWorldspace->formID)
{
updateTimestamp = true;
m_lastWorldspaceId = pWorldspace->formID;
}
}
UpdatePresence(updateTimestamp);
}
}
void DiscordService::UpdatePresence(bool newTimeStamp)
{
if (m_pActivity)
{
if (newTimeStamp)
m_ActivityState.timestamps.start = time(nullptr);
m_pActivity->update_activity(
m_pActivity, &m_ActivityState, nullptr,
[](void*, EDiscordResult result)
{
if (result != EDiscordResult::DiscordResult_Ok)
{
spdlog::error("Failed to update discord presence ({})", static_cast<int>(result));
}
});
}
}
void DiscordService::InitOverlay(IDXGISwapChain* pSwapchain)
{
m_pOverlayMgr->is_enabled(m_pOverlayMgr, &m_bOverlayEnabled);
// attempt to unlock it
m_pOverlayMgr->set_locked(m_pOverlayMgr, false, nullptr, nullptr);
// spdlog::info("Enabled discord overlay! ({})", static_cast<int>(result));
}
void DiscordService::WndProcHandler(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// this is a hack to force the auxiliary discord msg loop thread
// to quit in a timely manner
if (msg == WM_QUIT)
{
m_bRequestThreadKillHack = true;
m_bOverlayEnabled = false;
return;
}
#if DISCORD_OVERLAY_ENABLE
if (!m_bOverlayEnabled)
return;
switch (msg)
{
case WM_KEYDOWN:
case WM_KEYUP:
case WM_SYSKEYDOWN:
case WM_SYSKEYUP:
{
bool down = ((msg == WM_KEYDOWN) || (msg == WM_SYSKEYDOWN));
if (down && wParam == VK_F6)
{
m_pOverlayMgr->set_locked(m_pOverlayMgr, false, nullptr, [](void*, EDiscordResult result) { spdlog::info("unlocking discord overlay ({})", static_cast<int>(result)); });
}
/* if (wParam < 256)
m_pOverlayMgr->key_event(m_pOverlayMgr, down, reinterpret_cast<const char *>(wParam),
EDiscordKeyVariant::DiscordKeyVariant_Normal);*/
break;
}
default: return;
}
#endif
}
bool DiscordService::Init()
{
auto dllPath = TiltedPhoques::GetPath().wstring() + L"\\discord_game_sdk.dll";
// as we make the game sdk optional we need to dynamiclly resolve the export
HMODULE pHandle = LoadLibraryW(dllPath.c_str());
if (!pHandle)
return false;
auto* f_pDiscordCreate = (decltype(&DiscordCreate))(GetProcAddress(pHandle, "DiscordCreate"));
if (!f_pDiscordCreate)
return false;
DiscordCreateParams params{};
DiscordCreateParamsSetDefault(&params); // initialize version fields
params.client_id = 739600151277994076; //"Skyrim Together"
params.flags = DiscordCreateFlags_NoRequireDiscord;
params.event_data = this; // this is our userpointer
params.user_events = &s_mUserEvents;
auto result = f_pDiscordCreate(DISCORD_VERSION, &params, &m_pCore);
if (result != DiscordResult_Ok || !m_pCore)
{
spdlog::error("Failed to create Discord instance ({})", static_cast<int>(result));
FreeLibrary(pHandle);
return false;
}
#if 0
m_pCore->set_log_hook(m_pCore, EDiscordLogLevel::DiscordLogLevel_Debug, this,
[](void *, EDiscordLogLevel, const char *msg) { std::printf("NEW DISCORD MSG : %s\n", msg);
});
#endif
m_pUserMgr = m_pCore->get_user_manager(m_pCore);
m_pActivity = m_pCore->get_activity_manager(m_pCore);
m_pAppMgr = m_pCore->get_application_manager(m_pCore);
m_pOverlayMgr = m_pCore->get_overlay_manager(m_pCore);
if (!m_pUserMgr || !m_pActivity || !m_pAppMgr || !m_pOverlayMgr)
{
m_pCore->destroy(m_pCore);
FreeLibrary(pHandle);
return false;
}
// set initial presence state
m_ActivityState.application_id = params.client_id;
UpdatePresence(true);
// TODO (Force): i want to move this away from its own thread
// this is done because discord needs to be ticked before world
static std::thread updateThread(
[&]()
{
Base::SetCurrentThreadName("DiscordCallbacks");
while (!m_bRequestThreadKillHack)
{
const auto runResult = m_pCore->run_callbacks(m_pCore);
if (runResult != DiscordResult_Ok)
break;
// update at "60" fps
std::this_thread::sleep_for(std::chrono::milliseconds(16));
}
});
updateThread.detach();
return true;
}
void DiscordService::Update()
{
}