/*
Copyright (C) 1997-2001 Id Software, Inc.

This program 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.

This program 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 this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/ 
// cl_main.c  -- client main loop

#include "client.h"

cvar_t	*freelook;

cvar_t	*adr0;
cvar_t	*adr1;
cvar_t	*adr2;
cvar_t	*adr3;
cvar_t	*adr4;
cvar_t	*adr5;
cvar_t	*adr6;
cvar_t	*adr7;
cvar_t	*adr8;

cvar_t	*cl_stereo_separation;
cvar_t	*cl_stereo;

extern cvar_t	*rcon_password;
cvar_t	*rcon_address;

cvar_t	*cl_noskins;
cvar_t	*cl_autoskins;
cvar_t	*cl_footsteps;
cvar_t	*cl_timeout;
cvar_t	*cl_predict;
cvar_t	*cl_gun;
cvar_t	*cl_maxfps;
cvar_t	*cl_async;
cvar_t	*r_maxfps;

cvar_t	*cl_add_particles;
cvar_t	*cl_add_lights;
cvar_t	*cl_add_entities;
cvar_t	*cl_add_blend;
cvar_t	*cl_kickangles;

cvar_t	*cl_shownet;
cvar_t	*cl_showmiss;
cvar_t	*cl_showclamp;

cvar_t	*lookspring;
cvar_t	*lookstrafe;
cvar_t	*sensitivity;

cvar_t	*m_pitch;
cvar_t	*m_yaw;
cvar_t	*m_forward;
cvar_t	*m_side;

cvar_t	*cl_thirdperson;
cvar_t	*cl_thirdperson_angle;
cvar_t	*cl_thirdperson_range;

cvar_t *cl_railtrail_type;
cvar_t *cl_railtrail_time;
cvar_t *cl_railtrail_alpha;
cvar_t *cl_railcore_color;
cvar_t *cl_railcore_width;
cvar_t *cl_railrings_color;
cvar_t *cl_railrings_width;

cvar_t	*cl_disable_particles;
cvar_t	*cl_disable_explosions;
cvar_t	*cl_chat_notify;
cvar_t	*cl_chat_beep;

cvar_t *cl_gibs;

cvar_t *cl_protocol;

cvar_t	*gender_auto;

cvar_t	*cl_vwep;

client_static_t	cls;
client_state_t	cl;

clientAPI_t		client;

centity_t	cl_entities[ MAX_EDICTS ];

qboolean CL_SendStatusRequest( char *buffer, int bufferSize );

//======================================================================

typedef enum {
	REQ_FREE,
	REQ_STATUS,
	REQ_INFO,
	REQ_PING,
	REQ_RCON
} requestType_t;

typedef struct {
	requestType_t type;
	netadr_t adr;
	int time;
} request_t;

#define MAX_REQUESTS	32
#define REQUEST_MASK	( MAX_REQUESTS - 1 )

static request_t	clientRequests[MAX_REQUESTS];
static int			currentRequest;

static request_t *CL_AddRequest( netadr_t *adr, requestType_t type ) {
	request_t *r;

	r = &clientRequests[currentRequest & REQUEST_MASK];
	currentRequest++;

	r->adr = *adr;
	r->type = type;
	if( adr->type == NA_BROADCAST ) {
		r->time = cls.realtime + 3000;
	} else {
		r->time = cls.realtime + 6000;
	}

	return r;
}

/*
===================
CL_UpdateGunSetting
===================
*/
static void CL_UpdateGunSetting( void ) {
    int nogun;

    if ( cls.state < ca_connected || cls.state > ca_active ) {
        return;
    }

    if ( cls.serverProtocol < PROTOCOL_VERSION_R1Q2 ) {
        return;
    }

	if( cl_gun->integer == -1 ) {
		nogun = 2;
	} else if( cl_gun->integer == 0 || info_hand->integer == 2 ) {
		nogun = 1;
	} else {
		nogun = 0;
	}

    MSG_WriteByte( clc_setting );
    MSG_WriteShort( CLS_NOGUN );
    MSG_WriteShort( nogun );
    MSG_FlushTo( &cls.netchan->message );
}

/*
===================
CL_UpdateGibSetting
===================
*/
static void CL_UpdateGibSetting( void ) {
    if ( cls.state < ca_connected || cls.state > ca_active ) {
        return;
    }

    if ( cls.serverProtocol != PROTOCOL_VERSION_Q2PRO ) {
        return;
    }

    MSG_WriteByte( clc_setting );
    MSG_WriteShort( CLS_NOGIBS );
    MSG_WriteShort( !cl_gibs->integer );
    MSG_FlushTo( &cls.netchan->message );
}

/*
===================
CL_UpdateFootstepsSetting
===================
*/
static void CL_UpdateFootstepsSetting( void ) {
    if ( cls.state < ca_connected || cls.state > ca_active ) {
        return;
    }

    if ( cls.serverProtocol != PROTOCOL_VERSION_Q2PRO ) {
        return;
    }

    MSG_WriteByte( clc_setting );
    MSG_WriteShort( CLS_NOFOOTSTEPS );
    MSG_WriteShort( !cl_footsteps->integer );
    MSG_FlushTo( &cls.netchan->message );
}

/*
===================
CL_UpdatePredictSetting
===================
*/
static void CL_UpdatePredictSetting( void ) {
    if ( cls.state < ca_connected || cls.state > ca_active ) {
        return;
    }

    if ( cls.serverProtocol != PROTOCOL_VERSION_Q2PRO ) {
        return;
    }

    MSG_WriteByte( clc_setting );
    MSG_WriteShort( CLS_NOPREDICT );
    MSG_WriteShort( !cl_predict->integer );
    MSG_FlushTo( &cls.netchan->message );
}

/*
===================
CL_UpdateLocalFovSetting
===================
*/
void CL_UpdateLocalFovSetting( void ) {
    if ( cls.state < ca_connected || cls.state > ca_active ) {
        return;
    }

    if ( cls.serverProtocol != PROTOCOL_VERSION_Q2PRO ) {
        return;
    }

    MSG_WriteByte( clc_setting );
    MSG_WriteShort( CLS_LOCALFOV );
    MSG_WriteShort( cl_demo_local_fov->integer );
    MSG_FlushTo( &cls.netchan->message );
}

/*
===================
CL_ClientCommand
===================
*/
void CL_ClientCommand( const char *string ) {
    if ( !cls.netchan ) {
        return;
    }
    MSG_WriteByte( clc_stringcmd );
    MSG_WriteString( string );
    MSG_FlushTo( &cls.netchan->message );
}


/*
===================
Cmd_ForwardToServer
 
adds the current command line as a clc_stringcmd to the client message.
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 Cmd_ForwardToServer( void ) {
    char	* cmd;

    cmd = Cmd_Argv( 0 );
    if ( cls.state < ca_active || *cmd == '-' || *cmd == '+' ) {
        Com_Printf( "Unknown command \"%s\"\n", cmd );
        return;
    }

    if ( cls.demoplayback ) {
        return;
    }

    CL_ClientCommand( Cmd_RawArgsFrom( 0 ) );
}

/*
==================
CL_ForwardToServer_f
==================
*/
void CL_ForwardToServer_f( void ) {
    if ( cls.state < ca_connected ) {
        Com_Printf( "Can't \"%s\", not connected\n", Cmd_Argv( 0 ) );
        return;
    }

    if ( cls.demoplayback ) {
        return;
    }

    // don't forward the first argument
    if ( Cmd_Argc() > 1 ) {
        CL_ClientCommand( Cmd_RawArgs() );
    }
}

void CL_Setenv_f( void ) {
#ifndef _WIN32_WCE
    int argc = Cmd_Argc();

    if ( argc > 2 ) {
        char buffer[ MAX_STRING_CHARS ];

        Q_strncpyz( buffer, Cmd_Argv( 1 ), sizeof( buffer ) );
        Q_strcat( buffer, sizeof( buffer ), "=" );
		Q_strcat( buffer, sizeof( buffer ), Cmd_ArgsFrom( 2 ) );

        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 ) );
        }
    }
#endif
}

/*
==================
CL_Pause_f
==================
*/
void CL_Pause_f( void ) {
    Cvar_SetInteger( "cl_paused", !cl_paused->integer );
}

/*
=================
CL_CheckForResend
 
Resend a connect message if the last one has timed out
=================
*/
static void CL_CheckForResend( void ) {
	int ret;
    char tail[32];

    if ( cls.demoplayback ) {
        return;
    }

    // if the local server is running and we aren't
    // then connect
    if ( cls.state < ca_connecting && sv_running->integer > ss_loading ) {
        strcpy( cls.servername, "localhost" );
        NET_StringToAdr( cls.servername, &cls.serverAddress );
		if( sv_running->integer == ss_game || sv_running->integer == ss_broadcast ) {
			cls.serverProtocol = PROTOCOL_VERSION_Q2PRO;
		} else {
			cls.serverProtocol = PROTOCOL_VERSION_DEFAULT;
		}
    
        Com_DPrintf( "Going from ca_disconnected to ca_connecting\n" );

        // we don't need a challenge on the localhost
        cls.state = ca_connecting;
        cls.connect_time = -9999;
    }

    // resend if we haven't gotten a reply yet
    if ( cls.state != ca_connecting && cls.state != ca_challenging ) {
        return;
    }

    if ( cls.realtime - cls.connect_time < 3000 )
        return;

    cls.connect_time = cls.realtime;	// for retransmit requests

    cls.connectCount++;

    if ( cls.state == ca_challenging ) {
        Com_Printf( "Requesting challenge... %i\n", cls.connectCount );
        ret = Netchan_OutOfBandPrint( NS_CLIENT, &cls.serverAddress,
                "getchallenge\n" );
		if( ret == -1 ) {
			Com_Error( ERR_DISCONNECT, "%s to %s\n", Sys_NetErrorString(),
                    NET_AdrToString( &cls.serverAddress ) );
		}
        return;
    }

    //
    // We have gotten a challenge from the server, so try and connect.
    //
    Com_Printf( "Requesting connection... %i\n", cls.connectCount );

    cls.userinfo_modified = 0;

    cls.quakePort = net_qport->integer;
	if( cls.serverProtocol != PROTOCOL_VERSION_DEFAULT ) {
        Com_sprintf( tail, sizeof( tail ), " %d",
                net_maxmsglen->integer );
        if( cls.serverProtocol == PROTOCOL_VERSION_Q2PRO ) {
            strcat( tail, net_chantype->integer ? " 1" : " 0" );
        }
        cls.quakePort &= 0xFF;
	} else {
        tail[0] = 0;
	}
    ret = Netchan_OutOfBandPrint( NS_CLIENT, &cls.serverAddress,
        "connect %i %i %i \"%s\"%s\n",
        cls.serverProtocol,
        cls.quakePort,
        cls.challenge,
        Cvar_Userinfo(),
        tail );
	if( ret == -1 ) {
		Com_Error( ERR_DISCONNECT, "%s to %s\n", Sys_NetErrorString(),
                NET_AdrToString( &cls.serverAddress ) );
	}
}


