//========= 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 #include #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( 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