mirror of
https://github.com/Jous99/F4MP.git
synced 2026-01-13 00:00:54 +01:00
226 lines
6.8 KiB
C++
226 lines
6.8 KiB
C++
|
|
#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(¶ms); // 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, ¶ms, &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()
|
||
|
|
{
|
||
|
|
}
|