diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cc9a5acac..27f8feba4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -238,7 +238,7 @@ target_sources(SRB2SDL2 PRIVATE apng.c) target_link_libraries(SRB2SDL2 PRIVATE DiscordRPC::DiscordRPC) target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_DISCORDRPC -DUSE_STUN) -target_sources(SRB2SDL2 PRIVATE discord.c stun.c) +target_sources(SRB2SDL2 PRIVATE discord.c stun.cpp) target_link_libraries(SRB2SDL2 PRIVATE tcbrindle::span) target_link_libraries(SRB2SDL2 PRIVATE glm) @@ -277,7 +277,7 @@ if(${SRB2_CONFIG_HAVE_DISCORDRPC}) set(SRB2_HAVE_DISCORDRPC ON) target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_DISCORDRPC) target_compile_definitions(SRB2SDL2 PRIVATE -DUSE_STUN) - target_sources(SRB2SDL2 PRIVATE discord.c stun.c) + target_sources(SRB2SDL2 PRIVATE discord.c stun.cpp) else() message(WARNING "You have specified that Discord Rich Presence is available but it was not found.") endif() diff --git a/src/discord.c b/src/discord.c index 9d6011c11..4fe49719d 100644 --- a/src/discord.c +++ b/src/discord.c @@ -356,7 +356,7 @@ static void DRPC_GotServerIP(UINT32 address) --------------------------------------------------*/ static const char *DRPC_GetServerIP(void) { - const char *address; + const char *address; // If you're connected if (I_GetNodeAddress && (address = I_GetNodeAddress(servernode)) != NULL) @@ -403,15 +403,16 @@ static void DRPC_EmptyRequests(void) --------------------------------------------------*/ void DRPC_UpdatePresence(void) { - char detailstr[48+1]; - #ifdef USEMAPIMG char mapimg[8+1]; #endif +#ifndef DEVELOP + char detailstr[48+1]; char mapname[5+21+21+2+1]; char charimg[4+SKINNAMESIZE+1]; char charname[11+SKINNAMESIZE+1]; +#endif boolean joinSecretSet = false; @@ -437,10 +438,6 @@ void DRPC_UpdatePresence(void) discordPresence.largeImageKey = "miscdevelop"; discordPresence.largeImageText = "No peeking!"; discordPresence.state = "Development EXE"; - - DRPC_EmptyRequests(); - Discord_UpdatePresence(&discordPresence); - return; #endif // DEVELOP // Server info @@ -462,6 +459,7 @@ void DRPC_UpdatePresence(void) } } +#ifndef DEVELOP if (cv_advertise.value) { discordPresence.state = "Public"; @@ -470,6 +468,7 @@ void DRPC_UpdatePresence(void) { discordPresence.state = "Private"; } +#endif // DEVELOP discordPresence.partyId = server_context; // Thanks, whoever gave us Mumble support, for implementing the EXACT thing Discord wanted for this field! discordPresence.partySize = D_NumPlayers(); // Players in server @@ -482,6 +481,7 @@ void DRPC_UpdatePresence(void) // so that you don't ever end up using bad information from another server. memset(&discordInfo, 0, sizeof(discordInfo)); +#ifndef DEVELOP // Offline info if (Playing()) discordPresence.state = "Offline"; @@ -489,8 +489,10 @@ void DRPC_UpdatePresence(void) discordPresence.state = "Watching Replay"; else discordPresence.state = "Menu"; +#endif // DEVELOP } +#ifndef DEVELOP // Gametype info if ((gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_VOTING) && Playing()) { @@ -642,6 +644,7 @@ void DRPC_UpdatePresence(void) snprintf(charname, 28, "Character: %s", skins[players[consoleplayer].skin].realname); discordPresence.smallImageText = charname; // Character name } +#endif // DEVELOP if (joinSecretSet == false) { diff --git a/src/k_hud.c b/src/k_hud.c index 5211080a4..a278225dc 100644 --- a/src/k_hud.c +++ b/src/k_hud.c @@ -1876,7 +1876,7 @@ static void K_DrawKartPositionNum(INT32 num) while (num) { /* - + */ fx = K_DrawKartPositionNumPatch( @@ -1995,7 +1995,7 @@ static boolean K_drawKartPositionFaces(void) { flipflag = V_FLIP|V_VFLIP; // blonic flip xoff = yoff = 16; - } else + } else { flipflag = 0; xoff = yoff = 0; @@ -5324,3 +5324,24 @@ void K_drawKartHUD(void) K_DrawDirectorDebugger(); K_DrawGPRankDebugger(); } + +void K_DrawSticker(INT32 x, INT32 y, INT32 width, INT32 flags, boolean isSmall) +{ + patch_t *stickerEnd; + INT32 height; + + if (isSmall == true) + { + stickerEnd = W_CachePatchName("K_STIKE2", PU_CACHE); + height = 6; + } + else + { + stickerEnd = W_CachePatchName("K_STIKEN", PU_CACHE); + height = 11; + } + + V_DrawFixedPatch(x*FRACUNIT, y*FRACUNIT, FRACUNIT, flags, stickerEnd, NULL); + V_DrawFill(x, y, width, height, 24|flags); + V_DrawFixedPatch((x + width)*FRACUNIT, y*FRACUNIT, FRACUNIT, flags|V_FLIP, stickerEnd, NULL); +} diff --git a/src/k_hud.h b/src/k_hud.h index c16a45b69..df17214e2 100644 --- a/src/k_hud.h +++ b/src/k_hud.h @@ -46,6 +46,7 @@ void K_DrawLikeMapThumbnail(fixed_t x, fixed_t y, fixed_t width, UINT32 flags, p void K_drawTargetHUD(const vector3_t *origin, player_t *player); void K_drawButton(fixed_t x, fixed_t y, INT32 flags, patch_t *button[2], boolean pressed); void K_drawButtonAnim(INT32 x, INT32 y, INT32 flags, patch_t *button[2], tic_t animtic); +void K_DrawSticker(INT32 x, INT32 y, INT32 width, INT32 flags, boolean isSmall); extern patch_t *kp_capsuletarget_arrow[2][2]; extern patch_t *kp_capsuletarget_icon[2]; diff --git a/src/k_menu.h b/src/k_menu.h index 37a805dc6..8eaf787a8 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -427,6 +427,10 @@ extern menu_t MISC_StatisticsDef; extern menuitem_t MISC_SoundTest[]; extern menu_t MISC_SoundTestDef; +#ifdef HAVE_DISCORDRPC +extern menu_t MISC_DiscordRequestsDef; +#endif + // We'll need this since we're gonna have to dynamically enable and disable options depending on which state we're in. typedef enum { @@ -1232,6 +1236,20 @@ void M_SoundTest(INT32 choice); void M_DrawSoundTest(void); consvar_t *M_GetSoundTestVolumeCvar(void); +#ifdef HAVE_DISCORDRPC +extern struct discordrequestmenu_s { + tic_t ticker; + tic_t confirmDelay; + tic_t confirmLength; + boolean confirmAccept; + boolean removeRequest; +} discordrequestmenu; + +void M_DrawDiscordRequests(void); +void M_DiscordRequests(INT32 choice); +const char *M_GetDiscordName(discordRequest_t *r); +#endif + // These defines make it a little easier to make menus #define DEFAULTMENUSTYLE(source, prev, x, y)\ {\ diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 335a26a5e..ecb85d279 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -68,6 +68,10 @@ int snprintf(char *str, size_t n, const char *fmt, ...); //int vsnprintf(char *str, size_t n, const char *fmt, va_list ap); #endif +#ifdef HAVE_DISCORDRPC +#include "discord.h" +#endif + #define SKULLXOFF -32 #define LINEHEIGHT 16 #define STRINGHEIGHT 8 @@ -4082,7 +4086,7 @@ void M_DrawPause(void) y_data_t standings; memset(&standings, 0, sizeof (standings)); - standings.mainplayer = (demo.playback ? displayplayers[0] : consoleplayer); + standings.mainplayer = (demo.playback ? displayplayers[0] : consoleplayer); // See also G_GetNextMap, Y_CalculateMatchData if ( @@ -4900,7 +4904,7 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) } V_DrawThinString(1, BASEVIDHEIGHT-(9+3), V_ALLOWLOWERCASE|V_6WIDTHSPACE, gtname); - + break; } case SECRET_ENCORE: @@ -5856,3 +5860,94 @@ void M_DrawSoundTest(void) V_DrawCharacter(cursorx - 4, currentMenu->y - 8 - (skullAnimCounter/5), '\x1B' | V_SNAPTOTOP|highlightflags, false); // up arrow } + +#ifdef HAVE_DISCORDRPC +void M_DrawDiscordRequests(void) +{ + discordRequest_t *curRequest = discordRequestList; + UINT8 *colormap; + patch_t *hand = NULL; + + const char *wantText = "...would like to join!"; + const char *acceptText = "Accept" ; + const char *declineText = "Decline"; + + INT32 x = 100; + INT32 y = 133; + + INT32 slide = 0; + INT32 maxYSlide = 18; + + if (discordrequestmenu.confirmDelay > 0) + { + if (discordrequestmenu.confirmAccept == true) + { + colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_GREEN, GTC_MENUCACHE); + hand = W_CachePatchName("K_LAPH02", PU_CACHE); + } + else + { + colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_RED, GTC_MENUCACHE); + hand = W_CachePatchName("K_LAPH03", PU_CACHE); + } + + slide = discordrequestmenu.confirmLength - discordrequestmenu.confirmDelay; + } + else + { + colormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_GREY, GTC_MENUCACHE); + } + + V_DrawFixedPatch(56*FRACUNIT, 150*FRACUNIT, FRACUNIT, 0, W_CachePatchName("K_LAPE01", PU_CACHE), colormap); + + if (hand != NULL) + { + fixed_t handoffset = (4 - abs((signed)(skullAnimCounter - 4))) * FRACUNIT; + V_DrawFixedPatch(56*FRACUNIT, 150*FRACUNIT + handoffset, FRACUNIT, 0, hand, NULL); + } + + K_DrawSticker(x + (slide * 32), y - 2, V_ThinStringWidth(M_GetDiscordName(curRequest), V_ALLOWLOWERCASE|V_6WIDTHSPACE), 0, false); + V_DrawThinString(x + (slide * 32), y - 1, V_ALLOWLOWERCASE|V_6WIDTHSPACE|V_YELLOWMAP, M_GetDiscordName(curRequest)); + + K_DrawSticker(x, y + 12, V_ThinStringWidth(wantText, V_ALLOWLOWERCASE|V_6WIDTHSPACE), 0, true); + V_DrawThinString(x, y + 10, V_ALLOWLOWERCASE|V_6WIDTHSPACE, wantText); + + INT32 confirmButtonWidth = SHORT(kp_button_a[1][0]->width); + INT32 declineButtonWidth = SHORT(kp_button_b[1][0]->width); + INT32 altDeclineButtonWidth = SHORT(kp_button_x[1][0]->width); + INT32 acceptTextWidth = V_ThinStringWidth(acceptText, V_ALLOWLOWERCASE|V_6WIDTHSPACE); + INT32 declineTextWidth = V_ThinStringWidth(declineText, V_ALLOWLOWERCASE|V_6WIDTHSPACE); + INT32 stickerWidth = (confirmButtonWidth + declineButtonWidth + altDeclineButtonWidth + acceptTextWidth + declineTextWidth); + + K_DrawSticker(x, y + 26, stickerWidth, 0, true); + K_drawButtonAnim(x, y + 22, V_SNAPTORIGHT, kp_button_a[1], discordrequestmenu.ticker); + + INT32 xoffs = confirmButtonWidth; + + V_DrawThinString((x + xoffs), y + 24, V_ALLOWLOWERCASE|V_6WIDTHSPACE, acceptText); + xoffs += acceptTextWidth; + + K_drawButtonAnim((x + xoffs), y + 22, V_SNAPTORIGHT, kp_button_b[1], discordrequestmenu.ticker); + xoffs += declineButtonWidth; + + K_drawButtonAnim((x + xoffs), y + 22, V_SNAPTORIGHT, kp_button_x[1], discordrequestmenu.ticker); + xoffs += altDeclineButtonWidth; + + V_DrawThinString((x + xoffs), y + 24, V_ALLOWLOWERCASE|V_6WIDTHSPACE, declineText); + + y -= 18; + + while (curRequest->next != NULL) + { + INT32 ySlide = min(slide * 4, maxYSlide); + + curRequest = curRequest->next; + + K_DrawSticker(x, y - 1 + ySlide, V_ThinStringWidth(M_GetDiscordName(curRequest), V_ALLOWLOWERCASE|V_6WIDTHSPACE), 0, false); + V_DrawThinString(x, y + ySlide, V_ALLOWLOWERCASE|V_6WIDTHSPACE, M_GetDiscordName(curRequest)); + + y -= 12; + maxYSlide = 12; + } +} +#endif diff --git a/src/menus/transient/CMakeLists.txt b/src/menus/transient/CMakeLists.txt index a1d0cd1c6..1b9309749 100644 --- a/src/menus/transient/CMakeLists.txt +++ b/src/menus/transient/CMakeLists.txt @@ -5,6 +5,7 @@ target_sources(SRB2SDL2 PRIVATE gametype.c manual.c sound-test.c + discord-requests.c message-box.c pause-game.c pause-replay.c diff --git a/src/menus/transient/discord-requests.c b/src/menus/transient/discord-requests.c new file mode 100644 index 000000000..4cb352d09 --- /dev/null +++ b/src/menus/transient/discord-requests.c @@ -0,0 +1,114 @@ +/// \file menus/transient/discord-requests.c +/// \brief Discord Requests menu + +#ifdef HAVE_DISCORDRPC +#include "../../k_menu.h" +#include "../../s_sound.h" +#include "../../discord.h" + +struct discordrequestmenu_s discordrequestmenu; + +static void M_DiscordRequestHandler(INT32 choice) +{ + const UINT8 pid = 0; + (void)choice; + + if (discordrequestmenu.confirmDelay > 0) + return; + + if (M_MenuConfirmPressed(pid)) + { + Discord_Respond(discordRequestList->userID, DISCORD_REPLY_YES); + discordrequestmenu.confirmAccept = true; + discordrequestmenu.confirmDelay = discordrequestmenu.confirmLength; + S_StartSound(NULL, sfx_s3k63); + } + else if (M_MenuBackPressed(pid)) + { + Discord_Respond(discordRequestList->userID, DISCORD_REPLY_NO); + discordrequestmenu.confirmAccept = false; + discordrequestmenu.confirmDelay = discordrequestmenu.confirmLength; + S_StartSound(NULL, sfx_s3kb2); + } +} + +static menuitem_t MISC_DiscordRequests[] = +{ + {IT_NOTHING | IT_KEYHANDLER, NULL, NULL, NULL, {.routine = M_DiscordRequestHandler}, 0, 0}, +}; + +static void M_DiscordRequestTick(void) +{ + discordrequestmenu.ticker++; + + if (discordrequestmenu.confirmDelay > 0) + { + discordrequestmenu.confirmDelay--; + + if (discordrequestmenu.confirmDelay == 0) + { + discordrequestmenu.removeRequest = true; + } + } + + if (discordrequestmenu.removeRequest == true) + { + DRPC_RemoveRequest(discordRequestList); + + if (discordRequestList == NULL) + { + // No other requests + PAUSE_Main[mpause_discordrequests].status = IT_DISABLED; + + if (currentMenu->prevMenu) + { + M_SetupNextMenu(currentMenu->prevMenu, true); + itemOn = mpause_continue; + } + else + M_ClearMenus(true); + } + + discordrequestmenu.removeRequest = false; + } +} + +menu_t MISC_DiscordRequestsDef = { + sizeof(MISC_DiscordRequests) / sizeof(menuitem_t), + &PAUSE_MainDef, + 0, + MISC_DiscordRequests, + 0, 0, + 0, 0, + 0, + NULL, + 0, 0, + M_DrawDiscordRequests, + M_DiscordRequestTick, + NULL, + NULL, + NULL, +}; + +void M_DiscordRequests(INT32 choice) +{ + (void)choice; + static const tic_t confirmLength = 3*TICRATE/4; + + discordrequestmenu.confirmLength = confirmLength; + MISC_DiscordRequestsDef.prevMenu = currentMenu; + M_SetupNextMenu(&MISC_DiscordRequestsDef, true); +} + +const char *M_GetDiscordName(discordRequest_t *r) +{ + if (r == NULL) + return ""; + + if (cv_discordstreamer.value) + return r->username; + + return va("%s#%s", r->username, r->discriminator); +} + +#endif // HAVE_DISCORDRPC diff --git a/src/menus/transient/pause-game.c b/src/menus/transient/pause-game.c index dcfcfc9f5..556aa2fd7 100644 --- a/src/menus/transient/pause-game.c +++ b/src/menus/transient/pause-game.c @@ -6,6 +6,10 @@ #include "../../m_cond.h" #include "../../s_sound.h" +#ifdef HAVE_DISCORDRPC +#include "../../discord.h" +#endif + // ESC pause menu // Since there's no descriptions to each item, we'll use the descriptions as the names of the patches we want to draw for each option :) @@ -32,7 +36,7 @@ menuitem_t PAUSE_Main[] = #ifdef HAVE_DISCORDRPC {IT_STRING | IT_CALL, "DISCORD REQUESTS", "M_ICODIS", - NULL, {NULL}, 0, 0}, + NULL, {.routine = M_DiscordRequests}, 0, 0}, #endif {IT_STRING | IT_CALL, "RESUME GAME", "M_ICOUNP", @@ -214,6 +218,14 @@ void M_PauseTick(void) } else pausemenu.openoffset /= 2; + +#ifdef HAVE_DISCORDRPC + // Show discord requests menu option if any requests are pending + if (discordRequestList) + { + PAUSE_Main[mpause_discordrequests].status = IT_STRING | IT_CALL; + } +#endif } boolean M_PauseInputs(INT32 ch) diff --git a/src/stun.c b/src/stun.cpp similarity index 94% rename from src/stun.c rename to src/stun.cpp index 9845900af..97b418c24 100644 --- a/src/stun.c +++ b/src/stun.cpp @@ -6,11 +6,13 @@ // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- -/// \file stun.c +/// \file stun.cpp /// \brief RFC 5389 client implementation to fetch external IP address. /* https://tools.ietf.org/html/rfc5389 */ +#include + #if defined (__linux__) #include #elif defined (_WIN32) @@ -33,7 +35,7 @@ consvar_t cv_stunserver = CVAR_INIT ( "stunserver", "stun.l.google.com:19302", CV_SAVE, NULL, NULL ); -static stun_callback_t stun_callback; +static std::vector stun_callbacks; /* 18.4 STUN UDP and TCP Port Numbers */ @@ -125,7 +127,7 @@ STUN_bind (stun_callback_t callback) memcpy(&doomcom->data[4], &MAGIC_COOKIE, 4U); memcpy(&doomcom->data[8], transaction_id, 12U); - stun_callback = callback; + stun_callbacks.push_back(callback); I_NetSend(); Net_CloseConnection(node);/* will handle response at I_NetGet */ @@ -137,7 +139,10 @@ STUN_xor_mapped_address (const char * const value) const UINT32 xaddr = *(const UINT32 *)&value[4]; const UINT32 addr = xaddr ^ MAGIC_COOKIE; - (*stun_callback)(addr); + for (auto &callback : stun_callbacks) + { + callback(addr); + } return 0U; } @@ -190,7 +195,7 @@ STUN_got_response This totals 10 bytes for the attribute. */ - if (size < 30U || stun_callback == NULL) + if (size < 30U || stun_callbacks.empty()) { return false; } @@ -225,7 +230,7 @@ STUN_got_response while (p < end) ; } - stun_callback = NULL; + stun_callbacks = {}; return true; }