//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "cbase.h" #include "ienginevgui.h" #include "vgui/IVGui.h" #include "vgui/IInput.h" #include "vgui_controls/MenuItem.h" #include "vgui_controls/PropertySheet.h" #include "tf_party.h" #include "tf_lobbypanel.h" #include "tf_pvp_rank_panel.h" #include "tf_lobby_container_frame.h" #include "tf_controls.h" #include "tf_item_inventory.h" #include "tf_lobbypanel.h" #include "tf_hud_mainmenuoverride.h" #include "tf_matchmaking_dashboard.h" #include "tf_ping_panel.h" // memdbgon must be the last include file in a .cpp file!!! #include bool BIsPartyLeader() { CTFParty *pParty = GTFGCClientSystem()->GetParty(); return ( pParty == NULL || pParty->GetLeader() == steamapicontext->SteamUser()->GetSteamID() ); } bool BIsPartyInUIState() { if ( !GCClientSystem()->BConnectedtoGC() ) return false; CTFParty *pParty = GTFGCClientSystem()->GetParty(); return ( pParty == NULL || pParty->GetState() == CSOTFParty_State_UI ); } CSteamID SteamIDFromDecimalString( const char *pszUint64InDecimal ) { uint64 ulSteamID = 0; if ( sscanf( pszUint64InDecimal, "%llu", &ulSteamID ) ) { return CSteamID( ulSteamID ); } else { Assert( false ); return CSteamID(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CBaseLobbyContainerFrame::CBaseLobbyContainerFrame( const char* pszPanelName ) : vgui::PropertyDialog( NULL, pszPanelName ) , m_bNextButtonEnabled( false ) , m_pContextMenu( NULL ) { vgui::VPANEL gameuiPanel = enginevgui->GetPanel( PANEL_GAMEUIDLL ); SetParent( gameuiPanel ); SetMoveable( false ); SetSizeable( false ); vgui::HScheme scheme = vgui::scheme()->LoadSchemeFromFileEx( enginevgui->GetPanel( PANEL_CLIENTDLL ), "resource/ClientScheme.res", "ClientScheme"); SetScheme(scheme); SetProportional( true ); ListenForGameEvent( "gameui_hidden" ); ListenForGameEvent( "lobby_updated" ); ListenForGameEvent( "party_updated" ); ListenForGameEvent( "client_beginconnect" ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CBaseLobbyContainerFrame::~CBaseLobbyContainerFrame( void ) { } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseLobbyContainerFrame::ApplySchemeSettings( vgui::IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); // load control settings... LoadControlSettings( GetResFile() ); m_pStartPartyButton = dynamic_cast(FindChildByName( "StartPartyButton", true )); Assert( m_pStartPartyButton ); m_pBackButton = dynamic_cast( FindChildByName( "BackButton", true ) ); Assert( m_pBackButton ); m_pNextButton = dynamic_cast( FindChildByName( "NextButton", true ) ); Assert( m_pNextButton ); SetOKButtonVisible(false); SetCancelButtonVisible(false); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseLobbyContainerFrame::ShowPanel(bool bShow) { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); // Keep the MM dashboard on top of us bShow ? GetMMDashboardParentManager()->PushModalFullscreenPopup( this ) : GetMMDashboardParentManager()->PopModalFullscreenPopup( this ); m_pContents->SetControlVisible( "PartyActiveGroupBox", false ); // Make sure we're signed on if ( bShow ) { if ( GetPropertySheet()->GetActivePage() != m_pContents ) { GetPropertySheet()->SetActivePage( m_pContents ); } else { // VGUI doesn't tell the starting active page that it's active, so we post a pageshow to it vgui::ivgui()->PostMessage( m_pContents->GetVPanel(), new KeyValues("PageShow"), GetPropertySheet()->GetVPanel() ); } Activate(); // I don't know why, I don't want to know why, I shouldn't // have to wonder why, but for whatever reason this stupid // panel isn't laying out correctly unless we do this terribleness InvalidateLayout( true ); m_pContents->InvalidateLayout( true, true ); GTFGCClientSystem()->SetLocalPlayerSquadSurplus( false ); WriteControls(); m_pContents->UpdateControls(); Panel* pPvPRankPanel = FindChildByName( "RankPanel", true ); if ( pPvPRankPanel ) { pPvPRankPanel->OnCommand( "update_base_state" ); pPvPRankPanel->OnCommand( "begin_xp_lerp" ); } } else { if ( m_hPingPanel ) { m_hPingPanel->MarkForDeletion(); } } OnCommand( "leave_party" ); SetVisible( bShow ); m_pContents->SetVisible( bShow ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseLobbyContainerFrame::SetNextButtonEnabled( bool bValue ) { m_bNextButtonEnabled = bValue; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseLobbyContainerFrame::OnThink() { BaseClass::OnThink(); // Check if we don't want to be here, then get out! if ( GTFGCClientSystem()->GetMatchmakingUIState() == eMatchmakingUIState_Inactive ) { Msg( "Hiding LobbyContainerFrame" ); ShowPanel(false); return; } WriteControls(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseLobbyContainerFrame::FireGameEvent( IGameEvent *event ) { if ( GTFGCClientSystem()->GetSearchMode() != GetHandledMode() ) return; const char *pszEventname = event->GetName(); if ( !Q_stricmp( pszEventname, "lobby_updated" ) || !Q_stricmp( pszEventname, "party_updated" ) ) { WriteControls(); return; } // Bail when we connect to any server if ( !Q_stricmp( pszEventname, "client_beginconnect" ) ) { ShowPanel( false ); return; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseLobbyContainerFrame::StartSearch( void ) { // Is anyone banned from matchmaking? RTime32 rtimeExpire = 0; if ( m_pContents->IsAnyoneBanned( rtimeExpire ) ) { CRTime timeExpire( rtimeExpire ); timeExpire.SetToGMT( false ); char out_buf[k_RTimeRenderBufferSize]; wchar_t wszExpire[512]; wchar_t wszLocalized[512]; g_pVGuiLocalize->ConvertANSIToUnicode( timeExpire.Render( out_buf ), wszExpire, sizeof( wszExpire ) ); g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_Banned" ), 1, wszExpire ); ShowMessageBox( "#TF_Matchmaking_Title", wszLocalized, "#GameUI_OK" ); return; } if ( VerifyPartyAuthorization() ) { GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_SEARCHING ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseLobbyContainerFrame::OnCommand( const char *command ) { if ( FStrEq( command, "options" ) ) { OpenOptionsContextMenu(); } else if ( FStrEq( command, "back" ) ) { if ( !GCClientSystem()->BConnectedtoGC() || !BIsPartyLeader() ) { // TODO: Remove this when we have the dashboard everywhere. // Well...this entire panel should be gone. GTFGCClientSystem()->EndMatchmaking(); // And hide us ShowPanel( false ); return; } HandleBackPressed(); } else if ( FStrEq( command, "leave_party" ) ) { m_pStartPartyButton->SetVisible( true ); SetControlVisible( "PlayWithFriendsExplanation", true ); m_pContents->SetControlVisible( "PartyActiveGroupBox", false ); } else if ( FStrEq( command, "start_party" ) ) { m_pStartPartyButton->SetVisible( false ); SetControlVisible( "PlayWithFriendsExplanation", false ); m_pContents->SetControlVisible( "PartyActiveGroupBox", true ); Assert( steamapicontext ); IGameEvent *pEvent = gameeventmanager->CreateEvent( "mm_lobby_member_join" ); if ( pEvent ) { pEvent->SetString( "steamid", CFmtStr( "%llu", steamapicontext->SteamUser()->GetSteamID().ConvertToUint64() ) ); pEvent->SetInt( "solo", 1 ); gameeventmanager->FireEventClientSide( pEvent ); } } else { // What other commands are there? Assert( false ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseLobbyContainerFrame::PerformLayout( void ) { if ( GetVParent() ) { int w,h; vgui::ipanel()->GetSize( GetVParent(), w, h ); SetBounds(0,0,w,h); } BaseClass::PerformLayout(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- static void LeaveSearch( bool bConfirmed, void *pContext ) { if ( bConfirmed ) { switch ( GTFGCClientSystem()->GetSearchMode() ) { case TF_Matchmaking_MVM: GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_MVM_CHALLENGE ); break; case TF_Matchmaking_LADDER: GTFGCClientSystem()->RequestSelectWizardStep( TF_Matchmaking_WizardStep_LADDER ); break; default: AssertMsg1( false, "Unknown search mode %d", (int)GTFGCClientSystem()->GetSearchMode() ); break; } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseLobbyContainerFrame::OnKeyCodeTyped(vgui::KeyCode code) { if ( code == KEY_ESCAPE ) { if ( m_pContents->IsPartyActiveGroupBoxVisible() ) { ShowConfirmDialog( "#TF_MM_LeaveParty_Title", "#TF_MM_LeaveParty_Confirm", "#TF_Coach_Yes", "#TF_Coach_No", &CBaseLobbyContainerFrame::LeaveLobbyPanel ); return; } else if ( GTFGCClientSystem()->GetWizardStep() == TF_Matchmaking_WizardStep_SEARCHING ) { ShowConfirmDialog( "#TF_MM_LeaveQueue_Title", "#TF_MM_LeaveQueue_Confirm", "#TF_Coach_Yes", "#TF_Coach_No", &LeaveSearch ); return; } OnCommand( "back" ); return; } BaseClass::OnKeyCodeTyped( code ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseLobbyContainerFrame::OnKeyCodePressed(vgui::KeyCode code) { ButtonCode_t nButtonCode = GetBaseButtonCode( code ); if ( nButtonCode == KEY_XBUTTON_B || nButtonCode == STEAMCONTROLLER_B || nButtonCode == STEAMCONTROLLER_START ) { OnCommand( "back" ); return; } else if ( nButtonCode == KEY_XBUTTON_X ) { m_pContents->ToggleJoinLateCheckButton(); } BaseClass::OnKeyCodePressed( code ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseLobbyContainerFrame::WriteControls() { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); if ( !IsVisible() ) return; // Make sure we want to be in matchmaking. (If we don't, the frame should hide us pretty quickly.) // We might get an event or something right at the transition point occasionally when the UI should // not be visible if ( GTFGCClientSystem()->GetMatchmakingUIState() == eMatchmakingUIState_Inactive ) { return; } bool bNoGC = false; if ( !GCClientSystem()->BConnectedtoGC() || GTFGCClientSystem()->BHaveLiveMatch() || ( GTFGCClientSystem()->GetParty() && GTFGCClientSystem()->GetParty()->BOffline() ) ) { bNoGC = true; } SetControlVisible( "PlayWithFriendsExplanation", !bNoGC && ShouldShowPartyButton(), true ); SetControlVisible( "RankPanel", !bNoGC, true ); SetControlVisible( "NoGCGroupBox", bNoGC, true ); GetPropertySheet()->SetTabWidth( -1 ); // Check if we already have a party, then make sure and show it if ( !m_pStartPartyButton->IsVisible() && m_pContents->NumPlayersInParty() > 1 ) { m_pContents->SetControlVisible( "PartyActiveGroupBox", true ); } // Show/hide start party button as appropriate bool bShowPartyButton = ShouldShowPartyButton(); m_pStartPartyButton->SetVisible( bShowPartyButton && !bNoGC ); // Check for matchmaking bans and display time remaining if we're banned if ( steamapicontext == NULL || steamapicontext->SteamUser() == NULL ) return; CEconGameAccountClient *pGameAccountClient = NULL; if ( InventoryManager() && TFInventoryManager()->GetLocalTFInventory() && TFInventoryManager()->GetLocalTFInventory()->GetSOC() ) { pGameAccountClient = TFInventoryManager()->GetLocalTFInventory()->GetSOC()->GetSingleton(); } const IMatchGroupDescription *pMatchDesc = GetMatchGroupDescription( m_pContents->GetMatchGroup() ); EMMPenaltyPool ePenaltyPool = pMatchDesc ? pMatchDesc->m_params.m_ePenaltyPool : eMMPenaltyPool_Invalid; CRTime timeExpire = CRTime::RTime32TimeCur(); int nDuration = -1; bool bBanned = false; if ( pGameAccountClient && ePenaltyPool != eMMPenaltyPool_Invalid ) { switch ( ePenaltyPool ) { case eMMPenaltyPool_Casual: timeExpire = pGameAccountClient->Obj().matchmaking_casual_ban_expiration(); nDuration = pGameAccountClient->Obj().matchmaking_casual_ban_last_duration(); bBanned = timeExpire > CRTime::RTime32TimeCur(); break; case eMMPenaltyPool_Ranked: timeExpire = pGameAccountClient->Obj().matchmaking_ranked_ban_expiration(); nDuration = pGameAccountClient->Obj().matchmaking_ranked_ban_last_duration(); bBanned = timeExpire > CRTime::RTime32TimeCur(); break; default: Assert( false ); } SetControlVisible( "MatchmakingBanPanel", bBanned ); if ( bBanned ) { CExLabel *pBanLabel = FindControl( "MatchmakingBanDurationLabel", true ); if ( pBanLabel ) { timeExpire.SetToGMT( false ); CRTime rtNow = CRTime::RTime32TimeCur(); int nSecondsRemaining = timeExpire.GetRTime32() - rtNow.GetRTime32(); if ( nSecondsRemaining >= 0 ) { const int nDaysForLongBan = 2; int nDaysRemaining = nSecondsRemaining / 86400; int nHoursRemaining = nSecondsRemaining / 3600; int nMinutesRemaining = ( nSecondsRemaining % 3600 ) / 60; int nDurationDays = nDuration / 86400; int nDurationHours = nDuration / 3600; int nDurationMinutes = ( nDuration % 3600 ) / 60; // Want the remainder hours if we're going to display 'days' remaining if ( nDurationDays >= nDaysForLongBan ) { nDurationHours = ( nDuration % ( 86400 * nDurationDays ) ) / 3600; if ( nDaysRemaining >= nDaysForLongBan ) { nHoursRemaining = ( nSecondsRemaining % ( 86400 * nDaysRemaining ) ) / 3600; } } wchar_t wszDaysRemaining[16]; wchar_t wszHoursRemaining[16]; wchar_t wszMinutesRemaining[16]; wchar_t wszDurationDays[16]; wchar_t wszDurationHours[16]; wchar_t wszDurationMinutes[16]; _snwprintf( wszDaysRemaining, ARRAYSIZE( wszDaysRemaining ), L"%d", nDaysRemaining ); _snwprintf( wszHoursRemaining, ARRAYSIZE( wszHoursRemaining ), L"%d", nHoursRemaining ); _snwprintf( wszMinutesRemaining, ARRAYSIZE( wszMinutesRemaining ), L"%d", nMinutesRemaining ); _snwprintf( wszDurationDays, ARRAYSIZE( wszDurationDays ), L"%d", nDurationDays ); _snwprintf( wszDurationHours, ARRAYSIZE( wszDurationHours ), L"%d", nDurationHours ); _snwprintf( wszDurationMinutes, ARRAYSIZE( wszDurationMinutes ), L"%d", nDurationMinutes ); wchar_t wszLocalized[512]; // Short ban (less than "nDaysForLongBan" days and thus less than that remaining) if ( nDurationDays < nDaysForLongBan ) { // Less than an hour ban if ( nDurationHours < 1 ) { g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_Ban_Duration_Remaining_Short" ), 2, wszDurationMinutes, wszMinutesRemaining ); } else { g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_Ban_Duration_Remaining" ), 4, wszDurationHours, wszDurationMinutes, wszHoursRemaining, wszMinutesRemaining ); } } // Long ban (at least "nDaysForLongBan" days) but less than that remaining else if ( nDaysRemaining < nDaysForLongBan ) { g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "#TF_Matchmaking_Ban_Duration_Remaining_Long_Penalty_Short_Duration" ), 4, wszDurationDays, wszDurationHours, wszHoursRemaining, wszMinutesRemaining ); } // Long ban and at least that long remaining) else { g_pVGuiLocalize->ConstructString_safe( wszLocalized, g_pVGuiLocalize->Find( "TF_Matchmaking_Ban_Duration_Remaining_Long_Penalty" ), 4, wszDurationDays, wszDurationHours, wszDaysRemaining, wszHoursRemaining ); } pBanLabel->SetText( wszLocalized ); } else { pBanLabel->SetText( "#TF_Matchmaking_Ban_Duration_Remaining_Shortly" ); } } } } m_pNextButton->SetEnabled( m_bNextButtonEnabled && !bBanned && !bNoGC ); } //----------------------------------------------------------------------------- // Purpose: Handle cases not handled by our derived classes //----------------------------------------------------------------------------- void CBaseLobbyContainerFrame::HandleBackPressed() { // We dont know how or why we got here. Failsafe and just leave. if( GTFGCClientSystem()->GetSearchMode() != GetHandledMode() ) { Msg( "Lobby handles mode %d, but search mode is %d. Ending matchmaking", (int)( GetHandledMode() ), (int)( GTFGCClientSystem()->GetSearchMode() ) ); } GTFGCClientSystem()->EndMatchmaking(); ShowPanel( false ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CBaseLobbyContainerFrame::ShouldShowPartyButton() const { return !m_pContents->IsPartyActiveGroupBoxVisible() && GTFGCClientSystem()->GetWizardStep() != TF_Matchmaking_WizardStep_SEARCHING && GCClientSystem()->BConnectedtoGC() && BIsPartyLeader(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseLobbyContainerFrame::OpenOptionsContextMenu() { if ( m_pContextMenu ) delete m_pContextMenu; m_pContextMenu = new Menu( this, "ContextMenu" ); MenuBuilder contextMenuBuilder( m_pContextMenu, this ); const char *pszContextMenuBorder = "NotificationDefault"; const char *pszContextMenuFont = "HudFontMediumSecondary"; m_pContextMenu->SetBorder( scheme()->GetIScheme( GetScheme() )->GetBorder( pszContextMenuBorder ) ); m_pContextMenu->SetFont( scheme()->GetIScheme( GetScheme() )->GetFont( pszContextMenuFont ) ); contextMenuBuilder.AddMenuItem( "#TF_LobbyContainer_Ping", new KeyValues( "Context_Ping" ), "ping" ); contextMenuBuilder.AddMenuItem( "#TF_LobbyContainer_Help", "show_explanations", "help" ); m_pContextMenu->SetVisible(true); m_pContextMenu->AddActionSignalTarget(this); m_pContextMenu->MakeReadyForUse(); Panel* pOptionsButton = FindChildByName( "OptionsButton" ); if ( !pOptionsButton ) { // Position to the cursor's position int nX, nY; g_pVGuiInput->GetCursorPosition( nX, nY ); m_pContextMenu->SetPos( nX - 1, nY - 1 ); } else { int nOptionsX = pOptionsButton->GetXPos(); int nX = Min( nOptionsX, GetWide() - m_pContextMenu->GetWide() ); int nY = pOptionsButton->GetYPos() + pOptionsButton->GetTall(); m_pContextMenu->SetPos( nX - 1, nY - 1 ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseLobbyContainerFrame::OpenPingOptions() { // just create a new one. panel will destroy itself on close. m_hPingPanel = new CTFPingPanel( this, "PingPanel", m_pContents->GetMatchGroup() ); }