mirror of
https://github.com/w23/xash3d-fwgs
synced 2024-12-16 14:10:11 +01:00
5cdb35f508
This fixes a minor bug where sounds played via pfnPlaySoundByNameAtLocation (mainly from viewmodel animations e.g. MP5) would be left where it was emitted in the world, rather than following the local player as it does in Goldsrc.
4063 lines
84 KiB
C
4063 lines
84 KiB
C
/*
|
|
cl_game.c - client dll interaction
|
|
Copyright (C) 2008 Uncle Mike
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
*/
|
|
|
|
#include "common.h"
|
|
#include "client.h"
|
|
#include "const.h"
|
|
#include "triangleapi.h"
|
|
#include "r_efx.h"
|
|
#include "demo_api.h"
|
|
#include "ivoicetweak.h"
|
|
#include "pm_local.h"
|
|
#include "cl_tent.h"
|
|
#include "input.h"
|
|
#include "shake.h"
|
|
#include "sprite.h"
|
|
#include "library.h"
|
|
#include "vgui_draw.h"
|
|
#include "sound.h" // SND_STOP_LOOPING
|
|
#include "platform/platform.h"
|
|
|
|
#define MAX_LINELENGTH 80
|
|
#define MAX_TEXTCHANNELS 8 // must be power of two (GoldSrc uses 4 channels)
|
|
#define TEXT_MSGNAME "TextMessage%i"
|
|
|
|
char cl_textbuffer[MAX_TEXTCHANNELS][2048];
|
|
client_textmessage_t cl_textmessage[MAX_TEXTCHANNELS];
|
|
|
|
static dllfunc_t cdll_exports[] =
|
|
{
|
|
{ "Initialize", (void **)&clgame.dllFuncs.pfnInitialize },
|
|
{ "HUD_VidInit", (void **)&clgame.dllFuncs.pfnVidInit },
|
|
{ "HUD_Init", (void **)&clgame.dllFuncs.pfnInit },
|
|
{ "HUD_Shutdown", (void **)&clgame.dllFuncs.pfnShutdown },
|
|
{ "HUD_Redraw", (void **)&clgame.dllFuncs.pfnRedraw },
|
|
{ "HUD_UpdateClientData", (void **)&clgame.dllFuncs.pfnUpdateClientData },
|
|
{ "HUD_Reset", (void **)&clgame.dllFuncs.pfnReset },
|
|
{ "HUD_PlayerMove", (void **)&clgame.dllFuncs.pfnPlayerMove },
|
|
{ "HUD_PlayerMoveInit", (void **)&clgame.dllFuncs.pfnPlayerMoveInit },
|
|
{ "HUD_PlayerMoveTexture", (void **)&clgame.dllFuncs.pfnPlayerMoveTexture },
|
|
{ "HUD_ConnectionlessPacket", (void **)&clgame.dllFuncs.pfnConnectionlessPacket },
|
|
{ "HUD_GetHullBounds", (void **)&clgame.dllFuncs.pfnGetHullBounds },
|
|
{ "HUD_Frame", (void **)&clgame.dllFuncs.pfnFrame },
|
|
{ "HUD_PostRunCmd", (void **)&clgame.dllFuncs.pfnPostRunCmd },
|
|
{ "HUD_Key_Event", (void **)&clgame.dllFuncs.pfnKey_Event },
|
|
{ "HUD_AddEntity", (void **)&clgame.dllFuncs.pfnAddEntity },
|
|
{ "HUD_CreateEntities", (void **)&clgame.dllFuncs.pfnCreateEntities },
|
|
{ "HUD_StudioEvent", (void **)&clgame.dllFuncs.pfnStudioEvent },
|
|
{ "HUD_TxferLocalOverrides", (void **)&clgame.dllFuncs.pfnTxferLocalOverrides },
|
|
{ "HUD_ProcessPlayerState", (void **)&clgame.dllFuncs.pfnProcessPlayerState },
|
|
{ "HUD_TxferPredictionData", (void **)&clgame.dllFuncs.pfnTxferPredictionData },
|
|
{ "HUD_TempEntUpdate", (void **)&clgame.dllFuncs.pfnTempEntUpdate },
|
|
{ "HUD_DrawNormalTriangles", (void **)&clgame.dllFuncs.pfnDrawNormalTriangles },
|
|
{ "HUD_DrawTransparentTriangles", (void **)&clgame.dllFuncs.pfnDrawTransparentTriangles },
|
|
{ "HUD_GetUserEntity", (void **)&clgame.dllFuncs.pfnGetUserEntity },
|
|
{ "Demo_ReadBuffer", (void **)&clgame.dllFuncs.pfnDemo_ReadBuffer },
|
|
{ "CAM_Think", (void **)&clgame.dllFuncs.CAM_Think },
|
|
{ "CL_IsThirdPerson", (void **)&clgame.dllFuncs.CL_IsThirdPerson },
|
|
{ "CL_CameraOffset", (void **)&clgame.dllFuncs.CL_CameraOffset }, // unused callback. Now camera code is completely moved to the user area
|
|
{ "CL_CreateMove", (void **)&clgame.dllFuncs.CL_CreateMove },
|
|
{ "IN_ActivateMouse", (void **)&clgame.dllFuncs.IN_ActivateMouse },
|
|
{ "IN_DeactivateMouse", (void **)&clgame.dllFuncs.IN_DeactivateMouse },
|
|
{ "IN_MouseEvent", (void **)&clgame.dllFuncs.IN_MouseEvent },
|
|
{ "IN_Accumulate", (void **)&clgame.dllFuncs.IN_Accumulate },
|
|
{ "IN_ClearStates", (void **)&clgame.dllFuncs.IN_ClearStates },
|
|
{ "V_CalcRefdef", (void **)&clgame.dllFuncs.pfnCalcRefdef },
|
|
{ "KB_Find", (void **)&clgame.dllFuncs.KB_Find },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
// optional exports
|
|
static dllfunc_t cdll_new_exports[] = // allowed only in SDK 2.3 and higher
|
|
{
|
|
{ "HUD_GetStudioModelInterface", (void **)&clgame.dllFuncs.pfnGetStudioModelInterface },
|
|
{ "HUD_DirectorMessage", (void **)&clgame.dllFuncs.pfnDirectorMessage },
|
|
{ "HUD_VoiceStatus", (void **)&clgame.dllFuncs.pfnVoiceStatus },
|
|
{ "HUD_ChatInputPosition", (void **)&clgame.dllFuncs.pfnChatInputPosition },
|
|
{ "HUD_GetRenderInterface", (void **)&clgame.dllFuncs.pfnGetRenderInterface }, // Xash3D ext
|
|
{ "HUD_ClipMoveToEntity", (void **)&clgame.dllFuncs.pfnClipMoveToEntity }, // Xash3D ext
|
|
{ "IN_ClientTouchEvent", (void **)&clgame.dllFuncs.pfnTouchEvent}, // Xash3D FWGS ext
|
|
{ "IN_ClientMoveEvent", (void **)&clgame.dllFuncs.pfnMoveEvent}, // Xash3D FWGS ext
|
|
{ "IN_ClientLookEvent", (void **)&clgame.dllFuncs.pfnLookEvent}, // Xash3D FWGS ext
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
static void pfnSPR_DrawHoles( int frame, int x, int y, const wrect_t *prc );
|
|
|
|
/*
|
|
====================
|
|
CL_GetEntityByIndex
|
|
|
|
Render callback for studio models
|
|
====================
|
|
*/
|
|
cl_entity_t *CL_GetEntityByIndex( int index )
|
|
{
|
|
if( !clgame.entities ) // not in game yet
|
|
return NULL;
|
|
|
|
if( index < 0 || index >= clgame.maxEntities )
|
|
return NULL;
|
|
|
|
if( index == 0 )
|
|
return clgame.entities;
|
|
|
|
return CL_EDICT_NUM( index );
|
|
}
|
|
|
|
/*
|
|
================
|
|
CL_ModelHandle
|
|
|
|
get model handle by index
|
|
================
|
|
*/
|
|
model_t *CL_ModelHandle( int modelindex )
|
|
{
|
|
if( modelindex < 0 || modelindex >= MAX_MODELS )
|
|
return NULL;
|
|
return cl.models[modelindex];
|
|
}
|
|
|
|
/*
|
|
====================
|
|
CL_IsThirdPerson
|
|
|
|
returns true if thirdperson is enabled
|
|
====================
|
|
*/
|
|
qboolean CL_IsThirdPerson( void )
|
|
{
|
|
cl.local.thirdperson = clgame.dllFuncs.CL_IsThirdPerson();
|
|
|
|
if( cl.local.thirdperson )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
CL_GetPlayerInfo
|
|
|
|
get player info by render request
|
|
====================
|
|
*/
|
|
player_info_t *CL_GetPlayerInfo( int playerIndex )
|
|
{
|
|
if( playerIndex < 0 || playerIndex >= cl.maxclients )
|
|
return NULL;
|
|
|
|
return &cl.players[playerIndex];
|
|
}
|
|
|
|
/*
|
|
====================
|
|
CL_CreatePlaylist
|
|
|
|
Create a default valve playlist
|
|
====================
|
|
*/
|
|
void CL_CreatePlaylist( const char *filename )
|
|
{
|
|
file_t *f;
|
|
|
|
f = FS_Open( filename, "w", false );
|
|
if( !f ) return;
|
|
|
|
// make standard cdaudio playlist
|
|
FS_Print( f, "blank\n" ); // #1
|
|
FS_Print( f, "Half-Life01.mp3\n" ); // #2
|
|
FS_Print( f, "Prospero01.mp3\n" ); // #3
|
|
FS_Print( f, "Half-Life12.mp3\n" ); // #4
|
|
FS_Print( f, "Half-Life07.mp3\n" ); // #5
|
|
FS_Print( f, "Half-Life10.mp3\n" ); // #6
|
|
FS_Print( f, "Suspense01.mp3\n" ); // #7
|
|
FS_Print( f, "Suspense03.mp3\n" ); // #8
|
|
FS_Print( f, "Half-Life09.mp3\n" ); // #9
|
|
FS_Print( f, "Half-Life02.mp3\n" ); // #10
|
|
FS_Print( f, "Half-Life13.mp3\n" ); // #11
|
|
FS_Print( f, "Half-Life04.mp3\n" ); // #12
|
|
FS_Print( f, "Half-Life15.mp3\n" ); // #13
|
|
FS_Print( f, "Half-Life14.mp3\n" ); // #14
|
|
FS_Print( f, "Half-Life16.mp3\n" ); // #15
|
|
FS_Print( f, "Suspense02.mp3\n" ); // #16
|
|
FS_Print( f, "Half-Life03.mp3\n" ); // #17
|
|
FS_Print( f, "Half-Life08.mp3\n" ); // #18
|
|
FS_Print( f, "Prospero02.mp3\n" ); // #19
|
|
FS_Print( f, "Half-Life05.mp3\n" ); // #20
|
|
FS_Print( f, "Prospero04.mp3\n" ); // #21
|
|
FS_Print( f, "Half-Life11.mp3\n" ); // #22
|
|
FS_Print( f, "Half-Life06.mp3\n" ); // #23
|
|
FS_Print( f, "Prospero03.mp3\n" ); // #24
|
|
FS_Print( f, "Half-Life17.mp3\n" ); // #25
|
|
FS_Print( f, "Prospero05.mp3\n" ); // #26
|
|
FS_Print( f, "Suspense05.mp3\n" ); // #27
|
|
FS_Print( f, "Suspense07.mp3\n" ); // #28
|
|
FS_Close( f );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
CL_InitCDAudio
|
|
|
|
Initialize CD playlist
|
|
====================
|
|
*/
|
|
void CL_InitCDAudio( const char *filename )
|
|
{
|
|
byte *afile;
|
|
char *pfile;
|
|
string token;
|
|
int c = 0;
|
|
|
|
if( !FS_FileExists( filename, false ))
|
|
{
|
|
// create a default playlist
|
|
CL_CreatePlaylist( filename );
|
|
}
|
|
|
|
afile = FS_LoadFile( filename, NULL, false );
|
|
if( !afile ) return;
|
|
|
|
pfile = (char *)afile;
|
|
|
|
// format: trackname\n [num]
|
|
while(( pfile = COM_ParseFile( pfile, token )) != NULL )
|
|
{
|
|
if( !Q_stricmp( token, "blank" )) token[0] = '\0';
|
|
Q_strncpy( clgame.cdtracks[c], token, sizeof( clgame.cdtracks[0] ));
|
|
|
|
if( ++c > MAX_CDTRACKS - 1 )
|
|
{
|
|
Con_Reportf( S_WARN "CD_Init: too many tracks %i in %s\n", MAX_CDTRACKS, filename );
|
|
break;
|
|
}
|
|
}
|
|
|
|
Mem_Free( afile );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
CL_PointContents
|
|
|
|
Return contents for point
|
|
====================
|
|
*/
|
|
int CL_PointContents( const vec3_t p )
|
|
{
|
|
int cont = PM_PointContents( clgame.pmove, p );
|
|
|
|
if( cont <= CONTENTS_CURRENT_0 && cont >= CONTENTS_CURRENT_DOWN )
|
|
cont = CONTENTS_WATER;
|
|
return cont;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CL_AdjustXPos
|
|
|
|
adjust text by x pos
|
|
=============
|
|
*/
|
|
static int CL_AdjustXPos( float x, int width, int totalWidth )
|
|
{
|
|
int xPos;
|
|
|
|
if( x == -1 )
|
|
{
|
|
xPos = ( refState.width - width ) * 0.5f;
|
|
}
|
|
else
|
|
{
|
|
if ( x < 0 )
|
|
xPos = (1.0f + x) * refState.width - totalWidth; // Alight right
|
|
else // align left
|
|
xPos = x * refState.width;
|
|
}
|
|
|
|
if( xPos + width > refState.width )
|
|
xPos = refState.width - width;
|
|
else if( xPos < 0 )
|
|
xPos = 0;
|
|
|
|
return xPos;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CL_AdjustYPos
|
|
|
|
adjust text by y pos
|
|
=============
|
|
*/
|
|
static int CL_AdjustYPos( float y, int height )
|
|
{
|
|
int yPos;
|
|
|
|
if( y == -1 ) // centered?
|
|
{
|
|
yPos = ( refState.height - height ) * 0.5f;
|
|
}
|
|
else
|
|
{
|
|
// Alight bottom?
|
|
if( y < 0 )
|
|
yPos = (1.0f + y) * refState.height - height; // Alight bottom
|
|
else // align top
|
|
yPos = y * refState.height;
|
|
}
|
|
|
|
if( yPos + height > refState.height )
|
|
yPos = refState.height - height;
|
|
else if( yPos < 0 )
|
|
yPos = 0;
|
|
|
|
return yPos;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CL_CenterPrint
|
|
|
|
print centerscreen message
|
|
=============
|
|
*/
|
|
void CL_CenterPrint( const char *text, float y )
|
|
{
|
|
int length = 0;
|
|
int width = 0;
|
|
char *s;
|
|
|
|
if( !COM_CheckString( text ))
|
|
return;
|
|
|
|
clgame.centerPrint.lines = 1;
|
|
clgame.centerPrint.totalWidth = 0;
|
|
clgame.centerPrint.time = cl.mtime[0]; // allow pause for centerprint
|
|
Q_strncpy( clgame.centerPrint.message, text, sizeof( clgame.centerPrint.message ));
|
|
s = clgame.centerPrint.message;
|
|
|
|
// count the number of lines for centering
|
|
while( *s )
|
|
{
|
|
if( *s == '\n' )
|
|
{
|
|
clgame.centerPrint.lines++;
|
|
if( width > clgame.centerPrint.totalWidth )
|
|
clgame.centerPrint.totalWidth = width;
|
|
width = 0;
|
|
}
|
|
else width += clgame.scrInfo.charWidths[*s];
|
|
s++;
|
|
length++;
|
|
}
|
|
|
|
clgame.centerPrint.totalHeight = ( clgame.centerPrint.lines * clgame.scrInfo.iCharHeight );
|
|
clgame.centerPrint.y = CL_AdjustYPos( y, clgame.centerPrint.totalHeight );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
SPR_AdjustSize
|
|
|
|
draw hudsprite routine
|
|
====================
|
|
*/
|
|
static void SPR_AdjustSize( float *x, float *y, float *w, float *h )
|
|
{
|
|
float xscale, yscale;
|
|
|
|
// scale for screen sizes
|
|
xscale = refState.width / (float)clgame.scrInfo.iWidth;
|
|
yscale = refState.height / (float)clgame.scrInfo.iHeight;
|
|
|
|
if( x ) *x *= xscale;
|
|
if( y ) *y *= yscale;
|
|
if( w ) *w *= xscale;
|
|
if( h ) *h *= yscale;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
SPR_AdjustSize
|
|
|
|
draw hudsprite routine
|
|
====================
|
|
*/
|
|
static void SPR_AdjustSizei( int *x, int *y, int *w, int *h )
|
|
{
|
|
float xscale, yscale;
|
|
|
|
// scale for screen sizes
|
|
xscale = refState.width / (float)clgame.scrInfo.iWidth;
|
|
yscale = refState.height / (float)clgame.scrInfo.iHeight;
|
|
|
|
if( x ) *x *= xscale;
|
|
if( y ) *y *= yscale;
|
|
if( w ) *w *= xscale;
|
|
if( h ) *h *= yscale;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
PictAdjustSize
|
|
|
|
draw hudsprite routine
|
|
====================
|
|
*/
|
|
void PicAdjustSize( float *x, float *y, float *w, float *h )
|
|
{
|
|
if( !clgame.ds.adjust_size ) return;
|
|
|
|
SPR_AdjustSize( x, y, w, h );
|
|
}
|
|
|
|
static qboolean 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;
|
|
int texnum;
|
|
|
|
if( width == -1 && height == -1 )
|
|
{
|
|
int w, h;
|
|
|
|
// assume we get sizes from image
|
|
ref.dllFuncs.R_GetSpriteParms( &w, &h, NULL, frame, clgame.ds.pSprite );
|
|
|
|
width = w;
|
|
height = h;
|
|
}
|
|
|
|
if( prc )
|
|
{
|
|
wrect_t rc;
|
|
|
|
rc = *prc;
|
|
|
|
// Sigh! some stupid modmakers set wrong rectangles in hud.txt
|
|
if( rc.left <= 0 || rc.left >= width ) rc.left = 0;
|
|
if( rc.top <= 0 || rc.top >= height ) rc.top = 0;
|
|
if( rc.right <= 0 || rc.right > width ) rc.right = width;
|
|
if( rc.bottom <= 0 || rc.bottom > height ) rc.bottom = height;
|
|
|
|
// calc user-defined rectangle
|
|
s1 = (float)rc.left / width;
|
|
t1 = (float)rc.top / height;
|
|
s2 = (float)rc.right / width;
|
|
t2 = (float)rc.bottom / height;
|
|
width = rc.right - rc.left;
|
|
height = rc.bottom - rc.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 );
|
|
texnum = ref.dllFuncs.R_GetSpriteTexture( clgame.ds.pSprite, frame );
|
|
ref.dllFuncs.Color4ub( clgame.ds.spriteColor[0], clgame.ds.spriteColor[1], clgame.ds.spriteColor[2], clgame.ds.spriteColor[3] );
|
|
ref.dllFuncs.R_DrawStretchPic( x, y, width, height, s1, t1, s2, t2, texnum );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CL_DrawCenterPrint
|
|
|
|
called each frame
|
|
=============
|
|
*/
|
|
void CL_DrawCenterPrint( void )
|
|
{
|
|
char *pText;
|
|
int i, j, x, y;
|
|
int width, lineLength;
|
|
byte *colorDefault, line[MAX_LINELENGTH];
|
|
int charWidth, charHeight;
|
|
|
|
if( !clgame.centerPrint.time )
|
|
return;
|
|
|
|
if(( cl.time - clgame.centerPrint.time ) >= scr_centertime->value )
|
|
{
|
|
// time expired
|
|
clgame.centerPrint.time = 0.0f;
|
|
return;
|
|
}
|
|
|
|
y = clgame.centerPrint.y; // start y
|
|
colorDefault = g_color_table[7];
|
|
pText = clgame.centerPrint.message;
|
|
Con_DrawCharacterLen( 0, NULL, &charHeight );
|
|
|
|
for( i = 0; i < clgame.centerPrint.lines; i++ )
|
|
{
|
|
lineLength = 0;
|
|
width = 0;
|
|
|
|
while( *pText && *pText != '\n' && lineLength < MAX_LINELENGTH )
|
|
{
|
|
byte c = *pText;
|
|
line[lineLength] = c;
|
|
Con_DrawCharacterLen( c, &charWidth, NULL );
|
|
width += charWidth;
|
|
lineLength++;
|
|
pText++;
|
|
}
|
|
|
|
if( lineLength == MAX_LINELENGTH )
|
|
lineLength--;
|
|
|
|
pText++; // Skip LineFeed
|
|
line[lineLength] = 0;
|
|
|
|
x = CL_AdjustXPos( -1, width, clgame.centerPrint.totalWidth );
|
|
|
|
for( j = 0; j < lineLength; j++ )
|
|
{
|
|
if( x >= 0 && y >= 0 && x <= refState.width )
|
|
x += Con_DrawCharacter( x, y, line[j], colorDefault );
|
|
}
|
|
y += charHeight;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CL_DrawScreenFade
|
|
|
|
fill screen with specfied color
|
|
can be modulated
|
|
=============
|
|
*/
|
|
void CL_DrawScreenFade( void )
|
|
{
|
|
screenfade_t *sf = &clgame.fade;
|
|
int iFadeAlpha, testFlags;
|
|
|
|
// keep pushing reset time out indefinitely
|
|
if( sf->fadeFlags & FFADE_STAYOUT )
|
|
sf->fadeReset = cl.time + 0.1f;
|
|
|
|
if( sf->fadeReset == 0.0f && sf->fadeEnd == 0.0f )
|
|
return; // inactive
|
|
|
|
// all done?
|
|
if(( cl.time > sf->fadeReset ) && ( cl.time > sf->fadeEnd ))
|
|
{
|
|
memset( &clgame.fade, 0, sizeof( clgame.fade ));
|
|
return;
|
|
}
|
|
|
|
testFlags = (sf->fadeFlags & ~FFADE_MODULATE);
|
|
|
|
// fading...
|
|
if( testFlags == FFADE_STAYOUT )
|
|
{
|
|
iFadeAlpha = sf->fadealpha;
|
|
}
|
|
else
|
|
{
|
|
iFadeAlpha = sf->fadeSpeed * ( sf->fadeEnd - cl.time );
|
|
if( sf->fadeFlags & FFADE_OUT ) iFadeAlpha += sf->fadealpha;
|
|
iFadeAlpha = bound( 0, iFadeAlpha, sf->fadealpha );
|
|
}
|
|
|
|
ref.dllFuncs.Color4ub( sf->fader, sf->fadeg, sf->fadeb, iFadeAlpha );
|
|
|
|
if( sf->fadeFlags & FFADE_MODULATE )
|
|
ref.dllFuncs.GL_SetRenderMode( kRenderTransAdd );
|
|
else ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture );
|
|
ref.dllFuncs.R_DrawStretchPic( 0, 0, refState.width, refState.height, 0, 0, 1, 1,
|
|
R_GetBuiltinTexture( REF_WHITE_TEXTURE ));
|
|
ref.dllFuncs.Color4ub( 255, 255, 255, 255 );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
CL_InitTitles
|
|
|
|
parse all messages that declared in titles.txt
|
|
and hold them into permament memory pool
|
|
====================
|
|
*/
|
|
static void CL_InitTitles( const char *filename )
|
|
{
|
|
fs_offset_t fileSize;
|
|
byte *pMemFile;
|
|
int i;
|
|
|
|
// initialize text messages (game_text)
|
|
for( i = 0; i < MAX_TEXTCHANNELS; i++ )
|
|
{
|
|
cl_textmessage[i].pName = _copystring( clgame.mempool, va( TEXT_MSGNAME, i ), __FILE__, __LINE__ );
|
|
cl_textmessage[i].pMessage = cl_textbuffer[i];
|
|
}
|
|
|
|
// 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, false );
|
|
if( !pMemFile ) return;
|
|
|
|
CL_TextMessageParse( pMemFile, fileSize );
|
|
Mem_Free( pMemFile );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
CL_HudMessage
|
|
|
|
Template to show hud messages
|
|
====================
|
|
*/
|
|
void CL_HudMessage( const char *pMessage )
|
|
{
|
|
if( !COM_CheckString( pMessage )) return;
|
|
CL_DispatchUserMessage( "HudText", Q_strlen( pMessage ), (void *)pMessage );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
CL_ParseTextMessage
|
|
|
|
Parse TE_TEXTMESSAGE
|
|
====================
|
|
*/
|
|
void CL_ParseTextMessage( sizebuf_t *msg )
|
|
{
|
|
static int msgindex = 0;
|
|
client_textmessage_t *text;
|
|
int channel;
|
|
|
|
// read channel ( 0 - auto)
|
|
channel = MSG_ReadByte( msg );
|
|
|
|
if( channel <= 0 || channel > ( MAX_TEXTCHANNELS - 1 ))
|
|
{
|
|
channel = msgindex;
|
|
msgindex = (msgindex + 1) & (MAX_TEXTCHANNELS - 1);
|
|
}
|
|
|
|
// grab message channel
|
|
text = &cl_textmessage[channel];
|
|
|
|
text->x = (float)(MSG_ReadShort( msg ) / 8192.0f);
|
|
text->y = (float)(MSG_ReadShort( msg ) / 8192.0f);
|
|
text->effect = MSG_ReadByte( msg );
|
|
text->r1 = MSG_ReadByte( msg );
|
|
text->g1 = MSG_ReadByte( msg );
|
|
text->b1 = MSG_ReadByte( msg );
|
|
text->a1 = MSG_ReadByte( msg );
|
|
text->r2 = MSG_ReadByte( msg );
|
|
text->g2 = MSG_ReadByte( msg );
|
|
text->b2 = MSG_ReadByte( msg );
|
|
text->a2 = MSG_ReadByte( msg );
|
|
text->fadein = (float)(MSG_ReadShort( msg ) / 256.0f );
|
|
text->fadeout = (float)(MSG_ReadShort( msg ) / 256.0f );
|
|
text->holdtime = (float)(MSG_ReadShort( msg ) / 256.0f );
|
|
|
|
if( text->effect == 2 )
|
|
text->fxtime = (float)(MSG_ReadShort( msg ) / 256.0f );
|
|
else text->fxtime = 0.0f;
|
|
|
|
// to prevent grab too long messages
|
|
Q_strncpy( (char *)text->pMessage, MSG_ReadString( msg ), 2048 );
|
|
|
|
CL_HudMessage( text->pName );
|
|
}
|
|
|
|
/*
|
|
================
|
|
CL_ParseFinaleCutscene
|
|
|
|
show display finale or cutscene message
|
|
================
|
|
*/
|
|
void CL_ParseFinaleCutscene( sizebuf_t *msg, int level )
|
|
{
|
|
static int msgindex = 0;
|
|
client_textmessage_t *text;
|
|
int channel;
|
|
|
|
cl.intermission = level;
|
|
|
|
channel = msgindex;
|
|
msgindex = (msgindex + 1) & (MAX_TEXTCHANNELS - 1);
|
|
|
|
// grab message channel
|
|
text = &cl_textmessage[channel];
|
|
|
|
// NOTE: svc_finale and svc_cutscene has a
|
|
// predefined settings like Quake-style
|
|
text->x = -1.0f;
|
|
text->y = 0.15f;
|
|
text->effect = 2; // scan out effect
|
|
text->r1 = 245;
|
|
text->g1 = 245;
|
|
text->b1 = 245;
|
|
text->a1 = 0; // unused
|
|
text->r2 = 0;
|
|
text->g2 = 0;
|
|
text->b2 = 0;
|
|
text->a2 = 0;
|
|
text->fadein = 0.15f;
|
|
text->fadeout = 0.0f;
|
|
text->holdtime = 99999.0f;
|
|
text->fxtime = 0.0f;
|
|
|
|
// to prevent grab too long messages
|
|
Q_strncpy( (char *)text->pMessage, MSG_ReadString( msg ), 2048 );
|
|
|
|
if( *text->pMessage == '\0' )
|
|
return; // no real text
|
|
|
|
CL_HudMessage( text->pName );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
CL_GetLocalPlayer
|
|
|
|
Render callback for studio models
|
|
====================
|
|
*/
|
|
cl_entity_t *CL_GetLocalPlayer( void )
|
|
{
|
|
cl_entity_t *player;
|
|
|
|
player = CL_EDICT_NUM( cl.playernum + 1 );
|
|
Assert( player != NULL );
|
|
|
|
return player;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
CL_GetMaxlients
|
|
|
|
Render callback for studio models
|
|
====================
|
|
*/
|
|
int GAME_EXPORT CL_GetMaxClients( void )
|
|
{
|
|
return cl.maxclients;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
CL_SoundFromIndex
|
|
|
|
return soundname from index
|
|
====================
|
|
*/
|
|
const char *CL_SoundFromIndex( int index )
|
|
{
|
|
sfx_t *sfx = NULL;
|
|
int hSound;
|
|
|
|
// make sure what we in-bounds
|
|
index = bound( 0, index, MAX_SOUNDS );
|
|
hSound = cl.sound_index[index];
|
|
|
|
if( !hSound )
|
|
{
|
|
Con_DPrintf( S_ERROR "CL_SoundFromIndex: invalid sound index %i\n", index );
|
|
return NULL;
|
|
}
|
|
|
|
sfx = S_GetSfxByHandle( hSound );
|
|
if( !sfx )
|
|
{
|
|
Con_DPrintf( S_ERROR "CL_SoundFromIndex: bad sfx for index %i\n", index );
|
|
return NULL;
|
|
}
|
|
|
|
return sfx->name;
|
|
}
|
|
|
|
/*
|
|
=========
|
|
SPR_EnableScissor
|
|
|
|
=========
|
|
*/
|
|
static void GAME_EXPORT SPR_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;
|
|
}
|
|
|
|
/*
|
|
=========
|
|
SPR_DisableScissor
|
|
|
|
=========
|
|
*/
|
|
static void GAME_EXPORT SPR_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;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
CL_DrawCrosshair
|
|
|
|
Render crosshair
|
|
====================
|
|
*/
|
|
void CL_DrawCrosshair( void )
|
|
{
|
|
int x, y, width, height;
|
|
float xscale, yscale;
|
|
|
|
if( !clgame.ds.pCrosshair || !cl_crosshair->value )
|
|
return;
|
|
|
|
// any camera on or client is died
|
|
if( cl.local.health <= 0 || cl.viewentity != ( cl.playernum + 1 ))
|
|
return;
|
|
|
|
// get crosshair dimension
|
|
width = clgame.ds.rcCrosshair.right - clgame.ds.rcCrosshair.left;
|
|
height = clgame.ds.rcCrosshair.bottom - clgame.ds.rcCrosshair.top;
|
|
|
|
x = clgame.viewport[0] + ( clgame.viewport[2] >> 1 );
|
|
y = clgame.viewport[1] + ( clgame.viewport[3] >> 1 );
|
|
|
|
// g-cont - cl.crosshairangle is the autoaim angle.
|
|
// if we're not using autoaim, just draw in the middle of the screen
|
|
if( !VectorIsNull( cl.crosshairangle ))
|
|
{
|
|
vec3_t angles;
|
|
vec3_t forward;
|
|
vec3_t point, screen;
|
|
|
|
VectorAdd( refState.viewangles, cl.crosshairangle, angles );
|
|
AngleVectors( angles, forward, NULL, NULL );
|
|
VectorAdd( refState.vieworg, forward, point );
|
|
ref.dllFuncs.WorldToScreen( point, screen );
|
|
|
|
x += ( clgame.viewport[2] >> 1 ) * screen[0] + 0.5f;
|
|
y += ( clgame.viewport[3] >> 1 ) * screen[1] + 0.5f;
|
|
}
|
|
|
|
// back to logical sizes
|
|
xscale = (float)clgame.scrInfo.iWidth / refState.width;
|
|
yscale = (float)clgame.scrInfo.iHeight / refState.height;
|
|
|
|
x *= xscale;
|
|
y *= yscale;
|
|
|
|
// move at center the screen
|
|
x -= 0.5f * width;
|
|
y -= 0.5f * height;
|
|
|
|
clgame.ds.pSprite = clgame.ds.pCrosshair;
|
|
Vector4Copy( clgame.ds.rgbaCrosshair, clgame.ds.spriteColor );
|
|
|
|
pfnSPR_DrawHoles( 0, x, y, &clgame.ds.rcCrosshair );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CL_DrawLoading
|
|
|
|
draw loading progress bar
|
|
=============
|
|
*/
|
|
static void CL_DrawLoadingOrPaused( qboolean paused, float percent )
|
|
{
|
|
int x, y, width, height, right;
|
|
|
|
R_GetTextureParms( &width, &height, paused ? cls.pauseIcon : cls.loadingBar );
|
|
x = ( clgame.scrInfo.iWidth - width ) >> 1;
|
|
y = ( clgame.scrInfo.iHeight - height) >> 1;
|
|
|
|
SPR_AdjustSizei( &x, &y, &width, &height );
|
|
|
|
if( !paused && cl_allow_levelshots->value )
|
|
{
|
|
float step, s2;
|
|
|
|
ref.dllFuncs.Color4ub( 128, 128, 128, 255 );
|
|
ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture );
|
|
ref.dllFuncs.R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, cls.loadingBar );
|
|
|
|
step = (float)width / 100.0f;
|
|
right = (int)ceil( percent * step );
|
|
s2 = (float)right / width;
|
|
width = right;
|
|
|
|
ref.dllFuncs.Color4ub( 208, 152, 0, 255 );
|
|
ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture );
|
|
ref.dllFuncs.R_DrawStretchPic( x, y, width, height, 0, 0, s2, 1, cls.loadingBar );
|
|
ref.dllFuncs.Color4ub( 255, 255, 255, 255 );
|
|
}
|
|
else
|
|
{
|
|
ref.dllFuncs.Color4ub( 255, 255, 255, 255 );
|
|
ref.dllFuncs.GL_SetRenderMode( kRenderTransTexture );
|
|
ref.dllFuncs.R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, cls.loadingBar );
|
|
}
|
|
}
|
|
|
|
void CL_DrawHUD( int state )
|
|
{
|
|
if( state == CL_ACTIVE && !cl.video_prepped )
|
|
state = CL_LOADING;
|
|
|
|
if( state == CL_ACTIVE && cl.paused )
|
|
state = CL_PAUSED;
|
|
|
|
switch( state )
|
|
{
|
|
case CL_ACTIVE:
|
|
if( !cl.intermission )
|
|
CL_DrawScreenFade ();
|
|
CL_DrawCrosshair ();
|
|
CL_DrawCenterPrint ();
|
|
clgame.dllFuncs.pfnRedraw( cl.time, cl.intermission );
|
|
if( cl.intermission ) CL_DrawScreenFade ();
|
|
break;
|
|
case CL_PAUSED:
|
|
CL_DrawScreenFade ();
|
|
CL_DrawCrosshair ();
|
|
CL_DrawCenterPrint ();
|
|
clgame.dllFuncs.pfnRedraw( cl.time, cl.intermission );
|
|
CL_DrawLoadingOrPaused( true, 0.0f );
|
|
break;
|
|
case CL_LOADING:
|
|
CL_DrawLoadingOrPaused( false, scr_loading->value );
|
|
break;
|
|
case CL_CHANGELEVEL:
|
|
if( cls.draw_changelevel )
|
|
{
|
|
CL_DrawLoadingOrPaused( false, 100.0f );
|
|
cls.draw_changelevel = false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void CL_ClearUserMessage( char *pszName, int svc_num )
|
|
{
|
|
int i;
|
|
|
|
for( i = 0; i < MAX_USER_MESSAGES && clgame.msg[i].name[0]; i++ )
|
|
if( ( clgame.msg[i].number == svc_num ) && Q_strcmp( clgame.msg[i].name, pszName ) )
|
|
clgame.msg[i].number = 0;
|
|
}
|
|
|
|
void CL_LinkUserMessage( char *pszName, const int svc_num, int iSize )
|
|
{
|
|
int i;
|
|
|
|
if( !pszName || !*pszName )
|
|
Host_Error( "CL_LinkUserMessage: bad message name\n" );
|
|
|
|
if( svc_num <= svc_lastmsg )
|
|
Host_Error( "CL_LinkUserMessage: tried to hook a system message \"%s\"\n", svc_strings[svc_num] );
|
|
|
|
// see if already hooked
|
|
for( i = 0; i < MAX_USER_MESSAGES && clgame.msg[i].name[0]; i++ )
|
|
{
|
|
// NOTE: no check for DispatchFunc, check only name
|
|
if( !Q_stricmp( clgame.msg[i].name, pszName ))
|
|
{
|
|
clgame.msg[i].number = svc_num;
|
|
clgame.msg[i].size = iSize;
|
|
CL_ClearUserMessage( pszName, svc_num );
|
|
return;
|
|
}
|
|
}
|
|
|
|
if( i == MAX_USER_MESSAGES )
|
|
{
|
|
Host_Error( "CL_LinkUserMessage: MAX_USER_MESSAGES hit!\n" );
|
|
return;
|
|
}
|
|
|
|
// register new message without DispatchFunc, so we should parse it properly
|
|
Q_strncpy( clgame.msg[i].name, pszName, sizeof( clgame.msg[i].name ));
|
|
clgame.msg[i].number = svc_num;
|
|
clgame.msg[i].size = iSize;
|
|
CL_ClearUserMessage( pszName, svc_num );
|
|
}
|
|
|
|
void CL_FreeEntity( cl_entity_t *pEdict )
|
|
{
|
|
Assert( pEdict != NULL );
|
|
R_RemoveEfrags( pEdict );
|
|
CL_KillDeadBeams( pEdict );
|
|
}
|
|
|
|
void CL_ClearWorld( void )
|
|
{
|
|
cl_entity_t *worldmodel;
|
|
|
|
worldmodel = clgame.entities;
|
|
worldmodel->curstate.modelindex = 1; // world model
|
|
worldmodel->curstate.solid = SOLID_BSP;
|
|
worldmodel->curstate.movetype = MOVETYPE_PUSH;
|
|
worldmodel->model = cl.worldmodel;
|
|
worldmodel->index = 0;
|
|
|
|
world.max_recursion = 0;
|
|
|
|
clgame.ds.cullMode = TRI_FRONT;
|
|
clgame.numStatics = 0;
|
|
}
|
|
|
|
void CL_InitEdicts( void )
|
|
{
|
|
Assert( clgame.entities == NULL );
|
|
|
|
if( !clgame.mempool ) return; // Host_Error without client
|
|
#if XASH_LOW_MEMORY != 2
|
|
CL_UPDATE_BACKUP = ( cl.maxclients <= 1 ) ? SINGLEPLAYER_BACKUP : MULTIPLAYER_BACKUP;
|
|
#endif
|
|
cls.num_client_entities = CL_UPDATE_BACKUP * NUM_PACKET_ENTITIES;
|
|
cls.packet_entities = Mem_Realloc( clgame.mempool, cls.packet_entities, sizeof( entity_state_t ) * cls.num_client_entities );
|
|
clgame.entities = Mem_Calloc( clgame.mempool, sizeof( cl_entity_t ) * clgame.maxEntities );
|
|
clgame.static_entities = Mem_Calloc( clgame.mempool, sizeof( cl_entity_t ) * MAX_STATIC_ENTITIES );
|
|
clgame.numStatics = 0;
|
|
|
|
if(( clgame.maxRemapInfos - 1 ) != clgame.maxEntities )
|
|
{
|
|
CL_ClearAllRemaps (); // purge old remap info
|
|
clgame.maxRemapInfos = clgame.maxEntities + 1;
|
|
clgame.remap_info = (remap_info_t **)Mem_Calloc( clgame.mempool, sizeof( remap_info_t* ) * clgame.maxRemapInfos );
|
|
}
|
|
|
|
ref.dllFuncs.R_ProcessEntData( true );
|
|
}
|
|
|
|
void CL_FreeEdicts( void )
|
|
{
|
|
ref.dllFuncs.R_ProcessEntData( false );
|
|
|
|
if( clgame.entities )
|
|
Mem_Free( clgame.entities );
|
|
clgame.entities = NULL;
|
|
|
|
if( clgame.static_entities )
|
|
Mem_Free( clgame.static_entities );
|
|
clgame.static_entities = NULL;
|
|
|
|
if( cls.packet_entities )
|
|
Z_Free( cls.packet_entities );
|
|
|
|
cls.packet_entities = NULL;
|
|
cls.num_client_entities = 0;
|
|
cls.next_client_entities = 0;
|
|
clgame.numStatics = 0;
|
|
}
|
|
|
|
void CL_ClearEdicts( void )
|
|
{
|
|
if( clgame.entities != NULL )
|
|
return;
|
|
|
|
// in case we stopped with error
|
|
clgame.maxEntities = 2;
|
|
CL_InitEdicts();
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CL_ClearSpriteTextures
|
|
|
|
free studio cache on change level
|
|
==================
|
|
*/
|
|
void CL_ClearSpriteTextures( void )
|
|
{
|
|
int i;
|
|
|
|
for( i = 1; i < MAX_CLIENT_SPRITES; i++ )
|
|
clgame.sprites[i].needload = NL_UNREFERENCED;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CL_LoadHudSprite
|
|
|
|
upload sprite frames
|
|
=============
|
|
*/
|
|
static qboolean CL_LoadHudSprite( const char *szSpriteName, model_t *m_pSprite, uint type, uint texFlags )
|
|
{
|
|
byte *buf;
|
|
fs_offset_t size;
|
|
qboolean loaded;
|
|
|
|
Assert( m_pSprite != NULL );
|
|
|
|
Q_strncpy( m_pSprite->name, szSpriteName, sizeof( m_pSprite->name ));
|
|
|
|
// it's hud sprite, make difference names to prevent free shared textures
|
|
if( type == SPR_CLIENT || type == SPR_HUDSPRITE )
|
|
SetBits( m_pSprite->flags, MODEL_CLIENT );
|
|
m_pSprite->numtexinfo = texFlags; // store texFlags into numtexinfo
|
|
|
|
if( !FS_FileExists( szSpriteName, false ) )
|
|
{
|
|
if( cls.state != ca_active && cl.maxclients > 1 )
|
|
{
|
|
// trying to download sprite from server
|
|
CL_AddClientResource( szSpriteName, t_model );
|
|
m_pSprite->needload = NL_NEEDS_LOADED;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
Con_Reportf( S_ERROR "Could not load HUD sprite %s\n", szSpriteName );
|
|
Mod_FreeModel( m_pSprite );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
buf = FS_LoadFile( szSpriteName, &size, false );
|
|
if( buf == NULL )
|
|
return false;
|
|
|
|
if( type == SPR_MAPSPRITE )
|
|
ref.dllFuncs.Mod_LoadMapSprite( m_pSprite, buf, size, &loaded );
|
|
else
|
|
{
|
|
Mod_LoadSpriteModel( m_pSprite, buf, &loaded, texFlags );
|
|
ref.dllFuncs.Mod_ProcessRenderData( m_pSprite, true, buf );
|
|
}
|
|
|
|
Mem_Free( buf );
|
|
|
|
if( !loaded )
|
|
{
|
|
Mod_FreeModel( m_pSprite );
|
|
return false;
|
|
}
|
|
|
|
m_pSprite->needload = NL_PRESENT;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CL_LoadSpriteModel
|
|
|
|
some sprite models is exist only at client: HUD sprites,
|
|
tent sprites or overview images
|
|
=============
|
|
*/
|
|
static model_t *CL_LoadSpriteModel( const char *filename, uint type, uint texFlags )
|
|
{
|
|
char name[MAX_QPATH];
|
|
model_t *mod;
|
|
int i;
|
|
|
|
if( !COM_CheckString( filename ))
|
|
{
|
|
Con_Reportf( S_ERROR "CL_LoadSpriteModel: bad name!\n" );
|
|
return NULL;
|
|
}
|
|
|
|
Q_strncpy( name, filename, sizeof( name ));
|
|
COM_FixSlashes( name );
|
|
|
|
// slot 0 isn't used
|
|
for( i = 1, mod = clgame.sprites; i < MAX_CLIENT_SPRITES; i++, mod++ )
|
|
{
|
|
if( !Q_stricmp( mod->name, name ))
|
|
{
|
|
if( mod->needload == NL_NEEDS_LOADED )
|
|
{
|
|
if( CL_LoadHudSprite( name, mod, type, texFlags ))
|
|
return mod;
|
|
}
|
|
|
|
// prolonge registration
|
|
mod->needload = NL_PRESENT;
|
|
return mod;
|
|
}
|
|
}
|
|
|
|
// find a free model slot spot
|
|
for( i = 1, mod = clgame.sprites; i < MAX_CLIENT_SPRITES; i++, mod++ )
|
|
if( !mod->name[0] ) break; // this is a valid spot
|
|
|
|
if( i == MAX_CLIENT_SPRITES )
|
|
{
|
|
Con_Printf( S_ERROR "MAX_CLIENT_SPRITES limit exceeded (%d)\n", MAX_CLIENT_SPRITES );
|
|
return NULL;
|
|
}
|
|
|
|
// load new map sprite
|
|
if( CL_LoadHudSprite( name, mod, type, texFlags ))
|
|
return mod;
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CL_LoadClientSprite
|
|
|
|
load sprites for temp ents
|
|
=============
|
|
*/
|
|
model_t *CL_LoadClientSprite( const char *filename )
|
|
{
|
|
return CL_LoadSpriteModel( filename, SPR_CLIENT, 0 );
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
CGame Builtin Functions
|
|
|
|
===============================================================================
|
|
*/
|
|
/*
|
|
=========
|
|
pfnSPR_LoadExt
|
|
|
|
=========
|
|
*/
|
|
HSPRITE pfnSPR_LoadExt( const char *szPicName, uint texFlags )
|
|
{
|
|
model_t *spr;
|
|
|
|
if(( spr = CL_LoadSpriteModel( szPicName, SPR_CLIENT, texFlags )) == NULL )
|
|
return 0;
|
|
|
|
return (spr - clgame.sprites); // return index
|
|
}
|
|
|
|
/*
|
|
=========
|
|
pfnSPR_Load
|
|
|
|
=========
|
|
*/
|
|
HSPRITE GAME_EXPORT pfnSPR_Load( const char *szPicName )
|
|
{
|
|
model_t *spr;
|
|
|
|
if(( spr = CL_LoadSpriteModel( szPicName, SPR_HUDSPRITE, 0 )) == NULL )
|
|
return 0;
|
|
|
|
return (spr - clgame.sprites); // return index
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CL_GetSpritePointer
|
|
|
|
=============
|
|
*/
|
|
const model_t *CL_GetSpritePointer( HSPRITE hSprite )
|
|
{
|
|
model_t *mod;
|
|
|
|
if( hSprite <= 0 || hSprite >= MAX_CLIENT_SPRITES )
|
|
return NULL; // bad image
|
|
mod = &clgame.sprites[hSprite];
|
|
|
|
if( mod->needload == NL_NEEDS_LOADED )
|
|
{
|
|
int type = FBitSet( mod->flags, MODEL_CLIENT ) ? SPR_HUDSPRITE : SPR_MAPSPRITE;
|
|
|
|
if( CL_LoadHudSprite( mod->name, mod, type, mod->numtexinfo ))
|
|
return mod;
|
|
}
|
|
|
|
if( mod->mempool )
|
|
{
|
|
mod->needload = NL_PRESENT;
|
|
return mod;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
=========
|
|
pfnSPR_Frames
|
|
|
|
=========
|
|
*/
|
|
static int GAME_EXPORT pfnSPR_Frames( HSPRITE hPic )
|
|
{
|
|
int numFrames;
|
|
|
|
ref.dllFuncs.R_GetSpriteParms( NULL, NULL, &numFrames, 0, CL_GetSpritePointer( hPic ));
|
|
|
|
return numFrames;
|
|
}
|
|
|
|
/*
|
|
=========
|
|
pfnSPR_Height
|
|
|
|
=========
|
|
*/
|
|
static int GAME_EXPORT pfnSPR_Height( HSPRITE hPic, int frame )
|
|
{
|
|
int sprHeight;
|
|
|
|
ref.dllFuncs.R_GetSpriteParms( NULL, &sprHeight, NULL, frame, CL_GetSpritePointer( hPic ));
|
|
|
|
return sprHeight;
|
|
}
|
|
|
|
/*
|
|
=========
|
|
pfnSPR_Width
|
|
|
|
=========
|
|
*/
|
|
static int GAME_EXPORT pfnSPR_Width( HSPRITE hPic, int frame )
|
|
{
|
|
int sprWidth;
|
|
|
|
ref.dllFuncs.R_GetSpriteParms( &sprWidth, NULL, NULL, frame, CL_GetSpritePointer( hPic ));
|
|
|
|
return sprWidth;
|
|
}
|
|
|
|
/*
|
|
=========
|
|
pfnSPR_Set
|
|
|
|
=========
|
|
*/
|
|
static void GAME_EXPORT pfnSPR_Set( HSPRITE hPic, int r, int g, int b )
|
|
{
|
|
clgame.ds.pSprite = CL_GetSpritePointer( hPic );
|
|
clgame.ds.spriteColor[0] = bound( 0, r, 255 );
|
|
clgame.ds.spriteColor[1] = bound( 0, g, 255 );
|
|
clgame.ds.spriteColor[2] = bound( 0, b, 255 );
|
|
clgame.ds.spriteColor[3] = 255;
|
|
}
|
|
|
|
/*
|
|
=========
|
|
pfnSPR_Draw
|
|
|
|
=========
|
|
*/
|
|
static void GAME_EXPORT pfnSPR_Draw( int frame, int x, int y, const wrect_t *prc )
|
|
{
|
|
ref.dllFuncs.GL_SetRenderMode( kRenderNormal );
|
|
SPR_DrawGeneric( frame, x, y, -1, -1, prc );
|
|
}
|
|
|
|
/*
|
|
=========
|
|
pfnSPR_DrawHoles
|
|
|
|
=========
|
|
*/
|
|
static void GAME_EXPORT pfnSPR_DrawHoles( int frame, int x, int y, const wrect_t *prc )
|
|
{
|
|
#if 1 // REFTODO
|
|
ref.dllFuncs.GL_SetRenderMode( kRenderTransColor );
|
|
#else
|
|
pglEnable( GL_ALPHA_TEST );
|
|
pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
|
|
pglEnable( GL_BLEND );
|
|
#endif
|
|
SPR_DrawGeneric( frame, x, y, -1, -1, prc );
|
|
|
|
#if 1
|
|
ref.dllFuncs.GL_SetRenderMode( kRenderNormal );
|
|
#else
|
|
pglDisable( GL_ALPHA_TEST );
|
|
pglDisable( GL_BLEND );
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
=========
|
|
pfnSPR_DrawAdditive
|
|
|
|
=========
|
|
*/
|
|
static void GAME_EXPORT pfnSPR_DrawAdditive( int frame, int x, int y, const wrect_t *prc )
|
|
{
|
|
#if 1 // REFTODO
|
|
ref.dllFuncs.GL_SetRenderMode( kRenderTransAdd );
|
|
#else
|
|
pglEnable( GL_BLEND );
|
|
pglBlendFunc( GL_ONE, GL_ONE );
|
|
#endif
|
|
|
|
SPR_DrawGeneric( frame, x, y, -1, -1, prc );
|
|
|
|
#if 1 // REFTODO
|
|
ref.dllFuncs.GL_SetRenderMode( kRenderNormal );
|
|
#else
|
|
pglDisable( GL_BLEND );
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
=========
|
|
pfnSPR_GetList
|
|
|
|
for parsing half-life scripts - hud.txt etc
|
|
=========
|
|
*/
|
|
static client_sprite_t *pfnSPR_GetList( char *psz, int *piCount )
|
|
{
|
|
cached_spritelist_t *pEntry = &clgame.sprlist[0];
|
|
int slot, index, numSprites = 0;
|
|
byte *afile;
|
|
char *pfile;
|
|
string token;
|
|
|
|
if( piCount ) *piCount = 0;
|
|
|
|
// see if already in list
|
|
// NOTE: client.dll is cache hud.txt but reparse weapon lists again and again
|
|
// obviously there a memory leak by-design. Cache the sprite lists to prevent it
|
|
for( slot = 0; slot < MAX_CLIENT_SPRITES && pEntry->szListName[0]; slot++ )
|
|
{
|
|
pEntry = &clgame.sprlist[slot];
|
|
|
|
if( !Q_stricmp( pEntry->szListName, psz ))
|
|
{
|
|
if( piCount ) *piCount = pEntry->count;
|
|
return pEntry->pList;
|
|
}
|
|
}
|
|
|
|
if( slot == MAX_CLIENT_SPRITES )
|
|
{
|
|
Con_Printf( S_ERROR "SPR_GetList: overflow cache!\n" );
|
|
return NULL;
|
|
}
|
|
|
|
if( !clgame.itemspath[0] ) // typically it's sprites\*.txt
|
|
COM_ExtractFilePath( psz, clgame.itemspath );
|
|
|
|
afile = FS_LoadFile( psz, NULL, false );
|
|
if( !afile ) return NULL;
|
|
|
|
pfile = (char *)afile;
|
|
pfile = COM_ParseFile( pfile, token );
|
|
numSprites = Q_atoi( token );
|
|
|
|
Q_strncpy( pEntry->szListName, psz, sizeof( pEntry->szListName ));
|
|
|
|
// name, res, pic, x, y, w, h
|
|
pEntry->pList = Mem_Calloc( cls.mempool, sizeof( client_sprite_t ) * numSprites );
|
|
|
|
for( index = 0; index < numSprites; index++ )
|
|
{
|
|
if(( pfile = COM_ParseFile( pfile, token )) == NULL )
|
|
break;
|
|
|
|
Q_strncpy( pEntry->pList[index].szName, token, sizeof( pEntry->pList[0].szName ));
|
|
|
|
// read resolution
|
|
pfile = COM_ParseFile( pfile, token );
|
|
pEntry->pList[index].iRes = Q_atoi( token );
|
|
|
|
// read spritename
|
|
pfile = COM_ParseFile( pfile, token );
|
|
Q_strncpy( pEntry->pList[index].szSprite, token, sizeof( pEntry->pList[0].szSprite ));
|
|
|
|
// parse rectangle
|
|
pfile = COM_ParseFile( pfile, token );
|
|
pEntry->pList[index].rc.left = Q_atoi( token );
|
|
|
|
pfile = COM_ParseFile( pfile, token );
|
|
pEntry->pList[index].rc.top = Q_atoi( token );
|
|
|
|
pfile = COM_ParseFile( pfile, token );
|
|
pEntry->pList[index].rc.right = pEntry->pList[index].rc.left + Q_atoi( token );
|
|
|
|
pfile = COM_ParseFile( pfile, token );
|
|
pEntry->pList[index].rc.bottom = pEntry->pList[index].rc.top + Q_atoi( token );
|
|
|
|
pEntry->count++;
|
|
}
|
|
|
|
if( index < numSprites )
|
|
Con_DPrintf( S_WARN "unexpected end of %s (%i should be %i)\n", psz, numSprites, index );
|
|
if( piCount ) *piCount = pEntry->count;
|
|
Mem_Free( afile );
|
|
|
|
return pEntry->pList;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CL_FillRGBA
|
|
|
|
=============
|
|
*/
|
|
void GAME_EXPORT CL_FillRGBA( int x, int y, int w, int h, int r, int g, int b, int a )
|
|
{
|
|
float _x = x, _y = y, _w = w, _h = h;
|
|
|
|
r = bound( 0, r, 255 );
|
|
g = bound( 0, g, 255 );
|
|
b = bound( 0, b, 255 );
|
|
a = bound( 0, a, 255 );
|
|
|
|
SPR_AdjustSize( &_x, &_y, &_w, &_h );
|
|
|
|
#if 1
|
|
ref.dllFuncs.FillRGBA( _x, _y, _w, _h, r, g, b, a );
|
|
#else
|
|
pglDisable( GL_TEXTURE_2D );
|
|
pglEnable( GL_BLEND );
|
|
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
|
|
pglBlendFunc( GL_SRC_ALPHA, GL_ONE );
|
|
pglColor4f( r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f );
|
|
|
|
pglBegin( GL_QUADS );
|
|
pglVertex2f( _x, _y );
|
|
pglVertex2f( _x + _w, _y );
|
|
pglVertex2f( _x + _w, _y + _h );
|
|
pglVertex2f( _x, _y + _h );
|
|
pglEnd ();
|
|
|
|
pglColor3f( 1.0f, 1.0f, 1.0f );
|
|
pglEnable( GL_TEXTURE_2D );
|
|
pglDisable( GL_BLEND );
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnGetScreenInfo
|
|
|
|
get actual screen info
|
|
=============
|
|
*/
|
|
int GAME_EXPORT CL_GetScreenInfo( SCREENINFO *pscrinfo )
|
|
{
|
|
float scale_factor = hud_scale->value;
|
|
|
|
// setup screen info
|
|
clgame.scrInfo.iSize = sizeof( clgame.scrInfo );
|
|
clgame.scrInfo.iFlags = SCRINFO_SCREENFLASH;
|
|
|
|
if( scale_factor && scale_factor != 1.0f)
|
|
{
|
|
clgame.scrInfo.iWidth = (float)refState.width / scale_factor;
|
|
clgame.scrInfo.iHeight = (float)refState.height / scale_factor;
|
|
clgame.scrInfo.iFlags |= SCRINFO_STRETCHED;
|
|
}
|
|
else
|
|
{
|
|
clgame.scrInfo.iWidth = refState.width;
|
|
clgame.scrInfo.iHeight = refState.height;
|
|
clgame.scrInfo.iFlags &= ~SCRINFO_STRETCHED;
|
|
}
|
|
|
|
if( !pscrinfo ) return 0;
|
|
|
|
if( pscrinfo->iSize != clgame.scrInfo.iSize )
|
|
clgame.scrInfo.iSize = pscrinfo->iSize;
|
|
|
|
// copy screeninfo out
|
|
memcpy( pscrinfo, &clgame.scrInfo, clgame.scrInfo.iSize );
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnSetCrosshair
|
|
|
|
setup crosshair
|
|
=============
|
|
*/
|
|
static void GAME_EXPORT 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.pCrosshair = CL_GetSpritePointer( hspr );
|
|
clgame.ds.rcCrosshair = rc;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnHookUserMsg
|
|
|
|
=============
|
|
*/
|
|
static int GAME_EXPORT pfnHookUserMsg( const char *pszName, pfnUserMsgHook pfn )
|
|
{
|
|
int i;
|
|
|
|
// ignore blank names or invalid callbacks
|
|
if( !pszName || !*pszName || !pfn )
|
|
return 0;
|
|
|
|
for( i = 0; i < MAX_USER_MESSAGES && clgame.msg[i].name[0]; i++ )
|
|
{
|
|
// see if already hooked
|
|
if( !Q_stricmp( clgame.msg[i].name, pszName ))
|
|
return 1;
|
|
}
|
|
|
|
if( i == MAX_USER_MESSAGES )
|
|
{
|
|
Host_Error( "HookUserMsg: MAX_USER_MESSAGES hit!\n" );
|
|
return 0;
|
|
}
|
|
|
|
// hook new message
|
|
Q_strncpy( clgame.msg[i].name, pszName, sizeof( clgame.msg[i].name ));
|
|
clgame.msg[i].func = pfn;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnServerCmd
|
|
|
|
=============
|
|
*/
|
|
static int GAME_EXPORT pfnServerCmd( const char *szCmdString )
|
|
{
|
|
string buf;
|
|
|
|
if( !COM_CheckString( szCmdString ))
|
|
return 0;
|
|
|
|
// just like the client typed "cmd xxxxx" at the console
|
|
Q_snprintf( buf, sizeof( buf ) - 1, "cmd %s\n", szCmdString );
|
|
Cbuf_AddText( buf );
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnClientCmd
|
|
|
|
=============
|
|
*/
|
|
static int GAME_EXPORT pfnClientCmd( const char *szCmdString )
|
|
{
|
|
if( !COM_CheckString( szCmdString ))
|
|
return 0;
|
|
|
|
if( cls.initialized )
|
|
{
|
|
Cbuf_AddText( szCmdString );
|
|
Cbuf_AddText( "\n" );
|
|
}
|
|
else
|
|
{
|
|
// will exec later
|
|
Q_strncat( host.deferred_cmd, va( "%s\n", szCmdString ), sizeof( host.deferred_cmd ));
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnGetPlayerInfo
|
|
|
|
=============
|
|
*/
|
|
static void GAME_EXPORT 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 >= cl.maxclients || ent_num < 0 || !cl.players[ent_num].name[0] )
|
|
{
|
|
pinfo->name = NULL;
|
|
pinfo->thisplayer = false;
|
|
return;
|
|
}
|
|
|
|
player = &cl.players[ent_num];
|
|
pinfo->thisplayer = ( ent_num == cl.playernum ) ? true : false;
|
|
pinfo->name = player->name;
|
|
pinfo->model = player->model;
|
|
pinfo->spectator = player->spectator;
|
|
pinfo->ping = player->ping;
|
|
pinfo->packetloss = player->packet_loss;
|
|
pinfo->topcolor = player->topcolor;
|
|
pinfo->bottomcolor = player->bottomcolor;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnPlaySoundByName
|
|
|
|
=============
|
|
*/
|
|
static void GAME_EXPORT pfnPlaySoundByName( const char *szSound, float volume )
|
|
{
|
|
int hSound = S_RegisterSound( szSound );
|
|
S_StartSound( NULL, cl.viewentity, CHAN_ITEM, hSound, volume, ATTN_NORM, PITCH_NORM, SND_STOP_LOOPING );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnPlaySoundByIndex
|
|
|
|
=============
|
|
*/
|
|
static void GAME_EXPORT pfnPlaySoundByIndex( int iSound, float volume )
|
|
{
|
|
int hSound;
|
|
|
|
// make sure what we in-bounds
|
|
iSound = bound( 0, iSound, MAX_SOUNDS );
|
|
hSound = cl.sound_index[iSound];
|
|
if( !hSound ) return;
|
|
|
|
S_StartSound( NULL, cl.viewentity, CHAN_ITEM, hSound, volume, ATTN_NORM, PITCH_NORM, SND_STOP_LOOPING );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnTextMessageGet
|
|
|
|
returns specified message from titles.txt
|
|
=============
|
|
*/
|
|
client_textmessage_t *CL_TextMessageGet( const char *pName )
|
|
{
|
|
int i;
|
|
|
|
// first check internal messages
|
|
for( i = 0; i < MAX_TEXTCHANNELS; i++ )
|
|
{
|
|
if( !Q_strcmp( pName, va( TEXT_MSGNAME, i )))
|
|
return cl_textmessage + i;
|
|
}
|
|
|
|
// find desired message
|
|
for( i = 0; i < clgame.numTitles; i++ )
|
|
{
|
|
if( !Q_stricmp( pName, clgame.titles[i].pName ))
|
|
return clgame.titles + i;
|
|
}
|
|
return NULL; // found nothing
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnDrawCharacter
|
|
|
|
returns drawed chachter width (in real screen pixels)
|
|
=============
|
|
*/
|
|
static int GAME_EXPORT pfnDrawCharacter( int x, int y, int number, int r, int g, int b )
|
|
{
|
|
if( !cls.creditsFont.valid )
|
|
return 0;
|
|
|
|
if( hud_utf8->value )
|
|
number = Con_UtfProcessChar( number );
|
|
|
|
number &= 255;
|
|
|
|
if( number < 32 ) return 0;
|
|
if( y < -clgame.scrInfo.iCharHeight )
|
|
return 0;
|
|
|
|
clgame.ds.adjust_size = true;
|
|
pfnPIC_Set( cls.creditsFont.hFontTexture, r, g, b, 255 );
|
|
pfnPIC_DrawAdditive( x, y, -1, -1, &cls.creditsFont.fontRc[number] );
|
|
clgame.ds.adjust_size = false;
|
|
|
|
return clgame.scrInfo.charWidths[number];
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnDrawConsoleString
|
|
|
|
drawing string like a console string
|
|
=============
|
|
*/
|
|
int GAME_EXPORT pfnDrawConsoleString( int x, int y, char *string )
|
|
{
|
|
int drawLen;
|
|
|
|
if( !COM_CheckString( string ))
|
|
return 0; // silent ignore
|
|
Con_SetFont( con_fontsize->value );
|
|
|
|
clgame.ds.adjust_size = true;
|
|
drawLen = Con_DrawString( x, y, string, clgame.ds.textColor );
|
|
MakeRGBA( clgame.ds.textColor, 255, 255, 255, 255 );
|
|
clgame.ds.adjust_size = false;
|
|
|
|
Con_RestoreFont();
|
|
|
|
return (x + drawLen); // exclude color prexfixes
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnDrawSetTextColor
|
|
|
|
set color for anything
|
|
=============
|
|
*/
|
|
void GAME_EXPORT pfnDrawSetTextColor( float r, float g, float b )
|
|
{
|
|
// bound color and convert to byte
|
|
clgame.ds.textColor[0] = (byte)bound( 0, r * 255, 255 );
|
|
clgame.ds.textColor[1] = (byte)bound( 0, g * 255, 255 );
|
|
clgame.ds.textColor[2] = (byte)bound( 0, b * 255, 255 );
|
|
clgame.ds.textColor[3] = (byte)0xFF;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnDrawConsoleStringLen
|
|
|
|
compute string length in screen pixels
|
|
=============
|
|
*/
|
|
void GAME_EXPORT pfnDrawConsoleStringLen( const char *pText, int *length, int *height )
|
|
{
|
|
Con_SetFont( con_fontsize->value );
|
|
Con_DrawStringLen( pText, length, height );
|
|
Con_RestoreFont();
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnConsolePrint
|
|
|
|
prints directly into console (can skip notify)
|
|
=============
|
|
*/
|
|
static void GAME_EXPORT pfnConsolePrint( const char *string )
|
|
{
|
|
Con_Printf( "%s", string );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnCenterPrint
|
|
|
|
holds and fade message at center of screen
|
|
like trigger_multiple message in q1
|
|
=============
|
|
*/
|
|
static void GAME_EXPORT pfnCenterPrint( const char *string )
|
|
{
|
|
CL_CenterPrint( string, 0.25f );
|
|
}
|
|
|
|
/*
|
|
=========
|
|
GetWindowCenterX
|
|
|
|
=========
|
|
*/
|
|
static int GAME_EXPORT pfnGetWindowCenterX( void )
|
|
{
|
|
int x = 0;
|
|
#if XASH_WIN32
|
|
if( m_ignore->value )
|
|
{
|
|
POINT pos;
|
|
GetCursorPos( &pos );
|
|
return pos.x;
|
|
}
|
|
#endif
|
|
|
|
#if XASH_SDL == 2
|
|
SDL_GetWindowPosition( host.hWnd, &x, NULL );
|
|
#endif
|
|
|
|
return host.window_center_x + x;
|
|
}
|
|
|
|
/*
|
|
=========
|
|
GetWindowCenterY
|
|
|
|
=========
|
|
*/
|
|
static int GAME_EXPORT pfnGetWindowCenterY( void )
|
|
{
|
|
int y = 0;
|
|
#if XASH_WIN32
|
|
if( m_ignore->value )
|
|
{
|
|
POINT pos;
|
|
GetCursorPos( &pos );
|
|
return pos.y;
|
|
}
|
|
#endif
|
|
|
|
#if XASH_SDL == 2
|
|
SDL_GetWindowPosition( host.hWnd, NULL, &y );
|
|
#endif
|
|
|
|
return host.window_center_y + y;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnGetViewAngles
|
|
|
|
return interpolated angles from previous frame
|
|
=============
|
|
*/
|
|
static void GAME_EXPORT pfnGetViewAngles( float *angles )
|
|
{
|
|
if( angles ) VectorCopy( cl.viewangles, angles );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnSetViewAngles
|
|
|
|
return interpolated angles from previous frame
|
|
=============
|
|
*/
|
|
static void GAME_EXPORT pfnSetViewAngles( float *angles )
|
|
{
|
|
if( angles ) VectorCopy( angles, cl.viewangles );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnPhysInfo_ValueForKey
|
|
|
|
=============
|
|
*/
|
|
static const char* GAME_EXPORT pfnPhysInfo_ValueForKey( const char *key )
|
|
{
|
|
return Info_ValueForKey( cls.physinfo, key );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnServerInfo_ValueForKey
|
|
|
|
=============
|
|
*/
|
|
static const char* GAME_EXPORT pfnServerInfo_ValueForKey( const char *key )
|
|
{
|
|
return Info_ValueForKey( cl.serverinfo, key );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnGetClientMaxspeed
|
|
|
|
value that come from server
|
|
=============
|
|
*/
|
|
static float GAME_EXPORT pfnGetClientMaxspeed( void )
|
|
{
|
|
return cl.local.maxspeed;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnIsNoClipping
|
|
|
|
=============
|
|
*/
|
|
static int GAME_EXPORT pfnIsNoClipping( void )
|
|
{
|
|
return ( cl.frames[cl.parsecountmod].playerstate[cl.playernum].movetype == MOVETYPE_NOCLIP );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnGetViewModel
|
|
|
|
=============
|
|
*/
|
|
cl_entity_t* GAME_EXPORT CL_GetViewModel( void )
|
|
{
|
|
return &clgame.viewent;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnGetClientTime
|
|
|
|
=============
|
|
*/
|
|
static float GAME_EXPORT pfnGetClientTime( void )
|
|
{
|
|
return cl.time;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnCalcShake
|
|
|
|
=============
|
|
*/
|
|
void GAME_EXPORT pfnCalcShake( void )
|
|
{
|
|
int i;
|
|
float fraction, freq;
|
|
float localAmp;
|
|
|
|
if( clgame.shake.time == 0 )
|
|
return;
|
|
|
|
if(( cl.time > clgame.shake.time ) || clgame.shake.amplitude <= 0 || clgame.shake.frequency <= 0 )
|
|
{
|
|
memset( &clgame.shake, 0, sizeof( clgame.shake ));
|
|
return;
|
|
}
|
|
|
|
if( cl.time > clgame.shake.next_shake )
|
|
{
|
|
// higher frequency means we recalc the extents more often and perturb the display again
|
|
clgame.shake.next_shake = cl.time + ( 1.0f / clgame.shake.frequency );
|
|
|
|
// compute random shake extents (the shake will settle down from this)
|
|
for( i = 0; i < 3; i++ )
|
|
clgame.shake.offset[i] = COM_RandomFloat( -clgame.shake.amplitude, clgame.shake.amplitude );
|
|
clgame.shake.angle = COM_RandomFloat( -clgame.shake.amplitude * 0.25f, clgame.shake.amplitude * 0.25f );
|
|
}
|
|
|
|
// ramp down amplitude over duration (fraction goes from 1 to 0 linearly with slope 1/duration)
|
|
fraction = ( clgame.shake.time - cl.time ) / clgame.shake.duration;
|
|
|
|
// ramp up frequency over duration
|
|
if( fraction )
|
|
{
|
|
freq = ( clgame.shake.frequency / fraction );
|
|
}
|
|
else
|
|
{
|
|
freq = 0;
|
|
}
|
|
|
|
// square fraction to approach zero more quickly
|
|
fraction *= fraction;
|
|
|
|
// Sine wave that slowly settles to zero
|
|
fraction = fraction * sin( cl.time * freq );
|
|
|
|
// add to view origin
|
|
VectorScale( clgame.shake.offset, fraction, clgame.shake.applied_offset );
|
|
|
|
// add to roll
|
|
clgame.shake.applied_angle = clgame.shake.angle * fraction;
|
|
|
|
// drop amplitude a bit, less for higher frequency shakes
|
|
localAmp = clgame.shake.amplitude * ( host.frametime / ( clgame.shake.duration * clgame.shake.frequency ));
|
|
clgame.shake.amplitude -= localAmp;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnApplyShake
|
|
|
|
=============
|
|
*/
|
|
void GAME_EXPORT pfnApplyShake( float *origin, float *angles, float factor )
|
|
{
|
|
if( origin ) VectorMA( origin, factor, clgame.shake.applied_offset, origin );
|
|
if( angles ) angles[ROLL] += clgame.shake.applied_angle * factor;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnIsSpectateOnly
|
|
|
|
=============
|
|
*/
|
|
static int GAME_EXPORT pfnIsSpectateOnly( void )
|
|
{
|
|
return (cls.spectator != 0);
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnPointContents
|
|
|
|
=============
|
|
*/
|
|
static int GAME_EXPORT pfnPointContents( const float *p, int *truecontents )
|
|
{
|
|
int cont, truecont;
|
|
|
|
truecont = cont = PM_PointContents( clgame.pmove, p );
|
|
if( truecontents ) *truecontents = truecont;
|
|
|
|
if( cont <= CONTENTS_CURRENT_0 && cont >= CONTENTS_CURRENT_DOWN )
|
|
cont = CONTENTS_WATER;
|
|
return cont;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnTraceLine
|
|
|
|
=============
|
|
*/
|
|
static pmtrace_t *pfnTraceLine( float *start, float *end, int flags, int usehull, int ignore_pe )
|
|
{
|
|
static pmtrace_t tr;
|
|
int old_usehull;
|
|
|
|
old_usehull = clgame.pmove->usehull;
|
|
clgame.pmove->usehull = usehull;
|
|
|
|
switch( flags )
|
|
{
|
|
case PM_TRACELINE_PHYSENTSONLY:
|
|
tr = PM_PlayerTraceExt( clgame.pmove, start, end, 0, clgame.pmove->numphysent, clgame.pmove->physents, ignore_pe, NULL );
|
|
break;
|
|
case PM_TRACELINE_ANYVISIBLE:
|
|
tr = PM_PlayerTraceExt( clgame.pmove, start, end, 0, clgame.pmove->numvisent, clgame.pmove->visents, ignore_pe, NULL );
|
|
break;
|
|
}
|
|
|
|
clgame.pmove->usehull = old_usehull;
|
|
|
|
return &tr;
|
|
}
|
|
|
|
static void GAME_EXPORT pfnPlaySoundByNameAtLocation( char *szSound, float volume, float *origin )
|
|
{
|
|
int hSound = S_RegisterSound( szSound );
|
|
S_StartSound( origin, cl.viewentity, CHAN_AUTO, hSound, volume, ATTN_NORM, PITCH_NORM, 0 );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnPrecacheEvent
|
|
|
|
=============
|
|
*/
|
|
static word GAME_EXPORT pfnPrecacheEvent( int type, const char* psz )
|
|
{
|
|
return CL_EventIndex( psz );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnHookEvent
|
|
|
|
=============
|
|
*/
|
|
static void GAME_EXPORT pfnHookEvent( const char *filename, pfnEventHook pfn )
|
|
{
|
|
char name[64];
|
|
cl_user_event_t *ev;
|
|
int i;
|
|
|
|
// ignore blank names
|
|
if( !filename || !*filename )
|
|
return;
|
|
|
|
Q_strncpy( name, filename, sizeof( name ));
|
|
COM_FixSlashes( name );
|
|
|
|
// find an empty slot
|
|
for( i = 0; i < MAX_EVENTS; i++ )
|
|
{
|
|
ev = clgame.events[i];
|
|
if( !ev ) break;
|
|
|
|
if( !Q_stricmp( name, ev->name ) && ev->func != NULL )
|
|
{
|
|
Con_Reportf( S_WARN "CL_HookEvent: %s already hooked!\n", name );
|
|
return;
|
|
}
|
|
}
|
|
|
|
CL_RegisterEvent( i, name, pfn );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnKillEvent
|
|
|
|
=============
|
|
*/
|
|
static void GAME_EXPORT pfnKillEvents( int entnum, const char *eventname )
|
|
{
|
|
int i;
|
|
event_state_t *es;
|
|
event_info_t *ei;
|
|
int eventIndex = CL_EventIndex( eventname );
|
|
|
|
if( eventIndex < 0 || eventIndex >= MAX_EVENTS )
|
|
return;
|
|
|
|
if( entnum < 0 || entnum > clgame.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 )
|
|
{
|
|
CL_ResetEvent( ei );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnPlaySound
|
|
|
|
=============
|
|
*/
|
|
void GAME_EXPORT pfnPlaySound( int ent, float *org, int chan, const char *samp, float vol, float attn, int flags, int pitch )
|
|
{
|
|
S_StartSound( org, ent, chan, S_RegisterSound( samp ), vol, attn, pitch, flags );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CL_FindModelIndex
|
|
|
|
=============
|
|
*/
|
|
int GAME_EXPORT CL_FindModelIndex( const char *m )
|
|
{
|
|
char filepath[MAX_QPATH];
|
|
static float lasttimewarn;
|
|
int i;
|
|
|
|
if( !COM_CheckString( m ))
|
|
return 0;
|
|
|
|
Q_strncpy( filepath, m, sizeof( filepath ));
|
|
COM_FixSlashes( filepath );
|
|
|
|
for( i = 0; i < cl.nummodels; i++ )
|
|
{
|
|
if( !cl.models[i+1] )
|
|
continue;
|
|
|
|
if( !Q_stricmp( cl.models[i+1]->name, filepath ))
|
|
return i+1;
|
|
}
|
|
|
|
if( lasttimewarn < host.realtime )
|
|
{
|
|
// tell user about problem (but don't spam console)
|
|
Con_Printf( S_ERROR "Could not find index for model %s: not precached\n", filepath );
|
|
lasttimewarn = host.realtime + 1.0f;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnIsLocal
|
|
|
|
=============
|
|
*/
|
|
int GAME_EXPORT pfnIsLocal( int playernum )
|
|
{
|
|
if( playernum == cl.playernum )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnLocalPlayerDucking
|
|
|
|
=============
|
|
*/
|
|
int GAME_EXPORT pfnLocalPlayerDucking( void )
|
|
{
|
|
return (cl.local.usehull == 1) ? true : false;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnLocalPlayerViewheight
|
|
|
|
=============
|
|
*/
|
|
void GAME_EXPORT pfnLocalPlayerViewheight( float *view_ofs )
|
|
{
|
|
if( view_ofs ) VectorCopy( cl.viewheight, view_ofs );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnLocalPlayerBounds
|
|
|
|
=============
|
|
*/
|
|
void GAME_EXPORT pfnLocalPlayerBounds( int hull, float *mins, float *maxs )
|
|
{
|
|
if( hull >= 0 && hull < 4 )
|
|
{
|
|
if( mins ) VectorCopy( clgame.pmove->player_mins[hull], mins );
|
|
if( maxs ) VectorCopy( clgame.pmove->player_maxs[hull], maxs );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnIndexFromTrace
|
|
|
|
=============
|
|
*/
|
|
int GAME_EXPORT pfnIndexFromTrace( struct pmtrace_s *pTrace )
|
|
{
|
|
if( pTrace->ent >= 0 && pTrace->ent < clgame.pmove->numphysent )
|
|
{
|
|
// return cl.entities number
|
|
return clgame.pmove->physents[pTrace->ent].info;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnGetPhysent
|
|
|
|
=============
|
|
*/
|
|
physent_t *pfnGetPhysent( int idx )
|
|
{
|
|
if( idx >= 0 && idx < clgame.pmove->numphysent )
|
|
{
|
|
// return physent
|
|
return &clgame.pmove->physents[idx];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnGetVisent
|
|
|
|
=============
|
|
*/
|
|
physent_t *pfnGetVisent( int idx )
|
|
{
|
|
if( idx >= 0 && idx < clgame.pmove->numvisent )
|
|
{
|
|
// return physent
|
|
return &clgame.pmove->visents[idx];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnSetTraceHull
|
|
|
|
=============
|
|
*/
|
|
void GAME_EXPORT CL_SetTraceHull( int hull )
|
|
{
|
|
clgame.pmove->usehull = bound( 0, hull, 3 );
|
|
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnPlayerTrace
|
|
|
|
=============
|
|
*/
|
|
void GAME_EXPORT CL_PlayerTrace( float *start, float *end, int traceFlags, int ignore_pe, pmtrace_t *tr )
|
|
{
|
|
if( !tr ) return;
|
|
*tr = PM_PlayerTraceExt( clgame.pmove, start, end, traceFlags, clgame.pmove->numphysent, clgame.pmove->physents, ignore_pe, NULL );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnPlayerTraceExt
|
|
|
|
=============
|
|
*/
|
|
void GAME_EXPORT CL_PlayerTraceExt( float *start, float *end, int traceFlags, int (*pfnIgnore)( physent_t *pe ), pmtrace_t *tr )
|
|
{
|
|
if( !tr ) return;
|
|
*tr = PM_PlayerTraceExt( clgame.pmove, start, end, traceFlags, clgame.pmove->numphysent, clgame.pmove->physents, -1, pfnIgnore );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnTraceTexture
|
|
|
|
=============
|
|
*/
|
|
static const char *pfnTraceTexture( int ground, float *vstart, float *vend )
|
|
{
|
|
physent_t *pe;
|
|
|
|
if( ground < 0 || ground >= clgame.pmove->numphysent )
|
|
return NULL; // bad ground
|
|
|
|
pe = &clgame.pmove->physents[ground];
|
|
return PM_TraceTexture( pe, vstart, vend );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnTraceSurface
|
|
|
|
=============
|
|
*/
|
|
struct msurface_s *pfnTraceSurface( int ground, float *vstart, float *vend )
|
|
{
|
|
physent_t *pe;
|
|
|
|
if( ground < 0 || ground >= clgame.pmove->numphysent )
|
|
return NULL; // bad ground
|
|
|
|
pe = &clgame.pmove->physents[ground];
|
|
return PM_TraceSurface( pe, vstart, vend );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnGetMovevars
|
|
|
|
=============
|
|
*/
|
|
movevars_t *pfnGetMoveVars( void )
|
|
{
|
|
return &clgame.movevars;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnStopAllSounds
|
|
|
|
=============
|
|
*/
|
|
void GAME_EXPORT pfnStopAllSounds( int ent, int entchannel )
|
|
{
|
|
S_StopSound( ent, entchannel, NULL );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
CL_LoadModel
|
|
|
|
=============
|
|
*/
|
|
model_t *CL_LoadModel( const char *modelname, int *index )
|
|
{
|
|
int i;
|
|
|
|
if( index ) *index = -1;
|
|
|
|
if(( i = CL_FindModelIndex( modelname )) == 0 )
|
|
return NULL;
|
|
|
|
if( index ) *index = i;
|
|
|
|
return CL_ModelHandle( i );
|
|
}
|
|
|
|
int GAME_EXPORT CL_AddEntity( int entityType, cl_entity_t *pEnt )
|
|
{
|
|
if( !pEnt ) return false;
|
|
|
|
// clear effects for all temp entities
|
|
if( !pEnt->index ) pEnt->curstate.effects = 0;
|
|
|
|
// let the render reject entity without model
|
|
return CL_AddVisibleEntity( pEnt, entityType );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnGetGameDirectory
|
|
|
|
=============
|
|
*/
|
|
const char *pfnGetGameDirectory( void )
|
|
{
|
|
static char szGetGameDir[MAX_SYSPATH];
|
|
|
|
Q_strcpy( szGetGameDir, GI->gamefolder );
|
|
return szGetGameDir;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
Key_LookupBinding
|
|
|
|
=============
|
|
*/
|
|
const char *Key_LookupBinding( const char *pBinding )
|
|
{
|
|
return Key_KeynumToString( Key_GetKey( pBinding ));
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnGetLevelName
|
|
|
|
=============
|
|
*/
|
|
static const char *pfnGetLevelName( void )
|
|
{
|
|
static char mapname[64];
|
|
|
|
if( cls.state >= ca_connected )
|
|
Q_snprintf( mapname, sizeof( mapname ), "maps/%s.bsp", clgame.mapname );
|
|
else mapname[0] = '\0'; // not in game
|
|
|
|
return mapname;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnGetScreenFade
|
|
|
|
=============
|
|
*/
|
|
void GAME_EXPORT pfnGetScreenFade( struct screenfade_s *fade )
|
|
{
|
|
if( fade ) *fade = clgame.fade;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnSetScreenFade
|
|
|
|
=============
|
|
*/
|
|
static void GAME_EXPORT pfnSetScreenFade( struct screenfade_s *fade )
|
|
{
|
|
if( fade ) clgame.fade = *fade;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnLoadMapSprite
|
|
|
|
=============
|
|
*/
|
|
model_t *pfnLoadMapSprite( const char *filename )
|
|
{
|
|
return CL_LoadSpriteModel( filename, SPR_MAPSPRITE, 0 );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
PlayerInfo_ValueForKey
|
|
|
|
=============
|
|
*/
|
|
const char *PlayerInfo_ValueForKey( int playerNum, const char *key )
|
|
{
|
|
// find the player
|
|
if(( playerNum > cl.maxclients ) || ( playerNum < 1 ))
|
|
return NULL;
|
|
|
|
if( !cl.players[playerNum-1].name[0] )
|
|
return NULL;
|
|
|
|
return Info_ValueForKey( cl.players[playerNum-1].userinfo, key );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
PlayerInfo_SetValueForKey
|
|
|
|
=============
|
|
*/
|
|
void GAME_EXPORT PlayerInfo_SetValueForKey( const char *key, const char *value )
|
|
{
|
|
convar_t *var;
|
|
|
|
if( !Q_strcmp( Info_ValueForKey( cls.userinfo, key ), value ))
|
|
return; // no changes ?
|
|
|
|
var = Cvar_FindVar( key );
|
|
|
|
if( var && FBitSet( var->flags, FCVAR_USERINFO ))
|
|
{
|
|
Cvar_DirectSet( var, value );
|
|
}
|
|
else if( Info_SetValueForStarKey( cls.userinfo, key, value, MAX_INFO_STRING ))
|
|
{
|
|
// time to update server copy of userinfo
|
|
CL_ServerCommand( true, "setinfo \"%s\" \"%s\"\n", key, value );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnGetPlayerUniqueID
|
|
|
|
=============
|
|
*/
|
|
qboolean GAME_EXPORT pfnGetPlayerUniqueID( int iPlayer, char playerID[16] )
|
|
{
|
|
if( iPlayer < 1 || iPlayer > cl.maxclients )
|
|
return false;
|
|
|
|
// make sure there is a player here..
|
|
if( !cl.players[iPlayer-1].userinfo[0] || !cl.players[iPlayer-1].name[0] )
|
|
return false;
|
|
|
|
memcpy( playerID, cl.players[iPlayer-1].hashedcdkey, 16 );
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnGetTrackerIDForPlayer
|
|
|
|
obsolete, unused
|
|
=============
|
|
*/
|
|
int GAME_EXPORT pfnGetTrackerIDForPlayer( int playerSlot )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnGetPlayerForTrackerID
|
|
|
|
obsolete, unused
|
|
=============
|
|
*/
|
|
int GAME_EXPORT pfnGetPlayerForTrackerID( int trackerID )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnServerCmdUnreliable
|
|
|
|
=============
|
|
*/
|
|
int GAME_EXPORT pfnServerCmdUnreliable( char *szCmdString )
|
|
{
|
|
if( !COM_CheckString( szCmdString ))
|
|
return 0;
|
|
|
|
MSG_BeginClientCmd( &cls.datagram, clc_stringcmd );
|
|
MSG_WriteString( &cls.datagram, szCmdString );
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnGetMousePos
|
|
|
|
=============
|
|
*/
|
|
void GAME_EXPORT pfnGetMousePos( struct tagPOINT *ppt )
|
|
{
|
|
if( !ppt )
|
|
return;
|
|
|
|
Platform_GetMousePos( &ppt->x, &ppt->y );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnSetMouseEnable
|
|
|
|
legacy of dinput code
|
|
=============
|
|
*/
|
|
void GAME_EXPORT pfnSetMouseEnable( qboolean fEnable )
|
|
{
|
|
}
|
|
|
|
|
|
/*
|
|
=============
|
|
pfnGetServerTime
|
|
|
|
=============
|
|
*/
|
|
static float GAME_EXPORT pfnGetClientOldTime( void )
|
|
{
|
|
return cl.oldtime;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnGetGravity
|
|
|
|
=============
|
|
*/
|
|
static float GAME_EXPORT pfnGetGravity( void )
|
|
{
|
|
return clgame.movevars.gravity;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnEnableTexSort
|
|
|
|
TODO: implement
|
|
=============
|
|
*/
|
|
static void GAME_EXPORT pfnEnableTexSort( int enable )
|
|
{
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnSetLightmapColor
|
|
|
|
TODO: implement
|
|
=============
|
|
*/
|
|
static void GAME_EXPORT pfnSetLightmapColor( float red, float green, float blue )
|
|
{
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnSetLightmapScale
|
|
|
|
TODO: implement
|
|
=============
|
|
*/
|
|
static void GAME_EXPORT pfnSetLightmapScale( float scale )
|
|
{
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnSPR_DrawGeneric
|
|
|
|
=============
|
|
*/
|
|
static void GAME_EXPORT pfnSPR_DrawGeneric( int frame, int x, int y, const wrect_t *prc, int blendsrc, int blenddst, int width, int height )
|
|
{
|
|
#if 0 // REFTODO:
|
|
pglEnable( GL_BLEND );
|
|
pglBlendFunc( blendsrc, blenddst ); // g-cont. are params is valid?
|
|
#endif
|
|
SPR_DrawGeneric( frame, x, y, width, height, prc );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
LocalPlayerInfo_ValueForKey
|
|
|
|
=============
|
|
*/
|
|
static const char *GAME_EXPORT LocalPlayerInfo_ValueForKey( const char* key )
|
|
{
|
|
return Info_ValueForKey( cls.userinfo, key );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnVGUI2DrawCharacter
|
|
|
|
=============
|
|
*/
|
|
static int GAME_EXPORT pfnVGUI2DrawCharacter( int x, int y, int number, unsigned int font )
|
|
{
|
|
if( !cls.creditsFont.valid )
|
|
return 0;
|
|
|
|
number &= 255;
|
|
|
|
number = Con_UtfProcessChar( number );
|
|
|
|
if( number < 32 ) return 0;
|
|
if( y < -clgame.scrInfo.iCharHeight )
|
|
return 0;
|
|
|
|
clgame.ds.adjust_size = true;
|
|
gameui.ds.gl_texturenum = cls.creditsFont.hFontTexture;
|
|
pfnPIC_DrawAdditive( x, y, -1, -1, &cls.creditsFont.fontRc[number] );
|
|
clgame.ds.adjust_size = false;
|
|
|
|
return clgame.scrInfo.charWidths[number];
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnVGUI2DrawCharacterAdditive
|
|
|
|
=============
|
|
*/
|
|
static int GAME_EXPORT pfnVGUI2DrawCharacterAdditive( int x, int y, int ch, int r, int g, int b, unsigned int font )
|
|
{
|
|
if( !hud_utf8->value )
|
|
ch = Con_UtfProcessChar( ch );
|
|
|
|
return pfnDrawCharacter( x, y, ch, r, g, b );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnDrawString
|
|
|
|
=============
|
|
*/
|
|
static int GAME_EXPORT pfnDrawString( int x, int y, const char *str, int r, int g, int b )
|
|
{
|
|
Con_UtfProcessChar(0);
|
|
|
|
// draw the string until we hit the null character or a newline character
|
|
for ( ; *str != 0 && *str != '\n'; str++ )
|
|
{
|
|
x += pfnVGUI2DrawCharacterAdditive( x, y, (unsigned char)*str, r, g, b, 0 );
|
|
}
|
|
|
|
return x;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnDrawStringReverse
|
|
|
|
=============
|
|
*/
|
|
static int GAME_EXPORT pfnDrawStringReverse( int x, int y, const char *str, int r, int g, int b )
|
|
{
|
|
// find the end of the string
|
|
char *szIt;
|
|
for( szIt = (char*)str; *szIt != 0; szIt++ )
|
|
x -= clgame.scrInfo.charWidths[ (unsigned char) *szIt ];
|
|
pfnDrawString( x, y, str, r, g, b );
|
|
return x;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
GetCareerGameInterface
|
|
|
|
=============
|
|
*/
|
|
static void *GAME_EXPORT GetCareerGameInterface( void )
|
|
{
|
|
Msg( "^1Career GameInterface called!\n" );
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnPlaySoundVoiceByName
|
|
|
|
=============
|
|
*/
|
|
static void GAME_EXPORT pfnPlaySoundVoiceByName( char *filename, float volume, int pitch )
|
|
{
|
|
int hSound = S_RegisterSound( filename );
|
|
|
|
S_StartSound( NULL, cl.viewentity, CHAN_NETWORKVOICE_END + 1, hSound, volume, 1.0, pitch, SND_STOP_LOOPING );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnMP3_InitStream
|
|
|
|
=============
|
|
*/
|
|
static void GAME_EXPORT pfnMP3_InitStream( char *filename, int looping )
|
|
{
|
|
if( !filename )
|
|
{
|
|
S_StopBackgroundTrack();
|
|
return;
|
|
}
|
|
|
|
if( looping )
|
|
{
|
|
S_StartBackgroundTrack( filename, filename, 0, false );
|
|
}
|
|
else
|
|
{
|
|
S_StartBackgroundTrack( filename, NULL, 0, false );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnPlaySoundByNameAtPitch
|
|
|
|
=============
|
|
*/
|
|
static void GAME_EXPORT pfnPlaySoundByNameAtPitch( char *filename, float volume, int pitch )
|
|
{
|
|
int hSound = S_RegisterSound( filename );
|
|
S_StartSound( NULL, cl.viewentity, CHAN_ITEM, hSound, volume, 1.0, pitch, SND_STOP_LOOPING );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnFillRGBABlend
|
|
|
|
=============
|
|
*/
|
|
void GAME_EXPORT CL_FillRGBABlend( int x, int y, int w, int h, int r, int g, int b, int a )
|
|
{
|
|
float _x = x, _y = y, _w = w, _h = h;
|
|
|
|
r = bound( 0, r, 255 );
|
|
g = bound( 0, g, 255 );
|
|
b = bound( 0, b, 255 );
|
|
a = bound( 0, a, 255 );
|
|
|
|
SPR_AdjustSize( &_x, &_y, &_w, &_h );
|
|
|
|
#if 1 // REFTODO:
|
|
ref.dllFuncs.FillRGBABlend( _x, _y, _w, _h, r, g, b, a );
|
|
#else
|
|
pglDisable( GL_TEXTURE_2D );
|
|
pglEnable( GL_BLEND );
|
|
pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
|
|
pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
|
|
pglColor4f( r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f );
|
|
|
|
pglBegin( GL_QUADS );
|
|
pglVertex2f( _x, _y );
|
|
pglVertex2f( _x + _w, _y );
|
|
pglVertex2f( _x + _w, _y + _h );
|
|
pglVertex2f( _x, _y + _h );
|
|
pglEnd ();
|
|
|
|
pglColor3f( 1.0f, 1.0f, 1.0f );
|
|
pglEnable( GL_TEXTURE_2D );
|
|
pglDisable( GL_BLEND );
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnGetAppID
|
|
|
|
=============
|
|
*/
|
|
static int GAME_EXPORT pfnGetAppID( void )
|
|
{
|
|
return 70; // Half-Life AppID
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnVguiWrap2_GetMouseDelta
|
|
|
|
TODO: implement
|
|
=============
|
|
*/
|
|
static void GAME_EXPORT pfnVguiWrap2_GetMouseDelta( int *x, int *y )
|
|
{
|
|
}
|
|
|
|
/*
|
|
=============
|
|
pfnParseFile
|
|
|
|
handle colon separately
|
|
=============
|
|
*/
|
|
char *pfnParseFile( char *data, char *token )
|
|
{
|
|
char *out;
|
|
|
|
host.com_handlecolon = true;
|
|
out = COM_ParseFile( data, token );
|
|
host.com_handlecolon = false;
|
|
|
|
return out;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
TriAPI implementation
|
|
|
|
=================
|
|
*/
|
|
/*
|
|
=================
|
|
TriRenderMode
|
|
=================
|
|
*/
|
|
void TriRenderMode( int mode )
|
|
{
|
|
clgame.ds.renderMode = mode;
|
|
ref.dllFuncs.TriRenderMode( mode );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
TriColor4f
|
|
=================
|
|
*/
|
|
void TriColor4f( float r, float g, float b, float a )
|
|
{
|
|
if( clgame.ds.renderMode == kRenderTransAlpha )
|
|
ref.dllFuncs.Color4ub( r * 255.9f, g * 255.9f, b * 255.9f, a * 255.0f );
|
|
else ref.dllFuncs.Color4f( r * a, g * a, b * a, 1.0 );
|
|
|
|
clgame.ds.triRGBA[0] = r;
|
|
clgame.ds.triRGBA[1] = g;
|
|
clgame.ds.triRGBA[2] = b;
|
|
clgame.ds.triRGBA[3] = a;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
TriColor4ub
|
|
=============
|
|
*/
|
|
void TriColor4ub( byte r, byte g, byte b, byte a )
|
|
{
|
|
clgame.ds.triRGBA[0] = r * (1.0f / 255.0f);
|
|
clgame.ds.triRGBA[1] = g * (1.0f / 255.0f);
|
|
clgame.ds.triRGBA[2] = b * (1.0f / 255.0f);
|
|
clgame.ds.triRGBA[3] = a * (1.0f / 255.0f);
|
|
|
|
ref.dllFuncs.Color4f( clgame.ds.triRGBA[0], clgame.ds.triRGBA[1], clgame.ds.triRGBA[2], 1.0f );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
TriBrightness
|
|
=============
|
|
*/
|
|
void TriBrightness( float brightness )
|
|
{
|
|
float r, g, b;
|
|
|
|
r = clgame.ds.triRGBA[0] * clgame.ds.triRGBA[3] * brightness;
|
|
g = clgame.ds.triRGBA[1] * clgame.ds.triRGBA[3] * brightness;
|
|
b = clgame.ds.triRGBA[2] * clgame.ds.triRGBA[3] * brightness;
|
|
|
|
ref.dllFuncs.Color4f( r, g, b, 1.0f );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
TriCullFace
|
|
=============
|
|
*/
|
|
void TriCullFace( TRICULLSTYLE style )
|
|
{
|
|
clgame.ds.cullMode = style;
|
|
ref.dllFuncs.CullFace( style );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
TriWorldToScreen
|
|
convert world coordinates (x,y,z) into screen (x, y)
|
|
=============
|
|
*/
|
|
int TriWorldToScreen( const float *world, float *screen )
|
|
{
|
|
int retval;
|
|
|
|
retval = ref.dllFuncs.WorldToScreen( world, screen );
|
|
|
|
screen[0] = 0.5f * screen[0] * (float)clgame.viewport[2];
|
|
screen[1] = -0.5f * screen[1] * (float)clgame.viewport[3];
|
|
screen[0] += 0.5f * (float)clgame.viewport[2];
|
|
screen[1] += 0.5f * (float)clgame.viewport[3];
|
|
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
TriBoxInPVS
|
|
|
|
check box in pvs (absmin, absmax)
|
|
=============
|
|
*/
|
|
int TriBoxInPVS( float *mins, float *maxs )
|
|
{
|
|
return Mod_BoxVisible( mins, maxs, ref.dllFuncs.Mod_GetCurrentVis( ));
|
|
}
|
|
|
|
/*
|
|
=============
|
|
TriLightAtPoint
|
|
NOTE: dlights are ignored
|
|
=============
|
|
*/
|
|
void TriLightAtPoint( float *pos, float *value )
|
|
{
|
|
colorVec vLightColor;
|
|
|
|
if( !pos || !value ) return;
|
|
|
|
vLightColor = ref.dllFuncs.R_LightPoint( pos );
|
|
|
|
value[0] = vLightColor.r;
|
|
value[1] = vLightColor.g;
|
|
value[2] = vLightColor.b;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
TriColor4fRendermode
|
|
Heavy legacy of Quake...
|
|
=============
|
|
*/
|
|
void TriColor4fRendermode( float r, float g, float b, float a, int rendermode )
|
|
{
|
|
if( clgame.ds.renderMode == kRenderTransAlpha )
|
|
{
|
|
clgame.ds.triRGBA[3] = a / 255.0f;
|
|
ref.dllFuncs.Color4f( r, g, b, a );
|
|
}
|
|
else ref.dllFuncs.Color4f( r * a, g * a, b * a, 1.0f );
|
|
}
|
|
|
|
|
|
/*
|
|
=============
|
|
TriSpriteTexture
|
|
|
|
bind current texture
|
|
=============
|
|
*/
|
|
int TriSpriteTexture( model_t *pSpriteModel, int frame )
|
|
{
|
|
int gl_texturenum;
|
|
|
|
if(( gl_texturenum = ref.dllFuncs.R_GetSpriteTexture( pSpriteModel, frame )) <= 0 )
|
|
return 0;
|
|
|
|
ref.dllFuncs.GL_Bind( XASH_TEXTURE0, gl_texturenum );
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
DemoApi implementation
|
|
|
|
=================
|
|
*/
|
|
/*
|
|
=================
|
|
Demo_IsRecording
|
|
|
|
=================
|
|
*/
|
|
static int GAME_EXPORT Demo_IsRecording( void )
|
|
{
|
|
return cls.demorecording;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Demo_IsPlayingback
|
|
|
|
=================
|
|
*/
|
|
static int GAME_EXPORT Demo_IsPlayingback( void )
|
|
{
|
|
return cls.demoplayback;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Demo_IsTimeDemo
|
|
|
|
=================
|
|
*/
|
|
static int GAME_EXPORT Demo_IsTimeDemo( void )
|
|
{
|
|
return cls.timedemo;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Demo_WriteBuffer
|
|
|
|
=================
|
|
*/
|
|
static void GAME_EXPORT Demo_WriteBuffer( int size, byte *buffer )
|
|
{
|
|
CL_WriteDemoUserMessage( buffer, size );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
NetworkApi implementation
|
|
|
|
=================
|
|
*/
|
|
/*
|
|
=================
|
|
NetAPI_InitNetworking
|
|
|
|
=================
|
|
*/
|
|
void GAME_EXPORT NetAPI_InitNetworking( void )
|
|
{
|
|
NET_Config( true ); // allow remote
|
|
}
|
|
|
|
/*
|
|
=================
|
|
NetAPI_InitNetworking
|
|
|
|
=================
|
|
*/
|
|
void GAME_EXPORT NetAPI_Status( net_status_t *status )
|
|
{
|
|
qboolean connected = false;
|
|
int packet_loss = 0;
|
|
|
|
Assert( status != NULL );
|
|
|
|
if( cls.state > ca_disconnected && cls.state != ca_cinematic )
|
|
connected = true;
|
|
|
|
if( cls.state == ca_active )
|
|
packet_loss = bound( 0, (int)cls.packet_loss, 100 );
|
|
|
|
status->connected = connected;
|
|
status->connection_time = (connected) ? (host.realtime - cls.netchan.connect_time) : 0.0;
|
|
status->latency = (connected) ? cl.frames[cl.parsecountmod].latency : 0.0;
|
|
status->remote_address = cls.netchan.remote_address;
|
|
status->packet_loss = packet_loss;
|
|
status->local_address = net_local;
|
|
status->rate = rate->value;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
NetAPI_SendRequest
|
|
|
|
=================
|
|
*/
|
|
void GAME_EXPORT NetAPI_SendRequest( int context, int request, int flags, double timeout, netadr_t *remote_address, net_api_response_func_t response )
|
|
{
|
|
net_request_t *nr = NULL;
|
|
string req;
|
|
int i;
|
|
|
|
if( !response )
|
|
{
|
|
Con_DPrintf( S_ERROR "Net_SendRequest: no callbcak specified for request with context %i!\n", context );
|
|
return;
|
|
}
|
|
|
|
if( remote_address->type >= NA_IPX )
|
|
return; // IPX no longer support
|
|
|
|
// find a free request
|
|
for( i = 0; i < MAX_REQUESTS; i++ )
|
|
{
|
|
nr = &clgame.net_requests[i];
|
|
if( !nr->pfnFunc ) break;
|
|
}
|
|
|
|
if( i == MAX_REQUESTS )
|
|
{
|
|
double max_timeout = 0;
|
|
|
|
// no free requests? use oldest
|
|
for( i = 0, nr = NULL; i < MAX_REQUESTS; i++ )
|
|
{
|
|
if(( host.realtime - clgame.net_requests[i].timesend ) > max_timeout )
|
|
{
|
|
max_timeout = host.realtime - clgame.net_requests[i].timesend;
|
|
nr = &clgame.net_requests[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
Assert( nr != NULL );
|
|
|
|
// clear slot
|
|
memset( nr, 0, sizeof( *nr ));
|
|
|
|
// create a new request
|
|
nr->timesend = host.realtime;
|
|
nr->timeout = nr->timesend + timeout;
|
|
nr->pfnFunc = response;
|
|
nr->resp.context = context;
|
|
nr->resp.type = request;
|
|
nr->resp.remote_address = *remote_address;
|
|
nr->flags = flags;
|
|
|
|
if( request == NETAPI_REQUEST_SERVERLIST )
|
|
{
|
|
char fullquery[512] = "1\xFF" "0.0.0.0:0\0" "\\gamedir\\";
|
|
|
|
// make sure what port is specified
|
|
if( !nr->resp.remote_address.port ) nr->resp.remote_address.port = MSG_BigShort( PORT_MASTER );
|
|
|
|
// grab the list from the master server
|
|
Q_strcpy( &fullquery[22], GI->gamefolder );
|
|
NET_SendPacket( NS_CLIENT, Q_strlen( GI->gamefolder ) + 23, fullquery, nr->resp.remote_address );
|
|
clgame.request_type = NET_REQUEST_CLIENT;
|
|
clgame.master_request = nr; // holds the master request unitl the master acking
|
|
}
|
|
else
|
|
{
|
|
// local servers request
|
|
Q_snprintf( req, sizeof( req ), "netinfo %i %i %i", PROTOCOL_VERSION, context, request );
|
|
Netchan_OutOfBandPrint( NS_CLIENT, nr->resp.remote_address, "%s", req );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
NetAPI_CancelRequest
|
|
|
|
=================
|
|
*/
|
|
void GAME_EXPORT NetAPI_CancelRequest( int context )
|
|
{
|
|
net_request_t *nr;
|
|
int i;
|
|
|
|
// find a specified request
|
|
for( i = 0; i < MAX_REQUESTS; i++ )
|
|
{
|
|
nr = &clgame.net_requests[i];
|
|
|
|
if( clgame.net_requests[i].resp.context == context )
|
|
{
|
|
if( nr->pfnFunc )
|
|
{
|
|
SetBits( nr->resp.error, NET_ERROR_TIMEOUT );
|
|
nr->resp.ping = host.realtime - nr->timesend;
|
|
nr->pfnFunc( &nr->resp );
|
|
}
|
|
|
|
if( clgame.net_requests[i].resp.type == NETAPI_REQUEST_SERVERLIST && &clgame.net_requests[i] == clgame.master_request )
|
|
{
|
|
if( clgame.request_type == NET_REQUEST_CLIENT )
|
|
clgame.request_type = NET_REQUEST_CANCEL;
|
|
clgame.master_request = NULL;
|
|
}
|
|
|
|
memset( &clgame.net_requests[i], 0, sizeof( net_request_t ));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
NetAPI_CancelAllRequests
|
|
|
|
=================
|
|
*/
|
|
void GAME_EXPORT NetAPI_CancelAllRequests( void )
|
|
{
|
|
net_request_t *nr;
|
|
int i;
|
|
|
|
// tell the user about cancel
|
|
for( i = 0; i < MAX_REQUESTS; i++ )
|
|
{
|
|
nr = &clgame.net_requests[i];
|
|
if( !nr->pfnFunc ) continue; // not used
|
|
SetBits( nr->resp.error, NET_ERROR_TIMEOUT );
|
|
nr->resp.ping = host.realtime - nr->timesend;
|
|
nr->pfnFunc( &nr->resp );
|
|
}
|
|
|
|
memset( clgame.net_requests, 0, sizeof( clgame.net_requests ));
|
|
clgame.request_type = NET_REQUEST_CANCEL;
|
|
clgame.master_request = NULL;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
NetAPI_AdrToString
|
|
|
|
=================
|
|
*/
|
|
char *NetAPI_AdrToString( netadr_t *a )
|
|
{
|
|
return NET_AdrToString( *a );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
NetAPI_CompareAdr
|
|
|
|
=================
|
|
*/
|
|
int GAME_EXPORT NetAPI_CompareAdr( netadr_t *a, netadr_t *b )
|
|
{
|
|
return NET_CompareAdr( *a, *b );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
NetAPI_StringToAdr
|
|
|
|
=================
|
|
*/
|
|
int GAME_EXPORT NetAPI_StringToAdr( char *s, netadr_t *a )
|
|
{
|
|
return NET_StringToAdr( s, a );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
NetAPI_ValueForKey
|
|
|
|
=================
|
|
*/
|
|
const char *NetAPI_ValueForKey( const char *s, const char *key )
|
|
{
|
|
return Info_ValueForKey( s, key );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
NetAPI_RemoveKey
|
|
|
|
=================
|
|
*/
|
|
void GAME_EXPORT NetAPI_RemoveKey( char *s, const char *key )
|
|
{
|
|
Info_RemoveKey( s, key );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
NetAPI_SetValueForKey
|
|
|
|
=================
|
|
*/
|
|
void GAME_EXPORT NetAPI_SetValueForKey( char *s, const char *key, const char *value, int maxsize )
|
|
{
|
|
if( key[0] == '*' ) return;
|
|
Info_SetValueForStarKey( s, key, value, maxsize );
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
IVoiceTweak implementation
|
|
|
|
TODO: implement
|
|
=================
|
|
*/
|
|
/*
|
|
=================
|
|
Voice_StartVoiceTweakMode
|
|
|
|
=================
|
|
*/
|
|
int GAME_EXPORT Voice_StartVoiceTweakMode( void )
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Voice_EndVoiceTweakMode
|
|
|
|
=================
|
|
*/
|
|
void GAME_EXPORT Voice_EndVoiceTweakMode( void )
|
|
{
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Voice_SetControlFloat
|
|
|
|
=================
|
|
*/
|
|
void GAME_EXPORT Voice_SetControlFloat( VoiceTweakControl iControl, float value )
|
|
{
|
|
}
|
|
|
|
/*
|
|
=================
|
|
Voice_GetControlFloat
|
|
|
|
=================
|
|
*/
|
|
float GAME_EXPORT Voice_GetControlFloat( VoiceTweakControl iControl )
|
|
{
|
|
return 1.0f;
|
|
}
|
|
|
|
static void GAME_EXPORT VGui_ViewportPaintBackground( int extents[4] )
|
|
{
|
|
// stub
|
|
}
|
|
|
|
// shared between client and server
|
|
triangleapi_t gTriApi;
|
|
|
|
static efx_api_t gEfxApi =
|
|
{
|
|
R_AllocParticle,
|
|
R_BlobExplosion,
|
|
R_Blood,
|
|
R_BloodSprite,
|
|
R_BloodStream,
|
|
R_BreakModel,
|
|
R_Bubbles,
|
|
R_BubbleTrail,
|
|
R_BulletImpactParticles,
|
|
R_EntityParticles,
|
|
R_Explosion,
|
|
R_FizzEffect,
|
|
R_FireField,
|
|
R_FlickerParticles,
|
|
R_FunnelSprite,
|
|
R_Implosion,
|
|
R_LargeFunnel,
|
|
R_LavaSplash,
|
|
R_MultiGunshot,
|
|
R_MuzzleFlash,
|
|
R_ParticleBox,
|
|
R_ParticleBurst,
|
|
R_ParticleExplosion,
|
|
R_ParticleExplosion2,
|
|
R_ParticleLine,
|
|
R_PlayerSprites,
|
|
R_Projectile,
|
|
R_RicochetSound,
|
|
R_RicochetSprite,
|
|
R_RocketFlare,
|
|
R_RocketTrail,
|
|
R_RunParticleEffect,
|
|
R_ShowLine,
|
|
R_SparkEffect,
|
|
R_SparkShower,
|
|
R_SparkStreaks,
|
|
R_Spray,
|
|
R_Sprite_Explode,
|
|
R_Sprite_Smoke,
|
|
R_Sprite_Spray,
|
|
R_Sprite_Trail,
|
|
R_Sprite_WallPuff,
|
|
R_StreakSplash,
|
|
R_TracerEffect,
|
|
R_UserTracerParticle,
|
|
R_TracerParticles,
|
|
R_TeleportSplash,
|
|
R_TempSphereModel,
|
|
R_TempModel,
|
|
R_DefaultSprite,
|
|
R_TempSprite,
|
|
CL_DecalIndex,
|
|
CL_DecalIndexFromName,
|
|
CL_DecalShoot,
|
|
R_AttachTentToPlayer,
|
|
R_KillAttachedTents,
|
|
R_BeamCirclePoints,
|
|
R_BeamEntPoint,
|
|
R_BeamEnts,
|
|
R_BeamFollow,
|
|
R_BeamKill,
|
|
R_BeamLightning,
|
|
R_BeamPoints,
|
|
R_BeamRing,
|
|
CL_AllocDlight,
|
|
CL_AllocElight,
|
|
CL_TempEntAlloc,
|
|
CL_TempEntAllocNoModel,
|
|
CL_TempEntAllocHigh,
|
|
CL_TempEntAllocCustom,
|
|
R_GetPackedColor,
|
|
R_LookupColor,
|
|
CL_DecalRemoveAll,
|
|
CL_FireCustomDecal,
|
|
};
|
|
|
|
static event_api_t gEventApi =
|
|
{
|
|
EVENT_API_VERSION,
|
|
pfnPlaySound,
|
|
S_StopSound,
|
|
CL_FindModelIndex,
|
|
pfnIsLocal,
|
|
pfnLocalPlayerDucking,
|
|
pfnLocalPlayerViewheight,
|
|
pfnLocalPlayerBounds,
|
|
pfnIndexFromTrace,
|
|
pfnGetPhysent,
|
|
CL_SetUpPlayerPrediction,
|
|
CL_PushPMStates,
|
|
CL_PopPMStates,
|
|
CL_SetSolidPlayers,
|
|
CL_SetTraceHull,
|
|
CL_PlayerTrace,
|
|
CL_WeaponAnim,
|
|
pfnPrecacheEvent,
|
|
CL_PlaybackEvent,
|
|
pfnTraceTexture,
|
|
pfnStopAllSounds,
|
|
pfnKillEvents,
|
|
CL_PlayerTraceExt, // Xash3D added
|
|
CL_SoundFromIndex,
|
|
pfnTraceSurface,
|
|
pfnGetMoveVars,
|
|
CL_VisTraceLine,
|
|
pfnGetVisent,
|
|
CL_TestLine,
|
|
CL_PushTraceBounds,
|
|
CL_PopTraceBounds,
|
|
};
|
|
|
|
static demo_api_t gDemoApi =
|
|
{
|
|
Demo_IsRecording,
|
|
Demo_IsPlayingback,
|
|
Demo_IsTimeDemo,
|
|
Demo_WriteBuffer,
|
|
};
|
|
|
|
static net_api_t gNetApi =
|
|
{
|
|
NetAPI_InitNetworking,
|
|
NetAPI_Status,
|
|
NetAPI_SendRequest,
|
|
NetAPI_CancelRequest,
|
|
NetAPI_CancelAllRequests,
|
|
NetAPI_AdrToString,
|
|
NetAPI_CompareAdr,
|
|
NetAPI_StringToAdr,
|
|
NetAPI_ValueForKey,
|
|
NetAPI_RemoveKey,
|
|
NetAPI_SetValueForKey,
|
|
};
|
|
|
|
static IVoiceTweak gVoiceApi =
|
|
{
|
|
Voice_StartVoiceTweakMode,
|
|
Voice_EndVoiceTweakMode,
|
|
Voice_SetControlFloat,
|
|
Voice_GetControlFloat,
|
|
};
|
|
|
|
// engine callbacks
|
|
static cl_enginefunc_t gEngfuncs =
|
|
{
|
|
pfnSPR_Load,
|
|
pfnSPR_Frames,
|
|
pfnSPR_Height,
|
|
pfnSPR_Width,
|
|
pfnSPR_Set,
|
|
pfnSPR_Draw,
|
|
pfnSPR_DrawHoles,
|
|
pfnSPR_DrawAdditive,
|
|
SPR_EnableScissor,
|
|
SPR_DisableScissor,
|
|
pfnSPR_GetList,
|
|
CL_FillRGBA,
|
|
CL_GetScreenInfo,
|
|
pfnSetCrosshair,
|
|
pfnCvar_RegisterClientVariable,
|
|
Cvar_VariableValue,
|
|
Cvar_VariableString,
|
|
Cmd_AddClientCommand,
|
|
pfnHookUserMsg,
|
|
pfnServerCmd,
|
|
pfnClientCmd,
|
|
pfnGetPlayerInfo,
|
|
pfnPlaySoundByName,
|
|
pfnPlaySoundByIndex,
|
|
AngleVectors,
|
|
CL_TextMessageGet,
|
|
pfnDrawCharacter,
|
|
pfnDrawConsoleString,
|
|
pfnDrawSetTextColor,
|
|
pfnDrawConsoleStringLen,
|
|
pfnConsolePrint,
|
|
pfnCenterPrint,
|
|
pfnGetWindowCenterX,
|
|
pfnGetWindowCenterY,
|
|
pfnGetViewAngles,
|
|
pfnSetViewAngles,
|
|
CL_GetMaxClients,
|
|
Cvar_SetValue,
|
|
Cmd_Argc,
|
|
Cmd_Argv,
|
|
Con_Printf,
|
|
Con_DPrintf,
|
|
Con_NPrintf,
|
|
Con_NXPrintf,
|
|
pfnPhysInfo_ValueForKey,
|
|
pfnServerInfo_ValueForKey,
|
|
pfnGetClientMaxspeed,
|
|
COM_CheckParm,
|
|
Key_Event,
|
|
Platform_GetMousePos,
|
|
pfnIsNoClipping,
|
|
CL_GetLocalPlayer,
|
|
CL_GetViewModel,
|
|
CL_GetEntityByIndex,
|
|
pfnGetClientTime,
|
|
pfnCalcShake,
|
|
pfnApplyShake,
|
|
pfnPointContents,
|
|
CL_WaterEntity,
|
|
pfnTraceLine,
|
|
CL_LoadModel,
|
|
CL_AddEntity,
|
|
CL_GetSpritePointer,
|
|
pfnPlaySoundByNameAtLocation,
|
|
pfnPrecacheEvent,
|
|
CL_PlaybackEvent,
|
|
CL_WeaponAnim,
|
|
COM_RandomFloat,
|
|
COM_RandomLong,
|
|
pfnHookEvent,
|
|
Con_Visible,
|
|
pfnGetGameDirectory,
|
|
pfnCVarGetPointer,
|
|
Key_LookupBinding,
|
|
pfnGetLevelName,
|
|
pfnGetScreenFade,
|
|
pfnSetScreenFade,
|
|
VGui_GetPanel,
|
|
VGui_ViewportPaintBackground,
|
|
COM_LoadFile,
|
|
pfnParseFile,
|
|
COM_FreeFile,
|
|
&gTriApi,
|
|
&gEfxApi,
|
|
&gEventApi,
|
|
&gDemoApi,
|
|
&gNetApi,
|
|
&gVoiceApi,
|
|
pfnIsSpectateOnly,
|
|
pfnLoadMapSprite,
|
|
COM_AddAppDirectoryToSearchPath,
|
|
COM_ExpandFilename,
|
|
PlayerInfo_ValueForKey,
|
|
PlayerInfo_SetValueForKey,
|
|
pfnGetPlayerUniqueID,
|
|
pfnGetTrackerIDForPlayer,
|
|
pfnGetPlayerForTrackerID,
|
|
pfnServerCmdUnreliable,
|
|
pfnGetMousePos,
|
|
Platform_SetMousePos,
|
|
pfnSetMouseEnable,
|
|
Cvar_GetList,
|
|
(void*)Cmd_GetFirstFunctionHandle,
|
|
(void*)Cmd_GetNextFunctionHandle,
|
|
(void*)Cmd_GetName,
|
|
pfnGetClientOldTime,
|
|
pfnGetGravity,
|
|
Mod_Handle,
|
|
pfnEnableTexSort,
|
|
pfnSetLightmapColor,
|
|
pfnSetLightmapScale,
|
|
pfnSequenceGet,
|
|
pfnSPR_DrawGeneric,
|
|
pfnSequencePickSentence,
|
|
pfnDrawString,
|
|
pfnDrawStringReverse,
|
|
LocalPlayerInfo_ValueForKey,
|
|
pfnVGUI2DrawCharacter,
|
|
pfnVGUI2DrawCharacterAdditive,
|
|
(void*)Sound_GetApproxWavePlayLen,
|
|
GetCareerGameInterface,
|
|
(void*)Cvar_Set,
|
|
pfnIsCareerMatch,
|
|
pfnPlaySoundVoiceByName,
|
|
pfnMP3_InitStream,
|
|
Sys_DoubleTime,
|
|
pfnProcessTutorMessageDecayBuffer,
|
|
pfnConstructTutorMessageDecayBuffer,
|
|
pfnResetTutorMessageDecayData,
|
|
pfnPlaySoundByNameAtPitch,
|
|
CL_FillRGBABlend,
|
|
pfnGetAppID,
|
|
Cmd_AliasGetList,
|
|
pfnVguiWrap2_GetMouseDelta,
|
|
|
|
// HACKHACK: added it here so it wouldn't cause overflow or segfault
|
|
// TODO: itself client command filtering is not implemented yet
|
|
pfnClientCmd
|
|
};
|
|
|
|
void CL_UnloadProgs( void )
|
|
{
|
|
if( !clgame.hInstance ) return;
|
|
|
|
CL_FreeEdicts();
|
|
CL_FreeTempEnts();
|
|
CL_FreeViewBeams();
|
|
CL_FreeParticles();
|
|
CL_ClearAllRemaps();
|
|
Mod_ClearUserData();
|
|
|
|
// NOTE: HLFX 0.5 has strange bug: hanging on exit if no map was loaded
|
|
if( Q_stricmp( GI->gamefolder, "hlfx" ) || GI->version != 0.5f )
|
|
clgame.dllFuncs.pfnShutdown();
|
|
|
|
Cvar_FullSet( "cl_background", "0", FCVAR_READ_ONLY );
|
|
Cvar_FullSet( "host_clientloaded", "0", FCVAR_READ_ONLY );
|
|
|
|
COM_FreeLibrary( clgame.hInstance );
|
|
VGui_Shutdown();
|
|
Mem_FreePool( &cls.mempool );
|
|
Mem_FreePool( &clgame.mempool );
|
|
memset( &clgame, 0, sizeof( clgame ));
|
|
|
|
Cvar_Unlink( FCVAR_CLIENTDLL );
|
|
Cmd_Unlink( CMD_CLIENTDLL );
|
|
}
|
|
|
|
qboolean CL_LoadProgs( const char *name )
|
|
{
|
|
static playermove_t gpMove;
|
|
const dllfunc_t *func;
|
|
CL_EXPORT_FUNCS GetClientAPI; // single export
|
|
qboolean critical_exports = true;
|
|
|
|
if( clgame.hInstance ) CL_UnloadProgs();
|
|
|
|
// initialize PlayerMove
|
|
clgame.pmove = &gpMove;
|
|
|
|
cls.mempool = Mem_AllocPool( "Client Static Pool" );
|
|
clgame.mempool = Mem_AllocPool( "Client Edicts Zone" );
|
|
clgame.entities = NULL;
|
|
|
|
// NOTE: important stuff!
|
|
// vgui must startup BEFORE loading client.dll to avoid get error ERROR_NOACESS
|
|
// during LoadLibrary
|
|
VGui_Startup( name, gameui.globals->scrWidth, gameui.globals->scrHeight );
|
|
|
|
// a1ba: we need to check if client.dll has direct dependency on SDL2
|
|
// and if so, disable relative mouse mode
|
|
#if XASH_WIN32
|
|
if( ( clgame.client_dll_uses_sdl = COM_CheckLibraryDirectDependency( name, OS_LIB_PREFIX "SDL2." OS_LIB_EXT, false ) ) )
|
|
{
|
|
Con_Printf( S_NOTE "%s uses SDL2 for mouse input\n", name );
|
|
}
|
|
else
|
|
{
|
|
Con_Printf( S_NOTE "%s uses Windows API for mouse input\n", name );
|
|
}
|
|
#else
|
|
// this doesn't mean other platforms uses SDL2 in any case
|
|
// it just helps input code to stay platform-independent
|
|
clgame.client_dll_uses_sdl = false;
|
|
#endif
|
|
|
|
clgame.hInstance = COM_LoadLibrary( name, false, false );
|
|
if( !clgame.hInstance ) return false;
|
|
|
|
// clear exports
|
|
for( func = cdll_exports; func && func->name; func++ )
|
|
*func->func = NULL;
|
|
|
|
// trying to get single export
|
|
if(( GetClientAPI = (void *)COM_GetProcAddress( clgame.hInstance, "GetClientAPI" )) != NULL )
|
|
{
|
|
Con_Reportf( "CL_LoadProgs: found single callback export\n" );
|
|
|
|
// trying to fill interface now
|
|
GetClientAPI( &clgame.dllFuncs );
|
|
|
|
// check critical functions again
|
|
for( func = cdll_exports; func && func->name; func++ )
|
|
{
|
|
if( func->func == NULL )
|
|
break; // BAH critical function was missed
|
|
}
|
|
|
|
// because all the exports are loaded through function 'F"
|
|
if( !func || !func->name )
|
|
critical_exports = false;
|
|
}
|
|
|
|
for( func = cdll_exports; func && func->name != NULL; func++ )
|
|
{
|
|
if( *func->func != NULL )
|
|
continue; // already get through 'F'
|
|
|
|
// functions are cleared before all the extensions are evaluated
|
|
if(( *func->func = (void *)COM_GetProcAddress( clgame.hInstance, func->name )) == NULL )
|
|
{
|
|
Con_Reportf( "CL_LoadProgs: failed to get address of %s proc\n", func->name );
|
|
|
|
if( critical_exports )
|
|
{
|
|
COM_FreeLibrary( clgame.hInstance );
|
|
clgame.hInstance = NULL;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// it may be loaded through 'GetClientAPI' so we don't need to clear them
|
|
if( critical_exports )
|
|
{
|
|
// clear new exports
|
|
for( func = cdll_new_exports; func && func->name; func++ )
|
|
*func->func = NULL;
|
|
}
|
|
|
|
for( func = cdll_new_exports; func && func->name != NULL; func++ )
|
|
{
|
|
if( *func->func != NULL )
|
|
continue; // already get through 'F'
|
|
|
|
// functions are cleared before all the extensions are evaluated
|
|
// NOTE: new exports can be missed without stop the engine
|
|
if(( *func->func = (void *)COM_GetProcAddress( clgame.hInstance, func->name )) == NULL )
|
|
Con_Reportf( "CL_LoadProgs: failed to get address of %s proc\n", func->name );
|
|
}
|
|
|
|
if( !clgame.dllFuncs.pfnInitialize( &gEngfuncs, CLDLL_INTERFACE_VERSION ))
|
|
{
|
|
COM_FreeLibrary( clgame.hInstance );
|
|
Con_Reportf( "CL_LoadProgs: can't init client API\n" );
|
|
clgame.hInstance = NULL;
|
|
return false;
|
|
}
|
|
|
|
Cvar_FullSet( "host_clientloaded", "1", FCVAR_READ_ONLY );
|
|
|
|
clgame.maxRemapInfos = 0; // will be alloc on first call CL_InitEdicts();
|
|
clgame.maxEntities = 2; // world + localclient (have valid entities not in game)
|
|
|
|
CL_InitCDAudio( "media/cdaudio.txt" );
|
|
CL_InitTitles( "titles.txt" );
|
|
CL_InitParticles ();
|
|
CL_InitViewBeams ();
|
|
CL_InitTempEnts ();
|
|
|
|
if( !R_InitRenderAPI()) // Xash3D extension
|
|
Con_Reportf( S_WARN "CL_LoadProgs: couldn't get render API\n" );
|
|
|
|
if( !Mobile_Init() ) // Xash3D FWGS extension: mobile interface
|
|
Con_Reportf( S_WARN "CL_LoadProgs: couldn't get mobility API\n" );
|
|
|
|
CL_InitEdicts (); // initailize local player and world
|
|
CL_InitClientMove(); // initialize pm_shared
|
|
|
|
// initialize game
|
|
clgame.dllFuncs.pfnInit();
|
|
|
|
ref.dllFuncs.CL_InitStudioAPI();
|
|
|
|
return true;
|
|
}
|