diff --git a/Attorney_Online_remake.pro b/Attorney_Online_remake.pro index 40b2028..b19c6ec 100644 --- a/Attorney_Online_remake.pro +++ b/Attorney_Online_remake.pro @@ -47,7 +47,8 @@ SOURCES += main.cpp\ aotextarea.cpp \ aolineedit.cpp \ aotextedit.cpp \ - aoevidencedisplay.cpp + aoevidencedisplay.cpp \ + discord_rich_presence.cpp HEADERS += lobby.h \ aoimage.h \ @@ -76,10 +77,18 @@ HEADERS += lobby.h \ aotextarea.h \ aolineedit.h \ aotextedit.h \ - aoevidencedisplay.h + aoevidencedisplay.h \ + discord_rich_presence.h \ + discord-rpc.h -unix:LIBS += -L$$PWD -lbass -win32:LIBS += "$$PWD/bass.dll" +# 1. You need to get BASS and put the x86 bass DLL/headers in the project root folder +# AND the compilation output folder. If you want a static link, you'll probably +# need the .lib file too. MinGW-GCC is really finicky finding BASS, it seems. +# 2. You need to compile the Discord Rich Presence SDK separately and add the lib/headers +# in the same way as BASS. Discord RPC uses CMake, which does not play nicely with +# QMake, so this step must be manual. +unix:LIBS += -L$$PWD -lbass -ldiscord-rpc +win32:LIBS += -L$$PWD "$$PWD/bass.dll" -ldiscord-rpc #"$$PWD/discord-rpc.dll" android:LIBS += -L$$PWD\android\libs\armeabi-v7a\ -lbass CONFIG += c++11 diff --git a/aoapplication.cpp b/aoapplication.cpp index 4362902..e170c99 100644 --- a/aoapplication.cpp +++ b/aoapplication.cpp @@ -12,6 +12,7 @@ AOApplication::AOApplication(int &argc, char **argv) : QApplication(argc, argv) { net_manager = new NetworkManager(this); + discord = new AttorneyOnline::Discord(); QObject::connect(net_manager, SIGNAL(ms_connect_finished(bool, bool)), SLOT(ms_connect_finished(bool, bool))); } @@ -20,6 +21,7 @@ AOApplication::~AOApplication() { destruct_lobby(); destruct_courtroom(); + delete discord; } void AOApplication::construct_lobby() @@ -38,6 +40,8 @@ void AOApplication::construct_lobby() int y = (screenGeometry.height()-w_lobby->height()) / 2; w_lobby->move(x, y); + discord->state_lobby(); + w_lobby->show(); } diff --git a/aoapplication.h b/aoapplication.h index 592c030..9762549 100644 --- a/aoapplication.h +++ b/aoapplication.h @@ -3,6 +3,7 @@ #include "aopacket.h" #include "datatypes.h" +#include "discord_rich_presence.h" #include #include @@ -23,6 +24,7 @@ public: NetworkManager *net_manager; Lobby *w_lobby; Courtroom *w_courtroom; + AttorneyOnline::Discord *discord; bool lobby_constructed = false; bool courtroom_constructed = false; diff --git a/charselect.cpp b/charselect.cpp index 1f76e0b..4e4bccb 100644 --- a/charselect.cpp +++ b/charselect.cpp @@ -151,8 +151,12 @@ void Courtroom::char_clicked(int n_char) } if (n_real_char == m_cid) + { enter_courtroom(m_cid); + } else + { ao_app->send_server_packet(new AOPacket("CC#" + QString::number(ao_app->s_pv) + "#" + QString::number(n_real_char) + "#" + get_hdid() + "#%")); + } } diff --git a/courtroom.cpp b/courtroom.cpp index a8b96eb..14d3cbb 100644 --- a/courtroom.cpp +++ b/courtroom.cpp @@ -660,9 +660,15 @@ void Courtroom::enter_courtroom(int p_cid) QString f_char; if (m_cid == -1) + { + ao_app->discord->state_spectate(); f_char = ""; + } else + { f_char = ao_app->get_char_name(char_list.at(m_cid).name); + ao_app->discord->state_character(f_char.toStdString()); + } current_char = f_char; diff --git a/discord-rpc.h b/discord-rpc.h new file mode 100644 index 0000000..8c117ac --- /dev/null +++ b/discord-rpc.h @@ -0,0 +1,84 @@ +#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 DiscordJoinRequest { + const char* userId; + const char* username; + const char* discriminator; + const char* avatar; +} DiscordJoinRequest; + +typedef struct DiscordEventHandlers { + void (*ready)(); + 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 DiscordJoinRequest* 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_Respond(const char* userid, /* DISCORD_REPLY_ */ int reply); + +#ifdef __cplusplus +} /* extern "C" */ +#endif diff --git a/discord_rich_presence.cpp b/discord_rich_presence.cpp new file mode 100644 index 0000000..bcc0d2a --- /dev/null +++ b/discord_rich_presence.cpp @@ -0,0 +1,108 @@ +#include "discord_rich_presence.h" + +#include +#include + +#include + +namespace AttorneyOnline { + +Discord::Discord() +{ + DiscordEventHandlers handlers; + std::memset(&handlers, 0, sizeof(handlers)); + handlers = {}; + handlers.ready = [] { + qInfo() << "Discord RPC ready"; + }; + handlers.disconnected = [](int errorCode, const char* message) { + qInfo() << "Discord RPC disconnected! " << message; + }; + handlers.errored = [](int errorCode, const char* message) { + qWarning() << "Discord RPC errored out! " << message; + }; + qInfo() << "Initializing Discord RPC"; + Discord_Initialize(APPLICATION_ID, &handlers, 1, nullptr); +} + +Discord::~Discord() +{ + Discord_Shutdown(); +} + +void Discord::state_lobby() +{ + DiscordRichPresence presence; + std::memset(&presence, 0, sizeof(presence)); + presence.largeImageKey = "ao2-logo"; + presence.largeImageText = "Objection!"; + presence.instance = 1; + + presence.state = "In Lobby"; + presence.details = "Idle"; + Discord_UpdatePresence(&presence); +} + +void Discord::state_server(std::string name, std::string server_id) +{ + qDebug() << "Discord RPC: Setting server state"; + + DiscordRichPresence presence; + std::memset(&presence, 0, sizeof(presence)); + presence.largeImageKey = "ao2-logo"; + presence.largeImageText = "Objection!"; + presence.instance = 1; + + auto timestamp = static_cast(std::time(nullptr)); + + presence.state = "In a Server"; + presence.details = name.c_str(); + presence.matchSecret = server_id.c_str(); + presence.startTimestamp = this->timestamp; + + this->server_id = server_id; + this->server_name = name; + this->timestamp = timestamp; + Discord_UpdatePresence(&presence); +} + +void Discord::state_character(std::string name) +{ + auto name_internal = QString(name.c_str()).toLower().replace(' ', '_').toStdString(); + auto name_friendly = QString(name.c_str()).replace('_', ' ').toStdString(); + const std::string playing_as = "Playing as " + name_friendly; + qDebug() << "Discord RPC: Setting character state (" << playing_as.c_str() << ")"; + + DiscordRichPresence presence; + std::memset(&presence, 0, sizeof(presence)); + presence.largeImageKey = "ao2-logo"; + presence.largeImageText = "Objection!"; + presence.instance = 1; + presence.details = this->server_name.c_str(); + presence.matchSecret = this->server_id.c_str(); + presence.startTimestamp = this->timestamp; + + presence.state = playing_as.c_str(); + presence.smallImageKey = name_internal.c_str(); + // presence.smallImageText = name_internal.c_str(); + Discord_UpdatePresence(&presence); +} + +void Discord::state_spectate() +{ + qDebug() << "Discord RPC: Setting specator state"; + + DiscordRichPresence presence; + std::memset(&presence, 0, sizeof(presence)); + presence.largeImageKey = "ao2-logo"; + presence.largeImageText = "Objection!"; + presence.instance = 1; + presence.details = this->server_name.c_str(); + presence.matchSecret = this->server_id.c_str(); + presence.startTimestamp = this->timestamp; + + presence.state = "Spectating"; + Discord_UpdatePresence(&presence); +} + +} diff --git a/discord_rich_presence.h b/discord_rich_presence.h new file mode 100644 index 0000000..3c9f2bd --- /dev/null +++ b/discord_rich_presence.h @@ -0,0 +1,26 @@ +#ifndef DISCORD_RICH_PRESENCE_H +#define DISCORD_RICH_PRESENCE_H + +#include +#include + +namespace AttorneyOnline { + +class Discord +{ +private: + const char* APPLICATION_ID = "399779271737868288"; + std::string server_name, server_id; + int64_t timestamp; +public: + Discord(); + ~Discord(); + + void state_lobby(); + void state_server(std::string name, std::string server_id); + void state_character(std::string name); + void state_spectate(); +}; + +} +#endif // DISCORD_RICH_PRESENCE_H diff --git a/packet_distribution.cpp b/packet_distribution.cpp index 4d74654..3908ffa 100644 --- a/packet_distribution.cpp +++ b/packet_distribution.cpp @@ -8,6 +8,7 @@ #include "debug_functions.h" #include +#include void AOApplication::ms_packet_received(AOPacket *p_packet) { @@ -226,15 +227,24 @@ void AOApplication::server_packet_received(AOPacket *p_packet) QString window_title = "Attorney Online 2"; int selected_server = w_lobby->get_selected_server(); + QString server_address = "", server_name = ""; if (w_lobby->public_servers_selected) { - if (selected_server >= 0 && selected_server < server_list.size()) - window_title += ": " + server_list.at(selected_server).name; + if (selected_server >= 0 && selected_server < server_list.size()) { + auto info = server_list.at(selected_server); + server_name = info.name; + server_address = info.ip + info.port; + window_title += ": " + server_name; + } } else { - if (selected_server >= 0 && selected_server < favorite_list.size()) - window_title += ": " + favorite_list.at(selected_server).name; + if (selected_server >= 0 && selected_server < favorite_list.size()) { + auto info = favorite_list.at(selected_server); + server_name = info.name; + server_address = info.ip + info.port; + window_title += ": " + server_name; + } } w_courtroom->set_window_title(window_title); @@ -251,6 +261,10 @@ void AOApplication::server_packet_received(AOPacket *p_packet) f_packet = new AOPacket("askchar2#%"); send_server_packet(f_packet); + + QCryptographicHash hash(QCryptographicHash::Algorithm::Sha256); + hash.addData(server_address.toUtf8()); + discord->state_server(server_name.toStdString(), hash.result().toBase64().toStdString()); } else if (header == "CI") {