From: divverent Date: Thu, 10 Oct 2013 17:12:47 +0000 (+0000) Subject: Add SLIF_CATEGORY for the server list, and a menu QC function to set it. X-Git-Tag: xonotic-v0.8.0~96^2~19 X-Git-Url: https://git.rm.cloudns.org/?a=commitdiff_plain;h=d93b11b3f40d1ca94ed20e70c7a2fcf95670df73;p=xonotic%2Fdarkplaces.git Add SLIF_CATEGORY for the server list, and a menu QC function to set it. Callback: float m_gethostcachecategory(float entry_id) { // Is supposed to use gethostcachestring() etc. and return a // category integer. // The server list is sorted by this category first if // SLSF_CATEGORIES is set in the sort flags. } From: Samual Lenks git-svn-id: svn://svn.icculus.org/twilight/trunk/darkplaces@12021 d7cf8633-e32d-0410-b094-e92efae38249 --- diff --git a/cvar.c b/cvar.c index 06afc0d2..e92e0dfa 100644 --- a/cvar.c +++ b/cvar.c @@ -397,8 +397,10 @@ static void Cvar_SetQuick_Internal (cvar_t *var, const char *value) if(var->integer <= 0) Cvar_Set("rcon_password", ""); } +#ifdef CONFIG_MENU else if (!strcmp(var->name, "net_slist_favorites")) NetConn_UpdateFavorites(); +#endif } Cvar_UpdateAutoCvar(var); diff --git a/dpdefs/menudefs.qc b/dpdefs/menudefs.qc index ae51fb02..ad7c1bd5 100644 --- a/dpdefs/menudefs.qc +++ b/dpdefs/menudefs.qc @@ -18,6 +18,7 @@ void(float keynr, float ascii) m_keydown; void() m_draw; void(float mode) m_toggle; void() m_shutdown; +// optional: float(float) m_gethostcachecategory; ///////////////////////////////////////////////////////// // sys constants @@ -561,7 +562,10 @@ void resethostcachemasks(void) = #615; void sethostcachemaskstring(float mask, float fld, string str, float op) = #616; void sethostcachemasknumber(float mask, float fld, float num, float op) = #617; void resorthostcache(void) = #618; -void sethostcachesort(float fld, float descending) = #619; +float SLSF_DESCENDING = 1; +float SLSF_FAVORITES = 2; +float SLSF_CATEGORIES = 4; +void sethostcachesort(float fld, float slsf) = #619; void refreshhostcache(...) = #620; // optional boolean argument "clear_list" float gethostcachenumber(float fld, float hostnr) = #621; float gethostcacheindexforkey(string key) = #622; diff --git a/menu.c b/menu.c index b5270e28..42fe27bb 100644 --- a/menu.c +++ b/menu.c @@ -5018,6 +5018,11 @@ static void M_NewMap(void) { } +static int M_GetServerListEntryCategory(const serverlist_entry_t *entry) +{ + return 0; +} + void M_Shutdown(void) { // reset key_dest @@ -5332,6 +5337,24 @@ static void MP_NewMap(void) prog->ExecuteProgram(prog, PRVM_menufunction(m_newmap),"m_newmap() required"); } +const serverlist_entry_t *serverlist_callbackentry = NULL; +static int MP_GetServerListEntryCategory(const serverlist_entry_t *entry) +{ + prvm_prog_t *prog = MVM_prog; + serverlist_callbackentry = entry; + if (PRVM_menufunction(m_gethostcachecategory)) + { + prog->globals.fp[OFS_PARM0] = (prvm_vec_t) -1; + prog->ExecuteProgram(prog, PRVM_menufunction(m_gethostcachecategory),"m_gethostcachecategory(float entry) required"); + serverlist_callbackentry = NULL; + return prog->globals.fp[OFS_RETURN]; + } + else + { + return 0; + } +} + static void MP_Shutdown (void) { prvm_prog_t *prog = MVM_prog; @@ -5392,6 +5415,7 @@ void (*MR_Draw) (void); void (*MR_ToggleMenu) (int mode); void (*MR_Shutdown) (void); void (*MR_NewMap) (void); +int (*MR_GetServerListEntryCategory) (const serverlist_entry_t *entry); void MR_SetRouting(qboolean forceold) { @@ -5404,6 +5428,7 @@ void MR_SetRouting(qboolean forceold) MR_ToggleMenu = M_ToggleMenu; MR_Shutdown = M_Shutdown; MR_NewMap = M_NewMap; + MR_GetServerListEntryCategory = M_GetServerListEntryCategory; M_Init(); } else @@ -5414,6 +5439,7 @@ void MR_SetRouting(qboolean forceold) MR_ToggleMenu = MP_ToggleMenu; MR_Shutdown = MP_Shutdown; MR_NewMap = MP_NewMap; + MR_GetServerListEntryCategory = MP_GetServerListEntryCategory; MP_Init(); } } diff --git a/menu.h b/menu.h index f8094596..c68c4295 100644 --- a/menu.h +++ b/menu.h @@ -81,6 +81,7 @@ extern void (*MR_Draw) (void); extern void (*MR_ToggleMenu) (int mode); extern void (*MR_Shutdown) (void); extern void (*MR_NewMap) (void); +extern int (*MR_GetServerListEntryCategory) (const serverlist_entry_t *entry); typedef struct video_resolution_s { diff --git a/mvm_cmds.c b/mvm_cmds.c index 7c10da02..12fcecdf 100644 --- a/mvm_cmds.c +++ b/mvm_cmds.c @@ -414,6 +414,9 @@ static void VM_M_setserverlistmasknumber(prvm_prog_t *prog) case SLIF_FREESLOTS: mask->info.freeslots = number; break; + case SLIF_CATEGORY: + mask->info.category = number; + break; case SLIF_ISFAVORITE: mask->info.isfavorite = number != 0; break; @@ -449,7 +452,7 @@ string getserverliststring(float field, float hostnr) */ static void VM_M_getserverliststring(prvm_prog_t *prog) { - serverlist_entry_t *cache; + const serverlist_entry_t *cache; int hostnr; VM_SAFEPARMCOUNT(2, VM_M_getserverliststring); @@ -458,12 +461,19 @@ static void VM_M_getserverliststring(prvm_prog_t *prog) hostnr = (int)PRVM_G_FLOAT(OFS_PARM1); - if(hostnr < 0 || hostnr >= serverlist_viewcount) + if(hostnr == -1 && serverlist_callbackentry) { - Con_Print("VM_M_getserverliststring: bad hostnr passed!\n"); - return; + cache = serverlist_callbackentry; + } + else + { + if(hostnr < 0 || hostnr >= serverlist_viewcount) + { + Con_Print("VM_M_getserverliststring: bad hostnr passed!\n"); + return; + } + cache = ServerList_GetViewEntry(hostnr); } - cache = ServerList_GetViewEntry(hostnr); switch( (int) PRVM_G_FLOAT(OFS_PARM0) ) { case SLIF_CNAME: PRVM_G_INT( OFS_RETURN ) = PRVM_SetTempString( prog, cache->info.cname ); @@ -507,7 +517,7 @@ float getserverlistnumber(float field, float hostnr) */ static void VM_M_getserverlistnumber(prvm_prog_t *prog) { - serverlist_entry_t *cache; + const serverlist_entry_t *cache; int hostnr; VM_SAFEPARMCOUNT(2, VM_M_getserverliststring); @@ -516,12 +526,19 @@ static void VM_M_getserverlistnumber(prvm_prog_t *prog) hostnr = (int)PRVM_G_FLOAT(OFS_PARM1); - if(hostnr < 0 || hostnr >= serverlist_viewcount) + if(hostnr == -1 && serverlist_callbackentry) { - Con_Print("VM_M_getserverliststring: bad hostnr passed!\n"); - return; + cache = serverlist_callbackentry; + } + else + { + if(hostnr < 0 || hostnr >= serverlist_viewcount) + { + Con_Print("VM_M_getserverliststring: bad hostnr passed!\n"); + return; + } + cache = ServerList_GetViewEntry(hostnr); } - cache = ServerList_GetViewEntry(hostnr); switch( (int) PRVM_G_FLOAT(OFS_PARM0) ) { case SLIF_MAXPLAYERS: PRVM_G_FLOAT( OFS_RETURN ) = cache->info.maxplayers; @@ -544,6 +561,9 @@ static void VM_M_getserverlistnumber(prvm_prog_t *prog) case SLIF_PROTOCOL: PRVM_G_FLOAT( OFS_RETURN ) = cache->info.protocol; break; + case SLIF_CATEGORY: + PRVM_G_FLOAT( OFS_RETURN ) = cache->info.category; + break; case SLIF_ISFAVORITE: PRVM_G_FLOAT( OFS_RETURN ) = cache->info.isfavorite; break; @@ -626,6 +646,8 @@ static void VM_M_getserverlistindexforkey(prvm_prog_t *prog) PRVM_G_FLOAT( OFS_RETURN ) = SLIF_FREESLOTS; else if( !strcmp( key, "protocol" ) ) PRVM_G_FLOAT( OFS_RETURN ) = SLIF_PROTOCOL; + else if( !strcmp( key, "category" ) ) + PRVM_G_FLOAT( OFS_RETURN ) = SLIF_CATEGORY; else if( !strcmp( key, "isfavorite" ) ) PRVM_G_FLOAT( OFS_RETURN ) = SLIF_ISFAVORITE; else diff --git a/netconn.c b/netconn.c index 84439332..f971fd24 100755 --- a/netconn.c +++ b/netconn.c @@ -53,6 +53,7 @@ static cvar_t sv_masters [] = {0, NULL, NULL, NULL} }; +#ifdef CONFIG_MENU static cvar_t sv_qwmasters [] = { {CVAR_SAVE, "sv_qwmaster1", "", "user-chosen qwmaster server 1"}, @@ -66,6 +67,7 @@ static cvar_t sv_qwmasters [] = {0, "sv_qwmasterextra5", "qwmaster.fodquake.net:27000", "Global master server. (admin: unknown)"}, {0, NULL, NULL, NULL} }; +#endif static double nextheartbeattime = 0; @@ -114,6 +116,7 @@ int serverreplycount = 0; challenge_t challenge[MAX_CHALLENGES]; +#ifdef CONFIG_MENU /// this is only false if there are still servers left to query static qboolean serverlist_querysleep = true; static qboolean serverlist_paused = false; @@ -121,6 +124,7 @@ static qboolean serverlist_paused = false; /// reply is received, to avoid issuing queries while master replies are still /// flooding in (which would make a mess of the ping times) static double serverlist_querywaittime = 0; +#endif static int cl_numsockets; static lhnetsocket_t *cl_sockets[16]; @@ -144,6 +148,7 @@ char sv_net_extresponse[NET_EXTRESPONSE_MAX][1400]; int sv_net_extresponse_count = 0; int sv_net_extresponse_last = 0; +#ifdef CONFIG_MENU // ServerList interface serverlist_mask_t serverlist_andmasks[SERVERLIST_ANDMASKCOUNT]; serverlist_mask_t serverlist_ormasks[SERVERLIST_ORMASKCOUNT]; @@ -218,12 +223,19 @@ static qboolean _ServerList_Entry_Compare( serverlist_entry_t *A, serverlist_ent { int result = 0; // > 0 if for numbers A > B and for text if A < B - if( serverlist_sortflags & SLSF_FAVORITESFIRST ) + if( serverlist_sortflags & SLSF_CATEGORIES ) + { + result = A->info.category - B->info.category; + if (result != 0) + return result < 0; + } + + if( serverlist_sortflags & SLSF_FAVORITES ) { if(A->info.isfavorite != B->info.isfavorite) return A->info.isfavorite; } - + switch( serverlist_sortbyfield ) { case SLIF_PING: result = A->info.ping - B->info.ping; @@ -264,6 +276,9 @@ static qboolean _ServerList_Entry_Compare( serverlist_entry_t *A, serverlist_ent case SLIF_QCSTATUS: result = strcasecmp( B->info.qcstatus, A->info.qcstatus ); // not really THAT useful, though break; + case SLIF_CATEGORY: + result = A->info.category - B->info.category; + break; case SLIF_ISFAVORITE: result = !!B->info.isfavorite - !!A->info.isfavorite; break; @@ -391,6 +406,8 @@ static qboolean _ServerList_Entry_Mask( serverlist_mask_t *mask, serverlist_info if( *mask->info.players && !_ServerList_CompareStr( info->players, mask->tests[SLIF_PLAYERS], mask->info.players ) ) return false; + if( !_ServerList_CompareInt( info->category, mask->tests[SLIF_CATEGORY], mask->info.category ) ) + return false; if( !_ServerList_CompareInt( info->isfavorite, mask->tests[SLIF_ISFAVORITE], mask->info.isfavorite )) return false; return true; @@ -440,6 +457,9 @@ static void ServerList_ViewList_Insert( serverlist_entry_t *entry ) } } + // refresh the "category" + entry->info.category = MR_GetServerListEntryCategory(entry); + // FIXME: change this to be more readable (...) // now check whether it passes through the masks for( start = 0 ; start < SERVERLIST_ANDMASKCOUNT && serverlist_andmasks[start].active; start++ ) @@ -587,6 +607,7 @@ void ServerList_QueryList(qboolean resetcache, qboolean querydp, qboolean queryq NetConn_QueryMasters(querydp, queryqw); } +#endif // rest @@ -1521,6 +1542,7 @@ int NetConn_IsLocalGame(void) return false; } +#ifdef CONFIG_MENU static int NetConn_ClientParsePacket_ServerList_ProcessReply(const char *addressstring) { int n; @@ -1729,17 +1751,21 @@ static void NetConn_ClientParsePacket_ServerList_ParseDPList(lhnetaddress_t *sen serverlist_querysleep = false; serverlist_querywaittime = realtime + 3; } +#endif static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *data, int length, lhnetaddress_t *peeraddress) { qboolean fromserver; int ret, c; - const char *s; - char *string, addressstring2[128], ipstring[32]; + char *string, addressstring2[128]; char stringbuf[16384]; char senddata[NET_HEADERSIZE+NET_MAXMESSAGE+CRYPTO_HEADERSIZE]; size_t sendlength; +#ifdef CONFIG_MENU char infostringvalue[MAX_INPUTLINE]; + char ipstring[32]; + const char *s; +#endif char vabuf[1024]; // quakeworld ingame packet @@ -1880,6 +1906,7 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat #endif return true; } +#ifdef CONFIG_MENU if (length >= 15 && !memcmp(string, "statusResponse\x0A", 15)) { serverlist_info_t *info; @@ -2016,6 +2043,7 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat serverlist_querywaittime = realtime + 3; return true; } +#endif if (!strncmp(string, "extResponse ", 12)) { ++cl_net_extresponse_count; @@ -2059,6 +2087,7 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat } if (length > 2 && !memcmp(string, "n\\", 2)) { +#ifdef CONFIG_MENU serverlist_info_t *info; int n; @@ -2102,7 +2131,7 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat } NetConn_ClientParsePacket_ServerList_UpdateCache(n); - +#endif return true; } if (string[0] == 'n') @@ -2124,8 +2153,10 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat // netquake control packets, supported for compatibility only if (length >= 5 && BuffBigLong(data) == ((int)NETFLAG_CTL | length) && !ENCRYPTION_REQUIRED) { +#ifdef CONFIG_MENU int n; serverlist_info_t *info; +#endif data += 4; length -= 4; @@ -2177,6 +2208,7 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat case CCREP_SERVER_INFO: if (developer_extra.integer) Con_DPrintf("Datagram_ParseConnectionless: received CCREP_SERVER_INFO from %s.\n", addressstring2); +#ifdef CONFIG_MENU // LordHavoc: because the quake server may report weird addresses // we just ignore it and keep the real address MSG_ReadString(&cl_message, cl_readstring, sizeof(cl_readstring)); @@ -2195,7 +2227,7 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat info->protocol = MSG_ReadByte(&cl_message); NetConn_ClientParsePacket_ServerList_UpdateCache(n); - +#endif break; case CCREP_RCON: // RocketGuy: ProQuake rcon support if (developer_extra.integer) @@ -2227,6 +2259,7 @@ static int NetConn_ClientParsePacket(lhnetsocket_t *mysocket, unsigned char *dat return ret; } +#ifdef CONFIG_MENU void NetConn_QueryQueueFrame(void) { int index; @@ -2315,6 +2348,7 @@ void NetConn_QueryQueueFrame(void) } } } +#endif void NetConn_ClientFrame(void) { @@ -2369,7 +2403,9 @@ void NetConn_ClientFrame(void) // R_TimeReport("clientparsepacket"); } } +#ifdef CONFIG_MENU NetConn_QueryQueueFrame(); +#endif if (cls.netcon && realtime > cls.netcon->timeout && !sv.active) { Con_Print("Connection timed out\n"); @@ -3495,6 +3531,7 @@ void NetConn_SleepMicroseconds(int microseconds) LHNET_SleepUntilPacket_Microseconds(microseconds); } +#ifdef CONFIG_MENU void NetConn_QueryMasters(qboolean querydp, qboolean queryqw) { int i, j; @@ -3597,16 +3634,12 @@ void NetConn_QueryMasters(qboolean querydp, qboolean queryqw) { if (sv_qwmasters[masternum].string && LHNETADDRESS_FromString(&masteraddress, sv_qwmasters[masternum].string, QWMASTER_PORT) && LHNETADDRESS_GetAddressType(&masteraddress) == LHNETADDRESS_GetAddressType(LHNET_AddressFromSocket(cl_sockets[i]))) { -#ifdef CONFIG_MENU if (m_state != m_slist) { -#endif char lookupstring[128]; LHNETADDRESS_ToString(&masteraddress, lookupstring, sizeof(lookupstring), true); Con_Printf("Querying master %s (resolved from %s)\n", lookupstring, sv_qwmasters[masternum].string); -#ifdef CONFIG_MENU } -#endif masterquerycount++; NetConn_Write(cl_sockets[i], request, (int)strlen(request) + 1, &masteraddress); } @@ -3630,11 +3663,10 @@ void NetConn_QueryMasters(qboolean querydp, qboolean queryqw) if (!masterquerycount) { Con_Print("Unable to query master servers, no suitable network sockets active.\n"); -#ifdef CONFIG_MENU M_Update_Return_Reason("No network"); -#endif } } +#endif void NetConn_Heartbeat(int priority) { @@ -3700,18 +3732,15 @@ void Net_Stats_f(void) PrintStats(conn); } +#ifdef CONFIG_MENU void Net_Refresh_f(void) { -#ifdef CONFIG_MENU if (m_state != m_slist) { -#endif Con_Print("Sending new requests to master servers\n"); ServerList_QueryList(false, true, false, true); Con_Print("Listening for replies...\n"); -#ifdef CONFIG_MENU } else ServerList_QueryList(false, true, false, false); -#endif } void Net_Slist_f(void) @@ -3719,16 +3748,12 @@ void Net_Slist_f(void) ServerList_ResetMasks(); serverlist_sortbyfield = SLIF_PING; serverlist_sortflags = 0; -#ifdef CONFIG_MENU if (m_state != m_slist) { -#endif Con_Print("Sending requests to master servers\n"); ServerList_QueryList(true, true, false, true); Con_Print("Listening for replies...\n"); -#ifdef CONFIG_MENU } else ServerList_QueryList(true, true, false, false); -#endif } void Net_SlistQW_f(void) @@ -3736,18 +3761,15 @@ void Net_SlistQW_f(void) ServerList_ResetMasks(); serverlist_sortbyfield = SLIF_PING; serverlist_sortflags = 0; -#ifdef CONFIG_MENU if (m_state != m_slist) { -#endif Con_Print("Sending requests to master servers\n"); ServerList_QueryList(true, false, true, true); serverlist_consoleoutput = true; Con_Print("Listening for replies...\n"); -#ifdef CONFIG_MENU } else ServerList_QueryList(true, false, true, false); -#endif } +#endif void NetConn_Init(void) { @@ -3755,9 +3777,11 @@ void NetConn_Init(void) lhnetaddress_t tempaddress; netconn_mempool = Mem_AllocPool("network connections", 0, NULL); Cmd_AddCommand("net_stats", Net_Stats_f, "print network statistics"); +#ifdef CONFIG_MENU Cmd_AddCommand("net_slist", Net_Slist_f, "query dp master servers and print all server information"); Cmd_AddCommand("net_slistqw", Net_SlistQW_f, "query qw master servers and print all server information"); Cmd_AddCommand("net_refresh", Net_Refresh_f, "query dp master servers and refresh all server information"); +#endif Cmd_AddCommand("heartbeat", Net_Heartbeat_f, "send a heartbeat to the master server (updates your server information)"); Cvar_RegisterVariable(&net_test); Cvar_RegisterVariable(&net_usesizelimit); diff --git a/netconn.h b/netconn.h index 6744318e..e9e4ddac 100755 --- a/netconn.h +++ b/netconn.h @@ -242,6 +242,7 @@ extern mempool_t *netconn_mempool; extern cvar_t hostname; extern cvar_t developer_networking; +#ifdef CONFIG_MENU #define SERVERLIST_VIEWLISTSIZE SERVERLIST_TOTALSIZE typedef enum serverlist_maskop_e @@ -296,6 +297,9 @@ typedef struct serverlist_info_s /// (an integer that is used for filtering incompatible servers, /// not filterable by QC) int gameversion; + + // categorized sorting + int category; /// favorite server flag qboolean isfavorite; } serverlist_info_t; @@ -316,6 +320,7 @@ typedef enum SLIF_FREESLOTS, SLIF_QCSTATUS, SLIF_PLAYERS, + SLIF_CATEGORY, SLIF_ISFAVORITE, SLIF_COUNT } serverlist_infofield_t; @@ -323,7 +328,8 @@ typedef enum typedef enum { SLSF_DESCENDING = 1, - SLSF_FAVORITESFIRST = 2 + SLSF_FAVORITES = 2, + SLSF_CATEGORIES = 4 } serverlist_sortflags_t; typedef enum @@ -377,10 +383,12 @@ extern unsigned short serverlist_viewlist[SERVERLIST_VIEWLISTSIZE]; extern int serverlist_cachecount; extern serverlist_entry_t *serverlist_cache; +extern const serverlist_entry_t *serverlist_callbackentry; extern qboolean serverlist_consoleoutput; void ServerList_GetPlayerStatistics(int *numplayerspointer, int *maxplayerspointer); +#endif //============================================================================ // @@ -396,11 +404,13 @@ extern char sv_net_extresponse[NET_EXTRESPONSE_MAX][1400]; extern int sv_net_extresponse_count; extern int sv_net_extresponse_last; +#ifdef CONFIG_MENU extern double masterquerytime; extern int masterquerycount; extern int masterreplycount; extern int serverquerycount; extern int serverreplycount; +#endif extern sizebuf_t cl_message; extern sizebuf_t sv_message; @@ -441,10 +451,12 @@ int NetConn_IsLocalGame(void); void NetConn_ClientFrame(void); void NetConn_ServerFrame(void); void NetConn_SleepMicroseconds(int microseconds); -void NetConn_QueryMasters(qboolean querydp, qboolean queryqw); void NetConn_Heartbeat(int priority); -void NetConn_QueryQueueFrame(void); void Net_Stats_f(void); + +#ifdef CONFIG_MENU +void NetConn_QueryMasters(qboolean querydp, qboolean queryqw); +void NetConn_QueryQueueFrame(void); void Net_Slist_f(void); void Net_SlistQW_f(void); void Net_Refresh_f(void); @@ -457,6 +469,7 @@ void ServerList_QueryList(qboolean resetcache, qboolean querydp, qboolean queryq /// called whenever net_slist_favorites changes void NetConn_UpdateFavorites(void); +#endif #define MAX_CHALLENGES 128 typedef struct challenge_s diff --git a/prvm_offsets.h b/prvm_offsets.h index c02bc07a..36256c5f 100644 --- a/prvm_offsets.h +++ b/prvm_offsets.h @@ -438,6 +438,7 @@ PRVM_DECLARE_function(m_init) PRVM_DECLARE_function(m_keydown) PRVM_DECLARE_function(m_keyup) PRVM_DECLARE_function(m_newmap) +PRVM_DECLARE_function(m_gethostcachecategory) PRVM_DECLARE_function(m_shutdown) PRVM_DECLARE_function(m_toggle) PRVM_DECLARE_function(main)