mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-12-06 08:02:55 +00:00
569 lines
13 KiB
C
569 lines
13 KiB
C
// DR. ROBOTNIK'S RING RACERS
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 2025 by Kart Krew.
|
|
// Copyright (C) 2016 by Kay "Kaito" Sinclaire.
|
|
// Copyright (C) 2020 by Sonic Team Junior.
|
|
//
|
|
// This program is free software distributed under the
|
|
// terms of the GNU General Public License, version 2.
|
|
// See the 'LICENSE' file for more details.
|
|
//-----------------------------------------------------------------------------
|
|
/// \file menus/play-online-server-browser.c
|
|
/// \brief MULTIPLAYER ROOM FETCH / REFRESH THREADS
|
|
|
|
#include "../k_menu.h"
|
|
#include "../v_video.h"
|
|
#include "../s_sound.h"
|
|
|
|
//#define SERVERLISTDEBUG
|
|
|
|
#ifdef SERVERLISTDEBUG
|
|
#include "../m_random.h"
|
|
|
|
void M_ServerListFillDebug(void);
|
|
#endif
|
|
|
|
// Quit
|
|
static boolean M_ServerBrowserQuit(void)
|
|
{
|
|
serverlistultimatecount = 0;
|
|
return true;
|
|
}
|
|
|
|
menuitem_t PLAY_MP_ServerBrowser[] =
|
|
{
|
|
|
|
{IT_STRING | IT_CVAR, "SORT BY", NULL, // tooltip MUST be null.
|
|
NULL, {.cvar = &cv_serversort}, 0, 0},
|
|
|
|
{IT_STRING | IT_CALL, "REFRESH", NULL,
|
|
NULL, {.routine = &M_RefreshServers}, 0, 0},
|
|
|
|
{IT_NOTHING, NULL, NULL, NULL, {NULL}, 0, 0},
|
|
};
|
|
|
|
menu_t PLAY_MP_ServerBrowserDef = {
|
|
sizeof (PLAY_MP_ServerBrowser) / sizeof (menuitem_t),
|
|
&PLAY_MP_RoomSelectDef,
|
|
0,
|
|
PLAY_MP_ServerBrowser,
|
|
32, 36,
|
|
0, 0,
|
|
0,
|
|
"NETMD2",
|
|
0, 0,
|
|
M_DrawMPServerBrowser,
|
|
NULL,
|
|
M_MPServerBrowserTick,
|
|
NULL,
|
|
M_ServerBrowserQuit,
|
|
M_ServerBrowserInputs
|
|
};
|
|
|
|
// for server fetch threads...
|
|
M_waiting_mode_t m_waiting_mode = M_NOT_WAITING;
|
|
|
|
void
|
|
M_SetWaitingMode (int mode)
|
|
{
|
|
#ifdef HAVE_THREADS
|
|
I_lock_mutex(&k_menu_mutex);
|
|
#endif
|
|
{
|
|
m_waiting_mode = mode;
|
|
}
|
|
#ifdef HAVE_THREADS
|
|
I_unlock_mutex(k_menu_mutex);
|
|
#endif
|
|
}
|
|
|
|
int
|
|
M_GetWaitingMode (void)
|
|
{
|
|
int mode;
|
|
|
|
#ifdef HAVE_THREADS
|
|
I_lock_mutex(&k_menu_mutex);
|
|
#endif
|
|
{
|
|
mode = m_waiting_mode;
|
|
}
|
|
#ifdef HAVE_THREADS
|
|
I_unlock_mutex(k_menu_mutex);
|
|
#endif
|
|
|
|
return mode;
|
|
}
|
|
|
|
#ifdef MASTERSERVER
|
|
#ifdef HAVE_THREADS
|
|
void
|
|
Spawn_masterserver_thread (const char *name, void (*thread)(int*))
|
|
{
|
|
int *id = malloc(sizeof *id);
|
|
|
|
I_lock_mutex(&ms_QueryId_mutex);
|
|
{
|
|
*id = ms_QueryId;
|
|
}
|
|
I_unlock_mutex(ms_QueryId_mutex);
|
|
|
|
I_spawn_thread(name, (I_thread_fn)thread, id);
|
|
}
|
|
|
|
int
|
|
Same_instance (int id)
|
|
{
|
|
int okay;
|
|
|
|
I_lock_mutex(&ms_QueryId_mutex);
|
|
{
|
|
okay = ( id == ms_QueryId );
|
|
}
|
|
I_unlock_mutex(ms_QueryId_mutex);
|
|
|
|
return okay;
|
|
}
|
|
#endif/*HAVE_THREADS*/
|
|
|
|
void
|
|
Fetch_servers_thread (int *id)
|
|
{
|
|
msg_server_t * server_list;
|
|
|
|
(void)id;
|
|
|
|
M_SetWaitingMode(M_WAITING_SERVERS);
|
|
|
|
#ifdef HAVE_THREADS
|
|
server_list = GetShortServersList(*id);
|
|
#else
|
|
server_list = GetShortServersList(0);
|
|
#endif
|
|
|
|
if (server_list)
|
|
{
|
|
#ifdef HAVE_THREADS
|
|
if (Same_instance(*id))
|
|
#endif
|
|
{
|
|
M_SetWaitingMode(M_NOT_WAITING);
|
|
|
|
#ifdef HAVE_THREADS
|
|
I_lock_mutex(&ms_ServerList_mutex);
|
|
{
|
|
ms_ServerList = server_list;
|
|
}
|
|
I_unlock_mutex(ms_ServerList_mutex);
|
|
#else
|
|
CL_QueryServerList(server_list);
|
|
free(server_list);
|
|
#endif
|
|
}
|
|
#ifdef HAVE_THREADS
|
|
else
|
|
{
|
|
free(server_list);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef HAVE_THREADS
|
|
free(id);
|
|
#endif
|
|
}
|
|
#endif/*MASTERSERVER*/
|
|
|
|
// updates serverlist
|
|
void M_RefreshServers(INT32 choice)
|
|
{
|
|
(void)choice;
|
|
|
|
CL_UpdateServerList();
|
|
|
|
#ifdef SERVERLISTDEBUG
|
|
M_ServerListFillDebug();
|
|
#else /*SERVERLISTDEBUG*/
|
|
#ifdef MASTERSERVER
|
|
#ifdef HAVE_THREADS
|
|
Spawn_masterserver_thread("fetch-servers", Fetch_servers_thread);
|
|
#else/*HAVE_THREADS*/
|
|
Fetch_servers_thread(NULL);
|
|
#endif/*HAVE_THREADS*/
|
|
#endif/*MASTERSERVER*/
|
|
#endif /*SERVERLISTDEBUG*/
|
|
}
|
|
|
|
#ifdef UPDATE_ALERT
|
|
static void M_CheckMODVersion(int id)
|
|
{
|
|
char updatestring[500];
|
|
const char *updatecheck = GetMODVersion(id);
|
|
if(updatecheck)
|
|
{
|
|
sprintf(updatestring, UPDATE_ALERT_STRING, VERSIONSTRING, updatecheck);
|
|
#ifdef HAVE_THREADS
|
|
I_lock_mutex(&k_menu_mutex);
|
|
#endif
|
|
M_StartMessage("Game Update", updatestring, NULL, MM_NOTHING, NULL, "Return to Menu");
|
|
#ifdef HAVE_THREADS
|
|
I_unlock_mutex(k_menu_mutex);
|
|
#endif
|
|
}
|
|
}
|
|
#endif/*UPDATE_ALERT*/
|
|
|
|
#if defined (UPDATE_ALERT) && defined (HAVE_THREADS)
|
|
static void
|
|
Check_new_version_thread (int *id)
|
|
{
|
|
M_SetWaitingMode(M_WAITING_VERSION);
|
|
|
|
M_CheckMODVersion(*id);
|
|
|
|
if (Same_instance(*id))
|
|
{
|
|
Fetch_servers_thread(id);
|
|
}
|
|
else
|
|
{
|
|
free(id);
|
|
}
|
|
}
|
|
#endif/*defined (UPDATE_ALERT) && defined (HAVE_THREADS)*/
|
|
|
|
|
|
// Initializes serverlist when entering the menu...
|
|
void M_ServersMenu(INT32 choice)
|
|
{
|
|
(void)choice;
|
|
// modified game check: no longer handled
|
|
// we don't request a restart unless the filelist differs
|
|
|
|
CL_UpdateServerList();
|
|
|
|
mpmenu.ticker = 0;
|
|
mpmenu.servernum = 0;
|
|
mpmenu.scrolln = 0;
|
|
mpmenu.slide = 0;
|
|
|
|
PLAY_MP_ServerBrowserDef.prevMenu = currentMenu;
|
|
M_SetupNextMenu(&PLAY_MP_ServerBrowserDef, false);
|
|
itemOn = 0;
|
|
|
|
#ifdef SERVERLISTDEBUG
|
|
M_ServerListFillDebug();
|
|
#else /*SERVERLISTDEBUG*/
|
|
|
|
#if defined (MASTERSERVER) && defined (HAVE_THREADS)
|
|
I_lock_mutex(&ms_QueryId_mutex);
|
|
{
|
|
ms_QueryId++;
|
|
}
|
|
I_unlock_mutex(ms_QueryId_mutex);
|
|
|
|
I_lock_mutex(&ms_ServerList_mutex);
|
|
{
|
|
if (ms_ServerList)
|
|
{
|
|
free(ms_ServerList);
|
|
ms_ServerList = NULL;
|
|
}
|
|
}
|
|
I_unlock_mutex(ms_ServerList_mutex);
|
|
|
|
#ifdef UPDATE_ALERT
|
|
Spawn_masterserver_thread("check-new-version", Check_new_version_thread);
|
|
#else/*UPDATE_ALERT*/
|
|
Spawn_masterserver_thread("fetch-servers", Fetch_servers_thread);
|
|
#endif/*UPDATE_ALERT*/
|
|
#else/*defined (MASTERSERVER) && defined (HAVE_THREADS)*/
|
|
#ifdef UPDATE_ALERT
|
|
M_CheckMODVersion(0);
|
|
#endif/*UPDATE_ALERT*/
|
|
M_RefreshServers(0);
|
|
#endif/*defined (MASTERSERVER) && defined (HAVE_THREADS)*/
|
|
|
|
#endif /*SERVERLISTDEBUG*/
|
|
}
|
|
|
|
#ifdef SERVERLISTDEBUG
|
|
|
|
// Fill serverlist with a bunch of garbage to make our life easier in debugging
|
|
void M_ServerListFillDebug(void)
|
|
{
|
|
UINT8 i = 0;
|
|
|
|
serverlistcount = 40;
|
|
memset(serverlist, 0, sizeof(serverlist)); // zero out the array for convenience...
|
|
|
|
for (i = 0; i < serverlistcount; i++)
|
|
{
|
|
// We don't really care about the server node for this, let's just fill in the info so that we have a visual...
|
|
serverlist[i].info.maxplayer = M_RandomRange(8, 16);
|
|
UINT8 val = i % 16;
|
|
serverlist[i].info.numberofplayer = min(val, serverlist[i].info.maxplayer);
|
|
|
|
serverlist[i].info.avgpwrlv = M_RandomRange(500, 1500);
|
|
serverlist[i].info.time = M_RandomRange(1, 8); // ping
|
|
|
|
strcpy(serverlist[i].info.servername, va("Serv %d", i+1));
|
|
|
|
const char *tempgtname;
|
|
if (i < 10 && mpmenu.room == 1)
|
|
{
|
|
tempgtname = va("%c%c%c%c",
|
|
'A' + M_RandomKey(26),
|
|
'a' + M_RandomKey(26),
|
|
'a' + M_RandomKey(26),
|
|
'a' + M_RandomKey(26)
|
|
);
|
|
}
|
|
else
|
|
tempgtname = (i & 1) ? "Race" : "Battle";
|
|
|
|
strcpy(serverlist[i].info.gametypename, tempgtname);
|
|
|
|
const INT32 gtidentifier = G_GetGametypeByName(tempgtname);
|
|
UINT8 gtcalc = GTCALC_RACE;
|
|
if (gtidentifier != GT_RACE)
|
|
{
|
|
gtcalc = (gtidentifier == GT_BATTLE) ? GTCALC_BATTLE : GTCALC_CUSTOM;
|
|
}
|
|
serverlist[i].cachedgtcalc = gtcalc;
|
|
|
|
serverlist[i].info.kartvars = M_RandomRange(0, 3) & SV_SPEEDMASK;
|
|
|
|
serverlist[i].info.modifiedgame = M_RandomRange(0, 1);
|
|
|
|
CONS_Printf("Serv %d | %d...\n", i, serverlist[i].info.modifiedgame);
|
|
}
|
|
|
|
M_SortServerList();
|
|
}
|
|
|
|
#endif // SERVERLISTDEBUG
|
|
|
|
// Ascending order, not descending.
|
|
// The casts are safe as long as the caller doesn't do anything stupid.
|
|
#define SERVER_LIST_ENTRY_COMPARATOR(key) \
|
|
static int ServerListEntryComparator_##key(const void *entry1, const void *entry2) \
|
|
{ \
|
|
const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2; \
|
|
if (sa->info.key != sb->info.key) \
|
|
return sa->info.key - sb->info.key; \
|
|
return sa->info.time - sb->info.time; \
|
|
}
|
|
|
|
// This does descending instead of ascending.
|
|
#define SERVER_LIST_ENTRY_COMPARATOR_REVERSE(key) \
|
|
static int ServerListEntryComparator_##key##_reverse(const void *entry1, const void *entry2) \
|
|
{ \
|
|
const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2; \
|
|
if (sb->info.key != sa->info.key) \
|
|
return sb->info.key - sa->info.key; \
|
|
return sa->info.time - sb->info.time; \
|
|
}
|
|
|
|
//SERVER_LIST_ENTRY_COMPARATOR(time) -- done seperately due to the usual tiebreaker being time
|
|
SERVER_LIST_ENTRY_COMPARATOR(numberofplayer)
|
|
SERVER_LIST_ENTRY_COMPARATOR_REVERSE(numberofplayer)
|
|
SERVER_LIST_ENTRY_COMPARATOR_REVERSE(maxplayer)
|
|
SERVER_LIST_ENTRY_COMPARATOR(avgpwrlv)
|
|
|
|
static int ServerListEntryComparator_time(const void *entry1, const void *entry2)
|
|
{
|
|
const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2;
|
|
if (sa->info.time != sb->info.time)
|
|
return sa->info.time - sb->info.time;
|
|
return strcmp(sa->info.servername, sb->info.servername);
|
|
}
|
|
|
|
static int ServerListEntryComparator_gametypename(const void *entry1, const void *entry2)
|
|
{
|
|
const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2;
|
|
int c;
|
|
if (( c = strcasecmp(sa->info.gametypename, sb->info.gametypename) ))
|
|
return c;
|
|
return sa->info.time - sb->info.time;
|
|
}
|
|
|
|
static int ServerListEntryComparator_recommended(const void *entry1, const void *entry2)
|
|
{
|
|
const serverelem_t *sa = (const serverelem_t*)entry1, *sb = (const serverelem_t*)entry2;
|
|
|
|
INT32 saseedval = sa->info.numberofplayer;
|
|
INT32 sbseedval = sb->info.numberofplayer;
|
|
|
|
// Tyron wrote the following on 25072022:
|
|
// "sort should be two parts
|
|
// top part of the list is "all non-empty servers sorted by reverse playercount", with servers above a certain reported ping marked as bad connection or whatever
|
|
// bottom part of the list is all empty servers sorted by ping"
|
|
// toast is implementing on 27082023, over a year later, because
|
|
// "fixing server join flow" is saner to do near the end
|
|
|
|
{
|
|
const UINT8 SERVER_EMPTY = 1;
|
|
|
|
// The intent with this nudge is to show you
|
|
// good games you could get a memorable Duel in,
|
|
// with the possibility to really katamari into
|
|
// something more substantial.
|
|
// By comparison, empty games are not nearly as
|
|
// fun to get going, so let's lower their SEO.
|
|
if (!saseedval)
|
|
saseedval = MAXPLAYERS + SERVER_EMPTY;
|
|
if (!sbseedval)
|
|
sbseedval = MAXPLAYERS + SERVER_EMPTY;
|
|
}
|
|
|
|
if (saseedval != sbseedval)
|
|
return saseedval - sbseedval;
|
|
|
|
return sa->info.time - sb->info.time;
|
|
}
|
|
|
|
void M_SortServerList(void)
|
|
{
|
|
switch(cv_serversort.value)
|
|
{
|
|
case -1:
|
|
qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_recommended);
|
|
break;
|
|
case 0: // Ping.
|
|
qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_time);
|
|
break;
|
|
case 1: // AVG. Power Level
|
|
qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_avgpwrlv);
|
|
break;
|
|
case 2: // Most players.
|
|
qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_numberofplayer_reverse);
|
|
break;
|
|
case 3: // Least players.
|
|
qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_numberofplayer);
|
|
break;
|
|
case 4: // Max players.
|
|
qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_maxplayer_reverse);
|
|
break;
|
|
case 5: // Gametype.
|
|
qsort(serverlist, serverlistcount, sizeof(serverelem_t), ServerListEntryComparator_gametypename);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// Server browser inputs & ticker
|
|
void M_MPServerBrowserTick(void)
|
|
{
|
|
mpmenu.ticker++;
|
|
mpmenu.slide /= 2;
|
|
|
|
#if defined (MASTERSERVER) && defined (HAVE_THREADS)
|
|
I_lock_mutex(&ms_ServerList_mutex);
|
|
{
|
|
if (ms_ServerList)
|
|
{
|
|
CL_QueryServerList(ms_ServerList);
|
|
free(ms_ServerList);
|
|
ms_ServerList = NULL;
|
|
}
|
|
}
|
|
I_unlock_mutex(ms_ServerList_mutex);
|
|
#endif
|
|
|
|
CL_TimeoutServerList();
|
|
}
|
|
|
|
// Input handler for server browser.
|
|
boolean M_ServerBrowserInputs(INT32 ch)
|
|
{
|
|
UINT8 pid = 0;
|
|
INT16 maxscroll = serverlistcount - (SERVERSPERPAGE/2) - 2; // Why? Because
|
|
if (maxscroll < 0)
|
|
maxscroll = 0;
|
|
|
|
const INT16 serverbrowserOn = (currentMenu->numitems - 1);
|
|
|
|
(void) ch;
|
|
|
|
if (!itemOn && menucmd[pid].dpad_ud < 0)
|
|
{
|
|
if (serverlistcount)
|
|
{
|
|
// Return the MS listing to the bottom.
|
|
INT32 prevscroll = mpmenu.scrolln;
|
|
|
|
mpmenu.servernum = serverlistcount-1;
|
|
mpmenu.scrolln = maxscroll;
|
|
mpmenu.slide = SERVERSPACE * (prevscroll - (INT32)mpmenu.scrolln);
|
|
}
|
|
else
|
|
{
|
|
M_PrevOpt(); // Double apply
|
|
}
|
|
}
|
|
else if (itemOn == (serverbrowserOn - 1) && menucmd[pid].dpad_ud > 0 && !serverlistcount)
|
|
{
|
|
M_NextOpt(); // Double apply
|
|
}
|
|
else if (itemOn == serverbrowserOn) // server browser itself...
|
|
{
|
|
#ifndef SERVERLISTDEBUG
|
|
if (M_MenuConfirmPressed(pid))
|
|
{
|
|
M_SetMenuDelay(pid);
|
|
|
|
COM_BufAddText(va("connect node %d\n", serverlist[mpmenu.servernum].node));
|
|
|
|
M_PleaseWait();
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
if (menucmd[pid].dpad_ud > 0) // down
|
|
{
|
|
if ((UINT32)(mpmenu.servernum+1) < serverlistcount)
|
|
{
|
|
// Listing scroll down
|
|
mpmenu.servernum++;
|
|
if (mpmenu.scrolln < maxscroll && mpmenu.servernum > SERVERSPERPAGE/2)
|
|
{
|
|
mpmenu.scrolln++;
|
|
mpmenu.slide += SERVERSPACE;
|
|
}
|
|
|
|
S_StartSound(NULL, sfx_s3k5b);
|
|
M_SetMenuDelay(pid);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Return the MS listing to the top.
|
|
INT32 prevscroll = mpmenu.scrolln;
|
|
|
|
mpmenu.servernum = 0;
|
|
mpmenu.scrolln = 0;
|
|
mpmenu.slide = SERVERSPACE * (prevscroll - (INT32)mpmenu.scrolln);
|
|
}
|
|
else if (menucmd[pid].dpad_ud < 0)
|
|
{
|
|
if (mpmenu.servernum)
|
|
{
|
|
// Listing scroll up
|
|
if (mpmenu.servernum <= (INT16)maxscroll && mpmenu.scrolln)
|
|
{
|
|
mpmenu.scrolln--;
|
|
mpmenu.slide -= SERVERSPACE;
|
|
}
|
|
mpmenu.servernum--;
|
|
|
|
S_StartSound(NULL, sfx_s3k5b);
|
|
M_SetMenuDelay(pid);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false; // use normal behaviour.
|
|
}
|