/*
================
CL_Connect_f
 
================
*/
void CL_Connect_f( void ) {
    char	*server;
    netadr_t	address;
	int	protocol;

    if ( Cmd_Argc() < 2 ) {
usage:
        Com_Printf( "Usage: connect <server> [protocol]\n"
					"Protocol argument overrides cl_protocol setting\n"
					"Supported protocols: %d, %d and %d\n",
			PROTOCOL_VERSION_DEFAULT,
			PROTOCOL_VERSION_R1Q2,
			PROTOCOL_VERSION_Q2PRO );
        return;
    }

    server = Cmd_Argv( 1 );
	protocol = cl_protocol->integer;
	if( Cmd_Argc() > 2 ) {
		protocol = atoi( Cmd_Argv( 2 ) );
		if( protocol != PROTOCOL_VERSION_DEFAULT &&
			protocol != PROTOCOL_VERSION_R1Q2 &&
			protocol != PROTOCOL_VERSION_Q2PRO )
		{
			goto usage;
		}
	}

    if ( !NET_StringToAdr( server, &address ) ) {
        Com_Printf( "Bad server address\n" );
        return;
    }
    if ( address.port == 0 ) {
        address.port = BigShort( PORT_SERVER );
    }

    if ( sv_running->integer ) {
        // if running a local server, kill it and reissue
        SV_Shutdown( "Server was killed\n", KILL_DROP );
    }

	NET_Config( NET_CLIENT );

    CL_Disconnect( ERR_DISCONNECT, NULL );

    cls.serverAddress = address;
    cls.serverProtocol = protocol;
    Q_strncpyz( cls.servername, server, sizeof( cls.servername ) );

    Com_DPrintf( "Going from ca_disconnected to ca_challenging\n" );

    cls.state = ca_challenging;
    cls.connectCount = 0;
    cls.connect_time = -99999;	// CL_CheckForResend() will fire immediately

	CL_CheckForResend();

    Cvar_Set( "cl_paused", "0" );
	Cvar_Set( "timedemo", "0" );

    Con_Close();
    UI_OpenMenu( UIMENU_NONE );
}

static const char *CL_Connect_g( const char *partial, int state ) {
    static int length;
    static int index;
    const char *adrstring;
	char buffer[MAX_QPATH];
    
    if( !state ) {
        length = strlen( partial );
        index = 0;
    }

	while( index < MAX_LOCAL_SERVERS ) {
		Com_sprintf( buffer, sizeof( buffer ), "adr%i", index );
		index++;
		adrstring = Cvar_VariableString( buffer );
		if( !adrstring[ 0 ] ) {
			continue;
		}
		if( !Q_stricmpn( partial, adrstring, length ) ) {
			return adrstring;
		}
	}

    return NULL;
}


/*
=====================
CL_Rcon_f
 
  Send the rest of the command line over as
  an unconnected command.
=====================
*/
void CL_Rcon_f( void ) {
    netadr_t	address;
	int			ret;

	if( Cmd_Argc() < 2 ) {
		Com_Printf( "Usage: %s <command>\n", Cmd_Argv( 0 ) );
		return;
	}

    if( !rcon_password->string[0] ) {
        Com_Printf( "You must set 'rcon_password' before "
                    "issuing an rcon command.\n" );
        return;
    }

	if( !cls.netchan ) {
        if( !rcon_address->string[0] ) {
            Com_Printf( "You must either be connected, "
                        "or set the 'rcon_address' cvar "
                        "to issue rcon commands.\n" );
            return;
        }
		if( !NET_StringToAdr( rcon_address->string, &address ) ) {
			Com_Printf( "Bad address: %s\n", rcon_address->string );
			return;
		}
        if( !address.port )
            address.port = BigShort( PORT_SERVER );
	} else {
		address = cls.netchan->remote_address;
	}

	NET_Config( NET_CLIENT );

	CL_AddRequest( &address, REQ_RCON );

    ret = Netchan_OutOfBandPrint( NS_CLIENT, &address,
		"rcon \"%s\" %s", rcon_password->string, Cmd_RawArgs() );
	if( ret == -1 ) {
		Com_Printf( "%s to %s\n", Sys_NetErrorString(),
                NET_AdrToString( &address ) );
	}
}


/*
=====================
CL_ClearState
 
=====================
*/
void CL_ClearState( void ) {
    S_StopAllSounds();
    CL_ClearEffects();
    CL_ClearTEnts();
    LOC_FreeLocations();

    // wipe the entire cl structure
	CM_FreeMap( &cl.cm );
    memset( &cl, 0, sizeof( cl ) );
    memset( &cl_entities, 0, sizeof( cl_entities ) );
}

/*
=====================
CL_Disconnect
 
Goes from a connected state to full screen console state
Sends a disconnect message to the server
This is also called on Com_Error, so it shouldn't cause any errors
=====================
*/
void CL_Disconnect( comErrorType_t type, const char *text ) {
    if ( cls.state != ca_disconnected ) {
        Cmd_ExecTrigger( TRIG_CLIENT_SYSTEM, "disconnect" );
    }

    if ( cls.ref_initialized )
        ref.CinematicSetPalette( NULL );

    cls.connect_time = 0;
	cls.connectCount = 0;

    SCR_StopCinematic ();

    if ( cls.demoplayback ) {
        CL_CloseDemoFile();

        if ( com_timedemo->integer ) {
            float seconds, fps;

            seconds = ( Sys_Milliseconds() - cls.timeDemoStart ) * 0.001f;
            fps = cls.timeDemoFrames / seconds;

            Com_Printf( "%i frames, %3.1f seconds: %3.1f fps\n",
                    cls.timeDemoFrames, seconds, fps );
        }
    }
    
    if ( cls.demorecording )
        CL_Stop_f();

    if( cls.netchan ) {
        // send a disconnect message to the server
        MSG_WriteByte( clc_stringcmd );
        MSG_WriteString( "disconnect" );

        cls.netchan->Transmit( cls.netchan, msg_write.cursize, msg_write.data );
        cls.netchan->Transmit( cls.netchan, msg_write.cursize, msg_write.data );
        cls.netchan->Transmit( cls.netchan, msg_write.cursize, msg_write.data );

        SZ_Clear( &msg_write );
            
        Netchan_Close( cls.netchan );
        cls.netchan = NULL;
    }

    // stop download
    if ( cls.download ) {
        FS_FCloseFile( cls.download );
        cls.download = 0;
    }

    cls.downloadtempname[ 0 ] = 0;
    cls.downloadname[ 0 ] = 0;

    CL_ClearState ();

    Cvar_Set( "cl_paused", "0" );

    cls.demoplayback = qfalse;
    cls.state = ca_disconnected;
    cls.messageString[ 0 ] = 0;
	cls.userinfo_modified = 0;

	if( cls.ui_initialized ) {
		UI_ErrorMenu( type, text );
	}

}

/*
================
CL_Disconnect_f
================
*/
static void CL_Disconnect_f( void ) {
	if( cls.state > ca_disconnected ) {
		Com_Error( ERR_SILENT, "Disconnected from server" );
	}
}


/*
================
CL_ServerStatus_f
================
*/
static void CL_ServerStatus_f( void ) {
    char		*s;
    netadr_t	adr;
	int			ret;

    if ( Cmd_Argc() < 2 ) {
        Com_Printf( "Usage: %s <server>\n", Cmd_Argv( 0 ) );
        return;
    }

    s = Cmd_Argv( 1 );
    if ( !NET_StringToAdr( s, &adr ) ) {
        Com_Printf( "Bad address: %s\n", s );
        return;
    }

    if ( !adr.port ) {
        adr.port = BigShort( PORT_SERVER );
    }

	CL_AddRequest( &adr, REQ_STATUS );

	NET_Config( NET_CLIENT );

    ret = Netchan_OutOfBandPrint( NS_CLIENT, &adr, "status p=%d,%d,%d",
		PROTOCOL_VERSION_DEFAULT, PROTOCOL_VERSION_R1Q2, PROTOCOL_VERSION_Q2PRO );
	if( ret == -1 ) {
		Com_Printf( "%s to %s\n", Sys_NetErrorString(), NET_AdrToString( &adr ) );
	}
		
}

/*
====================
SortPlayers
====================
*/
static int SortPlayers( const void *v1, const void *v2 ) {
    const playerStatus_t * p1 = ( const playerStatus_t * ) v1;
    const playerStatus_t *p2 = ( const playerStatus_t * ) v2;

    if ( p1->score > p2->score ) {
        return -1;
    }

    if ( p1->score < p2->score ) {
        return 1;
    }

    return 0;
}

/*
====================
CL_ServerStatusResponse
====================
*/
static qboolean CL_ServerStatusResponse( const char *status,
        const netadr_t *from, serverStatus_t *dest )
{
	const char *s;
    playerStatus_t *player;
    int length;

    memset( dest, 0, sizeof( *dest ) );

    s = strchr( status, '\n' );
    if ( !s ) {
		return qfalse;
    }
    length = s - status;
	if( length > MAX_STRING_CHARS - 1 ) {
		return qfalse;
	}
    s++;

    strcpy( dest->address, NET_AdrToString( from ) );
    strncpy( dest->infostring, status, length );
    
    // HACK: check if this is a status response
	if( !strstr( dest->infostring, "\\hostname\\" ) ) {
		return qfalse;
    }

    // parse player list
    if( *s < 32 ) {
        return qtrue;
    }
    do {
        player = &dest->players[dest->numPlayers];
        player->score = atoi( COM_Parse( &s ) );
        player->ping = atoi( COM_Parse( &s ) );
        if( !s ) {
            break;
        } 
        Q_strncpyz( player->name, COM_Parse( &s ), sizeof( player->name ) );

        if ( ++dest->numPlayers == MAX_STATUS_PLAYERS ) {
            break;
        }
    } while( s );

    qsort( dest->players, dest->numPlayers, sizeof( dest->players[ 0 ] ),
        SortPlayers );

    return qtrue;

}

