You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4072 lines
84 KiB
4072 lines
84 KiB
/* |
|
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, sizeof( 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 ) |
|
{ |
|
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 ); |
|
} |
|
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.pauseIcon ); |
|
} |
|
} |
|
|
|
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 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 |
|
|
|
========= |
|
*/ |
|
int 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, sizeof( 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, sizeof( token ))) == NULL ) |
|
break; |
|
|
|
Q_strncpy( pEntry->pList[index].szName, token, sizeof( pEntry->pList[0].szName )); |
|
|
|
// read resolution |
|
pfile = COM_ParseFile( pfile, token, sizeof( token )); |
|
pEntry->pList[index].iRes = Q_atoi( token ); |
|
|
|
// read spritename |
|
pfile = COM_ParseFile( pfile, token, sizeof( token )); |
|
Q_strncpy( pEntry->pList[index].szSprite, token, sizeof( pEntry->pList[0].szSprite )); |
|
|
|
// parse rectangle |
|
pfile = COM_ParseFile( pfile, token, sizeof( token )); |
|
pEntry->pList[index].rc.left = Q_atoi( token ); |
|
|
|
pfile = COM_ParseFile( pfile, token, sizeof( token )); |
|
pEntry->pList[index].rc.top = Q_atoi( token ); |
|
|
|
pfile = COM_ParseFile( pfile, token, sizeof( token )); |
|
pEntry->pList[index].rc.right = pEntry->pList[index].rc.left + Q_atoi( token ); |
|
|
|
pfile = COM_ParseFile( pfile, token, sizeof( 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; |
|
} |
|
|
|
/* |
|
============= |
|
pfnFilteredClientCmd |
|
============= |
|
*/ |
|
static int GAME_EXPORT pfnFilteredClientCmd( const char *szCmdString ) |
|
{ |
|
if( !COM_CheckString( szCmdString )) |
|
return 0; |
|
|
|
// a1ba: |
|
// there should be stufftext validator, that checks |
|
// hardcoded commands and disallows them before passing to |
|
// filtered buffer, returning 0 |
|
// I've replaced it by hooking potentially exploitable |
|
// commands and variables(motd_write, motdfile, etc) in client interfaces |
|
|
|
Cbuf_AddFilteredText( szCmdString ); |
|
Cbuf_AddFilteredText( "\n" ); |
|
|
|
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; |
|
word eventIndex = CL_EventIndex( eventname ); |
|
|
|
if( 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 0 // Velaron: breaks compatibility with mods that call the function after CL_PopPMStates |
|
if( pTrace->ent >= 0 && pTrace->ent < clgame.pmove->numphysent ) |
|
{ |
|
// return cl.entities number |
|
return clgame.pmove->physents[pTrace->ent].info; |
|
} |
|
return -1; |
|
#endif |
|
return clgame.pmove->physents[pTrace->ent].info; |
|
} |
|
|
|
/* |
|
============= |
|
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 ) |
|
{ |
|
int iWidth = 0; |
|
Con_UtfProcessChar(0); |
|
|
|
// draw the string until we hit the null character or a newline character |
|
for ( ; *str != 0 && *str != '\n'; str++ ) |
|
{ |
|
iWidth += pfnVGUI2DrawCharacterAdditive( x + iWidth, y, (unsigned char)*str, r, g, b, 0 ); |
|
} |
|
|
|
return iWidth; |
|
} |
|
|
|
/* |
|
============= |
|
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 ]; |
|
return pfnDrawString( x, y, str, r, g, b ); |
|
} |
|
|
|
/* |
|
============= |
|
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; |
|
|
|
out = _COM_ParseFileSafe( data, token, PFILE_TOKEN_MAX_LENGTH, PFILE_HANDLECOLON, NULL ); |
|
|
|
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 ) |
|
{ |
|
return ref.dllFuncs.WorldToScreen( world, screen ); |
|
} |
|
|
|
/* |
|
============= |
|
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 |
|
|
|
================= |
|
*/ |
|
const 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, |
|
CL_ModelHandle, |
|
pfnEnableTexSort, |
|
pfnSetLightmapColor, |
|
pfnSetLightmapScale, |
|
pfnSequenceGet, |
|
pfnSPR_DrawGeneric, |
|
pfnSequencePickSentence, |
|
pfnDrawString, |
|
pfnDrawStringReverse, |
|
LocalPlayerInfo_ValueForKey, |
|
pfnVGUI2DrawCharacter, |
|
pfnVGUI2DrawCharacterAdditive, |
|
Sound_GetApproxWavePlayLen, |
|
GetCareerGameInterface, |
|
Cvar_Set, |
|
pfnIsCareerMatch, |
|
pfnPlaySoundVoiceByName, |
|
pfnMP3_InitStream, |
|
Sys_DoubleTime, |
|
pfnProcessTutorMessageDecayBuffer, |
|
pfnConstructTutorMessageDecayBuffer, |
|
pfnResetTutorMessageDecayData, |
|
pfnPlaySoundByNameAtPitch, |
|
CL_FillRGBABlend, |
|
pfnGetAppID, |
|
Cmd_AliasGetList, |
|
pfnVguiWrap2_GetMouseDelta, |
|
pfnFilteredClientCmd |
|
}; |
|
|
|
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 ); |
|
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 && !XASH_64BIT |
|
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 = true; |
|
#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 ); |
|
} |
|
else if(( GetClientAPI = (void *)COM_GetProcAddress( clgame.hInstance, "F" )) != NULL ) |
|
{ |
|
Con_Reportf( "CL_LoadProgs: found single callback export (secured client dlls)\n" ); |
|
|
|
// trying to fill interface now |
|
CL_GetSecuredClientAPI( GetClientAPI ); |
|
} |
|
|
|
if ( GetClientAPI != NULL ) |
|
{ |
|
// 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; |
|
}
|
|
|