diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/client/tf/vgui/tf_spectatorgui.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/client/tf/vgui/tf_spectatorgui.cpp')
| -rw-r--r-- | game/client/tf/vgui/tf_spectatorgui.cpp | 1279 |
1 files changed, 1279 insertions, 0 deletions
diff --git a/game/client/tf/vgui/tf_spectatorgui.cpp b/game/client/tf/vgui/tf_spectatorgui.cpp new file mode 100644 index 0000000..019944c --- /dev/null +++ b/game/client/tf/vgui/tf_spectatorgui.cpp @@ -0,0 +1,1279 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "cbase.h" +#include "hud.h" +#include "c_team.h" +#include "tf_playerpanel.h" +#include "tf_spectatorgui.h" +#include "tf_shareddefs.h" +#include "tf_gamerules.h" +#include "tf_hud_objectivestatus.h" +#include "tf_hud_statpanel.h" +#include "iclientmode.h" +#include "c_playerresource.h" +#include "tf_hud_building_status.h" +#include "tf_hud_tournament.h" +#include "tf_hud_winpanel.h" +#include "tf_tips.h" +#include "tf_mapinfomenu.h" +#include "econ_wearable.h" +#include "c_tf_playerresource.h" +#include "playerspawncache.h" +#include "econ_notifications.h" +#include "tf_hud_item_progress_tracker.h" +#include "tf_hud_target_id.h" +#include "c_baseobject.h" +#include "inputsystem/iinputsystem.h" + +#if defined( REPLAY_ENABLED ) +#include "replay/replay.h" +#include "replay/ireplaysystem.h" +#include "replay/ireplaymanager.h" +#endif // REPLAY_ENABLED + +#include <vgui/ILocalize.h> +#include <vgui/ISurface.h> +#include "vgui_avatarimage.h" + +using namespace vgui; + +extern ConVar _cl_classmenuopen; +extern ConVar tf_max_health_boost; +extern const char *g_pszItemClassImages[]; +extern int g_ClassDefinesRemap[]; +extern ConVar tf_mvm_buybacks_method; + +const char *GetMapDisplayName( const char *mapName ); + +static const wchar_t* GetSCGlyph( const char* action ) +{ + auto origin = g_pInputSystem->GetSteamControllerActionOrigin( action, GAME_ACTION_SET_SPECTATOR ); + return g_pInputSystem->GetSteamControllerFontCharacterForActionOrigin( origin ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFSpectatorGUI *GetTFSpectatorGUI() +{ + extern CSpectatorGUI *g_pSpectatorGUI; + return dynamic_cast< CTFSpectatorGUI * >( g_pSpectatorGUI ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +ConVar cl_spec_carrieditems( "cl_spec_carrieditems", "1", FCVAR_ARCHIVE, "Show non-standard items being carried by player you're spectating." ); + +void HUDTournamentSpecChangedCallBack( IConVar *var, const char *pOldString, float flOldValue ) +{ + CTFSpectatorGUI *pPanel = (CTFSpectatorGUI*)gViewPortInterface->FindPanelByName( PANEL_SPECGUI ); + if ( pPanel ) + { + pPanel->InvalidateLayout( true, true ); + } +} +ConVar cl_use_tournament_specgui( "cl_use_tournament_specgui", "0", FCVAR_ARCHIVE, "When in tournament mode, use the advanced tournament spectator UI.", HUDTournamentSpecChangedCallBack ); + +//----------------------------------------------------------------------------- +// Purpose: Constructor +//----------------------------------------------------------------------------- +CTFSpectatorGUI::CTFSpectatorGUI(IViewPort *pViewPort) : CSpectatorGUI(pViewPort) +{ + m_flNextTipChangeTime = 0; + m_iTipClass = TF_CLASS_UNDEFINED; + + m_nEngBuilds_xpos = m_nEngBuilds_ypos = 0; + m_nSpyBuilds_xpos = m_nSpyBuilds_ypos = 0; + + m_nMannVsMachineStatus_xpos = m_nMannVsMachineStatus_ypos = 0; + m_pBuyBackLabel = new CExLabel( this, "BuyBackLabel", "" ); + m_pReinforcementsLabel = new Label( this, "ReinforcementsLabel", "" ); + m_pClassOrTeamLabel = new Label( this, "ClassOrTeamLabel", "" ); + // m_pSwitchCamModeKeyLabel = new Label( this, "SwitchCamModeKeyLabel", "" ); + m_pSwitchCamModeKeyLabel = nullptr; + m_pClassOrTeamKeyLabel = nullptr; + m_pCycleTargetFwdKeyLabel = new Label( this, "CycleTargetFwdKeyLabel", "" ); + m_pCycleTargetRevKeyLabel = new Label( this, "CycleTargetRevKeyLabel", "" ); + m_pMapLabel = new Label( this, "MapLabel", "" ); + m_pItemPanel = new CItemModelPanel( this, "itempanel" ); + + m_pStudentHealth = new CTFSpectatorGUIHealth( this, "StudentGUIHealth" ); + m_pAvatar = NULL; + + m_flNextItemPanelUpdate = 0; + m_flNextPlayerPanelUpdate = 0; + m_iPrevItemShown = 0; + m_iFirstItemShown = 0; + m_bShownItems = false; + m_hPrevItemPlayer = NULL; + m_pPlayerPanelKVs = NULL; + m_bReapplyPlayerPanelKVs = false; + m_bCoaching = false; + + ListenForGameEvent( "spec_target_updated" ); + ListenForGameEvent( "player_death" ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CTFSpectatorGUI::~CTFSpectatorGUI() +{ + if ( m_pPlayerPanelKVs ) + { + m_pPlayerPanelKVs->deleteThis(); + m_pPlayerPanelKVs = NULL; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::Reset( void ) +{ + BaseClass::Reset(); + + for ( int i = 0; i < m_PlayerPanels.Count(); i++ ) + { + m_PlayerPanels[i]->Reset(); + } + + m_pStudentHealth->Reset(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CTFSpectatorGUI::GetTopBarHeight() +{ + int iPlayerPanelHeight = 0; + if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) + { + if ( GetLocalPlayerTeam() == TF_TEAM_PVE_DEFENDERS ) + { + if ( m_PlayerPanels.Count() > 0 ) + { + iPlayerPanelHeight = m_PlayerPanels[0]->GetTall(); + } + } + } + + return m_pTopBar->GetTall() + iPlayerPanelHeight; +} + +//----------------------------------------------------------------------------- +// Purpose: makes the GUI fill the screen +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::PerformLayout( void ) +{ + BaseClass::PerformLayout(); + + if ( m_bReapplyPlayerPanelKVs ) + { + m_bReapplyPlayerPanelKVs = false; + + if ( m_pPlayerPanelKVs ) + { + for ( int i = 0; i < m_PlayerPanels.Count(); i++ ) + { + m_PlayerPanels[i]->ApplySettings( m_pPlayerPanelKVs ); + m_PlayerPanels[i]->InvalidateLayout( false, true ); + } + } + } + + if ( m_pStudentHealth ) + { + Panel* pHealthPosPanel = FindChildByName( "HealthPositioning" ); + if ( pHealthPosPanel ) + { + int xPos, yPos, iWide, iTall; + pHealthPosPanel->GetBounds( xPos, yPos, iWide, iTall ); + m_pStudentHealth->SetBounds( xPos, yPos, iWide, iTall ); + m_pStudentHealth->SetZPos( pHealthPosPanel->GetZPos() ); + } + } + + UpdatePlayerPanels(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + bool bVisible = IsVisible(); + BaseClass::ApplySchemeSettings( pScheme ); + + m_bReapplyPlayerPanelKVs = true; + m_bPrevTournamentMode = InTournamentGUI(); + + m_pSwitchCamModeKeyLabel = dynamic_cast< Label* >( FindChildByName( "SwitchCamModeKeyLabel" ) ); + + m_pAvatar = dynamic_cast<CAvatarImagePanel *>( FindChildByName("AvatarImage") ); + if ( ::input->IsSteamControllerActive() ) + { + m_pClassOrTeamKeyLabel = dynamic_cast< CExLabel* >( FindChildByName( "ClassOrTeamKeyLabel" ) ); + } + else + { + m_pClassOrTeamKeyLabel = nullptr; + } + + if ( m_bCoaching ) + { + if ( m_pTopBar ) + { + m_pTopBar->SetBgColor( Color( 255, 255, 255, 0 ) ); + } + if ( m_pBottomBarBlank ) + { + m_pBottomBarBlank->SetVisible( false ); + } + } + + if ( m_bCoaching ) + { + if ( m_pClassOrTeamLabel && m_pClassOrTeamLabel->IsVisible() ) + { + m_pClassOrTeamLabel->SetVisible( false ); + } + + if ( m_pClassOrTeamKeyLabel && m_pClassOrTeamKeyLabel->IsVisible() ) + { + m_pClassOrTeamKeyLabel->SetVisible( false ); + } + } + + // Stay the same visibility as before the scheme reload. + SetVisible( bVisible ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::ApplySettings( KeyValues *inResourceData ) +{ + BaseClass::ApplySettings( inResourceData ); + + KeyValues *pItemKV = inResourceData->FindKey( "playerpanels_kv" ); + if ( pItemKV ) + { + if ( m_pPlayerPanelKVs ) + { + m_pPlayerPanelKVs->deleteThis(); + } + m_pPlayerPanelKVs = new KeyValues("playerpanels_kv"); + pItemKV->CopySubkeys( m_pPlayerPanelKVs ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFSpectatorGUI::NeedsUpdate( void ) +{ + if ( !C_BasePlayer::GetLocalPlayer() ) + return false; + + if( IsVisible() ) + return true; + + return BaseClass::NeedsUpdate(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::Update() +{ + BaseClass::Update(); + + UpdateReinforcements(); + UpdateKeyLabels(); + + if ( m_flNextItemPanelUpdate < gpGlobals->curtime ) + { + UpdateItemPanel(); + } + + // If we need to flip tournament mode, do it now + if ( m_bPrevTournamentMode != InTournamentGUI() ) + { + InvalidateLayout( false, true ); + } + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( pLocalPlayer && m_bCoaching != pLocalPlayer->m_bIsCoaching ) + { + m_bCoaching = pLocalPlayer->m_bIsCoaching; + InvalidateLayout( false, true ); + } + if ( pLocalPlayer && pLocalPlayer->m_hStudent && m_bCoaching ) + { + Vector vecTarget = pLocalPlayer->m_hStudent->GetAbsOrigin(); + Vector vecDelta = pLocalPlayer->GetAbsOrigin() - vecTarget; + float flDistance = vecDelta.Length(); + const float kInchesToMeters = 0.0254f; + int distance = RoundFloatToInt( flDistance * kInchesToMeters ); + wchar_t wzValue[32]; + _snwprintf( wzValue, ARRAYSIZE( wzValue ), L"%u", distance ); + wchar_t wzText[256]; + g_pVGuiLocalize->ConstructString_safe( wzText, g_pVGuiLocalize->Find( "#TR_DistanceToStudent" ), 1, wzValue ); + SetDialogVariable( "student_distance", wzText ); + } + + if ( m_flNextPlayerPanelUpdate < gpGlobals->curtime ) + { + RecalculatePlayerPanels(); + m_flNextPlayerPanelUpdate = gpGlobals->curtime + 0.1f; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::UpdateReinforcements( void ) +{ + if( !m_pReinforcementsLabel ) + return; + + C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( !pPlayer || pPlayer->IsHLTV() || + ( pPlayer->GetTeamNumber() != TF_TEAM_RED && pPlayer->GetTeamNumber() != TF_TEAM_BLUE ) || + ( pPlayer->m_Shared.GetState() != TF_STATE_OBSERVER && pPlayer->m_Shared.GetState() != TF_STATE_DYING ) || + ( pPlayer->GetObserverMode() == OBS_MODE_FREEZECAM ) ) + { + m_pReinforcementsLabel->SetVisible( false ); + m_pBuyBackLabel->SetVisible( false ); + + return; + } + + bool bBuyBackVisible = false; + wchar_t wLabel[256]; + + if ( TFGameRules()->InStalemate() ) + { + if ( TFGameRules()->IsInArenaMode() == true ) + { + g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find( "#TF_Arena_NoRespawning" ), 0 ); + } + else + { + g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find( "#game_respawntime_stalemate" ), 0 ); + } + } + else if ( TFGameRules()->State_Get() == GR_STATE_TEAM_WIN ) + { + // a team has won the round + g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find( "#game_respawntime_next_round" ), 0 ); + } + else + { + float flNextRespawn = 0.f; + bool bQuickSpawn = TFGameRules() && TFGameRules()->IsMannVsMachineMode() && pPlayer->IsPlayerClass( TF_CLASS_SCOUT ); + + if ( g_TF_PR ) + { + flNextRespawn = g_TF_PR->GetNextRespawnTime( pPlayer->entindex() ); + } + else if ( !bQuickSpawn ) + { + flNextRespawn = TFGameRules()->GetNextRespawnWave( pPlayer->GetTeamNumber(), pPlayer ); + } + + if ( !flNextRespawn ) + { + m_pReinforcementsLabel->SetVisible( false ); + m_pBuyBackLabel->SetVisible( false ); + return; + } + + int iRespawnWait = (flNextRespawn - gpGlobals->curtime); + if ( iRespawnWait <= 0 ) + { + g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find("#game_respawntime_now" ), 0 ); + } + else if ( iRespawnWait <= 1.0 ) + { + g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find("#game_respawntime_in_sec" ), 0 ); + } + else + { + char szSecs[6]; + wchar_t wSecs[4]; + + if ( TFGameRules()->IsMannVsMachineMode() ) + { + bool bNewMethod = tf_mvm_buybacks_method.GetBool(); + bBuyBackVisible = ( bNewMethod ) ? g_TF_PR->GetNumBuybackCredits( pPlayer->entindex() ) : true; + + if ( bBuyBackVisible ) + { + // When using the new system, we display "Hit '%use_action_slot_item%' to RESPAWN INSTANTLY! (%s1 remaining this wave)" + // When using the old system, we display "Hit '%use_action_slot_item%' to pay %s1 credits and RESPAWN INSTANTLY!" + int nCost = ( bNewMethod ) ? g_TF_PR->GetNumBuybackCredits( pPlayer->entindex() ) : iRespawnWait * MVM_BUYBACK_COST_PER_SEC; + const char *pszString = ( bNewMethod ) ? "#TF_PVE_Buyback_Fixed" : "#TF_PVE_Buyback"; + + Q_snprintf( szSecs, sizeof( szSecs ), "%d", nCost ); + g_pVGuiLocalize->ConvertANSIToUnicode( szSecs, wSecs, sizeof( wSecs ) ); + g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find( pszString ), 1, wSecs ); + + wchar_t wBuyBack[256]; + UTIL_ReplaceKeyBindings( wLabel, 0, wBuyBack, sizeof( wBuyBack ), GAME_ACTION_SET_SPECTATOR ); + + m_pBuyBackLabel->SetText( wBuyBack, true ); + } + } + + Q_snprintf( szSecs, sizeof(szSecs), "%d", iRespawnWait ); + + g_pVGuiLocalize->ConvertANSIToUnicode(szSecs, wSecs, sizeof(wSecs)); + g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find("#game_respawntime_in_secs" ), 1, wSecs ); + } + } + + m_pReinforcementsLabel->SetVisible( true ); + m_pReinforcementsLabel->SetText( wLabel, true ); + m_pBuyBackLabel->SetVisible( bBuyBackVisible ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::UpdateKeyLabels( void ) +{ + C_TFPlayer *pPlayer = C_TFPlayer::GetLocalTFPlayer(); + + bool bSteamController = ::input->IsSteamControllerActive(); + + if ( InTournamentGUI() == false ) + { + // get the desired player class + int iClass = TF_CLASS_UNDEFINED; + bool bIsHLTV = engine->IsHLTV(); + + if ( pPlayer ) + { + iClass = pPlayer->m_Shared.GetDesiredPlayerClassIndex(); + } + + // if it's time to change the tip, or the player has changed desired class, update the tip + if ( ( gpGlobals->curtime >= m_flNextTipChangeTime ) || ( iClass != m_iTipClass ) ) + { + if ( bIsHLTV ) + { + const wchar_t *wzTip = g_pVGuiLocalize->Find( "#Tip_HLTV" ); + + if ( wzTip ) + { + SetDialogVariable( "tip", wzTip ); + } + } + else + { + wchar_t wzTipLabel[512]=L""; + const wchar_t *wzTip = g_TFTips.GetNextClassTip( iClass ); + Assert( wzTip && wzTip[0] ); + g_pVGuiLocalize->ConstructString_safe( wzTipLabel, g_pVGuiLocalize->Find( "#Tip_Fmt" ), 1, wzTip ); + SetDialogVariable( "tip", wzTipLabel ); + } + + m_flNextTipChangeTime = gpGlobals->curtime + 10.0f; + m_iTipClass = iClass; + } + + if ( m_pClassOrTeamLabel ) + { + if ( pPlayer ) + { + static wchar_t wzFinal[512] = L""; + const wchar_t *wzTemp = NULL; + const wchar_t *wzIcon = nullptr; + + if ( TFGameRules() && TFGameRules()->IsInTraining() ) + { + wzTemp = L""; + wzIcon = L""; + } + else if ( bIsHLTV ) + { + wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_AutoDirector" ); + } + else if ( pPlayer->GetTeamNumber() == TEAM_SPECTATOR && TFGameRules()->IsInArenaMode() == false ) + { + if ( bSteamController ) + { + wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_ChangeTeam_NoKey" ); + wzIcon = GetSCGlyph( "changeteam" ); + } + else + { + wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_ChangeTeam" ); + } + } + else + { + if ( bSteamController ) + { + wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_ChangeClass_NoKey" ); + wzIcon = GetSCGlyph( "changeclass" ); + } + else + { + wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_ChangeClass" ); + } + } + + if ( wzTemp ) + { + UTIL_ReplaceKeyBindings( wzTemp, 0, wzFinal, sizeof( wzFinal ) ); + } + + m_pClassOrTeamLabel->SetText( wzFinal, true ); + + if ( m_pClassOrTeamKeyLabel ) + { + if ( wzIcon && m_pClassOrTeamLabel->IsVisible() ) + { + m_pClassOrTeamKeyLabel->SetText( wzIcon ); + m_pClassOrTeamKeyLabel->SetVisible( true ); + } + else + { + m_pClassOrTeamKeyLabel->SetVisible( false ); + } + } + } + } + + static ConVarRef cl_hud_minmode( "cl_hud_minmode", true ); + if ( m_bCoaching == true || ( cl_hud_minmode.IsValid() && ( cl_hud_minmode.GetBool() == false ) ) ) + { + if ( m_pSwitchCamModeKeyLabel ) + { + if ( ( pPlayer && pPlayer->GetTeamNumber() > TEAM_SPECTATOR ) && ( ( mp_forcecamera.GetInt() == OBS_ALLOW_TEAM ) || ( mp_forcecamera.GetInt() == OBS_ALLOW_NONE ) || mp_fadetoblack.GetBool() ) ) + { + if ( m_pSwitchCamModeKeyLabel->IsVisible() ) + { + m_pSwitchCamModeKeyLabel->SetVisible( false ); + + Label *pLabel = dynamic_cast<Label *>( FindChildByName( "SwitchCamModeLabel" ) ); + if ( pLabel ) + { + pLabel->SetVisible( false ); + } + } + } + else + { + if ( !m_pSwitchCamModeKeyLabel->IsVisible() ) + { + m_pSwitchCamModeKeyLabel->SetVisible( true ); + + Label *pLabel = dynamic_cast<Label *>( FindChildByName( "SwitchCamModeLabel" ) ); + if ( pLabel ) + { + pLabel->SetVisible( true ); + } + } + + wchar_t wLabel[256] = L""; + const wchar_t *wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_SwitchCamModeKey" ); + UTIL_ReplaceKeyBindings( wzTemp, 0, wLabel, sizeof( wLabel ) ); + m_pSwitchCamModeKeyLabel->SetText( wLabel, true ); + } + } + + if ( m_pCycleTargetFwdKeyLabel ) + { + if ( ( pPlayer && pPlayer->GetTeamNumber() > TEAM_SPECTATOR ) && ( mp_fadetoblack.GetBool() || ( mp_forcecamera.GetInt() == OBS_ALLOW_NONE ) ) ) + { + if ( m_pCycleTargetFwdKeyLabel->IsVisible() ) + { + m_pCycleTargetFwdKeyLabel->SetVisible( false ); + + Label *pLabel = dynamic_cast<Label *>( FindChildByName( "CycleTargetFwdLabel" ) ); + if ( pLabel ) + { + pLabel->SetVisible( false ); + } + } + } + else + { + if ( !m_pCycleTargetFwdKeyLabel->IsVisible() ) + { + m_pCycleTargetFwdKeyLabel->SetVisible( true ); + + Label *pLabel = dynamic_cast<Label *>( FindChildByName( "CycleTargetFwdLabel" ) ); + if ( pLabel ) + { + pLabel->SetVisible( true ); + } + } + + if ( !bSteamController ) + { + wchar_t wLabel[256] = L""; + const wchar_t *wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_CycleTargetFwdKey" ); + UTIL_ReplaceKeyBindings( wzTemp, 0, wLabel, sizeof( wLabel ) ); + m_pCycleTargetFwdKeyLabel->SetText( wLabel, true ); + } + else + { + m_pCycleTargetFwdKeyLabel->SetText( GetSCGlyph( "next_target" ) ); + } + } + } + + if ( m_pCycleTargetRevKeyLabel ) + { + if ( ( pPlayer && pPlayer->GetTeamNumber() > TEAM_SPECTATOR ) && ( mp_fadetoblack.GetBool() || ( mp_forcecamera.GetInt() == OBS_ALLOW_NONE ) ) ) + { + if ( m_pCycleTargetRevKeyLabel->IsVisible() ) + { + m_pCycleTargetRevKeyLabel->SetVisible( false ); + + Label *pLabel = dynamic_cast<Label *>( FindChildByName( "CycleTargetRevLabel" ) ); + if ( pLabel ) + { + pLabel->SetVisible( false ); + } + } + } + else + { + if ( !m_pCycleTargetRevKeyLabel->IsVisible() ) + { + m_pCycleTargetRevKeyLabel->SetVisible( true ); + + Label *pLabel = dynamic_cast<Label *>( FindChildByName( "CycleTargetRevLabel" ) ); + if ( pLabel ) + { + pLabel->SetVisible( true ); + } + } + + if ( !bSteamController ) + { + wchar_t wLabel[256] = L""; + const wchar_t *wzTemp = g_pVGuiLocalize->Find( "#TF_Spectator_CycleTargetRevKey" ); + UTIL_ReplaceKeyBindings( wzTemp, 0, wLabel, sizeof( wLabel ) ); + m_pCycleTargetRevKeyLabel->SetText( wLabel, true ); + } + else + { + m_pCycleTargetRevKeyLabel->SetText( GetSCGlyph( "prev_target" ) ); + } + } + } + + if ( m_pMapLabel ) + { + wchar_t wMapName[32]; + wchar_t wLabel[256]; + char szMapName[32]; + + char tempname[128]; + Q_FileBase( engine->GetLevelName(), tempname, sizeof( tempname ) ); + Q_strlower( tempname ); + + if ( IsX360() ) + { + char *pExt = Q_stristr( tempname, ".360" ); + if ( pExt ) + { + *pExt = '\0'; + } + } + + Q_strncpy( szMapName, GetMapDisplayName( tempname ), sizeof( szMapName ) ); + + g_pVGuiLocalize->ConvertANSIToUnicode( szMapName, wMapName, sizeof(wMapName)); + g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find( "#Spec_Map" ), 1, wMapName ); + + m_pMapLabel->SetText( wLabel ); + } + } + } + + // coaching stuff + if ( pPlayer && pPlayer->m_hStudent ) + { + int iHealth = 0; + int iMaxHealth = 1; + int iMaxBuffedHealth = 0; + + C_TFPlayer *pStudent = pPlayer->m_hStudent; + { + wchar_t wPlayerName[MAX_PLAYER_NAME_LENGTH]; + wchar_t wLabel[256]; + const char* pStudentName = g_TF_PR->GetPlayerName( pStudent->entindex() ); + + g_pVGuiLocalize->ConvertANSIToUnicode( pStudentName, wPlayerName, sizeof(wPlayerName)); + g_pVGuiLocalize->ConstructString_safe( wLabel, g_pVGuiLocalize->Find( "#TF_Coach_Student_Prefix" ), 1, wPlayerName ); + + SetDialogVariable( "student_name", wLabel ); + } + for ( int i = 1; i <= 2; ++i ) + { + wchar_t wLabel[256] = L""; + const wchar_t *wzTemp = g_pVGuiLocalize->Find( CFmtStr1024( "#TF_Coach_Slot%uLabel", i ) ); + UTIL_ReplaceKeyBindings( wzTemp, 0, wLabel, sizeof( wLabel ) ); + SetDialogVariable( CFmtStr1024( "coach_command_%u", i ), wLabel ); + } + if ( m_pAvatar ) + { + m_pAvatar->SetShouldDrawFriendIcon( false ); + + if ( steamapicontext && steamapicontext->SteamUser() ) + { + CSteamID studentSteamID; + if ( pStudent->GetSteamID( &studentSteamID ) ) + { + m_pAvatar->SetPlayer( studentSteamID, k_EAvatarSize64x64 ); + } + else + { + m_pAvatar->ClearAvatar(); + } + } + } + + // don't show crosshair when viewing the world from the student's POV + bool bShowCrosshair = pPlayer->GetObserverMode() != OBS_MODE_IN_EYE; + vgui::Panel *pCrosshair = FindChildByName( "Crosshair" ); + if ( pCrosshair && pCrosshair->IsVisible() != bShowCrosshair ) + { + pCrosshair->SetVisible( bShowCrosshair ); + } + + iHealth = pStudent->GetHealth(); + iMaxHealth = pStudent->GetMaxHealth(); + iMaxBuffedHealth = pStudent->m_Shared.GetMaxBuffedHealth(); + + if ( m_pStudentHealth ) + { + m_pStudentHealth->SetHealth( iHealth, iMaxHealth, iMaxBuffedHealth ); + + if ( !m_pStudentHealth->IsVisible() ) + { + m_pStudentHealth->SetVisible( true ); + } + } + } + else + { + if ( m_pStudentHealth && m_pStudentHealth->IsVisible() ) + { + m_pStudentHealth->SetVisible( false ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::ShowPanel(bool bShow) +{ + if ( bShow != IsVisible() ) + { + CTFHudObjectiveStatus *pStatus = GET_HUDELEMENT( CTFHudObjectiveStatus ); + CHudBuildingStatusContainer_Engineer *pEngBuilds = GET_NAMED_HUDELEMENT( CHudBuildingStatusContainer_Engineer, BuildingStatus_Engineer ); + CHudBuildingStatusContainer_Spy *pSpyBuilds = GET_NAMED_HUDELEMENT( CHudBuildingStatusContainer_Spy, BuildingStatus_Spy ); + CHudItemAttributeTracker *pAttribTrackers = GET_HUDELEMENT( CHudItemAttributeTracker ); + + if ( pAttribTrackers ) + { + pAttribTrackers->InvalidateLayout(); + } + + if ( bShow ) + { + int xPos = 0, yPos = 0; + + if ( pStatus ) + { + pStatus->SetParent( this ); + pStatus->SetProportional( true ); + } + + if ( pEngBuilds ) + { + pEngBuilds->GetPos( xPos, yPos ); + m_nEngBuilds_xpos = xPos; + m_nEngBuilds_ypos = yPos; + pEngBuilds->SetPos( xPos, GetTopBarHeight() ); + } + + if ( pSpyBuilds ) + { + pSpyBuilds->GetPos( xPos, yPos ); + m_nSpyBuilds_xpos = xPos; + m_nSpyBuilds_ypos = yPos; + pSpyBuilds->SetPos( xPos, GetTopBarHeight() ); + } + +#if defined( REPLAY_ENABLED ) + // We don't want to display this message the first time the spectator GUI is shown, since that is right + // when the class menu is shown. We use the player spawn cache here - which is a terrible name for something + // very useful - which will nuke m_nDisplaySaveReplay every time a new map is loaded, which is exactly what + // we want. + int &nDisplaySaveReplay = CPlayerSpawnCache::Instance().m_Data.m_nDisplaySaveReplay; + extern IReplayManager *g_pReplayManager; + CReplay *pCurLifeReplay = ( g_pReplayManager ) ? g_pReplayManager->GetReplayForCurrentLife() : NULL; + if ( g_pReplay->IsRecording() && + !engine->IsPlayingDemo() && + !::input->IsSteamControllerActive() && + nDisplaySaveReplay && + ( pCurLifeReplay && !pCurLifeReplay->m_bRequestedByUser && !pCurLifeReplay->m_bSaved ) ) + { + wchar_t wText[256]; + wchar wKeyBind[80]; + char szText[256 * sizeof(wchar_t)]; + + const char *pSaveReplayKey = engine->Key_LookupBinding( "save_replay" ); + if ( !pSaveReplayKey ) + { + pSaveReplayKey = "< not bound >"; + } + g_pVGuiLocalize->ConvertANSIToUnicode( pSaveReplayKey, wKeyBind, sizeof( wKeyBind ) ); + g_pVGuiLocalize->ConstructString_safe( wText, g_pVGuiLocalize->Find( "#Replay_SaveThisLifeMsg" ), 1, wKeyBind ); + g_pVGuiLocalize->ConvertUnicodeToANSI( wText, szText, sizeof( szText ) ); + + g_pClientMode->DisplayReplayMessage( szText, -1.0f, false, NULL, false ); + } + ++nDisplaySaveReplay; +#endif + + m_flNextTipChangeTime = 0; // force a new tip immediately + + InvalidateLayout(); + } + else + { + if ( pStatus ) + { + pStatus->SetParent( g_pClientMode->GetViewport() ); + } + + if ( pEngBuilds ) + { + pEngBuilds->SetPos( m_nEngBuilds_xpos, m_nEngBuilds_ypos ); + } + + if ( pSpyBuilds ) + { + pSpyBuilds->SetPos( m_nSpyBuilds_xpos, m_nSpyBuilds_ypos ); + } + } + + UpdateKeyLabels(); + + if ( bShow ) + { + m_flNextPlayerPanelUpdate = 0; + m_flNextItemPanelUpdate = 0; + UpdateItemPanel(); + RecalculatePlayerPanels(); + } + } + + BaseClass::ShowPanel( bShow ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::FireGameEvent( IGameEvent *event ) +{ + const char *pEventName = event->GetName(); + + if ( Q_strcmp( "spec_target_updated", pEventName ) == 0 ) + { + UpdateItemPanel(); + } + else if ( Q_strcmp( "player_death", pEventName ) == 0 && m_bCoaching ) + { + CBaseEntity *pVictim = ClientEntityList().GetEnt( engine->GetPlayerForUserID( event->GetInt("userid") ) ); + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( pLocalPlayer && ( pVictim == pLocalPlayer->m_hStudent ) ) + { + CEconNotification *pNotification = new CEconNotification(); + pNotification->SetText( "#TF_Coach_StudentHasDied" ); + pNotification->SetLifetime( 10.0f ); + pNotification->SetSoundFilename( "coach/coach_student_died.wav" ); + NotificationQueue_Add( pNotification ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::UpdateItemPanel( bool bForce ) +{ + bool bVisible = false; + + // Stat panel prevents the item panel from showing. + CTFStatPanel *pStatPanel = GET_HUDELEMENT( CTFStatPanel ); + if ( ( pStatPanel && pStatPanel->IsVisible() ) || ( TFGameRules() && TFGameRules()->State_Get() == GR_STATE_TEAM_WIN ) || (!cl_spec_carrieditems.GetBool() && !bForce) ) + { + bVisible = false; + } + else + { + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( pLocalPlayer ) + { + C_TFPlayer *pPlayer = ToTFPlayer( pLocalPlayer->GetObserverTarget() ); + if ( pPlayer && pPlayer != pLocalPlayer ) + { + if ( m_flNextItemPanelUpdate && m_flNextItemPanelUpdate > gpGlobals->curtime && m_hPrevItemPlayer == pPlayer ) + return; + + if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && ( pPlayer->GetTeamNumber() == TF_TEAM_PVE_INVADERS ) ) + return; + + if ( m_hPrevItemPlayer != pPlayer ) + { + m_iPrevItemShown = 0; + m_iFirstItemShown = 0; + m_bShownItems = false; + m_hPrevItemPlayer = pPlayer; + } + m_flNextItemPanelUpdate = gpGlobals->curtime + 8.0; + + // Don't reshow the items for a player unless we're being forced to + if ( !m_bShownItems || bForce ) + { + // If our killer is using a non-standard item, display its stats. + // Loop through all items and pick one at random, so that we show non-active weapons as well. + CEconItemView *pItemToShow = pPlayer->GetInspectItem( &m_iPrevItemShown ); + + // If we've looped, we're done. Hide the item. + if ( m_iFirstItemShown && m_iFirstItemShown == m_iPrevItemShown ) + { + m_iFirstItemShown = 0; + m_iPrevItemShown = 0; + pItemToShow = NULL; + m_bShownItems = true; + } + + if ( pItemToShow ) + { + if ( !m_iFirstItemShown ) + { + m_iFirstItemShown = m_iPrevItemShown; + } + + Label* pItemLabel = m_pItemPanel->FindControl<Label>( "ItemLabel" ); + + // Change the label text depending on if the original owner is holding the weapon + if ( pItemLabel ) + { + CSteamID steamIDOwner; + pPlayer->GetSteamID( &steamIDOwner ); + bool bOriginalOwner = steamIDOwner.GetAccountID() == pItemToShow->GetAccountID(); + pItemLabel->SetText( bOriginalOwner ? "#FreezePanel_Item" : "#FreezePanel_ItemOtherOwner" ); + } + + + bVisible = true; + m_pItemPanel->SetDialogVariable( "killername", g_TF_PR->GetPlayerName( pPlayer->entindex() ) ); + + // Set the item owner's name + CBasePlayer *pOriginalOwner = GetPlayerByAccountID( pItemToShow->GetAccountID() ); + if ( pOriginalOwner ) + { + m_pItemPanel->SetDialogVariable( "ownername", g_TF_PR->GetPlayerName( pOriginalOwner->entindex() ) ); + } + + m_pItemPanel->SetItem( pItemToShow ); + + // force update description to get the correct panel size + m_pItemPanel->UpdateDescription(); + m_pItemPanel->SetPos( ScreenWidth() - XRES( 10 ) - m_pItemPanel->GetWide(), ScreenHeight() - YRES( 12 ) - m_pItemPanel->GetTall() ); + } + } + } + } + } + + if ( m_pItemPanel->IsVisible() != bVisible ) + { + m_pItemPanel->SetVisible( bVisible ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::ForceItemPanelCycle( void ) +{ + m_flNextItemPanelUpdate = 0; + UpdateItemPanel( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +const char *CTFSpectatorGUI::GetResFile( void ) +{ + if ( m_bCoaching ) + { + return "Resource/UI/SpectatorCoach.res"; + } + else if ( InTournamentGUI() ) + { + return "Resource/UI/SpectatorTournament.res"; + } + else if ( ::input->IsSteamControllerActive() ) + { + return "Resource/UI/Spectator_SC.res"; + } + else + { + return "Resource/UI/Spectator.res"; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CTFSpectatorGUI::InTournamentGUI( void ) +{ + bool bOverride = false; + + if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) + { + bOverride = true; + } + + return ( TFGameRules()->IsInTournamentMode() && !TFGameRules()->IsCompetitiveMode() && ( cl_use_tournament_specgui.GetBool() || bOverride ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::RecalculatePlayerPanels( void ) +{ + if ( !InTournamentGUI() ) + { + if ( m_PlayerPanels.Count() > 0 ) + { + // Delete any player panels we have, we've turned off the tourney GUI. + for ( int i = m_PlayerPanels.Count()-1; i >= 0; i-- ) + { + m_PlayerPanels[i]->MarkForDeletion(); + } + m_PlayerPanels.Purge(); + } + + return; + } + + C_BasePlayer *pPlayer = C_BasePlayer::GetLocalPlayer(); + if ( !pPlayer || !g_TF_PR || !TFGameRules() ) + return; + + int iLocalTeam = pPlayer->GetTeamNumber(); + bool bMvM = TFGameRules()->IsMannVsMachineMode(); + + // Calculate the number of players that must be shown. Spectators see all players (except in MvM), team members only see their team. + int iPanel = 0; + + for ( int nClass = TF_FIRST_NORMAL_CLASS; nClass <= TF_LAST_NORMAL_CLASS; nClass++ ) + { + // we want to sort the images to match the class menu selections + int nCurrentClass = g_ClassDefinesRemap[nClass]; + + for ( int i = 0; i < MAX_PLAYERS; i++ ) + { + int iPlayer = i+1; + if ( !g_TF_PR->IsConnected( iPlayer ) ) + continue; + + bool bHideBots = false; + + #ifndef _DEBUG + if ( bMvM ) + { + bHideBots = true; + } + #endif + int iTeam = g_TF_PR->GetTeam( iPlayer ); + if ( iTeam != iLocalTeam && iLocalTeam != TEAM_SPECTATOR ) + continue; + if ( iTeam != TF_TEAM_RED && iTeam != TF_TEAM_BLUE ) + continue; + if ( g_TF_PR->IsFakePlayer( iPlayer ) && bHideBots ) + continue; + if ( bMvM && ( iTeam == TF_TEAM_PVE_INVADERS ) ) + continue; + if ( g_TF_PR->GetPlayerClass( iPlayer ) != nCurrentClass ) + continue; + + if ( m_PlayerPanels.Count() <= iPanel ) + { + CTFPlayerPanel *pPanel = new CTFPlayerPanel( this, VarArgs("playerpanel%d", i) ); + if ( m_pPlayerPanelKVs ) + { + pPanel->ApplySettings( m_pPlayerPanelKVs ); + } + m_PlayerPanels.AddToTail( pPanel ); + } + + m_PlayerPanels[iPanel]->SetPlayerIndex( iPlayer ); + iPanel++; + } + } + + for ( int i = iPanel; i < m_PlayerPanels.Count(); i++ ) + { + m_PlayerPanels[i]->SetPlayerIndex( 0 ); + } + + UpdatePlayerPanels(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::UpdatePlayerPanels( void ) +{ + if ( !g_TF_PR ) + return; + + uint nVisible = 0; + bool bNeedsPlayerLayout = false; + for ( int i = 0; i < m_PlayerPanels.Count(); i++ ) + { + if ( m_PlayerPanels[i]->Update() ) + { + bNeedsPlayerLayout = true; + } + + if ( m_PlayerPanels[i]->IsVisible() ) + { + nVisible++; + } + } + + if ( !bNeedsPlayerLayout ) + return; + + // Try and always put the local player's team on team1, if he's in a team + int iTeam1 = TF_TEAM_BLUE; + int iTeam2 = TF_TEAM_RED; + C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer(); + if ( !pLocalPlayer ) + return; + + int iLocalTeam = g_TF_PR->GetTeam( pLocalPlayer->entindex() ); + + if ( iLocalTeam == TF_TEAM_RED || iLocalTeam == TF_TEAM_BLUE ) + { + iTeam1 = iLocalTeam; + iTeam2 = ( iTeam1 == TF_TEAM_BLUE ) ? TF_TEAM_RED : TF_TEAM_BLUE; + } + + int iTeam1Count = 0; + int iTeam2Count = 0; + int iCenter = GetWide() * 0.5; + int iYPosOverride = 0; + + // We only want to draw the player panels for the defenders in MvM + if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) + { + iTeam1 = TF_TEAM_PVE_DEFENDERS; + iTeam2 = TF_TEAM_PVE_INVADERS; + + int iNumValidPanels = 0; + for ( int i = 0; i < m_PlayerPanels.Count(); i++ ) + { + if ( m_PlayerPanels[i]->GetPlayerIndex() <= 0 ) + { + continue; + } + + iNumValidPanels++; + } + + // we'll center the group of players in MvM + m_iTeam1PlayerBaseOffsetX = ( iNumValidPanels * m_iTeam1PlayerDeltaX ) * -0.5; + + if ( iLocalTeam > LAST_SHARED_TEAM ) + { + if ( m_pTopBar ) + { + iYPosOverride = m_pTopBar->GetTall(); + } + } + } + + for ( int i = 0; i < m_PlayerPanels.Count(); i++ ) + { + int iXPos = 0; + int iYPos = 0; + + if ( m_PlayerPanels[i]->GetPlayerIndex() <= 0 ) + { + m_PlayerPanels[i]->SetVisible( false ); + continue; + } + + int iTeam = g_TF_PR->GetTeam( m_PlayerPanels[i]->GetPlayerIndex() ); + if ( iTeam == iTeam1 ) + { + iXPos = m_iTeam1PlayerBaseOffsetX ? (iCenter + m_iTeam1PlayerBaseOffsetX) : m_iTeam1PlayerBaseX; + iXPos += (iTeam1Count * m_iTeam1PlayerDeltaX); + iYPos = iYPosOverride ? iYPosOverride : m_iTeam1PlayerBaseY + (iTeam1Count * m_iTeam1PlayerDeltaY); + m_PlayerPanels[i]->SetSpecIndex( 6 - iTeam1Count ); + m_PlayerPanels[i]->SetPos( iXPos, iYPos ); + m_PlayerPanels[i]->SetVisible( true ); + iTeam1Count++; + } + else if ( iTeam == iTeam2 ) + { + iXPos = m_iTeam2PlayerBaseOffsetX ? (iCenter + m_iTeam2PlayerBaseOffsetX) : m_iTeam2PlayerBaseX; + iXPos += (iTeam2Count * m_iTeam2PlayerDeltaX); + iYPos = iYPosOverride ? iYPosOverride : m_iTeam2PlayerBaseY + (iTeam2Count * m_iTeam2PlayerDeltaY); + m_PlayerPanels[i]->SetSpecIndex( 7 + iTeam2Count ); + m_PlayerPanels[i]->SetPos( iXPos, iYPos ); + m_PlayerPanels[i]->SetVisible( true ); + iTeam2Count++; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTFSpectatorGUI::SelectSpec( int iSlot ) +{ + if ( m_bCoaching ) + { + engine->ClientCmd_Unrestricted( CFmtStr1024( "coach_command %u", iSlot ) ); + return; + } + + if ( !InTournamentGUI() ) + return; + + for ( int i = 0; i < m_PlayerPanels.Count(); i++ ) + { + if ( m_PlayerPanels[i]->GetSpecIndex() == iSlot ) + { + engine->ClientCmd_Unrestricted( VarArgs( "spec_player %d\n", m_PlayerPanels[i]->GetPlayerIndex() ) ); + return; + } + } +} |