void CL_DumpServerInfo( const serverStatus_t *status ) {
	char	key[MAX_STRING_CHARS];
	char	value[MAX_STRING_CHARS];
    const   playerStatus_t *player, *last;
    const char    *infostring;

    Com_Printf( "Info response from %s:\n",
        NET_AdrToString( &net_from ) );

    infostring = status->infostring;
	do {
		Info_NextPair( &infostring, key, value );
		
		if( !key[0] ) {
			break;
		}

		if( value[0] ) {
			Com_Printf( "%-20s %s\n", key, value );
		} else {
			Com_Printf( "%-20s <MISSING VALUE>\n", key );
		}
	} while( infostring );

    Com_Printf( "\nScore Ping Name\n" );
    last = status->players + status->numPlayers;
    for( player = status->players; player != last; player++ ) {
        Com_Printf( "%5i %4i %s\n", player->score, player->ping,
            player->name );
    }
}

/*
====================
CL_ParsePrintMessage
====================
*/
static void CL_ParsePrintMessage( void ) {
	request_t *r;
    serverStatus_t serverStatus;
    char *string;
    int i, oldest;

    string = MSG_ReadString();

    if ( ( cls.state == ca_challenging || cls.state == ca_connecting ) &&
            NET_IsEqualBaseAdr( &net_from, &cls.serverAddress ) )
	{
		/* server rejected our connect request */
		if( NET_IsLocalAddress( &cls.serverAddress ) ) {
			Com_Error( ERR_DROP, "Server rejected loopback connection" );
		}
		Com_Printf( "%s", string );
        Q_strncpyz( cls.messageString, string, sizeof( cls.messageString ) );
		cls.state = ca_challenging;
		cls.connectCount = 0;
		return;
    }

	oldest = currentRequest - MAX_REQUESTS;
	if( oldest < 0 ) {
		oldest = 0;
	}
	for( i = currentRequest - 1; i >= oldest; i-- ) {
		r = &clientRequests[i & REQUEST_MASK];
		if( !r->type ) {
			continue;
		}
		if( r->adr.type == NA_BROADCAST ) {
			if( r->time < cls.realtime ) {
				continue;
			}
		} else {
			if( r->time < cls.realtime ) {
				break;
			}
			if( !NET_IsEqualBaseAdr( &net_from, &r->adr ) ) {
				continue;
			}
		}
		switch( r->type ) {
		case REQ_STATUS:
			if( CL_ServerStatusResponse( string, &net_from, &serverStatus ) ) {
			    CL_DumpServerInfo( &serverStatus );
            }
			break;
		case REQ_INFO:
			break;
		case REQ_PING:
			if( CL_ServerStatusResponse( string, &net_from, &serverStatus ) ) {
				UI_AddToServerList( &serverStatus );
			}
			break;
		case REQ_RCON:
			Com_Printf( "%s", string );
			CL_AddRequest( &net_from, REQ_RCON );
			break;
		default:
			break;
		}

		r->type = REQ_FREE;
		return;
	}

	Com_DPrintf( "Dropped unrequested packet\n" );
}


/*
====================
CL_Packet_f
 
packet <destination> <contents>
 
Contents allows \n escape character
====================
*/ 
/*
void CL_Packet_f (void)
{
	char	send[2048];
	int		i, l;
	char	*in, *out;
	netadr_t	adr;
 
	if (Cmd_Argc() != 3)
	{
		Com_Printf ("packet <destination> <contents>\n");
		return;
	}
 
	if (!NET_StringToAdr (Cmd_Argv(1), &adr))
	{
		Com_Printf ("Bad address\n");
		return;
	}
	if (!adr.port)
		adr.port = BigShort (PORT_SERVER);
 
	in = Cmd_Argv(2);
	out = send+4;
	send[0] = send[1] = send[2] = send[3] = (char)0xff;
 
	l = strlen (in);
	for (i=0 ; i<l ; i++)
	{
		if (in[i] == '\\' && in[i+1] == 'n')
		{
			*out++ = '\n';
			i++;
		}
		else
			*out++ = in[i];
	}
	*out = 0;
 
	NET_SendPacket (NS_CLIENT, out-send, send, &adr);
}
*/

/*
=================
CL_Changing_f
 
Just sent as a hint to the client that they should
drop to full console
=================
*/
static void CL_Changing_f( void ) {
    if ( cls.state < ca_connected ) {
        return;
    }

    //ZOID
    //if we are downloading, we don't change!
    //This so we don't suddenly stop downloading a map
    if ( cls.download )
        return;

    Com_Printf( "Changing map...\n" );

    Cmd_ExecTrigger( TRIG_CLIENT_SYSTEM, "changelevel" );

    SCR_BeginLoadingPlaque();

    cls.state = ca_connected;	// not active anymore, but not disconnected

    SCR_UpdateScreen();
}


/*
=================
CL_Reconnect_f
 
The server is changing levels
=================
*/
void CL_Reconnect_f( void ) {
    if ( cls.state < ca_connected ) {
        if( cls.serverAddress.type == NA_BAD ) {
            Com_Printf( "No server to reconnect to.\n" );
            return;
        }
        if( cls.serverAddress.type == NA_LOOPBACK ) {
            Com_Printf( "Cannot reconnect to loopback.\n" );
            return;
        }
    } else {
        S_StopAllSounds();

        if ( cls.demoplayback ) {
            cls.state = ca_connected;
            return;
        }

        if ( cls.state == ca_connected ) {
            if ( cls.download ) {
                return; // if we are downloading, we don't change!
            }

            Com_Printf( "Reconnecting (fast)...\n" );
            cls.state = ca_connected;

            CL_ClientCommand( "new" );
            return;
        }

        CL_Disconnect( ERR_SILENT, NULL );
    }

    Com_Printf( "Reconnecting...\n" );

    cls.connect_time = -9999;
    cls.state = ca_challenging;

    SCR_UpdateScreen();
}

#if 0
/*
=================
CL_ParseStatusMessage
 
Handle a reply from a ping
=================
*/
void CL_ParseStatusMessage ( void ) {}
#endif

/*
=================
CL_SendStatusRequest
=================
*/
qboolean CL_SendStatusRequest( char *buffer, int bufferSize ) {
    netadr_t	address;

    memset( &address, 0, sizeof( address ) );

	NET_Config( NET_CLIENT );

    // send a broadcast packet
    if ( !strcmp( buffer, "broadcast" ) ) {
        address.type = NA_BROADCAST;
        address.port = BigShort( PORT_SERVER );
    } else {
        if ( !NET_StringToAdr( buffer, &address ) ) {
            return qfalse;
        }

        if ( !address.port ) {
            address.port = BigShort( PORT_SERVER );
        }

        Q_strncpyz( buffer, NET_AdrToString( &address ), bufferSize );
    }

	CL_AddRequest( &address, REQ_PING );

    Netchan_OutOfBandPrint( NS_CLIENT, &address, "status" );

    Com_ProcessEvents();

    return qtrue;

}


/*
=================
CL_PingServers_f
=================
*/
static void CL_PingServers_f( void ) {
    int	i;
    char	buffer[ 32 ];
    char	*adrstring;
    netadr_t	address;

    memset( &address, 0, sizeof( address ) );

	NET_Config( NET_CLIENT );

    // send a broadcast packet
    Com_Printf( "pinging broadcast...\n" );
    address.type = NA_BROADCAST;
    address.port = BigShort( PORT_SERVER );

	CL_AddRequest( &address, REQ_STATUS );

    Netchan_OutOfBandPrint( NS_CLIENT, &address, "status" );

    SCR_UpdateScreen();

    // send a packet to each address book entry
    for ( i = 0; i < MAX_LOCAL_SERVERS; i++ ) {
        Com_sprintf( buffer, sizeof( buffer ), "adr%i", i );
        adrstring = Cvar_VariableString( buffer );
        if ( !adrstring[ 0 ] )
            continue;

        if ( !NET_StringToAdr( adrstring, &address ) ) {
            Com_Printf( "bad address: %s\n", adrstring );
            continue;
        }

        if ( !address.port ) {
            address.port = BigShort( PORT_SERVER );
        }

        Com_Printf( "pinging %s...\n", adrstring );
	    CL_AddRequest( &address, REQ_STATUS );

        Netchan_OutOfBandPrint( NS_CLIENT, &address, "status" );

        Com_ProcessEvents();
        SCR_UpdateScreen();

    }
}

/*
=================
CL_Skins_f
 
Load or download any custom player skins and models
=================
*/
void CL_Skins_f ( void ) {
    int	i;

    for ( i = 0 ; i < MAX_CLIENTS ; i++ ) {
        if ( !cl.configstrings[ CS_PLAYERSKINS + i ][ 0 ] )
            continue;
        Com_Printf ( "client %i: %s\n", i, cl.configstrings[ CS_PLAYERSKINS + i ] );
        SCR_UpdateScreen ();
        CL_ParseClientinfo ( i );
    }
}

