This repository has been archived on 2022-06-27. You can view files and clone it, but cannot push or open issues or pull requests.
Xash3DArchive/engine/client/cl_game.c

1847 lines
41 KiB
C

//=======================================================================
// Copyright XashXT Group 2008 ©
// cl_game.c - client dlls interaction
//=======================================================================
#include "common.h"
#include "client.h"
#include "byteorder.h"
#include "matrix_lib.h"
#include "const.h"
#include "com_library.h"
#include "triangle_api.h"
#include "effects_api.h"
#include "pm_defs.h"
/*
====================
CL_GetClientEntity
Render callback for studio models
====================
*/
edict_t *CL_GetEdictByIndex( int index )
{
if( index == MAX_EDICTS - 1 )
return &clgame.playermodel;
if( index < 0 || index > clgame.globals->numEntities )
{
if( index == VIEWENT_INDEX ) return &clgame.viewent;
if( index == NULLENT_INDEX ) return NULL;
MsgDev( D_ERROR, "CL_GetEntityByIndex: invalid entindex %i\n", index );
return NULL;
}
if( EDICT_NUM( index )->free )
return NULL;
return EDICT_NUM( index );
}
/*
====================
CL_GetServerTime
don't clamped time that come from server
====================
*/
int CL_GetServerTime( void )
{
return cl.frame.servertime;
}
/*
====================
StudioEvent
Event callback for studio models
====================
*/
void CL_StudioEvent( dstudioevent_t *event, edict_t *pEdict )
{
clgame.dllFuncs.pfnStudioEvent( event, pEdict );
}
/*
====================
Studio_FxTransform
apply fxtransforms for each studio bone
====================
*/
void CL_StudioFxTransform( edict_t *ent, float transform[4][4] )
{
clgame.dllFuncs.pfnStudioFxTransform( ent, transform );
}
bool CL_GetAttachment( int entityIndex, int number, vec3_t origin, vec3_t angles )
{
edict_t *ed = CL_GetEdictByIndex( entityIndex );
if( !ed || ed->free || !ed->pvClientData )
return false;
number = bound( 1, number, MAXSTUDIOATTACHMENTS );
if( origin ) VectorAdd( ed->v.origin, ed->pvClientData->origin[number-1], origin );
if( angles ) VectorCopy( ed->pvClientData->angles[number-1], angles );
return true;
}
bool CL_SetAttachment( int entityIndex, int number, vec3_t origin, vec3_t angles )
{
edict_t *ed = CL_GetEdictByIndex( entityIndex );
if( !ed || ed->free || !ed->pvClientData )
return false;
if( number > MAXSTUDIOATTACHMENTS )
number = MAXSTUDIOATTACHMENTS;
if( origin ) VectorCopy( origin, ed->pvClientData->origin[number-1] );
if( angles ) VectorCopy( angles, ed->pvClientData->angles[number-1] );
return true;
}
byte CL_GetMouthOpen( int entityIndex )
{
edict_t *ed;
if( entityIndex <= 0 || entityIndex >= clgame.globals->numEntities )
return 0;
ed = CL_GetEdictByIndex( entityIndex );
if( !ed || ed->free || !ed->pvClientData )
return 0;
return ed->pvClientData->mouth.open;
}
prevframe_t *CL_GetPrevFrame( int entityIndex )
{
edict_t *pEnt = CL_GetEdictByIndex( entityIndex );
if( !pEnt || !pEnt->pvClientData )
return NULL;
return &pEnt->pvClientData->latched;
}
/*
====================
CL_InitTitles
parse all messages that declared in titles.txt
and hold them into permament memory pool
====================
*/
static void CL_InitTitles( const char *filename )
{
token_t token;
client_textmessage_t state;
char *pName = NULL;
char *pMessage = NULL;
script_t *script;
Mem_Set( &state, 0, sizeof( state ));
Mem_Set( &clgame.titles, 0, sizeof( clgame.titles ));
clgame.numTitles = 0;
script = Com_OpenScript( filename, NULL, 0 );
if( !script ) return;
while( script )
{
if( !Com_ReadToken( script, SC_ALLOW_NEWLINES, &token ))
break;
if( !com.stricmp( token.string, "$" )) // skip dollar
Com_ReadToken( script, false, &token );
if( !com.stricmp( token.string, "position" ))
{
Com_ReadFloat( script, false, &state.x );
Com_ReadFloat( script, false, &state.y );
}
else if( !com.stricmp( token.string, "effect" ))
{
Com_ReadUlong( script, false, &state.effect );
}
else if( !com.stricmp( token.string, "fadein" ))
{
Com_ReadFloat( script, false, &state.fadein );
}
else if( !com.stricmp( token.string, "fadeout" ))
{
Com_ReadFloat( script, false, &state.fadeout );
}
else if( !com.stricmp( token.string, "fxtime" ))
{
Com_ReadFloat( script, false, &state.fxtime );
}
else if( !com.stricmp( token.string, "holdtime" ))
{
Com_ReadFloat( script, false, &state.holdtime );
}
else if( !com.strnicmp( token.string, "color2", 6 ))
{
uint temp;
Com_ReadUlong( script, false, &temp );
state.r2 = temp;
Com_ReadUlong( script, false, &temp );
state.g2 = temp;
Com_ReadUlong( script, false, &temp );
state.b2 = temp;
if( Com_ReadUlong( script, false, &temp ))
state.a2 = temp; // optional, nevers used in Half-Life
else state.a2 = 255;
}
else if( !com.stricmp( token.string, "color" ))
{
uint temp;
Com_ReadUlong( script, false, &temp );
state.r1 = temp;
Com_ReadUlong( script, false, &temp );
state.g1 = temp;
Com_ReadUlong( script, false, &temp );
state.b1 = temp;
if( Com_ReadUlong( script, false, &temp ))
state.a1 = temp; // optional, nevers used in Half-Life
else state.a1 = 255;
}
else if( !com.stricmp( token.string, "{" ))
{
client_textmessage_t *newmsg;
const char *buffer, *end;
size_t size;
Com_SaveToken( script, &token );
// parse the message
buffer = script->text;
Com_SkipBracedSection( script, 0 );
end = script->text - 1; // skip '}'
if( !buffer ) buffer = script->buffer; // missing body ?
if( !end ) end = script->buffer + script->size; // EOF ?
size = end - buffer;
pMessage = Mem_Alloc( cls.mempool, size + 1 );
Mem_Copy( pMessage, buffer, size );
pMessage[size] = 0; // terminator
// create new client textmessage
newmsg = &clgame.titles[clgame.numTitles];
Mem_Copy( newmsg, &state, sizeof( *newmsg ));
newmsg->pName = pName;
newmsg->pMessage = pMessage;
clgame.numTitles++; // registered
}
else
{
// begin message declaration
pName = com.stralloc( cls.mempool, token.string, __FILE__, __LINE__ );
}
}
Com_CloseScript( script );
}
/*
====================
CL_GetLocalPlayer
Render callback for studio models
====================
*/
edict_t *CL_GetLocalPlayer( void )
{
if( cls.state == ca_active )
{
edict_t *player = EDICT_NUM( cl.playernum + 1 );
if( CL_IsValidEdict( player )) return player;
Host_Error( "CL_GetLocalPlayer: invalid edict\n" );
}
return NULL;
}
/*
====================
CL_GetMaxlients
Render callback for studio models
====================
*/
int CL_GetMaxClients( void )
{
return com.atoi( cl.configstrings[CS_MAXCLIENTS] );
}
/*
================
CL_FadeAlpha
================
*/
void CL_FadeAlpha( float starttime, float endtime, rgba_t color )
{
float time, fade_time;
if( starttime == 0 )
{
MakeRGBA( color, 255, 255, 255, 255 );
return;
}
time = (cls.realtime * 0.001f) - starttime;
if( time >= endtime )
{
MakeRGBA( color, 255, 255, 255, 0 );
return;
}
// fade time is 1/4 of endtime
fade_time = endtime / 4;
fade_time = bound( 0.3f, fade_time, 10.0f );
color[0] = color[1] = color[2] = 255;
// fade out
if(( endtime - time ) < fade_time )
color[3] = bound( 0, ((endtime - time) * 1.0f / fade_time) * 255, 255 );
else color[3] = 255;
}
void CL_DrawHUD( int state )
{
if( state == CL_ACTIVE && !cl.video_prepped )
state = CL_LOADING;
if( state == CL_ACTIVE && cl.refdef.paused )
state = CL_PAUSED;
clgame.dllFuncs.pfnRedraw( cl.time * 0.001f, state );
if( state == CL_ACTIVE )
clgame.dllFuncs.pfnFrame( cl.time * 0.001f );
}
static void CL_CreateUserMessage( int lastnum, const char *szMsgName, int svc_num, int iSize, pfnUserMsgHook pfn )
{
user_message_t *msg;
if( lastnum == clgame.numMessages )
{
if( clgame.numMessages == MAX_USER_MESSAGES )
{
MsgDev( D_ERROR, "CL_CreateUserMessage: user messages limit is out\n" );
return;
}
clgame.numMessages++;
}
msg = clgame.msg[lastnum];
// clear existing or allocate new one
if( msg ) Mem_Set( msg, 0, sizeof( *msg ));
else msg = clgame.msg[lastnum] = Mem_Alloc( cls.mempool, sizeof( *msg ));
com.strncpy( msg->name, szMsgName, CS_SIZE );
msg->number = svc_num;
msg->size = iSize;
msg->func = pfn;
}
void CL_LinkUserMessage( char *pszName, const int svc_num )
{
user_message_t *msg;
char *end;
char msgName[CS_SIZE];
int i, msgSize;
if( !pszName || !*pszName ) return; // ignore blank names
com.strncpy( msgName, pszName, CS_SIZE );
end = com.strchr( msgName, '@' );
if( !end )
{
MsgDev( D_ERROR, "CL_LinkUserMessage: can't register message %s\n", msgName );
return;
}
msgSize = com.atoi( end + 1 );
msgName[end-msgName] = '\0'; // remove size description from MsgName
// search message by name to link with
for( i = 0; i < clgame.numMessages; i++ )
{
msg = clgame.msg[i];
if( !msg ) continue;
if( !com.strcmp( msg->name, msgName ))
{
msg->number = svc_num;
msg->size = msgSize;
return;
}
}
// create an empty message
CL_CreateUserMessage( i, msgName, svc_num, msgSize, NULL );
}
void CL_SortUserMessages( void )
{
// FIXME: implement
}
void CL_ParseUserMessage( sizebuf_t *net_buffer, int svc_num )
{
user_message_t *msg;
int i, iSize;
byte *pbuf;
// NOTE: any user message parse on engine, not in client.dll
if( svc_num >= clgame.numMessages )
{
// unregister message can't be parsed
Host_Error( "CL_ParseUserMessage: illegible server message %d\n", svc_num );
return;
}
// search for svc_num
for( i = 0; i < clgame.numMessages; i++ )
{
msg = clgame.msg[i];
if( !msg ) continue;
if( msg->number == svc_num )
break;
}
if( i == clgame.numMessages || !msg )
{
// unregistered message ?
Host_Error( "CL_ParseUserMessage: illegible server message %d\n", svc_num );
return;
}
iSize = msg->size;
pbuf = NULL;
// message with variable sizes receive an actual size as first byte
if( iSize == -1 ) iSize = MSG_ReadByte( net_buffer );
if( iSize > 0 ) pbuf = Mem_Alloc( clgame.private, iSize );
// parse user message into buffer
MSG_ReadData( net_buffer, pbuf, iSize );
if( msg->func ) msg->func( msg->name, iSize, pbuf );
else MsgDev( D_WARN, "CL_ParseUserMessage: %s not hooked\n", msg->name );
if( pbuf ) Mem_Free( pbuf );
}
void CL_InitEdict( edict_t *pEdict )
{
Com_Assert( pEdict == NULL );
Com_Assert( pEdict->pvPrivateData != NULL );
Com_Assert( pEdict->pvClientData != NULL );
pEdict->v.pContainingEntity = pEdict; // make cross-links for consistency
pEdict->pvClientData = (cl_priv_t *)Mem_Alloc( cls.mempool, sizeof( cl_priv_t ));
pEdict->pvPrivateData = NULL;
pEdict->serialnumber = NUM_FOR_EDICT( pEdict ); // merged on first update
pEdict->free = false;
}
void CL_FreeEdict( edict_t *pEdict )
{
Com_Assert( pEdict == NULL );
Com_Assert( pEdict->free );
// unlink from world
CL_UnlinkEdict( pEdict );
if( pEdict->pvClientData ) Mem_Free( pEdict->pvClientData );
if( pEdict->pvPrivateData ) Mem_Free( pEdict->pvPrivateData );
Mem_Set( pEdict, 0, sizeof( *pEdict ));
// mark edict as freed
pEdict->freetime = cl.time * 0.001f;
pEdict->v.nextthink = -1;
pEdict->free = true;
}
edict_t *CL_AllocEdict( void )
{
edict_t *pEdict;
int i;
for( i = 1; i < clgame.globals->numEntities; i++ )
{
pEdict = EDICT_NUM( i );
// the first couple seconds of client time can involve a lot of
// freeing and allocating, so relax the replacement policy
if( pEdict->free && ( pEdict->freetime < 2.0f || ((cl.time * 0.001f) - pEdict->freetime) > 0.5f ))
{
CL_InitEdict( pEdict );
return pEdict;
}
}
if( i == clgame.globals->maxEntities )
Host_Error( "CL_AllocEdict: no free edicts\n" );
clgame.globals->numEntities++;
pEdict = EDICT_NUM( i );
CL_InitEdict( pEdict );
return pEdict;
}
void CL_InitEdicts( void )
{
edict_t *ent;
int i;
clgame.globals->maxEntities = com.atoi( cl.configstrings[CS_MAXEDICTS] );
clgame.globals->maxClients = com.atoi( cl.configstrings[CS_MAXCLIENTS] );
clgame.edicts = Mem_Realloc( cls.mempool, clgame.edicts, sizeof( edict_t ) * clgame.globals->maxEntities );
ent = EDICT_NUM( 0 );
if( ent->free ) CL_InitEdict( ent );
ent->v.model = MAKE_STRING( cl.configstrings[CS_MODELS+1] );
ent->v.modelindex = 1; // world model
ent->v.solid = SOLID_BSP;
ent->v.movetype = MOVETYPE_PUSH;
clgame.globals->numEntities = 1;
for( i = 0, ent = EDICT_NUM( 1 ); i < clgame.globals->maxEntities; i++, ent++ )
ent->free = true; // mark all edicts as freed
clgame.globals->mapname = MAKE_STRING( cl.configstrings[CS_NAME] );
clgame.globals->deathmatch = Cvar_VariableInteger( "deathmatch" );
clgame.globals->coop = Cvar_VariableInteger( "coop" );
clgame.globals->teamplay = Cvar_VariableInteger( "teamplay" );
clgame.globals->serverflags = com.atoi( cl.configstrings[CS_SERVERFLAGS] );
// clear prevstate
Mem_Set( &clgame.viewent.pvClientData->latched, 0, sizeof( prevframe_t ));
CL_ClearWorld ();
}
void CL_FreeEdicts( void )
{
int i;
edict_t *ent;
for( i = 0; i < clgame.globals->numEntities; i++ )
{
ent = CL_GetEdictByIndex( i );
if( !CL_IsValidEdict( ent )) continue;
CL_FreeEdict( ent );
}
// clear globals
StringTable_Clear( clgame.hStringTable );
clgame.globals->numEntities = 0;
}
bool CL_IsValidEdict( const edict_t *e )
{
if( !e ) return false;
if( e->free ) return false;
if( e == EDICT_NUM( 0 )) return false; // world is the read-only entity
if( !e->pvServerData ) return false;
// edict without pvPrivateData is valid edict
// server.dll know how allocate it
return true;
}
const char *CL_ClassName( const edict_t *e )
{
if( !e ) return "(null)";
if( e->free ) return "freed";
return STRING( e->v.classname );
}
/*
===============================================================================
CGame Builtin Functions
===============================================================================
*/
/*
=========
pfnMemAlloc
=========
*/
static void *pfnMemAlloc( size_t cb, const char *filename, const int fileline )
{
return com.malloc( clgame.private, cb, filename, fileline );
}
/*
=========
pfnMemFree
=========
*/
static void pfnMemFree( void *mem, const char *filename, const int fileline )
{
com.free( mem, filename, fileline );
}
/*
=============
pfnLoadShader
=============
*/
shader_t pfnLoadShader( const char *szShaderName, int fShaderNoMip )
{
if( !re ) return 0; // render not initialized
if( !szShaderName || !*szShaderName )
{
MsgDev( D_ERROR, "CL_LoadShader: invalid shadername (%s)\n", fShaderNoMip ? "nomip" : "generic" );
return -1;
}
if( fShaderNoMip )
return re->RegisterShader( szShaderName, SHADER_NOMIP );
return re->RegisterShader( szShaderName, SHADER_GENERIC );
}
/*
=============
pfnFillRGBA
=============
*/
void pfnFillRGBA( int x, int y, int width, int height, byte r, byte g, byte b, byte alpha )
{
rgba_t color;
if( !re ) return;
MakeRGBA( color, r, g, b, alpha );
re->SetColor( color );
re->DrawStretchPic( x, y, width, height, 0, 0, 1, 1, cls.fillShader );
re->SetColor( NULL );
}
/*
=============
pfnDrawImageExt
=============
*/
void pfnDrawImageExt( HSPRITE shader, float x, float y, float w, float h, float s1, float t1, float s2, float t2 )
{
if( !re ) return;
re->DrawStretchPic( x, y, w, h, s1, t1, s2, t2, shader );
re->SetColor( NULL );
}
/*
=============
pfnSetColor
=============
*/
void pfnSetColor( byte r, byte g, byte b, byte a )
{
rgba_t color;
if( !re ) return; // render not initialized
MakeRGBA( color, r, g, b, a );
re->SetColor( color );
}
/*
=============
pfnCvarSetString
=============
*/
void pfnCvarSetString( const char *szName, const char *szValue )
{
Cvar_Set( szName, szValue );
}
/*
=============
pfnCvarSetValue
=============
*/
void pfnCvarSetValue( const char *szName, float flValue )
{
Cvar_SetValue( szName, flValue );
}
/*
=============
pfnGetCvarFloat
=============
*/
float pfnGetCvarFloat( const char *szName )
{
return Cvar_VariableValue( szName );
}
/*
=============
pfnGetCvarString
=============
*/
char* pfnGetCvarString( const char *szName )
{
return Cvar_VariableString( szName );
}
/*
=============
pfnAddCommand
=============
*/
void pfnAddCommand( const char *cmd_name, xcommand_t func, const char *cmd_desc )
{
if( !cmd_name || !*cmd_name ) return;
if( !cmd_desc ) cmd_desc = ""; // hidden for makehelep system
// NOTE: if( func == NULL ) cmd will be forwarded to a server
Cmd_AddCommand( cmd_name, func, cmd_desc );
}
/*
=============
pfnDelCommand
=============
*/
void pfnDelCommand( const char *cmd_name )
{
if( !cmd_name || !*cmd_name ) return;
Cmd_RemoveCommand( cmd_name );
}
/*
=============
pfnHookUserMsg
=============
*/
void pfnHookUserMsg( const char *szMsgName, pfnUserMsgHook pfn )
{
user_message_t *msg;
int i;
// ignore blank names
if( !szMsgName || !*szMsgName ) return;
// second call can change msgFunc
for( i = 0; i < clgame.numMessages; i++ )
{
msg = clgame.msg[i];
if( !msg ) continue;
if( !com.strcmp( szMsgName, msg->name ))
{
if( msg->func != pfn )
msg->func = pfn;
return;
}
}
// allocate a new one
CL_CreateUserMessage( i, szMsgName, 0, 0, pfn );
}
/*
=============
pfnServerCmd
=============
*/
void pfnServerCmd( const char *szCmdString )
{
// server command adding in cmds queue
Cbuf_AddText( va( "cmd %s", szCmdString ));
}
/*
=============
pfnClientCmd
=============
*/
void pfnClientCmd( const char *szCmdString )
{
// client command executes immediately
Cmd_ExecuteString( szCmdString );
}
/*
=============
pfnSetKeyDest
=============
*/
void pfnSetKeyDest( int key_dest )
{
switch( key_dest )
{
case KEY_GAME:
cls.key_dest = key_game;
break;
case KEY_HUDMENU:
cls.key_dest = key_hudmenu;
break;
default:
MsgDev( D_ERROR, "CL_SetKeyDest: wrong destination %i!\n", key_dest );
break;
}
}
/*
=============
pfnGetPlayerInfo
=============
*/
void pfnGetPlayerInfo( int player_num, hud_player_info_t *pinfo )
{
// FIXME: implement
static hud_player_info_t null_info;
Mem_Copy( pinfo, &null_info, sizeof( null_info ));
}
/*
=============
pfnTextMessageGet
=============
*/
client_textmessage_t *pfnTextMessageGet( const char *pName )
{
int i;
// find desired message
for( i = 0; i < clgame.numTitles; i++ )
{
if( !com.strcmp( pName, clgame.titles[i].pName ))
return clgame.titles + i;
}
return NULL; // found nothing
}
/*
=============
pfnCmdArgc
=============
*/
int pfnCmdArgc( void )
{
return Cmd_Argc();
}
/*
=============
pfnCmdArgv
=============
*/
char *pfnCmdArgv( int argc )
{
if( argc >= 0 && argc < Cmd_Argc())
return Cmd_Argv( argc );
return "";
}
/*
=============
pfnPlaySoundByName
=============
*/
void pfnPlaySoundByName( const char *szSound, float volume, int pitch, const float *org )
{
S_StartLocalSound( szSound, volume, pitch, org );
}
/*
=============
pfnPlaySoundByIndex
=============
*/
void pfnPlaySoundByIndex( int iSound, float volume, int pitch, const float *org )
{
// make sure what we in-bounds
iSound = bound( 0, iSound, MAX_SOUNDS );
if( cl.sound_precache[iSound] == 0 )
{
MsgDev( D_ERROR, "CL_PlaySoundByIndex: invalid sound handle %i\n", iSound );
return;
}
S_StartSound( org, cl.refdef.viewentity, CHAN_AUTO, cl.sound_precache[iSound], volume, ATTN_NORM, pitch, 0 );
}
/*
=============
pfnDrawCenterPrint
called each frame
=============
*/
void pfnDrawCenterPrint( void )
{
char *start;
int l, x, y, w;
rgba_t color;
if( !clgame.centerPrintTime ) return;
CL_FadeAlpha( clgame.centerPrintTime, scr_centertime->value, color );
if( *( int *)color == 0xFFFFFFFF )
{
clgame.centerPrintTime = 0;
return;
}
re->SetColor( color );
start = clgame.centerPrint;
y = clgame.centerPrintY - clgame.centerPrintLines * BIGCHAR_HEIGHT / 2;
while( 1 )
{
char linebuffer[1024];
for ( l = 0; l < 50; l++ )
{
if ( !start[l] || start[l] == '\n' )
break;
linebuffer[l] = start[l];
}
linebuffer[l] = 0;
w = clgame.centerPrintCharWidth * com.cstrlen( linebuffer );
x = ( SCREEN_WIDTH - w )>>1;
SCR_DrawStringExt( x, y, clgame.centerPrintCharWidth, BIGCHAR_HEIGHT, linebuffer, color, false );
y += clgame.centerPrintCharWidth * 1.5;
while( *start && ( *start != '\n' )) start++;
if( !*start ) break;
start++;
}
if( re ) re->SetColor( NULL );
}
/*
=============
pfnCenterPrint
called once from message
=============
*/
void CL_CenterPrint( const char *text, int y, int charWidth )
{
char *s;
com.strncpy( clgame.centerPrint, text, sizeof( clgame.centerPrint ));
clgame.centerPrintTime = cls.realtime * 0.001f;
clgame.centerPrintY = y;
clgame.centerPrintCharWidth = charWidth;
// count the number of lines for centering
clgame.centerPrintLines = 1;
s = clgame.centerPrint;
while( *s )
{
if( *s == '\n' )
clgame.centerPrintLines++;
s++;
}
}
/*
=============
pfnDrawString
=============
*/
void pfnDrawString( int x, int y, int width, int height, const char *text )
{
if( !text || !*text )
{
MsgDev( D_ERROR, "SCR_DrawStringExt: passed null string!\n" );
return;
}
SCR_DrawStringExt( x, y, width, height, text, g_color_table[7], false );
if( re ) re->SetColor( NULL );
}
/*
=============
pfnGetImageSize
=============
*/
void pfnGetDrawParms( int *w, int *h, int *f, int frame, shader_t shader )
{
if( re ) re->GetParms( w, h, f, frame, shader );
else
{
if( w ) *w = 0;
if( h ) *h = 0;
if( f ) *f = 1;
}
}
/*
=============
pfnSetDrawParms
=============
*/
void pfnSetDrawParms( shader_t handle, kRenderMode_t rendermode, int frame )
{
if( re ) re->SetParms( handle, rendermode, frame );
}
/*
=============
pfnGetViewAngles
return interpolated angles from previous frame
=============
*/
void pfnGetViewAngles( float *angles )
{
if( angles == NULL ) return;
VectorCopy( cl.refdef.viewangles, angles );
}
/*
=============
pfnIsSpectateOnly
=============
*/
int pfnIsSpectateOnly( void )
{
// FIXME: implement
return 0;
}
/*
=============
pfnGetClientTime
=============
*/
float pfnGetClientTime( void )
{
return cl.time * 0.001f;
}
/*
=============
CL_GetLerpFrac
=============
*/
float CL_GetLerpFrac( void )
{
return cl.lerpFrac;
}
/*
=============
pfnGetMaxClients
=============
*/
int pfnGetMaxClients( void )
{
return com.atoi( cl.configstrings[CS_MAXCLIENTS] );
}
/*
=============
pfnGetViewModel
can return NULL
=============
*/
edict_t* pfnGetViewModel( void )
{
return &clgame.viewent;
}
/*
=============
pfnGetModelPtr
returns pointer to a studiomodel
=============
*/
static void *pfnGetModelPtr( edict_t *pEdict )
{
if( !pEdict || pEdict->free )
return NULL;
return Mod_Extradata( pEdict->v.modelindex );
}
/*
=============
pfnGetScreenInfo
get actual screen info
=============
*/
int pfnGetScreenInfo( SCREENINFO *pscrinfo )
{
int i;
if( !pscrinfo ) return 0;
// setup screen info
pscrinfo->iRealWidth = scr_width->integer;
pscrinfo->iRealHeight = scr_height->integer;
if(pscrinfo->iFlags & SCRINFO_VIRTUALSPACE )
{
// virtual screen space 640x480
// see cl_screen.c from Quake3 code for more details
pscrinfo->iWidth = SCREEN_WIDTH;
pscrinfo->iHeight = SCREEN_HEIGHT;
}
else
{
pscrinfo->iWidth = scr_width->integer;
pscrinfo->iHeight = scr_height->integer;
}
// TODO: build real table of fonts widthInChars
for( i = 0; i < 256; i++ )
pscrinfo->charWidths[i] = SMALLCHAR_WIDTH;
pscrinfo->iCharHeight = SMALLCHAR_HEIGHT;
return 1;
}
/*
=============
pfnGetAttachment
=============
*/
static void pfnGetAttachment( const edict_t *pEdict, int iAttachment, float *rgflOrigin, float *rgflAngles )
{
if( !pEdict )
{
if( rgflOrigin ) VectorClear( rgflOrigin );
if( rgflAngles ) VectorClear( rgflAngles );
return;
}
CL_GetAttachment( pEdict->serialnumber, iAttachment, rgflOrigin, rgflAngles );
}
/*
=============
pfnPointContents
=============
*/
static int pfnPointContents( const float *rgflVector )
{
return CL_PointContents( rgflVector );
}
/*
=============
pfnTraceLine
=============
*/
static void pfnTraceLine( const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr )
{
trace_t result;
if( VectorIsNAN( v1 ) || VectorIsNAN( v2 ))
Host_Error( "TraceLine: NAN errors detected '%f %f %f', '%f %f %f'\n", v1[0], v1[1], v1[2], v2[0], v2[1], v2[2] );
result = CL_Move( v1, vec3_origin, vec3_origin, v2, fNoMonsters, pentToSkip );
if( ptr ) Mem_Copy( ptr, &result, sizeof( *ptr ));
}
/*
=================
pfnTraceToss
=================
*/
static void pfnTraceToss( edict_t* pent, edict_t* pentToIgnore, TraceResult *ptr )
{
trace_t result;
if( !CL_IsValidEdict( pent ))
{
MsgDev( D_WARN, "CL_MoveToss: invalid entity %s\n", CL_ClassName( pent ));
return;
}
result = CL_MoveToss( pent, pentToIgnore );
if( ptr ) Mem_Copy( ptr, &result, sizeof( *ptr ));
}
/*
=================
pfnTraceHull
=================
*/
static void pfnTraceHull( const float *v1, const float *v2, int fNoMonsters, int hullNumber, edict_t *pentToSkip, TraceResult *ptr )
{
trace_t result;
float *mins, *maxs;
hullNumber = bound( 0, hullNumber, 3 );
mins = GI->client_mins[hullNumber];
maxs = GI->client_maxs[hullNumber];
if( VectorIsNAN( v1 ) || VectorIsNAN( v2 ))
Host_Error( "TraceHull: NAN errors detected '%f %f %f', '%f %f %f'\n", v1[0], v1[1], v1[2], v2[0], v2[1], v2[2] );
result = CL_Move( v1, mins, maxs, v2, fNoMonsters, pentToSkip );
if( ptr ) Mem_Copy( ptr, &result, sizeof( *ptr ));
}
static void pfnTraceModel( const float *v1, const float *v2, edict_t *pent, TraceResult *ptr )
{
trace_t result;
if( !CL_IsValidEdict( pent ))
{
MsgDev( D_WARN, "CL_TraceModel: invalid entity %s\n", CL_ClassName( pent ));
return;
}
if( VectorIsNAN( v1 ) || VectorIsNAN( v2 ))
Host_Error( "TraceModel: NAN errors detected '%f %f %f', '%f %f %f'\n", v1[0], v1[1], v1[2], v2[0], v2[1], v2[2] );
result = CL_ClipMoveToEntity( pent, v1, pent->v.mins, pent->v.maxs, v2, MASK_SOLID, 0 );
if( ptr ) Mem_Copy( ptr, &result, sizeof( *ptr ));
}
static const char *pfnTraceTexture( edict_t *pTextureEntity, const float *v1, const float *v2 )
{
if( VectorIsNAN( v1 ) || VectorIsNAN( v2 ))
Host_Error( "TraceTexture: NAN errors detected '%f %f %f', '%f %f %f'\n", v1[0], v1[1], v1[2], v2[0], v2[1], v2[2] );
if( !pTextureEntity || pTextureEntity->free ) return NULL;
return CL_ClipMoveToEntity( pTextureEntity, v1, vec3_origin, vec3_origin, v2, MASK_SOLID, 0 ).pTexName;
}
/*
=============
pfnPrecacheEvent
=============
*/
static word pfnPrecacheEvent( int type, const char* psz )
{
// FIXME: implement
return 0;
}
/*
=============
pfnHookEvent
=============
*/
static void pfnHookEvent( const char *name, pfnEventHook pfn )
{
// FIXME: implement
}
/*
=============
pfnPlaybackEvent
=============
*/
static void pfnPlaybackEvent( int flags, const edict_t *pInvoker, word eventindex, float delay, event_args_t *args )
{
// FIXME: implement
}
/*
=============
pfnKillEvent
=============
*/
static void pfnKillEvent( word eventindex )
{
// FIXME: implement
}
/*
=============
CL_AllocString
=============
*/
string_t CL_AllocString( const char *szValue )
{
return StringTable_SetString( clgame.hStringTable, szValue );
}
/*
=============
CL_GetString
=============
*/
const char *CL_GetString( string_t iString )
{
return StringTable_GetString( clgame.hStringTable, iString );
}
/*
=================
pfnFindExplosionPlane
=================
*/
static void pfnFindExplosionPlane( const float *origin, float radius, float *result )
{
CL_FindExplosionPlane( origin, radius, result );
}
/*
=================
pfnDecalIndexFromName
=================
*/
static int pfnDecalIndexFromName( const char *szDecalName )
{
int i;
// look through the loaded sprite name list for SpriteName
for( i = 0; i < MAX_DECALS && cl.configstrings[CS_DECALS+i+1][0]; i++ )
{
if( !strcmp( szDecalName, cl.configstrings[CS_DECALS+i+1] ))
return cl.decal_shaders[i+1];
}
return 0; // invalid sprite
}
/*
=================
pfnDecalIndex
=================
*/
static int pfnDecalIndex( int id )
{
id = bound( 0, id, MAX_DECALS - 1 );
return cl.decal_shaders[id];
}
/*
=================
TriApi implementation
=================
*/
static void Tri_AddPolygon( void )
{
if( clgame.pTri->caps[TRI_SHADER] )
clgame.pTri->currentPolygon.shadernum = clgame.pTri->currentShader;
else clgame.pTri->currentPolygon.shadernum = cls.fillShader;
if( clgame.pTri->hasNormals )
clgame.pTri->currentPolygon.normals = clgame.pTri->normals;
else clgame.pTri->currentPolygon.normals = NULL; // no normals
clgame.pTri->currentPolygon.verts = clgame.pTri->verts;
clgame.pTri->currentPolygon.stcoords = clgame.pTri->stcoords;
clgame.pTri->currentPolygon.colors = clgame.pTri->colors;
clgame.pTri->currentPolygon.elems = clgame.pTri->elems;
clgame.pTri->currentPolygon.numverts = clgame.pTri->numVertex;
clgame.pTri->currentPolygon.numelems = clgame.pTri->numIndex;
clgame.pTri->numVertex = clgame.pTri->numIndex = clgame.pTri->numColor = 0;
if( re ) re->AddPolygon( &clgame.pTri->currentPolygon );
}
static void Tri_CheckOverflow( int numIndices, int numVertices )
{
if( numIndices > MAX_TRIELEMS )
Host_Error( "Tri_CheckOverflow: %i > MAX_TRIELEMS\n", numIndices );
if( numVertices > MAX_TRIVERTS )
Host_Error( "Tri_CheckOverflow: %i > MAX_TRIVERTS\n", numVertices );
if( clgame.pTri->numIndex + numIndices <= MAX_TRIELEMS && clgame.pTri->numVertex + numVertices <= MAX_TRIVERTS )
return;
Tri_AddPolygon();
}
static void Tri_SetVertex( float x, float y, float z )
{
uint oldIndex = clgame.pTri->numIndex;
switch( clgame.pTri->drawMode )
{
case TRI_LINES:
clgame.pTri->elems[clgame.pTri->numIndex++] = clgame.pTri->numVertex;
if( clgame.pTri->vertexState++ == 1 )
{
Tri_SetVertex( x + 1, y + 1, z + 1 );
clgame.pTri->vertexState = 0;
clgame.pTri->checkFlush = true; // Flush for long sequences of quads.
}
break;
case TRI_TRIANGLES:
clgame.pTri->elems[clgame.pTri->numIndex++] = clgame.pTri->numVertex;
if( clgame.pTri->vertexState++ == 2 )
{
clgame.pTri->vertexState = 0;
clgame.pTri->checkFlush = true; // Flush for long sequences of triangles.
}
break;
case TRI_QUADS:
if( clgame.pTri->vertexState++ < 3 )
{
clgame.pTri->elems[clgame.pTri->numIndex++] = clgame.pTri->numVertex;
}
else
{
// we've already done triangle (0, 1, 2), now draw (2, 3, 0)
clgame.pTri->elems[clgame.pTri->numIndex++] = clgame.pTri->numVertex - 1;
clgame.pTri->elems[clgame.pTri->numIndex++] = clgame.pTri->numVertex;
clgame.pTri->elems[clgame.pTri->numIndex++] = clgame.pTri->numVertex - 3;
clgame.pTri->vertexState = 0;
clgame.pTri->checkFlush = true; // flush for long sequences of quads.
}
break;
case TRI_TRIANGLE_STRIP:
if( clgame.pTri->numVertex + clgame.pTri->vertexState > MAX_TRIVERTS )
{
// This is a strip that's too big for us to buffer.
// (We can't just flush the buffer because we have to keep
// track of the last two vertices.
Host_Error( "Tri_SetVertex: overflow: %i > MAX_TRIVERTS\n", clgame.pTri->numVertex + clgame.pTri->vertexState );
}
if( clgame.pTri->vertexState++ < 3 )
{
clgame.pTri->elems[clgame.pTri->numIndex++] = clgame.pTri->numVertex;
}
else
{
// flip triangles between clockwise and counter clockwise
if( clgame.pTri->vertexState & 1 )
{
// draw triangle [n-2 n-1 n]
clgame.pTri->elems[clgame.pTri->numIndex++] = clgame.pTri->numVertex - 2;
clgame.pTri->elems[clgame.pTri->numIndex++] = clgame.pTri->numVertex - 1;
clgame.pTri->elems[clgame.pTri->numIndex++] = clgame.pTri->numVertex;
}
else
{
// draw triangle [n-1 n-2 n]
clgame.pTri->elems[clgame.pTri->numIndex++] = clgame.pTri->numVertex - 1;
clgame.pTri->elems[clgame.pTri->numIndex++] = clgame.pTri->numVertex - 2;
clgame.pTri->elems[clgame.pTri->numIndex++] = clgame.pTri->numVertex;
}
}
break;
case TRI_POLYGON:
case TRI_TRIANGLE_FAN: // same as polygon
if( clgame.pTri->numVertex + clgame.pTri->vertexState > MAX_TRIVERTS )
{
// This is a polygon or fan that's too big for us to buffer.
// (We can't just flush the buffer because we have to keep
// track of the starting vertex.
Host_Error( "Tri_SetVertex: overflow: %i > MAX_TRIVERTS\n", clgame.pTri->numVertex + clgame.pTri->vertexState );
}
if( clgame.pTri->vertexState++ < 3 )
{
clgame.pTri->elems[clgame.pTri->numIndex++] = clgame.pTri->numVertex;
}
else
{
// draw triangle [0 n-1 n]
clgame.pTri->elems[clgame.pTri->numIndex++] = clgame.pTri->numVertex - ( clgame.pTri->vertexState - 1 );
clgame.pTri->elems[clgame.pTri->numIndex++] = clgame.pTri->numVertex - 1;
clgame.pTri->elems[clgame.pTri->numIndex++] = clgame.pTri->numVertex;
}
break;
default:
Host_Error( "Tri_SetVertex: unknown mode: %i\n", clgame.pTri->drawMode );
break;
}
// copy current vertex
clgame.pTri->verts[clgame.pTri->numVertex][0] = x;
clgame.pTri->verts[clgame.pTri->numVertex][1] = y;
clgame.pTri->verts[clgame.pTri->numVertex][2] = z;
// copy current color
if( clgame.pTri->numColor == 0 )
Vector4Copy( clgame.pTri->color, clgame.pTri->colors[clgame.pTri->numVertex] );
clgame.pTri->numVertex++;
// flush buffer if needed
if( clgame.pTri->checkFlush )
Tri_CheckOverflow( clgame.pTri->numIndex - oldIndex, clgame.pTri->vertexState );
}
void TriRenderMode( kRenderMode_t mode )
{
if( !re ) return;
// FIXME: implement it properly
// re->SetParms( clgame.pTri->currentShader, mode, clgame.pTri->currentFrame );
}
void TriBind( shader_t shader, int frame )
{
clgame.pTri->currentShader = shader;
clgame.pTri->currentFrame = frame;
}
void TriBegin( TRI_DRAW mode )
{
clgame.pTri->drawMode = mode;
clgame.pTri->vertexState = 0;
clgame.pTri->checkFlush = false;
clgame.pTri->hasNormals = false;
clgame.pTri->verts = clgame.pTri->vertsArray[clgame.pTri->numPolys];
clgame.pTri->normals = clgame.pTri->normalsArray[clgame.pTri->numPolys];
clgame.pTri->stcoords = clgame.pTri->stcoordsArray[clgame.pTri->numPolys];
clgame.pTri->colors = clgame.pTri->colorsArray[clgame.pTri->numPolys];
clgame.pTri->elems = clgame.pTri->elemsArray[clgame.pTri->numPolys];
}
void TriEnd( void )
{
if( clgame.pTri->numIndex == 0 ) return;
Tri_AddPolygon();
if( ++clgame.pTri->numPolys > MAX_TRIPOLYS );
clgame.pTri->numPolys = 0; // reset it
clgame.pTri->verts = NULL;
clgame.pTri->normals = NULL;
clgame.pTri->stcoords = NULL;
clgame.pTri->colors = NULL;
clgame.pTri->elems = NULL;
}
void TriEnable( int cap )
{
if( cap < 0 || cap > TRI_MAXCAPS ) return;
clgame.pTri->caps[cap] = true;
}
void TriDisable( int cap )
{
if( cap < 0 || cap > TRI_MAXCAPS ) return;
clgame.pTri->caps[cap] = false;
}
void TriVertex3f( float x, float y, float z )
{
Tri_SetVertex( x, y, z );
}
void TriVertex3fv( const float *v )
{
if( !v ) return;
Tri_SetVertex( v[0], v[1], v[2] );
}
void TriNormal3f( float x, float y, float z )
{
clgame.pTri->hasNormals = true; // draw has normals
clgame.pTri->normals[clgame.pTri->numVertex][0] = x;
clgame.pTri->normals[clgame.pTri->numVertex][1] = y;
clgame.pTri->normals[clgame.pTri->numVertex][2] = z;
}
void TriNormal3fv( const float *v )
{
if( !v ) return;
clgame.pTri->hasNormals = true; // draw has normals
clgame.pTri->normals[clgame.pTri->numVertex][0] = v[0];
clgame.pTri->normals[clgame.pTri->numVertex][1] = v[1];
clgame.pTri->normals[clgame.pTri->numVertex][2] = v[2];
}
void TriColor4f( float r, float g, float b, float a )
{
if( clgame.pTri->colors )
{
clgame.pTri->colors[clgame.pTri->numVertex][0] = (byte)bound( 0, r * 255, 255 );
clgame.pTri->colors[clgame.pTri->numVertex][1] = (byte)bound( 0, g * 255, 255 );
clgame.pTri->colors[clgame.pTri->numVertex][2] = (byte)bound( 0, b * 255, 255 );
clgame.pTri->colors[clgame.pTri->numVertex][3] = (byte)bound( 0, a * 255, 255 );
clgame.pTri->numColor++;
}
else
{
clgame.pTri->color[0] = (byte)bound( 0, r * 255, 255 );
clgame.pTri->color[1] = (byte)bound( 0, g * 255, 255 );
clgame.pTri->color[2] = (byte)bound( 0, b * 255, 255 );
clgame.pTri->color[3] = (byte)bound( 0, a * 255, 255 );
}
}
void TriColor4ub( byte r, byte g, byte b, byte a )
{
if( clgame.pTri->colors )
{
clgame.pTri->colors[clgame.pTri->numVertex][0] = r;
clgame.pTri->colors[clgame.pTri->numVertex][1] = g;
clgame.pTri->colors[clgame.pTri->numVertex][2] = b;
clgame.pTri->colors[clgame.pTri->numVertex][3] = a;
clgame.pTri->numColor++;
}
else Vector4Set( clgame.pTri->color, r, g, b, a );
}
void TriTexCoord2f( float u, float v )
{
clgame.pTri->stcoords[clgame.pTri->numVertex][0] = u;
clgame.pTri->stcoords[clgame.pTri->numVertex][1] = v;
}
void TriCullFace( TRI_CULL mode )
{
// FIXME: implement
}
void TriScreenToWorld( float *screen, float *world )
{
if( !re ) return;
re->ScreenToWorld( screen, world );
}
int TriWorldToScreen( float *world, float *screen )
{
if( !re ) return 0;
return re->WorldToScreen( world, screen );
}
void TriFog( float flFogColor[3], float flStart, float flEnd, int bOn )
{
// FIXME: implement
}
static triapi_t gTriApi =
{
sizeof( triapi_t ),
TriRenderMode,
TriBegin,
TriEnd,
TriEnable,
TriDisable,
TriVertex3f,
TriVertex3fv,
TriNormal3f,
TriNormal3fv,
TriColor4f,
TriColor4ub,
TriTexCoord2f,
TriBind,
TriCullFace,
TriScreenToWorld,
TriWorldToScreen,
TriFog
};
static efxapi_t gEfxApi =
{
sizeof( efxapi_t ),
pfnAddParticle,
pfnAddDecal,
pfnAddDLight,
pfnFindExplosionPlane,
pfnDecalIndexFromName,
pfnDecalIndex,
};
// engine callbacks
static cl_enginefuncs_t gEngfuncs =
{
sizeof( cl_enginefuncs_t ),
pfnMemAlloc,
pfnMemCopy,
pfnMemFree,
pfnLoadShader,
pfnFillRGBA,
pfnDrawImageExt,
pfnSetColor,
pfnCVarRegister,
pfnCvarSetString,
pfnCvarSetValue,
pfnGetCvarFloat,
pfnGetCvarString,
pfnAddCommand,
pfnHookUserMsg,
pfnDelCommand,
pfnServerCmd,
pfnClientCmd,
pfnSetKeyDest,
pfnGetPlayerInfo,
pfnTextMessageGet,
pfnCmdArgc,
pfnCmdArgv,
pfnAlertMessage,
pfnPlaySoundByName,
pfnPlaySoundByIndex,
AngleVectors,
pfnDrawCenterPrint,
CL_CenterPrint,
pfnDrawString,
pfnGetDrawParms,
pfnSetDrawParms,
pfnGetViewAngles,
CL_GetEdictByIndex,
CL_GetLocalPlayer,
pfnIsSpectateOnly,
pfnGetClientTime,
CL_GetLerpFrac,
pfnGetMaxClients,
pfnGetViewModel,
pfnGetModelPtr,
pfnGetScreenInfo,
pfnGetAttachment,
pfnPointContents,
pfnTraceLine,
pfnTraceToss,
pfnTraceHull,
pfnTraceModel,
pfnTraceTexture,
pfnPrecacheEvent,
pfnHookEvent,
pfnPlaybackEvent,
pfnKillEvent,
CL_AllocString,
CL_GetString,
pfnRandomLong,
pfnRandomFloat,
pfnLoadFile,
pfnFileExists,
pfnRemoveFile,
pfnLoadLibrary,
pfnGetProcAddress,
pfnFreeLibrary,
Host_Error,
&gTriApi,
&gEfxApi
};
void CL_UnloadProgs( void )
{
// initialize game
clgame.dllFuncs.pfnShutdown();
CL_FreeEdicts();
CL_FreeEdict( &clgame.viewent );
CL_FreeEdict( &clgame.playermodel );
StringTable_Delete( clgame.hStringTable );
Com_FreeLibrary( clgame.hInstance );
Mem_FreePool( &cls.mempool );
Mem_FreePool( &clgame.private );
}
bool CL_LoadProgs( const char *name )
{
static CLIENTAPI GetClientAPI;
static cl_globalvars_t gpGlobals;
static tri_state_t gpTriState;
static playermove_t gpMove;
string libpath;
if( clgame.hInstance ) CL_UnloadProgs();
// setup globals
clgame.globals = &gpGlobals;
// initialize TriAPI
clgame.pmove = &gpMove;
clgame.pTri = &gpTriState;
clgame.pTri->currentPolygon.fognum = -1;
clgame.pTri->numPolys = 0;
Com_BuildPath( name, libpath );
cls.mempool = Mem_AllocPool( "Client Edicts Zone" );
clgame.private = Mem_AllocPool( "Client Private Zone" );
clgame.hInstance = Com_LoadLibrary( libpath );
if( !clgame.hInstance ) return false;
GetClientAPI = (CLIENTAPI)Com_GetProcAddress( clgame.hInstance, "CreateAPI" );
if( !GetClientAPI )
{
MsgDev( D_ERROR, "CL_LoadProgs: failed to get address of CreateAPI proc\n" );
return false;
}
if( !GetClientAPI( &clgame.dllFuncs, &gEngfuncs, clgame.globals ))
{
MsgDev( D_ERROR, "CL_LoadProgs: can't init client API\n" );
return false;
}
// 65535 unique strings should be enough ...
clgame.hStringTable = StringTable_Create( "Client", 0x10000 );
// register svc_bad message
pfnHookUserMsg( "bad", NULL );
CL_LinkUserMessage( "bad@0", svc_bad );
CL_InitEdict( &clgame.viewent );
clgame.viewent.serialnumber = VIEWENT_INDEX;
CL_InitEdict( &clgame.playermodel );
clgame.playermodel.serialnumber = MAX_EDICTS - 1;
CL_InitTitles( "scripts/titles.txt" );
// initialize game
clgame.dllFuncs.pfnInit();
return true;
}