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
2022-06-27 01:14:58 +03:00

2685 lines
56 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 "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;
if( index < 0 || index > GI->max_edicts )
MsgDev( D_ERROR, "CL_GetEntityByIndex: invalid entindex %i\n", index );
return NULL;
}
if( EDICT_NUM( index )->free )
return NULL;
return EDICT_NUM( index );
}
/*
=============
CL_AllocString
=============
*/
string_t CL_AllocString( const char *szValue )
{
if( sys_sharedstrings->integer )
{
const char *newString;
newString = com.stralloc( clgame.stringspool, szValue, __FILE__, __LINE__ );
return newString - clgame.globals->pStringBase;
}
return StringTable_SetString( clgame.hStringTable, szValue );
}
/*
=============
CL_GetString
=============
*/
const char *CL_GetString( string_t iString )
{
if( sys_sharedstrings->integer )
return (clgame.globals->pStringBase + iString);
return StringTable_GetString( clgame.hStringTable, iString );
}
/*
====================
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;
number = bound( 0, number, MAXSTUDIOATTACHMENTS );
if( origin ) VectorCopy( origin, ed->pvClientData->origin[number] );
if( angles ) VectorCopy( angles, ed->pvClientData->angles[number] );
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;
}
lerpframe_t *CL_GetLerpFrame( int entityIndex )
{
edict_t *pEnt = CL_GetEdictByIndex( entityIndex );
if( !pEnt || !pEnt->pvClientData )
return NULL;
return &pEnt->pvClientData->frame;
}
/*
=============
CL_GetLerpFrac
=============
*/
float CL_GetLerpFrac( void )
{
return cl.lerpFrac;
}
/*
================
CL_FadeAlpha
================
*/
void CL_FadeAlpha( int starttime, int endtime, rgba_t color )
{
int time, fade_time;
if( starttime == 0 )
{
MakeRGBA( color, 255, 255, 255, 255 );
return;
}
time = cls.realtime - 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( 300, fade_time, 10000 );
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;
}
/*
=============
CL_DrawCenterPrint
called each frame
=============
*/
void CL_DrawCenterPrint( void )
{
char *start;
int l, x, y, w;
rgba_t color;
if( !re || !clgame.ds.centerPrintTime ) return;
CL_FadeAlpha( clgame.ds.centerPrintTime, scr_centertime->value * 1000, color );
if( *(int *)color == 0x00FFFFFF )
{
// faded out
clgame.ds.centerPrintTime = 0;
return;
}
re->SetColor( color );
start = clgame.ds.centerPrint;
y = clgame.ds.centerPrintY - clgame.ds.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.ds.centerPrintCharWidth * com.cstrlen( linebuffer );
x = (SCREEN_WIDTH - w)>>1;
SCR_DrawStringExt( x, y, clgame.ds.centerPrintCharWidth, SMALLCHAR_HEIGHT, linebuffer, color, false );
y += clgame.ds.centerPrintCharWidth * 1.5;
while( *start && ( *start != '\n' )) start++;
if( !*start ) break;
start++;
}
re->SetColor( NULL );
}
/*
=============
CL_CenterPrint
print centerscreen message
=============
*/
void CL_CenterPrint( const char *text, int y, int charWidth )
{
char *s;
com.strncpy( clgame.ds.centerPrint, text, sizeof( clgame.ds.centerPrint ));
clgame.ds.centerPrintTime = cls.realtime;
clgame.ds.centerPrintCharWidth = charWidth;
clgame.ds.centerPrintY = y;
// count the number of lines for centering
clgame.ds.centerPrintLines = 1;
s = clgame.ds.centerPrint;
while( *s )
{
if( *s == '\n' )
clgame.ds.centerPrintLines++;
s++;
}
}
/*
====================
SPR_AdjustSize
draw hudsprite routine
====================
*/
static void SPR_AdjustSize( float *x, float *y, float *w, float *h )
{
float xscale, yscale;
if( !x && !y && !w && !h ) return;
// scale for screen sizes
xscale = clgame.scrInfo.iRealWidth / (float)clgame.scrInfo.iWidth;
yscale = clgame.scrInfo.iRealHeight / (float)clgame.scrInfo.iHeight;
if( x ) *x *= xscale;
if( y ) *y *= yscale;
if( w ) *w *= xscale;
if( h ) *h *= yscale;
}
static bool SPR_Scissor( float *x, float *y, float *width, float *height, float *u0, float *v0, float *u1, float *v1 )
{
float dudx, dvdy;
// clip sub rect to sprite
if(( width == 0 ) || ( height == 0 ))
return false;
if( *x + *width <= clgame.ds.scissor_x )
return false;
if( *x >= clgame.ds.scissor_x + clgame.ds.scissor_width )
return false;
if( *y + *height <= clgame.ds.scissor_y )
return false;
if( *y >= clgame.ds.scissor_y + clgame.ds.scissor_height )
return false;
dudx = (*u1 - *u0) / *width;
dvdy = (*v1 - *v0) / *height;
if( *x < clgame.ds.scissor_x )
{
*u0 += (clgame.ds.scissor_x - *x) * dudx;
*width -= clgame.ds.scissor_x - *x;
*x = clgame.ds.scissor_x;
}
if( *x + *width > clgame.ds.scissor_x + clgame.ds.scissor_width )
{
*u1 -= (*x + *width - (clgame.ds.scissor_x + clgame.ds.scissor_width)) * dudx;
*width = clgame.ds.scissor_x + clgame.ds.scissor_width - *x;
}
if( *y < clgame.ds.scissor_y )
{
*v0 += (clgame.ds.scissor_y - *y) * dvdy;
*height -= clgame.ds.scissor_y - *y;
*y = clgame.ds.scissor_y;
}
if( *y + *height > clgame.ds.scissor_y + clgame.ds.scissor_height )
{
*v1 -= (*y + *height - (clgame.ds.scissor_y + clgame.ds.scissor_height)) * dvdy;
*height = clgame.ds.scissor_y + clgame.ds.scissor_height - *y;
}
return true;
}
/*
====================
SPR_DrawGeneric
draw hudsprite routine
====================
*/
static void SPR_DrawGeneric( int frame, float x, float y, float width, float height, const wrect_t *prc )
{
float s1, s2, t1, t2;
if( !re ) return;
if( width == -1 && height == -1 )
{
int w, h;
// undoc feature: get drawsizes from image
re->GetParms( &w, &h, NULL, frame, clgame.ds.hSprite );
width = w;
height = h;
}
if( prc )
{
// calc user-defined rectangle
s1 = (float)prc->left / width;
t1 = (float)prc->top / height;
s2 = (float)prc->right / width;
t2 = (float)prc->bottom / height;
width = prc->right - prc->left;
height = prc->bottom - prc->top;
}
else
{
s1 = t1 = 0.0f;
s2 = t2 = 1.0f;
}
// pass scissor test if supposed
if( clgame.ds.scissor_test && !SPR_Scissor( &x, &y, &width, &height, &s1, &t1, &s2, &t2 ))
return;
// scale for screen sizes
SPR_AdjustSize( &x, &y, &width, &height );
re->DrawStretchPic( x, y, width, height, s1, t1, s2, t2, clgame.ds.hSprite );
re->SetColor( NULL );
}
/*
====================
CL_InitTitles
parse all messages that declared in titles.txt
and hold them into permament memory pool
====================
*/
static void CL_InitTitles( const char *filename )
{
size_t fileSize;
byte *pMemFile;
// clear out any old data that's sitting around.
if( clgame.titles ) Mem_Free( clgame.titles );
clgame.titles = NULL;
clgame.numTitles = 0;
pMemFile = FS_LoadFile( filename, &fileSize );
if( !pMemFile ) return;
CL_TextMessageParse( pMemFile, fileSize );
Mem_Free( pMemFile );
}
/*
====================
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 clgame.globals->maxClients;
}
void CL_DrawCrosshair( void )
{
int x, y, width, height;
edict_t *pPlayer;
if( !re || clgame.ds.hCrosshair <= 0 || cl.refdef.crosshairangle[2] || !cl_crosshair->integer )
return;
pPlayer = CL_GetLocalPlayer();
if( pPlayer->v.health <= 0.0f || pPlayer->v.flags & FL_FROZEN )
return;
// camera on
if( pPlayer->serialnumber != cl.refdef.viewentity )
return;
// get crosshair dimension
width = clgame.ds.rcCrosshair.right - clgame.ds.rcCrosshair.left;
height = clgame.ds.rcCrosshair.bottom - clgame.ds.rcCrosshair.top;
x = clgame.scrInfo.iWidth / 2;
y = clgame.scrInfo.iHeight / 2;
// g-cont - cl.refdef.crosshairangle is the autoaim angle.
// if we're not using autoaim, just draw in the middle of the screen
if( !VectorIsNull( cl.refdef.crosshairangle ))
{
vec3_t angles;
vec3_t forward;
vec3_t point, screen;
VectorAdd( cl.refdef.viewangles, cl.refdef.crosshairangle, angles );
AngleVectors( angles, forward, NULL, NULL );
VectorAdd( cl.refdef.vieworg, forward, point );
re->WorldToScreen( point, screen );
x += 0.5f * screen[0] * clgame.scrInfo.iRealWidth + 0.5f;
y += 0.5f * screen[1] * clgame.scrInfo.iRealHeight + 0.5f;
}
clgame.ds.hSprite = clgame.ds.hCrosshair;
re->SetColor( clgame.ds.rgbaCrosshair );
re->SetParms( clgame.ds.hSprite, kRenderTransAlpha, 0 );
SPR_DrawGeneric( 0, x - 0.5f * width, y - 0.5f * height, -1, -1, &clgame.ds.rcCrosshair );
}
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 || state == CL_PAUSED )
{
CL_DrawCrosshair ();
CL_DrawCenterPrint ();
}
}
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 );
}
/*
=======================
CL_MsgNumbers
=======================
*/
static int CL_MsgNumbers( const void *a, const void *b )
{
user_message_t *msga, *msgb;
msga = (user_message_t *)a;
msgb = (user_message_t *)b;
if( msga == NULL )
{
if ( msgb == NULL ) return 0;
else return -1;
}
else if ( msgb == NULL ) return 1;
if( msga->number < msgb->number )
return -1;
return 1;
}
void CL_SortUserMessages( void )
{
// qsort( clgame.msg, clgame.numMessages, sizeof( user_message_t ), CL_MsgNumbers );
}
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 );
}
static void CL_RegisterEvent( int lastnum, const char *szEvName, pfnEventHook func )
{
user_event_t *ev;
if( lastnum == MAX_EVENTS )
{
MsgDev( D_ERROR, "CL_RegisterEvent: MAX_EVENTS hit!!!\n" );
return;
}
ev = clgame.events[lastnum];
// clear existing or allocate new one
if( ev ) Mem_Set( ev, 0, sizeof( *ev ));
else ev = clgame.events[lastnum] = Mem_Alloc( cls.mempool, sizeof( *ev ));
com.strncpy( ev->name, szEvName, CS_SIZE );
ev->func = func;
// ev->index will be set later
}
void CL_SetEventIndex( const char *szEvName, int ev_index )
{
user_event_t *ev;
int i;
if( !szEvName || !*szEvName ) return; // ignore blank names
// search event by name to link with
for( i = 0; i < MAX_EVENTS; i++ )
{
ev = clgame.events[i];
if( !ev ) break;
if( !com.strcmp( ev->name, szEvName ))
{
ev->index = ev_index;
return;
}
}
}
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 );
clgame.dllFuncs.pfnOnFreeEntPrivateData( pEdict );
// 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 = clgame.globals->maxClients + 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_InitWorld( void )
{
edict_t *ent;
int i;
ent = EDICT_NUM( 0 );
if( ent->free ) CL_InitEdict( ent );
ent->v.classname = MAKE_STRING( "worldspawn" );
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;
clgame.globals->coop = Cvar_VariableInteger( "coop" );
clgame.globals->teamplay = Cvar_VariableInteger( "teamplay" );
clgame.globals->deathmatch = Cvar_VariableInteger( "deathmatch" );
clgame.globals->serverflags = com.atoi( cl.configstrings[CS_SERVERFLAGS] );
for( i = 0; i < clgame.globals->maxClients; i++ )
{
// setup all clients
ent = EDICT_NUM( i + 1 );
CL_InitEdict( ent );
}
// clear viewmodel prevstate
Mem_Set( &clgame.viewent.pvClientData->frame, 0, sizeof( lerpframe_t ));
}
void CL_InitEdicts( void )
{
edict_t *e;
int i;
Com_Assert( clgame.edicts != NULL );
Com_Assert( clgame.baselines != NULL );
CL_UPDATE_BACKUP = ( clgame.globals->maxClients == 1 ) ? SINGLEPLAYER_BACKUP : MULTIPLAYER_BACKUP;
cl.frames = Mem_Alloc( clgame.mempool, sizeof( frame_t ) * CL_UPDATE_BACKUP );
clgame.edicts = Mem_Alloc( clgame.mempool, sizeof( edict_t ) * clgame.globals->maxEntities );
clgame.baselines = Mem_Alloc( clgame.mempool, sizeof( entity_state_t ) * clgame.globals->maxEntities );
for( i = 0, e = clgame.edicts; i < clgame.globals->maxEntities; i++, e++ )
e->free = true; // mark all edicts as freed
}
void CL_FreeEdicts( void )
{
int i;
edict_t *ent;
if( clgame.edicts )
{
for( i = 0; i < clgame.globals->maxEntities; i++ )
{
ent = EDICT_NUM( i );
if( ent->free ) continue;
CL_FreeEdict( ent );
}
Mem_Free( clgame.edicts );
}
if( cl.frames ) Mem_Free( cl.frames );
if( clgame.baselines ) Mem_Free( clgame.baselines );
// clear globals
if( sys_sharedstrings->integer )
Mem_EmptyPool( clgame.stringspool );
else StringTable_Clear( clgame.hStringTable );
clgame.globals->numEntities = 0;
clgame.baselines = NULL;
clgame.edicts = NULL;
cl.frames = NULL;
}
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
===============================================================================
*/
/*
=========
pfnSPR_Load
=========
*/
static HSPRITE pfnSPR_Load( const char *szPicName )
{
if( !re ) return 0; // render not initialized
if( !szPicName || !*szPicName )
{
MsgDev( D_ERROR, "CL_SpriteLoad: invalid spritename\n" );
return -1;
}
return re->RegisterShader( szPicName, SHADER_NOMIP ); // replace with SHADER_GENERIC ?
}
/*
=========
pfnSPR_Load
=========
*/
static int pfnSPR_Frames( HSPRITE hPic )
{
int numFrames;
if( !re ) return 1;
re->GetParms( NULL, NULL, &numFrames, 0, hPic );
return numFrames;
}
/*
=========
pfnSPR_Load
=========
*/
static int pfnSPR_Height( HSPRITE hPic, int frame )
{
int sprHeight;
if( !re ) return 0;
re->GetParms( NULL, &sprHeight, NULL, frame, hPic );
return sprHeight;
}
/*
=========
pfnSPR_Load
=========
*/
static int pfnSPR_Width( HSPRITE hPic, int frame )
{
int sprWidth;
if( !re ) return 0;
re->GetParms( &sprWidth, NULL, NULL, frame, hPic );
return sprWidth;
}
/*
=========
pfnSPR_Load
=========
*/
static void pfnSPR_Set( HSPRITE hPic, int r, int g, int b, int a )
{
rgba_t color;
if( !re ) return; // render not initialized
clgame.ds.hSprite = hPic;
MakeRGBA( color, r, g, b, a );
re->SetColor( color );
}
/*
=========
pfnSPR_Draw
=========
*/
static void pfnSPR_Draw( int frame, int x, int y, int width, int height, const wrect_t *prc )
{
if( !re ) return; // render not initialized
re->SetParms( clgame.ds.hSprite, kRenderNormal, frame );
SPR_DrawGeneric( frame, x, y, width, height, prc );
}
/*
=========
pfnSPR_DrawTrans
=========
*/
static void pfnSPR_DrawTrans( int frame, int x, int y, int width, int height, const wrect_t *prc )
{
if( !re ) return; // render not initialized
re->SetParms( clgame.ds.hSprite, kRenderTransColor, frame );
SPR_DrawGeneric( frame, x, y, width, height, prc );
}
/*
=========
pfnSPR_DrawHoles
=========
*/
static void pfnSPR_DrawHoles( int frame, int x, int y, int width, int height, const wrect_t *prc )
{
if( !re ) return; // render not initialized
re->SetParms( clgame.ds.hSprite, kRenderTransAlpha, frame );
SPR_DrawGeneric( frame, x, y, width, height, prc );
}
/*
=========
pfnSPR_DrawAdditive
=========
*/
static void pfnSPR_DrawAdditive( int frame, int x, int y, int width, int height, const wrect_t *prc )
{
if( !re ) return; // render not initialized
re->SetParms( clgame.ds.hSprite, kRenderTransAdd, frame );
SPR_DrawGeneric( frame, x, y, width, height, prc );
}
/*
=========
pfnSPR_EnableScissor
=========
*/
static void pfnSPR_EnableScissor( int x, int y, int width, int height )
{
// check bounds
x = bound( 0, x, clgame.scrInfo.iWidth );
y = bound( 0, y, clgame.scrInfo.iHeight );
width = bound( 0, width, clgame.scrInfo.iWidth - x );
height = bound( 0, height, clgame.scrInfo.iHeight - y );
clgame.ds.scissor_x = x;
clgame.ds.scissor_width = width;
clgame.ds.scissor_y = y;
clgame.ds.scissor_height = height;
clgame.ds.scissor_test = true;
}
/*
=========
pfnSPR_DisableScissor
=========
*/
static void pfnSPR_DisableScissor( void )
{
clgame.ds.scissor_x = 0;
clgame.ds.scissor_width = 0;
clgame.ds.scissor_y = 0;
clgame.ds.scissor_height = 0;
clgame.ds.scissor_test = false;
}
/*
=========
pfnSPR_GetList
for parsing half-life scripts - hud.txt etc
=========
*/
static client_sprite_t *pfnSPR_GetList( char *psz, int *piCount )
{
client_sprite_t *pList;
int index, iTemp;
int numSprites;
script_t *script;
token_t token;
if( piCount ) *piCount = 0;
script = Com_OpenScript( psz, NULL, 0 );
if( !script ) return NULL;
Com_ReadUlong( script, false, &numSprites );
// name, res, pic, x, y, w, h
pList = Mem_Alloc( clgame.mempool, sizeof( *pList ) * numSprites );
for( index = 0; index < numSprites; index++ )
{
if( !Com_ReadToken( script, SC_ALLOW_NEWLINES, &token ))
break;
com.strncpy( pList[index].szName, token.string, sizeof( pList[index].szName ));
// read resolution
Com_ReadUlong( script, false, &iTemp );
pList[index].iRes = iTemp;
// read spritename
Com_ReadToken( script, false, &token );
com.strncpy( pList[index].szSprite, token.string, sizeof( pList[index].szSprite ));
// parse rectangle
Com_ReadUlong( script, false, &iTemp );
pList[index].rc.left = iTemp;
Com_ReadUlong( script, false, &iTemp );
pList[index].rc.top = iTemp;
Com_ReadUlong( script, false, &iTemp );
pList[index].rc.right = pList[index].rc.left + iTemp;
Com_ReadUlong( script, false, &iTemp );
pList[index].rc.bottom = pList[index].rc.top + iTemp;
if( piCount ) (*piCount)++;
}
if( index < numSprites )
MsgDev( D_WARN, "SPR_GetList: unexpected end of %s\n", psz );
Com_CloseScript( script );
return pList;
}
/*
=============
pfnFillRGBA
=============
*/
static void pfnFillRGBA( int x, int y, int width, int height, int r, int g, int b, int a )
{
rgba_t color;
if( !re ) return;
MakeRGBA( color, r, g, b, a );
re->SetColor( color );
SPR_AdjustSize( (float *)&x, (float *)&y, (float *)&width, (float *)&height );
re->DrawStretchPic( x, y, width, height, 0, 0, 1, 1, cls.fillShader );
re->SetColor( NULL );
}
/*
=============
pfnGetScreenInfo
get actual screen info
=============
*/
static int pfnGetScreenInfo( SCREENINFO *pscrinfo )
{
int i;
// setup screen info
clgame.scrInfo.iRealWidth = scr_width->integer;
clgame.scrInfo.iRealHeight = scr_height->integer;
if( pscrinfo && pscrinfo->iFlags & SCRINFO_VIRTUALSPACE )
{
// virtual screen space 640x480
// see cl_screen.c from Quake3 code for more details
clgame.scrInfo.iWidth = SCREEN_WIDTH;
clgame.scrInfo.iHeight = SCREEN_HEIGHT;
clgame.scrInfo.iFlags |= SCRINFO_VIRTUALSPACE;
}
else
{
clgame.scrInfo.iWidth = scr_width->integer;
clgame.scrInfo.iHeight = scr_height->integer;
clgame.scrInfo.iFlags &= ~SCRINFO_VIRTUALSPACE;
}
// TODO: build real table of fonts widthInChars
// TODO: load half-life credits font from wad
for( i = 0; i < 256; i++ )
clgame.scrInfo.charWidths[i] = SMALLCHAR_WIDTH;
clgame.scrInfo.iCharHeight = SMALLCHAR_HEIGHT;
if( !pscrinfo ) return 0;
*pscrinfo = clgame.scrInfo; // copy screeninfo out
return 1;
}
/*
=============
pfnSetCrosshair
setup auto-aim crosshair
=============
*/
static void pfnSetCrosshair( HSPRITE hspr, wrect_t rc, int r, int g, int b )
{
clgame.ds.rgbaCrosshair[0] = (byte)r;
clgame.ds.rgbaCrosshair[1] = (byte)g;
clgame.ds.rgbaCrosshair[2] = (byte)b;
clgame.ds.rgbaCrosshair[3] = (byte)0xFF;
clgame.ds.hCrosshair = hspr;
clgame.ds.rcCrosshair = rc;
}
/*
=============
pfnAddCommand
=============
*/
static 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 );
}
/*
=============
pfnHookUserMsg
=============
*/
static 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
=============
*/
static void pfnServerCmd( const char *szCmdString )
{
// server command adding in cmds queue
Cbuf_AddText( va( "cmd %s", szCmdString ));
}
/*
=============
pfnClientCmd
=============
*/
static void pfnClientCmd( const char *szCmdString )
{
// client command executes immediately
Cbuf_AddText( szCmdString );
}
/*
=============
pfnGetPlayerInfo
=============
*/
static void pfnGetPlayerInfo( int ent_num, hud_player_info_t *pinfo )
{
player_info_t *player;
ent_num -= 1; // player list if offset by 1 from ents
if( ent_num >= clgame.globals->maxClients || ent_num < 0 || !cl.players[ent_num].name[0] )
{
Mem_Set( pinfo, 0, sizeof( *pinfo ));
return;
}
player = &cl.players[ent_num];
pinfo->thisplayer = ( ent_num == cl.playernum ) ? true : false;
pinfo->name = player->name;
pinfo->model = player->model;
pinfo->ping = com.atoi( Info_ValueForKey( player->userinfo, "ping" ));
pinfo->spectator = com.atoi( Info_ValueForKey( player->userinfo, "spectator" ));
pinfo->packetloss = com.atoi( Info_ValueForKey( player->userinfo, "loss" ));
pinfo->topcolor = com.atoi( Info_ValueForKey( player->userinfo, "topcolor" ));
pinfo->bottomcolor = com.atoi( Info_ValueForKey( player->userinfo, "bottomcolor" ));
}
/*
=============
pfnPlaySoundByName
=============
*/
static void pfnPlaySoundByName( const char *szSound, float volume, int pitch, const float *org )
{
S_StartLocalSound( szSound, volume, pitch, org );
}
/*
=============
pfnPlaySoundByIndex
=============
*/
static 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 );
}
/*
=============
pfnTextMessageGet
returns specified message from titles.txt
=============
*/
static 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
}
/*
=============
pfnDrawCharacter
returns drawed chachter width (in real screen pixels)
=============
*/
static int pfnDrawCharacter( int x, int y, int number, int r, int g, int b )
{
float size, frow, fcol;
float ax, ay, aw, ah;
int fontWidth, fontHeight;
rgba_t color;
number &= 255;
if( !re ) return 0;
if( number <= ' ' ) return 0;
if( y < -clgame.scrInfo.iCharHeight )
return 0;
ax = x;
ay = y;
aw = clgame.scrInfo.charWidths[number];
ah = clgame.scrInfo.iCharHeight;
SPR_AdjustSize( &ax, &ay, &aw, &ah );
re->GetParms( &fontWidth, &fontHeight, NULL, 0, clgame.ds.hHudFont );
MakeRGBA( color, r, g, b, 255 );
re->SetColor( color );
frow = (number >> 4)*0.0625f + (0.5f / (float)fontWidth);
fcol = (number & 15)*0.0625f + (0.5f / (float)fontHeight);
size = 0.0625f - (1.0f / (float)fontWidth);
re->DrawStretchPic( ax, ay, aw, ah, fcol, frow, fcol + size, frow + size, clgame.ds.hHudFont );
re->SetColor( NULL ); // don't forget reset color
return clgame.scrInfo.charWidths[number];
}
/*
=============
pfnDrawConsoleString
drawing string like a console string
=============
*/
static int pfnDrawConsoleString( int x, int y, char *string )
{
if( !string || !*string ) return 0; // silent ignore
SCR_DrawSmallStringExt( x, y, string, NULL, false );
return com.cstrlen( string ) * SMALLCHAR_WIDTH; // not includes color prexfixes
}
/*
=============
pfnDrawSetTextColor
set color for anything
=============
*/
static void pfnDrawSetTextColor( float r, float g, float b )
{
rgba_t color;
// bound color and convert to byte
color[0] = (byte)bound( 0, r * 255, 255 );
color[1] = (byte)bound( 0, g * 255, 255 );
color[2] = (byte)bound( 0, b * 255, 255 );
color[3] = (byte)0xFF;
if( re ) re->SetColor( color );
}
/*
=============
pfnDrawConsoleStringLen
returns width and height (in real pixels)
for specified string
=============
*/
static void pfnDrawConsoleStringLen( const char *string, int *length, int *height )
{
// console used fixed font size
if( length ) *length = com.cstrlen( string ) * SMALLCHAR_WIDTH;
if( height ) *height = SMALLCHAR_HEIGHT;
}
/*
=============
pfnConsolePrint
prints dirctly into console (can skip notify)
=============
*/
static void pfnConsolePrint( const char *string )
{
if( !string || !*string ) return;
if( *string != 1 ) Con_Print( string ); // show notify
else Con_Print( va( "[skipnotify]%s", string + 1 )); // skip notify
}
/*
=============
pfnCenterPrint
holds and fade message at center of screen
like trigger_multiple message in q1
=============
*/
static void pfnCenterPrint( const char *string )
{
if( !string || !*string ) return; // someone stupid joke
CL_CenterPrint( string, 160, SMALLCHAR_WIDTH );
}
/*
=========
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 );
}
/*
=============
pfnGetViewAngles
return interpolated angles from previous frame
=============
*/
static void pfnGetViewAngles( float *angles )
{
if( angles == NULL ) return;
VectorCopy( cl.refdef.cl_viewangles, angles );
}
/*
=============
pfnSetViewAngles
return interpolated angles from previous frame
=============
*/
static void pfnSetViewAngles( float *angles )
{
if( angles == NULL ) return;
VectorCopy( angles, cl.refdef.cl_viewangles );
}
/*
=============
pfnDelCommand
=============
*/
static void pfnDelCommand( const char *cmd_name )
{
if( !cmd_name || !*cmd_name ) return;
Cmd_RemoveCommand( cmd_name );
}
/*
=============
pfnPhysInfo_ValueForKey
=============
*/
static const char* pfnPhysInfo_ValueForKey( const char *key )
{
return Info_ValueForKey( cl.physinfo, key );
}
/*
=============
pfnServerInfo_ValueForKey
=============
*/
static const char* pfnServerInfo_ValueForKey( const char *key )
{
return Info_ValueForKey( cl.serverinfo, key );
}
/*
=============
pfnGetClientMaxspeed
value that come from server
=============
*/
static float pfnGetClientMaxspeed( void )
{
edict_t *player = CL_GetLocalPlayer ();
if( !player ) return 0;
return player->v.maxspeed;
}
/*
=============
pfnGetModelPtr
returns pointer to a studiomodel
=============
*/
static void *pfnGetModelPtr( edict_t *pEdict )
{
if( !pEdict || pEdict->free )
return NULL;
return Mod_Extradata( pEdict->v.modelindex );
}
/*
=============
pfnGetBonePosition
=============
*/
static void pfnGetBonePosition( const edict_t* pEdict, int iBone, float *rgflOrigin, float *rgflAngles )
{
if( !CL_IsValidEdict( pEdict ))
{
MsgDev( D_WARN, "CL_GetBonePos: invalid entity %s\n", CL_ClassName( pEdict ));
return;
}
CM_GetBonePosition( (edict_t *)pEdict, iBone, rgflOrigin, rgflAngles );
}
/*
=============
pfnGetViewModel
can return NULL
=============
*/
static edict_t* pfnGetViewModel( void )
{
return &clgame.viewent;
}
/*
=============
pfnGetClientTime
=============
*/
static float pfnGetClientTime( void )
{
return cl.time * 0.001f;
}
/*
=============
pfnIsSpectateOnly
=============
*/
static int pfnIsSpectateOnly( void )
{
return com.atoi( Info_ValueForKey( Cvar_Userinfo(), "spectator" )) ? true : false;
}
/*
=============
pfnGetAttachment
=============
*/
static bool pfnGetAttachment( const edict_t *pEdict, int iAttachment, float *rgflOrigin, float *rgflAngles )
{
if( !pEdict )
{
if( rgflOrigin ) VectorClear( rgflOrigin );
if( rgflAngles ) VectorClear( rgflAngles );
return false;
}
return CL_GetAttachment( pEdict->serialnumber, iAttachment, rgflOrigin, rgflAngles );
}
/*
=============
pfnPointContents
=============
*/
static int pfnPointContents( const float *rgflVector )
{
return CL_PointContents( rgflVector );
}
/*
=============
pfnWaterEntity
=============
*/
static edict_t *pfnWaterEntity( const float *rgflPos )
{
edict_t *player, *pWater;
if( !rgflPos ) return NULL;
player = CL_GetLocalPlayer ();
if( !player ) return NULL;
pWater = CL_Move( rgflPos, vec3_origin, vec3_origin, rgflPos, MOVE_NOMONSTERS, player ).pHit;
if( CL_IsValidEdict( pWater ))
{
int mod_type = CM_GetModelType( pWater->v.modelindex );
int cont = pWater->v.skin;
// make sure what is a really water entity
if(( mod_type == mod_brush || mod_type == mod_world ) && (cont <= CONTENTS_WATER && cont >= CONTENTS_LAVA ))
return pWater;
}
return NULL;
}
/*
=============
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 )
{
return CL_PrecacheEvent( psz );
}
/*
=============
pfnPlaybackEvent
=============
*/
static void pfnPlaybackEvent( int flags, const edict_t *pInvoker, word eventindex, float delay, event_args_t *args )
{
event_args_t dummy;
int invokerIndex = 0;
// first check event for out of bounds
if( eventindex < 1 || eventindex > MAX_EVENTS )
{
MsgDev( D_ERROR, "CL_PlaybackEvent: invalid eventindex %i\n", eventindex );
return;
}
// check event for precached
if( !CL_PrecacheEvent( cl.configstrings[CS_EVENTS+eventindex] ))
{
MsgDev( D_ERROR, "CL_PlaybackEvent: event %i was not precached\n", eventindex );
return;
}
flags |= FEV_CLIENT; // it's a client event
flags &= ~(FEV_NOTHOST|FEV_HOSTONLY|FEV_GLOBAL);
if( delay < 0.0f )
delay = 0.0f; // fixup negative delays
if( CL_IsValidEdict( pInvoker ))
invokerIndex = NUM_FOR_EDICT( pInvoker );
if( args == NULL )
{
Mem_Set( &dummy, 0, sizeof( dummy ));
args = &dummy;
}
if( flags & FEV_RELIABLE )
{
args->ducking = 0;
VectorClear( args->velocity );
}
else if( invokerIndex )
{
// get up some info from invoker
if( VectorIsNull( args->origin ))
VectorCopy( pInvoker->v.origin, args->origin );
if( VectorIsNull( args->angles ))
VectorCopy( pInvoker->v.angles, args->angles );
VectorCopy( pInvoker->v.velocity, args->velocity );
args->ducking = (pInvoker->v.flags & FL_DUCKING) ? true : false;
}
CL_QueueEvent( flags, eventindex, delay, args );
}
/*
=============
pfnWeaponAnim
just set viewmodel anim
=============
*/
static void pfnWeaponAnim( int iAnim, int body, float framerate )
{
edict_t *viewmodel = &clgame.viewent;
viewmodel->v.animtime = clgame.globals->time; // start immediately
viewmodel->v.framerate = framerate;
viewmodel->v.sequence = iAnim;
viewmodel->v.frame = 0.0f;
viewmodel->v.scale = 1.0f;
viewmodel->v.body = body;
}
/*
=============
pfnHookEvent
=============
*/
static void pfnHookEvent( const char *name, pfnEventHook pfn )
{
word event_index = CL_PrecacheEvent( name );
user_event_t *ev;
int i;
// ignore blank names
if( !name || !*name ) return;
// second call can change EventFunc
for( i = 0; i < MAX_EVENTS; i++ )
{
ev = clgame.events[i];
if( !ev ) break;
if( !com.strcmp( name, ev->name ))
{
if( ev->func != pfn )
ev->func = pfn;
return;
}
}
CL_RegisterEvent( i, name, pfn );
}
/*
=============
pfnKillEvent
=============
*/
static void pfnKillEvents( int entnum, const char *eventname )
{
int i;
event_state_t *es;
event_info_t *ei;
int eventIndex = CL_PrecacheEvent( eventname );
if( eventIndex < 0 || eventIndex >= MAX_EVENTS )
return;
if( entnum < 0 || entnum > clgame.globals->maxEntities )
return;
es = &cl.events;
// find all events with specified index and kill it
for( i = 0; i < MAX_EVENT_QUEUE; i++ )
{
ei = &es->ei[i];
if( ei->index != eventIndex || ei->entity_index != entnum )
continue;
CL_ResetEvent( ei );
}
}
/*
=============
pfnPlaySound
=============
*/
void pfnPlaySound( edict_t *ent, float *org, int chan, const char *samp, float vol, float attn, int flags, int pitch )
{
int entindex = 0;
if( CL_IsValidEdict( ent )) entindex = ent->serialnumber;
S_StartSound( org, entindex, chan, S_RegisterSound( samp ), vol, attn, pitch, flags );
}
/*
=============
pfnStopSound
=============
*/
void pfnStopSound( int ent, int channel, const char *sample )
{
S_StartSound( vec3_origin, ent, channel, S_RegisterSound( sample ), 0, 0, PITCH_NORM, SND_STOP );
}
/*
=============
pfnFindModelIndex
=============
*/
int pfnFindModelIndex( const char *model )
{
int i;
if( !model || !*model ) return 0;
for( i = 1; i < MAX_MODELS; i++ )
{
if( !com.strcmp( model, cl.configstrings[CS_MODELS+i] ))
return i;
}
return 0;
}
/*
=============
pfnIsLocal
=============
*/
int pfnIsLocal( int playernum )
{
if( playernum == cl.playernum )
return true;
return false;
}
/*
=============
pfnLocalPlayerViewheight
=============
*/
void pfnLocalPlayerViewheight( float *view_ofs )
{
// predicted or smoothed
if( view_ofs ) VectorCopy( cl.predicted_viewofs, view_ofs );
}
/*
=============
pfnStopAllSounds
=============
*/
void pfnStopAllSounds( edict_t *ent, int entchannel )
{
// FIXME: this code is wrong
S_StopAllSounds ();
}
/*
=============
pfnGetModelType
=============
*/
modtype_t pfnGetModelType( model_t modelIndex )
{
if( !pe ) return mod_bad;
return pe->Mod_GetType( modelIndex );
}
/*
=============
pfnGetModFrames
=============
*/
int pfnGetModFrames( model_t modelIndex )
{
int numFrames = 1;
if( pe ) pe->Mod_GetFrames( modelIndex, &numFrames );
return numFrames;
}
/*
=============
pfnGetModBounds
=============
*/
void pfnGetModBounds( model_t modelIndex, float *mins, float *maxs )
{
if( pe )
{
pe->Mod_GetBounds( modelIndex, mins, maxs );
}
else
{
if( mins ) VectorClear( mins );
if( maxs ) VectorClear( maxs );
}
}
/*
=============
VGui_GetPanel
=============
*/
void *VGui_GetPanel( void )
{
// UNDONE: wait for version 0.75
return NULL;
}
/*
=============
VGui_ViewportPaintBackground
=============
*/
void VGui_ViewportPaintBackground( int extents[4] )
{
// UNDONE: wait for version 0.75
}
/*
=============
pfnIsInGame
=============
*/
int pfnIsInGame( void )
{
return ( cls.key_dest == key_game ) ? true : false;
}
/*
===============================================================================
EffectsAPI Builtin Functions
===============================================================================
*/
/*
=================
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( !com.stricmp( 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];
}
/*
=================
pfnCullBox
=================
*/
static int pfnCullBox( const vec3_t mins, const vec3_t maxs )
{
if( !re ) return false;
return re->CullBox( mins, maxs );
}
/*
=================
pfnEnvShot
=================
*/
static void pfnEnvShot( const float *vieworg, const char *name, int skyshot )
{
static vec3_t viewPoint;
if( !name )
{
MsgDev( D_ERROR, "R_%sShot: bad name\n", skyshot ? "Sky" : "Env" );
return;
}
if( cls.scrshot_action != scrshot_inactive )
{
if( cls.scrshot_action != scrshot_skyshot && cls.scrshot_action != scrshot_envshot )
MsgDev( D_ERROR, "R_%sShot: subsystem is busy, try later.\n", skyshot ? "Sky" : "Env" );
return;
}
cls.envshot_vieworg = NULL; // use client view
com.strncpy( cls.shotname, name, sizeof( cls.shotname ));
if( vieworg )
{
// make sure what viewpoint don't temporare
VectorCopy( vieworg, viewPoint );
cls.envshot_vieworg = viewPoint;
}
// make request for envshot
if( skyshot ) cls.scrshot_action = scrshot_skyshot;
else cls.scrshot_action = scrshot_envshot;
}
/*
=================
TriApi implementation
=================
*/
/*
=================
Tri_DrawTriangles
callback from renderer
=================
*/
void Tri_DrawTriangles( int fTrans )
{
clgame.dllFuncs.pfnDrawTriangles( fTrans );
}
/*
=============
TriLoadShader
=============
*/
shader_t TriLoadShader( const char *szShaderName, int fShaderNoMip )
{
if( !re ) return 0; // render not initialized
if( !szShaderName || !*szShaderName )
{
MsgDev( D_ERROR, "Tri_LoadShader: invalid shadername (%s)\n", fShaderNoMip ? "nomip" : "generic" );
return -1;
}
if( fShaderNoMip )
return re->RegisterShader( szShaderName, SHADER_NOMIP );
return re->RegisterShader( szShaderName, SHADER_GENERIC );
}
/*
=============
TriRenderMode
=============
*/
void TriRenderMode( kRenderMode_t mode )
{
if( !re ) return;
re->RenderMode( mode );
}
shader_t TriGetSpriteFrame( int spriteIndex, int spriteFrame )
{
if( !re ) return 0;
return re->GetSpriteTexture( spriteIndex, spriteFrame );
}
/*
=============
TriBind
bind current shader
=============
*/
void TriBind( shader_t shader, int frame )
{
if( !re ) return;
re->Bind( shader, frame );
}
/*
=============
TriBegin
begin triangle sequence
=============
*/
void TriBegin( TRI_DRAW mode )
{
if( re ) re->Begin( mode );
}
/*
=============
TriEnd
draw triangle sequence
=============
*/
void TriEnd( void )
{
if( re ) re->End();
}
/*
=============
TriEnable
=============
*/
void TriEnable( int cap )
{
if( !re ) return;
re->Enable( cap );
}
/*
=============
TriDisable
=============
*/
void TriDisable( int cap )
{
if( !re ) return;
re->Disable( cap );
}
/*
=============
TriVertex3f
=============
*/
void TriVertex3f( float x, float y, float z )
{
if( !re ) return;
re->Vertex3f( x, y, z );
}
/*
=============
TriVertex3fv
=============
*/
void TriVertex3fv( const float *v )
{
if( !re || !v ) return;
re->Vertex3f( v[0], v[1], v[2] );
}
/*
=============
TriNormal3f
=============
*/
void TriNormal3f( float x, float y, float z )
{
if( !re ) return;
re->Normal3f( x, y, z );
}
/*
=============
TriNormal3fv
=============
*/
void TriNormal3fv( const float *v )
{
if( !re || !v ) return;
re->Normal3f( v[0], v[1], v[2] );
}
/*
=============
TriColor4f
=============
*/
void TriColor4f( float r, float g, float b, float a )
{
rgba_t rgba;
if( !re ) return;
rgba[0] = (byte)bound( 0, (r * 255.0f), 255 );
rgba[1] = (byte)bound( 0, (g * 255.0f), 255 );
rgba[2] = (byte)bound( 0, (b * 255.0f), 255 );
rgba[3] = (byte)bound( 0, (a * 255.0f), 255 );
re->Color4ub( rgba[0], rgba[1], rgba[2], rgba[3] );
}
/*
=============
TriColor4ub
=============
*/
void TriColor4ub( byte r, byte g, byte b, byte a )
{
if( !re ) return;
re->Color4ub( r, g, b, a );
}
/*
=============
TriTexCoord2f
=============
*/
void TriTexCoord2f( float u, float v )
{
if( !re ) return;
re->TexCoord2f( u, v );
}
/*
=============
TriCullFace
=============
*/
void TriCullFace( TRI_CULL mode )
{
if( !re ) return;
re->CullFace( mode );
}
/*
=============
TriScreenToWorld
convert screen coordinates (x,y) into world (x, y, z)
=============
*/
void TriScreenToWorld( float *screen, float *world )
{
if( !re ) return;
re->ScreenToWorld( screen, world );
}
/*
=============
TriWorldToScreen
convert world coordinates (x,y,z) into screen (x, y)
=============
*/
int TriWorldToScreen( float *world, float *screen )
{
int retval = 0;
if( !re || !world || !screen )
return retval;
retval = re->WorldToScreen( world, screen );
screen[0] = 0.5f * screen[0] * (float)cl.refdef.viewport[2];
screen[1] = -0.5f * screen[1] * (float)cl.refdef.viewport[3];
screen[0] += 0.5f * (float)cl.refdef.viewport[2];
screen[1] += 0.5f * (float)cl.refdef.viewport[3];
return retval;
}
/*
=============
TriFog
enables global fog on the level
=============
*/
void TriFog( float flFogColor[3], float flStart, float flEnd, int bOn )
{
if( re ) re->Fog( flFogColor, flStart, flEnd, bOn );
}
static triapi_t gTriApi =
{
sizeof( triapi_t ),
TriLoadShader,
TriGetSpriteFrame,
TriRenderMode,
TriBegin,
TriEnd,
TriEnable,
TriDisable,
TriVertex3f,
TriVertex3fv,
TriNormal3f,
TriNormal3fv,
TriColor4f,
TriColor4ub,
TriTexCoord2f,
TriBind,
TriCullFace,
TriScreenToWorld,
TriWorldToScreen,
TriFog
};
static efxapi_t gEfxApi =
{
sizeof( efxapi_t ),
CL_AllocParticle,
CL_BlobExplosion,
CL_EntityParticles,
CL_LavaSplash,
CL_ParticleExplosion,
CL_ParticleExplosion2,
CL_RocketTrail,
CL_ParticleEffect,
CL_TeleportSplash,
CL_GetPaletteColor,
pfnDecalIndex,
pfnDecalIndexFromName,
CL_SpawnDecal,
CL_AddDLight,
CL_AddSLight,
CL_LightForPoint,
CM_BoxVisible,
pfnCullBox,
CL_AddEntity,
CL_AddTempEntity,
pfnEnvShot,
};
static event_api_t gEventApi =
{
sizeof( event_api_t ),
pfnPrecacheEvent,
pfnPlaybackEvent,
pfnWeaponAnim,
pfnRandomFloat,
pfnRandomLong,
pfnHookEvent,
pfnKillEvents,
pfnPlaySound,
pfnStopSound,
pfnFindModelIndex,
pfnIsLocal,
pfnLocalPlayerViewheight,
pfnStopAllSounds,
pfnGetModelType,
pfnGetModFrames,
pfnGetModBounds,
};
// engine callbacks
static cl_enginefuncs_t gEngfuncs =
{
sizeof( cl_enginefuncs_t ),
pfnSPR_Load,
pfnSPR_Frames,
pfnSPR_Height,
pfnSPR_Width,
pfnSPR_Set,
pfnSPR_Draw,
pfnSPR_DrawHoles,
pfnSPR_DrawTrans,
pfnSPR_DrawAdditive,
pfnSPR_EnableScissor,
pfnSPR_DisableScissor,
pfnSPR_GetList,
pfnFillRGBA,
pfnGetScreenInfo,
pfnSetCrosshair,
pfnCVarRegister,
pfnCVarGetValue,
pfnCVarGetString,
pfnAddCommand,
pfnHookUserMsg,
pfnServerCmd,
pfnClientCmd,
pfnGetPlayerInfo,
pfnPlaySoundByName,
pfnPlaySoundByIndex,
AngleVectors,
pfnTextMessageGet,
pfnDrawCharacter,
pfnDrawConsoleString,
pfnDrawSetTextColor,
pfnDrawConsoleStringLen,
pfnConsolePrint,
pfnCenterPrint,
pfnMemAlloc,
pfnMemFree,
pfnGetViewAngles,
pfnSetViewAngles,
pfnCVarSetString,
pfnCVarSetValue,
pfnCmd_Argc,
pfnCmd_Argv,
pfnCmd_Args,
CL_GetLerpFrac,
pfnDelCommand,
pfnAlertMessage,
pfnPhysInfo_ValueForKey,
pfnServerInfo_ValueForKey,
pfnGetClientMaxspeed,
pfnGetModelPtr,
pfnGetBonePosition,
CL_AllocString,
CL_GetString,
CL_GetLocalPlayer,
pfnGetViewModel,
CL_GetEdictByIndex,
pfnGetClientTime,
pfnIsSpectateOnly,
pfnGetAttachment,
pfnPointContents,
pfnWaterEntity,
pfnTraceLine,
pfnTraceToss,
pfnTraceHull,
pfnTraceModel,
pfnTraceTexture,
pfnFOpen,
pfnFRead,
pfnFWrite,
pfnFClose,
pfnFGets,
pfnFSeek,
pfnFTell,
pfnLoadLibrary,
pfnGetProcAddress,
pfnFreeLibrary,
Host_Error,
pfnFileExists,
pfnRemoveFile,
VGui_GetPanel,
VGui_ViewportPaintBackground,
pfnLoadFile,
pfnParseToken,
pfnFreeFile,
&gTriApi,
&gEfxApi,
&gEventApi,
pfnIsInGame
};
void CL_UnloadProgs( void )
{
CL_FreeEdicts();
CL_FreeEdict( &clgame.viewent );
CL_FreeEdict( &clgame.playermodel );
// deinitialize game
clgame.dllFuncs.pfnShutdown();
if( sys_sharedstrings->integer )
Mem_FreePool( &clgame.stringspool );
else StringTable_Delete( clgame.hStringTable );
FS_FreeLibrary( clgame.hInstance );
Mem_FreePool( &cls.mempool );
Mem_FreePool( &clgame.mempool );
Mem_FreePool( &clgame.private );
Mem_Set( &clgame, 0, sizeof( clgame ));
}
bool CL_LoadProgs( const char *name )
{
static CLIENTAPI GetClientAPI;
static cl_globalvars_t gpGlobals;
static playermove_t gpMove;
if( clgame.hInstance ) CL_UnloadProgs();
// setup globals
clgame.globals = &gpGlobals;
cl.refdef.movevars = &clgame.movevars;
clgame.globals->pViewParms = &cl.refdef;
// initialize TriAPI
clgame.pmove = &gpMove;
cls.mempool = Mem_AllocPool( "Client Static Pool" );
clgame.mempool = Mem_AllocPool( "Client Edicts Zone" );
clgame.private = Mem_AllocPool( "Client Private Zone" );
clgame.baselines = NULL;
clgame.edicts = NULL;
clgame.hInstance = FS_LoadLibrary( name, false );
if( !clgame.hInstance ) return false;
GetClientAPI = (CLIENTAPI)FS_GetProcAddress( clgame.hInstance, "CreateAPI" );
if( !GetClientAPI )
{
FS_FreeLibrary( clgame.hInstance );
MsgDev( D_NOTE, "CL_LoadProgs: failed to get address of CreateAPI proc\n" );
clgame.hInstance = NULL;
return false;
}
if( !GetClientAPI( &clgame.dllFuncs, &gEngfuncs, clgame.globals ))
{
FS_FreeLibrary( clgame.hInstance );
MsgDev( D_NOTE, "CL_LoadProgs: can't init client API\n" );
clgame.hInstance = NULL;
return false;
}
clgame.globals->pStringBase = ""; // setup string base
if( sys_sharedstrings->integer )
{
// just use Half-Life system - base pointer and malloc
clgame.stringspool = Mem_AllocPool( "Client Strings" );
}
else
{
// 65535 unique strings should be enough ...
clgame.hStringTable = StringTable_Create( "Client", 0x10000 );
}
clgame.globals->maxEntities = GI->max_edicts; // merge during loading
// 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();
// initialize pm_shared
CL_InitClientMove();
return true;
}