/*
=================
CL_ConnectionlessPacket
 
Responses to broadcasts, etc
=================
*/
static void CL_ConnectionlessPacket( void ) {
    char	*s;
    char	*c;
    int	i, j, k;

    MSG_BeginReading();
    MSG_ReadLong();	// skip the -1

    s = MSG_ReadStringLine();

    Cmd_TokenizeString( s, qfalse );

    c = Cmd_Argv( 0 );

    Com_DPrintf( "%s: %s\n", NET_AdrToString ( &net_from ), s );

    // challenge from the server we are connecting to
    if ( !strcmp( c, "challenge" ) ) {
		qboolean proto35 = qfalse, proto36 = qfalse;

        if ( cls.state < ca_challenging ) {
            Com_DPrintf( "Challenge received while not connecting.  Ignored.\n" );
            return;
        }
        if ( !NET_IsEqualBaseAdr( &net_from, &cls.serverAddress ) ) {
            Com_DPrintf( "Challenge from different address.  Ignored.\n" );
            return;
        }
        if ( cls.state > ca_challenging ) {
            Com_DPrintf( "Dup challenge received.  Ignored.\n" );
            return;
        }

        cls.challenge = atoi( Cmd_Argv( 1 ) );
        cls.state = ca_connecting;
        cls.connect_time = -9999;
        cls.connectCount = 0;

		/* parse additional parameters */
        j = Cmd_Argc();
		for( i = 2; i < j; i++ ) {
			s = Cmd_Argv( i );
			if( !strncmp( s, "p=", 2 ) ) {
				s += 2;
				while( *s ) {
					k = atoi( s );
					if( k == PROTOCOL_VERSION_R1Q2 ) {
						proto35 = qtrue;
					} else if( k == PROTOCOL_VERSION_Q2PRO ) {
						proto36 = qtrue;
					}
					s = strchr( s, ',' );
					if( s == NULL ) {
						break;
					}
					s++;
				}
			}
        }

		/* select the 'best' protocol available, unless told otherwise */
		if( cls.serverProtocol == 0 ||
			( cls.serverProtocol == PROTOCOL_VERSION_R1Q2 && !proto35 ) ||
			( cls.serverProtocol == PROTOCOL_VERSION_Q2PRO && !proto36 ) )
		{
			if( proto36 ) {
				cls.serverProtocol = PROTOCOL_VERSION_Q2PRO;
			} else if( proto35 ) {
				cls.serverProtocol = PROTOCOL_VERSION_R1Q2;
			} else {
				cls.serverProtocol = PROTOCOL_VERSION_DEFAULT;
			}
		}
		Com_DPrintf( "Selected protocol %d\n", cls.serverProtocol );

		cls.messageString[0] = 0;

        CL_CheckForResend();
        return;
    }

    // server connection
    if ( !strcmp( c, "client_connect" ) ) {
		netchan_type_t type;
        int anticheat = 0;

        if ( cls.state < ca_connecting ) {
            Com_DPrintf( "Connect received while not connecting.  Ignored.\n" );
            return;
        }
        if ( !NET_IsEqualBaseAdr( &net_from, &cls.serverAddress ) ) {
            Com_DPrintf( "Connect from different address.  Ignored.\n" );
            return;
        }
        if ( cls.state > ca_connecting ) {
            Com_DPrintf( "Dup connect received.  Ignored.\n" );
            return;
        }

        if ( cls.serverProtocol == PROTOCOL_VERSION_Q2PRO ) {
			type = NETCHAN_NEW;
        } else {
            type = NETCHAN_OLD;
        }

		/* parse additional parameters */
        j = Cmd_Argc();
		for( i = 1; i < j; i++ ) {
			s = Cmd_Argv( i );
			if( !strncmp( s, "ac=", 3 ) ) {
                s += 3;
                if( *s ) {
                    anticheat = atoi( s );
                }
            } else if( !strncmp( s, "nc=", 3 ) ) {
                s += 3;
                if( *s ) {
                    type = atoi( s );
                    if( type != NETCHAN_OLD && type != NETCHAN_NEW ) {
			            Com_Error( ERR_DISCONNECT,
                            "Server returned invalid netchan type" );
                    }
                }
            }
        }

		Com_Printf( "Connection to %s established (protocol %d).\n",
			NET_AdrToString( &cls.serverAddress ), cls.serverProtocol );
		if( cls.netchan ) {
			// this may happen after svc_reconnect
			Netchan_Close( cls.netchan );
		}
		cls.netchan = Netchan_Setup( NS_CLIENT, type, &cls.serverAddress,
                cls.quakePort, 1024, cls.serverProtocol );

#ifdef USE_ANTICHEAT
        if( anticheat ) {
			MSG_WriteByte( clc_nop );
			MSG_FlushTo( &cls.netchan->message );
			cls.netchan->Transmit( cls.netchan, 0, NULL );
			S_StopAllSounds();
			Com_Printf( "Loading anticheat, this may take a few moments...\n" );
			SCR_UpdateScreen();
			if( !Sys_GetAntiCheatAPI() ) {
				Com_Printf( "Trying to connect without anticheat.\n" );
			} else {
				Com_Printf( S_COLOR_CYAN "Anticheat loaded successfully.\n" );
			}
        }
#else
        if( anticheat >= 2 ) {
            Com_Printf( "Anticheat required by server, "
                    "but no anticheat support linked in.\n" );
        }
#endif

        CL_ClientCommand( "new" );
        cls.state = ca_connected;
		cls.messageString[0] = 0;
        return;
    }

#if 0
    // server responding to a status broadcast
    if ( !strcmp( c, "info" ) ) {
        CL_ParseStatusMessage();
        return;
    }
#endif

    // print command from somewhere
    if ( !strcmp( c, "print" ) ) {
        CL_ParsePrintMessage();
        return;
    }

    Com_DPrintf( "Unknown connectionless packet command.\n" );
}


/*
=================
CL_PacketEvent
=================
*/
void CL_PacketEvent( int ret ) {
    int bytesToSkip;

    if ( !cl_running || !cl_running->integer ) {
        return;
    }

    //
    // remote command packet
    //
    if ( ret == 1 && *( int * )msg_read.data == -1 ) {
        CL_ConnectionlessPacket();
        return;
    }

	if( cls.state < ca_connected ) {
		return;
	}

	if ( !cls.netchan ) {
        return;		// dump it if not connected
	}

    if ( ret == 1 && msg_read.cursize < 8 ) {
        Com_DPrintf( "%s: runt packet\n", NET_AdrToString( &net_from ) );
        return;
    }

    //
    // packet from server
    //
    if ( !NET_IsEqualAdr( &net_from, &cls.netchan->remote_address ) ) {
        Com_DPrintf( "%s: sequenced packet without connection\n", NET_AdrToString( &net_from ) );
        return;
    }

	if( ret == -1 ) {
		Com_Error( ERR_DISCONNECT, "Connection reset by peer" );
	}

    if ( !cls.netchan->Process( cls.netchan ) )
        return;		// wasn't accepted for some reason

    bytesToSkip = msg_read.readcount;

    CL_ParseServerMessage();

    //
    // if recording demos, copy the message out
    //
    //
    // we don't know if it is ok to save a demo message until
    // after we have parsed the frame
    //
    if ( cls.demorecording && !cls.demowaiting )
        CL_WriteDemoMessage( &msg_read, bytesToSkip );

    CL_AddNetgraph();

    SCR_AddLagometerPacketInfo();
}


//=============================================================================

/*
==============
CL_FixUpGender_f
==============
*/
static void CL_FixUpGender( void ) {
    char *p;
    char sk[MAX_QPATH];

    Q_strncpyz( sk, info_skin->string, sizeof( sk ) );
    if ( ( p = strchr( sk, '/' ) ) != NULL )
        *p = 0;
    if ( Q_stricmp( sk, "male" ) == 0 || Q_stricmp( sk, "cyborg" ) == 0 )
        Cvar_Set ( "gender", "male" );
    else if ( Q_stricmp( sk, "female" ) == 0 || Q_stricmp( sk, "crackhor" ) == 0 )
        Cvar_Set ( "gender", "female" );
    else
        Cvar_Set ( "gender", "none" );
    info_gender->modified = qfalse;
    
}

void CL_UpdateUserinfo( cvar_t *var, cvarSetSource_t source ) {
	int i;

	if( var == info_skin && source != CVAR_SET_CONSOLE &&
            gender_auto->integer )
    {
		 CL_FixUpGender();
	}
	if( !cls.netchan ) {
		return;
	}
	if( cls.serverProtocol != PROTOCOL_VERSION_Q2PRO ) {
        // transmit at next oportunity
		cls.userinfo_modified = MAX_PACKET_USERINFOS;	
		return;
	}

	if( cls.userinfo_modified == MAX_PACKET_USERINFOS ) {
		return; // can't hold any more
	}

	// check for the same variable being modified twice
	for( i = 0; i < cls.userinfo_modified; i++ ) {
		if( cls.userinfo_updates[i] == var ) {
			Com_DPrintf( "Dup modified %s at frame %u\n", var->name, com_framenum );
			return;
		}
	}

	Com_DPrintf( "Modified %s at frame %u\n", var->name, com_framenum );

	cls.userinfo_updates[cls.userinfo_modified++] = var;	
}

/*
==============
CL_Userinfo_f
==============
*/
void CL_Userinfo_f ( void ) {
    Com_Printf ( "User info settings:\n" );
    Info_Print ( Cvar_Userinfo() );
}

/*
======================
CL_RegisterSounds
======================
*/
void CL_RegisterSounds( void ) {
    int	i;
    char	*s;

    SCR_LoadingString( "sounds" );

    S_BeginRegistration ();
    CL_RegisterTEntSounds ();
    for ( i = 1; i < MAX_SOUNDS; i++ ) {
        s = cl.configstrings[ CS_SOUNDS + i ];
        if ( !s[ 0 ] )
            break;
        cl.sound_precache[ i ] = S_RegisterSound( s );
    }
    S_EndRegistration ();
}

/*
=================
CL_Snd_Restart_f
 
Restart the sound subsystem so it can pick up
new parameters and flush all sounds
=================
*/
void CL_Snd_Restart_f ( void ) {
    S_Shutdown ();
    S_Init ();
    CL_RegisterSounds ();
}

int precache_check; // for autodownload of precache items
int precache_spawncount;
int precache_tex;
int precache_model_skin;

byte *precache_model; // used for skin checking in alias models

#define PLAYER_MULT 5

// ENV_CNT is map load, ENV_CNT+1 is first env map
#define ENV_CNT (CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT)
#define TEXTURE_CNT (ENV_CNT+13)

static const char *env_suf[ 6 ] = {"rt", "bk", "lf", "ft", "up", "dn"
                                  };

