st/code/client/cl_main.c
2008-04-04 00:00:00 +00:00

3139 lines
75 KiB
C

/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2007 HermitWorks Entertainment Corporation
This file is part of the Space Trader source code.
The Space Trader source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
The Space Trader source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with the Space Trader source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
// cl_main.c -- client main loop
#include "client.h"
cvar_t *cl_nodelta;
cvar_t *cl_debugMove;
cvar_t *cl_noprint;
cvar_t *cl_motd;
cvar_t *rcon_client_password;
cvar_t *rconAddress;
cvar_t *cl_timeout;
cvar_t *cl_maxpackets;
cvar_t *cl_packetdup;
cvar_t *cl_timeNudge;
cvar_t *cl_showTimeDelta;
cvar_t *cl_freezeDemo;
cvar_t *cl_shownet;
cvar_t *cl_showSend;
cvar_t *cl_timedemo;
cvar_t *cl_avidemo;
cvar_t *cl_forceavidemo;
cvar_t *cl_freelook;
cvar_t *cl_sensitivity;
cvar_t *cl_mouseAccel;
cvar_t *cl_showMouseRate;
cvar_t *m_pitch;
cvar_t *m_yaw;
cvar_t *m_forward;
cvar_t *m_side;
cvar_t *m_filter;
cvar_t *cl_activeAction;
cvar_t *cl_motdString;
cvar_t *cl_conXOffset;
cvar_t *cl_conYOffset;
cvar_t *cl_conWidth;
cvar_t *cl_conHeight;
cvar_t *cl_inGameVideo;
cvar_t *cl_serverStatusResendTime;
cvar_t *cl_trn;
cvar_t *cl_authserver;
cvar_t *cl_ircport;
cvar_t *cl_lang;
clientActive_t cl;
clientConnection_t clc;
clientStatic_t cls;
vm_t *cgvm;
// Structure containing functions exported from refresh DLL
refexport_t re;
typedef struct serverStatus_s
{
char string[BIG_INFO_STRING];
netadr_t address;
int time, startTime;
qboolean pending;
qboolean print;
qboolean retrieved;
} serverStatus_t;
serverStatus_t cl_serverStatusList[MAX_SERVERSTATUSREQUESTS];
int serverStatusCount;
extern void SV_BotFrame( int time );
void CL_CheckForResend( void );
void Q_EXTERNAL_CALL CL_ShowIP_f(void);
void Q_EXTERNAL_CALL CL_ServerStatus_f(void);
void CL_ServerStatusResponse( netadr_t from, msg_t *msg );
sqlInfo_t * sql_getclientdb( void ) { return &cl.db; }
/*
===============
CL_CDDialog
Called by Com_Error when a cd is needed
===============
*/
void CL_CDDialog( void ) {
cls.cddialog = qtrue; // start it next frame
}
/*
=======================================================================
CLIENT RELIABLE COMMAND COMMUNICATION
=======================================================================
*/
char * CL_GetReliableCommand( int index ) {
char * cmd = clc.reliableCommands[ index & (MAX_RELIABLE_COMMANDS-1) ];
return cmd?cmd:"";
}
/*
======================
CL_AddReliableCommand
The given command will be transmitted to the server, and is gauranteed to
not have future usercmd_t executed before it is executed
======================
*/
void CL_AddReliableCommand( const char *cmd ) {
int index;
// if we would be losing an old command that hasn't been acknowledged,
// we must drop the connection
if ( clc.reliableSequence - clc.reliableAcknowledge > MAX_RELIABLE_COMMANDS ) {
Com_Error( ERR_DROP, "Client command overflow" );
}
clc.reliableSequence++;
index = clc.reliableSequence & ( MAX_RELIABLE_COMMANDS - 1 );
clc.reliableCommands[ index ] = Q_strcpy_ringbuffer( clc.reliableCommandBuffer,
sizeof( clc.reliableCommandBuffer ),
clc.reliableCommands[ (clc.reliableAcknowledge) & ( MAX_RELIABLE_COMMANDS - 1 ) ],
clc.reliableCommands[ (clc.reliableSequence-1) & ( MAX_RELIABLE_COMMANDS - 1 ) ],
cmd
);
if ( !clc.reliableCommands[ index ] ) {
Com_Error( ERR_DROP, "Client command buffer overflow" );
}
}
/*
=======================================================================
CLIENT SIDE DEMO RECORDING
=======================================================================
*/
/*
====================
CL_WriteDemoMessage
Dumps the current net message, prefixed by the length
====================
*/
void CL_WriteDemoMessage ( msg_t *msg, int headerBytes ) {
int len, swlen;
// write the packet sequence
len = clc.serverMessageSequence;
swlen = LittleLong( len );
FS_Write (&swlen, 4, clc.demofile);
// skip the packet sequencing information
len = msg->cursize - headerBytes;
swlen = LittleLong(len);
FS_Write (&swlen, 4, clc.demofile);
FS_Write ( msg->data + headerBytes, len, clc.demofile );
}
/*
====================
CL_StopRecording_f
stop recording a demo
====================
*/
void Q_EXTERNAL_CALL CL_StopRecord_f( void )
{
int len;
if ( !clc.demorecording ) {
Com_Printf ("Not recording a demo.\n");
return;
}
// finish up
len = -1;
FS_Write (&len, 4, clc.demofile);
FS_Write (&len, 4, clc.demofile);
FS_FCloseFile (clc.demofile);
clc.demofile = 0;
clc.demorecording = qfalse;
Com_Printf ("Stopped demo.\n");
}
/*
==================
CL_DemoFilename
==================
*/
void CL_DemoFilename( int number, char *fileName ) {
int a,b,c,d;
if ( number < 0 || number > 9999 ) {
Com_sprintf( fileName, MAX_OSPATH, "demo9999.tga" );
return;
}
a = number / 1000;
number -= a*1000;
b = number / 100;
number -= b*100;
c = number / 10;
number -= c*10;
d = number;
Com_sprintf( fileName, MAX_OSPATH, "demo%i%i%i%i"
, a, b, c, d );
}
/*
====================
CL_Record_f
record <demoname>
Begins recording a demo from the current position
====================
*/
void Q_EXTERNAL_CALL CL_Record_f( void ) {
char name[MAX_OSPATH];
byte bufData[MAX_MSGLEN];
char demoName[MAX_QPATH];
msg_t buf;
int i;
int len;
entityState_t *ent;
entityState_t nullstate;
char *s;
if ( Cmd_Argc() > 2 ) {
Com_Printf ("record <demoname>\n");
return;
}
if ( clc.demorecording ) {
Com_Printf ("Already recording.\n");
return;
}
if ( cls.state != CA_ACTIVE ) {
Com_Printf ("You must be in a level to record.\n");
return;
}
// sync 0 doesn't prevent recording, so not forcing it off .. everyone does g_sync 1 ; record ; g_sync 0 ..
if ( NET_IsLocalAddress( clc.serverAddress ) && !Cvar_VariableValue( "g_synchronousClients" ) ) {
Com_Printf (S_COLOR_YELLOW "WARNING: You should set 'g_synchronousClients 1' for smoother demo recording\n");
}
if ( Cmd_Argc() == 2 ) {
s = Cmd_Argv(1);
Q_strncpyz( demoName, s, sizeof( demoName ) );
Com_sprintf (name, sizeof(name), "demos/%s.dm_"PROTOCOL_VERSION, demoName );
} else {
int number;
// scan for a free demo name
for ( number = 0 ; number <= 9999 ; number++ ) {
CL_DemoFilename( number, demoName );
Com_sprintf (name, sizeof(name), "demos/%s.dm_"PROTOCOL_VERSION, demoName );
len = FS_ReadFile( name, NULL );
if ( len <= 0 ) {
break; // file doesn't exist
}
}
}
// open the demo file
Com_Printf ("recording to %s.\n", name);
clc.demofile = FS_FOpenFileWrite( name );
if ( !clc.demofile ) {
Com_Printf ("ERROR: couldn't open.\n");
return;
}
clc.demorecording = qtrue;
Q_strncpyz( clc.demoName, demoName, sizeof( clc.demoName ) );
// don't start saving messages until a non-delta compressed message is received
clc.demowaiting = qtrue;
// write out the gamestate message
MSG_Init (&buf, bufData, sizeof(bufData));
MSG_Bitstream(&buf);
// NOTE, MRE: all server->client messages now acknowledge
MSG_WriteLong( &buf, clc.reliableSequence );
MSG_WriteByte (&buf, svc_gamestate);
MSG_WriteLong (&buf, clc.serverCommandSequence );
// configstrings
for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) {
if ( !cl.gameState.stringOffsets[i] ) {
continue;
}
s = cl.gameState.stringData + cl.gameState.stringOffsets[i];
MSG_WriteByte (&buf, svc_configstring);
MSG_WriteShort (&buf, i);
MSG_WriteBigString (&buf, s);
}
// write sql tables
MSG_WriteAllTables( &buf, &cl.db );
// baselines
Com_Memset (&nullstate, 0, sizeof(nullstate));
for ( i = 0; i < MAX_GENTITIES ; i++ ) {
ent = &cl.entityBaselines[i];
if ( !ent->number ) {
continue;
}
MSG_WriteByte (&buf, svc_baseline);
MSG_WriteDeltaEntity (&buf, &nullstate, ent, qtrue );
}
MSG_WriteByte( &buf, svc_EOF );
// finished writing the gamestate stuff
// write the client num
MSG_WriteLong(&buf, clc.clientNum);
// write the checksum feed
MSG_WriteLong(&buf, clc.checksumFeed);
// finished writing the client packet
MSG_WriteByte( &buf, svc_EOF );
// write it to the demo file
len = LittleLong( clc.serverMessageSequence - 1 );
FS_Write (&len, 4, clc.demofile);
len = LittleLong (buf.cursize);
FS_Write (&len, 4, clc.demofile);
FS_Write (buf.data, buf.cursize, clc.demofile);
// the rest of the demo file will be copied from net messages
}
/*
=======================================================================
CLIENT SIDE DEMO PLAYBACK
=======================================================================
*/
/*
=================
CL_DemoCompleted
=================
*/
void CL_DemoCompleted( void ) {
if (cl_timedemo && cl_timedemo->integer) {
int time;
time = Sys_Milliseconds() - clc.timeDemoStart;
if ( time > 0 ) {
Com_Printf ("%i frames, %3.1f seconds: %3.1f fps\n", clc.timeDemoFrames,
time/1000.0, clc.timeDemoFrames*1000.0 / time);
}
}
CL_Disconnect( qtrue );
CL_NextDemo();
}
/*
=================
CL_ReadDemoMessage
=================
*/
void CL_ReadDemoMessage( void ) {
int r;
msg_t buf;
byte bufData[ MAX_MSGLEN ];
int s;
if ( !clc.demofile ) {
CL_DemoCompleted ();
return;
}
// get the sequence number
r = FS_Read( &s, 4, clc.demofile);
if ( r != 4 ) {
CL_DemoCompleted ();
return;
}
clc.serverMessageSequence = LittleLong( s );
// init the message
MSG_Init( &buf, bufData, sizeof( bufData ) );
// get the length
r = FS_Read (&buf.cursize, 4, clc.demofile);
if ( r != 4 ) {
CL_DemoCompleted ();
return;
}
buf.cursize = LittleLong( buf.cursize );
if ( buf.cursize == -1 ) {
CL_DemoCompleted ();
return;
}
if ( buf.cursize > buf.maxsize ) {
Com_Error (ERR_DROP, "CL_ReadDemoMessage: demoMsglen > MAX_MSGLEN");
}
r = FS_Read( buf.data, buf.cursize, clc.demofile );
if ( r != buf.cursize ) {
Com_Printf( "Demo file was truncated.\n");
CL_DemoCompleted ();
return;
}
clc.lastPacketTime = cls.realtime;
buf.readcount = 0;
CL_ParseServerMessage( &buf );
}
/*
====================
CL_WalkDemoExt
====================
*/
static void CL_WalkDemoExt(char *arg, char *name, int *demofile)
{
int i = 0;
*demofile = 0;
while(demo_protocols[i])
{
Com_sprintf (name, MAX_OSPATH, "demos/%s.dm_%d", arg, demo_protocols[i]);
FS_FOpenFileRead( name, demofile, qtrue );
if (*demofile)
{
Com_Printf("Demo file: %s\n", name);
break;
}
else
Com_Printf("Not found: %s\n", name);
i++;
}
}
/*
====================
CL_PlayDemo_f
demo <demoname>
====================
*/
void Q_EXTERNAL_CALL CL_PlayDemo_f( void )
{
char name[MAX_OSPATH];
char *arg, *ext_test;
int protocol, i;
char retry[MAX_OSPATH];
if (Cmd_Argc() != 2) {
Com_Printf ("playdemo <demoname>\n");
return;
}
// make sure a local server is killed
Cvar_Set( "sv_killserver", "1" );
CL_Disconnect( qtrue );
// open the demo file
arg = Cmd_Argv(1);
// check for an extension .dm_?? (?? is protocol)
ext_test = arg + strlen(arg) - 6;
if ((strlen(arg) > 6) && (ext_test[0] == '.') && ((ext_test[1] == 'd') || (ext_test[1] == 'D')) && ((ext_test[2] == 'm') || (ext_test[2] == 'M')) && (ext_test[3] == '_'))
{
protocol = atoi(ext_test+4);
i=0;
while(demo_protocols[i])
{
if (demo_protocols[i] == protocol)
break;
i++;
}
if (demo_protocols[i])
{
Com_sprintf (name, sizeof(name), "demos/%s", arg);
FS_FOpenFileRead( name, &clc.demofile, qtrue );
} else {
Com_Printf("Protocol %d not supported for demos\n", protocol);
Q_strncpyz(retry, arg, sizeof(retry));
retry[strlen(retry)-6] = 0;
CL_WalkDemoExt( retry, name, &clc.demofile );
}
} else {
CL_WalkDemoExt( arg, name, &clc.demofile );
}
if (!clc.demofile) {
Com_Error( ERR_DROP, "couldn't open %s", name);
return;
}
Q_strncpyz( clc.demoName, Cmd_Argv(1), sizeof( clc.demoName ) );
Con_Close();
cls.state = CA_CONNECTED;
clc.demoplaying = qtrue;
Q_strncpyz( cls.servername, Cmd_Argv(1), sizeof( cls.servername ) );
// read demo messages until connected
while ( cls.state >= CA_CONNECTED && cls.state < CA_PRIMED ) {
CL_ReadDemoMessage();
}
// don't get the first snapshot this frame, to prevent the long
// time from the gamestate load from messing causing a time skip
clc.firstDemoFrameSkipped = qfalse;
}
/*
====================
CL_StartDemoLoop
Closing the main menu will restart the demo loop
====================
*/
void CL_StartDemoLoop( void ) {
// start the demo loop again
Cbuf_AddText ("d1\n");
cls.keyCatchers = 0;
}
/*
==================
CL_NextDemo
Called when a demo or cinematic finishes
If the "nextdemo" cvar is set, that command will be issued
==================
*/
void CL_NextDemo( void ) {
char v[MAX_STRING_CHARS];
Q_strncpyz( v, Cvar_VariableString ("nextdemo"), sizeof(v) );
v[MAX_STRING_CHARS-1] = 0;
Com_DPrintf("CL_NextDemo: %s\n", v );
if (!v[0]) {
CL_FlushMemory();
return;
}
Cvar_Set ("nextdemo","");
Cbuf_AddText (v);
Cbuf_AddText ("\n");
Cbuf_Execute();
}
//======================================================================
/*
=====================
CL_ShutdownAll
=====================
*/
void CL_ShutdownAll( void )
{
// clear sounds
S_DisableSounds();
// shutdown CGame
CL_ShutdownCGame();
// shutdown UI
CL_ShutdownUI( qfalse );
// shutdown the renderer
if( re.Shutdown )
{
re.Shutdown( qfalse ); // don't destroy window or context
cls.rendererStarted = qfalse;
}
cls.uiStarted = qfalse;
cls.cgameStarted = qfalse;
cls.soundRegistered = qfalse;
}
/*
=================
CL_FlushMemory
Called by CL_MapLoading, CL_Connect_f, CL_PlayDemo_f, and CL_ParseGamestate the only
ways a client gets into a game
Also called by Com_Error
=================
*/
void CL_FlushMemory( void ) {
// shutdown all the client stuff
CL_ShutdownAll();
// if not running a server clear the whole hunk
if ( !com_sv_running->integer ) {
// clear the whole hunk
Hunk_Clear();
CM_ClearPreloaderData();
// clear collision map data
CM_ClearMap();
}
else {
// clear all the client data on the hunk
Hunk_ClearToMark();
}
CL_StartHunkUsers();
}
/*
=====================
CL_MapLoading
A local server is starting to load a map, so update the
screen to let the user know about it, then dump all client
memory on the hunk from cgame, ui, and renderer
=====================
*/
void CL_MapLoading( void ) {
if ( !com_cl_running->integer ) {
return;
}
Con_Close();
cls.keyCatchers = 0;
// if we are already connected to the local host, stay connected
if ( cls.state >= CA_CONNECTED && !Q_stricmp( cls.servername, "localhost" ) ) {
cls.state = CA_CONNECTED; // so the connect screen is drawn
Com_Memset( cls.updateInfoString, 0, sizeof( cls.updateInfoString ) );
Com_Memset( clc.serverMessage, 0, sizeof( clc.serverMessage ) );
Com_Memset( &cl.gameState, 0, sizeof( cl.gameState ) );
clc.lastPacketSentTime = -9999;
SCR_UpdateScreen();
} else {
// clear nextmap so the cinematic shutdown doesn't execute it
Cvar_Set( "nextmap", "" );
CL_Disconnect( qtrue );
Q_strncpyz( cls.servername, "localhost", sizeof(cls.servername) );
cls.state = CA_CHALLENGING; // so the connect screen is drawn
cls.keyCatchers = 0;
SCR_UpdateScreen();
clc.connectTime = -RETRANSMIT_TIMEOUT;
NET_StringToAdr( cls.servername, &clc.serverAddress);
// we don't need a challenge on the localhost
CL_CheckForResend();
}
}
static int sql_global_int( const char * name ) {
if ( name )
{
switch( SWITCHSTRING( name ) )
{
case CS('c','l','i','e'): return clc.clientNum;
default:
Cmd_TokenizeString( name );
return VM_Call( uivm, UI_GLOBAL_INT );
}
}
return 0;
}
static qhandle_t sql_portrait( const char * path )
{
char model[ MAX_QPATH ];
char * skin;
Q_strncpyz( model, path, sizeof(model) );
skin = Q_strrchr( model, '/' );
if ( skin ) {
*skin++ = '\0';
} else {
skin = "default";
}
return re.RegisterShaderNoMip( va("models/players/%s/icon_%s", model, skin ) );
}
static qhandle_t sql_shader( const char * name ) {
return re.RegisterShader( name );
}
static qhandle_t sql_sound( const char * name ) {
return S_RegisterSound( name, qtrue );
}
static qhandle_t sql_model( const char * name ) {
return re.RegisterModel( name );
}
/*
=====================
CL_ClearState
Called before parsing a gamestate
=====================
*/
void CL_ClearState (void) {
// S_StopAllSounds();
Com_Memset( &cl, 0, sizeof( cl ) );
// clear sql db
sql_reset ( &cl.db, TAG_SQL_CLIENT );
sql_assign_gs ( &cl.db, (int*)&cl.snap.ps.gs );
cl.db.ps = (int*)&cl.snap.ps;
sql_exec( &cl.db,
"CREATE TABLE servers"
"("
"addr STRING, "
"clients INTEGER, "
"hostname STRING, "
"mapname STRING, "
"sv_maxclients INTEGER, "
"game STRING, "
"gametype INTEGER, "
"nettype INTEGER, "
"minping INTEGER, "
"maxping INTEGER, "
"punkbuster INTEGER, "
"ping INTEGER, "
"start INTEGER, "
"source INTEGER, "
"lastupdate INTEGER, "
"channel STRING "
");"
);
cl.db.global_int = sql_global_int;
cl.db.portrait = sql_portrait;
cl.db.shader = sql_shader;
cl.db.sound = sql_sound;
cl.db.model = sql_model;
}
/*
=====================
CL_Disconnect
Called when a connection, demo, or cinematic is being terminated.
Goes from a connected state to either a menu state or a console state
Sends a disconnect message to the server
This is also called on Com_Error and Com_Quit, so it shouldn't cause any errors
=====================
*/
void CL_Disconnect( qboolean showMainMenu ) {
if ( !com_cl_running || !com_cl_running->integer ) {
return;
}
// shutting down the client so enter full screen ui mode
Cvar_Set("r_uiFullScreen", "1");
if ( clc.demorecording ) {
CL_StopRecord_f ();
}
if ( clc.demofile ) {
FS_FCloseFile( clc.demofile );
clc.demofile = 0;
}
if ( uivm && showMainMenu ) {
VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NONE );
}
SCR_StopCinematic ();
S_ClearSoundBuffer();
// send a disconnect message to the server
// send it a few times in case one is dropped
if ( cls.state >= CA_CONNECTED ) {
CL_AddReliableCommand( "disconnect" );
CL_WritePacket();
CL_WritePacket();
CL_WritePacket();
}
CL_ClearState ();
// wipe the client connection
Com_Memset( &clc, 0, sizeof( clc ) );
Cvar_Set( "cl_spacetrader", "0" );
cls.state = CA_DISCONNECTED;
// allow cheats locally
Cvar_Set( "sv_cheats", "1" );
// not connected to a pure server anymore
cl_connectedToPureServer = qfalse;
}
/*
===================
CL_ForwardCommandToServer
adds the current command line as a clientCommand
things like godmode, noclip, etc, are commands directed to the server,
so when they are typed in at the console, they will need to be forwarded.
===================
*/
void CL_ForwardCommandToServer( const char *string ) {
char *cmd;
cmd = Cmd_Argv(0);
// ignore key up commands
if ( cmd[0] == '-' ) {
return;
}
if ( clc.demoplaying || cls.state < CA_CONNECTED || cmd[0] == '+' ) {
Com_Printf ("Unknown command \"%s\"\n", cmd);
return;
}
if ( Cmd_Argc() > 1 ) {
CL_AddReliableCommand( string );
} else {
CL_AddReliableCommand( cmd );
}
}
#ifdef USE_WEBAUTH
/*
===================
CL_Highscore_response
gets highscores results from the website
===================
*/
static int QDECL CL_Highscore_response( httpInfo_e code, const char * buffer, int length, void * notifyData )
{
if ( code == HTTP_WRITE ) {
Cvar_Set( "st_postresults", buffer );
VM_Call(uivm, UI_REPORT_HIGHSCORE_RESPONSE );
}
return 1;
}
/*
===================
CL_Highscore_f
asks for highscores from the website
===================
*/
static void Q_EXTERNAL_CALL CL_Highscore_f( void ) {
if ( Cmd_Argc() != 7 ) {
Com_Printf("Usage: highscore <score> <skill> <kills> <time> <game> <real time>\n");
return;
}
HTTP_PostUrl( va("http://%s/user/report_score",AUTHORIZE_SERVER_NAME), CL_Highscore_response, 0,
"c[slot]=%d&h[score]=%d&h[skill]=%d&h[kills]=%d&h[time]=%d&h[game_id]=%d&h[real_time]=%d",
Cvar_VariableIntegerValue("slot"),
atoi(Cmd_Argv(1)),
atoi(Cmd_Argv(2)),
atoi(Cmd_Argv(3)),
atoi(Cmd_Argv(4)),
atoi(Cmd_Argv(5)),
atoi(Cmd_Argv(6)) );
}
/*
===================
CL_Login_response
website's response to a users attempt to login
===================
*/
static int QDECL CL_Login_response( httpInfo_e code, const char * buffer, int length, void * notifyData )
{
if ( code == HTTP_WRITE ) {
Cvar_Set( "cl_servermessage", Info_ValueForKey( buffer, "message" ) );
switch( atoi( Info_ValueForKey( buffer, "status" ) ) )
{
case 1: VM_Call( uivm, UI_AUTHORIZED, AUTHORIZE_OK ); break;
case -1: VM_Call( uivm, UI_AUTHORIZED, AUTHORIZE_NOTVERIFIED ); break;
default: VM_Call( uivm, UI_AUTHORIZED, AUTHORIZE_BAD ); break;
}
}
// VM_Call( uivm, UI_AUTHORIZED, AUTHORIZE_UNAVAILABLE);
return length;
}
/*
==================
CL_Login_f
==================
*/
static void Q_EXTERNAL_CALL CL_Login_f( void )
{
if ( Cmd_Argc() != 3) {
Com_Printf( "usage: login user password\n");
return;
}
HTTP_PostUrl( va("http://%s/user/login", AUTHORIZE_SERVER_NAME), CL_Login_response, 0, "user[login]=%s&user[password]=%s&version=%d", Cmd_Argv(1), Cmd_Argv(2), 31 );
}
/*
==================
CL_ForgotPassword_f
==================
*/
void Q_EXTERNAL_CALL CL_ForgotPassword_f(void)
{
if ( Cmd_Argc() != 2 ) {
Com_Printf( "usage: forgotpassword email\n" );
return;
}
HTTP_PostUrl( va("http://%s/user/forgot_password", AUTHORIZE_SERVER_NAME), 0, 0, "user[email]=%s", Cmd_Argv(1) );
}
/*
==================
CL_CreateCharacter_response
==================
*/
static int QDECL CL_CreateCharacter_response( httpInfo_e code, const char * buffer, int length, void * notifyData )
{
if ( code == HTTP_WRITE ) {
Cvar_Set( "cl_servermessage", buffer );
VM_Call( uivm, UI_AUTHORIZED, AUTHORIZE_CREATECHARACTER );
}
return 1;
}
/*
==================
CL_CreateCharacter_f
==================
*/
void Q_EXTERNAL_CALL CL_CreateCharacter_f(void)
{
if ( Cmd_Argc() != 4 ) {
Com_Printf( "usage: createcharacter <slot> <name> <model>\n" );
return;
}
HTTP_PostUrl( va("http://%s/user/create_character", AUTHORIZE_SERVER_NAME), CL_CreateCharacter_response, 0, "char[slot]=%s&char[name]=%s&char[model]=%s", Cmd_Argv(1), Cmd_Argv(2), Cmd_Argv(3) );
}
/*
==================
CL_DeleteCharacter_response
==================
*/
static int QDECL CL_DeleteCharacter_response( httpInfo_e code, const char * buffer, int length, void * notifyData )
{
if ( code == HTTP_WRITE ) {
if ( buffer && buffer[0] == '1' ) {
VM_Call( uivm, UI_AUTHORIZED, AUTHORIZE_DELETECHARACTER );
} else {
VM_Call( uivm, UI_AUTHORIZED, AUTHORIZE_BAD);
}
}
return 1;
}
/*
==================
CL_DeleteCharacter_f
==================
*/
void Q_EXTERNAL_CALL CL_DeleteCharacter_f(void)
{
if ( Cmd_Argc() != 2 ) {
Com_Printf( "usage: deletecharacter <slot>\n" );
return;
}
HTTP_PostUrl( va("http://%s/user/delete_character", AUTHORIZE_SERVER_NAME), CL_DeleteCharacter_response, 0, "slot=%s", Cmd_Argv(1) );
}
/*
==================
CL_GetAccount_response
==================
*/
static int QDECL CL_GetAccount_response( httpInfo_e code, const char * buffer, int length, void * notifyData )
{
if ( code == HTTP_WRITE ) {
Cvar_Set( "cl_servermessage", buffer );
VM_Call( uivm, UI_AUTHORIZED, AUTHORIZE_ACCOUNTINFO );
}
return 1;
}
/*
================== `
CL_GetAccount_f
==================
*/void Q_EXTERNAL_CALL CL_GetAccount_f(void)
{
if ( Cmd_Argc() != 1 ) {
Com_Printf( "usage: getaccount\n" );
return;
}
HTTP_PostUrl( va("http://%s/user/characters", AUTHORIZE_SERVER_NAME), CL_GetAccount_response, 0, 0 );
}
/*
==================
CL_GetHighScores_response
==================
*/
int QDECL CL_GetHighScores_response( httpInfo_e code, const char * buffer, int length, void * notifyData )
{
// ignore message from server if already in the game
if ( code == HTTP_WRITE && uivm && Cvar_VariableIntegerValue( "cl_spacetrader" ) == 0 ) {
if ( length > 1 ) {
sql_prepare ( &cl.db, "UPDATE OR INSERT scores CS $2 SEARCH challenge ?1;" );
sql_bindint ( &cl.db, 1, atoi( Info_ValueForKey(buffer,"challenge") ) );
sql_bindtext( &cl.db, 2, buffer );
sql_step ( &cl.db );
sql_done ( &cl.db );
}
}
return 1;
}
/*
==================
CL_GlobalHighScores_f
==================
*/
void Q_EXTERNAL_CALL CL_GlobalHighScores_f( void )
{
if ( Cmd_Argc() != 3) {
Com_Printf( "usage: globalhighscores [version] [char slot]\n");
return;
}
HTTP_PostUrl( va("http://%s/user/scores/version/%d/slot/%d",AUTHORIZE_SERVER_NAME, atoi(Cmd_Argv(1)), atoi(Cmd_Argv(2))), CL_GetHighScores_response, 0, 0 );
}
#endif
/*
==================
CL_UpdateServer_f
==================
*/
void Q_EXTERNAL_CALL CL_UpdateServer_f(void)
{
if ( Cmd_Argc() != 2 ) {
Com_Printf( "usage: updateserver <status>\n" );
return;
}
switch(atoi(Cmd_Argv(1)))
{
case 2:
{
#ifdef USE_IRC
cvar_t *channel = Cvar_Get("sv_ircchannel", "", 0);
Net_IRC_SendMessage(va("mode %s +s", channel->string) );
#endif
}
break;
}
}
/*
==================
CL_GetServers_f
==================
*/
void Q_EXTERNAL_CALL CL_GetServers_f(void)
{
if ( Cmd_Argc() != 1 ) {
Com_Printf( "usage: getservers\n" );
return;
}
#ifdef USE_IRC
Net_IRC_ListServers();
#endif
}
/*
==================
CL_OpenUrl_f
==================
*/
void Q_EXTERNAL_CALL CL_OpenUrl_f( void )
{
const char *url;
if( Cmd_Argc() != 2 )
{
Com_Printf( "Usage: openurl <url>\n" );
return;
}
url = Cmd_Argv( 1 );
{
/*
FixMe: URL sanity checks.
Random sanity checks. Scott: if you've got some magic URL
parsing and validating functions USE THEM HERE, this code
is a placeholder!!!
*/
int i;
const char *u;
const char *allowPrefixes[] = { "http://", "https://", "" };
const char *allowDomains[2] = { "www.playspacetrader.com", 0 };
#ifdef USE_WEBAUTH
allowDomains[1] = AUTHORIZE_SERVER_NAME;
#endif
u = url;
for( i = 0; i < lengthof( allowPrefixes ); i++ )
{
const char *p = allowPrefixes[i];
size_t len = strlen( p );
if( Q_strncmp( u, p, len ) == 0 )
{
u += len;
break;
}
}
if( i == lengthof( allowPrefixes ) )
{
/*
This really won't ever hit because of the "" at the end
of the allowedPrefixes array. As I said above, placeholder
code: fix it later!
*/
Com_Printf( "Invalid URL prefix.\n" );
return;
}
for( i = 0; i < lengthof( allowDomains ); i++ )
{
size_t len;
const char *d = allowDomains[i];
if ( !d )
break;
len = strlen( d );
if( Q_strncmp( u, d, len ) == 0 )
{
u += len;
break;
}
}
if( i == lengthof( allowDomains ) )
{
Com_Printf( "Invalid domain.\n" );
return;
}
/* my kingdom for a regex */
for (i=0; i < strlen(url); i++)
{
if ( !(
(url[i] >= 'a' && url[i] <= 'z') || // lower case alpha
(url[i] >= 'A' && url[i] <= 'Z') || // upper case alpha
(url[i] >= '0' && url[i] <= '9') || //numeric
(url[i] == '/') || (url[i] == ':' ) || // / and : chars
(url[i] == '.' ) || (url[i] == '&') || // . and & chars
(url[i] == ';' ) // ; char
) ) {
Com_Printf("Invalid URL\n");
return;
}
}
}
if( !Sys_OpenUrl( url ) )
Com_Printf( "System error opening URL\n" );
}
#ifdef USE_AUTOPATCH
/*
==================
CL_GetVersion_response
==================
*/
static int QDECL CL_GetVersion_response( httpInfo_e code, const char * buffer, int length, void * notifyData )
{
if ( code == HTTP_WRITE ) {
char key [ MAX_INFO_KEY ];
char value [ MAX_INFO_VALUE ];
for ( ;; )
{
Info_NextPair( &buffer, key, value );
if ( key[ 0 ] == '\0' )
break;
Cvar_Set( key, value );
}
Cbuf_ExecuteText( EXEC_INSERT, "sync_all 0\n" );
}
return 1;
}
/*
=================
CL_CheckVersion_f
=================
*/
void Q_EXTERNAL_CALL CL_CheckVersion_f(void)
{
if ( com_webhost && com_webhost->string && com_webhost->string[ 0 ] ) {
HTTP_GetUrl( va("%s/version/%s", com_webhost->string ,OS_STRING), CL_GetVersion_response, 0, 0);
}
}
#endif
/*
======================================================================
CONSOLE COMMANDS
======================================================================
*/
/*
==================
CL_ForwardToServer_f
==================
*/
void Q_EXTERNAL_CALL CL_ForwardToServer_f( void )
{
if ( cls.state != CA_ACTIVE || clc.demoplaying ) {
Com_Printf ("Not connected to a server.\n");
return;
}
// don't forward the first argument
if ( Cmd_Argc() > 1 ) {
CL_AddReliableCommand( Cmd_Args() );
}
}
/*
==================
CL_Setenv_f
Mostly for controlling voodoo environment variables
==================
*/
void Q_EXTERNAL_CALL CL_Setenv_f( void ) {
int argc = Cmd_Argc();
if ( argc > 2 ) {
char buffer[1024];
int i;
strcpy( buffer, Cmd_Argv(1) );
strcat( buffer, "=" );
for ( i = 2; i < argc; i++ ) {
strcat( buffer, Cmd_Argv( i ) );
strcat( buffer, " " );
}
putenv( buffer );
} else if ( argc == 2 ) {
char *env = getenv( Cmd_Argv(1) );
if ( env ) {
Com_Printf( "%s=%s\n", Cmd_Argv(1), env );
} else {
Com_Printf( "%s undefined\n", Cmd_Argv(1), env );
}
}
}
/*
==================
CL_Disconnect_f
==================
*/
void Q_EXTERNAL_CALL CL_Disconnect_f( void ) {
SCR_StopCinematic();
if ( cls.state != CA_DISCONNECTED && cls.state != CA_CINEMATIC ) {
Com_Error (ERR_DISCONNECT, "Disconnected from server");
}
}
/*
================
CL_Reconnect_f
================
*/
void Q_EXTERNAL_CALL CL_Reconnect_f( void )
{
if ( !strlen( cls.servername ) || !strcmp( cls.servername, "localhost" ) ) {
Com_Printf( "Can't reconnect to localhost.\n" );
return;
}
Cbuf_AddText( va("connect %s\n", cls.servername ) );
}
/*
================
CL_Connect_f
================
*/
void Q_EXTERNAL_CALL CL_Connect_f( void )
{
char *server;
if ( Cmd_Argc() != 2 ) {
Com_Printf( "usage: connect [server]\n");
return;
}
// clear any previous "server full" type messages
clc.serverMessage[0] = 0;
server = Cmd_Argv (1);
if ( com_sv_running->integer && !strcmp( server, "localhost" ) ) {
// if running a local server, kill it
SV_Shutdown( "Server quit\n" );
}
sql_prepare( &cl.db, "SELECT channel FROM servers SEARCH addr $1 WHERE channel!-''");
sql_bindtext( &cl.db, 1, server );
if ( sql_step( &cl.db ) ) {
#ifdef USE_IRC
Net_IRC_JoinChannel( sql_columnastext( &cl.db, 0 ) );
#endif
}
sql_done( &cl.db );
// make sure a local server is killed
Cvar_Set( "sv_killserver", "1" );
SV_Frame( 0 );
CL_Disconnect( qtrue );
Con_Close();
/* MrE: 2000-09-13: now called in CL_DownloadsComplete
CL_FlushMemory( );
*/
Q_strncpyz( cls.servername, server, sizeof(cls.servername) );
if (!NET_StringToAdr( cls.servername, &clc.serverAddress) ) {
Com_Printf ("Bad server address\n");
cls.state = CA_DISCONNECTED;
return;
}
if (clc.serverAddress.port == 0) {
clc.serverAddress.port = BigShort( PORT_SERVER );
}
Com_Printf( "%s resolved to %i.%i.%i.%i:%i\n", cls.servername,
clc.serverAddress.ip[0], clc.serverAddress.ip[1],
clc.serverAddress.ip[2], clc.serverAddress.ip[3],
BigShort( clc.serverAddress.port ) );
// if we aren't playing on a lan, we need to authenticate
// with the cd key
if ( NET_IsLocalAddress( clc.serverAddress ) ) {
cls.state = CA_CHALLENGING;
} else {
cls.state = CA_CONNECTING;
}
cls.keyCatchers = 0;
clc.connectTime = -99999; // CL_CheckForResend() will fire immediately
clc.connectPacketCount = 0;
// server connection string
Cvar_Set( "cl_currentServerAddress", server );
}
#define MAX_RCON_MESSAGE 1024
/*
=====================
CL_Rcon_f
Send the rest of the command line over as
an unconnected command.
=====================
*/
void Q_EXTERNAL_CALL CL_Rcon_f( void ) {
char message[MAX_RCON_MESSAGE];
netadr_t to;
if ( !rcon_client_password->string ) {
Com_Printf ("You must set 'rconpassword' before\n"
"issuing an rcon command.\n");
return;
}
message[0] = -1;
message[1] = -1;
message[2] = -1;
message[3] = -1;
message[4] = 0;
Q_strcat (message, MAX_RCON_MESSAGE, "rcon ");
Q_strcat (message, MAX_RCON_MESSAGE, rcon_client_password->string);
Q_strcat (message, MAX_RCON_MESSAGE, " ");
// https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543
Q_strcat (message, MAX_RCON_MESSAGE, Cmd_Cmd()+5);
if ( cls.state >= CA_CONNECTED ) {
to = clc.netchan.remoteAddress;
} else {
if (!strlen(rconAddress->string)) {
Com_Printf ("You must either be connected,\n"
"or set the 'rconAddress' cvar\n"
"to issue rcon commands\n");
return;
}
NET_StringToAdr (rconAddress->string, &to);
if (to.port == 0) {
to.port = BigShort (PORT_SERVER);
}
}
NET_SendPacket (NS_CLIENT, strlen(message)+1, message, to);
}
/*
=================
CL_SendPureChecksums
=================
*/
void CL_SendPureChecksums( void ) {
const char *pChecksums;
char cMsg[MAX_INFO_VALUE];
int i;
// if we are pure we need to send back a command with our referenced pk3 checksums
pChecksums = "hello"; //FS_ReferencedPakPureChecksums();
// "cp"
// "Yf"
Com_sprintf(cMsg, sizeof(cMsg), "Yf ");
Q_strcat(cMsg, sizeof(cMsg), va("%d ", cl.serverId) );
Q_strcat(cMsg, sizeof(cMsg), pChecksums);
for (i = 0; i < 2; i++) {
cMsg[i] += 10;
}
CL_AddReliableCommand( cMsg );
}
/*
=================
CL_ResetPureClientAtServer
=================
*/
void CL_ResetPureClientAtServer( void ) {
CL_AddReliableCommand( va("vdr") );
}
/*
=================
CL_Vid_Restart_f
Restart the video subsystem
we also have to reload the UI and CGame because the renderer
doesn't know what graphics to reload
=================
*/
void Q_EXTERNAL_CALL CL_Vid_Restart_f( void ) {
// don't let them loop during the restart
S_StopAllSounds();
// shutdown the UI
CL_ShutdownUI( qtrue );
// shutdown the CGame
CL_ShutdownCGame();
// shutdown the renderer and clear the renderer interface
CL_ShutdownRef();
// client is no longer pure untill new checksums are sent
CL_ResetPureClientAtServer();
// reinitialize the filesystem if the game directory or checksum has changed
FS_ConditionalRestart( clc.checksumFeed );
cls.rendererStarted = qfalse;
cls.uiStarted = qfalse;
cls.cgameStarted = qfalse;
cls.soundRegistered = qfalse;
// unpause so the cgame definately gets a snapshot and renders a frame
Cvar_Set( "cl_paused", "0" );
// if not running a server clear the whole hunk
if ( !com_sv_running->integer ) {
// clear the whole hunk
Hunk_Clear();
CM_ClearPreloaderData();
}
else {
// clear all the client data on the hunk
Hunk_ClearToMark();
}
// initialize the renderer interface
CL_InitRef();
// startup all the client stuff
CL_StartHunkUsers();
// start the cgame if connected
if ( cls.state > CA_CONNECTED && cls.state != CA_CINEMATIC ) {
cls.cgameStarted = qtrue;
CL_InitCGame();
// send pure checksums
CL_SendPureChecksums();
}
}
/*
=================
CL_Snd_Restart_f
Restart the sound subsystem
The cgame and game must also be forced to restart because
handles will be invalid
=================
*/
void Q_EXTERNAL_CALL CL_Snd_Restart_f( void ) {
S_Shutdown();
S_Init();
CL_Vid_Restart_f();
}
/*
==================
CL_Configstrings_f
==================
*/
void Q_EXTERNAL_CALL CL_Configstrings_f( void )
{
int i;
int ofs;
if ( cls.state != CA_ACTIVE ) {
Com_Printf( "Not connected to a server.\n");
return;
}
for ( i = 0 ; i < MAX_CONFIGSTRINGS ; i++ ) {
ofs = cl.gameState.stringOffsets[ i ];
if ( !ofs ) {
continue;
}
Com_Printf( "%4i: %s\n", i, cl.gameState.stringData + ofs );
}
}
/*
==============
CL_Clientinfo_f
==============
*/
void Q_EXTERNAL_CALL CL_Clientinfo_f( void )
{
Com_Printf( "--------- Client Information ---------\n" );
Com_Printf( "state: %i\n", cls.state );
Com_Printf( "Server: %s\n", cls.servername );
Com_Printf ("User info settings:\n");
Info_Print( Cvar_InfoString( CVAR_USERINFO ) );
Com_Printf( "--------------------------------------\n" );
}
//====================================================================
/*
=================
CL_DownloadsComplete
Called when all downloading has been completed
=================
*/
void CL_DownloadsComplete( void ) {
if ( cls.state < CA_CONNECTING ) {
// a download has completed outside of a game
return;
}
if ( cls.state == CA_ACTIVE ) {
// a download has completed while the game is playing
// inform the client that its download is complete
Cbuf_ExecuteText( EXEC_INSERT, "donedl" );
return;
}
// if we downloaded files we need to restart the file system
if (clc.downloadRestart) {
clc.downloadRestart = qfalse;
FS_Restart(clc.checksumFeed); // We possibly downloaded a pak, restart the file system to load it
// inform the server so we get new gamestate info
CL_AddReliableCommand( "donedl" );
// by sending the donedl command we request a new gamestate
// so we don't want to load stuff yet
return;
}
// let the client game init and load data
cls.state = CA_LOADING;
// Pump the loop, this may change gamestate!
Com_EventLoop();
// if the gamestate was changed by calling Com_EventLoop
// then we loaded everything already and we don't want to do it again.
if ( cls.state != CA_LOADING ) {
return;
}
// starting to load a map so we get out of full screen ui mode
Cvar_Set("r_uiFullScreen", "0");
// flush client memory and start loading stuff
// this will also (re)load the UI
// if this is a local client then only the client part of the hunk
// will be cleared, note that this is done after the hunk mark has been set
CL_FlushMemory();
// initialize the CGame
cls.cgameStarted = qtrue;
CL_InitCGame();
// set pure checksums
CL_SendPureChecksums();
CL_WritePacket();
CL_WritePacket();
CL_WritePacket();
}
/*
=================
CL_InitDownloads
After receiving a valid game state, we valid the cgame and local zip files here
and determine if we need to download them
=================
*/
void CL_InitDownloads(void) {
#if 0
// if connecting to a server, force all paks to download first.
#ifdef USE_WEBHOST
if ( !com_sv_running->integer || Cvar_VariableIntegerValue( "ui_singlePlayerActive" )==0 ) {
if ( Com_SyncAll() ) {
// if autodownloading is not enabled on the server
cls.state = CA_CONNECTED;
clc.downloadRestart = qtrue;
return;
}
}
#endif
#endif
// let the client game init and load data
cls.state = CA_LOADING;
CL_DownloadsComplete();
}
/*
=================
CL_CheckForResend
Resend a connect message if the last one has timed out
=================
*/
void CL_CheckForResend( void ) {
int port, i;
char info[MAX_INFO_STRING];
char data[MAX_INFO_STRING];
// don't send anything if playing back a demo
if ( clc.demoplaying ) {
return;
}
// resend if we haven't gotten a reply yet
if ( cls.state != CA_CONNECTING && cls.state != CA_CHALLENGING ) {
return;
}
if ( cls.realtime - clc.connectTime < RETRANSMIT_TIMEOUT ) {
return;
}
clc.connectTime = cls.realtime; // for retransmit requests
clc.connectPacketCount++;
switch ( cls.state ) {
case CA_CONNECTING:
// requesting a challenge
//Always request auth
//if ( !Sys_IsLANAddress( clc.serverAddress ) ) {
//CL_RequestAuthorization();
//}
NET_OutOfBandPrint(NS_CLIENT, clc.serverAddress, "getchallenge");
break;
case CA_CHALLENGING:
// sending back the challenge
port = Cvar_VariableValue ("net_qport");
Q_strncpyz( info, Cvar_InfoString( CVAR_USERINFO ), sizeof( info ) );
Info_SetValueForKey( info, "protocol", PROTOCOL_VERSION );
Info_SetValueForKey( info, "qport", va("%i", port ) );
Info_SetValueForKey( info, "challenge", va("%i", clc.challenge ) );
strcpy(data, "connect ");
// TTimo adding " " around the userinfo string to avoid truncated userinfo on the server
// (Com_TokenizeString tokenizes around spaces)
data[8] = '"';
for(i=0;i<strlen(info);i++) {
data[9+i] = info[i]; // + (clc.challenge)&0x3;
}
data[9+i] = '"';
data[10+i] = 0;
// NOTE TTimo don't forget to set the right data length!
NET_OutOfBandData( NS_CLIENT, clc.serverAddress, (byte *) &data[0], i+10 );
// the most current userinfo has been sent, so watch for any
// newer changes to userinfo variables
cvar_modifiedFlags &= ~CVAR_USERINFO;
break;
default:
Com_Error( ERR_FATAL, "CL_CheckForResend: bad cls.state" );
}
}
/*
===================
CL_DisconnectPacket
Sometimes the server can drop the client and the netchan based
disconnect can be lost. If the client continues to send packets
to the server, the server will send out of band disconnect packets
to the client so it doesn't have to wait for the full timeout period.
===================
*/
void CL_DisconnectPacket( netadr_t from ) {
if ( cls.state < CA_AUTHORIZING ) {
return;
}
// if not from our server, ignore it
if ( !NET_CompareAdr( from, clc.netchan.remoteAddress ) ) {
return;
}
// if we have received packets within three seconds, ignore it
// (it might be a malicious spoof)
if ( cls.realtime - clc.lastPacketTime < 3000 ) {
return;
}
// drop the connection
Com_Printf( "Server disconnected for unknown reason\n" );
Cvar_Set("com_errorMessage", "Server disconnected for unknown reason\n" );
CL_Disconnect( qtrue );
}
/*
===================
CL_MotdPacket
===================
*/
void CL_MotdPacket( netadr_t from ) {
char *challenge;
char *info;
// if not from our server, ignore it
if ( !NET_CompareAdr( from, cls.updateServer ) ) {
return;
}
info = Cmd_Argv(1);
// check challenge
challenge = Info_ValueForKey( info, "challenge" );
if ( strcmp( challenge, cls.updateChallenge ) ) {
return;
}
challenge = Info_ValueForKey( info, "motd" );
Q_strncpyz( cls.updateInfoString, info, sizeof( cls.updateInfoString ) );
Cvar_Set( "cl_motdString", challenge );
}
/*
=================
CL_ConnectionlessPacket
Responses to broadcasts, etc
=================
*/
void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) {
char *s;
char *c;
MSG_BeginReadingOOB( msg );
MSG_ReadLong( msg ); // skip the -1
s = MSG_ReadStringLine( msg );
Cmd_TokenizeString( s );
c = Cmd_Argv(0);
Com_DPrintf ("CL packet %s: %s\n", NET_AdrToString(from), c);
// challenge from the server we are connecting to
if ( !Q_stricmp(c, "challengeResponse") ) {
if ( cls.state != CA_CONNECTING ) {
Com_Printf( "Unwanted challenge response received. Ignored.\n" );
} else {
// start sending challenge repsonse instead of challenge request packets
clc.challenge = atoi(Cmd_Argv(1));
cls.state = CA_CHALLENGING;
clc.connectPacketCount = 0;
clc.connectTime = -99999;
// take this address as the new server address. This allows
// a server proxy to hand off connections to multiple servers
clc.serverAddress = from;
Com_DPrintf ("challengeResponse: %d\n", clc.challenge);
}
return;
}
// server connection
if ( !Q_stricmp(c, "connectResponse") ) {
if ( cls.state >= CA_CONNECTED ) {
Com_Printf ("Dup connect received. Ignored.\n");
return;
}
if ( cls.state != CA_CHALLENGING ) {
Com_Printf ("connectResponse packet while not connecting. Ignored.\n");
return;
}
if ( !NET_CompareBaseAdr( from, clc.serverAddress ) ) {
Com_Printf( "connectResponse from a different address. Ignored.\n" );
Com_Printf( "%s should have been %s\n", NET_AdrToString( from ),
NET_AdrToString( clc.serverAddress ) );
return;
}
Netchan_Setup (NS_CLIENT, &clc.netchan, from, Cvar_VariableValue( "net_qport" ) );
cls.state = CA_CONNECTED;
clc.lastPacketSentTime = -9999; // send first packet immediately
return;
}
// server responding to an info broadcast
if ( !Q_stricmp(c, "infoResponse") ) {
CL_ServerInfoPacket( from, msg );
return;
}
// server responding to a get playerlist
if ( !Q_stricmp(c, "statusResponse") ) {
CL_ServerStatusResponse( from, msg );
return;
}
// a disconnect message from the server, which will happen if the server
// dropped the connection but it is still getting packets from us
if (!Q_stricmp(c, "disconnect")) {
CL_DisconnectPacket( from );
return;
}
// echo request from server
if ( !Q_stricmp(c, "echo") ) {
NET_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv(1) );
return;
}
// cd check
if ( !Q_stricmp(c, "keyAuthorize") ) {
// we don't use these now, so dump them on the floor
return;
}
// global MOTD from id
if ( !Q_stricmp(c, "motd") ) {
CL_MotdPacket( from );
return;
}
// echo request from server
if ( !Q_stricmp(c, "print") ) {
s = MSG_ReadString( msg );
Q_strncpyz( clc.serverMessage, s, sizeof( clc.serverMessage ) );
Com_Printf( "%s", s );
return;
}
// error message from server
if ( !Q_stricmp( c, "error" ) ) {
s = MSG_ReadString( msg );
if ( uivm )
VM_Call( uivm, UI_SERVER_ERRORMESSAGE, s );
return;
}
Com_DPrintf ("Unknown connectionless packet command.\n");
}
/*
=================
CL_PacketEvent
A packet has arrived from the main event loop
=================
*/
void CL_PacketEvent( netadr_t from, msg_t *msg ) {
int headerBytes;
clc.lastPacketTime = cls.realtime;
if ( msg->cursize >= 4 && *(int *)msg->data == -1 ) {
CL_ConnectionlessPacket( from, msg );
return;
}
if ( cls.state < CA_CONNECTED ) {
return; // can't be a valid sequenced packet
}
if ( msg->cursize < 4 ) {
Com_Printf ("%s: Runt packet\n",NET_AdrToString( from ));
return;
}
//
// packet from server
//
if ( !NET_CompareAdr( from, clc.netchan.remoteAddress ) ) {
Com_DPrintf ("%s:sequenced packet without connection\n"
,NET_AdrToString( from ) );
// FIXME: send a client disconnect?
return;
}
if (!CL_Netchan_Process( &clc.netchan, msg) ) {
return; // out of order, duplicated, etc
}
// the header is different lengths for reliable and unreliable messages
headerBytes = msg->readcount;
// track the last message received so it can be returned in
// client messages, allowing the server to detect a dropped
// gamestate
clc.serverMessageSequence = LittleLong( *(int *)msg->data );
clc.lastPacketTime = cls.realtime;
CL_ParseServerMessage( msg );
//
// we don't know if it is ok to save a demo message until
// after we have parsed the frame
//
if ( clc.demorecording && !clc.demowaiting ) {
CL_WriteDemoMessage( msg, headerBytes );
}
}
/*
==================
CL_CheckTimeout
==================
*/
void CL_CheckTimeout( void )
{
//
// check timeout
//
if ( ( !cl_paused->integer || !sv_paused->integer )
&& cls.state >= CA_CONNECTED && cls.state != CA_CINEMATIC
&& cls.realtime - clc.lastPacketTime > cl_timeout->value*1000) {
if (++cl.timeoutcount > 5) { // timeoutcount saves debugger
Com_Printf ("\nServer connection timed out.\n");
CL_Disconnect( qtrue );
return;
}
} else {
cl.timeoutcount = 0;
}
}
//============================================================================
/*
==================
CL_CheckUserinfo
==================
*/
void CL_CheckUserinfo( void ) {
// don't add reliable commands when not yet connected
if ( cls.state < CA_CHALLENGING ) {
return;
}
// don't overflow the reliable command buffer when paused
if ( cl_paused->integer ) {
return;
}
// send a reliable userinfo update if needed
if( cvar_modifiedFlags & CVAR_USERINFO )
{
char *info = Cvar_InfoString( CVAR_USERINFO );
char cleanName[MAX_NAME_LENGTH];
if( Q_CleanPlayerName( Info_ValueForKey( info, "name" ), cleanName, sizeof( cleanName ) ) )
Info_SetValueForKey( info, "name", cleanName );
CL_AddReliableCommand( va("userinfo \"%s\"", info ) );
cvar_modifiedFlags &= ~CVAR_USERINFO;
}
}
/*
==================
CL_Frame
==================
*/
void CL_Frame ( int msec ) {
if ( !com_cl_running->integer ) {
return;
}
if ( uivm ) {
if ( cls.cddialog ) {
// bring up the cd error dialog if needed
cls.cddialog = qfalse;
VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_NEED_CD );
} else if ( cls.state == CA_DISCONNECTED && !( cls.keyCatchers & KEYCATCH_UI )
&& !com_sv_running->integer ) {
// if disconnected, bring up the menu
S_StopAllSounds();
VM_Call( uivm, UI_SET_ACTIVE_MENU, UIMENU_MAIN );
}
}
// if recording an avi, lock to a fixed fps
if ( cl_avidemo->integer && msec) {
// save the current screen
if ( cls.state == CA_ACTIVE || cl_forceavidemo->integer) {
Cbuf_ExecuteText( EXEC_NOW, "screenshot silent\n" );
}
// fixed time for next frame'
msec = (1000 / cl_avidemo->integer) * com_timescale->value;
if (msec == 0) {
msec = 1;
}
}
// save the msec before checking pause
cls.realFrametime = msec;
// decide the simulation time
cls.frametime = msec;
cls.realtime += cls.frametime;
if ( cl_timegraph->integer ) {
SCR_DebugGraph ( cls.realFrametime * 0.25, 0 );
}
// see if we need to update any userinfo
CL_CheckUserinfo();
// if we haven't gotten a packet in a long time,
// drop the connection
CL_CheckTimeout();
// send intentions now
CL_SendCmd();
// resend a connection request if necessary
CL_CheckForResend();
// decide on the serverTime to render
CL_SetCGameTime();
// update the screen
SCR_UpdateScreen();
// update audio
S_Update();
// advance local effects for next frame
SCR_RunCinematic();
Con_RunConsole();
cls.framecount++;
}
//============================================================================
/*
================
CL_RefPrintf
DLL glue
================
*/
void Q_EXTERNAL_CALL_VA CL_RefPrintf( int print_level, const char *fmt, ... )
{
va_list argptr;
char msg[MAXPRINTMSG];
va_start (argptr,fmt);
Q_vsnprintf( msg, sizeof( msg ) - 1, fmt, argptr );
msg[sizeof( msg ) - 1] = 0;
va_end (argptr);
if ( print_level == PRINT_ALL ) {
Com_Printf ("%s", msg);
} else if ( print_level == PRINT_WARNING ) {
Com_Printf (S_COLOR_YELLOW "%s", msg); // yellow
} else if ( print_level == PRINT_DEVELOPER ) {
Com_DPrintf (S_COLOR_RED "%s", msg); // red
}
}
/*
============
CL_ShutdownRef
============
*/
void CL_ShutdownRef( void ) {
if( !re.Shutdown )
return;
re.Shutdown( qtrue );
cls.rendererStarted = qfalse;
Com_Memset( &re, 0, sizeof( re ) );
}
/*
============
CL_InitRenderer
============
*/
void CL_InitRenderer( void )
{
if( cls.rendererStarted )
return;
cls.rendererStarted = qtrue;
// this sets up the renderer and calls R_Init
re.BeginRegistration( &cls.glconfig );
Con_Resize( 0, -1, 0, 0, 0, 0 );
Con_Resize( 1, -1, 0, 0, 0, 0 );
// load character sets
cls.whiteShader = re.RegisterShader( "*white" );
cls.menu = re.RegisterShader( "ui/assets/menubrushed" );
cls.font = re.RegisterFont( "profont" );
#ifdef USE_CALLHOME
HTTP_PostUrl( "http://www.playspacetrader.com/user/log", 0, 0,
"message="
"[%s] %s(%s)\n"
, Cvar_VariableString( "sys_osstring" )
, cls.glconfig.renderer_string
, cls.glconfig.version_string );
#endif
}
/*
============================
CL_StartHunkUsers
After the server has cleared the hunk, these will need to be restarted
This is the only place that any of these functions are called from
============================
*/
void CL_StartHunkUsers( void ) {
if (!com_cl_running) {
return;
}
if ( !com_cl_running->integer ) {
return;
}
if( !cls.rendererStarted )
{
CL_InitRenderer();
cls.rendererStarted = qtrue;
}
if ( !cls.soundStarted ) {
cls.soundStarted = qtrue;
S_Init();
}
if ( !cls.soundRegistered ) {
cls.soundRegistered = qtrue;
S_BeginRegistration();
}
if ( !cls.uiStarted ) {
cls.uiStarted = qtrue;
CL_InitUI();
}
}
/*
============
CL_RefMalloc
============
*/
void* Q_EXTERNAL_CALL CL_RefMalloc( int size )
{
return Z_TagMalloc( size, TAG_RENDERER );
}
int Q_EXTERNAL_CALL CL_ScaledMilliseconds( void )
{
return Sys_Milliseconds()*com_timescale->value;
}
// be_aas_main.c
void Q_EXTERNAL_CALL AAS_DrawDebugSurface( void (Q_EXTERNAL_CALL *drawPoly)( int color, int numPoints, float *points ) );
/*
============
CL_InitRef
============
*/
void CL_InitRef( void )
{
#ifdef DEVELOPER
intptr_t (QDECL *entryPoint)( int callNum, ... ) = 0;
char dllPath[ MAX_QPATH ];
#endif
refimport_t ri;
Com_Printf( "----- Initializing Renderer ----\n" );
#ifdef DEVELOPER
Sys_LoadDll( com_renderer->string, dllPath, &entryPoint, 0 );
#endif
ri.Cmd_AddCommand = Cmd_AddCommand;
ri.Cmd_RemoveCommand = Cmd_RemoveCommand;
ri.Cmd_Argc = Cmd_Argc;
ri.Cmd_Argv = Cmd_Argv;
ri.Cmd_ExecuteText = Cbuf_ExecuteText;
ri.Printf = CL_RefPrintf;
ri.Error = Com_Error;
ri.Milliseconds = CL_ScaledMilliseconds;
ri.Malloc = CL_RefMalloc;
ri.Free = Z_Free;
#ifdef HUNK_DEBUG
ri.Hunk_AllocDebug = Hunk_AllocDebug;
#else
ri.Hunk_Alloc = Hunk_Alloc;
#endif
ri.Hunk_AllocateTempMemory = Hunk_AllocateTempMemory;
ri.Hunk_FreeTempMemory = Hunk_FreeTempMemory;
ri.Hunk_AllocateFrameMemory = Hunk_FrameAlloc;
ri.CM_DrawDebugSurface = CM_DrawDebugSurface;
ri.AAS_DrawDebugSurface = AAS_DrawDebugSurface;
ri.FS_ReadFile = FS_ReadFile;
ri.FS_FreeFile = FS_FreeFile;
ri.FS_WriteFile = FS_WriteFile;
ri.FS_FOpenFile = FS_FOpenFileByMode;
ri.FS_Read = FS_Read;
ri.FS_Write = FS_Write;
ri.FS_Seek = FS_Seek;
ri.FS_FCloseFile = FS_FCloseFile;
ri.FS_FreeFileList = FS_FreeFileList;
ri.FS_ListFiles = FS_ListFiles;
ri.FS_FileExists = FS_FileExists;
ri.Cvar_Get = Cvar_Get;
ri.Cvar_Set = Cvar_Set;
// cinematic stuff
ri.CIN_UploadCinematic = CIN_UploadCinematic;
ri.CIN_PlayCinematic = CIN_PlayCinematic;
ri.CIN_RunCinematic = CIN_RunCinematic;
ri.CM_ClusterPVS = CM_ClusterPVS;
ri.PlatformGetVars = Sys_PlatformGetVars;
ri.Sys_LowPhysicalMemory = Sys_LowPhysicalMemory;
ri.Con_GetText = Con_GetText;
#ifdef DEVELOPER
if( entryPoint )
{
if ( !entryPoint( 1, REF_API_VERSION, &ri, &re ) )
Com_Error (ERR_FATAL, "Couldn't initialize refresh" );
}
else
#endif
{
RE_Startup( REF_API_VERSION, &re, &ri );
}
Com_Printf( "-------------------------------\n");
// unpause so the cgame definately gets a snapshot and renders a frame
Cvar_Set( "cl_paused", "0" );
}
//===========================================================================================
void Q_EXTERNAL_CALL CL_SetModel_f( void )
{
char *arg;
char name[256];
arg = Cmd_Argv( 1 );
if (arg[0]) {
Cvar_Set( "model", arg );
Cvar_Set( "headmodel", arg );
} else {
Cvar_VariableStringBuffer( "model", name, sizeof(name) );
Com_Printf("model is set to %s\n", name);
}
}
#ifdef DEVELOPER
static void Q_EXTERNAL_CALL CL_sql_f( void )
{
sql_prompt( &cl.db, Cmd_Argv(1) );
}
#endif
static void Q_EXTERNAL_CALL CL_sql_export_f( void ) {
if ( Cmd_Argc() == 1 ) {
sql_prepare( &cl.db, "SELECT value FROM missions SEARCH key 'db_name';" );
if ( sql_step( &cl.db ) ) {
sql_export( sql_columnastext( &cl.db, 0) );
}
sql_done( &cl.db);
} else {
sql_export( Cmd_Argv(1) );
}
}
#ifdef DEVELOPER
static void Q_EXTERNAL_CALL CL_sql_save_f( void ) {
if ( Cmd_Argc() == 1 ) {
sql_prepare( &cl.db, "SELECT value FROM missions SEARCH key 'db_name';" );
if ( sql_step( &cl.db ) ) {
sql_save( sql_columnastext( &cl.db, 0) );
}
sql_done( &cl.db);
} else {
sql_save( Cmd_Argv(1) );
}
}
#endif
/*
====================
CL_Init
====================
*/
void CL_Init( void ) {
Com_Printf( "----- Client Initialization -----\n" );
Con_Init ();
CL_ClearState ();
cls.state = CA_DISCONNECTED; // no longer CA_UNINITIALIZED
cls.realtime = 0;
CL_InitInput ();
//
// register our variables
//
cl_noprint = Cvar_Get( "cl_noprint", "0", 0 );
cl_motd = Cvar_Get ("cl_motd", "1", 0);
cl_timeout = Cvar_Get( "cl_timeout", "200", 0 );
cl_timeNudge = Cvar_Get ("cl_timeNudge", "30", CVAR_TEMP );
cl_shownet = Cvar_Get ("cl_shownet", "0", CVAR_TEMP );
cl_showSend = Cvar_Get ("cl_showSend", "0", CVAR_TEMP );
cl_showTimeDelta = Cvar_Get ("cl_showTimeDelta", "0", CVAR_TEMP );
cl_freezeDemo = Cvar_Get ("cl_freezeDemo", "0", CVAR_TEMP );
rcon_client_password = Cvar_Get ("rconPassword", "", CVAR_TEMP );
cl_activeAction = Cvar_Get( "activeAction", "", CVAR_TEMP );
cl_timedemo = Cvar_Get ("timedemo", "0", 0);
cl_avidemo = Cvar_Get ("cl_avidemo", "0", 0);
cl_forceavidemo = Cvar_Get ("cl_forceavidemo", "0", 0);
rconAddress = Cvar_Get ("rconAddress", "", 0);
cl_yawspeed = Cvar_Get ("cl_yawspeed", "140", CVAR_ARCHIVE);
cl_pitchspeed = Cvar_Get ("cl_pitchspeed", "140", CVAR_ARCHIVE);
cl_anglespeedkey = Cvar_Get ("cl_anglespeedkey", "1.5", 0);
cl_maxpackets = Cvar_Get ("cl_maxpackets", "30", CVAR_ARCHIVE );
cl_packetdup = Cvar_Get ("cl_packetdup", "1", CVAR_ARCHIVE );
cl_run = Cvar_Get ("cl_run", "1", CVAR_ARCHIVE);
cl_sensitivity = Cvar_Get ("sensitivity", "5", CVAR_ARCHIVE);
cl_mouseAccel = Cvar_Get ("cl_mouseAccel", "0", CVAR_ARCHIVE);
cl_freelook = Cvar_Get( "cl_freelook", "1", CVAR_ARCHIVE );
cl_showMouseRate = Cvar_Get ("cl_showmouserate", "0", 0);
cl_conXOffset = Cvar_Get ("cl_conXOffset", "0", 0);
cl_conYOffset = Cvar_Get ("cl_conYOffset", "0", 0);
cl_conWidth = Cvar_Get ("cl_conWidth", "640", 0);
cl_conHeight = Cvar_Get ("cl_conHeight", "32", 0);
#ifdef MACOS_X
// In game video is REALLY slow in Mac OS X right now due to driver slowness
cl_inGameVideo = Cvar_Get ("r_inGameVideo", "0", CVAR_ARCHIVE);
#else
cl_inGameVideo = Cvar_Get ("r_inGameVideo", "1", CVAR_ARCHIVE);
#endif
cl_serverStatusResendTime = Cvar_Get ("cl_serverStatusResendTime", "750", 0);
#ifdef USE_WEBAUTH
cl_authserver = Cvar_Get( "cl_authserver", "www.playspacetrader.com", CVAR_INIT );
#endif
cl_ircport = Cvar_Get( "cl_ircport", "27399", CVAR_INIT );
cl_lang = Cvar_Get( "cl_lang", "en-US", CVAR_ARCHIVE | CVAR_LATCH );
// init autoswitch so the ui will have it correctly even
// if the cgame hasn't been started
Cvar_Get ("cg_autoswitch", "1", CVAR_ARCHIVE);
#ifdef USE_WEBAUTH
// Initialize ui_logged_in to -1(not logged in)
// -2 - login in progress
// 0 - invalid login
// 1 - logged in
Cvar_Get ("ui_logged_in", "-1", CVAR_ROM);
//force ui_logged in to -1 so we can't reset it on the command line
Cvar_Set( "ui_logged_in", "-1" );
#endif
m_pitch = Cvar_Get ("m_pitch", "0.022", CVAR_ARCHIVE);
m_yaw = Cvar_Get ("m_yaw", "0.022", CVAR_ARCHIVE);
m_forward = Cvar_Get ("m_forward", "0.25", CVAR_ARCHIVE);
m_side = Cvar_Get ("m_side", "0.25", CVAR_ARCHIVE);
#ifdef MACOS_X
// Input is jittery on OS X w/o this
m_filter = Cvar_Get ("m_filter", "1", CVAR_ARCHIVE);
#else
m_filter = Cvar_Get ("m_filter", "0", CVAR_ARCHIVE);
#endif
cl_motdString = Cvar_Get( "cl_motdString", "", CVAR_ROM );
Cvar_Get( "cl_maxPing", "800", CVAR_ARCHIVE );
// userinfo
Cvar_Get ("name", "UnnamedPlayer", CVAR_USERINFO | CVAR_ARCHIVE );
Cvar_Get ("rate", "10000", CVAR_USERINFO | CVAR_ARCHIVE );
Cvar_Get ("snaps", "20", CVAR_USERINFO | CVAR_ARCHIVE );
Cvar_Get ("model", "mpc1", CVAR_USERINFO | CVAR_ARCHIVE );
Cvar_Get ("headmodel", "mpc1", CVAR_USERINFO | CVAR_ARCHIVE );
Cvar_Get ("color1", "4", CVAR_USERINFO | CVAR_ARCHIVE );
Cvar_Get ("color2", "5", CVAR_USERINFO | CVAR_ARCHIVE );
Cvar_Get ("handicap", "100", CVAR_USERINFO | CVAR_ARCHIVE );
Cvar_Get ("teamtask", "0", CVAR_USERINFO );
Cvar_Get ("sex", "male", CVAR_USERINFO | CVAR_ARCHIVE );
Cvar_Get ("cl_anonymous", "0", CVAR_USERINFO | CVAR_ARCHIVE );
Cvar_Get ("password", "", CVAR_USERINFO);
Cvar_Get ("cg_predictItems", "1", CVAR_USERINFO | CVAR_ARCHIVE );
Cvar_Get ("slot", "0", CVAR_USERINFO );
// cgame might not be initialized before menu is used
Cvar_Get ("cg_viewsize", "100", CVAR_ARCHIVE );
//
// register our commands
//
Cmd_AddCommand ("cmd", CL_ForwardToServer_f);
Cmd_AddCommand ("configstrings", CL_Configstrings_f);
Cmd_AddCommand ("clientinfo", CL_Clientinfo_f);
Cmd_AddCommand ("snd_restart", CL_Snd_Restart_f);
Cmd_AddCommand ("vid_restart", CL_Vid_Restart_f);
Cmd_AddCommand ("disconnect", CL_Disconnect_f);
Cmd_AddCommand ("record", CL_Record_f);
Cmd_AddCommand ("demo", CL_PlayDemo_f);
Cmd_AddCommand ("cinematic", CL_PlayCinematic_f);
Cmd_AddCommand ("stoprecord", CL_StopRecord_f);
Cmd_AddCommand ("connect", CL_Connect_f);
Cmd_AddCommand ("reconnect", CL_Reconnect_f);
Cmd_AddCommand ("localservers", CL_LocalServers_f);
#ifdef USE_WEBAUTH
Cmd_AddCommand ("login", CL_Login_f);
Cmd_AddCommand ("highscore", CL_Highscore_f);
Cmd_AddCommand ("forgotpassword", CL_ForgotPassword_f);
Cmd_AddCommand ("createcharacter", CL_CreateCharacter_f);
Cmd_AddCommand ("deletecharacter", CL_DeleteCharacter_f);
Cmd_AddCommand ("getaccount", CL_GetAccount_f);
Cmd_AddCommand ("globalhighscores", CL_GlobalHighScores_f);
#endif
Cmd_AddCommand ("updateserver", CL_UpdateServer_f);
Cmd_AddCommand ("getservers", CL_GetServers_f);
Cmd_AddCommand ("openurl", CL_OpenUrl_f );
#ifdef USE_AUTOPATCH
Cmd_AddCommand ("check_updates", CL_CheckVersion_f);
#endif
#ifdef USE_IRC
Cmd_AddCommand ("irc", CL_Irc_f);
#endif
// Cmd_AddCommand ("getauth", CL_RequestAuthorization);
Cmd_AddCommand ("rcon", CL_Rcon_f);
Cmd_AddCommand ("setenv", CL_Setenv_f );
Cmd_AddCommand ("ping", CL_Ping_f );
Cmd_AddCommand ("serverstatus", CL_ServerStatus_f );
Cmd_AddCommand ("showip", CL_ShowIP_f );
Cmd_AddCommand ("model", CL_SetModel_f );
Cmd_AddCommand ("dumpreferences", CL_DumpMapReferences );
#ifdef DEVELOPER
Cmd_AddCommand ("sql_c", CL_sql_f );
Cmd_AddCommand ("sql_save", CL_sql_save_f );
#endif
Cmd_AddCommand ("sql_export", CL_sql_export_f );
CL_InitRef();
SCR_Init ();
Cbuf_Execute ();
Cvar_Set( "cl_running", "1" );
Com_Printf( "language: %s\n", cl_lang->string );
Com_Printf( "----- Client Initialization Complete -----\n" );
}
/*
===============
CL_Shutdown
===============
*/
void CL_Shutdown( void ) {
static qboolean recursive = qfalse;
Com_Printf( "----- CL_Shutdown -----\n" );
if ( recursive ) {
printf ("recursive shutdown\n");
return;
}
recursive = qtrue;
CL_Disconnect( qtrue );
CL_ShutdownRef();
CL_ShutdownUI( qfalse );
S_Shutdown();
Cmd_RemoveCommand ("cmd");
Cmd_RemoveCommand ("configstrings");
Cmd_RemoveCommand ("userinfo");
Cmd_RemoveCommand ("snd_restart");
Cmd_RemoveCommand ("vid_restart");
Cmd_RemoveCommand ("disconnect");
Cmd_RemoveCommand ("record");
Cmd_RemoveCommand ("demo");
Cmd_RemoveCommand ("cinematic");
Cmd_RemoveCommand ("stoprecord");
Cmd_RemoveCommand ("connect");
Cmd_RemoveCommand ("localservers");
Cmd_RemoveCommand ("globalservers");
Cmd_RemoveCommand ("rcon");
Cmd_RemoveCommand ("setenv");
Cmd_RemoveCommand ("ping");
Cmd_RemoveCommand ("serverstatus");
Cmd_RemoveCommand ("showip");
Cmd_RemoveCommand ("model");
Cvar_Set( "cl_running", "0" );
recursive = qfalse;
Com_Memset( &cls, 0, sizeof( cls ) );
Com_Printf( "-----------------------\n" );
}
void CL_ServerAddToRefreshList( const char * info, const char * channel ) {
char * addr = Info_ValueForKey( info, "addr" );
netadr_t to;
Com_Memset( &to, 0, sizeof(netadr_t) );
if ( !NET_StringToAdr( addr, &to ) ) {
return;
}
NET_OutOfBandPrint( NS_CLIENT, to, "getinfo xxx" );
// update server's info
sql_prepare ( &cl.db, "UPDATE OR INSERT servers CS $1 SEARCH addr $2;" );
sql_bindtext( &cl.db, 1, va("%s\\channel\\%s", info, channel) );
sql_bindtext( &cl.db, 2, addr );
sql_step ( &cl.db );
sql_done ( &cl.db );
}
/*
===================
CL_ServerInfoPacket
===================
*/
void CL_ServerInfoPacket( netadr_t from, msg_t *msg ) {
const char * info = MSG_ReadString( msg );
const char * addr = NET_AdrToString( from );
// if this isn't the correct protocol version, ignore it
if ( Q_stricmp( Info_ValueForKey( info, "protocol" ), PROTOCOL_VERSION ) ) {
Com_DPrintf( "Different protocol info packet: %s\n", info );
return;
}
// attempt to update the server
sql_prepare ( &cl.db, "UPDATE servers SET ping=IF (start>0 ) THEN max(1,?2-start) ELSE ping,start=0,lastupdate=?2 SEARCH addr $1;" );
sql_bindtext( &cl.db, 1, addr );
sql_bindint ( &cl.db, 2, cls.realtime );
sql_step ( &cl.db );
if ( sql_done ( &cl.db ) == 0 ) {
// new server acquired from a broadcast...
sql_prepare ( &cl.db, "INSERT INTO servers(addr,ping,lastupdate) VALUES($1,?2,?3);" );
sql_bindtext( &cl.db, 1, addr );
sql_bindint ( &cl.db, 2, cls.realtime - cls.broadcastTime );
sql_bindint ( &cl.db, 3, cls.realtime );
sql_step ( &cl.db );
sql_done ( &cl.db );
}
// update server's info
sql_prepare ( &cl.db, "UPDATE servers CS $1 SEARCH addr $2;" );
sql_bindtext( &cl.db, 1, info );
sql_bindtext( &cl.db, 2, addr );
sql_step ( &cl.db );
sql_done ( &cl.db );
}
/*
===================
CL_GetServerStatus
===================
*/
serverStatus_t *CL_GetServerStatus( netadr_t from ) {
serverStatus_t *serverStatus;
int i, oldest, oldestTime;
serverStatus = NULL;
for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) {
if ( NET_CompareAdr( from, cl_serverStatusList[i].address ) ) {
return &cl_serverStatusList[i];
}
}
for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) {
if ( cl_serverStatusList[i].retrieved ) {
return &cl_serverStatusList[i];
}
}
oldest = -1;
oldestTime = 0;
for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) {
if (oldest == -1 || cl_serverStatusList[i].startTime < oldestTime) {
oldest = i;
oldestTime = cl_serverStatusList[i].startTime;
}
}
if (oldest != -1) {
return &cl_serverStatusList[oldest];
}
serverStatusCount++;
return &cl_serverStatusList[serverStatusCount & (MAX_SERVERSTATUSREQUESTS-1)];
}
/*
===================
CL_ServerStatus
===================
*/
int CL_ServerStatus( char *serverAddress, char *serverStatusString, int maxLen ) {
int i;
netadr_t to;
serverStatus_t *serverStatus;
// if no server address then reset all server status requests
if ( !serverAddress ) {
for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) {
cl_serverStatusList[i].address.port = 0;
cl_serverStatusList[i].retrieved = qtrue;
}
return qfalse;
}
// get the address
if ( !NET_StringToAdr( serverAddress, &to ) ) {
return qfalse;
}
serverStatus = CL_GetServerStatus( to );
// if no server status string then reset the server status request for this address
if ( !serverStatusString ) {
serverStatus->retrieved = qtrue;
return qfalse;
}
// if this server status request has the same address
if ( NET_CompareAdr( to, serverStatus->address) ) {
// if we recieved an response for this server status request
if (!serverStatus->pending) {
Q_strncpyz(serverStatusString, serverStatus->string, maxLen);
serverStatus->retrieved = qtrue;
serverStatus->startTime = 0;
return qtrue;
}
// resend the request regularly
else if ( serverStatus->startTime < Com_Milliseconds() - cl_serverStatusResendTime->integer ) {
serverStatus->print = qfalse;
serverStatus->pending = qtrue;
serverStatus->retrieved = qfalse;
serverStatus->time = 0;
serverStatus->startTime = Com_Milliseconds();
NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" );
return qfalse;
}
}
// if retrieved
else if ( serverStatus->retrieved ) {
serverStatus->address = to;
serverStatus->print = qfalse;
serverStatus->pending = qtrue;
serverStatus->retrieved = qfalse;
serverStatus->startTime = Com_Milliseconds();
serverStatus->time = 0;
NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" );
return qfalse;
}
return qfalse;
}
/*
===================
CL_ServerStatusResponse
===================
*/
void CL_ServerStatusResponse( netadr_t from, msg_t *msg ) {
char *s;
char info[MAX_INFO_STRING];
int i, l, score, ping;
int len;
serverStatus_t *serverStatus;
serverStatus = NULL;
for (i = 0; i < MAX_SERVERSTATUSREQUESTS; i++) {
if ( NET_CompareAdr( from, cl_serverStatusList[i].address ) ) {
serverStatus = &cl_serverStatusList[i];
break;
}
}
// if we didn't request this server status
if (!serverStatus) {
return;
}
s = MSG_ReadStringLine( msg );
len = 0;
Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "%s", s);
if (serverStatus->print) {
Com_Printf("Server settings:\n");
// print cvars
while (*s) {
for (i = 0; i < 2 && *s; i++) {
if (*s == '\\')
s++;
l = 0;
while (*s) {
info[l++] = *s;
if (l >= MAX_INFO_STRING-1)
break;
s++;
if (*s == '\\') {
break;
}
}
info[l] = '\0';
if (i) {
Com_Printf("%s\n", info);
}
else {
Com_Printf("%-24s", info);
}
}
}
}
len = strlen(serverStatus->string);
Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\");
if (serverStatus->print) {
Com_Printf("\nPlayers:\n");
Com_Printf("num: score: ping: name:\n");
}
for (i = 0, s = MSG_ReadStringLine( msg ); *s; s = MSG_ReadStringLine( msg ), i++) {
len = strlen(serverStatus->string);
Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\%s", s);
if (serverStatus->print) {
score = ping = 0;
sscanf(s, "%d %d", &score, &ping);
s = strchr(s, ' ');
if (s)
s = strchr(s+1, ' ');
if (s)
s++;
else
s = "unknown";
Com_Printf("%-2d %-3d %-3d %s\n", i, score, ping, s );
}
}
len = strlen(serverStatus->string);
Com_sprintf(&serverStatus->string[len], sizeof(serverStatus->string)-len, "\\");
serverStatus->time = Com_Milliseconds();
serverStatus->address = from;
serverStatus->pending = qfalse;
if (serverStatus->print) {
serverStatus->retrieved = qtrue;
}
}
/*
==================
CL_LocalServers_f
==================
*/
void Q_EXTERNAL_CALL CL_LocalServers_f( void )
{
char *message;
int i, j;
netadr_t to;
cls.broadcastTime = cls.realtime;
Com_Printf( "Scanning for servers on the local network...\n");
Com_Memset( &to, 0, sizeof( to ) );
// The 'xxx' in the message is a challenge that will be echoed back
// by the server. We don't care about that here, but master servers
// can use that to prevent spoofed server responses from invalid ip
message = "\377\377\377\377getinfo xxx";
// send each message twice in case one is dropped
for ( i = 0 ; i < 2 ; i++ ) {
// send a broadcast packet on each server port
// we support multiple server ports so a single machine
// can nicely run multiple servers
for ( j = 0 ; j < NUM_SERVER_PORTS ; j++ ) {
to.port = BigShort( (short)(PORT_SERVER + j) );
to.type = NA_BROADCAST;
NET_SendPacket( NS_CLIENT, strlen( message ), message, to );
to.type = NA_BROADCAST_IPX;
NET_SendPacket( NS_CLIENT, strlen( message ), message, to );
}
}
}
#ifdef USE_IRC
/*
==================
CL_Irc_f
==================
*/
void Q_EXTERNAL_CALL CL_Irc_f ( void )
{
Net_IRC_SendMessage(Cmd_Argv(1));
}
#endif
/*
==================
CL_Ping_f
==================
*/
void Q_EXTERNAL_CALL CL_Ping_f( void )
{
netadr_t to;
char* server;
if ( Cmd_Argc() != 2 ) {
Com_Printf( "usage: ping [server]\n");
return;
}
Com_Memset( &to, 0, sizeof(netadr_t) );
server = Cmd_Argv(1);
if ( !NET_StringToAdr( server, &to ) ) {
return;
}
NET_OutOfBandPrint( NS_CLIENT, to, "getinfo xxx" );
sql_prepare ( &cl.db, "UPDATE OR INSERT servers SET addr=$1,start=?2 SEARCH addr $1" );
sql_bindtext( &cl.db, 1, server );
sql_bindint ( &cl.db, 2, cls.realtime );
sql_step ( &cl.db );
sql_done ( &cl.db );
}
/*
==================
CL_UpdateVisiblePings_f
==================
*/
qboolean Q_EXTERNAL_CALL CL_UpdateVisiblePings_f( int source )
{
// spam servers that haven't responded in a while
sql_prepare ( &cl.db, "SELECT addr FROM servers WHERE lastupdate < ?1-1000" );
sql_bindint ( &cl.db, 1, cls.realtime );
while( sql_step( &cl.db ) ){
netadr_t adr;
NET_StringToAdr(sql_columnastext( &cl.db, 0 ), &adr);
NET_OutOfBandPrint( NS_CLIENT, adr, "getinfo xxx" );
}
sql_done ( &cl.db );
sql_prepare ( &cl.db, "UPDATE servers SET start=?1 WHERE lastupdate < ?1-1000" );
sql_bindint ( &cl.db, 1, cls.realtime );
sql_step ( &cl.db );
sql_done ( &cl.db );
return qtrue;
}
/*
==================
CL_ServerStatus_f
==================
*/
void Q_EXTERNAL_CALL CL_ServerStatus_f( void )
{
netadr_t to;
char *server;
serverStatus_t *serverStatus;
Com_Memset( &to, 0, sizeof(netadr_t) );
if ( Cmd_Argc() != 2 ) {
if ( cls.state != CA_ACTIVE || clc.demoplaying ) {
Com_Printf ("Not connected to a server.\n");
Com_Printf( "Usage: serverstatus [server]\n");
return;
}
server = cls.servername;
}
else {
server = Cmd_Argv(1);
}
if ( !NET_StringToAdr( server, &to ) ) {
return;
}
NET_OutOfBandPrint( NS_CLIENT, to, "getstatus" );
serverStatus = CL_GetServerStatus( to );
serverStatus->address = to;
serverStatus->print = qtrue;
serverStatus->pending = qtrue;
}
/*
==================
CL_ShowIP_f
==================
*/
void Q_EXTERNAL_CALL CL_ShowIP_f( void )
{
Sys_ShowIP();
}