mirror of
https://git.do.srb2.org/STJr/SRB2.git
synced 2025-02-10 18:21:04 +00:00
1424 lines
36 KiB
C
1424 lines
36 KiB
C
// SONIC ROBO BLAST 2
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 1998-2000 by DooM Legacy Team.
|
|
// Copyright (C) 1999-2024 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 client_connection.h
|
|
/// \brief Client connection handling
|
|
|
|
#include "client_connection.h"
|
|
#include "gamestate.h"
|
|
#include "d_clisrv.h"
|
|
#include "d_netfil.h"
|
|
#include "../d_main.h"
|
|
#include "../f_finale.h"
|
|
#include "../g_game.h"
|
|
#include "../g_input.h"
|
|
#include "i_net.h"
|
|
#include "../i_system.h"
|
|
#include "../i_time.h"
|
|
#include "../i_video.h"
|
|
#include "../keys.h"
|
|
#include "../m_menu.h"
|
|
#include "../m_misc.h"
|
|
#include "../snake.h"
|
|
#include "../s_sound.h"
|
|
#include "../v_video.h"
|
|
#include "../y_inter.h"
|
|
#include "../z_zone.h"
|
|
#include "../doomtype.h"
|
|
#include "../doomstat.h"
|
|
#if defined (__GNUC__) || defined (__unix__)
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
cl_mode_t cl_mode = CL_SEARCHING;
|
|
static UINT16 cl_lastcheckedfilecount = 0; // used for full file list
|
|
boolean serverisfull = false; // lets us be aware if the server was full after we check files, but before downloading, so we can ask if the user still wants to download or not
|
|
tic_t firstconnectattempttime = 0;
|
|
UINT8 mynode;
|
|
static void *snake = NULL;
|
|
|
|
static boolean IsDownloadingFile(void)
|
|
{
|
|
if (cl_mode == CL_DOWNLOADFILES || cl_mode == CL_DOWNLOADHTTPFILES)
|
|
return filedownload.current != -1;
|
|
|
|
return false;
|
|
}
|
|
|
|
static void DrawConnectionStatusBox(void)
|
|
{
|
|
M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT-16-8, 32, 1);
|
|
|
|
if (cl_mode == CL_CONFIRMCONNECT || IsDownloadingFile())
|
|
return;
|
|
|
|
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-16, V_YELLOWMAP, "Press ESC to abort");
|
|
}
|
|
|
|
static void DrawFileProgress(fileneeded_t *file, int y)
|
|
{
|
|
Net_GetNetStat();
|
|
|
|
INT32 dldlength = (INT32)((file->currentsize/(double)file->totalsize) * 256);
|
|
if (dldlength > 256)
|
|
dldlength = 256;
|
|
V_DrawFill(BASEVIDWIDTH/2-128, y, 256, 8, 111);
|
|
V_DrawFill(BASEVIDWIDTH/2-128, y, dldlength, 8, 96);
|
|
|
|
const char *progress_str;
|
|
if (file->totalsize >= 1024*1024)
|
|
progress_str = va(" %.2fMiB/%.2fMiB", (double)file->currentsize / (1024*1024), (double)file->totalsize / (1024*1024));
|
|
else if (file->totalsize < 1024)
|
|
progress_str = va(" %4uB/%4uB", file->currentsize, file->totalsize);
|
|
else
|
|
progress_str = va(" %.2fKiB/%.2fKiB", (double)file->currentsize / 1024, (double)file->totalsize / 1024);
|
|
|
|
V_DrawString(BASEVIDWIDTH/2-128, y, V_20TRANS|V_ALLOWLOWERCASE, progress_str);
|
|
|
|
V_DrawRightAlignedString(BASEVIDWIDTH/2+128, y, V_20TRANS|V_MONOSPACE, va("%3.1fK/s ", ((double)getbps)/1024));
|
|
}
|
|
|
|
//
|
|
// CL_DrawConnectionStatus
|
|
//
|
|
// Keep the local client informed of our status.
|
|
//
|
|
static void CL_DrawConnectionStatus(void)
|
|
{
|
|
INT32 ccstime = I_GetTime();
|
|
|
|
// Draw background fade
|
|
V_DrawFadeScreen(0xFF00, 16); // force default
|
|
|
|
if (cl_mode != CL_DOWNLOADFILES && cl_mode != CL_DOWNLOADHTTPFILES && cl_mode != CL_LOADFILES)
|
|
{
|
|
INT32 animtime = ((ccstime / 4) & 15) + 16;
|
|
UINT8 palstart;
|
|
const char *cltext;
|
|
|
|
// Draw the bottom box.
|
|
DrawConnectionStatusBox();
|
|
|
|
if (cl_mode == CL_SEARCHING)
|
|
palstart = 32; // Red
|
|
else if (cl_mode == CL_CONFIRMCONNECT)
|
|
palstart = 48; // Orange
|
|
else
|
|
palstart = 96; // Green
|
|
|
|
if (!(cl_mode == CL_DOWNLOADSAVEGAME && filedownload.current != -1))
|
|
for (INT32 i = 0; i < 16; ++i) // 15 pal entries total.
|
|
V_DrawFill((BASEVIDWIDTH/2-128) + (i * 16), BASEVIDHEIGHT-16, 16, 8, palstart + ((animtime - i) & 15));
|
|
|
|
switch (cl_mode)
|
|
{
|
|
case CL_DOWNLOADSAVEGAME:
|
|
if (fileneeded && filedownload.current != -1)
|
|
{
|
|
fileneeded_t *file = &fileneeded[filedownload.current];
|
|
|
|
cltext = M_GetText("Downloading game state...");
|
|
|
|
DrawFileProgress(file, BASEVIDHEIGHT-16);
|
|
}
|
|
else
|
|
cltext = M_GetText("Waiting to download game state...");
|
|
break;
|
|
case CL_ASKFULLFILELIST:
|
|
case CL_CHECKFILES:
|
|
cltext = M_GetText("Checking server addon list...");
|
|
break;
|
|
case CL_CONFIRMCONNECT:
|
|
cltext = "";
|
|
break;
|
|
case CL_LOADFILES:
|
|
cltext = M_GetText("Loading server addons...");
|
|
break;
|
|
case CL_ASKJOIN:
|
|
case CL_WAITJOINRESPONSE:
|
|
if (serverisfull)
|
|
cltext = M_GetText("Server full, waiting for a slot...");
|
|
else
|
|
cltext = M_GetText("Requesting to join...");
|
|
break;
|
|
default:
|
|
cltext = M_GetText("Connecting to server...");
|
|
break;
|
|
}
|
|
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-24, V_YELLOWMAP, cltext);
|
|
}
|
|
else
|
|
{
|
|
if (cl_mode == CL_LOADFILES)
|
|
{
|
|
INT32 totalfileslength;
|
|
INT32 loadcompletednum = 0;
|
|
|
|
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-16, V_YELLOWMAP, "Press ESC to abort");
|
|
|
|
// ima just count files here
|
|
if (fileneeded)
|
|
{
|
|
for (INT32 i = 0; i < fileneedednum; i++)
|
|
if (fileneeded[i].status == FS_OPEN)
|
|
loadcompletednum++;
|
|
}
|
|
|
|
// Loading progress
|
|
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-24, V_YELLOWMAP, "Loading server addons...");
|
|
totalfileslength = (INT32)((loadcompletednum/(double)(fileneedednum)) * 256);
|
|
M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT-16-8, 32, 1);
|
|
V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, 256, 8, 111);
|
|
V_DrawFill(BASEVIDWIDTH/2-128, BASEVIDHEIGHT-16, totalfileslength, 8, 96);
|
|
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16, V_20TRANS|V_MONOSPACE,
|
|
va(" %2u/%2u files",loadcompletednum,fileneedednum));
|
|
}
|
|
else if (filedownload.current != -1)
|
|
{
|
|
char tempname[28];
|
|
fileneeded_t *file;
|
|
char *filename;
|
|
|
|
if (snake)
|
|
Snake_Draw(snake);
|
|
|
|
// Draw the bottom box.
|
|
DrawConnectionStatusBox();
|
|
|
|
if (fileneeded)
|
|
{
|
|
file = &fileneeded[filedownload.current];
|
|
filename = file->filename;
|
|
}
|
|
else
|
|
return;
|
|
|
|
// offset filename to just the name only part
|
|
filename += strlen(filename) - nameonlylength(filename);
|
|
|
|
if (strlen(filename) > sizeof(tempname)-1) // too long to display fully
|
|
{
|
|
size_t endhalfpos = strlen(filename)-10;
|
|
// display as first 14 chars + ... + last 10 chars
|
|
// which should add up to 27 if our math(s) is correct
|
|
snprintf(tempname, sizeof(tempname), "%.14s...%.10s", filename, filename+endhalfpos);
|
|
}
|
|
else // we can copy the whole thing in safely
|
|
{
|
|
strlcpy(tempname, filename, sizeof(tempname));
|
|
}
|
|
|
|
// Lactozilla: Disabled, see below change
|
|
// (also it doesn't really fit on a typical SRB2 screen)
|
|
#if 0
|
|
const char *download_str = cl_mode == CL_DOWNLOADHTTPFILES
|
|
? M_GetText("HTTP downloading \"%s\"")
|
|
: M_GetText("Downloading \"%s\"");
|
|
#else
|
|
const char *download_str = M_GetText("Downloading \"%s\"");
|
|
#endif
|
|
|
|
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-24, V_ALLOWLOWERCASE|V_YELLOWMAP,
|
|
va(download_str, tempname));
|
|
|
|
// Rusty: actually lets do this instead
|
|
if (cl_mode == CL_DOWNLOADHTTPFILES)
|
|
{
|
|
const char *http_source = filedownload.http_source;
|
|
|
|
if (strlen(http_source) > sizeof(tempname)-1) // too long to display fully
|
|
{
|
|
size_t endhalfpos = strlen(http_source)-10;
|
|
// display as first 14 chars + ... + last 10 chars
|
|
// which should add up to 27 if our math(s) is correct
|
|
snprintf(tempname, sizeof(tempname), "%.14s...%.10s", http_source, http_source+endhalfpos);
|
|
}
|
|
else // we can copy the whole thing in safely
|
|
{
|
|
strlcpy(tempname, http_source, sizeof(tempname));
|
|
}
|
|
|
|
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-16, V_ALLOWLOWERCASE|V_YELLOWMAP,
|
|
va(M_GetText("from %s"), tempname));
|
|
}
|
|
else
|
|
{
|
|
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-16, V_ALLOWLOWERCASE|V_YELLOWMAP,
|
|
M_GetText("from the server"));
|
|
}
|
|
|
|
DrawFileProgress(file, BASEVIDHEIGHT-16);
|
|
}
|
|
else
|
|
{
|
|
if (snake)
|
|
Snake_Draw(snake);
|
|
|
|
DrawConnectionStatusBox();
|
|
V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-16-24, V_YELLOWMAP,
|
|
M_GetText("Waiting to download files..."));
|
|
}
|
|
}
|
|
}
|
|
|
|
static boolean CL_AskFileList(INT32 firstfile)
|
|
{
|
|
netbuffer->packettype = PT_TELLFILESNEEDED;
|
|
netbuffer->u.filesneedednum = firstfile;
|
|
|
|
return HSendPacket(servernode, false, 0, sizeof (INT32));
|
|
}
|
|
|
|
/** Sends a PT_CLIENTJOIN packet to the server
|
|
*
|
|
* \return True if the packet was successfully sent
|
|
*
|
|
*/
|
|
boolean CL_SendJoin(void)
|
|
{
|
|
UINT8 localplayers = 1;
|
|
char const *player2name;
|
|
if (netgame)
|
|
CONS_Printf(M_GetText("Sending join request...\n"));
|
|
netbuffer->packettype = PT_CLIENTJOIN;
|
|
|
|
netbuffer->u.clientcfg.modversion = MODVERSION;
|
|
strncpy(netbuffer->u.clientcfg.application,
|
|
SRB2APPLICATION,
|
|
sizeof netbuffer->u.clientcfg.application);
|
|
|
|
if (splitscreen || botingame)
|
|
localplayers++;
|
|
netbuffer->u.clientcfg.localplayers = localplayers;
|
|
|
|
CleanupPlayerName(consoleplayer, cv_playername.zstring);
|
|
if (splitscreen)
|
|
CleanupPlayerName(1, cv_playername2.zstring); // 1 is a HACK? oh no
|
|
|
|
// Avoid empty string on bots to avoid softlocking in singleplayer
|
|
if (botingame)
|
|
player2name = strcmp(cv_playername.zstring, "Tails") == 0 ? "Tail" : "Tails";
|
|
else
|
|
player2name = cv_playername2.zstring;
|
|
|
|
strncpy(netbuffer->u.clientcfg.names[0], cv_playername.zstring, MAXPLAYERNAME);
|
|
strncpy(netbuffer->u.clientcfg.names[1], player2name, MAXPLAYERNAME);
|
|
|
|
return HSendPacket(servernode, true, 0, sizeof (clientconfig_pak));
|
|
}
|
|
|
|
static void SendAskInfo(INT32 node)
|
|
{
|
|
const tic_t asktime = I_GetTime();
|
|
netbuffer->packettype = PT_ASKINFO;
|
|
netbuffer->u.askinfo.version = VERSION;
|
|
netbuffer->u.askinfo.time = (tic_t)LONG(asktime);
|
|
|
|
// Even if this never arrives due to the host being firewalled, we've
|
|
// now allowed traffic from the host to us in, so once the MS relays
|
|
// our address to the host, it'll be able to speak to us.
|
|
HSendPacket(node, false, 0, sizeof (askinfo_pak));
|
|
}
|
|
|
|
serverelem_t serverlist[MAXSERVERLIST];
|
|
UINT32 serverlistcount = 0;
|
|
|
|
#define FORCECLOSE 0x8000
|
|
|
|
static void SL_ClearServerList(INT32 connectedserver)
|
|
{
|
|
for (UINT32 i = 0; i < serverlistcount; i++)
|
|
if (connectedserver != serverlist[i].node)
|
|
{
|
|
Net_CloseConnection(serverlist[i].node|FORCECLOSE);
|
|
serverlist[i].node = 0;
|
|
}
|
|
serverlistcount = 0;
|
|
}
|
|
|
|
static UINT32 SL_SearchServer(INT32 node)
|
|
{
|
|
for (UINT32 i = 0; i < serverlistcount; i++)
|
|
if (serverlist[i].node == node)
|
|
return i;
|
|
|
|
return UINT32_MAX;
|
|
}
|
|
|
|
static void SL_InsertServer(serverinfo_pak* info, SINT8 node)
|
|
{
|
|
UINT32 i;
|
|
|
|
// search if not already on it
|
|
i = SL_SearchServer(node);
|
|
if (i == UINT32_MAX)
|
|
{
|
|
// not found add it
|
|
if (serverlistcount >= MAXSERVERLIST)
|
|
return; // list full
|
|
|
|
// check it later if connecting to this one
|
|
if (node != servernode)
|
|
{
|
|
if (info->_255 != 255)
|
|
return; // Old packet format
|
|
|
|
if (info->packetversion != PACKETVERSION)
|
|
return; // Old new packet format
|
|
|
|
if (info->version != VERSION)
|
|
return; // Not same version.
|
|
|
|
if (info->subversion != SUBVERSION)
|
|
return; // Close, but no cigar.
|
|
|
|
if (strcmp(info->application, SRB2APPLICATION))
|
|
return; // That's a different mod
|
|
}
|
|
|
|
i = serverlistcount++;
|
|
}
|
|
|
|
serverlist[i].info = *info;
|
|
serverlist[i].node = node;
|
|
|
|
// resort server list
|
|
M_SortServerList();
|
|
}
|
|
|
|
#if defined (MASTERSERVER) && defined (HAVE_THREADS)
|
|
struct Fetch_servers_ctx
|
|
{
|
|
int room;
|
|
int id;
|
|
};
|
|
|
|
static void
|
|
Fetch_servers_thread (struct Fetch_servers_ctx *ctx)
|
|
{
|
|
msg_server_t *server_list;
|
|
|
|
server_list = GetShortServersList(ctx->room, ctx->id);
|
|
|
|
if (server_list)
|
|
{
|
|
I_lock_mutex(&ms_QueryId_mutex);
|
|
{
|
|
if (ctx->id != ms_QueryId)
|
|
{
|
|
free(server_list);
|
|
server_list = NULL;
|
|
}
|
|
}
|
|
I_unlock_mutex(ms_QueryId_mutex);
|
|
|
|
if (server_list)
|
|
{
|
|
I_lock_mutex(&m_menu_mutex);
|
|
{
|
|
if (m_waiting_mode == M_WAITING_SERVERS)
|
|
m_waiting_mode = M_NOT_WAITING;
|
|
}
|
|
I_unlock_mutex(m_menu_mutex);
|
|
|
|
I_lock_mutex(&ms_ServerList_mutex);
|
|
{
|
|
ms_ServerList = server_list;
|
|
}
|
|
I_unlock_mutex(ms_ServerList_mutex);
|
|
}
|
|
}
|
|
|
|
free(ctx);
|
|
}
|
|
#endif // defined (MASTERSERVER) && defined (HAVE_THREADS)
|
|
|
|
void CL_QueryServerList (msg_server_t *server_list)
|
|
{
|
|
for (INT32 i = 0; server_list[i].header.buffer[0]; i++)
|
|
{
|
|
// Make sure MS version matches our own, to
|
|
// thwart nefarious servers who lie to the MS.
|
|
|
|
// lol bruh, that version COMES from the servers
|
|
//if (strcmp(version, server_list[i].version) == 0)
|
|
{
|
|
INT32 node = I_NetMakeNodewPort(server_list[i].ip, server_list[i].port);
|
|
if (node == -1)
|
|
break; // no more node free
|
|
SendAskInfo(node);
|
|
// Force close the connection so that servers can't eat
|
|
// up nodes forever if we never get a reply back from them
|
|
// (usually when they've not forwarded their ports).
|
|
//
|
|
// Don't worry, we'll get in contact with the working
|
|
// servers again when they send SERVERINFO to us later!
|
|
//
|
|
// (Note: as a side effect this probably means every
|
|
// server in the list will probably be using the same node (e.g. node 1),
|
|
// not that it matters which nodes they use when
|
|
// the connections are closed afterwards anyway)
|
|
// -- Monster Iestyn 12/11/18
|
|
Net_CloseConnection(node|FORCECLOSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CL_UpdateServerList(boolean internetsearch, INT32 room)
|
|
{
|
|
(void)internetsearch;
|
|
(void)room;
|
|
|
|
SL_ClearServerList(0);
|
|
|
|
if (!netgame && I_NetOpenSocket)
|
|
{
|
|
if (I_NetOpenSocket())
|
|
{
|
|
netgame = true;
|
|
multiplayer = true;
|
|
}
|
|
}
|
|
|
|
// search for local servers
|
|
if (netgame)
|
|
SendAskInfo(BROADCASTADDR);
|
|
|
|
#ifdef MASTERSERVER
|
|
if (internetsearch)
|
|
{
|
|
#ifdef HAVE_THREADS
|
|
struct Fetch_servers_ctx *ctx;
|
|
|
|
ctx = malloc(sizeof *ctx);
|
|
|
|
// This called from M_Refresh so I don't use a mutex
|
|
m_waiting_mode = M_WAITING_SERVERS;
|
|
|
|
I_lock_mutex(&ms_QueryId_mutex);
|
|
{
|
|
ctx->id = ms_QueryId;
|
|
}
|
|
I_unlock_mutex(ms_QueryId_mutex);
|
|
|
|
ctx->room = room;
|
|
|
|
I_spawn_thread("fetch-servers", (I_thread_fn)Fetch_servers_thread, ctx);
|
|
#else
|
|
msg_server_t *server_list;
|
|
|
|
server_list = GetShortServersList(room, 0);
|
|
|
|
if (server_list)
|
|
{
|
|
CL_QueryServerList(server_list);
|
|
free(server_list);
|
|
}
|
|
#endif
|
|
}
|
|
#endif // MASTERSERVER
|
|
}
|
|
|
|
static boolean IsFileDownloadable(fileneeded_t *file)
|
|
{
|
|
return file->status == FS_NOTFOUND || file->status == FS_MD5SUMBAD;
|
|
}
|
|
|
|
static boolean UseDirectDownloader(void)
|
|
{
|
|
return filedownload.http_source[0] == '\0' || filedownload.http_failed;
|
|
}
|
|
|
|
static void DoLoadFiles(void)
|
|
{
|
|
Snake_Free(&snake);
|
|
|
|
cl_mode = CL_LOADFILES;
|
|
}
|
|
|
|
static void AbortConnection(void)
|
|
{
|
|
Snake_Free(&snake);
|
|
|
|
D_QuitNetGame();
|
|
CL_Reset();
|
|
D_StartTitle();
|
|
|
|
// Will be reset by caller. Signals refusal.
|
|
cl_mode = CL_ABORTED;
|
|
}
|
|
|
|
static void BeginDownload(boolean direct)
|
|
{
|
|
filedownload.current = 0;
|
|
filedownload.remaining = 0;
|
|
|
|
for (int i = 0; i < fileneedednum; i++)
|
|
{
|
|
// Lactozilla: Rusty had fixed this SLIGHTLY incorrectly.
|
|
// Since [redacted] doesn't HAVE its own file transmission code - it spins an HTTP server - it wasn't
|
|
// instantly obvious to us where to do this, and we didn't want to check the original implementation.
|
|
if (fileneeded[i].status == FS_FALLBACK)
|
|
fileneeded[i].status = FS_NOTFOUND;
|
|
|
|
if (IsFileDownloadable(&fileneeded[i]))
|
|
filedownload.remaining++;
|
|
}
|
|
|
|
if (!filedownload.remaining)
|
|
{
|
|
DoLoadFiles();
|
|
return;
|
|
}
|
|
|
|
if (!direct)
|
|
{
|
|
cl_mode = CL_DOWNLOADHTTPFILES;
|
|
Snake_Allocate(&snake);
|
|
|
|
// Discard any paused downloads
|
|
CL_AbortDownloadResume();
|
|
}
|
|
else
|
|
{
|
|
// do old LEGACY request
|
|
if (CL_SendFileRequest())
|
|
{
|
|
cl_mode = CL_DOWNLOADFILES;
|
|
|
|
// don't alloc snake if already alloced
|
|
if (!snake)
|
|
Snake_Allocate(&snake);
|
|
}
|
|
else
|
|
{
|
|
AbortConnection();
|
|
|
|
// why was this its own cl_mode_t?
|
|
M_StartMessage(M_GetText(
|
|
"The direct downloader encountered an error.\n"
|
|
"See the logfile for more info.\n\n"
|
|
"Press ESC\n"
|
|
), NULL, MM_NOTHING);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void M_ConfirmConnect(event_t *ev)
|
|
{
|
|
if (ev->type == ev_keydown)
|
|
{
|
|
if (ev->key == ' ' || ev->key == 'y' || ev->key == KEY_ENTER || ev->key == KEY_JOY1)
|
|
{
|
|
BeginDownload(UseDirectDownloader());
|
|
M_ClearMenus(true);
|
|
}
|
|
else if (ev->key == 'n' || ev->key == KEY_ESCAPE || ev->key == KEY_JOY1 + 3)
|
|
{
|
|
cl_mode = CL_ABORTED;
|
|
M_ClearMenus(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
static const char *GetPrintableFileSize(UINT64 filesize)
|
|
{
|
|
static char downloadsize[32];
|
|
|
|
if (filesize >= 1024*1024)
|
|
snprintf(downloadsize, sizeof(downloadsize), "%.2fMiB", (double)filesize / (1024*1024));
|
|
else if (filesize < 1024)
|
|
snprintf(downloadsize, sizeof(downloadsize), "%sB", sizeu1(filesize));
|
|
else
|
|
snprintf(downloadsize, sizeof(downloadsize), "%.2fKiB", (double)filesize / 1024);
|
|
|
|
return downloadsize;
|
|
}
|
|
|
|
static void ShowDownloadConsentMessage(void)
|
|
{
|
|
UINT64 totalsize = 0;
|
|
|
|
filedownload.completednum = 0;
|
|
filedownload.completedsize = 0;
|
|
|
|
if (fileneeded == NULL)
|
|
I_Error("CL_FinishedFileList: fileneeded == NULL");
|
|
|
|
for (int i = 0; i < fileneedednum; i++)
|
|
{
|
|
if (IsFileDownloadable(&fileneeded[i]))
|
|
totalsize += fileneeded[i].totalsize;
|
|
}
|
|
|
|
const char *downloadsize = GetPrintableFileSize(totalsize);
|
|
|
|
if (serverisfull)
|
|
M_StartMessage(va(M_GetText(
|
|
"This server is full!\n"
|
|
"Download of %s of additional\ncontent is required to join.\n"
|
|
"\n"
|
|
"You may download server addons,\nand wait for a slot.\n"
|
|
"\n"
|
|
"Press ENTER to continue\nor ESC to cancel.\n"
|
|
), downloadsize), M_ConfirmConnect, MM_EVENTHANDLER);
|
|
else
|
|
M_StartMessage(va(M_GetText(
|
|
"Download of %s of additional\ncontent is required to join.\n"
|
|
"\n"
|
|
"Press ENTER to continue\nor ESC to cancel.\n"
|
|
), downloadsize), M_ConfirmConnect, MM_EVENTHANDLER);
|
|
|
|
cl_mode = CL_CONFIRMCONNECT;
|
|
curfadevalue = 0;
|
|
}
|
|
|
|
static const char *GetDirectDownloadFailReason(UINT8 dlstatus)
|
|
{
|
|
switch (dlstatus)
|
|
{
|
|
case DLSTATUS_TOOLARGE:
|
|
return M_GetText("Some addons are larger than the server is willing to send.");
|
|
case DLSTATUS_WONTSEND:
|
|
return M_GetText("The server is not allowing download requests.");
|
|
case DLSTATUS_NODOWNLOAD:
|
|
return M_GetText("All addons downloadable, but you have chosen to disable addon downloading.");
|
|
case DLSTATUS_FOLDER:
|
|
return M_GetText("One or more addons were added as a folder, which the server cannot send.");
|
|
}
|
|
|
|
return "Unknown reason";
|
|
}
|
|
|
|
static void HandleDirectDownloadFail(UINT8 dlstatus)
|
|
{
|
|
// not downloadable, put reason in console
|
|
CONS_Alert(CONS_NOTICE, M_GetText("You need additional addons to connect to this server:\n"));
|
|
|
|
for (UINT8 i = 0; i < fileneedednum; i++)
|
|
{
|
|
if (fileneeded[i].folder || (fileneeded[i].status != FS_FOUND && fileneeded[i].status != FS_OPEN))
|
|
{
|
|
CONS_Printf(" * \"%s\" ", fileneeded[i].filename);
|
|
|
|
if (fileneeded[i].folder)
|
|
{
|
|
CONS_Printf("(folder)");
|
|
}
|
|
else
|
|
{
|
|
CONS_Printf("(%s)", GetPrintableFileSize(fileneeded[i].totalsize));
|
|
|
|
if (fileneeded[i].status == FS_NOTFOUND)
|
|
CONS_Printf(M_GetText(" not found, md5: "));
|
|
else if (fileneeded[i].status == FS_MD5SUMBAD)
|
|
CONS_Printf(M_GetText(" wrong version, md5: "));
|
|
|
|
char md5tmp[33];
|
|
for (INT32 j = 0; j < 16; j++)
|
|
sprintf(&md5tmp[j*2], "%02x", fileneeded[i].md5sum[j]);
|
|
CONS_Printf("%s", md5tmp);
|
|
}
|
|
|
|
CONS_Printf("\n");
|
|
}
|
|
}
|
|
|
|
CONS_Printf("%s\n", GetDirectDownloadFailReason(dlstatus));
|
|
}
|
|
|
|
static boolean CL_FinishedFileList(void)
|
|
{
|
|
INT32 i = CL_CheckFiles();
|
|
if (i == 4) // still checking ...
|
|
{
|
|
return true;
|
|
}
|
|
else if (i == 3) // too many files
|
|
{
|
|
AbortConnection();
|
|
M_StartMessage(M_GetText(
|
|
"You have too many WAD files loaded\n"
|
|
"to add ones the server is using.\n"
|
|
"Please restart SRB2 before connecting.\n\n"
|
|
"Press ESC\n"
|
|
), NULL, MM_NOTHING);
|
|
return false;
|
|
}
|
|
else if (i == 2) // cannot join for some reason
|
|
{
|
|
AbortConnection();
|
|
M_StartMessage(M_GetText(
|
|
"You have the wrong addons loaded.\n"
|
|
"\n"
|
|
"To play on this server, restart\n"
|
|
"the game and don't load any addons.\n"
|
|
"SRB2 will automatically add\n"
|
|
"everything you need when you join.\n"
|
|
"\n"
|
|
"Press ESC\n"
|
|
), NULL, MM_NOTHING);
|
|
return false;
|
|
}
|
|
else if (i == 1)
|
|
{
|
|
if (serverisfull)
|
|
{
|
|
M_StartMessage(M_GetText(
|
|
"This server is full!\n"
|
|
"\n"
|
|
"You may load server addons (if any), and wait for a slot.\n"
|
|
"\n"
|
|
"Press ENTER to continue\nor ESC to cancel.\n\n"
|
|
), M_ConfirmConnect, MM_EVENTHANDLER);
|
|
cl_mode = CL_CONFIRMCONNECT;
|
|
curfadevalue = 0;
|
|
}
|
|
else
|
|
cl_mode = CL_LOADFILES;
|
|
}
|
|
else
|
|
{
|
|
// must download something
|
|
// can we, though?
|
|
// Rusty: always check downloadable
|
|
UINT8 status = CL_CheckDownloadable(UseDirectDownloader());
|
|
if (status != DLSTATUS_OK)
|
|
{
|
|
HandleDirectDownloadFail(status);
|
|
AbortConnection();
|
|
M_StartMessage(M_GetText(
|
|
"An error occurred when trying to\n"
|
|
"download missing addons.\n"
|
|
"(This is almost always a problem\n"
|
|
"with the server, not your game.)\n"
|
|
"\n"
|
|
"See the console or log file\n"
|
|
"for additional details.\n"
|
|
"\n"
|
|
"Press ESC\n"
|
|
), NULL, MM_NOTHING);
|
|
return false;
|
|
}
|
|
|
|
if (!filedownload.http_failed)
|
|
{
|
|
// show download consent modal ONCE!
|
|
ShowDownloadConsentMessage();
|
|
}
|
|
else
|
|
{
|
|
// do a direct download
|
|
BeginDownload(true);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static const char * InvalidServerReason (serverinfo_pak *info)
|
|
{
|
|
#define EOT "\nPress ESC\n"
|
|
|
|
// Magic number for new packet format
|
|
if (info->_255 != 255)
|
|
{
|
|
return
|
|
"Outdated server (version unknown).\n" EOT;
|
|
}
|
|
|
|
if (strncmp(info->application, SRB2APPLICATION, sizeof
|
|
info->application))
|
|
{
|
|
return va(
|
|
"%s cannot connect\n"
|
|
"to %s servers.\n" EOT,
|
|
SRB2APPLICATION,
|
|
info->application);
|
|
}
|
|
|
|
if (
|
|
info->packetversion != PACKETVERSION ||
|
|
info->version != VERSION ||
|
|
info->subversion != SUBVERSION
|
|
){
|
|
return va(
|
|
"Incompatible %s versions.\n"
|
|
"(server version %d.%d.%d)\n" EOT,
|
|
SRB2APPLICATION,
|
|
info->version / 100,
|
|
info->version % 100,
|
|
info->subversion);
|
|
}
|
|
|
|
switch (info->refusereason)
|
|
{
|
|
case REFUSE_BANNED:
|
|
return
|
|
"You have been banned\n"
|
|
"from the server.\n" EOT;
|
|
case REFUSE_JOINS_DISABLED:
|
|
return
|
|
"The server is not accepting\n"
|
|
"joins for the moment.\n" EOT;
|
|
case REFUSE_SLOTS_FULL:
|
|
return va(
|
|
"Maximum players reached: %d\n" EOT,
|
|
info->maxplayer - D_NumBots());
|
|
default:
|
|
if (info->refusereason)
|
|
{
|
|
return
|
|
"You can't join.\n"
|
|
"I don't know why,\n"
|
|
"but you can't join.\n" EOT;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
|
|
#undef EOT
|
|
}
|
|
|
|
/** Called by CL_ServerConnectionTicker
|
|
*
|
|
* \param asksent The last time we asked the server to join. We re-ask every second in case our request got lost in transmit.
|
|
* \return False if the connection was aborted
|
|
* \sa CL_ServerConnectionTicker
|
|
* \sa CL_ConnectToServer
|
|
*
|
|
*/
|
|
static boolean CL_ServerConnectionSearchTicker(tic_t *asksent)
|
|
{
|
|
INT32 i;
|
|
|
|
// serverlist is updated by GetPackets
|
|
if (serverlistcount > 0)
|
|
{
|
|
// This can be a response to our broadcast request
|
|
if (servernode == -1 || servernode >= MAXNETNODES)
|
|
{
|
|
i = 0;
|
|
servernode = serverlist[i].node;
|
|
CONS_Printf(M_GetText("Found, "));
|
|
}
|
|
else
|
|
{
|
|
i = SL_SearchServer(servernode);
|
|
if (i < 0)
|
|
return true;
|
|
}
|
|
|
|
if (client)
|
|
{
|
|
serverinfo_pak *info = &serverlist[i].info;
|
|
|
|
if (info->refusereason == REFUSE_SLOTS_FULL)
|
|
serverisfull = true;
|
|
else
|
|
{
|
|
const char *reason = InvalidServerReason(info);
|
|
|
|
// Quit here rather than downloading files
|
|
// and being refused later.
|
|
if (reason)
|
|
{
|
|
char *message = Z_StrDup(reason);
|
|
AbortConnection();
|
|
M_StartMessage(message, NULL, MM_NOTHING);
|
|
Z_Free(message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (serverlist[i].info.httpsource[0])
|
|
strlcpy(filedownload.http_source, serverlist[i].info.httpsource, MAX_MIRROR_LENGTH);
|
|
else
|
|
filedownload.http_source[0] = '\0';
|
|
|
|
D_ParseFileneeded(info->fileneedednum, info->fileneeded, 0);
|
|
|
|
if (info->flags & SV_LOTSOFADDONS)
|
|
{
|
|
cl_mode = CL_ASKFULLFILELIST;
|
|
cl_lastcheckedfilecount = 0;
|
|
return true;
|
|
}
|
|
|
|
cl_mode = CL_CHECKFILES;
|
|
}
|
|
else
|
|
{
|
|
cl_mode = CL_ASKJOIN; // files need not be checked for the server.
|
|
*asksent = 0;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Ask the info to the server (askinfo packet)
|
|
if (*asksent + NEWTICRATE < I_GetTime())
|
|
{
|
|
SendAskInfo(servernode);
|
|
*asksent = I_GetTime();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static void HandleHTTPDownloadFail(void)
|
|
{
|
|
char filename[MAX_WADPATH];
|
|
|
|
CONS_Alert(CONS_WARNING, M_GetText("One or more addons failed to download:\n"));
|
|
|
|
for (int i = 0; i < fileneedednum; i++)
|
|
{
|
|
if (fileneeded[i].failed == FDOWNLOAD_FAIL_NONE)
|
|
continue;
|
|
|
|
strlcpy(filename, fileneeded[i].filename, sizeof filename);
|
|
nameonly(filename);
|
|
|
|
CONS_Printf(" * \"%s\" (%s)", filename, GetPrintableFileSize(fileneeded[i].totalsize));
|
|
|
|
if (fileneeded[i].failed == FDOWNLOAD_FAIL_NOTFOUND)
|
|
CONS_Printf(M_GetText(" not found, md5: "));
|
|
else if (fileneeded[i].failed == FDOWNLOAD_FAIL_MD5SUMBAD)
|
|
CONS_Printf(M_GetText(" wrong version, md5: "));
|
|
else
|
|
CONS_Printf(M_GetText(" other error, md5: "));
|
|
|
|
char md5tmp[33];
|
|
for (INT32 j = 0; j < 16; j++)
|
|
snprintf(&md5tmp[j*2], sizeof(md5tmp), "%02x", fileneeded[i].md5sum[j]);
|
|
CONS_Printf("%s\n", md5tmp);
|
|
|
|
fileneeded[i].failed = FDOWNLOAD_FAIL_NONE;
|
|
}
|
|
|
|
CONS_Printf(M_GetText("Falling back to direct downloader.\n"));
|
|
|
|
cl_mode = CL_CHECKFILES;
|
|
}
|
|
|
|
/** Called by CL_ConnectToServer
|
|
*
|
|
* \param tmpsave The name of the gamestate file???
|
|
* \param oldtic Used for knowing when to poll events and redraw
|
|
* \param asksent ???
|
|
* \return False if the connection was aborted
|
|
* \sa CL_ServerConnectionSearchTicker
|
|
* \sa CL_ConnectToServer
|
|
*
|
|
*/
|
|
static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic_t *asksent)
|
|
{
|
|
boolean waitmore;
|
|
|
|
switch (cl_mode)
|
|
{
|
|
case CL_SEARCHING:
|
|
if (!CL_ServerConnectionSearchTicker(asksent))
|
|
return false;
|
|
break;
|
|
|
|
case CL_ASKFULLFILELIST:
|
|
if (cl_lastcheckedfilecount == UINT16_MAX) // All files retrieved
|
|
cl_mode = CL_CHECKFILES;
|
|
else if (fileneedednum != cl_lastcheckedfilecount || I_GetTime() >= *asksent)
|
|
{
|
|
if (CL_AskFileList(fileneedednum))
|
|
{
|
|
cl_lastcheckedfilecount = fileneedednum;
|
|
*asksent = I_GetTime() + NEWTICRATE;
|
|
}
|
|
}
|
|
break;
|
|
case CL_CHECKFILES:
|
|
if (!CL_FinishedFileList())
|
|
return false;
|
|
break;
|
|
|
|
case CL_DOWNLOADHTTPFILES:
|
|
waitmore = false;
|
|
for (int i = filedownload.current; i < fileneedednum; i++)
|
|
{
|
|
if (IsFileDownloadable(&fileneeded[i]))
|
|
{
|
|
if (!filedownload.http_running)
|
|
{
|
|
if (!CURLPrepareFile(filedownload.http_source, i))
|
|
HandleHTTPDownloadFail();
|
|
}
|
|
waitmore = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Rusty TODO: multithread
|
|
if (filedownload.http_running)
|
|
CURLGetFile();
|
|
|
|
if (waitmore)
|
|
break; // exit the case
|
|
|
|
// Done downloading files
|
|
if (!filedownload.remaining)
|
|
{
|
|
if (filedownload.http_failed)
|
|
HandleHTTPDownloadFail();
|
|
else
|
|
DoLoadFiles();
|
|
}
|
|
break;
|
|
case CL_DOWNLOADFILES:
|
|
// Done downloading files
|
|
if (!filedownload.remaining)
|
|
DoLoadFiles();
|
|
break;
|
|
case CL_LOADFILES:
|
|
if (CL_LoadServerFiles())
|
|
{
|
|
FreeFileNeeded();
|
|
*asksent = 0; // This ensures the first join request is right away
|
|
firstconnectattempttime = I_GetTime();
|
|
cl_mode = CL_ASKJOIN;
|
|
}
|
|
break;
|
|
case CL_ASKJOIN:
|
|
if (firstconnectattempttime + NEWTICRATE*300 < I_GetTime() && !server)
|
|
{
|
|
CONS_Printf(M_GetText("5 minute wait time exceeded.\n"));
|
|
AbortConnection();
|
|
M_StartMessage(M_GetText(
|
|
"5 minute wait time exceeded.\n"
|
|
"You may retry connection.\n"
|
|
"\n"
|
|
"Press ESC\n"
|
|
), NULL, MM_NOTHING);
|
|
return false;
|
|
}
|
|
|
|
// Prepare structures to save the file
|
|
CL_PrepareDownloadSaveGame(tmpsave);
|
|
|
|
if (I_GetTime() >= *asksent && CL_SendJoin())
|
|
{
|
|
*asksent = I_GetTime() + NEWTICRATE*3;
|
|
cl_mode = CL_WAITJOINRESPONSE;
|
|
}
|
|
break;
|
|
case CL_WAITJOINRESPONSE:
|
|
if (I_GetTime() >= *asksent)
|
|
{
|
|
cl_mode = CL_ASKJOIN;
|
|
}
|
|
break;
|
|
case CL_DOWNLOADSAVEGAME:
|
|
// At this state, the first (and only) needed file is the gamestate
|
|
if (fileneeded[0].status == FS_FOUND)
|
|
{
|
|
// Gamestate is now handled within CL_LoadReceivedSavegame()
|
|
CL_LoadReceivedSavegame(false);
|
|
cl_mode = CL_CONNECTED;
|
|
} // don't break case continue to CL_CONNECTED
|
|
else
|
|
break;
|
|
|
|
case CL_CONNECTED:
|
|
case CL_CONFIRMCONNECT: //logic is handled by M_ConfirmConnect
|
|
default:
|
|
break;
|
|
|
|
// Connection closed by cancel, timeout or refusal.
|
|
case CL_ABORTED:
|
|
cl_mode = CL_SEARCHING;
|
|
return false;
|
|
}
|
|
|
|
GetPackets();
|
|
Net_AckTicker();
|
|
|
|
// Call it only once by tic
|
|
if (*oldtic != I_GetTime())
|
|
{
|
|
I_OsPolling();
|
|
|
|
if (cl_mode == CL_CONFIRMCONNECT)
|
|
D_ProcessEvents(); //needed for menu system to receive inputs
|
|
else
|
|
{
|
|
// my hand has been forced and I am dearly sorry for this awful hack :vomit:
|
|
for (; eventtail != eventhead; eventtail = (eventtail+1) & (MAXEVENTS-1))
|
|
{
|
|
if (!Snake_JoyGrabber(snake, &events[eventtail]))
|
|
G_MapEventsToControls(&events[eventtail]);
|
|
}
|
|
}
|
|
|
|
if (gamekeydown[KEY_ESCAPE] || gamekeydown[KEY_JOY1+1] || cl_mode == CL_ABORTED)
|
|
{
|
|
CONS_Printf(M_GetText("Network game synchronization aborted.\n"));
|
|
M_StartMessage(M_GetText("Network game synchronization aborted.\n\nPress ESC\n"), NULL, MM_NOTHING);
|
|
|
|
AbortConnection();
|
|
|
|
memset(gamekeydown, 0, NUMKEYS);
|
|
return false;
|
|
}
|
|
else if ((cl_mode == CL_DOWNLOADFILES || cl_mode == CL_DOWNLOADHTTPFILES) && snake)
|
|
Snake_Update(snake);
|
|
|
|
if (client && (cl_mode == CL_DOWNLOADFILES || cl_mode == CL_DOWNLOADSAVEGAME))
|
|
FileReceiveTicker();
|
|
|
|
*oldtic = I_GetTime();
|
|
|
|
if (client && cl_mode != CL_CONNECTED && cl_mode != CL_ABORTED)
|
|
{
|
|
if (!snake)
|
|
{
|
|
F_MenuPresTicker(); // title sky
|
|
F_TitleScreenTicker(true);
|
|
F_TitleScreenDrawer();
|
|
}
|
|
CL_DrawConnectionStatus();
|
|
#ifdef HAVE_THREADS
|
|
I_lock_mutex(&m_menu_mutex);
|
|
#endif
|
|
M_Drawer(); //Needed for drawing messageboxes on the connection screen
|
|
#ifdef HAVE_THREADS
|
|
I_unlock_mutex(m_menu_mutex);
|
|
#endif
|
|
I_UpdateNoVsync(); // page flip or blit buffer
|
|
if (moviemode)
|
|
M_SaveFrame();
|
|
S_UpdateSounds();
|
|
S_UpdateClosedCaptions();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
I_Sleep(cv_sleep.value);
|
|
I_UpdateTime(cv_timescale.value);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#define TMPSAVENAME "$$$.sav"
|
|
|
|
void CL_ConnectToServer(void)
|
|
{
|
|
INT32 pnumnodes, nodewaited = doomcom->numnodes, i;
|
|
tic_t oldtic;
|
|
tic_t asksent;
|
|
char tmpsave[256];
|
|
|
|
sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home);
|
|
|
|
filedownload.current = -1;
|
|
|
|
cl_mode = CL_SEARCHING;
|
|
|
|
// Don't get a corrupt savegame error because tmpsave already exists
|
|
if (FIL_FileExists(tmpsave) && unlink(tmpsave) == -1)
|
|
I_Error("Can't delete %s\n", tmpsave);
|
|
|
|
if (netgame)
|
|
{
|
|
if (servernode < 0 || servernode >= MAXNETNODES)
|
|
CONS_Printf(M_GetText("Searching for a server...\n"));
|
|
else
|
|
CONS_Printf(M_GetText("Contacting the server...\n"));
|
|
}
|
|
|
|
if (gamestate == GS_INTERMISSION)
|
|
Y_EndIntermission(); // clean up intermission graphics etc
|
|
|
|
DEBFILE(va("waiting %d nodes\n", doomcom->numnodes));
|
|
G_SetGamestate(GS_WAITINGPLAYERS);
|
|
wipegamestate = GS_WAITINGPLAYERS;
|
|
|
|
ClearAdminPlayers();
|
|
pnumnodes = 1;
|
|
oldtic = I_GetTime() - 1;
|
|
|
|
asksent = (tic_t) - TICRATE;
|
|
firstconnectattempttime = I_GetTime();
|
|
|
|
i = SL_SearchServer(servernode);
|
|
|
|
if (i != -1)
|
|
{
|
|
char *gametypestr = serverlist[i].info.gametypename;
|
|
CONS_Printf(M_GetText("Connecting to: %s\n"), serverlist[i].info.servername);
|
|
gametypestr[sizeof serverlist[i].info.gametypename - 1] = '\0';
|
|
CONS_Printf(M_GetText("Gametype: %s\n"), gametypestr);
|
|
CONS_Printf(M_GetText("Version: %d.%d.%u\n"), serverlist[i].info.version/100,
|
|
serverlist[i].info.version%100, serverlist[i].info.subversion);
|
|
}
|
|
SL_ClearServerList(servernode);
|
|
|
|
do
|
|
{
|
|
// If the connection was aborted for some reason, leave
|
|
if (!CL_ServerConnectionTicker(tmpsave, &oldtic, &asksent))
|
|
return;
|
|
|
|
if (server)
|
|
{
|
|
pnumnodes = 0;
|
|
for (i = 0; i < MAXNETNODES; i++)
|
|
if (netnodes[i].ingame)
|
|
pnumnodes++;
|
|
}
|
|
}
|
|
while (!(cl_mode == CL_CONNECTED && (client || (server && nodewaited <= pnumnodes))));
|
|
|
|
if (netgame)
|
|
F_StartWaitingPlayers();
|
|
|
|
DEBFILE(va("Synchronisation Finished\n"));
|
|
|
|
displayplayer = consoleplayer;
|
|
}
|
|
|
|
/** Called when a PT_SERVERINFO packet is received
|
|
*
|
|
* \param node The packet sender
|
|
* \note What happens if the packet comes from a client or something like that?
|
|
*
|
|
*/
|
|
void PT_ServerInfo(SINT8 node)
|
|
{
|
|
// compute ping in ms
|
|
const tic_t ticnow = I_GetTime();
|
|
const tic_t ticthen = (tic_t)LONG(netbuffer->u.serverinfo.time);
|
|
const tic_t ticdiff = (ticnow - ticthen)*1000/NEWTICRATE;
|
|
netbuffer->u.serverinfo.time = (tic_t)LONG(ticdiff);
|
|
netbuffer->u.serverinfo.servername[MAXSERVERNAME-1] = 0;
|
|
netbuffer->u.serverinfo.application
|
|
[sizeof netbuffer->u.serverinfo.application - 1] = '\0';
|
|
netbuffer->u.serverinfo.gametypename
|
|
[sizeof netbuffer->u.serverinfo.gametypename - 1] = '\0';
|
|
|
|
SL_InsertServer(&netbuffer->u.serverinfo, node);
|
|
}
|
|
|
|
// Helper function for packets that should only be sent by the server
|
|
// If it is NOT from the server, bail out and close the connection!
|
|
static boolean ServerOnly(SINT8 node)
|
|
{
|
|
if (node == servernode)
|
|
return false;
|
|
|
|
Net_CloseConnection(node);
|
|
return true;
|
|
}
|
|
|
|
void PT_MoreFilesNeeded(SINT8 node)
|
|
{
|
|
if (server && serverrunning)
|
|
{ // But wait I thought I'm the server?
|
|
Net_CloseConnection(node);
|
|
return;
|
|
}
|
|
if (ServerOnly(node))
|
|
return;
|
|
if (cl_mode == CL_ASKFULLFILELIST && netbuffer->u.filesneededcfg.first == fileneedednum)
|
|
{
|
|
D_ParseFileneeded(netbuffer->u.filesneededcfg.num, netbuffer->u.filesneededcfg.files, netbuffer->u.filesneededcfg.first);
|
|
if (!netbuffer->u.filesneededcfg.more)
|
|
cl_lastcheckedfilecount = UINT16_MAX; // Got the whole file list
|
|
}
|
|
}
|
|
|
|
// Negative response of client join request
|
|
void PT_ServerRefuse(SINT8 node)
|
|
{
|
|
if (server && serverrunning)
|
|
{ // But wait I thought I'm the server?
|
|
Net_CloseConnection(node);
|
|
return;
|
|
}
|
|
if (ServerOnly(node))
|
|
return;
|
|
if (cl_mode == CL_WAITJOINRESPONSE)
|
|
{
|
|
// Save the reason so it can be displayed after quitting the netgame
|
|
char *reason = strdup(netbuffer->u.serverrefuse.reason);
|
|
if (!reason)
|
|
I_Error("Out of memory!\n");
|
|
|
|
if (strstr(reason, "Maximum players reached"))
|
|
{
|
|
serverisfull = true;
|
|
//Special timeout for when refusing due to player cap. The client will wait 3 seconds between join requests when waiting for a slot, so we need this to be much longer
|
|
//We set it back to the value of cv_nettimeout.value in CL_Reset
|
|
connectiontimeout = NEWTICRATE*7;
|
|
cl_mode = CL_ASKJOIN;
|
|
free(reason);
|
|
return;
|
|
}
|
|
|
|
M_StartMessage(va(M_GetText("Server refuses connection\n\nReason:\n%s"),
|
|
reason), NULL, MM_NOTHING);
|
|
|
|
AbortConnection();
|
|
|
|
free(reason);
|
|
}
|
|
}
|
|
|
|
// Positive response of client join request
|
|
void PT_ServerCFG(SINT8 node)
|
|
{
|
|
if (server && serverrunning && node != servernode)
|
|
{ // but wait I thought I'm the server?
|
|
Net_CloseConnection(node);
|
|
return;
|
|
}
|
|
if (ServerOnly(node))
|
|
return;
|
|
/// \note how would this happen? and is it doing the right thing if it does?
|
|
if (cl_mode != CL_WAITJOINRESPONSE)
|
|
return;
|
|
|
|
if (client)
|
|
{
|
|
maketic = gametic = neededtic = (tic_t)LONG(netbuffer->u.servercfg.gametic);
|
|
G_SetGametype(netbuffer->u.servercfg.gametype);
|
|
modifiedgame = netbuffer->u.servercfg.modifiedgame;
|
|
if (netbuffer->u.servercfg.usedCheats)
|
|
G_SetUsedCheats(true);
|
|
memcpy(server_context, netbuffer->u.servercfg.server_context, 8);
|
|
}
|
|
|
|
netnodes[(UINT8)servernode].ingame = true;
|
|
serverplayer = netbuffer->u.servercfg.serverplayer;
|
|
doomcom->numslots = SHORT(netbuffer->u.servercfg.totalslotnum);
|
|
mynode = netbuffer->u.servercfg.clientnode;
|
|
if (serverplayer >= 0)
|
|
playernode[(UINT8)serverplayer] = servernode;
|
|
|
|
if (netgame)
|
|
CONS_Printf(M_GetText("Join accepted, waiting for complete game state...\n"));
|
|
DEBFILE(va("Server accept join gametic=%u mynode=%d\n", gametic, mynode));
|
|
|
|
/// \note Wait. What if a Lua script uses some global custom variables synched with the NetVars hook?
|
|
/// Shouldn't they be downloaded even at intermission time?
|
|
/// Also, according to PT_ClientJoin, the server will send the savegame even during intermission...
|
|
if (netbuffer->u.servercfg.gamestate == GS_LEVEL/* ||
|
|
netbuffer->u.servercfg.gamestate == GS_INTERMISSION*/)
|
|
cl_mode = CL_DOWNLOADSAVEGAME;
|
|
else
|
|
cl_mode = CL_CONNECTED;
|
|
}
|