/*
===========================================================================
Copyright (C) 1999 - 2005, Id Software, Inc.
Copyright (C) 2000 - 2013, Raven Software, Inc.
Copyright (C) 2001 - 2013, Activision, Inc.
Copyright (C) 2005 - 2015, ioquake3 contributors
Copyright (C) 2013 - 2015, OpenJK contributors
This file is part of the OpenJK source code.
OpenJK is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
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, see .
===========================================================================
*/
// cl_main.c -- client main loop
#include "../server/exe_headers.h"
#include "client.h"
#include "client_ui.h"
#include
#include "../ghoul2/G2.h"
#include "qcommon/stringed_ingame.h"
#include "sys/sys_loadlib.h"
#include "qcommon/ojk_saved_game.h"
#include
#define RETRANSMIT_TIMEOUT 3000 // time between connection packet retransmits
cvar_t *cl_renderer;
cvar_t *cl_nodelta;
cvar_t *cl_debugMove;
cvar_t *cl_noprint;
cvar_t *cl_timeout;
cvar_t *cl_packetdup;
cvar_t *cl_timeNudge;
cvar_t *cl_showTimeDelta;
cvar_t *cl_newClock=0;
cvar_t *cl_shownet;
cvar_t *cl_avidemo;
cvar_t *cl_pano;
cvar_t *cl_panoNumShots;
cvar_t *cl_skippingcin;
cvar_t *cl_endcredits;
cvar_t *cl_freelook;
cvar_t *cl_sensitivity;
cvar_t *cl_mouseAccel;
cvar_t *cl_showMouseRate;
cvar_t *cl_framerate;
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_allowAltEnter;
cvar_t *cl_inGameVideo;
cvar_t *cl_consoleKeys;
cvar_t *cl_consoleUseScanCode;
clientActive_t cl;
clientConnection_t clc;
clientStatic_t cls;
// Structure containing functions exported from refresh DLL
refexport_t re;
static void *rendererLib = NULL;
//RAZFIXME: BAD BAD, maybe? had to move it out of ghoul2_shared.h -> CGhoul2Info_v at the least..
IGhoul2InfoArray &_TheGhoul2InfoArray( void ) {
return re.TheGhoul2InfoArray();
}
static void CL_ShutdownRef( qboolean restarting );
void CL_InitRef( void );
void CL_CheckForResend( void );
/*
=======================================================================
CLIENT RELIABLE COMMAND COMMUNICATION
=======================================================================
*/
/*
======================
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 );
if ( clc.reliableCommands[ index ] ) {
Z_Free( clc.reliableCommands[ index ] );
}
clc.reliableCommands[ index ] = CopyString( cmd );
}
//======================================================================
/*
=================
CL_FlushMemory
Called by CL_MapLoading, CL_Connect_f, and CL_ParseGamestate the only
ways a client gets into a game
Also called by Com_Error
=================
*/
void CL_FlushMemory( void ) {
// clear sounds (moved higher up within this func to avoid the odd sound stutter)
S_DisableSounds();
// unload the old VM
CL_ShutdownCGame();
CL_ShutdownUI();
if ( re.Shutdown ) {
re.Shutdown( qfalse, qfalse ); // don't destroy window or context
}
//rwwFIXMEFIXME: The game server appears to continue running, so clearing common bsp data causes crashing and other bad things
/*
CM_ClearMap();
*/
cls.soundRegistered = qfalse;
cls.rendererStarted = qfalse;
}
/*
=====================
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();
Key_SetCatcher( 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
memset( cls.updateInfoString, 0, sizeof( cls.updateInfoString ) );
// memset( clc.serverMessage, 0, sizeof( clc.serverMessage ) );
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();
Q_strncpyz( cls.servername, "localhost", sizeof(cls.servername) );
cls.state = CA_CHALLENGING; // so the connect screen is drawn
Key_SetCatcher( 0 );
SCR_UpdateScreen();
clc.connectTime = -RETRANSMIT_TIMEOUT;
NET_StringToAdr( cls.servername, &clc.serverAddress);
// we don't need a challenge on the localhost
CL_CheckForResend();
}
CL_FlushMemory();
}
/*
=====================
CL_ClearState
Called before parsing a gamestate
=====================
*/
void CL_ClearState (void) {
CL_ShutdownCGame();
S_StopAllSounds();
memset( &cl, 0, sizeof( cl ) );
}
/*
=====================
CL_FreeReliableCommands
Wipes all reliableCommands strings from clc
=====================
*/
void CL_FreeReliableCommands( void )
{
// wipe the client connection
for ( int i = 0 ; i < MAX_RELIABLE_COMMANDS ; i++ ) {
if ( clc.reliableCommands[i] ) {
Z_Free( clc.reliableCommands[i] );
clc.reliableCommands[i] = NULL;
}
}
}
/*
=====================
CL_Disconnect
Called when a connection, 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( void ) {
if ( !com_cl_running || !com_cl_running->integer ) {
return;
}
if (cls.uiStarted)
UI_SetActiveMenu( NULL,NULL );
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 ();
CL_FreeReliableCommands();
extern void CL_FreeServerCommands(void);
CL_FreeServerCommands();
memset( &clc, 0, sizeof( clc ) );
cls.state = CA_DISCONNECTED;
// allow cheats locally
Cvar_Set( "timescale", "1" );//jic we were skipping
Cvar_Set( "skippingCinematic", "0" );//jic we were skipping
}
/*
===================
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( void ) {
const char *cmd;
char string[MAX_STRING_CHARS];
cmd = Cmd_Argv(0);
// ignore key up commands
if ( cmd[0] == '-' ) {
return;
}
if ( cls.state != CA_ACTIVE || cmd[0] == '+' ) {
Com_Printf ("Unknown command \"%s\"\n", cmd);
return;
}
if ( Cmd_Argc() > 1 ) {
Com_sprintf( string, sizeof(string), "%s %s", cmd, Cmd_Args() );
} else {
Q_strncpyz( string, cmd, sizeof(string) );
}
CL_AddReliableCommand( string );
}
/*
======================================================================
CONSOLE COMMANDS
======================================================================
*/
/*
==================
CL_ForwardToServer_f
==================
*/
void CL_ForwardToServer_f( void ) {
if ( cls.state != CA_ACTIVE ) {
Com_Printf ("Not connected to a server.\n");
return;
}
// don't forward the first argument
if ( Cmd_Argc() > 1 ) {
CL_AddReliableCommand( Cmd_Args() );
}
}
/*
==================
CL_Disconnect_f
==================
*/
void CL_Disconnect_f( void ) {
SCR_StopCinematic();
//FIXME:
// TA codebase added additional CA_CINEMATIC check below, presumably so they could play cinematics
// in the menus when disconnected, although having the SCR_StopCinematic() call above is weird.
// Either there's a bug, or the new version of that function conditionally-doesn't stop cinematics...
//
if ( cls.state != CA_DISCONNECTED && cls.state != CA_CINEMATIC ) {
Com_Error (ERR_DISCONNECT, "Disconnected from server");
}
}
/*
=================
CL_Vid_Restart_f
Restart the video subsystem
=================
*/
void CL_Vid_Restart_f( void ) {
S_StopAllSounds(); // don't let them loop during the restart
S_BeginRegistration(); // all sound handles are now invalid
CL_ShutdownRef(qtrue);
CL_ShutdownUI();
CL_ShutdownCGame();
//rww - sof2mp does this here, but it seems to cause problems in this codebase.
// CM_ClearMap();
cls.rendererStarted = qfalse;
cls.uiStarted = qfalse;
cls.cgameStarted = qfalse;
cls.soundRegistered = qfalse;
CL_InitRef();
CL_StartHunkUsers();
// unpause so the cgame definately gets a snapshot and renders a frame
Cvar_Set( "cl_paused", "0" );
}
/*
=================
CL_Snd_Restart_f
Restart the sound subsystem
The cgame and game must also be forced to restart because
handles will be invalid
=================
*/
void CL_Snd_Restart_f( void ) {
S_Shutdown();
S_Init();
// CL_Vid_Restart_f();
extern qboolean s_soundMuted;
s_soundMuted = qfalse; // we can play again
S_RestartMusic();
extern void S_ReloadAllUsedSounds(void);
S_ReloadAllUsedSounds();
extern void AS_ParseSets(void);
AS_ParseSets();
}
/*
==================
CL_Configstrings_f
==================
*/
void 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 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" );
}
//====================================================================
void UI_UpdateConnectionString( const char *string );
/*
=================
CL_CheckForResend
Resend a connect message if the last one has timed out
=================
*/
void CL_CheckForResend( void ) {
int port;
char info[MAX_INFO_STRING];
// if ( cls.state == CA_CINEMATIC )
if ( cls.state == CA_CINEMATIC || CL_IsRunningInGameCinematic())
{
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++;
// requesting a challenge
switch ( cls.state ) {
case CA_CONNECTING:
UI_UpdateConnectionString( va("(%i)", clc.connectPacketCount ) );
NET_OutOfBandPrint(NS_CLIENT, clc.serverAddress, "getchallenge");
break;
case CA_CHALLENGING:
// sending back the challenge
port = Cvar_VariableIntegerValue("net_qport");
UI_UpdateConnectionString( va("(%i)", clc.connectPacketCount ) );
Q_strncpyz( info, Cvar_InfoString( CVAR_USERINFO ), sizeof( info ) );
Info_SetValueForKey( info, "protocol", va("%i", PROTOCOL_VERSION ) );
Info_SetValueForKey( info, "qport", va("%i", port ) );
Info_SetValueForKey( info, "challenge", va("%i", clc.challenge ) );
NET_OutOfBandPrint( NS_CLIENT, clc.serverAddress, "connect \"%s\"", info );
// 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_ACTIVE ) {
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 (FIXME: connection dropped dialog)
Com_Printf( "Server disconnected for unknown reason\n" );
CL_Disconnect();
}
/*
=================
CL_ConnectionlessPacket
Responses to broadcasts, etc
=================
*/
void CL_ConnectionlessPacket( netadr_t from, msg_t *msg ) {
char *s;
const char *c;
MSG_BeginReading( 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 ( !strcmp(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;
}
return;
}
// server connection
if ( !strcmp(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_VariableIntegerValue( "net_qport" ) );
cls.state = CA_CONNECTED;
clc.lastPacketSentTime = -9999; // send first packet immediately
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 (!strcmp(c, "disconnect")) {
CL_DisconnectPacket( from );
return;
}
// echo request from server
if ( !strcmp(c, "echo") ) {
NET_OutOfBandPrint( NS_CLIENT, from, "%s", Cmd_Argv(1) );
return;
}
// print request from server
if ( !strcmp(c, "print") ) {
s = MSG_ReadString( msg );
UI_UpdateConnectionMessageString( s );
Com_Printf( "%s", 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 ) {
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 < 8 ) {
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 (!Netchan_Process( &clc.netchan, msg) ) {
return; // out of order, duplicated, etc
}
clc.lastPacketTime = cls.realtime;
CL_ParseServerMessage( msg );
}
/*
==================
CL_CheckTimeout
==================
*/
void CL_CheckTimeout( void ) {
//
// check timeout
//
if ( ( !CL_CheckPaused() || !sv_paused->integer )
// && cls.state >= CA_CONNECTED && cls.state != CA_CINEMATIC
&& cls.state >= CA_CONNECTED && (cls.state != CA_CINEMATIC && !CL_IsRunningInGameCinematic())
&& cls.realtime - clc.lastPacketTime > cl_timeout->value*1000) {
if (++cl.timeoutcount > 5) { // timeoutcount saves debugger
Com_Printf ("\nServer connection timed out.\n");
CL_Disconnect ();
return;
}
} else {
cl.timeoutcount = 0;
}
}
/*
==================
CL_CheckPaused
Check whether client has been paused.
==================
*/
qboolean CL_CheckPaused(void)
{
// if cl_paused->modified is set, the cvar has only been changed in
// this frame. Keep paused in this frame to ensure the server doesn't
// lag behind.
if(cl_paused->integer || cl_paused->modified)
return qtrue;
return qfalse;
}
//============================================================================
/*
==================
CL_CheckUserinfo
==================
*/
void CL_CheckUserinfo( void ) {
if ( cls.state < CA_CHALLENGING ) {
return;
}
// don't overflow the reliable command buffer when paused
if ( CL_CheckPaused() ) {
return;
}
// send a reliable userinfo update if needed
if ( cvar_modifiedFlags & CVAR_USERINFO ) {
cvar_modifiedFlags &= ~CVAR_USERINFO;
CL_AddReliableCommand( va("userinfo \"%s\"", Cvar_InfoString( CVAR_USERINFO ) ) );
}
}
/*
==================
CL_Frame
==================
*/
extern cvar_t *cl_newClock;
static unsigned int frameCount;
float avgFrametime=0.0;
void CL_Frame ( int msec,float fractionMsec ) {
if ( !com_cl_running->integer ) {
return;
}
// load the ref / cgame if needed
CL_StartHunkUsers();
if ( cls.state == CA_DISCONNECTED && !( Key_GetCatcher( ) & KEYCATCH_UI )
&& !com_sv_running->integer ) {
// if disconnected, bring up the menu
if (!CL_CheckPendingCinematic()) // this avoid having the menu flash for one frame before pending cinematics
{
UI_SetActiveMenu( "mainMenu",NULL );
}
}
// if recording an avi, lock to a fixed fps
if ( cl_avidemo->integer ) {
// save the current screen
if ( cls.state == CA_ACTIVE ) {
if (cl_avidemo->integer > 0) {
Cbuf_ExecuteText( EXEC_NOW, "screenshot silent\n" );
} else {
Cbuf_ExecuteText( EXEC_NOW, "screenshot_tga silent\n" );
}
}
// fixed time for next frame
if (cl_avidemo->integer > 0) {
msec = 1000 / cl_avidemo->integer;
} else {
msec = 1000 / -cl_avidemo->integer;
}
}
// save the msec before checking pause
cls.realFrametime = msec;
// decide the simulation time
cls.frametime = msec;
if(cl_framerate->integer)
{
avgFrametime+=msec;
char mess[256];
if(!(frameCount&0x1f))
{
sprintf(mess,"Frame rate=%f\n\n",1000.0f*(1.0/(avgFrametime/32.0f)));
// OutputDebugString(mess);
Com_Printf(mess);
avgFrametime=0.0f;
}
frameCount++;
}
cls.frametimeFraction=fractionMsec;
cls.realtime += msec;
cls.realtimeFraction+=fractionMsec;
if (cls.realtimeFraction>=1.0f)
{
if (cl_newClock&&cl_newClock->integer)
{
cls.realtime++;
}
cls.realtimeFraction-=1.0f;
}
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();
if (cl_pano->integer && cls.state == CA_ACTIVE) { //grab some panoramic shots
int i = 1;
int pref = cl_pano->integer;
int oldnoprint = cl_noprint->integer;
Con_Close();
cl_noprint->integer = 1; //hide the screen shot msgs
for (; i <= cl_panoNumShots->integer; i++) {
Cvar_SetValue( "pano", i );
SCR_UpdateScreen();// update the screen
Cbuf_ExecuteText( EXEC_NOW, va("screenshot %dpano%02d\n", pref, i) ); //grab this screen
}
Cvar_SetValue( "pano", 0 ); //done
cl_noprint->integer = oldnoprint;
}
if (cl_skippingcin->integer && !cl_endcredits->integer && !com_developer->integer ) {
if (cl_skippingcin->modified){
S_StopSounds(); //kill em all but music
cl_skippingcin->modified=qfalse;
Com_Printf (S_COLOR_YELLOW "%s", SE_GetString("CON_TEXT_SKIPPING"));
SCR_UpdateScreen();
}
} else {
// update the screen
SCR_UpdateScreen();
}
// update audio
S_Update();
// advance local effects for next frame
SCR_RunCinematic();
Con_RunConsole();
VR_HapticEndFrame();
cls.framecount++;
}
//============================================================================
/*
============
CL_ShutdownRef
============
*/
static void CL_ShutdownRef( qboolean restarting ) {
if ( re.Shutdown ) {
re.Shutdown( qtrue, restarting );
}
memset( &re, 0, sizeof( re ) );
if ( rendererLib != NULL ) {
Sys_UnloadDll (rendererLib);
rendererLib = NULL;
}
}
/*
============================
CL_StartSound
Convenience function for the sound system to be started
REALLY early on Xbox, helps with memory fragmentation.
============================
*/
void CL_StartSound( void ) {
if ( !cls.soundStarted ) {
cls.soundStarted = qtrue;
S_Init();
}
if ( !cls.soundRegistered ) {
cls.soundRegistered = qtrue;
S_BeginRegistration();
}
}
/*
============
CL_InitRenderer
============
*/
void CL_InitRenderer( void ) {
// this sets up the renderer and calls R_Init
re.BeginRegistration( &cls.glconfig, (intptr_t )&vr );
// load character sets
cls.charSetShader = re.RegisterShaderNoMip("gfx/2d/charsgrid_med");
cls.whiteShader = re.RegisterShader( "white" );
cls.consoleShader = re.RegisterShader( "console" );
g_console_field_width = cls.glconfig.vidWidth / SMALLCHAR_WIDTH - 2;
g_consoleField.widthInChars = g_console_field_width;
}
/*
============================
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->integer ) {
return;
}
if ( !cls.rendererStarted ) {
cls.rendererStarted = qtrue;
CL_InitRenderer();
}
if ( !cls.soundStarted ) {
cls.soundStarted = qtrue;
S_Init();
}
if ( !cls.soundRegistered ) {
cls.soundRegistered = qtrue;
S_BeginRegistration();
}
//we require the ui to be loaded here or else it crashes trying to access the ui on command line map loads
if ( !cls.uiStarted ) {
cls.uiStarted = qtrue;
CL_InitUI();
}
// if ( !cls.cgameStarted && cls.state > CA_CONNECTED && cls.state != CA_CINEMATIC ) {
if ( !cls.cgameStarted && cls.state > CA_CONNECTED && (cls.state != CA_CINEMATIC && !CL_IsRunningInGameCinematic()) )
{
cls.cgameStarted = qtrue;
CL_InitCGame();
}
}
/*
================
CL_RefPrintf
DLL glue
================
*/
void QDECL CL_RefPrintf( int print_level, const char *fmt, ...) {
va_list argptr;
char msg[MAXPRINTMSG];
va_start (argptr,fmt);
Q_vsnprintf(msg, sizeof(msg), fmt, argptr);
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
}
}
/*
============
String_GetStringValue
DLL glue, but highly reusuable DLL glue at that
============
*/
const char *String_GetStringValue( const char *reference )
{
#ifndef JK2_MODE
return SE_GetString(reference);
#else
return JK2SP_GetStringTextString(reference);
#endif
}
extern qboolean gbAlreadyDoingLoad;
extern void *gpvCachedMapDiskImage;
extern char gsCachedMapDiskImage[MAX_QPATH];
extern qboolean gbUsingCachedMapDataRightNow;
char *get_gsCachedMapDiskImage( void )
{
return gsCachedMapDiskImage;
}
void *get_gpvCachedMapDiskImage( void )
{
return gpvCachedMapDiskImage;
}
qboolean *get_gbUsingCachedMapDataRightNow( void )
{
return &gbUsingCachedMapDataRightNow;
}
qboolean *get_gbAlreadyDoingLoad( void )
{
return &gbAlreadyDoingLoad;
}
int get_com_frameTime( void )
{
return com_frameTime;
}
void *CL_Malloc(int iSize, memtag_t eTag, qboolean bZeroit, int iAlign)
{
return Z_Malloc(iSize, eTag, bZeroit);
}
/*
============
CL_InitRef
============
*/
extern qboolean S_FileExists( const char *psFilename );
extern bool CM_CullWorldBox (const cplane_t *frustum, const vec3pair_t bounds);
extern qboolean SND_RegisterAudio_LevelLoadEnd(qboolean bDeleteEverythingNotUsedThisLevel /* 99% qfalse */);
extern cvar_t *Cvar_Set2( const char *var_name, const char *value, qboolean force);
extern CMiniHeap *G2VertSpaceServer;
static CMiniHeap *GetG2VertSpaceServer( void ) {
return G2VertSpaceServer;
}
#ifdef _WIN32
#ifdef JK2_MODE
#define DEFAULT_RENDER_LIBRARY "rdjosp-vanilla"
#else
#define DEFAULT_RENDER_LIBRARY "rdsp-vanilla"
#endif
#else
#ifdef JK2_MODE
#define DEFAULT_RENDER_LIBRARY "rd-gles-jo"
#else
#define DEFAULT_RENDER_LIBRARY "rd-gles-ja"
#endif
#endif
void CL_InitRef( void ) {
refexport_t *ret;
static refimport_t rit;
char dllName[MAX_OSPATH];
GetRefAPI_t GetRefAPI;
Com_Printf( "----- Initializing Renderer ----\n" );
cl_renderer = Cvar_Get( "cl_renderer", DEFAULT_RENDER_LIBRARY, CVAR_ARCHIVE|CVAR_LATCH|CVAR_PROTECTED );
Com_sprintf( dllName, sizeof( dllName ), "%s_" ARCH_STRING DLL_EXT, cl_renderer->string );
if( !(rendererLib = Sys_LoadDll( dllName, qfalse )) && strcmp( cl_renderer->string, cl_renderer->resetString ) )
{
Com_Printf( "failed: trying to load fallback renderer\n" );
Cvar_ForceReset( "cl_renderer" );
Com_sprintf( dllName, sizeof( dllName ), DEFAULT_RENDER_LIBRARY "_" ARCH_STRING DLL_EXT );
rendererLib = Sys_LoadDll( dllName, qfalse );
}
if ( !rendererLib ) {
Com_Error( ERR_FATAL, "Failed to load renderer\n" );
}
memset( &rit, 0, sizeof( rit ) );
GetRefAPI = (GetRefAPI_t)Sys_LoadFunction( rendererLib, "GetRefAPI" );
if ( !GetRefAPI )
Com_Error( ERR_FATAL, "Can't load symbol GetRefAPI: '%s'", Sys_LibraryError() );
#define RIT(y) rit.y = y
RIT(CIN_PlayCinematic);
RIT(CIN_RunCinematic);
RIT(CIN_UploadCinematic);
RIT(CL_IsRunningInGameCinematic);
RIT(Cmd_AddCommand);
RIT(Cmd_Argc);
RIT(Cmd_ArgsBuffer);
RIT(Cmd_Argv);
RIT(Cmd_ExecuteString);
RIT(Cmd_RemoveCommand);
RIT(CM_ClusterPVS);
RIT(CM_CullWorldBox);
RIT(CM_DeleteCachedMap);
RIT(CM_DrawDebugSurface);
RIT(CM_PointContents);
RIT(Cvar_Get);
RIT(Cvar_Set);
RIT(Cvar_SetValue);
RIT(Cvar_CheckRange);
RIT(Cvar_VariableIntegerValue);
RIT(Cvar_VariableString);
RIT(Cvar_VariableStringBuffer);
RIT(Cvar_VariableValue);
RIT(FS_FCloseFile);
RIT(FS_FileIsInPAK);
RIT(FS_FOpenFileByMode);
RIT(FS_FOpenFileRead);
RIT(FS_FOpenFileWrite);
RIT(FS_FreeFile);
RIT(FS_FreeFileList);
RIT(FS_ListFiles);
RIT(FS_Read);
RIT(FS_ReadFile);
RIT(FS_Write);
RIT(FS_WriteFile);
RIT(Hunk_ClearToMark);
RIT(SND_RegisterAudio_LevelLoadEnd);
//RIT(SV_PointContents);
RIT(SV_Trace);
RIT(S_RestartMusic);
RIT(Z_Free);
rit.Malloc=CL_Malloc;
RIT(Z_MemSize);
RIT(Z_MorphMallocTag);
RIT(Hunk_ClearToMark);
rit.WIN_Init = WIN_Init;
rit.WIN_SetGamma = WIN_SetGamma;
rit.WIN_Shutdown = WIN_Shutdown;
rit.WIN_Present = WIN_Present;
rit.GL_GetProcAddress = WIN_GL_GetProcAddress;
rit.GL_ExtensionSupported = WIN_GL_ExtensionSupported;
rit.PD_Load = PD_Load;
rit.PD_Store = PD_Store;
rit.Error = Com_Error;
rit.FS_FileExists = S_FileExists;
rit.GetG2VertSpaceServer = GetG2VertSpaceServer;
rit.LowPhysicalMemory = Sys_LowPhysicalMemory;
rit.Milliseconds = Sys_Milliseconds2;
rit.Printf = CL_RefPrintf;
rit.SE_GetString = String_GetStringValue;
rit.SV_Trace = SV_Trace;
rit.gpvCachedMapDiskImage = get_gpvCachedMapDiskImage;
rit.gsCachedMapDiskImage = get_gsCachedMapDiskImage;
rit.gbUsingCachedMapDataRightNow = get_gbUsingCachedMapDataRightNow;
rit.gbAlreadyDoingLoad = get_gbAlreadyDoingLoad;
rit.com_frameTime = get_com_frameTime;
rit.SV_PointContents = SV_PointContents;
rit.saved_game = &ojk::SavedGame::get_instance();
rit.TBXR_useScreenLayer = VR_UseScreenLayer;
rit.TBXR_GetVRProjection = VR_GetVRProjection;
ret = GetRefAPI( REF_API_VERSION, &rit );
if ( !ret ) {
Com_Error (ERR_FATAL, "Couldn't initialize refresh" );
}
re = *ret;
Com_Printf( "-------------------------------\n");
// unpause so the cgame definately gets a snapshot and renders a frame
Cvar_Set( "cl_paused", "0" );
}
//===========================================================================================
void CL_CompleteCinematic( char *args, int argNum );
/*
====================
CL_Init
====================
*/
void CL_Init( void ) {
Com_Printf( "----- Client Initialization -----\n" );
#ifdef JK2_MODE
JK2SP_Register("con_text", SP_REGISTER_REQUIRED); //reference is CON_TEXT
JK2SP_Register("keynames", SP_REGISTER_REQUIRED); // reference is KEYNAMES
#endif
Con_Init ();
CL_ClearState ();
cls.state = CA_DISCONNECTED; // no longer CA_UNINITIALIZED
//cls.keyCatchers = KEYCATCH_CONSOLE;
cls.realtime = 0;
cls.realtimeFraction=0.0f; // fraction of a msec accumulated
CL_InitInput ();
//
// register our variables
//
cl_noprint = Cvar_Get( "cl_noprint", "0", 0 );
cl_timeout = Cvar_Get ("cl_timeout", "125", 0);
cl_timeNudge = Cvar_Get ("cl_timeNudge", "0", CVAR_TEMP );
cl_shownet = Cvar_Get ("cl_shownet", "0", CVAR_TEMP );
cl_showTimeDelta = Cvar_Get ("cl_showTimeDelta", "0", CVAR_TEMP );
cl_newClock = Cvar_Get ("cl_newClock", "1", 0);
cl_activeAction = Cvar_Get( "activeAction", "", CVAR_TEMP );
cl_avidemo = Cvar_Get ("cl_avidemo", "0", 0);
cl_pano = Cvar_Get ("pano", "0", 0);
cl_panoNumShots= Cvar_Get ("panoNumShots", "10", CVAR_ARCHIVE_ND);
cl_skippingcin = Cvar_Get ("skippingCinematic", "0", CVAR_ROM);
cl_endcredits = Cvar_Get ("cg_endcredits", "0", 0);
cl_yawspeed = Cvar_Get ("cl_yawspeed", "140", CVAR_ARCHIVE_ND);
cl_pitchspeed = Cvar_Get ("cl_pitchspeed", "140", CVAR_ARCHIVE_ND);
cl_anglespeedkey = Cvar_Get ("cl_anglespeedkey", "1.5", CVAR_ARCHIVE_ND);
cl_packetdup = Cvar_Get ("cl_packetdup", "1", CVAR_ARCHIVE_ND );
cl_run = Cvar_Get ("cl_run", "1", CVAR_ARCHIVE_ND);
cl_sensitivity = Cvar_Get ("sensitivity", "5", CVAR_ARCHIVE);
cl_mouseAccel = Cvar_Get ("cl_mouseAccel", "0", CVAR_ARCHIVE_ND);
cl_freelook = Cvar_Get( "cl_freelook", "1", CVAR_ARCHIVE_ND );
cl_showMouseRate = Cvar_Get ("cl_showmouserate", "0", 0);
cl_allowAltEnter = Cvar_Get ("cl_allowAltEnter", "1", CVAR_ARCHIVE_ND);
cl_inGameVideo = Cvar_Get ("cl_inGameVideo", "1", CVAR_ARCHIVE_ND);
cl_framerate = Cvar_Get ("cl_framerate", "0", CVAR_TEMP);
// init autoswitch so the ui will have it correctly even
// if the cgame hasn't been started
Cvar_Get ("cg_autoswitch", "1", CVAR_ARCHIVE);
m_pitch = Cvar_Get ("m_pitch", "0.022", CVAR_ARCHIVE_ND);
m_yaw = Cvar_Get ("m_yaw", "0.022", CVAR_ARCHIVE_ND);
m_forward = Cvar_Get ("m_forward", "0.25", CVAR_ARCHIVE_ND);
m_side = Cvar_Get ("m_side", "0.25", CVAR_ARCHIVE_ND);
m_filter = Cvar_Get ("m_filter", "0", CVAR_ARCHIVE_ND);
// ~ and `, as keys and characters
cl_consoleKeys = Cvar_Get( "cl_consoleKeys", "~ ` 0x7e 0x60 0xb2", CVAR_ARCHIVE);
cl_consoleUseScanCode = Cvar_Get( "cl_consoleUseScanCode", "1", CVAR_ARCHIVE );
// userinfo
#ifdef JK2_MODE
Cvar_Get ("name", "Kyle", CVAR_USERINFO | CVAR_ARCHIVE_ND );
#else
Cvar_Get ("name", "Jaden", CVAR_USERINFO | CVAR_ARCHIVE_ND );
#endif
#ifdef JK2_MODE
// this is required for savegame compatibility - not ever actually used
Cvar_Get ("snaps", "20", CVAR_USERINFO );
Cvar_Get ("sex", "male", CVAR_USERINFO | CVAR_ARCHIVE );
Cvar_Get ("handicap", "100", CVAR_USERINFO | CVAR_SAVEGAME );
#else
Cvar_Get ("sex", "f", CVAR_USERINFO | CVAR_ARCHIVE | CVAR_SAVEGAME | CVAR_NORESTART );
Cvar_Get ("snd", "jaden_fmle", CVAR_USERINFO | CVAR_ARCHIVE | CVAR_SAVEGAME | CVAR_NORESTART );//UI_SetSexandSoundForModel changes to match sounds.cfg for model
Cvar_Get ("handicap", "100", CVAR_USERINFO | CVAR_SAVEGAME | CVAR_NORESTART);
#endif
//
// 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 ("cinematic", CL_PlayCinematic_f);
Cmd_SetCommandCompletionFunc( "cinematic", CL_CompleteCinematic );
Cmd_AddCommand ("ingamecinematic", CL_PlayInGameCinematic_f);
Cmd_AddCommand ("uimenu", CL_GenericMenu_f);
Cmd_AddCommand ("datapad", CL_DataPad_f);
Cmd_AddCommand ("endscreendissolve", CL_EndScreenDissolve_f);
CL_InitRef();
CL_StartHunkUsers();
SCR_Init ();
Cbuf_Execute ();
Cvar_Set( "cl_running", "1" );
Com_Printf( "----- Client Initialization Complete -----\n" );
}
/*
===============
CL_Shutdown
===============
*/
void CL_Shutdown( void ) {
static qboolean recursive = qfalse;
if ( !com_cl_running || !com_cl_running->integer ) {
return;
}
Com_Printf( "----- CL_Shutdown -----\n" );
if ( recursive ) {
Com_Printf( "WARNING: Recursive CL_Shutdown called!\n" );
return;
}
recursive = qtrue;
CL_ShutdownUI();
CL_Disconnect();
S_Shutdown();
CL_ShutdownRef(qfalse);
Cmd_RemoveCommand ("cmd");
Cmd_RemoveCommand ("configstrings");
Cmd_RemoveCommand ("clientinfo");
Cmd_RemoveCommand ("snd_restart");
Cmd_RemoveCommand ("vid_restart");
Cmd_RemoveCommand ("disconnect");
Cmd_RemoveCommand ("cinematic");
Cmd_RemoveCommand ("ingamecinematic");
Cmd_RemoveCommand ("uimenu");
Cmd_RemoveCommand ("datapad");
Cmd_RemoveCommand ("endscreendissolve");
Cvar_Set( "cl_running", "0" );
recursive = qfalse;
memset( &cls, 0, sizeof( cls ) );
Com_Printf( "-----------------------\n" );
}