//========= 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 "tf_gcmessages.h" #include "tf_duel_summary.h" #include "gc_clientsystem.h" // other #include "c_playerresource.h" #include "c_tf_player.h" #include "tf_item_wearable.h" #include "econ_notifications.h" #include "tf_hud_chat.h" #include "c_tf_gamestats.h" #include "tf_gamerules.h" #include "tf_item_tools.h" #include "c_tf_freeaccount.h" #include "tf_item_powerup_bottle.h" #include "tf_weapon_grapplinghook.h" // for UI #include "clientmode_tf.h" #include "confirm_dialog.h" #include "select_player_dialog.h" #include "econ_notifications.h" #include "vgui/ISurface.h" #include "vgui/character_info_panel.h" #include "tf_hud_mainmenuoverride.h" #include "econ_ui.h" #include "backpack_panel.h" #include "store/v1/tf_store_page.h" #include "econ_item_description.h" #include "weapon_selection.h" // memdbgon must be the last include file in a .cpp file!!! #include //----------------------------------------------------------------------------- // Wrapped Gift Declarations void UseGift( CEconItemView* pItem, CSteamID targetID ); extern void PerformToolAction_UnwrapGift( vgui::Panel* pParent, CEconItemView *pGiftItem ); extern void ShowWaitingDialog( CGenericWaitingDialog *pWaitingDialog, const char* pUpdateText, bool bAnimate, bool bShowCancel, float flMaxDuration ); class CDeliverGiftSelectDialog; CDeliverGiftSelectDialog *OpenDeliverGiftDialog( vgui::Panel *pParent, CEconItemView *pItem ); //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- // Duel Declarations bool DuelMiniGame_IsDuelingLocalPlayer( C_TFPlayer *pPlayer ); bool DuelMiniGame_IsDueling(); //----------------------------------------------------------------------------- class CWaitForPackageDialog : public CGenericWaitingDialog { public: CWaitForPackageDialog( vgui::Panel *pParent ) : CGenericWaitingDialog( pParent ) { } protected: virtual void OnTimeout() { // Play an exciting sound! vgui::surface()->PlaySound( "misc/achievement_earned.wav" ); // Show them their loot! InventoryManager()->ShowItemsPickedUp( true ); } }; bool IgnoreRequestFromUser( const CSteamID &steamID ) { // ignore blocked players if ( steamapicontext && steamapicontext->SteamFriends() ) { EFriendRelationship eRelationship = steamapicontext->SteamFriends()->GetFriendRelationship( steamID ); switch ( eRelationship ) { case k_EFriendRelationshipBlocked: { return true; } } } return false; } static void ShowSelectDuelTargetDialog( uint64 iItemID ); enum EServerPlayersGCSend { kServerPlayers_DontSend, kServerPlayers_Send, }; struct CUseItemConfirmContext { public: CUseItemConfirmContext( CEconItemView *pEconItemView, EServerPlayersGCSend eSendServerPlayers, const char* pszConfirmUseSound = NULL ) : m_pEconItemView( pEconItemView ) , m_bSendServerPlayers( eSendServerPlayers == kServerPlayers_Send ) , m_pszConfirmUseSound( pszConfirmUseSound ) { Assert( eSendServerPlayers == kServerPlayers_DontSend || eSendServerPlayers == kServerPlayers_Send ); } void OnConfirmUse() { if ( m_pszConfirmUseSound && *m_pszConfirmUseSound ) { vgui::surface()->PlaySound( m_pszConfirmUseSound ); } } const char* m_pszConfirmUseSound; CEconItemView *m_pEconItemView; bool m_bSendServerPlayers; }; static void UseItemConfirm( bool bConfirmed, void *pContext ) { static CSchemaAttributeDefHandle pAttrDef_UnlimitedUse( "unlimited quantity" ); CUseItemConfirmContext *pConfirmContext = (CUseItemConfirmContext *)pContext; CEconItemView *pEconItemView = pConfirmContext->m_pEconItemView; if ( bConfirmed ) { pConfirmContext->OnConfirmUse(); if ( pEconItemView && !pEconItemView->FindAttribute( pAttrDef_UnlimitedUse ) ) { GCSDK::CProtoBufMsg msg( k_EMsgGCUseItemRequest ); msg.Body().set_item_id( pConfirmContext->m_pEconItemView->GetItemID() ); if ( pConfirmContext->m_bSendServerPlayers ) { for ( int i = 1; i <= gpGlobals->maxClients; i++ ) { CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); if ( pPlayer == NULL ) continue; CSteamID steamIDPlayer; if ( !pPlayer->GetSteamID( &steamIDPlayer ) ) continue; msg.Body().add_gift__potential_targets( steamIDPlayer.GetAccountID() ); } } GCClientSystem()->BSendMessage( msg ); } EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_CONSUMABLE, pEconItemView, NULL ); // CBaseHudChat *pHUDChat = (CBaseHudChat *)GET_HUDELEMENT( CHudChat ); // if ( pHUDChat ) // { // char szAnsi[1024]; // Q_snprintf( szAnsi, 1024, "Using item: %ull", pItem->GetItemID() ); // pHUDChat->Printf( CHAT_FILTER_NONE, "%s", szAnsi ); // } } delete pConfirmContext; } static void OpenPass( bool bConfirmed, void *pContext ) { if ( bConfirmed ) { vgui::surface()->PlaySound( "ui/item_gift_wrap_unwrap.wav" ); ShowWaitingDialog( new CWaitForPackageDialog( NULL ), "#ToolRedeemingPass", true, false, 5.0f ); } UseItemConfirm( bConfirmed, pContext ); } static void PrintTextToChat( const char *pText, KeyValues *pKeyValues ) { GetClientModeTFNormal()->PrintTextToChat( pText, pKeyValues ); } void GetPlayerNameBySteamID( const CSteamID &steamID, OUT_Z_CAP(maxLenInChars) char *pDestBuffer, int maxLenInChars ) { // always attempt to precache this user's name -- we may need it later after they leave the server, for instance, // even if that's where they are now InventoryManager()->PersonaName_Precache( steamID.GetAccountID() ); // first, look through players on our connected gameserver if available -- we already have this information and // if there's a disagreement between Steam and the gameserver the gameserver view is what the player is probably // expecting anyway if ( engine->IsInGame() ) { for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ ) { if ( g_PR->IsConnected( iPlayerIndex ) ) { player_info_t pi; if ( !engine->GetPlayerInfo( iPlayerIndex, &pi ) ) continue; if ( !pi.friendsID ) continue; CSteamID steamIDTemp( pi.friendsID, 1, GetUniverse(), k_EAccountTypeIndividual ); if ( steamIDTemp == steamID ) { V_strncpy( pDestBuffer, pi.name, maxLenInChars ); return; } } } } // try the persona name cache // this goes to steam if necessary const char *pszName = InventoryManager()->PersonaName_Get( steamID.GetAccountID() ); if ( pszName != NULL ) { V_strncpy( pDestBuffer, pszName, maxLenInChars ); return; } // otherwise, return what we would normally return V_strncpy( pDestBuffer, steamapicontext->SteamFriends()->GetFriendPersonaName( steamID ), maxLenInChars ); } static bool IsGCUseableItem( const GameItemDefinition_t *pItemDef ) { Assert( pItemDef ); return (pItemDef->GetCapabilities() & ITEM_CAP_USABLE_GC) != 0; } //----------------------------------------------------------------------------- // Purpose: Implementation of the local response for someone who used a dueling minigame //----------------------------------------------------------------------------- void CEconTool_DuelingMinigame::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const { Assert( pItem ); C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); Assert( pLocalPlayer ); if ( DuelMiniGame_IsDueling() ) { // can't duel if already dueling ShowMessageBox( "#TF_Duel_Title", "#TF_Duel_InADuel_Initiator", "#GameUI_OK" ); return; } if ( pLocalPlayer->GetTeamNumber() != TF_TEAM_RED && pLocalPlayer->GetTeamNumber() != TF_TEAM_BLUE ) { // can't duel from spectator mode, etc. ShowMessageBox( "#TF_UseFail_NotOnTeam_Title", "#TF_UseFail_NotOnTeam", "#GameUI_OK" ); return; } if ( TFGameRules() && !TFGameRules()->CanInitiateDuels() ) { ShowMessageBox( "#TF_Duel_Title", "#TF_Duel_CannotUse", "#GameUI_OK" ); return; } ShowSelectDuelTargetDialog( pItem->GetItemID() ); } //----------------------------------------------------------------------------- // Purpose: Implementation of the local response for someone who used a noisemaker //----------------------------------------------------------------------------- void CEconTool_Noisemaker::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const { Assert( pItem ); C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); Assert( pLocalPlayer ); if ( gpGlobals->curtime < pLocalPlayer->m_Shared.GetNextNoiseMakerTime() ) return; if ( !pLocalPlayer->IsAlive() ) return; if ( pLocalPlayer->GetTeamNumber() < FIRST_GAME_TEAM ) return; // This may not be ideal. We're going to have the game server do the noise effect, // without checking the GC to see whether we have charges available. Querying the // GC would cause a significant delay before the item was used, so we simulate. // Tell the game server to play the sound. KeyValues *kv = new KeyValues( "use_action_slot_item_server" ); engine->ServerCmdKeyValues( kv ); // Tell the GC to consume a charge. CUseItemConfirmContext *context = new CUseItemConfirmContext( pItem, kServerPlayers_DontSend ); UseItemConfirm( true, context ); // Notify the player that they used their last charge. if ( pItem->GetItemQuantity() <= 1 ) { CEconNotification *pNotification = new CEconNotification(); pNotification->SetText( "#TF_NoiseMaker_Exhausted" ); pNotification->SetLifetime( 7.0f ); NotificationQueue_Add( pNotification ); } } //----------------------------------------------------------------------------- // Purpose: Implementation of the local response for someone who used a gift-wrapped item //----------------------------------------------------------------------------- void UseUntargetedGiftConfirm( bool bConfirmed, void *pContext ) { if ( bConfirmed ) { UseGift( static_cast( pContext ), k_steamIDNil ); } } void CEconTool_WrappedGift::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const { Assert( pItem ); if ( BIsDirectGift() ) { OpenDeliverGiftDialog( pParent, pItem ); } else { // ...otherwise, we should try and open the gift! PerformToolAction_UnwrapGift( pParent, pItem ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CEconTool_WeddingRing::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const { Assert( pItem ); // Don't do anything if we haven't been gifted already -- we don't expect to // ever get in here, really. static CSchemaAttributeDefHandle pAttrDef_GifterAccountID( "gifter account id" ); uint32 unAccountID; if ( !pItem->FindAttribute( pAttrDef_GifterAccountID, &unAccountID ) ) return; // We have been gifted, so pop up a dialog box to allow the user to accept/reject // the proposal. CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#TF_UseWeddingRing_Title", "#TF_UseWeddingRing_Text", "#TF_WeddingRing_AcceptProposal", "#TF_WeddingRing_RejectProposal", &UseItemConfirm ); pDialog->AddStringToken( "item_name", pItem->GetItemName() ); CUtlConstWideString sProposerPersonaName; GLocalizationProvider()->ConvertUTF8ToLocchar( TFInventoryManager()->PersonaName_Get( unAccountID ), &sProposerPersonaName ); pDialog->AddStringToken( "proposer_name", sProposerPersonaName.Get() ); pDialog->SetContext( new CUseItemConfirmContext( pItem, kServerPlayers_DontSend ) ); } //----------------------------------------------------------------------------- // Purpose: Implementation of the local response for someone who used a backpack expander //----------------------------------------------------------------------------- void CEconTool_BackpackExpander::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const { Assert( pItem ); // first validate that they aren't already at max inventory size and can use the item uint32 unExtraSlots =GetBackpackSlots(); if ( unExtraSlots == 0 ) { return; } uint32 unNewNumSlots = TFInventoryManager()->GetLocalTFInventory()->GetMaxItemCount() + unExtraSlots; if ( unNewNumSlots > MAX_NUM_BACKPACK_SLOTS ) { ShowMessageBox( "#TF_UseBackpackExpanderFail_Title", "#TF_UseBackpackExpanderFail_Text", "#GameUI_OK" ); return; } // Free Trials can use expanders but max out at a smaller value since premium gains a bunch of free slots if ( IsFreeTrialAccount() && unNewNumSlots > MAX_NUM_BACKPACK_SLOTS - (DEFAULT_NUM_BACKPACK_SLOTS - DEFAULT_NUM_BACKPACK_SLOTS_FREE_TRIAL_ACCOUNT) ) { ShowMessageBox( "#TF_UseBackpackExpanderFail_Title", "#TF_UseBackpackExpanderFail_Text", "#GameUI_OK" ); return; } CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#TF_UseBackpackExpander_Title", "#TF_UseBackpackExpander_Text", "#GameUI_OK", "#Cancel", &UseItemConfirm ); pDialog->AddStringToken( "item_name", pItem->GetItemName() ); wchar_t wszUsesLeft[32]; _snwprintf( wszUsesLeft, ARRAYSIZE(wszUsesLeft), L"%d", pItem->GetItemQuantity() ); pDialog->AddStringToken( "uses_left", wszUsesLeft ); wchar_t wszNewSize[32]; _snwprintf( wszNewSize, ARRAYSIZE(wszNewSize), L"%d", unNewNumSlots ); pDialog->AddStringToken( "new_size", wszNewSize ); pDialog->SetContext( new CUseItemConfirmContext( pItem, kServerPlayers_DontSend ) ); } //----------------------------------------------------------------------------- // Purpose: Implementation of the local response for someone who used an account upgrade //----------------------------------------------------------------------------- void CEconTool_AccountUpgradeToPremium::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const { Assert( pItem ); // if the account is already premium, abort here if ( !IsFreeTrialAccount() ) { ShowMessageBox( "#TF_UseAccountUpgradeToPremiumFail_Title", "#TF_UseAccountUpgradeToPremiumFail_Text", "#GameUI_OK" ); return; } // show a confirmation dialog to make sure they want to consume the charge CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#TF_UseAccountUpgradeToPremium_Title", "#TF_UseAccountUpgradeToPremium_Text", "#GameUI_OK", "#Cancel", &UseItemConfirm ); pDialog->AddStringToken( "item_name", pItem->GetItemName() ); wchar_t wszUsesLeft[32]; _snwprintf( wszUsesLeft, ARRAYSIZE(wszUsesLeft), L"%d", pItem->GetItemQuantity() ); pDialog->AddStringToken( "uses_left", wszUsesLeft ); pDialog->SetContext( new CUseItemConfirmContext( pItem, kServerPlayers_DontSend ) ); } //----------------------------------------------------------------------------- // Purpose: Implementation of the local response for someone who used a claim code item //----------------------------------------------------------------------------- void CEconTool_ClaimCode::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const { Assert( pItem ); CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#TF_UseClaimCode_Title", "#TF_UseClaimCode_Text", "#GameUI_OK", "#Cancel", &UseItemConfirm ); pDialog->AddStringToken( "item_name", pItem->GetItemName() ); const char *pszClaimValue = GetClaimType(); if ( pszClaimValue ) { wchar_t wszClaimType[128]; KeyValuesAD pkvDummy( "dummy" ); g_pVGuiLocalize->ConstructString_safe( wszClaimType, pszClaimValue, pkvDummy ); pDialog->AddStringToken( "claim_type", wszClaimType ); } pDialog->SetContext( new CUseItemConfirmContext( pItem, kServerPlayers_DontSend ) ); } //----------------------------------------------------------------------------- // Purpose: Implementation of the local response for someone who used an item that doesn't have // special-case handling (ie., paint); called into from other code //----------------------------------------------------------------------------- static bool s_bConsumableToolOpeningGift = false; static void ClientConsumableTool_Generic( CEconItemView *pItem, vgui::Panel *pParent ) { Assert( pItem ); Assert( pItem->GetItemDefinition() ); Assert( pItem->GetItemDefinition()->GetEconTool() ); CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#TF_UseItem_Title", "#TF_UseItem_Text", "#GameUI_OK", "#Cancel", &UseItemConfirm ); pDialog->AddStringToken( "item_name", pItem->GetItemName() ); wchar_t wszUsesLeft[32]; _snwprintf( wszUsesLeft, ARRAYSIZE(wszUsesLeft), L"%d", pItem->GetItemQuantity() ); pDialog->AddStringToken( "uses_left", wszUsesLeft ); pDialog->SetContext( new CUseItemConfirmContext( pItem, pItem->GetItemDefinition()->GetTypedEconTool() ? kServerPlayers_Send : kServerPlayers_DontSend ) ); // Minor Hack to get sound to play differently. Add a look up table s_bConsumableToolOpeningGift = false; const CEconTool_Gift *pGiftTool = pItem->GetItemDefinition()->GetTypedEconTool(); if ( pGiftTool && pGiftTool->GetTargetRule() == kGiftTargetRule_OnlySelf) { s_bConsumableToolOpeningGift = true; } } //----------------------------------------------------------------------------- // Purpose: Generic Response //----------------------------------------------------------------------------- void CEconTool_Xifier::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const { ClientConsumableTool_Generic( pItem, pParent ); } //----------------------------------------------------------------------------- void CEconTool_ItemEaterRecharger::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const { // Tell the GC to consume a charge. CUseItemConfirmContext *context = new CUseItemConfirmContext( pItem, kServerPlayers_DontSend ); UseItemConfirm( true, context ); } //----------------------------------------------------------------------------- // Purpose: Implementation of the local response for someone who used (tried to redeem) a collection //----------------------------------------------------------------------------- void CEconTool_PaintCan::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const { ClientConsumableTool_Generic( pItem, pParent ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CEconTool_Gift::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const { ClientConsumableTool_Generic( pItem, pParent ); } //----------------------------------------------------------------------------- // Purpose: Implementation of the local response for someone who used (tried to redeem) a collection //----------------------------------------------------------------------------- void CEconTool_Default::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const { ClientConsumableTool_Generic( pItem, pParent ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CEconTool_TFEventEnableHalloween::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const { Assert( pItem ); // Tell the GC we want to use this item. GCSDK::CProtoBufMsg msg( k_EMsgGC_Client_UseServerModificationItem ); msg.Body().set_item_id( pItem->GetItemID() ); GCClientSystem()->BSendMessage( msg ); } //----------------------------------------------------------------------------- void CEconTool_DuckToken::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const { ClientConsumableTool_Generic( pItem, pParent ); } //----------------------------------------------------------------------------- void CEconTool_GrantOperationPass::OnClientUseConsumable( CEconItemView *pItem, vgui::Panel *pParent ) const { Assert( pItem ); const CEconTool_GrantOperationPass *pEconToolOperationPass = pItem->GetItemDefinition()->GetTypedEconTool(); if ( !pEconToolOperationPass ) { ShowMessageBox( "#TF_UseOperationPassFail_Title", "#TF_UseOperationPassFail_Text", "#GameUI_OK" ); return; } // Check that the player doesn't already have an active pass const char *szPassName = pEconToolOperationPass->m_pOperationPassName; CEconItemDefinition *pActivePassItemDef = GetItemSchema()->GetItemDefinitionByName( szPassName ); CPlayerInventory *pLocalInv = TFInventoryManager()->GetLocalInventory(); if ( !pLocalInv || !pActivePassItemDef ) { ShowMessageBox( "#TF_UseOperationPassFail_Title", "#TF_UseOperationPassFail_Text", "#GameUI_OK" ); return; } for ( int i = 0; i < pLocalInv->GetItemCount(); ++i ) { CEconItemView *pItemLocal = pLocalInv->GetItem( i ); Assert( pItemLocal ); if ( pItemLocal->GetItemDefinition() == pActivePassItemDef ) { ShowMessageBox( "#TF_UseOperationPassAlreadyActive_Title", "#TF_UseOperationPassAlreadyActive_Text", "#GameUI_OK" ); return; } } vgui::surface()->PlaySound( "ui/quest_operation_pass_buy.wav" ); const char *pszTitle = "#TF_UseOperationPass_Title"; const char *pszBody = "#TF_UseOperationPass_Text"; static CSchemaItemDefHandle pItemDef_InvasionPass( "Unused Invasion Pass" ); if ( pItem->GetItemDefinition() == pItemDef_InvasionPass ) { pszBody = "#TF_UseInvasionPass_Text"; } // show a confirmation dialog to make sure they want to consume the charge CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( pszTitle, pszBody, "#GameUI_OK", "#Cancel", &OpenPass ); pDialog->AddStringToken( "item_name", pItem->GetItemName() ); wchar_t wszUsesLeft[32]; _snwprintf( wszUsesLeft, ARRAYSIZE( wszUsesLeft ), L"%d", pItem->GetItemQuantity() ); pDialog->AddStringToken( "uses_left", wszUsesLeft ); pDialog->SetContext( new CUseItemConfirmContext( pItem, kServerPlayers_DontSend, "ui/quest_operation_pass_use.wav" ) ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- class CGCEventEnableResponse : public GCSDK::CGCClientJob { public: CGCEventEnableResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) { GCSDK::CProtoBufMsg msg( pNetPacket ); switch ( msg.Body().response_code() ) { case CMsgGC_Client_UseServerModificationItem_Response::kServerModificationItemResponse_AlreadyInUse: ShowMessageBox( "#TF_ServerEnchantmentType", "#TF_Eternaween__AlreadyInUse", (KeyValues *)NULL ); break; case CMsgGC_Client_UseServerModificationItem_Response::kServerModificationItemResponse_NotOnAuthenticatedServer: ShowMessageBox( "#TF_ServerEnchantmentType", "#TF_Eternaween__AuthenticatedServerRequired", (KeyValues *)NULL ); break; case CMsgGC_Client_UseServerModificationItem_Response::kServerModificationItemResponse_ServerReject: ShowMessageBox( "#TF_ServerEnchantmentType", "#TF_Eternaween__ServerReject", (KeyValues *)NULL ); break; case CMsgGC_Client_UseServerModificationItem_Response::kServerModificationItemResponse_InternalError: ShowMessageBox( "#TF_ServerEnchantmentType", "#TF_Eternaween__InternalError", (KeyValues *)NULL ); break; case CMsgGC_Client_UseServerModificationItem_Response::kServerModificationItemResponse_EventAlreadyActive: ShowMessageBox( "#TF_ServerEnchantmentType", "#TF_Eternaween__EventAlreadyActive", (KeyValues *)NULL ); break; } return true; } }; GC_REG_JOB( GCSDK::CGCClient, CGCEventEnableResponse, "CGCEventEnableResponse", k_EMsgGC_Client_UseServerModificationItem_Response, GCSDK::k_EServerTypeGCClient ); // invoked when the local player attempts to consume the given item void UseConsumableItem( CEconItemView *pItem, vgui::Panel *pParent ) { Assert( pItem ); C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); const GameItemDefinition_t *pItemDef = pItem->GetStaticData(); Assert( pItemDef ); bool bUsableOutOfGame = (pItemDef->GetCapabilities() & ITEM_CAP_USABLE_OUT_OF_GAME) != 0; // if we aren't useable outside of the game then make sure that we're in a game and that // we have a local player we can use if ( !bUsableOutOfGame ) { if ( !engine->IsInGame() ) { ShowMessageBox( "#TF_UseFail_NotInGame_Title", "#TF_UseFail_NotInGame", "#GameUI_OK" ); return; } if ( pLocalPlayer == NULL ) return; } // make sure this item meets our baseline useable criteria if ( pItem->GetItemQuantity() <= 0 ) return; if ( !IsGCUseableItem( pItemDef ) ) return; const IEconTool *pEconTool = pItemDef->GetEconTool(); if ( !pEconTool ) return; // do whatever client work needs to be done, send a request to the GC to use the item, etc. pEconTool->OnClientUseConsumable( pItem, pParent ); } // Called from the trade dialog when the player selects a target user ID. void UseGift( CEconItemView* pItem, CSteamID targetID ) { // Validate pItem... if ( !pItem ) return; const GameItemDefinition_t *pItemDef = pItem->GetItemDefinition(); if ( !pItemDef ) return; if ( !IsGCUseableItem( pItemDef ) ) return; if ( !pItemDef->GetTypedEconTool() ) return; GCSDK::CGCMsg< MsgGCDeliverGift_t > msg( k_EMsgGCDeliverGift ); msg.Body().m_unGiftID = pItem->GetItemID(); msg.Body().m_ulTargetSteamID = targetID.ConvertToUint64(); GCClientSystem()->BSendMessage( msg ); CSteamID steamID = steamapicontext->SteamUser()->GetSteamID(); C_CTF_GameStats.Event_Trading( IE_TRADING_ITEM_GIFTED, pItem, true, steamID.ConvertToUint64(), targetID.ConvertToUint64() ); } class CDeliverGiftSelectDialog : public CSelectPlayerDialog { public: CDeliverGiftSelectDialog( vgui::Panel *parent ); virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); void SetItem( CEconItemView* pItem ) { m_pItem = pItem; } virtual bool AllowOutOfGameFriends() { return true; } virtual void OnSelectPlayer( const CSteamID &steamID ) { UseGift( m_pItem, steamID ); } private: CEconItemView* m_pItem; }; CDeliverGiftSelectDialog::CDeliverGiftSelectDialog( vgui::Panel *parent ) : CSelectPlayerDialog( parent ) { } void CDeliverGiftSelectDialog::ApplySchemeSettings( vgui::IScheme *pScheme ) { CSelectPlayerDialog::ApplySchemeSettings( pScheme ); SetDialogVariable( "title", g_pVGuiLocalize->Find( "TF_DeliverGiftDialog_Title" ) ); } static vgui::DHANDLE g_hDeliverGiftDialog; CDeliverGiftSelectDialog *OpenDeliverGiftDialog( vgui::Panel *pParent, CEconItemView *pItem ) { if (!g_hDeliverGiftDialog.Get()) { g_hDeliverGiftDialog = vgui::SETUP_PANEL( new CDeliverGiftSelectDialog( pParent ) ); } g_hDeliverGiftDialog->InvalidateLayout( false, true ); g_hDeliverGiftDialog->Reset(); g_hDeliverGiftDialog->SetVisible( true ); g_hDeliverGiftDialog->MakePopup(); g_hDeliverGiftDialog->MoveToFront(); g_hDeliverGiftDialog->SetKeyBoardInputEnabled(true); g_hDeliverGiftDialog->SetMouseInputEnabled(true); g_hDeliverGiftDialog->SetItem( pItem ); TFModalStack()->PushModal( g_hDeliverGiftDialog ); return g_hDeliverGiftDialog; } // This is the command the user will execute. // We want this to happen on the client, before forwarding to the game server, since we don't trust // the game server. static bool g_bUsedGCItem = false; static void StartUseActionSlotItem( const CCommand &args ) { if ( !engine->IsInGame() ) { return; } C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( pLocalPlayer == NULL ) { return; } pLocalPlayer->SetUsingActionSlot( true ); // Ghosts cant use action items! if ( pLocalPlayer->m_Shared.InCond( TF_COND_HALLOWEEN_GHOST_MODE ) ) { return; } // If we're in Mann Vs MAchine, and we're dead, we can use this to respawn instantly. if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() && pLocalPlayer->IsObserver() ) { float flNextRespawn = TFGameRules()->GetNextRespawnWave( pLocalPlayer->GetTeamNumber(), pLocalPlayer ); if ( flNextRespawn ) { int iRespawnWait = (flNextRespawn - gpGlobals->curtime); if ( iRespawnWait > 1.0 ) { engine->ClientCmd_Unrestricted( "td_buyback\n" ); return; } } } // trying to pick up a dropped weapon? if ( pLocalPlayer->GetDroppedWeaponInRange() != NULL ) { KeyValues *kv = new KeyValues( "+use_action_slot_item_server" ); engine->ServerCmdKeyValues( kv ); return; } if ( TFGameRules() && TFGameRules()->IsUsingGrapplingHook() ) { CTFGrapplingHook *pGrapplingHook = dynamic_cast< CTFGrapplingHook* >( pLocalPlayer->GetEntityForLoadoutSlot( LOADOUT_POSITION_ACTION ) ); if ( pGrapplingHook ) { if ( pLocalPlayer->GetActiveTFWeapon() != pGrapplingHook ) { pLocalPlayer->Weapon_Switch( pGrapplingHook ); } KeyValues *kv = new KeyValues( "+use_action_slot_item_server" ); engine->ServerCmdKeyValues( kv ); return; } } // send a request to the GC to use the item g_bUsedGCItem = false; CEconItemView *pItem = CTFPlayerSharedUtils::GetEconItemViewByLoadoutSlot( pLocalPlayer, LOADOUT_POSITION_ACTION ); if( pItem ) { const IEconTool *pEconTool = pItem->GetItemDefinition()->GetEconTool(); bool bIsRecharger = ( pEconTool && FStrEq( pEconTool->GetTypeName(), "item_eater_recharger" ) ); if ( IsGCUseableItem( pItem->GetItemDefinition() ) && pItem->GetItemQuantity() >= 1 && !bIsRecharger ) { UseConsumableItem( pItem, NULL ); g_bUsedGCItem = true; } } // otherwise, forward to game server if ( !g_bUsedGCItem ) { KeyValues *kv = new KeyValues( "+use_action_slot_item_server" ); engine->ServerCmdKeyValues( kv ); } } static ConCommand start_use_action_slot_item( "+use_action_slot_item", StartUseActionSlotItem, "Use the item in the action slot." ); static void EndUseActionSlotItem( const CCommand &args ) { C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( !pLocalPlayer ) return; pLocalPlayer->SetUsingActionSlot( false ); if ( TFGameRules() && TFGameRules()->IsUsingGrapplingHook() && pLocalPlayer->GetActiveTFWeapon() ) { // if we're using the hook, switch back to the last weapon if ( pLocalPlayer->GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_GRAPPLINGHOOK ) { KeyValues *kv = new KeyValues( "-use_action_slot_item_server" ); engine->ServerCmdKeyValues( kv ); C_BaseCombatWeapon* pLastWeapon = pLocalPlayer->GetLastWeapon(); // switch away from the hook if ( pLastWeapon && pLocalPlayer->Weapon_CanSwitchTo( pLastWeapon ) ) { pLocalPlayer->Weapon_Switch( pLastWeapon ); } else { // in case we failed to switch back to last weapon for some reason, just find the next best pLocalPlayer->SwitchToNextBestWeapon( pLastWeapon ); } return; } } // tell the game server we let go of the button if this wasn't a GC item if ( !g_bUsedGCItem ) { KeyValues *kv = new KeyValues( "-use_action_slot_item_server" ); engine->ServerCmdKeyValues( kv ); } } static ConCommand end_use_action_slot_item( "-use_action_slot_item", EndUseActionSlotItem ); static void StartContextAction( const CCommand &args ) { // Assume we're going to taunt bool bDoTaunt = true; if ( TFGameRules() && TFGameRules()->IsMannVsMachineMode() ) { C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( pLocalPlayer ) { CTFPowerupBottle *pPowerupBottle = dynamic_cast< CTFPowerupBottle* >( pLocalPlayer->GetEquippedWearableForLoadoutSlot( LOADOUT_POSITION_ACTION ) ); if ( pPowerupBottle && pPowerupBottle->GetNumCharges() > 0 ) { // They're in MvM and have a bottle with a charge, so do an action instead bDoTaunt = false; } if ( pLocalPlayer->IsPlayerClass( TF_CLASS_HEAVYWEAPONS ) && pLocalPlayer->GetActiveTFWeapon() && pLocalPlayer->GetActiveTFWeapon()->GetWeaponID() == TF_WEAPON_MINIGUN ) { int iRage = 0; CALL_ATTRIB_HOOK_INT_ON_OTHER( pLocalPlayer, iRage, generate_rage_on_dmg ); if ( iRage ) { if ( pLocalPlayer->m_Shared.GetRageMeter() >= 100.f && !pLocalPlayer->m_Shared.IsRageDraining() ) { // They have rage ready to go, do the taunt bDoTaunt = true; } } } } } if ( bDoTaunt ) { // Taunt engine->ClientCmd_Unrestricted( "+taunt\n" ); } else { // Action item StartUseActionSlotItem( args ); } } static ConCommand start_context_action( "+context_action", StartContextAction, "Use the item in the action slot." ); static void EndContextAction( const CCommand &args ) { // Undo both to be on the safe side EndUseActionSlotItem( args ); engine->ClientCmd_Unrestricted( "-taunt\n" ); } static ConCommand end_context_action( "-context_action", EndContextAction ); //----------------------------------------------------------------------------- class CTFGiftNotification : public CEconNotification { public: CTFGiftNotification( GCSDK::CProtoBufMsg &msg ) : CEconNotification() { const EUniverse eUniverse = GetUniverse(); Assert( msg.Body().recipient_account_ids_size() > 0 ); SetLifetime( 30.0f ); m_bRandomPerson = msg.Body().has_was_random_person() && msg.Body().was_random_person(); const CSteamID gifterSteamID( msg.Body().gifter_steam_id(), eUniverse, k_EAccountTypeIndividual ); SetSteamID( gifterSteamID ); if ( m_bRandomPerson ) { const CSteamID recipientSteamID( msg.Body().recipient_account_ids(0), eUniverse, k_EAccountTypeIndividual ); if ( msg.Body().recipient_account_ids_size() > 0 ) { // This might not really be a random gift but might instead be a gift they opened // themselves (ie., a shipment box). if ( recipientSteamID == gifterSteamID ) { SetText( "#TF_GifterText_SelfOpen" ); } else { SetText( "#TF_GifterText_Random" ); char szRecipientName[ MAX_PLAYER_NAME_LENGTH ]; GetPlayerNameBySteamID( recipientSteamID, szRecipientName, sizeof( szRecipientName ) ); g_pVGuiLocalize->ConvertANSIToUnicode( szRecipientName, m_wszPlayerName, sizeof( m_wszPlayerName ) ); AddStringToken( "recipient", m_wszPlayerName ); m_vecSteamIDRecipients.AddToTail( recipientSteamID ); } } } else { SetText( "#TF_GifterText_All" ); for ( int i = 0; i < msg.Body().recipient_account_ids_size(); ++i ) { const CSteamID recipientSteamID( msg.Body().recipient_account_ids(i), eUniverse, k_EAccountTypeIndividual ); m_vecSteamIDRecipients.AddToTail( recipientSteamID ); } } char szGifterName[ MAX_PLAYER_NAME_LENGTH ]; GetPlayerNameBySteamID( m_steamID, szGifterName, sizeof( szGifterName ) ); g_pVGuiLocalize->ConvertANSIToUnicode( szGifterName, m_wszPlayerName, sizeof( m_wszPlayerName ) ); AddStringToken( "giver", m_wszPlayerName ); PrintToChatLog(); SetSoundFilename( "misc/happy_birthday.wav" ); } void PrintToChatLog() { CBaseHudChat *pHUDChat = (CBaseHudChat *)GET_HUDELEMENT( CHudChat ); if ( pHUDChat ) { wchar_t *pFormat = g_pVGuiLocalize->Find( "TF_GiftedItems" ); if ( pFormat == NULL ) { return; } FOR_EACH_VEC( m_vecSteamIDRecipients, i ) { const CSteamID &steamIDRecipient = m_vecSteamIDRecipients[i]; char szRecipientName[ MAX_PLAYER_NAME_LENGTH ]; szRecipientName[0] = '\0'; GetPlayerNameBySteamID( steamIDRecipient, szRecipientName, sizeof( szRecipientName ) ); if ( szRecipientName[0] == '\0' ) { continue; } wchar_t wszRecipientName[MAX_PLAYER_NAME_LENGTH]; g_pVGuiLocalize->ConvertANSIToUnicode( szRecipientName, wszRecipientName, sizeof( wszRecipientName ) ); wchar_t wszNotification[1024]=L""; g_pVGuiLocalize->ConstructString_safe( wszNotification, pFormat, 2, m_wszPlayerName, wszRecipientName ); char szAnsi[1024]; g_pVGuiLocalize->ConvertUnicodeToANSI( wszNotification, szAnsi, sizeof(szAnsi) ); pHUDChat->Printf( CHAT_FILTER_NONE, "%s", szAnsi ); } } } virtual EType NotificationType() { return eType_Basic; } wchar_t m_wszPlayerName[ MAX_PLAYER_NAME_LENGTH ]; bool m_bRandomPerson; CUtlVector< CSteamID > m_vecSteamIDRecipients; }; //#ifdef _DEBUG //CON_COMMAND( cl_gifts_test, "tests the gift ui." ) //{ // if ( !engine->IsInGame() ) // { // return; // } // // C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); // if ( pLocalPlayer == NULL ) // { // return; // } // // if ( steamapicontext == NULL || steamapicontext->SteamUser() == NULL ) // { // return; // } // // CSteamID steamID = steamapicontext->SteamUser()->GetSteamID(); // GCSDK::CProtoBufMsg< CMsgGCGiftedItems > msg( k_EMsgGCGiftedItems ); // msg.Body().m_ulGifterSteamID = steamID.ConvertToUint64(); // msg.Body().m_bRandomPerson = ( args.ArgC() >= 2 ); // msg.Body().m_unNumGiftRecipients = msg.Body().m_bRandomPerson ? 1 : 31; // for ( int i = 0; i < msg.Body().m_unNumGiftRecipients; ++i ) // { // msg.AddUint64Data( steamID.ConvertToUint64() ); // } // msg.ResetReadPtr(); // NotificationQueue_Add( new CTFGiftNotification( msg ) ); //} //#endif //----------------------------------------------------------------------------- // Purpose: Feedback to the local player who used an item //----------------------------------------------------------------------------- class CTFUseItemNotification : public CEconNotification { public: CTFUseItemNotification( EGCMsgUseItemResponse eResponse) : CEconNotification() { switch ( eResponse ) { case k_EGCMsgUseItemResponse_ItemUsed: SetText( "#TF_UseItem_Success" ); break; case k_EGCMsgUseItemResponse_GiftNoOtherPlayers: SetText( "#TF_UseItem_GiftNoPlayers" ); break; case k_EGCMsgUseItemResponse_ServerError: SetText( "#TF_UseItem_Error" ); break; case k_EGCMsgUseItemResponse_MiniGameAlreadyStarted: SetText( "#TF_UseItem_MiniGameAlreadyStarted" ); break; case k_EGCMsgUseItemResponse_CannotBeUsedByAccount: SetText( "#TF_UseItem_CannotBeUsedByAccount" ); break; default: Assert( !"Unknown response in CTFUseItemNotification!" ); } SetLifetime( 20.0f ); } virtual EType NotificationType() { return eType_Basic; } }; //----------------------------------------------------------------------------- // Purpose: Local player used an item and the GC responded with the status of that request //----------------------------------------------------------------------------- class CGCUseItemResponse : public GCSDK::CGCClientJob { public: CGCUseItemResponse( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) { GCSDK::CGCMsg msg( pNetPacket ); EGCMsgUseItemResponse eResponse = (EGCMsgUseItemResponse)msg.Body().m_eResponse; if ( eResponse == k_EGCMsgUseItemResponse_ItemUsed_ItemsGranted ) { if ( s_bConsumableToolOpeningGift ) { vgui::surface()->PlaySound( "ui/item_gift_wrap_unwrap.wav" ); } else { vgui::surface()->PlaySound( "ui/item_open_crate.wav" ); } ShowWaitingDialog( new CWaitForPackageDialog( NULL ), "#ToolDecodeInProgress", true, false, 5.0f ); } else if ( eResponse != k_EGCMsgUseItemResponse_ItemUsed ) { NotificationQueue_Add( new CTFUseItemNotification( (EGCMsgUseItemResponse)msg.Body().m_eResponse ) ); } else { // refresh the backpack if ( EconUI()->GetBackpackPanel() ) { EconUI()->GetBackpackPanel()->UpdateModelPanels(); } } return true; } }; GC_REG_JOB( GCSDK::CGCClient, CGCUseItemResponse, "CGCUseItemResponse", k_EMsgGCUseItemResponse, GCSDK::k_EServerTypeGCClient ); //----------------------------------------------------------------------------- // Purpose: A player has gifted items //----------------------------------------------------------------------------- class CGCGiftedItems : public GCSDK::CGCClientJob { public: CGCGiftedItems( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) { GCSDK::CProtoBufMsg msg( pNetPacket ); if ( steamapicontext == NULL || steamapicontext->SteamFriends() == NULL ) { return true; } char szGifterName[ MAX_PLAYER_NAME_LENGTH ]; szGifterName[0] = '\0'; GetPlayerNameBySteamID( CSteamID( msg.Body().gifter_steam_id(), GetUniverse(), k_EAccountTypeIndividual ), szGifterName, sizeof( szGifterName ) ); if ( szGifterName[0] == '\0' ) { return true; } // notify UI NotificationQueue_Add( new CTFGiftNotification( msg ) ); return true; } }; GC_REG_JOB( GCSDK::CGCClient, CGCGiftedItems, "CGCGiftedItems", k_EMsgGCGiftedItems, GCSDK::k_EServerTypeGCClient ); //----------------------------------------------------------------------------- // Purpose: A player has used a claim code item //----------------------------------------------------------------------------- class CGCUsedClaimCodeItem : public GCSDK::CGCClientJob { public: CGCUsedClaimCodeItem( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) { GCSDK::CGCMsg msg( pNetPacket ); if ( steamapicontext == NULL ) { return true; } CUtlString url; if ( msg.BReadStr( &url ) ) { steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( url.Get() ); IViewPortPanel *pMMOverride = ( gViewPortInterface->FindPanelByName( PANEL_MAINMENUOVERRIDE ) ); if ( pMMOverride ) { ((CHudMainMenuOverride*)pMMOverride)->UpdatePromotionalCodes(); } } return true; } }; GC_REG_JOB( GCSDK::CGCClient, CGCUsedClaimCodeItem, "CGCUsedClaimCodeItem", k_EMsgGCUsedClaimCodeItem, GCSDK::k_EServerTypeGCClient ); //----------------------------------------------------------------------------- // Duel mini-game //----------------------------------------------------------------------------- class CDuelMiniGameEventListener; struct duel_minigame_local_data_t { duel_minigame_local_data_t() : m_pEventListener( NULL ) , m_steamIDOpponent() , m_unMyScore( 0 ) , m_unOpponentScore( 0 ) , m_iRequiredPlayerClass( TF_CLASS_UNDEFINED ) { } uint32 m_unMyScore; uint32 m_unOpponentScore; int m_iRequiredPlayerClass; CDuelMiniGameEventListener *m_pEventListener; CSteamID m_steamIDOpponent; }; static duel_minigame_local_data_t gDuelMiniGameLocalData; bool DuelMiniGame_IsDueling() { return gDuelMiniGameLocalData.m_steamIDOpponent != CSteamID(); } int DuelMiniGame_GetRequiredPlayerClass() { return gDuelMiniGameLocalData.m_steamIDOpponent != CSteamID() ? gDuelMiniGameLocalData.m_iRequiredPlayerClass : TF_CLASS_UNDEFINED; } static void DuelMiniGame_Reset(); static bool RemoveRelatedDuelNotifications( CEconNotification* pNotification ); /** * Duel info notification */ class CTFDuelInfoNotification : public CEconNotification { public: CTFDuelInfoNotification() { } static bool IsDuelInfoNotification( CEconNotification *pNotification ) { return dynamic_cast< CTFDuelInfoNotification* >( pNotification ) != NULL; } }; /** * Duel Notification */ class CTFDuelRequestNotification : public CEconNotification, public CGameEventListener { public: CTFDuelRequestNotification( const char *pInitiatorName, const CSteamID &steamIDInitiator, const CSteamID &steamIDTarget, const int iRequiredPlayerClass ) : CEconNotification() , m_steamIDInitiator( steamIDInitiator ) , m_steamIDTarget( steamIDTarget ) , m_iRequiredPlayerClass( iRequiredPlayerClass ) { g_pVGuiLocalize->ConvertANSIToUnicode( pInitiatorName, m_wszPlayerName, sizeof(m_wszPlayerName) ); ListenForGameEvent( "teamplay_round_win" ); ListenForGameEvent( "teamplay_round_stalemate" ); } virtual EType NotificationType() { CSteamID localSteamID; C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( pLocalPlayer && pLocalPlayer->GetSteamID( &localSteamID ) && localSteamID == m_steamIDTarget ) { return eType_AcceptDecline; } return eType_Basic; } // XXX(JohnS): Is there something that manually calls trigger here or is this dead code? virtual void Trigger() { CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#TF_Duel_Title", "#TF_Duel_Request", "#GameUI_OK", "#TF_Duel_JoinCancel", &ConfirmDuel ); pDialog->SetContext( this ); pDialog->AddStringToken( "initiator", m_wszPlayerName ); // so we aren't deleted SetIsInUse( true ); } virtual void Accept() { ConfirmDuel( true, this ); } virtual void Decline() { ConfirmDuel( false, this ); } static bool IsDuelRequestNotification( CEconNotification *pNotification ) { return dynamic_cast< CTFDuelRequestNotification* >( pNotification ) != NULL; } static void ConfirmDuel( bool bConfirmed, void *pContext ) { CTFDuelRequestNotification *pNotification = (CTFDuelRequestNotification*)pContext; // if this is a class restricted duel, then make sure the local player is the same class before // they can accept C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( bConfirmed && pLocalPlayer && pNotification->m_iRequiredPlayerClass != TF_CLASS_UNDEFINED ) { int iClass = pLocalPlayer->m_Shared.GetDesiredPlayerClassIndex(); if ( pNotification->m_iRequiredPlayerClass != iClass ) { KeyValues *pKeyValues = new KeyValues( "DuelConfirm" ); switch ( pNotification->m_iRequiredPlayerClass ) { case TF_CLASS_SCOUT: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Scout" ) ); break; case TF_CLASS_SNIPER: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Sniper" ) ); break; case TF_CLASS_SOLDIER: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Soldier" ) ); break; case TF_CLASS_DEMOMAN: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Demoman" ) ); break; case TF_CLASS_MEDIC: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Medic" ) ); break; case TF_CLASS_HEAVYWEAPONS: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_HWGuy" ) ); break; case TF_CLASS_PYRO: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Pyro" ) ); break; case TF_CLASS_SPY: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Spy" ) ); break; case TF_CLASS_ENGINEER: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Engineer" ) ); break; } ShowMessageBox( "#TF_Duel_Title", "#TF_Duel_WrongClass", pKeyValues, "#GameUI_OK" ); return; } } // notify GC of our choice GCSDK::CGCMsg< MsgGC_Duel_Response_t > msg( k_EMsgGC_Duel_Response ); msg.Body().m_ulInitiatorSteamID = pNotification->m_steamIDInitiator.ConvertToUint64(); msg.Body().m_ulTargetSteamID = pNotification->m_steamIDTarget.ConvertToUint64(); msg.Body().m_bAccepted = bConfirmed; GCClientSystem()->BSendMessage( msg ); pNotification->SetIsInUse( false ); pNotification->MarkForDeletion(); // remove all duel notifications if we've accepted if ( bConfirmed ) { NotificationQueue_Remove( &RemoveRelatedDuelNotifications ); } } void FireGameEvent( IGameEvent *event ) { const char *pEventName = event->GetName(); if ( FStrEq( "teamplay_round_win", pEventName ) || FStrEq( "teamplay_round_stalemate", pEventName ) ) { Decline(); return; } } CSteamID m_steamIDInitiator; CSteamID m_steamIDTarget; int m_iRequiredPlayerClass; wchar_t m_wszPlayerName[MAX_PLAYER_NAME_LENGTH]; }; /** * Listens for duel_status events and adds a notification. Ideally adds to a scoreboard or something... */ class CDuelMiniGameEventListener : public CGameEventListener { public: CDuelMiniGameEventListener() { ListenForGameEvent( "duel_status" ); ListenForGameEvent( "teamplay_round_win" ); ListenForGameEvent( "teamplay_round_stalemate" ); } virtual void FireGameEvent( IGameEvent *event ) { const char *pEventName = event->GetName(); C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( pLocalPlayer == NULL ) { return; } if ( Q_strcmp( "teamplay_round_win", pEventName ) == 0 || Q_strcmp( "teamplay_round_stalemate", pEventName ) == 0 ) { DuelMiniGame_Reset(); return; } else if ( Q_strcmp( "duel_status", pEventName ) == 0 ) { int iKillerID = engine->GetPlayerForUserID( event->GetInt( "killer" ) ); int iInitiatorID = engine->GetPlayerForUserID( event->GetInt( "initiator" ) ); int iTargetID = engine->GetPlayerForUserID( event->GetInt( "target" ) ); const char *pInitiatorName = ( iInitiatorID > 0 ? g_PR->GetPlayerName( iInitiatorID ) : "" ); wchar_t wszInitiatorName[MAX_PLAYER_NAME_LENGTH] = L""; g_pVGuiLocalize->ConvertANSIToUnicode( pInitiatorName, wszInitiatorName, sizeof(wszInitiatorName) ); const char *pTargetName = ( iTargetID > 0 ? g_PR->GetPlayerName( iTargetID ) : "" ); wchar_t wszTargetName[MAX_PLAYER_NAME_LENGTH] = L""; g_pVGuiLocalize->ConvertANSIToUnicode( pTargetName, wszTargetName, sizeof(wszTargetName) ); wchar_t wszInitiatorScore[16]; _snwprintf( wszInitiatorScore, ARRAYSIZE( wszInitiatorScore ), L"%i", event->GetInt( "initiator_score", 0 ) ); wchar_t wszTargetScore[16]; _snwprintf( wszTargetScore, ARRAYSIZE( wszTargetScore ), L"%i", event->GetInt( "target_score", 0 ) ); enum { kDuelScoreType_Kill, kDuelScoreType_Assist, kMaxDuelScoreTypes, }; int iScoreType = event->GetInt( "score_type" ); KeyValues *pKeyValues = new KeyValues( "DuelStatus" ); pKeyValues->SetWString( "killer", iKillerID == iInitiatorID ? wszInitiatorName : wszTargetName ); pKeyValues->SetWString( "initiator", wszInitiatorName ); pKeyValues->SetWString( "target", wszTargetName ); pKeyValues->SetWString( "initiator_score", wszInitiatorScore ); pKeyValues->SetWString( "target_score", wszTargetScore ); // if we aren't involved in the duel, don't show the notification if ( pLocalPlayer->entindex() == iInitiatorID || pLocalPlayer->entindex() == iTargetID ) { // remove existing duel info notifications NotificationQueue_Remove( &RemoveRelatedDuelNotifications ); // add new one CTFDuelInfoNotification *pNotification = new CTFDuelInfoNotification(); pNotification->SetLifetime( 10.0f ); pNotification->SetText( iScoreType == kDuelScoreType_Kill ? "TF_Duel_StatusKill" : "TF_Duel_StatusAssist" ); pNotification->SetKeyValues( pKeyValues ); pNotification->SetSoundFilename( "ui/duel_event.wav" ); player_info_t pi; if ( engine->GetPlayerInfo( iKillerID, &pi ) && pi.friendsID != 0 ) { CSteamID steamID( pi.friendsID, 1, GetUniverse(), k_EAccountTypeIndividual ); pNotification->SetSteamID( steamID ); } NotificationQueue_Add( pNotification ); if ( pLocalPlayer->entindex() == iInitiatorID ) { gDuelMiniGameLocalData.m_unMyScore = event->GetInt( "initiator_score" ); gDuelMiniGameLocalData.m_unOpponentScore = event->GetInt( "target_score" ); } else { gDuelMiniGameLocalData.m_unMyScore = event->GetInt( "target_score" ); gDuelMiniGameLocalData.m_unOpponentScore = event->GetInt( "initiator_score" ); } } // print to chat log PrintTextToChat( iScoreType == kDuelScoreType_Kill ? "TF_Duel_StatusForChat_Kill" : "TF_Duel_StatusForChat_Assist", pKeyValues ); // cleanup pKeyValues->deleteThis(); } } }; /** * Duel request */ class CGC_Duel_Request : public GCSDK::CGCClientJob { public: CGC_Duel_Request( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) { GCSDK::CGCMsg msg( pNetPacket ); CSteamID localSteamID; C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( pLocalPlayer == NULL || pLocalPlayer->GetSteamID( &localSteamID ) == false ) { return true; } // get player names CSteamID steamIDInitiator( msg.Body().m_ulInitiatorSteamID ); CSteamID steamIDTarget( msg.Body().m_ulTargetSteamID ); // ignore blocked players (we don't want to print out to the console either) if ( IgnoreRequestFromUser( steamIDInitiator ) || IgnoreRequestFromUser( steamIDTarget ) ) { GCSDK::CGCMsg< MsgGC_Duel_Response_t > msgGC( k_EMsgGC_Duel_Response ); msgGC.Body().m_ulInitiatorSteamID = steamIDInitiator.ConvertToUint64(); msgGC.Body().m_ulTargetSteamID = steamIDTarget.ConvertToUint64(); msgGC.Body().m_bAccepted = false; GCClientSystem()->BSendMessage( msgGC ); return true; } char szPlayerName_Initiator[ MAX_PLAYER_NAME_LENGTH ]; GetPlayerNameBySteamID( steamIDInitiator, szPlayerName_Initiator, sizeof( szPlayerName_Initiator ) ); char szPlayerName_Target[ MAX_PLAYER_NAME_LENGTH ]; GetPlayerNameBySteamID( steamIDTarget, szPlayerName_Target, sizeof( szPlayerName_Target ) ); wchar_t wszPlayerName_Initiator[MAX_PLAYER_NAME_LENGTH]; wchar_t wszPlayerName_Target[MAX_PLAYER_NAME_LENGTH]; g_pVGuiLocalize->ConvertANSIToUnicode( szPlayerName_Initiator, wszPlayerName_Initiator, sizeof(wszPlayerName_Initiator) ); g_pVGuiLocalize->ConvertANSIToUnicode( szPlayerName_Target, wszPlayerName_Target, sizeof(wszPlayerName_Target) ); // add notification KeyValues *pKeyValues = new KeyValues( "DuelText" ); pKeyValues->SetWString( "initiator", wszPlayerName_Initiator ); pKeyValues->SetWString( "target", wszPlayerName_Target ); const char *pText = localSteamID == steamIDTarget ? "TF_Duel_Request" : "TF_Duel_Challenge"; const char *pSoundFilename = "ui/duel_challenge.wav"; if ( msg.Body().m_usAsPlayerClass >= TF_FIRST_NORMAL_CLASS && msg.Body().m_usAsPlayerClass < TF_LAST_NORMAL_CLASS ) { pText = localSteamID == steamIDTarget ? "TF_Duel_Request_Class" : "TF_Duel_Challenge_Class"; pSoundFilename = "ui/duel_challenge_with_restriction.wav"; switch ( msg.Body().m_usAsPlayerClass ) { case TF_CLASS_SCOUT: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Scout" ) ); break; case TF_CLASS_SNIPER: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Sniper" ) ); break; case TF_CLASS_SOLDIER: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Soldier" ) ); break; case TF_CLASS_DEMOMAN: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Demoman" ) ); break; case TF_CLASS_MEDIC: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Medic" ) ); break; case TF_CLASS_HEAVYWEAPONS: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_HWGuy" ) ); break; case TF_CLASS_PYRO: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Pyro" ) ); break; case TF_CLASS_SPY: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Spy" ) ); break; case TF_CLASS_ENGINEER: pKeyValues->SetWString( "player_class", g_pVGuiLocalize->Find( "#TF_Class_Name_Engineer" ) ); break; } } if ( localSteamID == steamIDInitiator || localSteamID == steamIDTarget ) { CTFDuelRequestNotification *pNotification = new CTFDuelRequestNotification( szPlayerName_Initiator, steamIDInitiator, steamIDTarget, msg.Body().m_usAsPlayerClass ); pNotification->SetLifetime( localSteamID == steamIDTarget ? 30.0f : 7.0f ); pNotification->SetText( pText ); pNotification->SetKeyValues( pKeyValues ); pNotification->SetSteamID( steamIDInitiator ); pNotification->SetSoundFilename( pSoundFilename ); NotificationQueue_Add( pNotification ); } // print to chat log PrintTextToChat( pText, pKeyValues ); pKeyValues->deleteThis(); return true; } protected: }; GC_REG_JOB( GCSDK::CGCClient, CGC_Duel_Request, "CGC_Duel_Request", k_EMsgGC_Duel_Request, GCSDK::k_EServerTypeGCClient ); /** * Duel response */ class CGCClient_Duel_Response : public GCSDK::CGCClientJob { public: CGCClient_Duel_Response( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) { GCSDK::CGCMsg msg( pNetPacket ); CSteamID localSteamID; C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( pLocalPlayer == NULL || pLocalPlayer->GetSteamID( &localSteamID ) == false ) return true; // get player names CSteamID steamIDInitiator( msg.Body().m_ulInitiatorSteamID ); CSteamID steamIDTarget( msg.Body().m_ulTargetSteamID ); char szPlayerName_Initiator[ MAX_PLAYER_NAME_LENGTH ]; GetPlayerNameBySteamID( steamIDInitiator, szPlayerName_Initiator, sizeof( szPlayerName_Initiator ) ); char szPlayerName_Target[ MAX_PLAYER_NAME_LENGTH ]; GetPlayerNameBySteamID( steamIDTarget, szPlayerName_Target, sizeof( szPlayerName_Target ) ); wchar_t wszPlayerName_Initiator[MAX_PLAYER_NAME_LENGTH]; wchar_t wszPlayerName_Target[MAX_PLAYER_NAME_LENGTH]; g_pVGuiLocalize->ConvertANSIToUnicode( szPlayerName_Initiator, wszPlayerName_Initiator, sizeof(wszPlayerName_Initiator) ); g_pVGuiLocalize->ConvertANSIToUnicode( szPlayerName_Target, wszPlayerName_Target, sizeof(wszPlayerName_Target) ); KeyValues *pKeyValues = new KeyValues( "DuelText" ); pKeyValues->SetWString( "initiator", wszPlayerName_Initiator ); pKeyValues->SetWString( "target", wszPlayerName_Target ); bool bIsClassDuel = msg.Body().m_usAsPlayerClass >= TF_FIRST_NORMAL_CLASS && msg.Body().m_usAsPlayerClass < TF_LAST_NORMAL_CLASS; // add notification const char *pText = "TF_Duel_Accept"; const char *pSoundFilename = bIsClassDuel ? "ui/duel_challenge_accepted_with_restriction.wav" : "ui/duel_challenge_accepted.wav"; if ( msg.Body().m_bAccepted == false ) { const char *kDeclineStrings[] = { "TF_Duel_Decline", "TF_Duel_Decline2", "TF_Duel_Decline3" }; pText = kDeclineStrings[ RandomInt( 0, ARRAYSIZE( kDeclineStrings ) - 1 ) ]; pSoundFilename = bIsClassDuel ? "ui/duel_challenge_rejected_with_restriction.wav" : "ui/duel_challenge_rejected.wav"; } if ( ( TFGameRules() && TFGameRules()->CanInitiateDuels() ) || msg.Body().m_bAccepted ) { if ( localSteamID == steamIDInitiator || localSteamID == steamIDTarget ) { // remove existing duel info notifications NotificationQueue_Remove( &RemoveRelatedDuelNotifications ); // add new one CTFDuelInfoNotification *pNotification = new CTFDuelInfoNotification(); pNotification->SetLifetime( 7.0f ); pNotification->SetText( pText ); pNotification->SetKeyValues( pKeyValues ); pNotification->SetSteamID( steamIDInitiator ); pNotification->SetSoundFilename( pSoundFilename ); NotificationQueue_Add( pNotification ); } // print to chat PrintTextToChat( pText, pKeyValues ); } // store away opponent id and create event listener if applicable if ( msg.Body().m_bAccepted ) { if ( localSteamID == steamIDInitiator ) { gDuelMiniGameLocalData.m_steamIDOpponent = steamIDTarget; } else if ( localSteamID == steamIDTarget ) { gDuelMiniGameLocalData.m_steamIDOpponent = steamIDInitiator; } if ( gDuelMiniGameLocalData.m_pEventListener == NULL ) { gDuelMiniGameLocalData.m_pEventListener = new CDuelMiniGameEventListener(); } gDuelMiniGameLocalData.m_iRequiredPlayerClass = msg.Body().m_usAsPlayerClass; } pKeyValues->deleteThis(); return true; } protected: }; GC_REG_JOB( GCSDK::CGCClient, CGCClient_Duel_Response, "CGCClient_Duel_Response", k_EMsgGC_Duel_Response, GCSDK::k_EServerTypeGCClient ); /** * Duel status (whether the duel is in progress or cancelled) */ class CGC_Duel_Status : public GCSDK::CGCClientJob { public: CGC_Duel_Status( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) { GCSDK::CGCMsg msg( pNetPacket ); // get player names CSteamID steamIDInitiator( msg.Body().m_ulInitiatorSteamID ); CSteamID steamIDTarget( msg.Body().m_ulTargetSteamID ); char szPlayerName_Initiator[ MAX_PLAYER_NAME_LENGTH ]; GetPlayerNameBySteamID( steamIDInitiator, szPlayerName_Initiator, sizeof( szPlayerName_Initiator ) ); char szPlayerName_Target[ MAX_PLAYER_NAME_LENGTH ]; GetPlayerNameBySteamID( steamIDTarget, szPlayerName_Target, sizeof( szPlayerName_Target ) ); wchar_t wszPlayerName_Initiator[MAX_PLAYER_NAME_LENGTH]; wchar_t wszPlayerName_Target[MAX_PLAYER_NAME_LENGTH]; g_pVGuiLocalize->ConvertANSIToUnicode( szPlayerName_Initiator, wszPlayerName_Initiator, sizeof(wszPlayerName_Initiator) ); g_pVGuiLocalize->ConvertANSIToUnicode( szPlayerName_Target, wszPlayerName_Target, sizeof(wszPlayerName_Target) ); // add notification const char *pText = "TF_Duel_InProgress"; switch ( msg.Body().m_usStatus ) { case kDuel_Status_AlreadyInDuel_Inititator: pText = "TF_Duel_InADuel_Initiator"; break; case kDuel_Status_AlreadyInDuel_Target: pText = "TF_Duel_InADuel_Target"; break; case kDuel_Status_DuelBanned_Initiator: pText = "TF_Duel_TempBanned_Initiator"; break; case kDuel_Status_DuelBanned_Target: pText = "TF_Duel_TempBanned_Target"; break; case kDuel_Status_Cancelled: pText = "TF_Duel_Cancelled"; break; case kDuel_Status_MissingSession: pText = "TF_Duel_UserTemporarilyUnavailable"; break; default: AssertMsg1( false, "Unknown duel status %i in CGC_Duel_Status!", msg.Body().m_usStatus ); } // remove existing duel info notifications NotificationQueue_Remove( &RemoveRelatedDuelNotifications ); // add new one CTFDuelInfoNotification *pNotification = new CTFDuelInfoNotification(); pNotification->SetLifetime( 7.0f ); pNotification->SetText( pText ); pNotification->AddStringToken( "initiator", wszPlayerName_Initiator ); pNotification->AddStringToken( "target", wszPlayerName_Target ); pNotification->SetSteamID( steamIDInitiator ); pNotification->SetSoundFilename( "ui/duel_event.wav" ); NotificationQueue_Add( pNotification ); return true; } protected: }; GC_REG_JOB( GCSDK::CGCClient, CGC_Duel_Status, "CGC_Duel_Status", k_EMsgGC_Duel_Status, GCSDK::k_EServerTypeGCClient ); /** * Duel Results--ideally this is a scoreboard as well. */ class CGC_Duel_Results : public GCSDK::CGCClientJob { public: CGC_Duel_Results( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) { GCSDK::CGCMsg msg( pNetPacket ); if ( steamapicontext == NULL || steamapicontext->SteamUser() == NULL ) return true; CSteamID localSteamID = steamapicontext->SteamUser()->GetSteamID(); // get player names CSteamID steamIDWinner( msg.Body().m_ulWinnerSteamID ); CSteamID steamIDInitiator( msg.Body().m_ulInitiatorSteamID ); CSteamID steamIDTarget( msg.Body().m_ulTargetSteamID ); char szPlayerName_Winner[ MAX_PLAYER_NAME_LENGTH ]; GetPlayerNameBySteamID( steamIDWinner, szPlayerName_Winner, sizeof( szPlayerName_Winner ) ); char szPlayerName_Initiator[ MAX_PLAYER_NAME_LENGTH ]; GetPlayerNameBySteamID( steamIDInitiator, szPlayerName_Initiator, sizeof( szPlayerName_Initiator ) ); char szPlayerName_Target[ MAX_PLAYER_NAME_LENGTH ]; GetPlayerNameBySteamID( steamIDTarget, szPlayerName_Target, sizeof( szPlayerName_Target ) ); wchar_t wszPlayerName_Winner[MAX_PLAYER_NAME_LENGTH]; wchar_t wszPlayerName_Initiator[MAX_PLAYER_NAME_LENGTH]; wchar_t wszPlayerName_Target[MAX_PLAYER_NAME_LENGTH]; g_pVGuiLocalize->ConvertANSIToUnicode( szPlayerName_Winner, wszPlayerName_Winner, sizeof(wszPlayerName_Winner) ); g_pVGuiLocalize->ConvertANSIToUnicode( szPlayerName_Initiator, wszPlayerName_Initiator, sizeof(wszPlayerName_Initiator) ); g_pVGuiLocalize->ConvertANSIToUnicode( szPlayerName_Target, wszPlayerName_Target, sizeof(wszPlayerName_Target) ); // build text bool bTie = msg.Body().m_usScoreInitiator == msg.Body().m_usScoreTarget; const char *pText = "TF_Duel_Win"; switch ( msg.Body().m_usEndReason ) { case kDuelEndReason_DuelOver: break; case kDuelEndReason_PlayerDisconnected: bTie = false; pText = "TF_Duel_Win_Disconnect"; break; case kDuelEndReason_PlayerSwappedTeams: bTie = false; pText = "TF_Duel_Win_SwappedTeams"; break; case kDuelEndReason_LevelShutdown: bTie = true; pText = "TF_Duel_Refund_LevelShutdown"; break; case kDuelEndReason_ScoreTiedAtZero: bTie = true; pText = "TF_Duel_Refund_ScoreTiedAtZero"; break; case kDuelEndReason_ScoreTied: bTie = true; pText = "TF_Duel_Tie"; break; case kDuelEndReason_PlayerKicked: bTie = true; pText = "TF_Duel_Refund_Kicked"; break; case kDuelEndReason_PlayerForceSwappedTeams: bTie = true; pText = "TF_Duel_Refund_ForceTeamSwap"; break; case kDuelEndReason_Cancelled: bTie = true; pText = "TF_Duel_Cancelled"; break; } KeyValues *pKeyValues = new KeyValues( "DuelResults" ); if ( bTie == false ) { pKeyValues->SetWString( "winner", wszPlayerName_Winner ); pKeyValues->SetWString( "loser", steamIDWinner == steamIDInitiator ? wszPlayerName_Target : wszPlayerName_Initiator ); wchar_t wszScoreInitiator[16]; wchar_t wszScoreTarget[16]; _snwprintf( wszScoreInitiator, ARRAYSIZE( wszScoreInitiator ), L"%u", (uint32)msg.Body().m_usScoreInitiator ); _snwprintf( wszScoreTarget, ARRAYSIZE( wszScoreTarget ), L"%u", (uint32)msg.Body().m_usScoreTarget ); pKeyValues->SetWString( "winner_score", steamIDWinner == steamIDInitiator ? wszScoreInitiator : wszScoreTarget ); pKeyValues->SetWString( "loser_score", steamIDWinner == steamIDInitiator ? wszScoreTarget : wszScoreInitiator ); } else { wchar_t wszScoreInitiator[16]; _snwprintf( wszScoreInitiator, ARRAYSIZE( wszScoreInitiator ), L"%u", (uint32)msg.Body().m_usScoreInitiator ); pKeyValues->SetWString( "score", wszScoreInitiator ); pKeyValues->SetWString( "initiator", wszPlayerName_Initiator ); pKeyValues->SetWString( "target", wszPlayerName_Target ); } // add notification if ( localSteamID == steamIDInitiator || localSteamID == steamIDTarget ) { // remove existing duel info notifications NotificationQueue_Remove( &RemoveRelatedDuelNotifications ); // add new one CTFDuelInfoNotification *pNotification = new CTFDuelInfoNotification(); pNotification->SetText( pText ); pNotification->SetKeyValues( pKeyValues ); pNotification->SetSteamID( bTie == false ? steamIDWinner : ( localSteamID == steamIDInitiator ? steamIDTarget : steamIDInitiator ) ); pNotification->SetSoundFilename( "ui/duel_event.wav" ); NotificationQueue_Add( pNotification ); gDuelMiniGameLocalData.m_steamIDOpponent = CSteamID(); } // print out to chat PrintTextToChat( pText, pKeyValues ); // cleanup pKeyValues->deleteThis(); // reset the dueling minigame DuelMiniGame_Reset(); return true; } protected: }; GC_REG_JOB( GCSDK::CGCClient, CGC_Duel_Results, "CGC_Duel_Results", k_EMsgGC_Duel_Results, GCSDK::k_EServerTypeGCClient ); static bool RemoveRelatedDuelNotifications( CEconNotification *pNotification ) { return ( CTFDuelRequestNotification::IsDuelRequestNotification( pNotification ) || CTFDuelInfoNotification::IsDuelInfoNotification( pNotification ) ); } static void DuelMiniGame_Reset() { delete gDuelMiniGameLocalData.m_pEventListener; gDuelMiniGameLocalData.m_pEventListener = NULL; gDuelMiniGameLocalData.m_steamIDOpponent = CSteamID(); gDuelMiniGameLocalData.m_unMyScore = 0; gDuelMiniGameLocalData.m_unOpponentScore = 0; gDuelMiniGameLocalData.m_iRequiredPlayerClass = TF_CLASS_UNDEFINED; } bool DuelMiniGame_IsDuelingLocalPlayer( C_TFPlayer *pPlayer ) { CSteamID steamID; if ( pPlayer->GetSteamID( &steamID ) ) { return ( steamID == gDuelMiniGameLocalData.m_steamIDOpponent ); } return false; } bool DuelMiniGame_GetStats( C_TFPlayer **ppPlayer, uint32 &unMyScore, uint32 &unOpponentScore ) { *ppPlayer = NULL; int iLocalPlayerIndex = GetLocalPlayerIndex(); for( int iPlayerIndex = 1 ; iPlayerIndex <= MAX_PLAYERS; iPlayerIndex++ ) { // find all players who are on the local player's team if( ( iPlayerIndex != iLocalPlayerIndex ) && ( g_PR->IsConnected( iPlayerIndex ) ) ) { CSteamID steamID; C_TFPlayer *pPlayer = ToTFPlayer( UTIL_PlayerByIndex( iPlayerIndex ) ); if ( pPlayer && pPlayer->GetSteamID( &steamID ) && steamID == gDuelMiniGameLocalData.m_steamIDOpponent ) { *ppPlayer = pPlayer; break; } } } static bool sbTesting = false; if ( sbTesting ) { *ppPlayer = ToTFPlayer( C_BasePlayer::GetLocalPlayer() ); } if ( *ppPlayer != NULL ) { CSteamID steamID = steamapicontext->SteamUser()->GetSteamID(); unMyScore = gDuelMiniGameLocalData.m_unMyScore; unOpponentScore = gDuelMiniGameLocalData.m_unOpponentScore; return true; } return false; } /** * Select player for duel dialog. */ class CSelectPlayerForDuelDialog : public CSelectPlayerDialog, public CGameEventListener { DECLARE_CLASS_SIMPLE( CSelectPlayerForDuelDialog, CSelectPlayerDialog ); public: CSelectPlayerForDuelDialog( uint64 iItemID ); virtual ~CSelectPlayerForDuelDialog(); virtual void OnSelectPlayer( const CSteamID &steamID ); virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); virtual void FireGameEvent( IGameEvent *event ); MESSAGE_FUNC_PARAMS( OnClassIconSelected, "ClassIconSelected", data ); MESSAGE_FUNC_PARAMS( OnShowClassIconMouseover, "ShowClassIconMouseover", data ); MESSAGE_FUNC( OnHideClassIconMouseover, "HideClassIconMouseover" ); protected: virtual const char *GetResFile() { return "resource/ui/SelectPlayerDialog_Duel.res"; } void SetSelectedClass( int iClass ); void SetupClassImage( const char *pImageControlName, int iClass ); uint64 m_iItemID; uint8 m_iPlayerClass; vgui::Label *m_pClassIconMouseoverLabel; }; static vgui::DHANDLE< CSelectPlayerForDuelDialog > g_pSelectPlayerForDuelingDialog; CSelectPlayerForDuelDialog::CSelectPlayerForDuelDialog( uint64 iItemID ) : CSelectPlayerDialog( NULL ) , m_iItemID( iItemID ) , m_iPlayerClass( TF_CLASS_UNDEFINED ) { g_pSelectPlayerForDuelingDialog = this; m_bAllowSameTeam = false; m_bAllowOutsideServer = false; m_pClassIconMouseoverLabel = new vgui::Label( this, "ClassUsageMouseoverLabel", "" ); ListenForGameEvent( "teamplay_round_win" ); ListenForGameEvent( "teamplay_round_stalemate" ); } CSelectPlayerForDuelDialog::~CSelectPlayerForDuelDialog() { g_pSelectPlayerForDuelingDialog = NULL; } void CSelectPlayerForDuelDialog::OnSelectPlayer( const CSteamID &steamID ) { GCSDK::CProtoBufMsg msg( k_EMsgGCUseItemRequest ); msg.Body().set_item_id( m_iItemID ); msg.Body().set_target_steam_id( steamID.ConvertToUint64() ); msg.Body().set_duel__class_lock( m_iPlayerClass ); GCClientSystem()->BSendMessage( msg ); } void CSelectPlayerForDuelDialog::ApplySchemeSettings( vgui::IScheme *pScheme ) { CSelectPlayerDialog::ApplySchemeSettings( pScheme ); SetDialogVariable( "title", g_pVGuiLocalize->Find( "TF_DuelDialog_Title" ) ); SetupClassImage( "ClassUsageImage_Any", TF_CLASS_UNDEFINED ); C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); SetupClassImage( "ClassUsageImage_Locked", pLocalPlayer->m_Shared.GetDesiredPlayerClassIndex() ); SetSelectedClass( TF_CLASS_UNDEFINED ); } void CSelectPlayerForDuelDialog::SetupClassImage( const char *pImageControlName, int iClass ) { CStorePreviewClassIcon *pClassImage = dynamic_cast( FindChildByName( pImageControlName ) ); if ( pClassImage ) { pClassImage->SetClass( iClass ); } } void CSelectPlayerForDuelDialog::FireGameEvent( IGameEvent *event ) { const char *pEventName = event->GetName(); C_TFPlayer *pLocalPlayer = C_TFPlayer::GetLocalTFPlayer(); if ( pLocalPlayer == NULL ) { OnCommand( "cancel" ); return; } if ( Q_strcmp( "teamplay_round_win", pEventName ) == 0 || Q_strcmp( "teamplay_round_stalemate", pEventName ) == 0 ) { OnCommand( "cancel" ); return; } } void CSelectPlayerForDuelDialog::OnClassIconSelected( KeyValues *data ) { int iClass = data->GetInt( "class", 0 ); SetSelectedClass( iClass ); } void CSelectPlayerForDuelDialog::OnHideClassIconMouseover( void ) { if ( m_pClassIconMouseoverLabel ) { m_pClassIconMouseoverLabel->SetVisible( false ); } } void CSelectPlayerForDuelDialog::OnShowClassIconMouseover( KeyValues *data ) { if ( m_pClassIconMouseoverLabel ) { // Set the text to the correct string int iClass = data->GetInt( "class", 0 ); if ( iClass >= TF_FIRST_NORMAL_CLASS && iClass < TF_LAST_NORMAL_CLASS ) { wchar_t wzLocalized[256]; const char *pszLocString = "#TF_SelectPlayer_Duel_PlayerClass"; g_pVGuiLocalize->ConstructString_safe( wzLocalized, g_pVGuiLocalize->Find( pszLocString ), 1, g_pVGuiLocalize->Find( g_aPlayerClassNames[iClass] ) ); m_pClassIconMouseoverLabel->SetText( wzLocalized ); } else { const char *pszLocString = "#TF_SelectPlayer_Duel_AnyClass"; m_pClassIconMouseoverLabel->SetText( pszLocString ); } m_pClassIconMouseoverLabel->SetVisible( true ); } } void CSelectPlayerForDuelDialog::SetSelectedClass( int iClass ) { m_iPlayerClass = iClass; const char* pClassName = "#TF_SelectPlayer_DuelClass_None"; switch ( m_iPlayerClass ) { case TF_CLASS_SCOUT: pClassName = "#TF_Class_Name_Scout"; break; case TF_CLASS_SNIPER: pClassName = "#TF_Class_Name_Sniper"; break; case TF_CLASS_SOLDIER: pClassName = "#TF_Class_Name_Soldier"; break; case TF_CLASS_DEMOMAN: pClassName = "#TF_Class_Name_Demoman"; break; case TF_CLASS_MEDIC: pClassName = "#TF_Class_Name_Medic"; break; case TF_CLASS_HEAVYWEAPONS: pClassName = "#TF_Class_Name_HWGuy"; break; case TF_CLASS_PYRO: pClassName = "#TF_Class_Name_Pyro"; break; case TF_CLASS_SPY: pClassName = "#TF_Class_Name_Spy"; break; case TF_CLASS_ENGINEER: pClassName = "#TF_Class_Name_Engineer"; break; } wchar_t wszText[1024]=L""; g_pVGuiLocalize->ConstructString_safe( wszText, g_pVGuiLocalize->Find( "#TF_SelectPlayer_DuelClass" ), 1, g_pVGuiLocalize->Find( pClassName ) ); SetDialogVariable( "player_class", wszText ); } static void ShowSelectDuelTargetDialog( uint64 iItemID ) { CSelectPlayerForDuelDialog *pDialog = vgui::SETUP_PANEL( new CSelectPlayerForDuelDialog( iItemID ) ); pDialog->InvalidateLayout( false, true ); pDialog->Reset(); pDialog->SetVisible( true ); pDialog->MakePopup(); pDialog->MoveToFront(); pDialog->SetKeyBoardInputEnabled(true); pDialog->SetMouseInputEnabled(true); TFModalStack()->PushModal( pDialog ); } //----------------------------------------------------------------------------- void CL_Consumables_LevelShutdown() { DuelMiniGame_Reset(); if ( g_pSelectPlayerForDuelingDialog ) { g_pSelectPlayerForDuelingDialog->OnCommand( "cancel" ); } NotificationQueue_Remove( &CTFDuelRequestNotification::IsDuelRequestNotification ); } // // Players see this whenever a person on their server uses a name tool // class CTFNameItemNotification : public GCSDK::CGCClientJob { public: CTFNameItemNotification( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) { GCSDK::CProtoBufMsg msg( pNetPacket ); if ( steamapicontext == NULL || steamapicontext->SteamUser() == NULL ) return true; if ( !engine->IsInGame() ) return true; CBaseHudChat *pHUDChat = (CBaseHudChat *)GET_HUDELEMENT( CHudChat ); if ( pHUDChat ) { // Player wchar_t wszPlayerName[MAX_PLAYER_NAME_LENGTH]; char szPlayerName[ MAX_PLAYER_NAME_LENGTH ]; GetPlayerNameBySteamID( msg.Body().player_steamid(), szPlayerName, sizeof( szPlayerName ) ); g_pVGuiLocalize->ConvertANSIToUnicode( szPlayerName, wszPlayerName, sizeof( wszPlayerName ) ); // Item item_definition_index_t nDefIndex = msg.Body().item_def_index(); const GameItemDefinition_t *pItemDefinition = dynamic_cast( GetItemSchema()->GetItemDefinition( nDefIndex ) ); if ( !pItemDefinition ) return true; entityquality_t iItemQuality = pItemDefinition->GetQuality(); // Name wchar_t wszCustomName[MAX_ITEM_CUSTOM_NAME_LENGTH]; g_pVGuiLocalize->ConvertANSIToUnicode( msg.Body().item_name_custom().c_str(), wszCustomName, sizeof( wszCustomName ) ); wchar_t wszItemRenamed[256]; _snwprintf( wszItemRenamed, ARRAYSIZE( wszItemRenamed ), L"%ls", g_pVGuiLocalize->Find( "#Item_Named" ) ); const char *pszQualityColorString = EconQuality_GetColorString( (EEconItemQuality)iItemQuality ); if ( pszQualityColorString ) { pHUDChat->SetCustomColor( pszQualityColorString ); } wchar_t wszLocalizedString[256]; g_pVGuiLocalize->ConstructString_safe( wszLocalizedString, wszItemRenamed, 3, wszPlayerName, CEconItemLocalizedFullNameGenerator( GLocalizationProvider(), pItemDefinition, iItemQuality ).GetFullName(), wszCustomName ); char szLocalized[256]; g_pVGuiLocalize->ConvertUnicodeToANSI( wszLocalizedString, szLocalized, sizeof( szLocalized ) ); pHUDChat->ChatPrintf( 0, CHAT_FILTER_SERVERMSG, "%s", szLocalized ); } return true; } }; GC_REG_JOB( GCSDK::CGCClient, CTFNameItemNotification, "CTFNameItemNotification", k_EMsgGCNameItemNotification, GCSDK::k_EServerTypeGCClient ); // // A wrapper for a generic message sent down from the GC to clients. The clients do localization // and layout. // class CClientDisplayNotification : public CEconNotification { public: CClientDisplayNotification( GCSDK::IMsgNetPacket *pNetPacket ) : m_msg( pNetPacket ) { SetText( m_msg.Body().notification_body_localization_key().c_str() ); SetLifetime( 23.0f ); if ( m_msg.Body().body_substring_keys_size() == m_msg.Body().body_substring_values_size() ) { for ( int i = 0; i < m_msg.Body().body_substring_keys_size(); i++ ) { const char *pszSubstringKey = m_msg.Body().body_substring_keys( i ).c_str(); const char *pszSubstringValue = m_msg.Body().body_substring_values( i ).c_str(); if ( pszSubstringValue[0] == '#' ) { AddStringToken( pszSubstringKey, GLocalizationProvider()->Find( pszSubstringValue ) ); } else { CUtlConstWideString wsSubstringValue; GLocalizationProvider()->ConvertUTF8ToLocchar( pszSubstringValue, &wsSubstringValue ) ; AddStringToken( pszSubstringKey, wsSubstringValue.Get() ); } } } } private: // We make a local copy of the full message because dynamic-length strings are sent down from the // GC and we need to make sure they stay in memory until the user looks at the notification. GCSDK::CProtoBufMsg m_msg; }; class CTFClientDisplayNotification : public GCSDK::CGCClientJob { public: CTFClientDisplayNotification( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {} virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket ) { GCSDK::CProtoBufMsg msg( pNetPacket ); if ( steamapicontext == NULL || steamapicontext->SteamUser() == NULL ) return true; NotificationQueue_Add( new CClientDisplayNotification( pNetPacket ) ); return true; } }; GC_REG_JOB( GCSDK::CGCClient, CTFClientDisplayNotification, "CTFClientDisplayNotification", k_EMsgGCClientDisplayNotification, GCSDK::k_EServerTypeGCClient );