hlsdk-xash3d/cl_dll/hud_spectator.cpp

1528 lines
39 KiB
C++

//========= 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"
#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.0 )
{
sprintf( string, "drc_spray %.2f %.2f %.2f %i",
trace->endpos[0], trace->endpos[1], trace->endpos[2], trace->ent );
gEngfuncs.pfnServerCmd( string );
}
}
void SpectatorHelp( void )
{
{
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;
}
}
void ToggleScores( void )
{
}
//-----------------------------------------------------------------------------
// 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", "1", 0 );
m_pip = gEngfuncs.pfnRegisterVariable( "spec_pip", "1", 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;
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
//gViewPort->GetAllPlayersInfo();
gHUD.m_Scoreboard.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
sprintf( string, "%s", g_PlayerInfoList[i + 1].name );
lx = strlen( string ) * 3; // 3 is avg. character length :)
DrawSetTextColor( color[0], color[1], color[2] );
DrawConsoleString( m_vPlayerPos[i][0] - lx,m_vPlayerPos[i][1], string );
}
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(), 128 );
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
break;
case DRC_CMD_BANNER:
// gEngfuncs.Con_DPrintf( "GUI: Banner %s\n",READ_STRING() ); // name of banner tga eg gfx/temp/7454562234563475.tga
break;
case DRC_CMD_FADE:
break;
case DRC_CMD_STUFFTEXT:
ClientCmd( 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
//gViewPort->GetAllPlayersInfo();
gHUD.m_Scoreboard.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;
}
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 );
//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( ButtonPressed & IN_DUCK )
// gViewPort->m_pSpectatorPanel->ShowMenu( !gViewPort->m_pSpectatorPanel->m_menuVisible );
// '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( 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() )
{
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 );
}
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 );
}
}
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[255] = { 0 };
char levelname[255] = { 0 };
char token[1024] = { 0 };
float height;
char *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( strlen( m_OverviewData.map ) == 0 )
return false; // not active yet
strcpy( levelname, m_OverviewData.map + 5 );
levelname[strlen( levelname ) - 4] = 0;
sprintf( filename, "overviews/%s.txt", levelname );
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 false;
}
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 );
return false;
}
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 );
return false;
}
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 );
return false;
}
pfile = gEngfuncs.COM_ParseFile( pfile, token );
if( stricmp( token, "{" ) )
{
gEngfuncs.Con_Printf( "Error parsing overview file %s. (expected { )\n", filename );
return false;
}
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 );
return false;
}
pfile = gEngfuncs.COM_ParseFile( pfile, token ); // parse next token
}
m_OverviewData.layers++;
}
}
gEngfuncs.COM_FreeFile( pfile );
m_mapZoom = m_OverviewData.zoom;
m_mapOrigin = m_OverviewData.origin;
return true;
}
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.0, 1.0, 1.0, 1.0 );
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.0, 1.0, 1.0, 1.0 );
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.3 );
gEngfuncs.pTriAPI->Begin( TRI_QUADS );
gEngfuncs.pTriAPI->TexCoord2f( 1, 0 );
gEngfuncs.pTriAPI->Vertex3f( origin[0] + 4, origin[1] + 4, origin[2] - zScale );
gEngfuncs.pTriAPI->TexCoord2f( 0, 0 );
gEngfuncs.pTriAPI->Vertex3f( origin[0] - 4, origin[1] - 4, origin[2] - zScale );
gEngfuncs.pTriAPI->TexCoord2f( 0, 1 );
gEngfuncs.pTriAPI->Vertex3f( origin[0] - 4, origin[1] - 4, z );
gEngfuncs.pTriAPI->TexCoord2f( 1, 1 );
gEngfuncs.pTriAPI->Vertex3f( origin[0] + 4, origin[1] + 4, z );
gEngfuncs.pTriAPI->End();
gEngfuncs.pTriAPI->Begin( TRI_QUADS );
gEngfuncs.pTriAPI->TexCoord2f( 1, 0 );
gEngfuncs.pTriAPI->Vertex3f( origin[0] - 4, origin[1] + 4, origin[2] - zScale );
gEngfuncs.pTriAPI->TexCoord2f( 0, 0 );
gEngfuncs.pTriAPI->Vertex3f( origin[0] + 4, origin[1] - 4, origin[2] - zScale );
gEngfuncs.pTriAPI->TexCoord2f( 0, 1 );
gEngfuncs.pTriAPI->Vertex3f( origin[0] + 4, origin[1] - 4, z );
gEngfuncs.pTriAPI->TexCoord2f( 1, 1 );
gEngfuncs.pTriAPI->Vertex3f( origin[0] - 4, origin[1] + 4, 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.0 );
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, 0 );
gEngfuncs.pTriAPI->Vertex3f( x + right[0], y + right[1], ( z + right[2] ) * zScale);
gEngfuncs.pTriAPI->TexCoord2f( 0, 1 );
gEngfuncs.pTriAPI->Vertex3f( x, y, z * zScale );
gEngfuncs.pTriAPI->TexCoord2f( 1, 1 );
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.0f; // 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
}
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" );
}