void CL_RequestNextDownload ( void ) {
    unsigned	map_checksum;		// for detecting cheater maps
    char fn[ MAX_QPATH ];
    dmdl_t *pheader;

    if ( cls.state != ca_connected && cls.state != ca_loading )
        return;

	if ( ( !allow_download->integer || NET_IsLocalAddress( &cls.serverAddress )) && precache_check < ENV_CNT )
        precache_check = ENV_CNT;

    //ZOID
    if ( precache_check == CS_MODELS ) { // confirm map
        precache_check = CS_MODELS + 2; // 0 isn't used
        if ( allow_download_maps->integer )
            if ( !CL_CheckOrDownloadFile( cl.configstrings[ CS_MODELS + 1 ] ) )
                return; // started a download
    }
    if ( precache_check >= CS_MODELS && precache_check < CS_MODELS + MAX_MODELS ) {
        if ( allow_download_models->integer ) {
            while ( precache_check < CS_MODELS + MAX_MODELS &&
                    cl.configstrings[ precache_check ][ 0 ] ) {
                if ( cl.configstrings[ precache_check ][ 0 ] == '*' ||
                        cl.configstrings[ precache_check ][ 0 ] == '#' ) {
                    precache_check++;
                    continue;
                }
                if ( precache_model_skin == 0 ) {
                    if ( !CL_CheckOrDownloadFile( cl.configstrings[ precache_check ] ) ) {
                        precache_model_skin = 1;
                        return; // started a download
                    }
                    precache_model_skin = 1;
                }

                // checking for skins in the model
                if ( !precache_model ) {

                    FS_LoadFile ( cl.configstrings[ precache_check ], ( void ** ) & precache_model );
                    if ( !precache_model ) {
                        precache_model_skin = 0;
                        precache_check++;
                        continue; // couldn't load it
                    }
                    if ( LittleLong( *( unsigned * ) precache_model ) != IDALIASHEADER ) {
                        // not an alias model
                        FS_FreeFile( precache_model );
                        precache_model = 0;
                        precache_model_skin = 0;
                        precache_check++;
                        continue;
                    }
                    pheader = ( dmdl_t * ) precache_model;
                    if ( LittleLong ( pheader->version ) != ALIAS_VERSION ) {
                        precache_check++;
                        precache_model_skin = 0;
                        continue; // couldn't load it
                    }
                }

                pheader = ( dmdl_t * ) precache_model;

                while ( precache_model_skin - 1 < LittleLong( pheader->num_skins ) ) {
                    if ( !CL_CheckOrDownloadFile( ( char * ) precache_model +
                                                  LittleLong( pheader->ofs_skins ) +
                                                  ( precache_model_skin - 1 ) * MAX_SKINNAME ) ) {
                        precache_model_skin++;
                        return; // started a download
                    }
                    precache_model_skin++;
                }
                if ( precache_model ) {
                    FS_FreeFile( precache_model );
                    precache_model = 0;
                }
                precache_model_skin = 0;
                precache_check++;
            }
        }
        precache_check = CS_SOUNDS;
    }
    if ( precache_check >= CS_SOUNDS && precache_check < CS_SOUNDS + MAX_SOUNDS ) {
        if ( allow_download_sounds->integer ) {
            if ( precache_check == CS_SOUNDS )
                precache_check++; // zero is blank
            while ( precache_check < CS_SOUNDS + MAX_SOUNDS &&
                    cl.configstrings[ precache_check ][ 0 ] ) {
                if ( cl.configstrings[ precache_check ][ 0 ] == '*' ) {
                    precache_check++;
                    continue;
                }
                Com_sprintf( fn, sizeof( fn ), "sound/%s", cl.configstrings[ precache_check++ ] );
                if ( !CL_CheckOrDownloadFile( fn ) )
                    return; // started a download
            }
        }
        precache_check = CS_IMAGES;
    }
    if ( precache_check >= CS_IMAGES && precache_check < CS_IMAGES + MAX_IMAGES ) {
        if ( precache_check == CS_IMAGES )
            precache_check++; // zero is blank
        while ( precache_check < CS_IMAGES + MAX_IMAGES &&
                cl.configstrings[ precache_check ][ 0 ] ) {
            Com_sprintf( fn, sizeof( fn ), "pics/%s.pcx", cl.configstrings[ precache_check++ ] );
            if ( !CL_CheckOrDownloadFile( fn ) )
                return; // started a download
        }
        precache_check = CS_PLAYERSKINS;
    }
    // skins are special, since a player has three things to download:
    // model, weapon model and skin
    // so precache_check is now *3
    if ( precache_check >= CS_PLAYERSKINS && precache_check < CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT ) {
        if ( allow_download_players->integer ) {
            while ( precache_check < CS_PLAYERSKINS + MAX_CLIENTS * PLAYER_MULT ) {
                int i, n;
                char model[ MAX_QPATH ], skin[ MAX_QPATH ], *p;

                i = ( precache_check - CS_PLAYERSKINS ) / PLAYER_MULT;
                n = ( precache_check - CS_PLAYERSKINS ) % PLAYER_MULT;

                if ( !cl.configstrings[ CS_PLAYERSKINS + i ][ 0 ] ) {
                    precache_check = CS_PLAYERSKINS + ( i + 1 ) * PLAYER_MULT;
                    continue;
                }

                if ( ( p = strchr( cl.configstrings[ CS_PLAYERSKINS + i ], '\\' ) ) != NULL )
                    p++;
                else
                    p = cl.configstrings[ CS_PLAYERSKINS + i ];
                Q_strncpyz( model, p, sizeof( model ) );
                p = strchr( model, '/' );
                if ( !p )
                    p = strchr( model, '\\' );
                if ( p ) {
                    *p++ = 0;
                    strcpy( skin, p );
                } else
                    *skin = 0;

                switch ( n ) {
                case 0:   // model
                    Com_sprintf( fn, sizeof( fn ), "players/%s/tris.md2", model );
                    if ( !CL_CheckOrDownloadFile( fn ) ) {
                        precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 1;
                        return; // started a download
                    }
                    n++;
                    /*FALL THROUGH*/

                case 1:   // weapon model
                    Com_sprintf( fn, sizeof( fn ), "players/%s/weapon.md2", model );
                    if ( !CL_CheckOrDownloadFile( fn ) ) {
                        precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 2;
                        return; // started a download
                    }
                    n++;
                    /*FALL THROUGH*/

                case 2:   // weapon skin
                    Com_sprintf( fn, sizeof( fn ), "players/%s/weapon.pcx", model );
                    if ( !CL_CheckOrDownloadFile( fn ) ) {
                        precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 3;
                        return; // started a download
                    }
                    n++;
                    /*FALL THROUGH*/

                case 3:   // skin
                    Com_sprintf( fn, sizeof( fn ), "players/%s/%s.pcx", model, skin );
                    if ( !CL_CheckOrDownloadFile( fn ) ) {
                        precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 4;
                        return; // started a download
                    }
                    n++;
                    /*FALL THROUGH*/

                case 4:   // skin_i
                    Com_sprintf( fn, sizeof( fn ), "players/%s/%s_i.pcx", model, skin );
                    if ( !CL_CheckOrDownloadFile( fn ) ) {
                        precache_check = CS_PLAYERSKINS + i * PLAYER_MULT + 5;
                        return; // started a download
                    }
                    // move on to next model
                    precache_check = CS_PLAYERSKINS + ( i + 1 ) * PLAYER_MULT;
                }
            }
        }
        // precache phase completed
        precache_check = ENV_CNT;
    }

    if ( precache_check == ENV_CNT ) {
        precache_check = ENV_CNT + 1;
        SCR_LoadingString( "collision map" );

        CM_LoadMap ( &cl.cm, cl.configstrings[ CS_MODELS + 1 ], CM_LOAD_CLIENT, &map_checksum );

        if ( map_checksum != atoi( cl.configstrings[ CS_MAPCHECKSUM ] ) ) {
            Com_Error ( ERR_DROP, "Local map version differs from server: %i != '%s'\n",
                        map_checksum, cl.configstrings[ CS_MAPCHECKSUM ] );
            return;
        }
    }

    if ( precache_check > ENV_CNT && precache_check < TEXTURE_CNT ) {
        if ( allow_download->integer && allow_download_maps->integer ) {
            while ( precache_check < TEXTURE_CNT ) {
                int n = precache_check++ - ENV_CNT - 1;

                if ( n & 1 )
                    Com_sprintf( fn, sizeof( fn ), "env/%s%s.pcx",
                                 cl.configstrings[ CS_SKY ], env_suf[ n / 2 ] );
                else
                    Com_sprintf( fn, sizeof( fn ), "env/%s%s.tga",
                                 cl.configstrings[ CS_SKY ], env_suf[ n / 2 ] );
                if ( !CL_CheckOrDownloadFile( fn ) )
                    return; // started a download
            }
        }
        precache_check = TEXTURE_CNT;
    }

    if ( precache_check == TEXTURE_CNT ) {
        precache_check = TEXTURE_CNT + 1;
        precache_tex = 0;
    }

    // confirm existance of textures, download any that don't exist
    if ( precache_check == TEXTURE_CNT + 1 ) {
        if ( allow_download->integer && allow_download_maps->integer ) {
			while ( precache_tex < cl.cm.cache->numtexinfo ) {
				char *texname = cl.cm.cache->surfaces[ precache_tex++ ].rname;

                // Also check if 32bit images are present
                Com_sprintf( fn, sizeof( fn ), "textures/%s.jpg", texname );
                if ( FS_LoadFile( fn, NULL ) == -1 ) {
                    Com_sprintf( fn, sizeof( fn ), "textures/%s.tga", texname );
                    if ( FS_LoadFile( fn, NULL ) == -1 ) {
                        Com_sprintf( fn, sizeof( fn ), "textures/%s.wal", texname );
                        if ( !CL_CheckOrDownloadFile( fn ) ) {
                            return; // started a download
                        }
                    }
                }
            }
        }
        precache_check = TEXTURE_CNT + 999;
    }


    //ZOID
    CL_RegisterSounds ();
    CL_PrepRefresh ();

    LOC_LoadLocations();

    CL_ClientCommand( va( "begin %i\n", precache_spawncount ) );

    Cvar_FixCheats();

    CL_UpdateGunSetting();
    CL_UpdateGibSetting();
    CL_UpdateFootstepsSetting();
	CL_UpdatePredictSetting();
	CL_UpdateLocalFovSetting();

    cls.state = ca_precached;
}

