You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

210 lines
5.7 KiB

#include "discord.h"
#ifdef _WIN32
// On Windows, discordsrc-src/cpp/discord.h includes windows.h
#define NOMINMAX 1
#define WIN32_LEAN_AND_MEAN
#endif
#include <discordsrc-src/cpp/discord.h>
#include <algorithm>
#include <array>
#include <cctype>
#include <chrono>
#include <string>
#include <tuple>
#include <fmt/format.h>
#include "config.h"
#include "levels/gendung.h"
#include "levels/setmaps.h"
#include "lua/lua.hpp"
#include "multi.h"
#include "panels/charpanel.hpp"
#include "player.h"
#include "playerdat.hpp"
#include "utils/language.h"
#include "utils/str_cat.hpp"
namespace devilution {
namespace {
void ModChanged()
{
discord_manager::UpdateMenu(true);
}
const auto ModChangedHandler = (AddModsChangedHandler(ModChanged), true);
} // namespace
namespace discord_manager {
// App ID used for DevilutionX's Diablo (classic Diablo's is 496571953147150354)
constexpr discord::ClientId DiscordDevilutionxAppId = 795760213524742205;
constexpr auto IgnoreResult = [](discord::Result result) {};
discord::Core *discord_core = []() -> discord::Core * {
discord::Core *core;
discord::Result result = discord::Core::Create(DiscordDevilutionxAppId, DiscordCreateFlags_NoRequireDiscord, &core);
if (result != discord::Result::Ok) {
core = nullptr;
}
return core;
}();
struct PlayerData {
dungeon_type dungeonArea;
_setlevels questMap;
Uint8 dungeonLevel;
Uint8 playerLevel;
int playerGfx;
// Why??? This is POD
bool operator!=(const PlayerData &other) const
{
return std::tie(dungeonArea, dungeonLevel, playerLevel, playerGfx) != std::tie(other.dungeonArea, other.dungeonLevel, other.playerLevel, other.playerGfx);
}
};
bool want_menu_update = true;
PlayerData tracked_data;
Sint64 start_time = 0;
std::string GetLocationString()
{
// Quest Level Name
if (setlevel) {
return std::string(_(QuestLevelNames[setlvlnum]));
}
// Dungeon Name
constexpr std::array<const char *, DTYPE_LAST + 1> DungeonStrs = { N_("Town"), N_("Cathedral"), N_("Catacombs"), N_("Caves"), N_("Hell"), N_("Nest"), N_("Crypt") };
std::string dungeonStr;
if (tracked_data.dungeonArea != DTYPE_NONE) {
dungeonStr = _(DungeonStrs[tracked_data.dungeonArea]);
} else {
dungeonStr = _(/* TRANSLATORS: type of dungeon (i.e. Cathedral, Caves)*/ "None");
}
// Dungeon Level
if (tracked_data.dungeonLevel > 0) {
int level = tracked_data.dungeonLevel;
if (tracked_data.dungeonArea == DTYPE_NEST)
level -= 16;
else if (tracked_data.dungeonArea == DTYPE_CRYPT)
level -= 20;
return fmt::format(fmt::runtime(_(/* TRANSLATORS: dungeon type and floor number i.e. "Cathedral 3"*/ "{} {}")), dungeonStr, level);
}
return dungeonStr;
}
std::string GetCharacterString()
{
return fmt::format(fmt::runtime(_(/* TRANSLATORS: Discord character, i.e. "Lv 6 Warrior" */ "Lv {} {}")), tracked_data.playerLevel, MyPlayer->getClassName());
}
std::string GetDetailString()
{
return StrCat(GetCharacterString(), " - ", GetLocationString());
}
std::string GetStateString()
{
constexpr std::array<const char *, 3> DifficultyStrs = { N_("Normal"), N_("Nightmare"), N_("Hell") };
const std::string_view difficultyStr = _(DifficultyStrs[sgGameInitInfo.nDifficulty]);
return fmt::format(fmt::runtime(_(/* TRANSLATORS: Discord state i.e. "Nightmare difficulty" */ "{} difficulty")), difficultyStr);
}
std::string GetTooltipString()
{
return StrCat(MyPlayer->_pName, " - ", GetCharacterString());
}
std::string GetPlayerAssetString()
{
char chars[5] {
CharChar[static_cast<int>(MyPlayer->_pClass)],
ArmourChar[tracked_data.playerGfx >> 4],
WepChar[tracked_data.playerGfx & 0xF],
'a',
's'
};
return std::string(chars, 5);
}
void ResetStartTime()
{
start_time = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
}
const char *GetIconAsset()
{
return gbIsHellfire ? "hellfire" : "icon";
}
void UpdateGame()
{
if (discord_core == nullptr)
return;
auto newData = PlayerData {
leveltype, setlvlnum, currlevel, MyPlayer->getCharacterLevel(), MyPlayer->_pgfxnum
};
if (newData != tracked_data) {
tracked_data = newData;
// Update status strings
discord::Activity activity = {};
activity.SetName(PROJECT_NAME);
activity.SetState(GetStateString().c_str());
activity.SetDetails(GetDetailString().c_str());
activity.SetInstance(true);
activity.GetTimestamps().SetStart(start_time);
// Set image assets
activity.GetAssets().SetLargeImage(GetPlayerAssetString().c_str());
activity.GetAssets().SetLargeText(GetTooltipString().c_str());
activity.GetAssets().SetSmallImage(GetIconAsset());
activity.GetAssets().SetSmallText(gszProductName);
discord_core->ActivityManager().UpdateActivity(activity, IgnoreResult);
}
discord_core->RunCallbacks();
}
void StartGame()
{
tracked_data = PlayerData { dungeon_type::DTYPE_NONE, _setlevels::SL_NONE, 0, 0, 0 };
want_menu_update = true;
ResetStartTime();
}
void UpdateMenu(bool forced)
{
if (discord_core == nullptr)
return;
if (want_menu_update || forced) {
if (!forced) {
ResetStartTime();
}
want_menu_update = false;
discord::Activity activity = {};
activity.SetName(PROJECT_NAME);
activity.SetState(_(/* TRANSLATORS: Discord activity, not in game */ "In Menu").data());
activity.GetTimestamps().SetStart(start_time);
activity.GetAssets().SetLargeImage(GetIconAsset());
activity.GetAssets().SetLargeText(gszProductName);
discord_core->ActivityManager().UpdateActivity(activity, IgnoreResult);
}
discord_core->RunCallbacks();
}
} // namespace discord_manager
} // namespace devilution