You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2234 lines
78 KiB
2234 lines
78 KiB
![]()
4 years ago
|
//========= 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 );
|