/*
=================
CL_Precache_f
 
The server will send this command right
before allowing the client into the server
=================
*/
static void CL_Precache_f( void ) {
    if ( cls.state < ca_connected ) {
        return;
    }

    cls.state = ca_loading;

    S_StopAllSounds();

    //Yet another hack to let old demos work
    //the old precache sequence
    if ( cls.demoplayback || Cmd_Argc() < 2 ) {
        uint32	map_checksum;		// for detecting cheater maps

        SCR_LoadingString( "collision map" );
        CM_LoadMap( &cl.cm, cl.configstrings[ CS_MODELS + 1 ],
                CM_LOAD_CLIENT, &map_checksum );
        CL_RegisterSounds();
        CL_PrepRefresh();
        cls.state = ca_precached;
        return;
    }

    precache_check = CS_MODELS;
    precache_spawncount = atoi( Cmd_Argv( 1 ) );
    precache_model = 0;
    precache_model_skin = 0;

    CL_RequestNextDownload();

    if( cls.state != ca_precached ) {
        cls.state = ca_connected;
    }
}


static void CL_DumpClients_f( void ) {
    int i;

    if ( cls.state != ca_active ) {
		Com_Printf( "Must be in a level to dump\n" );
        return;
    }

    for ( i = 0 ; i < MAX_CLIENTS ; i++ ) {
        if ( !cl.clientinfo[ i ].name[ 0 ] ) {
            continue;
        }

        Com_Printf( "%3i: %s\n", i, cl.clientinfo[ i ].name );
    }
}

static void CL_DumpStatusbar_f( void ) {
	char buffer[MAX_QPATH];
	fileHandle_t f;

    if ( cls.state != ca_active ) {
		Com_Printf( "Must be in a level to dump.\n" );
        return;
    }

	if( Cmd_Argc() != 2 ) {
		Com_Printf( "Usage: %s <filename>\n", Cmd_Argv( 0 ) );
		return;
	}

	Cmd_ArgvBuffer( 1, buffer, sizeof( buffer ) );
	COM_DefaultExtension( buffer, ".txt", sizeof( buffer ) );

	FS_FOpenFile( buffer, &f, FS_MODE_WRITE );
	if( !f ) {
		Com_EPrintf( "Couldn't open %s for writing.\n", buffer );
		return;
	}

	FS_FPrintf( f, "// status bar program dump of '%s' mod\n",
            Cvar_VariableString( "gamedir" ) );
	FS_FPrintf( f, "%s\n", cl.configstrings[CS_STATUSBAR] );

	FS_FCloseFile( f );

	Com_Printf( "Dumped status bar program to %s.\n", buffer );
}

static void CL_DumpLayout_f( void ) {
	char buffer[MAX_QPATH];
	fileHandle_t f;

    if ( cls.state != ca_active ) {
		Com_Printf( "Must be in a level to dump.\n" );
        return;
    }

	if( Cmd_Argc() != 2 ) {
		Com_Printf( "Usage: %s <filename>\n", Cmd_Argv( 0 ) );
		return;
	}

	if( !cl.layout[0] ) {
		Com_Printf( "No layout to dump.\n" );
		return;
	}

	Cmd_ArgvBuffer( 1, buffer, sizeof( buffer ) );
	COM_DefaultExtension( buffer, ".txt", sizeof( buffer ) );

	FS_FOpenFile( buffer, &f, FS_MODE_WRITE );
	if( !f ) {
		Com_EPrintf( "Couldn't open %s for writing.\n", buffer );
		return;
	}

	FS_FPrintf( f, "// layout program dump of '%s' mod\n",
            Cvar_VariableString( "gamedir" ) );
	FS_FPrintf( f, "%s\n", cl.layout );

	FS_FCloseFile( f );

	Com_Printf( "Dumped layout program to %s.\n", buffer );
}

/*
====================
CL_Mapname_m
====================
*/
static void CL_Mapname_m( char *buffer, int bufferSize ) {
    if ( !cl.mapname[ 0 ] ) {
        Q_strncpyz( buffer, "nomap", bufferSize );
        return;
    }

    Q_strncpyz( buffer, cl.mapname, bufferSize );
}

/*
====================
CL_Server_m
====================
*/
static void CL_Server_m( char *buffer, int bufferSize ) {
    if ( cls.state <= ca_disconnected ) {
        Q_strncpyz( buffer, "noserver", bufferSize );
        return;
    }

    Q_strncpyz( buffer, cls.servername, bufferSize );
}

/*
====================
CL_DemoState_m
====================
*/
static void CL_DemoState_m( char *buffer, int bufferSize ) {
    Q_strncpyz( buffer, "0", bufferSize );

    if ( cls.state < ca_connected ) {
        return;
    }

    if ( cls.demorecording ) {
        Q_strncpyz( buffer, "1", bufferSize );
    } else if ( cls.demoplayback ) {
        Q_strncpyz( buffer, "2", bufferSize );
    }
}

/*
==============
CL_Ups_m
==============
*/
static void CL_Ups_m( char *buffer, int bufferSize ) {
	vec3_t vel;
	int ups;
	player_state_t *ps;

	if( cl.frame.ps.clientNum == CLIENTNUM_NONE ) {
		buffer[0] = 0;
		return;
	}

	if( !cls.demoplayback && cl.frame.ps.clientNum == cl.clientNum &&
            cl_predict->integer )
    {
		VectorCopy( cl.predicted_velocity, vel );
	} else {
		ps = &cl.frame.ps.ps;
		
		vel[0] = ps->pmove.velocity[0] * 0.125f;
		vel[1] = ps->pmove.velocity[1] * 0.125f;
		vel[2] = ps->pmove.velocity[2] * 0.125f;
	}

	ups = VectorLength( vel );
	Com_sprintf( buffer, bufferSize, "%d", ups );
}

/*
==============
CL_Timer_m
==============
*/
static void CL_Timer_m( char *buffer, int bufferSize ) {
	int hour, min, sec;

	sec = cl.time / 1000;
	min = sec / 60;
	hour = min / 60;
	min %= 60;
	sec %= 60;

	if( hour ) {
		Com_sprintf( buffer, bufferSize, "%i:%i:%02i", hour, min, sec );
	} else {
		Com_sprintf( buffer, bufferSize, "%i:%02i", min, sec );
	}

}

static void CL_Fps_m( char *buffer, int bufferSize ) {
	Com_sprintf( buffer, bufferSize, "%i", cls.currentFPS );
}

static void CL_Health_m( char *buffer, int bufferSize ) {
	Com_sprintf( buffer, bufferSize, "%i", cl.frame.ps.ps.stats[STAT_HEALTH] );
}
static void CL_Ammo_m( char *buffer, int bufferSize ) {
	Com_sprintf( buffer, bufferSize, "%i", cl.frame.ps.ps.stats[STAT_AMMO] );
}
static void CL_Armor_m( char *buffer, int bufferSize ) {
	Com_sprintf( buffer, bufferSize, "%i", cl.frame.ps.ps.stats[STAT_ARMOR] );
}

/*
====================
CL_RestartFilesystem
 
Flush caches and restart the VFS.
====================
*/
void CL_RestartFilesystem( void ) {
    int	cls_state;

    if ( !cl_running->integer ) {
        FS_Restart();
        return;
    }

    Com_DPrintf( "CL_RestartFilesystem()\n" );

    /* temporary switch to loading state */
    cls_state = cls.state;
    if ( cls.state >= ca_precached ) {
        cls.state = ca_loading;
    }

    UI_OpenMenu( UIMENU_NONE );
    CL_ShutdownUI();

    S_StopAllSounds();
	S_FreeAllSounds();

    ref.Shutdown( qfalse );

    FS_Restart();

    ref.Init( qfalse );

    SCR_RegisterMedia();
    Con_SetupDC();
    CL_InitUI();

    if ( cls_state == ca_disconnected ) {
        UI_OpenMenu( UIMENU_MAIN );
    } else if ( cls_state >= ca_loading ) {
		CL_RegisterSounds();
        CL_PrepRefresh();
    }

    /* switch back to original state */
    cls.state = cls_state;

}

/*
====================
CL_RestartRefresh
====================
*/
void CL_RestartRefresh_f( void ) {
    int	cls_state;

    Com_DPrintf( "CL_RestartRefresh()\n" );

    /* temporary switch to loading state */
    cls_state = cls.state;
    if ( cls.state >= ca_precached ) {
        cls.state = ca_loading;
    }

    UI_OpenMenu( UIMENU_NONE );
    CL_ShutdownUI();

    S_StopAllSounds();
   
    CL_ShutdownInput();
    CL_ShutdownRefresh();

    CL_InitRefresh();
    CL_InitInput();

    SCR_RegisterMedia();
    Con_SetupDC();
    CL_InitUI();

    if ( cls_state == ca_disconnected ) {
        UI_OpenMenu( UIMENU_MAIN );
    } else if ( cls_state >= ca_loading ) {
        CL_PrepRefresh();
    }

    /* switch back to original state */
    cls.state = cls_state;

}

/*
============
CL_LocalConnect
============
*/
void CL_LocalConnect( void ) {
    if ( FS_NeedRestart() ) {
        cls.state = ca_challenging;
        CL_RestartFilesystem();
    }
}

/*
============
CL_RestartFilesystem_f
 
Console command to re-start the file system.
============
*/
static void CL_RestartFilesystem_f( void ) {
    if( !FS_SafeToRestart() ) {
        Com_Printf( "Can't \"%s\", there are some open file handles.\n", Cmd_Argv( 0 ) );
        return;
    }
    
    CL_RestartFilesystem();
}

static void CL_Gun_OnChange( cvar_t *self, void *arg ) {
    CL_UpdateGunSetting();
}

static void CL_Hand_OnChange( cvar_t *self, void *arg ) {
    CL_UpdateGunSetting();
}

