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.
1676 lines
42 KiB
1676 lines
42 KiB
//========= Copyright (c) 1996-2002, Valve LLC, All rights reserved. ============ |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//============================================================================= |
|
|
|
#include "hud.h" |
|
#include "cl_util.h" |
|
#include "cl_entity.h" |
|
#include "triangleapi.h" |
|
#if USE_VGUI |
|
#include "vgui_TeamFortressViewport.h" |
|
#include "vgui_SpectatorPanel.h" |
|
#endif |
|
#include "hltv.h" |
|
|
|
#include "pm_shared.h" |
|
#include "pm_defs.h" |
|
#include "pmtrace.h" |
|
#include "parsemsg.h" |
|
#include "entity_types.h" |
|
|
|
// these are included for the math functions |
|
#include "com_model.h" |
|
#include "demo_api.h" |
|
#include "event_api.h" |
|
#include "studio_util.h" |
|
#include "screenfade.h" |
|
|
|
#pragma warning(disable: 4244) |
|
|
|
extern "C" int iJumpSpectator; |
|
extern "C" float vJumpOrigin[3]; |
|
extern "C" float vJumpAngles[3]; |
|
|
|
extern void V_GetInEyePos( int entity, float * origin, float * angles ); |
|
extern void V_ResetChaseCam(); |
|
extern void V_GetChasePos( int target, float * cl_angles, float * origin, float * angles ); |
|
extern void VectorAngles( const float *forward, float *angles ); |
|
extern "C" void NormalizeAngles( float *angles ); |
|
extern float * GetClientColor( int clientIndex ); |
|
|
|
extern vec3_t v_origin; // last view origin |
|
extern vec3_t v_angles; // last view angle |
|
extern vec3_t v_cl_angles; // last client/mouse angle |
|
extern vec3_t v_sim_org; // last sim origin |
|
|
|
void SpectatorMode( void ) |
|
{ |
|
if( gEngfuncs.Cmd_Argc() <= 1 ) |
|
{ |
|
gEngfuncs.Con_Printf( "usage: spec_mode <Main Mode> [<Inset Mode>]\n" ); |
|
return; |
|
} |
|
|
|
// SetModes() will decide if command is executed on server or local |
|
if( gEngfuncs.Cmd_Argc() == 2 ) |
|
gHUD.m_Spectator.SetModes( atoi( gEngfuncs.Cmd_Argv( 1 ) ), -1 ); |
|
else if ( gEngfuncs.Cmd_Argc() == 3 ) |
|
gHUD.m_Spectator.SetModes( atoi( gEngfuncs.Cmd_Argv( 1 ) ), atoi( gEngfuncs.Cmd_Argv( 2 ) ) ); |
|
} |
|
|
|
void SpectatorSpray( void ) |
|
{ |
|
vec3_t forward; |
|
char string[128]; |
|
|
|
if( !gEngfuncs.IsSpectateOnly() ) |
|
return; |
|
|
|
AngleVectors( v_angles, forward, NULL, NULL ); |
|
VectorScale( forward, 128, forward ); |
|
VectorAdd( forward, v_origin, forward ); |
|
pmtrace_t * trace = gEngfuncs.PM_TraceLine( v_origin, forward, PM_TRACELINE_PHYSENTSONLY, 2, -1 ); |
|
if( trace->fraction != 1.0f ) |
|
{ |
|
sprintf( string, "drc_spray %.2f %.2f %.2f %i", |
|
(double)trace->endpos[0], (double)trace->endpos[1], (double)trace->endpos[2], trace->ent ); |
|
gEngfuncs.pfnServerCmd( string ); |
|
} |
|
} |
|
|
|
void SpectatorHelp( void ) |
|
{ |
|
#if USE_VGUI |
|
if( gViewPort ) |
|
{ |
|
gViewPort->ShowVGUIMenu( MENU_SPECHELP ); |
|
} |
|
else |
|
#endif |
|
{ |
|
char *text = CHudTextMessage::BufferedLocaliseTextString( "#Spec_Help_Text" ); |
|
|
|
if( text ) |
|
{ |
|
while( *text ) |
|
{ |
|
if( *text != 13 ) |
|
gEngfuncs.Con_Printf( "%c", *text ); |
|
text++; |
|
} |
|
} |
|
} |
|
} |
|
|
|
void SpectatorMenu( void ) |
|
{ |
|
if( gEngfuncs.Cmd_Argc() <= 1 ) |
|
{ |
|
gEngfuncs.Con_Printf( "usage: spec_menu <0|1>\n" ); |
|
return; |
|
} |
|
|
|
#if USE_VGUI |
|
gViewPort->m_pSpectatorPanel->ShowMenu( atoi( gEngfuncs.Cmd_Argv( 1 ) ) != 0 ); |
|
#endif |
|
} |
|
|
|
void ToggleScores( void ) |
|
{ |
|
#if USE_VGUI && !USE_NOVGUI_SCOREBOARD |
|
if( gViewPort ) |
|
{ |
|
if( gViewPort->IsScoreBoardVisible() ) |
|
{ |
|
gViewPort->HideScoreBoard(); |
|
} |
|
else |
|
{ |
|
gViewPort->ShowScoreBoard(); |
|
} |
|
} |
|
#else |
|
if (gHUD.m_Scoreboard.m_iShowscoresHeld) { |
|
gHUD.m_Scoreboard.UserCmd_HideScores(); |
|
} else { |
|
gHUD.m_Scoreboard.UserCmd_ShowScores(); |
|
} |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int CHudSpectator::Init() |
|
{ |
|
gHUD.AddHudElem( this ); |
|
|
|
m_iFlags |= HUD_ACTIVE; |
|
m_flNextObserverInput = 0.0f; |
|
m_zoomDelta = 0.0f; |
|
m_moveDelta = 0.0f; |
|
m_chatEnabled = ( gHUD.m_SayText.m_HUD_saytext->value != 0 ); |
|
iJumpSpectator = 0; |
|
|
|
memset( &m_OverviewData, 0, sizeof(m_OverviewData) ); |
|
memset( &m_OverviewEntities, 0, sizeof(m_OverviewEntities) ); |
|
m_lastPrimaryObject = m_lastSecondaryObject = 0; |
|
|
|
gEngfuncs.pfnAddCommand( "spec_mode", SpectatorMode ); |
|
gEngfuncs.pfnAddCommand( "spec_decal", SpectatorSpray ); |
|
gEngfuncs.pfnAddCommand( "spec_help", SpectatorHelp ); |
|
gEngfuncs.pfnAddCommand( "spec_menu", SpectatorMenu ); |
|
gEngfuncs.pfnAddCommand( "togglescores", ToggleScores ); |
|
|
|
m_drawnames = gEngfuncs.pfnRegisterVariable( "spec_drawnames", "1", 0 ); |
|
m_drawcone = gEngfuncs.pfnRegisterVariable( "spec_drawcone", "1", 0 ); |
|
m_drawstatus = gEngfuncs.pfnRegisterVariable( "spec_drawstatus", "1", 0 ); |
|
m_autoDirector = gEngfuncs.pfnRegisterVariable( "spec_autodirector", "0", 0 ); |
|
m_pip = gEngfuncs.pfnRegisterVariable( "spec_pip", "0", 0 ); |
|
|
|
if( !m_drawnames || !m_drawcone || !m_drawstatus || !m_autoDirector || !m_pip ) |
|
{ |
|
gEngfuncs.Con_Printf( "ERROR! Couldn't register all spectator variables.\n" ); |
|
return 0; |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// UTIL_StringToVector originally from ..\dlls\util.cpp, slightly changed |
|
//----------------------------------------------------------------------------- |
|
void UTIL_StringToVector( float * pVector, const char *pString ) |
|
{ |
|
char *pstr, *pfront, tempString[128]; |
|
int j; |
|
|
|
strcpy( tempString, pString ); |
|
pstr = pfront = tempString; |
|
|
|
for( j = 0; j < 3; j++ ) |
|
{ |
|
pVector[j] = atof( pfront ); |
|
|
|
while( *pstr && *pstr != ' ' ) |
|
pstr++; |
|
if( !( *pstr ) ) |
|
break; |
|
pstr++; |
|
pfront = pstr; |
|
} |
|
|
|
if( j < 2 ) |
|
{ |
|
for( j = j + 1;j < 3; j++ ) |
|
pVector[j] = 0; |
|
} |
|
} |
|
|
|
int UTIL_FindEntityInMap( const char *name, float *origin, float *angle ) |
|
{ |
|
int n, found = 0; |
|
char keyname[256]; |
|
char token[2048]; |
|
|
|
cl_entity_t *pEnt = gEngfuncs.GetEntityByIndex( 0 ); // get world model |
|
|
|
if( !pEnt ) |
|
return 0; |
|
|
|
if( !pEnt->model ) |
|
return 0; |
|
|
|
char *data = pEnt->model->entities; |
|
|
|
while( data ) |
|
{ |
|
data = gEngfuncs.COM_ParseFile( data, token ); |
|
|
|
if( ( token[0] == '}' ) || ( token[0] == 0 ) ) |
|
break; |
|
|
|
if( !data ) |
|
{ |
|
gEngfuncs.Con_DPrintf( "UTIL_FindEntityInMap: EOF without closing brace\n" ); |
|
return 0; |
|
} |
|
|
|
if( token[0] != '{' ) |
|
{ |
|
gEngfuncs.Con_DPrintf( "UTIL_FindEntityInMap: expected {\n" ); |
|
return 0; |
|
} |
|
|
|
// we parse the first { now parse entities properties |
|
while( 1 ) |
|
{ |
|
// parse key |
|
data = gEngfuncs.COM_ParseFile( data, token ); |
|
if( token[0] == '}' ) |
|
break; // finish parsing this entity |
|
|
|
if( !data ) |
|
{ |
|
gEngfuncs.Con_DPrintf( "UTIL_FindEntityInMap: EOF without closing brace\n" ); |
|
return 0; |
|
} |
|
|
|
strcpy( keyname, token ); |
|
|
|
// another hack to fix keynames with trailing spaces |
|
n = strlen( keyname ); |
|
while( n && keyname[n - 1] == ' ' ) |
|
{ |
|
keyname[n - 1] = 0; |
|
n--; |
|
} |
|
|
|
// parse value |
|
data = gEngfuncs.COM_ParseFile( data, token ); |
|
if( !data ) |
|
{ |
|
gEngfuncs.Con_DPrintf( "UTIL_FindEntityInMap: EOF without closing brace\n" ); |
|
return 0; |
|
} |
|
|
|
if( token[0] == '}' ) |
|
{ |
|
gEngfuncs.Con_DPrintf( "UTIL_FindEntityInMap: closing brace without data" ); |
|
return 0; |
|
} |
|
|
|
if( !strcmp( keyname, "classname" ) ) |
|
{ |
|
if( !strcmp( token, name ) ) |
|
{ |
|
found = 1; // thats our entity |
|
} |
|
} |
|
|
|
if( !strcmp( keyname, "angle" ) ) |
|
{ |
|
float y = atof( token ); |
|
|
|
if( y >= 0 ) |
|
{ |
|
angle[0] = 0.0f; |
|
angle[1] = y; |
|
} |
|
else if( (int)y == -1 ) |
|
{ |
|
angle[0] = -90.0f; |
|
angle[1] = 0.0f;; |
|
} |
|
else |
|
{ |
|
angle[0] = 90.0f; |
|
angle[1] = 0.0f; |
|
} |
|
|
|
angle[2] = 0.0f; |
|
} |
|
|
|
if( !strcmp( keyname, "angles" ) ) |
|
{ |
|
UTIL_StringToVector( angle, token ); |
|
} |
|
|
|
if( !strcmp( keyname, "origin" ) ) |
|
{ |
|
UTIL_StringToVector( origin, token ); |
|
} |
|
} // while (1) |
|
|
|
if( found ) |
|
return 1; |
|
} |
|
|
|
return 0; // we search all entities, but didn't found the correct |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// SetSpectatorStartPosition(): |
|
// Get valid map position and 'beam' spectator to this position |
|
//----------------------------------------------------------------------------- |
|
void CHudSpectator::SetSpectatorStartPosition() |
|
{ |
|
// search for info_player start |
|
if( UTIL_FindEntityInMap( "trigger_camera", m_cameraOrigin, m_cameraAngles ) ) |
|
iJumpSpectator = 1; |
|
|
|
else if( UTIL_FindEntityInMap( "info_player_start", m_cameraOrigin, m_cameraAngles ) ) |
|
iJumpSpectator = 1; |
|
|
|
else if( UTIL_FindEntityInMap( "info_player_deathmatch", m_cameraOrigin, m_cameraAngles ) ) |
|
iJumpSpectator = 1; |
|
|
|
else if( UTIL_FindEntityInMap( "info_player_coop", m_cameraOrigin, m_cameraAngles ) ) |
|
iJumpSpectator = 1; |
|
//++ BulliT |
|
else if( UTIL_FindEntityInMap( "info_player_team1", m_cameraOrigin, m_cameraAngles ) ) |
|
iJumpSpectator = 1; |
|
else if( UTIL_FindEntityInMap( "info_player_team2", m_cameraOrigin, m_cameraAngles ) ) |
|
iJumpSpectator = 1; |
|
else if( UTIL_FindEntityInMap( "ctf_redspawn", m_cameraOrigin, m_cameraAngles ) ) |
|
iJumpSpectator = 1; |
|
else if( UTIL_FindEntityInMap( "ctf_bluespawn", m_cameraOrigin, m_cameraAngles ) ) |
|
iJumpSpectator = 1; |
|
//-- Martin Webrant |
|
else |
|
{ |
|
// jump to 0,0,0 if no better position was found |
|
VectorCopy( vec3_origin, m_cameraOrigin ); |
|
VectorCopy( vec3_origin, m_cameraAngles ); |
|
} |
|
|
|
VectorCopy( m_cameraOrigin, vJumpOrigin ); |
|
VectorCopy( m_cameraAngles, vJumpAngles ); |
|
|
|
iJumpSpectator = 1; // jump anyway |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Loads new icons |
|
//----------------------------------------------------------------------------- |
|
int CHudSpectator::VidInit() |
|
{ |
|
m_hsprPlayer = SPR_Load( "sprites/iplayer.spr" ); |
|
m_hsprPlayerBlue = SPR_Load( "sprites/iplayerblue.spr" ); |
|
m_hsprPlayerRed = SPR_Load( "sprites/iplayerred.spr" ); |
|
m_hsprPlayerDead = SPR_Load( "sprites/iplayerdead.spr" ); |
|
m_hsprUnkownMap = SPR_Load( "sprites/tile.spr" ); |
|
m_hsprBeam = SPR_Load( "sprites/laserbeam.spr" ); |
|
m_hsprCamera = SPR_Load( "sprites/camera.spr" ); |
|
m_hCrosshair = SPR_Load( "sprites/crosshairs.spr" ); |
|
|
|
return 1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : flTime - |
|
// intermission - |
|
//----------------------------------------------------------------------------- |
|
int CHudSpectator::Draw( float flTime ) |
|
{ |
|
int lx; |
|
|
|
char string[256]; |
|
float *color; |
|
|
|
// draw only in spectator mode |
|
if( !g_iUser1 ) |
|
return 0; |
|
|
|
// if user pressed zoom, aplly changes |
|
if( ( m_zoomDelta != 0.0f ) && ( g_iUser1 == OBS_MAP_FREE ) ) |
|
{ |
|
m_mapZoom += m_zoomDelta; |
|
|
|
if( m_mapZoom > 3.0f ) |
|
m_mapZoom = 3.0f; |
|
|
|
if( m_mapZoom < 0.5f ) |
|
m_mapZoom = 0.5f; |
|
} |
|
|
|
// if user moves in map mode, change map origin |
|
if( ( m_moveDelta != 0.0f ) && ( g_iUser1 != OBS_ROAMING ) ) |
|
{ |
|
vec3_t right; |
|
AngleVectors( v_angles, NULL, right, NULL ); |
|
VectorNormalize( right ); |
|
VectorScale( right, m_moveDelta, right ); |
|
|
|
VectorAdd( m_mapOrigin, right, m_mapOrigin ) |
|
} |
|
|
|
// Only draw the icon names only if map mode is in Main Mode |
|
if( g_iUser1 < OBS_MAP_FREE ) |
|
return 1; |
|
|
|
if( !m_drawnames->value ) |
|
return 1; |
|
|
|
// make sure we have player info |
|
gHUD.GetAllPlayersInfo(); |
|
|
|
// loop through all the players and draw additional infos to their sprites on the map |
|
for( int i = 0; i < MAX_PLAYERS; i++ ) |
|
{ |
|
if( m_vPlayerPos[i][2] < 0 ) // marked as invisible ? |
|
continue; |
|
|
|
// check if name would be in inset window |
|
if( m_pip->value != INSET_OFF ) |
|
{ |
|
if( m_vPlayerPos[i][0] > XRES( m_OverviewData.insetWindowX ) && |
|
m_vPlayerPos[i][1] > YRES( m_OverviewData.insetWindowY ) && |
|
m_vPlayerPos[i][0] < XRES( m_OverviewData.insetWindowX + m_OverviewData.insetWindowWidth ) && |
|
m_vPlayerPos[i][1] < YRES( m_OverviewData.insetWindowY + m_OverviewData.insetWindowHeight) ) |
|
continue; |
|
} |
|
|
|
color = GetClientColor( i + 1 ); |
|
|
|
// draw the players name and health underneath |
|
strcpy( string, g_PlayerInfoList[i + 1].name ); |
|
|
|
lx = strlen( string ) * 3; // 3 is avg. character length :) |
|
//++ BulliT |
|
//gEngfuncs.pfnDrawSetTextColor( color[0], color[1], color[2] ); |
|
//DrawConsoleString( m_vPlayerPos[i][0] - lx, m_vPlayerPos[i][1], string ); |
|
DrawConsoleString( m_vPlayerPos[i][0] - lx, m_vPlayerPos[i][1], string, color[0], color[1], color[2] ); |
|
//-- Martin Webrant |
|
} |
|
|
|
return 1; |
|
} |
|
|
|
void CHudSpectator::DirectorMessage( int iSize, void *pbuf ) |
|
{ |
|
float value; |
|
char *string; |
|
|
|
BEGIN_READ( pbuf, iSize ); |
|
|
|
int cmd = READ_BYTE(); |
|
|
|
switch( cmd ) // director command byte |
|
{ |
|
case DRC_CMD_START: |
|
// now we have to do some things clientside, since the proxy doesn't know our mod |
|
g_iPlayerClass = 0; |
|
g_iTeamNumber = 0; |
|
|
|
// fake a InitHUD & ResetHUD message |
|
gHUD.MsgFunc_InitHUD( NULL, 0, NULL ); |
|
gHUD.MsgFunc_ResetHUD( NULL, 0, NULL ); |
|
break; |
|
case DRC_CMD_EVENT: |
|
m_lastPrimaryObject = READ_WORD(); |
|
m_lastSecondaryObject = READ_WORD(); |
|
m_iObserverFlags = READ_LONG(); |
|
|
|
if( m_autoDirector->value ) |
|
{ |
|
if( ( g_iUser2 != m_lastPrimaryObject) || ( g_iUser3 != m_lastSecondaryObject ) ) |
|
V_ResetChaseCam(); |
|
|
|
g_iUser2 = m_lastPrimaryObject; |
|
g_iUser3 = m_lastSecondaryObject; |
|
} |
|
|
|
// gEngfuncs.Con_Printf( "Director Camera: %i %i\n", firstObject, secondObject ); |
|
break; |
|
case DRC_CMD_MODE: |
|
if( m_autoDirector->value ) |
|
{ |
|
SetModes( READ_BYTE(), -1 ); |
|
} |
|
break; |
|
case DRC_CMD_CAMERA: |
|
if( m_autoDirector->value ) |
|
{ |
|
vJumpOrigin[0] = READ_COORD(); // position |
|
vJumpOrigin[1] = READ_COORD(); |
|
vJumpOrigin[2] = READ_COORD(); |
|
|
|
vJumpAngles[0] = READ_COORD(); // view angle |
|
vJumpAngles[1] = READ_COORD(); |
|
vJumpAngles[2] = READ_COORD(); |
|
|
|
gEngfuncs.SetViewAngles( vJumpAngles ); |
|
|
|
iJumpSpectator = 1; |
|
} |
|
break; |
|
case DRC_CMD_MESSAGE: |
|
{ |
|
client_textmessage_t * msg = &m_HUDMessages[m_lastHudMessage]; |
|
|
|
msg->effect = READ_BYTE(); // effect |
|
|
|
UnpackRGB( (int&)msg->r1, (int&)msg->g1, (int&)msg->b1, READ_LONG() ); // color |
|
msg->r2 = msg->r1; |
|
msg->g2 = msg->g1; |
|
msg->b2 = msg->b1; |
|
msg->a2 = msg->a1 = 0xFF; // not transparent |
|
|
|
msg->x = READ_FLOAT(); // x pos |
|
msg->y = READ_FLOAT(); // y pos |
|
|
|
msg->fadein = READ_FLOAT(); // fadein |
|
msg->fadeout = READ_FLOAT(); // fadeout |
|
msg->holdtime = READ_FLOAT(); // holdtime |
|
msg->fxtime = READ_FLOAT(); // fxtime; |
|
|
|
strncpy( m_HUDMessageText[m_lastHudMessage], READ_STRING(), 127 ); |
|
m_HUDMessageText[m_lastHudMessage][127] = 0; // text |
|
|
|
msg->pMessage = m_HUDMessageText[m_lastHudMessage]; |
|
msg->pName = "HUD_MESSAGE"; |
|
|
|
gHUD.m_Message.MessageAdd( msg ); |
|
|
|
m_lastHudMessage++; |
|
m_lastHudMessage %= MAX_SPEC_HUD_MESSAGES; |
|
} |
|
break; |
|
case DRC_CMD_SOUND: |
|
string = READ_STRING(); |
|
value = READ_FLOAT(); |
|
|
|
// gEngfuncs.Con_Printf("DRC_CMD_FX_SOUND: %s %.2f\n", string, value ); |
|
gEngfuncs.pEventAPI->EV_PlaySound( 0, v_origin, CHAN_BODY, string, value, ATTN_NORM, 0, PITCH_NORM ); |
|
break; |
|
case DRC_CMD_TIMESCALE: |
|
value = READ_FLOAT(); |
|
break; |
|
case DRC_CMD_STATUS: |
|
READ_LONG(); // total number of spectator slots |
|
m_iSpectatorNumber = READ_LONG(); // total number of spectator |
|
READ_WORD(); // total number of relay proxies |
|
#if USE_VGUI |
|
gViewPort->UpdateSpectatorPanel(); |
|
#endif |
|
break; |
|
case DRC_CMD_BANNER: |
|
// gEngfuncs.Con_DPrintf( "GUI: Banner %s\n",READ_STRING() ); // name of banner tga eg gfx/temp/7454562234563475.tga |
|
#if USE_VGUI |
|
gViewPort->m_pSpectatorPanel->m_TopBanner->LoadImage( READ_STRING() ); |
|
gViewPort->UpdateSpectatorPanel(); |
|
#endif |
|
break; |
|
case DRC_CMD_FADE: |
|
break; |
|
case DRC_CMD_STUFFTEXT: |
|
gEngfuncs.pfnFilteredClientCmd( READ_STRING() ); |
|
break; |
|
default: |
|
gEngfuncs.Con_DPrintf( "CHudSpectator::DirectorMessage: unknown command %i.\n", cmd ); |
|
} |
|
} |
|
|
|
void CHudSpectator::FindNextPlayer( bool bReverse ) |
|
{ |
|
// MOD AUTHORS: Modify the logic of this function if you want to restrict the observer to watching |
|
// only a subset of the players. e.g. Make it check the target's team. |
|
|
|
int iStart; |
|
cl_entity_t * pEnt = NULL; |
|
|
|
// if we are NOT in HLTV mode, spectator targets are set on server |
|
if( !gEngfuncs.IsSpectateOnly() ) |
|
{ |
|
char cmdstring[32]; |
|
// forward command to server |
|
sprintf( cmdstring, "follownext %i", bReverse ? 1 : 0 ); |
|
gEngfuncs.pfnServerCmd( cmdstring ); |
|
return; |
|
} |
|
|
|
if( g_iUser2 ) |
|
iStart = g_iUser2; |
|
else |
|
iStart = 1; |
|
|
|
g_iUser2 = 0; |
|
|
|
int iCurrent = iStart; |
|
|
|
int iDir = bReverse ? -1 : 1; |
|
|
|
// make sure we have player info |
|
gHUD.GetAllPlayersInfo(); |
|
|
|
do |
|
{ |
|
iCurrent += iDir; |
|
|
|
// Loop through the clients |
|
if( iCurrent > MAX_PLAYERS ) |
|
iCurrent = 1; |
|
if( iCurrent < 1 ) |
|
iCurrent = MAX_PLAYERS; |
|
|
|
pEnt = gEngfuncs.GetEntityByIndex( iCurrent ); |
|
|
|
if( !IsActivePlayer( pEnt ) ) |
|
continue; |
|
|
|
// MOD AUTHORS: Add checks on target here. |
|
g_iUser2 = iCurrent; |
|
break; |
|
} while( iCurrent != iStart ); |
|
|
|
// Did we find a target? |
|
if( !g_iUser2 ) |
|
{ |
|
gEngfuncs.Con_DPrintf( "No observer targets.\n" ); |
|
// take save camera position |
|
VectorCopy( m_cameraOrigin, vJumpOrigin ); |
|
VectorCopy( m_cameraAngles, vJumpAngles ); |
|
} |
|
else |
|
{ |
|
// use new entity position for roaming |
|
VectorCopy( pEnt->origin, vJumpOrigin ); |
|
VectorCopy( pEnt->angles, vJumpAngles ); |
|
} |
|
iJumpSpectator = 1; |
|
#if USE_VGUI |
|
gViewPort->MsgFunc_ResetFade( NULL, 0, NULL ); |
|
#endif |
|
} |
|
|
|
void CHudSpectator::FindPlayer(const char *name) |
|
{ |
|
// MOD AUTHORS: Modify the logic of this function if you want to restrict the observer to watching |
|
// only a subset of the players. e.g. Make it check the target's team. |
|
|
|
// if we are NOT in HLTV mode, spectator targets are set on server |
|
if ( !gEngfuncs.IsSpectateOnly() ) |
|
{ |
|
char cmdstring[32]; |
|
// forward command to server |
|
sprintf(cmdstring,"follow %s",name); |
|
gEngfuncs.pfnServerCmd(cmdstring); |
|
return; |
|
} |
|
|
|
g_iUser2 = 0; |
|
|
|
// make sure we have player info |
|
gHUD.GetAllPlayersInfo(); |
|
|
|
cl_entity_t * pEnt = NULL; |
|
|
|
for (int i = 1; i < MAX_PLAYERS; i++ ) |
|
{ |
|
|
|
pEnt = gEngfuncs.GetEntityByIndex( i ); |
|
|
|
if ( !IsActivePlayer( pEnt ) ) |
|
continue; |
|
|
|
if(!stricmp(g_PlayerInfoList[pEnt->index].name,name)) |
|
{ |
|
g_iUser2 = i; |
|
break; |
|
} |
|
|
|
} |
|
|
|
// Did we find a target? |
|
if ( !g_iUser2 ) |
|
{ |
|
gEngfuncs.Con_DPrintf( "No observer targets.\n" ); |
|
// take save camera position |
|
VectorCopy(m_cameraOrigin, vJumpOrigin); |
|
VectorCopy(m_cameraAngles, vJumpAngles); |
|
} |
|
else |
|
{ |
|
// use new entity position for roaming |
|
VectorCopy ( pEnt->origin, vJumpOrigin ); |
|
VectorCopy ( pEnt->angles, vJumpAngles ); |
|
} |
|
|
|
iJumpSpectator = 1; |
|
#if USE_VGUI |
|
gViewPort->MsgFunc_ResetFade( NULL, 0, NULL ); |
|
#endif |
|
} |
|
|
|
void CHudSpectator::HandleButtonsDown( int ButtonPressed ) |
|
{ |
|
double time = gEngfuncs.GetClientTime(); |
|
|
|
int newMainMode = g_iUser1; |
|
int newInsetMode = m_pip->value; |
|
|
|
// gEngfuncs.Con_Printf( " HandleButtons:%i\n", ButtonPressed ); |
|
|
|
#if USE_VGUI |
|
if( !gViewPort ) |
|
return; |
|
#endif |
|
|
|
//Not in intermission. |
|
if( gHUD.m_iIntermission ) |
|
return; |
|
|
|
if( !g_iUser1 ) |
|
return; // dont do anything if not in spectator mode |
|
|
|
// don't handle buttons during normal demo playback |
|
if( gEngfuncs.pDemoAPI->IsPlayingback() && !gEngfuncs.IsSpectateOnly() ) |
|
return; |
|
// Slow down mouse clicks. |
|
if( m_flNextObserverInput > time ) |
|
return; |
|
|
|
// enable spectator screen |
|
#if USE_VGUI |
|
if( ButtonPressed & IN_DUCK ) |
|
gViewPort->m_pSpectatorPanel->ShowMenu( !gViewPort->m_pSpectatorPanel->m_menuVisible ); |
|
#endif |
|
|
|
// 'Use' changes inset window mode |
|
if( ButtonPressed & IN_USE ) |
|
{ |
|
newInsetMode = ToggleInset( true ); |
|
} |
|
|
|
// if not in HLTV mode, buttons are handled server side |
|
if( gEngfuncs.IsSpectateOnly() ) |
|
{ |
|
// changing target or chase mode not in overviewmode without inset window |
|
|
|
// Jump changes main window modes |
|
if( ButtonPressed & IN_JUMP ) |
|
{ |
|
if( g_iUser1 == OBS_CHASE_LOCKED ) |
|
newMainMode = OBS_CHASE_FREE; |
|
else if( g_iUser1 == OBS_CHASE_FREE ) |
|
newMainMode = OBS_IN_EYE; |
|
else if( g_iUser1 == OBS_IN_EYE ) |
|
newMainMode = OBS_ROAMING; |
|
else if( g_iUser1 == OBS_ROAMING ) |
|
newMainMode = OBS_MAP_FREE; |
|
else if( g_iUser1 == OBS_MAP_FREE ) |
|
newMainMode = OBS_MAP_CHASE; |
|
else |
|
newMainMode = OBS_CHASE_FREE; // don't use OBS_CHASE_LOCKED anymore |
|
} |
|
|
|
// Attack moves to the next player |
|
if( ButtonPressed & ( IN_ATTACK | IN_ATTACK2 ) ) |
|
{ |
|
FindNextPlayer( ( ButtonPressed & IN_ATTACK2 ) ? true : false ); |
|
|
|
if( g_iUser1 == OBS_ROAMING ) |
|
{ |
|
gEngfuncs.SetViewAngles( vJumpAngles ); |
|
iJumpSpectator = 1; |
|
} |
|
// lease directed mode if player want to see another player |
|
m_autoDirector->value = 0.0f; |
|
} |
|
} |
|
|
|
SetModes( newMainMode, newInsetMode ); |
|
|
|
if( g_iUser1 == OBS_MAP_FREE ) |
|
{ |
|
if( ButtonPressed & IN_FORWARD ) |
|
m_zoomDelta = 0.01f; |
|
|
|
if( ButtonPressed & IN_BACK ) |
|
m_zoomDelta = -0.01f; |
|
|
|
if( ButtonPressed & IN_MOVELEFT ) |
|
m_moveDelta = -12.0f; |
|
|
|
if( ButtonPressed & IN_MOVERIGHT ) |
|
m_moveDelta = 12.0f; |
|
} |
|
|
|
m_flNextObserverInput = time + 0.2; |
|
} |
|
|
|
void CHudSpectator::HandleButtonsUp( int ButtonPressed ) |
|
{ |
|
#if USE_VGUI |
|
if( !gViewPort ) |
|
return; |
|
|
|
if( !gViewPort->m_pSpectatorPanel->isVisible() ) |
|
return; // dont do anything if not in spectator mode |
|
#endif |
|
|
|
if( ButtonPressed & ( IN_FORWARD | IN_BACK ) ) |
|
m_zoomDelta = 0.0f; |
|
|
|
if( ButtonPressed & ( IN_MOVELEFT | IN_MOVERIGHT ) ) |
|
m_moveDelta = 0.0f; |
|
} |
|
|
|
void CHudSpectator::SetModes( int iNewMainMode, int iNewInsetMode ) |
|
{ |
|
// if value == -1 keep old value |
|
if( iNewMainMode == -1 ) |
|
iNewMainMode = g_iUser1; |
|
|
|
if( iNewInsetMode == -1 ) |
|
iNewInsetMode = m_pip->value; |
|
|
|
// inset mode is handled only clients side |
|
m_pip->value = iNewInsetMode; |
|
|
|
if( iNewMainMode < OBS_CHASE_LOCKED || iNewMainMode > OBS_MAP_CHASE ) |
|
{ |
|
gEngfuncs.Con_Printf( "Invalid spectator mode.\n" ); |
|
return; |
|
} |
|
|
|
// main modes ettings will override inset window settings |
|
if( iNewMainMode != g_iUser1 ) |
|
{ |
|
// if we are NOT in HLTV mode, main spectator mode is set on server |
|
if( !gEngfuncs.IsSpectateOnly() ) |
|
{ |
|
char cmdstring[32]; |
|
|
|
// forward command to server |
|
sprintf( cmdstring, "spec_mode %i", iNewMainMode ); |
|
gEngfuncs.pfnServerCmd( cmdstring ); |
|
return; |
|
} |
|
|
|
if( !g_iUser2 && ( iNewMainMode != OBS_ROAMING ) ) // make sure we have a target |
|
{ |
|
// choose last Director object if still available |
|
if( IsActivePlayer( gEngfuncs.GetEntityByIndex( m_lastPrimaryObject ) ) ) |
|
{ |
|
g_iUser2 = m_lastPrimaryObject; |
|
g_iUser3 = m_lastSecondaryObject; |
|
} |
|
else |
|
FindNextPlayer( false ); // find any target |
|
} |
|
|
|
switch( iNewMainMode ) |
|
{ |
|
case OBS_CHASE_LOCKED: |
|
g_iUser1 = OBS_CHASE_LOCKED; |
|
break; |
|
case OBS_CHASE_FREE: |
|
g_iUser1 = OBS_CHASE_FREE; |
|
break; |
|
case OBS_ROAMING: // jump to current vJumpOrigin/angle |
|
g_iUser1 = OBS_ROAMING; |
|
if( g_iUser2 ) |
|
{ |
|
V_GetChasePos( g_iUser2, v_cl_angles, vJumpOrigin, vJumpAngles ); |
|
gEngfuncs.SetViewAngles( vJumpAngles ); |
|
iJumpSpectator = 1; |
|
} |
|
break; |
|
case OBS_IN_EYE: |
|
g_iUser1 = OBS_IN_EYE; |
|
break; |
|
case OBS_MAP_FREE: |
|
g_iUser1 = OBS_MAP_FREE; |
|
// reset user values |
|
m_mapZoom = m_OverviewData.zoom; |
|
m_mapOrigin = m_OverviewData.origin; |
|
break; |
|
case OBS_MAP_CHASE: |
|
g_iUser1 = OBS_MAP_CHASE; |
|
// reset user values |
|
m_mapZoom = m_OverviewData.zoom; |
|
m_mapOrigin = m_OverviewData.origin; |
|
break; |
|
} |
|
|
|
if( ( g_iUser1 == OBS_IN_EYE ) || ( g_iUser1 == OBS_ROAMING ) ) |
|
{ |
|
m_crosshairRect.left = 24; |
|
m_crosshairRect.top = 0; |
|
m_crosshairRect.right = 48; |
|
m_crosshairRect.bottom = 24; |
|
|
|
SetCrosshair( m_hCrosshair, m_crosshairRect, 255, 255, 255 ); |
|
} |
|
else |
|
{ |
|
memset( &m_crosshairRect, 0, sizeof(m_crosshairRect) ); |
|
SetCrosshair( 0, m_crosshairRect, 0, 0, 0 ); |
|
} |
|
|
|
#if USE_VGUI |
|
gViewPort->MsgFunc_ResetFade( NULL, 0, NULL ); |
|
#endif |
|
|
|
char string[128]; |
|
sprintf( string, "#Spec_Mode%d", g_iUser1 ); |
|
sprintf( string, "%c%s", HUD_PRINTCENTER, CHudTextMessage::BufferedLocaliseTextString( string ) ); |
|
gHUD.m_TextMessage.MsgFunc_TextMsg( NULL, strlen( string ) + 1, string ); |
|
} |
|
|
|
#if USE_VGUI |
|
gViewPort->UpdateSpectatorPanel(); |
|
#endif |
|
} |
|
|
|
bool CHudSpectator::IsActivePlayer( cl_entity_t *ent ) |
|
{ |
|
return ( ent && |
|
ent->player && |
|
ent->curstate.solid != SOLID_NOT && |
|
ent != gEngfuncs.GetLocalPlayer() && |
|
g_PlayerInfoList[ent->index].name != NULL |
|
); |
|
} |
|
|
|
bool CHudSpectator::ParseOverviewFile() |
|
{ |
|
char filename[512] = { 0 }; |
|
char levelname[256] = { 0 }; |
|
char token[1024] = { 0 }; |
|
float height; |
|
bool ret = false; |
|
|
|
char *afile = NULL, *pfile = NULL; |
|
|
|
memset( &m_OverviewData, 0, sizeof(m_OverviewData) ); |
|
|
|
// fill in standrd values |
|
m_OverviewData.insetWindowX = 4; // upper left corner |
|
m_OverviewData.insetWindowY = 4; |
|
m_OverviewData.insetWindowHeight = 180; |
|
m_OverviewData.insetWindowWidth = 240; |
|
m_OverviewData.origin[0] = 0.0f; |
|
m_OverviewData.origin[1] = 0.0f; |
|
m_OverviewData.origin[2] = 0.0f; |
|
m_OverviewData.zoom = 1.0f; |
|
m_OverviewData.layers = 0; |
|
m_OverviewData.layersHeights[0] = 0.0f; |
|
strcpy( m_OverviewData.map, gEngfuncs.pfnGetLevelName() ); |
|
|
|
if( m_OverviewData.map[0] == '\0' ) |
|
return ret; // not active yet |
|
|
|
strcpy( levelname, m_OverviewData.map + 5 ); |
|
levelname[strlen( levelname ) - 4] = 0; |
|
|
|
sprintf( filename, "overviews/%s.txt", levelname ); |
|
|
|
afile = pfile = (char *)gEngfuncs.COM_LoadFile( filename, 5, NULL ); |
|
|
|
if( !pfile ) |
|
{ |
|
gEngfuncs.Con_DPrintf( "Couldn't open file %s. Using default values for overiew mode.\n", filename ); |
|
return ret; |
|
} |
|
|
|
while( true ) |
|
{ |
|
pfile = gEngfuncs.COM_ParseFile( pfile, token ); |
|
|
|
if( !pfile ) |
|
break; |
|
|
|
if( !stricmp( token, "global" ) ) |
|
{ |
|
// parse the global data |
|
pfile = gEngfuncs.COM_ParseFile( pfile, token ); |
|
if( stricmp( token, "{" ) ) |
|
{ |
|
gEngfuncs.Con_Printf( "Error parsing overview file %s. (expected { )\n", filename ); |
|
goto end; |
|
} |
|
|
|
pfile = gEngfuncs.COM_ParseFile( pfile, token ); |
|
|
|
while( stricmp( token, "}") ) |
|
{ |
|
if( !stricmp( token, "zoom" ) ) |
|
{ |
|
pfile = gEngfuncs.COM_ParseFile( pfile, token ); |
|
m_OverviewData.zoom = atof( token ); |
|
} |
|
else if( !stricmp( token, "origin" ) ) |
|
{ |
|
pfile = gEngfuncs.COM_ParseFile( pfile, token ); |
|
m_OverviewData.origin[0] = atof( token ); |
|
pfile = gEngfuncs.COM_ParseFile( pfile, token ); |
|
m_OverviewData.origin[1] = atof( token ); |
|
pfile = gEngfuncs.COM_ParseFile( pfile, token ); |
|
m_OverviewData.origin[2] = atof( token ); |
|
} |
|
else if( !stricmp( token, "rotated" ) ) |
|
{ |
|
pfile = gEngfuncs.COM_ParseFile( pfile, token ); |
|
m_OverviewData.rotated = atoi( token ); |
|
} |
|
else if( !stricmp( token, "inset" ) ) |
|
{ |
|
pfile = gEngfuncs.COM_ParseFile( pfile, token ); |
|
m_OverviewData.insetWindowX = atof( token ); |
|
pfile = gEngfuncs.COM_ParseFile( pfile, token ); |
|
m_OverviewData.insetWindowY = atof( token ); |
|
pfile = gEngfuncs.COM_ParseFile( pfile, token ); |
|
m_OverviewData.insetWindowWidth = atof( token ); |
|
pfile = gEngfuncs.COM_ParseFile( pfile, token ); |
|
m_OverviewData.insetWindowHeight = atof( token ); |
|
} |
|
else |
|
{ |
|
gEngfuncs.Con_Printf( "Error parsing overview file %s. (%s unkown)\n", filename, token ); |
|
goto end; |
|
} |
|
|
|
pfile = gEngfuncs.COM_ParseFile( pfile, token ); // parse next token |
|
} |
|
} |
|
else if( !stricmp( token, "layer" ) ) |
|
{ |
|
// parse a layer data |
|
if( m_OverviewData.layers == OVERVIEW_MAX_LAYERS ) |
|
{ |
|
gEngfuncs.Con_Printf( "Error parsing overview file %s. ( too many layers )\n", filename ); |
|
goto end; |
|
} |
|
|
|
pfile = gEngfuncs.COM_ParseFile( pfile, token ); |
|
|
|
if( stricmp( token, "{" ) ) |
|
{ |
|
gEngfuncs.Con_Printf( "Error parsing overview file %s. (expected { )\n", filename ); |
|
goto end; |
|
} |
|
|
|
pfile = gEngfuncs.COM_ParseFile( pfile, token ); |
|
|
|
while( stricmp( token, "}") ) |
|
{ |
|
if( !stricmp( token, "image" ) ) |
|
{ |
|
pfile = gEngfuncs.COM_ParseFile( pfile, token ); |
|
strcpy( m_OverviewData.layersImages[m_OverviewData.layers], token ); |
|
} |
|
else if ( !stricmp( token, "height" ) ) |
|
{ |
|
pfile = gEngfuncs.COM_ParseFile( pfile, token ); |
|
height = atof( token ); |
|
m_OverviewData.layersHeights[m_OverviewData.layers] = height; |
|
} |
|
else |
|
{ |
|
gEngfuncs.Con_Printf( "Error parsing overview file %s. (%s unkown)\n", filename, token ); |
|
goto end; |
|
} |
|
|
|
pfile = gEngfuncs.COM_ParseFile( pfile, token ); // parse next token |
|
} |
|
|
|
m_OverviewData.layers++; |
|
} |
|
} |
|
|
|
m_mapZoom = m_OverviewData.zoom; |
|
m_mapOrigin = m_OverviewData.origin; |
|
|
|
ret = true; |
|
end: |
|
gEngfuncs.COM_FreeFile( afile ); |
|
|
|
return ret; |
|
} |
|
|
|
void CHudSpectator::LoadMapSprites() |
|
{ |
|
// right now only support for one map layer |
|
if( m_OverviewData.layers > 0 ) |
|
{ |
|
m_MapSprite = gEngfuncs.LoadMapSprite( m_OverviewData.layersImages[0] ); |
|
} |
|
else |
|
m_MapSprite = NULL; // the standard "unkown map" sprite will be used instead |
|
} |
|
|
|
void CHudSpectator::DrawOverviewLayer() |
|
{ |
|
float screenaspect, xs, ys, xStep, yStep, x, y, z; |
|
int ix, iy, i, xTiles, yTiles, frame; |
|
|
|
qboolean hasMapImage = m_MapSprite ? TRUE : FALSE; |
|
model_t *dummySprite = (struct model_s *)gEngfuncs.GetSpritePointer( m_hsprUnkownMap ); |
|
|
|
if( hasMapImage ) |
|
{ |
|
i = m_MapSprite->numframes / ( 4 * 3 ); |
|
i = sqrt( float( i ) ); |
|
xTiles = i * 4; |
|
yTiles = i * 3; |
|
} |
|
else |
|
{ |
|
xTiles = 8; |
|
yTiles = 6; |
|
} |
|
|
|
screenaspect = 4.0f / 3.0f; |
|
|
|
xs = m_OverviewData.origin[0]; |
|
ys = m_OverviewData.origin[1]; |
|
z = ( 90.0f - v_angles[0] ) / 90.0f; |
|
z *= m_OverviewData.layersHeights[0]; // gOverviewData.z_min - 32; |
|
|
|
// i = r_overviewTexture + ( layer * OVERVIEW_X_TILES * OVERVIEW_Y_TILES ); |
|
|
|
gEngfuncs.pTriAPI->RenderMode( kRenderTransTexture ); |
|
gEngfuncs.pTriAPI->CullFace( TRI_NONE ); |
|
gEngfuncs.pTriAPI->Color4f( 1.0f, 1.0f, 1.0f, 1.0f ); |
|
|
|
frame = 0; |
|
|
|
// rotated view ? |
|
if( m_OverviewData.rotated ) |
|
{ |
|
xStep = ( 2 * 4096.0f / m_OverviewData.zoom ) / xTiles; |
|
yStep = -( 2 * 4096.0f / ( m_OverviewData.zoom* screenaspect ) ) / yTiles; |
|
|
|
y = ys + ( 4096.0f / ( m_OverviewData.zoom * screenaspect ) ); |
|
|
|
for( iy = 0; iy < yTiles; iy++ ) |
|
{ |
|
x = xs - ( 4096.0f / (m_OverviewData.zoom ) ); |
|
|
|
for( ix = 0; ix < xTiles; ix++ ) |
|
{ |
|
if( hasMapImage ) |
|
gEngfuncs.pTriAPI->SpriteTexture( m_MapSprite, frame ); |
|
else |
|
gEngfuncs.pTriAPI->SpriteTexture( dummySprite, 0 ); |
|
|
|
gEngfuncs.pTriAPI->Begin( TRI_QUADS ); |
|
gEngfuncs.pTriAPI->TexCoord2f( 0, 0 ); |
|
gEngfuncs.pTriAPI->Vertex3f( x, y, z ); |
|
|
|
gEngfuncs.pTriAPI->TexCoord2f( 1, 0 ); |
|
gEngfuncs.pTriAPI->Vertex3f( x + xStep, y, z); |
|
|
|
gEngfuncs.pTriAPI->TexCoord2f( 1, 1 ); |
|
gEngfuncs.pTriAPI->Vertex3f( x + xStep, y + yStep, z ); |
|
|
|
gEngfuncs.pTriAPI->TexCoord2f( 0, 1 ); |
|
gEngfuncs.pTriAPI->Vertex3f( x, y + yStep, z); |
|
gEngfuncs.pTriAPI->End(); |
|
|
|
frame++; |
|
x += xStep; |
|
} |
|
|
|
y += yStep; |
|
} |
|
} |
|
else |
|
{ |
|
xStep = -( 2 * 4096.0f / m_OverviewData.zoom ) / xTiles; |
|
yStep = -( 2 * 4096.0f / ( m_OverviewData.zoom* screenaspect ) ) / yTiles; |
|
|
|
x = xs + ( 4096.0f / ( m_OverviewData.zoom * screenaspect ) ); |
|
|
|
for( ix = 0; ix < yTiles; ix++ ) |
|
{ |
|
y = ys + ( 4096.0f / ( m_OverviewData.zoom ) ); |
|
|
|
for( iy = 0; iy < xTiles; iy++ ) |
|
{ |
|
if( hasMapImage ) |
|
gEngfuncs.pTriAPI->SpriteTexture( m_MapSprite, frame ); |
|
else |
|
gEngfuncs.pTriAPI->SpriteTexture( dummySprite, 0 ); |
|
|
|
gEngfuncs.pTriAPI->Begin( TRI_QUADS ); |
|
gEngfuncs.pTriAPI->TexCoord2f( 0, 0 ); |
|
gEngfuncs.pTriAPI->Vertex3f( x, y, z ); |
|
|
|
gEngfuncs.pTriAPI->TexCoord2f( 0, 1 ); |
|
gEngfuncs.pTriAPI->Vertex3f( x + xStep, y, z); |
|
|
|
gEngfuncs.pTriAPI->TexCoord2f( 1, 1 ); |
|
gEngfuncs.pTriAPI->Vertex3f( x + xStep, y + yStep, z ); |
|
|
|
gEngfuncs.pTriAPI->TexCoord2f( 1, 0 ); |
|
gEngfuncs.pTriAPI->Vertex3f( x, y + yStep, z ); |
|
gEngfuncs.pTriAPI->End(); |
|
|
|
frame++; |
|
|
|
y += yStep; |
|
} |
|
|
|
x += xStep; |
|
} |
|
} |
|
} |
|
|
|
void CHudSpectator::DrawOverviewEntities() |
|
{ |
|
int i, ir, ig, ib; |
|
struct model_s *hSpriteModel; |
|
vec3_t origin, angles, point, forward, right, left, up, world, screen, offset; |
|
float x, y, z, r, g, b, sizeScale = 4.0f; |
|
cl_entity_t * ent; |
|
float rmatrix[3][4]; // transformation matrix |
|
|
|
float zScale = ( 90.0f - v_angles[0] ) / 90.0f; |
|
|
|
z = m_OverviewData.layersHeights[0] * zScale; |
|
// get yellow/brown HUD color |
|
UnpackRGB( ir, ig, ib, RGB_YELLOWISH ); |
|
r = (float)ir / 255.0f; |
|
g = (float)ig / 255.0f; |
|
b = (float)ib / 255.0f; |
|
|
|
gEngfuncs.pTriAPI->CullFace( TRI_NONE ); |
|
|
|
for( i = 0; i < MAX_PLAYERS; i++ ) |
|
m_vPlayerPos[i][2] = -1; // mark as invisible |
|
|
|
// draw all players |
|
for( i = 0; i < MAX_OVERVIEW_ENTITIES; i++ ) |
|
{ |
|
if( !m_OverviewEntities[i].hSprite ) |
|
continue; |
|
|
|
hSpriteModel = (struct model_s *)gEngfuncs.GetSpritePointer( m_OverviewEntities[i].hSprite ); |
|
ent = m_OverviewEntities[i].entity; |
|
|
|
gEngfuncs.pTriAPI->SpriteTexture( hSpriteModel, 0 ); |
|
gEngfuncs.pTriAPI->RenderMode( kRenderTransTexture ); |
|
|
|
// see R_DrawSpriteModel |
|
// draws players sprite |
|
AngleVectors( ent->angles, right, up, NULL ); |
|
|
|
VectorCopy( ent->origin,origin ); |
|
|
|
gEngfuncs.pTriAPI->Begin( TRI_QUADS ); |
|
|
|
gEngfuncs.pTriAPI->Color4f( 1.0f, 1.0f, 1.0f, 1.0f ); |
|
|
|
gEngfuncs.pTriAPI->TexCoord2f(1, 0); |
|
VectorMA( origin, 16.0f * sizeScale, up, point ); |
|
VectorMA( point, 16.0f * sizeScale, right, point ); |
|
point[2] *= zScale; |
|
gEngfuncs.pTriAPI->Vertex3fv( point ); |
|
|
|
gEngfuncs.pTriAPI->TexCoord2f( 0, 0 ); |
|
|
|
VectorMA( origin, 16.0f * sizeScale, up, point ); |
|
VectorMA( point, -16.0f * sizeScale, right, point ); |
|
point[2] *= zScale; |
|
gEngfuncs.pTriAPI->Vertex3fv( point ); |
|
|
|
gEngfuncs.pTriAPI->TexCoord2f( 0, 1 ); |
|
VectorMA( origin, -16.0f * sizeScale, up, point ); |
|
VectorMA( point, -16.0f * sizeScale, right, point ); |
|
point[2] *= zScale; |
|
gEngfuncs.pTriAPI->Vertex3fv( point ); |
|
|
|
gEngfuncs.pTriAPI->TexCoord2f( 1, 1 ); |
|
VectorMA( origin, -16.0f * sizeScale, up, point ); |
|
VectorMA( point, 16.0f * sizeScale, right, point ); |
|
point[2] *= zScale; |
|
gEngfuncs.pTriAPI->Vertex3fv( point ); |
|
|
|
gEngfuncs.pTriAPI->End(); |
|
|
|
if( !ent->player ) |
|
continue; |
|
|
|
// draw line under player icons |
|
origin[2] *= zScale; |
|
|
|
gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); |
|
|
|
hSpriteModel = (struct model_s *)gEngfuncs.GetSpritePointer( m_hsprBeam ); |
|
gEngfuncs.pTriAPI->SpriteTexture( hSpriteModel, 0 ); |
|
|
|
gEngfuncs.pTriAPI->Color4f( r, g, b, 0.3f ); |
|
|
|
gEngfuncs.pTriAPI->Begin( TRI_QUADS ); |
|
gEngfuncs.pTriAPI->TexCoord2f( 1.0f, 0.0f ); |
|
gEngfuncs.pTriAPI->Vertex3f( origin[0] + 4.0f, origin[1] + 4.0f, origin[2] - zScale ); |
|
gEngfuncs.pTriAPI->TexCoord2f( 0.0f, 0.0f ); |
|
gEngfuncs.pTriAPI->Vertex3f( origin[0] - 4.0f, origin[1] - 4.0f, origin[2] - zScale ); |
|
gEngfuncs.pTriAPI->TexCoord2f( 0.0f, 1.0f ); |
|
gEngfuncs.pTriAPI->Vertex3f( origin[0] - 4.0f, origin[1] - 4.0f, z ); |
|
gEngfuncs.pTriAPI->TexCoord2f( 1.0f, 1.0f ); |
|
gEngfuncs.pTriAPI->Vertex3f( origin[0] + 4.0f, origin[1] + 4.0f, z ); |
|
gEngfuncs.pTriAPI->End(); |
|
|
|
gEngfuncs.pTriAPI->Begin( TRI_QUADS ); |
|
gEngfuncs.pTriAPI->TexCoord2f( 1.0f, 0.0f ); |
|
gEngfuncs.pTriAPI->Vertex3f( origin[0] - 4.0f, origin[1] + 4.0f, origin[2] - zScale ); |
|
gEngfuncs.pTriAPI->TexCoord2f( 0.0f, 0.0f ); |
|
gEngfuncs.pTriAPI->Vertex3f( origin[0] + 4.0f, origin[1] - 4.0f, origin[2] - zScale ); |
|
gEngfuncs.pTriAPI->TexCoord2f( 0.0f, 1.0f ); |
|
gEngfuncs.pTriAPI->Vertex3f( origin[0] + 4.0f, origin[1] - 4.0f, z ); |
|
gEngfuncs.pTriAPI->TexCoord2f( 1.0f, 1.0f ); |
|
gEngfuncs.pTriAPI->Vertex3f( origin[0] - 4.0f, origin[1] + 4.0f, z ); |
|
gEngfuncs.pTriAPI->End(); |
|
|
|
// calculate screen position for name and infromation in hud::draw() |
|
if( gEngfuncs.pTriAPI->WorldToScreen( origin, screen ) ) |
|
continue; // object is behind viewer |
|
|
|
screen[0] = XPROJECT( screen[0] ); |
|
screen[1] = YPROJECT( screen[1] ); |
|
screen[2] = 0.0f; |
|
|
|
// calculate some offset under the icon |
|
origin[0] += 32.0f; |
|
origin[1] += 32.0f; |
|
|
|
gEngfuncs.pTriAPI->WorldToScreen( origin, offset ); |
|
|
|
offset[0] = XPROJECT( offset[0] ); |
|
offset[1] = YPROJECT( offset[1] ); |
|
offset[2] = 0.0f; |
|
|
|
VectorSubtract( offset, screen, offset ); |
|
|
|
int playerNum = ent->index - 1; |
|
|
|
m_vPlayerPos[playerNum][0] = screen[0]; |
|
m_vPlayerPos[playerNum][1] = screen[1] + Length( offset ); |
|
m_vPlayerPos[playerNum][2] = 1; // mark player as visible |
|
} |
|
|
|
if( !m_pip->value || !m_drawcone->value ) |
|
return; |
|
|
|
// get current camera position and angle |
|
if( m_pip->value == INSET_IN_EYE || g_iUser1 == OBS_IN_EYE ) |
|
{ |
|
V_GetInEyePos( g_iUser2, origin, angles ); |
|
} |
|
else if( m_pip->value == INSET_CHASE_FREE || g_iUser1 == OBS_CHASE_FREE ) |
|
{ |
|
V_GetChasePos( g_iUser2, v_cl_angles, origin, angles ); |
|
} |
|
else if( g_iUser1 == OBS_ROAMING ) |
|
{ |
|
VectorCopy( v_sim_org, origin ); |
|
VectorCopy( v_cl_angles, angles ); |
|
} |
|
else |
|
V_GetChasePos( g_iUser2, NULL, origin, angles ); |
|
|
|
// draw camera sprite |
|
x = origin[0]; |
|
y = origin[1]; |
|
z = origin[2]; |
|
|
|
angles[0] = 0; // always show horizontal camera sprite |
|
|
|
hSpriteModel = (struct model_s *)gEngfuncs.GetSpritePointer( m_hsprCamera ); |
|
gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); |
|
gEngfuncs.pTriAPI->SpriteTexture( hSpriteModel, 0 ); |
|
|
|
gEngfuncs.pTriAPI->Color4f( r, g, b, 1.0f ); |
|
|
|
AngleVectors( angles, forward, NULL, NULL ); |
|
VectorScale( forward, 512.0f, forward ); |
|
|
|
offset[0] = 0.0f; |
|
offset[1] = 45.0f; |
|
offset[2] = 0.0f; |
|
|
|
AngleMatrix( offset, rmatrix ); |
|
VectorTransform( forward, rmatrix, right ); |
|
|
|
offset[1]= -45.0f; |
|
AngleMatrix( offset, rmatrix ); |
|
VectorTransform( forward, rmatrix , left ); |
|
|
|
gEngfuncs.pTriAPI->Begin( TRI_TRIANGLES ); |
|
gEngfuncs.pTriAPI->TexCoord2f( 0.0f, 0.0f ); |
|
gEngfuncs.pTriAPI->Vertex3f( x + right[0], y + right[1], ( z + right[2] ) * zScale); |
|
|
|
gEngfuncs.pTriAPI->TexCoord2f( 0.0f, 1.0f ); |
|
gEngfuncs.pTriAPI->Vertex3f( x, y, z * zScale ); |
|
|
|
gEngfuncs.pTriAPI->TexCoord2f( 1.0f, 1.0f ); |
|
gEngfuncs.pTriAPI->Vertex3f( x + left[0], y + left[1], ( z + left[2] ) * zScale ); |
|
gEngfuncs.pTriAPI->End (); |
|
} |
|
|
|
void CHudSpectator::DrawOverview() |
|
{ |
|
// draw only in sepctator mode |
|
if ( !g_iUser1 ) |
|
return; |
|
|
|
// Only draw the overview if Map Mode is selected for this view |
|
if( m_iDrawCycle == 0 && ( ( g_iUser1 != OBS_MAP_FREE ) && ( g_iUser1 != OBS_MAP_CHASE ) ) ) |
|
return; |
|
|
|
if ( m_iDrawCycle == 1 && m_pip->value < INSET_MAP_FREE ) |
|
return; |
|
|
|
DrawOverviewLayer(); |
|
DrawOverviewEntities(); |
|
CheckOverviewEntities(); |
|
} |
|
|
|
void CHudSpectator::CheckOverviewEntities() |
|
{ |
|
double time = gEngfuncs.GetClientTime(); |
|
|
|
// removes old entities from list |
|
for( int i = 0; i < MAX_OVERVIEW_ENTITIES; i++ ) |
|
{ |
|
// remove entity from list if it is too old |
|
if( m_OverviewEntities[i].killTime < time ) |
|
{ |
|
memset( &m_OverviewEntities[i], 0, sizeof(overviewEntity_t) ); |
|
} |
|
} |
|
} |
|
|
|
bool CHudSpectator::AddOverviewEntity( int type, struct cl_entity_s *ent, const char *modelname) |
|
{ |
|
HSPRITE hSprite = 0; |
|
double duration = -1.0; // duration -1 means show it only this frame; |
|
|
|
if( !ent ) |
|
return false; |
|
|
|
if( type == ET_PLAYER ) |
|
{ |
|
if( ent->curstate.solid != SOLID_NOT) |
|
{ |
|
switch ( g_PlayerExtraInfo[ent->index].teamnumber ) |
|
{ |
|
// blue and red teams are swapped in CS and TFC |
|
case 1: |
|
hSprite = m_hsprPlayerBlue; |
|
break; |
|
case 2: |
|
hSprite = m_hsprPlayerRed; |
|
break; |
|
default: |
|
hSprite = m_hsprPlayer; |
|
break; |
|
} |
|
} |
|
else |
|
return false; // it's an spectator |
|
} |
|
else if( type == ET_NORMAL ) |
|
{ |
|
return false; |
|
} |
|
else |
|
return false; |
|
|
|
return AddOverviewEntityToList( hSprite, ent, gEngfuncs.GetClientTime() + duration ); |
|
} |
|
|
|
void CHudSpectator::DeathMessage( int victim ) |
|
{ |
|
// find out where the victim is |
|
cl_entity_t *pl = gEngfuncs.GetEntityByIndex( victim ); |
|
|
|
if( pl && pl->player ) |
|
AddOverviewEntityToList(m_hsprPlayerDead, pl, gEngfuncs.GetClientTime() + 2.0f ); |
|
} |
|
|
|
bool CHudSpectator::AddOverviewEntityToList( HSPRITE sprite, cl_entity_t *ent, double killTime ) |
|
{ |
|
for( int i = 0; i < MAX_OVERVIEW_ENTITIES; i++ ) |
|
{ |
|
// find empty entity slot |
|
if( m_OverviewEntities[i].entity == NULL ) |
|
{ |
|
m_OverviewEntities[i].entity = ent; |
|
m_OverviewEntities[i].hSprite = sprite; |
|
m_OverviewEntities[i].killTime = killTime; |
|
return true; |
|
} |
|
} |
|
|
|
return false; // maximum overview entities reached |
|
} |
|
|
|
void CHudSpectator::CheckSettings() |
|
{ |
|
// disallow same inset mode as main mode: |
|
m_pip->value = (int)m_pip->value; |
|
|
|
if( ( g_iUser1 < OBS_MAP_FREE ) && ( m_pip->value == INSET_CHASE_FREE || m_pip->value == INSET_IN_EYE ) ) |
|
{ |
|
// otherwise both would show in World picures |
|
m_pip->value = INSET_MAP_FREE; |
|
} |
|
|
|
if( ( g_iUser1 >= OBS_MAP_FREE ) && ( m_pip->value >= INSET_MAP_FREE ) ) |
|
{ |
|
// both would show map views |
|
m_pip->value = INSET_CHASE_FREE; |
|
} |
|
|
|
// disble in intermission screen |
|
if( gHUD.m_iIntermission ) |
|
m_pip->value = INSET_OFF; |
|
|
|
// check chat mode |
|
if( m_chatEnabled != (gHUD.m_SayText.m_HUD_saytext->value != 0) ) |
|
{ |
|
// hud_saytext changed |
|
m_chatEnabled = ( gHUD.m_SayText.m_HUD_saytext->value != 0 ); |
|
|
|
if( gEngfuncs.IsSpectateOnly() ) |
|
{ |
|
// tell proxy our new chat mode |
|
char chatcmd[32]; |
|
sprintf( chatcmd, "ignoremsg %i", m_chatEnabled ? 0 : 1 ); |
|
gEngfuncs.pfnServerCmd( chatcmd ); |
|
} |
|
} |
|
|
|
// HL/TFC has no oberserver corsshair, so set it client side |
|
if( ( g_iUser1 == OBS_IN_EYE ) || ( g_iUser1 == OBS_ROAMING ) ) |
|
{ |
|
/* |
|
m_crosshairRect.left = 24; |
|
m_crosshairRect.top = 0; |
|
m_crosshairRect.right = 48; |
|
m_crosshairRect.bottom = 24; |
|
|
|
SetCrosshair( m_hCrosshair, m_crosshairRect, 255, 255, 255 ); |
|
*/ |
|
} |
|
else |
|
{ |
|
memset( &m_crosshairRect, 0, sizeof(m_crosshairRect) ); |
|
SetCrosshair( 0, m_crosshairRect, 0, 0, 0 ); |
|
} |
|
|
|
// if we are a real player on server don't allow inset window |
|
// in First Person mode since this is our resticted forcecamera mode 2 |
|
// team number 3 = SPECTATOR see player.h |
|
|
|
if( ( ( g_iTeamNumber == 1 ) || ( g_iTeamNumber == 2 ) ) && ( g_iUser1 == OBS_IN_EYE ) ) |
|
m_pip->value = INSET_OFF; |
|
|
|
// draw small border around inset view, adjust upper black bar |
|
#if USE_VGUI |
|
gViewPort->m_pSpectatorPanel->EnableInsetView( m_pip->value != INSET_OFF ); |
|
#endif |
|
} |
|
|
|
int CHudSpectator::ToggleInset( bool allowOff ) |
|
{ |
|
int newInsetMode = (int)m_pip->value + 1; |
|
|
|
if( g_iUser1 < OBS_MAP_FREE ) |
|
{ |
|
if( newInsetMode > INSET_MAP_CHASE ) |
|
{ |
|
if( allowOff ) |
|
newInsetMode = INSET_OFF; |
|
else |
|
newInsetMode = INSET_MAP_FREE; |
|
} |
|
|
|
if( newInsetMode == INSET_CHASE_FREE ) |
|
newInsetMode = INSET_MAP_FREE; |
|
} |
|
else |
|
{ |
|
if( newInsetMode > INSET_IN_EYE ) |
|
{ |
|
if( allowOff ) |
|
newInsetMode = INSET_OFF; |
|
else |
|
newInsetMode = INSET_CHASE_FREE; |
|
} |
|
} |
|
|
|
return newInsetMode; |
|
} |
|
|
|
void CHudSpectator::Reset() |
|
{ |
|
// Reset HUD |
|
if( strcmp( m_OverviewData.map, gEngfuncs.pfnGetLevelName() ) ) |
|
{ |
|
// update level overview if level changed |
|
ParseOverviewFile(); |
|
LoadMapSprites(); |
|
} |
|
|
|
memset( &m_OverviewEntities, 0, sizeof(m_OverviewEntities) ); |
|
|
|
SetSpectatorStartPosition(); |
|
} |
|
|
|
void CHudSpectator::InitHUDData() |
|
{ |
|
m_lastPrimaryObject = m_lastSecondaryObject = 0; |
|
m_flNextObserverInput = 0.0f; |
|
m_lastHudMessage = 0; |
|
m_iSpectatorNumber = 0; |
|
iJumpSpectator = 0; |
|
g_iUser1 = g_iUser2 = 0; |
|
|
|
memset( &m_OverviewData, 0, sizeof(m_OverviewData)); |
|
memset( &m_OverviewEntities, 0, sizeof(m_OverviewEntities)); |
|
|
|
if( gEngfuncs.IsSpectateOnly() || gEngfuncs.pDemoAPI->IsPlayingback() ) |
|
m_autoDirector->value = 1.0f; |
|
else |
|
m_autoDirector->value = 0.0f; |
|
|
|
Reset(); |
|
|
|
SetModes( OBS_CHASE_FREE, INSET_OFF ); |
|
|
|
g_iUser2 = 0; // fake not target until first camera command |
|
|
|
// reset HUD FOV |
|
gHUD.m_iFOV = CVAR_GET_FLOAT( "default_fov" ); |
|
}
|
|
|