summaryrefslogtreecommitdiff
path: root/game/client/tf/tf_consumables.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/client/tf/tf_consumables.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/client/tf/tf_consumables.cpp')
-rw-r--r--game/client/tf/tf_consumables.cpp2233
1 files changed, 2233 insertions, 0 deletions
diff --git a/game/client/tf/tf_consumables.cpp b/game/client/tf/tf_consumables.cpp
new file mode 100644
index 0000000..3508b8b
--- /dev/null
+++ b/game/client/tf/tf_consumables.cpp
@@ -0,0 +1,2233 @@
+//========= 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 <tier0/memdbgon.h>
+
+//-----------------------------------------------------------------------------
+// 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<CMsgUseItem> 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<CEconItemView *>( 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<CEconTool_Gift>()
+ ? 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<CEconTool_Gift>();
+ 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<CMsgGC_Client_UseServerModificationItem> 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<CEconTool_GrantOperationPass>();
+ 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<CMsgGC_Client_UseServerModificationItem_Response> 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<CEconTool_WrappedGift>() )
+ 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<CDeliverGiftSelectDialog> 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<CMsgGCGiftedItems> &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<MsgGCUseItemResponse_t> 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<CMsgGCGiftedItems> 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<MsgGCUsedClaimCodeItem_t> 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<MsgGC_Duel_Request_t> 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<MsgGC_Duel_Response_t> 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<MsgGC_Duel_Status_t> 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<MsgGC_Duel_Results_t> 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<CMsgUseItem> 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<CStorePreviewClassIcon*>( 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<CMsgGCNameItemNotification> 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<GameItemDefinition_t *>( 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<CMsgGCClientDisplayNotification> m_msg;
+};
+
+class CTFClientDisplayNotification : public GCSDK::CGCClientJob
+{
+public:
+ CTFClientDisplayNotification( GCSDK::CGCClient *pClient ) : GCSDK::CGCClientJob( pClient ) {}
+ virtual bool BYieldingRunGCJob( GCSDK::IMsgNetPacket *pNetPacket )
+ {
+ GCSDK::CProtoBufMsg<CMsgGCClientDisplayNotification> 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 );