diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 8ae37a308..152d62863 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -154,6 +154,8 @@ static boolean cl_redownloadinggamestate = false; static UINT8 localtextcmd[MAXSPLITSCREENPLAYERS][MAXTEXTCMD]; static tic_t neededtic; SINT8 servernode = 0; // the number of the server node +SINT8 joinnode = 0; // used for CL_VIEWSERVER + char connectedservername[MAXSERVERNAME+1]; /// \brief do we accept new players? /// \todo WORK! @@ -541,27 +543,6 @@ void ReadLmpExtraData(UINT8 **demo_pointer, INT32 playernum) static INT16 Consistancy(void); -typedef enum -{ - CL_SEARCHING, - CL_CHECKFILES, - CL_DOWNLOADFILES, - CL_DOWNLOADFAILED, - CL_ASKJOIN, - CL_LOADFILES, - CL_SETUPFILES, - CL_WAITJOINRESPONSE, - CL_DOWNLOADSAVEGAME, - CL_CONNECTED, - CL_ABORTED, - CL_ASKFULLFILELIST, - CL_CONFIRMCONNECT, -#ifdef HAVE_CURL - CL_PREPAREHTTPFILES, - CL_DOWNLOADHTTPFILES, -#endif -} cl_mode_t; - static void GetPackets(void); static cl_mode_t cl_mode = CL_SEARCHING; @@ -572,6 +553,34 @@ char http_source[MAX_MIRROR_LENGTH]; static UINT16 cl_lastcheckedfilecount = 0; // used for full file list +static const char* servmus_1 = "SRVMS1"; +static const char* servmus_2 = "SRVMS2"; +// "SRVMS3" allows for the music position to be kept between servmus_1 and servmus_3 +// also takes priority over servmus_2 +static const char* servmus_3 = "SRVMS3"; + +static void ChangeServMusic(const char* musname, boolean fallback, boolean keepPos) +{ + if (S_MusicExists(musname)) + if (keepPos) + S_ChangeMusicEx(musname,0,true, S_GetMusicPosition(), 0,0); + else + S_ChangeMusicInternal(musname,true); + else if (fallback) + S_ChangeMusicInternal("BLANCD",true); +} + +// Let external code read and modify this stuff. +INT32 GetClientMode(void) +{ + return cl_mode; +} + +void ChangeClientMode(INT32 mode) +{ + cl_mode = mode; +} + // // CL_DrawConnectionStatus // @@ -589,6 +598,7 @@ static inline void CL_DrawConnectionStatus(void) #ifdef HAVE_CURL && cl_mode != CL_DOWNLOADHTTPFILES #endif + && cl_mode != CL_VIEWSERVER ) { INT32 i, animtime = ((ccstime / 4) & 15) + 16; @@ -703,6 +713,14 @@ static inline void CL_DrawConnectionStatus(void) V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24, MENUCAPS|V_20TRANS|V_MONOSPACE, va(" %2u/%2u Files",loadcompletednum,fileneedednum)); } + else if (cl_mode == CL_VIEWSERVER) + { + if (!menustack[0]) + { + M_StartControlPanel(); + M_EnterMenu(MN_VIEWSERVER, true, 0); + } + } else if (lastfilenum != -1) { INT32 dldlength; @@ -944,7 +962,7 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime) else netbuffer->u.serverinfo.refusereason = 0; - strncpy(netbuffer->u.serverinfo.gametypename, Gametype_Names[prefgametype], + strncpy(netbuffer->u.serverinfo.gametypename, Gametype_Names[gametype], sizeof netbuffer->u.serverinfo.gametypename); netbuffer->u.serverinfo.modifiedgame = (UINT8)modifiedgame; netbuffer->u.serverinfo.cheatsenabled = CV_CheatsEnabled(); @@ -1043,20 +1061,16 @@ static void SV_SendPlayerInfo(INT32 node) for (i = 0; i < MSCOMPAT_MAXPLAYERS; i++) { - if (i >= MAXPLAYERS) - { - netbuffer->u.playerinfo[i].num = 255; // Master Server compat - continue; - } - - if (!playeringame[i]) + if (i >= MAXPLAYERS || playernode[i] == UINT8_MAX || !playeringame[i]) { netbuffer->u.playerinfo[i].num = 255; // This slot is empty. continue; } netbuffer->u.playerinfo[i].num = i; - strncpy(netbuffer->u.playerinfo[i].name, (const char *)&player_names[i], MAXPLAYERNAME+1); + memset(netbuffer->u.playerinfo[i].name, 0x00, sizeof(netbuffer->u.playerinfo[i].name)); + memcpy(netbuffer->u.playerinfo[i].name, player_names[i], sizeof(player_names[i])); + netbuffer->u.playerinfo[i].name[MAXPLAYERNAME] = '\0'; //fetch IP address @@ -1603,6 +1617,10 @@ static boolean CL_FinishedFileList(void) "Press ACCEL to continue or BRAKE to cancel.\n\n" ), M_ConfirmConnect, MM_EVENTHANDLER); cl_mode = CL_CONFIRMCONNECT; + if (S_MusicExists(servmus_3)) + ChangeServMusic(servmus_3, true,true); + else + ChangeServMusic(servmus_2, false,false); } else cl_mode = CL_LOADFILES; @@ -1672,6 +1690,11 @@ static boolean CL_FinishedFileList(void) Z_Free(downloadsize); cl_mode = CL_CONFIRMCONNECT; + + if (S_MusicExists(servmus_3)) + ChangeServMusic(servmus_3, true,true); + else + ChangeServMusic(servmus_2, false,false); } #ifdef HAVE_CURL else @@ -1771,7 +1794,8 @@ static boolean CL_ServerConnectionSearchTicker(tic_t *asksent) return true; } - cl_mode = CL_CHECKFILES; + cl_mode = CL_VIEWSERVER; //cl_mode = CL_CHECKFILES; + ChangeServMusic(servmus_1, true,false); } else { @@ -1816,7 +1840,10 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic case CL_ASKFULLFILELIST: if (cl_lastcheckedfilecount == UINT16_MAX) // All files retrieved - cl_mode = CL_CHECKFILES; + { + cl_mode = CL_VIEWSERVER; //cl_mode = CL_CHECKFILES; + ChangeServMusic(servmus_1, true,false); + } else if (fileneedednum != cl_lastcheckedfilecount || I_GetTime() >= *asksent) { if (CL_AskFileList(fileneedednum)) @@ -2002,8 +2029,15 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic else continue; - if (key == KEY_ESCAPE || G_ControlBoundToKey(0, gc_brake, key, true)) - cl_mode = CL_ABORTED; + if (cl_mode == CL_VIEWSERVER && menustack[0]) + { + M_Responder(ev); + } + else + { + if (key == KEY_ESCAPE || G_ControlBoundToKey(0, gc_brake, key, true)) + cl_mode = CL_ABORTED; + } } } @@ -4331,31 +4365,7 @@ static void HandlePacketFromAwayNode(SINT8 node) switch (netbuffer->packettype) { case PT_ASKINFOVIAMS: -#if 0 - if (server && serverrunning) - { - INT32 clientnode; - if (ms_RoomId < 0) // ignore if we're not actually on the MS right now - { - Net_CloseConnection(node); // and yes, close connection - return; - } - clientnode = I_NetMakeNode(netbuffer->u.msaskinfo.clientaddr); - if (clientnode != -1) - { - SV_SendServerInfo(clientnode, (tic_t)LONG(netbuffer->u.msaskinfo.time)); - SV_SendPlayerInfo(clientnode); // Send extra info - Net_CloseConnection(clientnode); - // Don't close connection to MS... - } - else - Net_CloseConnection(node); // ...unless the IP address is not valid - } - else - Net_CloseConnection(node); // you're not supposed to get it, so ignore it -#else Net_CloseConnection(node); -#endif break; case PT_TELLFILESNEEDED: @@ -4536,6 +4546,9 @@ static void HandlePacketFromAwayNode(SINT8 node) case PT_CLIENTCMD: break; // This is not an "unknown packet" + case PT_PLAYERINFO: + break; + case PT_SERVERTICS: // Do not remove my own server (we have just get a out of order packet) if (node == servernode) diff --git a/src/d_clisrv.h b/src/d_clisrv.h index 558d02cfa..5ccd63a3d 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -471,6 +471,28 @@ void SendNetXCmdForPlayer(UINT8 playerid, netxcmd_t id, const void *param, size_ #define SendNetXCmd(id, param, nparam) SendNetXCmdForPlayer(0, id, param, nparam) // Shortcut for P1 void SendKick(UINT8 playernum, UINT8 msg); +typedef enum +{ + CL_SEARCHING, + CL_CHECKFILES, + CL_DOWNLOADFILES, + CL_DOWNLOADFAILED, + CL_ASKJOIN, + CL_LOADFILES, + CL_SETUPFILES, + CL_WAITJOINRESPONSE, + CL_DOWNLOADSAVEGAME, + CL_CONNECTED, + CL_ABORTED, + CL_ASKFULLFILELIST, + CL_CONFIRMCONNECT, + CL_VIEWSERVER, +#ifdef HAVE_CURL + CL_PREPAREHTTPFILES, + CL_DOWNLOADHTTPFILES, +#endif +} cl_mode_t; + // Create any new ticcmds and broadcast to other players. void NetKeepAlive(void); void NetUpdate(void); @@ -530,6 +552,10 @@ extern char motd[254], server_context[8]; extern UINT8 playernode[MAXPLAYERS]; /* consoleplayer of this player (splitscreen) */ extern UINT8 playerconsole[MAXPLAYERS]; +extern SINT8 joinnode; + +INT32 GetClientMode(void); +void ChangeClientMode(INT32 mode); INT32 D_NumPlayers(void); boolean D_IsPlayerHumanAndGaming(INT32 player_number); diff --git a/src/deh_tables.c b/src/deh_tables.c index a33552a27..7bb50cad0 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -748,6 +748,8 @@ struct menu_routine_s const MENU_ROUTINES[] = { { "ASSIGNJOYSTICK", &MR_AssignJoystick }, { "HANDLEMONITORTOGGLES", &MR_HandleMonitorToggles }, { "RESTARTAUDIO", &MR_RestartAudio }, + { "QUITVIEWSERVER", &MR_QuitViewServer }, + { "HANDLEVIEWSERVER", &MR_HandleViewServer }, #ifdef HAVE_DISCORDRPC { "HANDLEDISCORDREQUESTS", &MR_HandleDiscordRequests }, #endif @@ -776,7 +778,7 @@ struct menu_drawer_s const MENU_DRAWERS[] = { { "DRAWCONTROL", &M_DrawControl }, { "DRAWJOYSTICK", &M_DrawJoystick }, { "DRAWMONITORTOGGLES", &M_DrawMonitorToggles }, - { "DRAWCONNECTMENU", &M_DrawConnectMenu }, + { "DRAWVIEWSERVER", &M_DrawViewServer }, #ifdef HAVE_DISCORDRPC { "DRAWDISCORDREQUESTS", &M_DrawDiscordRequests }, #endif diff --git a/src/doomdef.h b/src/doomdef.h index c69a3e0e7..2c0569e2f 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -103,7 +103,7 @@ extern "C" { // Special Hashing. //#define NOMD5 -//#define NOFILEHASH +#define NOFILEHASH // Uncheck this to compile debugging code //#define RANGECHECK diff --git a/src/f_finale.c b/src/f_finale.c index f1f5c749b..6990d2b7f 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -919,6 +919,8 @@ static const char *blancredits[] = { "\"Lactozilla\"", "\"xyzzy\"", "\"SuperJustinBros\"", + "SRB2Classic Team", + "\"luigi budd\"", "", "\1Ring Racers Programming", "Kart Krew Dev", diff --git a/src/g_game.c b/src/g_game.c index 547d3a1e5..bd0e0d377 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -768,6 +768,31 @@ INT32 G_MapNumber(const char * name) #endif } +/** Returns the map number from level title. + * + * \param name Map name; + * \return Map number. + * \sa G_BuildMapName, nextmapspecial_t + */ +INT32 G_LevelTitleToMapNum(const char * leveltitle) +{ + INT32 map; + + for (map = 0; map < nummapheaders; ++map) + { + if (!strcasecmp(leveltitle, mapheaderinfo[map]->lvlttl)) + { + return map; + } + + if (!strcasecmp(leveltitle, va("%s %s", mapheaderinfo[map]->lvlttl, mapheaderinfo[map]->actnum))) + { + return map; + } + } + return INT32_MIN; +} + // convert kart map number to native map number INT16 G_KartMapToNative(INT16 mapnum) { diff --git a/src/g_game.h b/src/g_game.h index 8886f0984..e5d2df6f5 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -100,6 +100,7 @@ void weaponPrefChange4(void); const char *G_BuildMapName(INT32 map); INT32 G_MapNumber(const char *mapname); +INT32 G_LevelTitleToMapNum(const char * leveltitle); INT16 G_KartMapToNative(INT16 mapnum); INT16 G_NativeMapToKart(INT16 mapnum); diff --git a/src/info/menus.h b/src/info/menus.h index febb36a16..aef00e12e 100644 --- a/src/info/menus.h +++ b/src/info/menus.h @@ -77,6 +77,7 @@ _(MISC_REPLAYSTART) _(AD_MAIN) // MISC +_(VIEWSERVER) _(HELP) _(SPAUSE) _(MPAUSE) diff --git a/src/m_menu.c b/src/m_menu.c index b7976ddab..4902a04e2 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -268,6 +268,8 @@ static INT16 M_GetMenuIndex(menutype_t type, const char *name) #define M_SetItemArgument(t, n, v) (M_GetMenuItem(t, n)->argument = v) #define M_SetItemX(t, n, v) (M_GetMenuItem(t, n)->x = v) #define M_SetItemY(t, n, v) (M_GetMenuItem(t, n)->y = v) +#define M_SetItemText(t, n, s) (M_GetMenuItem(t, n)->text = s) +#define M_GetItemX(t, n) (M_GetMenuItem(t, n)->x) #define M_GetItemY(t, n) (M_GetMenuItem(t, n)->y) static void M_ChangeItemStatus(menutype_t type, const char *name, UINT16 flag, boolean cond) @@ -7744,6 +7746,352 @@ INT32 MR_HandleMonitorToggles(INT32 choice) return true; } +INT32 MR_QuitViewServer(INT32 choice) +{ + (void)choice; + + D_QuitNetGame(); + CL_Reset(); + return true; +} + +static boolean viewserver_showaddons = false; +static INT32 viewserver_scroll = 0; +static INT16 viewserver_animcount = 8; + +INT32 MR_HandleViewServer(INT32 choice) +{ + switch (choice) + { + case KEY_ENTER: + S_StartSound(NULL, sfx_menu1); + ChangeClientMode(CL_CHECKFILES); + M_ClearMenus(false); + return true; + break; + + case KEY_SPACE: + S_StartSound(NULL, sfx_menu1); + viewserver_showaddons = !viewserver_showaddons; + return true; + break; + + case KEY_UPARROW: + case KEY_DOWNARROW: + if (viewserver_showaddons && fileneedednum > 22) + { + if (choice == KEY_UPARROW) + viewserver_scroll -= 1; + else if (choice == KEY_DOWNARROW) + viewserver_scroll += 1; + S_StartSound(NULL, sfx_menu1); + if (viewserver_scroll < 0) viewserver_scroll = 0; + if (viewserver_scroll >= fileneedednum - 22) viewserver_scroll = fileneedednum -22; + return true; + } + break; + + default: + break; + } + + return false; +} + +void M_DrawViewServer(void) +{ + UINT32 ping = (UINT32)serverlist[joinnode].info.time; + UINT8 kartvars = serverlist[joinnode].info.kartvars; + const char *speedstring = "Auto"; + const char *serverstring = "Listen"; + const char *maptitle = serverlist[joinnode].info.maptitle; + INT32 mapnum = G_LevelTitleToMapNum(maptitle); + patch_t *current_map; + + V_DrawFill(M_GetItemX(MN_VIEWSERVER, "TOPBOXXY"), M_GetItemY(MN_VIEWSERVER, "TOPBOXXY"), M_GetItemX(MN_VIEWSERVER, "TOPBOXWH"), M_GetItemY(MN_VIEWSERVER, "TOPBOXWH"), 159); + + V_DrawThinString(M_GetItemX(MN_VIEWSERVER, "SERVERNAME"), M_GetItemY(MN_VIEWSERVER, "SERVERNAME"), V_ALLOWLOWERCASE, va("%s", serverlist[joinnode].info.servername)); + + if (mapnum == INT32_MIN) + { + V_DrawSmallScaledPatch(10, 18, 0, W_CachePatchName("RANDOMLV", PU_CACHE)); + } + else + { + fixed_t scale = M_GetMapThumbnail(mapnum, ¤t_map)/4; + V_DrawFixedPatch(10< 0) + { + V_DrawThinString(M_GetItemX(MN_VIEWSERVER, "SERVERADDON"), M_GetItemY(MN_VIEWSERVER, "SERVERADDON"), V_ALLOWLOWERCASE|warningflags, va("%i Addons", fileneedednum)); + } + else + { + V_DrawThinString(M_GetItemX(MN_VIEWSERVER, "SERVERADDON"), M_GetItemY(MN_VIEWSERVER, "SERVERADDON"), V_ALLOWLOWERCASE|highlightflags, "Vanilla"); + } + + if (serverlist[joinnode].info.cheatsenabled) + { + V_DrawRightAlignedThinString(M_GetItemX(MN_VIEWSERVER, "SERVERCHEATS"), M_GetItemY(MN_VIEWSERVER, "SERVERCHEATS"), V_ALLOWLOWERCASE|recommendedflags, "Cheats"); + } + + V_DrawRightAlignedThinString(M_GetItemX(MN_VIEWSERVER, "SERVERDEDICATED"), M_GetItemY(MN_VIEWSERVER, "SERVERDEDICATED"), V_ALLOWLOWERCASE|recommendedflags, va("%s Server", serverstring)); + + V_DrawFill(M_GetItemX(MN_VIEWSERVER, "MIDBOXXY"), M_GetItemY(MN_VIEWSERVER, "MIDBOXXY"), M_GetItemX(MN_VIEWSERVER, "MIDBOXWH"), M_GetItemY(MN_VIEWSERVER, "MIDBOXWH"), 159); + + if (!viewserver_showaddons) + { + INT32 i; + INT32 count = 0; + INT32 x = 14; + INT32 y = 84; + char player_name[MAXPLAYERNAME+1]; + plrinfo *playerinfo = netbuffer->u.playerinfo; + UINT8 playeramount = serverlist[joinnode].info.numberofplayer; + UINT8 maxplayer = serverlist[joinnode].info.maxplayer; + + V_DrawString(12, 74, V_ALLOWLOWERCASE|highlightflags, "Players"); + V_DrawRightAlignedString(BASEVIDWIDTH - 12, 74, V_ALLOWLOWERCASE|highlightflags, va("%i / %i", playeramount, maxplayer)); + + if (playeramount > 0) + { + for (i = 0; i < MAXPLAYERS; i++) + { + if (playerinfo[i].num < 255) + { + if (playeramount <= 16) + { + UINT8 skinum = playerinfo[i].skin; + INT32 flags = 0; + + if (R_SkinAvailable(skins[skinum].name) == -1) + { + INT32 statuscolor = 1; + + if (playerinfo[i].team == 0) { statuscolor = 112; } // playing + if (playerinfo[i].team == 1) { statuscolor = 35; } // ctf red team + if (playerinfo[i].team == 2) { statuscolor = 152; } // ctf blue team + if (playerinfo[i].team == 255) { statuscolor = 16; flags |= V_40TRANS; } // spectator or non-team + + V_DrawFill(x, y+5, 16, 16, statuscolor); + } + else + { + patch_t *facerank = faceprefix[skinum][FACE_RANK]; + UINT8 *colormap = R_GetTranslationColormap(skinum, skins[skinum].prefcolor, GTC_MENUCACHE); + + if (playerinfo[i].team == 255) + { + colormap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_GREY, GTC_MENUCACHE); + flags |= V_40TRANS; + } + + + V_DrawFixedPatch(x< maxcharlen && !small_mode) + V_DrawThinString(x, y, V_ALLOWLOWERCASE|V_6WIDTHSPACE, + va("\x82[#%d]\x80: %.*s...%s",i+1, charsonside, file_name, file_name+strlen(file_name)-((charsonside+1))) + ); + else + { + V_DrawThinString(x, y, V_ALLOWLOWERCASE|V_6WIDTHSPACE, + va("\x82[#%d]\x80: %s",i+1, file_name) + ); + + // make sure we have the space + if (small_mode) + { + float file_size = ((float)addon_file.totalsize); + UINT8 size_mode = 0; // regular bytes + //in megabytes + if (file_size >= (1024.0f * 1024.0f)) + { + size_mode = 1; + file_size /= (1024.0f * 1024.0f); + } + // KB + else if (file_size >= 1024.0f) + { + size_mode = 2; + file_size /= 1024.0f; + } + + V_DrawRightAlignedThinString(x + 292, + y, highlightflags|V_ALLOWLOWERCASE, + // "~" since its approx this size, we mightve lost some + // accuracy from only having 4 bytes carry the size + va("(~%.1f%s)", file_size, size_mode == 0 ? "b" : (size_mode == 2 ? "kb" : "mb")) + ); + } + } + + y += 9; + count++; + if (count == 11) + { + x = BASEVIDWIDTH/2; + y = 84; + } + // Cannot draw any more + if (count == 22) + break; + } + + // reiterate again!!! Yes!!! Yay!!! + { + UINT32 totalsize = 0; + UINT8 size_mode = 0; // regular bytes + + for (INT32 j = 0; j < fileneedednum; j++) + totalsize += fileneeded[j].totalsize; + totalsize = (float)totalsize; + + //in megabytes + if (totalsize >= (1024.0f * 1024.0f)) + { + size_mode = 1; + totalsize /= (1024.0f * 1024.0f); + } + // KB + else if (totalsize >= 1024.0f) + { + size_mode = 2; + totalsize /= 1024.0f; + } + + V_DrawRightAlignedThinString(BASEVIDWIDTH - 22, 74, + V_ALLOWLOWERCASE|highlightflags, + va("~%.1f%s total", (float)totalsize, size_mode == 0 ? "b" : (size_mode == 2 ? "kb" : "mb")) + ); + } + + // draw the little arrows + if (fileneedednum >= 22) + { + // up arrow + if (viewserver_scroll) + V_DrawRightAlignedThinString(BASEVIDWIDTH - 12, + 74 - (viewserver_animcount/5), highlightflags, + "\x1A" + ); + + if (viewserver_scroll != fileneedednum-22) + V_DrawRightAlignedThinString(BASEVIDWIDTH - 12, + y-9 + (viewserver_animcount/5), highlightflags, + "\x1B" + ); + } + } + +#undef maxcharlen +#undef charsonside + + // Buttons + V_DrawFill(8, BASEVIDHEIGHT - 14, BASEVIDWIDTH - 16, 13, 159); + V_DrawThinString( + 16, BASEVIDHEIGHT - 11, + V_ALLOWLOWERCASE, "[""\x82""ESC""\x80""] = Return" + ); + + V_DrawCenteredThinString( + BASEVIDWIDTH/2, BASEVIDHEIGHT - 11, + V_ALLOWLOWERCASE, + va("[""\x82""SPACE""\x80""] = %s", (viewserver_showaddons ? "Players" : "Addons")) + ); + + V_DrawRightAlignedThinString( + BASEVIDWIDTH - 12, BASEVIDHEIGHT - 11, + V_ALLOWLOWERCASE, "[""\x82""ENTER""\x80""] = Join" + ); +} + // ========= // Quit Game // ========= diff --git a/src/m_menu.h b/src/m_menu.h index 0e728f780..3557ffe8e 100644 --- a/src/m_menu.h +++ b/src/m_menu.h @@ -312,6 +312,8 @@ INT32 MR_ChangeControl(INT32 arg); INT32 MR_AssignJoystick(INT32 arg); INT32 MR_HandleMonitorToggles(INT32 choice); INT32 MR_RestartAudio(INT32 choice); +INT32 MR_QuitViewServer(INT32 choice); +INT32 MR_HandleViewServer(INT32 choice); #ifdef HAVE_DISCORDRPC INT32 MR_HandleDiscordRequests(INT32 choice); #endif @@ -338,6 +340,7 @@ void M_DrawMusicTest(void); void M_DrawControl(void); void M_DrawJoystick(void); void M_DrawMonitorToggles(void); +void M_DrawViewServer(void); #ifdef HAVE_DISCORDRPC void M_DrawDiscordRequests(void); #endif