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/tf_coaching.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/tf_coaching.cpp')
| -rw-r--r-- | game/client/tf/tf_coaching.cpp | 878 |
1 files changed, 878 insertions, 0 deletions
diff --git a/game/client/tf/tf_coaching.cpp b/game/client/tf/tf_coaching.cpp new file mode 100644 index 0000000..42e0936 --- /dev/null +++ b/game/client/tf/tf_coaching.cpp @@ -0,0 +1,878 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= +#include "cbase.h" + +// for messaging with the GC +#include "econ_gcmessages.h" +#include "econ_item_inventory.h" +#include "econ_game_account_client.h" +#include "tf_gcmessages.h" +#include "gc_clientsystem.h" + +// ui related +#include "ienginevgui.h" +#include "confirm_dialog.h" +#include "tf_controls.h" +#include "econ_notifications.h" +#include "select_player_dialog.h" +#include "vgui_avatarimage.h" + +// for hud element +#include "iclientmode.h" + +// misc +#include "c_tf_freeaccount.h" +#include "c_tf_player.h" +#include "c_playerresource.h" +#include "tf_hud_statpanel.h" +#include "tf_gamerules.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include <tier0/memdbgon.h> + +//----------------------------------------------------------------------------- + +static bool g_bHadCoach = false; +static bool g_bCanLikeCoach = false; + +//----------------------------------------------------------------------------- + +// used by the waiting dialogs +class CCoachingWaitDialog : public CGenericWaitingDialog +{ +public: + CCoachingWaitDialog() : CGenericWaitingDialog( NULL ) + { + } + +protected: + virtual void OnTimeout() + { + ShowMessageBox( "#TF_Coach_Timeout_Title", "#TF_Coach_Timeout_Text", "#GameUI_OK" ); + } +}; + +//----------------------------------------------------------------------------- + +static bool BInCoachesList() +{ + if ( InventoryManager() && TFInventoryManager()->GetLocalTFInventory() && TFInventoryManager()->GetLocalTFInventory()->GetSOC() ) + { + CEconGameAccountClient *pGameAccountClient = TFInventoryManager()->GetLocalTFInventory()->GetSOC()->GetSingleton<CEconGameAccountClient>(); + if ( pGameAccountClient ) + return pGameAccountClient->Obj().in_coaches_list(); + } + return false; +} + +// send a message to the GC requesting to be added to the list of coaches +static void RequestAddToCoaches() +{ + GCSDK::CProtoBufMsg< CMsgTFCoaching_AddToCoaches > msg( k_EMsgGCCoaching_AddToCoaches ); + bool bSent = GCClientSystem()->BSendMessage( msg ); + if ( bSent ) + { + ShowWaitingDialog( new CCoachingWaitDialog(), "#TF_Coach_WaitingForServer", true, false, 20.0f ); + } +} + +// send a message to the GC requesting to be removed from the list of coaches +static void RequestRemoveFromCoaches() +{ + GCSDK::CProtoBufMsg< CMsgTFCoaching_RemoveFromCoaches > msg( k_EMsgGCCoaching_RemoveFromCoaches ); + GCClientSystem()->BSendMessage( msg ); + ShowWaitingDialog( new CCoachingWaitDialog(), "#TF_Coach_WaitingForServer", true, false, 20.0f ); +} + +// alternates between asking to be added/removed from the list of coaches +static void ToggleCoachingConfirm( bool bConfirmed, void *pContext ) +{ + if ( bConfirmed ) + { + if ( BInCoachesList() ) + { + RequestRemoveFromCoaches(); + } + else + { + RequestAddToCoaches(); + } + } +} + +static bool IsServerFull() +{ + int iNumPlayers = 0; + + for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ ) + { + if ( g_PR->IsConnected( iPlayerIndex ) == false ) + continue; + + player_info_t pi; + if ( !engine->GetPlayerInfo( iPlayerIndex, &pi ) ) + continue; + + ++iNumPlayers; + } + return iNumPlayers >= gpGlobals->maxClients; +} + +//----------------------------------------------------------------------------- + +/** + * Select player to ask to be a coach dialog. + */ +class CSelectPlayerForCoachDialog : public CSelectPlayerDialog +{ + DECLARE_CLASS_SIMPLE( CSelectPlayerForCoachDialog, CSelectPlayerDialog ); +public: + CSelectPlayerForCoachDialog(); + virtual ~CSelectPlayerForCoachDialog(); + virtual void OnSelectPlayer( const CSteamID &steamID ); + virtual void OnCommand( const char *command ); + virtual bool AllowOutOfGameFriends() { return false; } + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + +protected: + virtual const char *GetResFile() { return "resource/ui/SelectPlayerDialog_Coach.res"; } + bool CanAskPlayerToCoach( const CSteamID &steamID ); +}; + +static vgui::DHANDLE< CSelectPlayerForCoachDialog > g_pSelectPlayerForCoachDialog; + +CSelectPlayerForCoachDialog::CSelectPlayerForCoachDialog() + : CSelectPlayerDialog( NULL ) +{ + g_pSelectPlayerForCoachDialog = this; + m_bAllowSameTeam = true; + m_bAllowOutsideServer = true; +} + +CSelectPlayerForCoachDialog::~CSelectPlayerForCoachDialog() +{ + g_pSelectPlayerForCoachDialog = NULL; +} + +void CSelectPlayerForCoachDialog::OnSelectPlayer( const CSteamID &steamID ) +{ + if ( CanAskPlayerToCoach( steamID ) ) + { + g_bCanLikeCoach = false; + GCSDK::CProtoBufMsg< CMsgTFCoaching_FindCoach > msg( k_EMsgGCCoaching_FindCoach ); + msg.Body().set_account_id_friend_as_coach( steamID.GetAccountID() ); + GCClientSystem()->BSendMessage( msg ); + ShowWaitingDialog( new CCoachingWaitDialog(), "#TF_Coach_AskingFriend", true, false, 20.0f ); + } +} + +void CSelectPlayerForCoachDialog::OnCommand( const char *command ) +{ + if ( !Q_stricmp( command, "performmatchmaking" ) ) + { + if ( CanAskPlayerToCoach( CSteamID() ) ) + { + // close dialog + OnCommand( "cancel" ); + // send message + g_bCanLikeCoach = true; + GCSDK::CProtoBufMsg< CMsgTFCoaching_FindCoach > msg( k_EMsgGCCoaching_FindCoach ); + GCClientSystem()->BSendMessage( msg ); + ShowWaitingDialog( new CCoachingWaitDialog(), "#TF_Coach_CoachSearching", true, false, 20.0f ); + } + return; + } + BaseClass::OnCommand( command ); +} + +void CSelectPlayerForCoachDialog::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + CSelectPlayerDialog::ApplySchemeSettings( pScheme ); + SetDialogVariable( "title", g_pVGuiLocalize->Find( "TF_FindCoachDialog_Title" ) ); +} + +bool CSelectPlayerForCoachDialog::CanAskPlayerToCoach( const CSteamID &steamID ) +{ + bool bMatchesFriend = false; + const int iMaxPlayers = gpGlobals->maxClients; + int iNumPlayers = 0; + for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ ) + { + if ( g_PR->IsConnected( iPlayerIndex ) == false ) + continue; + + player_info_t pi; + if ( !engine->GetPlayerInfo( iPlayerIndex, &pi ) ) + continue; + + // friend on this server? + if ( pi.friendsID != 0 && pi.friendsID == steamID.GetAccountID() ) + { + bMatchesFriend = true; + } + + ++iNumPlayers; + } + if ( iMaxPlayers <= iNumPlayers ) + { + ShowMessageBox( "#TF_Coach_ServerFull_Title", "#TF_Coach_ServerFull_Text", "#GameUI_OK" ); + return false; + } + if ( bMatchesFriend ) + { + return true; + } + return steamID.GetAccountID() == 0; +} + +static void ShowFindCoachDialog() +{ + CSelectPlayerForCoachDialog *pDialog = vgui::SETUP_PANEL( new CSelectPlayerForCoachDialog() ); + pDialog->InvalidateLayout( false, true ); + + pDialog->Reset(); + pDialog->SetVisible( true ); + pDialog->MakePopup(); + pDialog->MoveToFront(); + pDialog->SetKeyBoardInputEnabled(true); + pDialog->SetMouseInputEnabled(true); + TFModalStack()->PushModal( pDialog ); +} + +//----------------------------------------------------------------------------- + +// sent from main menu +CON_COMMAND( cl_coach_toggle, "Toggle coach status" ) +{ + if ( IsFreeTrialAccount() ) + { + ShowMessageBox( "#TF_Coach_FreeAccount_Title", "#TF_Coach_FreeAccount_Text", "#GameUI_OK" ); + return; + } + if ( BInCoachesList() ) + { + ShowConfirmDialog( "#TF_Coach_RemoveCoach_Title", "#TF_Coach_RemoveCoach_Text", + "#TF_Coach_Yes", "#TF_Coach_No", + &ToggleCoachingConfirm ); + } + else + { + ShowConfirmDialog( "#TF_Coach_AddCoach_Title", "#TF_Coach_AddCoach_Text", + "#TF_Coach_Yes", "#TF_Coach_No", + &ToggleCoachingConfirm ); + } +} + +//----------------------------------------------------------------------------- + +class CGCCoaching_AddToCoachesResponse : public GCSDK::CGCClientJob +{ +public: + CGCCoaching_AddToCoachesResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket ); + + CloseWaitingDialog(); + + if ( msg.Body().m_eResponse == k_EGCMsgResponseOK ) + { + ShowMessageBox( "#TF_Coach_AddedCoach_Title", "#TF_Coach_AddedCoach_Text", "#GameUI_OK" ); + } + else if ( msg.Body().m_eResponse == k_EGCMsgResponseDenied ) + { + ShowMessageBox( "#TF_Coach_Denied_Title", "#TF_Coach_Denied_Text", "#GameUI_OK" ); + } + return true; + } +}; +GC_REG_JOB( GCSDK::CGCClient, CGCCoaching_AddToCoachesResponse, "CGCCoaching_AddToCoachesResponse", k_EMsgGCCoaching_AddToCoachesResponse, GCSDK::k_EServerTypeGCClient ); + +//----------------------------------------------------------------------------- + +class CGCCoaching_RemoveFromCoachesResponse : public GCSDK::CGCClientJob +{ +public: + CGCCoaching_RemoveFromCoachesResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + GCSDK::CGCMsg<MsgGCStandardResponse_t> msg( pNetPacket ); + + CloseWaitingDialog(); + + if ( msg.Body().m_eResponse == k_EGCMsgResponseOK ) + { + ShowMessageBox( "#TF_Coach_RemovedCoach_Title", "#TF_Coach_RemovedCoach_Text", "#GameUI_OK" ); + } + return true; + } +}; +GC_REG_JOB( GCSDK::CGCClient, CGCCoaching_RemoveFromCoachesResponse, "CGCCoaching_RemoveFromCoachesResponse", k_EMsgGCCoaching_RemoveFromCoachesResponse, GCSDK::k_EServerTypeGCClient ); + +//----------------------------------------------------------------------------- + +class CGCCoaching_RemovedAsCoach : public GCSDK::CGCClientJob +{ +public: + CGCCoaching_RemovedAsCoach( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + ShowMessageBox( "#TF_Coach_SessionEnded_Title", "#TF_Coach_SessionEnded_Text", "#GameUI_OK" ); + return true; + } +}; +GC_REG_JOB( GCSDK::CGCClient, CGCCoaching_RemovedAsCoach, "CGCCoaching_RemovedAsCoach", k_EMsgGCCoaching_RemoveCurrentCoach, GCSDK::k_EServerTypeGCClient ); + +static void FindCoach( bool bConfirmed, void *pContext ) +{ + if ( bConfirmed ) + { + ShowFindCoachDialog(); + } +} + +static void PromptFindCoach() +{ + C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( pLocalTFPlayer == NULL ) + { + ShowMessageBox( "#TF_Coach_NotInGame_Title", "#TF_Coach_NotInGame_Text", "#GameUI_OK" ); + } + else if ( pLocalTFPlayer->m_hCoach != NULL ) + { + ShowMessageBox( "#TF_Coach_AlreadyBeingCoached_Title", "#TF_Coach_AlreadyBeingCoached_Text", "#GameUI_OK" ); + } + else if ( pLocalTFPlayer->m_hStudent != NULL ) + { + ShowMessageBox( "#TF_Coach_AlreadyCoaching_Title", "#TF_Coach_AlreadyCoaching_Text", "#GameUI_OK" ); + } + else if ( TFGameRules() && TFGameRules()->IsInTraining() ) + { + ShowMessageBox( "#TF_Coach_Training_Title", "#TF_Coach_Training_Text", "#GameUI_OK" ); + } + else if ( IsServerFull() ) + { + ShowMessageBox( "#TF_Coach_ServerFull_Title", "#TF_Coach_ServerFull_Text", "#GameUI_OK" ); + } + else if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) + { + ShowMessageBox( "#TF_Coach_MannVsMachine_Title", "#TF_Coach_MannVsMachine_Text", "#GameUI_OK" ); + } + else + { + ShowConfirmDialog( "#TF_Coach_AskStudent_Title", "#TF_Coach_AskStudent_Text", + "#TF_Coach_Yes", "#TF_Coach_No", + &FindCoach ); + } +} + +CON_COMMAND( cl_coach_find_coach, "Request a coach for the current game" ) +{ + PromptFindCoach(); +} + +class CGCCoaching_FindCoachResponse : public GCSDK::CGCClientJob +{ +public: + CGCCoaching_FindCoachResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + GCSDK::CProtoBufMsg< CMsgTFCoaching_FindCoachResponse > msg( pNetPacket ); + + CloseWaitingDialog(); + + if ( msg.Body().found_coach() ) + { + const char* pText = "#TF_Coach_FoundCoach_Text"; + if ( msg.Body().num_likes() > 2 ) + { + pText = "#TF_Coach_FoundCoachLike_Text"; + } + CTFMessageBoxDialog *pDialog = ShowMessageBox( "#TF_Coach_FoundCoach_Title", pText, "#GameUI_OK" ); + if ( pDialog ) + { + wchar_t szPlayerName[MAX_PLAYER_NAME_LENGTH]; + g_pVGuiLocalize->ConvertANSIToUnicode( msg.Body().coach_name().c_str(), szPlayerName, sizeof(szPlayerName) ); + pDialog->AddStringToken( "coachname", szPlayerName ); + + wchar_t szNumLikes[32]; + V_snwprintf( szNumLikes, ARRAYSIZE(szNumLikes), L"%d", msg.Body().num_likes() ); + pDialog->AddStringToken( "numlikes", szNumLikes ); + } + } + else + { + // retry? + ShowConfirmDialog( "#TF_Coach_StudentRetry_Title", "#TF_Coach_StudentRetry_Text", + "#TF_Coach_Yes", "#TF_Coach_No", + &FindCoach ); + } + return true; + } +}; +GC_REG_JOB( GCSDK::CGCClient, CGCCoaching_FindCoachResponse, "CGCCoaching_FindCoachResponse", k_EMsgGCCoaching_FindCoachResponse, GCSDK::k_EServerTypeGCClient ); + +//----------------------------------------------------------------------------- + +class CTFAskCoachNotification : public CEconNotification +{ +public: + CTFAskCoachNotification( bool bStudentIsFriend ) + : CEconNotification() + , m_bStudentIsFriend( bStudentIsFriend ) + { + SetLifetime( 20.0f ); + SetText( bStudentIsFriend ? "#TF_Coach_AskCoachForFriend_Text" : "#TF_Coach_AskCoach_Text" ); + } + + virtual EType NotificationType() { return eType_AcceptDecline; } + + // XXX(JohnS): Dead code? This notification type was accept/decline, so how was it being triggered? + virtual void Trigger() + { + // prompt coach + ShowConfirmDialog( "#TF_Coach_AskCoach_Title", + m_bStudentIsFriend ? "#TF_Coach_AskCoachForFriend_Text" : "#TF_Coach_AskCoach_Text", + "#TF_Coach_Yes", "#TF_Coach_No", + &AskCoachCallback ); + } + + virtual void Accept() + { + AskCoachCallback( true, this ); + } + virtual void Decline() + { + AskCoachCallback( false, this ); + } + static void AskCoachCallback( bool bConfirmed, void *pContext ) + { + CTFAskCoachNotification *pNotification = (CTFAskCoachNotification*)pContext; + GCSDK::CProtoBufMsg< CMsgTFCoaching_AskCoachResponse > msg( k_EMsgGCCoaching_AskCoachResponse ); + msg.Body().set_accept_coaching_assignment( bConfirmed ); + GCClientSystem()->BSendMessage( msg ); + if ( bConfirmed ) + { + ShowWaitingDialog( new CCoachingWaitDialog(), "#TF_Coach_JoiningStudent", true, false, 30.0f ); + } + pNotification->MarkForDeletion(); + } + +protected: + bool m_bStudentIsFriend; +}; + +//----------------------------------------------------------------------------- + +class CGCCoaching_AskCoach : public GCSDK::CGCClientJob +{ +public: + CGCCoaching_AskCoach( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + GCSDK::CProtoBufMsg< CMsgTFCoaching_AskCoach > msg( pNetPacket ); + if ( steamapicontext && steamapicontext->SteamUtils() ) + { + bool bStudentIsFriend = msg.Body().has_student_is_friend() && msg.Body().student_is_friend(); + CTFAskCoachNotification *pNotification = new CTFAskCoachNotification( bStudentIsFriend ); + if ( bStudentIsFriend ) + { + wchar_t wszPlayerName[ MAX_PLAYER_NAME_LENGTH ]; + g_pVGuiLocalize->ConvertANSIToUnicode( InventoryManager()->PersonaName_Get( msg.Body().account_id_student() ), wszPlayerName, sizeof( wszPlayerName ) ); + pNotification->AddStringToken( "friend", wszPlayerName ); + } + CSteamID steamID( msg.Body().account_id_student(), 1, GetUniverse(), k_EAccountTypeIndividual ); + pNotification->SetSteamID( steamID ); + NotificationQueue_Add( pNotification ); + } + return true; + } +}; +GC_REG_JOB( GCSDK::CGCClient, CGCCoaching_AskCoach, "CGCCoaching_AskCoach", k_EMsgGCCoaching_AskCoach, GCSDK::k_EServerTypeGCClient ); + +//----------------------------------------------------------------------------- + +class CGCCoaching_CoachJoinGame : public GCSDK::CGCClientJob +{ +public: + CGCCoaching_CoachJoinGame( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + GCSDK::CProtoBufMsg< CMsgTFCoaching_CoachJoinGame > msg( pNetPacket ); + + CloseWaitingDialog(); + + if ( msg.Body().join_game() ) + { + if ( msg.Body().has_server_address() && msg.Body().has_server_port() ) + { + // join the game + uint32 iAddress = msg.Body().server_address(); + uint16 iPort = msg.Body().server_port(); + char command[256]; + Q_snprintf( command, sizeof(command), "connect %d.%d.%d.%d:%d coaching\n", (iAddress >> 24) & 0xff, (iAddress >> 16) & 0xff, (iAddress >> 8) & 0xff, iAddress & 0xff, iPort); + engine->ClientCmd_Unrestricted( command ); + } + } + else + { + ShowMessageBox( "#TF_Coach_JoinFail_Title", "#TF_Coach_JoinFail_Text", "#GameUI_OK" ); + } + return true; + } +}; +GC_REG_JOB( GCSDK::CGCClient, CGCCoaching_CoachJoinGame, "CGCCoaching_CoachJoinGame", k_EMsgGCCoaching_CoachJoinGame, GCSDK::k_EServerTypeGCClient ); + +class CGCCoaching_AlreadyRatedCoach : public GCSDK::CGCClientJob +{ +public: + CGCCoaching_AlreadyRatedCoach( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} + + virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) + { + ShowMessageBox( "#TF_Coach_AlreadyRatedCoach_Title", "#TF_Coach_AlreadyRatedCoach_Text", "#GameUI_OK" ); + return true; + } +}; +GC_REG_JOB( GCSDK::CGCClient, CGCCoaching_AlreadyRatedCoach, "CGCCoaching_AlreadyRatedCoach", k_EMsgGCCoaching_AlreadyRatedCoach, GCSDK::k_EServerTypeGCClient ); + + +//----------------------------------------------------------------------------- + +static void LikeCoachCallback( bool bConfirmed, void *pContext ) +{ + GCSDK::CProtoBufMsg< CMsgTFCoaching_LikeCurrentCoach > msg( k_EMsgGCCoaching_LikeCurrentCoach ); + msg.Body().set_like_coach( bConfirmed ); + GCClientSystem()->BSendMessage( msg ); + g_bCanLikeCoach = false; +} + +static void PromptIfLikeCoach() +{ + if ( g_bCanLikeCoach ) + { + ShowConfirmDialog( "#TF_Coach_LikeCoach_Title", "#TF_Coach_LikeCoach_Text", + "#TF_Coach_Yes", "#TF_Coach_No", + &LikeCoachCallback ); + } +} + +void CL_Coaching_LevelShutdown() +{ + if ( g_pSelectPlayerForCoachDialog ) + { + g_pSelectPlayerForCoachDialog->OnCommand( "cancel" ); + } + bool bHadCoach = g_bHadCoach; + g_bHadCoach = false; + if ( bHadCoach ) + { + PromptIfLikeCoach(); + } +} + +//----------------------------------------------------------------------------- + +ConVar tf_coach_min_time_played( "tf_coach_min_time_played", "7200", FCVAR_CLIENTDLL | FCVAR_HIDDEN ); +ConVar tf_coach_request_nevershowagain( "tf_coach_request_nevershowagain", "0", FCVAR_ARCHIVE | FCVAR_CLIENTDLL | FCVAR_HIDDEN ); + +// @return true if the current player has played less than a certain threshold of hours and +// so is deemed eligible for coaching +static bool Coaching_ShouldRequestCoach() +{ + // cannot request a coach while in training + if ( TFGameRules() && TFGameRules()->IsInTraining() ) + { + return false; + } + // cannot request a coach if server is full + if ( IsServerFull() ) + { + return false; + } + + // Grab generic stats and add time played to total time played + int totalTimePlayed = 0; + for ( int iClass = TF_FIRST_NORMAL_CLASS; iClass < TF_LAST_NORMAL_CLASS; iClass++ ) + { + ClassStats_t &classStats = CTFStatPanel::GetClassStats( iClass ); + totalTimePlayed += classStats.accumulated.m_iStat[TFSTAT_PLAYTIME] + classStats.accumulatedMVM.m_iStat[TFSTAT_PLAYTIME]; + } + + // Better to not risk losing slots needed for bot opponents to coaches, as this could + // inadvertently reduce difficulty and/or induce undefined behaviors/conditions + if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) + { + return false; + } + + // check how many hours this player has played + const int minTimePlayedForCoachingEligibility = tf_coach_min_time_played.GetInt(); + return ( totalTimePlayed < minTimePlayedForCoachingEligibility ); +} + +// If the player is eligible for coaching, then prompt them +void Coaching_CheckIfEligibleForCoaching() +{ + C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( pLocalPlayer == NULL ) + { + return; + } + + // first time only...better way to do this? + if ( pLocalPlayer->GetTeamNumber() != TEAM_UNASSIGNED ) + { + return; + } + + if ( !tf_coach_request_nevershowagain.GetBool() && Coaching_ShouldRequestCoach() ) + { + ShowConfirmOptOutDialog( "#TF_Coach_AskStudent_Title", "#TF_Coach_AskStudent_Text", + "#TF_Coach_Yes", "#TF_Coach_No", + "#TF_Coach_AskStudent_DoNotShowAgain", "tf_coach_request_nevershowagain", + &FindCoach ); + } +} + +//----------------------------------------------------------------------------- + +class CCoachedByPanel : public CHudElement, public vgui::EditablePanel +{ + DECLARE_CLASS_SIMPLE( CCoachedByPanel, vgui::EditablePanel ); +public: + CCoachedByPanel( const char *pElementName ) + : CHudElement( pElementName ) + , BaseClass( NULL, "CoachedByPanel" ) + , m_bCanLikeCoach( false ) + { + vgui::Panel *pParent = g_pClientMode->GetViewport(); + SetParent( pParent ); + + SetHiddenBits( HIDEHUD_MISCSTATUS ); + + ListenForGameEvent( "localplayer_changeteam" ); + ListenForGameEvent( "player_changename" ); + } + + virtual ~CCoachedByPanel() + { + + } + + virtual bool ShouldDraw( void ) + { + if ( !CHudElement::ShouldDraw() ) + { + return false; + } + + C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( pLocalTFPlayer == NULL || pLocalTFPlayer->m_hCoach == NULL || pLocalTFPlayer->GetObserverMode() > OBS_MODE_NONE ) + { + return false; + } + + if ( !IsVisible() ) + { + g_bHadCoach = true; + m_bCanLikeCoach = g_bCanLikeCoach; + UpdateUI(); + InvalidateLayout(); + } + if ( m_bCanLikeCoach != g_bCanLikeCoach ) + { + m_bCanLikeCoach = g_bCanLikeCoach; + UpdateUI(); + } + + return true; + } + + virtual void PerformLayout( void ) + { + int iXIndent = XRES(5); + int iXPostdent = XRES(10); + int iWidth = iXIndent + iXPostdent; + + int iTextW, iTextH; + + C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer(); + + if ( m_pCoachNameLabel && pLocalTFPlayer) + { + m_pCoachNameLabel->GetContentSize( iTextW, iTextH ); + iWidth += MAX( iTextW, m_minCoachNameLabelWidth ); + m_pCoachNameLabel->SetWide( iTextW ); + + if ( m_pAvatar ) + { + iWidth += m_pAvatar->GetWide(); + } + + SetSize( iWidth, GetTall() ); + + if ( m_pBGPanel_Blue ) + { + m_pBGPanel_Blue->SetSize( iWidth, GetTall() ); + } + + if ( m_pBGPanel_Red ) + { + m_pBGPanel_Red->SetSize( iWidth, GetTall() ); + } + + if ( m_pBGPanel_Blue && m_pBGPanel_Red ) + { + bool bRed = ( pLocalTFPlayer->GetTeamNumber() == TF_TEAM_RED ); + m_pBGPanel_Blue->SetVisible( !bRed ); + m_pBGPanel_Red->SetVisible( bRed ); + } + } + } + + virtual void ApplySchemeSettings( vgui::IScheme *scheme ) + { + LoadControlSettings( "resource/UI/CoachedByPanel.res" ); + + BaseClass::ApplySchemeSettings( scheme ); + + m_pCoachNameLabel = dynamic_cast< vgui::Label* >( FindChildByName("CoachNameLabel") ); + + m_minCoachNameLabelWidth = 0; + if ( m_pCoachNameLabel ) + { + m_minCoachNameLabelWidth = m_pCoachNameLabel->GetWide(); + } + m_pBGPanel_Blue = FindChildByName("Background_Blue"); + m_pBGPanel_Red = FindChildByName("Background_Red"); + + if ( m_pBGPanel_Blue ) + { + m_pBGPanel_Blue->SetVisible( true ); + } + + m_pAvatar = dynamic_cast<CAvatarImagePanel *>( FindChildByName("AvatarImage") ); + + UpdateUI(); + } + + virtual void FireGameEvent( IGameEvent *event ) + { + C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( pLocalTFPlayer == NULL || pLocalTFPlayer->m_hCoach == NULL ) + { + return; + } + const char *name = event->GetName(); + if ( FStrEq( name, "localplayer_changeteam" ) || + ( FStrEq( name, "player_changename" ) && event->GetInt( "userid" ) == pLocalTFPlayer->m_hCoach->GetUserID() ) ) + { + UpdateUI(); + InvalidateLayout(); + } + } + + int HudElementKeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding ) + { + if ( !IsVisible() ) + return 1; // key not handled + + if ( !down ) + return 1; // key not handled + + C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( pLocalTFPlayer != NULL && pLocalTFPlayer->m_hCoach != NULL ) + { + switch ( keynum ) + { + case KEY_F7: + { + PromptIfLikeCoach(); + } + break; + case KEY_F8: + { + GCSDK::CProtoBufMsg< CMsgTFCoaching_RemoveCurrentCoach > msg( k_EMsgGCCoaching_RemoveCurrentCoach ); + GCClientSystem()->BSendMessage( msg ); + return 0; + } + break; + } + } + + return 1; // key not handled + } + + void UpdateUI() + { + C_TFPlayer *pLocalTFPlayer = C_TFPlayer::GetLocalTFPlayer(); + if ( pLocalTFPlayer == NULL || m_pCoachNameLabel == NULL || pLocalTFPlayer->m_hCoach == NULL ) + { + return; + } + + C_TFPlayer *pCoachPlayer = pLocalTFPlayer->m_hCoach; + const char* pCoachName = pCoachPlayer->GetPlayerName(); + + wchar_t wszPlayerName[ MAX_PLAYER_NAME_LENGTH ]; + g_pVGuiLocalize->ConvertANSIToUnicode(pCoachName, wszPlayerName, sizeof( wszPlayerName ) ); + wchar_t wszText[ 256 ] = L""; + g_pVGuiLocalize->ConstructString_safe( wszText, g_pVGuiLocalize->Find( "#TF_Coach_Coach_Prefix" ), 1, wszPlayerName ); + m_pCoachNameLabel->SetText( wszText ); + + if ( m_pAvatar ) + { + m_pAvatar->SetShouldDrawFriendIcon( false ); + + if ( steamapicontext && steamapicontext->SteamUser() ) + { + CSteamID coachSteamID; + if ( pCoachPlayer->GetSteamID( &coachSteamID ) ) + { + m_pAvatar->SetPlayer( coachSteamID, k_EAvatarSize64x64 ); + } + else + { + m_pAvatar->ClearAvatar(); + } + } + } + + SetChildPanelVisible( this, "LikeCoachLabel", m_bCanLikeCoach ); + } + +protected: + + CAvatarImagePanel *m_pAvatar; + vgui::Label *m_pCoachNameLabel; + vgui::Panel *m_pBGPanel_Blue; + vgui::Panel *m_pBGPanel_Red; + int m_minCoachNameLabelWidth; + bool m_bCanLikeCoach; +}; + +DECLARE_HUDELEMENT( CCoachedByPanel ); + +//----------------------------------------------------------------------------- + +// @return true if the coaching panel handled the input +// false otherwise +bool CoachingHandlesKeyInput( int down, ButtonCode_t keynum, const char *pszCurrentBinding ) +{ + CCoachedByPanel *pCoachingPanel = ( CCoachedByPanel * )GET_HUDELEMENT( CCoachedByPanel ); + if ( pCoachingPanel ) + { + return pCoachingPanel->HudElementKeyInput( down, keynum, pszCurrentBinding ) == 0; + } + return false; +} |