/* 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 "gl_local.h" #include "library.h" #include "vgui_draw.h" #include "sound.h" // SND_STOP_LOOPING #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 { 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 cl.world; return CL_EDICT_NUM( index ); } /* ==================== 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 ) { char *afile, *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 = afile; // format: trackname\n [num] while(( pfile = COM_ParseFile( pfile, token )) != NULL ) { if( !Q_stricmp( token, "blank" )) token[0] = '\0'; Q_strncpy( clgame.cdtracks[c], token, sizeof( clgame.cdtracks[0] )); if( ++c > MAX_CDTRACKS - 1 ) { MsgDev( D_WARN, "CD_Init: too many tracks %i in %s\n", filename, MAX_CDTRACKS ); break; } } Mem_Free( afile ); } /* ==================== CL_PointContents Return contents for point ==================== */ int CL_PointContents( const vec3_t p ) { int cont = CL_TruePointContents( 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 = ( glState.width - width ) * 0.5f; } else { if ( x < 0 ) xPos = (1.0f + x) * glState.width - totalWidth; // Alight right else // align left xPos = x * glState.width; } if( xPos + width > glState.width ) xPos = glState.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 = ( glState.height - height ) * 0.5f; } else { // Alight bottom? if( y < 0 ) yPos = (1.0f + y) * glState.height - height; // Alight bottom else // align top yPos = y * glState.height; } if( yPos + height > glState.height ) yPos = glState.height - height; else if( yPos < 0 ) yPos = 0; return yPos; } /* ============= CL_CenterPrint print centerscreen message ============= */ void CL_CenterPrint( const char *text, float y ) { char *s; int width = 0; int length = 0; 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; if( !x && !y && !w && !h ) return; // scale for screen sizes xscale = glState.width / (float)clgame.scrInfo.iWidth; yscale = glState.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 ) { float xscale, yscale; if( !clgame.ds.adjust_size ) return; if( !x && !y && !w && !h ) return; // scale for screen sizes xscale = glState.width / (float)clgame.scrInfo.iWidth; yscale = glState.height / (float)clgame.scrInfo.iHeight; if( x ) *x *= xscale; if( y ) *y *= yscale; if( w ) *w *= xscale; if( h ) *h *= yscale; } 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 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 = R_GetSpriteTexture( clgame.ds.pSprite, frame ); pglColor4ubv( clgame.ds.spriteColor ); pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); 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 <= glState.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 ); } pglColor4ub( sf->fader, sf->fadeg, sf->fadeb, iFadeAlpha ); if( sf->fadeFlags & FFADE_MODULATE ) GL_SetRenderMode( kRenderTransAdd ); else GL_SetRenderMode( kRenderTransTexture ); R_DrawStretchPic( 0, 0, glState.width, glState.height, 0, 0, 1, 1, tr.whiteTexture ); pglColor4ub( 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 ) { size_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_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 )) { // invalid channel specified, use internal counter if( channel != 0 ) MsgDev( D_ERROR, "HudText: invalid channel %i\n", channel ); 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 ); // NOTE: a "HudText" message contain only 'string' with message name, so we // don't needs to use MSG_ routines here, just directly write msgname into netbuffer CL_DispatchUserMessage( "HudText", Q_strlen( text->pName ) + 1, (void *)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 // NOTE: a "HudText" message contain only 'string' with message name, so we // don't needs to use MSG_ routines here, just directly write msgname into netbuffer CL_DispatchUserMessage( "HudText", Q_strlen( text->pName ) + 1, (void *)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 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 ) { MsgDev( D_ERROR, "CL_SoundFromIndex: invalid sound index %i\n", index ); return NULL; } sfx = S_GetSfxByHandle( hSound ); if( !sfx ) { MsgDev( D_ERROR, "CL_SoundFromIndex: bad sfx for index %i\n", index ); return NULL; } return sfx->name; } /* ========= SPR_EnableScissor ========= */ static void 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 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; 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( RI.viewangles, cl.crosshairangle, angles ); AngleVectors( angles, forward, NULL, NULL ); VectorAdd( RI.vieworg, forward, point ); R_WorldToScreen( point, screen ); x += ( clgame.viewport[2] >> 1 ) * screen[0] + 0.5f; y += ( clgame.viewport[3] >> 1 ) * screen[1] + 0.5f; } // move at center the screen x -= 0.5f * width; y -= 0.5f * height; clgame.ds.pSprite = clgame.ds.pCrosshair; *(int *)clgame.ds.spriteColor = *(int *)clgame.ds.rgbaCrosshair; SPR_EnableScissor( x, y, width, height ); pfnSPR_DrawHoles( 0, x, y, &clgame.ds.rcCrosshair ); SPR_DisableScissor(); } /* ============= CL_DrawLoading draw loading progress bar ============= */ static void CL_DrawLoading( float percent ) { int x, y, width, height, right; float xscale, yscale, step, s2; R_GetTextureParms( &width, &height, cls.loadingBar ); x = ( clgame.scrInfo.iWidth - width ) >> 1; y = ( clgame.scrInfo.iHeight - height) >> 1; xscale = glState.width / (float)clgame.scrInfo.iWidth; yscale = glState.height / (float)clgame.scrInfo.iHeight; x *= xscale; y *= yscale; width *= xscale; height *= yscale; if( cl_allow_levelshots->value ) { pglColor4ub( 128, 128, 128, 255 ); GL_SetRenderMode( kRenderTransTexture ); R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, cls.loadingBar ); step = (float)width / 100.0f; right = (int)ceil( percent * step ); s2 = (float)right / width; width = right; pglColor4ub( 208, 152, 0, 255 ); GL_SetRenderMode( kRenderTransTexture ); R_DrawStretchPic( x, y, width, height, 0, 0, s2, 1, cls.loadingBar ); pglColor4ub( 255, 255, 255, 255 ); } else { pglColor4ub( 255, 255, 255, 255 ); GL_SetRenderMode( kRenderTransTexture ); R_DrawStretchPic( x, y, width, height, 0, 0, 1, 1, cls.loadingBar ); } } /* ============= CL_DrawPause draw pause sign ============= */ static void CL_DrawPause( void ) { int x, y, width, height; float xscale, yscale; R_GetTextureParms( &width, &height, cls.pauseIcon ); x = ( clgame.scrInfo.iWidth - width ) >> 1; y = ( clgame.scrInfo.iHeight - height) >> 1; xscale = glState.width / (float)clgame.scrInfo.iWidth; yscale = glState.height / (float)clgame.scrInfo.iHeight; x *= xscale; y *= yscale; width *= xscale; height *= yscale; pglColor4ub( 255, 255, 255, 255 ); GL_SetRenderMode( kRenderTransTexture ); 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_DrawPause(); break; case CL_LOADING: CL_DrawLoading( scr_loading->value ); break; case CL_CHANGELEVEL: if( cls.draw_changelevel ) { CL_DrawLoading( 100.0f ); cls.draw_changelevel = false; } break; } } 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_strcmp( clgame.msg[i].name, pszName )) { clgame.msg[i].number = svc_num; clgame.msg[i].size = iSize; 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; } void CL_FreeEntity( cl_entity_t *pEdict ) { ASSERT( pEdict ); R_RemoveEfrags( pEdict ); CL_KillDeadBeams( pEdict ); } void CL_ClearWorld( void ) { cl.world = clgame.entities; cl.world->curstate.modelindex = 1; // world model cl.world->curstate.solid = SOLID_BSP; cl.world->curstate.movetype = MOVETYPE_PUSH; cl.world->model = cl.worldmodel; cl.world->index = 0; clgame.ds.cullMode = GL_FRONT; clgame.numStatics = 0; } void CL_InitEdicts( void ) { ASSERT( clgame.entities == NULL ); if( !clgame.mempool ) return; // Host_Error without client CL_UPDATE_BACKUP = ( cl.maxclients == 1 ) ? SINGLEPLAYER_BACKUP : MULTIPLAYER_BACKUP; cls.num_client_entities = CL_UPDATE_BACKUP * NUM_PACKET_ENTITIES; cls.packet_entities = Z_Realloc( cls.packet_entities, sizeof( entity_state_t ) * cls.num_client_entities ); clgame.entities = Mem_Alloc( clgame.mempool, sizeof( cl_entity_t ) * clgame.maxEntities ); clgame.static_entities = Mem_Alloc( 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_Alloc( clgame.mempool, sizeof( remap_info_t* ) * clgame.maxRemapInfos ); } if( clgame.drawFuncs.R_ProcessEntData != NULL ) { // let the client.dll free custom data clgame.drawFuncs.R_ProcessEntData( true ); } } void CL_FreeEdicts( void ) { if( clgame.drawFuncs.R_ProcessEntData != NULL ) { // let the client.dll free custom data clgame.drawFuncs.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_LoadHudSprite upload sprite frames ============= */ static qboolean CL_LoadHudSprite( const char *szSpriteName, model_t *m_pSprite, uint type, uint texFlags ) { byte *buf; size_t size; qboolean loaded; ASSERT( m_pSprite != NULL ); buf = FS_LoadFile( szSpriteName, &size, false ); if( !buf ) return false; 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 ); if( type == SPR_MAPSPRITE ) Mod_LoadMapSprite( m_pSprite, buf, size, &loaded ); else Mod_LoadSpriteModel( m_pSprite, buf, &loaded, texFlags ); Mem_Free( buf ); if( !loaded ) { Mod_UnloadSpriteModel( m_pSprite ); return false; } 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[64]; int i; if( !filename || !*filename ) { MsgDev( D_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; i < MAX_IMAGES; i++ ) { if( !Q_stricmp( clgame.sprites[i].name, name )) { // prolonge registration clgame.sprites[i].needload = clgame.load_sequence; return &clgame.sprites[i]; } } // find a free model slot spot for( i = 1; i < MAX_IMAGES; i++ ) { if( !clgame.sprites[i].name[0] ) break; // this is a valid spot } if( i == MAX_IMAGES ) { MsgDev( D_ERROR, "CL_LoadSpriteModel: can't load %s, MAX_SPRITES limit exceeded\n", filename ); return NULL; } // load new map sprite if( CL_LoadHudSprite( name, &clgame.sprites[i], type, texFlags )) { if( i < ( MAX_IMAGES - 1 )) clgame.sprites[i].needload = clgame.load_sequence; return &clgame.sprites[i]; } 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 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 ) { if( hSprite <= 0 || hSprite > ( MAX_IMAGES - 1 )) return NULL; // bad image return &clgame.sprites[hSprite]; } /* ========= pfnSPR_Frames ========= */ static int pfnSPR_Frames( HSPRITE hPic ) { int numFrames; R_GetSpriteParms( NULL, NULL, &numFrames, 0, CL_GetSpritePointer( hPic )); return numFrames; } /* ========= pfnSPR_Height ========= */ static int pfnSPR_Height( HSPRITE hPic, int frame ) { int sprHeight; R_GetSpriteParms( NULL, &sprHeight, NULL, frame, CL_GetSpritePointer( hPic )); return sprHeight; } /* ========= pfnSPR_Width ========= */ static int pfnSPR_Width( HSPRITE hPic, int frame ) { int sprWidth; R_GetSpriteParms( &sprWidth, NULL, NULL, frame, CL_GetSpritePointer( hPic )); return sprWidth; } /* ========= pfnSPR_Set ========= */ static void 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 pfnSPR_Draw( int frame, int x, int y, const wrect_t *prc ) { pglDisable( GL_BLEND ); SPR_DrawGeneric( frame, x, y, -1, -1, prc ); } /* ========= pfnSPR_DrawHoles ========= */ static void pfnSPR_DrawHoles( int frame, int x, int y, const wrect_t *prc ) { pglEnable( GL_ALPHA_TEST ); pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); pglEnable( GL_BLEND ); SPR_DrawGeneric( frame, x, y, -1, -1, prc ); pglDisable( GL_ALPHA_TEST ); pglDisable( GL_BLEND ); } /* ========= pfnSPR_DrawAdditive ========= */ static void pfnSPR_DrawAdditive( int frame, int x, int y, const wrect_t *prc ) { pglEnable( GL_BLEND ); pglBlendFunc( GL_ONE, GL_ONE ); SPR_DrawGeneric( frame, x, y, -1, -1, prc ); pglDisable( GL_BLEND ); } /* ========= pfnSPR_GetList for parsing half-life scripts - hud.txt etc ========= */ static client_sprite_t *pfnSPR_GetList( char *psz, int *piCount ) { client_sprite_t *pList; int index, numSprites = 0; char *afile, *pfile; string token; byte *pool; if( piCount ) *piCount = 0; if( !clgame.itemspath[0] ) // typically it's sprites\*.txt FS_ExtractFilePath( psz, clgame.itemspath ); afile = FS_LoadFile( psz, NULL, false ); if( !afile ) return NULL; pfile = afile; pfile = COM_ParseFile( pfile, token ); numSprites = Q_atoi( token ); if( !cl.video_prepped ) pool = cls.mempool; // static memory else pool = com_studiocache; // temporary // name, res, pic, x, y, w, h // NOTE: we must use com_studiocache because it will be purge on next restart or change map pList = Mem_Alloc( pool, sizeof( client_sprite_t ) * numSprites ); for( index = 0; index < numSprites; index++ ) { if(( pfile = COM_ParseFile( pfile, token )) == NULL ) break; Q_strncpy( pList[index].szName, token, sizeof( pList[index].szName )); // read resolution pfile = COM_ParseFile( pfile, token ); pList[index].iRes = Q_atoi( token ); // read spritename pfile = COM_ParseFile( pfile, token ); Q_strncpy( pList[index].szSprite, token, sizeof( pList[index].szSprite )); // parse rectangle pfile = COM_ParseFile( pfile, token ); pList[index].rc.left = Q_atoi( token ); pfile = COM_ParseFile( pfile, token ); pList[index].rc.top = Q_atoi( token ); pfile = COM_ParseFile( pfile, token ); pList[index].rc.right = pList[index].rc.left + Q_atoi( token ); pfile = COM_ParseFile( pfile, token ); pList[index].rc.bottom = pList[index].rc.top + Q_atoi( token ); if( piCount ) (*piCount)++; } if( index < numSprites ) MsgDev( D_WARN, "SPR_GetList: unexpected end of %s (%i should be %i)\n", psz, numSprites, index ); Mem_Free( afile ); return pList; } /* ============= CL_FillRGBA ============= */ void CL_FillRGBA( int x, int y, int w, int h, int r, int g, int b, int a ) { r = bound( 0, r, 255 ); g = bound( 0, g, 255 ); b = bound( 0, b, 255 ); a = bound( 0, a, 255 ); SPR_AdjustSize( (float *)&x, (float *)&y, (float *)&w, (float *)&h ); 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 ); } /* ============= pfnGetScreenInfo get actual screen info ============= */ static int pfnGetScreenInfo( SCREENINFO *pscrinfo ) { // setup screen info clgame.scrInfo.iSize = sizeof( clgame.scrInfo ); clgame.scrInfo.iFlags = SCRINFO_SCREENFLASH; if( Cvar_VariableInteger( "hud_scale" )) { if( glState.width < 640 ) { // virtual screen space 320x200 clgame.scrInfo.iWidth = 320; clgame.scrInfo.iHeight = 200; } else { // virtual screen space 640x480 clgame.scrInfo.iWidth = 640; clgame.scrInfo.iHeight = 480; } clgame.scrInfo.iFlags |= SCRINFO_STRETCHED; } else { clgame.scrInfo.iWidth = glState.width; clgame.scrInfo.iHeight = glState.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 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 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_strcmp( 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 pfnServerCmd( const char *szCmdString ) { string buf; if( !szCmdString || !szCmdString[0] ) 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 pfnClientCmd( const char *szCmdString ) { if( !szCmdString || !szCmdString[0] ) return 0; Cbuf_AddText( szCmdString ); Cbuf_AddText( "\n" ); return 1; } /* ============= pfnGetPlayerInfo ============= */ static void pfnGetPlayerInfo( int ent_num, hud_player_info_t *pinfo ) { player_info_t *player; ent_num -= 1; // player list if offset by 1 from ents if( ent_num >= 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 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 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 ) { MsgDev( D_ERROR, "CL_PlaySoundByIndex: invalid sound handle %i\n", iSound ); 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 pfnDrawCharacter( int x, int y, int number, int r, int g, int b ) { if( !cls.creditsFont.valid ) return 0; 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 pfnDrawConsoleString( int x, int y, char *string ) { int drawLen; if( !string || !*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 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 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 pfnConsolePrint( const char *string ) { if( !string || !*string ) return; if( *string != 1 ) Con_Print( (char *)string ); // show notify else Con_NPrintf( 0, (char *)string + 1 ); // skip notify } /* ============= pfnCenterPrint holds and fade message at center of screen like trigger_multiple message in q1 ============= */ static void pfnCenterPrint( const char *string ) { if( !string || !*string ) return; // someone stupid joke CL_CenterPrint( string, 0.25f ); } /* ========= GetWindowCenterX ========= */ static int pfnGetWindowCenterX( void ) { return host.window_center_x; } /* ========= GetWindowCenterY ========= */ static int pfnGetWindowCenterY( void ) { return host.window_center_y; } /* ============= pfnGetViewAngles return interpolated angles from previous frame ============= */ static void pfnGetViewAngles( float *angles ) { if( angles ) VectorCopy( cl.viewangles, angles ); } /* ============= pfnSetViewAngles return interpolated angles from previous frame ============= */ static void pfnSetViewAngles( float *angles ) { if( angles ) VectorCopy( angles, cl.viewangles ); } /* ============= pfnPhysInfo_ValueForKey ============= */ static const char* pfnPhysInfo_ValueForKey( const char *key ) { return Info_ValueForKey( cls.physinfo, key ); } /* ============= pfnServerInfo_ValueForKey ============= */ static const char* pfnServerInfo_ValueForKey( const char *key ) { return Info_ValueForKey( cl.serverinfo, key ); } /* ============= pfnGetClientMaxspeed value that come from server ============= */ static float pfnGetClientMaxspeed( void ) { return cl.local.maxspeed; } /* ============= pfnCheckParm ============= */ static int pfnCheckParm( char *parm, char **ppnext ) { static char str[64]; if( Sys_GetParmFromCmdLine( parm, str )) { // get the pointer on cmdline param if( ppnext ) *ppnext = str; return 1; } return 0; } /* ============= pfnGetMousePosition ============= */ static void pfnGetMousePosition( int *mx, int *my ) { POINT curpos; GetCursorPos( &curpos ); ScreenToClient( host.hWnd, &curpos ); if( mx ) *mx = curpos.x; if( my ) *my = curpos.y; } /* ============= pfnIsNoClipping ============= */ int pfnIsNoClipping( void ) { return ( cl.frames[cl.parsecountmod].playerstate[cl.playernum].movetype == MOVETYPE_NOCLIP ); } /* ============= pfnGetViewModel ============= */ static cl_entity_t* pfnGetViewModel( void ) { return &clgame.viewent; } /* ============= pfnGetClientTime ============= */ static float pfnGetClientTime( void ) { return cl.time; } /* ============= pfnCalcShake ============= */ void 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 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 pfnIsSpectateOnly( void ) { return (cls.spectator != 0); } /* ============= pfnPointContents ============= */ static int pfnPointContents( const float *p, int *truecontents ) { int cont, truecont; truecont = cont = CL_TruePointContents( 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 pfnPlaySoundByNameAtLocation( char *szSound, float volume, float *origin ) { int hSound = S_RegisterSound( szSound ); S_StartSound( origin, 0, CHAN_AUTO, hSound, volume, ATTN_NORM, PITCH_NORM, 0 ); } /* ============= pfnPrecacheEvent ============= */ static word pfnPrecacheEvent( int type, const char* psz ) { return CL_EventIndex( psz ); } /* ============= pfnHookEvent ============= */ static void 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 ) { MsgDev( D_WARN, "CL_HookEvent: %s already hooked!\n", name ); return; } } CL_RegisterEvent( i, name, pfn ); } /* ============= pfnKillEvent ============= */ static void pfnKillEvents( int entnum, const char *eventname ) { int i; event_state_t *es; event_info_t *ei; int eventIndex = CL_EventIndex( eventname ); if( eventIndex < 0 || eventIndex >= MAX_EVENTS ) return; if( entnum < 0 || entnum > clgame.maxEntities ) return; es = &cl.events; // find all events with specified index and kill it for( i = 0; i < MAX_EVENT_QUEUE; i++ ) { ei = &es->ei[i]; if( ei->index == eventIndex && ei->entity_index == entnum ) { CL_ResetEvent( ei ); break; } } } /* ============= pfnPlaySound ============= */ void 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 CL_FindModelIndex( const char *m ) { static float lasttimewarn; int i; if( !m || !m[0] ) return 0; for( i = 1; i < MAX_MODELS && cl.model_precache[i][0]; i++ ) { if( !Q_stricmp( cl.model_precache[i], m )) return i; } if( lasttimewarn < host.realtime ) { // tell user about problem (but don't spam console) MsgDev( D_ERROR, "CL_ModelIndex: %s not precached\n", m ); lasttimewarn = host.realtime + 1.0f; } return 0; } /* ============= pfnIsLocal ============= */ int pfnIsLocal( int playernum ) { if( playernum == cl.playernum ) return true; return false; } /* ============= pfnLocalPlayerDucking ============= */ int pfnLocalPlayerDucking( void ) { return (cl.local.usehull == 1) ? true : false; } /* ============= pfnLocalPlayerViewheight ============= */ void pfnLocalPlayerViewheight( float *view_ofs ) { if( view_ofs ) VectorCopy( cl.viewheight, view_ofs ); } /* ============= pfnLocalPlayerBounds ============= */ void 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 pfnIndexFromTrace( struct pmtrace_s *pTrace ) { if( pTrace->ent >= 0 && pTrace->ent < clgame.pmove->numphysent ) { // return cl.entities number return clgame.pmove->physents[pTrace->ent].info; } return -1; } /* ============= pfnGetPhysent ============= */ physent_t *pfnGetPhysent( int idx ) { if( idx >= 0 && idx < clgame.pmove->numphysent ) { // return physent return &clgame.pmove->physents[idx]; } return NULL; } /* ============= pfnGetVisent ============= */ physent_t *pfnGetVisent( int idx ) { if( idx >= 0 && idx < clgame.pmove->numvisent ) { // return physent return &clgame.pmove->visents[idx]; } return NULL; } /* ============= pfnSetTraceHull ============= */ void CL_SetTraceHull( int hull ) { clgame.pmove->usehull = bound( 0, hull, 3 ); } /* ============= pfnPlayerTrace ============= */ void 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 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 ============= */ static 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 ============= */ static movevars_t *pfnGetMoveVars( void ) { return &clgame.movevars; } /* ============= pfnStopAllSounds ============= */ void pfnStopAllSounds( int ent, int entchannel ) { S_StopSound( ent, entchannel, NULL ); } /* ============= CL_LoadModel ============= */ model_t *CL_LoadModel( const char *modelname, int *index ) { int idx; idx = CL_FindModelIndex( modelname ); if( !idx ) return NULL; if( index ) *index = idx; return Mod_Handle( idx ); } int 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_sprintf( szGetGameDir, "%s/%s", host.rootdir, GI->gamedir ); 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 ============= */ static void pfnGetScreenFade( struct screenfade_s *fade ) { if( fade ) *fade = clgame.fade; } /* ============= pfnSetScreenFade ============= */ static void 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 PlayerInfo_SetValueForKey( const char *key, const char *value ) { convar_t *var; if( !Q_strcmp( Info_ValueForKey( cls.userinfo, key ), value )) return; // not 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 pfnGetPlayerUniqueID( int iPlayer, char playerID[16] ) { // TODO: implement playerID[0] = '\0'; return false; } /* ============= pfnGetTrackerIDForPlayer obsolete, unused ============= */ int pfnGetTrackerIDForPlayer( int playerSlot ) { return 0; } /* ============= pfnGetPlayerForTrackerID obsolete, unused ============= */ int pfnGetPlayerForTrackerID( int trackerID ) { return 0; } /* ============= pfnServerCmdUnreliable ============= */ int pfnServerCmdUnreliable( char *szCmdString ) { if( !szCmdString || !szCmdString[0] ) return 0; MSG_BeginClientCmd( &cls.datagram, clc_stringcmd ); MSG_WriteString( &cls.datagram, szCmdString ); return 1; } /* ============= pfnGetMousePos ============= */ void pfnGetMousePos( struct tagPOINT *ppt ) { GetCursorPos( ppt ); } /* ============= pfnSetMousePos ============= */ void pfnSetMousePos( int mx, int my ) { SetCursorPos( mx, my ); } /* ============= pfnSetMouseEnable legacy of dinput code ============= */ void pfnSetMouseEnable( qboolean fEnable ) { } /* ============= pfnParseFile handle colon separately ============= */ char *pfnParseFile( char *data, char *token ) { char *out; host.com_handlecolon = true; out = COM_ParseFile( data, token ); host.com_handlecolon = false; return out; } /* ================= TriApi implementation ================= */ /* ============= TriRenderMode set rendermode ============= */ void TriRenderMode( int mode ) { switch( mode ) { case kRenderNormal: pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); pglDisable( GL_BLEND ); pglDepthMask( GL_TRUE ); break; case kRenderTransAlpha: pglEnable( GL_BLEND ); pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); pglDepthMask( GL_FALSE ); break; case kRenderTransColor: case kRenderTransTexture: pglEnable( GL_BLEND ); pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); break; case kRenderGlow: case kRenderTransAdd: pglBlendFunc( GL_SRC_ALPHA, GL_ONE ); pglEnable( GL_BLEND ); pglDepthMask( GL_FALSE ); break; } clgame.ds.renderMode = mode; } /* ============= TriBegin begin triangle sequence ============= */ void TriBegin( int mode ) { switch( mode ) { case TRI_POINTS: mode = GL_POINTS; break; case TRI_TRIANGLES: mode = GL_TRIANGLES; break; case TRI_TRIANGLE_FAN: mode = GL_TRIANGLE_FAN; break; case TRI_QUADS: mode = GL_QUADS; break; case TRI_LINES: mode = GL_LINES; break; case TRI_TRIANGLE_STRIP: mode = GL_TRIANGLE_STRIP; break; case TRI_QUAD_STRIP: mode = GL_QUAD_STRIP; break; case TRI_POLYGON: default: mode = GL_POLYGON; break; } pglBegin( mode ); } /* ============= TriEnd draw triangle sequence ============= */ void TriEnd( void ) { pglEnd(); } /* ============= TriColor4f ============= */ void TriColor4f( float r, float g, float b, float a ) { if( clgame.ds.renderMode == kRenderTransAlpha ) pglColor4ub( r * 255.9f, g * 255.9f, b * 255.9f, a * 255.0f ); else pglColor4f( 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); pglColor4f( clgame.ds.triRGBA[0], clgame.ds.triRGBA[1], clgame.ds.triRGBA[2], 1.0f ); } /* ============= TriTexCoord2f ============= */ void TriTexCoord2f( float u, float v ) { pglTexCoord2f( u, v ); } /* ============= TriVertex3fv ============= */ void TriVertex3fv( const float *v ) { pglVertex3fv( v ); } /* ============= TriVertex3f ============= */ void TriVertex3f( float x, float y, float z ) { pglVertex3f( x, y, z ); } /* ============= 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; pglColor4f( r, g, b, 1.0f ); } /* ============= TriCullFace ============= */ void TriCullFace( int mode ) { switch( mode ) { case TRI_FRONT: clgame.ds.cullMode = GL_FRONT; break; default: clgame.ds.cullMode = GL_NONE; break; } GL_Cull( clgame.ds.cullMode ); } /* ============= TriSpriteTexture bind current texture ============= */ int TriSpriteTexture( model_t *pSpriteModel, int frame ) { int gl_texturenum; if(( gl_texturenum = R_GetSpriteTexture( pSpriteModel, frame )) == 0 ) return 0; if( gl_texturenum <= 0 || gl_texturenum > MAX_TEXTURES ) gl_texturenum = tr.defaultTexture; GL_Bind( GL_TEXTURE0, gl_texturenum ); return 1; } /* ============= TriWorldToScreen convert world coordinates (x,y,z) into screen (x, y) ============= */ int TriWorldToScreen( float *world, float *screen ) { int retval; retval = R_WorldToScreen( world, screen ); screen[0] = 0.5f * screen[0] * (float)clgame.viewport[2]; screen[1] = -0.5f * screen[1] * (float)clgame.viewport[3]; screen[0] += 0.5f * (float)clgame.viewport[2]; screen[1] += 0.5f * (float)clgame.viewport[3]; return retval; } /* ============= TriFog enables global fog on the level ============= */ void TriFog( float flFogColor[3], float flStart, float flEnd, int bOn ) { // overrided by internal fog if( RI.fogEnabled ) return; RI.fogCustom = bOn; // check for invalid parms if( flEnd <= flStart ) { RI.fogCustom = false; pglDisable( GL_FOG ); return; } if( RI.fogCustom ) pglEnable( GL_FOG ); else pglDisable( GL_FOG ); // copy fog params RI.fogColor[0] = flFogColor[0] / 255.0f; RI.fogColor[1] = flFogColor[1] / 255.0f; RI.fogColor[2] = flFogColor[2] / 255.0f; RI.fogStart = flStart; RI.fogColor[3] = 1.0f; RI.fogDensity = 0.0f; RI.fogSkybox = true; RI.fogEnd = flEnd; pglFogi( GL_FOG_MODE, GL_LINEAR ); pglFogfv( GL_FOG_COLOR, RI.fogColor ); pglFogf( GL_FOG_START, RI.fogStart ); pglFogf( GL_FOG_END, RI.fogEnd ); pglHint( GL_FOG_HINT, GL_NICEST ); } /* ============= TriGetMatrix very strange export ============= */ void TriGetMatrix( const int pname, float *matrix ) { pglGetFloatv( pname, matrix ); } /* ============= TriBoxInPVS check box in pvs (absmin, absmax) ============= */ int TriBoxInPVS( float *mins, float *maxs ) { return Mod_BoxVisible( mins, maxs, Mod_GetCurrentVis( )); } /* ============= TriLightAtPoint NOTE: dlights are ignored ============= */ void TriLightAtPoint( float *pos, float *value ) { colorVec vLightColor; if( !pos || !value ) return; vLightColor = 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; pglColor4f( r, g, b, a ); } else pglColor4f( r * a, g * a, b * a, 1.0f ); } /* ============= TriForParams ============= */ void TriFogParams( float flDensity, int iFogSkybox ) { RI.fogDensity = flDensity; RI.fogSkybox = iFogSkybox; } /* ================= DemoApi implementation ================= */ /* ================= Demo_IsRecording ================= */ static int Demo_IsRecording( void ) { return cls.demorecording; } /* ================= Demo_IsPlayingback ================= */ static int Demo_IsPlayingback( void ) { return cls.demoplayback; } /* ================= Demo_IsTimeDemo ================= */ static int Demo_IsTimeDemo( void ) { return cls.timedemo; } /* ================= Demo_WriteBuffer ================= */ static void Demo_WriteBuffer( int size, byte *buffer ) { CL_WriteDemoUserMessage( buffer, size ); } /* ================= NetworkApi implementation ================= */ /* ================= NetAPI_InitNetworking ================= */ void NetAPI_InitNetworking( void ) { NET_Config( true ); // allow remote } /* ================= NetAPI_InitNetworking ================= */ void 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 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 ) { MsgDev( D_ERROR, "Net_SendRequest: no callbcak specified for request with context %i!\n", context ); return; } // 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->gamedir ); NET_SendPacket( NS_CLIENT, Q_strlen( GI->gamedir ) + 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, req ); } } /* ================= NetAPI_CancelRequest ================= */ void 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 NetAPI_CancelAllRequests( void ) { net_request_t *nr; int i; // tell the user about cancel for( i = 0; i < MAX_REQUESTS; i++ ) { nr = &clgame.net_requests[i]; if( !nr->pfnFunc ) continue; // not used SetBits( nr->resp.error, NET_ERROR_TIMEOUT ); nr->resp.ping = host.realtime - nr->timesend; nr->pfnFunc( &nr->resp ); } memset( clgame.net_requests, 0, sizeof( clgame.net_requests )); clgame.request_type = NET_REQUEST_CANCEL; clgame.master_request = NULL; } /* ================= NetAPI_AdrToString ================= */ char *NetAPI_AdrToString( netadr_t *a ) { return NET_AdrToString( *a ); } /* ================= NetAPI_CompareAdr ================= */ int NetAPI_CompareAdr( netadr_t *a, netadr_t *b ) { return NET_CompareAdr( *a, *b ); } /* ================= NetAPI_StringToAdr ================= */ int 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 NetAPI_RemoveKey( char *s, const char *key ) { Info_RemoveKey( s, key ); } /* ================= NetAPI_SetValueForKey ================= */ void 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 Voice_StartVoiceTweakMode( void ) { return 0; } /* ================= Voice_EndVoiceTweakMode ================= */ void Voice_EndVoiceTweakMode( void ) { } /* ================= Voice_SetControlFloat ================= */ void Voice_SetControlFloat( VoiceTweakControl iControl, float value ) { } /* ================= Voice_GetControlFloat ================= */ float Voice_GetControlFloat( VoiceTweakControl iControl ) { return 1.0f; } // shared between client and server triangleapi_t gTriApi = { TRI_API_VERSION, TriRenderMode, TriBegin, TriEnd, TriColor4f, TriColor4ub, TriTexCoord2f, TriVertex3fv, TriVertex3f, TriBrightness, TriCullFace, TriSpriteTexture, R_WorldToScreen, // NOTE: XPROJECT, YPROJECT should be done in client.dll TriFog, R_ScreenToWorld, TriGetMatrix, TriBoxInPVS, TriLightAtPoint, TriColor4fRendermode, TriFogParams, R_LightVec, // Xash3D added }; 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_EventIndex, // Xash3D added CL_IndexEvent, CL_PlayerTraceExt, CL_SoundFromIndex, pfnTraceSurface, pfnGetMoveVars, CL_VisTraceLine, pfnGetVisent, }; 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, pfnGetScreenInfo, 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, pfnCheckParm, Key_Event, pfnGetMousePosition, pfnIsNoClipping, CL_GetLocalPlayer, pfnGetViewModel, 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, pfnSetMousePos, pfnSetMouseEnable, }; void CL_UnloadProgs( void ) { if( !clgame.hInstance ) return; CL_FreeEdicts(); CL_FreeTempEnts(); CL_FreeViewBeams(); CL_FreeParticles(); CL_ClearAllRemaps(); Mod_ClearUserData(); VGui_Shutdown(); // NOTE: HLFX 0.5 has strange bug: hanging on exit if no map was loaded if( !( !Q_stricmp( GI->gamedir, "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 (); clgame.hInstance = Com_LoadLibrary( name, false ); if( !clgame.hInstance ) return false; // clear exports for( func = cdll_exports; func && func->name; func++ ) *func->func = NULL; // trying to get single export named 'F' if(( GetClientAPI = (void *)Com_GetProcAddress( clgame.hInstance, "GetClientAPI" )) != NULL ) { MsgDev( D_NOTE, "CL_LoadProgs: found single callback export\n" ); // trying to fill interface now GetClientAPI( &clgame.dllFuncs ); // check critical functions again for( func = cdll_exports; func && func->name; func++ ) { if( func->func == NULL ) break; // BAH critical function was missed } // because all the exports are loaded through function 'F" if( !func || !func->name ) critical_exports = false; } for( func = cdll_exports; func && func->name != NULL; func++ ) { if( *func->func != NULL ) continue; // already get through 'F' // functions are cleared before all the extensions are evaluated if(!( *func->func = (void *)Com_GetProcAddress( clgame.hInstance, func->name ))) { MsgDev( D_NOTE, "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 ))) MsgDev( D_NOTE, "CL_LoadProgs: failed to get address of %s proc\n", func->name ); } if( !clgame.dllFuncs.pfnInitialize( &gEngfuncs, CLDLL_INTERFACE_VERSION )) { Com_FreeLibrary( clgame.hInstance ); MsgDev( D_NOTE, "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 MsgDev( D_WARN, "CL_LoadProgs: couldn't get render API\n" ); CL_InitEdicts (); // initailize local player and world CL_InitClientMove(); // initialize pm_shared // initialize game clgame.dllFuncs.pfnInit(); CL_InitStudioAPI( ); // trying to grab them from client.dll cl_righthand = Cvar_FindVar( "cl_righthand" ); if( cl_righthand == NULL ) cl_righthand = Cvar_Get( "cl_righthand", "0", FCVAR_ARCHIVE, "flip viewmodel (left to right)" ); return true; }