static void CL_Gibs_OnChange( cvar_t *self, void *arg ) {
    CL_UpdateGibSetting();
}

static void CL_Footsteps_OnChange( cvar_t *self, void *arg ) {
    CL_UpdateFootstepsSetting();
}

static void CL_Predict_OnChange( cvar_t *self, void *arg ) {
    CL_UpdatePredictSetting();
}

static void CL_GetClientState( uiClientState_t *state ) {
	if( !state ) {
		Com_Error( ERR_DROP, "CL_GetClientState: NULL" );
	}
	state->connState = cls.state;
	state->connectCount = cls.connectCount;
	state->demoplayback = cls.demoplayback;
	Q_strncpyz( state->servername, cls.servername, sizeof( state->servername ) );
	Q_strncpyz( state->mapname, cl.mapname, sizeof( state->mapname ) );
	Q_strncpyz( state->fullname, cl.configstrings[CS_NAME], sizeof( state->fullname ) );
	if( cls.state > ca_connected ) {
		Q_strncpyz( state->loadingString, cl.loadingString, sizeof( state->loadingString ) );
	} else {
		Q_strncpyz( state->loadingString, cls.messageString, sizeof( state->loadingString ) );
	}
}

void CL_FillAPI( clientAPI_t *api ) {
	api->StartLocalSound = S_StartLocalSound;
	api->StopAllSounds = S_StopAllSounds;
	api->SendStatusRequest = CL_SendStatusRequest;
	api->GetClientState = CL_GetClientState;
	api->GetDemoInfo = CL_GetDemoInfo;
	api->UpdateScreen = SCR_UpdateScreen;
}

/*
=================
CL_InitLocal
=================
*/
void CL_InitLocal ( void ) {
    int i;

    cls.state = ca_disconnected;
    cls.realtime = 0;

	CL_FillAPI( &client );

    CL_RegisterInput();

    CL_InitDemos();

    LOC_Init();

    for ( i = 0 ; i < MAX_LOCAL_SERVERS ; i++ ) {
        Cvar_Get( va( "adr%i", i ), "", CVAR_ARCHIVE );
    }

    //
    // register our variables
    //
    cl_stereo_separation = Cvar_Get( "cl_stereo_separation", "0.4", CVAR_ARCHIVE );
    cl_stereo = Cvar_Get( "cl_stereo", "0", 0 );

    cl_add_blend = Cvar_Get ( "cl_blend", "1", CVAR_ARCHIVE );
    cl_add_lights = Cvar_Get ( "cl_lights", "1", 0 );
    cl_add_particles = Cvar_Get ( "cl_particles", "1", 0 );
    cl_add_entities = Cvar_Get ( "cl_entities", "1", 0 );
    cl_gun = Cvar_Get ( "cl_gun", "1", 0 );
	cl_gun->changedFunc = CL_Gun_OnChange;
    cl_footsteps = Cvar_Get( "cl_footsteps", "1", 0 );
	cl_footsteps->changedFunc = CL_Footsteps_OnChange;
    cl_noskins = Cvar_Get ( "cl_noskins", "0", 0 );
    cl_autoskins = Cvar_Get ( "cl_autoskins", "0", 0 );
    cl_predict = Cvar_Get ( "cl_predict", "1", 0 );
	cl_predict->changedFunc = CL_Predict_OnChange;
    cl_kickangles = Cvar_Get( "cl_kickangles", "1", CVAR_CHEAT );
	cl_maxfps = Cvar_Get( "cl_maxfps", "90", CVAR_ARCHIVE );
	cl_async = Cvar_Get( "cl_async", "0", CVAR_ARCHIVE );
	r_maxfps = Cvar_Get( "r_maxfps", "0", CVAR_ARCHIVE );

    cl_upspeed = Cvar_Get ( "cl_upspeed", "200", 0 );
    cl_forwardspeed = Cvar_Get ( "cl_forwardspeed", "200", 0 );
    cl_sidespeed = Cvar_Get ( "cl_sidespeed", "200", 0 );
    cl_yawspeed = Cvar_Get ( "cl_yawspeed", "140", 0 );
    cl_pitchspeed = Cvar_Get ( "cl_pitchspeed", "150", CVAR_CHEAT );
    cl_anglespeedkey = Cvar_Get ( "cl_anglespeedkey", "1.5", CVAR_CHEAT );

    freelook = Cvar_Get( "freelook", "0", CVAR_ARCHIVE );
    lookspring = Cvar_Get ( "lookspring", "0", CVAR_ARCHIVE );
    lookstrafe = Cvar_Get ( "lookstrafe", "0", CVAR_ARCHIVE );
    sensitivity = Cvar_Get ( "sensitivity", "3", CVAR_ARCHIVE );

    cl_run = Cvar_Get( "cl_run", "1", CVAR_ARCHIVE );
    m_pitch = Cvar_Get ( "m_pitch", "0.022", CVAR_ARCHIVE );
    m_yaw = Cvar_Get ( "m_yaw", "0.022", 0 );
    m_forward = Cvar_Get ( "m_forward", "1", 0 );
    m_side = Cvar_Get ( "m_side", "1", 0 );

    cl_shownet = Cvar_Get( "cl_shownet", "0", 0 );
    cl_showmiss = Cvar_Get ( "cl_showmiss", "0", 0 );
    cl_showclamp = Cvar_Get ( "showclamp", "0", 0 );
    cl_timeout = Cvar_Get ( "cl_timeout", "120", 0 );

    rcon_password = Cvar_Get ( "rcon_password", "", CVAR_PRIVATE );
    rcon_address = Cvar_Get ( "rcon_address", "", 0 );

    cl_thirdperson = Cvar_Get( "cl_thirdperson", "0", CVAR_CHEAT );
    cl_thirdperson_angle = Cvar_Get( "cl_thirdperson_angle", "0", 0 );
    cl_thirdperson_range = Cvar_Get( "cl_thirdperson_range", "60", 0 );

    cl_railtrail_type = Cvar_Get( "cl_railtrail_type", "0", 0 );
    cl_railtrail_time = Cvar_Get( "cl_railtrail_time", "1.0", 0 );
    cl_railtrail_alpha = Cvar_Get( "cl_railtrail_alpha", "1.0", 0 );
    cl_railcore_color = Cvar_Get( "cl_railcore_color", "0xFF0000", 0 );
    cl_railcore_width = Cvar_Get( "cl_railcore_width", "3", 0 );
    cl_railrings_color = Cvar_Get( "cl_railrings_color", "0x0000FF", 0 );
    cl_railrings_width = Cvar_Get( "cl_railrings_width", "3", 0 );

    cl_disable_particles = Cvar_Get( "cl_disable_particles", "0", 0 );
	cl_disable_explosions = Cvar_Get( "cl_disable_explosions", "0", 0 );
    cl_gibs = Cvar_Get( "cl_gibs", "1", 0 );
	cl_gibs->changedFunc = CL_Gibs_OnChange;

    cl_chat_notify = Cvar_Get( "cl_chat_notify", "1", 0 );
    cl_chat_beep = Cvar_Get( "cl_chat_beep", "1", 0 );

    cl_protocol = Cvar_Get( "cl_protocol", "0", 0 );

	info_hand->changedFunc = CL_Hand_OnChange;
    info_gender->modified = qfalse; // clear this so we know when user sets it manually
    gender_auto = Cvar_Get ( "gender_auto", "1", CVAR_ARCHIVE );

    cl_vwep = Cvar_Get ( "cl_vwep", "1", CVAR_ARCHIVE );

    //
    // register our commands
    //
    Cmd_AddCommand ( "cmd", CL_ForwardToServer_f );
    Cmd_AddCommand ( "pause", CL_Pause_f );
    Cmd_AddCommand ( "pingservers", CL_PingServers_f );
    Cmd_AddCommand ( "skins", CL_Skins_f );

    Cmd_AddCommand ( "userinfo", CL_Userinfo_f );
    Cmd_AddCommand ( "snd_restart", CL_Snd_Restart_f );

    Cmd_AddCommand ( "changing", CL_Changing_f );
    Cmd_AddCommand ( "disconnect", CL_Disconnect_f );

    Cmd_AddCommandEx ( "connect", CL_Connect_f, CL_Connect_g );
    Cmd_AddCommand ( "reconnect", CL_Reconnect_f );

    Cmd_AddCommand ( "rcon", CL_Rcon_f );

    // 	Cmd_AddCommand ("packet", CL_Packet_f); // this is dangerous to leave in

    Cmd_AddCommand ( "setenv", CL_Setenv_f );

    Cmd_AddCommand ( "precache", CL_Precache_f );

    Cmd_AddCommand ( "download", CL_Download_f );

    Cmd_AddCommandEx( "serverstatus", CL_ServerStatus_f, CL_Connect_g );

    Cmd_AddCommand( "dumpclients", CL_DumpClients_f );
	Cmd_AddCommand( "dumpstatusbar", CL_DumpStatusbar_f );
	Cmd_AddCommand( "dumplayout", CL_DumpLayout_f );

    Cmd_AddCommand( "vid_restart", CL_RestartRefresh_f );
    Cmd_AddCommand( "fs_restart", CL_RestartFilesystem_f );


    //
    // forward to server commands
    //
    // the only thing this does is allow command completion
    // to work -- all unknown commands are automatically
    // forwarded to the server
    Cmd_AddCommand ( "wave", NULL );
    Cmd_AddCommand ( "inven", NULL );
    Cmd_AddCommand ( "kill", NULL );
    Cmd_AddCommand ( "use", NULL );
    Cmd_AddCommand ( "drop", NULL );
    Cmd_AddCommand ( "say", NULL );
    Cmd_AddCommand ( "say_team", NULL );
    Cmd_AddCommand ( "info", NULL );
    Cmd_AddCommand ( "prog", NULL );
    Cmd_AddCommand ( "give", NULL );
    Cmd_AddCommand ( "god", NULL );
    Cmd_AddCommand ( "notarget", NULL );
    Cmd_AddCommand ( "noclip", NULL );
    Cmd_AddCommand ( "invuse", NULL );
    Cmd_AddCommand ( "invprev", NULL );
    Cmd_AddCommand ( "invnext", NULL );
    Cmd_AddCommand ( "invdrop", NULL );
    Cmd_AddCommand ( "weapnext", NULL );
    Cmd_AddCommand ( "weapprev", NULL );
    Cmd_AddCommand ( "vote", NULL );
    Cmd_AddCommand ( "observe", NULL );
	Cmd_AddCommand ( "follow", NULL );
	Cmd_AddCommand ( "time", NULL );
	Cmd_AddCommand ( "playernext", NULL );
	Cmd_AddCommand ( "playerprev", NULL );
	Cmd_AddCommand ( "playertoggle", NULL );
	Cmd_AddCommand ( "mvdls", NULL );
	Cmd_AddCommand ( "mvddl", NULL );
	Cmd_AddCommand ( "!mvdadmin", NULL );

    Cmd_AddMacro( "cl_mapname", CL_Mapname_m );
    Cmd_AddMacro( "cl_server", CL_Server_m );
    Cmd_AddMacro( "cl_demo", CL_DemoState_m );
	Cmd_AddMacro( "cl_timer", CL_Timer_m );
	Cmd_AddMacro( "cl_ups", CL_Ups_m );
	Cmd_AddMacro( "cl_fps", CL_Fps_m );
	Cmd_AddMacro( "cl_health", CL_Health_m );
	Cmd_AddMacro( "cl_ammo", CL_Ammo_m );
	Cmd_AddMacro( "cl_armor", CL_Armor_m );


}

