//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ // // Purpose: // // $NoKeywords: $ //============================================================================= #include "hud.h" #include "cl_util.h" #include "cl_entity.h" #include "triangleapi.h" #include "tw_vgui.h" #include "vgui_TheWastesViewport.h" #include "hltv.h" #include "tw_common.h" #include "parsemsg.h" #include "pm_shared.h" #include "entity_types.h" // these are included for the math functions #include "com_model.h" #include "studio_util.h" #pragma warning(disable: 4244) extern "C" unsigned int uiDirectorFlags; // from pm_shared.c extern "C" float vecNewViewOrigin[3]; extern "C" float vecNewViewAngles[3]; extern "C" int iHasNewViewOrigin; 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 vec3_t v_angles; void SpectatorMode(void) { if ( !gEngfuncs.IsSpectateOnly() ) { gEngfuncs.Con_Printf( "specmode only in HLTV mode available.\n" ); return; } if ( gEngfuncs.Cmd_Argc() <= 1 ) { gEngfuncs.Con_Printf( "usage: specmode < modenumber >\n" ); return; } gHUD.m_Spectator.SetModes( atoi( gEngfuncs.Cmd_Argv(1) ), INSET_OFF ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CHudSpectator::Init() { gHUD.AddHudElem(this); m_iFlags |= HUD_ACTIVE; m_flNextObserverInput = 0.0f; m_iObserverTarget = 0; m_zoomDelta = 0.0f; m_moveDelta = 0.0f; memset( &m_OverviewData, 0, sizeof(m_OverviewData)); memset( &m_OverviewEntities, 0, sizeof(m_OverviewEntities)); m_iMainMode = 0; m_iInsetMode = 0; m_drawnames = gEngfuncs.pfnRegisterVariable( "cl_drawnames", "1", FCVAR_ARCHIVE ); gEngfuncs.pfnAddCommand ("specmode", SpectatorMode ); 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; } } //----------------------------------------------------------------------------- // SetSpectatorStartPosition(): // Get valid map position and 'beam' spectator to this position //----------------------------------------------------------------------------- void CHudSpectator::SetSpectatorStartPosition() { float position[3] = {0,0,0}; cl_entity_t * pEnt = gEngfuncs.GetEntityByIndex( 0 ); // get world model if ( pEnt && pEnt->model && pEnt->model->entities ) { char *data; char keyname[256]; int n; char token[1024]; for ( data = gEngfuncs.COM_ParseFile( pEnt->model->entities, token) ; // cl.worldmodel->entities *data && *token != '}'; data = gEngfuncs.COM_ParseFile(data, token) ) // TODO check this for null pointer crashes { if (token[0] != '{') return; while (1) { // parse key data = gEngfuncs.COM_ParseFile(data, token); if (token[0] == '}') break; if (!data) return; 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) return; if (token[0] == '}') return; if (!strcmp(keyname,"classname")) { if (!strcmp(token,"info_player_start")) { // origin should already be in SpectatorPos break; } }; if (!strcmp(keyname,"origin")) { UTIL_StringToVector(position, token); }; } // while (1) } } VectorCopy ( position, vJumpOrigin ); VectorCopy ( position, vecNewViewOrigin ); iHasNewViewOrigin = 1; iJumpSpectator = 1; } //----------------------------------------------------------------------------- // 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 red, green, blue; // draw only in spectator mode if ( gEngfuncs.IsSpectateOnly()!=1 ) return 1; // if user pressed zoom, aplly changes if ( m_zoomDelta != 0.0f && m_iMainMode != MAIN_ROAMING ) { 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 && m_iMainMode != MAIN_ROAMING ) { vec3_t right; AngleVectors(m_mapAngles, 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 ( m_iMainMode != MAIN_MAP_FREE ) return 1; if ( !m_drawnames->value ) return 1; // make sure we have player info gViewPort->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_iInsetMode != 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; } // hack in some team colors switch (g_PlayerExtraInfo[i+1].teamnumber) { // blue and red teams are swapped in CS and TFC case 2 : red = 1.0f; green = 0.4f; blue = 0.4f; break; // Team Red case 1 : red = 0.4f; green = 0.4f; blue = 1.0f; break; // Team Blue default : red = 0.8f; green = 0.8f; blue = 0.8f; break; // Unkown Team, grey } // draw the players name and health underneath sprintf(string, "%s", g_PlayerInfoList[i+1].name ); lx = strlen(string)*3; // 3 is avg. character length :) gEngfuncs.pfnDrawSetTextColor( red, green, blue ); DrawConsoleString( m_vPlayerPos[i][0]-lx,m_vPlayerPos[i][1], string); } return 1; } void CHudSpectator::DirectorMessage( int iSize, void *pbuf ) { BEGIN_READ( pbuf, iSize ); int cmd = READ_BYTE(); //read in what type of message it is switch ( cmd ) // director command byte { //sent by proxy 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); // put all init stuff in } break; //sent by client! case DRC_CMD_EVENT : { m_lastPrimaryObject = READ_WORD(); m_lastSecondaryObject = READ_WORD(); // m_iObserverFlags = READ_LONG(); //TODO //This section below was sent by Martin, but it seems to be a CS bit of code. I commented it out, and director mode works fine. /* TODO //this is where the magic happens! //unfortunately we don't know what m_autoDirector is :/ if ( m_autoDirector->value ) { if ( (g_iUser2 != m_lastPrimaryObject) || (g_iUser3 != m_lastSecondaryObject) ) V_ResetChaseCam(); g_iUser2 = m_lastPrimaryObject; g_iUser3 = m_lastSecondaryObject; } */ //I kept these two just in case g_iUser2 = m_lastPrimaryObject; g_iUser3 = m_lastSecondaryObject; //some debug text gEngfuncs.Con_Printf("Director Camera: %i %i\n", m_lastPrimaryObject,m_lastSecondaryObject); } break; case DRC_CMD_TIMESCALE : { float scale = READ_FLOAT(); // if timescale was changed by slow motion effect gEngfuncs.Con_Printf("HLTV Timescale: %f\n", scale ); } break; case DRC_CMD_STATUS: { int slots = READ_LONG(); // total number of spectator slots int numspecs = READ_LONG(); // total number of spectator int relays = READ_WORD(); // total number of relay proxies // gViewPort->UpdateSpectatorPanel(); TODO //if you wanted to update a VGUI showing the number of spectators, do something here gEngfuncs.Con_Printf("HLTV Status: %i / %i spectators, %i relays \n", numspecs, slots, relays ); } break; case DRC_CMD_MESSAGE: { //This message is from the "msg" command on the HLTV console, along with random "You are watching HLTV..." messages that I kept getting. //The first 4 bytes are color, and 29 and on are the string. The rest I was too lazy to figure out. char data[64]; memset( data, 0, 64 ); int i = 4; int color = READ_LONG(); while( i < 29 ) // we dont know yet what the format of this data is. it represents duration, x and y positions on the screen { data[i] = READ_BYTE(); i++; } //29++ is the string char *str = READ_STRING(); gEngfuncs.Con_Printf( "HLTV Message: %s \n", str ); } break; //below are some other messages that don't seem to be implemented. ( at least not by me ) case DRC_CMD_SOUND: { gEngfuncs.Con_Printf( "HLTV Sound: \n" ); } break; case DRC_CMD_FADE: { gEngfuncs.Con_Printf( "HLTV Fade: \n" ); } break; case DRC_CMD_SHAKE: { gEngfuncs.Con_Printf( "HLTV Shake!: \n" ); } break; case DRC_CMD_BANNER: { // gEngfuncs.Con_DPrintf("GUI: Banner %s\n",READ_STRING() ); // name of banner tga eg gfx/temp/7454562234563475.tga //not yet supported //gViewPort->m_pSpectatorPanel->m_TopBanner->LoadImage( READ_STRING() ); //gViewPort->UpdateSpectatorPanel(); } break; default: gEngfuncs.Con_DPrintf("CHudSpectator::DirectorMessage: unknown command %i.\n", cmd ); break; } } 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; if ( m_iObserverTarget ) iStart = m_iObserverTarget; else iStart = 1; m_iObserverTarget = 0; int iCurrent = iStart; int iDir = bReverse ? -1 : 1; // make sure we have player info gViewPort->GetAllPlayersInfo(); do { iCurrent += iDir; // Loop through the clients if (iCurrent > MAX_PLAYERS) iCurrent = 1; if (iCurrent < 1) iCurrent = MAX_PLAYERS; cl_entity_t *pEnt = gEngfuncs.GetEntityByIndex( iCurrent ); if ( !IsActivePlayer( pEnt ) ) continue; // MOD AUTHORS: Add checks on target here. m_iObserverTarget = iCurrent; break; } while ( iCurrent != iStart ); // Did we find a target? if ( m_iObserverTarget ) { // Store the target in pev so the physics DLL can get to it if ( m_iMainMode != MAIN_ROAMING ) { g_iUser2 = m_iObserverTarget; g_iUser3 = 0; } gEngfuncs.Con_Printf("Now Tracking %s\n", g_PlayerInfoList[m_iObserverTarget].name ); } else { gEngfuncs.Con_Printf( "No observer targets.\n" ); } } void CHudSpectator::HandleButtonsDown( int ButtonPressed ) { double time = gEngfuncs.GetClientTime(); int newMainMode = m_iMainMode; int newInsetMode = m_iInsetMode; // gEngfuncs.Con_Printf(" HandleButtons:%i\n", ButtonPressed ); // Slow down mouse clicks if ( m_flNextObserverInput > time || gEngfuncs.IsSpectateOnly()!=1 ) return; // changing target or chase mode not in overviewmode without inset window // Jump changes main window modes if ( ButtonPressed & IN_JUMP ) { newMainMode+=1; if (newMainMode > MAIN_MAP_FREE ) newMainMode = MAIN_CHASE_FREE; // Main window can't be turned off } // Duck changes inset window mode if ( ButtonPressed & IN_DUCK ) { newInsetMode+=1; if ( newInsetMode > INSET_MAP_FREE ) newInsetMode = INSET_OFF; // inset window can be turned off } // Attack moves to the next player if ( ButtonPressed & (IN_ATTACK | IN_ATTACK2) ) { // in any mode change now from directed to users mode g_iUser1 = OBS_CHASE_FREE; // leave directed sub mode FindNextPlayer( (ButtonPressed & IN_ATTACK2) ? true:false ); if ( m_iMainMode == MAIN_ROAMING && m_iObserverTarget ) { cl_entity_t * ent = gEngfuncs.GetEntityByIndex( m_iObserverTarget ); VectorCopy ( ent->origin, vJumpOrigin ); VectorCopy ( ent->angles, vJumpAngles ); gEngfuncs.SetViewAngles( vJumpAngles ); iJumpSpectator = 1; } } 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; if ( newMainMode != m_iMainMode || newInsetMode != m_iInsetMode ) SetModes(newMainMode, newInsetMode); m_flNextObserverInput = time + 0.2; } void CHudSpectator::HandleButtonsUp( int ButtonPressed ) { if ( gEngfuncs.IsSpectateOnly() != 1 ) return; 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) { char string[32]; static wrect_t crosshairRect; if ( iNewMainMode < MAIN_CHASE_LOCKED || iNewMainMode > MAIN_MAP_FREE ) { gEngfuncs.Con_Printf("Invalid spectator mode.\n"); return; } // Just abort if we're changing to the mode we're already in if (iNewInsetMode != m_iInsetMode) { m_iInsetMode = iNewInsetMode; sprintf(string, "%c#Spec_Mode_Inset%d", HUD_PRINTCENTER, m_iInsetMode); gHUD.m_TextMessage.MsgFunc_TextMsg(NULL, strlen(string)+1, string ); } // main modes ettings will override inset window settings if ( iNewMainMode != m_iMainMode ) { switch ( iNewMainMode ) { case MAIN_CHASE_FREE : g_iUser1 = OBS_DIRECTED; break; case MAIN_ROAMING : g_iUser1 = OBS_ROAMING; if ( m_iMainMode == MAIN_CHASE_FREE ) { VectorCopy ( vecNewViewOrigin, vJumpOrigin ); VectorCopy ( vecNewViewAngles, vJumpAngles ); gEngfuncs.SetViewAngles( vJumpAngles ); iJumpSpectator = 1; } if ( m_iMainMode == MAIN_IN_EYE ) { V_GetInEyePos( m_iObserverTarget, vJumpOrigin, vJumpAngles ); gEngfuncs.SetViewAngles( vJumpAngles ); iJumpSpectator = 1; } g_iUser2 = m_iObserverTarget = 0; g_iUser3 = 0; break; case MAIN_IN_EYE : g_iUser1 = OBS_DIRECTED; break; case MAIN_MAP_FREE : g_iUser1 = OBS_DIRECTED; // reset user values m_mapZoom = m_OverviewData.zoom; m_mapOrigin = m_OverviewData.origin; break; } m_iMainMode = iNewMainMode; sprintf(string, "%c#Spec_Mode%d", HUD_PRINTCENTER, m_iMainMode); gHUD.m_TextMessage.MsgFunc_TextMsg(NULL, strlen(string)+1, string ); if ( g_iUser1 == OBS_DIRECTED && m_iObserverTarget == 0 ) { // choose last Director object if still available if ( IsActivePlayer( gEngfuncs.GetEntityByIndex( m_lastPrimaryObject ) ) ) { g_iUser2 = m_iObserverTarget = m_lastPrimaryObject; g_iUser3 = m_lastSecondaryObject; } } // enable or disable crosshair if ( m_iMainMode == MAIN_ROAMING || m_iMainMode == MAIN_IN_EYE ) { crosshairRect.left = 24; crosshairRect.top = 0; crosshairRect.right = 48; crosshairRect.bottom = 24; SetCrosshair( m_hCrosshair, crosshairRect, 255, 255, 255 ); } else { memset( &crosshairRect,0,sizeof(crosshairRect) ); SetCrosshair( 0, crosshairRect, 0, 0, 0 ); } } // disallow same inset mode as main mode: if ( ( m_iMainMode != MAIN_MAP_FREE ) && ( m_iInsetMode == INSET_CHASE_FREE || m_iInsetMode == INSET_IN_EYE ) ) { // both would show in World picures SetModes( m_iMainMode, INSET_MAP_FREE ); } if ( ( m_iMainMode == MAIN_MAP_FREE ) && ( m_iInsetMode == INSET_MAP_FREE ) ) { // both would show map views SetModes( m_iMainMode, INSET_OFF ); } gViewPort->UpdateSpectatorMenu(); } 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]; char levelname[255]; char token[1024]; 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(levelname, gEngfuncs.pfnGetLevelName() + 5); levelname[strlen(levelname)-4] = 0; sprintf(filename, "overviews/%s.txt", levelname ); strcpy( m_OverviewData.map, levelname ); pfile = (char *)gEngfuncs.COM_LoadFile( filename, 5, NULL); if (!pfile) { gEngfuncs.Con_Printf("Couldn't open file %s. Using default values for overiew mode.\n", filename ); return false; } else { 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++; } } } 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(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 - m_mapAngles[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::GetMapPosition( float * returnvec ) { vec3_t forward; vec3_t zScaledTarget; zScaledTarget[0] = m_mapOrigin[0]; zScaledTarget[1] = m_mapOrigin[1]; zScaledTarget[2] = m_mapOrigin[2] * (( 90.0f - m_mapAngles[0] ) / 90.0f ); AngleVectors(m_mapAngles, forward, NULL, NULL); VectorNormalize(forward); VectorScale(forward, -( 4096.0f / m_mapZoom ), forward ); VectorAdd( zScaledTarget, forward, returnvec ) } void CHudSpectator::DrawOverviewEntities() { int i,ir,ig,ib; struct model_s *hSpriteModel; vec3_t origin,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 - m_mapAngles[0] ) / 90.0f; z = m_OverviewData.layersHeights[0] * zScale; // get yellow/brown HUD color UnpackRGB(ir,ig,ib, RGB_WHITE); 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_iInsetMode == INSET_OFF ) return; if ( m_iInsetMode == INSET_IN_EYE || m_iMainMode == MAIN_IN_EYE ) { V_GetInEyePos(m_iObserverTarget, vecNewViewOrigin, vecNewViewAngles ); } // draw camera sprite x = vecNewViewOrigin[0]; y = vecNewViewOrigin[1]; z = vecNewViewOrigin[2]; 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(vecNewViewAngles, 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 (); } extern void VectorAngles( const float *forward, float *angles ); extern "C" void NormalizeAngles( float *angles ); void CHudSpectator::DrawOverview() { // don't draw it in developer mode if ( gEngfuncs.IsSpectateOnly() != 1) return; // Only draw the overview if Map Mode is selected for this view if ( m_iDrawCycle == 0 && m_iMainMode != MAIN_MAP_FREE ) return; if ( m_iDrawCycle == 1 && m_iInsetMode != 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::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)); /* // this block was in martin's code - not clear what m_autoDirector is. if ( gEngfuncs.IsSpectateOnly() || gEngfuncs.pDemoAPI->IsPlayingback() ) m_autoDirector->value = 1.0f; else m_autoDirector->value = 0.0f; */ Reset(); SetModes( OBS_CHASE_FREE, INSET_OFF ); CenterPrint(""); // Prevent stupid "Free Chase-Camera" on startup - gage g_iUser2 = 0; // fake not target until first camera command // reset HUD FOV gHUD.m_iFOV = CVAR_GET_FLOAT("default_fov"); }