/*
==================
CL_CheatsOK
==================
*/
qboolean CL_CheatsOK( void ) {
    if ( cls.state < ca_connected ) {
        return qtrue;
    }

    if ( cls.demoplayback ) {
        return qtrue;
    }

    // developer option
	if ( sv_running->integer ) {
		if( sv_running->integer != ss_game || Cvar_VariableInteger( "cheats" ) ) {
			return qtrue;
		}
    }

    // single player can cheat
    if ( atoi( cl.configstrings[ CS_MAXCLIENTS ] ) < 2 ) {
        return qtrue;
    }

    return qfalse;
}

//============================================================================

void CL_AppActivate( qboolean active ) {
	cls.appactive = active;
    Key_ClearStates();
    CL_InputActivate( active );
    S_Activate( active );
    CDAudio_Activate( active );
}

/*
==================
CL_SetClientTime
==================
*/
static void CL_SetClientTime( void ) {
    int	prevtime;

    prevtime = cl.oldframe.serverFrame * 100;
    if ( prevtime >= cl.serverTime ) {
        /* this may happen on a very first frame */
        cl.lerpfrac = 0;
        return;
    }

    if ( cl.time > cl.serverTime ) {
        if ( cl_showclamp->integer )
            Com_Printf( "high clamp %i\n", cl.time - cl.serverTime );
        cl.time = cl.serverTime;
        cl.lerpfrac = 1.0;
    } else if ( cl.time < prevtime ) {
        if ( cl_showclamp->integer )
            Com_Printf( "low clamp %i\n", cl.serverTime - prevtime );
        cl.time = prevtime;
        cl.lerpfrac = 0;
    } else {
        cl.lerpfrac = ( float ) ( cl.time - prevtime ) / ( float ) ( cl.serverTime - prevtime );
    }

    if ( com_timedemo->integer ) {
        cl.lerpfrac = 1.0;
    }

    if ( cl_showclamp->integer == 2 )
        Com_Printf( "time %i, lerpfrac %.3f\n", cl.time, cl.lerpfrac );

}

static void CL_MeasureFPS( void ) {
	int time;

	time = Sys_Milliseconds();
	if( time - cls.measureTime > 1000 ) {
		cls.measureTime = time;
		cls.currentFPS = cls.measureFramecount;
		cls.measureFramecount = 0;
	}
	cls.measureFramecount++;
}

/*
==================
CL_Frame
 
==================
*/
void CL_Frame( int msec ) {
	static int extratime;
	static int extraphystime;
	int minFrameTime, minPhysTime, delta;

	time_after_ref = time_before_ref = 0;

	if ( !cl_running->integer ) {
		Cbuf_Execute();
        return;
	}

	extratime += msec;
	cls.realtime += msec;

	minFrameTime = 1;
	if( cl_async->integer ) {
		extraphystime += msec;

		Cvar_ClampInteger( cl_maxfps, 10, 120 );
		minPhysTime = 1000 / cl_maxfps->integer;
		if( extraphystime >= minPhysTime ) {
			CL_FinalizeCmd();
			extraphystime = 0;
		}

		if( r_maxfps->integer ) {
			if( r_maxfps->integer < 10 ) {
				Cvar_Set( "r_maxfps", "10" );
			}
			minFrameTime = 1000 / r_maxfps->integer;
		}
	} else {
		extraphystime = 0;
		if( cl_maxfps->integer ) {
			if( cl_maxfps->integer < 10 ) {
				Cvar_Set( "cl_maxfps", "10" );
			}
			minFrameTime = 1000 / cl_maxfps->integer;
		}
	}
	
	if( com_timedemo->integer ) {
		minFrameTime = 1;
	} else if( !cls.appactive && !sv_running->integer && com_sleep->integer ) {
		// run at 10 fps if background app
		minFrameTime = 100;
	}

	if( extratime < minFrameTime ) {
		// always sleep in background, but
		// never sleep if running a server
		if( ( !cls.appactive || cls.state < ca_active ) &&
                !sv_running->integer && com_sleep->integer )
        {
            if( com_sleep->integer > 1 ) {
    			delta = minFrameTime - extratime;
            } else {
                delta = 1;
            }
			Sys_Sleep( delta );
		}
		// send pending intentions
		CL_SendCmd();
		return;
	}

    if ( cls.demoplayback ) {
        sv_paused->integer = cl_paused->integer; // FIXME: HACK
    }

    // decide the simulation time
    cls.frametime = extratime * 0.001f;

    if( cls.frametime > 1.0 / 5 )
        cls.frametime = 1.0 / 5;

	// read next demo frame
	CL_DemoFrame( extratime );

    // calculate local time
    CL_SetClientTime();

    CL_CheckForReply();

	// read user intentions
	CL_UpdateCmd( extratime );

	// finalize them
	if( !cl_async->integer ) {
		CL_FinalizeCmd();
	}

	// send pending intentions
	CL_SendCmd();

    // resend a connection request if necessary
    CL_CheckForResend();

    // predict all unacknowledged movements
    CL_PredictMovement();

    // update the screen
    if ( host_speeds->integer )
        time_before_ref = Sys_Milliseconds();

    SCR_UpdateScreen();

    if ( host_speeds->integer )
        time_after_ref = Sys_Milliseconds();

    // update audio
    S_Update();

    CDAudio_Update();

    // advance local effects for next frame
    CL_RunDLights();
    CL_RunLightStyles();
    SCR_RunCinematic();
    Con_RunConsole();

	CL_MeasureFPS();

	if( vid_autorestart->integer && ( cvar_latchedModified & ( 1 << CVAR_SYSTEM_VIDEO ) ) ) {
		Cbuf_ExecuteText( EXEC_NOW, "vid_restart\n" );
	}

    //
    // check timeout
    //
    if( cls.netchan && Sys_Milliseconds() - cls.netchan->last_received > cl_timeout->value * 1000 ) {
        // timeoutcount saves debugger
        if ( ++cl.timeoutcount > 5 ) {
            Com_Error( ERR_DISCONNECT, "Server connection timed out." );
        }
    } else {
        cl.timeoutcount = 0;
    }

	extratime = 0;
    cls.framecount++;

}


//============================================================================

/*
====================
CL_CheckForPCMD
====================
*/
void CL_CheckForPCMD( int level, const char *string ) {
    char * p;

    if ( cls.demoplayback ) {
        return;
    }

    if ( level != PRINT_CHAT ) {
        return;
    }

    p = strstr( string, ": " );
    if ( !p ) {
        return;
    }

    if ( strncmp( p + 2, "!version", 8 ) ) {
        return;
    }

    if ( cl.replyTime && cls.realtime - cl.replyTime < 120000 ) {
        return;
    }

	Cvar_VariableStringBuffer( "version", cl.replyString, sizeof( cl.replyString ) );
    cl.replyTime = cls.realtime + 1024 + ( rand() & 1023 );
}

/*
====================
CL_CheckForReply
====================
*/
void CL_CheckForReply( void ) {
    if ( !cl.replyString[ 0 ] ) {
        return;
    }

    if ( cls.realtime < cl.replyTime ) {
        return;
    }

    Cbuf_AddText( "cmd say \"" );
    Cbuf_AddText( cl.replyString );
    Cbuf_AddText( "\"\n" );

    cl.replyString[ 0 ] = 0;
}

/*
====================
CL_Init
====================
*/
void CL_Init( void ) {
    if ( dedicated->integer ) {
        return; // nothing running on the client
    }

    if ( cl_running->integer ) {
        return;
    }

    // all archived variables will now be loaded

#if defined __unix__ || defined __sgi
    S_Init();
    CL_InitRefresh();
#else

    CL_InitRefresh();
    S_Init();	// sound must be initialized after window is created
#endif

    V_Init();
    SCR_Init();
    CDAudio_Init();
    CL_InitLocal();
    CL_InitInput();
    SCR_RegisterMedia();
    Con_SetupDC();
    CL_InitUI();

    UI_OpenMenu( UIMENU_MAIN );

    Con_RunConsole();

    Cvar_Set( "cl_running", "1" );
}


/*
===============
CL_Shutdown
 
FIXME: this is a callback from Sys_Quit and Com_Error.  It would be better
to run quit through here before the final handoff to the sys code.
===============
*/
void CL_Shutdown( void ) {
    static qboolean isdown = qfalse;

    if ( isdown ) {
        Com_Printf( "CL_Shutdown: recursive shutdown\n" );
        return;
    }
    isdown = qtrue;

    CL_Disconnect( ERR_SILENT, NULL );

    CL_ShutdownUI();
    CDAudio_Shutdown();
    S_Shutdown();
    CL_ShutdownInput();
    Con_Shutdown();
    CL_ShutdownRefresh();

    Cvar_Set( "cl_running", "0" );

    isdown = qfalse;
}
